diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41c99434..b9671280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,62 @@ 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: + # 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 + with: + persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} 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");