@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -1,7 +1,117 @@
1
1
  # Worktree Path Safety
2
2
 
3
- Guards for executor agents running inside Claude Code worktrees. Three checks
4
- must run before any staging, Edit, or Write operation in worktree mode.
3
+ Guards for executor agents running inside Claude Code worktrees. The
4
+ supplied-root pin (step 0p) runs in EVERY mode; the remaining checks run before
5
+ any staging, Edit, or Write operation in worktree mode.
6
+
7
+ ---
8
+
9
+ ## Supplied-root pin — step 0p (#4254, EVERY mode)
10
+
11
+ Sequential-mode dispatch (no `isolation="worktree"`) gives the executor no
12
+ spawn-time cwd guarantee, and the worktree-only guards below do not apply — so
13
+ a sequential executor whose process cwd resolved to a different checkout of
14
+ the same repo would self-derive that checkout as its root and commit there,
15
+ silently. Step 0p closes that hole by comparing the executor's actual root
16
+ against a root the ORCHESTRATOR already validated — never against anything the
17
+ executor derives itself.
18
+
19
+ **Runtime contract (executor):** if your prompt contains a `<project_root_pin>`
20
+ block, run its guard script verbatim before your first Edit/Write and again
21
+ before every commit, in the same cwd as that write or commit. On FATAL, halt
22
+ and report — recovery (moving commits between checkouts) is an
23
+ orchestrator/human decision, never agent self-repair. If your prompt contains
24
+ NO `<project_root_pin>` block (worktree/isolated dispatch, or a legacy
25
+ orchestrator), emit one warning line and continue with steps 0a/0b below — do
26
+ not fail closed on dispatches that never carried a pin. **Never bind
27
+ `{PINNED_ROOT}` yourself**: if this template reaches you unbound it is
28
+ reference prose, not your pin — only the orchestrator's build-time
29
+ substitution produces a valid guard.
30
+
31
+ **Composition contract (orchestrator — build time, NOT a sub-agent runtime
32
+ step):** copy the guard below into the dispatched prompt inside a
33
+ `<project_root_pin>` block, substituting `{PINNED_ROOT}` with the literal value
34
+ of `$ORCHESTRATOR_WT` captured at execute_waves entry, shell-single-quoted:
35
+ wrap the path in `'…'` and escape any embedded `'` as `'\''`. A path that
36
+ cannot be quoted this way must halt the phase (surface a blocker) rather than
37
+ ship a pin that could mis-parse. The comparison is git-vs-git on BOTH sides —
38
+ `git -C` resolves the pinned path to its repo's canonical toplevel in git's
39
+ own path representation, so symlink aliases, trailing slashes, `/var` vs
40
+ `/private/var` spellings, and Windows drive-letter forms — forward- or
41
+ backslash-separated, `RUNNER~1`-style short names included — compare equal by
42
+ construction (shell `pwd -P` normalization does NOT match git's emission on
43
+ Windows — do not re-introduce it).
44
+
45
+ Two portability rules baked into the guard below, learned from the #4254 CI
46
+ Windows legs: (1) a backslash comparator must be GENERATED at runtime
47
+ (`printf '\134'`), because a backslash written twice in the script text does
48
+ not survive the Windows command-line round-trip into bash — the doubled form
49
+ arrives halved, which silently rewrites any escape pattern that relies on it;
50
+ (2) every FATAL names its `Guard stage` and, where a git capture failed,
51
+ git's own stderr in a `Diagnostic` line, so a platform failure self-describes
52
+ instead of surfacing as a bare `Actual root: <none>`.
53
+
54
+ ```bash
55
+ # gsd:guard=supplied-root-pin (#4254) — run before the first Edit/Write and before every commit.
56
+ PINNED_ROOT='{PINNED_ROOT}' # orchestrator build-time substitution — the only valid source of this value
57
+ PIN_STAGE=''
58
+ PIN_DIAG=''
59
+ gsd_pin_fail() {
60
+ echo "FATAL: executor root does not match the orchestrator-supplied PROJECT_ROOT pin (#4254)." >&2
61
+ echo " Pinned root: ${PINNED_ROOT:-<empty or unexpanded>}" >&2
62
+ echo " Actual root: ${ACTUAL_ROOT:-<none>}" >&2
63
+ echo " Guard stage: ${PIN_STAGE:-<unset>}" >&2
64
+ if [ -n "$PIN_DIAG" ]; then echo " Diagnostic: $PIN_DIAG" >&2; fi
65
+ echo " No writes or commits are permitted from this checkout. HALT and report; recovery is an" >&2
66
+ echo " orchestrator/human decision. Only the IMMEDIATE submodule of the pinned checkout is a" >&2
67
+ echo " legitimate other cwd — nested submodules must surface as a blocker, not self-route." >&2
68
+ exit 1
69
+ }
70
+ # Backslash comparator, generated at runtime: a backslash written twice in this
71
+ # script does not survive the Windows spawn path into bash (the command-line
72
+ # round-trip halves the doubled form), which rejected every C:\ pin at the form
73
+ # gate on the #4254 CI Windows legs. printf's octal escape is a lone backslash,
74
+ # which does survive; the quoted expansion below is literal in a case pattern.
75
+ BS=$(printf '\134')
76
+ # Fail closed if the comparator could not be generated: an empty BS would widen
77
+ # the drive-form arm below to drive-RELATIVE pins (C:foo) — the one fail-open
78
+ # seam in this construction, closed loudly rather than trusted to the shell.
79
+ if [ -z "$BS" ]; then
80
+ PIN_STAGE=form-gate
81
+ PIN_DIAG='backslash comparator generation failed (printf octal escape returned empty)'
82
+ gsd_pin_fail
83
+ fi
84
+ case "$PINNED_ROOT" in
85
+ ''|'{PINNED_ROOT}') PIN_STAGE=pin-unbound; gsd_pin_fail ;; # empty or unexpanded pin — fail closed, never warn-and-proceed
86
+ /*) ;; # absolute POSIX form
87
+ [A-Za-z]:/*|[A-Za-z]:"$BS"*) ;; # Windows drive form, forward- or backslash-separated
88
+ *) PIN_STAGE=form-gate; gsd_pin_fail ;; # relative pin — never trustworthy across cwds
89
+ esac
90
+ ACTUAL_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
91
+ if [ -z "$ACTUAL_ROOT" ]; then
92
+ PIN_STAGE=actual-capture
93
+ PIN_DIAG="git rev-parse --show-toplevel from the cwd failed: $(git rev-parse --show-toplevel 2>&1 1>/dev/null)"
94
+ gsd_pin_fail
95
+ fi
96
+ PINNED_TL=$(git -C "$PINNED_ROOT" rev-parse --show-toplevel 2>/dev/null)
97
+ if [ -z "$PINNED_TL" ]; then
98
+ PIN_STAGE=pinned-capture
99
+ PIN_DIAG="git -C <pinned root> rev-parse --show-toplevel failed: $(git -C "$PINNED_ROOT" rev-parse --show-toplevel 2>&1 1>/dev/null)"
100
+ gsd_pin_fail
101
+ fi
102
+ if [ "$ACTUAL_ROOT" != "$PINNED_TL" ]; then
103
+ # Registered-submodule allowance: sub_repos plans legitimately commit inside an
104
+ # immediate submodule of the pinned checkout. The superproject working tree is
105
+ # git-emitted in the same representation as PINNED_TL, so the equality is
106
+ # representation-safe on every platform.
107
+ SUPER_TL=$(git rev-parse --show-superproject-working-tree 2>/dev/null)
108
+ if [ "$SUPER_TL" != "$PINNED_TL" ]; then
109
+ PIN_STAGE=root-mismatch
110
+ PIN_DIAG="actual=${ACTUAL_ROOT} pinned=${PINNED_TL} superproject=${SUPER_TL:-<none>}"
111
+ gsd_pin_fail
112
+ fi
113
+ fi
114
+ ```
5
115
 
