Files
msd-core/scripts/docs-guard-registry.cjs
0xdhx 472f585f7c fix(#3726)!: require --confirm before milestone complete mutates (#3774)
* fix(#3726): require --confirm before milestone complete mutates

`milestone complete <version>` is a one-way door — ROADMAP.md and
REQUIREMENTS.md archived, every phase directory in the milestone MOVED,
STATE.md rewritten — and ran unconditionally on first invocation through
every invocation path, including `query milestone.complete <version>`,
whose `query` meta-prefix reads as a read-only namespace but performs no
filtering (#167's invocation-compatibility shim + #3243's dotted-form
normalization).

The gate lives on the destructive command itself, not on the `query`
prefix (the prefix is an intentional invocation mechanism, not a
permission boundary — restricting it would break dozens of shipped
workflow callers). Without --confirm and without --dry-run the command
now refuses via error() before reading anything beyond its arg checks,
so an unconfirmed invocation is a guaranteed no-op on disk. --dry-run
still previews with no confirmation needed and is now documented in the
usage block (it was only documented for the sibling archive-quick).
--force keeps its narrow meaning — bypassing the TRUNCATED-scope and
unstarted-phase guards — and does not double as the mutation opt-in.
--confirm follows the existing `phases clear --confirm` idiom in the
same module.

complete-milestone.md's two invocations pass --confirm (the workflow has
gathered explicit user intent by that step). Existing tests get
--confirm appended — pre-change behavior is exactly confirmed behavior —
and a #3726 regression block covers: refusal + full-tree byte-identity
on both invocation forms, --force not satisfying the gate, --dry-run
still passing without confirmation, and --confirm proceeding. The
refusal tests fail against pre-fix code (negative control run).

Fixes #3726

* docs(#3726): document the --confirm requirement in CLI-TOOLS and COMMANDS

Cross-AI review of the fix diff (codex, pre-create) caught three shipped
doc sites still instructing the now-refused bare invocation: the
CLI-TOOLS.md milestone-complete synopsis + flag table, and COMMANDS.md's
two guard-override instructions (`--force` alone now refuses without
--confirm). Localized CLI-TOOLS copies already lag the English synopsis
(no --force/--dry-run either) and follow the translation pipeline, not
this fix.

* chore(#3726): set changeset fragment pr to 3774

* test(#3726): confirm-gate CI repairs — QA scenario caller + growth ack

Two CI reds from the --confirm gate, both this branch's own misses:

- tests/qa/scenarios/milestone-rollover.json invoked `milestone complete
  1.0 --force` as a JSON arg-array fixture — a caller shape the test
  sweep (which grepped runGsdTools/runSdkQuery in tests/*.cjs) never
  enumerated. Adds --confirm; the scenario's boundary-crossing contract
  is otherwise untouched.
- complete-milestone.md's +420-byte --confirm note trips the
  emitted-attribution growth ratchet. Acknowledged as a #3726 append to
  the existing complete-milestone.md entry in
  3409-unreachable-guard-arms.json (two ack sources may never name the
  same path, per that fragment's own precedent).

Local: lint-emitted-drift-ack ok; loop-walk.qa 115/115 green sandboxed.

* docs(#3726): CLI-TOOLS.md guard-override sentences say --force --confirm

Review Major 1: the truncated-window and unstarted-phase guard paragraphs
still told the reader to "Pass `--force` to override", which now refuses
(--force alone does not satisfy the confirmation gate), while the flag
table 470 lines later said the opposite. Mirror the docs/COMMANDS.md pair
so the file no longer contradicts itself.

* docs(#3726): synopsis renders --confirm and --dry-run as alternatives

Review Nit 1: `milestone complete <version> --confirm [--dry-run]` read as
"a dry run still needs --confirm", the opposite of AC 3. Render the pair
as `(--confirm | --dry-run)` in the CLI-TOOLS.md synopsis and the usage
docblock, and let the flag rows carry the rule.

* test(#3726): pass --confirm in base-added milestone fixtures; re-file the growth ack

Rebase onto next (26 commits) surfaced three tests the gate now refuses:
the #3685 write-flag contract pair in tests/milestone.test.cjs and the
`milestone complete` boundary fixture in tests/state-contract.test.cjs
all invoke the command bare. Each now passes --confirm (a mutating run is
exactly what they assert on).

The +420 byte complete-milestone.md growth ack rode on
3409-unreachable-guard-arms.json, which #3078 swept from next as fully
spent — hence the modify/delete conflict. Re-filed under a fresh fragment
named for this issue, never resurrecting the swept one.

* test(#3726): pin the present-but-falsy arm of the confirmation gate

Review Minor 1: the boundary triple covered absent and present but not
present-but-falsy. The gate is an exact-token match, so --confirm=false
and --confirm=0 refuse today — pinned (canonical + query forms, whole
.planning/ tree byte-identical) so a future `=`-aware or prefix-matching
parser cannot silently turn --confirm=false into a confirmed run of an
irreversible command.

* test(#3726): drop --confirm from dry-run-only invocations

Review Nit 2: --confirm was mass-appended to 14 pre-existing --dry-run
invocations that never needed it, so each stopped standing as incidental
proof that a preview needs no confirmation. Reverted to the pre-PR form;
the dedicated AC-3 test carries the explicit assertion.

* docs(#3726): sync the localized CLI-TOOLS synopsis with the confirm gate

REQ-I18N-02 (docs/features/internationalized-documentation.md) requires
translations to stay synchronized with the English source. The four
localized CLI-TOOLS.md guides still advertised a bare
`milestone complete <version>`, which now exits 1. Render the English
synopsis verbatim — `(--confirm | --dry-run)` plus the `[--force]` and
`[--archive-quick]` flags the translations had also fallen behind on.

* test(#3726): drop --confirm from the remaining preview-only invocations

Round 2 reverted the --confirm appends on --dry-run-only invocations in
tests/milestone.test.cjs, but four more sat in two files the sweep missed:
tests/milestone-archive.test.cjs (three) and
tests/milestone-window-single-owner.test.cjs (one).

Each is a preview run whose whole purpose is to document that a preview
mutates nothing, so `--dry-run ... --confirm` contradicted the semantics
the test exists to pin. Dropping the token restores each as incidental
proof that a preview needs no confirmation; the dedicated AC-3 test keeps
the explicit assertion.

No assertion added, relaxed, or removed — the change is four tokens.

* chore(#3726): migrate the emitted-drift ack from a fragment to a commit trailer

#3954 (ADR-3942) moved emitted-drift acknowledgments out of
tests/emitted-drift-acks/ and into git commit trailers, and the fragment
directory no longer exists on next. The reason this PR's fragment carried
moves verbatim into the Emitted-Drift-Ack-Growth trailer on this commit;
the fragment file is removed rather than resurrected.

Emitted-Drift-Ack-Growth: complete-milestone.md — #3726: +420 bytes (40186 -> 40606). The archive_milestone step's two `milestone complete` invocations now pass the required --confirm flag (the command refuses to mutate without it — the archive is irreversible), with a note explaining the flag and pointing at --dry-run for previews. Deliberate runtime-loaded workflow text for the new gate, not converter drift.

* fix(#3726): name --confirm in the version-required refusal

The documented arg-discovery path (gsd-tools.cjs top-level usage: invoke
the command without args and the error lists what is required) stopped at
`version required for milestone complete (e.g., v1.0)` — one required
argument short. Discovering --confirm took a second round trip through the
gate. The refusal now reads `… — and --confirm to mutate`, pinned by a test
that also asserts the version-less invocation leaves .planning/ untouched.

* test(#3726): pin the milestone complete docs against a silent regression

The changeset is `type: Fixed`, which the docs-required lint exempts, so
nothing in CI would notice a later edit that reinstated the bare-`--force`
override prose or dropped `--confirm` from the synopsis. Four tests in
tests/milestone.test.cjs now pin: the synopsis line in docs/CLI-TOOLS.md
and its four localized mirrors; the `--confirm` flag row; both
guard-override instructions in docs/CLI-TOOLS.md and docs/COMMANDS.md,
by guard name (a substring match on each instruction's `--force
--confirm` text); and — as an identity ratchet over the
milestone-complete sections — every `--force` sentence or clause that
lacks `--confirm`, so a new bare instruction in its own sentence or
clause fails whatever its wording. Named residual: a bare instruction
spliced into the same clause as a compliant one coalesces with it and
passes the ratchet; the by-name pins are what keep the four known
instructions from losing the pairing that way. The file is registered
in scripts/docs-guard-registry.cjs so the pin runs on the PR that
changes those docs, not only after merge.

---------

Co-authored-by: CI Rebase Check <ci@gsd-redux>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-08-29 17:00:45 -04:00

397 lines
18 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 zeroes fullMatrix/targeted_tests/windows_tests for
* docs-only diffs) made the RULE inert to classify()'s scope decision.
*
* 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/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/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.
'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'],
'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-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,
};