* feat(#3586): warn when .planning/ is gitignored but still tracked
git ignore rules have no effect on files git already tracks, so a project
that committed .planning/ before ignoring it keeps staging those files --
while commit_docs correctly resolves to false, which is exactly what makes
the contradiction invisible.
The probe lives in the SNAPSHOT BUILDER, not the rule: Rule.check may perform
no ambient I/O (ADR-3180 8.1 rule 1, enforced by lint-planning-snapshot-bypass).
buildPlanningTrackedField follows buildWorktreeHealthField's precedent --
injected execGit, bounded, degrading to UNREADABLE with a typed reason rather
than throwing. W024 went inline instead only because no snapshot field carried
its fact; that precondition does not apply here.
W029 fires only on COMPLETE scope with ignored and tracked both true, so a
degraded probe yields neither a finding nor a false all-clear, and the default
project (tracked, not ignored) stays silent. The remedy is ADVISE-only --
--repair never untracks anything.
* docs(#3586): document W029 and correct the health rule count
CONFIGURATION.md documented the gitignore auto-detect without the caveat that
ignore rules do not affect already-tracked files -- the very gap W029 exists to
surface. Adds the caveat, the warning, its remedy, and why --repair will not
act on it.
CONTEXT.md's rule count was stale at 31 before this change (actual 32 through
W028); corrected to 33 and pointed at the two other places the count is locked,
so the next editor updates all three together.
* fix(#3586): treat ls-files overflow as tracked, add CLI-level W029 tests
Review findings.
Security (minor, confirmed): execGit sets no maxBuffer, so Node's 1MB default
applies to git ls-files. A .planning/ tree large enough to overflow it failed
into git_list_failed and silenced W029 -- a false negative in exactly the
large-history case most likely to have the real bug. Overflow is now treated
as PROOF of tracking (the output was non-empty by definition) and resolves to
tracked:true, scope COMPLETE, reason ok_truncated.
Spec (major): test-matrix rows C1 and C2 were never implemented -- there was no
CLI-level integration test at all, only rule-level ones. Both now drive the real
validate-health dispatch and confirm W029 is reachable end-to-end.
Known limit documented, not papered over: a deliberate git add -f under an
otherwise-ignored .planning/ raises the same signal as the accidental case.
There is no reliable way to tell them apart, the finding is advisory-only, and
a heuristic that cannot actually distinguish them would be worse than the
honest caveat.
* test(#3586): update frozen health-doc counts and acknowledge health.md growth
The remote matrix caught three gates that lint:ci does not cover.
gen-health-docs.test.cjs froze a 35-row / 32-rule assertion; W029 makes it
36/33. Updated both the assertion and the test NAME, which embeds the counts --
a stale name is a lie even when the assertion passes. The second reported
failure was the same assertion surfacing at describe-rollup granularity, not a
distinct bug.
emitted-attribution's growth arm needed an ack for the generated health.md.
health.md was already named in 3309-health-docs-generated.json, and two ack
sources naming one path is a hard error -- so a new fragment was not an option.
That fragment's own history shows the pattern: #3309 created it, #2873 amended
it in place for W028. Amended again for W029, with a note recording why this
one file is amended rather than joined by a sibling.
* docs(#3586): add the private-planning how-to and fix a wrong link
docs/CONFIGURATION.md pointed 'Configure private planning' at
how-to/configure-model-profiles.md -- an unrelated page -- and no
private-planning how-to existed at all. Found while editing that section.
The how-to test genuinely fires here: going private is four steps and crosses
planning.search_gitignored, a setting owned by another concern, so a reference
table structurally cannot carry it. The new page walks the whole sequence and
leads with the step people miss -- .gitignore does not untrack what git already
tracks -- which is the exact state W029 now detects.
Also corrects 'artefacts' to 'artifacts' (repo house style is American).
* chore(#3586): backfill changeset pr number to 3598
---------
Co-authored-by: sim <sim@local>
W007 (orphan disk dir with no ROADMAP entry) cannot be sourced from
phaseDirs, which is windowed to ROADMAP-declared phases only — an
orphan dir can never appear in an already-ROADMAP-filtered set. Adds
an unwindowed allPhaseDirNames field so the rule can actually fire.
Phase 11 of epic #3180 (ADR-3180 §8.1 rule 2). PlanningSnapshot grows from
7 fields to 15: projectSections, statePhaseTokens, stateStatus,
roadmapDeclaredPhases, roadmapPhaseCheckboxes, researchValidationStatus,
milestoneArchiveStatus, planningRootFiles.
Every field is a reused owner (buildRoadmapPhaseVariants/
buildNotStartedPhaseVariants from src/validate.cts, stateFieldValue) or a
small relocation of already-working verify.cts logic (PHASE_NUMBER_TOKEN_SOURCE
scanning, the checkMilestonePrefixMismatches sectionRx walk, W009/W018's
file-existence checks) — never a new algorithm, and never raw document text:
§8.1 rule 2 forbids exposing raw text, not exposing a parsed list or boolean
derived from it once by the snapshot builder.
roadmapPhaseCheckboxes deliberately reads the same ROADMAP checkbox
isPhaseComplete (§7.4, disk-strict) refuses to consult — that owner decides
completion and must not read it; this field only exposes what the checkbox
says, for a diagnostic (W011) whose whole purpose is flagging disagreement.
Not a re-derivation of §7.4, recorded explicitly to prevent that reading.
Adds PROJECT_UNREADABLE to UNUSABLE_REASON (ninth #1879 site), closing a
gap the implementing agent correctly flagged rather than silently leaving
absent-vs-corrupt collapsed for PROJECT.md, matching the STATE_UNREADABLE/
CONFIG_UNREADABLE precedent from this same effort's prior commits.
currentPhaseLabel/statePhaseTokens/stateStatus share one STATE.md read
(buildStateFields) rather than three independent reads.
Additive only — all prior fields and worstScope/buildPhaseSnapshot
unchanged.
Phase 11 of epic #3180 (ADR-3180 §8.2/§8.3/§8.5) foundation. Extends the
already-merged Phase-10 PlanningSnapshot additively with three fields the
upcoming health-diagnostic rule table needs and Phase 10 never required:
- config: {value, scope, exists} — parsed .planning/config.json. `exists`
distinguishes absent (no diagnostic, non-answer) from present-but-invalid
(CONFIG_UNREADABLE diagnostic, corruption) — both collapse to scope
UNREADABLE, so a rule needs the extra bit to tell "not configured yet"
apart from "config.json is broken."
- agentInstall / worktreeHealth — not .planning/-sourced, wrap the existing
checkAgentsInstalled/inspectWorktreeHealth owners with the same arguments
cmdValidateHealth already passes them, so a later migration step reads
these fields instead of calling the owners itself.
Adds CONFIG_UNREADABLE to src/unusable-input.cts's UNUSABLE_REASON (eighth
#1879 site), mirroring STATE_UNREADABLE's exact shape from Phase 10.
Additive only — the four Phase-10 fields and worstScope/buildPhaseSnapshot
are unchanged; existing tests for them are untouched.
ADR-3180 epic #3180 Phase 10 (§8.1): tests for the not-yet-existing
src/planning-snapshot.cts (buildPlanningSnapshot, worstScope), the
not-yet-existing scripts/lint-planning-snapshot-bypass-drift.cjs guard,
and the new STATE_UNREADABLE reason on tests/unusable-input.test.cjs's
already-shipped UNUSABLE_REASON enum. RED by construction: the modules
under test do not exist yet.