@opengsd/gsd-core 1.11.0 → 1.13.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 (498) 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 +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -13,7 +13,7 @@
13
13
  * readSurface(runtimeConfigDir)
14
14
  * writeSurface(runtimeConfigDir, surfaceState)
15
15
  * resolveSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
16
- * applySurface(runtimeConfigDir, layout, manifest, clusterMap?, registry?)
16
+ * applySurface(runtimeConfigDir, layout, manifest, clusterMap?, registry?, opts?, deps?)
17
17
  * listSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
18
18
  * pruneSkillDirs(skillsDir, retainedNames, prefix, manifest)
19
19
  *
@@ -37,6 +37,8 @@ const node_path_1 = __importDefault(require("node:path"));
37
37
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
38
38
  // eslint-disable-next-line @typescript-eslint/no-require-imports
39
39
  const installProfiles = require("./install-profiles.cjs");
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ const testHomeGuard = require("./real-home-guard.cjs");
40
42
  const { readActiveProfile, resolveProfile, loadSkillsManifest,
41
43
  // #2322 HIGH-3: shared marker name — single source of truth with the writer
42
44
  // (install-profiles.cts stageSkillsForRuntimeAsSkills) so the prune reader
@@ -163,7 +165,7 @@ function normalizeSkillManifest(runtimeConfigDir, manifest) {
163
165
  * - registry is threaded into resolveProfile so capability skills
164
166
  * participate in the base skill set and their requires: chains expand.
165
167
  */
166
- function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
168
+ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry, surfaceOverride) {
167
169
  // Merge capability clusters into the cluster map when registry is provided.
168
170
  // The ADR-857 phase 4a HARD gate guarantees that when a capId matches a CLUSTERS
169
171
  // key, the values are EQUAL — so the spread is idempotent for matching names.
@@ -203,7 +205,7 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
203
205
  cm = merged;
204
206
  }
205
207
  const skillManifest = normalizeSkillManifest(runtimeConfigDir, manifest);
206
- const surface = readSurface(runtimeConfigDir);
208
+ const surface = surfaceOverride === undefined ? readSurface(runtimeConfigDir) : surfaceOverride;
207
209
  // Determine base profile name: from surface state or from .gsd-profile marker
208
210
  const baseProfileName = (surface && surface.baseProfile)
209
211
  ? surface.baseProfile
@@ -303,15 +305,20 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
303
305
  * Re-stage the active surface using the resolved layout.
304
306
  * Iterates layout.kinds and syncs each artifact kind to its destination.
305
307
  */
306
- function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts) {
308
+ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts, deps = {}) {
307
309
  if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
308
310
  throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
309
311
  }
312
+ // #3712: the dest selection below prefers `kind.home` over layout.configDir and
313
+ // then hands it to the destructive _syncGsdDir, so surface apply is a third
314
+ // escape route into the developer's real home alongside install/uninstall.
315
+ testHomeGuard.assertTestHomeSandboxed('applySurface', layout.runtime, layout.kinds, {
316
+ os: deps.os, env: deps.env,
317
+ });
310
318
  const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
311
- const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry);
312
- // Profile toggles must converge retired surfaces too. Once a kind disappears
313
- // from artifactLayout there is no normal sync pass left to prune it (#2644).
314
- retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(layout.runtime, layout.configDir);
319
+ const hasCandidateState = opts !== undefined && Object.prototype.hasOwnProperty.call(opts, 'surfaceState');
320
+ const candidateState = hasCandidateState ? (opts.surfaceState ?? null) : undefined;
321
+ const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry, candidateState);
315
322
  // #1575: agents kind now mirrors createRuntimeArtifactInstallPlan — build
316
323
  // agentCtx (pathPrefix + attribution) and pass it to kind.stage() so
317
324
  // stageAgentsForRuntimeWithConverter applies the full inline-loop pipeline
@@ -345,7 +352,7 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
345
352
  // not referenced by any skill's _calls_agents_ manifest entry would be silently
346
353
  // dropped from the surface path. For tiered profiles (core/standard) or when
