* feat(#3587): add a per-phase commit_docs override Delivers epic #2292's second user story: commit an architecture phase's artifacts while execution phases stay local. commit_docs was project-wide and binary, so the only choices were all phases or none. Shape is a config dynamic key phase_commit_docs.<phase-id>, following the 14 existing dynamicKeyPatterns precedents rather than inventing a PLAN.md frontmatter spec -- which #2292 itself flags as becoming its own maintenance surface. Tier 1 resolves in cmdCommit, NOT in loadConfig: loadConfig has no phase context and is called by nearly every command, so threading one through it to serve a single caller would be a far larger blast radius for no gain. The phase comes from detectPhaseNumberFromFiles, which cmdCommit already computes for branch naming and which is already hardened against the #2539 project-code bug. Suppression by the per-phase tier returns its own reason rather than reusing skipped_commit_docs_false -- telling a user their project setting is false when it is true would be actively misleading. Additive; the two existing reason strings that agents/gsd-executor.md matches on are unchanged. The manifest's phase-id pattern is a hand-copy of PHASE_NUMBER_TOKEN_SOURCE because the manifest is hand-maintained JSON, so a behavioral parity test asserts both surfaces accept and reject the same token shapes. * fix(#3587): fold tests, close review findings, update reference docs Fold: the new tests were added as their own file, which required loosening a grandfathered lint-test-file-count bucket 5-to-6. A ratchet exists to go down only. commit-docs-bypass.test.cjs is the established commit_docs test home and already hosts two folded suites, so the tests fold there as a third block and the allowlist is reverted untouched. Standards review: CONTEXT.md and the test header both cited a phase-commit-docs-manifest-parity.test.cjs that never existed; a repo-wide sweep found a fourth stale cite in the schema manifest description. All four now name the real location. Spec review: the issue's Scope of changes named planning-config.md and git-planning-commit.md and neither was touched. Both now document the four-tier precedence and the new skip reason. Security review, minor and unproven: detectPhaseNumberFromFiles returns the FIRST matching path's phase, so a --files list spanning two phases resolves the override against whichever comes first. That helper is hardened and widely used, so it is not changed; the behavior is pinned by a named test and disclosed in the design and user docs. A pinned behavior is not a bug; an unpinned surprise is. * chore(#3587): backfill changeset pr number to 3601 --------- Co-authored-by: sim <sim@local>
5.7 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 override
You just made the whole project local-only in step 1. If instead you want ONE phase's artifacts —
say, an architecture or ADR phase whose PLAN.md and REQUIREMENTS.md are worth sharing with the
team — committed while every other phase stays local, set a phase_commit_docs entry for that
phase's number instead of (or on top of) the project-wide switch:
gsd-tools config-set planning.commit_docs false
gsd-tools config-set phase_commit_docs.03 true
{
"planning": { "commit_docs": false },
"phase_commit_docs": { "03": true }
}
Now gsd-tools query commit commits phase 03's artifacts normally, and skips (with the
skipped_commit_docs_phase_false-or-skipped_commit_docs_false reason depending on which tier
decided) every other phase's — the project-wide setting from step 1 still governs everything
phase_commit_docs does not name.
A few things worth knowing before you rely on this:
- The phase-id form doesn't matter.
phase_commit_docs.3,phase_commit_docs.03, and (if your project uses aproject_code)phase_commit_docs.PROJ-03all resolve to the same entry — GSD normalizes the phase number before lookup, the same way it does everywhere else phase numbers are compared. - It only applies to a commit that names a phase-scoped file.
gsd-tools query commitresolves the phase from the--filespaths you pass it (via the.planning/phases/<phase-dir>/…segment). A commit that names no phase file — e.g. a bareROADMAP.mdupdate — has no phase to look up, sophase_commit_docsnever applies to it and the project-wide setting governs, same as before this feature existed. - It's per phase, not per artifact. You cannot commit a phase's ADR but suppress its SUMMARY within the same commit; the override applies to the whole phase.
- Reverse direction works too. With the project-wide default (
commit_docs: true), setphase_commit_docs.<phase-id> falseto suppress just one noisy execution phase while everything else commits normally.
Full precedence order (highest wins): phase_commit_docs.<phase-id> → explicit commit_docs /
planning.commit_docs → .gitignore auto-detect → the manifest default. See
Configuration reference — per-phase override
for the complete rules, including how a non-boolean value is handled.