Files
msd-core/scripts/docs-guard-registry.cjs
Tom Boucher eea9247c93 enhance(#4095): checkpoint:decision auto-selection is opt-in via auto_select (#4912)
* enhance(#4095): checkpoint:decision auto-selection is opt-in via auto_select

Auto-mode used to auto-select a checkpoint:decision's first <option>
unconditionally, making a decision checkpoint's safety depend on option
presentation order rather than an authored choice. Add an optional
auto_select="<option-id>" attribute on the <task> tag: absent, auto-mode
now escalates to a human exactly like gate="blocking-human" does; present,
it names the option auto-mode selects; naming an id with no matching
<option id> is a hard structural-validation error at plan-parse time
rather than a silent fallback to the first option. gate="blocking-human"
continues to win over everything, unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4095): anchor auto_select/id attribute regexes past hyphenated decoys

An isolated adversarial review of the auto_select work found that both new
attribute regexes used \b as their left anchor, which is a word boundary,
not a "start of attribute name" boundary. A decoy attribute ending in the
same word (e.g. data-id="...") sitting before the real id="..." on the
same <option> tag matched first, silently corrupting the extracted option
id. Anchor on (?:^|\s) instead so only the real attribute name can match.
Adds a regression test reproducing the exact decoy-attribute shape, plus a
Unicode option-id test from the same review pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4095): register auto-select-attribute.test.cjs in the docs-guard lane

lint-docs-guard-registration failed: the new test reads docs/reference/
plan-md.md but was not registered, so a future edit to that doc could
silently desync from the test without the guard catching it on the PR
that changed the doc. Registered alongside its direct precedents
(precondition-element.test.cjs, reversibility-tagging.test.cjs), which
read the same file for the same reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4095): fit the decision bullet under execute-phase.md's frozen byte ceiling

The remote gsd-test run caught what local checks missed: execute-phase.md
carries a frozen ADR-857 Phase-6 byte ceiling (93600) with only 36 bytes
of headroom before this change, and the original checkpoint:decision
wording pushed it to 93772 (over the ceiling). Cascaded into failures in
phase6-capstone-conformance, execute-phase-completion-reconciliation,
claude-orchestration, and the compact-content drift-report test.

Also caught: tests/package-legitimacy-gate.test.cjs anchors a
"decision is conditional, not unconditional" safety check on the literal
phrase "first option" in the decision bullet — which #4095 deliberately
removes, since there is no more unconditional first-option pick. The test
was asserting an assumption this change intentionally makes obsolete;
re-anchored on tokens that still identify the bullet ('decision',
'auto-spawn') without weakening what the test actually verifies (the
bullet must still carry a blocking-human carve-out).

Also fixed a word-order mismatch between my own new test's regex and the
actual doc text it was asserting against (tests/auto-select-attribute.test.cjs).

Regenerated the compact-content benchmark baseline
(tests/fixtures/compact-content-benchmark-baseline.json) to match the new
byte counts.

Emitted-Drift-Ack-Growth: gsd-executor.md — +7 bytes (49138 -> 49145), from the auto_select carve-out added to the checkpoint:decision auto-mode bullet; already trimmed once to fit the 49152 hard cap.
Emitted-Drift-Ack-Growth: execute-phase.md — +12 bytes (93564 -> 93576), from the same carve-out in the orchestrator's decision bullet; kept 24 bytes under the frozen 93600 ADR-857 ceiling after two rounds of trimming for clarity vs. margin.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4095): backfill changeset PR number

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:47:01 -04:00

