@therocketcode/gsd-core 1.4.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 (568) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/GEMINI.md +53 -0
  3. package/LICENSE +21 -0
  4. package/README.ja-JP.md +125 -0
  5. package/README.ko-KR.md +125 -0
  6. package/README.md +144 -0
  7. package/README.pt-BR.md +125 -0
  8. package/README.zh-CN.md +125 -0
  9. package/agents/gsd-advisor-researcher.md +108 -0
  10. package/agents/gsd-ai-researcher.md +114 -0
  11. package/agents/gsd-assumptions-analyzer.md +105 -0
  12. package/agents/gsd-code-fixer.md +668 -0
  13. package/agents/gsd-code-reviewer.md +387 -0
  14. package/agents/gsd-codebase-mapper.md +853 -0
  15. package/agents/gsd-debug-session-manager.md +314 -0
  16. package/agents/gsd-debugger.md +1452 -0
  17. package/agents/gsd-doc-classifier.md +168 -0
  18. package/agents/gsd-doc-synthesizer.md +204 -0
  19. package/agents/gsd-doc-verifier.md +217 -0
  20. package/agents/gsd-doc-writer.md +616 -0
  21. package/agents/gsd-domain-researcher.md +147 -0
  22. package/agents/gsd-eval-auditor.md +191 -0
  23. package/agents/gsd-eval-planner.md +154 -0
  24. package/agents/gsd-executor.md +785 -0
  25. package/agents/gsd-framework-selector.md +160 -0
  26. package/agents/gsd-integration-checker.md +470 -0
  27. package/agents/gsd-intel-updater.md +342 -0
  28. package/agents/gsd-nyquist-auditor.md +203 -0
  29. package/agents/gsd-pattern-mapper.md +335 -0
  30. package/agents/gsd-phase-researcher.md +867 -0
  31. package/agents/gsd-plan-checker.md +978 -0
  32. package/agents/gsd-planner.md +1204 -0
  33. package/agents/gsd-project-researcher.md +611 -0
  34. package/agents/gsd-research-synthesizer.md +259 -0
  35. package/agents/gsd-roadmapper.md +688 -0
  36. package/agents/gsd-security-auditor.md +155 -0
  37. package/agents/gsd-ui-auditor.md +495 -0
  38. package/agents/gsd-ui-checker.md +309 -0
  39. package/agents/gsd-ui-researcher.md +374 -0
  40. package/agents/gsd-user-profiler.md +171 -0
  41. package/agents/gsd-verifier.md +923 -0
  42. package/assets/gsd-logo-2000-transparent.png +0 -0
  43. package/assets/gsd-logo-2000-transparent.svg +17 -0
  44. package/assets/gsd-logo-2000.png +0 -0
  45. package/assets/gsd-logo-2000.svg +21 -0
  46. package/assets/terminal.svg +68 -0
  47. package/bin/install.js +12726 -0
  48. package/bin/lib/ui-safety-gate.cjs +107 -0
  49. package/commands/gsd/add-tests.md +42 -0
  50. package/commands/gsd/ai-integration-phase.md +37 -0
  51. package/commands/gsd/audit-fix.md +34 -0
  52. package/commands/gsd/audit-milestone.md +37 -0
  53. package/commands/gsd/audit-uat.md +24 -0
  54. package/commands/gsd/autonomous.md +48 -0
  55. package/commands/gsd/capture.md +62 -0
  56. package/commands/gsd/cleanup.md +24 -0
  57. package/commands/gsd/code-review.md +59 -0
  58. package/commands/gsd/complete-milestone.md +143 -0
  59. package/commands/gsd/config.md +56 -0
  60. package/commands/gsd/debug.md +52 -0
  61. package/commands/gsd/discover-product.md +65 -0
  62. package/commands/gsd/discuss-phase.md +77 -0
  63. package/commands/gsd/docs-update.md +49 -0
  64. package/commands/gsd/eval-review.md +33 -0
  65. package/commands/gsd/execute-phase.md +66 -0
  66. package/commands/gsd/explore.md +27 -0
  67. package/commands/gsd/extract-learnings.md +23 -0
  68. package/commands/gsd/fast.md +31 -0
  69. package/commands/gsd/forensics.md +57 -0
  70. package/commands/gsd/graphify.md +204 -0
  71. package/commands/gsd/health.md +31 -0
  72. package/commands/gsd/help.md +28 -0
  73. package/commands/gsd/import.md +45 -0
  74. package/commands/gsd/inbox.md +39 -0
  75. package/commands/gsd/ingest-docs.md +42 -0
  76. package/commands/gsd/manager.md +45 -0
  77. package/commands/gsd/map-codebase.md +83 -0
  78. package/commands/gsd/milestone-summary.md +51 -0
  79. package/commands/gsd/model-domain.md +65 -0
  80. package/commands/gsd/mvp-phase.md +45 -0
  81. package/commands/gsd/new-milestone.md +45 -0
  82. package/commands/gsd/new-project.md +47 -0
  83. package/commands/gsd/ns-context.md +23 -0
  84. package/commands/gsd/ns-ideate.md +24 -0
  85. package/commands/gsd/ns-manage.md +29 -0
  86. package/commands/gsd/ns-project.md +22 -0
  87. package/commands/gsd/ns-review.md +26 -0
  88. package/commands/gsd/ns-workflow.md +28 -0
  89. package/commands/gsd/pause-work.md +43 -0
  90. package/commands/gsd/phase.md +56 -0
  91. package/commands/gsd/plan-phase.md +64 -0
  92. package/commands/gsd/plan-review-convergence.md +59 -0
  93. package/commands/gsd/pr-branch.md +26 -0
  94. package/commands/gsd/profile-user.md +46 -0
  95. package/commands/gsd/progress.md +48 -0
  96. package/commands/gsd/quick.md +174 -0
  97. package/commands/gsd/recommend-architecture.md +64 -0
  98. package/commands/gsd/resume-work.md +30 -0
  99. package/commands/gsd/review-backlog.md +63 -0
  100. package/commands/gsd/review.md +42 -0
  101. package/commands/gsd/secure-phase.md +36 -0
  102. package/commands/gsd/settings.md +29 -0
  103. package/commands/gsd/ship.md +24 -0
  104. package/commands/gsd/sketch.md +60 -0
  105. package/commands/gsd/spec-phase.md +63 -0
  106. package/commands/gsd/spike.md +57 -0
  107. package/commands/gsd/stats.md +20 -0
  108. package/commands/gsd/surface.md +155 -0
  109. package/commands/gsd/testing-strategy.md +65 -0
  110. package/commands/gsd/thread.md +24 -0
  111. package/commands/gsd/ui-phase.md +35 -0
  112. package/commands/gsd/ui-review.md +33 -0
  113. package/commands/gsd/ultraplan-phase.md +34 -0
  114. package/commands/gsd/undo.md +35 -0
  115. package/commands/gsd/update.md +49 -0
  116. package/commands/gsd/validate-phase.md +36 -0
  117. package/commands/gsd/verify-work.md +39 -0
  118. package/commands/gsd/workspace.md +52 -0
  119. package/commands/gsd/workstreams.md +70 -0
  120. package/gemini-extension.json +6 -0
  121. package/gsd-core/bin/check-latest-version.cjs +161 -0
  122. package/gsd-core/bin/gsd-tools.cjs +1928 -0
  123. package/gsd-core/bin/lib/active-workstream-store.cjs +291 -0
  124. package/gsd-core/bin/lib/adr-parser.cjs +399 -0
  125. package/gsd-core/bin/lib/agent-command-router.cjs +68 -0
  126. package/gsd-core/bin/lib/artifacts.cjs +51 -0
  127. package/gsd-core/bin/lib/audit.cjs +743 -0
  128. package/gsd-core/bin/lib/check-command-router.cjs +343 -0
  129. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
  130. package/gsd-core/bin/lib/cli-exit.cjs +42 -0
  131. package/gsd-core/bin/lib/clock.cjs +95 -0
  132. package/gsd-core/bin/lib/clusters.cjs +132 -0
  133. package/gsd-core/bin/lib/code-review-flags.cjs +59 -0
  134. package/gsd-core/bin/lib/command-aliases.cjs +809 -0
  135. package/gsd-core/bin/lib/command-arg-projection.cjs +55 -0
  136. package/gsd-core/bin/lib/command-routing-hub.cjs +300 -0
  137. package/gsd-core/bin/lib/commands.cjs +1203 -0
  138. package/gsd-core/bin/lib/config-schema.cjs +29 -0
  139. package/gsd-core/bin/lib/config-types.cjs +19 -0
  140. package/gsd-core/bin/lib/config.cjs +738 -0
  141. package/gsd-core/bin/lib/configuration.cjs +239 -0
  142. package/gsd-core/bin/lib/context-utilization.cjs +48 -0
  143. package/gsd-core/bin/lib/core.cjs +2051 -0
  144. package/gsd-core/bin/lib/decisions.cjs +118 -0
  145. package/gsd-core/bin/lib/docs.cjs +252 -0
  146. package/gsd-core/bin/lib/drift.cjs +364 -0
  147. package/gsd-core/bin/lib/fallow-runner.cjs +115 -0
  148. package/gsd-core/bin/lib/frontmatter.cjs +442 -0
  149. package/gsd-core/bin/lib/gap-checker.cjs +257 -0
  150. package/gsd-core/bin/lib/graphify.cjs +496 -0
  151. package/gsd-core/bin/lib/gsd2-import.cjs +456 -0
  152. package/gsd-core/bin/lib/init-command-router.cjs +62 -0
  153. package/gsd-core/bin/lib/init.cjs +1815 -0
  154. package/gsd-core/bin/lib/install-profiles.cjs +584 -0
  155. package/gsd-core/bin/lib/installer-migration-authoring.cjs +122 -0
  156. package/gsd-core/bin/lib/installer-migration-report.cjs +350 -0
  157. package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +218 -0
  158. package/gsd-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
  159. package/gsd-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
  160. package/gsd-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
  161. package/gsd-core/bin/lib/installer-migrations.cjs +823 -0
  162. package/gsd-core/bin/lib/intel.cjs +590 -0
  163. package/gsd-core/bin/lib/learnings.cjs +270 -0
  164. package/gsd-core/bin/lib/legacy-cleanup.cjs +253 -0
  165. package/gsd-core/bin/lib/milestone.cjs +373 -0
  166. package/gsd-core/bin/lib/model-catalog.cjs +154 -0
  167. package/gsd-core/bin/lib/model-profiles.cjs +24 -0
  168. package/gsd-core/bin/lib/observability/event.cjs +51 -0
  169. package/gsd-core/bin/lib/observability/logger.cjs +146 -0
  170. package/gsd-core/bin/lib/observability/redaction.cjs +48 -0
  171. package/gsd-core/bin/lib/package-identity.cjs +35 -0
  172. package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
  173. package/gsd-core/bin/lib/phase-command-router.cjs +189 -0
  174. package/gsd-core/bin/lib/phase-lifecycle.cjs +74 -0
  175. package/gsd-core/bin/lib/phase.cjs +1307 -0
  176. package/gsd-core/bin/lib/phases-command-router.cjs +43 -0
  177. package/gsd-core/bin/lib/plan-scan.cjs +91 -0
  178. package/gsd-core/bin/lib/planning-workspace.cjs +245 -0
  179. package/gsd-core/bin/lib/profile-output.cjs +1120 -0
  180. package/gsd-core/bin/lib/profile-pipeline.cjs +517 -0
  181. package/gsd-core/bin/lib/project-root.cjs +119 -0
  182. package/gsd-core/bin/lib/prompt-budget.cjs +305 -0
  183. package/gsd-core/bin/lib/research-provider.cjs +137 -0
  184. package/gsd-core/bin/lib/research-store.cjs +167 -0
  185. package/gsd-core/bin/lib/review-reviewer-selection.cjs +121 -0
  186. package/gsd-core/bin/lib/roadmap-command-router.cjs +166 -0
  187. package/gsd-core/bin/lib/roadmap-upgrade.cjs +476 -0
  188. package/gsd-core/bin/lib/roadmap.cjs +600 -0
  189. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +312 -0
  190. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
  191. package/gsd-core/bin/lib/runtime-homes.cjs +190 -0
  192. package/gsd-core/bin/lib/runtime-name-policy.cjs +96 -0
  193. package/gsd-core/bin/lib/runtime-slash.cjs +119 -0
  194. package/gsd-core/bin/lib/schema-detect.cjs +159 -0
  195. package/gsd-core/bin/lib/secrets.cjs +34 -0
  196. package/gsd-core/bin/lib/security.cjs +480 -0
  197. package/gsd-core/bin/lib/semver-compare.cjs +42 -0
  198. package/gsd-core/bin/lib/shell-command-projection.cjs +533 -0
  199. package/gsd-core/bin/lib/state-command-router.cjs +160 -0
  200. package/gsd-core/bin/lib/state-document.cjs +259 -0
  201. package/gsd-core/bin/lib/state.cjs +2010 -0
  202. package/gsd-core/bin/lib/surface.cjs +449 -0
  203. package/gsd-core/bin/lib/task-command-router.cjs +85 -0
  204. package/gsd-core/bin/lib/template.cjs +237 -0
  205. package/gsd-core/bin/lib/uat.cjs +297 -0
  206. package/gsd-core/bin/lib/ui-safety-gate.cjs +98 -0
  207. package/gsd-core/bin/lib/update-context.cjs +218 -0
  208. package/gsd-core/bin/lib/validate-command-router.cjs +91 -0
  209. package/gsd-core/bin/lib/validate.cjs +112 -0
  210. package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
  211. package/gsd-core/bin/lib/verification.cjs +193 -0
  212. package/gsd-core/bin/lib/verify-command-router.cjs +44 -0
  213. package/gsd-core/bin/lib/verify.cjs +1451 -0
  214. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +81 -0
  215. package/gsd-core/bin/lib/workstream-inventory.cjs +147 -0
  216. package/gsd-core/bin/lib/workstream-name-policy.cjs +91 -0
  217. package/gsd-core/bin/lib/workstream.cjs +380 -0
  218. package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
  219. package/gsd-core/bin/lib/worktree-safety.cjs +943 -0
  220. package/gsd-core/bin/shared/config-defaults.manifest.json +98 -0
  221. package/gsd-core/bin/shared/config-schema.manifest.json +192 -0
  222. package/gsd-core/bin/shared/model-catalog.json +149 -0
  223. package/gsd-core/bin/shared/runtime-aliases.manifest.json +75 -0
  224. package/gsd-core/bin/verify-reapply-patches.cjs +349 -0
  225. package/gsd-core/contexts/dev.md +21 -0
  226. package/gsd-core/contexts/research.md +22 -0
  227. package/gsd-core/contexts/review.md +23 -0
  228. package/gsd-core/references/agent-contracts.md +79 -0
  229. package/gsd-core/references/ai-evals.md +156 -0
  230. package/gsd-core/references/ai-frameworks.md +186 -0
  231. package/gsd-core/references/architecture-decision.md +74 -0
  232. package/gsd-core/references/artifact-types.md +131 -0
  233. package/gsd-core/references/auth-in-tests.md +91 -0
  234. package/gsd-core/references/autonomous-smart-discuss.md +277 -0
  235. package/gsd-core/references/checkpoints.md +814 -0
  236. package/gsd-core/references/common-bug-patterns.md +114 -0
  237. package/gsd-core/references/context-budget.md +85 -0
  238. package/gsd-core/references/continuation-format.md +253 -0
  239. package/gsd-core/references/db-test-isolation.md +54 -0
  240. package/gsd-core/references/debugger-philosophy.md +76 -0
  241. package/gsd-core/references/decimal-phase-calculation.md +64 -0
  242. package/gsd-core/references/doc-conflict-engine.md +91 -0
  243. package/gsd-core/references/domain-modeling.md +80 -0
  244. package/gsd-core/references/domain-probes.md +125 -0
  245. package/gsd-core/references/e2e-tiering.md +35 -0
  246. package/gsd-core/references/execute-mvp-tdd.md +81 -0
  247. package/gsd-core/references/executor-examples.md +110 -0
  248. package/gsd-core/references/few-shot-examples/plan-checker.md +73 -0
  249. package/gsd-core/references/few-shot-examples/verifier.md +109 -0
  250. package/gsd-core/references/flaky-test-checklist.md +22 -0
  251. package/gsd-core/references/gate-prompts.md +100 -0
  252. package/gsd-core/references/gates.md +70 -0
  253. package/gsd-core/references/git-integration.md +298 -0
  254. package/gsd-core/references/git-planning-commit.md +40 -0
  255. package/gsd-core/references/ios-scaffold.md +123 -0
  256. package/gsd-core/references/mandatory-initial-read.md +2 -0
  257. package/gsd-core/references/model-profile-resolution.md +38 -0
  258. package/gsd-core/references/model-profiles.md +245 -0
  259. package/gsd-core/references/mvp-concepts.md +49 -0
  260. package/gsd-core/references/phase-argument-parsing.md +61 -0
  261. package/gsd-core/references/planner-antipatterns.md +89 -0
  262. package/gsd-core/references/planner-chunked.md +49 -0
  263. package/gsd-core/references/planner-gap-closure.md +62 -0
  264. package/gsd-core/references/planner-graphify-auto-update.md +67 -0
  265. package/gsd-core/references/planner-human-verify-mode.md +57 -0
  266. package/gsd-core/references/planner-interface-context.md +62 -0
  267. package/gsd-core/references/planner-load-graph-context.md +36 -0
  268. package/gsd-core/references/planner-mvp-mode.md +53 -0
  269. package/gsd-core/references/planner-reviews.md +39 -0
  270. package/gsd-core/references/planner-revision.md +87 -0
  271. package/gsd-core/references/planner-source-audit.md +73 -0
  272. package/gsd-core/references/planning-config.md +473 -0
  273. package/gsd-core/references/product-discovery.md +49 -0
  274. package/gsd-core/references/project-skills-discovery.md +19 -0
  275. package/gsd-core/references/questioning.md +162 -0
  276. package/gsd-core/references/realistic-test-data.md +44 -0
  277. package/gsd-core/references/research-documentation-lookup.md +29 -0
  278. package/gsd-core/references/research-philosophy.md +29 -0
  279. package/gsd-core/references/research-verification-protocol.md +27 -0
  280. package/gsd-core/references/revision-loop.md +97 -0
  281. package/gsd-core/references/scout-codebase.md +51 -0
  282. package/gsd-core/references/skeleton-template.md +48 -0
  283. package/gsd-core/references/sketch-interactivity.md +41 -0
  284. package/gsd-core/references/sketch-theme-system.md +94 -0
  285. package/gsd-core/references/sketch-tooling.md +45 -0
  286. package/gsd-core/references/sketch-variant-patterns.md +81 -0
  287. package/gsd-core/references/spidr-splitting.md +69 -0
  288. package/gsd-core/references/tdd.md +330 -0
  289. package/gsd-core/references/test-containers.md +55 -0
  290. package/gsd-core/references/test-strategy.md +75 -0
  291. package/gsd-core/references/thinking-models-debug.md +44 -0
  292. package/gsd-core/references/thinking-models-execution.md +50 -0
  293. package/gsd-core/references/thinking-models-planning.md +62 -0
  294. package/gsd-core/references/thinking-models-research.md +50 -0
  295. package/gsd-core/references/thinking-models-verification.md +55 -0
  296. package/gsd-core/references/thinking-partner.md +96 -0
  297. package/gsd-core/references/ui-brand.md +162 -0
  298. package/gsd-core/references/universal-anti-patterns.md +63 -0
  299. package/gsd-core/references/user-profiling.md +681 -0
  300. package/gsd-core/references/user-story-template.md +58 -0
  301. package/gsd-core/references/verification-overrides.md +227 -0
  302. package/gsd-core/references/verification-patterns.md +612 -0
  303. package/gsd-core/references/verify-mvp-mode.md +85 -0
  304. package/gsd-core/references/workstream-flag.md +111 -0
  305. package/gsd-core/references/worktree-branch-check.md +38 -0
  306. package/gsd-core/references/worktree-path-safety.md +67 -0
  307. package/gsd-core/templates/AI-SPEC.md +246 -0
  308. package/gsd-core/templates/DEBUG.md +169 -0
  309. package/gsd-core/templates/README.md +77 -0
  310. package/gsd-core/templates/SECURITY.md +61 -0
  311. package/gsd-core/templates/UAT.md +265 -0
  312. package/gsd-core/templates/UI-SPEC.md +100 -0
  313. package/gsd-core/templates/VALIDATION.md +76 -0
  314. package/gsd-core/templates/adr.md +58 -0
  315. package/gsd-core/templates/claude-md.md +145 -0
  316. package/gsd-core/templates/codebase/architecture.md +255 -0
  317. package/gsd-core/templates/codebase/concerns.md +310 -0
  318. package/gsd-core/templates/codebase/conventions.md +307 -0
  319. package/gsd-core/templates/codebase/integrations.md +280 -0
  320. package/gsd-core/templates/codebase/stack.md +186 -0
  321. package/gsd-core/templates/codebase/structure.md +285 -0
  322. package/gsd-core/templates/codebase/testing.md +480 -0
  323. package/gsd-core/templates/config.json +62 -0
  324. package/gsd-core/templates/context.md +352 -0
  325. package/gsd-core/templates/continue-here.md +78 -0
  326. package/gsd-core/templates/copilot-instructions.md +7 -0
  327. package/gsd-core/templates/debug-subagent-prompt.md +91 -0
  328. package/gsd-core/templates/dev-preferences.md +21 -0
  329. package/gsd-core/templates/discovery.md +146 -0
  330. package/gsd-core/templates/discussion-log.md +63 -0
  331. package/gsd-core/templates/domain-model.md +54 -0
  332. package/gsd-core/templates/milestone-archive.md +123 -0
  333. package/gsd-core/templates/milestone.md +115 -0
  334. package/gsd-core/templates/phase-prompt.md +610 -0
  335. package/gsd-core/templates/planner-subagent-prompt.md +117 -0
  336. package/gsd-core/templates/product-brief.md +55 -0
  337. package/gsd-core/templates/project.md +186 -0
  338. package/gsd-core/templates/requirements.md +231 -0
  339. package/gsd-core/templates/research-project/ARCHITECTURE.md +204 -0
  340. package/gsd-core/templates/research-project/FEATURES.md +147 -0
  341. package/gsd-core/templates/research-project/PITFALLS.md +200 -0
  342. package/gsd-core/templates/research-project/STACK.md +120 -0
  343. package/gsd-core/templates/research-project/SUMMARY.md +170 -0
  344. package/gsd-core/templates/research.md +592 -0
  345. package/gsd-core/templates/retrospective.md +54 -0
  346. package/gsd-core/templates/roadmap.md +202 -0
  347. package/gsd-core/templates/spec.md +307 -0
  348. package/gsd-core/templates/state.md +195 -0
  349. package/gsd-core/templates/summary-complex.md +59 -0
  350. package/gsd-core/templates/summary-minimal.md +41 -0
  351. package/gsd-core/templates/summary-standard.md +48 -0
  352. package/gsd-core/templates/summary.md +248 -0
  353. package/gsd-core/templates/test-strategy.md +50 -0
  354. package/gsd-core/templates/user-profile.md +146 -0
  355. package/gsd-core/templates/user-setup.md +311 -0
  356. package/gsd-core/templates/verification-report.md +322 -0
  357. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -0
  358. package/gsd-core/workflows/add-backlog.md +91 -0
  359. package/gsd-core/workflows/add-phase.md +113 -0
  360. package/gsd-core/workflows/add-tests.md +355 -0
  361. package/gsd-core/workflows/add-todo.md +161 -0
  362. package/gsd-core/workflows/ai-integration-phase.md +295 -0
  363. package/gsd-core/workflows/analyze-dependencies.md +96 -0
  364. package/gsd-core/workflows/audit-fix.md +178 -0
  365. package/gsd-core/workflows/audit-milestone.md +360 -0
  366. package/gsd-core/workflows/audit-uat.md +110 -0
  367. package/gsd-core/workflows/autonomous.md +797 -0
  368. package/gsd-core/workflows/check-todos.md +180 -0
  369. package/gsd-core/workflows/cleanup.md +195 -0
  370. package/gsd-core/workflows/code-review-fix.md +502 -0
  371. package/gsd-core/workflows/code-review.md +658 -0
  372. package/gsd-core/workflows/complete-milestone.md +855 -0
  373. package/gsd-core/workflows/debug.md +237 -0
  374. package/gsd-core/workflows/diagnose-issues.md +245 -0
  375. package/gsd-core/workflows/discover-product.md +112 -0
  376. package/gsd-core/workflows/discovery-phase.md +291 -0
  377. package/gsd-core/workflows/discuss-phase/modes/advisor.md +176 -0
  378. package/gsd-core/workflows/discuss-phase/modes/all.md +28 -0
  379. package/gsd-core/workflows/discuss-phase/modes/analyze.md +44 -0
  380. package/gsd-core/workflows/discuss-phase/modes/auto.md +57 -0
  381. package/gsd-core/workflows/discuss-phase/modes/batch.md +52 -0
  382. package/gsd-core/workflows/discuss-phase/modes/chain.md +98 -0
  383. package/gsd-core/workflows/discuss-phase/modes/default.md +141 -0
  384. package/gsd-core/workflows/discuss-phase/modes/power.md +44 -0
  385. package/gsd-core/workflows/discuss-phase/modes/text.md +55 -0
  386. package/gsd-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
  387. package/gsd-core/workflows/discuss-phase/templates/context.md +136 -0
  388. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
  389. package/gsd-core/workflows/discuss-phase-assumptions.md +675 -0
  390. package/gsd-core/workflows/discuss-phase-power.md +291 -0
  391. package/gsd-core/workflows/discuss-phase.md +499 -0
  392. package/gsd-core/workflows/do.md +111 -0
  393. package/gsd-core/workflows/docs-update.md +1176 -0
  394. package/gsd-core/workflows/edit-phase.md +295 -0
  395. package/gsd-core/workflows/eval-review.md +156 -0
  396. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
  397. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  398. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
  399. package/gsd-core/workflows/execute-phase.md +1752 -0
  400. package/gsd-core/workflows/execute-plan.md +526 -0
  401. package/gsd-core/workflows/explore.md +146 -0
  402. package/gsd-core/workflows/extract-learnings.md +243 -0
  403. package/gsd-core/workflows/fast.md +124 -0
  404. package/gsd-core/workflows/forensics.md +279 -0
  405. package/gsd-core/workflows/graduation.md +196 -0
  406. package/gsd-core/workflows/health.md +224 -0
  407. package/gsd-core/workflows/help/modes/brief.md +22 -0
  408. package/gsd-core/workflows/help/modes/default.md +50 -0
  409. package/gsd-core/workflows/help/modes/full.md +789 -0
  410. package/gsd-core/workflows/help/modes/topic.md +74 -0
  411. package/gsd-core/workflows/help.md +24 -0
  412. package/gsd-core/workflows/import.md +256 -0
  413. package/gsd-core/workflows/inbox.md +387 -0
  414. package/gsd-core/workflows/ingest-docs.md +340 -0
  415. package/gsd-core/workflows/insert-phase.md +152 -0
  416. package/gsd-core/workflows/list-phase-assumptions.md +178 -0
  417. package/gsd-core/workflows/list-workspaces.md +57 -0
  418. package/gsd-core/workflows/manager.md +393 -0
  419. package/gsd-core/workflows/map-codebase.md +446 -0
  420. package/gsd-core/workflows/milestone-summary.md +224 -0
  421. package/gsd-core/workflows/model-domain.md +162 -0
  422. package/gsd-core/workflows/mvp-phase.md +222 -0
  423. package/gsd-core/workflows/new-milestone.md +635 -0
  424. package/gsd-core/workflows/new-project.md +1555 -0
  425. package/gsd-core/workflows/new-workspace.md +240 -0
  426. package/gsd-core/workflows/next.md +299 -0
  427. package/gsd-core/workflows/node-repair.md +92 -0
  428. package/gsd-core/workflows/note.md +158 -0
  429. package/gsd-core/workflows/pause-work.md +244 -0
  430. package/gsd-core/workflows/plan-milestone-gaps.md +281 -0
  431. package/gsd-core/workflows/plan-phase.md +1814 -0
  432. package/gsd-core/workflows/plan-review-convergence.md +346 -0
  433. package/gsd-core/workflows/plant-seed.md +230 -0
  434. package/gsd-core/workflows/pr-branch.md +157 -0
  435. package/gsd-core/workflows/profile-user.md +453 -0
  436. package/gsd-core/workflows/progress.md +699 -0
  437. package/gsd-core/workflows/quick.md +1017 -0
  438. package/gsd-core/workflows/reapply-patches.md +426 -0
  439. package/gsd-core/workflows/recommend-architecture.md +135 -0
  440. package/gsd-core/workflows/remove-phase.md +156 -0
  441. package/gsd-core/workflows/remove-workspace.md +108 -0
  442. package/gsd-core/workflows/resume-project.md +332 -0
  443. package/gsd-core/workflows/review.md +748 -0
  444. package/gsd-core/workflows/scan.md +107 -0
  445. package/gsd-core/workflows/secure-phase.md +182 -0
  446. package/gsd-core/workflows/session-report.md +146 -0
  447. package/gsd-core/workflows/settings-advanced.md +810 -0
  448. package/gsd-core/workflows/settings-integrations.md +312 -0
  449. package/gsd-core/workflows/settings.md +566 -0
  450. package/gsd-core/workflows/ship.md +405 -0
  451. package/gsd-core/workflows/sketch-wrap-up.md +286 -0
  452. package/gsd-core/workflows/sketch.md +361 -0
  453. package/gsd-core/workflows/spec-phase.md +263 -0
  454. package/gsd-core/workflows/spike-wrap-up.md +307 -0
  455. package/gsd-core/workflows/spike.md +453 -0
  456. package/gsd-core/workflows/stats.md +80 -0
  457. package/gsd-core/workflows/sync-skills.md +182 -0
  458. package/gsd-core/workflows/testing-strategy.md +122 -0
  459. package/gsd-core/workflows/thread.md +222 -0
  460. package/gsd-core/workflows/transition.md +694 -0
  461. package/gsd-core/workflows/ui-phase.md +328 -0
  462. package/gsd-core/workflows/ui-review.md +193 -0
  463. package/gsd-core/workflows/ultraplan-phase.md +199 -0
  464. package/gsd-core/workflows/undo.md +314 -0
  465. package/gsd-core/workflows/update.md +496 -0
  466. package/gsd-core/workflows/validate-phase.md +181 -0
  467. package/gsd-core/workflows/verify-phase.md +544 -0
  468. package/gsd-core/workflows/verify-work.md +781 -0
  469. package/hooks/dist/gsd-check-update-worker.js +108 -0
  470. package/hooks/dist/gsd-check-update.js +66 -0
  471. package/hooks/dist/gsd-config-reload.js +133 -0
  472. package/hooks/dist/gsd-context-monitor.js +195 -0
  473. package/hooks/dist/gsd-cursor-post-tool.js +75 -0
  474. package/hooks/dist/gsd-cursor-session-start.js +52 -0
  475. package/hooks/dist/gsd-graphify-update.sh +158 -0
  476. package/hooks/dist/gsd-phase-boundary.sh +47 -0
  477. package/hooks/dist/gsd-prompt-guard.js +97 -0
  478. package/hooks/dist/gsd-read-guard.js +101 -0
  479. package/hooks/dist/gsd-read-injection-scanner.js +203 -0
  480. package/hooks/dist/gsd-session-state.sh +59 -0
  481. package/hooks/dist/gsd-statusline.js +566 -0
  482. package/hooks/dist/gsd-update-banner.js +138 -0
  483. package/hooks/dist/gsd-validate-commit.sh +57 -0
  484. package/hooks/dist/gsd-workflow-guard.js +167 -0
  485. package/hooks/dist/gsd-worktree-path-guard.js +169 -0
  486. package/hooks/dist/lib/git-cmd.js +150 -0
  487. package/hooks/dist/lib/gsd-graphify-rebuild.sh +65 -0
  488. package/hooks/dist/managed-hooks-registry.cjs +38 -0
  489. package/hooks/gsd-check-update-worker.js +108 -0
  490. package/hooks/gsd-check-update.js +66 -0
  491. package/hooks/gsd-config-reload.js +133 -0
  492. package/hooks/gsd-context-monitor.js +195 -0
  493. package/hooks/gsd-cursor-post-tool.js +75 -0
  494. package/hooks/gsd-cursor-session-start.js +52 -0
  495. package/hooks/gsd-graphify-update.sh +158 -0
  496. package/hooks/gsd-phase-boundary.sh +47 -0
  497. package/hooks/gsd-prompt-guard.js +97 -0
  498. package/hooks/gsd-read-guard.js +101 -0
  499. package/hooks/gsd-read-injection-scanner.js +203 -0
  500. package/hooks/gsd-session-state.sh +59 -0
  501. package/hooks/gsd-statusline.js +566 -0
  502. package/hooks/gsd-update-banner.js +138 -0
  503. package/hooks/gsd-validate-commit.sh +57 -0
  504. package/hooks/gsd-workflow-guard.js +167 -0
  505. package/hooks/gsd-worktree-path-guard.js +169 -0
  506. package/hooks/hooks.json +69 -0
  507. package/hooks/lib/git-cmd.js +150 -0
  508. package/hooks/lib/gsd-graphify-rebuild.sh +65 -0
  509. package/hooks/managed-hooks-registry.cjs +38 -0
  510. package/package.json +115 -0
  511. package/scripts/affected-tests-lib.cjs +542 -0
  512. package/scripts/audit-workflow-script-paths.cjs +73 -0
  513. package/scripts/base64-scan.sh +351 -0
  514. package/scripts/build-hooks.js +247 -0
  515. package/scripts/changeset/README.md +129 -0
  516. package/scripts/changeset/cli.cjs +590 -0
  517. package/scripts/changeset/github-release-notes.cjs +199 -0
  518. package/scripts/changeset/lint.cjs +111 -0
  519. package/scripts/changeset/new.cjs +137 -0
  520. package/scripts/changeset/parse.cjs +114 -0
  521. package/scripts/changeset/render.cjs +34 -0
  522. package/scripts/changeset/serialize.cjs +130 -0
  523. package/scripts/check-alias-drift.cjs +114 -0
  524. package/scripts/check-env.cjs +312 -0
  525. package/scripts/check-npm-integrity.cjs +215 -0
  526. package/scripts/ci-guard-runner.cjs +22 -0
  527. package/scripts/ci-prepare-test-scope.cjs +51 -0
  528. package/scripts/ci-rebase-check.cjs +86 -0
  529. package/scripts/ci-test-scope.cjs +431 -0
  530. package/scripts/command-contract-helpers.cjs +64 -0
  531. package/scripts/diff-touches-shipped-paths.cjs +155 -0
  532. package/scripts/fix-slash-commands.cjs +147 -0
  533. package/scripts/gen-inventory-manifest.cjs +115 -0
  534. package/scripts/gen-research-agents.cjs +276 -0
  535. package/scripts/generate-package-identity.cjs +125 -0
  536. package/scripts/issue-dedupe.cjs +278 -0
  537. package/scripts/lib/allowlist-ratchet.cjs +136 -0
  538. package/scripts/lib/cli-exit.cjs +56 -0
  539. package/scripts/lint-command-contract.cjs +114 -0
  540. package/scripts/lint-descriptions.cjs +87 -0
  541. package/scripts/lint-docs-required.cjs +222 -0
  542. package/scripts/lint-legacy-dir-name.cjs +160 -0
  543. package/scripts/lint-package-identity-drift.cjs +141 -0
  544. package/scripts/lint-pr-check-project-dir.cjs +99 -0
  545. package/scripts/lint-shell-command-projection-drift.cjs +62 -0
  546. package/scripts/lint-skill-deps.cjs +185 -0
  547. package/scripts/lint-test-file-count.allowlist.json +135 -0
  548. package/scripts/lint-test-file-count.cjs +246 -0
  549. package/scripts/mutation-matrix.cjs +222 -0
  550. package/scripts/pr-template-policy.cjs +268 -0
  551. package/scripts/prompt-injection-scan.sh +207 -0
  552. package/scripts/release-notes/discord-release-summary.cjs +373 -0
  553. package/scripts/release-notes/format-github-release-notes.cjs +261 -0
  554. package/scripts/release-tarball-smoke.cjs +629 -0
  555. package/scripts/research-profiles.cjs +149 -0
  556. package/scripts/run-affected-tests.cjs +7 -0
  557. package/scripts/run-cross-platform-tests.cjs +67 -0
  558. package/scripts/run-tests.cjs +315 -0
  559. package/scripts/secret-scan-lint.sh +231 -0
  560. package/scripts/secret-scan.sh +358 -0
  561. package/scripts/setup-branch-protection.sh +236 -0
  562. package/scripts/strip-prose-atrefs.cjs +106 -0
  563. package/scripts/sync-manifest-versions.cjs +119 -0
  564. package/scripts/sync-rulesets.sh +34 -0
  565. package/scripts/sync-runtime-launcher.cjs +399 -0
  566. package/scripts/test-failure-reasons.cjs +34 -0
  567. package/scripts/verify-npm-publish.cjs +240 -0
  568. package/scripts/workflow-policy.cjs +450 -0
