From 73aad1a38f9fb3655a323c61b759ccf744275d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 17 Aug 2026 19:18:21 +0200 Subject: [PATCH 1/2] ci: skip device lanes for root-level docs-only changes (#1781 A9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add AGENTS.md, CHANGELOG.md, CONTEXT.md, CONTRIBUTING.md, LICENSE, and SECURITY.md to the pull_request paths-ignore block in ios.yml, android.yml, linux.yml, macos.yml, ci.yml, and size.yml. These root-level docs files were the only gap left after docs/**, website/**, and README.md — PRs #1568 (SECURITY.md only), #1697 (CONTEXT.md + docs/adr only), and #1722 (AGENTS.md + docs/) each still triggered a full 9-15 min macOS iOS run despite touching only prose. Why each file is safe to ignore for every one of these six workflows: - None of the four device workflows (ios/android/linux/macos) or their composite actions read any of these six files at runtime; the only hits from `grep -rln` across scripts/, src/, test/, and .github/actions/ are prose comments pointing humans at CONTEXT.md or AGENTS.md sections (e.g. scripts/layering/check.ts, scripts/wire-compat/run.ts, src/mcp/tool-ref-pins.ts) — never an `fs.readFileSync`/`readFile` of the file itself. - The check-affected selector (scripts/check-affected/model.ts) already classifies all six as pure docs: `isDocs()` matches any `.md` file plus the literal `LICENSE`, and `docsOwnership()` only special-cases `website/docs/docs/commands.md` (unrelated). So these files already select zero checks — they only ever produced `docsOnlyPaths` entries, never `SelectionReason`s. - Because they select zero checks, the gate-manifest's path-coverage category derivation (`scripts/gate/model.ts` `categories()`, which iterates `plan.reasons`) never records a category for them, so ci.yml has nothing check-manifest-only that these six files would need to keep reachable. `pnpm check:gate-manifest` and `pnpm check:gate-manifest:test` both stay green after the change (48 checks / 33 lanes, 28/28 gate tests passing). - size.yml's bundle-size job (scripts/size-report.mjs) measures the `pnpm build` dist output and startup timing only — no reference to any of these six files. (npm packs LICENSE/README.md into the publishable tarball, but that's a `pnpm check:package` node-22.12 concern in ci.yml's packaged-cli job, which is driven by `dist` contents and `package.json`, not by LICENSE/README prose — already evidenced by README.md being ignored here since before this change.) Scope disclosure: `mutation-affected.yml` uses a `paths:` allowlist (not paths-ignore) so it's structurally unaffected; `test-app-build-cache.yml` has no path filter at all. Neither was touched. actionlint and `pnpm check:gate-manifest`/`:test` pass on the changed workflows. --- .github/workflows/android.yml | 6 ++++++ .github/workflows/ci.yml | 6 ++++++ .github/workflows/ios.yml | 6 ++++++ .github/workflows/linux.yml | 6 ++++++ .github/workflows/macos.yml | 6 ++++++ .github/workflows/size.yml | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 77cb488560..a62801e06d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29979fed5e..4901b90581 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index c75bf6c2af..b5d094dc1b 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c910a47c93..4c37cb62c0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9d315fba07..b762d6c9c1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' diff --git a/.github/workflows/size.yml b/.github/workflows/size.yml index 150d3e11db..63ca37e187 100644 --- a/.github/workflows/size.yml +++ b/.github/workflows/size.yml @@ -6,6 +6,12 @@ on: - 'docs/**' - 'website/**' - 'README.md' + - 'AGENTS.md' + - 'CHANGELOG.md' + - 'CONTEXT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' - '.github/actions/build-docs/action.yml' - '.github/workflows/deploy.yml' - '.github/workflows/pr-preview.yml' From 994dab9ce2d1f9f35c0845da3cd7503d5067d1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 18 Aug 2026 07:54:23 +0200 Subject: [PATCH 2/2] test: pin root-doc paths-ignore entries with a regression test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback on #1791 from thymikee: the docs-only classifier for AGENTS.md/CHANGELOG.md/CONTEXT.md/CONTRIBUTING.md/ LICENSE/SECURITY.md across ios.yml/android.yml/linux.yml/macos.yml/ ci.yml/size.yml had no regression pin. Neither check:gate-manifest (only proves a *registered check* is reachable) nor actionlint (only validates YAML shape) nor generic Markdown coverage would catch a single dropped entry — e.g. LICENSE reappearing in one workflow's paths-ignore list but not another's would silently put a full 9-15 min device run back on prose-only PRs. test/ci/root-docs-paths-ignore.test.ts parses the six real workflow files and asserts, using the same matchesGlob the gate-manifest model uses to decide lane triggering, that each of the six root docs is ignored by each workflow's pull_request paths-ignore. Registered in vitest.config.ts's unit-core project next to its sibling upload-agent-device-artifacts.test.ts (parse-only, no device/subprocess lane needed). Verified red on main (all 36 file x doc assertions fail — confirmed via a throwaway script reading `git show main:.github/workflows/*.yml`) and green on this branch (6/6). Full unit-core project (873 files / 6641 tests) still passes; check:gate-manifest and check:gate-manifest:test unchanged (48 checks / 33 lanes, 28/28). --- test/ci/root-docs-paths-ignore.test.ts | 58 ++++++++++++++++++++++++++ vitest.config.ts | 3 ++ 2 files changed, 61 insertions(+) create mode 100644 test/ci/root-docs-paths-ignore.test.ts diff --git a/test/ci/root-docs-paths-ignore.test.ts b/test/ci/root-docs-paths-ignore.test.ts new file mode 100644 index 0000000000..d7ad80e4b4 --- /dev/null +++ b/test/ci/root-docs-paths-ignore.test.ts @@ -0,0 +1,58 @@ +// Regression pin for #1781 A9: the six root-level docs files +// (AGENTS.md, CHANGELOG.md, CONTEXT.md, CONTRIBUTING.md, LICENSE, SECURITY.md) +// must stay in the `pull_request` `paths-ignore` list of every workflow that +// also ignores `docs/**`/`website/**`/`README.md` — the four device lanes plus +// `ci.yml` and `size.yml`. Nothing else derives this: `check:gate-manifest` +// only asks whether a *registered check* is reachable, generic Markdown +// coverage does not look at workflow trigger config at all, and `actionlint` +// only validates YAML shape, not policy — so a PR that quietly drops one entry +// (e.g. re-adds `LICENSE` to a device workflow while missing it in `size.yml`) +// would pass every other gate and put a full 9-15 min device run back on +// prose-only PRs. This test reads the real workflow files and asserts the +// behavior directly, via the same glob matcher the gate-manifest model uses +// to decide whether a lane triggers for a given path. + +import fs from 'node:fs'; +import path from 'node:path'; +import { expect, test } from 'vitest'; +import { parse } from 'yaml'; +import { matchesGlob } from '../../scripts/gate/workflows.ts'; + +const repoRoot = path.resolve(import.meta.dirname, '../..'); + +const WORKFLOWS = ['ios.yml', 'android.yml', 'linux.yml', 'macos.yml', 'ci.yml', 'size.yml']; + +const ROOT_DOCS = [ + 'AGENTS.md', + 'CHANGELOG.md', + 'CONTEXT.md', + 'CONTRIBUTING.md', + 'LICENSE', + 'SECURITY.md', +]; + +type WorkflowDoc = { + // A bare `on:` key can parse as the boolean key `true` under YAML 1.1 + // semantics; scripts/gate/workflows.ts already guards against this, so this + // test mirrors that fallback rather than trusting `on` alone. + on?: Record; + true?: Record; +}; + +function pathsIgnore(file: string): string[] { + const doc = parse( + fs.readFileSync(path.join(repoRoot, '.github/workflows', file), 'utf8'), + ) as WorkflowDoc; + const on = doc.on ?? doc.true ?? {}; + return on.pull_request?.['paths-ignore'] ?? []; +} + +test.each(WORKFLOWS)('%s skips a pull_request triggered by only a root doc', (file) => { + const ignored = pathsIgnore(file); + for (const rootDoc of ROOT_DOCS) { + expect( + ignored.some((pattern) => matchesGlob(pattern, rootDoc)), + `${file}'s paths-ignore must match ${rootDoc} (got ${JSON.stringify(ignored)})`, + ).toBe(true); + } +}); diff --git a/vitest.config.ts b/vitest.config.ts index 4c922b100f..80faa4771c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -86,6 +86,9 @@ export default defineConfig({ 'scripts/__tests__/package-closure-audit.test.ts', // Parses CI configuration only, so this action guard needs no device or subprocess lane. 'test/ci/upload-agent-device-artifacts.test.ts', + // #1781 A9: pins the root-doc paths-ignore entries directly against the + // real workflow YAML, parse-only like its sibling above. + 'test/ci/root-docs-paths-ignore.test.ts', // The frozen replay-compat corpus (#1417): parse-only, no device or // subprocess work, so it belongs in the fast lane next to the // grammar it guards.