@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
@@ -59,6 +59,20 @@ const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]{0,200}\\))?';
59
59
  // (scripts/lint-phase-id-drift.cjs) fails CI if a literal re-derivation is
60
60
  // introduced outside this module without a `// phase-id-owner:` justification.
61
61
  const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
62
+ // #4764: a phase REFERENCE in depends-on PROSE — the token in context, directly
63
+ // following "Phase"/"Phases", with bare-token list continuation ("Phases 1 and 2",
64
+ // "Phase 1, 2, and 3", "Phase 1-3"). Built HERE, beside the token grammar it is
65
+ // anchored on, because two readers consume Depends-on prose (init.manager's
66
+ // dep_phases and planning-inspect's dependencies) and re-deriving the anchored
67
+ // form at each would drift exactly the way PHASE_NUMBER_TOKEN_SOURCE's own
68
+ // anti-divergence rule exists to prevent. A bare digit run is the right shape
69
+ // test for a phase id and the WRONG test for a reference: the whole-field
70
+ // scrape this replaces pulled calendar dates ("2026-09-14" → 2026/09/14), git
71
+ // shas ("8bf403100d" → 8b/403100d/…) and ledger ids (WINDOWS #1843) in as
72
+ // dependencies. The `-` separator deliberately extracts range ENDPOINTS only
73
+ // ("Phase 1-3" → 1, 3) — the pre-#4764 behavior; interior enumeration stays
74
+ // out (a range's middle is not written as a reference).
75
+ const PHASE_DEP_REF_SOURCE = `\\bphases?\\s+(${PHASE_NUMBER_TOKEN_SOURCE}(?:(?:\\s*,\\s*(?:and\\s+)?|\\s+and\\s+|\\s*&\\s*|\\s+(?:to|through)\\s+|\\s*-\\s*)${PHASE_NUMBER_TOKEN_SOURCE})*)`;
62
76
  // #2528 review: the CASE-FLEXIBLE renderings of the two sources above, for call
63
77
  // sites that scan directory names (where a project code or a variant suffix may
64
78
  // legitimately be lowercase) and therefore cannot use a case-sensitive class.
@@ -308,6 +322,30 @@ function phaseHeadingPrefixSrcFor(baseline, convention, capturing = false) {
308
322
  const bracketAlt = `\\[${id}\\][ \\t]*(?:Phase\\s+|(?=\\d))`;
309
323
  return `(?:${bracketAlt}|${base})`;
310
324
  }
