@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
@@ -23,6 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_os_1 = __importDefault(require("node:os"));
24
24
  const node_path_1 = __importDefault(require("node:path"));
25
25
  const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
26
+ const security_cjs_1 = require("./security.cjs");
26
27
  const runtimeArtifactLayout = require("./runtime-artifact-layout.cjs");
27
28
  const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs");
28
29
  const runtimeNamePolicy = require("./runtime-name-policy.cjs");
@@ -111,7 +112,7 @@ function previousOwnedCorpusFiles(configDir, prefix) {
111
112
  }
112
113
  function pruneEmptyCorpusParents(start, stop) {
113
114
  let current = node_path_1.default.dirname(start);
114
- while (current !== stop && current.startsWith(stop + node_path_1.default.sep)) {
115
+ while (current !== stop && current.startsWith(stop + node_path_1.default.sep)) { // allow-handrolled-containment: ancestor-walk loop condition, not a containment gate
115
116
  if (installFs().readdirSync(current).length > 0)
116
117
  return;
117
118
  installFs().rmdirSync(current);
@@ -347,8 +348,11 @@ function hasExistingSymlinkBetween(root, fullPath, options = {}) {
347
348
  const resolvedFullPath = node_path_1.default.resolve(fullPath);
348
349
  // (a) Path-traversal refusal — ALWAYS enforced, even with opt-in. An untrusted
349
350
  // destSubpath string that escapes the install root via '..' is rejected
350
- // regardless of user opt-in state (ADR-1239 Phase B threat (a)).
351
- if (resolvedFullPath !== resolvedRoot && !resolvedFullPath.startsWith(resolvedRoot + node_path_1.default.sep)) {
351
+ // regardless of user opt-in state (ADR-1239 Phase B threat (a)). Lexical
352
+ // (ADR-4650 decision 6): this function's whole purpose is to DETECT
353
+ // symlinks between root and target, so resolving them here would erase what
354
+ // it measures.
355
+ if ((0, security_cjs_1.tryWithinRootLexical)(resolvedFullPath, resolvedRoot) === null) {
352
356
  return true;
353
357
  }
354
358
  // #2393 (security-review finding): realpathSync fully resolves all symlink
@@ -726,6 +730,16 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
726
730
  continue;
727
731
  if (!entry.name.endsWith('.md'))
728
732
  continue;
733
+ // #4782: compact agent variants are consumed ONLY through the non-claude
734
+ // gate in init.cts's agent-skills persona fallback (`runtime !== 'claude'`
735
+ // — claude's contract is a skills-injection path, never a persona
736
+ // fallback). Staging them into Claude's agents directory shipped 29 dead
737
+ // files whose `name:` frontmatter is identical to their canonical
738
+ // sibling's, leaving the harness resolution unstated. Claude never
739
+ // selects compact, so claude is the one runtime whose agents kind skips
740
+ // them; every other runtime's emission is byte-identical.
741
+ if (kind.kind === 'agents' && runtime === 'claude' && entry.name.endsWith('.compact.md'))
742
+ continue;
729
743
  const stem = entry.name.slice(0, -3); // strip .md
730
744
  let destName;
731
745
  if (kind.kind === 'agents') {
@@ -1492,6 +1506,15 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
1492
1506
  content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
1493
1507
  content = processAttribution(content, resolveAttribution(runtime));
1494
1508
  const skillDir = node_path_1.default.join(dest, skillName);
1509
+ // isPathConfined is lexical and cannot see a symlink. mkdirSync({recursive:true})
1510
+ // does NOT throw when skillDir already exists as a symlink to a directory, so a
1511
+ // pre-planted link would redirect the SKILL.md write outside `dest`. Refuse to
1512
+ // write through a link (epic #4636; mirrors retired-artifact-cleanup.cts:77).
1513
+ try {
1514
+ if (installFs().lstatSync(skillDir).isSymbolicLink())
1515
+ continue;
1516
+ }
1517
+ catch { /* ENOENT: not created yet — the normal case */ }
1495
1518
  installFs().mkdirSync(skillDir, { recursive: true });
1496
1519
  installFs().writeFileSync(node_path_1.default.join(skillDir, 'SKILL.md'), content);
1497
1520
  // #2322 HIGH-3 parity: persist the capability-owned marker so a later
@@ -1868,6 +1891,9 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
1868
1891
  isWindowsHost: process.platform === 'win32',
1869
1892
  resolvedTarget: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir)),
1870
1893
  homeDir: (0, shell_command_projection_cjs_1.posixNormalize)(node_os_1.default.homedir()),
1894
+ // #4377: the runtime's own localConfigDir, so an opted-in local install
1895
+ // emits `<dir>/...` instead of this checkout's absolute path.
1896
+ localDirName: runtimeArtifactConversion._localIncludeDirName(runtime),
1871
1897
  });
1872
1898
  // #2329: destDir is derived from the SAME hostBehaviors.flatCommandDir
1873
1899
  // descriptor value read by writeManifest's manifest-key prefix and by
@@ -740,6 +740,11 @@ function readInstalledCapabilitySkill(stem, registry) {
740
740
  return null;
741
741
  const skillPath = node_path_1.default.join(capDir, relSkillPath);
742
742
  try {
743
+ // statSync follows symlinks; isPathConfined is lexical and cannot see one.
744
+ // Refuse to read through a link so an outside file's content cannot be
745
+ // installed as a capability skill (epic #4636).
746
+ if (node_fs_1.default.lstatSync(skillPath).isSymbolicLink())
747
+ return null;
743
748
  if (!node_fs_1.default.statSync(skillPath).isFile())
744
749
  return null;
745
750
  return { capId, content: node_fs_1.default.readFileSync(skillPath, 'utf8') };
@@ -858,6 +863,15 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
858
863
  if (!(0, external_descriptor_trust_cjs_1.isPathConfined)(skillName, stageDir))
859
864
  continue; // defense-in-depth
860
865
  const destDir = node_path_1.default.join(stageDir, skillName);
866
+ // isPathConfined is lexical and cannot see a symlink. mkdirSync({recursive:true})
867
+ // does NOT throw when destDir already exists as a symlink to a directory, so a
868
+ // pre-planted link would redirect the SKILL.md write outside `stageDir`. Refuse
869
+ // to write through a link (epic #4636; mirrors retired-artifact-cleanup.cts:77).
870
+ try {
871
+ if (installFs().lstatSync(destDir).isSymbolicLink())
872
+ continue;
873
+ }
874
+ catch { /* ENOENT: not created yet — the normal case */ }
861
875
  installFs().mkdirSync(destDir, { recursive: true });
862
876
  installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), found.content);
863
877
  // #2322 HIGH-3: persist the capability-owned marker so a later prune
@@ -17,6 +17,7 @@ const installer_migration_authoring_cjs_1 = require("./installer-migration-autho
17
17
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
18
18
  const clock_cjs_1 = require("./clock.cjs");
19
19
  const install_scope_cjs_1 = require("./install-scope.cjs");
20
+ const security_cjs_1 = require("./security.cjs");
20
21
  // #2874 (ADR-58 cleanup phase): this file is the ~1200-line migration
21
22
  // plan/apply/rollback/lock/journal engine — almost none of it is on the
22
23
  // installRuntimeArtifacts call tree. Only `readInstallManifest` and
@@ -122,8 +123,17 @@ function evaluateRemoveEmptyDir(configDir, fullPath) {
122
123
  catch {
123
124
  return 'left-in-place';
124
125
  }
125
- if (resolvedTarget === resolvedRoot || !resolvedTarget.startsWith(resolvedRoot + node_path_1.default.sep)) {
126
- // Refuses both "target IS configDir" and "target escaped configDir".
126
+ // `resolvedTarget === resolvedRoot` is a DELIBERATE ADDITIONAL rejection,
127
+ // separate from the containment decision: `tryWithinRootLexical` treats
128
+ // target === root as CONTAINED, but removing the config root itself is
129
+ // never in scope for this action (see the doc comment above) — this arm
130
+ // prevents `rmdirSync` from ever being asked to remove `configDir` itself.
131
+ // Kept as its own check per ADR-4650 decision 6 (a wrapper may add its own
132
+ // conditions on top of the canonical predicate, never invert it).
133
+ if (resolvedTarget === resolvedRoot)
134
+ return 'left-in-place';
135
+ if ((0, security_cjs_1.tryWithinRootLexical)(resolvedTarget, resolvedRoot) === null) {
136
+ // Refuses "target escaped configDir".
127
137
  return 'left-in-place';
128
138
  }
129
139
  let entries;
@@ -270,7 +280,14 @@ function readInstallManifest(configDir) {
270
280
  version: typeof m.version === 'string' ? m.version : null,
271
281
  timestamp: typeof m.timestamp === 'string' ? m.timestamp : null,
272
282
  mode: typeof m.mode === 'string' ? m.mode : null,
273
- files: m.files && typeof m.files === 'object' ? m.files : {},
283
+ // #4544 (review): `typeof [] === 'object'` — a manifest whose `files` is a
284
+ // JSON array passed the object-shape guard, and Object.keys() then yielded
285
+ // "0","1",... as install-relative file paths. Consumers iterate these keys,
286
+ // so an array shape must degrade to the empty set exactly like a
287
+ // non-object shape does.
288
+ files: m.files && typeof m.files === 'object' && !Array.isArray(m.files)
289
+ ? m.files
290
+ : {},
274
291
  manifestVersion: normalizeManifestVersion(m.manifestVersion),
275
292
  runtime: normalizeReportedRuntime(rawRuntime),
276
293
  scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
@@ -598,11 +615,30 @@ function acquireInstallMigrationLock(configDir, { timeoutMs = DEFAULT_LOCK_TIMEO
598
615
  }
599
616
  }
600
617
  }
618
+ // DELIBERATELY LEXICAL — the RESOLUTION policy stays lexical, never realpath
619
+ // (`assertWithinRoot` / `tryWithinRoot`, src/security.cts).
620
+ //
621
+ // Reviewed under epic #4636 Phase 3 and reverted after the remote matrix proved
622
+ // the realpath collapse wrong. This module's contract is that a symlinked
623
+ // managed path is treated AS A LINK and never dereferenced — it is snapshotted
624
+ // as a link, restored as a link, and backed up as a link. The realpath-based
625
+ // predicate dereferences exactly the symlinks this module exists to preserve
626
+ // and then rejects them for escaping configDir
627
+ // ("migration path escapes configDir: extensions/gsd.cjs"). Four tests in
628
+ // tests/installer-migrations.test.cjs pin that behavior.
629
+ //
630
+ // The containment DECISION now routes through the canonical LEXICAL predicate
631
+ // (`tryWithinRootLexical`, ADR-4650 decision 6) — only the comparison moved;
632
+ // the lexical policy itself remains this module's own required choice, and
633
+ // the thrown message / returned `fullPath` are unchanged.
634
+ //
635
+ // `normalizeRelPath` is the pre-gate: it throws on absolute paths and on any
636
+ // '..' segment BEFORE this runs, so the check below is defense-in-depth over
637
+ // already-traversal-free input rather than the primary boundary.
601
638
  function ensureInsideConfig(configDir, relPath) {
602
639
  const normalized = normalizeRelPath(relPath);
603
640
  const fullPath = node_path_1.default.resolve(configDir, normalized);
604
- const root = node_path_1.default.resolve(configDir);
605
- if (fullPath !== root && !fullPath.startsWith(root + node_path_1.default.sep)) {
641
+ if ((0, security_cjs_1.tryWithinRootLexical)(fullPath, configDir) === null) {
606
642
  throw new Error(`migration path escapes configDir: ${relPath}`);
607
643
  }
608
644
  return { normalized, fullPath };
@@ -224,6 +224,9 @@ function resolveLoopHooks(input) {
224
224
  active.consumes = consumes;
225
225
  if (onError !== undefined)
226
226
  active.onError = onError;
227
+ // #4209 DISP-02: only a literal `true` projects; absent/false stay inert.
228
+ if (hook['supportsReviewerLanes'] === true)
229
+ active.supportsReviewerLanes = true;
227
230
  activeHooks.push(active);
228
231
  }
229
232
  // Process contributions
@@ -422,17 +425,20 @@ function sanitizeLoadFailReason(reason) {
422
425
  .slice(0, 300);
423
426
  return cleaned || '(no reason given)';
424
427
  }
425
- function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
426
- if (!point) {
427
- coreError('loop render-hooks requires a <point> argument. Valid points: ' + CANONICAL_POINTS.join(', '));
428
- return;
429
- }
430
- // --active-cap <capId> mode: emit 'true' or 'false' only (scanner-safe, no JSON envelope)
431
- const activeCapId = typeof options['activeCap'] === 'string' ? options['activeCap'] : undefined;
432
- if (activeCapId !== undefined && activeCapId === '') {
433
- coreError('--active-cap requires a <capId> value (e.g. --active-cap tdd)');
434
- return;
435
- }
428
+ /**
429
+ * The full config/registry/capability-state resolution `cmdLoopRenderHooks` performs, minus its
430
+ * CLI-only output formatting — extracted so an in-process caller (e.g. `review-lane dispatch-step`
431
+ * self-verifying a `supportsReviewerLanes` trait) can reach the SAME resolution `gsd_run loop
432
+ * render-hooks <point> --raw` would give it, without spawning a subprocess and re-parsing its
433
+ * stdout (which was subject to `io.cjs`'s `@file:` overflow protocol on the rendered-string
434
+ * envelope — a bug class this in-process call cannot hit, since it never touches that envelope
435
+ * or its rendering at all).
436
+ *
437
+ * Throws on an invalid `point` (mirrors `resolveLoopHooks`); callers convert to their own error
438
+ * channel. Emits the same loud stderr load-failure warnings `cmdLoopRenderHooks` always has,
439
+ * regardless of caller — a skipped gate must never be silently invisible.
440
+ */
441
+ function resolveActiveHooksForPoint(cwd, point, options = {}) {
436
442
  const runtimeConfigDir = typeof options['configDir'] === 'string'
437
443
  ? options['configDir']
438
444
  : undefined;
@@ -468,15 +474,7 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
468
474
  for (const cap of state.capabilities || []) {
469
475
  capabilityStatesById.set(cap.id, cap);
470
476
  }
471
- let resolved;
472
- try {
473
- resolved = resolveLoopHooks({ point, registry, config, cwd, capabilityStatesById });
474
- }
475
- catch (err) {
476
- const msg = (err instanceof Error) ? err.message : String(err);
477
- coreError(msg);
478
- return;
479
- }
477
+ const resolved = resolveLoopHooks({ point, registry, config, cwd, capabilityStatesById });
480
478
  // ── ADR-1244 D2: load-failed capability gates FAIL OPEN with a loud warning ────
481
479
  // Decision (#2009): a capability that failed to LOAD must not block the loop.
482
480
  // The prior behavior injected a BLOCKING synthetic gate (blocking:true,
@@ -525,24 +523,44 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
525
523
  for (const w of loadFailWarnings) {
526
524
  process.stderr.write(`gsd: warning — ${w}\n`);
527
525
  }
528
- // --active-cap mode: print exactly 'true' or 'false' with no envelope
526
+ // Surface capability-state warnings and the #2009 load-failure fail-open warnings together
527
+ // (in addition to the stderr emission above, which is the channel host workflows actually see).
528
+ const combinedWarnings = [...(state.warnings || []), ...loadFailWarnings];
529
+ return { point: resolved.point, activeHooks: resolved.activeHooks, warnings: combinedWarnings };
530
+ }
531
+ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
532
+ if (!point) {
533
+ coreError('loop render-hooks requires a <point> argument. Valid points: ' + CANONICAL_POINTS.join(', '));
534
+ return;
535
+ }
536
+ // --active-cap <capId> mode: emit 'true' or 'false' only (scanner-safe, no JSON envelope)
537
+ const activeCapId = typeof options['activeCap'] === 'string' ? options['activeCap'] : undefined;
538
+ if (activeCapId !== undefined && activeCapId === '') {
539
+ coreError('--active-cap requires a <capId> value (e.g. --active-cap tdd)');
540
+ return;
541
+ }
542
+ let result;
543
+ try {
544
+ result = resolveActiveHooksForPoint(cwd, point, options);
545
+ }
546
+ catch (err) {
547
+ const msg = (err instanceof Error) ? err.message : String(err);
548
+ coreError(msg);
549
+ return;
550
+ }
529
551
  if (activeCapId !== undefined) {
530
- const isActive = resolved.activeHooks.some((h) => h.capId === activeCapId);
552
+ const isActive = result.activeHooks.some((h) => h.capId === activeCapId);
531
553
  process.stdout.write(isActive ? 'true\n' : 'false\n');
532
554
  return;
533
555
  }
534
- const rendered = renderLoopHooks(resolved);
556
+ const rendered = renderLoopHooks({ point: result.point, activeHooks: result.activeHooks });
535
557
  const envelope = {
536
- point: resolved.point,
537
- activeHooks: resolved.activeHooks,
558
+ point: result.point,
559
+ activeHooks: result.activeHooks,
538
560
  rendered,
539
561
  };
540
- // Surface capability-state warnings and the #2009 load-failure fail-open
541
- // warnings together in the structured `warnings` channel (in addition to the
542
- // stderr emission above, which is the channel host workflows actually see).
543
- const combinedWarnings = [...(state.warnings || []), ...loadFailWarnings];
544
- if (combinedWarnings.length > 0) {
545
- envelope.warnings = combinedWarnings;
562
+ if (result.warnings.length > 0) {
563
+ envelope.warnings = result.warnings;
546
564
  }
547
565
  coreOutput(envelope, raw);
548
566
  }
@@ -550,6 +568,7 @@ module.exports = {
550
568
  resolveLoopHooks,
551
569
  renderLoopHooks,
552
570
  cmdLoopRenderHooks,
571
+ resolveActiveHooksForPoint,
553
572
  // Exported for tests
554
573
  _getNestedConfigValue,
555
574
  _resolveActivationValue,
@@ -402,8 +402,8 @@ function classifyUnindexedUri(uri) {
402
402
  }
403
403
  /** Second, independent gate (design "Hostile inputs" gate 2): re-validate an INDEXED entry's relPath against the catalog root, catching a symlink planted after the index was built. */
404
404
  function readIndexedResource(catalog, entry) {
405
- const check = (0, security_cjs_1.validatePath)(entry.relPath, catalog.root);
406
- if (!check.safe) {
405
+ const contained = (0, security_cjs_1.tryWithinRoot)(entry.relPath, catalog.root);
406
+ if (contained === null) {
407
407
  fail(exports.REASON.TRAVERSAL_REFUSED, `indexed resource escapes catalog root: ${entry.relPath}`);
408
408
  }
409
409
  let raw;
@@ -38,7 +38,7 @@ const stateContract = require("./state-contract.cjs");
38
38
  const { publishStateContract } = stateContract;
39
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports
40
40
  const phaseIdMod = require("./phase-id.cjs");
41
- const { normalizePhaseName, matchPhaseDirs, PHASE_NUMBER_TOKEN_SOURCE, isSentinelPhaseId, isSentinelPhaseDir } = phaseIdMod;
41
+ const { normalizePhaseName, matchPhaseDirs, isSentinelPhaseId, isSentinelPhaseDir, buildPhaseHeadingScanRegex, PHASE_HEADING_BASELINE } = phaseIdMod;
42
42
  const pattern_cjs_1 = require("./pattern.cjs");
43
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
44
  const roadmapParserMod = require("./roadmap-parser.cjs");
@@ -642,8 +642,21 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
642
642
  // windows converge to the same value regardless of which version drove
643
643
  // the lookup.
644
644
  const scopedContent = sliceMilestoneWindow(roadmapContent, version) ?? extractCurrentMilestone(roadmapContent, cwd);
645
- // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
646
- const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
645
+ // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned
646
+ // by buildPhaseHeadingScanRegex (phase-id.cts) so this guard also
647
+ // recognizes bracket-convention headings instead of hand-rolling a
648
+ // literal `Phase\s+`.
649
+ // #4984 fix: resolved ONCE and threaded into BOTH the heading scan and
650
+ // the disk-side matchPhaseDirs check below — the heading scan alone
651
+ // recognizing `[GSD.02] 01:` and extracting the bare phase-number token
652
+ // "01" is only half the fix. Without also passing this convention to
653
+ // matchPhaseDirs, its bracket-qualified match arm never fires, "01"
654
+ // matches no bracket directory name ("GSD.02-01-setup"), and every
655
+ // properly-scaffolded bracket phase reads as disk_status: 'no_directory'
656
+ // — turning a real bracket project's own phases into false "ROADMAP
657
+ // lists N unstarted phase(s)" failures on `milestone complete`.
658
+ const convention = resolvePhaseIdConvention(cwd);
659
+ const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, convention);
647
660
  const noDirectoryPhases = [];
648
661
  let pm;
649
662
  const phaseDirEntries = (() => {
@@ -658,7 +671,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
658
671
  }
659
672
  })();
660
673
  while ((pm = phasePattern.exec(scopedContent)) !== null) {
661
- const phaseNum = pm[1];
674
+ const phaseNum = pm[phaseNumGroup];
662
675
  // Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not
663
676
  // real phases — they legitimately have no directory and must not block
664
677
  // milestone completion. Mirrors the engine-wide sentinel convention
@@ -672,7 +685,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
672
685
  // with a matching token exists on disk. Use the same matchPhaseDirs
673
686
  // owner that roadmap.analyze uses to avoid false positives on decimal
674
687
  // (2.1) and letter-suffix (12A) phase IDs. (#2528)
675
- const hasDirectory = matchPhaseDirs(phaseDirEntries, normalized).matches.length > 0;
688
+ const hasDirectory = matchPhaseDirs(phaseDirEntries, normalized, convention).matches.length > 0;
676
689
  if (!hasDirectory) {
677
690
  noDirectoryPhases.push(phaseNum);
678
691
  }
@@ -826,7 +839,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
826
839
  // never disagree with what a real run actually archives. Absent
827
840
  // --archive-quick this stays `[]` and nothing on disk is touched either
828
841
  // way (dry-run always returns before any mutation below).
829
- const quickDirsToArchive = options.archiveQuick ? listQuickTaskDirsForArchive(cwd) : [];
842
+ const quickDirsToArchive = options.archiveQuick ? listQuickTaskDirsForArchive(cwd).map((d) => d.name) : [];
830
843
  const dryRunResult = {
831
844
  dry_run: true,
832
845
  version,
@@ -1482,6 +1495,16 @@ function writeQuickArchiveReadme(archiveQuickDir) {
1482
1495
  * written three times, and only the real-run copy applied `requireSafePath`,
1483
1496
  * so a dry-run preview could list a directory the real run would silently
1484
1497
  * skip).
1498
+ *
1499
+ * Returns the proven `ContainedPath` alongside each entry's bare `name`
1500
+ * (`no-unconfined-path-join`'s `discardedContainmentResult` arm — a bare
1501
+ * statement call to `requireSafePath` throws away the exact answer it just
1502
+ * computed). The two dry-run previews only need `name` for display;
1503
+ * `archiveQuickTaskDirectories` deliberately does NOT reuse `abs` for its
1504
+ * rename — it re-derives and re-validates independently as TOCTOU
1505
+ * defense-in-depth (see its own comment), so `abs` exists here only to make
1506
+ * this function's own discard explicit, not to be trusted downstream as a
1507
+ * stale-safe proof.
1485
1508
  */
1486
1509
  function listQuickTaskDirsForArchive(cwd) {
1487
1510
  const planningBase = planningPaths(cwd).planning;
@@ -1494,19 +1517,20 @@ function listQuickTaskDirsForArchive(cwd) {
1494
1517
  // .planning/quick absent or unreadable — nothing to select.
1495
1518
  return [];
1496
1519
  }
1497
- const names = [];
1520
+ const results = [];
1498
1521
  for (const entry of sourceEntries) {
1499
1522
  if (!entry.isDirectory())
1500
1523
  continue; // excludes symlinks too — see MAJOR 3 note above
1524
+ let abs;
1501
1525
  try {
1502
- (0, security_cjs_1.requireSafePath)(node_path_1.default.join(quickDir, entry.name), planningBase, 'quick task dir', { allowAbsolute: true });
1526
+ abs = (0, security_cjs_1.requireSafePath)(node_path_1.default.join(quickDir, entry.name), planningBase, 'quick task dir', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
1503
1527
  }
1504
1528
  catch {
1505
1529
  continue; // symlink/escape attempt — never a candidate, in preview OR real run
1506
1530
  }
1507
- names.push(entry.name);
1531
+ results.push({ name: entry.name, abs });
1508
1532
  }
1509
- return names.sort();
1533
+ return results.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
1510
1534
  }
1511
1535
  /**
1512
1536
  * #2142: move each DIRECTORY entry under `.planning/quick/` into
@@ -1555,7 +1579,7 @@ function archiveQuickTaskDirectories(cwd, version) {
1555
1579
  // #2142 MAJOR 5 (review): dirNames is the SAME selection
1556
1580
  // `listQuickTaskDirsForArchive` hands to both dry-run previews — this is
1557
1581
  // the real run, so it cannot disagree with what a preview reported.
1558
- const dirNames = listQuickTaskDirsForArchive(cwd);
1582
+ const dirNames = listQuickTaskDirsForArchive(cwd).map((d) => d.name);
1559
1583
  if (dirNames.length === 0) {
1560
1584
  // Boundary 0 (#2142): zero (safe) directory entries (empty dir, only
1561
1585
  // stray files, or every entry excluded by the selection rule) must not
@@ -1576,7 +1600,7 @@ function archiveQuickTaskDirectories(cwd, version) {
1576
1600
  // rename are two separate filesystem observations, and an entry
1577
1601
  // that was a safe real directory at selection time could in theory
1578
1602
  // be swapped for a symlink before this loop reaches it.
1579
- safeSrc = (0, security_cjs_1.requireSafePath)(src, planningBase, 'quick task dir', { allowAbsolute: true });
1603
+ safeSrc = (0, security_cjs_1.requireSafePath)(src, planningBase, 'quick task dir', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
1580
1604
  }
1581
1605
  catch {
1582
1606
  continue; // symlink/escape attempt — skip, not archived
@@ -1669,7 +1693,7 @@ function cmdQuickArchive(cwd, version, options, raw) {
1669
1693
  // `cmdMilestoneComplete`'s own dry-run preview and the real
1670
1694
  // `archiveQuickTaskDirectories` both use, so all three can never disagree.
1671
1695
  if (options.dryRun) {
1672
- const quickDirsToArchive = listQuickTaskDirsForArchive(cwd);
1696
+ const quickDirsToArchive = listQuickTaskDirsForArchive(cwd).map((d) => d.name);
1673
1697
  output({
1674
1698
  dry_run: true,
1675
1699
  version,