@opengsd/gsd-core 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +131 -34
  5. package/agents/gsd-debugger.md +12 -246
  6. package/agents/gsd-executor.md +7 -5
  7. package/agents/gsd-integration-checker.md +3 -0
  8. package/agents/gsd-plan-checker.md +9 -0
  9. package/agents/gsd-planner.md +5 -8
  10. package/agents/gsd-roadmapper.md +21 -3
  11. package/agents/gsd-verifier.md +14 -70
  12. package/bin/install.js +503 -341
  13. package/commands/gsd/mempalace-capture.md +1 -1
  14. package/commands/gsd/new-milestone.md +1 -1
  15. package/commands/gsd/plan-phase.md +1 -1
  16. package/gsd-core/bin/gsd-tools.cjs +607 -63
  17. package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
  18. package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
  19. package/gsd-core/bin/lib/api-coverage.cjs +120 -0
  20. package/gsd-core/bin/lib/audit.cjs +89 -1
  21. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  22. package/gsd-core/bin/lib/capability-registry.cjs +96 -110
  23. package/gsd-core/bin/lib/capability-validator.cjs +12 -2
  24. package/gsd-core/bin/lib/check-command-router.cjs +43 -1
  25. package/gsd-core/bin/lib/command-aliases.cjs +72 -0
  26. package/gsd-core/bin/lib/commands.cjs +26 -25
  27. package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
  28. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  29. package/gsd-core/bin/lib/config.cjs +12 -1
  30. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  31. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  32. package/gsd-core/bin/lib/core-utils.cjs +91 -12
  33. package/gsd-core/bin/lib/docs.cjs +3 -2
  34. package/gsd-core/bin/lib/external-job.cjs +19 -4
  35. package/gsd-core/bin/lib/frontmatter.cjs +84 -12
  36. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  37. package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
  38. package/gsd-core/bin/lib/graphify.cjs +142 -27
  39. package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
  40. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  41. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  42. package/gsd-core/bin/lib/init.cjs +1021 -57
  43. package/gsd-core/bin/lib/install-engine.cjs +64 -10
  44. package/gsd-core/bin/lib/install-profiles.cjs +27 -1
  45. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  47. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  48. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  49. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  50. package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
  51. package/gsd-core/bin/lib/io.cjs +28 -3
  52. package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
  53. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  54. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  55. package/gsd-core/bin/lib/milestone.cjs +106 -51
  56. package/gsd-core/bin/lib/phase-id.cjs +63 -0
  57. package/gsd-core/bin/lib/phase-locator.cjs +138 -45
  58. package/gsd-core/bin/lib/phase.cjs +260 -25
  59. package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
  60. package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
  61. package/gsd-core/bin/lib/project-root.cjs +48 -0
  62. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  63. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
  64. package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
  65. package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
  66. package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
  67. package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
  68. package/gsd-core/bin/lib/roadmap.cjs +37 -7
  69. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
  70. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
  71. package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
  72. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
  73. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  74. package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
  75. package/gsd-core/bin/lib/smart-entry.cjs +12 -0
  76. package/gsd-core/bin/lib/state-transition.cjs +73 -8
  77. package/gsd-core/bin/lib/state.cjs +151 -62
  78. package/gsd-core/bin/lib/surface.cjs +12 -1
  79. package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
  80. package/gsd-core/bin/lib/uat.cjs +320 -21
  81. package/gsd-core/bin/lib/unusable-input.cjs +9 -0
  82. package/gsd-core/bin/lib/verification.cjs +29 -12
  83. package/gsd-core/bin/lib/verify.cjs +29 -5
  84. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  85. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
  86. package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
  87. package/gsd-core/bin/lib/workstream.cjs +6 -0
  88. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  89. package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
  90. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  91. package/gsd-core/references/artifact-types.md +10 -3
  92. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  93. package/gsd-core/references/debugger-techniques.md +255 -0
  94. package/gsd-core/references/research-documentation-lookup.md +5 -3
  95. package/gsd-core/references/specless-probe-fallback.md +7 -6
  96. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  97. package/gsd-core/references/worktree-branch-check.md +2 -2
  98. package/gsd-core/templates/summary-complex.md +2 -0
  99. package/gsd-core/templates/summary-minimal.md +2 -0
  100. package/gsd-core/templates/summary-standard.md +2 -0
  101. package/gsd-core/templates/summary.md +2 -0
  102. package/gsd-core/workflows/audit-milestone.md +3 -0
  103. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  104. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  105. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  106. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  107. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  108. package/gsd-core/workflows/autonomous.md +32 -69
  109. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  110. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
  111. package/gsd-core/workflows/code-review.md +42 -145
  112. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  113. package/gsd-core/workflows/complete-milestone.md +23 -81
  114. package/gsd-core/workflows/debug.md +9 -12
  115. package/gsd-core/workflows/diagnose-issues.md +22 -0
  116. package/gsd-core/workflows/discovery-phase.md +4 -4
  117. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  118. package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
  119. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  120. package/gsd-core/workflows/docs-update.md +8 -51
  121. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
  122. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  123. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  124. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  126. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  127. package/gsd-core/workflows/execute-phase.md +65 -137
  128. package/gsd-core/workflows/execute-plan.md +1 -1
  129. package/gsd-core/workflows/help/modes/full.md +6 -1
  130. package/gsd-core/workflows/ingest-docs.md +2 -1
  131. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  132. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  133. package/gsd-core/workflows/new-milestone.md +21 -38
  134. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  135. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  136. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  137. package/gsd-core/workflows/new-project.md +13 -226
  138. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  139. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  140. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  141. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  142. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  143. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  144. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  145. package/gsd-core/workflows/plan-phase.md +49 -193
  146. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  147. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  148. package/gsd-core/workflows/progress.md +11 -153
  149. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  150. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  151. package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
  152. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  153. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  154. package/gsd-core/workflows/quick.md +20 -390
  155. package/gsd-core/workflows/resume-project.md +3 -0
  156. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  157. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  158. package/gsd-core/workflows/review.md +15 -8
  159. package/gsd-core/workflows/section-manifest.json +219 -0
  160. package/gsd-core/workflows/sketch.md +1 -1
  161. package/gsd-core/workflows/spec-phase.md +17 -14
  162. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  163. package/gsd-core/workflows/spike.md +50 -16
  164. package/gsd-core/workflows/sync-skills.md +49 -11
  165. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  166. package/gsd-core/workflows/transition.md +8 -21
  167. package/gsd-core/workflows/ui-phase.md +8 -7
  168. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  169. package/gsd-core/workflows/update.md +18 -7
  170. package/gsd-core/workflows/verify-phase.md +4 -7
  171. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  172. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  173. package/gsd-core/workflows/verify-work.md +8 -58
  174. package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
  175. package/hooks/dist/gsd-check-update-worker.js +14 -5
  176. package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
  177. package/hooks/dist/gsd-read-injection-scanner.js +7 -0
  178. package/hooks/dist/gsd-statusline.js +72 -6
  179. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  180. package/hooks/dist/gsd-write-guard.js +359 -0
  181. package/hooks/dist/lib/isolation-sentinel.js +268 -0
  182. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  183. package/hooks/gsd-agent-isolation-guard.js +428 -0
  184. package/hooks/gsd-check-update-worker.js +14 -5
  185. package/hooks/gsd-cursor-subagent-start.js +532 -26
  186. package/hooks/gsd-read-injection-scanner.js +7 -0
  187. package/hooks/gsd-statusline.js +72 -6
  188. package/hooks/gsd-worktree-path-guard.js +2 -1
  189. package/hooks/gsd-write-guard.js +359 -0
  190. package/hooks/hooks.json +12 -0
  191. package/hooks/lib/isolation-sentinel.js +268 -0
  192. package/hooks/managed-hooks-registry.cjs +2 -0
  193. package/package.json +14 -5
  194. package/pi/gsd.cjs +57 -12
  195. package/scripts/build-hooks.js +9 -0
  196. package/scripts/changeset/lint.cjs +9 -2
  197. package/scripts/changeset/serialize.cjs +5 -1
  198. package/scripts/gen-capability-matrix.cjs +1 -1
  199. package/scripts/gen-context-index.cjs +448 -0
  200. package/scripts/gen-inventory-manifest.cjs +101 -1
  201. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  202. package/scripts/gen-registry.cjs +39 -15
  203. package/scripts/gen-section-manifest.cjs +638 -0
  204. package/scripts/generate-package-identity.cjs +4 -2
  205. package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
  206. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  207. package/scripts/lint-docs-command-form.cjs +195 -0
  208. package/scripts/lint-docs-required.cjs +9 -1
  209. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  210. package/scripts/lint-example-parser-parity.cjs +395 -0
  211. package/scripts/lint-test-file-count.allowlist.json +27 -1
  212. package/scripts/mutation-matrix.cjs +13 -0
  213. package/scripts/prompt-injection-scan.sh +27 -6
  214. package/scripts/registry-schema.cjs +323 -94
  215. package/scripts/run-tests.cjs +3 -2
  216. package/scripts/validate-registry.cjs +10 -6
  217. package/skills/gsd-autonomous/SKILL.md +1 -1
  218. package/skills/gsd-execute-phase/SKILL.md +1 -1
  219. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  220. package/skills/gsd-new-milestone/SKILL.md +1 -1
  221. package/skills/gsd-plan-phase/SKILL.md +2 -2
  222. package/vscode/package.json +1 -1
  223. package/scripts/gen-emitted-baseline.cjs +0 -145
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "gsd-core",
11
11
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
12
- "version": "1.9.0",
12
+ "version": "1.10.0",
13
13
  "source": "./",