347
354
  // surface mods exist, pass the resolved set so only the filtered subset stages.
348
- const _surfaceState = readSurface(layout.configDir);
355
+ const _surfaceState = candidateState === undefined ? readSurface(layout.configDir) : candidateState;
349
356
  const _baseProfileName = (_surfaceState && _surfaceState.baseProfile)
350
357
  ? _surfaceState.baseProfile
351
358
  : (readActiveProfile(layout.configDir) || 'full');
@@ -353,6 +360,7 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
353
360
  _surfaceState.explicitAdds.length > 0 ||
354
361
  _surfaceState.explicitRemoves.length > 0);
355
362
  const _isUnmodifiedFull = _baseProfileName === 'full' && !_hasSurfaceMods;
363
+ const stagedKinds = [];
356
364
  try {
357
365
  for (const kind of layout.kinds) {
358
366
  let staged;
@@ -388,7 +396,22 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
388
396
  // the parity test in tests/runtime-artifact-layout-surface.test.cjs
389
397
  // enforces that the two writers never diverge again.
390
398
  const dest = assertDestWithinConfigHome(kind.home ?? layout.configDir, kind.destSubpath);
391
- _syncGsdDir(staged, dest, kind, skillManifest, layout.runtime);
399
+ stagedKinds.push({ kind, staged, dest });
400
+ }
401
+ // Do not mutate installed artifacts until every kind has staged
402
+ // successfully. A missing source provider therefore leaves both artifacts
403
+ // and the persisted surface state untouched.
404
+ retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(layout.runtime, layout.configDir);
405
+ for (const item of stagedKinds) {
406
+ _syncGsdDir(item.staged, item.dest, item.kind, skillManifest, layout.runtime);
407
+ }
408
+ if (hasCandidateState) {
409
+ if (candidateState === null) {
410
+ node_fs_1.default.rmSync(node_path_1.default.join(runtimeConfigDir, SURFACE_FILE_NAME), { force: true });
411
+ }
412
+ else if (candidateState !== undefined) {
413
+ writeSurface(runtimeConfigDir, candidateState);
414
+ }
392
415
  }
393
416
  }