6
116
  ---
7
117
 
@@ -65,3 +175,324 @@ fi
65
175
  **Prefer relative paths** for all Edit/Write operations. When an absolute path is
66
176
  unavoidable, always derive it from `git rev-parse --show-toplevel` run inside the
67
177
  worktree — never from `pwd` captured in the orchestrator context.
178
+
179
+ ---
180
+
181
+ ## `<automated>` command guard — step 0c (#4767)
182
+
183
+ The plan's `<automated>` text is where an orchestrator-cwd absolute path most often
184
+ arrives: the planner saw absolute paths in its own context and wrote one into the
185
+ command. Run as written, `cd /abs/main-checkout/… && <test>` leaves the worktree, runs
186
+ against the main tree, and **passes on code this worktree changed and the main tree did
187
+ not** — a green verify that verified nothing. Before executing any `<automated>` command,
188
+ scan its text for absolute paths and halt if one is outside the worktree. Fail loud; never
189
+ rewrite the prefix silently (#3050) — a rewritten command hides the defective plan, and the
190
+ next executor meets it again.
191
+
192
+ ```bash
193
+ # WT_ROOT as in step 0b. MAIN_ROOT is the checkout this worktree was created from — the one an
194
+ # orchestrator-cwd path points at.
195
+ WT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
196
+ MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." 2>/dev/null && pwd -P)
197
+ # Resolve a path the way the shell would land in it. A relative path is taken from the second
198
+ # argument — the cwd a chained `cd` has reached so far — or the worktree root. An existing directory resolves through `cd && pwd -P`; a file through its directory (a
199
+ # symlinked file through its link target first), so a symlink or `..` hop inside the worktree that
200
+ # lands in the main checkout is seen for what it is; a path that does not exist yet — a Wave-0
201
+ # scaffold — is normalized lexically and must still PASS when it sits under the worktree. Never
202
+ # `readlink -m` / `realpath -m` — GNU-only. `_norm` splits with `read -ra` (no glob expansion) and
203
+ # expands the array with the `${a[@]+"${a[@]}"}` idiom (bash < 4.4 errors on an empty array under -u).
204
+ _norm(){ local -a out=() seg; local s; IFS=/ read -ra seg <<<"$1"
205
+ for s in ${seg[@]+"${seg[@]}"}; do case "$s" in ''|.) ;; ..) [ ${#out[@]} -gt 0 ] && unset 'out[${#out[@]}-1]' ;; *) out+=("$s") ;; esac; done
206
+ printf '/%s' ${out[@]+"${out[@]}"}; [ ${#out[@]} -gt 0 ] || printf '/'; }
207
+ _resolve(){ local p t; case "$1" in /*) p=$1 ;; *) p="${2:-$WT_ROOT}/$1" ;; esac
208
+ if [ -L "$p" ] && ! [ -d "$p" ]; then t=$(readlink "$p"); case "$t" in /*) p=$t ;; *) p="$(dirname -- "$p")/$t" ;; esac; fi
209
+ ( cd -- "$p" 2>/dev/null && pwd -P ) \
210
+ || ( cd -- "$(dirname -- "$p")" 2>/dev/null && printf '%s/%s' "$(pwd -P)" "$(basename -- "$p")" ) \
211
+ || _norm "$p"; }
212
+ _outside_wt(){ case "$1" in "$WT_ROOT"|"$WT_ROOT"/*) return 1 ;; *) return 0 ;; esac; }
213
+ # A shell word as the planner wrote it: runs of bare characters, "…" / '…' spans, and backslash
214
+ # escapes, in any mix (`"/x"/y`, `"O'Reilly"`, `path\ with\ space`, `release=main`). `_unquote`
215
+ # walks it with the shell's own three quoting states and returns the string the shell would pass.
216
+ _TOK='("[^"]*"|'"'"'[^'"'"']*'"'"'|\\.|[^[:space:]"'"'"';|&()])+'
217
+ _unquote(){ local s=$1 out='' q='' c i
218
+ for ((i=0; i<${#s}; i++)); do c=${s:i:1}
219
+ if [ -z "$q" ]; then case "$c" in '"'|"'") q=$c ;; '\') i=$((i+1)); out+=${s:i:1} ;; *) out+=$c ;; esac
220
+ elif [ "$q" = '"' ]; then case "$c" in '"') q='' ;; '\') i=$((i+1)); out+=${s:i:1} ;; *) out+=$c ;; esac
221
+ else case "$c" in "'") q='' ;; *) out+=$c ;; esac; fi
222
+ done; printf '%s' "$out"; }
223
+ # True when raw shell text contains an unquoted single `&` or `|`. `&&` and
224
+ # `||` keep the current shell's cwd; the single forms cross a process boundary.
225
+ _resets_cwd(){ local s=$1 q='' i c p n; for ((i=0;i<${#s};i++)); do c=${s:i:1}
226
+ if [ -z "$q" ]; then case "$c" in
227
+ "'"|'"') q=$c ;; '\') i=$((i+1)) ;;
228
+ '&'|'|') p=''; n=''; [ "$i" -gt 0 ] && p=${s:i-1:1}; [ "$i" -lt $((${#s}-1)) ] && n=${s:i+1:1};
229
+ [ "$p" = "$c" ] || [ "$n" = "$c" ] || return 0 ;;
230
+ esac
231
+ elif [ "$q" = '"' ]; then case "$c" in '"') q='' ;; '\') i=$((i+1)) ;; esac
232
+ else [ "$c" = "'" ] && q=''; fi
233
+ done; return 1; }
234
+ # The relocating verbs — `cd` / `pushd` (bare, `builtin`/`command`-prefixed, env-prefixed, or with
235
+ # `--`) and a tool's own directory flag (`npm`/`npx`/`pnpm` `--prefix`, `yarn --cwd`, `pnpm -C`,
236
+ # `make -C`, `git -C`; both `--flag <p>` and `--flag=<p>`) at the start of a segment, including after a
237
+ # `(`/`{` opener or a `&` background operator. Each match is stripped of its prefix with an ANCHORED sed,
238
+ # so the target is taken verbatim, unquoted, and compared LITERALLY — never interpolated into a regex.
239
+ _VERB='((builtin|command)[[:space:]]+)?(cd|pushd)([[:space:]]+--)?'
240
+ _ENV='([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+)*'
241
+ # `&` joins the operator set; grep is line-oriented, so a newline is already covered by `^`.
242
+ # CONTROL KEYWORDS (`if`, `then`, `do`, …) WERE TRIED HERE AND REVERTED, and the reason is the rule
243
+ # this fence is built on rather than a detail: these operators are matched against RAW TEXT, with no
244
+ # awareness of quoting, so a keyword that is also an ordinary English word turns prose inside a
245
+ # quoted argument into a command boundary — `grep -F 'if cd ../main; then' README.md` halted on a
246
+ # command that relocates nothing. That buys a rare false negative (`if cd ../main; then …`) at the
247
+ # cost of a realistic false positive, and this guard's whole posture is that the false-positive
248
+ # direction is the one that gets it routed around. The residual is disclosed instead.
249
+ # Spell the literal ERE operators as bracket expressions. `\|` / `\(` / `\{`
250
+ # are GNU-tolerated but undefined by POSIX and BSD sed rejects the same boundary
251
+ # expression with "unbalanced brackets" before the scan can run (#4767).
252
+ _OPEN='(^|&&|&|;|[|][|]|[|]|[(]|[{])[[:space:]]*'
253
+ # LAUNCHERS are a NAMED set, not `[^&;|]*`. A wrapper reached through a launcher is still a wrapper,
254
+ # and the set admits an absolute path (`/usr/bin/env`) and an option with a SEPARATE operand
255
+ # (`env -u FOO`, `timeout --signal TERM`, `stdbuf -o L`). Which options TAKE an operand is
256
+ # TOOL-SCOPED for the same reason `_DIR`'s flags are: `-i` takes one on `stdbuf` and NONE on `env`,
257
+ # so a flat set consumed the COMMAND after an operandless flag and `env -i echo bash -c '…'` —
258
+ # which runs `echo` — halted. `command` and `exec` are deliberately NOT in the set: `command -v
259
+ # bash` is a name PROBE that executes nothing, and halting it is the false-positive direction
260
+ # again. The cost is that a genuine `command bash -c …` is not unwrapped; that is disclosed.
261
+ # but a catch-all leading run also unwraps interpreter TEXT that is merely an argument — `echo bash -c
262
+ # 'cd ../main'` halted on a command that executes nothing. A spurious unwrap is NOT harmless: the scans
263
+ # halt on a path the shell would never visit. Name the launchers instead.
264
+ _LOPT='([[:space:]]+-[^[:space:]]+|[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*)*'
265
+ # THE SHAPE IS `(<generic opts> <opt> <operand>)* <generic opts>`, NOT `<generic opts> (<opt>
266
+ # <operand>)* <generic opts>`. The second admits only ONE contiguous run of operand-taking options,
267
+ # so a generic option BETWEEN two of them ends the run and hides everything after it:
268
+ # `env -u FOO --debug -u BAR -C ../main` relocates and passed silently. Interleaving is the normal
269
+ # way these tools are invoked, so the grammar has to allow it. Same shape for timeout and stdbuf.
270
+ # env's own separate-operand options, factored out because BOTH consumers need them: $_LAUNCH, so a
271
+ # payload behind `env -u FOO bash -c …` is unwrapped, and $_DIR, so `env -u FOO -C ../main` is still
272
+ # seen as a relocation. Generic $_LOPT cannot consume `FOO`, so without this the option HID the flag.
273
+ _ENVOPT='('"$_LOPT"'[[:space:]]+(-u|--unset)[[:space:]]+'"$_TOK"')*'"$_LOPT"
274
+ # $_LAUNCH needs a WIDER env group than $_DIR does, and the difference is deliberate. $_DIR must leave
275
+ # `-C`/`--chdir` UNCONSUMED — that flag is the thing it is looking for. $_LAUNCH must consume it, or
276
+ # `env -C scripts bash -c '…'` has its relocation seen and its PAYLOAD never unwrapped, which is the
277
+ # bypass class this whole guard is about. Two groups, one per consumer.
278
+ _ENVOPTL='('"$_LOPT"'[[:space:]]+((-u|--unset)[[:space:]]+|(-C|--chdir)([[:space:]]+|=))'"$_TOK"')*'"$_LOPT"
279
+ _LAUNCH='((/[^[:space:]]*/)?((env|setsid|nohup)'"$_ENVOPTL"'|timeout('"$_LOPT"'[[:space:]]+(-s|--signal|-k|--kill-after)[[:space:]]+[^[:space:]]+)*'"$_LOPT"'[[:space:]]+[0-9]+[smhd]?|(stdbuf|nice|ionice)('"$_LOPT"'[[:space:]]+(-i|-o|-e|-n|-c)[[:space:]]+[^[:space:]]+)*'"$_LOPT"')[[:space:]]+)*'
280
+ # The directory flags are TOOL-SCOPED, because the same spelling is not the same flag on every tool:
281
+ # `git archive --prefix=../main/ HEAD` names archive MEMBERS and changes no directory, so a flat
282
+ # six-tool `--prefix` halts a legitimate command. `--prefix`/`--cwd`/`--dir`/`-C` belong to the node
283
+ # package managers; `-C`/`--directory` to make and git. $_LAUNCH leads it for $_EXEC's reason — a
284
+ # recognised tool behind `env` is still that tool.
285
+ _DIR='(('"$_LAUNCH"')(npm|npx|pnpm|yarn)[[:space:]]+([^&;|]*[[:space:]]+)?(--prefix|--cwd|--dir|-C)|('"$_LAUNCH"')(make|git)[[:space:]]+([^&;|]*[[:space:]]+)?(-C|--directory)|('"$_LAUNCH"')(/[^[:space:]]*/)?env'"$_ENVOPT"'[[:space:]]+(--chdir|-C))[[:space:]=]+'
286
+ # Command-string interpreters. `eval "cd <main> && x"`, `sh -c "…"`, `bash -c '…'`, `node -e '…'` bury
287
+ # the real command inside ONE opaque quoted token, and that defeats BOTH scans below at once: the
288
+ # relocating verb never sits at a boundary scan 1 recognizes, and scan 2's tokenizer swallows the whole
289
+ # quoted span, which after unquoting does not begin with `/`. These are ordinary portability idioms, not
290
+ # obfuscation — a planner imitating a CI script reaches for one without any adversarial intent — so the
291
+ # payload is UNWRAPPED and re-scanned rather than trusted or rejected wholesale. $_LAUNCH lets the
292
+ # launcher forms a planner actually writes — `env bash -c`, a `timeout`-wrapped `bash -c`,
293
+ # `nohup sh -c` — reach the same unwrap as a bare one, and an absolute or option-bearing
294
+ # interpreter (`/bin/bash -c`, `bash --noprofile -c`) with it.
295
+ _EXEC='('"$_LAUNCH"')((/[^[:space:]]*/)?(sh|bash|zsh|dash|ksh)([[:space:]]+--[a-z-]+)*[[:space:]]+-[a-z]*c|(/[^[:space:]]*/)?(python3?|node|perl|ruby)([[:space:]]+--[a-z-]+)*[[:space:]]+-[a-z]*[ce]|eval)[[:space:]]+'
296
+ # `eval` runs in the current shell, unlike the other interpreters above; its payload's final cwd must
297
+ # therefore flow into the next event. A subprocess payload is scanned from the reached cwd and then
298
+ # restores its caller's cwd.
299
+ _EVAL='eval[[:space:]]+'
300
+ # ONE ordered pass per scanned string, each event tagged `C` (a cd/pushd — moves the cwd), `N` (a
301
+ # tool's directory flag — resolved from the cwd reached so far but does NOT move it), `E` (`eval`,
302
+ # whose cwd change persists), or `W` (a subprocess interpreter, whose cwd change does not). Ordering
303
+ # is load-bearing: scanning every outer relocation before an earlier payload lets a later `cd` supply
304
+ # that payload with a cwd the shell has not reached yet. `_scan` is a FUNCTION so an unwrapped payload
305
+ # reaches the SAME event scan as the top-level command; it halts with
306
+ # `exit 1`, never a return code, so a halt inside a payload is exactly as fatal as one outside; and it
307
+ # ends `return 0` so a trailing non-`C` target cannot make the function itself look failed under `set -e`.
308
+ CUR=$WT_ROOT
309
+ _scan(){ local CMD=$1 D=$2 RAW BODY BFR REST K Q T R P S PREFIX LDS LD _T _P
310
+ _T=$( printf '%s' "$CMD" | grep -oE "${_OPEN}(${_ENV}${_EXEC}|${_ENV}${_VERB}[[:space:]]+|${_DIR})${_TOK}" \
311
+ | sed '/^$/d' )
312
+ # 1. Every relocating target, relative or absolute, resolved from the cwd the command has reached
313
+ # (chained `cd scripts && cd ..` lands back at the root and passes; `cd scripts && cd ../..` does
314
+ # not): outside the worktree → halt. A target the shell would expand (`~`, `$VAR`, `$(…)`) cannot
315
+ # be evaluated here and passes through — the plan-checker's probe already reports those as
316
+ # `dynamic_path`, and `$(git rev-parse --show-toplevel)` is the form step 0b itself recommends.
317
+ REST=$CMD
318
+ while IFS= read -r RAW; do
319
+ [ -n "$RAW" ] || continue
320
+ # Preserve process boundaries even when an unrecognized command sits between
321
+ # one and the next recognized event (`cd x & echo y && bash -c ...`).
322
+ BFR=${REST%%"$RAW"*}; REST=${REST#*"$RAW"}
323
+ if _resets_cwd "$BFR" || _resets_cwd "$RAW"; then CUR=$WT_ROOT; fi
324
+ BODY=$(printf '%s' "$RAW" | sed -E "s/^(&&|&|;|[|][|]|[|]|[(]|[{])?[[:space:]]*//")
325
+ # Classify with the same grep implementation that produced the event and
326
+ # remove the matched prefix literally. Expanding these composed EREs into a
327
+ # multi-expression BSD sed program fails with "unbalanced brackets" on
328
+ # macOS even though BSD grep accepts the event expression (#4767).
329
+ if _P=$(printf '%s' "$BODY" | grep -oE "^${_ENV}${_EVAL}"); then K=E
330
+ elif _P=$(printf '%s' "$BODY" | grep -oE "^${_ENV}${_EXEC}"); then K=W
331
+ elif _P=$(printf '%s' "$BODY" | grep -oE "^${_DIR}"); then K=N
332
+ else _P=$(printf '%s' "$BODY" | grep -oE "^${_ENV}${_VERB}[[:space:]]+"); K=C
333
+ fi
334
+ Q=${BODY#"$_P"}; T=$(_unquote "$Q")
335
+ case "$K" in
336
+ E) [ "$D" -ge 4 ] || _walk "$T" "$((D+1))" ;;
337
+ W) if [ "$D" -lt 4 ]; then
338
+ S=$CUR
339
+ # `_EXEC` is longer than `_DIR`, so a wrapper reached through
340
+ # `env -C/--chdir` wins grep's leftmost-longest match. Recover that
341
+ # launcher's relocation here: validate it, start the child there,
342
+ # then restore the parent cwd when the payload scan returns.
343
+ PREFIX=${BODY%"$Q"}
344
+ LDS=$(printf '%s' "$PREFIX" | grep -oE "(^|[[:space:]])(-C|--chdir)([[:space:]]+|=)${_TOK}" \
345
+ | sed -E 's/^[[:space:]]*(-C|--chdir)([[:space:]]+|=)//' || true)
346
+ while IFS= read -r LD; do
347
+ [ -n "$LD" ] || continue
348
+ LD=$(_unquote "$LD"); R=$(_resolve "$LD" "$CUR")
349
+ if _outside_wt "$R"; then
350
+ echo "FATAL: <automated> command relocates to $LD -> $R, outside the worktree ($WT_ROOT) — it would verify the wrong checkout. Rewrite the plan's command root-relative (cwd is the checkout root); do not rewrite it in place." >&2
351
+ exit 1
352
+ fi
353
+ CUR=$R
354
+ done <<EOF_LAUNCH_DIRS
355
+ $LDS
356
+ EOF_LAUNCH_DIRS
357
+ _walk "$T" "$((D+1))"; CUR=$S
358
+ fi ;;
359
+ C|N)
360
+ R=$(_resolve "$T" "$CUR")
361
+ if _outside_wt "$R"; then
362
+ echo "FATAL: <automated> command relocates to $T -> $R, outside the worktree ($WT_ROOT) — it would verify the wrong checkout. Rewrite the plan's command root-relative (cwd is the checkout root); do not rewrite it in place." >&2
363
+ exit 1
364
+ fi
365
+ [ "$K" = C ] && CUR=$R ;;
366
+ esac
367
+ done <<EOF_TARGETS
368
+ $_T
369
+ EOF_TARGETS
370
+ # 2. Any other absolute word that resolves under the MAIN checkout — a file argument, a redirect,
371
+ # an include — is the same defect by a different verb; system paths such as /dev/null or /usr/bin
372
+ # are neither and pass.
373
+ while IFS= read -r P; do
374
+ [ -n "$P" ] || continue
375
+ P=$(_unquote "$P"); case "$P" in [A-Za-z_]*=*|--*=*) P=${P#*=} ;; esac # FOO=/x, --flag=/x
376
+ case "$P" in /*) ;; *) continue ;; esac
377
+ R=$(_resolve "$P")
378
+ _outside_wt "$R" || continue
379
+ case "$R" in
380
+ "$MAIN_ROOT"|"$MAIN_ROOT"/*)
381
+ echo "FATAL: <automated> command names $P (-> $R) inside the main checkout, outside the worktree ($WT_ROOT) — it would verify the wrong checkout. Rewrite the plan's command root-relative (cwd is the checkout root); do not rewrite it in place." >&2
382
+ exit 1 ;;
383
+ esac
384
+ done <<EOF_ABS
385
+ $(printf '%s' "$CMD" | grep -oE "$_TOK")
386
+ EOF_ABS
387
+ return 0; }
388
+ # EVERY s-COMMAND THAT INTERPOLATES ONE OF THESE REGEXES USES `#`, NOT `/`. $_EXEC, $_LAUNCH and
389
+ # (through $_LAUNCH) $_DIR all contain a literal `/` from their absolute-path groups, and a `/`
390
+ # inside the pattern closes an `s/…/…/` early: sed dies `unknown option to 's'` on STDERR inside a
391
+ # command substitution, the extracted list comes back EMPTY, and the scan silently stops happening
392
+ # while every command reads as a clean pass. It is written as a rule about ALL of them rather than
393
+ # about the one that had a slash first, because that is exactly how it recurred: $_EXEC was fixed,
394
+ # then $_LAUNCH gained `(/[^[:space:]]*/)?` and took $_DIR down with it. Both driven, not theorised.
395
+ # 3. The ordered event scan unwraps each interpreter payload in place, to a bounded depth — a wrapper
396
+ # inside a wrapper is still a wrapper, and the bound is what keeps a pathological nest finite.
397
+ # A payload the shell would build at run time (`bash -c $CMD`) is not a literal token here and passes
398
+ # through as a dynamic_path, exactly as scan 1 treats an expansion.
399
+ _walk(){ _scan "$1" "$2"; }
400
+ _walk "$AUTOMATED_CMD" 0
401
+ ```
402
+
403
+ A halt here is a plan defect, not an executor deviation: report it via the checkpoint return
404
+ format naming the task and the offending command verbatim, and stop. The plan-checker's path
405
+ probe (`check verify-command-paths`) warns on the *outside-orchestrator-root* case before
406
+ execution; this guard is the one that sees the executor's actual root.
407
+
408
+ ### What this guard does NOT see — step 0c's stated boundary
409
+
410
+ The two scans recognize **fixed sets** — relocating verbs, directory flags, interpreters, launchers,
411
+ command boundaries — and the domain they act on, shell a planner may write, can acquire a member without
412
+ this file changing. So the boundary is written out rather than left to be rediscovered, and each entry
413
+ says what follows from it.
414
+
415
+ **The largest one first, because it is the one a reader will otherwise assume away:**
416
+
417
+ - **A RELATIVE path argument to a command that is not a recognized relocating verb.**
418
+ `python3 -m pytest ../main/tests`, `node ../main/test.js`, `make -f ../main/Makefile`, a redirect from
419
+ `../main/file` — all pass. The catch-all scan evaluates only words that begin with `/`, so it never
420
+ resolves a relative one, and the verb scan does not fire because none of these relocates. This is the
421
+ widest gap in the guard and it is not new. Closing it means resolving every relative word in the
422
+ command against the reached cwd, which halts on ordinary in-worktree arguments unless it can tell a
423
+ path from a flag value from a bare string — a much larger change than this guard is, and one that
424
+ fails toward false positives, which is the direction that gets a guard deleted.
425
+
426
+ The rest, in descending order of how likely a planner is to reach them:
427
+
428
+ - **Spellings that hide the verb from a text scan.** `cd$IFS/x` and its relatives split at run time, so
429
+ no static tokenizer sees a `cd` at a boundary. Deliberate: the guard's domain is what a planner
430
+ plausibly writes, not an adversary.
431
+ - **Anything the shell builds at run time.** `cd $VAR`, `cd "$(…)"`, `~`, and equally `bash -c "$CMD"` —
432
+ the payload is not a literal token, so there is nothing to unwrap. These pass through by design;
433
+ `check verify-command-paths` reports them as `dynamic_path` before execution, and
434
+ `$(git rev-parse --show-toplevel)` is the form step 0b recommends.
435
+ - **A command boundary outside `$_OPEN`'s set — and the set is matched against RAW TEXT.** `&&` `&`
436
+ `;` `|` `(` `{` open a command. Control keywords (`if cd ../main; then …`) are deliberately NOT in
437
+ the set: these operators have no awareness of quoting, so a keyword that is also an ordinary English
438
+ word turns prose inside a quoted argument into a boundary — `grep -F 'if cd ../main; then' README.md`
439
+ halted on a command that relocates nothing. The same quote-blindness is why
440
+ `echo 'note; bash -c "cd ../main"'` halts on its `;`: an over-halt on quoted data is the standing
441
+ cost of scanning text, and it is paid in the loud direction.
442
+ - **A launcher outside `$_LAUNCH`'s set**, and therefore an interpreter or directory flag behind it.
443
+ `env` `timeout` `nohup` `stdbuf` `nice` `ionice` `setsid` are recognized, named by bare word or
444
+ absolute path, with options that take a separate operand (`env -u FOO`, `timeout --signal TERM`).
445
+ The set is named rather than a catch-all deliberately: a catch-all also unwrapped interpreter text
446
+ that was merely an *argument* (`echo bash -c '…'`), and halting on a command that executes nothing
447
+ is the false-positive direction. `command` and `exec` are excluded for that same reason —
448
+ `command -v bash` is a name probe.
449
+ - **An interpreter outside `$_EXEC`'s set.** The set is stated because the entries above name only the
450
+ handful a planner reaches for most, which reads as the whole of it — `perl -e 'chdir "/main"'` IS
451
+ unwrapped and rescanned. Recognized, each named by a bare word OR an absolute path: `sh` `bash`
452
+ `zsh` `dash` `ksh`, and `python3`/`python` `node` `perl` `ruby`. The option is a short-option
453
+ CLUSTER, not the literal flag — `-[a-z]*c` for the shells and `-[a-z]*[ce]` for the rest — so
454
+ `-c`, `-ec` and even `-abc` all match. Recognized as a BARE WORD ONLY, with no absolute-path
455
+ form: `eval`; `/bin/eval 'cd ../main'` is NOT seen (driven).
456
+ Not recognized: an interpreter with no entry (`awk`, `php`, a shell not listed), whose payload stays
457
+ one opaque token, so only an absolute path written as a bare word in it is caught by the catch-all
458
+ scan. **The two lists are delimited on purpose** — the parity test in
459
+ `tests/executor-mvp-tdd-section.test.cjs` reads only the span between `Recognized,` and
460
+ `Not recognized:`, matching BACKTICKED TOKENS rather than substrings, because a containment check over the whole section is satisfied by a name that
461
+ appears here saying it is UNsupported: adding `awk` to `$_EXEC` with this prose untouched passed
462
+ that test, driven by the pre-push review of the round that added it.
463
+ - **An interpreter option that takes an operand.** `bash --noprofile -c` is recognized;
464
+ `bash -O extglob -c` is not, because `_EXEC` admits long options without values, and widening it to
465
+ consume operands risks swallowing the `-c` it is looking for.
466
+ - **A genuine `command bash -c …` or `exec bash -c …` is not unwrapped.** Those two words were removed
467
+ from `$_LAUNCH` to stop `command -v bash` — a name probe that executes nothing — from halting. A
468
+ deliberate trade of a rare false negative for a common false positive, recorded so it reads as a
469
+ decision rather than an omission.
470
+ - **A payload behind a RELOCATING launcher is scanned from the command's cwd, not the launcher's.**
471
+ `env -C scripts bash -c 'cd ..'` lands back inside the worktree in reality, and halts here, because
472
+ the `-C` target is checked but is not threaded into the payload's starting cwd. An over-halt, in the
473
+ loud direction, on a shape a planner is unlikely to write.
474
+ - **An operand-taking option ordering outside the grammar.** Options that take a separate operand
475
+ are matched interleaved with ordinary ones, so `env -u FOO --debug -u BAR -C ../main` is seen; a
476
+ spelling outside each tool's own list still is not.
477
+ - **A launcher form outside the grammar**: `env -S 'cmd args'`, a launcher named through a quoted
478
+ string, or an option-with-operand spelling not in the tool's list. Which options take an operand is
479
+ TOOL-SCOPED (`-i` takes one on `stdbuf`, none on `env`); a flat list consumed the command itself
480
+ after an operandless flag.
481
+ - **`_DIR`'s tool-to-flag run is still a catch-all**, so a pass-through argument that happens to spell
482
+ a directory flag can over-halt: `npm run package -- --prefix=../main/` halts although the flag goes
483
+ to the script, not to npm. Pre-existing, and again in the loud direction.
484
+ - **A non-shell payload whose path is computed.** `python3 -c` / `node -e` payloads are scanned *as
485
+ shell text*, which catches a path written as a literal word (`os.chdir('/main')`) and not one the
486
+ program assembles (`os.path.join(root, '..')`).
487
+ - **Wrapping nested more than four deep.** `_walk` bounds its own recursion.
488
+ - **A directory flag on a tool outside the recognized set.** `--prefix`/`--cwd`/`--dir`/`-C` on `npm`
489
+ `npx` `pnpm` `yarn`, `-C`/`--directory` on `make` `git`, and `--chdir`/`-C` on `env` itself — the
490
+ last because `env` is not only a launcher, it relocates — including behind a launcher. The
491
+ scoping is per-tool because the same spelling is not the same flag everywhere: `git archive
492
+ --prefix=` names archive members and changes no directory. For an unrecognized tool the **absolute**
493
+ form of its flag is still caught by the catch-all; what is uncovered is the **relative** form.
494
+
495
+ The enumerations are the parts that rot: they mirror a toolchain this file does not own. Adding a tool
496
+ is one alternation in `_DIR` plus a row in each of the two tables in
497
+ `tests/executor-mvp-tdd-section.test.cjs`. The guard is the executor's last containment check, not its
498
+ only one — a halt it misses still has to get past `check verify-command-paths` at plan time.
@@ -21,8 +21,14 @@ These files live directly at `.planning/` — not inside phase subdirectories.
21
21
  | `LEARNINGS.md` | *(inline)* | `/gsd:extract-learnings`, `/gsd:execute-phase` (gated: `features.global_learnings`) | Phase retrospective learnings for future plans |
22
22
  | `THREADS.md` | *(inline)* | `/gsd:thread` | Persistent discussion threads |
23
23
  | `config.json` | `config.json` | `/gsd:new-project`, `/gsd:health --repair` | Project-specific GSD configuration |
24
- | `CLAUDE.md` | `claude-md.md` | `/gsd-profile` | Auto-assembled Claude Code context file |
24
+ | `CLAUDE.md` | *(inline)* | `/gsd-profile` | Auto-assembled Claude Code context file |
25
25
  | `RETROSPECTIVE.md` | *(inline)* | `/gsd:complete-milestone` | Living milestone retrospective updated at each milestone close |
26
+ | `WINDOWS.md` | *(none)* | broken-windows ledger (`src/broken-windows.cts`) | Tracked known-broken items pending resolution (#3224) |
27
+ | `STATE-ARCHIVE.md` | *(none)* | `state.cts`'s `cmdStatePrune` | Pruned historical STATE.md entries |
28
+ | `milestone.lock` | *(none)* | `src/milestone-lock.cts` | Persistent milestone (phase + session) claim, unlike the transient `STATE.md.lock`/`WAITING.json` (#3311) |
29
+ | `state.json` | *(none)* | `src/state-contract.cts` | Machine-readable state contract published at step boundaries (#3227) |
30
+ | `skill-manifest.json` | *(none)* | `init.cts`'s `cmdSkillManifest --write` | Project-scoped skill manifest (#3964) |
31
+ | `PATTERNS.md` | *(inline)* | `/gsd:extract-learnings` (graduation, `workflows/graduation.md`, `patterns` target) | Graduated cross-phase patterns -- distinct from the per-phase `NN-PATTERNS.md` below (#4282) |
26
32
 
27
33
  ### Version-stamped artifacts (pattern: `vX.Y-*.md`)
28
34
 
@@ -172,9 +172,12 @@ Updated after each plan completion.
172
172
  **Decisions:** Reference to PROJECT.md Key Decisions table, plus recent decisions summary for quick access. Full decision log lives in PROJECT.md.
173
173
 
174
174
  **Pending Todos:** Ideas captured via /gsd-add-todo
175
- - Count of pending todos
176
- - Reference to .planning/todos/pending/
177
- - Brief list if few, count if many (e.g., "5 pending todos — see /gsd:capture --list")
175
+ - One bullet per pending todo, rendered by `init.todos`'s `pending_todos_markdown`
176
+ (each bullet capped at 240 characters: `- [date] [area] title — [todo file](path) — Needs ...`;
177
+ the todo-file link is repo-relative, so the cap does not depend on checkout path length)
178
+ - `None yet.` when there are no pending todos
179
+ - No collapse-by-count fallback — every pending todo gets its own line, always
180
+ (see #2618 design doc for why a "count if many" fallback was rejected)
178
181
 
179
182
  **Blockers/Concerns:** From "Next Phase Readiness" sections
180
183
  - Issues that affect future work