14
14
  "author": {
15
15
  "name": "open-gsd",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gsd-core",
3
3
  "displayName": "GSD Core",
4
- "version": "1.9.0",
4
+ "version": "1.10.0",
5
5
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
6
6
  "author": {
7
7
  "name": "open-gsd",
@@ -19,6 +19,5 @@
19
19
  "gsd"
20
20
  ],
21
21
  "commands": "./commands/gsd/",
22
- "skills": "./skills/",
23
- "hooks": "./hooks/hooks.json"
22
+ "skills": "./skills/"
24
23
  }
@@ -563,7 +563,14 @@ const GsdCorePlugin = async ({ directory } = {}) => {
563
563
  handleHookResult(r, output);
564
564
  }
565
565
 
566
- // 4. gsd-workflow-guard.js — workflow advisory + git-force-add block
566
+ // 4. gsd-write-guard.js — hard-block catastrophic shrink of curated
567
+ // .planning/ artifacts (ROADMAP.md, milestones/*-ROADMAP.md, STATE.md)
568
+ if (claudeTool === "Write") {
569
+ const r = runHook("gsd-write-guard.js", prePayload());
570
+ handleHookResult(r, output);
571
+ }
572
+
573
+ // 5. gsd-workflow-guard.js — workflow advisory + git-force-add block
567
574
  // (covers Write/Edit/MultiEdit AND Bash force-add detection)
568
575
  if (isWriteLike || claudeTool === "Bash") {
569
576
  const r = runHook("gsd-workflow-guard.js", prePayload());
@@ -216,15 +216,53 @@ If a finding references multiple files (in Fix section or Issue section):
216
216
 
217
217
  This agent runs as a background process that makes commits. Operating on the main working tree would race the foreground session (shared index, HEAD, and on-disk files). Instead, every instance runs in its own isolated worktree.
218
218
 
219
+ **#2825: honor `workflow.use_worktrees`.** This is the ONLY writer that hand-rolls a git worktree
220
+ inside the agent prompt; every other writer path (`/gsd:execute-phase`, `/gsd:execute-plan`,
221
+ `/gsd:quick`, `/gsd:diagnose-issues`) reads `workflow.use_worktrees` and skips isolation when it is
222
+ `false`. Read the same flag here and, when it is `false`, edit and commit in the main checkout
223
+ directly (set `wt="."`, no `reviewfix_branch`, no recovery sentinel, no `git worktree add`, and skip
224
+ the cleanup tail — there is no worktree to remove). When the flag is not `false`, the transactional
225
+ worktree path below runs unchanged. A user who explicitly opted out of worktrees must never have a
226
+ worktree created; the hand-rolled worktree also cannot run the project's gates safely (no
227
+ `node_modules`), so the opt-out is also the safe path.
228
+
219
229
  The cleanup tail (commit fixes -> remove worktree -> drop recovery sentinel) MUST be **transactional**: either all of (worktree, branch advance, sentinel) end in a clean state, or — if the process is interrupted (system restart, OOM kill) between the last commit and `git worktree remove` — a discoverable recovery sentinel is left behind so a future run, `/gsd:resume-work`, or `/gsd:progress` can complete the cleanup. The bug fixed by #2839 was that the cleanup tail was non-transactional and silently left orphan worktrees + unmerged branches with no resume marker.
