@opengsd/gsd-core 1.14.0 → 1.16.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 (551) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/README.ja-JP.md +3 -3
  5. package/README.ko-KR.md +3 -3
  6. package/README.pt-BR.md +3 -3
  7. package/README.zh-CN.md +3 -3
  8. package/agents/gsd-code-fixer.compact.md +7 -6
  9. package/agents/gsd-code-fixer.md +9 -8
  10. package/agents/gsd-code-reviewer.compact.md +5 -3
  11. package/agents/gsd-code-reviewer.md +8 -6
  12. package/agents/gsd-debug-session-manager.compact.md +17 -2
  13. package/agents/gsd-debug-session-manager.md +17 -2
  14. package/agents/gsd-debugger.md +3 -3
  15. package/agents/gsd-eval-auditor.compact.md +1 -1
  16. package/agents/gsd-eval-auditor.md +1 -1
  17. package/agents/gsd-executor.md +17 -12
  18. package/agents/gsd-intel-updater.compact.md +1 -1
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +2 -2
  21. package/agents/gsd-phase-researcher.md +19 -11
  22. package/agents/gsd-plan-checker.md +15 -9
  23. package/agents/gsd-planner.md +15 -11
  24. package/agents/gsd-project-researcher.compact.md +1 -1
  25. package/agents/gsd-project-researcher.md +1 -1
  26. package/agents/gsd-research-synthesizer.compact.md +1 -1
  27. package/agents/gsd-research-synthesizer.md +1 -1
  28. package/agents/gsd-ui-auditor.compact.md +21 -30
  29. package/agents/gsd-ui-auditor.md +166 -37
  30. package/agents/gsd-ui-researcher.compact.md +1 -1
  31. package/agents/gsd-ui-researcher.md +1 -1
  32. package/agents/gsd-verifier.md +37 -14
  33. package/bin/install.js +764 -287
  34. package/commands/gsd/add-tests.md +6 -1
  35. package/commands/gsd/ai-integration-phase.md +6 -1
  36. package/commands/gsd/audit-fix.md +5 -0
  37. package/commands/gsd/audit-milestone.md +6 -1
  38. package/commands/gsd/autonomous.md +7 -2
  39. package/commands/gsd/capture.md +9 -5
  40. package/commands/gsd/code-review.md +7 -2
  41. package/commands/gsd/complete-milestone.md +4 -0
  42. package/commands/gsd/config.md +7 -3
  43. package/commands/gsd/debug.md +11 -7
  44. package/commands/gsd/discuss-phase.md +7 -3
  45. package/commands/gsd/docs-update.md +12 -7
  46. package/commands/gsd/eval-review.md +6 -1
  47. package/commands/gsd/execute-phase.md +12 -7
  48. package/commands/gsd/extract-learnings.md +5 -0
  49. package/commands/gsd/fast.md +4 -0
  50. package/commands/gsd/forensics.md +5 -1
  51. package/commands/gsd/graphify.md +10 -6
  52. package/commands/gsd/health.md +5 -0
  53. package/commands/gsd/help.md +7 -2
  54. package/commands/gsd/import.md +7 -3
  55. package/commands/gsd/inbox.md +5 -0
  56. package/commands/gsd/ingest-docs.md +5 -1
  57. package/commands/gsd/manager.md +6 -1
  58. package/commands/gsd/map-codebase.md +7 -3
  59. package/commands/gsd/mempalace-capture.md +12 -4
  60. package/commands/gsd/mempalace-recall.md +5 -1
  61. package/commands/gsd/milestone-summary.md +5 -1
  62. package/commands/gsd/mvp-phase.md +8 -3
  63. package/commands/gsd/new-milestone.md +6 -1
  64. package/commands/gsd/new-project.md +5 -0
  65. package/commands/gsd/next.md +6 -1
  66. package/commands/gsd/ns-context.md +4 -0
  67. package/commands/gsd/ns-ideate.md +4 -0
  68. package/commands/gsd/ns-manage.md +4 -0
  69. package/commands/gsd/ns-project.md +4 -0
  70. package/commands/gsd/ns-review.md +4 -0
  71. package/commands/gsd/ns-workflow.md +4 -0
  72. package/commands/gsd/onboard.md +6 -1
  73. package/commands/gsd/pause-work.md +5 -1
  74. package/commands/gsd/phase.md +8 -4
  75. package/commands/gsd/plan-phase.md +6 -1
  76. package/commands/gsd/plan-review-convergence.md +11 -7
  77. package/commands/gsd/pr-branch.md +4 -0
  78. package/commands/gsd/profile-user.md +5 -1
  79. package/commands/gsd/progress.md +7 -2
  80. package/commands/gsd/quick-batch.md +21 -9
  81. package/commands/gsd/quick.md +12 -7
  82. package/commands/gsd/review.md +7 -4
  83. package/commands/gsd/secure-phase.md +6 -1
  84. package/commands/gsd/ship.md +5 -0
  85. package/commands/gsd/sketch.md +7 -2
  86. package/commands/gsd/spec-phase.md +5 -1
  87. package/commands/gsd/spike.md +8 -3
  88. package/commands/gsd/surface.md +5 -1
  89. package/commands/gsd/thread.md +4 -0
  90. package/commands/gsd/ui-phase.md +6 -1
  91. package/commands/gsd/ui-review.md +6 -1
  92. package/commands/gsd/ultraplan-phase.md +5 -1
  93. package/commands/gsd/undo.md +5 -1
  94. package/commands/gsd/update.md +6 -2
  95. package/commands/gsd/validate-phase.md +6 -1
  96. package/commands/gsd/verify-work.md +6 -1
  97. package/commands/gsd/workspace.md +7 -3
  98. package/gsd-core/bin/gsd-tools.cjs +477 -78
  99. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  100. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  101. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  102. package/gsd-core/bin/lib/audit.cjs +144 -42
  103. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  104. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  105. package/gsd-core/bin/lib/capability-registry.cjs +197 -222
  106. package/gsd-core/bin/lib/capability-validator.cjs +16 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +343 -207
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  117. package/gsd-core/bin/lib/config.cjs +76 -19
  118. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  119. package/gsd-core/bin/lib/coverage.cjs +4 -8
  120. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  121. package/gsd-core/bin/lib/decisions.cjs +30 -14
  122. package/gsd-core/bin/lib/drift.cjs +177 -42
  123. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  124. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  125. package/gsd-core/bin/lib/frontmatter.cjs +426 -234
  126. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  127. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  128. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  129. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  130. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  131. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  132. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  133. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  134. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  135. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  136. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  137. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  138. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  139. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  140. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  142. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  143. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  144. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  145. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  146. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  147. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  148. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  149. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  150. package/gsd-core/bin/lib/graphify.cjs +10 -2
  151. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  152. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  153. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  154. package/gsd-core/bin/lib/init.cjs +364 -132
  155. package/gsd-core/bin/lib/install-engine.cjs +30 -33
  156. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  157. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  158. package/gsd-core/bin/lib/io.cjs +122 -3
  159. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  160. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  161. package/gsd-core/bin/lib/milestone.cjs +37 -6
  162. package/gsd-core/bin/lib/model-resolver.cjs +171 -62
  163. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  164. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  165. package/gsd-core/bin/lib/pattern.cjs +10 -0
  166. package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
  167. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  168. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  169. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  170. package/gsd-core/bin/lib/phase-id.cjs +110 -8
  171. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  172. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  173. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  174. package/gsd-core/bin/lib/phase.cjs +489 -88
  175. package/gsd-core/bin/lib/plan-document.cjs +142 -20
  176. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  177. package/gsd-core/bin/lib/planning-document.cjs +692 -0
  178. package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
  179. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  180. package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
  181. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  184. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  185. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  186. package/gsd-core/bin/lib/project-root.cjs +41 -2
  187. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  188. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  189. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  190. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  191. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  192. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  193. package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
  194. package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
  195. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
  196. package/gsd-core/bin/lib/roadmap.cjs +405 -88
  197. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
  198. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
  199. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  200. package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
  201. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
  202. package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
  203. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  204. package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
  205. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  206. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  207. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  208. package/gsd-core/bin/lib/state-document.cjs +100 -22
  209. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  210. package/gsd-core/bin/lib/state.cjs +256 -105
  211. package/gsd-core/bin/lib/surface.cjs +19 -2
  212. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
  213. package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
  214. package/gsd-core/bin/lib/uat.cjs +432 -7
  215. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  216. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
  217. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  218. package/gsd-core/bin/lib/vendor/README.md +31 -9
  219. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  220. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  221. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  222. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  223. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  224. package/gsd-core/bin/lib/verification.cjs +1378 -274
  225. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  226. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  227. package/gsd-core/bin/lib/verify.cjs +357 -531
  228. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  229. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  230. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  231. package/gsd-core/bin/lib/workstream.cjs +11 -2
  232. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  233. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  234. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
  235. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  236. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  237. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  238. package/gsd-core/references/checkpoints.md +5 -3
  239. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  240. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  241. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  242. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  243. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  244. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  245. package/gsd-core/references/edge-probe.md +195 -21
  246. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  247. package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
  248. package/gsd-core/references/execute-phase-response-language.md +1 -1
  249. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  250. package/gsd-core/references/gsd-run-resolver.md +1 -1
  251. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  252. package/gsd-core/references/model-profiles.md +1 -1
  253. package/gsd-core/references/offer-next.md +1 -1
  254. package/gsd-core/references/phase-argument-parsing.md +9 -7
  255. package/gsd-core/references/phase-id-convention.md +28 -0
  256. package/gsd-core/references/planner-gap-closure.md +2 -0
  257. package/gsd-core/references/planner-load-graph-context.md +24 -13
  258. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  259. package/gsd-core/references/planning-config.md +12 -3
  260. package/gsd-core/references/spidr-splitting.md +1 -1
  261. package/gsd-core/references/tdd.md +37 -8
  262. package/gsd-core/references/ui-consideration-probe.md +10 -5
  263. package/gsd-core/references/verifier-phase-gates.md +5 -2
  264. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  265. package/gsd-core/references/verify-mvp-mode.md +2 -2
  266. package/gsd-core/references/workstream-flag.md +33 -3
  267. package/gsd-core/references/worktree-path-safety.md +321 -0
  268. package/gsd-core/templates/README.md +1 -1
  269. package/gsd-core/templates/UAT.md +17 -1
  270. package/gsd-core/templates/config.json +2 -11
  271. package/gsd-core/templates/verification-report.md +1 -1
  272. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  273. package/gsd-core/workflows/add-backlog.md +1 -1
  274. package/gsd-core/workflows/add-phase.md +8 -7
  275. package/gsd-core/workflows/add-tests.md +4 -3
  276. package/gsd-core/workflows/add-todo.md +6 -5
  277. package/gsd-core/workflows/ai-integration-phase.md +13 -4
  278. package/gsd-core/workflows/audit-fix.md +1 -1
  279. package/gsd-core/workflows/audit-milestone.md +4 -3
  280. package/gsd-core/workflows/audit-uat.md +1 -1
  281. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  282. package/gsd-core/workflows/autonomous.md +43 -23
  283. package/gsd-core/workflows/check-todos.md +7 -6
  284. package/gsd-core/workflows/cleanup.md +2 -2
  285. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  286. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  287. package/gsd-core/workflows/code-review-fix.md +112 -25
  288. package/gsd-core/workflows/code-review.md +146 -135
  289. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  290. package/gsd-core/workflows/complete-milestone.md +13 -8
  291. package/gsd-core/workflows/debug.md +32 -7
  292. package/gsd-core/workflows/diagnose-issues.md +3 -2
  293. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  294. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  295. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  296. package/gsd-core/workflows/discuss-phase.md +4 -3
  297. package/gsd-core/workflows/do.md +2 -2
  298. package/gsd-core/workflows/docs-update.md +6 -5
  299. package/gsd-core/workflows/edit-phase.md +4 -3
  300. package/gsd-core/workflows/eval-review.md +14 -5
  301. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  302. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
  303. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  304. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  305. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  306. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  307. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  308. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  309. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  310. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  311. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  312. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  313. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  314. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  315. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  316. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  317. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  318. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  319. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  320. package/gsd-core/workflows/execute-phase.md +96 -178
  321. package/gsd-core/workflows/execute-plan.md +18 -23
  322. package/gsd-core/workflows/explore.md +4 -4
  323. package/gsd-core/workflows/extract-learnings.md +4 -2
  324. package/gsd-core/workflows/fast.md +1 -1
  325. package/gsd-core/workflows/forensics.md +1 -1
  326. package/gsd-core/workflows/graduation.md +1 -1
  327. package/gsd-core/workflows/health.md +3 -2
  328. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  329. package/gsd-core/workflows/help/modes/full.md +5 -5
  330. package/gsd-core/workflows/help/modes/topic.md +15 -5
  331. package/gsd-core/workflows/import.md +4 -3
  332. package/gsd-core/workflows/inbox.md +2 -2
  333. package/gsd-core/workflows/ingest-docs.md +3 -3
  334. package/gsd-core/workflows/insert-phase.md +4 -3
  335. package/gsd-core/workflows/list-seeds.md +1 -1
  336. package/gsd-core/workflows/list-workspaces.md +1 -1
  337. package/gsd-core/workflows/manager.md +6 -4
  338. package/gsd-core/workflows/map-codebase.md +5 -4
  339. package/gsd-core/workflows/milestone-summary.md +3 -2
  340. package/gsd-core/workflows/mvp-phase.md +14 -14
  341. package/gsd-core/workflows/new-milestone.md +11 -11
  342. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  343. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  344. package/gsd-core/workflows/new-project.md +7 -7
  345. package/gsd-core/workflows/new-workspace.md +2 -2
  346. package/gsd-core/workflows/next.md +1 -1
  347. package/gsd-core/workflows/note.md +1 -1
  348. package/gsd-core/workflows/onboard.md +1 -1
  349. package/gsd-core/workflows/pause-work.md +2 -2
  350. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  351. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  352. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  353. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  354. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  355. package/gsd-core/workflows/plan-phase.md +50 -24
  356. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  357. package/gsd-core/workflows/plant-seed.md +62 -20
  358. package/gsd-core/workflows/pr-branch.md +113 -13
  359. package/gsd-core/workflows/profile-user.md +2 -2
  360. package/gsd-core/workflows/progress.md +19 -49
  361. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
  362. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  363. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  364. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  365. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  366. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  367. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  368. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  369. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  370. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  371. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  372. package/gsd-core/workflows/quick-batch.md +15 -10
  373. package/gsd-core/workflows/quick.md +62 -39
  374. package/gsd-core/workflows/reapply-patches.md +9 -3
  375. package/gsd-core/workflows/remove-phase.md +3 -2
  376. package/gsd-core/workflows/remove-workspace.md +2 -2
  377. package/gsd-core/workflows/resume-project.md +3 -2
  378. package/gsd-core/workflows/review.md +33 -17
  379. package/gsd-core/workflows/scan.md +3 -2
  380. package/gsd-core/workflows/secure-phase.md +13 -13
  381. package/gsd-core/workflows/settings-advanced.md +30 -10
  382. package/gsd-core/workflows/settings-integrations.md +2 -3
  383. package/gsd-core/workflows/settings.md +4 -4
  384. package/gsd-core/workflows/ship.md +14 -13
  385. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  386. package/gsd-core/workflows/sketch.md +1 -1
  387. package/gsd-core/workflows/smart-entry.md +2 -2
  388. package/gsd-core/workflows/spec-phase.md +15 -5
  389. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  390. package/gsd-core/workflows/spike.md +1 -1
  391. package/gsd-core/workflows/stats.md +1 -1
  392. package/gsd-core/workflows/sync-skills.md +5 -5
  393. package/gsd-core/workflows/thread.md +2 -2
  394. package/gsd-core/workflows/transition.md +13 -23
  395. package/gsd-core/workflows/ui-phase.md +48 -11
  396. package/gsd-core/workflows/ui-review.md +21 -6
  397. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  398. package/gsd-core/workflows/undo.md +339 -20
  399. package/gsd-core/workflows/update.md +7 -7
  400. package/gsd-core/workflows/validate-phase.md +12 -13
  401. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  402. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  403. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  404. package/gsd-core/workflows/verify-work.md +129 -55
  405. package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
  406. package/hooks/dist/gsd-check-update-worker.js +8 -0
  407. package/hooks/dist/gsd-check-update.js +8 -0
  408. package/hooks/dist/gsd-context-monitor.js +31 -8
  409. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  410. package/hooks/dist/gsd-secret-read-guard.js +161 -4
  411. package/hooks/dist/gsd-statusline.js +85 -20
  412. package/hooks/dist/gsd-update-banner.js +8 -0
  413. package/hooks/dist/gsd-validate-commit.sh +63 -4
  414. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  415. package/hooks/dist/gsd-workflow-guard.js +5 -4
  416. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  417. package/hooks/dist/lib/cli-exit.js +12 -0
  418. package/hooks/dist/lib/git-probe.js +17 -1
  419. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  420. package/hooks/gsd-agent-isolation-guard.js +32 -0
  421. package/hooks/gsd-check-update-worker.js +8 -0
  422. package/hooks/gsd-check-update.js +8 -0
  423. package/hooks/gsd-context-monitor.js +31 -8
  424. package/hooks/gsd-cursor-subagent-start.js +8 -0
  425. package/hooks/gsd-secret-read-guard.js +161 -4
  426. package/hooks/gsd-statusline.js +85 -20
  427. package/hooks/gsd-update-banner.js +8 -0
  428. package/hooks/gsd-validate-commit.sh +63 -4
  429. package/hooks/gsd-windsurf-pre-write.js +11 -2
  430. package/hooks/gsd-workflow-guard.js +5 -4
  431. package/hooks/gsd-worktree-path-guard.js +6 -2
  432. package/hooks/hooks.json +5 -5
  433. package/hooks/lib/cli-exit.js +12 -0
  434. package/hooks/lib/git-probe.js +17 -1
  435. package/hooks/lib/isolation-sentinel.js +2 -2
  436. package/package.json +22 -4
  437. package/scripts/build-hooks.js +15 -6
  438. package/scripts/changeset/parse.cjs +52 -4
  439. package/scripts/check-contract-drift.cjs +127 -11
  440. package/scripts/ci-timeout-report.cjs +770 -4
  441. package/scripts/command-contract-helpers.cjs +15 -8
  442. package/scripts/docs-guard-registry.cjs +34 -0
  443. package/scripts/gen-features.cjs +13 -8
  444. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  445. package/scripts/gen-loop-host-contract.cjs +79 -1
  446. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  447. package/scripts/gen-plugin-skills.cjs +87 -1
  448. package/scripts/gen-research-agents.cjs +24 -31
  449. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  450. package/scripts/gen-test-timings.cjs +32 -7
  451. package/scripts/lib/cli-exit.cjs +12 -0
  452. package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
  453. package/scripts/lib/ndjson-reporter.cjs +31 -5
  454. package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
  455. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  456. package/scripts/lib/vendor-bundle.cjs +59 -0
  457. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  458. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  459. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  460. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  461. package/scripts/lint-descriptions.cjs +7 -3
  462. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
  463. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  464. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  465. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  466. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  467. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  468. package/scripts/lint-phase-id-drift.cjs +319 -5
  469. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  470. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  471. package/scripts/lint-response-language-coverage.cjs +3 -0
  472. package/scripts/lint-retired-runtime-name.cjs +619 -0
  473. package/scripts/lint-skill-deps.cjs +7 -3
  474. package/scripts/lint-state-write-path-drift.cjs +93 -0
  475. package/scripts/lint-test-file-count.allowlist.json +38 -9
  476. package/scripts/lint-test-file-count.cjs +34 -1
  477. package/scripts/lint-vendored-deps.cjs +41 -5
  478. package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
  479. package/scripts/mutation-matrix.cjs +50 -5
  480. package/scripts/prompt-injection-scan.sh +4 -0
  481. package/scripts/release-tarball-smoke.cjs +194 -1
  482. package/scripts/require-issue-link-policy.cjs +6 -2
  483. package/scripts/sync-runtime-launcher.cjs +184 -2
  484. package/scripts/verify-npm-publish.cjs +76 -20
  485. package/skills/gsd-add-tests/SKILL.md +6 -1
  486. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  487. package/skills/gsd-audit-fix/SKILL.md +5 -0
  488. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  489. package/skills/gsd-autonomous/SKILL.md +7 -2
  490. package/skills/gsd-capture/SKILL.md +9 -5
  491. package/skills/gsd-code-review/SKILL.md +7 -2
  492. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  493. package/skills/gsd-config/SKILL.md +8 -4
  494. package/skills/gsd-debug/SKILL.md +11 -7
  495. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  496. package/skills/gsd-docs-update/SKILL.md +12 -7
  497. package/skills/gsd-eval-review/SKILL.md +6 -1
  498. package/skills/gsd-execute-phase/SKILL.md +12 -7
  499. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  500. package/skills/gsd-fast/SKILL.md +4 -0
  501. package/skills/gsd-forensics/SKILL.md +5 -1
  502. package/skills/gsd-graphify/SKILL.md +10 -6
  503. package/skills/gsd-health/SKILL.md +5 -0
  504. package/skills/gsd-help/SKILL.md +7 -2
  505. package/skills/gsd-import/SKILL.md +7 -3
  506. package/skills/gsd-inbox/SKILL.md +5 -0
  507. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  508. package/skills/gsd-manager/SKILL.md +6 -1
  509. package/skills/gsd-map-codebase/SKILL.md +7 -3
  510. package/skills/gsd-mempalace-capture/SKILL.md +12 -4
  511. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  512. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  513. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  514. package/skills/gsd-new-milestone/SKILL.md +6 -1
  515. package/skills/gsd-new-project/SKILL.md +5 -0
  516. package/skills/gsd-next/SKILL.md +6 -1
  517. package/skills/gsd-ns-context/SKILL.md +4 -0
  518. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  519. package/skills/gsd-ns-manage/SKILL.md +4 -0
  520. package/skills/gsd-ns-project/SKILL.md +4 -0
  521. package/skills/gsd-ns-review/SKILL.md +4 -0
  522. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  523. package/skills/gsd-onboard/SKILL.md +6 -1
  524. package/skills/gsd-pause-work/SKILL.md +5 -1
  525. package/skills/gsd-phase/SKILL.md +8 -4
  526. package/skills/gsd-plan-phase/SKILL.md +6 -1
  527. package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
  528. package/skills/gsd-pr-branch/SKILL.md +4 -0
  529. package/skills/gsd-profile-user/SKILL.md +5 -1
  530. package/skills/gsd-progress/SKILL.md +7 -2
  531. package/skills/gsd-quick/SKILL.md +16 -10
  532. package/skills/gsd-quick-batch/SKILL.md +21 -9
  533. package/skills/gsd-review/SKILL.md +7 -4
  534. package/skills/gsd-review-backlog/SKILL.md +3 -2
  535. package/skills/gsd-secure-phase/SKILL.md +6 -1
  536. package/skills/gsd-ship/SKILL.md +5 -0
  537. package/skills/gsd-sketch/SKILL.md +7 -2
  538. package/skills/gsd-spec-phase/SKILL.md +5 -1
  539. package/skills/gsd-spike/SKILL.md +8 -3
  540. package/skills/gsd-surface/SKILL.md +5 -1
  541. package/skills/gsd-thread/SKILL.md +4 -0
  542. package/skills/gsd-ui-phase/SKILL.md +6 -1
  543. package/skills/gsd-ui-review/SKILL.md +6 -1
  544. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  545. package/skills/gsd-undo/SKILL.md +5 -1
  546. package/skills/gsd-update/SKILL.md +6 -2
  547. package/skills/gsd-validate-phase/SKILL.md +6 -1
  548. package/skills/gsd-verify-work/SKILL.md +6 -1
  549. package/skills/gsd-workspace/SKILL.md +7 -3
  550. package/skills/gsd-workstreams/SKILL.md +6 -6
  551. package/vscode/package.json +1 -1