440 lines
21 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* docs-guard-registry.cjs — the sole source of truth for which doc-reading
* test files the docs-guard lane must run.
*
* ## Why this is its own module, not a `scripts/ci-test-scope.cjs` RULE
*
* A prior version of this PR added a `docs guards` RULE to `RULES` in
* scripts/ci-test-scope.cjs, on the theory that classify()'s `!codeChanged`
* normalization (which at the time zeroed fullMatrix/targeted_tests/
* windows_tests for docs-only diffs) made the RULE inert to classify()'s
* scope decision. (#4641 later removed `windows_tests` from classify()'s
* output entirely; the normalization today only zeroes fullMatrix and
* targeted_tests. This paragraph is historical narration of a rejected
* design, not a description of current behaviour.)
*
* That is true for docs-ONLY diffs and FALSE for MIXED docs+code diffs:
* `codeChanged` is true whenever ANY changed file is product/pipeline code,
* so the normalization never runs, and every one of this registry's test
* files joined `targeted_tests` for every mixed PR. Probed on that version:
* `node scripts/ci-test-scope.cjs --files "docs/a.md src/semver.cts"`
* returned 25 targeted_tests, vs. 3 on `origin/next` — an unstated blowup
* of the scoped/targeted lane on every mixed docs+code PR.
*
* Root cause: `RULES` answers "given these changed files, what should
* test.yml run" for the MAIN scoped-lane pipeline. The docs-guard registry
* is a LANE MANIFEST for a completely different consumer (the `docs-lint`
* job in .github/workflows/docs-required.yml, gated on a `docs_changed`
* step output). Putting a lane manifest inside a scope-classification rule
* set was the defect; extracting it here removes any path by which it can
* influence classify() at all.
*
* ## Who reads this
*
* - .github/workflows/docs-required.yml (`docs-lint` job) — derives the
* PER-PR-SELECTED subset of this registry from scripts/select-docs-guards.cjs,
* which is itself driven by this module's DOCS_GUARD_TESTS map. This job has
* no `paths:` filter, so it always reports a status and can supply the
* already-required `docs-lint` context; a dedicated `paths:`-filtered
* workflow cannot be made required without hanging non-docs PRs forever.
* - scripts/lint-docs-guard-registration.cjs — derives its registration
* lint's comparison set from this module (so a docs-reading test file
* that is neither registered here nor exempted fails the lint).
* - scripts/select-docs-guards.cjs — the pure selector that maps a PR's
* changed docs/ paths to the subset of this registry that actually needs
* to run (#3753 follow-up: a flat "run everything" list is disproportionate
* for a single-file docs typo fix).
*
* ## Registry shape (#3753 follow-up)
*
* `DOCS_GUARD_TESTS` is a MAP from test file path to the array of docs/
* paths it actually reads, so a changed-docs-file can be resolved to the
* narrow subset of guards that read it, instead of always running the
* entire registry. Each value is a non-empty array of PATTERNS:
*
* - a plain path (e.g. `'docs/AGENTS.md'`) matches that exact file only;
* - a trailing-slash path (e.g. `'docs/adr/'`) matches any changed path
* under that directory prefix — use this for a test that walks or
* `readdirSync`s a whole docs/ subdirectory;
* - the sentinel `'*'` means "run on ANY docs/ change" — reserved for a
* test that cannot be resolved to a narrower set of paths (the path is
* computed, looped over an unresolvable variable, or the test walks
* docs/ generally). Conservative fallback: when in doubt, use `'*'`,
* never a guessed-narrow path — a false negative here (a guard that
* silently stops running) is exactly the #3753 defect class this
* registry exists to prevent.
*
* `DOCS_GUARD_TEST_FILES` (derived: `Object.keys(DOCS_GUARD_TESTS)`) is kept
* as a flat array export so the registration lint and its parity test
* continue to consume a plain file list without needing to know about the
* map shape.
*
* ## Adding a docs guard
*
* Add the test file's path (relative to the repo root, `tests/<file>`) as a
* key in DOCS_GUARD_TESTS below, with the docs/ paths it reads as the value
* (or `['*']` if that cannot be resolved narrowly). Do not duplicate this
* list anywhere else — a second, independently maintained list is exactly
* the #3753 defect class (10 registered-but-not-run guards, silently
* drifted) this registry exists to prevent from recurring.
*
* Sorted alphabetically by basename for unambiguous diffs.
*/
/**
* Duplicate of scripts/run-tests.cjs:50's `SUITES` array. That array is not
* exported by run-tests.cjs (module.exports there is deliberately narrow;
* run-tests.cjs's own behavior is intentional and out of scope for this
* registry to alter), so it cannot be imported directly without changing a
* shared, behavior-locked file.
*
* Why this must exist at all: `run-tests.cjs`'s `selectExplicitFiles`
* (scripts/run-tests.cjs:651) treats any registry entry that EQUALS a
* SUITES member (e.g. `all`, `unit`) as a suite selector, not a filename —
* so a typo in DOCS_GUARD_TESTS matching a suite name would silently run
* the ENTIRE suite inside the required `docs-lint` job instead of erroring.
* `assertNoSuiteCollision` below rejects that at the registry boundary
* instead.
*
* Divergence risk: if run-tests.cjs's real SUITES list ever changes and this
* copy is not updated, this check could under- or over-reject. That risk is
* covered by a parity test (tests/ci-docs-guard-registry.test.cjs) that
* drives run-tests.cjs's own exported `selectExplicitFiles` behaviorally —
* for every token here it asserts run-tests.cjs treats it as a suite
* selector (never "file not found"), and for a control non-member it
* asserts the opposite — so a real divergence fails that test rather than
* silently drifting.
*/
const RUN_TESTS_SUITES = ['all', 'unit', 'integration', 'install', 'security', 'slow', 'qa'];
/**
* Normalize a registry entry EXACTLY the way scripts/run-tests.cjs's
* `splitFileList` (:617-625) normalizes a requested token before its SUITES
* check (:651): strip a leading `tests/` and normalize `\`->`/`. Without this,
* comparing RAW registry keys (which all carry the `tests/` prefix by
* convention) against RUN_TESTS_SUITES misses the realistic typo `'tests/all'`
* entirely — proven by probe: `assertNoSuiteCollision(['tests/all'])` did not
* throw, and `selectExplicitFiles(allFiles, 'tests/all')` selected all 824
* files (the whole suite) inside the required docs-lint job.
*
* @param {string} t
* @returns {string}
*/
function normalizeForSuiteCheck(t) {
return t.replace(/\\/g, '/').replace(/^tests\//, '');
}
/**
* Reject any docs-guard registry entry that collides with a run-tests.cjs
* suite token (see RUN_TESTS_SUITES doc comment above). Throws with every
* offending entry named, rather than failing on only the first. Compares
* entries AFTER normalizeForSuiteCheck, mirroring run-tests.cjs's own
* splitFileList normalization, so a `tests/`-prefixed or backslash-spelled
* entry that would collide post-normalization is caught here too.
*
* @param {string[]} tests
*/
function assertNoSuiteCollision(tests) {
const collisions = tests.filter((t) => RUN_TESTS_SUITES.includes(normalizeForSuiteCheck(t)));
if (collisions.length > 0) {
throw new Error(
'docs-guard-registry: DOCS_GUARD_TESTS entry collides with a run-tests.cjs SUITES token ' +
`(${collisions.join(', ')}) — scripts/run-tests.cjs:651's selectExplicitFiles treats a ` +
'registry entry that equals a suite name as a suite selector, not a filename, so this ' +
'would silently run the entire suite instead of the intended file(s). Fix the registry entry.',
);
}
}
/**
* Map from docs-guard test file to the docs/ path patterns it reads. See
* this module's header comment for pattern semantics (exact / trailing-slash
* dir-prefix / `'*'` sentinel). Every value here was derived by reading the
* test file's actual read call(s) — not guessed — per #3753's own lesson: an
* unresolvable read is recorded as `'*'`, never a narrowed guess.
*/
const DOCS_GUARD_TESTS = {
'tests/adr-15-progress-converge.test.cjs': [
'docs/COMMANDS.md',
'docs/how-to/run-phases-autonomously.md',
],
// Walks docs/adr/ as a directory (builds/reads docs/adr/README.md and
// fixture ADRs throughout) and separately reads docs/contributor-standards.md.
'tests/adr-index-gate.test.cjs': ['docs/adr/', 'docs/contributor-standards.md'],
// Walks docs/features/ as a directory (the fragment corpus) and asserts the
// committed docs/FEATURES.md equals the generated projection of it (#3840).
'tests/features-index-gate.test.cjs': ['docs/features/', 'docs/FEATURES.md'],
'tests/agent-classification-parity.test.cjs': ['docs/AGENTS.md', 'docs/INVENTORY.md'],
// #3683: pins the learnings feature section's agreement with the canonical
// artifact registry (reads docs/FEATURES.md around the extract-learnings
// entry).
'tests/learnings.test.cjs': ['docs/FEATURES.md'],
'tests/analyze-dependencies.test.cjs': ['docs/COMMANDS.md'],
'tests/auto-select-attribute.test.cjs': ['docs/reference/plan-md.md'],
'tests/autonomous-converge.test.cjs': [
'docs/COMMANDS.md',
'docs/how-to/run-phases-autonomously.md',
],
'tests/capability-matrix-sync.test.cjs': ['docs/reference/capability-matrix.md'],
'tests/capability-registry.test.cjs': [
'docs/tutorials/build-your-first-capability.md',
'docs/tutorials/install-your-first-capability.md',
'docs/reference/capability-manifest.md',
],
'tests/claude-md.test.cjs': ['docs/COMMANDS.md'],
'tests/claude-orchestration.test.cjs': ['docs/explanation/claude-orchestration-capability.md'],
'tests/command-contract.test.cjs': [
'docs/INVENTORY.md',
'docs/ja-JP/INVENTORY.md',
'docs/ko-KR/INVENTORY.md',
'docs/zh-CN/INVENTORY.md',
'docs/pt-BR/INVENTORY.md',
],
// uncoveredFiles(...) scans 'docs' as a generic coverage-scan root
// (commit-files-pathspec.test.cjs:1618) — cannot be resolved to specific
// files without re-deriving the scan's own file-discovery logic.
'tests/commit-files-pathspec.test.cjs': ['*'],
'tests/compact-content-4139.test.cjs': ['docs/CONFIGURATION.md'],
'tests/config-field-docs.test.cjs': ['docs/CONFIGURATION.md'],
'tests/config.test.cjs': ['docs/CONFIGURATION.md'],
'tests/context-index-sync.test.cjs': ['docs/CONTEXT-INDEX.json'],
'tests/context-predicates-query.test.cjs': ['docs/contributor-standards.md'],
// SCAN_DIRS includes 'docs' and recursively walks every .md file under it
// (context7-tool-name-parity.test.cjs:31-38) — a generic tree walk, not a
// fixed file set.
'tests/context7-tool-name-parity.test.cjs': ['*'],
'tests/contributor-standards.test.cjs': ['docs/contributor-standards.md'],
'tests/cursor-reviewer.test.cjs': [
'docs/COMMANDS.md',
'docs/FEATURES.md',
'docs/ja-JP/COMMANDS.md',
'docs/ja-JP/FEATURES.md',
'docs/ko-KR/COMMANDS.md',
'docs/ko-KR/FEATURES.md',
],
'tests/discuss-all-flag.test.cjs': ['docs/COMMANDS.md'],
'tests/discuss-mode.test.cjs': ['docs/workflow-discuss-mode.md'],
// Walks docs/*.md and every docs/<locale>/*.md dir dynamically
// (docs-parity-live-registry.test.cjs:42, 428) — deliberately generic.
'tests/docs-parity-live-registry.test.cjs': ['*'],
// #3839: pins every hook-table Event cell in the five locales' ARCHITECTURE
// and INVENTORY files against src/runtime-hooks-surface.cts registrations.
'tests/docs-hooks-table-parity.test.cjs': [
'docs/ARCHITECTURE.md',
'docs/INVENTORY.md',
'docs/ja-JP/ARCHITECTURE.md',
'docs/ja-JP/INVENTORY.md',
'docs/zh-CN/ARCHITECTURE.md',
'docs/zh-CN/INVENTORY.md',
'docs/ko-KR/ARCHITECTURE.md',
'docs/ko-KR/INVENTORY.md',
'docs/pt-BR/ARCHITECTURE.md',
'docs/pt-BR/INVENTORY.md',
],
'tests/docs-state-md-locale-parity.test.cjs': [
'docs/reference/state-md.md',
'docs/ja-JP/reference/state-md.md',
'docs/zh-CN/reference/state-md.md',
'docs/ko-KR/reference/state-md.md',
'docs/pt-BR/reference/state-md.md',
],
'tests/drift-detection.test.cjs': ['docs/CONFIGURATION.md', 'docs/AGENTS.md'],
'tests/edge-probe-docs-fixtures.test.cjs': ['docs/adr/550-spec-phase-probe-contract.md'],
'tests/edit-phase.test.cjs': [
'docs/INVENTORY.md',
'docs/INVENTORY-MANIFEST.json',
'docs/COMMANDS.md',
],
'tests/effort-surface-axis.test.cjs': ['docs/reference/host-integration-capability-matrix.md'],
'tests/execute-phase-active-flags.test.cjs': [
'docs/reference/host-integration-capability-matrix.md',
],
'tests/execute-phase-wave.test.cjs': ['docs/COMMANDS.md'],
// #3913 (ADR-3889 terminal phase): reads the generated docs/reference/exit-codes.md
// (content invariants, F1/F3) and docs/README.md (F4, the index link).
'tests/exit-code-registry.test.cjs': ['docs/reference/exit-codes.md', 'docs/README.md'],
'tests/external-job-waiting.test.cjs': ['docs/reference/planning-artifacts.md'],
// Rows 8/9 (negative controls) read real docs/registries/eos.json and
// docs/adr/0001-dispatch-policy-module.md and assert on their EXACT
// committed content (an entry's name field, ADR-0001's H1 title) as a
// sanity check before mutating an overlay fixture — a content edit to
// either file changes the string this test asserts on.
'tests/fragment-single-edit-propagation.install.test.cjs': [
'docs/registries/eos.json',
'docs/adr/0001-dispatch-policy-module.md',
],
// Seeds a temp fixture copy of these five files (never mutates the real
// tree) to exercise scripts/gen-state-md-docs.cjs's marked-region splicing
// against them — #3873 (ADR-3473 §8.8), rows 10-22/27. Read for fixture
// seeding, so a content edit to any of them (e.g. renaming a landmark
// heading/string a hostile-input test targets) can change this test's
// fixture assumptions.
// #4728: pins the retirement of Gemini CLI prose from every localized
// how-to/ARCHITECTURE/USER-GUIDE/CONFIGURATION/context-monitor mirror, plus
// the PRESERVE (Antigravity) and MODEL-AXIS (Gemini 2.5 Pro) negative-space
// checks in the same locales.
'tests/gemini-runtime-removed.test.cjs': [
'docs/ja-JP/how-to/install-on-your-runtime.md',
'docs/ko-KR/how-to/install-on-your-runtime.md',
'docs/pt-BR/how-to/install-on-your-runtime.md',
'docs/zh-CN/how-to/install-on-your-runtime.md',
'docs/ja-JP/ARCHITECTURE.md',
'docs/ko-KR/ARCHITECTURE.md',
'docs/pt-BR/ARCHITECTURE.md',
'docs/zh-CN/ARCHITECTURE.md',
'docs/ja-JP/USER-GUIDE.md',
'docs/ko-KR/USER-GUIDE.md',
'docs/pt-BR/USER-GUIDE.md',
'docs/zh-CN/USER-GUIDE.md',
'docs/ja-JP/CONFIGURATION.md',
'docs/ko-KR/CONFIGURATION.md',
'docs/pt-BR/CONFIGURATION.md',
'docs/zh-CN/CONFIGURATION.md',
'docs/ja-JP/context-monitor.md',
'docs/ko-KR/context-monitor.md',
'docs/pt-BR/context-monitor.md',
'docs/zh-CN/context-monitor.md',
],
'tests/gen-state-md-docs.test.cjs': [
'docs/reference/state-md.md',
'docs/ja-JP/reference/state-md.md',
'docs/zh-CN/reference/state-md.md',
'docs/ko-KR/reference/state-md.md',
'docs/pt-BR/reference/state-md.md',
],
'tests/gsd-write-guard.test.cjs': ['docs/USER-GUIDE.md'],
'tests/host-integration-descriptors.test.cjs': [
'docs/reference/host-integration-capability-matrix.md',
],
'tests/install.test.cjs': ['docs/AGENTS.md', 'docs/INVENTORY.md', 'docs/INVENTORY-MANIFEST.json'],
// SOURCE_DIRS includes 'docs' and walks it recursively for every .md file
// (intel.test.cjs:1223-1228) — a generic tree walk.
'tests/intel.test.cjs': ['*'],
'tests/inventory-headings-countfree.test.cjs': ['docs/INVENTORY.md'],
'tests/inventory-manifest-sync.test.cjs': ['docs/INVENTORY.md', 'docs/INVENTORY-MANIFEST.json'],
'tests/kilo-upgrades.test.cjs': ['docs/how-to/connect-gsd-mcp-server.md'],
'tests/live-config-guard.test.cjs': ['docs/TESTING-SUITES.md'],
// #3726: pins the `milestone complete` synopsis (English + four localized
// mirrors), the `--confirm` flag row and the guard-override instructions —
// `type: Fixed` exempts that PR from the docs-required lint, so this is the
// only gate on that prose.
'tests/milestone.test.cjs': [
'docs/CLI-TOOLS.md',
'docs/COMMANDS.md',
'docs/ja-JP/CLI-TOOLS.md',
'docs/ko-KR/CLI-TOOLS.md',
'docs/pt-BR/CLI-TOOLS.md',
'docs/zh-CN/CLI-TOOLS.md',
],
'tests/model-catalog-runtime-defaults.test.cjs': ['docs/CONFIGURATION.md'],
// Scans every git-tracked file in the whole repo via `git ls-files`
// (no-pending-3212-markers.test.cjs:37-46), which includes all of docs/ —
// cannot be resolved to a fixed docs/ path set.
'tests/no-pending-3212-markers.test.cjs': ['*'],
'tests/phase6-capability-docs.test.cjs': ['docs/how-to/develop-a-capability.md', 'docs/README.md'],
'tests/phase6-review-capabilities.test.cjs': [
'docs/reference/review-verification-capabilities.md',
'docs/how-to/develop-a-capability.md',
'docs/README.md',
],
'tests/plan-checker-coupling.test.cjs': ['docs/AGENTS.md'],
'tests/plan-phase-drift-guard.test.cjs': [
'docs/CONFIGURATION.md',
'docs/COMMANDS.md',
'docs/USER-GUIDE.md',
'docs/ARCHITECTURE.md',
'docs/INVENTORY.md',
'docs/INVENTORY-MANIFEST.json',
],
'tests/plan-phase-stall-detection.test.cjs': ['docs/CONFIGURATION.md'],
'tests/plan-review-convergence.test.cjs': [
'docs/CONFIGURATION.md',
'docs/USER-GUIDE.md',
'docs/ARCHITECTURE.md',
],
'tests/planner-estimate-emission.test.cjs': ['docs/reference/plan-md.md', 'docs/CONFIGURATION.md'],
'tests/precondition-element.test.cjs': ['docs/reference/plan-md.md'],
'tests/product-name-purity.test.cjs': [
'docs/README.md',
'docs/zh-CN/README.md',
'docs/ko-KR/README.md',
'docs/ja-JP/README.md',
'docs/pt-BR/README.md',
],
'tests/progress-forensic.test.cjs': ['docs/COMMANDS.md'],
'tests/repo-layout.test.cjs': ['docs/contributing/bootstrap.md'],
// Reads REQ-LANG-04 and runs every form it offers an author through the
// matcher that enforces it, so a reword of the requirement alone is exactly
// the change this guard must run on (#2529). Both paths are named because
// #3840 made docs/FEATURES.md a generated projection: the requirement's
// source is the fragment, and an edit there that is not regenerated would
// otherwise reach this guard through neither path.
'tests/response-language-coverage.test.cjs': [
'docs/FEATURES.md',
'docs/features/response-language-config.md',
],
'tests/reversibility-tagging.test.cjs': ['docs/reference/plan-md.md'],
'tests/reviewer-docs-parity.test.cjs': [
'docs/COMMANDS.md',
'docs/ja-JP/COMMANDS.md',
'docs/ko-KR/COMMANDS.md',
'docs/pt-BR/COMMANDS.md',
'docs/zh-CN/COMMANDS.md',
'docs/FEATURES.md',
'docs/ja-JP/FEATURES.md',
'docs/ko-KR/FEATURES.md',
'docs/pt-BR/FEATURES.md',
'docs/zh-CN/FEATURES.md',
],
'tests/runtime-converters.test.cjs': ['docs/CONFIGURATION.md'],
'tests/secure-phase.test.cjs': ['docs/CONFIGURATION.md'],
'tests/security-dead-exports.regression.test.cjs': ['docs/FEATURES.md'],
'tests/security.test.cjs': ['docs/INVENTORY-MANIFEST.json'],
// Walks docs/ recursively (todos-done-rename-guard.test.cjs:19-22
// SCAN_DIRS) looking for stale references — a generic tree walk.
'tests/todos-done-rename-guard.test.cjs': ['*'],
'tests/tracer-bullet.test.cjs': [
'docs/COMMANDS.md',
'docs/reference/plan-md.md',
'docs/how-to/plan-a-phase.md',
'docs/AGENTS.md',
],
'tests/ui-interaction-capture.test.cjs': ['docs/CONFIGURATION.md'],
'tests/ui-spec-inventory-provenance.test.cjs': [
'docs/FEATURES.md',
'docs/how-to/design-a-ui-phase.md',
'docs/ja-JP/FEATURES.md',
'docs/ja-JP/how-to/design-a-ui-phase.md',
'docs/zh-CN/FEATURES.md',
'docs/zh-CN/how-to/design-a-ui-phase.md',
'docs/ko-KR/FEATURES.md',
'docs/ko-KR/how-to/design-a-ui-phase.md',
'docs/pt-BR/how-to/design-a-ui-phase.md',
],
'tests/verifier-behavior-unverified.test.cjs': ['docs/reference/planning-artifacts.md'],
'tests/verifier-coincidental-reliance.test.cjs': ['docs/AGENTS.md'],
'tests/verify.test.cjs': ['docs/reference/plan-md.md'],
'tests/workflow-fragments.test.cjs': ['docs/reference/workflow-fragments.md'],
};
/**
* Flat file-list view of DOCS_GUARD_TESTS, kept for consumers (the
* registration lint and its parity test) that only need "which test files
* are registered", not their per-file docs path patterns.
*/
const DOCS_GUARD_TEST_FILES = Object.keys(DOCS_GUARD_TESTS);
assertNoSuiteCollision(DOCS_GUARD_TEST_FILES);
module.exports = {
DOCS_GUARD_TESTS,
DOCS_GUARD_TEST_FILES,
RUN_TESTS_SUITES,
assertNoSuiteCollision,
normalizeForSuiteCheck,
};