394
417
  finally {
@@ -14,6 +14,14 @@ const node_path_1 = __importDefault(require("node:path"));
14
14
  // eslint-disable-next-line @typescript-eslint/no-require-imports
15
15
  const ioMod = require("./io.cjs");
16
16
  const { output, error, ERROR_REASON } = ioMod;
17
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
18
+ const planDocumentMod = require("./plan-document.cjs");
19
+ const { parsePlanDocument } = planDocumentMod;
20
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
21
+ const capabilityLoaderMod = require("./capability-loader.cjs");
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const taskContentResolutionMod = require("./task-content-resolution.cjs");
24
+ const { resolveTaskContent, ResolverAmbiguousError, ResolverFailedError, ResolverTimeoutError, ResolverMalformedOutputError, } = taskContentResolutionMod;
17
25
  // ─── Implementation ───────────────────────────────────────────────────────────
18
26
  function isBehaviorAddingTaskContent(content) {
19
27
  const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
@@ -52,10 +60,111 @@ function isBehaviorAddingTaskContent(content) {
52
60
  reason: isBehaviorAdding ? null : `Not behavior-adding: ${missing.join('; ')}`,
53
61
  };
54
62
  }
63
+ /**
64
+ * Default (production) capability loader for `resolve-content`: the merged
65
+ * first-party + validated-installed-overlay registry (ADR-1244 D2), the
66
+ * established runtime read path for "installed capabilities including
67
+ * third-party" — as opposed to `capability-loader.cts`'s heavier build-time
68
+ * validation entry points or the static `capability-registry.cjs` alone
69
+ * (first-party only, would miss a third-party capability's
70
+ * `taskContentResolver` declaration entirely).
71
+ */
72
+ function defaultLoadCapabilities(cwd) {
73
+ const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
74
+ return Object.values(registry.capabilities ?? {});
75
+ }
76
+ function parseResolveContentArgs(args) {
77
+ let plan = null;
78
+ let taskId = null;
79
+ for (let i = 2; i < args.length; i++) {
80
+ if (args[i] === '--plan') {
81
+ plan = args[i + 1] ?? null;
82
+ i++;
83
+ }
84
+ else if (args[i] === '--task-id') {
85
+ taskId = args[i + 1] ?? null;
86
+ i++;
87
+ }
88
+ }
89
+ return { plan, taskId };
90
+ }
91
+ /**
92
+ * `task resolve-content --plan <PLAN.md path> --task-id <tracker-id value> --raw`
93
+ * (ADR-3646 Decision 2). Resolves one task's content from the external
94
+ * tracker its `tracker-id` attribute names, via `task-content-resolution.cts`.
95
+ *
96
+ * HARD-HALT CONTRACT: a thrown `ResolverAmbiguousError` / `ResolverFailedError`
97
+ * / `ResolverTimeoutError` / `ResolverMalformedOutputError` from
98
+ * `resolveTaskContent` is turned into this CLI's own non-zero exit via
99
+ * `error()` — never swallowed into a `{resolved: false}` JSON answer. Any
100
+ * other thrown error is not one of the four documented resolver-error
101
+ * classes and is allowed to propagate uncaught.
102
+ */
103
+ function routeResolveContent({ args, cwd, raw }, deps = {}) {
104
+ const usage = 'Usage: task resolve-content --plan <path> --task-id <tracker-id> --raw';
105
+ const { plan, taskId } = parseResolveContentArgs(args);
106
+ if (!plan || !taskId) {
107
+ error(usage, ERROR_REASON.USAGE);
108
+ return;
109
+ }
110
+ const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
111
+ const resolvedPlanPath = node_path_1.default.resolve(projectRoot, plan);
112
+ const rel = node_path_1.default.relative(projectRoot, resolvedPlanPath);
113
+ if (rel === '..' || rel.startsWith(`..${node_path_1.default.sep}`)) {
114
+ error(`Plan file is outside project scope: ${plan}`, ERROR_REASON.USAGE);
115
+ return;
116
+ }
117
+ if (!node_fs_1.default.existsSync(resolvedPlanPath)) {
118
+ error(`Plan file not found: ${plan}`, ERROR_REASON.USAGE);
119
+ return;
120
+ }
121
+ const planContent = node_fs_1.default.readFileSync(resolvedPlanPath, 'utf-8');
122
+ const parsedPlan = parsePlanDocument(planContent, resolvedPlanPath);
123
+ const task = (parsedPlan.tasks ?? []).find((t) => t.trackerId === taskId);
124
+ if (!task) {
125
+ error(`No task with tracker-id '${taskId}' found in plan: ${plan}`, ERROR_REASON.USAGE);
126
+ return;
127
+ }
128
+ const loadCapabilities = deps.loadCapabilities ?? defaultLoadCapabilities;
129
+ const capabilities = loadCapabilities(projectRoot);
130
+ const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
131
+ let result;
132
+ try {
133
+ result = resolveFn({ trackerId: task.trackerId, capabilities });
134
+ }
135
+ catch (err) {
136
+ if (err instanceof ResolverAmbiguousError ||
137
+ err instanceof ResolverFailedError ||
138
+ err instanceof ResolverTimeoutError ||
139
+ err instanceof ResolverMalformedOutputError) {
140
+ error(err.message, ERROR_REASON.UNKNOWN);
141
+ return;
142
+ }
143
+ throw err;
144
+ }
145
+ switch (result.kind) {
146
+ case 'not-applicable':
147
+ output({ resolved: false }, raw, undefined);
148
+ return;
149
+ case 'no-resolver':
150
+ output({ resolved: false, reason: 'no-resolver' }, raw, undefined);
151
+ return;
152
+ case 'empty':
153
+ output({ resolved: false, reason: 'empty' }, raw, undefined);
154
+ return;
155
+ case 'resolved':
156
+ output({ resolved: true, content: result.content }, raw, undefined);
157
+ return;
158
+ }
159
+ }
55
160
  function routeTaskCommand({ args, cwd, raw }) {
56
161
  const subcommand = args[1];
162
+ if (subcommand === 'resolve-content') {
163
+ routeResolveContent({ args, cwd, raw });
164
+ return;
165
+ }
57
166
  if (subcommand !== 'is-behavior-adding') {
58
- error('Unknown task subcommand. Available: is-behavior-adding', ERROR_REASON.SDK_UNKNOWN_COMMAND);
167
+ error('Unknown task subcommand. Available: is-behavior-adding, resolve-content', ERROR_REASON.SDK_UNKNOWN_COMMAND);
59
168
  }
60
169
  let content = null;
61
170
  if (args[2] === '--task-content') {
@@ -82,4 +191,5 @@ function routeTaskCommand({ args, cwd, raw }) {
82
191
  module.exports = {
83
192
  isBehaviorAddingTaskContent,
84
193
  routeTaskCommand,
194
+ routeResolveContent,
85
195
  };
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ /**
3
+ * Task Content Resolution Module (ADR-3646 Phase 1, #3970).
4
+ *
5
+ * Given a task's `tracker-id` attribute value (parsed verbatim by
6
+ * `plan-document.cts`, never split there) and the set of installed
7
+ * capabilities' `taskContentResolver` declarations, resolves the task's
8
+ * content from the matching external tracker via a bounded subprocess call —
9
+ * or reports that no resolution applies.
10
+ *
11
+ * PURE / IMPURE SPLIT, loosely mirroring `review-lane-invocation.cts`'s
12
+ * resolve-then-run shape, but deliberately NOT copying its full machinery
13
+ * (Gall's Law — see `40-design.md`'s "Laws that apply" section): this problem
14
+ * has no probe/model/effort/prompt-channel axes, just one deterministic
15
+ * id-lookup. `splitTrackerId`, `findResolver`, and `buildInvocation` are pure
16
+ * and total (never throw, even on hostile third-party-shaped input — a
17
+ * capability manifest is third-party-authored, and while `capability-
18
+ * validator.cjs` validates it at install time, this module re-validates
19
+ * defensively rather than trusting that boundary). `resolveTaskContent` is
20
+ * the one impure boundary: it spawns exactly one bounded subprocess, through
21
+ * an injectable `execFn` so tests never spawn a real process or wait a real
22
+ * timeout (CLAUDE.md's clock-seam rule).
23
+ *
24
+ * HARD-HALT CONTRACT (ADR-3646 Decision 4): an ambiguous resolver match, a
25
+ * non-zero resolver exit, a timeout, or malformed resolver stdout all THROW.
26
+ * None of these degrade to a silently-empty or silently-picked result — a
27
+ * task-content resolution failure must halt the caller (`task-command-
28
+ * router.cts`'s `resolve-content` subcommand turns each throw into the CLI's
29
+ * own non-zero exit), never fall back to inline PLAN.md content pretending
30
+ * nothing happened.
31
+ *
32
+ * ADR-457 build-at-publish: source in src/task-content-resolution.cts,
33
+ * compiled to gsd-core/bin/lib/task-content-resolution.cjs (gitignored).
34
+ */
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ // Use non-destructured namespace import so test-time mock.method(childProcess,
39
+ // 'spawnSync') can intercept calls from this seam — destructured imports
40
+ // capture references at load time and become un-mockable (matches the
41
+ // convention documented in shell-command-projection.cts).
42
+ const node_child_process_1 = __importDefault(require("node:child_process"));
43
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
44
+ const ioMod = require("./io.cjs");
45
+ const { formatDiagnosticToken } = ioMod;
46
+ // ─── Result taxonomy ──────────────────────────────────────────────────────────
47
+ /**
48
+ * The four non-throwing outcomes of `resolveTaskContent`. Frozen because the
49
+ * `kind` discriminant is the product — callers (the CLI seam) switch on it
50
+ * directly rather than string-matching prose.
51
+ */
52
+ const TASK_CONTENT_RESULT = Object.freeze({
53
+ NOT_APPLICABLE: 'not-applicable',
54
+ NO_RESOLVER: 'no-resolver',
55
+ RESOLVED: 'resolved',
56
+ EMPTY: 'empty',
57
+ });
58
+ // ─── Throwable error taxonomy ─────────────────────────────────────────────────
59
+ // These four ALWAYS throw — they are configuration/execution defects, never a
60
+ // value `resolveTaskContent` returns. See the module docstring's hard-halt
61
+ // contract.
62
+ /**
63
+ * Two or more installed capabilities declare a `taskContentResolver` for the
64
+ * same `trackerPrefix`. Structurally impossible in a correctly-validated
65
+ * install (`capability-validator.cjs` enforces cross-capability prefix
66
+ * uniqueness), but `findResolver` must still refuse to silently pick one if a
67
+ * test harness or a validator bug ever produces this shape.
68
+ */
69
+ class ResolverAmbiguousError extends Error {
70
+ prefix;
71
+ capabilityIds;
72
+ constructor(prefix, capabilityIds) {
73
+ super(`tracker prefix '${prefix}' matches ${capabilityIds.length} installed capability resolvers ` +
74
+ `(${capabilityIds.join(', ')}) — ambiguous resolver registration must never silently pick one`);
75
+ this.name = 'ResolverAmbiguousError';
76
+ this.prefix = prefix;
77
+ this.capabilityIds = capabilityIds;
78
+ }
79
+ }
80
+ /**
81
+ * The resolver subprocess exited non-zero (or failed to spawn at all).
82
+ *
83
+ * `stderrTail` is UNTRUSTED subprocess-sourced text (the resolver binary is
84
+ * declared by a capability manifest and invoked with an argv token derived
85
+ * from a PLAN.md `tracker-id` attribute, which is often LLM/agent-authored —
86
+ * a hostile or buggy resolver could echo attacker-influenced text back on
87
+ * its own stderr). `.message` embeds it through `io.cjs`'s
88
+ * `formatDiagnosticToken()` so every caller of `resolveTaskContent` gets a
89
+ * `.message` that is already safe to write verbatim to a plain-text
90
+ * diagnostic — see that function's docstring for why this must happen here,
91
+ * at the point the untrusted substring is embedded, rather than at each
92
+ * call site.
93
+ */
94
+ class ResolverFailedError extends Error {
95
+ exitCode;
96
+ stderrTail;
97
+ constructor(binary, exitCode, stderrTail) {
98
+ super(`resolver command '${binary}' exited ${exitCode === null ? 'with no exit code (spawn failure)' : exitCode}` +
99
+ (stderrTail ? `: ${formatDiagnosticToken(stderrTail)}` : ''));
100
+ this.name = 'ResolverFailedError';
101
+ this.exitCode = exitCode;
102
+ this.stderrTail = stderrTail;
103
+ }
104
+ }
105
+ /** The resolver subprocess exceeded its declared `invoke.timeoutMs` bound. */
106
+ class ResolverTimeoutError extends Error {
107
+ timeoutMs;
108
+ constructor(binary, timeoutMs) {
109
+ super(`resolver command '${binary}' timed out after ${timeoutMs}ms`);
110
+ this.name = 'ResolverTimeoutError';
111
+ this.timeoutMs = timeoutMs;
112
+ }
113
+ }
114
+ /**
115
+ * The resolver's stdout was not valid JSON, or was valid JSON that is not a
116
+ * plain object (a `null`, array, string, number, or boolean top-level value
117
+ * is rejected — only a plain object can carry the `description`/`verify`/
118
+ * `acceptance_criteria`/`read_first`/`done` fields this seam reads).
119
+ */
120
+ class ResolverMalformedOutputError extends Error {
121
+ stdoutSample;
122
+ constructor(binary, reason, stdoutSample) {
123
+ super(`resolver command '${binary}' produced malformed output: ${reason}` +
124
+ (stdoutSample ? ` (stdout sample: ${formatDiagnosticToken(stdoutSample)})` : ''));
125
+ this.name = 'ResolverMalformedOutputError';
126
+ this.stdoutSample = stdoutSample;
127
+ }
128
+ }
129
+ // ─── Pure functions ────────────────────────────────────────────────────────────
130
+ /**
131
+ * The SAME kebab-case grammar `capability-validator.cjs`'s `KEBAB_RE`
132
+ * enforces on `taskContentResolver.trackerPrefix` at install time
133
+ * (`validateTaskContentResolverFields`). Duplicated as a literal rather than
134
+ * imported — `.cts` (build-at-publish, ADR-457) and the hand-written
135
+ * `capability-validator.cjs` are genuinely two different build targets with
136
+ * no shared-constants module between them today — but `tests/task-content-
137
+ * resolver-grammar-parity.test.cjs` asserts both regexes agree on a shared
138
+ * table of inputs, so a future edit to either one that silently diverges from
139
+ * the other fails a test instead of drifting quietly (CLAUDE.md's Generative
140
+ * Fix Divergence rule).
141
+ */
142
+ const TRACKER_PREFIX_RE = /^[a-z][a-z0-9-]*$/;
143
+ /**
144
+ * Split a `tracker-id` attribute value into its prefix and id, on the FIRST
145
+ * `:` only — colons after the first stay in the id verbatim (a tracker whose
146
+ * native ids contain colons, e.g. `beads:issue:GSD-1` → `{prefix: "beads",
147
+ * id: "issue:GSD-1"}`).
148
+ *
149
+ * PURE. Returns `null` for `null`/empty input, and for a string with no `:`
150
+ * at all (nothing to split — there is no prefix to match a resolver against).
151
+ */
152
+ function splitTrackerId(trackerId) {
153
+ if (typeof trackerId !== 'string' || trackerId.length === 0)
154
+ return null;
155
+ const colonIdx = trackerId.indexOf(':');
156
+ if (colonIdx === -1)
157
+ return null;
158
+ const prefix = trackerId.slice(0, colonIdx);
159
+ const id = trackerId.slice(colonIdx + 1);
160
+ if (!prefix || !id)
161
+ return null;
162
+ return { prefix, id };
163
+ }
164
+ /**
165
+ * Defensively re-validate a raw `taskContentResolver` declaration's shape.
166
+ * `capability-validator.cjs` already enforces this at install time, but this
167
+ * module treats every capability manifest as third-party-authored input and
168
+ * never trusts a shape it has not itself checked — a malformed declaration is
169
+ * treated as though it does not exist for matching purposes, never thrown on.
170
+ */
171
+ function parseResolverDeclaration(capabilityId, raw) {
172
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
173
+ return null;
174
+ const body = raw;
175
+ const trackerPrefix = typeof body.trackerPrefix === 'string' ? body.trackerPrefix.trim() : '';
176
+ if (!trackerPrefix || !TRACKER_PREFIX_RE.test(trackerPrefix))
177
+ return null;
178
+ const inv = body.invoke;
179
+ if (inv === null || typeof inv !== 'object' || Array.isArray(inv))
180
+ return null;
181
+ const invBody = inv;
182
+ const binary = typeof invBody.binary === 'string' ? invBody.binary.trim() : '';
183
+ if (!binary)
184
+ return null;
185
+ const args = Array.isArray(invBody.args)
186
+ ? invBody.args.filter((a) => typeof a === 'string')
187
+ : null;
188
+ if (args === null || args.length !== invBody.args.length)
189
+ return null;
190
+ if (!args.includes('{{id}}'))
191
+ return null;
192
+ const timeoutMs = invBody.timeoutMs;
193
+ if (typeof timeoutMs !== 'number' || !Number.isInteger(timeoutMs) || timeoutMs <= 0)
194
+ return null;
195
+ return {
196
+ capabilityId,
197
+ trackerPrefix,
198
+ invoke: { binary, args, timeoutMs },
199
+ };
200
+ }
201
+ /**
202
+ * Find the resolver declared for `prefix` among `capabilities`.
203
+ *
204
+ * PURE, total. Returns:
205
+ * - the single matching declaration when exactly one well-formed resolver
206
+ * declares `trackerPrefix === prefix`;
207
+ * - `null` when zero capabilities declare a well-formed resolver for it
208
+ * (an unrecognized prefix is a data case, not a defect — see design row 11);
209
+ * - the literal string `'ambiguous'` when two or more do — this must be
210
+ * structurally impossible in a correctly-validated install, but this
211
+ * function refuses to silently pick one regardless.
212
+ */
213
+ function findResolver(prefix, capabilities) {
214
+ const matches = [];
215
+ for (const cap of capabilities ?? []) {
216
+ if (!cap || typeof cap !== 'object')
217
+ continue;
218
+ const decl = parseResolverDeclaration(cap.id, cap.taskContentResolver);
219
+ if (decl && decl.trackerPrefix === prefix)
220
+ matches.push(decl);
221
+ }
222
+ if (matches.length === 0)
223
+ return null;
224
+ if (matches.length > 1)
225
+ return 'ambiguous';
226
+ return matches[0];
227
+ }
228
+ /**
229
+ * Expand a resolver's `invoke.args` template, replacing every `"{{id}}"`
230
+ * entry with the literal `id` string. Exact-match token replacement, not
231
+ * template-string interpolation — mirrors `review-lane-invocation.cts`'s
232
+ * argv-expansion discipline (a placeholder is a whole array element, not a
233
+ * substring).
234
+ *
235
+ * PURE.
236
+ */
237
+ function buildInvocation(resolver, id) {
238
+ return {
239
+ binary: resolver.invoke.binary,
240
+ args: resolver.invoke.args.map((a) => (a === '{{id}}' ? id : a)),
241
+ timeoutMs: resolver.invoke.timeoutMs,
242
+ };
243
+ }
244
+ /**
245
+ * Real subprocess execution — the default `execFn`. Uses Node's `spawnSync`
246
+ * with the `timeout` option so a hung resolver is killed at the bound rather
247
+ * than hanging the caller (CLAUDE.md's Unbounded Subprocesses gauntlet line).
248
+ */
249
+ function realExec(binary, args, opts) {
250
+ const result = node_child_process_1.default.spawnSync(binary, args, {
251
+ encoding: 'utf-8',
252
+ stdio: 'pipe',
253
+ timeout: opts.timeout,
254
+ windowsHide: true,
255
+ });
256
+ return {
257
+ status: result.status ?? null,
258
+ stdout: (result.stdout ?? '').toString(),
259
+ stderr: (result.stderr ?? '').toString(),
260
+ error: result.error ?? undefined,
261
+ };
262
+ }
263
+ /**
264
+ * True when an `ExecResult` indicates the subprocess was killed by the
265
+ * `timeout` option, i.e. it never completed and reported a real answer. Only
266
+ * `error.code === 'ETIMEDOUT'` is checked — Node.js guarantees this
267
+ * cross-platform when `spawnSync`'s `timeout` option fires; pairing it with a
268
+ * `signal === 'SIGTERM'` check is platform-fragile (Windows does not
269
+ * necessarily report SIGTERM the same way) and risks a false negative. Same
270
+ * predicate discipline as `shell-command-projection.cts`'s `isSpawnTimeout`.
271
+ */
272
+ function isExecTimeout(result) {
273
+ const err = result.error;
274
+ return err?.code === 'ETIMEDOUT';
275
+ }
276
+ // ─── Resolver JSON → content mapping ──────────────────────────────────────────
277
+ function coerceStringOrNull(value) {
278
+ return typeof value === 'string' ? value : null;
279
+ }
280
+ function coerceStringArray(value) {
281
+ return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
282
+ }
283
+ /**
284
+ * Map a resolver's validated JSON object onto `ResolvedTaskContent`. Every
285
+ * field is coerced defensively — the resolver's JSON is a third-party CLI's
286
+ * output, validated for exit code and JSON-object-shape upstream, but never
287
+ * trusted field-by-field. A missing or wrong-typed field degrades sanely
288
+ * (string/null fields fall back to `null`, array fields fall back to `[]`);
289
+ * only the caller's `description`-emptiness check throws no further errors
290
+ * here — this function is never the one that decides `resolved` vs `empty`.
291
+ */
292
+ function mapResolverOutput(body) {
293
+ return {
294
+ action: coerceStringOrNull(body['description']),
295
+ verify: coerceStringOrNull(body['verify']),
296
+ acceptanceCriteria: coerceStringArray(body['acceptance_criteria']),
297
+ readFirst: coerceStringArray(body['read_first']),
298
+ done: coerceStringOrNull(body['done']),
299
+ };
300
+ }
301
+ /**
302
+ * Orchestrate one task's content resolution: split the `tracker-id`, find the
303
+ * matching capability's resolver, invoke it through the bounded subprocess
304
+ * boundary, and map its JSON output onto the four documented outcomes.
305
+ *
306
+ * The only impure boundary is `execFn` (defaults to a real `spawnSync` call).
307
+ * Injecting a fake `execFn` lets tests assert every outcome — including a
308
+ * timeout — deterministically, without spawning a real process or waiting a
309
+ * real `timeoutMs`.
310
+ */
311
+ function resolveTaskContent(input) {
312
+ const split = splitTrackerId(input.trackerId);
313
+ if (split === null)
314
+ return { kind: TASK_CONTENT_RESULT.NOT_APPLICABLE };
315
+ const resolver = findResolver(split.prefix, input.capabilities ?? []);
316
+ if (resolver === null)
317
+ return { kind: TASK_CONTENT_RESULT.NO_RESOLVER };
318
+ if (resolver === 'ambiguous') {
319
+ // findResolver never returns the capability ids for the ambiguous case
320
+ // (it discards the losing matches) — re-derive them here for the error.
321
+ const ids = (input.capabilities ?? [])
322
+ .filter((cap) => {
323
+ const decl = parseResolverDeclaration(cap?.id, cap?.taskContentResolver);
324
+ return decl !== null && decl.trackerPrefix === split.prefix;
325
+ })
326
+ .map((cap) => cap.id);
327
+ throw new ResolverAmbiguousError(split.prefix, ids);
328
+ }
329
+ const invocation = buildInvocation(resolver, split.id);
330
+ const timeoutMs = input.timeoutOverrideMs ?? invocation.timeoutMs;
331
+ const execFn = input.execFn ?? realExec;
332
+ const result = execFn(invocation.binary, invocation.args, { timeout: timeoutMs });
333
+ if (isExecTimeout(result)) {
334
+ throw new ResolverTimeoutError(invocation.binary, timeoutMs);
335
+ }
336
+ if (result.error || result.status !== 0) {
337
+ const stderrTail = (result.stderr ?? '').trim().slice(-2000);
338
+ throw new ResolverFailedError(invocation.binary, result.status, stderrTail);
339
+ }
340
+ let parsed;
341
+ try {
342
+ parsed = JSON.parse(result.stdout);
343
+ }
344
+ catch {
345
+ throw new ResolverMalformedOutputError(invocation.binary, 'stdout is not valid JSON', result.stdout.slice(0, 200));
346
+ }
347
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
348
+ throw new ResolverMalformedOutputError(invocation.binary, `stdout parsed as valid JSON but is not a plain object (got ${Array.isArray(parsed) ? 'array' : typeof parsed})`, result.stdout.slice(0, 200));
349
+ }
350
+ const content = mapResolverOutput(parsed);
351
+ const description = typeof content.action === 'string' ? content.action.trim() : '';
352
+ if (description.length === 0) {
353
+ return { kind: TASK_CONTENT_RESULT.EMPTY };
354
+ }
355
+ return { kind: TASK_CONTENT_RESULT.RESOLVED, content };
356
+ }
357
+ const taskContentResolution = {
358
+ TASK_CONTENT_RESULT,
359
+ splitTrackerId,
360
+ findResolver,
361
+ buildInvocation,
362
+ resolveTaskContent,
363
+ ResolverAmbiguousError,
364
+ ResolverFailedError,
365
+ ResolverTimeoutError,
366
+ ResolverMalformedOutputError,
367
+ };
368
+ module.exports = taskContentResolution;