220
230
 
221
231
  ```bash
232
+ # #2825: honor workflow.use_worktrees — the documented opt-out. When false,
233
+ # edit/commit in the main checkout (wt=".", no temp branch, no sentinel, no
234
+ # cleanup tail). Read the flag the same way the four sibling writer workflows
235
+ # do. NOTE: this read parses .planning/config.json directly via `node` rather
236
+ # than the gsd-tools CLI, because setup_worktree runs BEFORE the canonical
237
+ # launcher preamble is sourced — invoking the CLI here would be undefined at
238
+ # runtime and violates the runtime-launcher-parity preamble-ordering rule.
239
+ # Once the preamble is sourced (later steps), the CLI is available.
240
+ USE_WORKTREES=$(node -e '
241
+ try {
242
+ const fs = require("fs");
243
+ const p = (process.env.GSD_PROJECT_DIR || process.cwd()) + "/.planning/config.json";
244
+ const cfg = JSON.parse(fs.readFileSync(p, "utf8"));
245
+ process.stdout.write(String((cfg.workflow && cfg.workflow.use_worktrees) ?? true));
246
+ } catch { process.stdout.write("true"); }
247
+ ')
248
+
222
249
  # Derive worktree path from padded_phase (parsed from config in next step,
223
250
  # but the shell snippet below is illustrative — adapt once config is parsed).
224
251
  # In practice: parse padded_phase from config first, then run:
225
252
  branch=$(git branch --show-current)
226
253
  test -n "$branch" || { echo "Detached HEAD is not supported for review-fix (#2686)"; exit 1; }
227
254
 
255
+ # #2647 defense-in-depth: padded_phase is interpolated into a worktree PATH
256
+ # and a git BRANCH NAME below. The orchestrator (code-review-fix.md) already
257
+ # validates it as ^[0-9]+(\.[0-9]+)?$, but this agent prompt is a literal bash
258
+ # contract any caller can spawn — validate at the SINK too, so a future caller
259
+ # that forgets cannot turn ${padded_phase} into a path-traversal or branch-name
260
+ # injection. Reject anything that is not digits + an optional single dotted
261
+ # numeric suffix (e.g. '02' or '36.14'); reject '../', spaces, shell metachars.
262
+ if ! [[ "$padded_phase" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
263
+ echo "Invalid padded_phase for review-fix: '$padded_phase' (expected e.g. '02' or '36.14')"; exit 1
264
+ fi
265
+
228
266
  # Recovery-sentinel handling (#2839):
229
267
  # Path is ${phase_dir}/.review-fix-recovery-pending.json. If it already exists,
230
268
  # a previous run was interrupted between fix commits and `git worktree remove`.
@@ -264,50 +302,85 @@ if [ -f "$sentinel" ]; then
264
302
  rm -f "$sentinel"
265
303
  fi
266
304
 
267
- wt=$(mktemp -d "/tmp/sv-${padded_phase}-reviewfix-XXXXXX")
268
-
269
- # Create a temp branch from the current branch tip so the worktree
270
- # attaches to that NEW branch rather than the user's currently-checked-out
271
- # branch (#2990: git refuses to check out the same branch in two
272
- # worktrees by default; the original `git worktree add "$wt" "$branch"`
273
- # failed before the agent could do any work). The temp branch shares
274
- # history with $branch up to the moment of creation, so commits made
275
- # inside the worktree fast-forward $branch on cleanup.
276
- reviewfix_branch="gsd-reviewfix/${padded_phase}-$$"
277
- git worktree add -b "$reviewfix_branch" "$wt" "$branch"
278
-
279
- # Write the recovery sentinel ONLY AFTER `git worktree add` succeeds.
280
- # Writing it before would leave a sentinel pointing at a worktree that does
281
- # not exist if `git worktree add` itself failed.
282
- node -e '
283
- const fs = require("fs");
284
- const [sentinelPath, worktree_path, branch, reviewfix_branch, padded_phase] = process.argv.slice(1);
285
- fs.writeFileSync(sentinelPath, JSON.stringify({
286
- worktree_path,
287
- branch,
288
- reviewfix_branch,
289
- padded_phase,
290
- started_at: new Date().toISOString()
291
- }, null, 2));
292
- ' "$sentinel" "$wt" "$branch" "$reviewfix_branch" "$padded_phase"
293
-
294
- cd "$wt"
305
+ # #2825: when the user opted out of worktrees, edit/commit in the main
306
+ # checkout directly — no temp branch, no sentinel, no cleanup tail. This is
307
+ # the safe path: the hand-rolled worktree has no node_modules, so it cannot
308
+ # run the project's gates, and an improvised teardown can destroy the real
309
+ # node_modules on Windows (a junction followed by rm -rf). wt="." means every
310
+ # downstream read/edit/commit lands in the main working tree, and the cleanup
311
+ # tail below is a no-op (nothing to fast-forward, no worktree to remove).
312
+ if [ "$USE_WORKTREES" = "false" ]; then
313
+ wt="."
314
+ reviewfix_branch="$branch"
315
+ echo "workflow.use_worktrees=false — editing/committing in the main checkout (no worktree)."
316
+ else
317
+ # #2647: create the worktree INSIDE the repo under the same `.claude/worktrees/`
318
+ # dir the harness-managed executor worktrees already use. An absolute `/tmp`
319
+ # path landed outside the project tree (outside the agent session's permission
320
+ # allowlist → every Read inside prompted; on Windows/Git Bash mktemp also
321
+ # produced an un-removable short `C:/mvwtNN` path to dodge MAX_PATH). A
322
+ # repo-relative path inherits the repository's existing permission scope, is
323
+ # valid and short on Windows as well as POSIX, and is covered by the single
324
+ # `.gitignore` rule for `.claude/` (`.gitignore:12`). Uniqueness across
325
+ # concurrent runs for the same phase comes from the PID (`$$`) + epoch suffix
326
+ # (replacing mktemp's XXXXXX). `$main_repo` is resolved the same way the
327
+ # cleanup tail below resolves it (`git worktree list --porcelain` first line).
328
+ main_repo="$(git worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, ""); print; exit }')"
329
+ wt="$main_repo/.claude/worktrees/rf-${padded_phase}-$$-$(date +%s)"
330
+ mkdir -p "$wt"
331
+
332
+ # Create a temp branch from the current branch tip so the worktree
333
+ # attaches to that NEW branch rather than the user's currently-checked-out
334
+ # branch (#2990: git refuses to check out the same branch in two
335
+ # worktrees by default; the original `git worktree add "$wt" "$branch"`
336
+ # failed before the agent could do any work). The temp branch shares
337
+ # history with $branch up to the moment of creation, so commits made
338
+ # inside the worktree fast-forward $branch on cleanup.
339
+ reviewfix_branch="gsd-reviewfix/${padded_phase}-$$"
340
+ git worktree add -b "$reviewfix_branch" "$wt" "$branch"
341
+
342
+ # Write the recovery sentinel ONLY AFTER `git worktree add` succeeds.
343
+ # Writing it before would leave a sentinel pointing at a worktree that does
344
+ # not exist if `git worktree add` itself failed.
345
+ node -e '
346
+ const fs = require("fs");
347
+ const [sentinelPath, worktree_path, branch, reviewfix_branch, padded_phase] = process.argv.slice(1);
348
+ fs.writeFileSync(sentinelPath, JSON.stringify({
349
+ worktree_path,
350
+ branch,
351
+ reviewfix_branch,
352
+ padded_phase,
353
+ started_at: new Date().toISOString()
354
+ }, null, 2));
355
+ ' "$sentinel" "$wt" "$branch" "$reviewfix_branch" "$padded_phase"
356
+
357
+ cd "$wt"
358
+ fi
295
359
  ```
