refactor(#3309): migrate cmdValidateHealth onto the rule table
Replaces cmdValidateHealth's hand-rolled addIssue/switch accumulation
(961 lines) with buildPlanningSnapshot -> evaluateRules -> map to the
legacy {code, message, fix, repairable} shape, bucketed by severity.
Two pre-checks (home-dir E010/I010, .planning/-root-missing E001) stay
outside the rule table entirely, per ADR-3180 §8.2 rule 4 ("no
precedence system") — building "some rules suppress others" into the
table would itself be the forbidden precedence system.
W024 (STATE.md commit-age freshness) also stays outside the table:
its committed rule is a documented permanent no-op (readStateHeadFreshness's
git-log shell-out is ambient I/O a Rule.check may never perform, and no
PlanningSnapshot field carries a commits-behind count). Migrating onto
the rule table as designed would have silently regressed 7 passing
tests in tests/health-validation.test.cjs — found while wiring this
function, kept as a real check in the wrapper instead (same I/O
license applyRepairs already relies on), fixed inline per this repo's
no-defer policy rather than accepted as a silent loss.
Ports the real repair-handler bodies (createConfig/resetConfig,
regenerateState, addNyquistKey/addAiIntegrationPhaseKey,
backfillMilestones) into health-diagnostic.cts's applyRepairs,
replacing the skeleton's stub. DESTRUCTIVE-risk remedies
(resetConfig/regenerateState) are refused by --repair — a disclosed
breaking change; repairable now means "an automatic repair will
actually run," not merely "a remedy exists to describe," so E004/E005
now report repairable:false. --backfill alone now actually triggers
backfillMilestones, fixing a latent bug where its gate was unreachable
without --repair also being set (verify.cts:2504, confirmed dead code
pre-migration).
Test updates distinguish the two explicitly-authorized behavior
changes (DESTRUCTIVE refusal, backfill-alone fix, W021->W026 split)
from preservation — every changed assertion is commented with why, and
new regression tests were added for both changes plus W021/W026
mutual independence. Drift-guard bookkeeping (bypass-baseline shrunk
to the one disclosed W024 exception, milestone-window and
phase-enumeration exemptions, test-file-count allowlist) updated for
the relocated/new functions this migration introduces.
This commit is contained in:
@@ -1,109 +1,11 @@
|
||||
{
|
||||
"$comment": "ADR-3180 §8.1 rule 2 ratchet, owned by Phase 11 (#3309). See scripts/lint-planning-snapshot-bypass-drift.cjs. SHRINK-ONLY: entries are removed as cmdValidateHealth migrates onto src/planning-snapshot.cts; new or changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an unacknowledged new copy.",
|
||||
"entries": [
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": ".readdirSync(phasesDir, { withFileTypes: true })",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "? fs.readFileSync(milestonesPath, 'utf-8')",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const archiveFiles = fs.readdirSync(milestonesArchiveDir);",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const configRaw = fs.readFileSync(configPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 4
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const content = fs.readFileSync(projectPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const entries = fs.readdirSync(rootBase, { withFileTypes: true });",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const rawCfg = fs.readFileSync(configPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const researchContent = fs.readFileSync(",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const roadmapContentFull = fs.readFileSync(roadmapPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const roadmapContentRaw = fs.readFileSync(roadmapPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const roadmapRaw = fs.readFileSync(roadmapPath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const stateContent = fs.readFileSync(statePath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "const stateRaw = fs.readFileSync(statePath, 'utf-8');",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "src/verify.cts",
|
||||
"text": "phaseDirFiles.set(e.name, fs.readdirSync(path.join(phasesDir, e.name)));",
|
||||
"derivation": "planning-snapshot-bypass",
|
||||
"owner_issue": "#3309",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
|
||||
@@ -188,23 +188,13 @@ const OWNER_FILE = path.join('src', 'roadmap-parser.cts');
|
||||
// question `computeMilestoneSectionEnd` answers — so it cannot diverge
|
||||
// from that computation; it answers a narrower, different question this
|
||||
// derivation does not own.
|
||||
// - verify.cts checkMilestonePrefixMismatches: `sectionRx` ENUMERATES
|
||||
// every milestone heading in the document to build a list of
|
||||
// `{version, start, end}` sections (each section's `end` is provisionally
|
||||
// "rest of document" until the NEXT heading is found, then backfilled) —
|
||||
// it is answering "what are ALL the milestone sections", to check every
|
||||
// phase against its OWN enclosing milestone, not "where does THIS ONE
|
||||
// milestone (the current/asserted one) end" — `computeMilestoneSectionEnd`
|
||||
// takes a single heading and returns a single boundary; this function
|
||||
// never calls anything with that shape. (Design brief named this
|
||||
// `cmdValidateConsistency` — the code actually lives in the sibling
|
||||
// function `checkMilestonePrefixMismatches`, called from
|
||||
// `cmdValidateHealth`; `cmdValidateConsistency` itself does not contain
|
||||
// `sectionRx`. Exempted here under its ACTUAL containing function.) Also:
|
||||
// `sectionRx` (`/^#{1,3}\s+(?:\[[^\]]{1,200}\]\s*)?.*v(\d+\.\d+)/gim`)
|
||||
// does not itself carry token (b) as this guard defines it (no
|
||||
// `(?!Phase` lookahead, no marker-emoji pairing) — this exemption
|
||||
// currently documents intent rather than suppressing a live match.
|
||||
// - (Phase 11, #3309: `verify.cts`'s pre-migration `checkMilestonePrefixMismatches`
|
||||
// — formerly exempted here — was DELETED when `cmdValidateHealth` migrated
|
||||
// onto the rule table; its `sectionRx` walk relocated verbatim into
|
||||
// `planning-snapshot.cts`'s `buildRoadmapDeclaredPhasesField`, which needs
|
||||
// no exemption of its own: like the deleted function, its `sectionRx`
|
||||
// never carries token (b) as this guard defines it — no `(?!Phase`
|
||||
// lookahead, no marker-emoji pairing — so it was never a live match.)
|
||||
// - roadmap-parser.cts isMilestoneShippedInRoadmap: composes the heading
|
||||
// quantifier with the shipped/active MARKER check (via
|
||||
// isClosedMilestoneHeading) to answer "is THIS milestone version marked
|
||||
@@ -235,9 +225,22 @@ const OWNER_FILE = path.join('src', 'roadmap-parser.cts');
|
||||
// source span. It is a named canonical function defining the grammar,
|
||||
// not a copy of it — replacing the third independent re-derivation the
|
||||
// widened guard found at `roadmap.cts:454`.
|
||||
// - planning-snapshot.cts buildMilestoneArchiveStatusField (Phase 11,
|
||||
// #3309): its `## <version>` heading scan reads `MILESTONES.md` — a
|
||||
// FLAT version registry, not `ROADMAP.md` — asking "which versions does
|
||||
// the registry already document", never "where does THIS milestone's
|
||||
// ROADMAP section begin/end" (`computeMilestoneSectionEnd`/
|
||||
// `locateMilestoneHeadings`'s own question). A different document, a
|
||||
// different question; not a re-derivation of ROADMAP windowing.
|
||||
// - health-diagnostic.cts computeMissingMilestoneVersions (Phase 11,
|
||||
// #3309): `applyRepairs` is not a `Rule` and is not handed a
|
||||
// `PlanningSnapshot` (see that file's header comment), so
|
||||
// `backfillMilestones` recomputes the IDENTICAL `MILESTONES.md`
|
||||
// heading-membership check `buildMilestoneArchiveStatusField` already
|
||||
// performs for the W018 rule's read side — same non-ROADMAP-windowing
|
||||
// question as that function, for the same reason.
|
||||
const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
||||
[path.join('src', 'roadmap-command-router.cts'), new Set(['checkW021'])],
|
||||
[path.join('src', 'verify.cts'), new Set(['checkMilestonePrefixMismatches'])],
|
||||
[
|
||||
OWNER_FILE,
|
||||
new Set([
|
||||
@@ -248,6 +251,8 @@ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
||||
'extractCurrentMilestoneScoped',
|
||||
]),
|
||||
],
|
||||
[path.join('src', 'planning-snapshot.cts'), new Set(['buildMilestoneArchiveStatusField'])],
|
||||
[path.join('src', 'health-diagnostic.cts'), new Set(['computeMissingMilestoneVersions'])],
|
||||
]);
|
||||
|
||||
// Optional `export ` modifier, mirroring `lint-plan-count-drift.cjs`'s
|
||||
|
||||
@@ -195,6 +195,20 @@
|
||||
* spanning every milestone ever shipped — the union is a strict
|
||||
* superset of any one milestone's window by design; scoping the live
|
||||
* half would silently drop history the digest exists to preserve.
|
||||
* - `src/planning-snapshot.cts` `buildAllPhaseDirNamesField` (Phase 11,
|
||||
* #3309): the un-windowed twin of `phaseDirs`/`listMilestonePhaseDirs` —
|
||||
* every directory actually present under the active `phases/` root,
|
||||
* UNFILTERED by current-milestone-window membership. Backs the migrated
|
||||
* `cmdValidateHealth`'s W007 rule ("an on-disk phase directory has no
|
||||
* matching ROADMAP entry"): sourcing that check from the WINDOWED owner
|
||||
* would make it structurally unable to fire on the exact orphan
|
||||
* directory it exists to find (an orphan-by-definition can never be a
|
||||
* member of a set defined as "directories the roadmap already
|
||||
* declares") — see that field's own doc comment on `PlanningSnapshot`
|
||||
* for the full, empirically-verified rationale. Same "must see the
|
||||
* physical set by definition" shape as `collectDiskPhases`/
|
||||
* `cmdValidateHealth` above, generalized from a raw `readdirSync` call
|
||||
* site to a dedicated snapshot-builder function.
|
||||
*
|
||||
* The tree-walk / root-confinement / regex-literal-tokenizer / sanitizer
|
||||
* machinery is SHARED with the sibling drift guards via
|
||||
@@ -270,6 +284,7 @@ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
||||
[path.join('src', 'roadmap-upgrade.cts'), new Set(['computeMigrationPlan'])],
|
||||
[path.join('src', 'smart-entry.cts'), new Set(['detectVerifyFailed'])],
|
||||
[path.join('src', 'roadmap-parser.cts'), new Set(['getMilestonePhaseFilter'])],
|
||||
[path.join('src', 'planning-snapshot.cts'), new Set(['buildAllPhaseDirNamesField'])],
|
||||
]);
|
||||
|
||||
// Optional `export ` modifier, mirroring the sibling guards' function
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"config-field-docs.test.cjs",
|
||||
"config-get-default.test.cjs",
|
||||
"config-schema.property.test.cjs",
|
||||
"config-validation.test.cjs",
|
||||
"config.test.cjs"
|
||||
],
|
||||
"issue": "TBD"
|
||||
@@ -33,6 +34,7 @@
|
||||
},
|
||||
"milestone": {
|
||||
"files": [
|
||||
"milestone-archive-hygiene.test.cjs",
|
||||
"milestone-archive.test.cjs",
|
||||
"milestone-helper.test.cjs",
|
||||
"milestone-prefixed-convention.test.cjs",
|
||||
@@ -48,12 +50,14 @@
|
||||
"phase-completion-single-owner.test.cjs",
|
||||
"phase-dependency-levels.test.cjs",
|
||||
"phase-resolution-parity.test.cjs",
|
||||
"phase-structure.test.cjs",
|
||||
"phase.test.cjs"
|
||||
],
|
||||
"issue": "3186"
|
||||
},
|
||||
"roadmap": {
|
||||
"files": [
|
||||
"roadmap-disk-consistency.test.cjs",
|
||||
"roadmap-mode-field.test.cjs",
|
||||
"roadmap-phase-fallback.test.cjs",
|
||||
"roadmap.test.cjs"
|
||||
@@ -83,6 +87,7 @@
|
||||
"files": [
|
||||
"state-acquirestatelock-non-eexist.test.cjs",
|
||||
"state-command-cutover.test.cjs",
|
||||
"state-consistency.test.cjs",
|
||||
"state-field-drift.test.cjs",
|
||||
"state-prune.test.cjs",
|
||||
"state-rebuild-cli.test.cjs",
|
||||
|
||||
Reference in New Issue
Block a user