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.
Phase 10 of epic #3180. src/planning-snapshot.cts is a new parsed
projection of .planning/, composed exclusively from the already-
consolidated §7 owners (getMilestoneInfo, listMilestonePhaseDirs,
isPhaseComplete, scanPhasePlans, stateFieldValue, planningPaths) plus
the frozen SCOPE enum. No new semantic derivation is introduced beyond
worstScope, a pure combinator folding several independently-scoped
owner answers into one composite signal.
Adds STATE_UNREADABLE to src/unusable-input.cts's UNUSABLE_REASON
(seventh #1879 site) for STATE.md exists-but-unreadable, distinct
from absent.
Adds scripts/lint-planning-snapshot-bypass-drift.cjs, a ratcheted
drift guard (ADR-3180 Decision 4(e)) scoped to DIAGNOSTIC_RULE_FUNCTIONS
(currently cmdValidateHealth in src/verify.cts only) preventing new
raw .planning/ reads from bypassing the snapshot, while acknowledging
cmdValidateHealth's existing 15 raw-read sites as debt owned by
Phase 11 (#3309).
Six-gate .cts ripple: .gitignore, eslint.config.mjs,
docs/INVENTORY.md + manifest regen, CONTEXT.md glossary entry.
Breaking changes: none. This phase adds the subject only; Phase 11
migrates cmdValidateHealth onto it.