fix(3678): executor must respect commit_docs:false; teach SDK skip envelope (#3679)
* fix(3678): executor must respect commit_docs:false; teach SDK skip envelope Closes #3678 When `commit_docs: false` in `.planning/config.json`, the SDK's `cmdCommit` correctly short-circuits and returns `{committed: false, hash: null, reason: 'skipped_commit_docs_false'}` without staging or committing anything. The agent prompt at `agents/gsd-executor.md:710-720` (final_commit block) tells the executor to call `gsd-sdk query commit "docs(...)" --files .planning/...` but says NOTHING about how to interpret a skipped return. With no explicit instruction, the LLM improvises raw `git add` / `git add -f` / `git commit` to "fulfill" the per-plan commit step it was told to make, which leaks gitignored `.planning/` artifacts into the user's git history (exactly what the reporter observed). Three coordinated fixes: 1. **agents/gsd-executor.md final_commit block** — adds explicit handling text for all three SDK return envelopes (`committed:true`, `skipped:true commit_docs`, `skipped:true gitignored`, `committed:false other reasons`). States plainly: "Do not fall back to raw `git add` / `git commit` / `git add -f` when the SDK returns `skipped: true`." 2. **get-shit-done/bin/lib/commands.cjs cmdCommit** — adds `skipped: true` to both skip-path envelopes so agents see "skipped" as a first-class success signal rather than inferring "no commit happened, I must improvise" from absent `hash` / `committed:false`. Backward-compatible: existing callers reading `committed` / `hash` / `reason` are unaffected. 3. **tests/bug-3678-executor-commit-docs-respect.test.cjs** — 7-test regression covering: - A1/A2: agent prompt mentions the skip envelope AND explicitly forbids raw-git fallback (`source-text-is-the-product` exception) - B1: SDK envelope carries `committed:false`, `skipped:true`, canonical `reason: 'skipped_commit_docs_false'` (frozen enum) - B2: git index empty after commit_docs:false skip (no `.planning/` staged) - B3: HEAD unchanged after commit_docs:false skip - C1/C2: structural ban on `git add -f` / `git add --force` in any agent or workflow body (prohibition-sentence exception preserves audit prose) Verification: - node --test tests/bug-3678-*: 7/7 pass - Targeted regression (10 commit/executor-adjacent files): 135/135 pass - Full docker suite (gsd-test-summary): 11751/11740 pass / 0 fail (the 11 added are this test plus a few collateral pickups) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(changeset): add fragment for #3678 fix (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>) * chore(changeset): set PR number 3679 (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>) * fix(3678): preserve skip-aware carve-out in executor completion checklist The new `final_commit` prose at lines 717-741 teaches the executor to treat `skipped:true` as success and forbids raw-git fallback, but the downstream completion checklist still contained an unconditional "Final metadata commit made" checkbox. An LLM executor reading an unchecked mandatory box may attempt to satisfy it via raw `git add`, re-introducing the exact regression this PR is meant to prevent. Update the checklist line to carve out the intentional-skip case and add a regression test asserting the carve-out remains present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -717,6 +717,28 @@ gsd-sdk query commit "docs({phase}-{plan}): complete [plan-name] plan" --files \
|
||||
```
|
||||
|
||||
Separate from per-task commits — captures execution results only.
|
||||
|
||||
**Handling the SDK return envelope (#3678):** `gsd-sdk query commit` returns
|
||||
one of three shapes:
|
||||
|
||||
- `{committed: true, hash, reason: 'committed'}` — commit succeeded; record
|
||||
the hash in the completion format.
|
||||
- `{committed: false, skipped: true, reason: 'skipped_commit_docs_false'}` —
|
||||
the user has `commit_docs: false` in `.planning/config.json`. **This is an
|
||||
intentional success path.** Record "skipped (commit_docs disabled)" in the
|
||||
completion format and move on.
|
||||
- `{committed: false, skipped: true, reason: 'skipped_gitignored'}` —
|
||||
`.planning/` is gitignored in the user's project. **Also an intentional
|
||||
success path.** Record "skipped (.planning gitignored)" and move on.
|
||||
- `{committed: false, reason: 'nothing_to_commit' | 'commit_failed', ...}` —
|
||||
no-op / genuine failure; surface in the completion notes.
|
||||
|
||||
**Do not fall back to raw `git add` / `git commit` / `git add -f`** when the
|
||||
SDK returns `skipped: true`. The SDK's skip is the user's deliberate choice
|
||||
to keep `.planning/` files out of git history. Force-staging gitignored
|
||||
content via `git add -f .planning/...` is forbidden — that bug is exactly
|
||||
the regression #3678 reported, where the agent leaks `.planning/` artifacts
|
||||
into the user's project history.
|
||||
</final_commit>
|
||||
|
||||
<completion_format>
|
||||
@@ -747,6 +769,6 @@ Plan execution complete when:
|
||||
- [ ] SUMMARY.md created with substantive content
|
||||
- [ ] STATE.md updated (position, decisions, issues, session)
|
||||
- [ ] ROADMAP.md updated with plan progress (via `roadmap update-plan-progress`)
|
||||
- [ ] Final metadata commit made (includes SUMMARY.md, STATE.md, ROADMAP.md)
|
||||
- [ ] Final metadata commit made (includes SUMMARY.md, STATE.md, ROADMAP.md), or SDK returned an intentional skip (`skipped_commit_docs_false` / `skipped_gitignored`) — record "skipped (<reason>)" in completion notes
|
||||
- [ ] Completion format returned to orchestrator
|
||||
</success_criteria>
|
||||
|
||||
Reference in New Issue
Block a user