@@ -0,0 +1,1203 @@
1
+ "use strict";
2
+ /**
3
+ * Commands — Standalone utility commands
4
+ *
5
+ * ADR-457 build-at-publish: the hand-written bin/lib/commands.cjs collapsed
6
+ * to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
7
+ * from the prior hand-written .cjs; only strict types are added.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ const core = require("./core.cjs");
17
+ const { loadConfig, isGitIgnored, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, stripShippedMilestones: _stripShippedMilestones, extractCurrentMilestone, toPosixPath, output, error, findPhaseInternal, extractOneLinerFromBody, getRoadmapPhaseInternal, extractPhaseToken, resolveGranularityInternal, assertValidGranularityOverride, } = core;
18
+ const model_catalog_cjs_1 = require("./model-catalog.cjs");
19
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
20
+ const planningWorkspace = require("./planning-workspace.cjs");
21
+ const { planningDir, planningPaths } = planningWorkspace;
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const frontmatter = require("./frontmatter.cjs");
24
+ const { extractFrontmatter } = frontmatter;
25
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
26
+ const modelProfiles = require("./model-profiles.cjs");
27
+ const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
28
+ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
29
+ // ─── Phase Status ─────────────────────────────────────────────────────────────
30
+ /**
31
+ * Determine phase status by checking plan/summary counts AND verification state.
32
+ * Introduces "Executed" for phases with all summaries but no passing verification.
33
+ */
34
+ function determinePhaseStatus(plans, summaries, phaseDir, defaultPending) {
35
+ if (plans === 0)
36
+ return defaultPending;
37
+ if (summaries < plans && summaries > 0)
38
+ return 'In Progress';
39
+ if (summaries < plans)
40
+ return 'Planned';
41
+ // summaries >= plans — check verification
42
+ try {
43
+ const files = node_fs_1.default.readdirSync(phaseDir);
44
+ const verificationFile = files.find(f => f === 'VERIFICATION.md' || f.endsWith('-VERIFICATION.md'));
45
+ if (verificationFile) {
46
+ const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(phaseDir, verificationFile)) || '';
47
+ if (/status:\s*passed/i.test(content))
48
+ return 'Complete';
49
+ if (/status:\s*human_needed/i.test(content))
50
+ return 'Needs Review';
51
+ if (/status:\s*gaps_found/i.test(content))
52
+ return 'Executed';
53
+ // Verification exists but unrecognized status — treat as executed
54
+ return 'Executed';
55
+ }
56
+ }
57
+ catch { /* directory read failed — fall through */ }
58
+ // No verification file — executed but not verified
59
+ return 'Executed';
60
+ }
61
+ function cmdGenerateSlug(text, raw) {
62
+ if (!text) {
63
+ error('text required for slug generation');
64
+ }
65
+ const slug = text
66
+ .toLowerCase()
67
+ .replace(/[^a-z0-9]+/g, '-')
68
+ .replace(/^-+|-+$/g, '')
69
+ .substring(0, 60);
70
+ const result = { slug };
71
+ output(result, raw, slug);
72
+ }
73
+ function cmdCurrentTimestamp(format, raw) {
74
+ const now = new Date();
75
+ let result;
76
+ switch (format) {
77
+ case 'date':
78
+ result = now.toISOString().split('T')[0];
79
+ break;
80
+ case 'filename':
81
+ result = now.toISOString().replace(/:/g, '-').replace(/\..+/, '');
82
+ break;
83
+ case 'full':
84
+ default:
85
+ result = now.toISOString();
86
+ break;
87
+ }
88
+ output({ timestamp: result }, raw, result);
89
+ }
90
+ function cmdListTodos(cwd, area, raw) {
91
+ const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
92
+ let count = 0;
93
+ const todos = [];
94
+ try {
95
+ const files = node_fs_1.default.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
96
+ for (const file of files) {
97
+ const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(pendingDir, file));
98
+ if (content === null)
99
+ continue;
100
+ const createdMatch = content.match(/^created:\s*(.+)$/m);
101
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
102
+ const areaMatch = content.match(/^area:\s*(.+)$/m);
103
+ const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
104
+ // Apply area filter if specified
105
+ if (area && todoArea !== area)
106
+ continue;
107
+ count++;
108
+ todos.push({
109
+ file,
110
+ created: createdMatch ? createdMatch[1].trim() : 'unknown',
111
+ title: titleMatch ? titleMatch[1].trim() : 'Untitled',
112
+ area: todoArea,
113
+ path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(pendingDir, file))),
114
+ });
115
+ }
116
+ }
117
+ catch { /* intentionally empty */ }
118
+ const result = { count, todos };
119
+ output(result, raw, count.toString());
120
+ }
121
+ function cmdVerifyPathExists(cwd, targetPath, raw) {
122
+ if (!targetPath) {
123
+ error('path required for verification');
124
+ }
125
+ // Reject null bytes and validate path does not contain traversal attempts
126
+ if (targetPath.includes('\0')) {
127
+ error('path contains null bytes');
128
+ }
129
+ const fullPath = node_path_1.default.isAbsolute(targetPath) ? targetPath : node_path_1.default.join(cwd, targetPath);
130
+ try {
131
+ const stats = node_fs_1.default.statSync(fullPath);
132
+ const type = stats.isDirectory() ? 'directory' : stats.isFile() ? 'file' : 'other';
133
+ const result = { exists: true, type };
134
+ output(result, raw, 'true');
135
+ }
136
+ catch {
137
+ const result = { exists: false, type: null };
138
+ output(result, raw, 'false');
139
+ }
140
+ }
141
+ function cmdHistoryDigest(cwd, raw) {
142
+ const phasesDir = planningPaths(cwd).phases;
143
+ const digest = { phases: {}, decisions: [], tech_stack: new Set() };
144
+ // Collect all phase directories: archived + current
145
+ const allPhaseDirs = [];
146
+ // Add archived phases first (oldest milestones first)
147
+ const archived = getArchivedPhaseDirs(cwd);
148
+ for (const a of archived) {
149
+ allPhaseDirs.push({ name: a.name, fullPath: a.fullPath, milestone: a.milestone });
150
+ }
151
+ // Add current phases
152
+ if (node_fs_1.default.existsSync(phasesDir)) {
153
+ try {
154
+ const currentDirs = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
155
+ .filter(e => e.isDirectory())
156
+ .map(e => e.name)
157
+ .sort();
158
+ for (const dir of currentDirs) {
159
+ allPhaseDirs.push({ name: dir, fullPath: node_path_1.default.join(phasesDir, dir), milestone: null });
160
+ }
161
+ }
162
+ catch { /* intentionally empty */ }
163
+ }
164
+ if (allPhaseDirs.length === 0) {
165
+ digest.tech_stack = [];
166
+ output(digest, raw, undefined);
167
+ return;
168
+ }
169
+ try {
170
+ for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
171
+ const summaries = node_fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
172
+ for (const summary of summaries) {
173
+ const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(dirPath, summary));
174
+ if (content === null)
175
+ continue;
176
+ try {
177
+ const fm = extractFrontmatter(content);
178
+ const phaseNum = fm['phase'] || dir.split('-')[0];
179
+ if (!digest.phases[phaseNum]) {
180
+ digest.phases[phaseNum] = {
181
+ name: fm['name'] || dir.split('-').slice(1).join(' ') || 'Unknown',
182
+ provides: new Set(),
183
+ affects: new Set(),
184
+ patterns: new Set(),
185
+ };
186
+ }
187
+ // Merge provides
188
+ const depGraph = fm['dependency-graph'];
189
+ if (depGraph && depGraph['provides']) {
190
+ depGraph['provides'].forEach((p) => digest.phases[phaseNum].provides.add(p));
191
+ }
192
+ else if (fm['provides']) {
193
+ fm['provides'].forEach((p) => digest.phases[phaseNum].provides.add(p));
194
+ }
195
+ // Merge affects
196
+ if (depGraph && depGraph['affects']) {
197
+ depGraph['affects'].forEach((a) => digest.phases[phaseNum].affects.add(a));
198
+ }
199
+ // Merge patterns
200
+ if (fm['patterns-established']) {
201
+ fm['patterns-established'].forEach((p) => digest.phases[phaseNum].patterns.add(p));
202
+ }
203
+ // Merge decisions
204
+ if (fm['key-decisions']) {
205
+ fm['key-decisions'].forEach((d) => {
206
+ digest.decisions.push({ phase: phaseNum, decision: d });
207
+ });
208
+ }
209
+ // Merge tech stack
210
+ const techStack = fm['tech-stack'];
211
+ if (techStack && techStack['added']) {
212
+ techStack['added'].forEach((t) => digest.tech_stack.add(typeof t === 'string' ? t : t.name));
213
+ }
214
+ }
215
+ catch {
216
+ // Skip malformed summaries
217
+ }
218
+ }
219
+ }
220
+ // Convert Sets to Arrays for JSON output
221
+ Object.keys(digest.phases).forEach(p => {
222
+ digest.phases[p].provides = [...digest.phases[p].provides];
223
+ digest.phases[p].affects = [...digest.phases[p].affects];
224
+ digest.phases[p].patterns = [...digest.phases[p].patterns];
225
+ });
226
+ digest.tech_stack = [...digest.tech_stack];
227
+ output(digest, raw, undefined);
228
+ }
229
+ catch (e) {
230
+ error('Failed to generate history digest: ' + e.message);
231
+ }
232
+ }
233
+ function cmdResolveModel(cwd, agentType, raw) {
234
+ if (!agentType) {
235
+ error('agent-type required');
236
+ }
237
+ const config = loadConfig(cwd);
238
+ const profile = config['model_profile'] || 'balanced';
239
+ const model = resolveModelInternal(cwd, agentType);
240
+ const effort = resolveEffortInternal(cwd, agentType);
241
+ const agentModels = MODEL_PROFILES[agentType];
242
+ const result = agentModels
243
+ ? { model, profile, effort }
244
+ : { model, profile, effort, unknown_agent: true };
245
+ output(result, raw, model);
246
+ }
247
+ function cmdResolveGranularity(cwd, phaseType, raw, override) {
248
+ if (!phaseType) {
249
+ error('phase-type required');
250
+ }
251
+ assertValidGranularityOverride(override, error);
252
+ const granularity = resolveGranularityInternal(cwd, phaseType, override);
253
+ const result = (VALID_PHASE_TYPES).has(phaseType)
254
+ ? { granularity, phase_type: phaseType }
255
+ : { granularity, phase_type: phaseType, unknown_phase_type: true };
256
+ output(result, raw, granularity);
257
+ }
258
+ /**
259
+ * #443 — Superset execution query: model + unified effort + fast_mode.
260
+ *
261
+ * Emits JSON:
262
+ * { model, profile, effort, effort_rendered, effort_param, effort_propagation,
263
+ * fast_mode, fast_mode_supported, [unknown_agent] }
264
+ *
265
+ * Flags: --effort <level>, --fast-mode <true|false>, --attempt <n>
266
+ */
267
+ function cmdResolveExecution(cwd, agentType, raw, opts) {
268
+ if (!agentType) {
269
+ error('agent-type required');
270
+ }
271
+ opts = opts || {};
272
+ const config = loadConfig(cwd);
273
+ const profile = config['model_profile'] || 'balanced';
274
+ const model = resolveModelInternal(cwd, agentType);
275
+ const effortOpts = {};
276
+ if (typeof opts.effortOverride === 'string')
277
+ effortOpts['override'] = opts.effortOverride;
278
+ const fastModeOpts = {};
279
+ if (typeof opts.fastModeOverride === 'boolean')
280
+ fastModeOpts['override'] = opts.fastModeOverride;
281
+ const effort = (opts.attempt !== undefined && opts.attempt !== null)
282
+ ? resolveEffortForTier(cwd, agentType, opts.attempt)
283
+ : resolveEffortInternal(cwd, agentType, effortOpts);
284
+ const fastMode = resolveFastModeInternal(cwd, agentType, fastModeOpts);
285
+ const runtime = config['runtime'] || 'claude';
286
+ const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, effort);
287
+ const fastModeSupported = model_catalog_cjs_1.RUNTIMES_WITH_FAST_MODE.has(runtime);
288
+ const agentModels = MODEL_PROFILES[agentType];
289
+ const result = {
290
+ model,
291
+ profile,
292
+ effort,
293
+ effort_rendered: rendered.value,
294
+ effort_param: rendered.param,
295
+ effort_propagation: rendered.channel,
296
+ fast_mode: fastMode,
297
+ fast_mode_supported: fastModeSupported,
298
+ };
299
+ if (!agentModels)
300
+ result['unknown_agent'] = true;
301
+ output(result, raw, effort);
302
+ }
303
+ /**
304
+ * #488 — Replace or inject the `effort:` value in YAML frontmatter.
305
+ * Unlike injectEffortFrontmatter (install.js), this overwrites an existing value.
306
+ */
307
+ function setEffortFrontmatter(content, effortValue) {
308
+ const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
309
+ const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
310
+ const match = fmRe.exec(content);
311
+ if (!match)
312
+ return content;
313
+ const fmBody = match[1];
314
+ if (/^effort:/m.test(fmBody)) {
315
+ return content.replace(/^(effort:)[ \t]*.*$/m, `$1 ${effortValue}`);
316
+ }
317
+ const openLen = 3 + eol.length;
318
+ const closingStart = match.index + openLen + fmBody.length;
319
+ return content.slice(0, closingStart) + `effort: ${effortValue}${eol}` + content.slice(closingStart);
320
+ }
321
+ /**
322
+ * #488 — Re-sync effort: frontmatter in all installed gsd-*.md agent files to
323
+ * match the current effort config, without requiring a full reinstall.
324
+ *
325
+ * Uses install-time resolution (readGsdEffectiveEffortConfig + resolveInstallTimeEffort
326
+ * from bin/install.js) rather than the runtime resolver (resolveEffortInternal), because
327
+ * the sync must mirror what install actually wrote: home defaults merged with project config.
328
+ * The runtime resolver (loadConfig) does not merge ~/.gsd/defaults.json when a project
329
+ * .planning/config.json exists, so it would silently ignore home-level effort changes.
330
+ */
331
+ function cmdEffortSync(cwd, raw, opts) {
332
+ opts = opts || {};
333
+ const dryRun = opts.dryRun !== false;
334
+ const config = loadConfig(cwd);
335
+ const runtime = opts.runtime || config['runtime'] || 'claude';
336
+ if (runtime !== 'claude') {
337
+ output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, reason: `runtime '${runtime}' does not use effort: frontmatter` }, raw, '');
338
+ return;
339
+ }
340
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
341
+ const { getGlobalConfigDir } = require('./runtime-homes.cjs');
342
+ // Use install-time resolvers: they merge ~/.gsd/defaults.json with project config,
343
+ // matching the exact logic used when agents were originally installed.
344
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
345
+ const { readGsdEffectiveEffortConfig, resolveInstallTimeEffort } = require('../../../bin/install.js');
346
+ const effortCfg = readGsdEffectiveEffortConfig(cwd);
347
+ const agentsDir = node_path_1.default.join(opts.configDir || getGlobalConfigDir(runtime), 'agents');
348
+ if (!node_fs_1.default.existsSync(agentsDir)) {
349
+ output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, agents_dir: agentsDir, reason: 'agents directory not found' }, raw, '');
350
+ return;
351
+ }
352
+ // Skip symlinks — only write regular files to avoid clobbering symlink targets.
353
+ const files = node_fs_1.default.readdirSync(agentsDir).filter(f => {
354
+ if (!f.startsWith('gsd-') || !f.endsWith('.md'))
355
+ return false;
356
+ try {
357
+ return node_fs_1.default.lstatSync(node_path_1.default.join(agentsDir, f)).isFile();
358
+ }
359
+ catch {
360
+ return false;
361
+ }
362
+ });
363
+ const changes = [];
364
+ let synced = 0;
365
+ let skipped = 0;
366
+ for (const file of files) {
367
+ const agentName = file.replace(/\.md$/, '');
368
+ const filePath = node_path_1.default.join(agentsDir, file);
369
+ const content = node_fs_1.default.readFileSync(filePath, 'utf8');
370
+ // Resolve using install-time logic: home defaults merged with project config.
371
+ const universalEffort = resolveInstallTimeEffort(effortCfg, agentName);
372
+ const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, universalEffort);
373
+ const newEffortValue = rendered.value;
374
+ const fmMatch = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(content);
375
+ if (!fmMatch) {
376
+ skipped++;
377
+ continue;
378
+ }
379
+ const effortMatch = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch[1]);
380
+ const currentEffort = effortMatch ? effortMatch[1] : null;
381
+ if (currentEffort === newEffortValue) {
382
+ skipped++;
383
+ continue;
384
+ }
385
+ changes.push({ agent: agentName, from: currentEffort, to: newEffortValue });
386
+ synced++;
387
+ if (!dryRun) {
388
+ node_fs_1.default.writeFileSync(filePath, setEffortFrontmatter(content, newEffortValue));
389
+ }
390
+ }
391
+ output({ synced, skipped, changes, dry_run: dryRun, agents_dir: agentsDir }, raw, synced > 0 ? 'changed' : 'ok');
392
+ }
393
+ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
394
+ if (!message && !amend) {
395
+ error('commit message required');
396
+ }
397
+ // Sanitize commit message: strip invisible chars and injection markers
398
+ // that could hijack agent context when commit messages are read back
399
+ let sanitizedMessage = message;
400
+ if (sanitizedMessage) {
401
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
402
+ const { sanitizeForPrompt } = require('./security.cjs');
403
+ sanitizedMessage = sanitizeForPrompt(sanitizedMessage);
404
+ }
405
+ const config = loadConfig(cwd);
406
+ // Check commit_docs config
407
+ // `skipped: true` is explicit so agent prompts can match on a first-class
408
+ // success signal rather than inferring "skip" from "committed is missing"
409
+ // and improvising raw git fallbacks (#3678).
410
+ if (!config['commit_docs']) {
411
+ const result = { committed: false, skipped: true, hash: null, reason: 'skipped_commit_docs_false' };
412
+ output(result, raw, 'skipped');
413
+ return;
414
+ }
415
+ // Check if .planning is gitignored
416
+ if (isGitIgnored(cwd, '.planning')) {
417
+ const result = { committed: false, skipped: true, hash: null, reason: 'skipped_gitignored' };
418
+ output(result, raw, 'skipped');
419
+ return;
420
+ }
421
+ // Ensure branching strategy branch exists before first commit (#1278).
422
+ // Pre-execution workflows (discuss, plan, research) commit artifacts but the branch
423
+ // was previously only created during execute-phase — too late.
424
+ const branchingStrategy = config['branching_strategy'];
425
+ if (branchingStrategy && branchingStrategy !== 'none') {
426
+ let branchName = null;
427
+ if (branchingStrategy === 'phase') {
428
+ // Determine which phase we're committing for from the file paths
429
+ const phaseMatch = (files || []).join(' ').match(/(\d+(?:\.\d+)*)-/);
430
+ if (phaseMatch) {
431
+ const phaseNum = phaseMatch[1];
432
+ const phaseInfo = findPhaseInternal(cwd, phaseNum);
433
+ if (phaseInfo) {
434
+ branchName = config['phase_branch_template']
435
+ .replace('{phase}', phaseInfo['phase_number'])
436
+ .replace('{slug}', phaseInfo['phase_slug'] || 'phase');
437
+ }
438
+ }
439
+ }
440
+ else if (branchingStrategy === 'milestone') {
441
+ const milestone = getMilestoneInfo(cwd);
442
+ if (milestone && milestone.version) {
443
+ branchName = config['milestone_branch_template']
444
+ .replace('{milestone}', milestone.version)
445
+ .replace('{slug}', generateSlugInternal(milestone.name) || 'milestone');
446
+ }
447
+ }
448
+ if (branchName) {
449
+ const currentBranch = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
450
+ if (currentBranch.exitCode === 0 && currentBranch.stdout.trim() !== branchName) {
451
+ // Create branch if it doesn't exist, or switch to it if it does
452
+ const create = (0, shell_command_projection_cjs_1.execGit)(['checkout', '-b', branchName], { cwd });
453
+ if (create.exitCode !== 0) {
454
+ (0, shell_command_projection_cjs_1.execGit)(['checkout', branchName], { cwd });
455
+ }
456
+ }
457
+ }
458
+ }
459
+ // Stage files
460
+ const explicitFiles = files && files.length > 0;
461
+ const filesToStage = explicitFiles ? files : ['.planning/'];
462
+ for (const file of filesToStage) {
463
+ const fullPath = node_path_1.default.join(cwd, file);
464
+ if (!node_fs_1.default.existsSync(fullPath)) {
465
+ if (explicitFiles) {
466
+ // Caller passed an explicit --files list: missing files are skipped.
467
+ // Staging a deletion here would silently remove tracked planning files
468
+ // (e.g. STATE.md, ROADMAP.md) when they are temporarily absent (#2014).
469
+ continue;
470
+ }
471
+ // Default mode (staging all of .planning/): stage the deletion so
472
+ // removed planning files are not left dangling in the index.
473
+ (0, shell_command_projection_cjs_1.execGit)(['rm', '--cached', '--ignore-unmatch', file], { cwd });
474
+ }
475
+ else {
476
+ (0, shell_command_projection_cjs_1.execGit)(['add', file], { cwd });
477
+ }
478
+ }
479
+ // Commit (--no-verify skips pre-commit hooks, used by parallel executor agents)
480
+ const commitArgs = amend ? ['commit', '--amend', '--no-edit'] : ['commit', '-m', sanitizedMessage];
481
+ if (noVerify)
482
+ commitArgs.push('--no-verify');
483
+ const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd });
484
+ if (commitResult.exitCode !== 0) {
485
+ if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
486
+ const result = { committed: false, hash: null, reason: 'nothing_to_commit' };
487
+ output(result, raw, 'nothing');
488
+ return;
489
+ }
490
+ const result = {
491
+ committed: false,
492
+ hash: null,
493
+ reason: 'commit_failed',
494
+ error: commitResult.stderr || commitResult.stdout,
495
+ };
496
+ output(result, raw, 'failed');
497
+ return;
498
+ }
499
+ // Get short hash
500
+ const hashResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--short', 'HEAD'], { cwd });
501
+ const hash = hashResult.exitCode === 0 ? hashResult.stdout : null;
502
+ const result = { committed: true, hash, reason: 'committed' };
503
+ output(result, raw, hash || 'committed');
504
+ }
505
+ /**
506
+ * Route a list of changed files to their sub-repo prefixes.
507
+ *
508
+ * Bucket sub-repos by their first path segment. Any file that matches a
509
+ * sub-repo prefix must share that sub-repo's first segment, so we only scan
510
+ * the (small) bucket for the file's first segment instead of all sub-repos
511
+ * — O(F + R) expected vs the prior O(F*R) find-in-loop. Candidates stay in
512
+ * sub-repo array order, preserving the original first-match semantics
513
+ * (incl. multi-segment sub-repos like "vendor/pkg", which resolve via the
514
+ * inner startsWith). (#311)
515
+ *
516
+ * @param files - changed file paths (relative to project root)
517
+ * @param subRepos - sub-repo path prefixes from config.sub_repos
518
+ */
519
+ function groupFilesBySubrepo(files, subRepos) {
520
+ const reposByFirstSeg = new Map();
521
+ for (const repo of subRepos) {
522
+ const firstSeg = String(repo).split('/')[0];
523
+ let bucket = reposByFirstSeg.get(firstSeg);
524
+ if (!bucket) {
525
+ bucket = [];
526
+ reposByFirstSeg.set(firstSeg, bucket);
527
+ }
528
+ bucket.push(repo);
529
+ }
530
+ const grouped = {};
531
+ const unmatched = [];
532
+ for (const file of files) {
533
+ const candidates = reposByFirstSeg.get(file.split('/')[0]);
534
+ const match = candidates ? candidates.find(repo => file.startsWith(repo + '/')) : undefined;
535
+ if (match) {
536
+ (grouped[match] ||= []).push(file);
537
+ }
538
+ else {
539
+ unmatched.push(file);
540
+ }
541
+ }
542
+ return { grouped, unmatched };
543
+ }
544
+ function cmdCommitToSubrepo(cwd, message, files, raw) {
545
+ if (!message) {
546
+ error('commit message required');
547
+ }
548
+ const config = loadConfig(cwd);
549
+ const subRepos = config['sub_repos'];
550
+ if (!subRepos || subRepos.length === 0) {
551
+ error('no sub_repos configured in .planning/config.json');
552
+ }
553
+ if (!files || files.length === 0) {
554
+ error('--files required for commit-to-subrepo');
555
+ }
556
+ // Group files by sub-repo prefix
557
+ const { grouped, unmatched } = groupFilesBySubrepo(files, subRepos);
558
+ if (unmatched.length > 0) {
559
+ process.stderr.write(`Warning: ${unmatched.length} file(s) did not match any sub-repo prefix: ${unmatched.join(', ')}\n`);
560
+ }
561
+ const repos = {};
562
+ for (const [repo, repoFiles] of Object.entries(grouped)) {
563
+ const repoCwd = node_path_1.default.join(cwd, repo);
564
+ // Stage files (strip sub-repo prefix for paths relative to that repo)
565
+ for (const file of repoFiles) {
566
+ const relativePath = file.slice(repo.length + 1);
567
+ (0, shell_command_projection_cjs_1.execGit)(['add', relativePath], { cwd: repoCwd });
568
+ }
569
+ // Commit
570
+ const commitResult = (0, shell_command_projection_cjs_1.execGit)(['commit', '-m', message], { cwd: repoCwd });
571
+ if (commitResult.exitCode !== 0) {
572
+ if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
573
+ repos[repo] = { committed: false, hash: null, files: repoFiles, reason: 'nothing_to_commit' };
574
+ continue;
575
+ }
576
+ repos[repo] = { committed: false, hash: null, files: repoFiles, reason: 'error', error: commitResult.stderr };
577
+ continue;
578
+ }
579
+ // Get hash
580
+ const hashResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--short', 'HEAD'], { cwd: repoCwd });
581
+ const hash = hashResult.exitCode === 0 ? hashResult.stdout : null;
582
+ repos[repo] = { committed: true, hash, files: repoFiles };
583
+ }
584
+ const result = {
585
+ committed: Object.values(repos).some(r => r.committed),
586
+ repos,
587
+ unmatched: unmatched.length > 0 ? unmatched : undefined,
588
+ };
589
+ output(result, raw, Object.entries(repos).map(([r, v]) => `${r}:${v.hash || 'skip'}`).join(' '));
590
+ }
591
+ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
592
+ if (!summaryPath) {
593
+ error('summary-path required for summary-extract');
594
+ }
595
+ const fullPath = node_path_1.default.join(cwd, summaryPath);
596
+ if (!node_fs_1.default.existsSync(fullPath)) {
597
+ output({ error: 'File not found', path: summaryPath }, raw, undefined);
598
+ return;
599
+ }
600
+ const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
601
+ const fm = extractFrontmatter(content);
602
+ // Parse key-decisions into structured format
603
+ const parseDecisions = (decisionsList) => {
604
+ if (!decisionsList || !Array.isArray(decisionsList))
605
+ return [];
606
+ return decisionsList.map(d => {
607
+ const colonIdx = d.indexOf(':');
608
+ if (colonIdx > 0) {
609
+ return {
610
+ summary: d.substring(0, colonIdx).trim(),
611
+ rationale: d.substring(colonIdx + 1).trim(),
612
+ };
613
+ }
614
+ return { summary: d, rationale: null };
615
+ });
616
+ };
617
+ const techStack = fm['tech-stack'];
618
+ // Build full result
619
+ const fullResult = {
620
+ path: summaryPath,
621
+ one_liner: fm['one-liner'] || extractOneLinerFromBody(content) || null,
622
+ key_files: fm['key-files'] || [],
623
+ tech_added: (techStack && techStack['added']) || [],
624
+ patterns: fm['patterns-established'] || [],
625
+ decisions: parseDecisions(fm['key-decisions']),
626
+ // Tolerate both key forms: the template/reader use kebab `requirements-completed`,
627
+ // but the tool's own JSON output and the milestone audit `--pick` use snake
628
+ // `requirements_completed`. Reading both prevents a snake-keyed SUMMARY (the form the
629
+ // tool emits) from being silently dropped to []. See #628.
630
+ requirements_completed: fm['requirements-completed'] ?? fm['requirements_completed'] ?? [],
631
+ };
632
+ // If fields specified, filter to only those fields
633
+ if (fields && fields.length > 0) {
634
+ const filtered = { path: summaryPath };
635
+ for (const field of fields) {
636
+ if (fullResult[field] !== undefined) {
637
+ filtered[field] = fullResult[field];
638
+ }
639
+ }
640
+ output(filtered, raw, undefined);
641
+ return;
642
+ }
643
+ output(fullResult, raw, undefined);
644
+ }
645
+ function _wsSleep(ms) {
646
+ return new Promise(resolve => setTimeout(resolve, ms));
647
+ }
648
+ function _wsParseRetryAfter(header) {
649
+ if (!header)
650
+ return null;
651
+ const trimmed = header.trim();
652
+ if (/^\d+$/.test(trimmed)) {
653
+ return Math.min(Math.max(parseInt(trimmed, 10) * 1000, 0), 60000);
654
+ }
655
+ const asDate = Date.parse(trimmed);
656
+ if (!isNaN(asDate)) {
657
+ return Math.min(Math.max(asDate - Date.now(), 0), 60000);
658
+ }
659
+ return null;
660
+ }
661
+ function _wsRetryDelayMs(attempt) {
662
+ const base = 250;
663
+ const cap = 2000;
664
+ const exp = Math.min(base * Math.pow(2, attempt), cap);
665
+ return exp + Math.floor(Math.random() * 100);
666
+ }
667
+ async function cmdWebsearch(query, options, raw) {
668
+ const apiKey = process.env['BRAVE_API_KEY'];
669
+ if (!apiKey) {
670
+ // No key = silent skip, agent falls back to built-in WebSearch
671
+ output({ available: false, reason: 'BRAVE_API_KEY not set' }, raw, '');
672
+ return;
673
+ }
674
+ if (!query) {
675
+ output({ available: false, error: 'Query required' }, raw, '');
676
+ return;
677
+ }
678
+ const params = new URLSearchParams({
679
+ q: query,
680
+ count: String(options.limit || 10),
681
+ country: 'us',
682
+ search_lang: 'en',
683
+ text_decorations: 'false'
684
+ });
685
+ if (options.freshness) {
686
+ params.set('freshness', options.freshness);
687
+ }
688
+ const rawTimeout = parseInt(process.env['GSD_WEBSEARCH_TIMEOUT_MS'], 10);
689
+ const timeoutMs = (Number.isInteger(rawTimeout) && rawTimeout > 0) ? rawTimeout : 10000;
690
+ const MAX_RETRIES = 2;
691
+ let attempt = 0;
692
+ while (true) {
693
+ try {
694
+ const ac = new AbortController();
695
+ const timer = setTimeout(() => ac.abort(new Error('timeout')), timeoutMs);
696
+ let response;
697
+ try {
698
+ response = await fetch(
699
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
700
+ `https://api.search.brave.com/res/v1/web/search?${params}`, {
701
+ headers: {
702
+ 'Accept': 'application/json',
703
+ 'X-Subscription-Token': apiKey
704
+ },
705
+ signal: ac.signal
706
+ });
707
+ }
708
+ finally {
709
+ clearTimeout(timer);
710
+ }
711
+ if (response.ok) {
712
+ const data = await response.json();
713
+ const results = (data.web?.results || []).map(r => ({
714
+ title: r.title,
715
+ url: r.url,
716
+ description: r.description,
717
+ age: r.age || null
718
+ }));
719
+ output({
720
+ available: true,
721
+ query,
722
+ count: results.length,
723
+ results
724
+ }, raw, results.map(r => `${r.title}\n${r.url}\n${r.description}`).join('\n\n'));
725
+ return;
726
+ }
727
+ const status = response.status;
728
+ const isRetryable = status === 429 || status >= 500;
729
+ if (!isRetryable) {
730
+ // Non-retryable 4xx — fail immediately, no attempts field
731
+ output({ available: false, error: `API error: ${status}` }, raw, '');
732
+ return;
733
+ }
734
+ // Retryable HTTP error
735
+ attempt++;
736
+ if (attempt > MAX_RETRIES) {
737
+ output({ available: false, error: `API error: ${status}`, attempts: attempt }, raw, '');
738
+ return;
739
+ }
740
+ let delay;
741
+ if (status === 429) {
742
+ const retryAfter = _wsParseRetryAfter(response.headers.get('retry-after'));
743
+ delay = retryAfter !== null ? retryAfter : _wsRetryDelayMs(attempt - 1);
744
+ }
745
+ else {
746
+ delay = _wsRetryDelayMs(attempt - 1);
747
+ }
748
+ await _wsSleep(delay);
749
+ }
750
+ catch (err) {
751
+ attempt++;
752
+ if (attempt > MAX_RETRIES) {
753
+ output({ available: false, error: err.message, attempts: attempt }, raw, '');
754
+ return;
755
+ }
756
+ await _wsSleep(_wsRetryDelayMs(attempt - 1));
757
+ }
758
+ }
759
+ }
760
+ function cmdProgressRender(cwd, format, raw) {
761
+ const phasesDir = planningPaths(cwd).phases;
762
+ const milestone = getMilestoneInfo(cwd);
763
+ const phases = [];
764
+ let totalPlans = 0;
765
+ let totalSummaries = 0;
766
+ try {
767
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
768
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => comparePhaseNum(a, b));
769
+ for (const dir of dirs) {
770
+ const dm = dir.match(/^(\d+(?:\.\d+)*)-?(.*)/);
771
+ const phaseNum = dm ? dm[1] : dir;
772
+ const phaseName = dm && dm[2] ? dm[2].replace(/-/g, ' ') : '';
773
+ const phaseFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, dir));
774
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
775
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
776
+ totalPlans += plans;
777
+ totalSummaries += summaries;
778
+ const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Pending');
779
+ phases.push({ number: phaseNum, name: phaseName, plans, summaries, status });
780
+ }
781
+ }
782
+ catch { /* intentionally empty */ }
783
+ const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
784
+ if (format === 'table') {
785
+ // Render markdown table
786
+ const barWidth = 10;
787
+ const filled = Math.round((percent / 100) * barWidth);
788
+ const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
789
+ let out = `# ${milestone.version} ${milestone.name}\n\n`;
790
+ out += `**Progress:** [${bar}] ${totalSummaries}/${totalPlans} plans (${percent}%)\n\n`;
791
+ out += `| Phase | Name | Plans | Status |\n`;
792
+ out += `|-------|------|-------|--------|\n`;
793
+ for (const p of phases) {
794
+ out += `| ${p.number} | ${p.name} | ${p.summaries}/${p.plans} | ${p.status} |\n`;
795
+ }
796
+ output({ rendered: out }, raw, out);
797
+ }
798
+ else if (format === 'bar') {
799
+ const barWidth = 20;
800
+ const filled = Math.round((percent / 100) * barWidth);
801
+ const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
802
+ const text = `[${bar}] ${totalSummaries}/${totalPlans} plans (${percent}%)`;
803
+ output({ bar: text, percent, completed: totalSummaries, total: totalPlans }, raw, text);
804
+ }
805
+ else {
806
+ // JSON format
807
+ output({
808
+ milestone_version: milestone.version,
809
+ milestone_name: milestone.name,
810
+ phases,
811
+ total_plans: totalPlans,
812
+ total_summaries: totalSummaries,
813
+ percent,
814
+ }, raw, undefined);
815
+ }
816
+ }
817
+ /**
818
+ * Match pending todos against a phase's goal/name/requirements.
819
+ * Returns todos with relevance scores based on keyword, area, and file overlap.
820
+ * Used by discuss-phase to surface relevant todos before scope-setting.
821
+ */
822
+ function cmdTodoMatchPhase(cwd, phase, raw) {
823
+ if (!phase) {
824
+ error('phase required for todo match-phase');
825
+ }
826
+ const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
827
+ const todos = [];
828
+ // Load pending todos
829
+ try {
830
+ const files = node_fs_1.default.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
831
+ for (const file of files) {
832
+ const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(pendingDir, file));
833
+ if (content === null)
834
+ continue;
835
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
836
+ const areaMatch = content.match(/^area:\s*(.+)$/m);
837
+ const filesMatch = content.match(/^files:\s*(.+)$/m);
838
+ const body = content.replace(/^(title|area|files|created|priority):.*$/gm, '').trim();
839
+ todos.push({
840
+ file,
841
+ title: titleMatch ? titleMatch[1].trim() : 'Untitled',
842
+ area: areaMatch ? areaMatch[1].trim() : 'general',
843
+ files: filesMatch ? filesMatch[1].trim().split(/[,\s]+/).filter(Boolean) : [],
844
+ body: body.slice(0, 200), // first 200 chars for context
845
+ });
846
+ }
847
+ }
848
+ catch { /* intentionally empty */ }
849
+ if (todos.length === 0) {
850
+ output({ phase, matches: [], todo_count: 0 }, raw, undefined);
851
+ return;
852
+ }
853
+ // Load phase goal/name from ROADMAP
854
+ const phaseInfo = getRoadmapPhaseInternal(cwd, phase);
855
+ const phaseName = phaseInfo ? (phaseInfo['phase_name'] || '') : '';
856
+ const phaseGoal = phaseInfo ? (phaseInfo['goal'] || '') : '';
857
+ const phaseSection = phaseInfo ? (phaseInfo['section'] || '') : '';
858
+ // Build keyword set from phase name + goal + section text
859
+ const phaseText = `${phaseName} ${phaseGoal} ${phaseSection}`.toLowerCase();
860
+ const stopWords = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'will', 'are', 'was', 'has', 'have', 'been', 'not', 'but', 'all', 'can', 'into', 'each', 'when', 'any', 'use', 'new']);
861
+ const phaseKeywords = new Set(phaseText.split(/[\s\-_/.,;:()\[\]{}|]+/)
862
+ .map(w => w.replace(/[^a-z0-9]/g, ''))
863
+ .filter(w => w.length > 2 && !stopWords.has(w)));
864
+ // Find phase directory to get expected file paths
865
+ const phaseInfoDisk = findPhaseInternal(cwd, phase);
866
+ const phasePlans = [];
867
+ if (phaseInfoDisk && phaseInfoDisk['found']) {
868
+ try {
869
+ const phaseDir = node_path_1.default.join(cwd, phaseInfoDisk['directory']);
870
+ const planFiles = node_fs_1.default.readdirSync(phaseDir).filter(f => f.endsWith('-PLAN.md'));
871
+ for (const pf of planFiles) {
872
+ const planContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(phaseDir, pf));
873
+ if (planContent === null)
874
+ continue;
875
+ const fmFiles = planContent.match(/files_modified:\s*\[([^\]]*)\]/);
876
+ if (fmFiles) {
877
+ phasePlans.push(...fmFiles[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean));
878
+ }
879
+ }
880
+ }
881
+ catch { /* intentionally empty */ }
882
+ }
883
+ // Score each todo for relevance
884
+ const matches = [];
885
+ for (const todo of todos) {
886
+ let score = 0;
887
+ const reasons = [];
888
+ // Keyword match: todo title/body terms in phase text
889
+ const todoWords = `${todo.title} ${todo.body}`.toLowerCase()
890
+ .split(/[\s\-_/.,;:()\[\]{}|]+/)
891
+ .map(w => w.replace(/[^a-z0-9]/g, ''))
892
+ .filter(w => w.length > 2 && !stopWords.has(w));
893
+ const matchedKeywords = todoWords.filter(w => phaseKeywords.has(w));
894
+ if (matchedKeywords.length > 0) {
895
+ score += Math.min(matchedKeywords.length * 0.2, 0.6);
896
+ reasons.push(`keywords: ${[...new Set(matchedKeywords)].slice(0, 5).join(', ')}`);
897
+ }
898
+ // Area match: todo area appears in phase text
899
+ if (todo.area !== 'general' && phaseText.includes(todo.area.toLowerCase())) {
900
+ score += 0.3;
901
+ reasons.push(`area: ${todo.area}`);
902
+ }
903
+ // File match: todo files overlap with phase plan files
904
+ if (todo.files.length > 0 && phasePlans.length > 0) {
905
+ const fileOverlap = todo.files.filter(f => phasePlans.some(pf => pf.includes(f) || f.includes(pf)));
906
+ if (fileOverlap.length > 0) {
907
+ score += 0.4;
908
+ reasons.push(`files: ${fileOverlap.slice(0, 3).join(', ')}`);
909
+ }
910
+ }
911
+ if (score > 0) {
912
+ matches.push({
913
+ file: todo.file,
914
+ title: todo.title,
915
+ area: todo.area,
916
+ score: Math.round(score * 100) / 100,
917
+ reasons,
918
+ });
919
+ }
920
+ }
921
+ // Sort by score descending
922
+ matches.sort((a, b) => b.score - a.score);
923
+ output({ phase, matches, todo_count: todos.length }, raw, undefined);
924
+ }
925
+ function cmdTodoComplete(cwd, filename, raw) {
926
+ if (!filename) {
927
+ error('filename required for todo complete');
928
+ }
929
+ const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
930
+ const completedDir = node_path_1.default.join(planningDir(cwd), 'todos', 'completed');
931
+ const sourcePath = node_path_1.default.join(pendingDir, filename);
932
+ if (!node_fs_1.default.existsSync(sourcePath)) {
933
+ error(`Todo not found: ${filename}`);
934
+ }
935
+ // Ensure completed directory exists
936
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(completedDir);
937
+ // Read, add completion timestamp, move
938
+ let content = node_fs_1.default.readFileSync(sourcePath, 'utf-8');
939
+ const today = new Date().toISOString().split('T')[0];
940
+ content = `completed: ${today}\n` + content;
941
+ (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(completedDir, filename), content);
942
+ node_fs_1.default.unlinkSync(sourcePath);
943
+ output({ completed: true, file: filename, date: today }, raw, 'completed');
944
+ }
945
+ function cmdScaffold(cwd, type, options, raw) {
946
+ const { phase, name } = options;
947
+ const padded = phase ? normalizePhaseName(phase) : '00';
948
+ const today = new Date().toISOString().split('T')[0];
949
+ // Find phase directory
950
+ const phaseInfo = phase ? findPhaseInternal(cwd, phase) : null;
951
+ const phaseDir = phaseInfo ? node_path_1.default.join(cwd, phaseInfo['directory']) : null;
952
+ if (phase && !phaseDir && type !== 'phase-dir') {
953
+ error(`Phase ${phase} directory not found`);
954
+ }
955
+ let filePath, content;
956
+ switch (type) {
957
+ case 'context': {
958
+ filePath = node_path_1.default.join(phaseDir, `${padded}-CONTEXT.md`);
959
+ content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.['phase_name'] || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.['phase_name'] || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during ${String((0, runtime_slash_cjs_1.formatGsdSlash)('discuss-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd)))} ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
960
+ break;
961
+ }
962
+ case 'uat': {
963
+ filePath = node_path_1.default.join(phaseDir, `${padded}-UAT.md`);
964
+ content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.['phase_name'] || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.['phase_name'] || 'Unnamed'} — User Acceptance Testing\n\n## Test Results\n\n| # | Test | Status | Notes |\n|---|------|--------|-------|\n\n## Summary\n\n_Pending UAT_\n`;
965
+ break;
966
+ }
967
+ case 'verification': {
968
+ filePath = node_path_1.default.join(phaseDir, `${padded}-VERIFICATION.md`);
969
+ content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.['phase_name'] || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.['phase_name'] || 'Unnamed'} — Verification\n\n## Goal-Backward Verification\n\n**Phase Goal:** [From ROADMAP.md]\n\n## Checks\n\n| # | Requirement | Status | Evidence |\n|---|------------|--------|----------|\n\n## Result\n\n_Pending verification_\n`;
970
+ break;
971
+ }
972
+ case 'phase-dir': {
973
+ if (!phase || !name) {
974
+ error('phase and name required for phase-dir scaffold');
975
+ }
976
+ const slug = generateSlugInternal(name);
977
+ // #3287: apply project_code prefix to stay consistent with phase.add/phase.insert
978
+ const scaffoldConfig = loadConfig(cwd);
979
+ const scaffoldProjectCode = scaffoldConfig['project_code'] || '';
980
+ const scaffoldPrefix = scaffoldProjectCode ? `${scaffoldProjectCode}-` : '';
981
+ const dirName = `${scaffoldPrefix}${padded}-${slug}`;
982
+ const phasesParent = planningPaths(cwd).phases;
983
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(phasesParent);
984
+ const dirPath = node_path_1.default.join(phasesParent, dirName);
985
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
986
+ output({ created: true, directory: toPosixPath(node_path_1.default.relative(cwd, dirPath)), path: dirPath }, raw, dirPath);
987
+ return;
988
+ }
989
+ default:
990
+ error(`Unknown scaffold type: ${type}. Available: context, uat, verification, phase-dir`);
991
+ // unreachable — error() calls process.exit
992
+ return;
993
+ }
994
+ if (node_fs_1.default.existsSync(filePath)) {
995
+ output({ created: false, reason: 'already_exists', path: filePath }, raw, 'exists');
996
+ return;
997
+ }
998
+ (0, shell_command_projection_cjs_1.platformWriteSync)(filePath, content);
999
+ const relPath = toPosixPath(node_path_1.default.relative(cwd, filePath));
1000
+ output({ created: true, path: relPath }, raw, relPath);
1001
+ }
1002
+ function cmdStats(cwd, format, raw) {
1003
+ const phasesDir = planningPaths(cwd).phases;
1004
+ const roadmapPath = planningPaths(cwd).roadmap;
1005
+ const reqPath = planningPaths(cwd).requirements;
1006
+ const statePath = planningPaths(cwd).state;
1007
+ const milestone = getMilestoneInfo(cwd);
1008
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
1009
+ // Phase & plan stats (reuse progress pattern)
1010
+ const phasesByNumber = new Map();
1011
+ let totalPlans = 0;
1012
+ let totalSummaries = 0;
1013
+ try {
1014
+ const roadmapRaw = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
1015
+ if (roadmapRaw === null)
1016
+ throw new Error('roadmap missing');
1017
+ const roadmapContent = extractCurrentMilestone(roadmapRaw, cwd);
1018
+ // Matches both plain numeric (Phase 1:) and milestone-prefixed (Phase 2-01:) headings.
1019
+ // Also tolerates optional [bracket-token] scope prefix on phase headings.
1020
+ const headingPattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:\s*([^\n]+)/gi;
1021
+ let match;
1022
+ while ((match = headingPattern.exec(roadmapContent)) !== null) {
1023
+ const key = normalizePhaseName(match[1]);
1024
+ phasesByNumber.set(key, {
1025
+ number: key,
1026
+ name: match[2].replace(/\(INSERTED\)/i, '').trim(),
1027
+ plans: 0,
1028
+ summaries: 0,
1029
+ status: 'Not Started',
1030
+ });
1031
+ }
1032
+ }
1033
+ catch { /* intentionally empty */ }
1034
+ try {
1035
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
1036
+ const dirs = entries
1037
+ .filter(e => e.isDirectory())
1038
+ .map(e => e.name)
1039
+ .filter(isDirInMilestone)
1040
+ .sort((a, b) => comparePhaseNum(a, b));
1041
+ for (const dir of dirs) {
1042
+ // Use extractPhaseToken to correctly parse M-NN-style and code-prefixed dir names.
1043
+ const phaseToken = extractPhaseToken(dir);
1044
+ const phaseNum = phaseToken || dir;
1045
+ // phaseName is everything after the token (strip leading '-')
1046
+ const afterToken = dir.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
1047
+ const phaseName = afterToken ? afterToken.replace(/-/g, ' ') : '';
1048
+ const phaseFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, dir));
1049
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
1050
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
1051
+ totalPlans += plans;
1052
+ totalSummaries += summaries;
1053
+ const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Not Started');
1054
+ const normalizedNum = normalizePhaseName(phaseNum);
1055
+ const existing = phasesByNumber.get(normalizedNum);
1056
+ phasesByNumber.set(normalizedNum, {
1057
+ number: normalizedNum,
1058
+ name: existing?.name || phaseName,
1059
+ plans: (existing?.plans || 0) + plans,
1060
+ summaries: (existing?.summaries || 0) + summaries,
1061
+ status,
1062
+ });
1063
+ }
1064
+ }
1065
+ catch { /* intentionally empty */ }
1066
+ const phases = [...phasesByNumber.values()].sort((a, b) => comparePhaseNum(a.number, b.number));
1067
+ const completedPhases = phases.filter(p => p.status === 'Complete').length;
1068
+ const planPercent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
1069
+ const percent = phases.length > 0 ? Math.min(100, Math.round((completedPhases / phases.length) * 100)) : 0;
1070
+ // Requirements stats
1071
+ let requirementsTotal = 0;
1072
+ let requirementsComplete = 0;
1073
+ const reqContent = (0, shell_command_projection_cjs_1.platformReadSync)(reqPath);
1074
+ if (reqContent !== null) {
1075
+ const checked = reqContent.match(/^- \[x\] \*\*/gm);
1076
+ const unchecked = reqContent.match(/^- \[ \] \*\*/gm);
1077
+ requirementsComplete = checked ? checked.length : 0;
1078
+ requirementsTotal = requirementsComplete + (unchecked ? unchecked.length : 0);
1079
+ }
1080
+ // Last activity from STATE.md
1081
+ let lastActivity = null;
1082
+ const stateContent = (0, shell_command_projection_cjs_1.platformReadSync)(statePath);
1083
+ if (stateContent !== null) {
1084
+ const activityMatch = stateContent.match(/^last_activity:\s*(.+)$/im)
1085
+ || stateContent.match(/\*\*Last Activity:\*\*\s*(.+)/i)
1086
+ || stateContent.match(/^Last Activity:\s*(.+)$/im)
1087
+ || stateContent.match(/^Last activity:\s*(.+)$/im);
1088
+ if (activityMatch)
1089
+ lastActivity = activityMatch[1].trim();
1090
+ }
1091
+ // Git stats
1092
+ let gitCommits = 0;
1093
+ let gitFirstCommitDate = null;
1094
+ const commitCount = (0, shell_command_projection_cjs_1.execGit)(['rev-list', '--count', 'HEAD'], { cwd });
1095
+ if (commitCount.exitCode === 0) {
1096
+ gitCommits = parseInt(commitCount.stdout, 10) || 0;
1097
+ }
1098
+ const rootHash = (0, shell_command_projection_cjs_1.execGit)(['rev-list', '--max-parents=0', 'HEAD'], { cwd });
1099
+ if (rootHash.exitCode === 0 && rootHash.stdout) {
1100
+ const firstCommit = rootHash.stdout.split('\n')[0].trim();
1101
+ const firstDate = (0, shell_command_projection_cjs_1.execGit)(['show', '-s', '--format=%as', firstCommit], { cwd });
1102
+ if (firstDate.exitCode === 0) {
1103
+ gitFirstCommitDate = firstDate.stdout || null;
1104
+ }
1105
+ }
1106
+ const result = {
1107
+ milestone_version: milestone.version,
1108
+ milestone_name: milestone.name,
1109
+ phases,
1110
+ phases_completed: completedPhases,
1111
+ phases_total: phases.length,
1112
+ total_plans: totalPlans,
1113
+ total_summaries: totalSummaries,
1114
+ percent,
1115
+ plan_percent: planPercent,
1116
+ requirements_total: requirementsTotal,
1117
+ requirements_complete: requirementsComplete,
1118
+ git_commits: gitCommits,
1119
+ git_first_commit_date: gitFirstCommitDate,
1120
+ last_activity: lastActivity,
1121
+ };
1122
+ if (format === 'table') {
1123
+ const barWidth = 10;
1124
+ const filled = Math.round((percent / 100) * barWidth);
1125
+ const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
1126
+ let out = `# ${milestone.version} ${milestone.name} — Statistics\n\n`;
1127
+ out += `**Progress:** [${bar}] ${completedPhases}/${phases.length} phases (${percent}%)\n`;
1128
+ if (totalPlans > 0) {
1129
+ out += `**Plans:** ${totalSummaries}/${totalPlans} complete (${planPercent}%)\n`;
1130
+ }
1131
+ out += `**Phases:** ${completedPhases}/${phases.length} complete\n`;
1132
+ if (requirementsTotal > 0) {
1133
+ out += `**Requirements:** ${requirementsComplete}/${requirementsTotal} complete\n`;
1134
+ }
1135
+ out += '\n';
1136
+ out += `| Phase | Name | Plans | Completed | Status |\n`;
1137
+ out += `|-------|------|-------|-----------|--------|\n`;
1138
+ for (const p of phases) {
1139
+ out += `| ${p.number} | ${p.name} | ${p.plans} | ${p.summaries} | ${p.status} |\n`;
1140
+ }
1141
+ if (gitCommits > 0) {
1142
+ out += `\n**Git:** ${gitCommits} commits`;
1143
+ if (gitFirstCommitDate)
1144
+ out += ` (since ${gitFirstCommitDate})`;
1145
+ out += '\n';
1146
+ }
1147
+ if (lastActivity)
1148
+ out += `**Last activity:** ${lastActivity}\n`;
1149
+ output({ rendered: out }, raw, out);
1150
+ }
1151
+ else {
1152
+ output(result, raw, undefined);
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Check whether a commit should be allowed based on commit_docs config.
1157
+ * When commit_docs is false, rejects commits that stage .planning/ files.
1158
+ * Intended for use as a pre-commit hook guard.
1159
+ */
1160
+ function cmdCheckCommit(cwd, raw) {
1161
+ const config = loadConfig(cwd);
1162
+ // If commit_docs is true (or not set), allow all commits
1163
+ if (config['commit_docs'] !== false) {
1164
+ output({ allowed: true, reason: 'commit_docs_enabled' }, raw, 'allowed');
1165
+ return;
1166
+ }
1167
+ // commit_docs is false — check if any .planning/ files are staged
1168
+ const stagedResult = (0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only'], { cwd });
1169
+ if (stagedResult.exitCode === 0) {
1170
+ const planningFiles = stagedResult.stdout.split('\n').filter(f => f.startsWith('.planning/') || f.startsWith('.planning\\'));
1171
+ if (planningFiles.length > 0) {
1172
+ error(`commit_docs is false but ${planningFiles.length} .planning/ file(s) are staged:\n` +
1173
+ planningFiles.map(f => ` ${f}`).join('\n') +
1174
+ `\n\nTo unstage: git reset HEAD ${planningFiles.join(' ')}`);
1175
+ }
1176
+ }
1177
+ // exitCode !== 0 → no staged files or not a git repo — allow
1178
+ output({ allowed: true, reason: 'no_planning_files_staged' }, raw, 'allowed');
1179
+ }
1180
+ module.exports = {
1181
+ groupFilesBySubrepo,
1182
+ determinePhaseStatus,
1183
+ cmdGenerateSlug,
1184
+ cmdCurrentTimestamp,
1185
+ cmdListTodos,
1186
+ cmdVerifyPathExists,
1187
+ cmdHistoryDigest,
1188
+ cmdResolveModel,
1189
+ cmdResolveGranularity,
1190
+ cmdResolveExecution,
1191
+ cmdEffortSync,
1192
+ cmdCommit,
1193
+ cmdCommitToSubrepo,
1194
+ cmdSummaryExtract,
1195
+ cmdWebsearch,
1196
+ cmdProgressRender,
1197
+ cmdTodoComplete,
1198
+ cmdTodoMatchPhase,
1199
+ cmdScaffold,
1200
+ cmdStats,
1201
+ cmdCheckCommit,
1202
+ _wsParseRetryAfter,
1203
+ };