From f6257f3745f06ff3f183e9e7bdb13e6e2d6709ab Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 31 Jul 2026 22:16:26 -0400 Subject: [PATCH] ci(#2952): shard the full test lane instead of widening its cap (#2960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(#2952): budget CI job timeouts by headroom over measured cost `origin/next` was red. The only failing check was `Required tests`, and its sole cause was `test (ubuntu-latest, 24)` reported `cancelled` — GitHub's conclusion for a job that exceeds its `timeout-minutes`, not a button press. That job's `ubuntu-latest / 24` entry is the only `scope: full` matrix entry: it runs the whole unit suite under c8 coverage, then the scripts/ coverage floor, integration, security, install and slow, serially on one runner. On next@5a0a9f097 it ran 15m16s against `timeout-minutes: 15` and was axed 23s into `npm run test:slow`. Projected to a completed slow step (27s on the last green run) the lane costs ~15m20s. Confirmed hypothesis: the budget, not the suite. The lane had been riding the ceiling all day — 12m03s, 11m34s, 11m50s, 14m25s, 14m51s — and crossed on three of the last four full-lane runs (d2d2f7c08, 07603df8f, 5a0a9f097). There is no pathological test: the unit run is cost-first bin-packed into 12 chunks, chunk 1 is gated by run-tests-harness.test.cjs at 169s (expensive by design — it spawns real harness subprocesses, one of which exercises the per-chunk timeout), and the remaining chunks are 32-94s. 769s is the honest cost of 689 files under c8. Re-running could not have helped; the work exceeded the budget. Review of the first cut surfaced the same defect one runner away: `full test (windows-latest, 22, shard 3/3)` reached 18m59s against its own 20-minute cap on 05b170e44 (94%) and 18m14s on 81eeb8a53 (91%). That lane has already blown its cap twice (#1051, #1212). Fixed here rather than deferred. The first cut also asserted `test >= test-full`, which is unsound — those two budgets are dominated by different platforms, so their ordering carries no meaning. Replaced with the invariant that actually generalises: every lane is held to a headroom FACTOR over its own measured cost. `test` 15 -> 25 (1.5x of 16m), `test-full` 20 -> 30 (1.5x of 19m), `test-inert` unchanged at 15. tests/ci-test-job-timeout-budget.test.cjs locks that rule. No unit test can prove a lane still FITS its budget — only a real run measures that — but a budget can no longer be lowered back beneath what its lane is known to need, and a lane that gets slower must be re-measured rather than excused. Separately: the earlier `failure` at 05b170e44 was an unrelated, already-fixed CONTEXT-INDEX.json drift (07603df8f re-synced it; lint-tests is green at HEAD). 07603df8f's own run hit this same timeout, which is why it never reported green. Refs #869, #1051, #1212 * ci(#2952): shard the full test lane instead of widening its cap The `scope: full` lane was the only unsharded lane in this file. It ran the entire unit suite under c8 on one runner, grew past a 15-minute cap, and reddened `next`. Raising the cap bought room; it did not change the shape, and the same lane would have walked back into the ceiling. Shard it, the way #1212 answered this for the Windows lane. Balance comes from measurement, not file counts. scripts/run-tests.cjs already partitions by measured per-file duration using LPT (#2472); the table it reads was 10 days stale — 638 of 695 files timed, 64 missing, including the whole context-predicates group. Regenerated from a verified matrix run: 700 files, 0 missing. On that table the 685-file unit suite splits 19.37m / 19.37m / 19.37m — 0.0% spread — and the split is a total, disjoint cover with 0 files dropped. Completeness, disjointness, balance and determinism of the partition itself are already pinned against selectShard in run-tests-harness.test.cjs, including a fast-check property, so this change does not restate them. Sharding a COVERAGE run is the part that needs care. A per-shard percentage is meaningless — shard 2 never executes shard 1's files, so those read 0% — and leaving the gate on the shards would have quietly measured a third of the tree. Each shard now renders no report and only leaves raw V8 dumps; a new `coverage-gate` job merges all three into one coverage/tmp and runs the gate there. c8's default temp directory is where the download lands, so the ≥70% lines / ≥60% branches gate and the ≥55% scripts floor run unmodified against merged data. Both surfaces call the same npm scripts rather than inlining c8 into YAML, so the include/exclude globs and both thresholds stay defined once in package.json. The workflow holding its own copy is the divergence this repo has a rule against, and the new test cross-checks package.json so an inline reintroduction fails rather than drifts. tests/ci-full-lane-sharding.test.cjs covers the two ways this stays GREEN while being wrong: an incomplete shard set (declare 1/3 and 2/3, never 3/3, and a third of the suite silently stops running) and a coverage gate that stops being required. required-tests now depends on coverage-gate and fails on it, while still tolerating `skipped` so docs-only PRs are not blocked. `timeout-minutes: 25` on the lane is deliberately left alone. The budget test requires a real measurement before a lane's declared cost changes, and the sharded cost is not measured until this PR's own CI run. Refs #1212, #2472 * ci(#2952): tighten the sharded lane's budget to its measured cost The sharding commit deliberately left `timeout-minutes: 25` alone, because tests/ci-test-job-timeout-budget.test.cjs requires a real measurement before a lane's declared cost changes and the sharded cost did not exist yet. It exists now. Run 30677442953: shard 1/3 7m12s, 2/3 4m32s, 3/3 3m59s, and coverage-gate 1m20s. Shard 1 is the long pole because the unsharded aux suites ride along on it, which is deliberate — they total ~1m35s and sharding them would cost more than it saves. So the lane's budget is 15 against a slowest measured shard of 8 minutes (~1.9x), and coverage-gate joins LANE_COSTS at 2 minutes. 15 is the same number the lane blew before sharding; the work behind it is now a third the size. Merged coverage was checked against the pre-shard single-runner baseline rather than assumed from a green check: 94.36 stmts / 96.3 funcs / 94.36 lines identical, branches 84.22 vs 84.21 — one branch across two different trees, noise rather than a regression. --------- Co-authored-by: sim --- .github/workflows/test.yml | 174 ++- package.json | 2 + tests/ci-full-lane-sharding.test.cjs | 277 +++++ tests/ci-test-job-timeout-budget.test.cjs | 157 +++ tests/test-timings.json | 1334 +++++++++++---------- 5 files changed, 1273 insertions(+), 671 deletions(-) create mode 100644 tests/ci-full-lane-sharding.test.cjs create mode 100644 tests/ci-test-job-timeout-budget.test.cjs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c8d8ed85..f370faa4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -112,10 +112,22 @@ jobs: run: npm run lint:ci test: - name: test (${{ matrix.os }}, ${{ matrix.node-version }}) + name: test (${{ matrix.os }}, ${{ matrix.node-version }}${{ matrix.shard && format(', shard {0}', matrix.shard) || '' }}) needs: changes if: needs.changes.outputs.product_changed == 'true' runs-on: ${{ matrix.os }} + # #2952: this lane is sharded three ways (see the matrix below), so the + # budget covers ONE shard, not the whole unit suite. Measured on run + # 30677442953: shard 1/3 7m12s, 2/3 4m32s, 3/3 3m59s. Shard 1 is the long + # pole because the unsharded aux suites (integration/security/install/slow) + # ride along on it — that is deliberate; they total ~1m35s and sharding them + # would cost more than it saves. + # + # 15 is ~1.9x the slowest measured shard. Before sharding this same lane ran + # 15m20s against a 15-minute cap and was killed mid-run, which is the whole + # of #2952 — the number is unchanged, the work behind it is a third the size. + # tests/ci-test-job-timeout-budget.test.cjs holds every lane here to a + # headroom factor over its own measured cost. timeout-minutes: 15 env: GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled @@ -136,12 +148,31 @@ jobs: # and one Windows shell/path lane. The non-primary OS/runtime lanes run # scoped tests from scripts/ci-test-scope.cjs; Ubuntu/Node 24 runs the # broader default suite. + # + # #2952: the `scope: full` lane is SHARDED three ways. It was the only + # unsharded lane in this file, and the whole unit suite under c8 on one + # runner grew until it blew a 15-minute cap and reddened `next`. Raising + # the cap treated the symptom; sharding changes the shape. Shards are + # partitioned by MEASURED per-file duration (tests/test-timings.json) + # using LPT in scripts/run-tests.cjs — the same cost-aware packer #2472 + # gave the `test-full` lane, which measures 0.0% spread across 3 bins on + # the current table. The aux suites (integration/security/install/slow) + # total ~1m35s and are not worth sharding; they run on shard 1 only. - os: ubuntu-latest node-version: 22 scope: targeted - os: ubuntu-latest node-version: 24 scope: full + shard: 1/3 + - os: ubuntu-latest + node-version: 24 + scope: full + shard: 2/3 + - os: ubuntu-latest + node-version: 24 + scope: full + shard: 3/3 - os: windows-latest node-version: 24 scope: windows @@ -226,55 +257,47 @@ jobs: if: matrix.scope != 'full' run: node scripts/run-tests.cjs --files-from .ci-selected-tests.txt - # The unit suite runs ONCE here, under c8 with the coverage gate — the - # former standalone `coverage` job duplicated this lane's entire unit - # run (~4 min of runner time per PR) just to collect the same numbers. - - name: Run unit tests (coverage gate ≥70% on gsd-core/bin/lib) + # #2952: each shard runs its slice of the unit suite under c8 but renders + # NO report and enforces NO gate — it only leaves raw V8 dumps in + # coverage/tmp. A per-shard percentage is meaningless (shard 2 never + # executes shard 1's files, so every file outside its slice reads 0%), so + # the gate has to see all three slices merged. The `coverage-gate` job + # below does that. Both call the SAME npm scripts, so the c8 globs and the + # ≥70% lib / ≥55% scripts floor thresholds stay defined once, in package.json. + - name: Run unit tests (shard ${{ matrix.shard }}, raw coverage only) if: matrix.scope == 'full' env: NODE_OPTIONS: --max-old-space-size=6144 - run: npm run test:coverage:unit + run: npm run test:coverage:unit:raw -- --shard ${{ matrix.shard }} - # Second-tier floor over the CI/release/lint tooling itself. Re-slices - # the SAME V8 coverage data left in coverage/tmp by the run above — no - # extra suite execution. Audit 2026-06: scripts/ measured 65.95%; the - # 55% floor prevents a collapse to zero-coverage tooling while leaving - # headroom for variance. The threshold lives in package.json next to - # the 70% lib gate — raise deliberately, never lower. - - name: Coverage floor — scripts/ tooling (≥55%) + # Raw V8 dumps, not rendered reports — coverage-gate merges these. They + # compress hard in the artifact zip, but they are still bulk: keep the + # retention at one day so they cannot accumulate against the org quota. + - name: Upload raw coverage for merge if: matrix.scope == 'full' - run: npm run test:coverage:scripts-floor - - - name: Upload coverage artifact - if: always() && matrix.scope == 'full' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-unit - # coverage/tmp holds raw per-process V8 dumps (>1 GB for the full - # unit suite) — exclude it; the rendered reports are the artifact. - path: | - coverage/ - !coverage/tmp - .nyc_output/ - if-no-files-found: ignore - # ~440 MB per full run; same-run diagnostic never consumed by other - # jobs — keep short so it cannot accumulate against the org quota. - retention-days: 3 + name: coverage-tmp-shard-${{ strategy.job-index }} + path: coverage/tmp + if-no-files-found: error + retention-days: 1 + # The aux suites are small (~1m35s combined) and unsharded — running them + # on every shard would triple their cost for no signal. - name: Run integration tests - if: matrix.scope == 'full' + if: matrix.scope == 'full' && matrix.shard == '1/3' run: npm run test:integration - name: Run security tests - if: matrix.scope == 'full' + if: matrix.scope == 'full' && matrix.shard == '1/3' run: npm run test:security - name: Run install tests - if: matrix.scope == 'full' && needs.changes.outputs.full_matrix == 'true' + if: matrix.scope == 'full' && matrix.shard == '1/3' && needs.changes.outputs.full_matrix == 'true' run: npm run test:install - name: Run slow tests - if: matrix.scope == 'full' && needs.changes.outputs.full_matrix == 'true' + if: matrix.scope == 'full' && matrix.shard == '1/3' && needs.changes.outputs.full_matrix == 'true' run: npm run test:slow test-inert: @@ -353,7 +376,13 @@ jobs: # and a NESTED `leg.os` key is not resolvable by the H1 shell-policy linter # in scripts/workflow-policy.cjs, which reads `matrix.os`/`matrix.shell` # directly. Explicit rows keep both the cross-product and the linter happy.) - timeout-minutes: 20 + # #2952: `full test (windows-latest, 22, shard 3/3)` reached 18m59s (94% of + # a 20-minute cap) on 05b170e44 and 18m14s (91%) on 81eeb8a53. The Windows + # shards are slow for platform reasons — process spawn and filesystem cost, + # not extra work — and this lane has already blown its cap twice before + # (#1051, #1212). 30 is ~1.5x the worst observed shard, restoring the + # headroom that #1212's sharding bought and this suite has since eaten. + timeout-minutes: 30 env: GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled # #2854: pin the emitted gate's baseline to the SAME commit the tree was merged @@ -482,6 +511,67 @@ jobs: if: matrix.shard == 1 run: npm run test:security + # #2952: the coverage gate that the `test` lane used to run inline. Sharding + # the unit suite means no single runner sees the whole picture, so the gate + # moves here, downloads every shard's raw V8 dumps into one coverage/tmp, and + # runs the SAME commands as before against the merged data. Splitting the + # gate out is what makes sharding safe: nothing about the thresholds or the + # include/exclude globs changes, only where they are evaluated. + coverage-gate: + name: Coverage gate (merged shards) + needs: [changes, test] + if: needs.changes.outputs.product_changed == 'true' && needs.test.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: true + token: ${{ github.token }} + + - name: Set up Node.js 24 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + # merge-multiple flattens every shard's dumps into ONE coverage/tmp, which + # is exactly the layout c8 expects from a single run. The dumps are + # per-process files with distinct names, so there is nothing to collide. + - name: Download every shard's raw coverage + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + pattern: coverage-tmp-shard-* + path: coverage/tmp + merge-multiple: true + + # c8's default --temp-directory is ./coverage/tmp, which is exactly where + # the download above landed every shard's dumps — so these run unmodified + # against merged data and the thresholds stay defined in package.json. + - name: Report merged coverage + gate gsd-core/bin/lib (≥70% lines, ≥60% branches) + run: npm run test:coverage:report + + # Second-tier floor over the CI/release/lint tooling itself. Re-slices the + # SAME merged V8 data — no extra suite execution. Audit 2026-06: scripts/ + # measured 65.95%; the 55% floor prevents a collapse to zero-coverage + # tooling while leaving headroom for variance. Raise deliberately, never lower. + - name: Coverage floor — scripts/ tooling (≥55%) + run: npm run test:coverage:scripts-floor + + - name: Upload merged coverage report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-unit-merged + path: | + coverage/ + !coverage/tmp + if-no-files-found: ignore + retention-days: 3 + required-tests: name: Required tests needs: @@ -490,6 +580,7 @@ jobs: - test - test-inert - test-full + - coverage-gate if: always() runs-on: ubuntu-latest timeout-minutes: 1 @@ -503,6 +594,7 @@ jobs: TEST_RESULT: ${{ needs.test.result }} INERT_RESULT: ${{ needs.test-inert.result }} FULL_TEST_RESULT: ${{ needs.test-full.result }} + COVERAGE_GATE_RESULT: ${{ needs.coverage-gate.result }} run: | set -euo pipefail echo "code_changed=$CODE_CHANGED" @@ -512,6 +604,7 @@ jobs: echo "test=$TEST_RESULT" echo "test-inert=$INERT_RESULT" echo "test-full=$FULL_TEST_RESULT" + echo "coverage-gate=$COVERAGE_GATE_RESULT" if [ "$CHANGES_RESULT" != "success" ]; then echo "::error::test scope detection did not pass" @@ -533,12 +626,23 @@ jobs: echo "::error::test matrix did not pass" exit 1 fi - # The coverage gates (lib 70% + scripts/ 55% floor) run inside the - # ubuntu/24 full lane of the `test` matrix, so TEST_RESULT covers them. + # #2952: the coverage gates no longer run inside the `test` matrix — + # sharding moved them to the `coverage-gate` job, which merges every + # shard's dumps. TEST_RESULT therefore does NOT cover them any more; + # COVERAGE_GATE_RESULT below is what gates coverage. if [ "$FULL_TEST_RESULT" != "success" ] && [ "$FULL_TEST_RESULT" != "skipped" ]; then echo "::error::full parity matrix did not pass" exit 1 fi + + # #2952: the coverage gate is skipped when product code did not change + # (same condition as the test lane). Only a non-success, non-skipped + # result is a failure — treating `skipped` as red would block every + # docs-only PR. + if [ "$COVERAGE_GATE_RESULT" != "success" ] && [ "$COVERAGE_GATE_RESULT" != "skipped" ]; then + echo "::error::coverage-gate did not pass" + exit 1 + fi else if [ "$INERT_RESULT" != "success" ]; then echo "::error::inert CI lane did not pass" diff --git a/package.json b/package.json index 373ef14d6..be04e6a2e 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,8 @@ "test:coverage": "c8 --check-coverage --lines 70 --branches 60 --reporter text --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs", "test:coverage:scripts-floor": "c8 check-coverage --lines 55 --include 'scripts/**/*.cjs' --exclude 'tests/**' --all", "test:coverage:unit": "c8 --reporter text --reporter json-summary --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs --suite unit && node scripts/check-coverage-gate.cjs", + "test:coverage:unit:raw": "c8 --reporter none node scripts/run-tests.cjs --suite unit", + "test:coverage:report": "c8 report --reporter text --reporter json-summary --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all && node scripts/check-coverage-gate.cjs", "test:coverage:all": "npm run test:coverage", "test:mutation": "stryker run", "test:mutation:since": "stryker run --incremental --since origin/next" diff --git a/tests/ci-full-lane-sharding.test.cjs b/tests/ci-full-lane-sharding.test.cjs new file mode 100644 index 000000000..2721d82b6 --- /dev/null +++ b/tests/ci-full-lane-sharding.test.cjs @@ -0,0 +1,277 @@ +'use strict'; + +/** + * The full test lane is sharded, and the coverage gate that sharding displaced + * is still wired in — .github/workflows/test.yml (#2952). + * + * The `scope: full` lane was the only unsharded lane in this file. It ran the + * entire unit suite under c8 on a single runner, grew past a 15-minute cap, and + * reddened `next` (#2952). Raising the cap treated the symptom; sharding is the + * shape fix, and it is the same answer #1212 reached for the Windows lane. + * + * Sharding introduces two failure modes that stay GREEN while being wrong, so + * both are pinned here: + * + * 1. An incomplete shard set. If the matrix declares shards 1/3 and 2/3 but + * never 3/3, a third of the unit suite simply stops running and every check + * still passes. The partition MATH is already covered — completeness, + * disjointness, balance and determinism are asserted against selectShard in + * run-tests-harness.test.cjs (#1212), including a fast-check property. What + * is NOT covered there, and is asserted here, is that the WORKFLOW asks for + * a complete set: same denominator everywhere, numerators exactly 1..N. + * + * 2. A dropped coverage gate. A sharded run leaves each runner with a partial + * picture — shard 2 never executes shard 1's files, so those read 0%. The + * ≥70% gate therefore cannot live on the shards; it moved to `coverage-gate`, + * which merges every shard's raw V8 dumps. If that job silently stopped + * being required, coverage enforcement would vanish without any red check. + */ + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const yaml = require('js-yaml'); + +const WORKFLOWS_DIR = path.join(__dirname, '..', '.github', 'workflows'); + +function loadWorkflow(name) { + return yaml.load(fs.readFileSync(path.join(WORKFLOWS_DIR, name), 'utf8')); +} + +/** + * Parse an `i/n` shard spec into { index, total }, or null if malformed. + * Mirrors the grammar scripts/run-tests.cjs accepts for --shard. + */ +function parseShardSpec(spec) { + const m = /^(\d+)\/(\d+)$/.exec(String(spec)); + if (!m) return null; + const index = Number(m[1]); + const total = Number(m[2]); + if (!Number.isInteger(index) || !Number.isInteger(total)) return null; + if (total < 1 || index < 1 || index > total) return null; + return { index, total }; +} + +/** True iff `specs` is exactly one complete shard set: same N, numerators 1..N. */ +function isCompleteShardSet(specs) { + if (specs.length === 0) return false; + const parsed = specs.map(parseShardSpec); + if (parsed.some((p) => p === null)) return false; + const total = parsed[0].total; + if (parsed.some((p) => p.total !== total)) return false; + if (parsed.length !== total) return false; + const seen = new Set(parsed.map((p) => p.index)); + return seen.size === total && [...seen].every((i) => i >= 1 && i <= total); +} + +test('the full test lane is sharded and complete (#2952)', async (t) => { + const workflow = loadWorkflow('test.yml'); + const include = workflow.jobs.test.strategy.matrix.include; + const fullLanes = include.filter((e) => e.scope === 'full'); + + await t.test('the full lane is actually sharded, not a single runner', () => { + assert.ok(fullLanes.length > 0, 'expected at least one `scope: full` matrix entry'); + assert.ok( + fullLanes.length > 1, + 'the `scope: full` lane is back to a single unsharded entry. That is the ' + + '#2952 regression: the whole unit suite under c8 on one runner grew past ' + + 'its cap and reddened `next`.', + ); + for (const lane of fullLanes) { + assert.ok( + lane.shard !== undefined, + `a \`scope: full\` matrix entry declares no shard: ${JSON.stringify(lane)}`, + ); + } + }); + + await t.test('the declared shards form one complete set', () => { + const specs = fullLanes.map((e) => e.shard); + assert.ok( + isCompleteShardSet(specs), + `the full lane's shards ${JSON.stringify(specs)} are not a complete set. ` + + 'Every entry must share one denominator N and the numerators must be ' + + 'exactly 1..N — a missing numerator silently stops running that slice of ' + + 'the unit suite while every check stays green.', + ); + }); + + await t.test('only the full lane is sharded', () => { + for (const lane of include.filter((e) => e.scope !== 'full')) { + assert.equal( + lane.shard, undefined, + `non-full lane ${JSON.stringify(lane)} declares a shard; the scoped and ` + + 'Windows lanes run a selected file list, not a partition.', + ); + } + }); + + await t.test('each shard runs its own slice, not the whole suite', () => { + const unitStep = workflow.jobs.test.steps.find( + (s) => typeof s.run === 'string' && s.run.includes('test:coverage:unit:raw'), + ); + assert.ok(unitStep, 'no step in the test job runs the raw unit coverage script'); + assert.match( + unitStep.run, /--shard \$\{\{ matrix\.shard \}\}/, + 'the unit step does not pass matrix.shard through to run-tests.cjs, so ' + + 'every shard would run the ENTIRE suite — N times the cost, no speedup.', + ); + }); + + await t.test('the aux suites are pinned to one shard that actually exists', () => { + // Pinning to a LIVE shard is the whole assertion. A pin to a shard the + // matrix no longer declares — say the count moves to 4 and these `if:` + // conditions keep naming 1/3 — means the aux suites stop running entirely + // while every check stays green. Checking only that some shard literal is + // present would not catch that, so the literal is resolved against the + // shards the matrix actually declares. + const declared = fullLanes.map((e) => String(e.shard)); + const auxScripts = ['test:integration', 'test:security', 'test:install', 'test:slow']; + const pins = new Set(); + + for (const script of auxScripts) { + const step = workflow.jobs.test.steps.find( + (s) => typeof s.run === 'string' && s.run.includes(script), + ); + assert.ok(step, `no step runs \`npm run ${script}\``); + + const pin = /matrix\.shard == '([^']+)'/.exec(String(step.if)); + assert.ok( + pin, + `the \`${script}\` step is not pinned to a single shard; it would run ` + + 'once per shard and multiply its cost for no extra signal.', + ); + assert.ok( + declared.includes(pin[1]), + `the \`${script}\` step is pinned to shard '${pin[1]}', which the matrix ` + + `does not declare (${JSON.stringify(declared)}). That condition can ` + + 'never be true, so this suite would silently never run.', + ); + pins.add(pin[1]); + } + + assert.equal( + pins.size, 1, + `the aux suites are split across shards ${JSON.stringify([...pins])}; they ` + + 'are meant to run together on exactly one.', + ); + }); +}); + +test('the merged coverage gate survives sharding (#2952)', async (t) => { + const workflow = loadWorkflow('test.yml'); + + await t.test('a dedicated coverage-gate job exists and consumes the shards', () => { + const gate = workflow.jobs['coverage-gate']; + assert.ok(gate, '.github/workflows/test.yml declares no `coverage-gate` job'); + assert.ok( + (gate.needs || []).includes('test'), + 'coverage-gate must depend on `test` — it merges that job\'s shard artifacts', + ); + + const merges = gate.steps.some( + (s) => String(s.uses || '').includes('download-artifact') + && s.with && s.with['merge-multiple'] === true, + ); + assert.ok( + merges, + 'coverage-gate does not download the shard artifacts with merge-multiple. ' + + 'Without every shard merged into one coverage/tmp, the gate scores a ' + + 'partial run: files no shard in hand executed read 0%.', + ); + }); + + await t.test('both coverage thresholds are still enforced', () => { + const runs = workflow.jobs['coverage-gate'].steps + .map((s) => s.run).filter((r) => typeof r === 'string').join('\n'); + const pkg = JSON.parse( + fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'), + ); + + assert.match( + runs, /test:coverage:report/, + 'coverage-gate never runs the coverage report+gate script — the >=70% ' + + 'lines / >=60% branches gate on gsd-core/bin/lib would be gone', + ); + assert.match( + runs, /test:coverage:scripts-floor/, + 'coverage-gate never enforces the >=55% scripts/ floor', + ); + + // The workflow calls npm scripts precisely so the thresholds are defined + // once. If a future edit inlines c8 into the YAML, the two surfaces can + // drift silently — the gate would still be green while measuring something + // other than what package.json says. + assert.match( + pkg.scripts['test:coverage:report'], /check-coverage-gate\.cjs/, + 'test:coverage:report no longer runs check-coverage-gate.cjs', + ); + assert.match( + pkg.scripts['test:coverage:scripts-floor'], /--lines 55/, + 'test:coverage:scripts-floor no longer enforces 55%', + ); + assert.match( + pkg.scripts['test:coverage:unit:raw'], /--suite unit/, + 'test:coverage:unit:raw no longer runs the unit suite', + ); + assert.doesNotMatch( + pkg.scripts['test:coverage:unit:raw'], /--shard/, + 'the shard must come from the workflow matrix, not be baked into the script', + ); + }); + + await t.test('required-tests fails when the coverage gate fails', () => { + const required = workflow.jobs['required-tests']; + assert.ok( + (required.needs || []).includes('coverage-gate'), + 'required-tests does not depend on coverage-gate, so a red gate could not ' + + 'block a merge', + ); + + const summarize = required.steps[0]; + assert.ok( + 'COVERAGE_GATE_RESULT' in (summarize.env || {}), + 'required-tests does not read needs.coverage-gate.result', + ); + assert.match( + summarize.run, /coverage-gate did not pass/, + 'required-tests never fails on a red coverage-gate — depending on a job ' + + 'without checking its result makes the dependency decorative', + ); + }); + + await t.test('a skipped coverage gate is not treated as a failure', () => { + // coverage-gate is conditioned on product_changed, exactly like the test + // lane. A docs-only PR skips both; treating `skipped` as red would block + // every one of them. + assert.match( + workflow.jobs['required-tests'].steps[0].run, + /COVERAGE_GATE_RESULT" != "skipped"/, + 'required-tests treats a skipped coverage-gate as a failure', + ); + }); +}); + +test('shard-spec parsing is exact at its boundaries (#2952)', () => { + assert.deepEqual(parseShardSpec('1/3'), { index: 1, total: 3 }); + assert.deepEqual(parseShardSpec('3/3'), { index: 3, total: 3 }); + assert.deepEqual(parseShardSpec('1/1'), { index: 1, total: 1 }); + + // index 0 is below the range, index total+1 above it. + assert.equal(parseShardSpec('0/3'), null); + assert.equal(parseShardSpec('4/3'), null); + assert.equal(parseShardSpec('1/0'), null); + + assert.equal(parseShardSpec('1'), null); + assert.equal(parseShardSpec('a/3'), null); + assert.equal(parseShardSpec(''), null); + assert.equal(parseShardSpec(undefined), null); + + // A complete set, and the three ways one stops being complete. + assert.equal(isCompleteShardSet(['1/3', '2/3', '3/3']), true); + assert.equal(isCompleteShardSet(['1/3', '2/3']), false, 'missing numerator'); + assert.equal(isCompleteShardSet(['1/3', '2/3', '2/3']), false, 'duplicate numerator'); + assert.equal(isCompleteShardSet(['1/3', '2/3', '3/4']), false, 'mixed denominator'); + assert.equal(isCompleteShardSet([]), false, 'empty set'); +}); diff --git a/tests/ci-test-job-timeout-budget.test.cjs b/tests/ci-test-job-timeout-budget.test.cjs new file mode 100644 index 000000000..4c52de419 --- /dev/null +++ b/tests/ci-test-job-timeout-budget.test.cjs @@ -0,0 +1,157 @@ +'use strict'; + +/** + * CI job timeout budgets — .github/workflows/test.yml (#2952). + * + * A GitHub Actions job that exceeds its `timeout-minutes` is reported + * `cancelled`, not `failed`. That conclusion propagates into `Required tests` + * and reddens the branch, while reading like someone hit the cancel button — + * which is what makes this failure mode expensive to diagnose and worth a gate. + * + * It has now happened three times in this repo: #1051 and #1212 on the Windows + * full-test lane, and #2952 on the unsharded `test` lane, whose + * `ubuntu-latest / 24` entry is the only `scope: full` matrix entry — it runs + * the whole unit suite under c8 coverage, then the scripts/ coverage floor, + * integration, security, install and slow, serially on one runner. Every one of + * those was the same root cause: a budget sized to what the lane cost that + * week, with no headroom for the suite to grow into. + * + * So the rule enforced here is not a fixed number per lane — it is a HEADROOM + * FACTOR over each lane's measured cost. A lane may be slow; what it may not be + * is budgeted to finish with seconds to spare. + * + * This is a budget assertion, not a duration assertion. No unit test can prove + * a lane still FITS its budget — only a real CI run measures that. What this + * file guarantees is that a budget cannot be quietly lowered back beneath what + * its lane is already known to need. + */ + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const yaml = require('js-yaml'); + +const WORKFLOWS_DIR = path.join(__dirname, '..', '.github', 'workflows'); + +function loadWorkflow(name) { + return yaml.load(fs.readFileSync(path.join(WORKFLOWS_DIR, name), 'utf8')); +} + +/** + * Multiplier applied to a lane's measured cost to get its required budget. + * 1.5x is enough slack for ordinary suite growth across a release cycle without + * letting a genuinely runaway lane hide behind a large number. + */ +const HEADROOM_FACTOR = 1.5; + +/** + * Measured wall-clock cost per lane, in whole minutes rounded UP, each from a + * named run. Raise an entry only alongside a fresh measurement — never to make + * a red gate green. Raising a measurement raises the required budget with it, + * which is the point: a lane that got slower must be re-budgeted, not excused. + */ +const LANE_COSTS = [ + { + job: 'test', + measuredMinutes: 8, + // Sharded three ways as of #2952, so this is ONE shard's cost, not the + // whole unit suite. Run 30677442953: shard 1/3 7m12s, 2/3 4m32s, 3/3 3m59s. + // Shard 1 is the long pole because the unsharded aux suites ride on it. + // Before sharding the same lane cost 15m20s and blew a 15-minute cap. + evidence: 'run 30677442953 — 7m12s slowest shard', + }, + { + job: 'test-full', + measuredMinutes: 19, + // Worst observed shard is `full test (windows-latest, 22, shard 3/3)`: + // 18m59s on 05b170e44 and 18m14s on 81eeb8a53. The Windows shards are slow + // for platform reasons, not extra work. + evidence: 'run 30650559192 — 18m59s, windows-22 shard 3/3', + }, + { + job: 'coverage-gate', + measuredMinutes: 2, + // Downloads three shards' raw V8 dumps, renders one merged report and runs + // both thresholds. Run 30677442953: 1m20s end to end, most of it npm ci. + evidence: 'run 30677442953 — 1m20s', + }, + { + job: 'test-inert', + measuredMinutes: 2, + // Runs only the targeted-test step when no product code changed; observed + // around a minute. Listed so its budget cannot be dropped to nothing. + evidence: 'targeted-only lane, ~1m observed', + }, +]; + +function requiredBudgetMinutes(measuredMinutes, headroomFactor = HEADROOM_FACTOR) { + return Math.ceil(measuredMinutes * headroomFactor); +} + +function hasSufficientBudget(budgetMinutes, measuredMinutes, headroomFactor = HEADROOM_FACTOR) { + return Number.isInteger(budgetMinutes) + && budgetMinutes >= requiredBudgetMinutes(measuredMinutes, headroomFactor); +} + +test('CI job timeout budgets carry headroom over measured cost (#2952)', async (t) => { + const workflow = loadWorkflow('test.yml'); + + for (const lane of LANE_COSTS) { + await t.test(`${lane.job} is budgeted above its measured cost`, () => { + assert.ok( + workflow.jobs && Object.prototype.hasOwnProperty.call(workflow.jobs, lane.job), + `.github/workflows/test.yml declares no job \`${lane.job}\`. If it was ` + + 'renamed or removed, update LANE_COSTS in this file to match — do not ' + + 'delete the entry to make this pass.', + ); + + const budget = workflow.jobs[lane.job]['timeout-minutes']; + const required = requiredBudgetMinutes(lane.measuredMinutes); + + assert.equal( + typeof budget, 'number', + `.github/workflows/test.yml jobs.${lane.job} must declare timeout-minutes`, + ); + assert.ok( + hasSufficientBudget(budget, lane.measuredMinutes), + `jobs.${lane.job}.timeout-minutes is ${budget}, but the lane measured ` + + `${lane.measuredMinutes}m (${lane.evidence}) and needs at least ` + + `${required} — ${HEADROOM_FACTOR}x — so suite growth does not breach ` + + 'the cap. A job that exceeds timeout-minutes is reported `cancelled` ' + + 'and reddens `Required tests`.', + ); + }); + } + + // Boundary coverage on the predicate that decides every lane above: + // required-1 must be rejected, required and required+1 accepted. + await t.test('budget sufficiency is exact at the boundary', () => { + for (const lane of LANE_COSTS) { + const required = requiredBudgetMinutes(lane.measuredMinutes); + + assert.equal(hasSufficientBudget(required - 1, lane.measuredMinutes), false, + `${lane.job}: a budget one minute under the requirement must be rejected`); + assert.equal(hasSufficientBudget(required, lane.measuredMinutes), true, + `${lane.job}: a budget exactly at the requirement must be accepted`); + assert.equal(hasSufficientBudget(required + 1, lane.measuredMinutes), true, + `${lane.job}: a budget over the requirement must be accepted`); + } + }); + + await t.test('a non-integer budget is not a sufficient budget', () => { + // `timeout-minutes: 24.5` is not something GitHub accepts; treating it as + // sufficient would let a malformed workflow through this gate. + assert.equal(hasSufficientBudget(24.5, 16), false); + assert.equal(hasSufficientBudget(Number.NaN, 16), false); + assert.equal(hasSufficientBudget(undefined, 16), false); + }); + + await t.test('requiredBudgetMinutes rounds up rather than truncating', () => { + // 15 * 1.5 = 22.5 — truncation would hand back 22 and under-budget the lane. + assert.equal(requiredBudgetMinutes(15, 1.5), 23); + assert.equal(requiredBudgetMinutes(16, 1.5), 24); + assert.equal(requiredBudgetMinutes(19, 1.5), 29); + assert.equal(requiredBudgetMinutes(10, 1.5), 15); + }); +}); diff --git a/tests/test-timings.json b/tests/test-timings.json index b0735e816..322f2edc5 100644 --- a/tests/test-timings.json +++ b/tests/test-timings.json @@ -6,645 +6,707 @@ "test-events-linux-node22.jsonl", "test-events-linux-node24.jsonl" ], - "file_count": 638, + "file_count": 700, "timings": { - "active-workstream-store.test.cjs": 102, - "active-workstream-store.unit.test.cjs": 332, - "adapter-declarative-equivalence.test.cjs": 31, - "adapter-imperative.test.cjs": 63, - "adr-15-progress-converge.test.cjs": 50, - "adr-218-release-version-validation.test.cjs": 62, - "adr-22-plan-drift-guard.test.cjs": 2737, - "adr-230-pr-target-policy.test.cjs": 117, - "adr-612-collision-characterization.test.cjs": 88, - "adr-index-gate.test.cjs": 4976, - "adr-parser.property.test.cjs": 1305, - "adr-parser.test.cjs": 34, - "adr-parser.unit.test.cjs": 380, - "adr857-contribution-merge.test.cjs": 90, - "adr857-core-without-capabilities.test.cjs": 4109, - "adr857-predicate-boundary.test.cjs": 100, - "affected-tests-lib.test.cjs": 175, - "agent-classification-parity.test.cjs": 92, - "agent-frontmatter.test.cjs": 526, - "agent-install-check.test.cjs": 91, - "agent-install-validation.test.cjs": 7395, - "agent-required-reading-consistency.test.cjs": 142, - "agent-size-budget.test.cjs": 111, - "agent-skills-awareness.test.cjs": 55, - "agent-skills-bootstrap.test.cjs": 315, - "agent-skills.test.cjs": 20045, - "ai-evals.test.cjs": 4637, - "allowlist-ratchet.test.cjs": 70, - "analyze-dependencies.test.cjs": 30, - "anti-pattern-enforcement.test.cjs": 59, - "antigravity-repo-grounding.test.cjs": 220, + "active-workstream-store.test.cjs": 42, + "active-workstream-store.unit.test.cjs": 277, + "adapter-declarative-equivalence.test.cjs": 29, + "adapter-imperative.test.cjs": 169, + "adr-15-progress-converge.test.cjs": 334, + "adr-218-release-version-validation.test.cjs": 88, + "adr-22-plan-drift-guard.test.cjs": 3331, + "adr-230-pr-target-policy.test.cjs": 153, + "adr-612-bracket-grammar.test.cjs": 249, + "adr-612-collision-characterization.test.cjs": 71, + "adr-index-gate.test.cjs": 5307, + "adr-parser.property.test.cjs": 1855, + "adr-parser.test.cjs": 62, + "adr-parser.unit.test.cjs": 452, + "adr857-contribution-merge.test.cjs": 119, + "adr857-core-without-capabilities.test.cjs": 5207, + "adr857-predicate-boundary.test.cjs": 85, + "affected-tests-lib.test.cjs": 105, + "agent-classification-parity.test.cjs": 89, + "agent-frontmatter.test.cjs": 945, + "agent-install-check.test.cjs": 101, + "agent-install-validation.test.cjs": 8799, + "agent-required-reading-consistency.test.cjs": 160, + "agent-size-budget.test.cjs": 163, + "agent-skills-awareness.test.cjs": 68, + "agent-skills-bootstrap.test.cjs": 244, + "agent-skills.test.cjs": 21368, + "ai-evals.test.cjs": 5307, + "allowlist-ratchet.test.cjs": 148, + "analyze-dependencies.test.cjs": 35, + "anti-pattern-enforcement.test.cjs": 66, + "antigravity-repo-grounding.test.cjs": 79, "antigravity-reviewer.test.cjs": 58, - "antigravity-upgrades.test.cjs": 4908, - "api-coverage-gate-e2e.test.cjs": 4990, - "api-coverage.test.cjs": 597, - "artifacts.test.cjs": 246, - "ask-user-questions-fallback.test.cjs": 108, - "assumption-delta-checkpoint-e2e.test.cjs": 3823, - "assumption-delta.test.cjs": 1017, - "atomic-write-coverage.test.cjs": 72, - "audit-command-cutover.test.cjs": 4706, - "audit-fix-command.test.cjs": 159, - "augment-conversion.test.cjs": 83, - "augment-upgrades.test.cjs": 6978, - "autonomous-allowed-tools.test.cjs": 19, - "autonomous-converge.test.cjs": 41, - "autonomous-decomposition.test.cjs": 42, - "autonomous-interactive.test.cjs": 47, - "autonomous-to-flag.test.cjs": 44, - "autonomous-ui-steps.test.cjs": 74, - "backwards-compat-phase-id.test.cjs": 51, - "broken-windows.test.cjs": 9220, - "bugs-1656-1657.test.cjs": 192, - "capability-cli.test.cjs": 37059, - "capability-command-dispatch.test.cjs": 177, - "capability-consent.test.cjs": 6516, - "capability-consumption.test.cjs": 203, - "capability-ledger.test.cjs": 1050, - "capability-lifecycle.test.cjs": 5010, - "capability-loader.test.cjs": 667, - "capability-manifest-version.test.cjs": 266, - "capability-matrix-sync.test.cjs": 31, - "capability-precedence-parity.test.cjs": 94, - "capability-registry.test.cjs": 31553, - "capability-source.test.cjs": 944, - "capability-state.test.cjs": 6635, - "capability-trust.test.cjs": 232, - "capability-writer.test.cjs": 3085, - "chain-flag-plan-phase.test.cjs": 91, - "changeset-cli.test.cjs": 6838, - "changeset-github-release-notes.test.cjs": 814, - "changeset-lint.test.cjs": 1006, - "changeset-new.test.cjs": 170, - "changeset-parse.test.cjs": 122, - "changeset-render.test.cjs": 37, - "changeset-serialize.test.cjs": 87, - "check-env.test.cjs": 9927, - "check-gap-analysis-plan-post-e2e.test.cjs": 14298, - "check-glossary-refs.test.cjs": 1108, - "check-predicate.test.cjs": 306, - "check-tdd-review-checkpoint-e2e.test.cjs": 5647, - "check-ui-plan-gate.test.cjs": 127, - "check-ui-safety-gate.test.cjs": 1839, - "check-update-config-dir.test.cjs": 277, - "ci-rebase-check.test.cjs": 1643, - "ci-test-scope.test.cjs": 40081, - "cjs-command-router-adapter.test.cjs": 6540, - "claude-imperative-reference.test.cjs": 72, - "claude-md-path.test.cjs": 3670, - "claude-md.test.cjs": 7773, - "claude-orchestration-command-router.test.cjs": 2626, - "claude-orchestration.test.cjs": 464, - "claude-skills-migration.test.cjs": 214, - "cleanup-branch-pruning.test.cjs": 49, - "cli-exit.test.cjs": 719, - "cline-beforetool-upgrade.test.cjs": 66, - "cline-dispatch-degradation.test.cjs": 135, - "cline-imperative-reference.test.cjs": 101, - "cline-install.test.cjs": 3279, - "cline-model-override-upgrade.test.cjs": 51, - "cline-support.test.cjs": 40, - "clock-seam.test.cjs": 8384, - "close-phase-todos-stage-deletion.test.cjs": 24, - "clusters.test.cjs": 59, - "code-review-agent-skills.test.cjs": 32, - "code-review-command.test.cjs": 413, - "code-review-flags.test.cjs": 119, - "code-review-pipeline-regression.test.cjs": 150, - "code-review-summary-parser.test.cjs": 87, - "code-review.test.cjs": 2574, - "codebuddy-install.test.cjs": 2807, - "codebuddy-upgrades.test.cjs": 2532, - "codex-config.test.cjs": 108295, - "codex-declarative-reference.test.cjs": 110, - "command-arg-projection.test.cjs": 50, - "command-contract.test.cjs": 1023, - "command-routing-hub.test.cjs": 29430, - "commands.test.cjs": 70578, - "commit-docs-bypass.test.cjs": 2188, - "commit-files-deletion.test.cjs": 2583, - "commit-files-pathspec.test.cjs": 4154, - "concurrency-safety.test.cjs": 12250, - "config-field-docs.test.cjs": 1203, - "config-get-default.test.cjs": 18007, - "config-loader.test.cjs": 6320, - "config-schema.property.test.cjs": 5229, - "config.test.cjs": 114417, - "configuration-migrate-config.test.cjs": 1751, - "context-enrichment.test.cjs": 124, - "context-utilization.property.test.cjs": 203, - "context-utilization.test.cjs": 109, - "context7-plugin-grant-parity.test.cjs": 111, - "continuation-grammar-parity.test.cjs": 62, - "contributor-standards.test.cjs": 63, - "conventional-title.property.test.cjs": 265, - "conventional-title.test.cjs": 175, - "copilot-install.test.cjs": 44480, - "copilot-upgrades.test.cjs": 4816, - "core-utils.test.cjs": 247, - "coverage-metadata-parser.test.cjs": 10769, - "coverage-uat-routing.test.cjs": 1959, - "cross-ai-execution.test.cjs": 97, - "cursor-conversion.test.cjs": 79, - "cursor-dispatch-upgrade.test.cjs": 68, - "cursor-hook-bus-upgrade.test.cjs": 69, - "cursor-hooks.test.cjs": 407, - "cursor-imperative-reference.test.cjs": 164, - "cursor-reviewer.test.cjs": 165, - "debug-session-management.test.cjs": 105, - "debugger-bug-taxonomy.test.cjs": 103, - "debugger-fix-acceptance.test.cjs": 83, - "debugger-prevention.test.cjs": 109, - "debugger-rca-branching.test.cjs": 112, - "debugger-repro-hardening.test.cjs": 90, - "debugger-sbfl.test.cjs": 207, - "debugger-semantic-recall.test.cjs": 73, - "decisions.test.cjs": 10172, - "declarative-reference-antigravity.test.cjs": 1177, - "declarative-reference-augment.test.cjs": 1271, - "declarative-reference-codebuddy.test.cjs": 1354, - "declarative-reference-copilot.test.cjs": 1410, - "declarative-reference-windsurf.test.cjs": 1779, - "declarative-reference-zcode.test.cjs": 1756, - "defaults-json-fallback.test.cjs": 118, - "derive-progress.property.test.cjs": 207, - "discord-release-summary.test.cjs": 35, - "discuss-all-flag.test.cjs": 100, - "discuss-checkpoint.test.cjs": 69, - "discuss-mode.test.cjs": 84, - "discuss-phase-power.test.cjs": 214, - "dispatcher.test.cjs": 15150, - "docs-parity-live-registry.test.cjs": 585, - "docs-update.test.cjs": 7877, - "drift-detection.test.cjs": 8590, - "edge-probe-docs-fixtures.test.cjs": 95, - "edge-probe-planner-contract.test.cjs": 33, - "edge-probe-spec-phase-contract.test.cjs": 39, - "edge-probe.test.cjs": 1251, - "edit-phase.test.cjs": 159, - "effort-sync-installed-runtime.test.cjs": 2194, - "ensure-runtime-build.test.cjs": 6601, - "eslint-rules.test.cjs": 793, - "eval.property.test.cjs": 207, - "eval.test.cjs": 92, - "event.test.cjs": 162, - "execute-mvp-tdd-gate.test.cjs": 1420, - "execute-phase-active-flags.test.cjs": 199, - "execute-phase-step-5-5-deviation-doc.test.cjs": 321, - "execute-phase-wave.test.cjs": 1293, - "execute-phase-worktree-artifacts.test.cjs": 73, - "execute-wave-post-gate-pipeline-e2e.test.cjs": 9138, - "executor-mvp-tdd-section.test.cjs": 109, - "explore-command.test.cjs": 44, - "external-descriptor-confinement.test.cjs": 56, - "external-descriptor-loader-wiring.test.cjs": 261, - "external-job-waiting.test.cjs": 82, - "external-job.test.cjs": 179, - "extract-learnings.test.cjs": 102, - "extraction-discipline-consistency.test.cjs": 67, - "federated-config-key-removal.test.cjs": 137, - "federated-config-loadconfig.test.cjs": 915, - "federated-config.test.cjs": 206, - "few-shot-calibration.test.cjs": 68, - "fix-1941-quick-worktree-stale-base.test.cjs": 31, - "fix-2068-resolve-execution-dynamic-routing.test.cjs": 4537, - "fix-2136-clock-local-today.test.cjs": 746, - "fix-2138-ship-note-lost-on-merge.test.cjs": 27, - "fix-2194-review-timeout-guidance.test.cjs": 57, - "fix-2196-debug-agent-handoff.test.cjs": 51, - "fix-2257-debug-nonterminal-resume.test.cjs": 104, - "fix-2284-hermes-agent-delegate-task-projection.test.cjs": 7880, - "fix-2285-claude-orchestration-wiring.test.cjs": 3226, - "fix-2287-deferred-items-reader.test.cjs": 2854, - "fix-2289-context-monitor-event-allowlist.test.cjs": 4471, - "fix-2297-resolve-model-ids-runtime-scoping.test.cjs": 333, - "fix-2337-add-todo-severity.test.cjs": 29, - "fix-2358-review-temp-path-scoping.test.cjs": 117, - "fixture-builder.test.cjs": 198, - "forensics.test.cjs": 92, - "format-github-release-notes.test.cjs": 57, - "frontmatter-cli.test.cjs": 14617, - "frontmatter.property.test.cjs": 2347, - "frontmatter.test.cjs": 7786, - "frontmatter.unit.test.cjs": 438, - "gap-checker.property.test.cjs": 10443, - "gate-predicate-evaluator.test.cjs": 113, - "gates-taxonomy.test.cjs": 92, - "gemini-runtime-removed.test.cjs": 5019, - "gen-registry.test.cjs": 1299, - "getdirname-registry-derivation.test.cjs": 41, - "git-base-branch.test.cjs": 6663, - "global-config-home-fragment.test.cjs": 56, - "golden-install-parity.test.cjs": 22621, - "golden-install-tree.test.cjs": 24864, - "golden-parity-single-source.test.cjs": 56, - "graphify-auto-update.slow.test.cjs": 31613, - "graphify-command-cutover.test.cjs": 10275, - "graphify-graph-path.test.cjs": 681, - "graphify-query.test.cjs": 1101, - "graphify-visualization.test.cjs": 3720, - "graphify.test.cjs": 1175, - "gsd-check-update-worker-platform-gate.test.cjs": 1382, - "gsd-mcp-server-bin.test.cjs": 451, - "gsd-mcp-server.test.cjs": 1033, - "gsd-researcher-app-aware.test.cjs": 79, - "gsd-researcher-flow-diagram.test.cjs": 101, - "gsd-settings-advanced.test.cjs": 2641, - "gsd-statusline-git.property.test.cjs": 1600, - "gsd-statusline-state.property.test.cjs": 223, - "gsd-statusline.test.cjs": 6310, - "gsd-tools-path-refs.test.cjs": 298, - "gsd-workflow.structure.test.cjs": 128, - "gsd2-import.test.cjs": 3459, - "handshake-serialized.test.cjs": 93, - "hardcoded-paths.test.cjs": 497, - "health-validation.test.cjs": 13246, - "helpers-cleanup.test.cjs": 89, - "helpers-process-isolation.test.cjs": 38, - "hermes-dispatch-upgrade.test.cjs": 75, - "hermes-imperative-reference.test.cjs": 169, - "hermes-skills-migration.test.cjs": 139, - "hook-bus.test.cjs": 65, - "hook-validation.test.cjs": 87, - "hooks-opt-in.test.cjs": 6422, - "host-integration-descriptors.test.cjs": 310, - "host-integration-validator-parity.test.cjs": 150, - "host-integration.test.cjs": 343, - "hub-logger-integration.test.cjs": 293, - "import-command.test.cjs": 207, - "ingest-docs.test.cjs": 3456, - "init-manager.test.cjs": 20615, - "init.test.cjs": 64096, - "injection-blocking-config.test.cjs": 661, - "inline-plan-threshold.test.cjs": 815, - "install-minimal-hooks.test.cjs": 136334, - "install-nested-layout.test.cjs": 1852, - "install-path-detection.test.cjs": 5261, - "install-regressions.test.cjs": 30044, - "install-runtime-artifacts.test.cjs": 30370, - "install-update-marker.test.cjs": 137, - "install-write-confinement.test.cjs": 5239, - "install.test.cjs": 114366, - "installer-migration-authoring.test.cjs": 90, - "installer-migration-install.integration.test.cjs": 55927, - "installer-migration-prune-stale-pristine.test.cjs": 118, - "installer-migration-rename-gsd-core.test.cjs": 145, - "installer-migration-report.test.cjs": 273, - "installer-migrations.test.cjs": 4798, - "intel-command-cutover.test.cjs": 6280, - "intel.test.cjs": 6372, - "inventory-headings-countfree.test.cjs": 51, - "inventory-manifest-sync.test.cjs": 42, - "io.test.cjs": 2003, - "ios-scaffold-safety.test.cjs": 97, - "issue-1575-agent-descriptor-parity.test.cjs": 2781, - "issue-1855-marketplace-manifest.test.cjs": 146, - "issue-2045-third-party-skills-surface.test.cjs": 2049, - "issue-2517-runtime-aware-profiles.test.cjs": 1598, - "issue-2639-codex-toml-neutralization.test.cjs": 47, - "issue-429-comment-text-gate.test.cjs": 1232, - "issue-498-identity-drift-lint.test.cjs": 211, - "issue-498-package-identity.test.cjs": 99, - "issue-498-update-backup-runtime-dir.test.cjs": 50, - "issue-498-update-context.test.cjs": 791, - "issue-57-runtime-install-no-drift.test.cjs": 148, - "issue-607-cache-lineage.test.cjs": 109, - "issue-607-installer-dry-run.install.test.cjs": 866, - "issue-607-legacy-cleanup.test.cjs": 181, - "issue-69-surface-keeps-nested.test.cjs": 463, - "issue-766-plugin-manifest.test.cjs": 442, - "issue-787-cline-hooks-agents.test.cjs": 7503, - "issue-815-update-next-channel.test.cjs": 125, - "issue-844-manifest-version-sync.test.cjs": 290, - "issue-dedupe.test.cjs": 303, - "issue-version-gate.test.cjs": 238, - "kilo-imperative-reference.test.cjs": 168, - "kilo-upgrades.test.cjs": 11403, - "kimi-agent-converter.test.cjs": 7497, - "kimi-imperative-reference.test.cjs": 95, - "kimi-skill-converter.test.cjs": 44, - "kimi-tool-mapping.test.cjs": 77, - "kimi-upgrades.test.cjs": 7445, - "learnings.test.cjs": 1921, - "lint-docs-required.test.cjs": 127, - "lint-legacy-dir-name.test.cjs": 957, - "lint-pr-check-project-dir.test.cjs": 520, - "lint-regression-test-names.test.cjs": 2172, - "lint-resolution-provenance.test.cjs": 91, - "lint-skill-deps.test.cjs": 1579, - "lint-test-file-count.test.cjs": 1486, - "list-seeds.property.test.cjs": 184, - "list-seeds.test.cjs": 8729, - "locking-bugs-1909-1916-1925-1927.test.cjs": 3269, - "logger.test.cjs": 146, - "loop-hook-firing-spike.test.cjs": 70, - "loop-hooks-empty-points-e2e.test.cjs": 11918, - "loop-hooks-ship-pre-e2e.test.cjs": 3698, - "loop-hooks-verify-post-e2e.test.cjs": 5985, - "loop-host-contract.test.cjs": 246, - "loop-render-hooks.test.cjs": 6447, - "m8-writestatemd-scan-after-lock.test.cjs": 58, - "m9-statelock-write-error-orphan.test.cjs": 40, - "managed-hooks.test.cjs": 10224, - "markdown-sectionizer.test.cjs": 2010, - "markdown-table.test.cjs": 313, - "mcp-tool-inheritance.test.cjs": 217, - "mempalace-capture-headless-invocation.test.cjs": 50, - "methodology-artifact.test.cjs": 85, - "milestone-archive.test.cjs": 8439, - "milestone-helper.test.cjs": 70, - "milestone-prefixed-convention.test.cjs": 3208, - "milestone-summary.test.cjs": 1646, - "milestone.test.cjs": 30666, - "model-adapter.test.cjs": 31, - "model-alias-map.test.cjs": 25, - "model-catalog-runtime-defaults.test.cjs": 74, - "model-catalog-valid-tiers.test.cjs": 58, - "model-profiles.test.cjs": 2200, - "model-resolver.test.cjs": 35231, - "model-routing-spawn-threading.test.cjs": 235, - "multi-runtime-select.test.cjs": 97, - "mutation-matrix-ratchet.test.cjs": 373, - "mutation-matrix-stdin-eagain.test.cjs": 35, - "mvp-phase-command.test.cjs": 61, - "mvp-phase-integration.test.cjs": 2508, - "mvp-phase-spidr.test.cjs": 32, - "new-milestone-clear-phases.test.cjs": 10113, - "new-project-mvp-prompt.test.cjs": 103, - "next-decimal-roadmap-scan.test.cjs": 4986, - "next-safety-gates.test.cjs": 97, - "next-up-clear-order.test.cjs": 223, - "no-bare-npm-exec.rule.test.cjs": 262, - "no-crlf-fragile-split.rule.test.cjs": 361, - "no-dead-sdk-refs.test.cjs": 155, - "no-hardcoded-home-gsd-tools.test.cjs": 285, - "no-hardcoded-tmp.rule.test.cjs": 369, - "no-path-literal-in-assert.rule.test.cjs": 588, - "no-phantom-issue-refs.test.cjs": 890, - "no-posix-mode-bit-assert.rule.test.cjs": 384, - "no-unguarded-nonportable-exec.rule.test.cjs": 670, - "non-claude-runtimes-registry-derivation.test.cjs": 57, - "normalize-path-in-content.rule.test.cjs": 1379, - "normalize-test-command.test.cjs": 257, - "npm-integrity-gate.test.cjs": 4285, - "onboard-command.test.cjs": 10665, - "opencode-command-dir-plural.test.cjs": 14645, - "opencode-imperative-reference.test.cjs": 71, - "opencode-permissions.test.cjs": 42, - "opencode-plugin-adapter.test.cjs": 2776, - "opencode-review-reconstruction.property.test.cjs": 35, - "orphan-worktree-detection.test.cjs": 1618, - "orphaned-hooks.test.cjs": 70, - "package-legitimacy-gate.test.cjs": 378, - "package-legitimacy.property.test.cjs": 167, - "package-legitimacy.test.cjs": 294, - "package-manifest.test.cjs": 59, + "antigravity-upgrades.test.cjs": 5412, + "api-coverage-gate-e2e.test.cjs": 5637, + "api-coverage.test.cjs": 877, + "artifacts.test.cjs": 267, + "ask-user-questions-fallback.test.cjs": 232, + "assumption-delta-checkpoint-e2e.test.cjs": 4596, + "assumption-delta.test.cjs": 1246, + "atomic-write-coverage.test.cjs": 43, + "audit-command-cutover.test.cjs": 5401, + "audit-fix-command.test.cjs": 150, + "augment-conversion.test.cjs": 85, + "augment-upgrades.test.cjs": 8688, + "autonomous-allowed-tools.test.cjs": 29, + "autonomous-converge.test.cjs": 416, + "autonomous-decomposition.test.cjs": 48, + "autonomous-interactive.test.cjs": 42, + "autonomous-to-flag.test.cjs": 49, + "autonomous-ui-steps.test.cjs": 114, + "backwards-compat-phase-id.test.cjs": 49, + "branch-no-track-guard.test.cjs": 102, + "broken-windows.test.cjs": 12686, + "bugs-1656-1657.test.cjs": 281, + "capability-cli.test.cjs": 43755, + "capability-command-dispatch.test.cjs": 218, + "capability-consent.test.cjs": 7222, + "capability-consumption.test.cjs": 393, + "capability-ledger.test.cjs": 1211, + "capability-lifecycle.test.cjs": 5491, + "capability-loader.test.cjs": 1208, + "capability-manifest-version.test.cjs": 435, + "capability-matrix-sync.test.cjs": 26, + "capability-precedence-parity.test.cjs": 118, + "capability-registry.test.cjs": 36285, + "capability-source.test.cjs": 1141, + "capability-state.test.cjs": 7930, + "capability-trust.test.cjs": 161, + "capability-writer.test.cjs": 2794, + "chain-flag-plan-phase.test.cjs": 58, + "changeset-cli.test.cjs": 5900, + "changeset-github-release-notes.test.cjs": 1201, + "changeset-lint.test.cjs": 1296, + "changeset-new.test.cjs": 261, + "changeset-parse.test.cjs": 178, + "changeset-render.test.cjs": 43, + "changeset-serialize.test.cjs": 44, + "check-env.test.cjs": 9683, + "check-gap-analysis-plan-post-e2e.test.cjs": 14843, + "check-glossary-refs.test.cjs": 1546, + "check-predicate.test.cjs": 204, + "check-tdd-review-checkpoint-e2e.test.cjs": 5991, + "check-ui-plan-gate.test.cjs": 401, + "check-ui-safety-gate.test.cjs": 1935, + "check-update-config-dir.test.cjs": 394, + "ci-rebase-check.test.cjs": 1634, + "ci-test-job-timeout-budget.test.cjs": 82, + "ci-test-scope.test.cjs": 44131, + "cjs-command-router-adapter.test.cjs": 6166, + "claude-imperative-reference.test.cjs": 149, + "claude-md-path.test.cjs": 4212, + "claude-md.test.cjs": 11827, + "claude-orchestration-command-router.test.cjs": 2863, + "claude-orchestration.test.cjs": 1618, + "claude-skills-migration.test.cjs": 126, + "cleanup-branch-pruning.test.cjs": 73, + "cli-exit.test.cjs": 1042, + "cline-beforetool-upgrade.test.cjs": 64, + "cline-dispatch-degradation.test.cjs": 69, + "cline-imperative-reference.test.cjs": 115, + "cline-install.test.cjs": 3360, + "cline-model-override-upgrade.test.cjs": 82, + "cline-support.test.cjs": 32, + "clock-seam.test.cjs": 9641, + "close-phase-todos-padded-resolves.test.cjs": 712, + "close-phase-todos-stage-deletion.test.cjs": 23, + "clusters.test.cjs": 68, + "code-review-agent-skills.test.cjs": 33, + "code-review-command.test.cjs": 570, + "code-review-flags.test.cjs": 106, + "code-review-pipeline-regression.test.cjs": 258, + "code-review-summary-parser.test.cjs": 138, + "code-review.test.cjs": 2592, + "codebuddy-install.test.cjs": 3870, + "codebuddy-upgrades.test.cjs": 2201, + "codex-config.test.cjs": 118852, + "codex-declarative-reference.test.cjs": 101, + "command-arg-projection.test.cjs": 67, + "command-contract.test.cjs": 866, + "command-routing-hub.test.cjs": 29374, + "commands.test.cjs": 80272, + "commit-docs-bypass.test.cjs": 2116, + "commit-files-deletion.test.cjs": 2759, + "commit-files-pathspec.test.cjs": 6175, + "concurrency-safety.test.cjs": 11518, + "config-field-docs.test.cjs": 1319, + "config-get-default.test.cjs": 16526, + "config-loader.test.cjs": 8375, + "config-schema.property.test.cjs": 6073, + "config.test.cjs": 128262, + "configuration-migrate-config.test.cjs": 2451, + "context-enrichment.test.cjs": 70, + "context-index-sync.test.cjs": 140, + "context-predicates-query.test.cjs": 14493, + "context-predicates.property.test.cjs": 419, + "context-predicates.test.cjs": 285, + "context-utilization.property.test.cjs": 187, + "context-utilization.test.cjs": 106, + "context7-plugin-grant-parity.test.cjs": 170, + "continuation-grammar-parity.test.cjs": 116, + "contributor-standards.test.cjs": 259, + "conventional-title.property.test.cjs": 282, + "conventional-title.test.cjs": 202, + "copilot-install.test.cjs": 50303, + "copilot-upgrades.test.cjs": 4932, + "core-utils.test.cjs": 521, + "coverage-metadata-parser.test.cjs": 10083, + "coverage-uat-routing.test.cjs": 2242, + "cross-ai-execution.test.cjs": 382, + "cursor-conversion.test.cjs": 59, + "cursor-dispatch-upgrade.test.cjs": 33, + "cursor-hook-bus-upgrade.test.cjs": 76, + "cursor-hooks.test.cjs": 408, + "cursor-imperative-reference.test.cjs": 80, + "cursor-reviewer.test.cjs": 117, + "debug-session-management.test.cjs": 94, + "debug-session-manager-commit.test.cjs": 1696, + "debugger-bug-taxonomy.test.cjs": 105, + "debugger-fix-acceptance.test.cjs": 71, + "debugger-prevention.test.cjs": 69, + "debugger-rca-branching.test.cjs": 59, + "debugger-repro-hardening.test.cjs": 56, + "debugger-sbfl.test.cjs": 176, + "debugger-semantic-recall.test.cjs": 66, + "decisions.test.cjs": 12151, + "declarative-reference-antigravity.test.cjs": 1490, + "declarative-reference-augment.test.cjs": 2001, + "declarative-reference-codebuddy.test.cjs": 1357, + "declarative-reference-copilot.test.cjs": 1644, + "declarative-reference-windsurf.test.cjs": 1663, + "declarative-reference-zcode.test.cjs": 1500, + "defaults-json-fallback.test.cjs": 161, + "derive-progress.property.test.cjs": 240, + "discord-release-summary.test.cjs": 75, + "discuss-all-flag.test.cjs": 70, + "discuss-checkpoint.test.cjs": 88, + "discuss-mode.test.cjs": 438, + "discuss-phase-power.test.cjs": 90, + "dispatcher.test.cjs": 14429, + "docs-parity-live-registry.test.cjs": 613, + "docs-update.test.cjs": 7300, + "drift-detection.test.cjs": 7147, + "edge-probe-docs-fixtures.test.cjs": 57, + "edge-probe-planner-contract.test.cjs": 28, + "edge-probe-spec-phase-contract.test.cjs": 48, + "edge-probe.test.cjs": 1274, + "edit-phase.test.cjs": 174, + "effort-surface-axis.test.cjs": 6187, + "effort-sync-installed-runtime.test.cjs": 1523, + "emitted-attribution.test.cjs": 174010, + "emitted-provenance.test.cjs": 32365, + "ensure-runtime-build.test.cjs": 6792, + "eslint-rules.test.cjs": 1622, + "estimate-calibrate.test.cjs": 10138, + "estimate-loop-convergence.test.cjs": 13467, + "eval.property.test.cjs": 232, + "eval.test.cjs": 82, + "event.test.cjs": 90, + "execute-mvp-tdd-gate.test.cjs": 1710, + "execute-phase-active-flags.test.cjs": 191, + "execute-phase-step-5-5-deviation-doc.test.cjs": 80, + "execute-phase-wave.test.cjs": 2068, + "execute-phase-worktree-artifacts.test.cjs": 141, + "execute-phase-worktree-guard.test.cjs": 3527, + "execute-wave-post-gate-pipeline-e2e.test.cjs": 8859, + "executor-mvp-tdd-section.test.cjs": 123, + "explore-command.test.cjs": 95, + "external-descriptor-confinement.test.cjs": 75, + "external-descriptor-loader-wiring.test.cjs": 214, + "external-job-waiting.test.cjs": 72, + "external-job.test.cjs": 157, + "extract-learnings.test.cjs": 122, + "extraction-discipline-consistency.test.cjs": 52, + "feat-2296-provider-escalation.test.cjs": 28336, + "federated-config-key-removal.test.cjs": 111, + "federated-config-loadconfig.test.cjs": 642, + "federated-config.test.cjs": 202, + "few-shot-calibration.test.cjs": 105, + "fix-1941-quick-worktree-stale-base.test.cjs": 61, + "fix-2068-resolve-execution-dynamic-routing.test.cjs": 5656, + "fix-2136-clock-local-today.test.cjs": 555, + "fix-2138-ship-note-lost-on-merge.test.cjs": 55, + "fix-2194-review-timeout-guidance.test.cjs": 39, + "fix-2196-debug-agent-handoff.test.cjs": 58, + "fix-2257-debug-nonterminal-resume.test.cjs": 46, + "fix-2284-hermes-agent-delegate-task-projection.test.cjs": 8516, + "fix-2285-claude-orchestration-wiring.test.cjs": 4018, + "fix-2287-deferred-items-reader.test.cjs": 3324, + "fix-2289-context-monitor-event-allowlist.test.cjs": 4290, + "fix-2297-resolve-model-ids-runtime-scoping.test.cjs": 195, + "fix-2337-add-todo-severity.test.cjs": 68, + "fix-2358-review-temp-path-scoping.test.cjs": 84, + "fix-2494-review-claude-gemini-empty-guard.test.cjs": 161, + "fix-2587-cursor-hook-workspace-roots.test.cjs": 3751, + "fix-2589-workflow-jq-dependency.test.cjs": 186, + "fix-2590-workflow-script-contract.test.cjs": 2504, + "fix-2598-opencode-background-dispatch.test.cjs": 40, + "fix-2603-kimi-code-host-matrix.test.cjs": 190, + "fix-2605-review-local-server-empty-guard.test.cjs": 100, + "fix-2608-commit-staging-failure.test.cjs": 7752, + "fix-2615-effortsurface-matrix-parity.test.cjs": 261, + "fix-2717-cursor-windsurf-codex-commonjs-marker.test.cjs": 9415, + "fix-2794-review-qwen-empty-guard.test.cjs": 81, + "fixture-builder.test.cjs": 342, + "forensics.test.cjs": 107, + "format-github-release-notes.test.cjs": 193, + "frontmatter-cli.test.cjs": 16119, + "frontmatter.property.test.cjs": 2602, + "frontmatter.test.cjs": 10076, + "frontmatter.unit.test.cjs": 543, + "gap-checker.property.test.cjs": 11380, + "gate-predicate-evaluator.test.cjs": 296, + "gates-taxonomy.test.cjs": 66, + "gemini-runtime-removed.test.cjs": 5349, + "gen-context-index.test.cjs": 4858, + "gen-registry.test.cjs": 2902, + "getdirname-registry-derivation.test.cjs": 38, + "git-base-branch.test.cjs": 7151, + "global-config-home-fragment.test.cjs": 91, + "golden-install-tree.test.cjs": 27233, + "golden-parity-single-source.test.cjs": 123, + "graphify-auto-update.slow.test.cjs": 40040, + "graphify-command-cutover.test.cjs": 12649, + "graphify-graph-path.test.cjs": 487, + "graphify-query.test.cjs": 1285, + "graphify-visualization.test.cjs": 4536, + "graphify.test.cjs": 986, + "gsd-check-update-worker-platform-gate.test.cjs": 1485, + "gsd-mcp-server-bin.test.cjs": 467, + "gsd-mcp-server.test.cjs": 1020, + "gsd-researcher-app-aware.test.cjs": 124, + "gsd-researcher-flow-diagram.test.cjs": 55, + "gsd-settings-advanced.test.cjs": 2870, + "gsd-statusline-git.property.test.cjs": 2359, + "gsd-statusline-state.property.test.cjs": 313, + "gsd-statusline.test.cjs": 7746, + "gsd-tools-path-refs.test.cjs": 261, + "gsd-workflow.structure.test.cjs": 191, + "gsd2-import.test.cjs": 5048, + "handshake-serialized.test.cjs": 259, + "hardcoded-paths.test.cjs": 501, + "health-validation.test.cjs": 16001, + "helpers-cleanup.test.cjs": 32, + "helpers-process-isolation.test.cjs": 44, + "hermes-dispatch-upgrade.test.cjs": 34, + "hermes-imperative-reference.test.cjs": 150, + "hermes-skills-migration.test.cjs": 132, + "hook-bus.test.cjs": 62, + "hook-validation.test.cjs": 128, + "hooks-opt-in.test.cjs": 8147, + "host-integration-descriptors.test.cjs": 410, + "host-integration-validator-parity.test.cjs": 107, + "host-integration.test.cjs": 11827, + "hub-logger-integration.test.cjs": 98, + "import-command.test.cjs": 189, + "ingest-docs.test.cjs": 3247, + "init-manager.test.cjs": 20228, + "init.test.cjs": 70651, + "injection-blocking-config.test.cjs": 1034, + "inline-plan-threshold.test.cjs": 1185, + "install-minimal-hooks.test.cjs": 145698, + "install-nested-layout.test.cjs": 3004, + "install-path-detection.test.cjs": 6137, + "install-regressions.test.cjs": 30663, + "install-runtime-artifacts.test.cjs": 32308, + "install-update-marker.test.cjs": 142, + "install-write-confinement.test.cjs": 4019, + "install.test.cjs": 112670, + "installer-migration-authoring.test.cjs": 119, + "installer-migration-install.integration.test.cjs": 59743, + "installer-migration-pi-extension-ext.test.cjs": 59, + "installer-migration-prune-stale-pristine.test.cjs": 92, + "installer-migration-rename-gsd-core.test.cjs": 141, + "installer-migration-report.test.cjs": 279, + "installer-migrations.test.cjs": 4771, + "intel-command-cutover.test.cjs": 6893, + "intel.test.cjs": 6937, + "inventory-headings-countfree.test.cjs": 37, + "inventory-manifest-sync.test.cjs": 76, + "io.test.cjs": 2030, + "ios-scaffold-safety.test.cjs": 73, + "issue-1575-agent-descriptor-parity.test.cjs": 3011, + "issue-1855-marketplace-manifest.test.cjs": 75, + "issue-2045-third-party-skills-surface.test.cjs": 2906, + "issue-2517-runtime-aware-profiles.test.cjs": 1842, + "issue-2639-codex-toml-neutralization.test.cjs": 42, + "issue-2695-codex-hook-set.test.cjs": 23677, + "issue-2701-nul-corrupted-validators.test.cjs": 4725, + "issue-2762-plan-reviews-chunked.test.cjs": 40, + "issue-2765-brace-expansion-lockfile.test.cjs": 2351, + "issue-2771-advisor-subagent-type.test.cjs": 21, + "issue-2772-discuss-phase-text-inconsistencies.test.cjs": 46, + "issue-2828-flat-roadmap-total-phases.test.cjs": 832, + "issue-2834-codex-install-model-ordering.test.cjs": 58, + "issue-429-comment-text-gate.test.cjs": 1560, + "issue-498-identity-drift-lint.test.cjs": 212, + "issue-498-package-identity.test.cjs": 118, + "issue-498-update-backup-runtime-dir.test.cjs": 31, + "issue-498-update-context.test.cjs": 589, + "issue-57-runtime-install-no-drift.test.cjs": 113, + "issue-607-cache-lineage.test.cjs": 55, + "issue-607-installer-dry-run.install.test.cjs": 821, + "issue-607-legacy-cleanup.test.cjs": 228, + "issue-69-surface-keeps-nested.test.cjs": 512, + "issue-766-plugin-manifest.test.cjs": 421, + "issue-787-cline-hooks-agents.test.cjs": 7701, + "issue-815-update-next-channel.test.cjs": 221, + "issue-844-manifest-version-sync.test.cjs": 306, + "issue-dedupe.test.cjs": 297, + "issue-version-gate.test.cjs": 220, + "kilo-imperative-reference.test.cjs": 69, + "kilo-upgrades.test.cjs": 12639, + "kimi-agent-converter.test.cjs": 7478, + "kimi-guard-normalization-parity.test.cjs": 62, + "kimi-guard-typed-payload-reads.test.cjs": 91, + "kimi-imperative-reference.test.cjs": 165, + "kimi-normalize-payload.property.test.cjs": 359, + "kimi-payload-field-shadowing.security.test.cjs": 1280, + "kimi-skill-converter.test.cjs": 63, + "kimi-tool-mapping.test.cjs": 33, + "kimi-upgrades.test.cjs": 7963, + "kimi-variant-disambiguation.test.cjs": 1745, + "learnings.test.cjs": 2333, + "lint-docs-required.test.cjs": 140, + "lint-legacy-dir-name.test.cjs": 1035, + "lint-pr-check-project-dir.test.cjs": 304, + "lint-regression-test-names.test.cjs": 1703, + "lint-resolution-provenance.test.cjs": 47, + "lint-skill-deps.test.cjs": 1875, + "lint-test-file-count.test.cjs": 1547, + "list-seeds.property.test.cjs": 195, + "list-seeds.test.cjs": 7535, + "locking-bugs-1909-1916-1925-1927.test.cjs": 3772, + "logger.test.cjs": 163, + "loop-hook-firing-spike.test.cjs": 41, + "loop-hooks-empty-points-e2e.test.cjs": 14192, + "loop-hooks-ship-pre-e2e.test.cjs": 3657, + "loop-hooks-verify-post-e2e.test.cjs": 7479, + "loop-host-contract.test.cjs": 362, + "loop-render-hooks.test.cjs": 8041, + "m8-writestatemd-scan-after-lock.test.cjs": 77, + "m9-statelock-write-error-orphan.test.cjs": 61, + "managed-hooks.test.cjs": 9104, + "markdown-sectionizer.test.cjs": 2218, + "markdown-table.test.cjs": 307, + "mcp-tool-inheritance.test.cjs": 374, + "mempalace-capture-headless-invocation.test.cjs": 181, + "methodology-artifact.test.cjs": 61, + "milestone-archive.test.cjs": 10365, + "milestone-helper.test.cjs": 86, + "milestone-prefixed-convention.test.cjs": 3461, + "milestone-summary.test.cjs": 1511, + "milestone.test.cjs": 32900, + "model-adapter.test.cjs": 62, + "model-alias-map.test.cjs": 50, + "model-catalog-runtime-defaults.test.cjs": 63, + "model-catalog-valid-tiers.test.cjs": 125, + "model-omit-when-inherit-guard.test.cjs": 6322, + "model-profiles.test.cjs": 2920, + "model-resolver.test.cjs": 37304, + "model-routing-spawn-threading.test.cjs": 360, + "multi-runtime-select.test.cjs": 168, + "mutation-matrix-ratchet.test.cjs": 407, + "mutation-matrix-stdin-eagain.test.cjs": 44, + "mutation-workflow-base-ref.test.cjs": 2979, + "mvp-phase-command.test.cjs": 65, + "mvp-phase-integration.test.cjs": 2771, + "mvp-phase-spidr.test.cjs": 64, + "new-milestone-clear-phases.test.cjs": 12089, + "new-project-mvp-prompt.test.cjs": 143, + "next-decimal-roadmap-scan.test.cjs": 4968, + "next-safety-gates.test.cjs": 60, + "next-up-clear-order.test.cjs": 133, + "no-bare-gsd-tools-command-position.test.cjs": 167, + "no-bare-npm-exec.rule.test.cjs": 229, + "no-crlf-fragile-split.rule.test.cjs": 436, + "no-dead-sdk-refs.test.cjs": 223, + "no-hardcoded-home-gsd-tools.test.cjs": 261, + "no-hardcoded-tmp.rule.test.cjs": 257, + "no-path-literal-in-assert.rule.test.cjs": 559, + "no-phantom-issue-refs.test.cjs": 1008, + "no-posix-mode-bit-assert.rule.test.cjs": 492, + "no-unguarded-nonportable-exec.rule.test.cjs": 638, + "non-claude-runtimes-registry-derivation.test.cjs": 34, + "normalize-path-in-content.rule.test.cjs": 1395, + "normalize-test-command.test.cjs": 170, + "npm-integrity-gate.test.cjs": 3769, + "onboard-command.test.cjs": 11426, + "opencode-command-dir-plural.test.cjs": 14426, + "opencode-imperative-reference.test.cjs": 78, + "opencode-permissions.test.cjs": 40, + "opencode-plugin-adapter.test.cjs": 3248, + "opencode-review-reconstruction.property.test.cjs": 314, + "orphan-worktree-detection.test.cjs": 1193, + "orphaned-hooks.test.cjs": 73, + "package-legitimacy-gate.test.cjs": 371, + "package-legitimacy.property.test.cjs": 179, + "package-legitimacy.test.cjs": 243, + "package-manifest.test.cjs": 137, "package-name-single-source.test.cjs": 143, - "parallel-dependent-plans.test.cjs": 100, - "path-replacement.test.cjs": 347, - "pattern-mapper.test.cjs": 3386, - "pause-work-improvements.test.cjs": 337, - "perf-315-loadconfig-subrepo-scan.test.cjs": 46, - "perf-316-state-lock-buffer-alloc.test.cjs": 516, - "perf-317-context-monitor-fs.test.cjs": 5249, - "perf-407-planning-lock-buffer-alloc.test.cjs": 80, - "phase-command-router.test.cjs": 2814, - "phase-dependency-levels.test.cjs": 86, - "phase-id-drift-guard.test.cjs": 505, - "phase-id.test.cjs": 500, - "phase-lifecycle.test.cjs": 105, - "phase-locator.test.cjs": 239, - "phase.test.cjs": 136772, - "phase6-capability-docs.test.cjs": 71, - "phase6-capstone-conformance.test.cjs": 3569, + "parallel-dependent-plans.test.cjs": 65, + "path-replacement.test.cjs": 499, + "pattern-mapper.test.cjs": 2907, + "pause-work-improvements.test.cjs": 299, + "perf-315-loadconfig-subrepo-scan.test.cjs": 65, + "perf-316-state-lock-buffer-alloc.test.cjs": 654, + "perf-317-context-monitor-fs.test.cjs": 6679, + "perf-407-planning-lock-buffer-alloc.test.cjs": 54, + "phase-command-router.test.cjs": 2634, + "phase-dependency-levels.test.cjs": 38, + "phase-estimation.test.cjs": 19034, + "phase-id-drift-guard.test.cjs": 589, + "phase-id.test.cjs": 1184, + "phase-lifecycle.test.cjs": 49, + "phase-locator.test.cjs": 410, + "phase.test.cjs": 158303, + "phase6-capability-docs.test.cjs": 70, + "phase6-capstone-conformance.test.cjs": 3974, "phase6-planning-capabilities.test.cjs": 60, - "phase6-review-capabilities.test.cjs": 135, - "phases-command-router.test.cjs": 1234, - "pi-extension-reachability.test.cjs": 1653, - "pi-imperative-reference.test.cjs": 48, - "pi-upgrades.test.cjs": 114, - "pick-flag.test.cjs": 4302, - "plan-bounce.test.cjs": 1329, - "plan-phase-drift-guard.test.cjs": 631, - "plan-phase-mvp-flag.test.cjs": 1426, - "plan-phase-ui-redirect.test.cjs": 232, - "plan-pre-hook-e2e.test.cjs": 7778, - "plan-review-convergence.test.cjs": 1818, - "planner-decomposition.test.cjs": 189, - "planner-language-regression.test.cjs": 1075, - "planner-mvp-mode.test.cjs": 194, - "planning-workspace.test.cjs": 6988, - "platform-guard.unit.test.cjs": 693, - "playwright-ui-verify.test.cjs": 68, - "policy-138-nyquist-config-default.test.cjs": 33, - "policy-160-route0-resume.test.cjs": 102, - "policy-lint-shallow-checkout.test.cjs": 67, - "policy-release-no-npm-self-upgrade.test.cjs": 62, - "policy-shell-pinning.test.cjs": 396, - "portability-rule-disable-ban.test.cjs": 24574, - "portability-vocab-drift.test.cjs": 1546, - "post-planning-gaps-2493.test.cjs": 21648, - "pr-template-policy.test.cjs": 175, - "precommit-alias-drift-hook.test.cjs": 352, - "precondition-element.test.cjs": 1141, - "prepush-enterprise-email-hook.test.cjs": 178, - "probe-core.property.test.cjs": 325, - "probe-core.test.cjs": 252, - "product-name-purity.test.cjs": 69, - "profile-output.test.cjs": 10693, - "profile-pipeline.test.cjs": 4712, - "progress-forensic.test.cjs": 432, - "progress-mvp-display.test.cjs": 50, - "prohibition-enforcement.property.test.cjs": 291, - "prohibition-enforcement.test.cjs": 54884, - "prohibition-probe.docs-fixtures.test.cjs": 82, - "prohibition-probe.planner-contract.test.cjs": 25, - "prohibition-probe.schema.test.cjs": 140, - "prohibition-probe.spec-phase-contract.test.cjs": 56, - "prohibition-probe.validators.test.cjs": 37, - "prohibition-probe.verify-tier.test.cjs": 620, - "project-instruction-file-parity.test.cjs": 5145, - "project-root.test.cjs": 114, - "prompt-budget-cli.test.cjs": 2758, - "prompt-budget.property.test.cjs": 1749, - "prompt-budget.test.cjs": 68, - "prompt-budget.unit.test.cjs": 408, - "prompt-injection-scan.security.test.cjs": 1318, - "prompt-thinning.test.cjs": 356, - "prune-orphaned-worktrees.test.cjs": 1375, - "quick-branching.test.cjs": 1315, - "quick-commit-boundary.test.cjs": 67, - "quick-research.test.cjs": 3543, - "quick-session-management.test.cjs": 49, - "qwen-imperative-reference.test.cjs": 101, - "qwen-skills-migration.test.cjs": 534, - "qwen-upgrades.test.cjs": 4761, - "reachability-check.test.cjs": 55, - "read-guard.test.cjs": 2465, - "read-injection-scanner.property.test.cjs": 20842, - "read-injection-scanner.security.test.cjs": 4662, - "reapply-patches.test.cjs": 178, - "reapply-verify-hunks.test.cjs": 4848, - "redaction.test.cjs": 61, - "refactor-1390-t3-characterization.test.cjs": 138, - "registry-schema.test.cjs": 378, - "release-coverage-scope.test.cjs": 31, - "release-finalize-syncs-next-version.test.cjs": 55, - "release-tarball-smoke-bin-invocation.test.cjs": 35, - "release-tarball-smoke-workflow.test.cjs": 59, - "release-tarball-smoke.install.test.cjs": 144419, - "repo-invariants.test.cjs": 21850, - "repo-layout.test.cjs": 101, - "representative-corpus.test.cjs": 4322, - "require-fs-op-fallback.rule.test.cjs": 430, - "require-userprofile-with-home.rule.test.cjs": 205, - "research-agent-profiles.test.cjs": 316, - "research-cli.test.cjs": 11909, - "research-provider.property.test.cjs": 262, - "research-provider.test.cjs": 144, - "research-store.property.test.cjs": 326, - "research-store.test.cjs": 405, - "resolution.test.cjs": 37, - "reversibility-tagging.test.cjs": 5595, - "review-default-reviewers-config.test.cjs": 6391, - "review-default-reviewers-resolution.test.cjs": 81, - "review-default-reviewers-workflow.test.cjs": 128, - "review-model-config.test.cjs": 9411, - "review-reviewer-instances-config.test.cjs": 1998, - "review-reviewer-instances.test.cjs": 84, - "review-reviewer-selection.test.cjs": 61, - "roadmap-command-router.test.cjs": 77, - "roadmap-mode-field.test.cjs": 2558, - "roadmap-parser.test.cjs": 33080, - "roadmap-phase-fallback.test.cjs": 3736, - "roadmap-upgrade.test.cjs": 957, - "roadmap.test.cjs": 55342, - "roadmapper-granularity.test.cjs": 5203, - "run-tests-harness.test.cjs": 161263, - "run-with-timeout.test.cjs": 18008, - "runtime-artifact-install-plan.test.cjs": 142, - "runtime-artifact-layout-descriptor-drive.test.cjs": 322, - "runtime-artifact-layout-install-profiles.test.cjs": 2604, - "runtime-artifact-layout-surface.test.cjs": 1754, - "runtime-artifact-layout.test.cjs": 3980, - "runtime-config-adapter-registry.test.cjs": 196, - "runtime-converters.test.cjs": 893, - "runtime-flags.test.cjs": 62, - "runtime-homes-descriptor-drive.test.cjs": 473, - "runtime-homes.property.test.cjs": 501, - "runtime-label-policy.test.cjs": 51, - "runtime-launcher-parity.test.cjs": 3251, - "runtime-name-policy.test.cjs": 255, - "scan-command.test.cjs": 91, - "schema-drift.test.cjs": 6347, - "sdk-removal-query-family-dispatch.test.cjs": 3029, - "sdk-smoke.test.cjs": 42, - "secret-scan-lint.security.test.cjs": 4752, - "secure-phase-single-writer.test.cjs": 52, - "secure-phase.test.cjs": 261, - "security-dead-exports.regression.test.cjs": 112, - "security-prompt-injection.security.test.cjs": 9359, - "security-scan.security.test.cjs": 11654, - "security.test.cjs": 1956, - "seed-scan-new-milestone.test.cjs": 54, - "semver-compare.test.cjs": 168, - "settings-integrations.test.cjs": 13052, - "settings-jsonc.test.cjs": 149, - "sh-hook-paths.test.cjs": 195, - "shell-command-projection-dispatch.test.cjs": 3711, - "shell-command-projection-path-sep.test.cjs": 173, - "skill-frontmatter-contract.test.cjs": 872, - "skill-manifest.test.cjs": 4312, - "slash-command-namespace.test.cjs": 4520, - "slurm-adapter.test.cjs": 112, - "smart-entry.unit.test.cjs": 3312, - "spawn-liveness-banner.test.cjs": 160, - "spec-section.test.cjs": 606, - "stale-bake-guard.test.cjs": 376, - "state-acquirestatelock-non-eexist.test.cjs": 126, - "state-command-cutover.test.cjs": 1467, - "state-io.test.cjs": 72, - "state-prune.test.cjs": 6401, - "state-rebuild-cli.test.cjs": 3801, - "state-rebuild.test.cjs": 85, - "state-transition.test.cjs": 455, - "state.test.cjs": 116217, - "stats-mvp-display.test.cjs": 1082, - "subagent-timeout.test.cjs": 6840, - "surface-empty-manifest-agents.test.cjs": 119, - "surface-md-paths.regression.test.cjs": 38, - "sync-next-version.test.cjs": 148, - "table-schema-drift-lint.test.cjs": 789, - "tdd-mode.test.cjs": 8685, - "teams-status.test.cjs": 3434, - "temp-subdir.test.cjs": 93, - "template.test.cjs": 5110, - "test-failure-reasons.test.cjs": 47, - "test-gate-watch-mode.test.cjs": 198, - "thinking-model-guidance.test.cjs": 296, - "thinking-partner.test.cjs": 716, - "thread-session-management.test.cjs": 114, - "trace-correlation.test.cjs": 74, - "tracer-bullet.test.cjs": 2778, - "trae-imperative-reference.test.cjs": 66, - "trae-upgrades.test.cjs": 4593, - "transition-verification-gate.test.cjs": 38, - "tsconfig-noemit.test.cjs": 6383, - "uat-predicate.test.cjs": 668, - "uat.test.cjs": 15879, - "ui-checker-adversarial-stance.test.cjs": 26, - "ui-consideration-probe-docs-fixtures.test.cjs": 88, - "ui-consideration-probe.test.cjs": 347, - "ui-review-next-guidance.test.cjs": 65, - "ui-safety-gate.test.cjs": 78, - "ultraplan-phase.test.cjs": 169, - "untrusted-input-isolation.test.cjs": 706, - "update-custom-backup.test.cjs": 8333, - "update-size-baseline.test.cjs": 115, - "validate-context.test.cjs": 4346, - "validate-registry.test.cjs": 1266, - "verification-overrides.test.cjs": 136, - "verification-status.test.cjs": 614, - "verifier-behavior-unverified.test.cjs": 116, - "verifier-deferred-items.test.cjs": 92, - "verifier-mvp-section.test.cjs": 72, - "verifier-spotcheck-test-discipline.test.cjs": 139, - "verify-health.test.cjs": 34751, - "verify-mvp-uat.test.cjs": 63, - "verify-npm-publish.test.cjs": 100, - "verify-test-quality.test.cjs": 532, - "verify-work-auto-transition.test.cjs": 78, - "verify.test.cjs": 26790, - "vscode-browser-no-node-api.test.cjs": 36, - "vscode-extension-reachability.test.cjs": 1838, - "vscode-ide-reference.test.cjs": 155, - "vscode-lm-tools.test.cjs": 1616, - "vscode-subagent-dispatch.test.cjs": 1866, - "windows-robustness.test.cjs": 234, - "windsurf-conversion.test.cjs": 133, - "windsurf-hooks-bridge.test.cjs": 5096, - "windsurf-install.test.cjs": 47, - "workflow-compat.test.cjs": 386, - "workflow-guard-registration.test.cjs": 375, - "workflow-maintainer-skip.test.cjs": 142, - "workflow-shell-pinning.test.cjs": 101, - "workflow-size-budget.test.cjs": 473, - "workflow-size.test.cjs": 196, - "workflow-step-tag-balance.test.cjs": 336, - "workspace.test.cjs": 7399, - "workstream-inventory.test.cjs": 73, - "workstream-name-policy.test.cjs": 61, - "workstream.test.cjs": 40951, - "worktree-base-ref.test.cjs": 202, - "worktree-baseref-install.test.cjs": 11950, - "worktree-cleanup.test.cjs": 1901, - "worktree-safety.test.cjs": 14332, - "worktree.test.cjs": 1409, - "write-set.test.cjs": 172 + "phase6-review-capabilities.test.cjs": 113, + "phases-command-router.test.cjs": 2148, + "pi-extension-reachability.test.cjs": 1709, + "pi-imperative-reference.test.cjs": 79, + "pi-upgrades.test.cjs": 322, + "pick-flag.test.cjs": 2970, + "plan-bounce.test.cjs": 1299, + "plan-phase-drift-guard.test.cjs": 688, + "plan-phase-mvp-flag.test.cjs": 991, + "plan-phase-ui-redirect.test.cjs": 201, + "plan-pre-hook-e2e.test.cjs": 7507, + "plan-review-convergence.test.cjs": 6033, + "planner-decomposition.test.cjs": 109, + "planner-estimate-emission.test.cjs": 88, + "planner-language-regression.test.cjs": 1153, + "planner-mvp-mode.test.cjs": 70, + "planning-workspace.test.cjs": 7034, + "platform-guard.unit.test.cjs": 462, + "playwright-ui-verify.test.cjs": 37, + "policy-138-nyquist-config-default.test.cjs": 59, + "policy-160-route0-resume.test.cjs": 249, + "policy-lint-shallow-checkout.test.cjs": 66, + "policy-release-no-npm-self-upgrade.test.cjs": 47, + "policy-shell-pinning.test.cjs": 781, + "portability-rule-disable-ban.test.cjs": 29608, + "portability-vocab-drift.test.cjs": 2089, + "post-planning-gaps-2493.test.cjs": 25540, + "pr-template-policy.test.cjs": 112, + "precommit-alias-drift-hook.test.cjs": 958, + "precondition-element.test.cjs": 1454, + "prepush-enterprise-email-hook.test.cjs": 535, + "probe-core.property.test.cjs": 391, + "probe-core.test.cjs": 218, + "product-name-purity.test.cjs": 51, + "profile-output.test.cjs": 8064, + "profile-pipeline.test.cjs": 5317, + "progress-forensic.test.cjs": 142, + "progress-mvp-display.test.cjs": 98, + "prohibition-enforcement.property.test.cjs": 160, + "prohibition-enforcement.test.cjs": 56619, + "prohibition-probe.docs-fixtures.test.cjs": 76, + "prohibition-probe.planner-contract.test.cjs": 79, + "prohibition-probe.schema.test.cjs": 179, + "prohibition-probe.spec-phase-contract.test.cjs": 128, + "prohibition-probe.validators.test.cjs": 67, + "prohibition-probe.verify-tier.test.cjs": 51, + "project-instruction-file-parity.test.cjs": 5695, + "project-root.test.cjs": 112, + "prompt-budget-cli.test.cjs": 3641, + "prompt-budget.property.test.cjs": 2106, + "prompt-budget.test.cjs": 103, + "prompt-budget.unit.test.cjs": 774, + "prompt-injection-scan.security.test.cjs": 1901, + "prompt-thinning.test.cjs": 193, + "prune-orphaned-worktrees.test.cjs": 1832, + "quick-branching.test.cjs": 1782, + "quick-commit-boundary.test.cjs": 110, + "quick-research.test.cjs": 4017, + "quick-session-management.test.cjs": 84, + "qwen-imperative-reference.test.cjs": 111, + "qwen-skills-migration.test.cjs": 244, + "qwen-upgrades.test.cjs": 5675, + "reachability-check.test.cjs": 88, + "read-guard.test.cjs": 6080, + "read-injection-scanner.property.test.cjs": 18583, + "read-injection-scanner.security.test.cjs": 6784, + "reapply-patches.test.cjs": 259, + "reapply-verify-hunks.test.cjs": 4674, + "redaction.test.cjs": 78, + "refactor-1390-t3-characterization.test.cjs": 290, + "registry-axes-parity.test.cjs": 154, + "registry-reviewer-parity.test.cjs": 85, + "registry-schema.test.cjs": 826, + "release-backmerge-invariants.test.cjs": 130, + "release-coverage-scope.test.cjs": 44, + "release-finalize-syncs-next-version.test.cjs": 31, + "release-tarball-smoke-bin-invocation.test.cjs": 57, + "release-tarball-smoke-workflow.test.cjs": 64, + "release-tarball-smoke.install.test.cjs": 156753, + "repo-invariants.test.cjs": 23695, + "repo-layout.test.cjs": 116, + "representative-corpus.test.cjs": 3967, + "require-fs-op-fallback.rule.test.cjs": 396, + "require-userprofile-with-home.rule.test.cjs": 397, + "research-agent-profiles.test.cjs": 188, + "research-cli.test.cjs": 10934, + "research-provider.property.test.cjs": 316, + "research-provider.test.cjs": 185, + "research-store.property.test.cjs": 299, + "research-store.test.cjs": 347, + "resolution.test.cjs": 218, + "resolve-dispatch-type.test.cjs": 92, + "reversibility-tagging.test.cjs": 3685, + "review-default-reviewers-config.test.cjs": 6287, + "review-default-reviewers-resolution.test.cjs": 53, + "review-default-reviewers-workflow.test.cjs": 222, + "review-lane-descriptor.test.cjs": 524, + "review-lane-invocation.test.cjs": 605, + "review-lane-runner.test.cjs": 326, + "review-model-config.test.cjs": 8789, + "review-reviewer-instances-config.test.cjs": 1689, + "review-reviewer-instances.test.cjs": 134, + "review-reviewer-selection.test.cjs": 85, + "reviewer-config-federation.test.cjs": 5448, + "reviewer-docs-parity.test.cjs": 12981, + "reviewer-lane-declarations.test.cjs": 145, + "reviewer-manifest-body.test.cjs": 1243, + "reviewer-trust-disclosure.test.cjs": 808, + "roadmap-command-router.test.cjs": 87, + "roadmap-mode-field.test.cjs": 2499, + "roadmap-parser.test.cjs": 30303, + "roadmap-phase-fallback.test.cjs": 3910, + "roadmap-upgrade.test.cjs": 536, + "roadmap.test.cjs": 51467, + "roadmapper-granularity.test.cjs": 6335, + "run-tests-harness.test.cjs": 194169, + "run-with-timeout.test.cjs": 18838, + "runtime-artifact-install-plan.test.cjs": 98, + "runtime-artifact-layout-descriptor-drive.test.cjs": 293, + "runtime-artifact-layout-install-profiles.test.cjs": 2619, + "runtime-artifact-layout-surface.test.cjs": 2347, + "runtime-artifact-layout.test.cjs": 6450, + "runtime-config-adapter-registry.test.cjs": 235, + "runtime-converters.test.cjs": 771, + "runtime-flags.test.cjs": 63, + "runtime-homes-descriptor-drive.test.cjs": 489, + "runtime-homes.property.test.cjs": 378, + "runtime-label-policy.test.cjs": 183, + "runtime-launcher-parity.test.cjs": 2615, + "runtime-name-policy.test.cjs": 149, + "scan-command.test.cjs": 63, + "schema-drift.test.cjs": 5026, + "sdk-removal-query-family-dispatch.test.cjs": 2727, + "sdk-smoke.test.cjs": 104, + "secret-scan-lint.security.test.cjs": 4384, + "secure-phase-single-writer.test.cjs": 49, + "secure-phase.test.cjs": 287, + "security-dead-exports.regression.test.cjs": 61, + "security-prompt-injection.security.test.cjs": 9278, + "security-scan.security.test.cjs": 11821, + "security.test.cjs": 1590, + "seed-scan-new-milestone.test.cjs": 72, + "semver-compare.test.cjs": 144, + "settings-integrations.test.cjs": 15868, + "settings-jsonc.test.cjs": 133, + "sh-hook-paths.test.cjs": 133, + "shell-command-projection-dispatch.test.cjs": 4647, + "shell-command-projection-path-sep.test.cjs": 193, + "skill-frontmatter-contract.test.cjs": 1149, + "skill-manifest.test.cjs": 4759, + "slash-command-namespace.test.cjs": 5158, + "slurm-adapter.test.cjs": 59, + "smart-entry.unit.test.cjs": 2494, + "spawn-liveness-banner.test.cjs": 174, + "spec-phase-probe-reachability.test.cjs": 210, + "spec-section.test.cjs": 620, + "stale-bake-guard.test.cjs": 462, + "state-acquirestatelock-non-eexist.test.cjs": 199, + "state-command-cutover.test.cjs": 1147, + "state-document.test.cjs": 454, + "state-io.test.cjs": 67, + "state-prune.test.cjs": 7892, + "state-rebuild-cli.test.cjs": 3037, + "state-rebuild.test.cjs": 134, + "state-transition.test.cjs": 352, + "state.test.cjs": 131709, + "stats-mvp-display.test.cjs": 754, + "subagent-timeout.test.cjs": 7278, + "surface-empty-manifest-agents.test.cjs": 184, + "surface-md-paths.regression.test.cjs": 55, + "sync-next-version.test.cjs": 91, + "table-schema-drift-lint.test.cjs": 231, + "tdd-mode.test.cjs": 10230, + "teams-status.test.cjs": 3130, + "temp-subdir.test.cjs": 94, + "template.test.cjs": 5352, + "test-failure-reasons.test.cjs": 337, + "test-gate-watch-mode.test.cjs": 136, + "thinking-model-guidance.test.cjs": 212, + "thinking-partner.test.cjs": 799, + "thread-session-management.test.cjs": 65, + "todos-done-rename-guard.test.cjs": 235, + "trace-correlation.test.cjs": 66, + "tracer-bullet.test.cjs": 3119, + "trae-imperative-reference.test.cjs": 79, + "trae-upgrades.test.cjs": 5069, + "transition-verification-gate.test.cjs": 47, + "tsconfig-noemit.test.cjs": 7241, + "uat-predicate.test.cjs": 498, + "uat.test.cjs": 16989, + "ui-checker-adversarial-stance.test.cjs": 29, + "ui-consideration-probe-docs-fixtures.test.cjs": 81, + "ui-consideration-probe.test.cjs": 321, + "ui-review-next-guidance.test.cjs": 72, + "ui-safety-gate.test.cjs": 106, + "ultraplan-phase.test.cjs": 437, + "untrusted-input-isolation.test.cjs": 183, + "unusable-input.test.cjs": 438, + "update-custom-backup.test.cjs": 27765, + "validate-context.test.cjs": 5228, + "validate-registry.test.cjs": 2174, + "verification-overrides.test.cjs": 141, + "verification-status.test.cjs": 3372, + "verifier-behavior-unverified.test.cjs": 93, + "verifier-deferred-items.test.cjs": 91, + "verifier-mvp-section.test.cjs": 49, + "verifier-spotcheck-test-discipline.test.cjs": 107, + "verify-health.test.cjs": 35460, + "verify-mvp-uat.test.cjs": 76, + "verify-npm-publish.test.cjs": 54, + "verify-test-quality.test.cjs": 133, + "verify-work-auto-transition.test.cjs": 108, + "verify.test.cjs": 38878, + "vscode-browser-no-node-api.test.cjs": 54, + "vscode-extension-reachability.test.cjs": 1841, + "vscode-ide-reference.test.cjs": 153, + "vscode-lm-tools.test.cjs": 1894, + "vscode-subagent-dispatch.test.cjs": 2095, + "windows-robustness.test.cjs": 501, + "windsurf-conversion.test.cjs": 170, + "windsurf-hooks-bridge.test.cjs": 6161, + "windsurf-install.test.cjs": 66, + "workflow-compat.test.cjs": 140, + "workflow-guard-registration.test.cjs": 410, + "workflow-guard.test.cjs": 2286, + "workflow-maintainer-skip.test.cjs": 109, + "workflow-shell-pinning.test.cjs": 48, + "workflow-size-budget.test.cjs": 321, + "workflow-size.test.cjs": 216, + "workflow-step-tag-balance.test.cjs": 143, + "workspace.test.cjs": 7781, + "workstream-inventory.test.cjs": 77, + "workstream-name-policy.test.cjs": 36, + "workstream.test.cjs": 41535, + "worktree-base-ref.test.cjs": 272, + "worktree-baseref-install.test.cjs": 10423, + "worktree-cleanup.test.cjs": 3393, + "worktree-safety.test.cjs": 18865, + "worktree.test.cjs": 1034, + "write-set.test.cjs": 201 } }