@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  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 +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  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 +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -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,12 +37,19 @@ 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
43
45
  // below can never drift from what the stage-time writer actually wrote.
44
46
  CAPABILITY_SKILL_MARKER, } = installProfiles;
45
47
  const clusters_cjs_1 = require("./clusters.cjs");
48
+ // #2870: `isGlobalScope` centralizes the `scope === 'global'` boolean
49
+ // projection `applySurface` needs at `_computePathPrefix`'s `isGlobal:
50
+ // boolean` boundary (see its doc comment in install-scope.cts for why the
51
+ // projection is centralized rather than eliminated).
52
+ const install_scope_cjs_1 = require("./install-scope.cjs");
46
53
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
54
  const runtimeArtifactLayout = require("./runtime-artifact-layout.cjs");
48
55
  const { findInstallSourceRoot } = runtimeArtifactLayout;
@@ -298,10 +305,16 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
298
305
  * Re-stage the active surface using the resolved layout.
299
306
  * Iterates layout.kinds and syncs each artifact kind to its destination.
300
307
  */
301
- function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts) {
308
+ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts, deps = {}) {
302
309
  if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
303
310
  throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
304
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
+ });
305
318
  const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
306
319
  const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry);
307
320
  // Profile toggles must converge retired surfaces too. Once a kind disappears
@@ -316,12 +329,23 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
316
329
  const _homedirFn = opts?.homedir ?? (() => node_os_1.default.homedir());
317
330
  const _resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(layout.configDir));
318
331
  const _homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(_homedirFn());
319
- const _isGlobal = (layout.scope ?? 'global') === 'global';
332
+ // #2870: same judgment as createRuntimeArtifactInstallPlan's identical
333
+ // line — `layout.scope` is already the resolved scope value on the
334
+ // `Layout` this function received. `layout.scope` is optional, so the
335
+ // pre-existing `?? 'global'` default is kept ahead of the call: it must
336
+ // run BEFORE `isGlobalScope`, because `isGlobalScope(undefined)` throws
337
+ // (unlike the old inline `undefined === 'global'`, which silently
338
+ // evaluated to `false`) — the default is what makes an undefined scope
339
+ // resolve to `'global'` here, exactly as it did before. `isGlobalScope`
340
+ // then projects the defaulted value to the boolean `_computePathPrefix`'s
341
+ // existing `isGlobal: boolean` API requires.
342
+ const _isGlobal = (0, install_scope_cjs_1.isGlobalScope)(layout.scope ?? 'global');
320
343
  const _isOpencode = layout.runtime === 'opencode';
321
344
  const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
322
345
  const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir });
323
346
  const _attribution = opts?.resolveAttribution ? opts.resolveAttribution(layout.runtime) : undefined;
324
- const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution };
347
+ // #2875 Part 2 (row I1): layout.configDir is this call's install root.
348
+ const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution, targetDir: layout.configDir };
325
349
  const tempDirsToClean = [];
326
350
  // #1575: When the surface has no state modifications AND the base profile is
327
351
  // 'full', pass the '*' sentinel for agents staging so ALL agents are staged —
@@ -557,13 +581,11 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest, runtime) {
557
581
  // from install and orphaned the installed gsd-*.md files, and the unscoped
558
582
  // prune deleted user-owned command files.
559
583
  //
560
- // NOTE: the destName rule below intentionally mirrors bin/install.js
561
- // `_copyStaged` (the `namespacedByDir` decision). Keep them in sync.
562
- const destLast = (typeof kind === 'object' && kind !== null && kind.destSubpath)
563
- ? node_path_1.default.basename(kind.destSubpath)
564
- : '';
565
- const prefixStem = kindPrefix ? kindPrefix.replace(/-$/, '') : '';
566
- const namespacedByDir = kindName === 'commands' && destLast === prefixStem;
584
+ // Single source of truth: runtimeArtifactLayout.isNamespacedByDir (#2871
585
+ // Phase 2 review finding — this rule previously drifted independently
586
+ // across install-engine.cts / surface.cts / runtime-artifact-layout.cts).
587
+ const kindDestSubpath = (typeof kind === 'object' && kind !== null && kind.destSubpath) ? kind.destSubpath : '';
588
+ const namespacedByDir = runtimeArtifactLayout.isNamespacedByDir(kindName, kindDestSubpath, kindPrefix);
567
589
  const stagedFiles = node_fs_1.default.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
568
590
  const stagedDestNames = new Set();
569
591
  for (const file of stagedFiles) {
@@ -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;
@@ -31,6 +31,9 @@ exports.cmdTeamsStatus = cmdTeamsStatus;
31
31
  // eslint-disable-next-line @typescript-eslint/no-require-imports
32
32
  const ioMod = require("./io.cjs");
33
33
  const { output: coreOutput } = ioMod;
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
35
+ const cliExitModule = require("./cli-exit.cjs");
36
+ const { ExitError } = cliExitModule;
34
37
  // ─── Pure core ────────────────────────────────────────────────────────────────
35
38
  /**
36
39
  * Resolve the agent-teams status from injected runtime and env.
@@ -67,7 +70,7 @@ function cmdTeamsStatus(cwd, opts) {
67
70
  const status = resolveTeamsStatus({ runtime, env: process.env });
68
71
  if (opts.active) {
69
72
  // --active mode: no output, exit code encodes the boolean
70
- process.exit(status.active ? 0 : 1);
73
+ throw new ExitError(status.active ? 0 : 1);
71
74
  }
72
75
  // Default: emit JSON to stdout via io.output, exit 0
73
76
  coreOutput(status, false);