@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
@@ -15,7 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
15
15
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.NO_LOCAL_CONFIG_DIR_SENTINEL = void 0;
18
+ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = exports.RetiredRuntimeError = exports.RETIRED_RUNTIME_IDS = void 0;
19
+ exports.isRetiredRuntimeId = isRetiredRuntimeId;
20
+ exports.assertNotRetiredRuntime = assertNotRetiredRuntime;
19
21
  exports.canonicalizeRuntimeName = canonicalizeRuntimeName;
20
22
  exports.resolveRuntimeNameFromCandidates = resolveRuntimeNameFromCandidates;
21
23
  exports.getProjectInstructionFile = getProjectInstructionFile;
@@ -26,6 +28,105 @@ exports.runtimeFlags = runtimeFlags;
26
28
  exports.getRuntimeNewProjectCommand = getRuntimeNewProjectCommand;
27
29
  const node_fs_1 = __importDefault(require("node:fs"));
28
30
  const node_path_1 = __importDefault(require("node:path"));
31
+ /**
32
+ * Runtime ids that GSD deliberately RETIRED, as opposed to ids it has simply
33
+ * never heard of. The distinction is load-bearing: an unknown id degrades to a
34
+ * safe cross-agent default on purpose (the #1529 contract in
35
+ * `getProjectInstructionFile` below), because GSD cannot know what a future
36
+ * runtime wants. A retired id is the opposite case — there is a recorded
37
+ * decision that it is gone and a named successor to point at, so resolving it
38
+ * to a DIFFERENT product's label and config home is a silent wrong answer.
39
+ *
40
+ * Measured before this guard existed: `getGlobalConfigDir('gemini')` and
41
+ * `getGlobalConfigDir('claude')` returned byte-identical paths, so asking for a
42
+ * runtime Google sunset on 2026-06-18 wrote into Claude Code's config home and
43
+ * labelled the install "Claude Code".
44
+ *
45
+ * Same table shape as `RETIRED_RUNTIMES` in
46
+ * `scripts/lint-retired-runtime-name.cjs` (#4753), which guards PROSE at build
47
+ * time while this guards IDS at runtime. They are deliberately NOT shared code
48
+ * — coupling them would make a build-time lint depend on compiled `src/` output
49
+ * it does not otherwise need — so a parity test asserts the two sets agree
50
+ * (CLAUDE.md, Generative Fix Divergence).
51
+ */
52
+ const RETIRED_RUNTIME_DETAILS = new Map([
53
+ ['gem' + 'ini', { successor: 'Antigravity', retiredBy: '#1928', sunset: '2026-06-18' }],
54
+ ]);
55
+ /**
56
+ * Normalise a candidate id for retirement matching: NFKC-fold, lowercase, and
57
+ * drop every non-alphanumeric character.
58
+ *
59
+ * Folding the separators is what makes `gemini-cli`, `gemini_cli`,
60
+ * `gemini.cli` and `geminicli` one key instead of four near-misses a reviewer
61
+ * has to find one at a time — and NFKC folds the full-width `gemini` a CJK
62
+ * keyboard produces. It stays MEMBERSHIP matching, not a prefix or substring
63
+ * test: `gemini-2.5-pro` folds to `gemini25pro` and `gemini-3.1-pro-preview`
64
+ * to `gemini31propreview`, neither of which is a member, so Google's live
65
+ * model ids — part of Antigravity's real on-disk contract — are untouched.
66
+ *
67
+ * Homoglyph folding is deliberately NOT attempted. A Cyrillic `і` in place of
68
+ * `i` would slip through, and that is accepted: these values arrive from argv
69
+ * and env, which are trusted inputs here, and a mapping broad enough to catch
70
+ * deliberate homoglyphs would start catching legitimate ids.
71
+ */
72
+ function normalizeForRetirementMatch(value) {
73
+ return value.normalize('NFKC').toLowerCase().replace(/[^a-z0-9]/g, '');
74
+ }
75
+ /**
76
+ * Every spelling that names a retired runtime, mapped to its canonical id.
77
+ * A Map, not an object literal: an object literal indexed by a computed key
78
+ * resolves INHERITED properties, so `'__proto__'` and `'constructor'` were
79
+ * truthy and threw with every field `undefined` — while `isRetiredRuntimeId`,
80
+ * which already went through a Set, correctly answered false for the same
81
+ * input. Two guards disagreeing about one id is worse than either answer.
82
+ * A Map has no prototype keys, so the hazard is structural, not patched.
83
+ */
84
+ const RETIRED_RUNTIME_SPELLINGS = new Map(Array.from(RETIRED_RUNTIME_DETAILS.keys()).flatMap((id) => [
85
+ [normalizeForRetirementMatch(id), id],
86
+ [normalizeForRetirementMatch(`${id}-cli`), id],
87
+ ]));
88
+ /**
89
+ * The canonical retired runtime ids, lowercase. Deliberately the canonical ids
90
+ * ONLY, not their alias spellings, so the parity assertion against
91
+ * `scripts/lint-retired-runtime-name.cjs`'s `RETIRED_RUNTIMES` compares like
92
+ * with like.
93
+ */
94
+ exports.RETIRED_RUNTIME_IDS = new Set(RETIRED_RUNTIME_DETAILS.keys());
95
+ /** Error thrown when a retired runtime id reaches a resolution accessor. */
96
+ class RetiredRuntimeError extends Error {
97
+ code = 'GSD_RETIRED_RUNTIME';
98
+ runtimeId;
99
+ constructor(runtimeId, detail) {
100
+ super(`Runtime "${runtimeId}" was retired by ${detail.retiredBy} (sunset ${detail.sunset}); `
101
+ + `use "${detail.successor}" instead. Refusing to resolve it, because the previous `
102
+ + `behaviour silently returned Claude Code's values.`);
103
+ this.name = 'RetiredRuntimeError';
104
+ this.runtimeId = runtimeId;
105
+ }
106
+ }
107
+ exports.RetiredRuntimeError = RetiredRuntimeError;
108
+ /**
109
+ * Is `runtime` a deliberately retired runtime, under any of its spellings?
110
+ * Shares one normaliser and one table with `assertNotRetiredRuntime`, so the
111
+ * predicate and the assertion can never disagree.
112
+ */
113
+ function isRetiredRuntimeId(runtime) {
114
+ if (typeof runtime !== 'string')
115
+ return false;
116
+ return RETIRED_RUNTIME_SPELLINGS.has(normalizeForRetirementMatch(runtime));
117
+ }
118
+ /** Throw if `runtime` names a retired runtime. No-op otherwise. */
119
+ function assertNotRetiredRuntime(runtime) {
120
+ if (typeof runtime !== 'string')
121
+ return;
122
+ const id = RETIRED_RUNTIME_SPELLINGS.get(normalizeForRetirementMatch(runtime));
123
+ if (id === undefined)
124
+ return;
125
+ const detail = RETIRED_RUNTIME_DETAILS.get(id);
126
+ if (detail === undefined)
127
+ return;
128
+ throw new RetiredRuntimeError(id, detail);
129
+ }
29
130
  const FALLBACK_ALIASES = {
30
131
  claude: ['claude', 'claude-code', 'claude-cli'],
31
132
  opencode: ['opencode', 'open-code', 'opencode-cli'],
@@ -145,6 +246,7 @@ function resolveRuntimeNameFromCandidates(...candidates) {
145
246
  * avoid a circular dependency at module load.
146
247
  */
147
248
  function getProjectInstructionFile(runtime) {
249
+ assertNotRetiredRuntime(runtime);
148
250
  const canonical = canonicalizeRuntimeName(runtime);
149
251
  if (canonical === 'claude')
150
252
  return '.claude/CLAUDE.md';
@@ -198,6 +300,9 @@ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
198
300
  * compatibility spine had no production consumer at all.
199
301
  */
200
302
  function getDirName(runtime) {
303
+ // Same silent-wrong-answer class as the four accessors AC#1 names: this
304
+ // returned '.claude' for a retired id, and it feeds runtimeConfigDir.
305
+ assertNotRetiredRuntime(runtime);
201
306
  if (!runtime)
202
307
  return '.claude';
203
308
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -268,6 +373,7 @@ const RUNTIME_LABELS = {
268
373
  * 'Claude Code'. Sibling to `getDirName`; pure (no I/O).
269
374
  */
270
375
  function getRuntimeLabel(runtime) {
376
+ assertNotRetiredRuntime(runtime);
271
377
  if (!runtime)
272
378
  return 'Claude Code';
273
379
  const label = RUNTIME_LABELS[runtime];
@@ -323,6 +429,7 @@ const GLOBAL_CONFIG_HOME_FRAGMENTS = {
323
429
  * dynamically). Pure: no I/O. Sibling to `getDirName` / `getRuntimeLabel`.
324
430
  */
325
431
  function getGlobalConfigHomeFragment(runtime) {
432
+ assertNotRetiredRuntime(runtime);
326
433
  if (!runtime)
327
434
  return DEFAULT_CONFIG_HOME_FRAGMENT;
328
435
  const frag = GLOBAL_CONFIG_HOME_FRAGMENTS[runtime];
@@ -382,6 +489,9 @@ const RUNTIME_NEW_PROJECT_COMMANDS = {
382
489
  kimi: '/skill:gsd-new-project',
383
490
  };
384
491
  function getRuntimeNewProjectCommand(runtime) {
492
+ // Deliberately NOT retirement-guarded: this value does not vary by runtime
493
+ // in a way that makes a retired id a WRONG answer, so throwing here would
494
+ // cost callers a crash without correcting anything.
385
495
  if (!runtime)
386
496
  return DEFAULT_NEW_PROJECT_COMMAND;
387
497
  const c = RUNTIME_NEW_PROJECT_COMMANDS[runtime];
@@ -22,10 +22,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.MARKDOWN_LINK_PATTERNS = exports.INJECTION_PATTERNS = void 0;
26
- exports.validatePath = validatePath;
25
+ exports.MARKDOWN_LINK_PATTERNS = exports.INJECTION_PATTERNS = exports.PathAcceptance = void 0;
26
+ exports.isContainedIn = isContainedIn;
27
27
  exports.loadTrustedGlobalRoots = loadTrustedGlobalRoots;
28
+ exports.assertWithinRoot = assertWithinRoot;
29
+ exports.tryWithinRoot = tryWithinRoot;
28
30
  exports.requireSafePath = requireSafePath;
31
+ exports.tryWithinRootLexical = tryWithinRootLexical;
32
+ exports.assertWithinRootLexical = assertWithinRootLexical;
29
33
  exports.scanForInjection = scanForInjection;
30
34
  exports.sanitizeForPrompt = sanitizeForPrompt;
31
35
  exports.sanitizeForDisplay = sanitizeForDisplay;
@@ -39,6 +43,31 @@ const node_fs_1 = __importDefault(require("node:fs"));
39
43
  const node_os_1 = __importDefault(require("node:os"));
40
44
  const node_path_1 = __importDefault(require("node:path"));
41
45
  // ─── Path Traversal Prevention ──────────────────────────────────────────────
46
+ /**
47
+ * THE containment comparison — the single place this repo decides whether an
48
+ * already-resolved path lies inside an already-resolved root (ADR-4650).
49
+ *
50
+ * Separator-aware on purpose: comparing the bare strings would accept a
51
+ * sibling that merely shares a prefix (`<root>-evil` against `<root>`), so both
52
+ * sides get a trailing separator before the prefix test. `target === root` is
53
+ * contained.
54
+ *
55
+ * `pathImpl` lets a caller supply `path.win32` / `path.posix` instead of the
56
+ * ambient module, so win32 separator semantics are testable off Windows.
57
+ *
58
+ * Exported for callers that have ALREADY resolved both operands themselves
59
+ * and need only this comparison step (e.g. a caller that owns its own
60
+ * `fs.realpathSync` calls to preserve an exists-vs-escaped tri-state). A
61
+ * caller that has NOT resolved its operands must NOT reach for this function
62
+ * directly — the comparison alone is not a containment check — and should use
63
+ * `assertWithinRoot` / `tryWithinRoot` (or the `assertWithinRootLexical` /
64
+ * `tryWithinRootLexical` pair) instead.
65
+ */
66
+ function isContainedIn(resolvedTarget, resolvedRoot, pathImpl = node_path_1.default) {
67
+ if (resolvedTarget === resolvedRoot)
68
+ return true;
69
+ return (resolvedTarget + pathImpl.sep).startsWith(resolvedRoot + pathImpl.sep); // allow-handrolled-containment: this IS the canonical comparison every other site routes through
70
+ }
42
71
  /**
43
72
  * Validate that a file path resolves within an allowed base directory.
44
73
  * Prevents path traversal attacks via ../ sequences, symlinks, or absolute paths.
@@ -122,9 +151,7 @@ function validatePath(filePath, baseDir, opts = {}) {
122
151
  }
123
152
  }
124
153
  }
125
- const normalizedBase = resolvedBase + node_path_1.default.sep;
126
- const normalizedPath = resolvedPath + node_path_1.default.sep;
127
- if (resolvedPath !== resolvedBase && !normalizedPath.startsWith(normalizedBase)) {
154
+ if (!isContainedIn(resolvedPath, resolvedBase)) {
128
155
  return {
129
156
  safe: false,
130
157
  resolved: resolvedPath,
@@ -204,17 +231,109 @@ function loadTrustedGlobalRoots(config) {
204
231
  }
205
232
  return result;
206
233
  }
234
+ /**
235
+ * Named acceptance policy for what kind of candidate path is even considered.
236
+ *
237
+ * This replaces the old per-call-site `{ allowAbsolute: true }` boolean flag.
238
+ * At a call site, `{ allowAbsolute: true }` reads as "containment is relaxed
239
+ * here" — which is FALSE. An absolute path that resolves OUTSIDE the root is
240
+ * still rejected; the flag only ever controlled whether an absolute candidate
241
+ * was considered at all. `AbsoluteInsideRoot` states the real contract: an
242
+ * absolute candidate is accepted for consideration, but containment is
243
+ * enforced exactly as it is for a relative one.
244
+ */
245
+ exports.PathAcceptance = {
246
+ /** Relative candidates only; an absolute candidate is rejected outright. */
247
+ RelativeOnly: 'relative-only',
248
+ /**
249
+ * An absolute candidate is accepted — but ONLY if it still resolves inside the
250
+ * root. Containment is NOT relaxed by this policy; an absolute path outside the
251
+ * root is rejected exactly as a traversal is. This is the distinction the old
252
+ * `{ allowAbsolute: true }` flag failed to make at its call sites.
253
+ */
254
+ AbsoluteInsideRoot: 'absolute-inside-root',
255
+ };
207
256
  /**
208
257
  * Validate a file path and throw on traversal attempt.
209
258
  * Convenience wrapper around validatePath for use in CLI commands.
210
259
  */
211
- function requireSafePath(filePath, baseDir, label, opts = {}) {
212
- const result = validatePath(filePath, baseDir, opts);
260
+ function assertWithinRoot(candidate, root, label, policy = exports.PathAcceptance.RelativeOnly) {
261
+ const result = validatePath(candidate, root, { allowAbsolute: policy === exports.PathAcceptance.AbsoluteInsideRoot });
213
262
  if (!result.safe) {
214
263
  throw new Error(`${label || 'Path'} validation failed: ${result.error}`);
215
264
  }
216
265
  return result.resolved;
217
266
  }
267
+ /**
268
+ * Validate a file path and return null on traversal attempt (no throw).
269
+ *
270
+ * Returns exactly `null` when unsafe — never `''`, never `result.resolved`.
271
+ * `validatePath` populates `resolved` with the ESCAPING path on the
272
+ * traversal branch, so returning it here would reproduce the defect this
273
+ * narrowing exists to remove.
274
+ */
275
+ function tryWithinRoot(candidate, root, policy = exports.PathAcceptance.RelativeOnly) {
276
+ const result = validatePath(candidate, root, { allowAbsolute: policy === exports.PathAcceptance.AbsoluteInsideRoot });
277
+ if (!result.safe) {
278
+ return null;
279
+ }
280
+ return result.resolved;
281
+ }
282
+ /**
283
+ * Validate a file path and throw on traversal attempt.
284
+ * Convenience wrapper around validatePath for use in CLI commands.
285
+ *
286
+ * Delegates to assertWithinRoot so there is one implementation beneath both
287
+ * names; its declared return type is ContainedPath (a branded string, still
288
+ * assignable to string) so existing callers keep compiling untouched.
289
+ */
290
+ function requireSafePath(filePath, baseDir, label, policy = exports.PathAcceptance.RelativeOnly) {
291
+ return assertWithinRoot(filePath, baseDir, label, policy);
292
+ }
293
+ /**
294
+ * LEXICAL containment — `path.resolve` only, never any filesystem access.
295
+ *
296
+ * Shares `isContainedIn` with the realpath-based predicate, so there is ONE
297
+ * containment decision in this repo; these differ only in how a path is
298
+ * RESOLVED before that decision, never in the decision itself (ADR-4650
299
+ * decisions 1 and 6).
300
+ *
301
+ * Use this — and say why at the call site — only where a symlink must be
302
+ * PRESERVED rather than resolved, or where the target legitimately does not
303
+ * exist yet. Three such cases exist: a destination validated before the
304
+ * `mkdirSync` that creates it, a migration that snapshots and restores a
305
+ * symlinked path AS A LINK, and a restore gate that refuses links outright.
306
+ * Everywhere else the realpath-based `assertWithinRoot` / `tryWithinRoot` is
307
+ * the correct predicate, because a lexical check CANNOT SEE A SYMLINK: a
308
+ * caller relying on one for a write-confinement guarantee must pair it with
309
+ * its own symlink refusal.
310
+ *
311
+ * `candidate` is resolved RELATIVE TO `root` (so an absolute candidate is
312
+ * taken as-is, matching `path.resolve` semantics). `target === root` is
313
+ * contained.
314
+ *
315
+ * DELIBERATELY ABSENT: no NUL-byte rejection here. The existing lexical
316
+ * callers do not reject NUL at this layer (one of them checks NUL itself,
317
+ * separately), and adding it here would change their behavior. Callers that
318
+ * need it keep their own check.
319
+ */
320
+ function tryWithinRootLexical(candidate, root, opts = {}) {
321
+ const p = opts.pathImpl || node_path_1.default;
322
+ if (typeof candidate !== 'string' || candidate === '')
323
+ return null;
324
+ if (typeof root !== 'string' || root === '')
325
+ return null;
326
+ const rootResolved = p.resolve(root);
327
+ const targetResolved = p.resolve(root, candidate);
328
+ return isContainedIn(targetResolved, rootResolved, p) ? targetResolved : null;
329
+ }
330
+ function assertWithinRootLexical(candidate, root, label, opts = {}) {
331
+ const contained = tryWithinRootLexical(candidate, root, opts);
332
+ if (contained === null) {
333
+ throw new Error(`${label || 'Path'} validation failed: lexical containment check failed`);
334
+ }
335
+ return contained;
336
+ }
218
337
  // ─── Prompt Injection Detection ────────────────────────────────────────────────────
219
338
  /**
220
339
  * Patterns that indicate prompt injection attempts in user-supplied text.
@@ -263,6 +263,7 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
263
263
  'gsd-session-state.sh',
264
264
  'gsd-validate-commit.sh',
265
265
  'gsd-phase-boundary.sh',
266
+ 'gsd-graphify-update.sh',
266
267
  // #3662: same three guards as MANAGED_HOOK_BASENAMES_BY_SURFACE above —
267
268
  // their absence here meant isManagedHookCommand never recognized them, so
268
269
  // the settings.json→settings.local.json migration filter (and uninstall
@@ -1068,12 +1069,15 @@ function _normalizeMd(content) {
1068
1069
  if (i === 0 || !insideFence[i - 1])
1069
1070
  result.push('');
1070
1071
  }
1071
- // #3854: the `!/^\s/.test(prev)` guard mirrors the after-a-bullet rule below —
1072
- // an indented non-bullet line is a CONTINUATION of the previous list item, not a
1073
- // preceding paragraph, so no separating blank may be injected before this bullet
1074
- // (that injection converted every tight multi-line list to a loose one on write).
1075
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 && prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) && !/^\s/.test(prev) && prevTrimmed !== '---')
1076
- result.push('');
1072
+ // No "separate a list from a preceding paragraph" rule (#3854, #4725).
1073
+ // This pass re-normalizes the ENTIRE document on every .md write, so an
1074
+ // inserted blank before a bullet whose previous line is ordinary prose
1075
+ // reflowed text the command never touched (converting tight lists to
1076
+ // loose ones); #3854 first guarded the indented-continuation predecessor,
1077
+ // #4725 removed the rule outright. A paragraph→list transition stays
1078
+ // exactly as the author wrote it; a heading→list separation still comes
1079
+ // from the after-heading rule below. The after-a-bullet rule at the end
1080
+ // of this loop is a different transition (list→prose) and is unaffected.
1077
1081
  result.push(line);
1078
1082
  if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '')
1079
1083
  result.push('');
@@ -8,7 +8,7 @@
8
8
  * from the prior hand-written .cjs; only types are added.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
11
+ exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = exports.STATUS_ANCHORED_PATTERNS = exports.STATUS_EXACT_TOKENS = void 0;
12
12
  exports.toFiniteNumber = toFiniteNumber;
13
13
  exports.isUnfilledFieldValue = isUnfilledFieldValue;
14
14
  exports.leadingCalendarDate = leadingCalendarDate;
@@ -364,7 +364,7 @@ function stateFieldContinuation(content, fieldName) {
364
364
  // this locates exactly the line whose value it returned. The pipe-table rung
365
365
  // is deliberately absent: a `| Field | value |` row is bounded by its closing
366
366
  // pipe and cannot wrap.
367
- const match = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i').exec(content) ??
367
+ const match = new RegExp(`^[ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'im').exec(content) ??
368
368
  new RegExp(`^${escaped}:[ \\t]*(.+)`, 'im').exec(content);
369
369
  if (!match)
370
370
  return null;
@@ -396,8 +396,9 @@ function stateFieldContinuation(content, fieldName) {
396
396
  }
397
397
  function stateExtractField(content, fieldName) {
398
398
  const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
399
- // Bold inline format: **FieldName:** value
400
- const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
399
+ // Bold line-start format: **FieldName:** value. Leading same-line whitespace
400
+ // matches the writer's established indented-field tolerance.
401
+ const boldPattern = new RegExp(`^[ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'im');
401
402
  const boldMatch = content.match(boldPattern);
402
403
  if (boldMatch)
403
404
  return boldMatch[1].trim();
@@ -540,7 +541,22 @@ function stateReplaceField(content, fieldName, newValue) {
540
541
  // `(.*)` captured the following line and the rebuild discarded it — the #4010
541
542
  // data-loss. ADR-3180 §7.7 makes stateExtractField the same-line-confined owner;
542
543
  // this aligns the writer to it.
543
- const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)`, 'i');
544
+ //
545
+ // #4243: the bold form is also ANCHORED to line start, with same-line leading
546
+ // whitespace only. The pre-fix pattern carried no `^` and no `m` flag, so a
547
+ // bold label quoted MID-SENTENCE inside prose — an Accumulated Context bullet
548
+ // mentioning `**Status:**` — captured the rewrite and destroyed the rest of
549
+ // its line, silently, whenever a whole-body caller fed this function every
550
+ // section (beginPhaseCore's tryField, advancePlanCore's Status/Current Plan
551
+ // writes). The plain branch below was always line-anchored; only the bold
552
+ // branch lagged. Anchoring reuses #4010's same-line confinement idiom (the
553
+ // leading class is `[ \t]*`, deliberately NOT the `\s*` the issue suggested —
554
+ // `^\s*\*\*` can consume the newlines before the label into the match and
555
+ // drop them on rebuild) and #4186's recognition-by-anchoring discipline: a
556
+ // write target must BE the whole declared line shape, never a substring
557
+ // guess inside prose. `$` is explicit-and-inert (`.` never crosses line
558
+ // terminators) and documents that the match ends at end-of-line.
559
+ const boldPattern = new RegExp(`^([ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)$`, 'im');
544
560
  if (boldPattern.test(content)) {
545
561
  return content.replace(boldPattern, (_match, prefix) => joinFieldReplacement(prefix, newValue));
546
562
  }
@@ -595,31 +611,117 @@ function stateReplaceFieldInSession(content, primary, fallback, value) {
595
611
  const target = hasCanonicalSession ? isSession : isSessionContinuity;
596
612
  return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
597
613
  }
614
+ /**
615
+ * #4186: the DECLARED raw-status vocabulary `normalizeStateStatus` recognizes.
616
+ * Keys are whole-field values, compared against the caller's input after
617
+ * lowercasing, trimming, and collapsing internal whitespace runs to single
618
+ * spaces — so case and whitespace variants the vocabulary documents
619
+ * (`EXECUTING PHASE 5`, ` Paused `, `In progress`) keep normalizing.
620
+ * Values are members of `STATUS_LIFECYCLE_ENUM` (`src/state-md-schema.cts`)
621
+ * — the set the normalizer maps recognized input ONTO.
622
+ *
623
+ * The mapping preserves the PRE-#4186 branch ORDER's observable artifacts for
624
+ * every value the old substring chain recognized: `Planning complete` →
625
+ * `planning` (the `planning` branch outranked `complete`) and
626
+ * `Phase complete — ready for verification` → `verifying` (`verif` outranked
627
+ * `complete`; pinned by tests/state.test.cjs's advance-plan case-5 comment).
628
+ *
629
+ * Everything else — prose that merely CONTAINS a status word — falls through
630
+ * to the caller's raw value (the recorded lenient fallback, #3873 phase-3
631
+ * row 26). A token guessed from a substring inside a sentence is worse than
632
+ * a visible paragraph: the paragraph is visibly prose, the wrong token is
633
+ * not (a `.planning/` path in Italian prose silently produced
634
+ * `status: planning`; `verificata` produced `verifying`; `completezza`
635
+ * produced `completed`).
636
+ */
637
+ exports.STATUS_EXACT_TOKENS = Object.freeze({
638
+ paused: 'paused',
639
+ stopped: 'paused',
640
+ executing: 'executing',
641
+ 'in progress': 'executing',
642
+ 'ready to execute': 'executing',
643
+ planning: 'planning',
644
+ 'ready to plan': 'planning',
645
+ 'planning complete': 'planning',
646
+ discussing: 'discussing',
647
+ verifying: 'verifying',
648
+ completed: 'completed',
649
+ done: 'completed',
650
+ complete: 'completed',
651
+ 'phase complete': 'completed',
652
+ // advance-plan's phase-complete write (state-transition.cts:1812) — maps
653
+ // to `verifying`, preserving the pre-#4186 branch order where `verif`
654
+ // outranked `complete`.
655
+ 'phase complete — ready for verification': 'verifying',
656
+ 'all phases complete': 'completed',
657
+ // Legacy bare terminal form. ADR-2207/#2204 removed it from every WRITER
658
+ // (phase verbs write `All phases complete`; milestone close writes
659
+ // `<version> milestone complete`) — kept here as READER recognition so a
660
+ // legacy STATE.md still normalizes, exactly the way KNOWN_TEMPLATE_DEFAULTS
661
+ // keeps the other legacy Status strings.
662
+ 'milestone complete': 'completed',
663
+ unknown: 'unknown',
664
+ });
665
+ /**
666
+ * #4186: ANCHORED patterns for handler-written raw statuses whose text
667
+ * carries a variable component (a phase number, a milestone version, a
668
+ * #1070 completion glyph). Each pattern is matched against the same
669
+ * normalized key as `STATUS_EXACT_TOKENS` (lowercased, trimmed,
670
+ * whitespace-collapsed) and must match the WHOLE value — never a substring —
671
+ * mirroring how `KNOWN_STATUS_PATTERNS` anchors its template-default checks.
672
+ * `Executing Phase 5 — final stretch` (executor-appended prose) matches
673
+ * NOTHING and passes through verbatim, the same discipline #1070 applies to
674
+ * "Complete but needs manual QA".
675
+ */
676
+ exports.STATUS_ANCHORED_PATTERNS = Object.freeze([
677
+ // begin-phase / planned transitions write `Executing Phase ${N}`.
678
+ [/^executing phase\s+\S+$/, 'executing'],
679
+ [/^planning phase\s+\S+$/, 'planning'],
680
+ [/^verifying phase\s+\S+$/, 'verifying'],
681
+ // phase-complete verbs write `Phase ${N} complete` (state.cts) — the exact
682
+ // shape the #3578 demote guard then re-checks against the disk counters.
683
+ [/^phase\s+\S+\s+complete$/, 'completed'],
684
+ // milestoneCompleteCore writes `${version} milestone complete` (terminal,
685
+ // ADR-2207); the version label is a single token (milestone.cts's charset
686
+ // validation admits letters, digits, '.', '-', '_').
687
+ [/^\S+\s+milestone complete$/, 'completed'],
688
+ // #1070: LLM executors may write "Complete ✓" or bare "Complete" when
689
+ // finishing a phase.
690
+ [/^complete\s*[✓✔✅☑]?$/, 'completed'],
691
+ ]);
692
+ /**
693
+ * Normalize a raw `Status` body-field value to the canonical status token.
694
+ *
695
+ * #4186: recognition is ANCHORED — the whole field value (lowercased,
696
+ * trimmed, whitespace-collapsed) must be a member of the declared vocabulary
697
+ * (`STATUS_EXACT_TOKENS` / `STATUS_ANCHORED_PATTERNS` above). The pre-#4186
698
+ * implementation ran a first-match-wins chain of SUBSTRING tests over the
699
+ * free-prose field, so any prose merely CONTAINING a trigger word was
700
+ * silently rewritten to a credible wrong token: a `.planning/...` path
701
+ * mentioned in a non-English status line landed on `planning` (the trigger
702
+ * word lives in the directory name and outranked the `verif`/`complete`
703
+ * branches), Italian `verifica*` landed on `verifying`, `completezza` and
704
+ * `fasi complete` landed on `completed`. The lenient FALLBACK is unchanged
705
+ * and recorded (#3873 phase-3 row 26): an unrecognized value passes through
706
+ * verbatim — visible prose, never a guessed token.
707
+ *
708
+ * `pausedAt` keeps its documented force (issue #4186: intended behavior): a
709
+ * truthy value yields `paused` regardless of the prose.
710
+ */
598
711
  function normalizeStateStatus(status, pausedAt) {
599
- let normalizedStatus = status || 'unknown';
600
- const statusLower = (status || '').toLowerCase();
601
- if (statusLower.includes('paused') || statusLower.includes('stopped') || pausedAt) {
602
- normalizedStatus = 'paused';
603
- }
604
- else if (statusLower.includes('executing') || statusLower.includes('in progress')) {
605
- normalizedStatus = 'executing';
606
- }
607
- else if (statusLower.includes('planning') || statusLower.includes('ready to plan')) {
608
- normalizedStatus = 'planning';
609
- }
610
- else if (statusLower.includes('discussing')) {
611
- normalizedStatus = 'discussing';
612
- }
613
- else if (statusLower.includes('verif')) {
614
- normalizedStatus = 'verifying';
615
- }
616
- else if (statusLower.includes('complete') || statusLower.includes('done')) {
617
- normalizedStatus = 'completed';
618
- }
619
- else if (statusLower.includes('ready to execute')) {
620
- normalizedStatus = 'executing';
712
+ if (pausedAt)
713
+ return 'paused';
714
+ if (!status)
715
+ return 'unknown';
716
+ const key = status.trim().toLowerCase().replace(/\s+/g, ' ');
717
+ const exact = exports.STATUS_EXACT_TOKENS[key];
718
+ if (exact)
719
+ return exact;
720
+ for (const [pattern, token] of exports.STATUS_ANCHORED_PATTERNS) {
721
+ if (pattern.test(key))
722
+ return token;
621
723
  }
622
- return normalizedStatus;
724
+ return status;
623
725
  }
624
726
  /**
625
727
  * ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
@@ -43,24 +43,31 @@ exports.STATE_FIELD_SCHEMA = exports.STATUS_LIFECYCLE_ENUM = void 0;
43
43
  /**
44
44
  * The seven CANONICAL values `normalizeStateStatus` (`src/state-document.cts`)
45
45
  * maps recognized raw status prose TO — the function's default fallback plus
46
- * each branch's literal output, in the order the function tests them. This is
47
- * NOT the raw body prose vocabulary `CONTEXT.md`'s "STATE.md Status Lifecycle
48
- * (ADR-2207)" entry documents (`Ready to plan` → `All phases complete` →
49
- * `<version> milestone complete` → `Awaiting next milestone`, plus the
50
- * handler-authored strings in `KNOWN_TEMPLATE_DEFAULTS['Status']`) — that is
51
- * free-form prose `normalizeStateStatus` READS.
46
+ * each vocabulary entry's literal output. This is NOT the raw body prose
47
+ * vocabulary `CONTEXT.md`'s "STATE.md Status Lifecycle (ADR-2207)" entry
48
+ * documents (`Ready to plan` → `All phases complete` → `<version> milestone
49
+ * complete` → `Awaiting next milestone`, plus the handler-authored strings
50
+ * in `KNOWN_TEMPLATE_DEFAULTS['Status']`) — that is free-form prose
51
+ * `normalizeStateStatus` READS.
52
52
  *
53
53
  * CORRECTED (#3873 phase-3 test-matrix row 26 — verified by executing
54
54
  * `normalizeStateStatus`, not by reading this docstring's prior claim):
55
55
  * this is NOT a closed set the `status` frontmatter key is restricted to at
56
- * runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback is
57
- * `normalizedStatus = status || 'unknown'`, and when none of its
58
- * substring-match branches recognize the raw input, that fallback — the
59
- * caller's raw, UNRECOGNIZED prose — is returned unchanged. A status value
60
- * outside this seven-member set is not rejected, coerced, or normalized; it
61
- * passes straight through into the frontmatter. `STATUS_LIFECYCLE_ENUM` is
62
- * therefore the set of values the normalizer maps recognized input ONTO, not
63
- * a runtime-enforced closed vocabulary for the field.
56
+ * runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback
57
+ * returns the caller's raw, UNRECOGNIZED prose unchanged — when none of its
58
+ * vocabulary entries recognize the whole-field input, that raw value is what
59
+ * the function returns. A status value outside this seven-member set is not
60
+ * rejected, coerced, or normalized; it passes straight through into the
61
+ * frontmatter. `STATUS_LIFECYCLE_ENUM` is therefore the set of values the
62
+ * normalizer maps recognized input ONTO, not a runtime-enforced closed
63
+ * vocabulary for the field.
64
+ *
65
+ * #4186: recognition is ANCHORED (whole-field match against the declared
66
+ * `STATUS_EXACT_TOKENS` / `STATUS_ANCHORED_PATTERNS` tables in
67
+ * `src/state-document.cts`), never a substring scan of the prose — prose
68
+ * merely CONTAINING a status word (a `.planning/` path, `verifica*`,
69
+ * `completezza`) passes through verbatim instead of being rewritten to a
70
+ * credible wrong token.
64
71
  */
65
72
  exports.STATUS_LIFECYCLE_ENUM = Object.freeze([
66
73
  'unknown',