325
+ /**
326
+ * The single owner of the "scan a whole document for every phase heading"
327
+ * pattern (#4865 / ADR-4910 §8) — anchored (`^ {0,3}#{2,4}`, so indentation up
328
+ * to 3 spaces is tolerated but a heading is never matched mid-line), global,
329
+ * multiline, convention-aware via `phaseHeadingPrefixSrcFor`. Returns the
330
+ * capture-group layout alongside the regex so a caller never re-derives the
331
+ * bracket-convention offset (`const G = convention === 'bracket' ? 1 : 0`)
332
+ * itself — a second, independently-computed offset is exactly the kind of
333
+ * drift `scripts/lint-phase-id-drift.cjs` exists to catch structurally, not
334
+ * just by pattern text.
335
+ *
336
+ * Distinct from `buildPhaseHeadingRegex` (`src/roadmap.cts`), which searches
337
+ * for ONE already-known phase number (anchored `^`, no `g` flag, phase number
338
+ * interpolated literally) — a different contract for a different question.
339
+ * This function answers "which phases exist in this content", not "does this
340
+ * specific phase exist".
341
+ */
342
+ function buildPhaseHeadingScanRegex(baseline, convention) {
343
+ const bracketGroup = convention === 'bracket' ? 1 : null;
344
+ const phaseNumGroup = bracketGroup ? 2 : 1;
345
+ const phaseNameGroup = phaseNumGroup + 1;
346
+ const regex = new RegExp(`^ {0,3}#{2,4}\\s*${phaseHeadingPrefixSrcFor(baseline, convention, true)}(${PHASE_NUMBER_TOKEN_SOURCE})${OPTIONAL_PHASE_TAG_SOURCE}:\\s*([^\\n]+)`, 'gim');
347
+ return { regex, bracketGroup, phaseNumGroup, phaseNameGroup };
348
+ }
311
349
  function stripProjectCodePrefix(value, caseInsensitive = true) {
312
350
  const input = String(value);
313
351
  const re = caseInsensitive ? PROJECT_CODE_PREFIX_STRIP_RE_I : PROJECT_CODE_PREFIX_STRIP_RE;
@@ -451,10 +489,27 @@ function parsePhaseId(input) {
451
489
  // tokens unchanged.
452
490
  throw new Error(`parsePhaseId: not a bracket phase id: ${JSON.stringify(input)}`);
453
491
  }
492
+ function renderMilestoneId(id) {
493
+ return `[${id.project}.${id.milestone}]`;
494
+ }
454
495
  function renderPhaseId(id) {
455
496
  const sub = id.subphase ? `.${id.subphase}` : '';
456
497
  const plan = id.plan ? `-${id.plan}` : '';
457
- return `[${id.project}.${id.milestone}] ${id.phase}${sub}${plan}`;
498
+ return `${renderMilestoneId(id)} ${id.phase}${sub}${plan}`;
499
+ }
500
+ /** Whether a legacy phase token has a lossless spelling in bracket display grammar. */
501
+ function isBracketPhaseTokenRepresentable(token) {
502
+ try {
503
+ const bracketToken = normalizePhaseName(token)
504
+ .split('.')
505
+ .map((segment) => segment.padStart(2, '0'))
506
+ .join('.');
507
+ parsePhaseId(`[GSD.00] ${bracketToken}`);
508
+ return true;
509
+ }
510
+ catch {
511
+ return false;
512
+ }
458
513
  }
459
514
  // PhaseId is a structural type: nothing forces a caller through parsePhaseId,
460
515
  // so toDir cannot trust project/milestone/phase/subphase are already
@@ -884,19 +939,42 @@ function extractPhaseToken(dirName, convention) {
884
939
  * cannot resolve a genuinely different phase's artifact (`02.1-...` still
885
940
  * fails every `01`-rooted candidate).
886
941
  *
887
- * BRACKET CONVENTION (review item 7): a letter-prefixed-decimal dir
888
- * (`P0.3-2-slug`) is string-INDISTINGUISHABLE from a bracket-dir token
889
- * (`extractPhaseToken` above, gated on `convention === 'bracket'`) without an
890
- * explicit convention signal — and this predicate is never given one: none
891
- * of its 9 call sites thread `convention`/config through today. Rather than
892
- * guess a reading it cannot know is active and risk excluding the phase's OWN
893
- * artifact (the exact defect class this rework exists to fix), this family
894
- * (`firstLetterPrefixed` dirs) falls into the same include-everything
895
- * fail-safe as the zero-segment case below — a documented, deliberate
896
- * widening (it also stops excluding a genuine stray from a DIFFERENT
897
- * letter-prefixed-decimal phase, narrowly) accepted in trade for never
898
- * dropping the phase's own report. Convention-aware scoping for this family
899
- * is deferred to whenever a call site actually threads `convention` through.
942
+ * BRACKET CONVENTION (review item 7; reworked by #612/#2761): a
943
+ * letter-prefixed-decimal dir (`P0.3-2-slug`) is string-INDISTINGUISHABLE
944
+ * from a bracket-dir token (`extractPhaseToken` above, gated on
945
+ * `convention === 'bracket'`) without an explicit convention signal. This
946
+ * predicate now takes the same optional trailing `convention` its sibling
947
+ * helpers do (ADR-2121 additive shape): when a call site threads
948
+ * `'bracket'` and the dir is a WELL-FORMED bracket dir
949
+ * (`BRACKET_DIR_TOKEN_RE` — the one grammar owner, shared with
950
+ * `extractPhaseToken`'s bracket branch), the dir's real phase token is
951
+ * readable and membership is scoped through the identical candidate
952
+ * comparison its legacy twin gets — so `GSD.02-02-two` reads exactly what
953
+ * `02-two` reads, which is PR-2's core invariant. Convention-less calls,
954
+ * bracket-MALFORMED names (1-digit milestone, letter-suffixed token — the
955
+ * shapes W005 reports), and legacy-shaped dirs inside a bracket repo all
956
+ * resolve to the unchanged body below.
957
+ *
958
+ * A filename carrying its own bracket-qualified stem is compared on the
959
+ * qualified key — see the branch body. An M-NN-stem artifact name
960
+ * (`02-01-VERIFICATION.md` in `GSD.02-01-one`) is excluded under the bracket
961
+ * convention, deliberately: that is exactly what the legacy twin does with
962
+ * the same file, twin-parity is this PR's contract, and migration-window
963
+ * artifact renaming belongs to the migrator slice per ADR-612 — the trade is
964
+ * pinned in tests/adr-612-bracket-phase-counting.test.cjs.
965
+ *
966
+ * For those unthreaded/unparseable cases the include-everything trade
967
+ * stands: rather than guess a reading it cannot know is active and risk
968
+ * excluding the phase's OWN artifact (the exact defect class this rework
969
+ * exists to fix), the ambiguous families widen to include-everything,
970
+ * accepted in trade for never dropping the phase's own report. Note the
971
+ * bracket family splits across BOTH fail-safes, not just one: a short
972
+ * digit-bearing project code (`A1.02-…`) lands in `firstLetterPrefixed`,
973
+ * but a 3+-letter code (`GSD.02-…`) derives ZERO token segments —
974
+ * `PROJECT_CODE_PREFIX_CAPTURE_RE_I` strips `{CODE}-`, not `{CODE}.` — so
975
+ * the zero-token fail-safe fires first. A fixer patching only the
976
+ * `firstLetterPrefixed` branch covers neither; the convention gate above
977
+ * covers both.
900
978
  *
901
979
  * FAIL-SAFE (#3511, unchanged): when dirName's own leading segment carries no
902
980
  * phase-number token at all (`derivePhaseTokenSegments` finds zero segments —
@@ -933,7 +1011,44 @@ function extractPhaseToken(dirName, convention) {
933
1011
  * the original pre-#3357 "alphabetically first of ALL dashed candidates"
934
1012
  * behavior, exactly as it always did for that directory shape.
935
1013
  */
936
- function isPhaseArtifact(fileName, phaseDirName) {
1014
+ function isPhaseArtifact(fileName, phaseDirName, convention) {
1015
+ // #612/#2761: the bracket branch — see "BRACKET CONVENTION" in the docblock.
1016
+ // Gated on the same explicit signal AND the same grammar
1017
+ // (`BRACKET_DIR_TOKEN_RE`) as `extractPhaseToken`'s bracket branch, so the
1018
+ // two surfaces can never disagree about which names are bracket dirs. A
1019
+ // name that fails the grammar falls through to the unchanged body below.
1020
+ if (convention === 'bracket') {
1021
+ const bracketDir = phaseDirName.match(BRACKET_DIR_TOKEN_RE);
1022
+ if (bracketDir) {
1023
+ // The convention must gate the FILENAME reading, not just the directory
1024
+ // reading — a milestone-qualified artifact name (the layout the
1025
+ // read-tolerance suite models) derives zero legacy token segments, so
1026
+ // without this it enters FIX 2's token-less containment and any bracket
1027
+ // dir admits it. Compared on the qualified key: the phase's own
1028
+ // qualified artifact stays included — the exact key, and the dotted
1029
+ // sub-phase continuation (`GSD.02-05.1-…` is `GSD.02-05`'s own file,
1030
+ // the same dash-OR-dot rule matchesPhaseTokenCandidates applies; a
1031
+ // dash-continuation never reaches this comparison, the phase group's
1032
+ // `(?=-|$)` boundary already stopped the key before it) — while a
1033
+ // WELL-FORMED qualified stem naming a different phase or milestone is
1034
+ // excluded. A qualified-SHAPED but grammar-malformed stem (letter
1035
+ // suffix, 1-digit milestone, 2-level dot — the W005 shapes) yields a
1036
+ // null fileKey and keeps the module's documented include-everything
1037
+ // fail-safe via FIX 2 below, same as every other undecidable name.
1038
+ const fileKey = bracketQualifiedKey(fileName, convention);
1039
+ if (fileKey !== null) {
1040
+ const dirKey = bracketQualifiedKey(phaseDirName, convention);
1041
+ if (dirKey !== null)
1042
+ return fileKey === dirKey || fileKey.startsWith(`${dirKey}.`);
1043
+ }
1044
+ // Reached when the filename carries no well-formed qualified stem (the
1045
+ // common case — every unqualified name), or when the DIR's own key is
1046
+ // null despite matching the dir grammar (an unsafe-integer milestone or
1047
+ // phase — bracketQualifiedKey refuses both rather than collide).
1048
+ // Either way the candidate path decides, deliberately.
1049
+ return matchesPhaseTokenCandidates(fileName, [bracketDir[1]]);
1050
+ }
1051
+ }
937
1052
  const { tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(phaseDirName);
938
1053
  if (tokenSegments.length === 0)
939
1054
  return true;
@@ -942,6 +1057,22 @@ function isPhaseArtifact(fileName, phaseDirName) {
942
1057
  const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
943
1058
  const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
944
1059
  const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((t) => Boolean(t));
1060
+ if (matchesPhaseTokenCandidates(fileName, rawCandidates))
1061
+ return true;
1062
+ // Bracket-convention ambiguity fail-safe — see docblock above.
1063
+ if (firstLetterPrefixed)
1064
+ return true;
1065
+ return false;
1066
+ }
1067
+ /**
1068
+ * Candidate-comparison core shared by `isPhaseArtifact`'s two entry paths —
1069
+ * the legacy segment-derived readings and the #612 bracket-dir token. One
1070
+ * comparison rule, not two that agree today and drift tomorrow: the padded/
1071
+ * de-padded expansion, the separator class, and FIX 2 apply identically
1072
+ * whichever path produced the candidates, which is what makes a bracket dir
1073
+ * read exactly what its legacy twin reads.
1074
+ */
1075
+ function expandPhaseTokenCandidates(rawCandidates) {
945
1076
  // Each reading is compared in BOTH its padded and de-padded form: files are
946
1077
  // written padded by `normalizePhaseName` (`cmdScaffold`) while directories
947
1078
  // are often not (`1-unpadded`), and legacy trees carry the reverse pairing.
@@ -949,9 +1080,19 @@ function isPhaseArtifact(fileName, phaseDirName) {
949
1080
  // sub-phase (`03A`, `03.1`) has no meaningful de-padded form and is left
950
1081
  // alone, so this only ever ADDS a reading and can never drop one.
951
1082
  const depad = (t) => (/^\d+$/.test(t) ? String(Number(t)) : t);
952
- const candidates = new Set(rawCandidates
1083
+ return new Set(rawCandidates
953
1084
  .flatMap(t => [t, normalizePhaseName(t), depad(t)])
954
1085
  .map(t => t.toUpperCase()));
1086
+ }
1087
+ function matchesPhaseTokenCandidates(fileName, rawCandidates) {
1088
+ const candidates = expandPhaseTokenCandidates(rawCandidates);
1089
+ if (matchPhaseTokenCandidateSpan(fileName, candidates) !== null)
1090
+ return true;
1091
+ // FIX 2: token-less filename (bare "VERIFICATION.md"/"UAT.md") — containment
1092
+ // in this phase's own directory listing is sufficient.
1093
+ return derivePhaseTokenSegments(fileName).tokenSegments.length === 0;
1094
+ }
1095
+ function matchPhaseTokenCandidateSpan(fileName, candidates) {
955
1096
  const fileUpper = fileName.toUpperCase();
956
1097
  for (const candidate of candidates) {
957
1098
  // A dotted sub-phase segment (e.g. `01.1-CONTEXT.md`) is a legitimate
@@ -977,16 +1118,43 @@ function isPhaseArtifact(fileName, phaseDirName) {
977
1118
  if (fileUpper.startsWith(`${candidate}-`) ||
978
1119
  fileUpper.startsWith(`${candidate}.`) ||
979
1120
  fileUpper.startsWith(`${candidate}_`))
980
- return true;
1121
+ return { start: 0, end: candidate.length };
981
1122
  }
982
- // FIX 2: token-less filename (bare "VERIFICATION.md"/"UAT.md") — containment
983
- // in this phase's own directory listing is sufficient.
984
- if (derivePhaseTokenSegments(fileName).tokenSegments.length === 0)
985
- return true;
986
- // Bracket-convention ambiguity fail-safe — see docblock above.
987
- if (firstLetterPrefixed)
988
- return true;
989
- return false;
1123
+ return null;
1124
+ }
1125
+ /**
1126
+ * Return the exact leading token span by which the phase-artifact reader
1127
+ * attributes a phase-qualified filename to `phaseDirName`.
1128
+ *
1129
+ * Membership is first decided by `isPhaseArtifact`, then the span is selected
1130
+ * from the same padded, de-padded, case-folded candidate set used by
1131
+ * `matchesPhaseTokenCandidates`. Token-less containment fallbacks return null
1132
+ * because there is no phase token in the filename to replace.
1133
+ */
1134
+ function phaseArtifactTokenSpan(fileName, phaseDirName, convention) {
1135
+ if (!isPhaseArtifact(fileName, phaseDirName, convention))
1136
+ return null;
1137
+ if (convention === 'bracket') {
1138
+ const bracketDir = phaseDirName.match(BRACKET_DIR_TOKEN_RE);
1139
+ if (bracketDir) {
1140
+ const qualified = fileName.match(BRACKET_QUALIFIED_KEY_RE);
1141
+ if (qualified && bracketQualifiedKey(fileName, convention) !== null) {
1142
+ return { start: 0, end: qualified[0].length };
1143
+ }
1144
+ const bracketCandidates = expandPhaseTokenCandidates([bracketDir[1]]);
1145
+ return matchPhaseTokenCandidateSpan(fileName, bracketCandidates);
1146
+ }
1147
+ }
1148
+ const { tokenSegments } = derivePhaseTokenSegments(phaseDirName);
1149
+ if (tokenSegments.length === 0)
1150
+ return null;
1151
+ const literalToken = extractPhaseToken(phaseDirName);
1152
+ const strippedDir = stripProjectCodePrefix(phaseDirName);
1153
+ const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
1154
+ const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
1155
+ const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((token) => Boolean(token));
1156
+ const candidates = expandPhaseTokenCandidates(rawCandidates);
1157
+ return matchPhaseTokenCandidateSpan(fileName, candidates);
990
1158
  }
991
1159
  /**
992
1160
  * #3511: scope `fileNames` to the subset that passes
@@ -1035,9 +1203,19 @@ function isPhaseArtifact(fileName, phaseDirName) {
1035
1203
  * re-derived per site (CLAUDE.md's Generative Fix Divergence class).
1036
1204
  * `isPhaseArtifact` stays exported for single-item membership questions and
1037
1205
  * its own unit tests.
1206
+ *
1207
+ * #612/#2761: both helpers take the same optional trailing `convention` their
1208
+ * sibling primitives (`extractPhaseToken`, `phaseTokenMatches`) do — the
1209
+ * ADR-2121 additive shape, so every existing two-argument call resolves to
1210
+ * the unchanged legacy body. A call site that already holds the resolved
1211
+ * convention threads it and bracket dirs scope exactly like their legacy
1212
+ * twins; convention-less call sites keep the documented include-everything
1213
+ * fail-safes and are the follow-up-slice work (they cannot scope a bracket
1214
+ * dir until they can resolve the convention, and guessing is the one thing
1215
+ * this predicate must never do).
1038
1216
  */
1039
- function scopeToPhase(fileNames, phaseDirName) {
1040
- return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName));
1217
+ function scopeToPhase(fileNames, phaseDirName, convention) {
1218
+ return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName, convention));
1041
1219
  }
1042
1220
  /**
1043
1221
  * Canonical comparable key for a milestone-qualified bracket id or dir name.
@@ -1386,6 +1564,63 @@ function isForeignPrefixedPhaseQuery(phase, projectCode) {
1386
1564
  * form so a canonical heading (`### Phase 117:`) is preferred over a drifted
1387
1565
  * prefixed one (`### Phase MANIFOLD-117:`) when both exist in one ROADMAP.
1388
1566
  */
1567
+ // Separator characters a branch-name template may plausibly use to join
1568
+ // `{slug}` to its neighbours — the four this repo's shipped templates and
1569
+ // docs/CONFIGURATION.md's custom-template examples use (`/`, `-`, `_`, `.`).
1570
+ const BRANCH_TEMPLATE_SEP_RE = /[-_./]/;
1571
+ const BRANCH_TEMPLATE_SEP_RUN_RE = /([-_./])\1+/g;
1572
+ const BRANCH_TEMPLATE_EDGE_SEP_RE = /^[-_./]+|[-_./]+$/g;
1573
+ /**
1574
+ * #4126: the ONE branch-name-template renderer for the `phase` branching
1575
+ * strategy, shared by `cmdCommit` (src/commands.cts) and
1576
+ * `cmdInitExecutePhase` (src/init.cts) so the two call sites cannot diverge
1577
+ * on how an undeliverable `phaseSlug` degrades (they previously each
1578
+ * independently substituted the literal word `phase`, producing a
1579
+ * non-identifying branch name like `gsd/phase-08-phase` that contradicts an
1580
+ * honestly-reported `phase_slug: null`).
1581
+ *
1582
+ * `{project}` is deliberately NOT handled here — init.cts substitutes it
1583
+ * separately, before calling this, because it is a config-level field with
1584
+ * its own fallback (`''`), not a phase-derived one.
1585
+ *
1586
+ * When `phaseSlug` is a non-empty string, `{slug}` substitutes normally
1587
+ * (unchanged from prior behavior). When it is empty or not a string (e.g.
1588
+ * `null`, `undefined`, a number), the `{slug}` token is DROPPED — along with
1589
+ * one adjacent separator character — rather than replaced with a placeholder
1590
+ * word: a dropped token keeps the branch name honest about what it does not
1591
+ * know, where a placeholder reads as a real (but wrong) name. The result is
1592
+ * always a syntactically plausible git-ref fragment: no leading, trailing, or
1593
+ * doubled separator, for both the shipped default template
1594
+ * (`gsd/phase-{phase}-{slug}`) and a differently-shaped custom one (this
1595
+ * field is user-configurable per docs/CONFIGURATION.md).
1596
+ */
1597
+ function renderPhaseBranchName(template, phaseNumber, phaseSlug) {
1598
+ const withPhase = template.replace('{phase}', normalizePhaseName(phaseNumber));
1599
+ const slug = typeof phaseSlug === 'string' ? phaseSlug : '';
1600
+ if (slug)
1601
+ return withPhase.replace('{slug}', slug);
1602
+ const at = withPhase.indexOf('{slug}');
1603
+ if (at === -1)
1604
+ return withPhase;
1605
+ let before = withPhase.slice(0, at);
1606
+ let after = withPhase.slice(at + '{slug}'.length);
1607
+ // Drop exactly ONE adjacent separator — preferring the one immediately
1608
+ // before the token — so `a-{slug}` and `{slug}-a` both degrade to `a`
1609
+ // rather than leaving a dangling `a-` / `-a`.
1610
+ if (before && BRANCH_TEMPLATE_SEP_RE.test(before[before.length - 1])) {
1611
+ before = before.slice(0, -1);
1612
+ }
1613
+ else if (after && BRANCH_TEMPLATE_SEP_RE.test(after[0])) {
1614
+ after = after.slice(1);
1615
+ }
1616
+ // Collapse any doubled separator the drop can leave behind (e.g. a
1617
+ // `feature//{slug}` template dropping to `feature/`→`feature`), then trim
1618
+ // a resulting leading/trailing separator — valid for the shipped default
1619
+ // template AND any user-configured shape, not just the one this repo ships.
1620
+ const joined = before + after;
1621
+ const collapsed = joined.replace(BRANCH_TEMPLATE_SEP_RUN_RE, '$1').replace(BRANCH_TEMPLATE_EDGE_SEP_RE, '');
1622
+ return collapsed || null;
1623
+ }
1389
1624
  function roadmapPhaseLookupSources(phaseNum) {
1390
1625
  const sources = [];
1391
1626
  const exactSource = phaseMarkdownRegexSourceExact(phaseNum);
@@ -1400,6 +1635,7 @@ module.exports = {
1400
1635
  OPTIONAL_PROJECT_CODE_PREFIX_SOURCE,
1401
1636
  OPTIONAL_PHASE_TAG_SOURCE,
1402
1637
  PHASE_NUMBER_TOKEN_SOURCE,
1638
+ PHASE_DEP_REF_SOURCE,
1403
1639
  CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE,
1404
1640
  CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE,
1405
1641
  PHASE_CONTINUATION_SEGMENT_SOURCE,
@@ -1416,6 +1652,7 @@ module.exports = {
1416
1652
  BASE_PHASE_LABEL_PREFIX_SRC,
1417
1653
  PHASE_HEADING_BASELINE,
1418
1654
  phaseHeadingPrefixSrcFor,
1655
+ buildPhaseHeadingScanRegex,
1419
1656
  foldBracketId,
1420
1657
  bracketQualifiedKey,
1421
1658
  stripProjectCodePrefix,
@@ -1423,7 +1660,9 @@ module.exports = {
1423
1660
  getMilestoneFromPhaseId,
1424
1661
  getPhaseDirFromPhaseId,
1425
1662
  parsePhaseId,
1663
+ renderMilestoneId,
1426
1664
  renderPhaseId,
1665
+ isBracketPhaseTokenRepresentable,
1427
1666
  toDir,
1428
1667
  SENTINEL_RANGES,
1429
1668
  isSentinelPhaseId,
@@ -1433,6 +1672,7 @@ module.exports = {
1433
1672
  comparePhaseNum,
1434
1673
  extractPhaseToken,
1435
1674
  isPhaseArtifact,
1675
+ phaseArtifactTokenSpan,
1436
1676
  scopeToPhase,
1437
1677
  phaseTokenMatches,
1438
1678
  matchPhaseDirs,
@@ -1445,4 +1685,5 @@ module.exports = {
1445
1685
  stripConfiguredProjectCodePrefix,
1446
1686
  isForeignPrefixedPhaseQuery,
1447
1687
  roadmapPhaseLookupSources,
1688
+ renderPhaseBranchName,
1448
1689
  };
@@ -25,6 +25,8 @@ exports.locateProgressTable = locateProgressTable;
25
25
  exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
26
26
  exports.clampPercentFromFraction = clampPercentFromFraction;
27
27
  exports.clampPercent = clampPercent;
28
+ exports.progressBarFilledCells = progressBarFilledCells;
29
+ exports.renderProgressBar = renderProgressBar;
28
30
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
29
31
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
30
32
  const phaseIdMod = require("./phase-id.cjs");
@@ -140,3 +142,62 @@ function clampPercent(completed, total) {
140
142
  return 0;
141
143
  return clampPercentFromFraction(completed / total);
142
144
  }
145
+ /**
146
+ * How many cells of a `width`-cell progress bar are filled for `percent`.
147
+ *
148
+ * #4294: the RENDER half's kernel — the counterpart of `clampPercentFromFraction`
149
+ * one layer down. That function owns `fraction -> integer percent`; this one
150
+ * owns `integer percent -> filled cells`, and is the single place that rounding
151
+ * is expressed. Five inline copies of the rounding carried it before this
152
+ * change — three in `commands.cts`, and one each in `gsd2-import.cts` and
153
+ * `formatProgressMachineSegment` here; the latter two used `/ 10` with the
154
+ * width already substituted (`pct` in `gsd2-import.cts`, `clamped` in the
155
+ * formatter). (#4294 counts SIX call sites because it counts
156
+ * `cmdStateUpdateProgress` and `syncCore` separately; #4231 had already routed
157
+ * both through `formatProgressMachineSegment` — as it does
158
+ * `applyPostSyncPreservation` — so by this branch's base they share one copy.)
159
+ * Every copy saturated: at width 10 that rounds to a full bar from 95 up, at
160
+ * width 20 from 98 up, so a project at 19/20 plans drew the same bar as a
161
+ * shipped one beside a number that said otherwise.
162
+ *
163
+ * Contract:
164
+ * - A FULL bar is reserved for an actual 100. Below 100 the fill is held one
165
+ * cell short of the width. This is the only departure from the old formula:
166
+ * at width 10 exactly 95-99 move (10 -> 9), at width 20 exactly 98-99
167
+ * (20 -> 19); every other percent in 0-100 rounds as before.
168
+ * - `null` / `undefined` / non-finite renders an empty bar, matching the
169
+ * `percent === null ? 0 : ...` guard the `progress` renderers already carried.
170
+ * - Out-of-range input is clamped to 0-100 before rounding, so the count is
171
+ * always within `[0, width]` and a `'░'.repeat(width - filled)` can never
172
+ * be handed a negative count (the old inline form threw `RangeError` at
173
+ * 120%). A non-positive width yields 0.
174
+ *
175
+ * Callers wanting the glyph run call `renderProgressBar`; this is exported so
176
+ * the rounding rule can be pinned directly against the legacy curve.
177
+ */
178
+ function progressBarFilledCells(percent, width) {
179
+ const cells = Number.isFinite(width) && width > 0 ? Math.floor(width) : 0;
180
+ if (cells === 0)
181
+ return 0;
182
+ if (typeof percent !== 'number' || !Number.isFinite(percent))
183
+ return 0;
184
+ const clamped = Math.max(0, Math.min(100, percent));
185
+ if (clamped >= 100)
186
+ return cells;
187
+ // Scale by the WIDTH, not by 100 — this is cells-from-percent, not the
188
+ // completion-ratio derivation lint-completion-ratio-drift.cjs guards.
189
+ const rounded = Math.round((clamped / 100) * cells);
190
+ return Math.min(rounded, cells - 1);
191
+ }
192
+ /**
193
+ * Render the glyph run of a `width`-cell progress bar for `percent` —
194
+ * `'█'` for each filled cell, `'░'` for the rest, always exactly `width`
195
+ * glyphs (an empty string for a non-positive width). Brackets, the printed
196
+ * percent and any suffix stay with the caller; the fill rule is
197
+ * `progressBarFilledCells` (#4294).
198
+ */
199
+ function renderProgressBar(percent, width) {
200
+ const filled = progressBarFilledCells(percent, width);
201
+ const cells = Number.isFinite(width) && width > 0 ? Math.floor(width) : 0;
202
+ return '█'.repeat(filled) + '░'.repeat(cells - filled);
203
+ }
@@ -35,7 +35,7 @@ const frontmatterModule = require("./frontmatter.cjs");
35
35
  const { extractFrontmatter } = frontmatterModule;
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
37
37
  const planDependencyGraphModule = require("./plan-dependency-graph.cjs");
38
- const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted } = planDependencyGraphModule;
38
+ const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted, isSummaryFileBlocked } = planDependencyGraphModule;
39
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports
40
40
  const roadmapParserModule = require("./roadmap-parser.cjs");
41
41
  const { getMilestonePhaseFilter } = roadmapParserModule;
@@ -146,13 +146,15 @@ function listArchiveVersionDirs(cwd, wsOverride) {
146
146
  }
147
147
  return out;
148
148
  }
149
- function searchPhaseInDir(baseDir, relBase, normalized) {
149
+ function searchPhaseInDir(baseDir, relBase, normalized, convention) {
150
150
  try {
151
151
  const dirs = readSubdirectories(baseDir, true);
152
152
  // #2528: canonical two-pass selection (exact token match, then the
153
153
  // bare-integer leading-digit-run fallback) shared with the find-phase and
154
154
  // phase-plan-index scans — see phase-id.cts::matchPhaseDirs.
155
- const { matches, usedBareFallback } = matchPhaseDirs(dirs, normalized);
155
+ // #4801: the convention is threaded (optional) so bracket-convention
156
+ // consumers get exact token matching here too.
157
+ const { matches, usedBareFallback } = matchPhaseDirs(dirs, normalized, convention);
156
158
  if (matches.length === 0)
157
159
  return null;
158
160
  // #2237: fail loud when multiple directories match the same bare phase
@@ -176,6 +178,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
176
178
  halted_plans: [],
177
179
  blocked_by: {},
178
180
  runnable_plans: [],
181
+ ready_plans: [],
179
182
  };
180
183
  }
181
184
  const match = matches[0];
@@ -208,12 +211,19 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
208
211
  const planIds = plans.map(p => p.replace('-PLAN.md', '').replace('PLAN.md', ''));
209
212
  const planIdByLower = new Map(planIds.map(id => [id.toLowerCase(), id]));
210
213
  const canonicalToPlanId = new Map(plans.map((p, i) => [extractCanonicalPlanId(p).toLowerCase(), planIds[i]]));
214
+ // #4628: raw depends_on per plan, so readiness can fail closed on a
215
+ // DROPPED edge (a dep token that resolves to nothing carries no evidence)
216
+ // instead of the resolution silently shrinking the dependency list.
217
+ const rawDeps = plans.map((p) => parsePlanDependsOn(phaseDir, p));
218
+ // #4628: completion evidence excludes status:blocked summaries (#3345) —
219
+ // a failure record is not completion, matching cmdPhasePlanIndex's count.
220
+ const completionEvidence = buildSummaryFileIndex(summaries.filter((f) => !isSummaryFileBlocked(node_path_1.default.join(phaseDir, f))), extractCanonicalPlanId);
211
221
  const haltNodes = plans.map((p, i) => {
212
222
  const planId = planIds[i];
213
223
  const canonical = extractCanonicalPlanId(p);
214
224
  const summaryFile = summaryFileByPlanId.get(planId) ?? summaryFileByPlanId.get(canonical);
215
225
  const halted = summaryFile !== undefined && isSummaryFileHalted(node_path_1.default.join(phaseDir, summaryFile));
216
- const resolvedDependsOn = parsePlanDependsOn(phaseDir, p)
226
+ const resolvedDependsOn = rawDeps[i]
217
227
  .map((dep) => {
218
228
  const lower = dep.toLowerCase();
219
229
  return planIdByLower.get(lower) ?? canonicalToPlanId.get(lower) ?? null;
@@ -226,6 +236,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
226
236
  const incompletePlanSet = new Set(incompletePlans);
227
237
  const blockedByFiles = {};
228
238
  const runnablePlans = [];
239
+ const readyPlans = [];
229
240
  for (let i = 0; i < plans.length; i++) {
230
241
  const p = plans[i];
231
242
  if (!incompletePlanSet.has(p))
@@ -233,10 +244,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
233
244
  const causes = blockedBy.get(planIds[i]) ?? [];
234
245
  if (causes.length > 0) {
235
246
  blockedByFiles[p] = causes;
247
+ continue;
236
248
  }
237
- else {
238
- runnablePlans.push(p);
239
- }
249
+ runnablePlans.push(p);
250
+ // #4628: DAG-ready on top of runnable — every dependency must have
251
+ // completion evidence (a matching, non-blocked SUMMARY) and no dropped
252
+ // edge: both readers of this contract fail closed identically.
253
+ const depsComplete = rawDeps[i].length === haltNodes[i].resolvedDependsOn.length &&
254
+ haltNodes[i].resolvedDependsOn.every((dep) => completionEvidence.has(dep));
255
+ if (depsComplete)
256
+ readyPlans.push(p);
240
257
  }
241
258
  return {
242
259
  found: true,
@@ -259,19 +276,21 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
259
276
  halted_plans: haltedPlans,
260
277
  blocked_by: blockedByFiles,
261
278
  runnable_plans: runnablePlans,
279
+ ready_plans: readyPlans,
262
280
  };
263
281
  }
264
282
  catch {
265
283
  return null;
266
284
  }
267
285
  }
268
- function findPhaseInternal(cwd, phase) {
286
+ function findPhaseInternal(cwd, phase, convention) {
269
287
  if (!phase)
270
288
  return null;
271
289
  const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
272
290
  const normalized = normalizePhaseName(phase);
273
291
  const relPhasesDir = toPosixPath(node_path_1.default.relative(cwd, phasesDir));
274
- const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized);
292
+ // #4801: convention threaded through to the matcher (see searchPhaseInDir).
293
+ const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized, convention);
275
294
  if (current)
276
295
  return current;
277
296
  // #2855: scope the archived-milestone fallback to the SAME workstream as the
@@ -285,7 +304,7 @@ function findPhaseInternal(cwd, phase) {
285
304
  // getArchivedPhaseDirs via listArchiveVersionDirs (see its doc comment).
286
305
  for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
287
306
  const relBase = toPosixPath(node_path_1.default.relative(cwd, archivePath));
288
- const result = searchPhaseInDir(archivePath, relBase, normalized);
307
+ const result = searchPhaseInDir(archivePath, relBase, normalized, convention);
289
308
  if (result) {
290
309
  result.archived = version;
291
310
  return result;