Files
msd-core/docs/how-to/keep-planning-docs-private.md
Tom Boucher 5f64d999dc fix(#3586): warn when .planning/ is gitignored but still tracked (#3598)
* 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>
2026-08-17 15:56:46 -04:00

3.4 KiB

Keep planning docs out of a shared repo

You are working in a team repo and want GSD's .planning/ artifacts — PLAN.md, SUMMARY.md, ROADMAP.md, STATE.md — to stay on your machine instead of appearing in commits your teammates read.

This takes four steps, and the third is the one people miss.

1. Turn off doc commits

gsd-tools config-set planning.commit_docs false

gsd-tools query commit now returns a skipped envelope instead of committing, and every workflow that writes a planning artifact honors it.

2. Ignore the directory

Add to .gitignore:

.planning/

This is also enough on its own: when .planning/ is gitignored and config.json sets no explicit value, GSD auto-resolves commit_docs to false. Setting it explicitly in step 1 is clearer, and it survives someone later editing .gitignore.

3. Untrack what git is already tracking

This is the step that catches people out. .gitignore only stops git picking up new files. It has no effect on files already committed — git keeps tracking those, so git add -A keeps staging them even though steps 1 and 2 are both done.

Because GSD's default is commit_docs: true, most existing projects already have .planning/ in history, which makes this the common case rather than an edge case.

git rm -r --cached .planning/
git commit -m "chore: stop tracking planning docs"

--cached removes the files from the index only — your files on disk are untouched.

To check whether this applies to you before running it:

git ls-files .planning

Any output means git is still tracking those paths.

4. Keep search working

With .planning/ ignored, tools that respect .gitignore stop searching it — including GSD's own broad searches, which is rarely what you want, since the planning docs are exactly what you want an agent to read.

gsd-tools config-set planning.search_gitignored true

This adds --no-ignore to broad searches so .planning/ is still found locally.

Verify

gsd-tools validate health

A clean result means you are done. If you skipped step 3, you will see:

W029  .planning/ is gitignored but N file(s) are still tracked by git
      Fix: git rm -r --cached .planning/ && git commit -m "chore: stop tracking planning docs"

W029 is advisory. GSD will not untrack files for you, and --repair deliberately does not act on it — removing files from the index is destructive and the timing is yours.

Notes

  • A deliberate force-add also raises W029. If you intentionally keep one file tracked under an otherwise-ignored .planning/ (git add -f .planning/decisions.md), the warning still appears. There is no reliable way to tell an intentional force-add from the accidental case, so the warning is expected there too.
  • Teammates who have already pulled the tracked files will see them deleted by your step-3 commit. That is the intended effect — the files leave the repo, not their working copies of your branch — but say so in the commit message or PR description so it is not a surprise.
  • Per-phase control (committing docs for an architecture phase while keeping execution phases local) is tracked separately; commit_docs is currently project-wide.