From 1eb2f5198106f461ae81a4b86b551ee807a30e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 00:40:14 +0200 Subject: [PATCH 1/6] fix: resolve protoc binary with fallback chain Context: build-proto.mjs hard-coded the grpc-tools bundled protoc path. When the grpc-tools postinstall download fails, that binary is absent and `npm run protos` exits with status 127, leaving the proto TS modules ungenerated and ~431 tsc errors. Approach: replace the rigid constant with resolveProtoc(), a fallback chain. Changes: - Try the bundled grpc-tools/bin/protoc(.exe) first, gated on an executable-access check, to keep version consistency. - Fall back to a protoc found on PATH via which/where. - Fall back to a bare protoc if it answers --version. - Otherwise print a clear remediation message and exit. Impact: npm run protos now succeeds with a system protoc; no other proto pipeline behaviour changes. --- scripts/build-proto.mjs | 54 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/scripts/build-proto.mjs b/scripts/build-proto.mjs index 1da22877..eaf71d62 100755 --- a/scripts/build-proto.mjs +++ b/scripts/build-proto.mjs @@ -2,6 +2,7 @@ import chalk from "chalk" import { execSync } from "child_process" +import * as fsSync from "fs" import * as fs from "fs/promises" import { globby } from "globby" import { createRequire } from "module" @@ -12,7 +13,58 @@ import { main as generateHostBridgeClient } from "./generate-host-bridge-client. import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs" const require = createRequire(import.meta.url) -const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc") + +// Resolve the `protoc` binary with a fallback chain so the build does not +// hard-fail when grpc-tools failed to download its bundled binary on install. +function resolveProtoc() { + const isWin = process.platform === "win32" + + // 1. Bundled grpc-tools binary keeps priority for version consistency. + try { + const grpcToolsDir = path.dirname(require.resolve("grpc-tools/package.json")) + const bundled = path.join(grpcToolsDir, "bin", isWin ? "protoc.exe" : "protoc") + fsSync.accessSync(bundled, fsSync.constants.X_OK) + return bundled + } catch { + // Fall through to a system protoc. + } + + // 2. A `protoc` available on the PATH. + const lookup = isWin ? "where" : "which" + try { + const found = execSync(`${lookup} protoc`, { stdio: ["ignore", "pipe", "ignore"] }) + .toString() + .split(/\r?\n/)[0] + .trim() + if (found) { + return found + } + } catch { + // `which`/`where` found nothing. + } + + // 3. Last resort: trust `protoc` if it answers on the PATH. + try { + execSync("protoc --version", { stdio: "ignore" }) + return "protoc" + } catch { + // No protoc anywhere. + } + + console.error( + chalk.red( + "Could not find a `protoc` binary.\n" + + "The grpc-tools bundled binary is missing (its postinstall download likely failed)\n" + + "and no `protoc` was found on your PATH.\n" + + "Fix this by either:\n" + + " - installing protoc (e.g. `brew install protobuf` or your distro's package), or\n" + + " - reinstalling grpc-tools (`npm install grpc-tools --force`) to restore the bundled binary.", + ), + ) + process.exit(1) +} + +const PROTOC = resolveProtoc() const PROTO_DIR = path.resolve("proto") const TS_OUT_DIR = path.resolve("src/shared/proto") From a8f8d01e704fbad06a8fb5a89021f319d97424ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 00:40:22 +0200 Subject: [PATCH 2/6] ci: add typecheck, lint and test workflow Context: .github/workflows only had pack-cli.yml, triggered on v*-cli tags. No workflow validated push or pull requests. Changes: - Add ci.yml triggered on push to master and on pull_request. - build job: install, generate protos, check-types, lint (blocking). - test job: run the mocha unit suite with continue-on-error, since it is flaky in CI and should signal without gating. Impact: every push and PR now gets typecheck and lint coverage. --- .github/workflows/ci.yml | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..77dd1e4f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + name: Typecheck & Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm install + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Protocol Buffers + run: npm run protos + + - name: Check types + run: npm run check-types + + - name: Lint + run: npm run lint + + test: + name: Unit Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm install + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Protocol Buffers + run: npm run protos + + # The mocha-based unit suite (`test:unit`) can be flaky in CI (it loads + # ts-node + VS Code shims). Keep it non-blocking so it surfaces failures + # as a signal without gating typecheck/lint, which run in the `build` job. + - name: Run unit tests + run: npm run test:unit + continue-on-error: true From cc4207b81a591a718822703d7a83e3bf182588ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 00:49:30 +0200 Subject: [PATCH 3/6] ci: address pre-merge review of protoc and CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patches from the critic review of PR #33: - ci.yml: drop the redundant "Generate Protocol Buffers" step in the build job — `check-types` already runs `npm run protos`. - ci.yml: add a concurrency group (cancel superseded runs), `permissions: contents: read`, and `timeout-minutes` on both jobs. - ci.yml: rename the test job "Unit Tests (non-blocking)" so the continue-on-error behaviour is explicit rather than implied. - build-proto.mjs: warn when falling back to a system protoc so a version divergence from grpc-tools is visible in the log. --- .github/workflows/ci.yml | 24 +++++++++++++++++------- scripts/build-proto.mjs | 7 +++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77dd1e4f..66796c8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,20 @@ on: - master pull_request: +# Cancel superseded runs on the same ref to save CI minutes. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +# Least privilege: this workflow only reads the repository. +permissions: + contents: read + jobs: build: name: Typecheck & Lint runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -25,9 +35,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Generate Protocol Buffers - run: npm run protos - + # `check-types` runs `npm run protos` itself, so protos is not a + # separate step here. - name: Check types run: npm run check-types @@ -35,8 +44,9 @@ jobs: run: npm run lint test: - name: Unit Tests + name: Unit Tests (non-blocking) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -55,9 +65,9 @@ jobs: - name: Generate Protocol Buffers run: npm run protos - # The mocha-based unit suite (`test:unit`) can be flaky in CI (it loads - # ts-node + VS Code shims). Keep it non-blocking so it surfaces failures - # as a signal without gating typecheck/lint, which run in the `build` job. + # The mocha unit suite is currently fragile in CI (ts-node ESM + + # VS Code shims). It runs non-blocking so it surfaces failures as a + # signal without gating merges; hardening it is tracked separately. - name: Run unit tests run: npm run test:unit continue-on-error: true diff --git a/scripts/build-proto.mjs b/scripts/build-proto.mjs index eaf71d62..c93d7a9d 100755 --- a/scripts/build-proto.mjs +++ b/scripts/build-proto.mjs @@ -37,6 +37,12 @@ function resolveProtoc() { .split(/\r?\n/)[0] .trim() if (found) { + console.warn( + chalk.yellow( + `grpc-tools protoc missing; using system protoc at ${found}. ` + + `Generated code may differ if its version diverges from grpc-tools.`, + ), + ) return found } } catch { @@ -46,6 +52,7 @@ function resolveProtoc() { // 3. Last resort: trust `protoc` if it answers on the PATH. try { execSync("protoc --version", { stdio: "ignore" }) + console.warn(chalk.yellow("grpc-tools protoc missing; using `protoc` from PATH.")) return "protoc" } catch { // No protoc anywhere. From 37206cf6caf2da48a602b833b010296e3fd19c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 00:53:27 +0200 Subject: [PATCH 4/6] ci: fix monorepo bootstrap ordering The first CI run exposed a pre-existing bootstrap defect: the `cli` workspace has a `prepare` script that builds (and runs tsc) during `npm install`, which fails before protos exist. Approach: install with `--ignore-scripts` so the cli prepare build does not run at install time, install a system protoc (grpc-tools' postinstall is skipped too), then generate protos and run the checks in the correct order. Changes: - Both jobs: `npm install --ignore-scripts`. - Both jobs: add an "Install protoc" step (apt protobuf-compiler) so build-proto.mjs has a protoc to fall back to. --- .github/workflows/ci.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66796c8c..7789b45d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,13 +30,20 @@ jobs: node-version: 20 cache: 'npm' + # --ignore-scripts: the `cli` workspace has a `prepare` script that + # builds (and typechecks) on install, which fails before protos are + # generated. Install deps only, then generate protos, then build. - name: Install dependencies - run: npm install + run: npm install --ignore-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # `check-types` runs `npm run protos` itself, so protos is not a - # separate step here. + # grpc-tools postinstall is skipped above; provide a system protoc + # for build-proto.mjs to fall back to. + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + + # `check-types` runs `npm run protos` itself before the tsc passes. - name: Check types run: npm run check-types @@ -58,10 +65,13 @@ jobs: cache: 'npm' - name: Install dependencies - run: npm install + run: npm install --ignore-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Generate Protocol Buffers run: npm run protos From da5fdeeba3e5458b30913cca13402306e1aee7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 01:04:13 +0200 Subject: [PATCH 5/6] ci: fix monorepo bootstrap via phased install Context: npm --ignore-scripts does not suppress prepare for local workspace packages (known npm v7+ behaviour). The cli workspace prepare runs tsc which fails before protos are generated. Approach: split install into phases so protos exist before cli prepare runs. Changes: - Phase 1: npm install --workspaces=false --ignore-scripts (root deps only, cli workspace not linked, no prepare triggered) - Phase 2: sudo apt-get install protobuf-compiler + npm run protos - Phase 3: npm install --workspace=cli (prepare now succeeds) - Phase 4: cd webview-ui && npm install (needed for webview-ui tsc) - Same fix applied to the test job for consistency Impact: Typecheck & Lint job can now install and typecheck without hitting the chicken-and-egg proto/prepare deadlock. --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7789b45d..5a9f691c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,20 +30,41 @@ jobs: node-version: 20 cache: 'npm' - # --ignore-scripts: the `cli` workspace has a `prepare` script that - # builds (and typechecks) on install, which fails before protos are - # generated. Install deps only, then generate protos, then build. - - name: Install dependencies - run: npm install --ignore-scripts + # Phase 1: install root deps only, skipping workspace packages entirely. + # npm --ignore-scripts does NOT suppress `prepare` for local workspace + # packages (known npm v7+ behaviour). --workspaces=false avoids linking + # the `cli` workspace so its prepare script never runs here. + - name: Install root dependencies + run: npm install --workspaces=false --ignore-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # grpc-tools postinstall is skipped above; provide a system protoc - # for build-proto.mjs to fall back to. + # as the fallback in build-proto.mjs. - name: Install protoc run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - # `check-types` runs `npm run protos` itself before the tsc passes. + # Phase 2: generate protos before the cli workspace is installed so + # that cli's prepare (tsc) can resolve the generated types. + - name: Generate Protocol Buffers + run: npm run protos + + # Phase 3: install the cli workspace. Its prepare script now runs + # successfully because the generated proto files already exist. + - name: Install cli workspace + run: npm install --workspace=cli + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Phase 4: webview-ui is not a declared workspace; install its deps + # so that `check-types` can run `tsc --noEmit` inside it. + - name: Install webview-ui dependencies + run: cd webview-ui && npm install + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # check-types re-runs protos (idempotent) then runs tsc for root, + # webview-ui, and cli. - name: Check types run: npm run check-types @@ -64,8 +85,8 @@ jobs: node-version: 20 cache: 'npm' - - name: Install dependencies - run: npm install --ignore-scripts + - name: Install root dependencies + run: npm install --workspaces=false --ignore-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -75,6 +96,11 @@ jobs: - name: Generate Protocol Buffers run: npm run protos + - name: Install cli workspace + run: npm install --workspace=cli + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # The mocha unit suite is currently fragile in CI (ts-node ESM + # VS Code shims). It runs non-blocking so it surfaces failures as a # signal without gating merges; hardening it is tracked separately. From ed6807227a5bbac701bf89f3fe3a8111b4bbfc3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 01:08:25 +0200 Subject: [PATCH 6/6] fix(lint): remove unused os import and console.warn Remove the unused `os` import in ailiance-memory.test.ts (dead code left from a refactor that replaced os.homedir() mocking with vi.mock). Replace the Logger.warn/console.warn fallback pattern in ailiance-memory.ts with a direct Logger.warn call. The fallback was unnecessary: Logger is always available in this context, and biome's no-console rule flags the console.warn at error level. --- cli/src/utils/__tests__/ailiance-memory.test.ts | 1 - src/utils/ailiance-memory.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cli/src/utils/__tests__/ailiance-memory.test.ts b/cli/src/utils/__tests__/ailiance-memory.test.ts index f442581c..05e8dd16 100644 --- a/cli/src/utils/__tests__/ailiance-memory.test.ts +++ b/cli/src/utils/__tests__/ailiance-memory.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import * as fs from "fs/promises" import * as path from "path" -import * as os from "os" import { deleteMemory, findMemories, diff --git a/src/utils/ailiance-memory.ts b/src/utils/ailiance-memory.ts index 178bf9e6..073c6d51 100644 --- a/src/utils/ailiance-memory.ts +++ b/src/utils/ailiance-memory.ts @@ -137,11 +137,7 @@ async function quarantineCorruptMemory(filePath: string, reason: string): Promis try { await fs.rename(filePath, quarantinedPath) const msg = `[ailiance-memory] corrupt memory file quarantined: ${filePath} → ${quarantinedPath} (${reason})` - try { - Logger.warn(msg) - } catch { - console.warn(msg) - } + Logger.warn(msg) } catch { // Quarantine failed (file vanished, permissions, etc.) — silent. }