@@ -55,7 +55,7 @@ const install_scope_cjs_1 = require("./install-scope.cjs");
55
55
  const { processAttribution } = runtimeArtifactConversion;
56
56
  // resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
57
57
  // test stubs that monkeypatch the module's exports are seen at call time.
58
- const { getDirName } = runtimeNamePolicy;
58
+ const { getDirName, hostBehaviorsFor } = runtimeNamePolicy;
59
59
  function withInstallerPackageSource(configDir, fn) {
60
60
  // Reuse the existing compatibility-marker contract through the existing fs
61
61
  // seam. The marker exists only in this synchronous call tree: no disk state,
@@ -181,7 +181,7 @@ function provisionRuntimeSurfaceCorpus(layout, configDir, scope) {
181
181
  const destination = node_path_1.default.join(corpusRoot, 'agents');
182
182
  syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/agents/');
183
183
  }
184
- const markerFile = _hostBehaviors(layout.runtime).sourceMarkerFile;
184
+ const markerFile = hostBehaviorsFor(layout.runtime).sourceMarkerFile;
185
185
  if (typeof markerFile === 'string' && markerFile !== '' && required.has('commands')) {
186
186
  try {
187
187
  const markerPath = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, markerFile);
@@ -220,25 +220,6 @@ function executingPackageRoot() { return node_path_1.default.dirname(node_path_1
220
220
  */
221
221
  const USER_OWNED_ARTIFACTS = ['USER-PROFILE.md'];
222
222
  // ---------------------------------------------------------------------------
223
- // Host-behavior helpers
224
- // ---------------------------------------------------------------------------
225
- /**
226
- * Host-specific install behaviors declared on the runtime descriptor
227
- * (capabilities/<runtime>/capability.json -> runtime.hostBehaviors).
228
- * Mirrors bin/install.js's `_hostBehaviors` (ADR-1239 / #2086/#2087). Returns
229
- * {} for runtimes that declare none or if the registry fails to load, so
230
- * every behavior branch degrades to the generic path by default.
231
- */
232
- function _hostBehaviors(runtime) {
233
- try {
234
- const reg = require('./capability-registry.cjs');
235
- return (reg && reg.runtimes && reg.runtimes[runtime] && reg.runtimes[runtime].runtime && reg.runtimes[runtime].runtime.hostBehaviors) || {};
236
- }
237
- catch {
238
- return {};
239
- }
240
- }
241
- // ---------------------------------------------------------------------------
242
223
  // Conversion helpers
243
224
  // ---------------------------------------------------------------------------
244
225
  /**
@@ -730,6 +711,17 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
730
711
  continue;
731
712
  if (!entry.name.endsWith('.md'))
732
713
  continue;
714
+ // #4782: compact agent variants are consumed ONLY through the non-claude
715
+ // gate in init.cts's agent-skills persona fallback (`runtime !== 'claude'`
716
+ // — claude's contract is a skills-injection path, never a persona
717
+ // fallback). Staging them into Claude's agents directory shipped 29 dead
718
+ // files whose `name:` frontmatter is identical to their canonical
719
+ // sibling's, leaving the harness resolution unstated. Claude never
720
+ // selects compact, so claude is the one runtime whose agents kind skips
721
+ // them; every other runtime's emission is byte-identical. #5169: the
722
+ // descriptor declares this (`hostBehaviors.skipCompactAgents`), not a name test.
723
+ if (kind.kind === 'agents' && hostBehaviorsFor(runtime).skipCompactAgents && entry.name.endsWith('.compact.md'))
724
+ continue;
733
725
  const stem = entry.name.slice(0, -3); // strip .md
734
726
  let destName;
735
727
  if (kind.kind === 'agents') {
@@ -738,7 +730,7 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
738
730
  // hardcoded `runtime === 'copilot'`). copilot declares '.agent.md';
739
731
  // every other runtime's descriptor leaves this unset, so destName falls
740
732
  // back to entry.name unchanged (byte-parity, #1575 origin comment).
741
- const _agentExt = runtime ? _hostBehaviors(runtime).agentFileExtension : undefined;
733
+ const _agentExt = runtime ? hostBehaviorsFor(runtime).agentFileExtension : undefined;
742
734
  destName = _agentExt
743
735
  ? entry.name.replace(/\.md$/, _agentExt)
744
736
  : entry.name;
@@ -877,7 +869,7 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
877
869
  // that for Hermes the flat skills/gsd-*/ removal (below) does not delete the freshly
878
870
  // created skills/gsd-dev-preferences/ skill dir.
879
871
  let stagedLegacyArtifacts = null;
880
- if (_hostBehaviors(runtime).legacyCommandsGsdInstallMigration) {
872
+ if (hostBehaviorsFor(runtime).legacyCommandsGsdInstallMigration) {
881
873
  if (installFs().existsSync(legacyCommandsGsd)) {
882
874
  // #2875: staging root resolved lazily, only when there is actually
883
875
  // something to stage — reused below by every other call site sharing
@@ -901,8 +893,8 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
901
893
  }
902
894
  }
903
895
  // Hermes: remove pre-#2841 flat skills/gsd-*/ entries that lived alongside
904
- // the new skills/gsd/ nested layout.
905
- if (runtime === 'hermes') {
896
+ // the new skills/gsd/ nested layout. (#5169: `hostBehaviors.legacyFlatSkillsCleanup`.)
897
+ if (hostBehaviorsFor(runtime).legacyFlatSkillsCleanup) {
906
898
  const flatSkillsDir = node_path_1.default.join(configDir, 'skills');
907
899
  if (installFs().existsSync(flatSkillsDir)) {
908
900
  for (const entry of installFs().readdirSync(flatSkillsDir, { withFileTypes: true })) {
@@ -1040,7 +1032,7 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
1040
1032
  // commands/gsd/ for claude local, preserving dev-preferences.md by restoring it
1041
1033
  // to the same location (#1423). Using migrateLegacyDevPreferencesToSkill here
1042
1034
  // (which would redirect to skills/) conflicts with the test contract for local installs.
1043
- const _lu = _hostBehaviors(runtime).legacyCommandsGsdUninstall;
1035
+ const _lu = hostBehaviorsFor(runtime).legacyCommandsGsdUninstall;
1044
1036
  // #2870: `scope` keeps its exported `string = 'global'` signature (no
1045
1037
  // signature change), but every real caller — `uninstallRuntimeArtifacts`'s
1046
1038
  // own required `scope` param, always fed a validated 'global' | 'local'
@@ -1065,8 +1057,8 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
1065
1057
  }
1066
1058
  }
1067
1059
  }
1068
- // Hermes: pre-#2841 flat skills/gsd-*/ entries
1069
- if (runtime === 'hermes') {
1060
+ // Hermes: pre-#2841 flat skills/gsd-*/ entries (#5169: `legacyFlatSkillsCleanup`)
1061
+ if (hostBehaviorsFor(runtime).legacyFlatSkillsCleanup) {
1070
1062
  const flatSkillsDir = node_path_1.default.join(configDir, 'skills');
1071
1063
  if (node_fs_1.default.existsSync(flatSkillsDir)) {
1072
1064
  for (const entry of node_fs_1.default.readdirSync(flatSkillsDir, { withFileTypes: true })) {
@@ -1075,6 +1067,8 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
1075
1067
  }
1076
1068
  }
1077
1069
  }
1070
+ }
1071
+ if (hostBehaviorsFor(runtime).bareStemSkillsCleanup) {
1078
1072
  // Hermes: pre-#947 bare-stem skills/gsd/<stem>/ entries (dirs that do NOT
1079
1073
  // start with 'gsd-') — the #3664 layout used prefix='' so GSD-owned skills
1080
1074
  // had bare names (e.g. skills/gsd/help/). These are stale on uninstall.
@@ -1132,7 +1126,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
1132
1126
  // the dedicated combined commands+skills+plugin orchestrator instead of the
1133
1127
  // generic layout-driven loop below, mirroring the bespoke install path that
1134
1128
  // previously lived inline in bin/install.js.
1135
- const behaviors = _hostBehaviors(runtime);
1129
+ const behaviors = hostBehaviorsFor(runtime);
1136
1130
  const projectDir = scope === 'global' ? process.cwd() : configDir;
1137
1131
  if (behaviors.combinedFamilyInstall) {
1138
1132
  // #2329: combined-family runtimes (OpenCode/Kilo) bypass
@@ -1326,7 +1320,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
1326
1320
  // with the same prefix). User-owned bare dirs with no gsd-<stem> counterpart
1327
1321
  // are untouched.
1328
1322
  let hermesBareStemCleanup = false;
1329
- if (runtime === 'hermes') {
1323
+ if (hostBehaviorsFor(runtime).bareStemSkillsCleanup) {
1330
1324
  const nestedGsdDirForCleanup = node_path_1.default.join(configDir, 'skills', 'gsd');
1331
1325
  _removeHermesBareStemDirs(nestedGsdDirForCleanup);
1332
1326
  hermesBareStemCleanup = true;
@@ -1678,7 +1672,7 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
1678
1672
  // already satisfies the fold-to-descriptor requirement. Only the SKILLS
1679
1673
  // converter site above (installOpencodeFamilySkills) has a real
1680
1674
  // `converter` string to key on via SKILLS_CONVERTER_REGISTRY.
1681
- content = _hostBehaviors(runtime).frontmatterDialect === 'kilo'
1675
+ content = hostBehaviorsFor(runtime).frontmatterDialect === 'kilo'
1682
1676
  ? runtimeArtifactConversion.convertClaudeToKiloFrontmatter(content)
1683
1677
  : runtimeArtifactConversion.convertClaudeToOpencodeFrontmatter(content);
1684
1678
  installFs().writeFileSync(node_path_1.default.join(destDir, destName), content);
@@ -1881,6 +1875,9 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
1881
1875
  isWindowsHost: process.platform === 'win32',
1882
1876
  resolvedTarget: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir)),
1883
1877
  homeDir: (0, shell_command_projection_cjs_1.posixNormalize)(node_os_1.default.homedir()),
1878
+ // #4377: the runtime's own localConfigDir, so an opted-in local install
1879
+ // emits `<dir>/...` instead of this checkout's absolute path.
1880
+ localDirName: runtimeArtifactConversion._localIncludeDirName(runtime),
1884
1881
  });
1885
1882
  // #2329: destDir is derived from the SAME hostBehaviors.flatCommandDir
1886
1883
  // descriptor value read by writeManifest's manifest-key prefix and by
@@ -1958,7 +1955,8 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope, deps = {}) {
1958
1955
  // the GSD-managed DESCRIPTION.md and then the category dir itself if it
1959
1956
  // contains no user content (#947). _removeGsdEntries removed gsd-* dirs
1960
1957
  // but left the category container and DESCRIPTION.md intact.
1961
- if (runtime === 'hermes') {
1958
+ // (#5169: `hostBehaviors.categoryContainerCleanup`.)
1959
+ if (hostBehaviorsFor(runtime).categoryContainerCleanup) {
1962
1960
  const nestedGsdDir = node_path_1.default.join(configDir, 'skills', 'gsd');
1963
1961
  if (node_fs_1.default.existsSync(nestedGsdDir)) {
1964
1962
  // Remove GSD-owned DESCRIPTION.md (written by writeHermesCategoryDescription)
@@ -2016,7 +2014,6 @@ module.exports = {
2016
2014
  installAgentsKindStandalone,
2017
2015
  installOpencodeFamilyArtifacts,
2018
2016
  _installNativePluginIfDeclared,
2019
- _hostBehaviors,
2020
2017
  _copyStaged,
2021
2018
  hasExistingSymlinkBetween,
2022
2019
  isSymlinkedDestOptIn,
@@ -27,6 +27,7 @@ const node_os_1 = __importDefault(require("node:os"));
27
27
  const installFsAdapter = require("./install-fs-adapter.cjs");
28
28
  const { installFs, mkInstallTempDir } = installFsAdapter;
29
29
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
30
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
30
31
  // #2322: reuse the existing pure path-containment seam (ADR-1239 Phase C-2)
31
32
  // instead of hand-rolling a new traversal check for capability skill stems.
32
33
  const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
@@ -95,13 +96,15 @@ const PROFILES = Object.freeze({
95
96
  * Returns string[] — empty array if no requires: field.
96
97
  *
97
98
  * No external YAML parser dependency — hand-parse the single line
98
- * since GSD enforces flow-style arrays for requires:.
99
+ * since GSD enforces flow-style arrays for requires:. The block is the one the
100
+ * one fence owner (`locateFrontmatterFence`) finds — a `---` pair later in the
101
+ * body (a thematic break, a YAML example) is never frontmatter.
99
102
  */
100
103
  function parseRequires(content) {
101
- const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/m);
102
- if (!fmMatch)
104
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
105
+ if (!fence?.closed)
103
106
  return [];
104
- const fm = fmMatch[1];
107
+ const fm = content.slice(fence.openEnd, fence.bodyEnd);
105
108
  const line = fm.match(/^requires:\s*(.+)$/m);
106
109
  if (!line)
107
110
  return [];
@@ -280,7 +280,14 @@ function readInstallManifest(configDir) {
280
280
  version: typeof m.version === 'string' ? m.version : null,
281
281
  timestamp: typeof m.timestamp === 'string' ? m.timestamp : null,
282
282
  mode: typeof m.mode === 'string' ? m.mode : null,
283
- 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
+ : {},
284
291
  manifestVersion: normalizeManifestVersion(m.manifestVersion),
285
292
  runtime: normalizeReportedRuntime(rawRuntime),
286
293
  scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
@@ -121,6 +121,84 @@ function writeAllSync(fd, data) {
121
121
  }
122
122
  }
123
123
  }
124
+ /**
125
+ * Temporarily patch `fs.writeSync` so everything written to fd 1 (stdout) is
126
+ * captured into a string instead of reaching the real stdout, run `run`, then
127
+ * restore the original `fs.writeSync` and resolve with what was captured.
128
+ *
129
+ * The ONE shared helper (#5105 S9) for "run a nested command and read its own
130
+ * JSON envelope back as data instead of letting it reach the real stdout" —
131
+ * used by gsd-tools.cjs's `--pick`/`@file:` resolution and by `uat.cts`'s
132
+ * `cmdUatCompleteSession` (which needs `cmdCommit`'s result object without
133
+ * emitting cmdCommit's own envelope as a second line of output). A single
134
+ * definition means both callers see the same Buffer/string/encoding handling
135
+ * and the same throw-mid-write flush behavior, rather than two hand-rolled
136
+ * monkeypatches drifting apart.
137
+ *
138
+ * On a throw from `run`, whatever was captured before the throw is flushed to
139
+ * the REAL stdout — the wrapped command may have already written its own
140
+ * JSON envelope and THEN thrown to set a non-zero exit code (e.g. a
141
+ * capability set/disable on an unknown id); without this flush that output
142
+ * would be silently discarded, since the success-path flush at the call site
143
+ * never runs on a throw. The error still propagates so the exit code is
144
+ * preserved.
145
+ */
146
+ function captureStdoutSyncWrites(run) {
147
+ const originalWriteSync = node_fs_1.default.writeSync;
148
+ let captured = '';
149
+ node_fs_1.default.writeSync = ((fd, data, ...rest) => {
150
+ if (fd === 1) {
151
+ if (Buffer.isBuffer(data)) {
152
+ captured += data.toString('utf-8');
153
+ return data.length;
154
+ }
155
+ const text = String(data);
156
+ captured += text;
157
+ let encoding = 'utf-8';
158
+ if (typeof rest[1] === 'string')
159
+ encoding = rest[1];
160
+ return Buffer.byteLength(text, encoding);
161
+ }
162
+ return originalWriteSync.call(node_fs_1.default, fd, data, ...rest);
163
+ });
164
+ const restore = () => {
165
+ node_fs_1.default.writeSync = originalWriteSync;
166
+ };
167
+ return Promise.resolve()
168
+ .then(() => run())
169
+ .then(() => {
170
+ restore();
171
+ return captured;
172
+ }, (err) => {
173
+ restore();
174
+ if (captured) {
175
+ try {
176
+ originalWriteSync.call(node_fs_1.default, 1, captured);
177
+ }
178
+ catch { /* best-effort flush */ }
179
+ }
180
+ throw err;
181
+ });
182
+ }
183
+ /**
184
+ * Resolve `output()`'s `@file:<path>` redirection (emitted for a >50KB JSON
185
+ * payload) back to the real content, or return `captured` unchanged when it
186
+ * is not that shape.
187
+ *
188
+ * #5105 review finding 7: the ONE definition, shared by gsd-tools.cjs's
189
+ * `--pick`/CLI-passthrough resolution and by `uat.cts`'s
190
+ * `cmdUatCompleteSession` (which reads `cmdCommit`'s captured result back
191
+ * without emitting cmdCommit's own envelope as a second stdout line) — same
192
+ * reasoning as `captureStdoutSyncWrites` just below: one definition means
193
+ * both callers agree on the trailing-newline-free prefix and the read
194
+ * encoding, rather than two hand-rolled `startsWith('@file:')` checks
195
+ * drifting apart.
196
+ */
197
+ function resolveAtFileOutput(captured) {
198
+ if (!captured.startsWith('@file:'))
199
+ return captured;
200
+ return node_fs_1.default.readFileSync(captured.slice('@file:'.length), 'utf-8');
201
+ }
124
202
  /**
125
203
  * The wire form of a JSON result: the exact bytes `output()` emits for it.
126
204
  *
@@ -290,6 +368,16 @@ const ERROR_REASON = Object.freeze({
290
368
  // exit 0. See .gsd/phase/feat-3884-failure-is-a-value/40-design.md.
291
369
  PICK_FIELD_ABSENT: 'pick_field_absent',
292
370
  PICK_OUTPUT_NOT_JSON: 'pick_output_not_json',
371
+ // summary-extract (#5013): the SUMMARY's frontmatter fence exists but failed
372
+ // to parse (extractFrontmatter's FRONTMATTER_UNPARSEABLE marker) — a
373
+ // distinct failure from "no frontmatter"/"field absent", never a silent
374
+ // demotion to empty fields at exit 0.
375
+ SUMMARY_EXTRACT_UNPARSEABLE: 'summary_extract_unparseable',
376
+ // verification (#5118, ADR-5057 Phase 4): a *-VERIFICATION.md frontmatter
377
+ // `status` outside the closed writer set (`passed | gaps_found |
378
+ // human_needed`) — verification.cts's VerificationStatusError, translated
379
+ // once, centrally, by gsd-tools.cjs. Never read as "no result" at exit 0.
380
+ VERIFICATION_STATUS_INVALID: 'verification_status_invalid',
293
381
  // generic
294
382
  USAGE: 'usage',
295
383
  UNKNOWN: 'unknown',
@@ -334,14 +422,35 @@ const ERROR_REASON = Object.freeze({
334
422
  * more than one line or introduce an unescaped `"`. Callers embedding the
335
423
  * result MUST NOT add their own surrounding quotes — that would
336
424
  * double-quote it.
425
+ *
426
+ * #5118 security review: `JSON.stringify` leaves characters that render
427
+ * invisibly or reorder the text around them untouched — DEL and the C1
428
+ * controls (U+007F–U+009F), the line/paragraph separators (U+2028–U+2029),
429
+ * zero-width and directional marks (U+200B–U+200F), the bidi embeddings and
430
+ * overrides (U+202A–U+202E), the bidi isolates (U+2066–U+2069) and the BOM
431
+ * (U+FEFF). Each is escaped as `\uXXXX` so the token reads exactly as its
432
+ * code points are, whoever renders it.
337
433
  */
434
+ const INVISIBLE_OR_BIDI_RANGES = [
435
+ [0x7f, 0x9f],
436
+ [0x2028, 0x2029],
437
+ [0x200b, 0x200f],
438
+ [0x202a, 0x202e],
439
+ [0x2066, 0x2069],
440
+ [0xfeff, 0xfeff],
441
+ ];
442
+ /** Code points as `\uXXXX` escape text (source stays ASCII; no literal invisible characters). */
443
+ function toUnicodeEscape(codePoint) {
444
+ return `\\u${codePoint.toString(16).padStart(4, '0')}`;
445
+ }
446
+ const INVISIBLE_OR_BIDI_RE = new RegExp(`[${INVISIBLE_OR_BIDI_RANGES.map(([lo, hi]) => `${toUnicodeEscape(lo)}-${toUnicodeEscape(hi)}`).join('')}]`, 'g');
338
447
  function formatDiagnosticToken(value) {
339
- return JSON.stringify(value);
448
+ return JSON.stringify(value).replace(INVISIBLE_OR_BIDI_RE, (ch) => toUnicodeEscape(ch.charCodeAt(0)));
340
449
  }
341
450
  /**
342
451
  * Map an ERROR_REASON wire value onto a declared outcome name (#3912,
343
- * ADR-3889 §4). Closed over the 25-member enum: every reason gets an
344
- * explicit entry below, so a 26th member added without a mapping falls
452
+ * ADR-3889 §4). Closed over the 27-member enum: every reason gets an
453
+ * explicit entry below, so a 28th member added without a mapping falls
345
454
  * through to the `?? 'FAIL'` default rather than silently mis-projecting —
346
455
  * and tests/A1 iterates `Object.values(ERROR_REASON)`, so that default is
347
456
  * exactly what makes an unmapped addition visible instead of invisible.
@@ -389,6 +498,14 @@ const REASON_TO_OUTCOME = Object.freeze({
389
498
  // supposed to be there and was not; a prerequisite of the query failed.
390
499
  [ERROR_REASON.PICK_FIELD_ABSENT]: 'UNAVAILABLE',
391
500
  [ERROR_REASON.PICK_OUTPUT_NOT_JSON]: 'UNAVAILABLE',
501
+ // #5013: the SUMMARY's frontmatter fence exists but failed to parse — the
502
+ // prerequisite of the query (readable frontmatter) is what's broken, same
503
+ // shape as PICK_FIELD_ABSENT/PICK_OUTPUT_NOT_JSON just above.
504
+ [ERROR_REASON.SUMMARY_EXTRACT_UNPARSEABLE]: 'UNAVAILABLE',
505
+ // #5118: the verification report exists but its `status` is not a verdict
506
+ // the reader accepts — a broken prerequisite of the query, the same shape
507
+ // as SUMMARY_EXTRACT_UNPARSEABLE just above.
508
+ [ERROR_REASON.VERIFICATION_STATUS_INVALID]: 'UNAVAILABLE',
392
509
  // Self-failure: the run itself broke, not its inputs.
393
510
  [ERROR_REASON.SDK_FAIL_FAST]: 'INTERNAL',
394
511
  [ERROR_REASON.SECURITY_SCAN_FAILED]: 'INTERNAL',
@@ -433,4 +550,6 @@ module.exports = {
433
550
  getJsonErrorMode,
434
551
  error,
435
552
  formatDiagnosticToken,
553
+ captureStdoutSyncWrites,
554
+ resolveAtFileOutput,
436
555
  };
@@ -28,10 +28,19 @@
28
28
  * - capability-registry.cjs (byLoopPoint, consumed at call time)
29
29
  * - capability-state.cjs (resolveCapabilityRuntimeState — for capabilities list)
30
30
  */
31
+ var __importDefault = (this && this.__importDefault) || function (mod) {
32
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33
+ };
34
+ const node_fs_1 = __importDefault(require("node:fs"));
35
+ const node_path_1 = __importDefault(require("node:path"));
31
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
32
37
  const ioMod = require("./io.cjs");
33
38
  const { output: coreOutput, error: coreError } = ioMod;
34
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const verificationMod = require("./verification.cjs");
41
+ const { resolvePhaseArtifactFile } = verificationMod;
42
+ const security_cjs_1 = require("./security.cjs");
43
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
35
44
  const configLoaderModule = require("./config-loader.cjs");
36
45
  const { loadConfig } = configLoaderModule;
37
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -528,6 +537,56 @@ function resolveActiveHooksForPoint(cwd, point, options = {}) {
528
537
  const combinedWarnings = [...(state.warnings || []), ...loadFailWarnings];
529
538
  return { point: resolved.point, activeHooks: resolved.activeHooks, warnings: combinedWarnings };
530
539
  }
540
+ /**
541
+ * #5105 R2: a `produces` entry `p` "exists" in `phaseDir` when it resolves via
542
+ * the SAME phase-artifact selection core `resolveVerificationFile`/
543
+ * `resolveUatFile` delegate to (`resolvePhaseArtifactFile`, `verification.cts`)
544
+ * — no second derivation of "which file counts as this phase's artifact"
545
+ * (#3473 F2's generative-divergence class). That core is pure and takes an
546
+ * already-read directory listing of REGULAR-file names only, so a directory
547
+ * of the same name, or a suffixed near-miss (`p.bak`, `p.tmp`), never counts,
548
+ * and `phaseDirName` scoping rejects a stray cross-phase file (`02-SECURITY.md`
549
+ * inside a `01-foo` phase dir) exactly as the aggregate scans do.
550
+ */
551
+ function producesEntryPresent(fileNames, phaseDirName, p) {
552
+ return resolvePhaseArtifactFile([...fileNames], p, { phaseDirName, allowBare: true }) !== null;
553
+ }
554
+ /**
555
+ * #5105 R2: partition `activeHooks` by whether every one of a `kind:"step"`
556
+ * hook's declared `produces` artifacts already exists (as a regular file)
557
+ * directly in `phaseDir`. A hook with `produces: []` (e.g. mempalace-capture),
558
+ * and every gate/contribution, passes through unchanged in `activeHooks`.
559
+ *
560
+ * Throws when `phaseDir` cannot be read (fail closed — the caller converts
561
+ * this to a `coreError` exit, never a silent pass-everything-through).
562
+ */
563
+ function partitionHooksByFingerprint(activeHooks, phaseDir) {
564
+ const entries = node_fs_1.default.readdirSync(phaseDir, { withFileTypes: true });
565
+ const fileNames = entries.filter((e) => e.isFile()).map((e) => e.name);
566
+ const phaseDirName = node_path_1.default.basename(phaseDir);
567
+ const kept = [];
568
+ const skipped = [];
569
+ for (const hook of activeHooks) {
570
+ if (hook.kind !== 'step' || !hook.produces || hook.produces.length === 0) {
571
+ kept.push(hook);
572
+ continue;
573
+ }
574
+ const everyProduced = hook.produces.every((p) => producesEntryPresent(fileNames, phaseDirName, p));
575
+ if (everyProduced) {
576
+ skipped.push({
577
+ capId: hook.capId,
578
+ kind: 'step',
579
+ ref: hook.ref,
580
+ reason: 'produces-present',
581
+ artifacts: [...hook.produces],
582
+ });
583
+ }
584
+ else {
585
+ kept.push(hook);
586
+ }
587
+ }
588
+ return { activeHooks: kept, skippedHooks: skipped };
589
+ }
531
590
  function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
532
591
  if (!point) {
533
592
  coreError('loop render-hooks requires a <point> argument. Valid points: ' + CANONICAL_POINTS.join(', '));
@@ -548,6 +607,38 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
548
607
  coreError(msg);
549
608
  return;
550
609
  }
610
+ // #5105 R2: --after-fingerprint <phaseDir> — gate out verify:post steps
611
+ // whose declared artifact(s) already exist in phaseDir (execute-phase
612
+ // already dispatched them before its own fingerprint; a re-dispatch here
613
+ // would write a post-fingerprint covered path for no reason, #4981/#4887).
614
+ const afterFingerprintDir = typeof options['afterFingerprint'] === 'string' ? options['afterFingerprint'] : undefined;
615
+ let skippedHooks;
616
+ if (afterFingerprintDir !== undefined) {
617
+ // #5105 S10: resolve relative to the handler's own cwd (never the
618
+ // process cwd) and fail closed if it escapes the project root — the same
619
+ // `requireSafePath` seam `uat.cts`'s `cmdUatCompleteSession` uses for its
620
+ // own path argument.
621
+ let safePhaseDir;
622
+ try {
623
+ safePhaseDir = (0, security_cjs_1.requireSafePath)(afterFingerprintDir, cwd, '--after-fingerprint directory', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
624
+ }
625
+ catch (err) {
626
+ const msg = (err instanceof Error) ? err.message : String(err);
627
+ coreError(`--after-fingerprint directory is unsafe: ${msg}`);
628
+ return;
629
+ }
630
+ let partition;
631
+ try {
632
+ partition = partitionHooksByFingerprint(result.activeHooks, safePhaseDir);
633
+ }
634
+ catch (err) {
635
+ const msg = (err instanceof Error) ? err.message : String(err);
636
+ coreError(`--after-fingerprint phase directory not found or unreadable: ${afterFingerprintDir} (${msg})`);
637
+ return;
638
+ }
639
+ result = { point: result.point, activeHooks: partition.activeHooks, warnings: result.warnings };
640
+ skippedHooks = partition.skippedHooks;
641
+ }
551
642
  if (activeCapId !== undefined) {
552
643
  const isActive = result.activeHooks.some((h) => h.capId === activeCapId);
553
644
  process.stdout.write(isActive ? 'true\n' : 'false\n');
@@ -562,6 +653,9 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
562
653
  if (result.warnings.length > 0) {
563
654
  envelope.warnings = result.warnings;
564
655
  }
656
+ if (skippedHooks !== undefined) {
657
+ envelope.skippedHooks = skippedHooks;
658
+ }
565
659
  coreOutput(envelope, raw);
566
660
  }
567
661
  module.exports = {
@@ -569,6 +663,7 @@ module.exports = {
569
663
  renderLoopHooks,
570
664
  cmdLoopRenderHooks,
571
665
  resolveActiveHooksForPoint,
666
+ partitionHooksByFingerprint,
572
667
  // Exported for tests
573
668
  _getNestedConfigValue,
574
669
  _resolveActivationValue,
@@ -11,6 +11,7 @@
11
11
  * ADR-457 build-at-publish: compiled by tsc to gsd-core/bin/lib/markdown-sectionizer.cjs.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.XML_DECISION_TAG_NAMES = void 0;
14
15
  exports.stripFencedCode = stripFencedCode;
15
16
  exports.stripInlineCode = stripInlineCode;
16
17
  exports.scanInlineCodeSpans = scanInlineCodeSpans;
@@ -21,11 +22,13 @@ exports.collectSections = collectSections;
21
22
  exports.collectSection = collectSection;
22
23
  exports.iterateBullets = iterateBullets;
23
24
  exports.updateBullet = updateBullet;
25
+ exports.updateHeading = updateHeading;
24
26
  exports.extractTaggedBlocks = extractTaggedBlocks;
25
27
  exports.stripTaggedBlocks = stripTaggedBlocks;
26
28
  exports.replaceSection = replaceSection;
27
29
  exports.withSection = withSection;
28
30
  exports.deleteSection = deleteSection;
31
+ exports.extractXmlTagBodies = extractXmlTagBodies;
29
32
  const pattern_cjs_1 = require("./pattern.cjs");
30
33
  // ─── stripFencedCode ──────────────────────────────────────────────────────────
31
34
  /**
@@ -722,7 +725,7 @@ function updateBullet(content, match, transform) {
722
725
  bulletText = nm[2];
723
726
  }
724
727
  }
725
- if (bulletText !== null && match(bulletText, rawLine)) {
728
+ if (bulletText !== null && match(bulletText, rawLine, i)) {
726
729
  const newLine = transform(rawLine);
727
730
  if (typeof newLine !== 'string')
728
731
  return content;
@@ -732,6 +735,40 @@ function updateBullet(content, match, transform) {
732
735
  }
733
736
  return content;
734
737
  }
738
+ // ─── updateHeading ────────────────────────────────────────────────────────────
739
+ /**
740
+ * Rewrite the physical line of the FIRST ATX heading (outside fenced code)
741
+ * accepted by `match` — the heading analogue of `updateBullet` (ADR-5057 §6,
742
+ * Phase 13, #5217). Heading discovery is `tokenizeHeadings`' (the one fence
743
+ * state machine for headings); this function adds only the splice.
744
+ *
745
+ * `rawLine` (second argument to `match`, the only argument to `transform`) is
746
+ * the UNMODIFIED physical line between `\n` separators, so on a CRLF document
747
+ * its trailing `\r` is included and every untouched byte — including every
748
+ * other line's own terminator — is copied from `content` verbatim. `lineIndex`
749
+ * is the 0-based physical line index, the same indexing as
750
+ * `content.split('\n')`, so a caller holding a pre-computed line-indexed plan
751
+ * can disambiguate two headings with identical text.
752
+ *
753
+ * Bounded no-op: no accepted heading, or `transform` returning a non-string,
754
+ * returns `content` unchanged.
755
+ */
756
+ function updateHeading(content, match, transform) {
757
+ if (typeof content !== 'string' || content.length === 0)
758
+ return content;
759
+ for (const heading of tokenizeHeadings(content)) {
760
+ const newlineAt = content.indexOf('\n', heading.offset);
761
+ const lineEnd = newlineAt === -1 ? content.length : newlineAt;
762
+ const rawLine = content.slice(heading.offset, lineEnd);
763
+ if (!match(heading, rawLine, heading.line - 1))
764
+ continue;
765
+ const newLine = transform(rawLine);
766
+ if (typeof newLine !== 'string')
767
+ return content;
768
+ return content.slice(0, heading.offset) + newLine + content.slice(lineEnd);
769
+ }
770
+ return content;
771
+ }
735
772
  // ─── extractTaggedBlocks ──────────────────────────────────────────────────────
736
773
  /**
737
774
  * Return the inner text of every `<tagName>…</tagName>` block in `content`,
@@ -962,5 +999,42 @@ function deleteSection(content, headingPredicate, opts = {}) {
962
999
  const collapsedBefore = before.replace(/(?:\r\n|\n){3,}$/, (m) => (m.includes('\r\n') ? '\r\n\r\n' : '\n\n'));
963
1000
  return collapsedBefore + after;
964
1001
  }
1002
+ // ─── extractXmlTagBodies ──────────────────────────────────────────────────────
1003
+ // #2372: scanned-tag set must match the planner-canonical surfaces where a D-NN citation
1004
+ // is meaningful. `<objective>`/`<tasks>`/`<task>`/`<action>` are the historical core. The
1005
+ // planner is also explicitly told (plan-phase.md) to cite decisions in `<read_first>`,
1006
+ // `<behavior>`, `<verify>`, `<acceptance_criteria>`, and `<done>` — those are scanned too,
1007
+ // so the decision-coverage gate does not report a false gap when a decision is cited in any of them.
1008
+ //
1009
+ // Implementation: per-tag matching, NOT a single wide alternation. A single alternation
1010
+ // like `<(?:a|b|c)>...<\/(?:a|b|c)>` halts the outer tag's body capture at any inner tag
1011
+ // in the set, dropping any citation in the outer tag's prefix prose — e.g.
1012
+ // `<action>per D-05 <verify>npm test</verify></action>` would lose D-05 because `<verify>`
1013
+ // halts the `<action>` body before the citation. Per-tag matching avoids this: each tag's
1014
+ // body terminates only at its OWN closing tag, so `<verify>` inside `<action>` is absorbed
1015
+ // into `<action>`'s body (D-05 caught) AND `<verify>` is matched separately on its own pass.
1016
+ // Each per-tag regex keeps the ReDoS-safe negative-lookahead tempering (#2128).
1017
+ exports.XML_DECISION_TAG_NAMES = ['objective', 'tasks', 'task', 'action', 'read_first', 'behavior', 'verify', 'acceptance_criteria', 'done'];
1018
+ function buildXmlDecisionTagRegex(tagName) {
1019
+ // Per-tag: body tempering stops only at the SAME tag's reopening or closing — other
1020
+ // scanned tags pass through as text into this body. Non-greedy `*?` to first close.
1021
+ return new RegExp(`<${tagName}(?:\\s[^>]{0,1000})?>((?:(?!<${tagName}[\\s>])[\\s\\S])*?)<\\/${tagName}>`, 'gi');
1022
+ }
1023
+ /**
1024
+ * The bodies of every planner-canonical XML tag (`XML_DECISION_TAG_NAMES`) in `text`,
1025
+ * joined by `\n`. Each tag's body ends only at its OWN closing tag, so an inner scanned tag
1026
+ * is absorbed into the outer body AND matched on its own pass.
1027
+ */
1028
+ function extractXmlTagBodies(text) {
1029
+ const parts = [];
1030
+ for (const tagName of exports.XML_DECISION_TAG_NAMES) {
1031
+ const re = buildXmlDecisionTagRegex(tagName);
1032
+ for (const match of text.matchAll(re)) {
1033
+ if (match[1])
1034
+ parts.push(match[1]);
1035
+ }
1036
+ }
1037
+ return parts.join('\n');
1038
+ }
965
1039
  // Consumers: require('../gsd-core/bin/lib/markdown-sectionizer.cjs')
966
1040
  // Named CJS exports are the canonical surface (ADR-457 .cts → .cjs build-at-publish).