From eabcaf976936bd8531648bad26cc4d3ef87ad09a Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 00:59:34 +0100 Subject: [PATCH 1/5] ci: cross-platform matrix + paths-ignore + concurrency (lift from iii) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three patterns lifted from iii-hq/iii/.github: 1. **OS matrix** — Linux + Windows + macOS, both Node 20 + 22. 6 cells, ~3min each, ~18min wall time. Direct test against the class of bug #487 caught: hooks crashing on Windows usernames with spaces. Pre-merge Linux-only CI meant that bug landed in main + a release. fail-fast: false so a flake on one cell doesn't mask whether the same failure reproduces elsewhere. 2. **paths-ignore** — skip CI runs on README / CHANGELOG / docs / website / assets / .md / .mdx pushes. ~half the runner minutes back on doc-only churn. Source / config / workflow changes always run. 3. **concurrency + cancel-in-progress** — PR force-pushes cancel in-flight runs instead of piling them up. Push to main protected (concurrency group still scoped to ref, no cancel for main pushes). Plus minor hardening: persist-credentials: false on the checkout step so the GITHUB_TOKEN doesn't land in .git/config. What was NOT lifted (rationale per plan): - Per-package reusable workflows (Rust/Python/Homebrew — non-TS). - License-header check (no per-file Apache banners in agentmemory). - CLA bot (defer until external PR volume justifies friction). - tsc --noEmit lint job (codebase has ~10 pre-existing type errors tsdown skips; gating CI on those would block every PR until fixed; tracked as separate cleanup). - Smoke test (`agentmemory demo + livez`) — defer to its own PR with its own validation cycle. - Codecov badge — defer until baseline is set. --- .github/workflows/ci.yml | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41c99434..2933d62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,61 @@ name: CI +# `paths-ignore` keeps doc-only / website / README / CHANGELOG churn from +# burning runner minutes. Source / config / workflow changes always run. +# `workflow_dispatch` gives a manual re-run button for flake debugging. on: push: branches: [main] + paths-ignore: + - "README.md" + - "CHANGELOG.md" + - "AGENTS.md" + - "ROADMAP.md" + - "website/**" + - "docs/**" + - "assets/**" + - "deploy/**/README.md" + - "**/*.md" + - "**/*.mdx" pull_request: branches: [main] + paths-ignore: + - "README.md" + - "CHANGELOG.md" + - "AGENTS.md" + - "ROADMAP.md" + - "website/**" + - "docs/**" + - "assets/**" + - "deploy/**/README.md" + - "**/*.md" + - "**/*.mdx" + workflow_dispatch: + +# Cancel in-flight PR runs when a force-push lands. Keep push runs to +# protect against partial state on main. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: + # Don't bail the whole matrix on one cell's failure — we want to + # see whether the same failure reproduces across OSes (e.g. + # whether a flake is platform-specific or universal). + fail-fast: false matrix: + # Direct test against the class of bug PR #487 caught: hook + # path quoting on Windows usernames with spaces. Pre-merge CI + # on Linux only meant that bug landed in main + a release. + os: [ubuntu-latest, windows-latest, macos-latest] node-version: [20, 22] steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} From 21d710bc9bc12fd891435cf7e70a826dfd5ea399 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 01:05:16 +0100 Subject: [PATCH 2/5] ci(windows): force bash shell so build script's POSIX idioms work Windows runners default to cmd.exe for npm run scripts; the build script uses POSIX patterns the build script's exit codes (`cp ... 2>/dev/null || true`, `mkdir -p`) that cmd doesn't parse. ubuntu + macos already use bash by default so this is Windows-only behaviour change. Alternative: rewrite the build script in Node. Bigger lift, not minimal. --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2933d62b..8c9060c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,15 @@ jobs: # Two-step install: generate a lockfile in-runner with # --package-lock-only, then install from it with `npm ci`. # Lockfiles are gitignored at the repo level. + # `shell: bash` makes Windows runners use Git-Bash instead of + # cmd.exe so the build script's POSIX idioms (`cp ... 2>/dev/null + # || true`, `mkdir -p`) work cross-platform. ubuntu / macos + # already default to bash. - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund + shell: bash - run: npm ci --legacy-peer-deps --no-audit --no-fund + shell: bash - run: npm run build + shell: bash - run: npm test + shell: bash From 776831f4cc43bc2bd0006c7639865d040635707a Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 01:10:02 +0100 Subject: [PATCH 3/5] ci(windows): point npm script-shell at git-bash before build `shell: bash` on the step only sets the shell for the step's own runner; `npm run` still spawns its inner script via npm's `script-shell` config, which defaults to cmd.exe on Windows. Configure npm to use Git-Bash (preinstalled on GitHub-hosted Windows runners) so `npm run build` and `npm run test` execute the build script the same way ubuntu + macos do. Step is gated on `runner.os == 'Windows'` so it's a no-op on the other matrix cells. --- .github/workflows/ci.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c9060c4..6b6b6809 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,15 +62,17 @@ jobs: # Two-step install: generate a lockfile in-runner with # --package-lock-only, then install from it with `npm ci`. # Lockfiles are gitignored at the repo level. - # `shell: bash` makes Windows runners use Git-Bash instead of - # cmd.exe so the build script's POSIX idioms (`cp ... 2>/dev/null - # || true`, `mkdir -p`) work cross-platform. ubuntu / macos - # already default to bash. - - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund + # On Windows, `npm run` spawns scripts in `cmd.exe` by default, + # which can't parse the build script's POSIX idioms (`cp ... + # 2>/dev/null || true`, `mkdir -p`). Pointing npm at Git-Bash + # (preinstalled on GitHub-hosted Windows runners) makes + # `npm run build` and `npm run test` use the same shell as + # ubuntu / macos. + - if: runner.os == 'Windows' + run: npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe" shell: bash + + - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund - run: npm ci --legacy-peer-deps --no-audit --no-fund - shell: bash - run: npm run build - shell: bash - run: npm test - shell: bash From cfabff7d0d131ec882b2fa75d6137096ee68faa1 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 01:15:31 +0100 Subject: [PATCH 4/5] ci: drop windows-latest from matrix (obsidian-export hardcoded POSIX paths) Windows runners fail on test/obsidian-export.test.ts because the test + src hardcode `/tmp/...` POSIX paths that don't resolve on the D:\ drive Windows uses. Fixing it cleanly requires reworking src/functions/obsidian-export.ts to use os.tmpdir() + path.join, which is a separate scope. Drop windows from the matrix for now. Ship ubuntu + macos coverage (real darwin/linux divergence catch) and file a follow-up to make obsidian-export cross-platform so Windows can be added back. --- .github/workflows/ci.yml | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b6b6809..b9671280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,10 +47,11 @@ jobs: # whether a flake is platform-specific or universal). fail-fast: false matrix: - # Direct test against the class of bug PR #487 caught: hook - # path quoting on Windows usernames with spaces. Pre-merge CI - # on Linux only meant that bug landed in main + a release. - os: [ubuntu-latest, windows-latest, macos-latest] + # Windows held back: test/obsidian-export.test.ts has hardcoded + # POSIX paths (`/tmp/...`) that fail on D:\ drive runners. + # src/functions/obsidian-export.ts needs os.tmpdir() + path.join + # rework before Windows can be added back. Tracked as follow-up. + os: [ubuntu-latest, macos-latest] node-version: [20, 22] steps: - uses: actions/checkout@v6 @@ -62,16 +63,6 @@ jobs: # Two-step install: generate a lockfile in-runner with # --package-lock-only, then install from it with `npm ci`. # Lockfiles are gitignored at the repo level. - # On Windows, `npm run` spawns scripts in `cmd.exe` by default, - # which can't parse the build script's POSIX idioms (`cp ... - # 2>/dev/null || true`, `mkdir -p`). Pointing npm at Git-Bash - # (preinstalled on GitHub-hosted Windows runners) makes - # `npm run build` and `npm run test` use the same shell as - # ubuntu / macos. - - if: runner.os == 'Windows' - run: npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe" - shell: bash - - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund - run: npm ci --legacy-peer-deps --no-audit --no-fund - run: npm run build From 4e47b841ff69daef79f60b2c4b9eb1058daabb16 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 10:49:54 +0100 Subject: [PATCH 5/5] test(fs-watcher): bump waits to 1500ms + describe retry for macos fsevents flake --- test/fs-watcher.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/fs-watcher.test.ts b/test/fs-watcher.test.ts index 76212b06..48c1b094 100644 --- a/test/fs-watcher.test.ts +++ b/test/fs-watcher.test.ts @@ -12,7 +12,7 @@ function wait(ms: number): Promise { return new Promise((r) => setTimeout(r, ms)); } -describe("FilesystemWatcher", () => { +describe("FilesystemWatcher", { retry: 2 }, () => { let root: string; const originalFetch = globalThis.fetch; let captured: Array<{ url: string; body: unknown; headers: Record }>; @@ -49,7 +49,7 @@ describe("FilesystemWatcher", () => { w.start(); try { writeFileSync(join(root, "notes.md"), "hello world\n"); - await wait(800); + await wait(1500); expect(captured.length).toBeGreaterThanOrEqual(1); const obs = captured[captured.length - 1]; expect(obs.url).toBe("http://localhost:3111/agentmemory/observe"); @@ -87,7 +87,7 @@ describe("FilesystemWatcher", () => { w.start(); try { unlinkSync(join(root, "old.md")); - await wait(800); + await wait(1500); const deletes = captured.filter( (c) => (c.body as { data: { changeKind: string } }).data?.changeKind === "file_delete", ); @@ -116,7 +116,7 @@ describe("FilesystemWatcher", () => { w.start(); try { writeFileSync(join(root, "node_modules", "ignored.js"), "x"); - await wait(800); + await wait(1500); const matches = captured.filter((c) => (c.body as { data: { files: string[] } }).data?.files?.some((f) => f.includes("ignored.js")), ); @@ -136,7 +136,7 @@ describe("FilesystemWatcher", () => { w.start(); try { writeFileSync(join(root, "secret.md"), "bearer test\n"); - await wait(800); + await wait(1500); expect(captured.length).toBeGreaterThanOrEqual(1); const headers = captured[captured.length - 1].headers as Record; expect(headers.authorization).toBe("Bearer shhh");