296
360
 
297
361
  Concrete steps:
298
362
  1. Parse `padded_phase` and `phase_dir` from the `<config>` block (needed for the path and for the sentinel location).
299
363
  2. Resolve the current branch: `branch=$(git branch --show-current)`. If empty (detached HEAD), print an error and exit — detached-HEAD state is not supported; commits made in a detached-HEAD worktree would not advance the branch.
300
364
  3. **Recovery check (#2839, #2990):** If `${phase_dir}/.review-fix-recovery-pending.json` already exists, a prior run was interrupted. Parse the JSON, attempt to remove the orphan worktree it points at (best-effort, with `--force`), and delete the stale `reviewfix_branch` (best-effort, with `git branch -D`), then delete the stale sentinel before continuing. This makes a re-run of `/gsd:code-review --fix` self-healing.
301
- 4. Create a unique worktree path: `wt=$(mktemp -d "/tmp/sv-${padded_phase}-reviewfix-XXXXXX")`. The `mktemp` suffix ensures concurrent runs for the same phase do not collide.
365
+ 4. Create a unique worktree path **inside the repo**: `main_repo="$(git worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, ""); print; exit }')"` then `wt="$main_repo/.claude/worktrees/rf-${padded_phase}-$$-$(date +%s)"` + `mkdir -p "$wt"`. The path lives under the same `.claude/worktrees/` dir the harness-managed executor worktrees use (already gitignored via `.claude/`, already in the session's permission scope), and the `$$`-PID + epoch suffix ensures concurrent runs for the same phase do not collide (#2647 — an absolute `/tmp` path landed outside the project tree and prompted on every read).
302
366
  5. Run `git worktree add -b "$reviewfix_branch" "$wt" "$branch"` — this creates a NEW branch (`gsd-reviewfix/${padded_phase}-$$`) starting from the current branch tip and attaches the worktree to that new branch. Attaching to a new branch (rather than `$branch` directly) is what allows the worktree to coexist with the user's checkout — git refuses to check out the same branch in two worktrees by default (#2990). Commits made inside the worktree advance `$reviewfix_branch`; the cleanup tail fast-forwards `$branch` to `$reviewfix_branch` so the user's branch ends up with the agent's commits.
303
367
  6. **Write the recovery sentinel** at `${phase_dir}/.review-fix-recovery-pending.json` containing `{worktree_path, branch, reviewfix_branch, padded_phase, started_at}`. Doing this AFTER `git worktree add` ensures the sentinel only ever points at a real worktree. The sentinel includes `reviewfix_branch` so recovery can clean both the orphan worktree AND its temp branch.
304
368
  7. All subsequent file reads, edits, and commits happen inside `$wt` (which is on `$reviewfix_branch`, not `$branch`).
305
369
 
306
370
  **If `git worktree add` fails**, surface the error and exit — do not force-remove the path, as another concurrent run may be holding it. Do not write the sentinel (the worktree does not exist). Do not delete `$reviewfix_branch` either; if `-b` failed, no temp branch was created.
307
371
 
308
- **Cleanup tail (transactional, ALWAYS — even on failure):** After writing REVIEW-FIX.md and before returning to the orchestrator, run the cleanup in this exact order:
372
+ **Cleanup tail (transactional, ALWAYS — even on failure — when a worktree was created):** After writing REVIEW-FIX.md and before returning to the orchestrator, run the cleanup in this exact order. (When `workflow.use_worktrees` is `false`, no worktree was created — the cleanup is a no-op and the bash below early-exits.)
309
373
 
310
374
  ```bash
375
+ # #2825: when worktrees were disabled, there is nothing to clean up — the
376
+ # agent edited/committed on $branch directly in the main checkout (wt=".",
377
+ # reviewfix_branch==$branch, no sentinel, no temp worktree). Skip the whole
378
+ # tail; the four steps below are all no-ops or harmful (e.g. `git worktree
379
+ # remove "."` ) in that mode.
380
+ if [ "$USE_WORKTREES" = "false" ]; then
381
+ exit 0
382
+ fi
383
+
311
384
  # Step 1 (#2990): fast-forward $branch to capture the commits the agent
312
385
  # made on $reviewfix_branch. Run from the main repo (not $wt) — the user's
313
386
  # checkout owns $branch. --ff-only ensures we never silently drop or
@@ -354,7 +427,7 @@ fi
354
427
  rm -f "$sentinel"
355
428
  ```
356
429
 
357
- This cleanup is unconditional — register it mentally as a finally-block obligation. If the agent exits early (config error, no findings, etc.), still run the cleanup tail in order (fast-forward → worktree remove → temp branch delete → sentinel rm) before exit. The sentinel must NEVER be removed before `git worktree remove` succeeds. The temp branch must NEVER be deleted while the fast-forward is in a diverged state.
430
+ This cleanup is unconditional when a worktree was created — register it mentally as a finally-block obligation. If the agent exits early (config error, no findings, etc.), still run the cleanup tail in order (fast-forward → worktree remove → temp branch delete → sentinel rm) before exit. (When `workflow.use_worktrees` is `false`, no worktree exists and the bash above early-exits before these steps.) The sentinel must NEVER be removed before `git worktree remove` succeeds. The temp branch must NEVER be deleted while the fast-forward is in a diverged state.
358
431
  </step>
359
432
 
360
433
  <step name="load_context">
@@ -587,9 +660,33 @@ _Iteration: {N}_
587
660
 
588
661
  <critical_rules>
589
662
 
590
- **ALWAYS run inside the isolated worktree** — set up via `branch=$(git branch --show-current)` + `wt=$(mktemp -d "/tmp/sv-${padded_phase}-reviewfix-XXXXXX")` + `git worktree add -b "$reviewfix_branch" "$wt" "$branch"` at the very start (see `setup_worktree` step). Using `mktemp` ensures concurrent runs do not collide. Attaching to a NEW branch `$reviewfix_branch` (not `$branch` directly) is required because git refuses to check out the same branch in two worktrees by default — `$branch` is already checked out in the user's main repo (#2990). Commits advance `$reviewfix_branch`; the cleanup tail fast-forwards `$branch` to `$reviewfix_branch` so the user's branch ends up with the agent's commits. Every file read, edit, and commit must happen inside `$wt`. Run the four-step cleanup tail unconditionally when done (treat it as a finally block). If `git worktree add` fails, exit with an error rather than force-removing a path another run may hold. This prevents racing the foreground session on the shared main working tree (#2686).
591
-
592
- **ALWAYS run the transactional cleanup tail in order** (#2839, #2990): the cleanup is four steps with strict ordering. (1) `git -C "$main_repo" merge --ff-only "$reviewfix_branch"` — fast-forward the user's branch to capture the agent's commits; on divergence, fail loudly and preserve the temp branch. (2) `git worktree remove "$wt" --force`. (3) `git -C "$main_repo" branch -D "$reviewfix_branch"` ONLY if the fast-forward succeeded; otherwise leave the temp branch for manual merge. (4) `rm -f "$sentinel"` (the recovery sentinel at `${phase_dir}/.review-fix-recovery-pending.json`). The sentinel is written AFTER `git worktree add` succeeds and removed only AFTER `git worktree remove` returns successfully. The temp branch is deleted only when the fast-forward succeeded. This ordering is what makes the cleanup tail transactional — an interruption between commits and `git worktree remove` leaves the sentinel behind (with `reviewfix_branch` recorded) so a future run, `/gsd:resume-work`, or `/gsd:progress` can detect and complete the recovery. Reversing the order recreates the orphan-worktree bug.
663
+ **ALWAYS run inside the isolated worktree** — set up via `branch=$(git branch --show-current)` + `main_repo="$(git worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, ""); print; exit }')"` + `wt="$main_repo/.claude/worktrees/rf-${padded_phase}-$$-$(date +%s)"` + `mkdir -p "$wt"` + `git worktree add -b "$reviewfix_branch" "$wt" "$branch"` at the very start (see `setup_worktree` step). The worktree path is repo-relative under `.claude/worktrees/` (the same dir the harness-managed executor worktrees use — gitignored via `.claude/`, inside the session's permission scope); the `$$`-PID + epoch suffix ensures concurrent runs do not collide (#2647 — a hardcoded `/tmp` path landed outside the project tree and prompted on every read). Attaching to a NEW branch `$reviewfix_branch` (not `$branch` directly) is required because git refuses to check out the same branch in two worktrees by default — `$branch` is already checked out in the user's main repo (#2990). Commits advance `$reviewfix_branch`; the cleanup tail fast-forwards `$branch` to `$reviewfix_branch` so the user's branch ends up with the agent's commits. Every file read, edit, and commit must happen inside `$wt`. Run the four-step cleanup tail when done (treat it as a finally block) — but only when a worktree was actually created; when `workflow.use_worktrees` is `false` the cleanup early-exits (no worktree to remove). If `git worktree add` fails, exit with an error rather than force-removing a path another run may hold. This prevents racing the foreground session on the shared main working tree (#2686).
664
+
665
+ **#2825 — honor `workflow.use_worktrees`.** Before creating a worktree, read the
666
+ `workflow.use_worktrees` config flag (the documented opt-out — same key the four sibling writer
667
+ workflows honor). `setup_worktree` reads it via `node` directly from `.planning/config.json`
668
+ (because that step runs BEFORE the canonical gsd_run launcher preamble is sourced; later steps may
669
+ use `gsd_run query config-get workflow.use_worktrees`). When it is `false`, do NOT create a worktree
670
+ — edit and commit in the main checkout directly (`wt="."`, no temp branch, no sentinel, no cleanup
671
+ tail). A user who opted out of worktrees must
672
+ never have one created. See the `setup_worktree` step for the gated bash.
673
+
674
+ **NEVER `rm -rf` a possible reparse point** (#2825). On Windows, `node_modules` inside the worktree
675
+ may be a junction/reparse point whose target is the REAL `node_modules` in the main checkout — and
676
+ `rm -rf` follows the link and deletes the target's contents (silent, misdiagnosable data loss). Do
677
+ NOT improvise a `node_modules` teardown. The worktree has no `node_modules` by design; if you need
678
+ the project's gates, run them in the main checkout after the fast-forward, OR leave the worktree's
679
+ dependency handling to `git worktree remove` (which does not recurse into a separately-managed
680
+ link). Never use `rm -rf` (or `2>/dev/null || rm -rf || true`) as a fallback for removing a path
681
+ that might be a reparse point — on failure, STOP and surface the error rather than falling through
682
+ to a destructive remove.
683
+
684
+ **Record where verification ran** (#2825). The REVIEW-FIX.md verification section must state whether
685
+ the gates ran in the main checkout or the isolated worktree, so a reader can tell whether the numbers
686
+ are reproducible from the tree they are looking at (a worktree-env run is not reproducible from the
687
+ main checkout after teardown).
688
+
689
+ **ALWAYS run the transactional cleanup tail in order when a worktree was created** (#2839, #2990; skipped — bash early-exits — when `workflow.use_worktrees` is `false`): the cleanup is four steps with strict ordering. (1) `git -C "$main_repo" merge --ff-only "$reviewfix_branch"` — fast-forward the user's branch to capture the agent's commits; on divergence, fail loudly and preserve the temp branch. (2) `git worktree remove "$wt" --force`. (3) `git -C "$main_repo" branch -D "$reviewfix_branch"` ONLY if the fast-forward succeeded; otherwise leave the temp branch for manual merge. (4) `rm -f "$sentinel"` (the recovery sentinel at `${phase_dir}/.review-fix-recovery-pending.json`). The sentinel is written AFTER `git worktree add` succeeds and removed only AFTER `git worktree remove` returns successfully. The temp branch is deleted only when the fast-forward succeeded. This ordering is what makes the cleanup tail transactional — an interruption between commits and `git worktree remove` leaves the sentinel behind (with `reviewfix_branch` recorded) so a future run, `/gsd:resume-work`, or `/gsd:progress` can detect and complete the recovery. Reversing the order recreates the orphan-worktree bug.
593
690
 
594
691
  **ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
595
692
 
@@ -168,73 +168,20 @@ try {
168
168
 
169
169
  <investigation_techniques>
170
170
 
171
- ## Binary Search / Divide and Conquer
171
+ ## Technique Catalog
172
172
 
173
- **When:** Large codebase, long execution path, many possible failure points.
173
+ Full step-by-step bodies for every technique below: @gsd-core/references/debugger-techniques.md
174
174
 
175
- **How:** Cut problem space in half repeatedly until you isolate the issue.
176
-
177
- 1. Identify boundaries (where works, where fails)
178
- 2. Add logging/testing at midpoint
179
- 3. Determine which half contains the bug
180
- 4. Repeat until you find exact line
181
-
182
- **Example:** API returns wrong data
183
- - Test: Data leaves database correctly? YES
184
- - Test: Data reaches frontend correctly? NO
185
- - Test: Data leaves API route correctly? YES
186
- - Test: Data survives serialization? NO
187
- - **Found:** Bug in serialization layer (4 tests eliminated 90% of code)
188
-
189
- ## Rubber Duck Debugging
190
-
191
- **When:** Stuck, confused, mental model doesn't match reality.
192
-
193
- **How:** Explain the problem out loud in complete detail.
194
-
195
- Write or say:
196
- 1. "The system should do X"
197
- 2. "Instead it does Y"
198
- 3. "I think this is because Z"
199
- 4. "The code path is: A -> B -> C -> D"
200
- 5. "I've verified that..." (list what you tested)
201
- 6. "I'm assuming that..." (list assumptions)
202
-
203
- Often you'll spot the bug mid-explanation: "Wait, I never verified that B returns what I think it does."
204
-
205
- ## Delta Debugging
206
-
207
- **When:** Large change set is suspected (many commits, a big refactor, or a complex feature that broke something). Also when "comment out everything" is too slow.
208
-
209
- **How:** Binary search over the change space — not just the code, but the commits, configs, and inputs.
210
-
211
- **Over commits (use git bisect):**
212
- Already covered under Git Bisect. But delta debugging extends it: after finding the breaking commit, delta-debug the commit itself — identify which of its N changed files/lines actually causes the failure.
213
-
214
- **Over code (systematic elimination):**
215
- 1. Identify the boundary: a known-good state (commit, config, input) vs the broken state
216
- 2. List all differences between good and bad states
217
- 3. Split the differences in half. Apply only half to the good state.
218
- 4. If broken: bug is in the applied half. If not: bug is in the other half.
219
- 5. Repeat until you have the minimal change set that causes the failure.
220
-
221
- **Over inputs:**
222
- 1. Find a minimal input that triggers the bug (strip out unrelated data fields)
223
- 2. The minimal input reveals which code path is exercised
224
-
225
- **When to use:**
226
- - "This worked yesterday, something changed" → delta debug commits
227
- - "Works with small data, fails with real data" → delta debug inputs
228
- - "Works without this config change, fails with it" → delta debug config diff
229
-
230
- **Example:** 40-file commit introduces bug
231
- ```
232
- Split into two 20-file halves.
233
- Apply first 20: still works → bug in second half.
234
- Split second half into 10+10.
235
- Apply first 10: broken → bug in first 10.
236
- ... 6 splits later: single file isolated.
237
- ```
175
+ - **Binary Search / Divide and Conquer** — halve the search space until the fault localizes.
176
+ - **Rubber Duck Debugging** — reconstruct the mental model aloud; the gap is the bug.
177
+ - **Delta Debugging** — shrink a failing input to its minimal failing core.
178
+ - **Minimal Reproduction** — strip everything not required to reproduce.
179
+ - **Working Backwards** — start at the symptom and walk causality in reverse.
180
+ - **Differential Debugging** — compare a working case against a failing one.
181
+ - **Observability First** — add instrumentation before forming further hypotheses.
182
+ - **Comment Out Everything** — reduce to nothing, restore until the fault returns.
183
+ - **Git Bisect** — binary-search history for the introducing commit.
184
+ - **Follow the Indirection** — trace each hop when the fault hides behind a layer.
238
185
 
239
186
  ## Structured Reasoning Checkpoint
240
187
 
@@ -268,187 +215,6 @@ reasoning_checkpoint:
268
215
 
269
216
  If you cannot fill all seven fields with specific, concrete answers — you do not have a confirmed root cause yet. Return to investigation_loop.
270
217
 
271
- ## Minimal Reproduction
272
-
273
- **When:** Complex system, many moving parts, unclear which part fails.
274
-
275
- **How:** Strip away everything until smallest possible code reproduces the bug.
276
-
277
- 1. Copy failing code to new file
278
- 2. Remove one piece (dependency, function, feature)
279
- 3. Test: Does it still reproduce? YES = keep removed. NO = put back.
280
- 4. Repeat until bare minimum
281
- 5. Bug is now obvious in stripped-down code
282
- 6. **Shrinking (input-space bugs)** — when the bug triggers on a class of inputs, wrap it in a property (fast-check for JS/TS, Hypothesis for Python) and let the shrinker auto-minimize the counterexample; store the **minimized** input as the regression seed. See `gsd-core/references/debugger-repro-hardening.md`.
283
-
284
- **Example:**
285
- ```jsx
286
- // Start: 500-line React component with 15 props, 8 hooks, 3 contexts
287
- // End after stripping:
288
- function MinimalRepro() {
289
- const [count, setCount] = useState(0);
290
-
291
- useEffect(() => {
292
- setCount(count + 1); // Bug: infinite loop, missing dependency array
293
- });
294
-
295
- return <div>{count}</div>;
296
- }
297
- // The bug was hidden in complexity. Minimal reproduction made it obvious.
298
- ```
299
-
300
- ## Working Backwards
301
-
302
- **When:** You know correct output, don't know why you're not getting it.
303
-
304
- **How:** Start from desired end state, trace backwards.
305
-
306
- 1. Define desired output precisely
307
- 2. What function produces this output?
308
- 3. Test that function with expected input - does it produce correct output?
309
- - YES: Bug is earlier (wrong input)
310
- - NO: Bug is here
311
- 4. Repeat backwards through call stack
312
- 5. Find divergence point (where expected vs actual first differ)
313
-
314
- **Example:** UI shows "User not found" when user exists
315
- ```
316
- Trace backwards:
317
- 1. UI displays: user.error → Is this the right value to display? YES
318
- 2. Component receives: user.error = "User not found" → Correct? NO, should be null
319
- 3. API returns: { error: "User not found" } → Why?
320
- 4. Database query: SELECT * FROM users WHERE id = 'undefined' → AH!
321
- 5. FOUND: User ID is 'undefined' (string) instead of a number
322
- ```
323
-
324
- ## Differential Debugging
325
-
326
- **When:** Something used to work and now doesn't. Works in one environment but not another.
327
-
328
- **Time-based (worked, now doesn't):**
329
- - What changed in code since it worked?
330
- - What changed in environment? (Node version, OS, dependencies)
331
- - What changed in data?
332
- - What changed in configuration?
333
-
334
- **Environment-based (works in dev, fails in prod):**
335
- - Configuration values
336
- - Environment variables
337
- - Network conditions (latency, reliability)
338
- - Data volume
339
- - Third-party service behavior
340
-
341
- **Process:** List differences, test each in isolation, find the difference that causes failure.
342
-
343
- **Example:** Works locally, fails in CI
344
- ```
345
- Differences:
346
- - Node version: Same ✓
347
- - Environment variables: Same ✓
348
- - Timezone: Different! ✗
349
-
350
- Test: Set local timezone to UTC (like CI)
351
- Result: Now fails locally too
352
- FOUND: Date comparison logic assumes local timezone
353
- ```
354
-
355
- ## Observability First
356
-
357
- **When:** Always. Before making any fix.
358
-
359
- **Add visibility before changing behavior:**
360
-
361
- ```javascript
362
- // Strategic logging (useful):
363
- console.log('[handleSubmit] Input:', { email, password: '***' });
364
- console.log('[handleSubmit] Validation result:', validationResult);
365
- console.log('[handleSubmit] API response:', response);
366
-
367
- // Assertion checks:
368
- console.assert(user !== null, 'User is null!');
369
- console.assert(user.id !== undefined, 'User ID is undefined!');
370
-
371
- // Timing measurements:
372
- console.time('Database query');
373
- const result = await db.query(sql);
374
- console.timeEnd('Database query');
375
-
376
- // Stack traces at key points:
377
- console.log('[updateUser] Called from:', new Error().stack);
378
- ```
379
-
380
- **Workflow:** Add logging -> Run code -> Observe output -> Form hypothesis -> Then make changes.
381
-
382
- ## Comment Out Everything
383
-
384
- **When:** Many possible interactions, unclear which code causes issue.
385
-
386
- **How:**
387
- 1. Comment out everything in function/file
388
- 2. Verify bug is gone
389
- 3. Uncomment one piece at a time
390
- 4. After each uncomment, test
391
- 5. When bug returns, you found the culprit
392
-
393
- **Example:** Some middleware breaks requests, but you have 8 middleware functions
394
- ```javascript
395
- app.use(helmet()); // Uncomment, test → works
396
- app.use(cors()); // Uncomment, test → works
397
- app.use(compression()); // Uncomment, test → works
398
- app.use(bodyParser.json({ limit: '50mb' })); // Uncomment, test → BREAKS
399
- // FOUND: Body size limit too high causes memory issues
400
- ```
401
-
402
- ## Git Bisect
403
-
404
- **When:** Feature worked in past, broke at unknown commit.
405
-
406
- **How:** Binary search through git history.
407
-
408
- ```bash
409
- git bisect start
410
- git bisect bad # Current commit is broken
411
- git bisect good abc123 # This commit worked
412
- # Git checks out middle commit
413
- git bisect bad # or good, based on testing
414
- # Repeat until culprit found
415
- ```
416
-
417
- 100 commits between working and broken: ~7 tests to find exact breaking commit.
418
-
419
- ## Follow the Indirection
420
-
421
- **When:** Code constructs paths, URLs, keys, or references from variables — and the constructed value might not point where you expect.
422
-
423
- **The trap:** You read code that builds a path like `path.join(configDir, 'hooks')` and assume it's correct because it looks reasonable. But you never verified that the constructed path matches where another part of the system actually writes/reads.
424
-
425
- **How:**
426
- 1. Find the code that **produces** the value (writer/installer/creator)
427
- 2. Find the code that **consumes** the value (reader/checker/validator)
428
- 3. Trace the actual resolved value in both — do they agree?
429
- 4. Check every variable in the path construction — where does each come from? What's its actual value at runtime?
430
-
431
- **Common indirection bugs:**
432
- - Path A writes to `dir/sub/hooks/` but Path B checks `dir/hooks/` (directory mismatch)
433
- - Config value comes from cache/template that wasn't updated
434
- - Variable is derived differently in two places (e.g., one adds a subdirectory, the other doesn't)
435
- - Template placeholder (`{{VERSION}}`) not substituted in all code paths
436
-
437
- **Example:** Stale hook warning persists after update
438
- ```
439
- Check code says: hooksDir = path.join(configDir, 'hooks')
440
- configDir = ~/.claude
441
- → checks ~/.claude/hooks/
442
-
443
- Installer says: hooksDest = path.join(targetDir, 'hooks')
444
- targetDir = ~/.claude/gsd-core
445
- → writes to ~/.claude/gsd-core/hooks/
446
-
447
- MISMATCH: Checker looks in wrong directory → hooks "not found" → reported as stale
448
- ```
449
-
450
- **The discipline:** Never assume a constructed path is correct. Resolve it to its actual value and verify the other side agrees. When two systems share a resource (file, directory, key), trace the full path in both.
451
-
452
218
  ## Technique Selection (routed by bug class)
453
219
 
454
220
  Classify the failure first (Phase 1.75), then route by class — not by ad-hoc
@@ -26,10 +26,12 @@ When you need library or framework documentation, check in this order:
26
26
 
27
27
  1. If Context7 MCP tools (`mcp__context7__*, mcp__plugin_context7_context7__*`) are available in your environment, use them:
28
28
  - Resolve library ID: `mcp__context7__resolve-library-id` with `libraryName`
29
- - Fetch docs: `mcp__context7__get-library-docs` with `context7CompatibleLibraryId` and `topic`
29
+ - Fetch docs: `mcp__context7__query-docs` with `libraryId` (the ID from step 1) and `query`
30
30
 
31
- 2. If Context7 MCP is not available (upstream bug anthropics/claude-code#13898 strips MCP
32
- tools from agents with a `tools:` frontmatter restriction), use the CLI fallback via Bash:
31
+ 2. If Context7 MCP is not available (custom subagents cannot see project-scoped
32
+ `.mcp.json` servers — they only inherit user-scoped `~/.claude/mcp.json`, so a
33
+ context7 server configured at the project scope is invisible to spawned
34
+ agents), use the CLI fallback via Bash:
33
35
 
34
36
  Step 1 — Resolve library ID:
35
37
  ```bash
@@ -498,8 +500,8 @@ if [ -f .git ]; then # worktree
498
500
  # Positive allow-list: HEAD must be on a per-agent branch (`agent-<id>` or
499
501
  # legacy `worktree-agent-<id>`). This catches feature/* and any other
500
502
  # arbitrary branch that the deny-list would silently allow (#2924, #1995).
501
- if ! echo "$ACTUAL_BRANCH" | grep -Eq '^(worktree-)?agent-[A-Za-z0-9._/-]+$'; then
502
- echo "FATAL: refusing to commit — worktree HEAD '$ACTUAL_BRANCH' is not in the agent-* / worktree-agent-* namespace." >&2
503
+ if ! echo "$ACTUAL_BRANCH" | grep -Eq '^((worktree-)?agent-|worktree-wf_)[A-Za-z0-9._/-]+$'; then
504
+ echo "FATAL: refusing to commit — worktree HEAD '$ACTUAL_BRANCH' is not in the agent-* / worktree-agent-* / worktree-wf_* namespace." >&2
503
505
  echo "Agent commits must live on per-agent branches; surface as blocker (#2924)." >&2
504
506
  exit 1
505
507
  fi
@@ -94,6 +94,9 @@ For each phase, extract what it provides and what it should consume.
94
94
  **From SUMMARYs, extract:**
95
95
 
96
96
  ```bash
97
+ # #2962: zsh aborts the block on an unmatched for-list glob (nomatch); bash passes it through. nullglob both.
98
+ shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
99
+
97
100
  # Key exports from each phase
98
101
  for summary in .planning/phases/*/*-SUMMARY.md; do
99
102
  echo "=== $summary ==="
@@ -714,6 +714,9 @@ Extract from init JSON: `phase_dir`, `phase_number`, `has_plans`, `plan_count`.
714
714
  Orchestrator provides CONTEXT.md content in the verification prompt. If provided, parse for locked decisions, discretion areas, deferred ideas.
715
715
 
716
716
  ```bash
717
+ # #2962: zsh aborts the block on an unmatched for-list glob (nomatch); bash passes it through. nullglob both.
718
+ shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
719
+
717
720
  gsd_run query phase.list-plans "$phase_number"
718
721
  # Research / brief artifacts (deterministic listing)
719
722
  gsd_run query phase.list-artifacts "$phase_number" --type research
@@ -735,6 +738,9 @@ done
735
738
  Use `gsd-tools query` to validate plan structure:
736
739
 
737
740
  ```bash
741
+ # #2962: zsh aborts the block on an unmatched for-list glob (nomatch); bash passes it through. nullglob both.
742
+ shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
743
+
738
744
  for plan in "$PHASE_DIR"/*-PLAN.md; do
739
745
  echo "=== $plan ==="
740
746
  PLAN_STRUCTURE=$(gsd_run query verify.plan-structure "$plan")
@@ -820,6 +826,9 @@ Inspect `tasks` in the JSON; open the PLAN in the editor for prose-level review.
820
826
  ## Step 6: Verify Dependency Graph
821
827
 
822
828
  ```bash
829
+ # #2962: zsh aborts the block on an unmatched for-list glob (nomatch); bash passes it through. nullglob both.
830
+ shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
831
+
823
832
  for plan in "$PHASE_DIR"/*-PLAN.md; do
824
833
  grep "depends_on:" "$plan"
825
834
  done