@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
@@ -5,7 +5,9 @@
5
5
  * truth). Behaviour is preserved byte-for-behaviour from the prior hand-written
6
6
  * .cjs; only types are added.
7
7
  *
8
- * Accepts both numeric (D-42) and alphanumeric (D-INFRA-01) IDs.
8
+ * Accepts numeric (D-42), alphanumeric (D-INFRA-01), and phase-prefixed
9
+ * (D4-01 — an optional digit-run between the leading letter and the hyphen,
10
+ * #4130) IDs.
9
11
  * Returns {id, text, category, tags, trackable} per decision.
10
12
  * CJS callers that only use {id, text} safely ignore the extra fields.
11
13
  *
@@ -16,6 +18,11 @@
16
18
  * - Outer bullet loop → seam's `iterateBullets` (for the header-fallback path)
17
19
  *
18
20
  * Resolves #1364 (markdown-header + em-dash recall) and #1365 (fail-loud gate).
21
+ *
22
+ * #4130 follow-up (hardening): the three bullet grammars below consume the
23
+ * decision ID atomically and narrow the em-dash first separator, eliminating
24
+ * the quadratic-backtracking cliff on pathological single bullets. Output is
25
+ * byte-identical on all legal inputs — see the notes at DECISION_ID_SOURCE.
19
26
  */
20
27
  Object.defineProperty(exports, "__esModule", { value: true });
21
28
  exports.extractDecisions = extractDecisions;
@@ -30,39 +37,130 @@ const DISCRETION_HEADINGS = new Set([
30
37
  const NON_TRACKABLE_TAGS = new Set(['informational', 'folded', 'deferred']);
31
38
  // ─── Bullet parsers (decisions-specific grammar) ─────────────────────────────
32
39
  /**
33
- * Colon form: `- **D-NN[ [tags]]:** text`
34
- * (#1343: `[^:*]*` subsumes any pre-colon prose, stops at `:**`)
40
+ * #4130: the ID grammar every extractor regex below shares, as ONE source.
41
+ * `D`, an OPTIONAL digit-run phase prefix, a hyphen, then the pre-existing
42
+ * alphanumeric tail — so `D-01` (bare), `D4-01`/`D12-01` (phase-prefixed,
43
+ * the reporter's multi-phase convention where bare D-01 collides across
44
+ * phases), and `D-INFRA-01` (alnum tail) are all the same grammar now.
45
+ * #2347 had already taught the shape DETECTOR to call `D4-01` decision-shaped
46
+ * while the EXTRACTOR still anchored on the literal `**D-` — the disagreement
47
+ * that made a whole phase-prefixed CONTEXT.md report could-not-parse. Deriving
48
+ * the three grammars (and the token evidence below) from this one constant is
49
+ * the parity pin: the extractor's ID universe cannot drift from the declared
50
+ * grammar again without editing this line, which the #4130 property tests
51
+ * watch from the other side.
52
+ */
53
+ const DECISION_ID_SOURCE = 'D[0-9]*-[A-Za-z0-9][A-Za-z0-9_-]*';
54
+ /**
55
+ * #4130 follow-up (hardening): how the three grammars below CONSUME the ID —
56
+ * atomically, via the `(?=(X))\1` lookahead emulation (lookarounds are atomic
57
+ * in ECMAScript; the backreference must replay exactly what the lookahead
58
+ * captured, so the engine can never give the ID tail back one character at a
59
+ * time). That give-back was quadratic driver #1: the tail class
60
+ * `[A-Za-z0-9_-]*` overlaps the pre-separator class `[^:*]*` (every id char
61
+ * is also `[^:*]`), so on a FAILING bullet the base regex re-split the tail
62
+ * O(n) times with an O(n) scan after each — measured ~1.1s @ 40k chars on
63
+ * `- **D-` + `a-`×20k (the #4357 review's deferred cliff).
64
+ *
65
+ * Byte-identical on all legal inputs: a successful match always consumes the
66
+ * MAXIMAL id run (the lookahead's own match is exactly that maximal run), and
67
+ * the continuation's success depends only on the position of the first
68
+ * `:`/`*` (or `*` for the em-dash form) after the id boundary — id chars
69
+ * contain neither, so moving the boundary inside the run cannot change
70
+ * success or any capture. Group 1 stays the full id (the lookahead's capture
71
+ * IS group 1), so handlers keep reading match[1]/[2]/[3] untouched. Pinned by
72
+ * the differential property test against a frozen copy of the pre-hardening
73
+ * grammars and by the regex-lattice test in tests/decisions.test.cjs.
74
+ */
75
+ /**
76
+ * #4130: the bold lead-in that ATTEMPTS the ID grammar above — used by the
77
+ * parse-miss guard and the #3939 join regexes, where recognising MORE shapes
78
+ * is the conservative direction (an over-broad match can only make a
79
+ * malformed bullet fail loud). The prefix run is either empty (bare `D-`) or
80
+ * DIGIT-INITIAL (`4`, `4x` — a phase prefix with a typo still counts as an
81
+ * attempted ID, so `D4x-01` reaches the guard and fails loud instead of
82
+ * vanishing), but never letter-initial: `D` + letters + `-` (`Deferred-until`)
83
+ * is a prose word, and prose must stay `none-present` (#2347's law).
84
+ */
85
+ const ID_ATTEMPT_SOURCE = 'D(?:[0-9][A-Za-z0-9]*)?-';
86
+ /**
87
+ * Colon form: `- **D[phase]-NN[ [tags]]:** text`
88
+ * (#1343: the pre-colon run subsumes any pre-colon prose, stops at `:**`; #4788: the run is code-span-aware — a backticked span is consumed whole, so a `:` or `*` INSIDE it is data, never grammar)
89
+ * Group 1 captures the FULL id including any phase prefix (#4130).
90
+ * The ID is consumed atomically `(?=(…))\1` — see the hardening note above
91
+ * the constants (#4130 follow-up); with the tail unable to give back, the
92
+ * remaining `[^:*]*:` scan has a single viable split and the whole match is
93
+ * linear in line length.
35
94
  */
36
- const bulletColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:\*\*\s*(.*)$/;
95
+ const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|[^:*\\u0060])*:\\*\\*\\s*(.*)$`);
37
96
  /**
38
- * Em-dash form: `- **D-NN[ [tags]] — title** body`
97
+ * Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
39
98
  * The em-dash (U+2014) or its lookalike separates the ID+tags group from a title
40
99
  * that lives inside the bold markers; the body (which may be empty) follows
41
100
  * outside the closing `**`. This form was not handled pre-T1 (bug #1364).
42
101
  *
43
102
  * Accepts both U+2014 em-dash (—) and U+2013 en-dash (–) for robustness.
103
+ *
104
+ * #4130 follow-up (hardening), quadratic driver #2: the first separator was
105
+ * `[^*]*[—–]`, whose leading class ALSO accepts the dash — on a failing
106
+ * dash-laden title the engine retried the separator at every dash position
107
+ * with an O(n) scan after each (~1.7s @ 40k). Narrowed to `[^*—–]*[—–]`:
108
+ * the leading class now excludes the dash, so the separator is the FIRST
109
+ * dash — one viable split, single pass. Behavior-preserving because every
110
+ * candidate dash lies before the first `*` (the leading class cannot cross
111
+ * a star), so the trailing `[^*]*` reaches that same first star from any
112
+ * candidate and `**` succeeds or fails identically; no capture involves the
113
+ * dash position. The ID is atomic like the other forms (driver #1).
44
114
  */
45
- const bulletEmDashRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^*]*[—–][^*]*\*\*\s*(.*)$/;
115
+ const bulletEmDashRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|[^*—–\\u0060])*[—–](?:\\u0060[^\\u0060]*\\u0060|[^*\\u0060])*\\*\\*\\s*(.*)$`);
46
116
  /**
47
- * Titled-colon form: `- **D-NN[ [tags]]: Title.** body`
117
+ * Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body`
48
118
  * A title sits between the colon and the closing `**` (so the `:**` anchor of
49
119
  * bulletColonRe fails, and there is no em-dash for bulletEmDashRe). This is a strict
50
120
  * superset of the colon-immediate form, so it MUST be checked AFTER bulletColonRe and
51
- * bulletEmDashRe — it only catches bullets those two miss. The title run is `[^:*]*` (no
52
- * colon, no `*`) so a genuinely-malformed bullet with a colon in the pre-separator run
53
- * (e.g. `D-07 ratio 3:1:**`) still fails the anchor and falls through to the parse-miss
54
- * guard — matching bulletColonRe's `[^:*]*` discipline that the separator colon is the
55
- * only colon permitted before `**`. (#1639)
121
+ * bulletEmDashRe — it only catches bullets those two miss. The title runs are code-span-aware
122
+ * (#4788) but still exclude `*`, so a genuinely-malformed bullet with no bare colon at all
123
+ * before `**` still fails the anchor and falls through to the parse-miss guard — the #1639
124
+ * fail-loud discipline: the separator must be a BARE colon before `**`. A code span's
125
+ * `:`/`*` is data, never grammar; an UNTERMINATED backtick now fails loud (previously it was
126
+ * an ordinary character) — the deliberate cost of span opacity.
127
+ *
128
+ * #4793/#4958: the pre-separator run tolerates a bare colon as prose ONLY when it is
129
+ * immediately followed by whitespace (`:(?=\s)`), e.g. a natural sentence colon in
130
+ * `D-02: Two managers … same pair: the second is rejected.`. A colon with no following
131
+ * whitespace (a compact/ratio-style colon like `3:1`) is never consumed by this branch, so
132
+ * it still hard-caps the pre-separator run and the #1639/#4130/#1343 negative-space fixtures
133
+ * (e.g. `D-07 ratio 3:1:**`) still fail loud. The post-separator branch is unchanged and
134
+ * still excludes colons, so normal greedy backtracking selects the LAST valid split (a bare
135
+ * colon followed by whitespace, or the anchor colon itself) before the closing `**`.
136
+ *
137
+ * The ID is consumed atomically `(?=(…))\1` like the other forms — the
138
+ * hardening note above the constants explains why (#4130 follow-up).
139
+ */
140
+ const bulletTitledColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|:(?=\\s)|[^:*\\u0060])*:(?:\\u0060[^\\u0060]*\\u0060|[^:*\\u0060])*\\*\\*\\s*(.*)$`);
141
+ /**
142
+ * #4130: the parse-miss guard's probe — a line whose bold lead-in ATTEMPTS the
143
+ * ID grammar (see `ID_ATTEMPT_SOURCE`) but failed all three bullet patterns
144
+ * above. Bare `D-` attempts behave exactly as before #4130; a digit-initial
145
+ * prefix run (`D4-`… including a typo'd `D4x-`) is new evidence of an attempt,
146
+ * so the malformed-prefixed bullet fails loud instead of silently vanishing.
147
+ */
148
+ const parseMissGuardRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
149
+ /**
150
+ * #4130: bare-token evidence of decision-shaped content — a `D…-<alnum>` token
151
+ * in running text. `D-01` matched before; the digit-run phase prefix (`D4-01`)
152
+ * is added so token evidence agrees with the extractor's ID grammar
153
+ * (DECISION_ID_SOURCE) instead of silently ignoring prefixed mentions.
56
154
  */
57
- const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:[^:*]*\*\*\s*(.*)$/;
155
+ const decisionTokenRe = new RegExp(`\\bD[0-9]*-[A-Za-z0-9]`, 'm');
58
156
  /**
59
157
  * #2347: format-agnostic evidence that a block/section holds real decision
60
158
  * ENTRIES the parser could not read — a bullet whose bold lead-in is an
61
159
  * ID-SHAPED token (uppercase prefix, optional digits, hyphen, alnum), whatever
62
- * the exact ID grammar. The three parser grammars above all require a `D-`
63
- * prefix; #1365's fail-loud guard reused that same `\bD-` test as its "is this
64
- * decision-shaped?" evidence, so any other prefix (e.g. `D5-01`) was invisible
65
- * to BOTH parser and guard, collapsing `could-not-parse` into a clean
160
+ * the exact ID grammar. #1365's fail-loud guard originally reused the parser's
161
+ * own `\bD-` test as its "is this decision-shaped?" evidence, so any prefix the
162
+ * parser could not read (e.g. `D5-01` then, `DEC-01` now) was invisible to
163
+ * BOTH parser and guard, collapsing `could-not-parse` into a clean
66
164
  * `none-present` pass.
67
165
  *
68
166
  * The ID-shape requirement (not "any bold bullet") is deliberate: a decisions
@@ -70,25 +168,35 @@ const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([
70
168
  * bullets with bold labels (`- **Scope:** …`, `- **Why:** …`, `- **Note:** …`).
71
169
  * Those are NOT decision entries and must stay `none-present` — a false
72
170
  * `could-not-parse` hard-blocks the plan gate. `[A-Z]+[0-9]*-[A-Za-z0-9]` matches
73
- * `D-01` / `D5-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
171
+ * `D-01` / `D4-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
74
172
  * case) / `TODO:` (no `-<alnum>` id) — mirroring the parser's own `D-<alnum>`
75
173
  * shape without hardcoding the `D`.
174
+ *
175
+ * #4130 parity note: for the D-prefixed universe this detector's grammar
176
+ * (`D` + digit-run + `-` + alnum) is exactly `DECISION_ID_SOURCE` above, so a
177
+ * well-formed bullet the detector calls decision-shaped is now always one the
178
+ * extractor can read. The detector stays WIDER on purpose (`DEC-01` is still
179
+ * evidence): an ID grammar outside the parser's universe must keep failing
180
+ * loud, never silently passing. The #4130 property tests pin both directions.
76
181
  */
77
182
  const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
78
183
  /**
79
- * #3939: a decision bullet's DECLARATION line — the `- **D-NN … **` bold lead-in
80
- * the three grammars above anchor on — may wrap across a line break. Physical
81
- * line breaks inside a bullet are markdown-insignificant, and GSD's own
184
+ * #3939: a decision bullet's DECLARATION line — the `- **D[phase]-NN … **` bold
185
+ * lead-in the three grammars above anchor on — may wrap across a line break.
186
+ * Physical line breaks inside a bullet are markdown-insignificant, and GSD's own
82
187
  * discuss-phase writer emits the wrapped shape whenever a decision title runs
83
188
  * past the wrap column. All three grammars require the closing `**` in the same
84
- * string as the `- **D-` anchor, so a wrapped declaration matched none of them
189
+ * string as the `- **D…-` anchor, so a wrapped declaration matched none of them
85
190
  * and fell to the #1365 parse-miss guard, forcing `could-not-parse` (which
86
191
  * hard-blocks `check.decision-coverage-plan`) on a well-formed CONTEXT.md.
87
192
  *
88
193
  * The repair is confined to how the LOGICAL bullet is assembled — the grammars
89
194
  * themselves are untouched, so every single-line form parses exactly as before.
195
+ * #4130: the anchor uses `ID_ATTEMPT_SOURCE` (digit-run phase prefixes join
196
+ * like bare ones; recognising more start shapes only reassembles the logical
197
+ * bullet, which then parses or fails loud as itself).
90
198
  */
91
- const decisionBulletStartRe = /^\s*-\s+\*\*D-/;
199
+ const decisionBulletStartRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
92
200
  /**
93
201
  * A line that opens a new BLOCK-LEVEL construct, and therefore terminates the
94
202
  * bullet above it: a list marker of any family (`-`, `*`, `+`, `1.`, `1)`), an
@@ -136,7 +244,8 @@ const blockConstructRe = /^(?:[-*+]\s|\d+[.)]\s|#{1,6}\s|>\s|\|)/;
136
244
  * to watch for a splice.
137
245
  *
138
246
  * The id character class is deliberately looser than the grammars' (it admits
139
- * an empty id, so a bare `- **D-` still counts as unsettled). This regex only
247
+ * an empty id, so a bare `- **D-` still counts as unsettled, and — #4130 — a
248
+ * digit-run phase prefix between the `D` and the first hyphen). This regex only
140
249
  * answers "may an id-adjacent bracket still open here?", where recognising MORE
141
250
  * shapes is the conservative direction: an over-broad match can only make a
142
251
  * malformed bullet fail loud, while a missed one silently re-classifies.
@@ -145,7 +254,7 @@ const blockConstructRe = /^(?:[-*+]\s|\d+[.)]\s|#{1,6}\s|>\s|\|)/;
145
254
  * into `tags` (and therefore into `trackable`). A `[` further along the title is
146
255
  * ordinary text and does not restrict the join.
147
256
  */
148
- const tagRegionRe = /^\s*-\s+\*\*D-[A-Za-z0-9_-]*\s*(?:\[([^\]]*))?$/;
257
+ const tagRegionRe = new RegExp(`^\\s*-\\s+\\*\\*D[0-9]*-[A-Za-z0-9_-]*\\s*(?:\\[([^\\]]*))?$`);
149
258
  /**
150
259
  * #3939 (review): would folding `next` onto a lead-in whose `[tags]` bracket is
151
260
  * still open splice the inserted space INTO a tag token?
@@ -304,6 +413,7 @@ function parseDecisionLines(block) {
304
413
  let current = null;
305
414
  let openIndent = null;
306
415
  let parseMisses = 0;
416
+ const unreadableIds = [];
307
417
  const flush = () => {
308
418
  if (current) {
309
419
  current.text = current.text.trim();
@@ -347,11 +457,11 @@ function parseDecisionLines(block) {
347
457
  current.text += ' ' + trimmed;
348
458
  continue;
349
459
  }
350
- // Colon form: `- **D-NN[ [tags]]:** text`
460
+ // Colon form: `- **D[phase]-NN[ [tags]]:** text`
351
461
  const colonMatch = line.match(bulletColonRe);
352
462
  if (colonMatch) {
353
463
  flush();
354
- const id = `D-${colonMatch[1]}`;
464
+ const id = colonMatch[1];
355
465
  const tags = colonMatch[2]
356
466
  ? colonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
357
467
  : [];
@@ -360,11 +470,11 @@ function parseDecisionLines(block) {
360
470
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
361
471
  continue;
362
472
  }
363
- // Em-dash form: `- **D-NN[ [tags]] — title** body`
473
+ // Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
364
474
  const emDashMatch = line.match(bulletEmDashRe);
365
475
  if (emDashMatch) {
366
476
  flush();
367
- const id = `D-${emDashMatch[1]}`;
477
+ const id = emDashMatch[1];
368
478
  const tags = emDashMatch[2]
369
479
  ? emDashMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
370
480
  : [];
@@ -376,14 +486,14 @@ function parseDecisionLines(block) {
376
486
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
377
487
  continue;
378
488
  }
379
- // Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
489
+ // Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
380
490
  // a strict superset of bulletColonRe, so it only catches bullets the colon-immediate
381
491
  // and em-dash forms missed (minimal blast radius). id + [tags] trackability honored;
382
492
  // the body after the closing bold run is reported as text.
383
493
  const titledColonMatch = line.match(bulletTitledColonRe);
384
494
  if (titledColonMatch) {
385
495
  flush();
386
- const id = `D-${titledColonMatch[1]}`;
496
+ const id = titledColonMatch[1];
387
497
  const tags = titledColonMatch[2]
388
498
  ? titledColonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
389
499
  : [];
@@ -392,12 +502,21 @@ function parseDecisionLines(block) {
392
502
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
393
503
  continue;
394
504
  }
395
- // Parse-miss guard (FIX B + #1343): a line that looks like a `D-NN` decision
396
- // bullet but failed both patterns — flush, warn, and record the miss.
505
+ // Parse-miss guard (FIX B + #1343, grammar widened #4130): a line whose bold
506
+ // lead-in ATTEMPTS the ID grammar but failed all three patterns — flush,
507
+ // warn, and record the miss. `ID_ATTEMPT_SOURCE` accepts the bare `D-` form
508
+ // (as before) plus a digit-initial prefix run, so a typo'd phase prefix
509
+ // (`D4x-01`) fails loud instead of silently vanishing, while a letter-initial
510
+ // run (`Deferred-until`) stays prose and stays invisible.
397
511
  // parseMisses > 0 forces could-not-parse even when other decisions parsed.
398
- if (/^\s*-\s+\*\*D-/.test(line)) {
512
+ if (parseMissGuardRe.test(line)) {
399
513
  flush();
400
514
  parseMisses += 1;
515
+ // #4794: carry the attempted id so a caller capturing stdout (a JSON gate)
516
+ // can name the decision to fix — today it exists only in this stderr warn.
517
+ const attempted = line.match(/\*\*\s*(D(?:[0-9][A-Za-z0-9]*)?-[A-Za-z0-9_-]*)/);
518
+ if (attempted && !unreadableIds.includes(attempted[1]))
519
+ unreadableIds.push(attempted[1]);
401
520
  console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
402
521
  continue;
403
522
  }
@@ -413,7 +532,7 @@ function parseDecisionLines(block) {
413
532
  }
414
533
  }
415
534
  flush();
416
- return { decisions: out, parseMisses };
535
+ return { decisions: out, parseMisses, unreadableIds };
417
536
  }
418
537
  // ─── Primary entry point: extractDecisions ────────────────────────────────────
419
538
  /**
@@ -438,21 +557,21 @@ function extractDecisions(content) {
438
557
  const taggedBlocks = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(stripped, 'decisions');
439
558
  if (taggedBlocks.length > 0) {
440
559
  const combined = taggedBlocks.join('\n\n');
441
- const { decisions, parseMisses } = parseDecisionLines(combined);
560
+ const { decisions, parseMisses, unreadableIds } = parseDecisionLines(combined);
442
561
  if (decisions.length > 0 && parseMisses === 0) {
443
562
  return { decisions, outcome: 'parsed' };
444
563
  }
445
564
  // FIX B: parse-misses present — could-not-parse even if some decisions extracted.
446
565
  if (parseMisses > 0) {
447
- return { decisions, outcome: 'could-not-parse' };
566
+ return { decisions, outcome: 'could-not-parse', unreadableIds };
448
567
  }
449
568
  // FIX A: Block present but 0 extracted and no parse-misses.
450
569
  // Only report could-not-parse when there is genuine evidence of real decisions
451
570
  // that failed to parse: a bold-lead-in bullet (`- **…**`, any ID grammar — #2347),
452
- // a \bD- token in the block text, or an unterminated fence. An empty scaffold
571
+ // a bare `D[phase]-<alnum>` token (#4130) in the block text, or an unterminated fence. An empty scaffold
453
572
  // (<decisions></decisions>) or an all-prose block has no such evidence — treat
454
573
  // as none-present so the gate passes cleanly.
455
- const hasDecisionTokenInBlock = /\bD-[A-Za-z0-9]/m.test(combined);
574
+ const hasDecisionTokenInBlock = decisionTokenRe.test(combined);
456
575
  const hasBoldLeadInBullet = boldLeadInBulletRe.test(combined);
457
576
  if (hasDecisionTokenInBlock || hasBoldLeadInBullet || unterminatedFence) {
458
577
  return { decisions: [], outcome: 'could-not-parse' };
@@ -465,20 +584,20 @@ function extractDecisions(content) {
465
584
  // stripFences:true → inner fences inside the section body are stripped.
466
585
  const section = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => /decisions?\b/i.test(h.text), { levelBounded: true, stripFences: true });
467
586
  if (section !== null) {
468
- const { decisions, parseMisses } = parseDecisionLines(section.body);
587
+ const { decisions, parseMisses, unreadableIds } = parseDecisionLines(section.body);
469
588
  if (decisions.length > 0 && parseMisses === 0) {
470
589
  return { decisions, outcome: 'parsed' };
471
590
  }
472
591
  // FIX B: parse-misses present — could-not-parse even if some decisions extracted.
473
592
  if (parseMisses > 0) {
474
- return { decisions, outcome: 'could-not-parse' };
593
+ return { decisions, outcome: 'could-not-parse', unreadableIds };
475
594
  }
476
595
  // FIX A: Heading found but 0 extracted and no parse-misses.
477
596
  // Report could-not-parse when the section body holds a decision-entry-shaped
478
- // bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a D- token. A
597
+ // bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a `D[phase]-<alnum>` token (#4130). A
479
598
  // heading with only prose, sub-headings, or all-discretion content (no such
480
599
  // evidence) is a legitimate empty/discretion section → none-present.
481
- const hasDecisionTokenInSection = /\bD-[A-Za-z0-9]/m.test(section.body);
600
+ const hasDecisionTokenInSection = decisionTokenRe.test(section.body);
482
601
  const hasBoldLeadInBulletInSection = boldLeadInBulletRe.test(section.body);
483
602
  if (hasDecisionTokenInSection || hasBoldLeadInBulletInSection) {
484
603
  return { decisions: [], outcome: 'could-not-parse' };
@@ -487,8 +606,8 @@ function extractDecisions(content) {
487
606
  }
488
607
  // ── Path 3: no blocks, no heading ────────────────────────────────────────────
489
608
  // Apply shape heuristics to distinguish none-present from could-not-parse.
490
- // We re-use the already-computed unterminatedFence and check for D- tokens.
491
- const hasDecisionToken = /\bD-[A-Za-z0-9]/m.test(stripped);
609
+ // We re-use the already-computed unterminatedFence and check for decision tokens.
610
+ const hasDecisionToken = decisionTokenRe.test(stripped);
492
611
  if (unterminatedFence || hasDecisionToken) {
493
612
  return { decisions: [], outcome: 'could-not-parse' };
494
613
  }
@@ -18,13 +18,10 @@
18
18
  * concern sharing the word "trust".
19
19
  */
20
20
  'use strict';
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
21
  Object.defineProperty(exports, "__esModule", { value: true });
25
22
  exports.isPathConfined = isPathConfined;
26
23
  exports.assertDescriptorConfined = assertDescriptorConfined;
27
- const node_path_1 = __importDefault(require("node:path"));
24
+ const security_cjs_1 = require("./security.cjs");
28
25
  /**
29
26
  * Pure LEXICAL path-containment check (cross-platform). `target` is confined
30
27
  * to `root` iff resolving it relative to `root` (via `path.resolve` — string
@@ -36,20 +33,38 @@ const node_path_1 = __importDefault(require("node:path"));
36
33
  * `root`) that would redirect the LEXICALLY-confined path to a physically
37
34
  * different, unconfined location on disk. A caller relying on this for a
38
35
  * write-confinement guarantee against a symlink-planting attacker must pair
39
- * it with a symlink check (or refuse to follow symlinks at write time) — see
40
- * capability-source.cts's install adapters (:491,577,675), which is what
41
- * currently keeps every caller of this function's callers symlink-safe: they
42
- * reject symlinks upstream, before a target ever reaches a lexical-only check
43
- * like this one.
36
+ * it with a symlink check (or refuse to follow symlinks at write time). Only
37
+ * the capability-loader.cts route into assertDescriptorConfined gets this for
38
+ * free today: capability-source.cts's staging path rejects symlinks upstream,
39
+ * before a target ever reaches a lexical-only check like this one — see
40
+ * copyDirRecursive's `entry.isSymbolicLink()` throw (capability-source.cts:585-586)
41
+ * and the post-copy budget-walk re-check (capability-source.cts:671-674). This
42
+ * does NOT extend to isPathConfined's other callers: install-engine.cts:1608
43
+ * and install-profiles.cts:755,880 do not go through capability-source.cts's
44
+ * adapters at all and have no symlink guard of their own here. Of the
45
+ * remaining callers, only retired-artifact-cleanup.cts:69 carries its own
46
+ * defense, via a local `lstatSync(destDir).isSymbolicLink()` check at line 77.
47
+ *
48
+ * `opts.pathImpl` (default: the ambient `path` module) lets a caller inject
49
+ * `path.win32` or `path.posix`. This is security-relevant: the win32 branch
50
+ * (drive letters, UNC paths, `\` separator) is otherwise only reachable by
51
+ * actually running this process on a Windows host, so without injection a
52
+ * win32-specific confinement escape would be unverified on every other
53
+ * platform. This mirrors the platform-injection seam already used elsewhere
54
+ * in this repo, e.g. src/shell-command-projection.cts's `opts.platform`
55
+ * (#4641). All existing 2-arg callers are unaffected: the default resolves to
56
+ * the ambient `path`, preserving byte-identical behaviour.
57
+ *
58
+ * The containment DECISION here now comes from the canonical predicate in
59
+ * src/security.cts (`tryWithinRootLexical`, ADR-4650 decision 6) — this
60
+ * function keeps only the lexical RESOLUTION policy (no realpath, no
61
+ * filesystem access) as its own choice; the comparison itself is shared.
44
62
  */
45
- function isPathConfined(target, root) {
63
+ function isPathConfined(target, root, opts = {}) {
46
64
  if (typeof target !== 'string' || typeof root !== 'string' || target.length === 0 || root.length === 0) {
47
65
  return false;
48
66
  }
49
- const rootResolved = node_path_1.default.resolve(root);
50
- const targetResolved = node_path_1.default.resolve(root, target);
51
- const prefix = rootResolved + node_path_1.default.sep;
52
- return targetResolved === rootResolved || targetResolved.startsWith(prefix);
67
+ return (0, security_cjs_1.tryWithinRootLexical)(target, root, { pathImpl: opts.pathImpl }) !== null;
53
68
  }
54
69
  /**
55
70
  * Assert every destSubpath the descriptor declares (global + local artifact
@@ -1394,6 +1394,14 @@ function cmdFrontmatterGet(cwd, filePath, field, raw) {
1394
1394
  // Pass the resolved path so a truncated file is named in the diagnostic and deduplicated
1395
1395
  // per file rather than per content digest (#1882, ADR-1411 wiring clause).
1396
1396
  const fm = extractFrontmatter(content, fullPath);
1397
+ // #4806: an unparseable frontmatter block is a distinct outcome — the file
1398
+ // HAS a frontmatter block but its YAML failed to parse. Reporting
1399
+ // "Field not found" tells the caller the key is absent, which is
1400
+ // indistinguishable from a file that genuinely lacks it.
1401
+ if (fm[FRONTMATTER_UNPARSEABLE] === true) {
1402
+ output({ error: 'Frontmatter is not parseable YAML — fix the syntax error in the frontmatter block', path: filePath }, raw, undefined);
1403
+ return;
1404
+ }
1397
1405
  if (field) {
1398
1406
  const value = fm[field];
1399
1407
  if (value === undefined) {
@@ -1649,4 +1657,9 @@ module.exports = {
1649
1657
  cmdFrontmatterMerge,
1650
1658
  cmdFrontmatterValidate,
1651
1659
  propagateCommentChannel,
1660
+ // #4917 / ADR-4910 Decision 1: additive-only export so `planning-document.cts`
1661
+ // can COMPOSE this seam's fence-detection grammar (byte-0 rule, BOM strip,
1662
+ // CR handling) instead of reimplementing it. No behavior change — same
1663
+ // function `extractFrontmatter`/`frontmatterListEntries` already call.
1664
+ frontmatterRegion,
1652
1665
  };
@@ -445,14 +445,17 @@ function graphifyStatus(cwd) {
445
445
  return disabledResponse();
446
446
  const { graphPath, configured } = resolveGraphLocation(cwd, planningDir);
447
447
  if (!node_fs_1.default.existsSync(graphPath)) {
448
- return { exists: false, message: configured
448
+ return { exists: false, graph_path: graphPath, message: configured
449
449
  ? `Configured graph not found at ${graphPath}. Set graphify.graph_path or run /gsd:graphify build.`
450
450
  : 'No graph built yet. Run graphify build to create one.' };
451
451
  }
452
452
  const stat = node_fs_1.default.statSync(graphPath);
453
453
  const graph = safeReadJson(graphPath);
454
454
  if (!graph) {
455
- return { error: 'Failed to parse graph.json' };
455
+ // Still surface graph_path (#4836 Minor 1): callers gate on `exists`, not
456
+ // on this outcome, so without it they'd fall through to the CLI-first
457
+ // branch with no --graph value to substitute for the <graph> placeholder.
458
+ return { error: 'Failed to parse graph.json', graph_path: graphPath };
456
459
  }
457
460
  const STALE_MS = 24 * 60 * 60 * 1000; // 24 hours
458
461
  const age = Date.now() - stat.mtimeMs;
@@ -482,6 +485,11 @@ function graphifyStatus(cwd) {
482
485
  (lastBuildAutoUpdate.status === 'failed' || lastBuildAutoUpdate.status === 'running');
483
486
  return {
484
487
  exists: true,
488
+ // The absolute location the whole graphify surface reads, already resolved
489
+ // through `graphify.graph_path` (#1825). Callers that shell out to the
490
+ // graphify CLI pass this as `--graph` so the umbrella override is honoured
491
+ // there too, instead of re-deriving `.planning/graphs/graph.json` (#4836).
492
+ graph_path: graphPath,
485
493
  last_build: stat.mtime.toISOString(),
486
494
  node_count: (graph.nodes || []).length,
487
495
  edge_count: (graph.edges || graph.links || []).length,
@@ -307,8 +307,7 @@ function buildStateMd(phaseMap) {
307
307
  const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
308
308
  const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
309
309
  const status = currentEntry ? 'Ready to plan' : 'All phases complete';
310
- const filled = Math.round(pct / 10);
311
- const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
310
+ const bar = `[${(0, phase_lifecycle_cjs_1.renderProgressBar)(pct, 10)}]`;
312
311
  const today = clock_cjs_1.realClock.localToday();
313
312
  return [
314
313
  '# Project State',
@@ -158,6 +158,17 @@ const RULE_W002 = {
158
158
  const normalizedValid = normalizePhaseTokenSet(validPhases);
159
159
  const sortedValid = [...validPhases].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
160
160
  const diagnostics = [];
161
+ // #4257: the valid set above is WORKSTREAM-scoped by construction (every
162
+ // source field is read from `planningPaths(cwd)`'s base, which resolves
163
+ // under `.planning/workstreams/<active-ws>/`), and per-workstream phase
164
+ // numbering is deliberate — so under a workstream the message must NAME
165
+ // that scope rather than make an unqualified project-wide claim (a
166
+ // reference to a phase declared only in a SIBLING workstream reads as
167
+ // "undeclared" against this list; that is the scope speaking, not drift).
168
+ // Root scope (`workstream === null`, flat or root-planning projects)
169
+ // keeps the byte-identical message — no clause is appended when none
170
+ // applies.
171
+ const scopeClause = snapshot.workstream ? ` in workstream ${snapshot.workstream}` : '';
161
172
  for (const ref of snapshot.statePhaseTokens.value) {
162
173
  const dotIdx = ref.indexOf('.');
163
174
  const head = dotIdx === -1 ? ref : ref.slice(0, dotIdx);
@@ -168,7 +179,7 @@ const RULE_W002 = {
168
179
  diagnostics.push({
169
180
  code: 'W002',
170
181
  severity: SEVERITY.WARNING,
171
- message: `STATE.md references phase ${ref}, but only phases ${sortedValid.join(', ')} are declared`,
182
+ message: `STATE.md references phase ${ref}, but only phases ${sortedValid.join(', ')} are declared${scopeClause}`,
172
183
  remedy: adviseRemedy('Review STATE.md manually before changing it; /gsd-health --repair will not overwrite an existing STATE.md for phase mismatches'),
173
184
  });
174
185
  }
@@ -135,7 +135,7 @@ function checkW017(snapshot) {
135
135
  function isActiveWorktreePath(activeCwd, worktreePath, platform = process.platform) {
136
136
  const active = shellCmdProjection.toComparablePathKey(activeCwd, platform);
137
137
  const worktree = shellCmdProjection.toComparablePathKey(worktreePath, platform);
138
- return active === worktree || active.startsWith(worktree + '/');
138
+ return active === worktree || active.startsWith(worktree + '/'); // allow-handrolled-containment: worktree identity matching, not a containment gate
139
139
  }
140
140
  function checkW027(snapshot) {
141
141
  const diagnostics = [];
@@ -24,6 +24,7 @@ exports.resolveReportedRuntime = resolveReportedRuntime;
24
24
  const node_fs_1 = __importDefault(require("node:fs"));
25
25
  const node_path_1 = __importDefault(require("node:path"));
26
26
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
27
+ const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
27
28
  const update_context_cjs_1 = require("./update-context.cjs");
28
29
  Object.defineProperty(exports, "CODEX_CONFIG_MARKER", { enumerable: true, get: function () { return update_context_cjs_1.CODEX_CONFIG_MARKER; } });
29
30
  // Codex sandbox env vars set by the shell tool / Seatbelt child-process spawn.
@@ -127,6 +128,14 @@ function resolveReportedRuntimeUnsafe(projectDir, deps) {
127
128
  const explicit = (0, runtime_slash_cjs_1.resolveExplicitRuntime)(projectDir, deps?.env ?? process.env);
128
129
  if (explicit)
129
130
  return explicit;
131
+ // #4717: the per-install marker names the runtime that owns THIS tree — a
132
+ // stronger signal than host sniffing, which misreports every session on a
133
+ // multi-runtime machine (e.g. a globally exported CODEX_HOME makes a Claude
134
+ // Code session read as codex). Marker-less trees (dev/source, pre-#2297
135
+ // installs) fall through to host detection unchanged.
136
+ const marker = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)((0, runtime_slash_cjs_1.readInstallRuntimeMarker)());
137
+ if (marker)
138
+ return marker;
130
139
  const detected = detectHostRuntime(deps);
131
140
  if (detected.runtime)
132
141
  return detected.runtime;