From a7ed001b27a474ba0959170d4f0c8a36818819f6 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 29 May 2026 19:03:22 -0400 Subject: [PATCH] fix(#494): ci-test-scope selects checks a diff can break (tests->full matrix, docs->docs-parity) (#495) classify() under-approximated breakable checks, so scoped PRs skipped the check their diff would break and regressions reached next (#484 docs-parity, #482 windows-22 EBUSY). Fail-safe widen: any tests/** change forces full_matrix (OS-specific test failures); any docs/**, commands/**, agents/** change marks code_changed and selects docs-parity-live-registry (its runtime inputs). Updated the docs-only test that asserted the old buggy contract. Fixes #494 Co-authored-by: CI Rebase Check --- scripts/ci-test-scope.cjs | 13 ++++++++- tests/ci-test-scope.test.cjs | 54 +++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/scripts/ci-test-scope.cjs b/scripts/ci-test-scope.cjs index 3f6ce97c0..a9c571374 100644 --- a/scripts/ci-test-scope.cjs +++ b/scripts/ci-test-scope.cjs @@ -105,6 +105,7 @@ const RULES = [ 'tests/command-contract.test.cjs', 'tests/command-routing-hub.test.cjs', 'tests/commands.test.cjs', + 'tests/docs-parity-live-registry.test.cjs', 'tests/phase-command-router.test.cjs', 'tests/roadmap-command-router.test.cjs', ], @@ -129,6 +130,15 @@ const RULES = [ 'tests/agent-skills.test.cjs', 'tests/agent-skills-awareness.test.cjs', 'tests/agent-required-reading-consistency.test.cjs', + 'tests/docs-parity-live-registry.test.cjs', + ], + }, + { + name: 'docs content', + match: path => path.startsWith('docs/'), + fullMatrix: false, + tests: [ + 'tests/docs-parity-live-registry.test.cjs', ], }, { @@ -219,7 +229,7 @@ function classify(files) { let fullMatrix = false; for (const file of files) { - if (/^(bin|get-shit-done|agents|commands|hooks|tests|scripts)\//.test(file) || + if (/^(bin|get-shit-done|agents|commands|docs|hooks|tests|scripts)\//.test(file) || /^package(-lock)?\.json$/.test(file) || /^tsconfig.*\.json$/.test(file) || file.startsWith('.github/workflows/') || @@ -229,6 +239,7 @@ function classify(files) { if (file.startsWith('tests/') && file.endsWith('.test.cjs')) { targeted.add(file); + fullMatrix = true; if (/windows|path|shell|workflow|install|hook/i.test(file)) { windows.add(file); } diff --git a/tests/ci-test-scope.test.cjs b/tests/ci-test-scope.test.cjs index cec4be1ec..fca7e6530 100644 --- a/tests/ci-test-scope.test.cjs +++ b/tests/ci-test-scope.test.cjs @@ -18,12 +18,14 @@ function scopeFor(files) { } describe('ci-test-scope.cjs', () => { - test('docs-only changes do not request code matrix work', () => { + test('docs-only changes mark code_changed and select docs-parity (new correct contract)', () => { const result = scopeFor(['docs/usage.md']); - assert.strictEqual(result.code_changed, false); + assert.strictEqual(result.code_changed, true); assert.strictEqual(result.full_matrix, false); - assert.deepStrictEqual(result.targeted_tests, []); - assert.deepStrictEqual(result.windows_tests, []); + assert.ok( + result.targeted_tests.some(t => t.includes('docs-parity-live-registry')), + `expected docs-parity-live-registry in targeted_tests, got: ${JSON.stringify(result.targeted_tests)}`, + ); }); test('workflow changes request full matrix and workflow contract tests', () => { @@ -103,3 +105,47 @@ describe('ci-test-scope.cjs', () => { 'targeted_tests must be [\'unit\'] when code changed but no rule matched'); }); }); + +describe('ci-test-scope superset invariant (#494)', () => { + // Facet A: any tests/** change → full_matrix === true + test('A1: a specific changed test file forces full_matrix', () => { + const result = scopeFor(['tests/bug-1974-context-exhaustion-record.test.cjs']); + assert.strictEqual(result.full_matrix, true, + `expected full_matrix=true for tests/** change, got: ${JSON.stringify(result)}`); + }); + + test('A2: any tests/** path forces full_matrix', () => { + const result = scopeFor(['tests/some-new.test.cjs']); + assert.strictEqual(result.full_matrix, true, + `expected full_matrix=true for tests/** change, got: ${JSON.stringify(result)}`); + }); + + // Facet B: docs/**, commands/**, agents/** → code_changed AND docs-parity selected + test('B1: docs/adr change marks code_changed and selects docs-parity-live-registry', () => { + const result = scopeFor(['docs/adr/22-plan-drift-guard.md']); + assert.strictEqual(result.code_changed, true, + `expected code_changed=true for docs/** change, got: ${JSON.stringify(result)}`); + assert.ok( + result.targeted_tests.some(t => t.includes('docs-parity-live-registry')), + `expected docs-parity-live-registry in targeted_tests, got: ${JSON.stringify(result.targeted_tests)}`, + ); + }); + + test('B2: docs locale dir change marks code_changed and selects docs-parity-live-registry', () => { + const result = scopeFor(['docs/ja-JP/USAGE.md']); + assert.strictEqual(result.code_changed, true, + `expected code_changed=true for docs/ja-JP/** change, got: ${JSON.stringify(result)}`); + assert.ok( + result.targeted_tests.some(t => t.includes('docs-parity-live-registry')), + `expected docs-parity-live-registry in targeted_tests, got: ${JSON.stringify(result.targeted_tests)}`, + ); + }); + + test('B3: commands/** change selects docs-parity-live-registry', () => { + const result = scopeFor(['commands/gsd/plan-phase.md']); + assert.ok( + result.targeted_tests.some(t => t.includes('docs-parity-live-registry')), + `expected docs-parity-live-registry in targeted_tests for commands/** change, got: ${JSON.stringify(result.targeted_tests)}`, + ); + }); +});