@bhargavvc/sdd-cc 1.30.1 → 1.42.3

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 (1392) hide show
  1. package/README.ja-JP.md +165 -129
  2. package/README.ko-KR.md +161 -123
  3. package/README.md +103 -679
  4. package/README.pt-BR.md +92 -52
  5. package/README.zh-CN.md +145 -103
  6. package/agents/sdd-advisor-researcher.md +23 -0
  7. package/agents/sdd-ai-researcher.md +133 -0
  8. package/agents/sdd-code-fixer.md +668 -0
  9. package/agents/sdd-code-reviewer.md +387 -0
  10. package/agents/sdd-codebase-mapper.md +86 -3
  11. package/agents/sdd-debug-session-manager.md +314 -0
  12. package/agents/sdd-debugger.md +157 -78
  13. package/agents/sdd-doc-classifier.md +168 -0
  14. package/agents/sdd-doc-synthesizer.md +204 -0
  15. package/agents/sdd-doc-verifier.md +217 -0
  16. package/agents/sdd-doc-writer.md +615 -0
  17. package/agents/sdd-domain-researcher.md +153 -0
  18. package/agents/sdd-eval-auditor.md +191 -0
  19. package/agents/sdd-eval-planner.md +154 -0
  20. package/agents/sdd-executor.md +283 -40
  21. package/agents/sdd-framework-selector.md +160 -0
  22. package/agents/sdd-integration-checker.md +30 -3
  23. package/agents/sdd-intel-updater.md +342 -0
  24. package/agents/sdd-nyquist-auditor.md +31 -4
  25. package/agents/sdd-pattern-mapper.md +335 -0
  26. package/agents/sdd-phase-researcher.md +254 -24
  27. package/agents/sdd-plan-checker.md +223 -18
  28. package/agents/sdd-planner.md +286 -362
  29. package/agents/sdd-project-researcher.md +28 -5
  30. package/agents/sdd-research-synthesizer.md +4 -4
  31. package/agents/sdd-roadmapper.md +14 -5
  32. package/agents/sdd-security-auditor.md +155 -0
  33. package/agents/sdd-ui-auditor.md +60 -4
  34. package/agents/sdd-ui-checker.md +11 -2
  35. package/agents/sdd-ui-researcher.md +27 -4
  36. package/agents/sdd-user-profiler.md +2 -2
  37. package/agents/sdd-verifier.md +258 -41
  38. package/bin/install.js +6862 -618
  39. package/bin/sdd-sdk.js +37 -0
  40. package/commands/sdd/add-tests.md +3 -2
  41. package/commands/sdd/ai-integration-phase.md +37 -0
  42. package/commands/sdd/audit-fix.md +34 -0
  43. package/commands/sdd/audit-milestone.md +3 -2
  44. package/commands/sdd/autonomous.md +10 -5
  45. package/commands/sdd/capture.md +62 -0
  46. package/commands/sdd/cleanup.md +7 -1
  47. package/commands/sdd/code-review.md +59 -0
  48. package/commands/sdd/complete-milestone.md +11 -4
  49. package/commands/sdd/config.md +58 -0
  50. package/commands/sdd/debug.md +23 -144
  51. package/commands/sdd/discuss-phase.md +22 -10
  52. package/commands/sdd/docs-update.md +49 -0
  53. package/commands/sdd/eval-review.md +33 -0
  54. package/commands/sdd/execute-phase.md +9 -4
  55. package/commands/sdd/explore.md +27 -0
  56. package/commands/sdd/extract-learnings.md +23 -0
  57. package/commands/sdd/fast.md +2 -1
  58. package/commands/sdd/forensics.md +3 -2
  59. package/commands/sdd/graphify.md +199 -0
  60. package/commands/sdd/health.md +12 -3
  61. package/commands/sdd/help.md +3 -1
  62. package/commands/sdd/import.md +41 -0
  63. package/commands/sdd/inbox.md +39 -0
  64. package/commands/sdd/ingest-docs.md +42 -0
  65. package/commands/sdd/manager.md +9 -3
  66. package/commands/sdd/map-codebase.md +15 -3
  67. package/commands/sdd/milestone-summary.md +1 -1
  68. package/commands/sdd/mvp-phase.md +45 -0
  69. package/commands/sdd/new-milestone.md +3 -2
  70. package/commands/sdd/new-project.md +7 -2
  71. package/commands/sdd/ns-context.md +23 -0
  72. package/commands/sdd/ns-ideate.md +24 -0
  73. package/commands/sdd/ns-manage.md +29 -0
  74. package/commands/sdd/ns-project.md +22 -0
  75. package/commands/sdd/ns-review.md +26 -0
  76. package/commands/sdd/ns-workflow.md +28 -0
  77. package/commands/sdd/pause-work.md +6 -1
  78. package/commands/sdd/phase.md +56 -0
  79. package/commands/sdd/plan-phase.md +19 -4
  80. package/commands/sdd/plan-review-convergence.md +59 -0
  81. package/commands/sdd/pr-branch.md +2 -1
  82. package/commands/sdd/profile-user.md +2 -2
  83. package/commands/sdd/progress.md +27 -5
  84. package/commands/sdd/quick.md +132 -5
  85. package/commands/sdd/resume-work.md +2 -12
  86. package/commands/sdd/review-backlog.md +4 -2
  87. package/commands/sdd/review.md +7 -3
  88. package/commands/sdd/secure-phase.md +36 -0
  89. package/commands/sdd/settings.md +2 -9
  90. package/commands/sdd/ship.md +1 -0
  91. package/commands/sdd/sketch.md +60 -0
  92. package/commands/sdd/spec-phase.md +63 -0
  93. package/commands/sdd/spike.md +57 -0
  94. package/commands/sdd/stats.md +2 -1
  95. package/commands/sdd/surface.md +129 -0
  96. package/commands/sdd/thread.md +8 -111
  97. package/commands/sdd/ui-phase.md +3 -2
  98. package/commands/sdd/ui-review.md +3 -2
  99. package/commands/sdd/ultraplan-phase.md +34 -0
  100. package/commands/sdd/undo.md +35 -0
  101. package/commands/sdd/update.md +21 -10
  102. package/commands/sdd/validate-phase.md +3 -2
  103. package/commands/sdd/verify-work.md +4 -3
  104. package/commands/sdd/workspace.md +52 -0
  105. package/commands/sdd/workstreams.md +15 -8
  106. package/hooks/dist/sdd-check-update-worker.js +116 -0
  107. package/hooks/dist/sdd-check-update.js +19 -69
  108. package/hooks/dist/sdd-context-monitor.js +43 -7
  109. package/hooks/dist/sdd-phase-boundary.sh +47 -0
  110. package/hooks/dist/sdd-prompt-guard.js +1 -0
  111. package/hooks/dist/sdd-read-guard.js +101 -0
  112. package/hooks/dist/sdd-read-injection-scanner.js +152 -0
  113. package/hooks/dist/sdd-session-state.sh +59 -0
  114. package/hooks/dist/sdd-statusline.js +439 -21
  115. package/hooks/dist/sdd-update-banner.js +134 -0
  116. package/hooks/dist/sdd-validate-commit.sh +57 -0
  117. package/hooks/dist/sdd-workflow-guard.js +2 -2
  118. package/hooks/lib/git-cmd.js +150 -0
  119. package/hooks/sdd-check-update-worker.js +116 -0
  120. package/hooks/sdd-check-update.js +64 -0
  121. package/hooks/sdd-context-monitor.js +192 -0
  122. package/hooks/sdd-phase-boundary.sh +47 -0
  123. package/hooks/sdd-prompt-guard.js +97 -0
  124. package/hooks/sdd-read-guard.js +101 -0
  125. package/hooks/sdd-read-injection-scanner.js +152 -0
  126. package/hooks/sdd-session-state.sh +59 -0
  127. package/hooks/sdd-statusline.js +537 -0
  128. package/hooks/sdd-update-banner.js +134 -0
  129. package/hooks/sdd-validate-commit.sh +57 -0
  130. package/hooks/sdd-workflow-guard.js +94 -0
  131. package/package.json +34 -9
  132. package/scripts/audit-workflow-script-paths.cjs +73 -0
  133. package/scripts/build-hooks.js +114 -9
  134. package/scripts/changeset/cli.cjs +269 -0
  135. package/scripts/changeset/github-release-notes.cjs +198 -0
  136. package/scripts/changeset/lint.cjs +110 -0
  137. package/scripts/changeset/new.cjs +137 -0
  138. package/scripts/changeset/parse.cjs +60 -0
  139. package/scripts/changeset/render.cjs +34 -0
  140. package/scripts/changeset/serialize.cjs +74 -0
  141. package/scripts/command-contract-helpers.cjs +61 -0
  142. package/scripts/diff-touches-shipped-paths.cjs +147 -0
  143. package/scripts/fix-slash-commands.cjs +106 -0
  144. package/scripts/gen-inventory-manifest.cjs +109 -0
  145. package/scripts/lint-command-contract.cjs +108 -0
  146. package/scripts/lint-descriptions.cjs +83 -0
  147. package/scripts/lint-no-source-grep-extras.cjs +81 -0
  148. package/scripts/lint-no-source-grep.cjs +174 -0
  149. package/scripts/lint-shell-command-projection-drift.cjs +57 -0
  150. package/scripts/lint-skill-deps.cjs +180 -0
  151. package/scripts/pr-template-policy.cjs +169 -0
  152. package/scripts/prompt-injection-scan.sh +3 -0
  153. package/scripts/rebrand-gsd-to-sdd.sh +222 -220
  154. package/scripts/run-tests.cjs +5 -1
  155. package/scripts/strip-prose-atrefs.cjs +106 -0
  156. package/scripts/verify-tarball-sdk-dist.sh +69 -0
  157. package/sdd/bin/check-latest-version.cjs +104 -0
  158. package/sdd/bin/lib/active-workstream-store.cjs +85 -0
  159. package/sdd/bin/lib/adr-parser.cjs +394 -0
  160. package/sdd/bin/lib/artifacts.cjs +53 -0
  161. package/sdd/bin/lib/audit.cjs +755 -0
  162. package/sdd/bin/lib/cjs-command-router-adapter.cjs +39 -0
  163. package/sdd/bin/lib/clusters.cjs +135 -0
  164. package/sdd/bin/lib/command-aliases.generated.cjs +838 -0
  165. package/sdd/bin/lib/commands.cjs +179 -107
  166. package/sdd/bin/lib/config-schema.cjs +135 -0
  167. package/sdd/bin/lib/config.cjs +313 -86
  168. package/sdd/bin/lib/context-utilization.cjs +47 -0
  169. package/sdd/bin/lib/core.cjs +1146 -391
  170. package/sdd/bin/lib/decisions.cjs +48 -0
  171. package/sdd/bin/lib/docs.cjs +270 -0
  172. package/sdd/bin/lib/drift.cjs +379 -0
  173. package/sdd/bin/lib/fallow-runner.cjs +109 -0
  174. package/sdd/bin/lib/frontmatter.cjs +389 -336
  175. package/sdd/bin/lib/gap-checker.cjs +197 -0
  176. package/sdd/bin/lib/graphify.cjs +577 -0
  177. package/sdd/bin/lib/init-command-router.cjs +70 -0
  178. package/sdd/bin/lib/init.cjs +692 -97
  179. package/sdd/bin/lib/install-profiles.cjs +572 -0
  180. package/sdd/bin/lib/installer-migration-authoring.cjs +117 -0
  181. package/sdd/bin/lib/installer-migration-report.cjs +328 -0
  182. package/sdd/bin/lib/installer-migrations/000-first-time-baseline.cjs +220 -0
  183. package/sdd/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +41 -0
  184. package/sdd/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +80 -0
  185. package/sdd/bin/lib/installer-migrations.cjs +703 -0
  186. package/sdd/bin/lib/intel.cjs +643 -0
  187. package/sdd/bin/lib/learnings.cjs +379 -0
  188. package/sdd/bin/lib/milestone.cjs +313 -252
  189. package/sdd/bin/lib/model-catalog.cjs +136 -0
  190. package/sdd/bin/lib/model-profiles.cjs +25 -68
  191. package/sdd/bin/lib/phase-command-router.cjs +96 -0
  192. package/sdd/bin/lib/phase.cjs +868 -335
  193. package/sdd/bin/lib/phases-command-router.cjs +39 -0
  194. package/sdd/bin/lib/plan-scan.cjs +138 -0
  195. package/sdd/bin/lib/planning-workspace.cjs +361 -0
  196. package/sdd/bin/lib/profile-output.cjs +197 -35
  197. package/sdd/bin/lib/profile-pipeline.cjs +1 -1
  198. package/sdd/bin/lib/review-reviewer-selection.cjs +125 -0
  199. package/sdd/bin/lib/roadmap-command-router.cjs +23 -0
  200. package/sdd/bin/lib/roadmap.cjs +416 -124
  201. package/sdd/bin/lib/runtime-homes.cjs +178 -0
  202. package/sdd/bin/lib/schema-detect.cjs +238 -0
  203. package/sdd/bin/lib/sdd2-import.cjs +511 -0
  204. package/sdd/bin/lib/secrets.cjs +33 -0
  205. package/sdd/bin/lib/security.cjs +131 -9
  206. package/sdd/bin/lib/shell-command-projection.cjs +548 -0
  207. package/sdd/bin/lib/state-command-router.cjs +100 -0
  208. package/sdd/bin/lib/state-document.cjs +12 -0
  209. package/sdd/bin/lib/state-document.generated.cjs +127 -0
  210. package/sdd/bin/lib/state.cjs +1253 -367
  211. package/sdd/bin/lib/surface.cjs +398 -0
  212. package/sdd/bin/lib/template.cjs +11 -5
  213. package/sdd/bin/lib/uat.cjs +9 -2
  214. package/sdd/bin/lib/validate-command-router.cjs +55 -0
  215. package/sdd/bin/lib/verify-command-router.cjs +34 -0
  216. package/sdd/bin/lib/verify.cjs +648 -140
  217. package/sdd/bin/lib/workstream-inventory.cjs +159 -0
  218. package/sdd/bin/lib/workstream-name-policy.cjs +33 -0
  219. package/sdd/bin/lib/workstream.cjs +78 -196
  220. package/sdd/bin/lib/worktree-safety.cjs +563 -0
  221. package/sdd/bin/sdd-tools.cjs +528 -222
  222. package/sdd/bin/verify-reapply-patches.cjs +247 -0
  223. package/sdd/contexts/dev.md +21 -0
  224. package/sdd/contexts/research.md +22 -0
  225. package/sdd/contexts/review.md +23 -0
  226. package/sdd/references/agent-contracts.md +79 -0
  227. package/sdd/references/ai-evals.md +156 -0
  228. package/sdd/references/ai-frameworks.md +186 -0
  229. package/sdd/references/artifact-types.md +131 -0
  230. package/sdd/references/autonomous-smart-discuss.md +277 -0
  231. package/sdd/references/checkpoints.md +36 -0
  232. package/sdd/references/common-bug-patterns.md +114 -0
  233. package/sdd/references/context-budget.md +85 -0
  234. package/sdd/references/continuation-format.md +30 -26
  235. package/sdd/references/debugger-philosophy.md +76 -0
  236. package/sdd/references/decimal-phase-calculation.md +5 -5
  237. package/sdd/references/doc-conflict-engine.md +91 -0
  238. package/sdd/references/domain-probes.md +125 -0
  239. package/sdd/references/execute-mvp-tdd.md +81 -0
  240. package/sdd/references/executor-examples.md +110 -0
  241. package/sdd/references/few-shot-examples/plan-checker.md +73 -0
  242. package/sdd/references/few-shot-examples/verifier.md +109 -0
  243. package/sdd/references/gate-prompts.md +100 -0
  244. package/sdd/references/gates.md +70 -0
  245. package/sdd/references/git-integration.md +9 -6
  246. package/sdd/references/git-planning-commit.md +6 -4
  247. package/sdd/references/ios-scaffold.md +123 -0
  248. package/sdd/references/mandatory-initial-read.md +2 -0
  249. package/sdd/references/model-profile-resolution.md +2 -0
  250. package/sdd/references/model-profiles.md +128 -22
  251. package/sdd/references/mvp-concepts.md +49 -0
  252. package/sdd/references/phase-argument-parsing.md +3 -3
  253. package/sdd/references/planner-antipatterns.md +89 -0
  254. package/sdd/references/planner-chunked.md +49 -0
  255. package/sdd/references/planner-gap-closure.md +62 -0
  256. package/sdd/references/planner-human-verify-mode.md +57 -0
  257. package/sdd/references/planner-mvp-mode.md +53 -0
  258. package/sdd/references/planner-reviews.md +39 -0
  259. package/sdd/references/planner-revision.md +87 -0
  260. package/sdd/references/planner-source-audit.md +73 -0
  261. package/sdd/references/planning-config.md +276 -7
  262. package/sdd/references/project-skills-discovery.md +19 -0
  263. package/sdd/references/revision-loop.md +97 -0
  264. package/sdd/references/scout-codebase.md +51 -0
  265. package/sdd/references/skeleton-template.md +48 -0
  266. package/sdd/references/sketch-interactivity.md +41 -0
  267. package/sdd/references/sketch-theme-system.md +94 -0
  268. package/sdd/references/sketch-tooling.md +45 -0
  269. package/sdd/references/sketch-variant-patterns.md +81 -0
  270. package/sdd/references/spidr-splitting.md +69 -0
  271. package/sdd/references/tdd.md +67 -0
  272. package/sdd/references/thinking-models-debug.md +44 -0
  273. package/sdd/references/thinking-models-execution.md +50 -0
  274. package/sdd/references/thinking-models-planning.md +62 -0
  275. package/sdd/references/thinking-models-research.md +50 -0
  276. package/sdd/references/thinking-models-verification.md +55 -0
  277. package/sdd/references/thinking-partner.md +96 -0
  278. package/sdd/references/ui-brand.md +4 -4
  279. package/sdd/references/universal-anti-patterns.md +63 -0
  280. package/sdd/references/user-story-template.md +58 -0
  281. package/sdd/references/verification-overrides.md +227 -0
  282. package/sdd/references/verify-mvp-mode.md +85 -0
  283. package/sdd/references/workstream-flag.md +63 -10
  284. package/sdd/references/worktree-path-safety.md +89 -0
  285. package/sdd/templates/AI-SPEC.md +246 -0
  286. package/sdd/templates/DEBUG.md +7 -2
  287. package/sdd/templates/README.md +77 -0
  288. package/sdd/templates/SECURITY.md +61 -0
  289. package/sdd/templates/VALIDATION.md +3 -3
  290. package/sdd/templates/claude-md.md +27 -4
  291. package/sdd/templates/config.json +20 -2
  292. package/sdd/templates/discovery.md +2 -2
  293. package/sdd/templates/research.md +40 -0
  294. package/sdd/templates/spec.md +307 -0
  295. package/sdd/templates/state.md +10 -2
  296. package/sdd/workflows/add-backlog.md +90 -0
  297. package/sdd/workflows/add-phase.md +12 -12
  298. package/sdd/workflows/add-tests.md +6 -3
  299. package/sdd/workflows/add-todo.md +8 -6
  300. package/sdd/workflows/ai-integration-phase.md +294 -0
  301. package/sdd/workflows/analyze-dependencies.md +96 -0
  302. package/sdd/workflows/audit-fix.md +177 -0
  303. package/sdd/workflows/audit-milestone.md +35 -18
  304. package/sdd/workflows/audit-uat.md +1 -1
  305. package/sdd/workflows/autonomous.md +202 -304
  306. package/sdd/workflows/check-todos.md +12 -10
  307. package/sdd/workflows/cleanup.md +3 -1
  308. package/sdd/workflows/code-review-fix.md +501 -0
  309. package/sdd/workflows/code-review.md +613 -0
  310. package/sdd/workflows/complete-milestone.md +115 -28
  311. package/sdd/workflows/debug.md +231 -0
  312. package/sdd/workflows/diagnose-issues.md +14 -5
  313. package/sdd/workflows/discovery-phase.md +3 -1
  314. package/sdd/workflows/discuss-phase/modes/advisor.md +175 -0
  315. package/sdd/workflows/discuss-phase/modes/all.md +28 -0
  316. package/sdd/workflows/discuss-phase/modes/analyze.md +44 -0
  317. package/sdd/workflows/discuss-phase/modes/auto.md +56 -0
  318. package/sdd/workflows/discuss-phase/modes/batch.md +52 -0
  319. package/sdd/workflows/discuss-phase/modes/chain.md +97 -0
  320. package/sdd/workflows/discuss-phase/modes/default.md +141 -0
  321. package/sdd/workflows/discuss-phase/modes/power.md +44 -0
  322. package/sdd/workflows/discuss-phase/modes/text.md +55 -0
  323. package/sdd/workflows/discuss-phase/templates/checkpoint.json +18 -0
  324. package/sdd/workflows/discuss-phase/templates/context.md +136 -0
  325. package/sdd/workflows/discuss-phase/templates/discussion-log.md +50 -0
  326. package/sdd/workflows/discuss-phase-assumptions.md +41 -20
  327. package/sdd/workflows/discuss-phase-power.md +291 -0
  328. package/sdd/workflows/discuss-phase.md +242 -792
  329. package/sdd/workflows/do.md +13 -7
  330. package/sdd/workflows/docs-update.md +1161 -0
  331. package/sdd/workflows/edit-phase.md +294 -0
  332. package/sdd/workflows/eval-review.md +155 -0
  333. package/sdd/workflows/execute-phase/steps/codebase-drift-gate.md +81 -0
  334. package/sdd/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  335. package/sdd/workflows/execute-phase/steps/post-merge-gate.md +116 -0
  336. package/sdd/workflows/execute-phase.md +1062 -108
  337. package/sdd/workflows/execute-plan.md +118 -107
  338. package/sdd/workflows/explore.md +143 -0
  339. package/sdd/workflows/extract-learnings.md +242 -0
  340. package/sdd/workflows/forensics.md +17 -4
  341. package/sdd/workflows/graduation.md +195 -0
  342. package/sdd/workflows/health.md +45 -3
  343. package/sdd/workflows/help.md +265 -88
  344. package/sdd/workflows/import.md +253 -0
  345. package/sdd/workflows/inbox.md +387 -0
  346. package/sdd/workflows/ingest-docs.md +339 -0
  347. package/sdd/workflows/insert-phase.md +37 -16
  348. package/sdd/workflows/list-phase-assumptions.md +2 -2
  349. package/sdd/workflows/list-workspaces.md +3 -3
  350. package/sdd/workflows/manager.md +62 -32
  351. package/sdd/workflows/map-codebase.md +90 -24
  352. package/sdd/workflows/milestone-summary.md +6 -6
  353. package/sdd/workflows/mvp-phase.md +221 -0
  354. package/sdd/workflows/new-milestone.md +168 -20
  355. package/sdd/workflows/new-project.md +273 -47
  356. package/sdd/workflows/new-workspace.md +8 -6
  357. package/sdd/workflows/next.md +127 -4
  358. package/sdd/workflows/note.md +7 -5
  359. package/sdd/workflows/pause-work.md +79 -12
  360. package/sdd/workflows/plan-milestone-gaps.md +14 -7
  361. package/sdd/workflows/plan-phase.md +987 -62
  362. package/sdd/workflows/plan-review-convergence.md +329 -0
  363. package/sdd/workflows/plant-seed.md +145 -85
  364. package/sdd/workflows/pr-branch.md +41 -13
  365. package/sdd/workflows/profile-user.md +20 -18
  366. package/sdd/workflows/progress.md +186 -44
  367. package/sdd/workflows/quick.md +470 -58
  368. package/sdd/workflows/reapply-patches.md +390 -0
  369. package/sdd/workflows/remove-phase.md +12 -12
  370. package/sdd/workflows/remove-workspace.md +24 -7
  371. package/sdd/workflows/resume-project.md +18 -15
  372. package/sdd/workflows/review.md +242 -11
  373. package/sdd/workflows/scan.md +104 -0
  374. package/sdd/workflows/secure-phase.md +179 -0
  375. package/sdd/workflows/session-report.md +2 -2
  376. package/sdd/workflows/settings-advanced.md +579 -0
  377. package/sdd/workflows/settings-integrations.md +281 -0
  378. package/sdd/workflows/settings.md +221 -16
  379. package/sdd/workflows/ship.md +140 -13
  380. package/sdd/workflows/sketch-wrap-up.md +285 -0
  381. package/sdd/workflows/sketch.md +360 -0
  382. package/sdd/workflows/spec-phase.md +262 -0
  383. package/sdd/workflows/spike-wrap-up.md +306 -0
  384. package/sdd/workflows/spike.md +452 -0
  385. package/sdd/workflows/stats.md +20 -1
  386. package/sdd/workflows/sync-skills.md +182 -0
  387. package/sdd/workflows/thread.md +221 -0
  388. package/sdd/workflows/transition.md +44 -22
  389. package/sdd/workflows/ui-phase.md +39 -14
  390. package/sdd/workflows/ui-review.md +33 -6
  391. package/sdd/workflows/ultraplan-phase.md +198 -0
  392. package/sdd/workflows/undo.md +314 -0
  393. package/sdd/workflows/update.md +350 -29
  394. package/sdd/workflows/validate-phase.md +10 -6
  395. package/sdd/workflows/verify-phase.md +307 -18
  396. package/sdd/workflows/verify-work.md +153 -10
  397. package/sdk/dist/cli-transport.d.ts +19 -0
  398. package/sdk/dist/cli-transport.d.ts.map +1 -0
  399. package/sdk/dist/cli-transport.js +104 -0
  400. package/sdk/dist/cli-transport.js.map +1 -0
  401. package/sdk/dist/cli.d.ts +46 -0
  402. package/sdk/dist/cli.d.ts.map +1 -0
  403. package/sdk/dist/cli.js +511 -0
  404. package/sdk/dist/cli.js.map +1 -0
  405. package/sdk/dist/config.d.ts +84 -0
  406. package/sdk/dist/config.d.ts.map +1 -0
  407. package/sdk/dist/config.js +135 -0
  408. package/sdk/dist/config.js.map +1 -0
  409. package/sdk/dist/context-engine.d.ts +49 -0
  410. package/sdk/dist/context-engine.d.ts.map +1 -0
  411. package/sdk/dist/context-engine.js +142 -0
  412. package/sdk/dist/context-engine.js.map +1 -0
  413. package/sdk/dist/context-truncation.d.ts +33 -0
  414. package/sdk/dist/context-truncation.d.ts.map +1 -0
  415. package/sdk/dist/context-truncation.js +197 -0
  416. package/sdk/dist/context-truncation.js.map +1 -0
  417. package/sdk/dist/errors.d.ts +46 -0
  418. package/sdk/dist/errors.d.ts.map +1 -0
  419. package/sdk/dist/errors.js +64 -0
  420. package/sdk/dist/errors.js.map +1 -0
  421. package/sdk/dist/event-stream.d.ts +53 -0
  422. package/sdk/dist/event-stream.d.ts.map +1 -0
  423. package/sdk/dist/event-stream.js +321 -0
  424. package/sdk/dist/event-stream.js.map +1 -0
  425. package/sdk/dist/golden/capture.d.ts +15 -0
  426. package/sdk/dist/golden/capture.d.ts.map +1 -0
  427. package/sdk/dist/golden/capture.js +67 -0
  428. package/sdk/dist/golden/capture.js.map +1 -0
  429. package/sdk/dist/golden/golden-integration-covered.d.ts +6 -0
  430. package/sdk/dist/golden/golden-integration-covered.d.ts.map +1 -0
  431. package/sdk/dist/golden/golden-integration-covered.js +30 -0
  432. package/sdk/dist/golden/golden-integration-covered.js.map +1 -0
  433. package/sdk/dist/golden/golden-mutation-covered.d.ts +7 -0
  434. package/sdk/dist/golden/golden-mutation-covered.d.ts.map +1 -0
  435. package/sdk/dist/golden/golden-mutation-covered.js +17 -0
  436. package/sdk/dist/golden/golden-mutation-covered.js.map +1 -0
  437. package/sdk/dist/golden/golden-policy.d.ts +10 -0
  438. package/sdk/dist/golden/golden-policy.d.ts.map +1 -0
  439. package/sdk/dist/golden/golden-policy.js +98 -0
  440. package/sdk/dist/golden/golden-policy.js.map +1 -0
  441. package/sdk/dist/golden/init-golden-normalize.d.ts +8 -0
  442. package/sdk/dist/golden/init-golden-normalize.d.ts.map +1 -0
  443. package/sdk/dist/golden/init-golden-normalize.js +14 -0
  444. package/sdk/dist/golden/init-golden-normalize.js.map +1 -0
  445. package/sdk/dist/golden/read-only-golden-rows.d.ts +20 -0
  446. package/sdk/dist/golden/read-only-golden-rows.d.ts.map +1 -0
  447. package/sdk/dist/golden/read-only-golden-rows.js +67 -0
  448. package/sdk/dist/golden/read-only-golden-rows.js.map +1 -0
  449. package/sdk/dist/golden/registry-canonical-commands.d.ts +6 -0
  450. package/sdk/dist/golden/registry-canonical-commands.d.ts.map +1 -0
  451. package/sdk/dist/golden/registry-canonical-commands.js +30 -0
  452. package/sdk/dist/golden/registry-canonical-commands.js.map +1 -0
  453. package/sdk/dist/index.d.ts +125 -0
  454. package/sdk/dist/index.d.ts.map +1 -0
  455. package/sdk/dist/index.js +298 -0
  456. package/sdk/dist/index.js.map +1 -0
  457. package/sdk/dist/init-runner.d.ts +90 -0
  458. package/sdk/dist/init-runner.d.ts.map +1 -0
  459. package/sdk/dist/init-runner.js +613 -0
  460. package/sdk/dist/init-runner.js.map +1 -0
  461. package/sdk/dist/logger.d.ts +50 -0
  462. package/sdk/dist/logger.d.ts.map +1 -0
  463. package/sdk/dist/logger.js +70 -0
  464. package/sdk/dist/logger.js.map +1 -0
  465. package/sdk/dist/model-catalog.d.ts +31 -0
  466. package/sdk/dist/model-catalog.d.ts.map +1 -0
  467. package/sdk/dist/model-catalog.js +31 -0
  468. package/sdk/dist/model-catalog.js.map +1 -0
  469. package/sdk/dist/phase-prompt.d.ts +72 -0
  470. package/sdk/dist/phase-prompt.d.ts.map +1 -0
  471. package/sdk/dist/phase-prompt.js +213 -0
  472. package/sdk/dist/phase-prompt.js.map +1 -0
  473. package/sdk/dist/phase-runner.d.ts +145 -0
  474. package/sdk/dist/phase-runner.d.ts.map +1 -0
  475. package/sdk/dist/phase-runner.js +1206 -0
  476. package/sdk/dist/phase-runner.js.map +1 -0
  477. package/sdk/dist/plan-parser.d.ts +55 -0
  478. package/sdk/dist/plan-parser.d.ts.map +1 -0
  479. package/sdk/dist/plan-parser.js +389 -0
  480. package/sdk/dist/plan-parser.js.map +1 -0
  481. package/sdk/dist/planning-journal.d.ts +64 -0
  482. package/sdk/dist/planning-journal.d.ts.map +1 -0
  483. package/sdk/dist/planning-journal.js +88 -0
  484. package/sdk/dist/planning-journal.js.map +1 -0
  485. package/sdk/dist/planning-runtime.d.ts +67 -0
  486. package/sdk/dist/planning-runtime.d.ts.map +1 -0
  487. package/sdk/dist/planning-runtime.js +58 -0
  488. package/sdk/dist/planning-runtime.js.map +1 -0
  489. package/sdk/dist/prompt-builder.d.ts +44 -0
  490. package/sdk/dist/prompt-builder.d.ts.map +1 -0
  491. package/sdk/dist/prompt-builder.js +180 -0
  492. package/sdk/dist/prompt-builder.js.map +1 -0
  493. package/sdk/dist/prompt-sanitizer.d.ts +35 -0
  494. package/sdk/dist/prompt-sanitizer.d.ts.map +1 -0
  495. package/sdk/dist/prompt-sanitizer.js +101 -0
  496. package/sdk/dist/prompt-sanitizer.js.map +1 -0
  497. package/sdk/dist/query/active-workstream-store.d.ts +7 -0
  498. package/sdk/dist/query/active-workstream-store.d.ts.map +1 -0
  499. package/sdk/dist/query/active-workstream-store.js +56 -0
  500. package/sdk/dist/query/active-workstream-store.js.map +1 -0
  501. package/sdk/dist/query/agent-failure-classifier.d.ts +38 -0
  502. package/sdk/dist/query/agent-failure-classifier.d.ts.map +1 -0
  503. package/sdk/dist/query/agent-failure-classifier.js +83 -0
  504. package/sdk/dist/query/agent-failure-classifier.js.map +1 -0
  505. package/sdk/dist/query/audit-open.d.ts +46 -0
  506. package/sdk/dist/query/audit-open.d.ts.map +1 -0
  507. package/sdk/dist/query/audit-open.js +662 -0
  508. package/sdk/dist/query/audit-open.js.map +1 -0
  509. package/sdk/dist/query/check-auto-mode.d.ts +13 -0
  510. package/sdk/dist/query/check-auto-mode.d.ts.map +1 -0
  511. package/sdk/dist/query/check-auto-mode.js +40 -0
  512. package/sdk/dist/query/check-auto-mode.js.map +1 -0
  513. package/sdk/dist/query/check-completion.d.ts +10 -0
  514. package/sdk/dist/query/check-completion.d.ts.map +1 -0
  515. package/sdk/dist/query/check-completion.js +157 -0
  516. package/sdk/dist/query/check-completion.js.map +1 -0
  517. package/sdk/dist/query/check-decision-coverage.d.ts +33 -0
  518. package/sdk/dist/query/check-decision-coverage.d.ts.map +1 -0
  519. package/sdk/dist/query/check-decision-coverage.js +472 -0
  520. package/sdk/dist/query/check-decision-coverage.js.map +1 -0
  521. package/sdk/dist/query/check-gates.d.ts +10 -0
  522. package/sdk/dist/query/check-gates.d.ts.map +1 -0
  523. package/sdk/dist/query/check-gates.js +89 -0
  524. package/sdk/dist/query/check-gates.js.map +1 -0
  525. package/sdk/dist/query/check-ship-ready.d.ts +10 -0
  526. package/sdk/dist/query/check-ship-ready.d.ts.map +1 -0
  527. package/sdk/dist/query/check-ship-ready.js +93 -0
  528. package/sdk/dist/query/check-ship-ready.js.map +1 -0
  529. package/sdk/dist/query/check-verification-status.d.ts +10 -0
  530. package/sdk/dist/query/check-verification-status.d.ts.map +1 -0
  531. package/sdk/dist/query/check-verification-status.js +142 -0
  532. package/sdk/dist/query/check-verification-status.js.map +1 -0
  533. package/sdk/dist/query/command-aliases.generated.d.ts +31 -0
  534. package/sdk/dist/query/command-aliases.generated.d.ts.map +1 -0
  535. package/sdk/dist/query/command-aliases.generated.js +135 -0
  536. package/sdk/dist/query/command-aliases.generated.js.map +1 -0
  537. package/sdk/dist/query/command-catalog.d.ts +9 -0
  538. package/sdk/dist/query/command-catalog.d.ts.map +1 -0
  539. package/sdk/dist/query/command-catalog.js +17 -0
  540. package/sdk/dist/query/command-catalog.js.map +1 -0
  541. package/sdk/dist/query/command-definition.d.ts +19 -0
  542. package/sdk/dist/query/command-definition.d.ts.map +1 -0
  543. package/sdk/dist/query/command-definition.js +44 -0
  544. package/sdk/dist/query/command-definition.js.map +1 -0
  545. package/sdk/dist/query/command-family-handlers.d.ts +3 -0
  546. package/sdk/dist/query/command-family-handlers.d.ts.map +1 -0
  547. package/sdk/dist/query/command-family-handlers.js +94 -0
  548. package/sdk/dist/query/command-family-handlers.js.map +1 -0
  549. package/sdk/dist/query/command-manifest.d.ts +2 -0
  550. package/sdk/dist/query/command-manifest.d.ts.map +1 -0
  551. package/sdk/dist/query/command-manifest.init.d.ts +6 -0
  552. package/sdk/dist/query/command-manifest.init.d.ts.map +1 -0
  553. package/sdk/dist/query/command-manifest.init.js +23 -0
  554. package/sdk/dist/query/command-manifest.init.js.map +1 -0
  555. package/sdk/dist/query/command-manifest.js +17 -0
  556. package/sdk/dist/query/command-manifest.js.map +1 -0
  557. package/sdk/dist/query/command-manifest.non-family.d.ts +9 -0
  558. package/sdk/dist/query/command-manifest.non-family.d.ts.map +1 -0
  559. package/sdk/dist/query/command-manifest.non-family.js +59 -0
  560. package/sdk/dist/query/command-manifest.non-family.js.map +1 -0
  561. package/sdk/dist/query/command-manifest.phase.d.ts +6 -0
  562. package/sdk/dist/query/command-manifest.phase.d.ts.map +1 -0
  563. package/sdk/dist/query/command-manifest.phase.js +15 -0
  564. package/sdk/dist/query/command-manifest.phase.js.map +1 -0
  565. package/sdk/dist/query/command-manifest.phases.d.ts +7 -0
  566. package/sdk/dist/query/command-manifest.phases.d.ts.map +1 -0
  567. package/sdk/dist/query/command-manifest.phases.js +10 -0
  568. package/sdk/dist/query/command-manifest.phases.js.map +1 -0
  569. package/sdk/dist/query/command-manifest.roadmap.d.ts +6 -0
  570. package/sdk/dist/query/command-manifest.roadmap.d.ts.map +1 -0
  571. package/sdk/dist/query/command-manifest.roadmap.js +10 -0
  572. package/sdk/dist/query/command-manifest.roadmap.js.map +1 -0
  573. package/sdk/dist/query/command-manifest.state.d.ts +9 -0
  574. package/sdk/dist/query/command-manifest.state.d.ts.map +1 -0
  575. package/sdk/dist/query/command-manifest.state.js +30 -0
  576. package/sdk/dist/query/command-manifest.state.js.map +1 -0
  577. package/sdk/dist/query/command-manifest.types.d.ts +12 -0
  578. package/sdk/dist/query/command-manifest.types.d.ts.map +1 -0
  579. package/sdk/dist/query/command-manifest.types.js +2 -0
  580. package/sdk/dist/query/command-manifest.types.js.map +1 -0
  581. package/sdk/dist/query/command-manifest.validate.d.ts +6 -0
  582. package/sdk/dist/query/command-manifest.validate.d.ts.map +1 -0
  583. package/sdk/dist/query/command-manifest.validate.js +10 -0
  584. package/sdk/dist/query/command-manifest.validate.js.map +1 -0
  585. package/sdk/dist/query/command-manifest.verify.d.ts +6 -0
  586. package/sdk/dist/query/command-manifest.verify.d.ts.map +1 -0
  587. package/sdk/dist/query/command-manifest.verify.js +14 -0
  588. package/sdk/dist/query/command-manifest.verify.js.map +1 -0
  589. package/sdk/dist/query/command-static-catalog-domain.d.ts +3 -0
  590. package/sdk/dist/query/command-static-catalog-domain.d.ts.map +1 -0
  591. package/sdk/dist/query/command-static-catalog-domain.js +116 -0
  592. package/sdk/dist/query/command-static-catalog-domain.js.map +1 -0
  593. package/sdk/dist/query/command-static-catalog-foundation.d.ts +7 -0
  594. package/sdk/dist/query/command-static-catalog-foundation.d.ts.map +1 -0
  595. package/sdk/dist/query/command-static-catalog-foundation.js +98 -0
  596. package/sdk/dist/query/command-static-catalog-foundation.js.map +1 -0
  597. package/sdk/dist/query/command-topology.d.ts +32 -0
  598. package/sdk/dist/query/command-topology.d.ts.map +1 -0
  599. package/sdk/dist/query/command-topology.js +66 -0
  600. package/sdk/dist/query/command-topology.js.map +1 -0
  601. package/sdk/dist/query/commands-list.d.ts +14 -0
  602. package/sdk/dist/query/commands-list.d.ts.map +1 -0
  603. package/sdk/dist/query/commands-list.js +18 -0
  604. package/sdk/dist/query/commands-list.js.map +1 -0
  605. package/sdk/dist/query/commit.d.ts +79 -0
  606. package/sdk/dist/query/commit.d.ts.map +1 -0
  607. package/sdk/dist/query/commit.js +340 -0
  608. package/sdk/dist/query/commit.js.map +1 -0
  609. package/sdk/dist/query/config-gates.d.ts +12 -0
  610. package/sdk/dist/query/config-gates.d.ts.map +1 -0
  611. package/sdk/dist/query/config-gates.js +66 -0
  612. package/sdk/dist/query/config-gates.js.map +1 -0
  613. package/sdk/dist/query/config-mutation.d.ts +86 -0
  614. package/sdk/dist/query/config-mutation.d.ts.map +1 -0
  615. package/sdk/dist/query/config-mutation.js +518 -0
  616. package/sdk/dist/query/config-mutation.js.map +1 -0
  617. package/sdk/dist/query/config-query.d.ts +57 -0
  618. package/sdk/dist/query/config-query.d.ts.map +1 -0
  619. package/sdk/dist/query/config-query.js +208 -0
  620. package/sdk/dist/query/config-query.js.map +1 -0
  621. package/sdk/dist/query/config-schema.d.ts +36 -0
  622. package/sdk/dist/query/config-schema.d.ts.map +1 -0
  623. package/sdk/dist/query/config-schema.js +147 -0
  624. package/sdk/dist/query/config-schema.js.map +1 -0
  625. package/sdk/dist/query/decisions.d.ts +58 -0
  626. package/sdk/dist/query/decisions.d.ts.map +1 -0
  627. package/sdk/dist/query/decisions.js +161 -0
  628. package/sdk/dist/query/decisions.js.map +1 -0
  629. package/sdk/dist/query/detect-custom-files.d.ts +11 -0
  630. package/sdk/dist/query/detect-custom-files.d.ts.map +1 -0
  631. package/sdk/dist/query/detect-custom-files.js +89 -0
  632. package/sdk/dist/query/detect-custom-files.js.map +1 -0
  633. package/sdk/dist/query/detect-phase-type.d.ts +9 -0
  634. package/sdk/dist/query/detect-phase-type.d.ts.map +1 -0
  635. package/sdk/dist/query/detect-phase-type.js +124 -0
  636. package/sdk/dist/query/detect-phase-type.js.map +1 -0
  637. package/sdk/dist/query/docs-init.d.ts +26 -0
  638. package/sdk/dist/query/docs-init.d.ts.map +1 -0
  639. package/sdk/dist/query/docs-init.js +231 -0
  640. package/sdk/dist/query/docs-init.js.map +1 -0
  641. package/sdk/dist/query/fallow-audit.d.ts +44 -0
  642. package/sdk/dist/query/fallow-audit.d.ts.map +1 -0
  643. package/sdk/dist/query/fallow-audit.js +44 -0
  644. package/sdk/dist/query/fallow-audit.js.map +1 -0
  645. package/sdk/dist/query/frontmatter-mutation.d.ts +77 -0
  646. package/sdk/dist/query/frontmatter-mutation.d.ts.map +1 -0
  647. package/sdk/dist/query/frontmatter-mutation.js +317 -0
  648. package/sdk/dist/query/frontmatter-mutation.js.map +1 -0
  649. package/sdk/dist/query/frontmatter.d.ts +93 -0
  650. package/sdk/dist/query/frontmatter.d.ts.map +1 -0
  651. package/sdk/dist/query/frontmatter.js +365 -0
  652. package/sdk/dist/query/frontmatter.js.map +1 -0
  653. package/sdk/dist/query/helpers.d.ts +191 -0
  654. package/sdk/dist/query/helpers.d.ts.map +1 -0
  655. package/sdk/dist/query/helpers.js +613 -0
  656. package/sdk/dist/query/helpers.js.map +1 -0
  657. package/sdk/dist/query/index.d.ts +8 -0
  658. package/sdk/dist/query/index.d.ts.map +1 -0
  659. package/sdk/dist/query/index.js +6 -0
  660. package/sdk/dist/query/index.js.map +1 -0
  661. package/sdk/dist/query/init-complex.d.ts +47 -0
  662. package/sdk/dist/query/init-complex.d.ts.map +1 -0
  663. package/sdk/dist/query/init-complex.js +718 -0
  664. package/sdk/dist/query/init-complex.js.map +1 -0
  665. package/sdk/dist/query/init.d.ts +106 -0
  666. package/sdk/dist/query/init.d.ts.map +1 -0
  667. package/sdk/dist/query/init.js +1159 -0
  668. package/sdk/dist/query/init.js.map +1 -0
  669. package/sdk/dist/query/intel.d.ts +43 -0
  670. package/sdk/dist/query/intel.d.ts.map +1 -0
  671. package/sdk/dist/query/intel.js +416 -0
  672. package/sdk/dist/query/intel.js.map +1 -0
  673. package/sdk/dist/query/mutation-event-decorator.d.ts +5 -0
  674. package/sdk/dist/query/mutation-event-decorator.d.ts.map +1 -0
  675. package/sdk/dist/query/mutation-event-decorator.js +28 -0
  676. package/sdk/dist/query/mutation-event-decorator.js.map +1 -0
  677. package/sdk/dist/query/mutation-event-mapper.d.ts +4 -0
  678. package/sdk/dist/query/mutation-event-mapper.d.ts.map +1 -0
  679. package/sdk/dist/query/mutation-event-mapper.js +70 -0
  680. package/sdk/dist/query/mutation-event-mapper.js.map +1 -0
  681. package/sdk/dist/query/mvp.d.ts +113 -0
  682. package/sdk/dist/query/mvp.d.ts.map +1 -0
  683. package/sdk/dist/query/mvp.js +225 -0
  684. package/sdk/dist/query/mvp.js.map +1 -0
  685. package/sdk/dist/query/phase-filesystem-adapter.d.ts +4 -0
  686. package/sdk/dist/query/phase-filesystem-adapter.d.ts.map +1 -0
  687. package/sdk/dist/query/phase-filesystem-adapter.js +33 -0
  688. package/sdk/dist/query/phase-filesystem-adapter.js.map +1 -0
  689. package/sdk/dist/query/phase-lifecycle-policy.d.ts +34 -0
  690. package/sdk/dist/query/phase-lifecycle-policy.d.ts.map +1 -0
  691. package/sdk/dist/query/phase-lifecycle-policy.js +138 -0
  692. package/sdk/dist/query/phase-lifecycle-policy.js.map +1 -0
  693. package/sdk/dist/query/phase-lifecycle.d.ts +116 -0
  694. package/sdk/dist/query/phase-lifecycle.d.ts.map +1 -0
  695. package/sdk/dist/query/phase-lifecycle.js +1486 -0
  696. package/sdk/dist/query/phase-lifecycle.js.map +1 -0
  697. package/sdk/dist/query/phase-list-queries.d.ts +18 -0
  698. package/sdk/dist/query/phase-list-queries.d.ts.map +1 -0
  699. package/sdk/dist/query/phase-list-queries.js +129 -0
  700. package/sdk/dist/query/phase-list-queries.js.map +1 -0
  701. package/sdk/dist/query/phase-ready.d.ts +9 -0
  702. package/sdk/dist/query/phase-ready.d.ts.map +1 -0
  703. package/sdk/dist/query/phase-ready.js +132 -0
  704. package/sdk/dist/query/phase-ready.js.map +1 -0
  705. package/sdk/dist/query/phase-roadmap-mutation.d.ts +13 -0
  706. package/sdk/dist/query/phase-roadmap-mutation.d.ts.map +1 -0
  707. package/sdk/dist/query/phase-roadmap-mutation.js +65 -0
  708. package/sdk/dist/query/phase-roadmap-mutation.js.map +1 -0
  709. package/sdk/dist/query/phase.d.ts +48 -0
  710. package/sdk/dist/query/phase.d.ts.map +1 -0
  711. package/sdk/dist/query/phase.js +451 -0
  712. package/sdk/dist/query/phase.js.map +1 -0
  713. package/sdk/dist/query/pipeline.d.ts +53 -0
  714. package/sdk/dist/query/pipeline.d.ts.map +1 -0
  715. package/sdk/dist/query/pipeline.js +198 -0
  716. package/sdk/dist/query/pipeline.js.map +1 -0
  717. package/sdk/dist/query/plan-scan.d.ts +14 -0
  718. package/sdk/dist/query/plan-scan.d.ts.map +1 -0
  719. package/sdk/dist/query/plan-scan.js +70 -0
  720. package/sdk/dist/query/plan-scan.js.map +1 -0
  721. package/sdk/dist/query/plan-task-structure.d.ts +9 -0
  722. package/sdk/dist/query/plan-task-structure.d.ts.map +1 -0
  723. package/sdk/dist/query/plan-task-structure.js +59 -0
  724. package/sdk/dist/query/plan-task-structure.js.map +1 -0
  725. package/sdk/dist/query/profile-extract-messages.d.ts +40 -0
  726. package/sdk/dist/query/profile-extract-messages.d.ts.map +1 -0
  727. package/sdk/dist/query/profile-extract-messages.js +195 -0
  728. package/sdk/dist/query/profile-extract-messages.js.map +1 -0
  729. package/sdk/dist/query/profile-output.d.ts +11 -0
  730. package/sdk/dist/query/profile-output.d.ts.map +1 -0
  731. package/sdk/dist/query/profile-output.js +873 -0
  732. package/sdk/dist/query/profile-output.js.map +1 -0
  733. package/sdk/dist/query/profile-questionnaire-data.d.ts +21 -0
  734. package/sdk/dist/query/profile-questionnaire-data.d.ts.map +1 -0
  735. package/sdk/dist/query/profile-questionnaire-data.js +171 -0
  736. package/sdk/dist/query/profile-questionnaire-data.js.map +1 -0
  737. package/sdk/dist/query/profile-sample.d.ts +22 -0
  738. package/sdk/dist/query/profile-sample.d.ts.map +1 -0
  739. package/sdk/dist/query/profile-sample.js +136 -0
  740. package/sdk/dist/query/profile-sample.js.map +1 -0
  741. package/sdk/dist/query/profile-scan-sessions.d.ts +49 -0
  742. package/sdk/dist/query/profile-scan-sessions.d.ts.map +1 -0
  743. package/sdk/dist/query/profile-scan-sessions.js +137 -0
  744. package/sdk/dist/query/profile-scan-sessions.js.map +1 -0
  745. package/sdk/dist/query/profile.d.ts +61 -0
  746. package/sdk/dist/query/profile.d.ts.map +1 -0
  747. package/sdk/dist/query/profile.js +307 -0
  748. package/sdk/dist/query/profile.js.map +1 -0
  749. package/sdk/dist/query/progress.d.ts +77 -0
  750. package/sdk/dist/query/progress.d.ts.map +1 -0
  751. package/sdk/dist/query/progress.js +481 -0
  752. package/sdk/dist/query/progress.js.map +1 -0
  753. package/sdk/dist/query/query-cli-adapter.d.ts +8 -0
  754. package/sdk/dist/query/query-cli-adapter.d.ts.map +1 -0
  755. package/sdk/dist/query/query-cli-adapter.js +32 -0
  756. package/sdk/dist/query/query-cli-adapter.js.map +1 -0
  757. package/sdk/dist/query/query-cli-output.d.ts +9 -0
  758. package/sdk/dist/query/query-cli-output.d.ts.map +1 -0
  759. package/sdk/dist/query/query-cli-output.js +28 -0
  760. package/sdk/dist/query/query-cli-output.js.map +1 -0
  761. package/sdk/dist/query/query-command-diagnosis.d.ts +6 -0
  762. package/sdk/dist/query/query-command-diagnosis.d.ts.map +1 -0
  763. package/sdk/dist/query/query-command-diagnosis.js +6 -0
  764. package/sdk/dist/query/query-command-diagnosis.js.map +1 -0
  765. package/sdk/dist/query/query-command-resolution-strategy.d.ts +29 -0
  766. package/sdk/dist/query/query-command-resolution-strategy.d.ts.map +1 -0
  767. package/sdk/dist/query/query-command-resolution-strategy.js +103 -0
  768. package/sdk/dist/query/query-command-resolution-strategy.js.map +1 -0
  769. package/sdk/dist/query/query-command-semantics.d.ts +7 -0
  770. package/sdk/dist/query/query-command-semantics.d.ts.map +1 -0
  771. package/sdk/dist/query/query-command-semantics.js +7 -0
  772. package/sdk/dist/query/query-command-semantics.js.map +1 -0
  773. package/sdk/dist/query/query-dispatch-contract.d.ts +21 -0
  774. package/sdk/dist/query/query-dispatch-contract.d.ts.map +1 -0
  775. package/sdk/dist/query/query-dispatch-contract.js +2 -0
  776. package/sdk/dist/query/query-dispatch-contract.js.map +1 -0
  777. package/sdk/dist/query/query-dispatch-error-mapper.d.ts +6 -0
  778. package/sdk/dist/query/query-dispatch-error-mapper.d.ts.map +1 -0
  779. package/sdk/dist/query/query-dispatch-error-mapper.js +6 -0
  780. package/sdk/dist/query/query-dispatch-error-mapper.js.map +1 -0
  781. package/sdk/dist/query/query-dispatch-formatting.d.ts +6 -0
  782. package/sdk/dist/query/query-dispatch-formatting.d.ts.map +1 -0
  783. package/sdk/dist/query/query-dispatch-formatting.js +6 -0
  784. package/sdk/dist/query/query-dispatch-formatting.js.map +1 -0
  785. package/sdk/dist/query/query-dispatch-input-validation.d.ts +6 -0
  786. package/sdk/dist/query/query-dispatch-input-validation.d.ts.map +1 -0
  787. package/sdk/dist/query/query-dispatch-input-validation.js +6 -0
  788. package/sdk/dist/query/query-dispatch-input-validation.js.map +1 -0
  789. package/sdk/dist/query/query-dispatch-observability.d.ts +2 -0
  790. package/sdk/dist/query/query-dispatch-observability.d.ts.map +1 -0
  791. package/sdk/dist/query/query-dispatch-observability.js +7 -0
  792. package/sdk/dist/query/query-dispatch-observability.js.map +1 -0
  793. package/sdk/dist/query/query-dispatch-plan.d.ts +6 -0
  794. package/sdk/dist/query/query-dispatch-plan.d.ts.map +1 -0
  795. package/sdk/dist/query/query-dispatch-plan.js +6 -0
  796. package/sdk/dist/query/query-dispatch-plan.js.map +1 -0
  797. package/sdk/dist/query/query-dispatch-result-builder.d.ts +6 -0
  798. package/sdk/dist/query/query-dispatch-result-builder.d.ts.map +1 -0
  799. package/sdk/dist/query/query-dispatch-result-builder.js +6 -0
  800. package/sdk/dist/query/query-dispatch-result-builder.js.map +1 -0
  801. package/sdk/dist/query/query-dispatch.d.ts +48 -0
  802. package/sdk/dist/query/query-dispatch.d.ts.map +1 -0
  803. package/sdk/dist/query/query-dispatch.js +175 -0
  804. package/sdk/dist/query/query-dispatch.js.map +1 -0
  805. package/sdk/dist/query/query-error-details-schema.d.ts +19 -0
  806. package/sdk/dist/query/query-error-details-schema.d.ts.map +1 -0
  807. package/sdk/dist/query/query-error-details-schema.js +10 -0
  808. package/sdk/dist/query/query-error-details-schema.js.map +1 -0
  809. package/sdk/dist/query/query-error-taxonomy.d.ts +38 -0
  810. package/sdk/dist/query/query-error-taxonomy.d.ts.map +1 -0
  811. package/sdk/dist/query/query-error-taxonomy.js +74 -0
  812. package/sdk/dist/query/query-error-taxonomy.js.map +1 -0
  813. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts +14 -0
  814. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts.map +1 -0
  815. package/sdk/dist/query/query-fallback-bridge-adapter.js +33 -0
  816. package/sdk/dist/query/query-fallback-bridge-adapter.js.map +1 -0
  817. package/sdk/dist/query/query-fallback-executor.d.ts +11 -0
  818. package/sdk/dist/query/query-fallback-executor.d.ts.map +1 -0
  819. package/sdk/dist/query/query-fallback-executor.js +31 -0
  820. package/sdk/dist/query/query-fallback-executor.js.map +1 -0
  821. package/sdk/dist/query/query-fallback-output-classifier.d.ts +6 -0
  822. package/sdk/dist/query/query-fallback-output-classifier.d.ts.map +1 -0
  823. package/sdk/dist/query/query-fallback-output-classifier.js +27 -0
  824. package/sdk/dist/query/query-fallback-output-classifier.js.map +1 -0
  825. package/sdk/dist/query/query-fallback-policy.d.ts +6 -0
  826. package/sdk/dist/query/query-fallback-policy.d.ts.map +1 -0
  827. package/sdk/dist/query/query-fallback-policy.js +7 -0
  828. package/sdk/dist/query/query-fallback-policy.js.map +1 -0
  829. package/sdk/dist/query/query-native-dispatch-adapter.d.ts +7 -0
  830. package/sdk/dist/query/query-native-dispatch-adapter.d.ts.map +1 -0
  831. package/sdk/dist/query/query-native-dispatch-adapter.js +6 -0
  832. package/sdk/dist/query/query-native-dispatch-adapter.js.map +1 -0
  833. package/sdk/dist/query/query-policy-capability.d.ts +10 -0
  834. package/sdk/dist/query/query-policy-capability.d.ts.map +1 -0
  835. package/sdk/dist/query/query-policy-capability.js +17 -0
  836. package/sdk/dist/query/query-policy-capability.js.map +1 -0
  837. package/sdk/dist/query/query-runtime-context.d.ts +19 -0
  838. package/sdk/dist/query/query-runtime-context.d.ts.map +1 -0
  839. package/sdk/dist/query/query-runtime-context.js +31 -0
  840. package/sdk/dist/query/query-runtime-context.js.map +1 -0
  841. package/sdk/dist/query/query-unknown-command-hints.d.ts +2 -0
  842. package/sdk/dist/query/query-unknown-command-hints.d.ts.map +1 -0
  843. package/sdk/dist/query/query-unknown-command-hints.js +6 -0
  844. package/sdk/dist/query/query-unknown-command-hints.js.map +1 -0
  845. package/sdk/dist/query/registry-assembly-descriptor.d.ts +12 -0
  846. package/sdk/dist/query/registry-assembly-descriptor.d.ts.map +1 -0
  847. package/sdk/dist/query/registry-assembly-descriptor.js +61 -0
  848. package/sdk/dist/query/registry-assembly-descriptor.js.map +1 -0
  849. package/sdk/dist/query/registry-assembly-invariants.d.ts +30 -0
  850. package/sdk/dist/query/registry-assembly-invariants.d.ts.map +1 -0
  851. package/sdk/dist/query/registry-assembly-invariants.js +77 -0
  852. package/sdk/dist/query/registry-assembly-invariants.js.map +1 -0
  853. package/sdk/dist/query/registry-assembly.d.ts +10 -0
  854. package/sdk/dist/query/registry-assembly.d.ts.map +1 -0
  855. package/sdk/dist/query/registry-assembly.js +53 -0
  856. package/sdk/dist/query/registry-assembly.js.map +1 -0
  857. package/sdk/dist/query/registry.d.ts +90 -0
  858. package/sdk/dist/query/registry.d.ts.map +1 -0
  859. package/sdk/dist/query/registry.js +129 -0
  860. package/sdk/dist/query/registry.js.map +1 -0
  861. package/sdk/dist/query/requirements-extract-from-plans.d.ts +9 -0
  862. package/sdk/dist/query/requirements-extract-from-plans.d.ts.map +1 -0
  863. package/sdk/dist/query/requirements-extract-from-plans.js +76 -0
  864. package/sdk/dist/query/requirements-extract-from-plans.js.map +1 -0
  865. package/sdk/dist/query/roadmap-update-plan-progress.d.ts +11 -0
  866. package/sdk/dist/query/roadmap-update-plan-progress.d.ts.map +1 -0
  867. package/sdk/dist/query/roadmap-update-plan-progress.js +124 -0
  868. package/sdk/dist/query/roadmap-update-plan-progress.js.map +1 -0
  869. package/sdk/dist/query/roadmap.d.ts +137 -0
  870. package/sdk/dist/query/roadmap.d.ts.map +1 -0
  871. package/sdk/dist/query/roadmap.js +753 -0
  872. package/sdk/dist/query/roadmap.js.map +1 -0
  873. package/sdk/dist/query/route-next-action.d.ts +9 -0
  874. package/sdk/dist/query/route-next-action.d.ts.map +1 -0
  875. package/sdk/dist/query/route-next-action.js +318 -0
  876. package/sdk/dist/query/route-next-action.js.map +1 -0
  877. package/sdk/dist/query/schema-detect.d.ts +21 -0
  878. package/sdk/dist/query/schema-detect.d.ts.map +1 -0
  879. package/sdk/dist/query/schema-detect.js +146 -0
  880. package/sdk/dist/query/schema-detect.js.map +1 -0
  881. package/sdk/dist/query/secrets.d.ts +27 -0
  882. package/sdk/dist/query/secrets.d.ts.map +1 -0
  883. package/sdk/dist/query/secrets.js +42 -0
  884. package/sdk/dist/query/secrets.js.map +1 -0
  885. package/sdk/dist/query/skill-manifest.d.ts +50 -0
  886. package/sdk/dist/query/skill-manifest.d.ts.map +1 -0
  887. package/sdk/dist/query/skill-manifest.js +171 -0
  888. package/sdk/dist/query/skill-manifest.js.map +1 -0
  889. package/sdk/dist/query/skills.d.ts +27 -0
  890. package/sdk/dist/query/skills.d.ts.map +1 -0
  891. package/sdk/dist/query/skills.js +137 -0
  892. package/sdk/dist/query/skills.js.map +1 -0
  893. package/sdk/dist/query/state-document.d.ts +14 -0
  894. package/sdk/dist/query/state-document.d.ts.map +1 -0
  895. package/sdk/dist/query/state-document.js +110 -0
  896. package/sdk/dist/query/state-document.js.map +1 -0
  897. package/sdk/dist/query/state-mutation.d.ts +224 -0
  898. package/sdk/dist/query/state-mutation.d.ts.map +1 -0
  899. package/sdk/dist/query/state-mutation.js +1539 -0
  900. package/sdk/dist/query/state-mutation.js.map +1 -0
  901. package/sdk/dist/query/state-project-load.d.ts +23 -0
  902. package/sdk/dist/query/state-project-load.d.ts.map +1 -0
  903. package/sdk/dist/query/state-project-load.js +75 -0
  904. package/sdk/dist/query/state-project-load.js.map +1 -0
  905. package/sdk/dist/query/state.d.ts +78 -0
  906. package/sdk/dist/query/state.d.ts.map +1 -0
  907. package/sdk/dist/query/state.js +430 -0
  908. package/sdk/dist/query/state.js.map +1 -0
  909. package/sdk/dist/query/summary.d.ts +18 -0
  910. package/sdk/dist/query/summary.d.ts.map +1 -0
  911. package/sdk/dist/query/summary.js +249 -0
  912. package/sdk/dist/query/summary.js.map +1 -0
  913. package/sdk/dist/query/template.d.ts +46 -0
  914. package/sdk/dist/query/template.d.ts.map +1 -0
  915. package/sdk/dist/query/template.js +210 -0
  916. package/sdk/dist/query/template.js.map +1 -0
  917. package/sdk/dist/query/uat.d.ts +34 -0
  918. package/sdk/dist/query/uat.d.ts.map +1 -0
  919. package/sdk/dist/query/uat.js +339 -0
  920. package/sdk/dist/query/uat.js.map +1 -0
  921. package/sdk/dist/query/utils.d.ts +59 -0
  922. package/sdk/dist/query/utils.d.ts.map +1 -0
  923. package/sdk/dist/query/utils.js +74 -0
  924. package/sdk/dist/query/utils.js.map +1 -0
  925. package/sdk/dist/query/validate.d.ts +67 -0
  926. package/sdk/dist/query/validate.d.ts.map +1 -0
  927. package/sdk/dist/query/validate.js +908 -0
  928. package/sdk/dist/query/validate.js.map +1 -0
  929. package/sdk/dist/query/verify.d.ts +110 -0
  930. package/sdk/dist/query/verify.d.ts.map +1 -0
  931. package/sdk/dist/query/verify.js +631 -0
  932. package/sdk/dist/query/verify.js.map +1 -0
  933. package/sdk/dist/query/websearch.d.ts +24 -0
  934. package/sdk/dist/query/websearch.d.ts.map +1 -0
  935. package/sdk/dist/query/websearch.js +68 -0
  936. package/sdk/dist/query/websearch.js.map +1 -0
  937. package/sdk/dist/query/workspace.d.ts +62 -0
  938. package/sdk/dist/query/workspace.d.ts.map +1 -0
  939. package/sdk/dist/query/workspace.js +104 -0
  940. package/sdk/dist/query/workspace.js.map +1 -0
  941. package/sdk/dist/query/workstream-inventory.d.ts +52 -0
  942. package/sdk/dist/query/workstream-inventory.d.ts.map +1 -0
  943. package/sdk/dist/query/workstream-inventory.js +141 -0
  944. package/sdk/dist/query/workstream-inventory.js.map +1 -0
  945. package/sdk/dist/query/workstream.d.ts +35 -0
  946. package/sdk/dist/query/workstream.d.ts.map +1 -0
  947. package/sdk/dist/query/workstream.js +298 -0
  948. package/sdk/dist/query/workstream.js.map +1 -0
  949. package/sdk/dist/query/worktree.d.ts +3 -0
  950. package/sdk/dist/query/worktree.d.ts.map +1 -0
  951. package/sdk/dist/query/worktree.js +36 -0
  952. package/sdk/dist/query/worktree.js.map +1 -0
  953. package/sdk/dist/query-command-executor.d.ts +22 -0
  954. package/sdk/dist/query-command-executor.d.ts.map +1 -0
  955. package/sdk/dist/query-command-executor.js +22 -0
  956. package/sdk/dist/query-command-executor.js.map +1 -0
  957. package/sdk/dist/query-execution-policy.d.ts +24 -0
  958. package/sdk/dist/query-execution-policy.d.ts.map +1 -0
  959. package/sdk/dist/query-execution-policy.js +27 -0
  960. package/sdk/dist/query-execution-policy.js.map +1 -0
  961. package/sdk/dist/query-failure-classification.d.ts +9 -0
  962. package/sdk/dist/query-failure-classification.d.ts.map +1 -0
  963. package/sdk/dist/query-failure-classification.js +32 -0
  964. package/sdk/dist/query-failure-classification.js.map +1 -0
  965. package/sdk/dist/query-hotpath-methods.d.ts +19 -0
  966. package/sdk/dist/query-hotpath-methods.d.ts.map +1 -0
  967. package/sdk/dist/query-hotpath-methods.js +34 -0
  968. package/sdk/dist/query-hotpath-methods.js.map +1 -0
  969. package/sdk/dist/query-native-direct-adapter.d.ts +20 -0
  970. package/sdk/dist/query-native-direct-adapter.d.ts.map +1 -0
  971. package/sdk/dist/query-native-direct-adapter.js +52 -0
  972. package/sdk/dist/query-native-direct-adapter.js.map +1 -0
  973. package/sdk/dist/query-native-hotpath-adapter.d.ts +15 -0
  974. package/sdk/dist/query-native-hotpath-adapter.d.ts.map +1 -0
  975. package/sdk/dist/query-native-hotpath-adapter.js +32 -0
  976. package/sdk/dist/query-native-hotpath-adapter.js.map +1 -0
  977. package/sdk/dist/query-raw-output-projection.d.ts +6 -0
  978. package/sdk/dist/query-raw-output-projection.d.ts.map +1 -0
  979. package/sdk/dist/query-raw-output-projection.js +67 -0
  980. package/sdk/dist/query-raw-output-projection.js.map +1 -0
  981. package/sdk/dist/query-runtime-bridge.d.ts +61 -0
  982. package/sdk/dist/query-runtime-bridge.d.ts.map +1 -0
  983. package/sdk/dist/query-runtime-bridge.js +144 -0
  984. package/sdk/dist/query-runtime-bridge.js.map +1 -0
  985. package/sdk/dist/query-sdd-tools-path.d.ts +2 -0
  986. package/sdk/dist/query-sdd-tools-path.d.ts.map +1 -0
  987. package/sdk/dist/query-sdd-tools-path.js +2 -0
  988. package/sdk/dist/query-sdd-tools-path.js.map +1 -0
  989. package/sdk/dist/query-sdd-tools-runtime.d.ts +20 -0
  990. package/sdk/dist/query-sdd-tools-runtime.d.ts.map +1 -0
  991. package/sdk/dist/query-sdd-tools-runtime.js +47 -0
  992. package/sdk/dist/query-sdd-tools-runtime.js.map +1 -0
  993. package/sdk/dist/query-subprocess-adapter.d.ts +18 -0
  994. package/sdk/dist/query-subprocess-adapter.d.ts.map +1 -0
  995. package/sdk/dist/query-subprocess-adapter.js +92 -0
  996. package/sdk/dist/query-subprocess-adapter.js.map +1 -0
  997. package/sdk/dist/query-tools-error-factory.d.ts +16 -0
  998. package/sdk/dist/query-tools-error-factory.d.ts.map +1 -0
  999. package/sdk/dist/query-tools-error-factory.js +33 -0
  1000. package/sdk/dist/query-tools-error-factory.js.map +1 -0
  1001. package/sdk/dist/research-gate.d.ts +24 -0
  1002. package/sdk/dist/research-gate.d.ts.map +1 -0
  1003. package/sdk/dist/research-gate.js +70 -0
  1004. package/sdk/dist/research-gate.js.map +1 -0
  1005. package/sdk/dist/runtime-gate.d.ts +14 -0
  1006. package/sdk/dist/runtime-gate.d.ts.map +1 -0
  1007. package/sdk/dist/runtime-gate.js +48 -0
  1008. package/sdk/dist/runtime-gate.js.map +1 -0
  1009. package/sdk/dist/sdd-tools-error.d.ts +23 -0
  1010. package/sdk/dist/sdd-tools-error.d.ts.map +1 -0
  1011. package/sdk/dist/sdd-tools-error.js +29 -0
  1012. package/sdk/dist/sdd-tools-error.js.map +1 -0
  1013. package/sdk/dist/sdd-tools.d.ts +97 -0
  1014. package/sdk/dist/sdd-tools.d.ts.map +1 -0
  1015. package/sdk/dist/sdd-tools.js +168 -0
  1016. package/sdk/dist/sdd-tools.js.map +1 -0
  1017. package/sdk/dist/sdd-transport-policy.d.ts +10 -0
  1018. package/sdk/dist/sdd-transport-policy.d.ts.map +1 -0
  1019. package/sdk/dist/sdd-transport-policy.js +32 -0
  1020. package/sdk/dist/sdd-transport-policy.js.map +1 -0
  1021. package/sdk/dist/sdd-transport.d.ts +39 -0
  1022. package/sdk/dist/sdd-transport.d.ts.map +1 -0
  1023. package/sdk/dist/sdd-transport.js +78 -0
  1024. package/sdk/dist/sdd-transport.js.map +1 -0
  1025. package/sdk/dist/sdk-package-compatibility.d.ts +38 -0
  1026. package/sdk/dist/sdk-package-compatibility.d.ts.map +1 -0
  1027. package/sdk/dist/sdk-package-compatibility.js +90 -0
  1028. package/sdk/dist/sdk-package-compatibility.js.map +1 -0
  1029. package/sdk/dist/session-runner.d.ts +40 -0
  1030. package/sdk/dist/session-runner.d.ts.map +1 -0
  1031. package/sdk/dist/session-runner.js +274 -0
  1032. package/sdk/dist/session-runner.js.map +1 -0
  1033. package/sdk/dist/tool-scoping.d.ts +31 -0
  1034. package/sdk/dist/tool-scoping.d.ts.map +1 -0
  1035. package/sdk/dist/tool-scoping.js +54 -0
  1036. package/sdk/dist/tool-scoping.js.map +1 -0
  1037. package/sdk/dist/types.d.ts +794 -0
  1038. package/sdk/dist/types.d.ts.map +1 -0
  1039. package/sdk/dist/types.js +77 -0
  1040. package/sdk/dist/types.js.map +1 -0
  1041. package/sdk/dist/workstream-name-policy.d.ts +13 -0
  1042. package/sdk/dist/workstream-name-policy.d.ts.map +1 -0
  1043. package/sdk/dist/workstream-name-policy.js +24 -0
  1044. package/sdk/dist/workstream-name-policy.js.map +1 -0
  1045. package/sdk/dist/workstream-utils.d.ts +15 -0
  1046. package/sdk/dist/workstream-utils.d.ts.map +1 -0
  1047. package/sdk/dist/workstream-utils.js +21 -0
  1048. package/sdk/dist/workstream-utils.js.map +1 -0
  1049. package/sdk/dist/ws-transport.d.ts +32 -0
  1050. package/sdk/dist/ws-transport.d.ts.map +1 -0
  1051. package/sdk/dist/ws-transport.js +84 -0
  1052. package/sdk/dist/ws-transport.js.map +1 -0
  1053. package/sdk/package-lock.json +2502 -0
  1054. package/sdk/package.json +57 -0
  1055. package/sdk/prompts/templates/project.md +186 -0
  1056. package/sdk/prompts/templates/requirements.md +231 -0
  1057. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  1058. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  1059. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  1060. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  1061. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  1062. package/sdk/prompts/templates/roadmap.md +202 -0
  1063. package/sdk/prompts/templates/state.md +175 -0
  1064. package/sdk/shared/model-catalog.json +122 -0
  1065. package/sdk/src/assembled-prompts.test.ts +349 -0
  1066. package/sdk/src/bug-3591-sddtools-runtime-workstream.test.ts +179 -0
  1067. package/sdk/src/cli-transport.test.ts +388 -0
  1068. package/sdk/src/cli-transport.ts +130 -0
  1069. package/sdk/src/cli.test.ts +426 -0
  1070. package/sdk/src/cli.ts +589 -0
  1071. package/sdk/src/config.test.ts +271 -0
  1072. package/sdk/src/config.ts +218 -0
  1073. package/sdk/src/context-engine.test.ts +295 -0
  1074. package/sdk/src/context-engine.ts +170 -0
  1075. package/sdk/src/context-truncation.test.ts +163 -0
  1076. package/sdk/src/context-truncation.ts +233 -0
  1077. package/sdk/src/e2e.integration.test.ts +181 -0
  1078. package/sdk/src/errors.ts +72 -0
  1079. package/sdk/src/event-stream.test.ts +661 -0
  1080. package/sdk/src/event-stream.ts +441 -0
  1081. package/sdk/src/golden/capture.ts +95 -0
  1082. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  1083. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  1084. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  1085. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  1086. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  1087. package/sdk/src/golden/golden-mutation-covered.ts +17 -0
  1088. package/sdk/src/golden/golden-policy.test.ts +8 -0
  1089. package/sdk/src/golden/golden-policy.ts +120 -0
  1090. package/sdk/src/golden/golden.integration.test.ts +677 -0
  1091. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  1092. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  1093. package/sdk/src/golden/read-only-parity.integration.test.ts +133 -0
  1094. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  1095. package/sdk/src/index.ts +352 -0
  1096. package/sdk/src/init-e2e.integration.test.ts +138 -0
  1097. package/sdk/src/init-runner.test.ts +740 -0
  1098. package/sdk/src/init-runner.ts +734 -0
  1099. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  1100. package/sdk/src/logger.test.ts +149 -0
  1101. package/sdk/src/logger.ts +113 -0
  1102. package/sdk/src/milestone-runner.test.ts +421 -0
  1103. package/sdk/src/model-catalog.ts +70 -0
  1104. package/sdk/src/phase-prompt.test.ts +535 -0
  1105. package/sdk/src/phase-prompt.ts +259 -0
  1106. package/sdk/src/phase-runner-types.test.ts +421 -0
  1107. package/sdk/src/phase-runner.integration.test.ts +377 -0
  1108. package/sdk/src/phase-runner.test.ts +2720 -0
  1109. package/sdk/src/phase-runner.ts +1442 -0
  1110. package/sdk/src/plan-parser.test.ts +579 -0
  1111. package/sdk/src/plan-parser.ts +431 -0
  1112. package/sdk/src/planning-journal.test.ts +70 -0
  1113. package/sdk/src/planning-journal.ts +153 -0
  1114. package/sdk/src/planning-runtime.test.ts +29 -0
  1115. package/sdk/src/planning-runtime.ts +100 -0
  1116. package/sdk/src/prompt-builder.test.ts +318 -0
  1117. package/sdk/src/prompt-builder.ts +218 -0
  1118. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  1119. package/sdk/src/prompt-sanitizer.ts +116 -0
  1120. package/sdk/src/query/QUERY-HANDLERS.md +349 -0
  1121. package/sdk/src/query/active-workstream-store.ts +50 -0
  1122. package/sdk/src/query/agent-failure-classifier.test.ts +157 -0
  1123. package/sdk/src/query/agent-failure-classifier.ts +105 -0
  1124. package/sdk/src/query/audit-open.ts +722 -0
  1125. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  1126. package/sdk/src/query/check-auto-mode.ts +49 -0
  1127. package/sdk/src/query/check-completion.test.ts +113 -0
  1128. package/sdk/src/query/check-completion.ts +182 -0
  1129. package/sdk/src/query/check-decision-coverage.test.ts +519 -0
  1130. package/sdk/src/query/check-decision-coverage.ts +554 -0
  1131. package/sdk/src/query/check-gates.test.ts +103 -0
  1132. package/sdk/src/query/check-gates.ts +112 -0
  1133. package/sdk/src/query/check-ship-ready.test.ts +111 -0
  1134. package/sdk/src/query/check-ship-ready.ts +104 -0
  1135. package/sdk/src/query/check-verification-status.test.ts +143 -0
  1136. package/sdk/src/query/check-verification-status.ts +160 -0
  1137. package/sdk/src/query/command-aliases.generated.ts +156 -0
  1138. package/sdk/src/query/command-catalog.ts +31 -0
  1139. package/sdk/src/query/command-definition.test.ts +47 -0
  1140. package/sdk/src/query/command-definition.ts +70 -0
  1141. package/sdk/src/query/command-family-handlers.ts +117 -0
  1142. package/sdk/src/query/command-manifest.init.ts +24 -0
  1143. package/sdk/src/query/command-manifest.non-family.ts +85 -0
  1144. package/sdk/src/query/command-manifest.phase.ts +16 -0
  1145. package/sdk/src/query/command-manifest.phases.ts +11 -0
  1146. package/sdk/src/query/command-manifest.roadmap.ts +11 -0
  1147. package/sdk/src/query/command-manifest.state.ts +31 -0
  1148. package/sdk/src/query/command-manifest.ts +17 -0
  1149. package/sdk/src/query/command-manifest.types.ts +13 -0
  1150. package/sdk/src/query/command-manifest.validate.ts +11 -0
  1151. package/sdk/src/query/command-manifest.verify.ts +15 -0
  1152. package/sdk/src/query/command-resolution.test.ts +70 -0
  1153. package/sdk/src/query/command-seam-coverage.test.ts +118 -0
  1154. package/sdk/src/query/command-static-catalog-domain.ts +117 -0
  1155. package/sdk/src/query/command-static-catalog-foundation.ts +103 -0
  1156. package/sdk/src/query/command-topology.test.ts +28 -0
  1157. package/sdk/src/query/command-topology.ts +114 -0
  1158. package/sdk/src/query/commands-list.test.ts +36 -0
  1159. package/sdk/src/query/commands-list.ts +19 -0
  1160. package/sdk/src/query/commit.test.ts +485 -0
  1161. package/sdk/src/query/commit.ts +383 -0
  1162. package/sdk/src/query/config-gates.test.ts +89 -0
  1163. package/sdk/src/query/config-gates.ts +69 -0
  1164. package/sdk/src/query/config-mutation.test.ts +598 -0
  1165. package/sdk/src/query/config-mutation.ts +575 -0
  1166. package/sdk/src/query/config-query.test.ts +367 -0
  1167. package/sdk/src/query/config-query.ts +244 -0
  1168. package/sdk/src/query/config-schema.ts +159 -0
  1169. package/sdk/src/query/decisions.test.ts +215 -0
  1170. package/sdk/src/query/decisions.ts +192 -0
  1171. package/sdk/src/query/decomposed-handlers.test.ts +431 -0
  1172. package/sdk/src/query/detect-custom-files.test.ts +115 -0
  1173. package/sdk/src/query/detect-custom-files.ts +96 -0
  1174. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  1175. package/sdk/src/query/detect-phase-type.ts +141 -0
  1176. package/sdk/src/query/docs-init.ts +258 -0
  1177. package/sdk/src/query/fallow-audit.ts +88 -0
  1178. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  1179. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  1180. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  1181. package/sdk/src/query/frontmatter.test.ts +326 -0
  1182. package/sdk/src/query/frontmatter.ts +395 -0
  1183. package/sdk/src/query/helpers.test.ts +615 -0
  1184. package/sdk/src/query/helpers.ts +646 -0
  1185. package/sdk/src/query/index-thin-seam.test.ts +16 -0
  1186. package/sdk/src/query/index.ts +9 -0
  1187. package/sdk/src/query/init-complex.test.ts +616 -0
  1188. package/sdk/src/query/init-complex.ts +799 -0
  1189. package/sdk/src/query/init-progress-precedence.test.ts +177 -0
  1190. package/sdk/src/query/init-workstream-milestone-op.test.ts +321 -0
  1191. package/sdk/src/query/init.test.ts +792 -0
  1192. package/sdk/src/query/init.ts +1262 -0
  1193. package/sdk/src/query/intel.test.ts +90 -0
  1194. package/sdk/src/query/intel.ts +404 -0
  1195. package/sdk/src/query/mutation-event-decorator.test.ts +45 -0
  1196. package/sdk/src/query/mutation-event-decorator.ts +37 -0
  1197. package/sdk/src/query/mutation-event-mapper.test.ts +33 -0
  1198. package/sdk/src/query/mutation-event-mapper.ts +102 -0
  1199. package/sdk/src/query/mvp.test.ts +335 -0
  1200. package/sdk/src/query/mvp.ts +292 -0
  1201. package/sdk/src/query/normalize-query-command.test.ts +102 -0
  1202. package/sdk/src/query/phase-filesystem-adapter.ts +35 -0
  1203. package/sdk/src/query/phase-lifecycle-policy.ts +171 -0
  1204. package/sdk/src/query/phase-lifecycle.test.ts +1750 -0
  1205. package/sdk/src/query/phase-lifecycle.ts +1833 -0
  1206. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  1207. package/sdk/src/query/phase-list-queries.ts +152 -0
  1208. package/sdk/src/query/phase-ready.test.ts +65 -0
  1209. package/sdk/src/query/phase-ready.ts +159 -0
  1210. package/sdk/src/query/phase-roadmap-mutation.ts +77 -0
  1211. package/sdk/src/query/phase.test.ts +651 -0
  1212. package/sdk/src/query/phase.ts +550 -0
  1213. package/sdk/src/query/pipeline.test.ts +169 -0
  1214. package/sdk/src/query/pipeline.ts +243 -0
  1215. package/sdk/src/query/plan-scan.test.ts +35 -0
  1216. package/sdk/src/query/plan-scan.ts +82 -0
  1217. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  1218. package/sdk/src/query/plan-task-structure.ts +63 -0
  1219. package/sdk/src/query/policy-convergence.test.ts +28 -0
  1220. package/sdk/src/query/profile-extract-messages.ts +247 -0
  1221. package/sdk/src/query/profile-output.ts +929 -0
  1222. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  1223. package/sdk/src/query/profile-sample.ts +184 -0
  1224. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  1225. package/sdk/src/query/profile.test.ts +136 -0
  1226. package/sdk/src/query/profile.ts +337 -0
  1227. package/sdk/src/query/progress.test.ts +156 -0
  1228. package/sdk/src/query/progress.ts +566 -0
  1229. package/sdk/src/query/query-cli-adapter.test.ts +79 -0
  1230. package/sdk/src/query/query-cli-adapter.ts +39 -0
  1231. package/sdk/src/query/query-cli-output.test.ts +33 -0
  1232. package/sdk/src/query/query-cli-output.ts +35 -0
  1233. package/sdk/src/query/query-command-diagnosis.test.ts +22 -0
  1234. package/sdk/src/query/query-command-diagnosis.ts +5 -0
  1235. package/sdk/src/query/query-command-resolution-strategy.test.ts +34 -0
  1236. package/sdk/src/query/query-command-resolution-strategy.ts +121 -0
  1237. package/sdk/src/query/query-command-semantics.test.ts +22 -0
  1238. package/sdk/src/query/query-command-semantics.ts +22 -0
  1239. package/sdk/src/query/query-dispatch-contract.ts +30 -0
  1240. package/sdk/src/query/query-dispatch-error-mapper.test.ts +62 -0
  1241. package/sdk/src/query/query-dispatch-error-mapper.ts +5 -0
  1242. package/sdk/src/query/query-dispatch-formatting.test.ts +28 -0
  1243. package/sdk/src/query/query-dispatch-formatting.ts +5 -0
  1244. package/sdk/src/query/query-dispatch-input-validation.test.ts +23 -0
  1245. package/sdk/src/query/query-dispatch-input-validation.ts +5 -0
  1246. package/sdk/src/query/query-dispatch-observability.test.ts +10 -0
  1247. package/sdk/src/query/query-dispatch-observability.ts +6 -0
  1248. package/sdk/src/query/query-dispatch-plan.test.ts +25 -0
  1249. package/sdk/src/query/query-dispatch-plan.ts +5 -0
  1250. package/sdk/src/query/query-dispatch-result-builder.test.ts +16 -0
  1251. package/sdk/src/query/query-dispatch-result-builder.ts +5 -0
  1252. package/sdk/src/query/query-dispatch.test.ts +399 -0
  1253. package/sdk/src/query/query-dispatch.ts +243 -0
  1254. package/sdk/src/query/query-error-details-schema.ts +29 -0
  1255. package/sdk/src/query/query-error-taxonomy.test.ts +39 -0
  1256. package/sdk/src/query/query-error-taxonomy.ts +117 -0
  1257. package/sdk/src/query/query-fallback-bridge-adapter.test.ts +32 -0
  1258. package/sdk/src/query/query-fallback-bridge-adapter.ts +54 -0
  1259. package/sdk/src/query/query-fallback-executor.test.ts +82 -0
  1260. package/sdk/src/query/query-fallback-executor.ts +44 -0
  1261. package/sdk/src/query/query-fallback-output-classifier.test.ts +36 -0
  1262. package/sdk/src/query/query-fallback-output-classifier.ts +31 -0
  1263. package/sdk/src/query/query-fallback-policy.test.ts +13 -0
  1264. package/sdk/src/query/query-fallback-policy.ts +11 -0
  1265. package/sdk/src/query/query-native-dispatch-adapter.ts +16 -0
  1266. package/sdk/src/query/query-policy-capability.test.ts +10 -0
  1267. package/sdk/src/query/query-policy-capability.ts +26 -0
  1268. package/sdk/src/query/query-policy-snapshot.test.ts +9 -0
  1269. package/sdk/src/query/query-registry-capability.test.ts +14 -0
  1270. package/sdk/src/query/query-runtime-context.ts +44 -0
  1271. package/sdk/src/query/query-unknown-command-hints.test.ts +9 -0
  1272. package/sdk/src/query/query-unknown-command-hints.ts +5 -0
  1273. package/sdk/src/query/registry-assembly-descriptor.ts +87 -0
  1274. package/sdk/src/query/registry-assembly-invariants.ts +127 -0
  1275. package/sdk/src/query/registry-assembly.test.ts +138 -0
  1276. package/sdk/src/query/registry-assembly.ts +78 -0
  1277. package/sdk/src/query/registry.test.ts +208 -0
  1278. package/sdk/src/query/registry.ts +142 -0
  1279. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  1280. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  1281. package/sdk/src/query/roadmap-update-plan-progress.test.ts +233 -0
  1282. package/sdk/src/query/roadmap-update-plan-progress.ts +159 -0
  1283. package/sdk/src/query/roadmap.test.ts +1181 -0
  1284. package/sdk/src/query/roadmap.ts +894 -0
  1285. package/sdk/src/query/route-next-action.test.ts +61 -0
  1286. package/sdk/src/query/route-next-action.ts +345 -0
  1287. package/sdk/src/query/schema-detect.ts +189 -0
  1288. package/sdk/src/query/secrets.test.ts +66 -0
  1289. package/sdk/src/query/secrets.ts +43 -0
  1290. package/sdk/src/query/skill-manifest.test.ts +62 -0
  1291. package/sdk/src/query/skill-manifest.ts +216 -0
  1292. package/sdk/src/query/skills.test.ts +234 -0
  1293. package/sdk/src/query/skills.ts +143 -0
  1294. package/sdk/src/query/state-document.test.ts +197 -0
  1295. package/sdk/src/query/state-document.ts +129 -0
  1296. package/sdk/src/query/state-mutation.test.ts +1198 -0
  1297. package/sdk/src/query/state-mutation.ts +1718 -0
  1298. package/sdk/src/query/state-project-load.ts +80 -0
  1299. package/sdk/src/query/state.test.ts +616 -0
  1300. package/sdk/src/query/state.ts +463 -0
  1301. package/sdk/src/query/sub-repos-root.integration.test.ts +79 -0
  1302. package/sdk/src/query/summary.test.ts +95 -0
  1303. package/sdk/src/query/summary.ts +296 -0
  1304. package/sdk/src/query/template.test.ts +180 -0
  1305. package/sdk/src/query/template.ts +242 -0
  1306. package/sdk/src/query/uat.test.ts +77 -0
  1307. package/sdk/src/query/uat.ts +365 -0
  1308. package/sdk/src/query/utils.test.ts +82 -0
  1309. package/sdk/src/query/utils.ts +106 -0
  1310. package/sdk/src/query/validate.test.ts +831 -0
  1311. package/sdk/src/query/validate.ts +952 -0
  1312. package/sdk/src/query/verify.test.ts +414 -0
  1313. package/sdk/src/query/verify.ts +692 -0
  1314. package/sdk/src/query/websearch.test.ts +31 -0
  1315. package/sdk/src/query/websearch.ts +82 -0
  1316. package/sdk/src/query/workspace.test.ts +120 -0
  1317. package/sdk/src/query/workspace.ts +145 -0
  1318. package/sdk/src/query/workstream-inventory.ts +195 -0
  1319. package/sdk/src/query/workstream.test.ts +153 -0
  1320. package/sdk/src/query/workstream.ts +324 -0
  1321. package/sdk/src/query/worktree.ts +39 -0
  1322. package/sdk/src/query-command-executor.ts +31 -0
  1323. package/sdk/src/query-execution-policy.test.ts +52 -0
  1324. package/sdk/src/query-execution-policy.ts +46 -0
  1325. package/sdk/src/query-failure-classification.test.ts +23 -0
  1326. package/sdk/src/query-failure-classification.ts +42 -0
  1327. package/sdk/src/query-hotpath-methods.ts +48 -0
  1328. package/sdk/src/query-native-direct-adapter.test.ts +35 -0
  1329. package/sdk/src/query-native-direct-adapter.ts +70 -0
  1330. package/sdk/src/query-native-hotpath-adapter.test.ts +43 -0
  1331. package/sdk/src/query-native-hotpath-adapter.ts +45 -0
  1332. package/sdk/src/query-raw-output-projection.test.ts +39 -0
  1333. package/sdk/src/query-raw-output-projection.ts +74 -0
  1334. package/sdk/src/query-runtime-bridge.test.ts +150 -0
  1335. package/sdk/src/query-runtime-bridge.ts +215 -0
  1336. package/sdk/src/query-runtime-seam-coverage.test.ts +20 -0
  1337. package/sdk/src/query-sdd-tools-path.ts +1 -0
  1338. package/sdk/src/query-sdd-tools-runtime.ts +89 -0
  1339. package/sdk/src/query-subprocess-adapter.test.ts +84 -0
  1340. package/sdk/src/query-subprocess-adapter.ts +146 -0
  1341. package/sdk/src/query-tools-error-factory.test.ts +35 -0
  1342. package/sdk/src/query-tools-error-factory.ts +76 -0
  1343. package/sdk/src/research-gate.test.ts +190 -0
  1344. package/sdk/src/research-gate.ts +94 -0
  1345. package/sdk/src/runtime-bridge-options.test.ts +33 -0
  1346. package/sdk/src/runtime-gate.test.ts +84 -0
  1347. package/sdk/src/runtime-gate.ts +52 -0
  1348. package/sdk/src/sdd-tools-error.test.ts +21 -0
  1349. package/sdk/src/sdd-tools-error.ts +65 -0
  1350. package/sdk/src/sdd-tools.test.ts +472 -0
  1351. package/sdk/src/sdd-tools.ts +237 -0
  1352. package/sdk/src/sdd-transport-policy.test.ts +34 -0
  1353. package/sdk/src/sdd-transport-policy.ts +48 -0
  1354. package/sdk/src/sdd-transport.test.ts +292 -0
  1355. package/sdk/src/sdd-transport.ts +117 -0
  1356. package/sdk/src/sdk-package-compatibility.test.ts +97 -0
  1357. package/sdk/src/sdk-package-compatibility.ts +141 -0
  1358. package/sdk/src/session-runner.test.ts +164 -0
  1359. package/sdk/src/session-runner.ts +327 -0
  1360. package/sdk/src/tool-scoping.test.ts +160 -0
  1361. package/sdk/src/tool-scoping.ts +61 -0
  1362. package/sdk/src/types.ts +927 -0
  1363. package/sdk/src/workflow-agent-skills-consistency.test.ts +98 -0
  1364. package/sdk/src/workstream-name-policy.ts +24 -0
  1365. package/sdk/src/workstream-utils.ts +21 -0
  1366. package/sdk/src/ws-flag.test.ts +285 -0
  1367. package/sdk/src/ws-transport.test.ts +161 -0
  1368. package/sdk/src/ws-transport.ts +93 -0
  1369. package/sdk/tsconfig.json +20 -0
  1370. package/commands/sdd/add-backlog.md +0 -76
  1371. package/commands/sdd/add-phase.md +0 -43
  1372. package/commands/sdd/add-todo.md +0 -47
  1373. package/commands/sdd/check-todos.md +0 -45
  1374. package/commands/sdd/do.md +0 -30
  1375. package/commands/sdd/insert-phase.md +0 -32
  1376. package/commands/sdd/join-discord.md +0 -18
  1377. package/commands/sdd/list-phase-assumptions.md +0 -46
  1378. package/commands/sdd/list-workspaces.md +0 -19
  1379. package/commands/sdd/new-workspace.md +0 -44
  1380. package/commands/sdd/next.md +0 -24
  1381. package/commands/sdd/note.md +0 -34
  1382. package/commands/sdd/plan-milestone-gaps.md +0 -34
  1383. package/commands/sdd/plant-seed.md +0 -28
  1384. package/commands/sdd/reapply-patches.md +0 -123
  1385. package/commands/sdd/remove-phase.md +0 -31
  1386. package/commands/sdd/remove-workspace.md +0 -26
  1387. package/commands/sdd/research-phase.md +0 -195
  1388. package/commands/sdd/session-report.md +0 -19
  1389. package/commands/sdd/set-profile.md +0 -12
  1390. package/scripts/sync-upstream.sh +0 -56
  1391. package/sdd/commands/sdd/workstreams.md +0 -63
  1392. package/sdd/workflows/research-phase.md +0 -82
@@ -3,9 +3,27 @@
3
3
  */
4
4
 
5
5
  const fs = require('fs');
6
+ const os = require('os');
6
7
  const path = require('path');
7
- const { execSync, execFileSync, spawnSync } = require('child_process');
8
- const { MODEL_PROFILES } = require('./model-profiles.cjs');
8
+ const { execGit, platformWriteSync, platformReadSync, platformEnsureDir } = require('./shell-command-projection.cjs');
9
+ const { MODEL_PROFILES, AGENT_TO_PHASE_TYPE, VALID_PHASE_TYPES, AGENT_DEFAULT_TIERS, VALID_AGENT_TIERS, nextTier } = require('./model-profiles.cjs');
10
+ const { MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, KNOWN_RUNTIMES, RUNTIMES_WITH_REASONING_EFFORT } = require('./model-catalog.cjs');
11
+ const {
12
+ resolveWorktreeContext,
13
+ parseWorktreePorcelain: parseWorktreePorcelainPolicy,
14
+ planWorktreePrune,
15
+ executeWorktreePrunePlan,
16
+ inspectWorktreeHealth,
17
+ } = require('./worktree-safety.cjs');
18
+ // Compatibility shim: new imports should use planning-workspace.cjs directly.
19
+ const {
20
+ planningDir,
21
+ planningRoot,
22
+ planningPaths,
23
+ withPlanningLock,
24
+ getActiveWorkstream,
25
+ setActiveWorkstream,
26
+ } = require('./planning-workspace.cjs');
9
27
 
10
28
  // ─── Path helpers ────────────────────────────────────────────────────────────
11
29
 
@@ -89,7 +107,9 @@ function findProjectRoot(startDir) {
89
107
  if (fs.existsSync(parentPlanning) && fs.statSync(parentPlanning).isDirectory()) {
90
108
  const configPath = path.join(parentPlanning, 'config.json');
91
109
  try {
92
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
110
+ const raw = platformReadSync(configPath);
111
+ if (raw === null) throw new Error('missing');
112
+ const config = JSON.parse(raw);
93
113
  const subRepos = config.sub_repos || config.planning?.sub_repos || [];
94
114
 
95
115
  // Check explicit sub_repos list
@@ -129,14 +149,25 @@ function findProjectRoot(startDir) {
129
149
  * @param {number} opts.maxAgeMs - max age in ms before removal (default: 5 min)
130
150
  * @param {boolean} opts.dirsOnly - if true, only remove directories (default: false)
131
151
  */
152
+ /**
153
+ * Dedicated SDD temp directory: path.join(os.tmpdir(), 'sdd').
154
+ * Created on first use. Keeps SDD temp files isolated from the system
155
+ * temp directory so reap scans only SDD files (#1975).
156
+ */
157
+ const SDD_TEMP_DIR = path.join(require('os').tmpdir(), 'sdd');
158
+
159
+ function ensureSddTempDir() {
160
+ platformEnsureDir(SDD_TEMP_DIR);
161
+ }
162
+
132
163
  function reapStaleTempFiles(prefix = 'sdd-', { maxAgeMs = 5 * 60 * 1000, dirsOnly = false } = {}) {
133
164
  try {
134
- const tmpDir = require('os').tmpdir();
165
+ ensureSddTempDir();
135
166
  const now = Date.now();
136
- const entries = fs.readdirSync(tmpDir);
167
+ const entries = fs.readdirSync(SDD_TEMP_DIR);
137
168
  for (const entry of entries) {
138
169
  if (!entry.startsWith(prefix)) continue;
139
- const fullPath = path.join(tmpDir, entry);
170
+ const fullPath = path.join(SDD_TEMP_DIR, entry);
140
171
  try {
141
172
  const stat = fs.statSync(fullPath);
142
173
  if (now - stat.mtimeMs > maxAgeMs) {
@@ -165,8 +196,9 @@ function output(result, raw, rawValue) {
165
196
  // Write to tmpfile and output the path prefixed with @file: so callers can detect it.
166
197
  if (json.length > 50000) {
167
198
  reapStaleTempFiles();
168
- const tmpPath = path.join(require('os').tmpdir(), `sdd-${Date.now()}.json`);
169
- fs.writeFileSync(tmpPath, json, 'utf-8');
199
+ ensureSddTempDir();
200
+ const tmpPath = path.join(SDD_TEMP_DIR, `sdd-${Date.now()}.json`);
201
+ platformWriteSync(tmpPath, json);
170
202
  data = '@file:' + tmpPath;
171
203
  } else {
172
204
  data = json;
@@ -179,91 +211,283 @@ function output(result, raw, rawValue) {
179
211
  fs.writeSync(1, data);
180
212
  }
181
213
 
182
- function error(message) {
183
- fs.writeSync(2, 'Error: ' + message + '\n');
214
+ /**
215
+ * Frozen enum of typed reason codes used by error() for structured errors.
216
+ * Each subcommand contributes its own codes; the enum exists so tests can
217
+ * assert against typed values instead of grepping stderr (#2974).
218
+ *
219
+ * Adding a new code:
220
+ * - Pick a snake_case lowercase value (the JSON wire form)
221
+ * - Group by subsystem prefix (CONFIG_*, SDK_*, etc)
222
+ * - Pass it to error(msg, ERROR_REASON.NEW_CODE) at the call site
223
+ */
224
+ const ERROR_REASON = Object.freeze({
225
+ // config-get / config-set
226
+ CONFIG_KEY_NOT_FOUND: 'config_key_not_found',
227
+ CONFIG_NO_FILE: 'config_no_file',
228
+ CONFIG_PARSE_FAILED: 'config_parse_failed',
229
+ CONFIG_INVALID_KEY: 'config_invalid_key',
230
+ // SDK / sdd-tools dispatch
231
+ SDK_FAIL_FAST: 'sdk_fail_fast',
232
+ SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
233
+ SDK_MISSING_ARG: 'sdk_missing_arg',
234
+ // workflow / phase
235
+ PHASE_NOT_FOUND: 'phase_not_found',
236
+ SUMMARY_NO_PLANNING: 'summary_no_planning',
237
+ // graphify
238
+ GRAPHIFY_NO_GRAPH: 'graphify_no_graph',
239
+ GRAPHIFY_INVALID_QUERY: 'graphify_invalid_query',
240
+ // hooks
241
+ HOOKS_OPT_OUT: 'hooks_opt_out',
242
+ // security-scan
243
+ SECURITY_SCAN_FAILED: 'security_scan_failed',
244
+ // generic
245
+ USAGE: 'usage',
246
+ UNKNOWN: 'unknown',
247
+ });
248
+
249
+ /**
250
+ * Process-level flag: when true, error() emits structured JSON to stderr
251
+ * instead of plain "Error: <message>" text. Set by sdd-tools.cjs when the
252
+ * CLI is invoked with `--json-errors`. Tests opt in to typed-IR error
253
+ * assertions by passing that flag and parsing the JSON.
254
+ *
255
+ * Default off so existing callers and human operators keep their plain-text
256
+ * diagnostics. The structured form is opt-in for tooling and tests (#2974).
257
+ */
258
+ let _jsonErrorMode = false;
259
+ function setJsonErrorMode(v) { _jsonErrorMode = !!v; }
260
+ function getJsonErrorMode() { return _jsonErrorMode; }
261
+
262
+ /**
263
+ * Emit an error and exit. When the second argument is provided it must be
264
+ * a value from ERROR_REASON; tests can assert on `result.reason`. When the
265
+ * process is in JSON-error mode, stderr receives `{ ok: false, reason,
266
+ * message }` so callers can parse it; otherwise stderr keeps the plain
267
+ * text form for human operators.
268
+ */
269
+ function error(message, reason = ERROR_REASON.UNKNOWN) {
270
+ if (_jsonErrorMode) {
271
+ const payload = JSON.stringify({ ok: false, reason, message }) + '\n';
272
+ fs.writeSync(2, payload);
273
+ } else {
274
+ fs.writeSync(2, 'Error: ' + message + '\n');
275
+ }
184
276
  process.exit(1);
185
277
  }
186
278
 
187
279
  // ─── File & Config utilities ──────────────────────────────────────────────────
188
280
 
189
- function safeReadFile(filePath) {
190
- try {
191
- return fs.readFileSync(filePath, 'utf-8');
192
- } catch {
193
- return null;
281
+ /**
282
+ * Canonical config defaults. Single source of truth — imported by config.cjs and verify.cjs.
283
+ */
284
+ const CONFIG_DEFAULTS = {
285
+ model_profile: 'balanced',
286
+ commit_docs: true,
287
+ search_gitignored: false,
288
+ branching_strategy: 'none',
289
+ phase_branch_template: 'sdd/phase-{phase}-{slug}',
290
+ milestone_branch_template: 'sdd/{milestone}-{slug}',
291
+ quick_branch_template: null,
292
+ research: true,
293
+ plan_checker: true,
294
+ verifier: true,
295
+ nyquist_validation: true,
296
+ ai_integration_phase: true,
297
+ parallelization: true,
298
+ brave_search: false,
299
+ firecrawl: false,
300
+ exa_search: false,
301
+ text_mode: false, // when true, use plain-text numbered lists instead of AskUserQuestion menus
302
+ sub_repos: [],
303
+ resolve_model_ids: false, // false: return alias as-is | true: map to full Claude model ID | "omit": return '' (runtime uses its default)
304
+ context_window: 200000, // default 200k; set to 1000000 for Opus/Sonnet 4.6 1M models
305
+ phase_naming: 'sequential', // 'sequential' (default, auto-increment) or 'custom' (arbitrary string IDs)
306
+ project_code: null, // optional short prefix for phase dirs (e.g., 'CK' → 'CK-01-foundation')
307
+ subagent_timeout: 300000, // 5 min default; increase for large codebases or slower models (ms)
308
+ security_enforcement: true, // workflow.security_enforcement — threat-model-anchored security verification via /sdd:secure-phase
309
+ security_asvs_level: 1, // workflow.security_asvs_level — OWASP ASVS verification level (1=opportunistic, 2=standard, 3=comprehensive)
310
+ security_block_on: 'high', // workflow.security_block_on — minimum severity that blocks phase advancement ('high' | 'medium' | 'low')
311
+ post_planning_gaps: true, // workflow.post_planning_gaps — unified post-planning gap report (#2493): scan REQUIREMENTS.md + CONTEXT.md decisions vs all PLAN.md files
312
+ };
313
+
314
+ /**
315
+ * Deep-merge two plain config objects. `overlay` wins on key conflict.
316
+ * Explicit `null` in overlay overrides base (null means "unset this key").
317
+ * Arrays are replaced, not merged. Non-object primitives use overlay value.
318
+ *
319
+ * Note: `undefined` in overlay is treated as "no value provided" and falls
320
+ * back to base (preserves inheritance). Explicit `null` overrides base.
321
+ */
322
+ function _deepMergeConfig(base, overlay) {
323
+ if (overlay === null || overlay === undefined) return overlay;
324
+ if (typeof base !== 'object' || typeof overlay !== 'object') return overlay;
325
+ const result = { ...base };
326
+ for (const key of Object.keys(overlay)) {
327
+ if (overlay[key] !== null && typeof overlay[key] === 'object' && !Array.isArray(overlay[key])) {
328
+ result[key] = _deepMergeConfig(base[key] ?? {}, overlay[key]);
329
+ } else {
330
+ result[key] = overlay[key];
331
+ }
194
332
  }
333
+ return result;
195
334
  }
196
335
 
197
- function loadConfig(cwd) {
198
- const configPath = path.join(cwd, '.planning', 'config.json');
199
- const defaults = {
200
- model_profile: 'balanced',
201
- commit_docs: true,
202
- search_gitignored: false,
203
- branching_strategy: 'none',
204
- phase_branch_template: 'sdd/phase-{phase}-{slug}',
205
- milestone_branch_template: 'sdd/{milestone}-{slug}',
206
- quick_branch_template: null,
207
- research: true,
208
- plan_checker: true,
209
- verifier: true,
210
- nyquist_validation: true,
211
- parallelization: true,
212
- brave_search: false,
213
- firecrawl: false,
214
- exa_search: false,
215
- text_mode: false, // when true, use plain-text numbered lists instead of AskUserQuestion menus
216
- sub_repos: [],
217
- resolve_model_ids: false, // false: return alias as-is | true: map to full Claude model ID | "omit": return '' (runtime uses its default)
218
- context_window: 200000, // default 200k; set to 1000000 for Opus/Sonnet 4.6 1M models
219
- phase_naming: 'sequential', // 'sequential' (default, auto-increment) or 'custom' (arbitrary string IDs)
220
- };
336
+ function loadConfig(cwd, options = {}) {
337
+ const activeWorkstream = Object.prototype.hasOwnProperty.call(options, 'workstream')
338
+ ? options.workstream
339
+ : (process.env.SDD_WORKSTREAM || null);
340
+ // When SDD_WORKSTREAM is set, load root config first so workstream config
341
+ // can inherit from it. This prevents users from duplicating model_overrides,
342
+ // workflow.*, etc. across every workstream config (#2714).
343
+ const ws = activeWorkstream;
344
+ let rootParsed = null;
345
+ if (ws) {
346
+ const rootConfigPath = path.join(planningRoot(cwd), 'config.json');
347
+ try {
348
+ const raw = platformReadSync(rootConfigPath);
349
+ if (raw === null) throw new Error('missing');
350
+ rootParsed = JSON.parse(raw);
351
+ if (Object.prototype.hasOwnProperty.call(rootParsed, 'branching_strategy')) {
352
+ if (!rootParsed.git) rootParsed.git = {};
353
+ if (rootParsed.git.branching_strategy === undefined) {
354
+ rootParsed.git.branching_strategy = rootParsed.branching_strategy;
355
+ }
356
+ delete rootParsed.branching_strategy;
357
+ try { platformWriteSync(rootConfigPath, JSON.stringify(rootParsed, null, 2)); } catch {}
358
+ }
359
+ } catch {
360
+ // Root config missing or unparseable — workstream config stands alone
361
+ }
362
+ }
363
+
364
+ const configPath = path.join(planningDir(cwd, ws), 'config.json');
365
+ const defaults = CONFIG_DEFAULTS;
221
366
 
222
367
  try {
223
- const raw = fs.readFileSync(configPath, 'utf-8');
224
- const parsed = JSON.parse(raw);
368
+ const raw = platformReadSync(configPath);
369
+ if (raw === null) throw new Error('missing');
370
+ // `fileData` is the parsed content of the config.json file on disk — used
371
+ // for migrations and writes so we never persist merged values back to disk.
372
+ const fileData = JSON.parse(raw);
225
373
 
226
374
  // Migrate deprecated "depth" key to "granularity" with value mapping
227
- if ('depth' in parsed && !('granularity' in parsed)) {
375
+ if ('depth' in fileData && !('granularity' in fileData)) {
228
376
  const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
229
- parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
230
- delete parsed.depth;
231
- try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch { /* intentionally empty */ }
377
+ fileData.granularity = depthToGranularity[fileData.depth] || fileData.depth;
378
+ delete fileData.depth;
379
+ try { platformWriteSync(configPath, JSON.stringify(fileData, null, 2)); } catch { /* intentionally empty */ }
232
380
  }
233
381
 
234
382
  // Auto-detect and sync sub_repos: scan for child directories with .git
235
383
  let configDirty = false;
236
384
 
237
- // Migrate legacy "multiRepo: true" boolean → sub_repos array
238
- if (parsed.multiRepo === true && !parsed.sub_repos && !parsed.planning?.sub_repos) {
385
+ // Migrate legacy "multiRepo: true" boolean → planning.sub_repos array.
386
+ // Canonical location is planning.sub_repos (#2561); writing to top-level
387
+ // would be flagged as unknown by the validator below (#2638).
388
+ if (fileData.multiRepo === true && !fileData.sub_repos && !fileData.planning?.sub_repos) {
239
389
  const detected = detectSubRepos(cwd);
240
390
  if (detected.length > 0) {
241
- parsed.sub_repos = detected;
242
- if (!parsed.planning) parsed.planning = {};
243
- parsed.planning.commit_docs = false;
244
- delete parsed.multiRepo;
391
+ if (!fileData.planning) fileData.planning = {};
392
+ fileData.planning.sub_repos = detected;
393
+ fileData.planning.commit_docs = false;
394
+ delete fileData.multiRepo;
245
395
  configDirty = true;
246
396
  }
247
397
  }
248
398
 
249
- // Keep sub_repos in sync with actual filesystem
250
- const currentSubRepos = parsed.sub_repos || parsed.planning?.sub_repos || [];
399
+ // Self-heal legacy/buggy installs: strip any stale top-level sub_repos,
400
+ // preserving its value as the planning.sub_repos seed if that slot is empty.
401
+ if (Object.prototype.hasOwnProperty.call(fileData, 'sub_repos')) {
402
+ if (!fileData.planning) fileData.planning = {};
403
+ if (!fileData.planning.sub_repos) {
404
+ fileData.planning.sub_repos = fileData.sub_repos;
405
+ }
406
+ delete fileData.sub_repos;
407
+ configDirty = true;
408
+ }
409
+
410
+ // #3523 — Migrate legacy top-level branching_strategy → git.branching_strategy.
411
+ // Canonical location is git.branching_strategy (per config-schema.cjs); writing
412
+ // at the top level trips the unknown-key warning even though loadConfig:485 actively
413
+ // reads it via the nested fallback. This migration mirrors the multiRepo → sub_repos
414
+ // precedent: graft then delete so the warning never fires again on this project.
415
+ // The nested value wins if already set (matches SDK mergeDefaults precedence, PR #3116).
416
+ if (Object.prototype.hasOwnProperty.call(fileData, 'branching_strategy')) {
417
+ if (!fileData.git) fileData.git = {};
418
+ if (fileData.git.branching_strategy === undefined) {
419
+ fileData.git.branching_strategy = fileData.branching_strategy;
420
+ }
421
+ delete fileData.branching_strategy;
422
+ configDirty = true;
423
+ }
424
+
425
+ // Keep planning.sub_repos in sync with actual filesystem
426
+ const currentSubRepos = fileData.planning?.sub_repos || [];
251
427
  if (Array.isArray(currentSubRepos) && currentSubRepos.length > 0) {
252
428
  const detected = detectSubRepos(cwd);
253
429
  if (detected.length > 0) {
254
430
  const sorted = [...currentSubRepos].sort();
255
431
  if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
256
- parsed.sub_repos = detected;
432
+ if (!fileData.planning) fileData.planning = {};
433
+ fileData.planning.sub_repos = detected;
257
434
  configDirty = true;
258
435
  }
259
436
  }
260
437
  }
261
438
 
262
- // Persist sub_repos changes (migration or sync)
439
+ // Persist sub_repos changes (migration or sync) — write only the on-disk
440
+ // file contents, never the merged result, to avoid polluting workstream configs.
263
441
  if (configDirty) {
264
- try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch {}
442
+ try { platformWriteSync(configPath, JSON.stringify(fileData, null, 2)); } catch {}
265
443
  }
266
444
 
445
+ // Now apply root→workstream inheritance. `parsed` is the effective config
446
+ // used for value extraction below; fileData is kept for disk writes only.
447
+ const parsed = rootParsed ? _deepMergeConfig(rootParsed, fileData) : fileData;
448
+
449
+ // Warn about unrecognized top-level keys so users don't silently lose config.
450
+ // Derived from config-set's VALID_CONFIG_KEYS (canonical source) plus internal-only
451
+ // keys that loadConfig handles but config-set doesn't expose. This avoids maintaining
452
+ // a hardcoded duplicate that drifts when new config keys are added.
453
+ // DYNAMIC_KEY_PATTERNS supplies topLevel for each pattern so adding a new
454
+ // dynamic-pattern namespace to config-schema.cjs automatically updates this set
455
+ // — no more drift between the read side and the write side (#2687).
456
+ const { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS } = require('./config-schema.cjs');
457
+ const KNOWN_TOP_LEVEL = new Set([
458
+ // Extract top-level key names from dot-notation paths (e.g., 'workflow.research' → 'workflow')
459
+ ...[...VALID_CONFIG_KEYS].map(k => k.split('.')[0]),
460
+ // Dynamic-pattern top-level containers (e.g. review, model_profile_overrides)
461
+ ...DYNAMIC_KEY_PATTERNS.map(p => p.topLevel),
462
+ // Internal keys loadConfig reads but config-set doesn't expose
463
+ 'model_overrides', 'context_window', 'resolve_model_ids', 'claude_md_path',
464
+ // Deprecated keys (still accepted for migration, not in config-set)
465
+ // 'branching_strategy' is kept here as a safety net: it is migrated to
466
+ // git.branching_strategy above (#3523), but on the first read of a root
467
+ // config that feeds into a workstream merge, `parsed` may still surface it.
468
+ 'depth', 'multiRepo', 'branching_strategy',
469
+ ]);
470
+ const unknownKeys = Object.keys(parsed).filter(k => !KNOWN_TOP_LEVEL.has(k));
471
+ if (unknownKeys.length > 0) {
472
+ // Deduplicate: a single `init phase-op N` invocation calls loadConfig twice
473
+ // (once for the sub-command setup, once for git-config resolution). Guard with
474
+ // a module-level Set so the same message never fires more than once per process.
475
+ const warnKey = unknownKeys.join(',');
476
+ if (!_warnedUnknownConfigKeys.has(warnKey)) {
477
+ _warnedUnknownConfigKeys.add(warnKey);
478
+ process.stderr.write(
479
+ `sdd-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} — these will be ignored\n`
480
+ );
481
+ }
482
+ }
483
+
484
+ // #2517 — Validate runtime/tier values for keys that loadConfig handles but
485
+ // can be edited directly into config.json (bypassing config-set's enum check).
486
+ // This catches typos like `runtime: "codx"` and `model_profile_overrides.codex.banana`
487
+ // at read time without rejecting back-compat values from new runtimes
488
+ // (review findings #10, #13).
489
+ _warnUnknownProfileOverrides(parsed, '.planning/config.json');
490
+
267
491
  const get = (key, nested) => {
268
492
  if (parsed[key] !== undefined) return parsed[key];
269
493
  if (nested && parsed[nested.section] && parsed[nested.section][nested.field] !== undefined) {
@@ -299,158 +523,122 @@ function loadConfig(cwd) {
299
523
  plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
300
524
  verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
301
525
  nyquist_validation: get('nyquist_validation', { section: 'workflow', field: 'nyquist_validation' }) ?? defaults.nyquist_validation,
526
+ post_planning_gaps: get('post_planning_gaps', { section: 'workflow', field: 'post_planning_gaps' }) ?? defaults.post_planning_gaps,
302
527
  parallelization,
303
528
  brave_search: get('brave_search') ?? defaults.brave_search,
304
529
  firecrawl: get('firecrawl') ?? defaults.firecrawl,
305
530
  exa_search: get('exa_search') ?? defaults.exa_search,
531
+ tdd_mode: get('tdd_mode', { section: 'workflow', field: 'tdd_mode' }) ?? false,
532
+ mvp_mode: get('mvp_mode', { section: 'workflow', field: 'mvp_mode' }) ?? false,
306
533
  text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode,
534
+ auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false,
535
+ _auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
536
+ mode: get('mode') ?? 'interactive',
307
537
  sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
308
538
  resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
309
539
  context_window: get('context_window') ?? defaults.context_window,
310
540
  phase_naming: get('phase_naming') ?? defaults.phase_naming,
541
+ project_code: get('project_code') ?? defaults.project_code,
542
+ subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
311
543
  model_overrides: parsed.model_overrides || null,
544
+ // #3023 — per-phase-type model map. Six named slots
545
+ // (planning/discuss/research/execution/verification/completion).
546
+ // Resolves between per-agent override and profile-derived tier in
547
+ // resolveModelInternal. Defaults to null so configs without it
548
+ // behave exactly as today.
549
+ models: parsed.models || null,
550
+ // #3024 — dynamic routing block. When `enabled: true`, the
551
+ // resolveModelForTier() resolver picks tier_models[default_tier]
552
+ // for the agent and escalates one tier per attempt up to
553
+ // max_escalations. Disabled by default for backward compat.
554
+ dynamic_routing: parsed.dynamic_routing || null,
555
+ // #2517 — runtime-aware profiles. `runtime` defaults to null (back-compat).
556
+ // When null, resolveModelInternal preserves today's Claude-native behavior.
557
+ // NOTE: `runtime` and `model_profile_overrides` are intentionally read
558
+ // flat-only (not via `get()` with a workflow.X fallback) — they are
559
+ // top-level keys per docs/CONFIGURATION.md. The lighter-touch decision
560
+ // here was to document the constraint rather than introduce nested
561
+ // resolution edge cases for two new keys (review finding #9). The
562
+ // schema validation in `_warnUnknownProfileOverrides` runs against the
563
+ // raw `parsed` blob, so direct `.planning/config.json` edits surface
564
+ // unknown runtime/tier names at load time, not silently (review finding #10).
565
+ runtime: parsed.runtime || null,
566
+ model_profile_overrides: parsed.model_profile_overrides || null,
312
567
  agent_skills: parsed.agent_skills || {},
568
+ manager: parsed.manager || {},
569
+ response_language: get('response_language') || null,
570
+ claude_md_path: get('claude_md_path') || null,
571
+ claude_md_assembly: parsed.claude_md_assembly || null,
313
572
  };
314
573
  } catch {
315
- return defaults;
316
- }
317
- }
318
-
319
- // ─── Git utilities ────────────────────────────────────────────────────────────
320
-
321
- function isGitIgnored(cwd, targetPath) {
322
- try {
323
- // --no-index checks .gitignore rules regardless of whether the file is tracked.
324
- // Without it, git check-ignore returns "not ignored" for tracked files even when
325
- // .gitignore explicitly lists them — a common source of confusion when .planning/
326
- // was committed before being added to .gitignore.
327
- // Use execFileSync (array args) to prevent shell interpretation of special characters
328
- // in file paths — avoids command injection via crafted path names.
329
- execFileSync('git', ['check-ignore', '-q', '--no-index', '--', targetPath], {
330
- cwd,
331
- stdio: 'pipe',
332
- });
333
- return true;
334
- } catch {
335
- return false;
336
- }
337
- }
338
-
339
- // ─── Markdown normalization ─────────────────────────────────────────────────
340
-
341
- /**
342
- * Normalize markdown to fix common markdownlint violations.
343
- * Applied at write points so SDD-generated .planning/ files are IDE-friendly.
344
- *
345
- * Rules enforced:
346
- * MD022 — Blank lines around headings
347
- * MD031 — Blank lines around fenced code blocks
348
- * MD032 — Blank lines around lists
349
- * MD012 — No multiple consecutive blank lines (collapsed to 2 max)
350
- * MD047 — Files end with a single newline
351
- */
352
- function normalizeMd(content) {
353
- if (!content || typeof content !== 'string') return content;
354
-
355
- // Normalize line endings to LF for consistent processing
356
- let text = content.replace(/\r\n/g, '\n');
357
-
358
- const lines = text.split('\n');
359
- const result = [];
360
-
361
- for (let i = 0; i < lines.length; i++) {
362
- const line = lines[i];
363
- const prev = i > 0 ? lines[i - 1] : '';
364
- const prevTrimmed = prev.trimEnd();
365
- const trimmed = line.trimEnd();
366
-
367
- // MD022: Blank line before headings (skip first line and frontmatter delimiters)
368
- if (/^#{1,6}\s/.test(trimmed) && i > 0 && prevTrimmed !== '' && prevTrimmed !== '---') {
369
- result.push('');
370
- }
371
-
372
- // MD031: Blank line before fenced code blocks
373
- if (/^```/.test(trimmed) && i > 0 && prevTrimmed !== '' && !isInsideFencedBlock(lines, i)) {
374
- result.push('');
375
- }
376
-
377
- // MD032: Blank line before lists (- item, * item, N. item, - [ ] item)
378
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 &&
379
- prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) &&
380
- prevTrimmed !== '---') {
381
- result.push('');
382
- }
383
-
384
- result.push(line);
385
-
386
- // MD022: Blank line after headings
387
- if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1) {
388
- const next = lines[i + 1];
389
- if (next !== undefined && next.trimEnd() !== '') {
390
- result.push('');
574
+ // Fall back to ~/.sdd/defaults.json only for truly pre-project contexts (#1683)
575
+ // If .planning/ exists, the project is initialized — just missing config.json.
576
+ // When SDD_WORKSTREAM is set and root config was loaded, the workstream config
577
+ // doesn't exist — treat root config as the effective config for this workstream.
578
+ if (fs.existsSync(planningDir(cwd, ws))) {
579
+ if (rootParsed) {
580
+ // Workstream has no config.json: re-parse using root config as the sole source.
581
+ // Keep env immutable by explicitly reloading with workstream context cleared.
582
+ return loadConfig(cwd, { workstream: null });
391
583
  }
584
+ return defaults;
392
585
  }
393
-
394
- // MD031: Blank line after closing fenced code blocks
395
- if (/^```\s*$/.test(trimmed) && isClosingFence(lines, i) && i < lines.length - 1) {
396
- const next = lines[i + 1];
397
- if (next !== undefined && next.trimEnd() !== '') {
398
- result.push('');
399
- }
400
- }
401
-
402
- // MD032: Blank line after last list item in a block
403
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i < lines.length - 1) {
404
- const next = lines[i + 1];
405
- if (next !== undefined && next.trimEnd() !== '' &&
406
- !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(next) &&
407
- !/^\s/.test(next)) {
408
- // Only add blank line if next line is not a continuation/indented line
409
- result.push('');
410
- }
586
+ try {
587
+ const home = process.env.SDD_HOME || os.homedir();
588
+ const globalDefaultsPath = path.join(home, '.sdd', 'defaults.json');
589
+ const raw = platformReadSync(globalDefaultsPath);
590
+ if (raw === null) throw new Error('missing');
591
+ const globalDefaults = JSON.parse(raw);
592
+ return {
593
+ ...defaults,
594
+ model_profile: globalDefaults.model_profile ?? defaults.model_profile,
595
+ commit_docs: globalDefaults.commit_docs ?? defaults.commit_docs,
596
+ research: globalDefaults.research ?? defaults.research,
597
+ plan_checker: globalDefaults.plan_checker ?? defaults.plan_checker,
598
+ verifier: globalDefaults.verifier ?? defaults.verifier,
599
+ nyquist_validation: globalDefaults.nyquist_validation ?? defaults.nyquist_validation,
600
+ post_planning_gaps: globalDefaults.post_planning_gaps
601
+ ?? globalDefaults.workflow?.post_planning_gaps
602
+ ?? defaults.post_planning_gaps,
603
+ parallelization: globalDefaults.parallelization ?? defaults.parallelization,
604
+ text_mode: globalDefaults.text_mode ?? defaults.text_mode,
605
+ resolve_model_ids: globalDefaults.resolve_model_ids ?? defaults.resolve_model_ids,
606
+ context_window: globalDefaults.context_window ?? defaults.context_window,
607
+ subagent_timeout: globalDefaults.subagent_timeout ?? defaults.subagent_timeout,
608
+ model_overrides: globalDefaults.model_overrides || null,
609
+ models: globalDefaults.models || null,
610
+ dynamic_routing: globalDefaults.dynamic_routing || null,
611
+ agent_skills: globalDefaults.agent_skills || {},
612
+ response_language: globalDefaults.response_language || null,
613
+ };
614
+ } catch {
615
+ return defaults;
411
616
  }
412
617
  }
413
-
414
- text = result.join('\n');
415
-
416
- // MD012: Collapse 3+ consecutive blank lines to 2
417
- text = text.replace(/\n{3,}/g, '\n\n');
418
-
419
- // MD047: Ensure file ends with exactly one newline
420
- text = text.replace(/\n*$/, '\n');
421
-
422
- return text;
423
618
  }
424
619
 
425
- /** Check if line index i is inside an already-open fenced code block */
426
- function isInsideFencedBlock(lines, i) {
427
- let fenceCount = 0;
428
- for (let j = 0; j < i; j++) {
429
- if (/^```/.test(lines[j].trimEnd())) fenceCount++;
430
- }
431
- return fenceCount % 2 === 1;
432
- }
620
+ // ─── Git utilities ────────────────────────────────────────────────────────────
433
621
 
434
- /** Check if a ``` line is a closing fence (odd number of fences up to and including this one) */
435
- function isClosingFence(lines, i) {
436
- let fenceCount = 0;
437
- for (let j = 0; j <= i; j++) {
438
- if (/^```/.test(lines[j].trimEnd())) fenceCount++;
439
- }
440
- return fenceCount % 2 === 0;
441
- }
622
+ // Module-level deduplication for unknown-key warnings (#3523).
623
+ // A single `init phase-op N` call invokes loadConfig more than once; this Set
624
+ // prevents the same warning from being echoed on each invocation.
625
+ const _warnedUnknownConfigKeys = new Set();
442
626
 
443
- function execGit(cwd, args) {
444
- const result = spawnSync('git', args, {
445
- cwd,
446
- stdio: 'pipe',
447
- encoding: 'utf-8',
448
- });
449
- return {
450
- exitCode: result.status ?? 1,
451
- stdout: (result.stdout ?? '').toString().trim(),
452
- stderr: (result.stderr ?? '').toString().trim(),
453
- };
627
+ const _gitIgnoredCache = new Map();
628
+
629
+ function isGitIgnored(cwd, targetPath) {
630
+ const key = cwd + '::' + targetPath;
631
+ if (_gitIgnoredCache.has(key)) return _gitIgnoredCache.get(key);
632
+ // --no-index checks .gitignore rules regardless of whether the file is tracked.
633
+ // Without it, git check-ignore returns "not ignored" for tracked files even when
634
+ // .gitignore explicitly lists them — a common source of confusion when .planning/
635
+ // was committed before being added to .gitignore.
636
+ // Array args (via the seam) prevent shell interpretation of special characters in
637
+ // file paths — avoids command injection via crafted path names.
638
+ const result = execGit(['check-ignore', '-q', '--no-index', '--', targetPath], { cwd });
639
+ const ignored = result.exitCode === 0;
640
+ _gitIgnoredCache.set(key, ignored);
641
+ return ignored;
454
642
  }
455
643
 
456
644
  // ─── Common path helpers ──────────────────────────────────────────────────────
@@ -461,155 +649,58 @@ function execGit(cwd, args) {
461
649
  * Returns the main worktree path, or cwd if not in a worktree.
462
650
  */
463
651
  function resolveWorktreeRoot(cwd) {
464
- // If the current directory already has its own .planning/, respect it.
465
- // This handles linked worktrees with independent planning state (e.g., Conductor workspaces).
466
- if (fs.existsSync(path.join(cwd, '.planning'))) {
467
- return cwd;
468
- }
469
-
470
- // Check if we're in a linked worktree
471
- const gitDir = execGit(cwd, ['rev-parse', '--git-dir']);
472
- const commonDir = execGit(cwd, ['rev-parse', '--git-common-dir']);
473
-
474
- if (gitDir.exitCode !== 0 || commonDir.exitCode !== 0) return cwd;
475
-
476
- // In a linked worktree, .git is a file pointing to .git/worktrees/<name>
477
- // and git-common-dir points to the main repo's .git directory
478
- const gitDirResolved = path.resolve(cwd, gitDir.stdout);
479
- const commonDirResolved = path.resolve(cwd, commonDir.stdout);
480
-
481
- if (gitDirResolved !== commonDirResolved) {
482
- // We're in a linked worktree — resolve main worktree root
483
- // The common dir is the main repo's .git, so its parent is the main worktree root
484
- return path.dirname(commonDirResolved);
485
- }
486
-
487
- return cwd;
488
- }
489
-
490
- /**
491
- * Acquire a file-based lock for .planning/ writes.
492
- * Prevents concurrent worktrees from corrupting shared planning files.
493
- * Lock is auto-released after the callback completes.
494
- */
495
- function withPlanningLock(cwd, fn) {
496
- const lockPath = path.join(planningDir(cwd), '.lock');
497
- const lockTimeout = 10000; // 10 seconds
498
- const retryDelay = 100;
499
- const start = Date.now();
500
-
501
- // Ensure .planning/ exists
502
- try { fs.mkdirSync(planningDir(cwd), { recursive: true }); } catch { /* ok */ }
503
-
504
- while (Date.now() - start < lockTimeout) {
505
- try {
506
- // Atomic create — fails if file exists
507
- fs.writeFileSync(lockPath, JSON.stringify({
508
- pid: process.pid,
509
- cwd,
510
- acquired: new Date().toISOString(),
511
- }), { flag: 'wx' });
512
-
513
- // Lock acquired — run the function
514
- try {
515
- return fn();
516
- } finally {
517
- try { fs.unlinkSync(lockPath); } catch { /* already released */ }
518
- }
519
- } catch (err) {
520
- if (err.code === 'EEXIST') {
521
- // Lock exists — check if stale (>30s old)
522
- try {
523
- const stat = fs.statSync(lockPath);
524
- if (Date.now() - stat.mtimeMs > 30000) {
525
- fs.unlinkSync(lockPath);
526
- continue; // retry
527
- }
528
- } catch { continue; }
529
-
530
- // Wait and retry
531
- spawnSync('sleep', ['0.1'], { stdio: 'ignore' });
532
- continue;
533
- }
534
- throw err;
535
- }
536
- }
537
- // Timeout — force acquire (stale lock recovery)
538
- try { fs.unlinkSync(lockPath); } catch { /* ok */ }
539
- return fn();
652
+ // Omit execGit so worktree-safety uses its own execGitDefault that wrapper
653
+ // delegates to the seam and derives the `timedOut` field that pruneResult
654
+ // branches on below.
655
+ const context = resolveWorktreeContext(cwd, {
656
+ existsSync: fs.existsSync,
657
+ });
658
+ return context.effectiveRoot;
540
659
  }
541
660
 
542
661
  /**
543
- * Get the .planning directory path, workstream-aware.
544
- * When a workstream is active (via explicit ws arg or SDD_WORKSTREAM env var),
545
- * returns `.planning/workstreams/{ws}/`. Otherwise returns `.planning/`.
662
+ * Parse `git worktree list --porcelain` output into an array of
663
+ * { path, branch } objects. Entries with a detached HEAD (no branch line)
664
+ * are skipped because we cannot safely reason about their merge status.
546
665
  *
547
- * @param {string} cwd - project root
548
- * @param {string} [ws] - explicit workstream name; if omitted, checks SDD_WORKSTREAM env var
666
+ * @param {string} porcelain - raw output from git worktree list --porcelain
667
+ * @returns {{ path: string, branch: string }[]}
549
668
  */
550
- function planningDir(cwd, ws) {
551
- if (ws === undefined) ws = process.env.SDD_WORKSTREAM || null;
552
- if (!ws) return path.join(cwd, '.planning');
553
- return path.join(cwd, '.planning', 'workstreams', ws);
554
- }
555
-
556
- /** Always returns the root .planning/ path, ignoring workstreams. For shared resources. */
557
- function planningRoot(cwd) {
558
- return path.join(cwd, '.planning');
669
+ function parseWorktreePorcelain(porcelain) {
670
+ return parseWorktreePorcelainPolicy(porcelain);
559
671
  }
560
672
 
561
673
  /**
562
- * Get common .planning file paths, workstream-aware.
563
- * Scoped paths (state, roadmap, phases, requirements) resolve to the active workstream.
564
- * Shared paths (project, config) always resolve to the root .planning/.
565
- */
566
- function planningPaths(cwd, ws) {
567
- const base = planningDir(cwd, ws);
568
- const root = path.join(cwd, '.planning');
569
- return {
570
- planning: base,
571
- state: path.join(base, 'STATE.md'),
572
- roadmap: path.join(base, 'ROADMAP.md'),
573
- project: path.join(root, 'PROJECT.md'),
574
- config: path.join(root, 'config.json'),
575
- phases: path.join(base, 'phases'),
576
- requirements: path.join(base, 'REQUIREMENTS.md'),
577
- };
578
- }
579
-
580
- // ─── Active Workstream Detection ─────────────────────────────────────────────
581
-
582
- /**
583
- * Get the active workstream name from .planning/active-workstream file.
584
- * Returns null if no active workstream or file doesn't exist.
674
+ * Clear stale worktree metadata references via `git worktree prune`.
675
+ *
676
+ * Destructive linked-worktree removal is disabled by default for safety.
677
+ *
678
+ * @param {string} repoRoot - absolute path to the main (or any) worktree of
679
+ * the repository; used as `cwd` for git commands.
680
+ * @returns {string[]} list of worktree paths that were removed (always empty)
585
681
  */
586
- function getActiveWorkstream(cwd) {
587
- const filePath = path.join(planningRoot(cwd), 'active-workstream');
682
+ function pruneOrphanedWorktrees(repoRoot) {
588
683
  try {
589
- const name = fs.readFileSync(filePath, 'utf-8').trim();
590
- if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) return null;
591
- const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
592
- if (!fs.existsSync(wsDir)) return null;
593
- return name;
594
- } catch {
595
- return null;
596
- }
684
+ const plan = planWorktreePrune(
685
+ repoRoot,
686
+ { allowDestructive: false },
687
+ { parseWorktreePorcelain }
688
+ );
689
+ const pruneResult = executeWorktreePrunePlan(plan);
690
+ if (pruneResult && pruneResult.timedOut) {
691
+ // AC2: surface structured warning instead of silently swallowing the timeout.
692
+ // Uses process.stderr.write to match the [sdd-tools] WARNING prefix style.
693
+ process.stderr.write(
694
+ '[sdd-tools] WARNING: worktree health check degraded' +
695
+ ' — git worktree prune timed out after 10s.' +
696
+ ' Orphaned worktree metadata may remain until the next successful run.\n'
697
+ );
698
+ }
699
+ } catch { /* never crash the caller */ }
700
+ return [];
597
701
  }
598
702
 
599
- /**
600
- * Set the active workstream. Pass null to clear.
601
- */
602
- function setActiveWorkstream(cwd, name) {
603
- const filePath = path.join(planningRoot(cwd), 'active-workstream');
604
- if (!name) {
605
- try { fs.unlinkSync(filePath); } catch {}
606
- return;
607
- }
608
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
609
- throw new Error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
610
- }
611
- fs.writeFileSync(filePath, name + '\n', 'utf-8');
612
- }
703
+ // ─── Planning workspace (pathing + active workstream + lock) moved to planning-workspace.cjs ───
613
704
 
614
705
  // ─── Phase utilities ──────────────────────────────────────────────────────────
615
706
 
@@ -619,11 +710,16 @@ function escapeRegex(value) {
619
710
 
620
711
  function normalizePhaseName(phase) {
621
712
  const str = String(phase);
713
+ // Strip optional project_code prefix (e.g., 'CK-01' → '01')
714
+ const stripped = str.replace(/^[A-Z]{1,6}-(?=\d)/, '');
622
715
  // Standard numeric phases: 1, 01, 12A, 12.1
623
- const match = str.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
716
+ const match = stripped.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
624
717
  if (match) {
625
718
  const padded = match[1].padStart(2, '0');
626
- const letter = match[2] ? match[2].toUpperCase() : '';
719
+ // Preserve original case of letter suffix (#1962).
720
+ // Uppercasing causes directory/roadmap mismatches on case-sensitive filesystems
721
+ // (e.g., "16c" in ROADMAP.md → directory "16C-name" → progress can't match).
722
+ const letter = match[2] || '';
627
723
  const decimal = match[3] || '';
628
724
  return padded + letter + decimal;
629
725
  }
@@ -631,9 +727,56 @@ function normalizePhaseName(phase) {
631
727
  return str;
632
728
  }
633
729
 
730
+ /**
731
+ * Render a regex source fragment matching a phase number against ROADMAP/STATE
732
+ * prose regardless of zero-padding on either side. Skills pass the resolved
733
+ * padded form (`02.7`), but human-authored ROADMAP prose is conventionally
734
+ * un-padded (`### Phase 2.7:`); a naive `escapeRegex(phaseNum)` fragment never
735
+ * matches when the two diverge. Strips leading zeros from the integer part
736
+ * before re-emitting with a `0*` prefix, so the fragment matches both `2.7`
737
+ * and `02.7` (and `002.7`).
738
+ *
739
+ * Falls back to `escapeRegex(phaseNum)` for non-numeric IDs (custom project
740
+ * codes like `PROJ-42`) so callers can substitute it unconditionally.
741
+ *
742
+ * See #3537 — wired into every ROADMAP-prose regex builder.
743
+ */
744
+ function phaseMarkdownRegexSource(phaseNum) {
745
+ const stripped = String(phaseNum).replace(/^[A-Z]{1,6}-(?=\d)/i, '');
746
+ const match = stripped.match(/^0*(\d+)([A-Z])?((?:\.\d+)*)$/i);
747
+ if (!match) return escapeRegex(phaseNum);
748
+
749
+ const integer = match[1].replace(/^0+/, '') || '0';
750
+ const letter = match[2] ? escapeRegex(match[2]) : '';
751
+ const decimal = match[3] ? escapeRegex(match[3]) : '';
752
+ return `0*${escapeRegex(integer)}${letter}${decimal}`;
753
+ }
754
+
755
+ /**
756
+ * #3599: when the caller passed a project-code-prefixed ID like `PROJ-42`,
757
+ * return the exact-escaped form so the caller can search the ROADMAP for
758
+ * `### Phase PROJ-42:` BEFORE falling back to the padding-tolerant numeric
759
+ * form. Returns null when the input has no project-code prefix — in that
760
+ * case the numeric form (`phaseMarkdownRegexSource`) is the only thing the
761
+ * caller needs.
762
+ *
763
+ * Two-pass at the call site preserves the #3537 contract (`CK-01` directory
764
+ * names mapping to `Phase 1:` prose) while letting `PROJ-42` resolve to its
765
+ * own prefixed heading without cross-matching a bare `### Phase 42:` that
766
+ * happens to share the trailing integer.
767
+ */
768
+ function phaseMarkdownRegexSourceExact(phaseNum) {
769
+ const raw = String(phaseNum);
770
+ if (!/^[A-Z]{1,6}-(?=\d)/i.test(raw)) return null;
771
+ return escapeRegex(raw);
772
+ }
773
+
634
774
  function comparePhaseNum(a, b) {
635
- const pa = String(a).match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
636
- const pb = String(b).match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
775
+ // Strip optional project_code prefix before comparing (e.g., 'CK-01-name' → '01-name')
776
+ const sa = String(a).replace(/^[A-Z]{1,6}-/, '');
777
+ const sb = String(b).replace(/^[A-Z]{1,6}-/, '');
778
+ const pa = sa.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
779
+ const pb = sb.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
637
780
  // If either is non-numeric (custom ID), fall back to string comparison
638
781
  if (!pa || !pb) return String(a).localeCompare(String(b));
639
782
  const intDiff = parseInt(pa[1], 10) - parseInt(pb[1], 10);
@@ -660,20 +803,61 @@ function comparePhaseNum(a, b) {
660
803
  return 0;
661
804
  }
662
805
 
806
+ /**
807
+ * Extract the phase token from a directory name.
808
+ * Supports: '01-name', '1009A-name', '999.6-name', 'CK-01-name', 'PROJ-42-name'.
809
+ * Returns the token portion (e.g. '01', '1009A', '999.6', 'PROJ-42') or the full name if no separator.
810
+ */
811
+ function extractPhaseToken(dirName) {
812
+ // Try project-code-prefixed numeric: CK-01-name → CK-01, CK-01A.2-name → CK-01A.2
813
+ const codePrefixed = dirName.match(/^([A-Z]{1,6}-\d+[A-Z]?(?:\.\d+)*)(?:-|$)/i);
814
+ if (codePrefixed) return codePrefixed[1];
815
+ // Try plain numeric: 01-name, 1009A-name, 999.6-name
816
+ const numeric = dirName.match(/^(\d+[A-Z]?(?:\.\d+)*)(?:-|$)/i);
817
+ if (numeric) return numeric[1];
818
+ // Custom IDs: PROJ-42-name → everything before the last segment that looks like a name
819
+ const custom = dirName.match(/^([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*)(?:-[a-z]|$)/i);
820
+ if (custom) return custom[1];
821
+ return dirName;
822
+ }
823
+
824
+ /**
825
+ * Check if a directory name's phase token matches the normalized phase exactly.
826
+ * Case-insensitive comparison for the token portion.
827
+ */
828
+ function phaseTokenMatches(dirName, normalized) {
829
+ const token = extractPhaseToken(dirName);
830
+ if (token.toUpperCase() === normalized.toUpperCase()) return true;
831
+ // Strip optional project_code prefix from dir and retry
832
+ const stripped = dirName.replace(/^[A-Z]{1,6}-(?=\d)/i, '');
833
+ if (stripped !== dirName) {
834
+ const strippedToken = extractPhaseToken(stripped);
835
+ if (strippedToken.toUpperCase() === normalized.toUpperCase()) return true;
836
+ }
837
+ return false;
838
+ }
839
+
840
+ function extractCanonicalPlanId(filename) {
841
+ const base = filename.replace(/-PLAN\.md$/i, '').replace(/-SUMMARY\.md$/i, '').replace(/\.md$/i, '');
842
+ const parts = base.split('-').filter(Boolean);
843
+ const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
844
+ const phaseIdx = parts.findIndex(p => tokenRe.test(p));
845
+ if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
846
+ return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
847
+ }
848
+ return base;
849
+ }
850
+
663
851
  function searchPhaseInDir(baseDir, relBase, normalized) {
664
852
  try {
665
853
  const dirs = readSubdirectories(baseDir, true);
666
- // Match: starts with normalized (numeric) OR contains normalized as prefix segment (custom ID)
667
- const match = dirs.find(d => {
668
- if (d.startsWith(normalized)) return true;
669
- // For custom IDs like PROJ-42, match case-insensitively
670
- if (d.toUpperCase().startsWith(normalized.toUpperCase())) return true;
671
- return false;
672
- });
854
+ // Match: exact phase token comparison (not prefix matching)
855
+ const match = dirs.find(d => phaseTokenMatches(d, normalized));
673
856
  if (!match) return null;
674
857
 
675
- // Extract phase number and name — supports both numeric (01-name) and custom (PROJ-42-name)
676
- const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i)
858
+ // Extract phase number and name — supports numeric (01-name), project-code-prefixed (CK-01-name), and custom (PROJ-42-name)
859
+ const dirMatch = match.match(/^(?:[A-Z]{1,6}-)(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i)
860
+ || match.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i)
677
861
  || match.match(/^([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*)-(.+)/i)
678
862
  || [null, match, null];
679
863
  const phaseNumber = dirMatch ? dirMatch[1] : normalized;
@@ -684,11 +868,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
684
868
  const summaries = unsortedSummaries.sort();
685
869
 
686
870
  const completedPlanIds = new Set(
687
- summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
871
+ summaries.flatMap(s => {
872
+ const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
873
+ const canonical = extractCanonicalPlanId(s);
874
+ return canonical === exact ? [exact] : [exact, canonical];
875
+ })
688
876
  );
689
877
  const incompletePlans = plans.filter(p => {
690
878
  const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
691
- return !completedPlanIds.has(planId);
879
+ const canonical = extractCanonicalPlanId(p);
880
+ return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
692
881
  });
693
882
 
694
883
  return {
@@ -817,8 +1006,8 @@ function extractCurrentMilestone(content, cwd) {
817
1006
  let version = null;
818
1007
  try {
819
1008
  const statePath = path.join(planningDir(cwd), 'STATE.md');
820
- if (fs.existsSync(statePath)) {
821
- const stateRaw = fs.readFileSync(statePath, 'utf-8');
1009
+ const stateRaw = platformReadSync(statePath);
1010
+ if (stateRaw !== null) {
822
1011
  const milestoneMatch = stateRaw.match(/^milestone:\s*(.+)/m);
823
1012
  if (milestoneMatch) {
824
1013
  version = milestoneMatch[1].trim();
@@ -850,21 +1039,42 @@ function extractCurrentMilestone(content, cwd) {
850
1039
 
851
1040
  const sectionStart = sectionMatch.index;
852
1041
 
853
- // Find the end: next milestone heading at same or higher level, or EOF
1042
+ // Find the end: next milestone heading at same or higher level, or EOF.
854
1043
  // Milestone headings look like: ## v2.0, ## Roadmap v2.0, ## ✅ v1.0, etc.
1044
+ // Scan line-by-line so that heading-like lines inside fenced code blocks
1045
+ // (``` or ~~~) are not mistaken for milestone boundaries. See #2787.
855
1046
  const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
856
1047
  const restContent = content.slice(sectionStart + sectionMatch[0].length);
1048
+ // Exclude phase headings (e.g. "### Phase 12: v1.0 Tech-Debt Closure") from
1049
+ // being treated as milestone boundaries just because they mention vX.Y in
1050
+ // the title. Phase headings always start with the literal `Phase `. See #2619.
857
1051
  const nextMilestonePattern = new RegExp(
858
- `^#{1,${headingLevel}}\\s+(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
859
- 'mi'
1052
+ `^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
1053
+ 'i'
860
1054
  );
861
- const nextMatch = restContent.match(nextMilestonePattern);
862
1055
 
863
- let sectionEnd;
864
- if (nextMatch) {
865
- sectionEnd = sectionStart + sectionMatch[0].length + nextMatch.index;
866
- } else {
867
- sectionEnd = content.length;
1056
+ let sectionEnd = content.length;
1057
+ let fenceChar = null;
1058
+ let fenceLen = 0;
1059
+ let charOffset = 0;
1060
+ for (const line of restContent.split('\n')) {
1061
+ const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
1062
+ if (fenceMatch) {
1063
+ const char = fenceMatch[1][0];
1064
+ const len = fenceMatch[1].length;
1065
+ const trailing = fenceMatch[2] || '';
1066
+ if (!fenceChar) {
1067
+ fenceChar = char;
1068
+ fenceLen = len;
1069
+ } else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
1070
+ fenceChar = null;
1071
+ fenceLen = 0;
1072
+ }
1073
+ } else if (!fenceChar && nextMilestonePattern.test(line)) {
1074
+ sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
1075
+ break;
1076
+ }
1077
+ charOffset += line.length + 1;
868
1078
  }
869
1079
 
870
1080
  // Return everything before the current milestone section (non-milestone content
@@ -903,10 +1113,16 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
903
1113
  if (!fs.existsSync(roadmapPath)) return null;
904
1114
 
905
1115
  try {
906
- const content = extractCurrentMilestone(fs.readFileSync(roadmapPath, 'utf-8'), cwd);
907
- const escapedPhase = escapeRegex(phaseNum.toString());
908
- // Match both numeric (Phase 1:) and custom (Phase PROJ-42:) headers
909
- const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
1116
+ const roadmapRaw = platformReadSync(roadmapPath);
1117
+ if (roadmapRaw === null) throw new Error('missing');
1118
+ const content = extractCurrentMilestone(roadmapRaw, cwd);
1119
+ // #3537: route through canonical padding-tolerant fragment. The prior
1120
+ // hand-rolled `isNumeric` branch only stripped padding on integer-only
1121
+ // ids and missed decimal padding (`02.7` against `Phase 2.7:` headings).
1122
+ const phasePattern = new RegExp(
1123
+ `#{2,4}\\s*Phase\\s+${phaseMarkdownRegexSource(phaseNum)}:\\s*([^\\n]+)`,
1124
+ 'i'
1125
+ );
910
1126
  const headerMatch = content.match(phasePattern);
911
1127
  if (!headerMatch) return null;
912
1128
 
@@ -939,9 +1155,15 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
939
1155
  * sdd-tools.cjs lives at <configDir>/sdd/bin/sdd-tools.cjs,
940
1156
  * so agents/ is at <configDir>/agents/.
941
1157
  *
1158
+ * SDD_AGENTS_DIR env var overrides the default path. Used in tests and for
1159
+ * installs where the agents directory is not co-located with sdd-tools.cjs.
1160
+ *
942
1161
  * @returns {string} Absolute path to the agents directory
943
1162
  */
944
1163
  function getAgentsDir() {
1164
+ if (process.env.SDD_AGENTS_DIR) {
1165
+ return process.env.SDD_AGENTS_DIR;
1166
+ }
945
1167
  // __dirname is sdd/bin/lib/ → go up 3 levels to configDir
946
1168
  return path.join(__dirname, '..', '..', '..', 'agents');
947
1169
  }
@@ -950,6 +1172,9 @@ function getAgentsDir() {
950
1172
  * Check which SDD agents are installed on disk.
951
1173
  * Returns an object with installation status and details.
952
1174
  *
1175
+ * Recognises both standard format (sdd-planner.md) and Copilot format
1176
+ * (sdd-planner.agent.md). Copilot renames agent files during install (#1512).
1177
+ *
953
1178
  * @returns {{ agents_installed: boolean, missing_agents: string[], installed_agents: string[], agents_dir: string }}
954
1179
  */
955
1180
  function checkAgentsInstalled() {
@@ -968,8 +1193,10 @@ function checkAgentsInstalled() {
968
1193
  }
969
1194
 
970
1195
  for (const agent of expectedAgents) {
1196
+ // Check both .md (standard) and .agent.md (Copilot) file formats.
971
1197
  const agentFile = path.join(agentsDir, `${agent}.md`);
972
- if (fs.existsSync(agentFile)) {
1198
+ const agentFileCopilot = path.join(agentsDir, `${agent}.agent.md`);
1199
+ if (fs.existsSync(agentFile) || fs.existsSync(agentFileCopilot)) {
973
1200
  installed.push(agent);
974
1201
  } else {
975
1202
  missing.push(agent);
@@ -986,43 +1213,202 @@ function checkAgentsInstalled() {
986
1213
 
987
1214
  // ─── Model alias resolution ───────────────────────────────────────────────────
988
1215
 
1216
+ const RUNTIME_OVERRIDE_TIERS = new Set(['opus', 'sonnet', 'haiku']);
1217
+ const _warnedConfigKeys = new Set();
1218
+
1219
+ function _warnUnknownProfileOverrides(parsed, configLabel) {
1220
+ if (!parsed || typeof parsed !== 'object') return;
1221
+
1222
+ const runtime = parsed.runtime;
1223
+ if (runtime && typeof runtime === 'string' && !KNOWN_RUNTIMES.has(runtime)) {
1224
+ const key = `${configLabel}::runtime::${runtime}`;
1225
+ if (!_warnedConfigKeys.has(key)) {
1226
+ _warnedConfigKeys.add(key);
1227
+ try {
1228
+ process.stderr.write(
1229
+ `sdd: warning — config key "runtime" has unknown value "${runtime}". ` +
1230
+ `Known runtimes: ${[...KNOWN_RUNTIMES].sort().join(', ')}. ` +
1231
+ `Resolution will fall back to safe defaults. (#2517)\n`
1232
+ );
1233
+ } catch { /* stderr might be closed in some test harnesses */ }
1234
+ }
1235
+ }
1236
+
1237
+ const overrides = parsed.model_profile_overrides;
1238
+ if (!overrides || typeof overrides !== 'object') return;
1239
+ for (const [overrideRuntime, tierMap] of Object.entries(overrides)) {
1240
+ if (!KNOWN_RUNTIMES.has(overrideRuntime)) {
1241
+ const key = `${configLabel}::override-runtime::${overrideRuntime}`;
1242
+ if (!_warnedConfigKeys.has(key)) {
1243
+ _warnedConfigKeys.add(key);
1244
+ try {
1245
+ process.stderr.write(
1246
+ `sdd: warning — model_profile_overrides.${overrideRuntime}.* uses ` +
1247
+ `unknown runtime "${overrideRuntime}". Known runtimes: ` +
1248
+ `${[...KNOWN_RUNTIMES].sort().join(', ')}. (#2517)\n`
1249
+ );
1250
+ } catch { /* ok */ }
1251
+ }
1252
+ }
1253
+ if (!tierMap || typeof tierMap !== 'object') continue;
1254
+ for (const tierName of Object.keys(tierMap)) {
1255
+ if (!RUNTIME_OVERRIDE_TIERS.has(tierName)) {
1256
+ const key = `${configLabel}::override-tier::${overrideRuntime}.${tierName}`;
1257
+ if (!_warnedConfigKeys.has(key)) {
1258
+ _warnedConfigKeys.add(key);
1259
+ try {
1260
+ process.stderr.write(
1261
+ `sdd: warning — model_profile_overrides.${overrideRuntime}.${tierName} ` +
1262
+ `uses unknown tier "${tierName}". Allowed tiers: opus, sonnet, haiku. (#2517)\n`
1263
+ );
1264
+ } catch { /* ok */ }
1265
+ }
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // Internal helper exposed for tests so per-process warning state can be reset
1272
+ // between cases that intentionally exercise the warning path repeatedly.
1273
+ function _resetRuntimeWarningCacheForTests() {
1274
+ _warnedConfigKeys.clear();
1275
+ }
1276
+
989
1277
  /**
990
- * Map short model aliases to full model IDs.
991
- * Updated each release to match current model versions.
992
- * Users can override with model_overrides in config.json for custom/latest models.
1278
+ * #2517 Resolve the runtime-aware tier entry for (runtime, tier).
1279
+ *
1280
+ * Single source of truth shared by core.cjs (resolveModelInternal /
1281
+ * resolveReasoningEffortInternal) and bin/install.js (Codex/OpenCode TOML emit
1282
+ * paths). Always merges built-in defaults with user overrides at the field
1283
+ * level so partial overrides keep the unspecified fields:
1284
+ *
1285
+ * `{ codex: { opus: "gpt-5-pro" } }` keeps reasoning_effort: 'xhigh'
1286
+ * `{ codex: { opus: { reasoning_effort: 'low' } } }` keeps model: 'gpt-5.4'
1287
+ *
1288
+ * Without this field-merge, the documented string-shorthand example silently
1289
+ * dropped reasoning_effort and a partial-object override silently dropped the
1290
+ * model — both reported as critical findings in the #2609 review.
1291
+ *
1292
+ * Inputs:
1293
+ * - runtime: string (e.g. 'codex', 'claude', 'opencode')
1294
+ * - tier: 'opus' | 'sonnet' | 'haiku'
1295
+ * - overrides: optional `model_profile_overrides` blob (may be null/undefined)
1296
+ *
1297
+ * Returns `{ model: string, reasoning_effort?: string } | null`.
993
1298
  */
994
- const MODEL_ALIAS_MAP = {
995
- 'opus': 'claude-opus-4-0',
996
- 'sonnet': 'claude-sonnet-4-5',
997
- 'haiku': 'claude-haiku-3-5',
998
- };
1299
+ function resolveTierEntry({ runtime, tier, overrides }) {
1300
+ if (!runtime || !tier) return null;
1301
+
1302
+ const builtin = RUNTIME_PROFILE_MAP[runtime]?.[tier] || null;
1303
+ const userRaw = overrides?.[runtime]?.[tier];
1304
+
1305
+ // String shorthand from CONFIGURATION.md examples — `{ codex: { opus: "gpt-5-pro" } }`.
1306
+ // Treat as `{ model: "gpt-5-pro" }` so the field-merge below still preserves
1307
+ // reasoning_effort from the built-in defaults.
1308
+ let userEntry = null;
1309
+ if (userRaw) {
1310
+ userEntry = typeof userRaw === 'string' ? { model: userRaw } : userRaw;
1311
+ }
1312
+
1313
+ if (!builtin && !userEntry) return null;
1314
+ // Field-merge: user fields win, built-in fills the gaps.
1315
+ return { ...(builtin || {}), ...(userEntry || {}) };
1316
+ }
1317
+
1318
+ /**
1319
+ * Convenience wrapper used by resolveModelInternal / resolveReasoningEffortInternal.
1320
+ * Pulls runtime + overrides out of a loaded config and delegates to resolveTierEntry.
1321
+ */
1322
+ function _resolveRuntimeTier(config, tier) {
1323
+ return resolveTierEntry({
1324
+ runtime: config.runtime,
1325
+ tier,
1326
+ overrides: config.model_profile_overrides,
1327
+ });
1328
+ }
999
1329
 
1000
1330
  function resolveModelInternal(cwd, agentType) {
1001
1331
  const config = loadConfig(cwd);
1002
1332
 
1003
- // Check per-agent override first — always respected regardless of resolve_model_ids.
1333
+ // 1. Per-agent override — always respected; highest precedence.
1004
1334
  // Users who set fully-qualified model IDs (e.g., "openai/gpt-5.4") get exactly that.
1005
1335
  const override = config.model_overrides?.[agentType];
1006
1336
  if (override) {
1007
1337
  return override;
1008
1338
  }
1009
1339
 
1010
- // resolve_model_ids: "omit" return empty string so the runtime uses its configured
1011
- // default model. For non-Claude runtimes (OpenCode, Codex, etc.) that don't recognize
1012
- // Claude aliases (opus/sonnet/haiku/inherit). Set automatically during install. See #1156.
1340
+ // 2. Compute the tier (opus/sonnet/haiku/inherit) for this agent.
1341
+ //
1342
+ // #3023: phase-type slot can override the profile-derived tier.
1343
+ // Precedence: per-agent override (above) > phase-type slot > profile.
1344
+ // Phase-type values are tier aliases (opus/sonnet/haiku/inherit) — same
1345
+ // shape as model_profile output — so the runtime-resolution chain
1346
+ // (step 3), resolve_model_ids handling (step 4), and profile lookup
1347
+ // (step 5) all stay correct without further branching.
1348
+ const profile = String(config.model_profile || 'balanced').toLowerCase();
1349
+ const agentModels = MODEL_PROFILES[agentType];
1350
+ const phaseType = AGENT_TO_PHASE_TYPE[agentType];
1351
+ const phaseTypeTier = (phaseType && config.models && typeof config.models === 'object')
1352
+ ? config.models[phaseType]
1353
+ : undefined;
1354
+ // Only honor phase-type tier if it's one of the recognized aliases.
1355
+ // Anything else falls through to profile lookup so a typo doesn't
1356
+ // silently break tier resolution.
1357
+ const VALID_TIERS = new Set(['opus', 'sonnet', 'haiku', 'inherit']);
1358
+ // Resolve tier: phase-type wins when valid; else profile-derived; else
1359
+ // (when profile === 'inherit') propagate inherit so the later short-
1360
+ // circuit fires. CR Major (#3030): a config like
1361
+ // { model_profile: 'inherit', models: { execution: 'opus' } }
1362
+ // must honor the phase-type opus, not return 'inherit'. Synthesizing
1363
+ // tier='inherit' only when there's no phase-type override keeps the
1364
+ // original inherit semantics intact while letting a valid phase-type
1365
+ // tier win.
1366
+ const tier = (phaseTypeTier && VALID_TIERS.has(phaseTypeTier))
1367
+ ? phaseTypeTier
1368
+ : (profile === 'inherit'
1369
+ ? 'inherit'
1370
+ : (agentModels ? (agentModels[profile] || agentModels['balanced']) : null));
1371
+
1372
+ // 3. Runtime-aware resolution (#2517) — only when `runtime` is explicitly set
1373
+ // to a non-Claude runtime. `runtime: "claude"` is the implicit default and is
1374
+ // treated as a no-op here so it does not silently override `resolve_model_ids:
1375
+ // "omit"` (review finding #4). Deliberate ordering for non-Claude runtimes:
1376
+ // explicit opt-in beats `resolve_model_ids: "omit"` so users on Codex installs
1377
+ // that auto-set "omit" can still flip on tiered behavior by setting runtime
1378
+ // alone. Gate on tier !== 'inherit' (not profile !== 'inherit') so a
1379
+ // valid phase-type tier flips runtime resolution on even when the
1380
+ // profile is inherit.
1381
+ if (config.runtime && config.runtime !== 'claude' && tier && tier !== 'inherit') {
1382
+ const entry = _resolveRuntimeTier(config, tier);
1383
+ if (entry?.model) return entry.model;
1384
+ // Unknown runtime with no user-supplied overrides — fall through to Claude-safe
1385
+ // default rather than emit an ID the runtime can't accept.
1386
+ }
1387
+
1388
+ // 4. resolve_model_ids: "omit" — return empty string so the runtime uses its
1389
+ // configured default model. For non-Claude runtimes (OpenCode, Codex, etc.) that
1390
+ // don't recognize Claude aliases. Set automatically during install. See #1156.
1013
1391
  if (config.resolve_model_ids === 'omit') {
1014
1392
  return '';
1015
1393
  }
1016
1394
 
1017
- // Fall back to profile lookup
1018
- const profile = String(config.model_profile || 'balanced').toLowerCase();
1019
- const agentModels = MODEL_PROFILES[agentType];
1020
- if (!agentModels) return 'sonnet';
1021
- if (profile === 'inherit') return 'inherit';
1022
- const alias = agentModels[profile] || agentModels['balanced'] || 'sonnet';
1023
-
1024
- // resolve_model_ids: true map alias to full Claude model ID
1025
- // Prevents 404s when the Task tool passes aliases directly to the API
1395
+ // 5. Profile lookup (Claude-native default).
1396
+ if (!agentModels) {
1397
+ return profile === 'quality' ? 'opus'
1398
+ : profile === 'budget' ? 'haiku'
1399
+ : profile === 'inherit' ? 'inherit'
1400
+ : 'sonnet';
1401
+ }
1402
+ // Gate on tier (not profile) so a valid phase-type override beats
1403
+ // profile=inherit (#3030 CR Major).
1404
+ if (tier === 'inherit') return 'inherit';
1405
+ // `tier` is guaranteed truthy here: agentModels exists, and MODEL_PROFILES
1406
+ // entries always define `balanced`, so `agentModels[profile] || agentModels.balanced`
1407
+ // resolves to a string. Keep the local for readability — no defensive fallback.
1408
+ const alias = tier;
1409
+
1410
+ // resolve_model_ids: true — map alias to full Claude model ID.
1411
+ // Prevents 404s when the Task tool passes aliases directly to the API.
1026
1412
  if (config.resolve_model_ids) {
1027
1413
  return MODEL_ALIAS_MAP[alias] || alias;
1028
1414
  }
@@ -1030,6 +1416,156 @@ function resolveModelInternal(cwd, agentType) {
1030
1416
  return alias;
1031
1417
  }
1032
1418
 
1419
+ /**
1420
+ * #3024 — Resolve a model for a specific dynamic-routing attempt.
1421
+ *
1422
+ * The orchestrator (workflow agent) tracks the attempt counter. On
1423
+ * the first spawn, it calls with attempt=0. If the orchestrator detects
1424
+ * a soft failure (verification inconclusive, plan-check FLAG, etc.),
1425
+ * it re-spawns with attempt=1, which escalates the agent's tier one
1426
+ * step up. `max_escalations` caps how many escalations are allowed.
1427
+ *
1428
+ * Resolution precedence (highest → lowest):
1429
+ * 1. config.model_overrides[agent] (full IDs accepted)
1430
+ * 2. dynamic_routing.tier_models[escalated_tier] (when enabled)
1431
+ * 3. models[phase_type] / model_profile (existing chain via
1432
+ * resolveModelInternal)
1433
+ *
1434
+ * When dynamic_routing is null/disabled, this function is identical
1435
+ * to resolveModelInternal — orchestrators can call it unconditionally
1436
+ * without breaking back-compat.
1437
+ *
1438
+ * @param {string} cwd - Project directory.
1439
+ * @param {string} agentType - Agent name (e.g. 'sdd-verifier').
1440
+ * @param {number} [attempt=0] - 0 for first spawn; 1+ for escalation.
1441
+ * Capped internally at max_escalations.
1442
+ * @returns {string} Model alias (opus/sonnet/haiku) or full ID.
1443
+ */
1444
+ function resolveModelForTier(cwd, agentType, attempt) {
1445
+ const config = loadConfig(cwd);
1446
+ const attemptN = Number.isInteger(attempt) && attempt > 0 ? attempt : 0;
1447
+
1448
+ // Per-agent override always wins — same as resolveModelInternal step 1.
1449
+ // User-supplied full IDs bypass the entire tier mechanism.
1450
+ const override = config.model_overrides?.[agentType];
1451
+ if (override) return override;
1452
+
1453
+ const dr = config.dynamic_routing;
1454
+ // Disabled / missing / non-object → fall back to the existing resolver.
1455
+ if (!dr || typeof dr !== 'object' || dr.enabled !== true) {
1456
+ return resolveModelInternal(cwd, agentType);
1457
+ }
1458
+
1459
+ const tierModels = dr.tier_models;
1460
+ if (!tierModels || typeof tierModels !== 'object') {
1461
+ // tier_models missing — can't dynamic-route; fall back.
1462
+ return resolveModelInternal(cwd, agentType);
1463
+ }
1464
+
1465
+ const defaultTier = AGENT_DEFAULT_TIERS[agentType];
1466
+ if (!defaultTier || !VALID_AGENT_TIERS.has(defaultTier)) {
1467
+ // Unmapped agent — no default tier; fall back so we don't silently
1468
+ // pick the wrong model.
1469
+ return resolveModelInternal(cwd, agentType);
1470
+ }
1471
+
1472
+ // Cap effective escalation at max_escalations (default 1). Beyond
1473
+ // the cap, the resolver returns the model for the cap level so the
1474
+ // orchestrator can log "max escalations reached" without burning
1475
+ // further budget.
1476
+ //
1477
+ // CR Major (#3031): `escalate_on_failure: false` is the kill-switch
1478
+ // for escalation — when false, every attempt resolves to the default
1479
+ // tier regardless of the attempt counter. Without this guard, an
1480
+ // orchestrator that blindly bumps the counter on retry would silently
1481
+ // escalate even though the user opted out.
1482
+ const maxEscalations = Number.isInteger(dr.max_escalations) && dr.max_escalations >= 0
1483
+ ? dr.max_escalations
1484
+ : 1;
1485
+ const escalationEnabled = dr.escalate_on_failure !== false;
1486
+ const effectiveAttempt = escalationEnabled
1487
+ ? Math.min(attemptN, maxEscalations)
1488
+ : 0;
1489
+
1490
+ // Walk the escalation chain N times from the default tier.
1491
+ let tier = defaultTier;
1492
+ for (let i = 0; i < effectiveAttempt; i += 1) {
1493
+ const next = nextTier(tier);
1494
+ if (!next || next === tier) break; // already at top
1495
+ tier = next;
1496
+ }
1497
+
1498
+ const alias = tierModels[tier];
1499
+ if (typeof alias !== 'string' || alias.length === 0) {
1500
+ // Misconfigured tier_models — missing slot. Fall back rather
1501
+ // than emit an empty model id.
1502
+ return resolveModelInternal(cwd, agentType);
1503
+ }
1504
+ return alias;
1505
+ }
1506
+
1507
+ /**
1508
+ * #2517 — Resolve runtime-specific reasoning_effort for an agent.
1509
+ * Returns null unless:
1510
+ * - `runtime` is explicitly set in config,
1511
+ * - the runtime supports reasoning_effort (currently: codex),
1512
+ * - profile is not 'inherit',
1513
+ * - the resolved tier entry has a `reasoning_effort` value.
1514
+ *
1515
+ * Never returns a value for Claude — keeps reasoning_effort out of Claude spawn paths.
1516
+ */
1517
+ function resolveReasoningEffortInternal(cwd, agentType) {
1518
+ const config = loadConfig(cwd);
1519
+ if (!config.runtime) return null;
1520
+ // Strict allowlist: reasoning_effort only propagates for runtimes whose
1521
+ // install path actually accepts it. Adding a new runtime here is the only
1522
+ // way to enable effort propagation — overrides cannot bypass the gate.
1523
+ // Without this, a typo in `runtime` (e.g. `"codx"`) plus a user override
1524
+ // for that typo would leak `xhigh` into a Claude or unknown install
1525
+ // (review finding #3).
1526
+ if (!RUNTIMES_WITH_REASONING_EFFORT.has(config.runtime)) return null;
1527
+ // Per-agent override means user supplied a fully-qualified ID; reasoning_effort
1528
+ // for that case must be set via per-agent mechanism, not tier inference.
1529
+ if (config.model_overrides?.[agentType]) return null;
1530
+
1531
+ const profile = String(config.model_profile || 'balanced').toLowerCase();
1532
+ const agentModels = MODEL_PROFILES[agentType];
1533
+ if (!agentModels) return null;
1534
+
1535
+ // #3023 (CR Major): mirror the phase-type tier lookup from
1536
+ // resolveModelInternal. Without this, `model` and `reasoning_effort`
1537
+ // derive from different tier sources on Codex when models.<phase_type>
1538
+ // overrides the profile.
1539
+ //
1540
+ // #3030 CR follow-up: do NOT short-circuit on profile === 'inherit'
1541
+ // before reading the phase-type tier. A config like
1542
+ // { model_profile: 'inherit', models: { execution: 'opus' } }
1543
+ // must produce the opus runtime effort, not null. Compute tier from
1544
+ // phase-type first; only fall back to profile when there's no valid
1545
+ // phase-type override; only return null when the resolved tier is
1546
+ // 'inherit' or unknown.
1547
+ const phaseType = AGENT_TO_PHASE_TYPE[agentType];
1548
+ const phaseTypeTier = (phaseType && config.models && typeof config.models === 'object')
1549
+ ? config.models[phaseType]
1550
+ : undefined;
1551
+ // Explicit phase-type 'inherit' is the user opting out of tier-based
1552
+ // effort for this phase — return null instead of falling through to
1553
+ // profile (which would silently emit the profile's effort and
1554
+ // contradict the user's choice).
1555
+ if (phaseTypeTier === 'inherit') return null;
1556
+ const VALID_TIERS = new Set(['opus', 'sonnet', 'haiku']);
1557
+ const tier = (phaseTypeTier && VALID_TIERS.has(phaseTypeTier))
1558
+ ? phaseTypeTier
1559
+ : (profile === 'inherit'
1560
+ ? 'inherit'
1561
+ : (agentModels[profile] || agentModels['balanced']));
1562
+ // 'inherit' (from profile fallback) yields no runtime effort.
1563
+ if (!tier || tier === 'inherit') return null;
1564
+
1565
+ const entry = _resolveRuntimeTier(config, tier);
1566
+ return entry?.reasoning_effort || null;
1567
+ }
1568
+
1033
1569
  // ─── Summary body helpers ─────────────────────────────────────────────────
1034
1570
 
1035
1571
  /**
@@ -1040,11 +1576,28 @@ function resolveModelInternal(cwd, agentType) {
1040
1576
  */
1041
1577
  function extractOneLinerFromBody(content) {
1042
1578
  if (!content) return null;
1579
+ // Normalize EOLs so matching works for LF and CRLF files.
1580
+ const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
1043
1581
  // Strip frontmatter first
1044
- const body = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
1045
- // Find the first **...** line after a # heading
1046
- const match = body.match(/^#[^\n]*\n+\*\*([^*]+)\*\*/m);
1047
- return match ? match[1].trim() : null;
1582
+ const body = normalized.replace(/^---\n[\s\S]*?\n---\n*/, '');
1583
+ // Find the first **...** span on a line after a # heading.
1584
+ // Two supported template forms:
1585
+ // 1) Labeled: **One-liner:** Real prose here. (bug #2660 — new template)
1586
+ // 2) Bare: **Real prose here.** (legacy template)
1587
+ // For (1), the first bold span ends in a colon and the prose that follows
1588
+ // on the same line is the one-liner. For (2), the bold span itself is the
1589
+ // one-liner.
1590
+ const match = body.match(/^#[^\n]*\n+\*\*([^*\n]+)\*\*([^\n]*)/m);
1591
+ if (!match) return null;
1592
+ const boldInner = match[1].trim();
1593
+ const afterBold = match[2];
1594
+ // Labeled form: bold span is a "Label:" prefix — capture prose after it.
1595
+ if (/:\s*$/.test(boldInner)) {
1596
+ const prose = afterBold.trim();
1597
+ return prose.length > 0 ? prose : null;
1598
+ }
1599
+ // Bare form: the bold content itself is the one-liner.
1600
+ return boldInner.length > 0 ? boldInner : null;
1048
1601
  }
1049
1602
 
1050
1603
  // ─── Misc utilities ───────────────────────────────────────────────────────────
@@ -1059,14 +1612,101 @@ function pathExistsInternal(cwd, targetPath) {
1059
1612
  }
1060
1613
  }
1061
1614
 
1615
+ /**
1616
+ * Detect whether `cwd` sits inside a git worktree, and if so, return the
1617
+ * absolute path of the worktree root.
1618
+ *
1619
+ * Bug #3491: the previous shallow `pathExistsInternal(cwd, '.git')` check
1620
+ * only saw a `.git` entry directly in cwd, so subdirectories of an existing
1621
+ * repo reported `has_git: false` and the new-project workflow then ran
1622
+ * `git init` — creating a nested `.git` inside the outer repo's worktree.
1623
+ *
1624
+ * Mirrors `git rev-parse --is-inside-work-tree` semantics. Uses the existing
1625
+ * `execGit` seam so behaviour is consistent with the rest of the toolchain
1626
+ * (non-interactive env, 10s timeout, mockable in tests).
1627
+ *
1628
+ * Returns: { inside: boolean, worktreeRoot: string | null }
1629
+ * - inside=true → cwd is somewhere inside a git worktree
1630
+ * - inside=false → cwd is not inside any git worktree (or git is unavailable)
1631
+ *
1632
+ * Failure modes (git not installed, command times out, non-zero exit) all
1633
+ * collapse to `{ inside: false, worktreeRoot: null }` — the conservative
1634
+ * default that preserves pre-fix behaviour for environments without git.
1635
+ */
1636
+ function gitWorktreeInfoInternal(cwd) {
1637
+ try {
1638
+ const insideResult = execGit(['rev-parse', '--is-inside-work-tree'], { cwd, timeout: 5000 });
1639
+ if (insideResult.exitCode !== 0) {
1640
+ return { inside: false, worktreeRoot: null };
1641
+ }
1642
+ const insideStdout = String(insideResult.stdout || '').trim();
1643
+ if (insideStdout !== 'true') {
1644
+ return { inside: false, worktreeRoot: null };
1645
+ }
1646
+ const rootResult = execGit(['rev-parse', '--show-toplevel'], { cwd, timeout: 5000 });
1647
+ if (rootResult.exitCode !== 0) {
1648
+ return { inside: true, worktreeRoot: null };
1649
+ }
1650
+ const root = String(rootResult.stdout || '').trim();
1651
+ return { inside: true, worktreeRoot: root || null };
1652
+ } catch {
1653
+ return { inside: false, worktreeRoot: null };
1654
+ }
1655
+ }
1656
+
1062
1657
  function generateSlugInternal(text) {
1063
1658
  if (!text) return null;
1064
- return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
1659
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 60);
1065
1660
  }
1066
1661
 
1067
1662
  function getMilestoneInfo(cwd) {
1068
1663
  try {
1069
- const roadmap = fs.readFileSync(path.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8');
1664
+ const roadmap = platformReadSync(path.join(planningDir(cwd), 'ROADMAP.md'));
1665
+ if (roadmap === null) throw new Error('missing');
1666
+
1667
+ // 0. Prefer STATE.md milestone: frontmatter as the authoritative source.
1668
+ // This prevents falling through to a regex that may match an old heading
1669
+ // when the active milestone's 🚧 marker is inside a <summary> tag without
1670
+ // **bold** formatting (bug #2409).
1671
+ let stateVersion = null;
1672
+ if (cwd) {
1673
+ try {
1674
+ const statePath = path.join(planningDir(cwd), 'STATE.md');
1675
+ const stateRaw = platformReadSync(statePath);
1676
+ if (stateRaw !== null) {
1677
+ const m = stateRaw.match(/^milestone:\s*(.+)/m);
1678
+ if (m) stateVersion = m[1].trim();
1679
+ }
1680
+ } catch { /* intentionally empty */ }
1681
+ }
1682
+
1683
+ if (stateVersion) {
1684
+ // Look up the name for this version in ROADMAP.md
1685
+ const escapedVer = escapeRegex(stateVersion);
1686
+ // Match heading-format: ## Roadmap v2.9: Name or ## v2.9 Name
1687
+ const headingMatch = roadmap.match(
1688
+ new RegExp(`##[^\\n]*${escapedVer}[:\\s]+([^\\n(]+)`, 'i')
1689
+ );
1690
+ if (headingMatch) {
1691
+ // If the heading line contains ✅ the milestone is already shipped.
1692
+ // Fall through to normal detection so the NEW active milestone is returned
1693
+ // instead of the stale shipped one still recorded in STATE.md.
1694
+ if (!headingMatch[0].includes('✅')) {
1695
+ return { version: stateVersion, name: headingMatch[1].trim() };
1696
+ }
1697
+ // Shipped milestone — do not early-return; fall through to normal detection below.
1698
+ } else {
1699
+ // Match list-format: 🚧 **v2.9 Name** or 🚧 v2.9 Name
1700
+ const listMatch = roadmap.match(
1701
+ new RegExp(`🚧\\s*\\*?\\*?${escapedVer}\\s+([^*\\n]+)`, 'i')
1702
+ );
1703
+ if (listMatch) {
1704
+ return { version: stateVersion, name: listMatch[1].trim() };
1705
+ }
1706
+ // Version found in STATE.md but no name match in ROADMAP — return bare version
1707
+ return { version: stateVersion, name: 'milestone' };
1708
+ }
1709
+ }
1070
1710
 
1071
1711
  // First: check for list-format roadmaps using 🚧 (in-progress) marker
1072
1712
  // e.g. "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
@@ -1079,11 +1719,14 @@ function getMilestoneInfo(cwd) {
1079
1719
  };
1080
1720
  }
1081
1721
 
1082
- // Second: heading-format roadmaps — strip shipped milestones in <details> blocks
1722
+ // Second: heading-format roadmaps — strip shipped milestones.
1723
+ // <details> blocks are stripped by stripShippedMilestones; heading-format ✅ markers
1724
+ // are excluded by the negative lookahead below so a stale STATE.md version (or any
1725
+ // shipped ✅ heading) never wins over the first non-shipped milestone heading.
1083
1726
  const cleaned = stripShippedMilestones(roadmap);
1084
- // Extract version and name from the same ## heading for consistency
1727
+ // Negative lookahead skips headings that contain (shipped milestone marker).
1085
1728
  // Supports 2+ segment versions: v1.2, v1.2.1, v2.0.1, etc.
1086
- const headingMatch = cleaned.match(/## .*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
1729
+ const headingMatch = cleaned.match(/## (?!.*✅).*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
1087
1730
  if (headingMatch) {
1088
1731
  return {
1089
1732
  version: 'v' + headingMatch[1],
@@ -1106,10 +1749,63 @@ function getMilestoneInfo(cwd) {
1106
1749
  * to the current milestone based on ROADMAP.md phase headings.
1107
1750
  * If no ROADMAP exists or no phases are listed, returns a pass-all filter.
1108
1751
  */
1109
- function getMilestonePhaseFilter(cwd) {
1752
+ function getMilestonePhaseFilter(cwd, versionOverride) {
1110
1753
  const milestonePhaseNums = new Set();
1754
+ let missingExplicitVersion = false;
1111
1755
  try {
1112
- const roadmap = extractCurrentMilestone(fs.readFileSync(path.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
1756
+ const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
1757
+ const roadmapContent = platformReadSync(roadmapPath);
1758
+ if (roadmapContent === null) throw new Error('missing');
1759
+ let roadmap = extractCurrentMilestone(roadmapContent, cwd);
1760
+
1761
+ if (versionOverride) {
1762
+ const escapedVersion = escapeRegex(versionOverride);
1763
+ const sectionPattern = new RegExp(`(^#{1,3}\\s+.*${escapedVersion}[^\\n]*)`, 'mi');
1764
+ const sectionMatch = roadmapContent.match(sectionPattern);
1765
+ if (!sectionMatch) {
1766
+ // Only treat this as an error case when the roadmap is milestone-versioned.
1767
+ // Older/flat roadmap formats without vX.Y milestone headings should keep
1768
+ // legacy pass-through behavior for milestone.complete.
1769
+ const hasVersionedMilestones = /^#{1,3}\s+.*v\d+\.\d+/mi.test(roadmapContent);
1770
+ if (hasVersionedMilestones) {
1771
+ roadmap = '';
1772
+ missingExplicitVersion = true;
1773
+ }
1774
+ } else {
1775
+ const sectionStart = sectionMatch.index;
1776
+ const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
1777
+ const restContent = roadmapContent.slice(sectionStart + sectionMatch[0].length);
1778
+ const nextMilestonePattern = new RegExp(`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`, 'i');
1779
+
1780
+ let sectionEnd = roadmapContent.length;
1781
+ let fenceChar = null;
1782
+ let fenceLen = 0;
1783
+ let charOffset = 0;
1784
+ for (const line of restContent.split('\n')) {
1785
+ const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
1786
+ if (fenceMatch) {
1787
+ const char = fenceMatch[1][0];
1788
+ const len = fenceMatch[1].length;
1789
+ const trailing = fenceMatch[2] || '';
1790
+ if (!fenceChar) {
1791
+ fenceChar = char;
1792
+ fenceLen = len;
1793
+ } else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
1794
+ fenceChar = null;
1795
+ fenceLen = 0;
1796
+ }
1797
+ } else if (!fenceChar && nextMilestonePattern.test(line)) {
1798
+ sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
1799
+ break;
1800
+ }
1801
+ charOffset += line.length + 1;
1802
+ }
1803
+
1804
+ const currentSection = roadmapContent.slice(sectionStart, sectionEnd);
1805
+ roadmap = currentSection;
1806
+ }
1807
+ }
1808
+
1113
1809
  // Match both numeric phases (Phase 1:) and custom IDs (Phase PROJ-42:)
1114
1810
  const phasePattern = /#{2,4}\s*Phase\s+([\w][\w.-]*)\s*:/gi;
1115
1811
  let m;
@@ -1121,11 +1817,12 @@ function getMilestonePhaseFilter(cwd) {
1121
1817
  if (milestonePhaseNums.size === 0) {
1122
1818
  const passAll = () => true;
1123
1819
  passAll.phaseCount = 0;
1820
+ passAll.missingExplicitVersion = missingExplicitVersion;
1124
1821
  return passAll;
1125
1822
  }
1126
1823
 
1127
1824
  const normalized = new Set(
1128
- [...milestonePhaseNums].map(n => (n.replace(/^0+/, '') || '0').toLowerCase())
1825
+ [...milestonePhaseNums].map(n => (n.replace(/^0+(?=\d)/, '') || '0').toLowerCase())
1129
1826
  );
1130
1827
 
1131
1828
  function isDirInMilestone(dirName) {
@@ -1135,9 +1832,22 @@ function getMilestonePhaseFilter(cwd) {
1135
1832
  // Try custom ID match (e.g. PROJ-42-description → PROJ-42)
1136
1833
  const customMatch = dirName.match(/^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*)/);
1137
1834
  if (customMatch && normalized.has(customMatch[1].toLowerCase())) return true;
1835
+ // #3600: project-code-prefixed directory (`CK-01-name`) against a
1836
+ // numeric ROADMAP heading (`### Phase 1:`). Strip the same prefix
1837
+ // shape `normalizePhaseName` recognises (`^[A-Z]{1,6}-(?=\d)`) and
1838
+ // retry the numeric match. This runs AFTER the custom-ID match so
1839
+ // a roadmap that uses `Phase PROJ-42:` continues to win via the
1840
+ // existing custom-ID path; the strip-and-retry only fires when the
1841
+ // milestone is keyed on the bare numeric form.
1842
+ const stripped = dirName.replace(/^[A-Z]{1,6}-(?=\d)/i, '');
1843
+ if (stripped !== dirName) {
1844
+ const sm = stripped.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
1845
+ if (sm && normalized.has(sm[1].toLowerCase())) return true;
1846
+ }
1138
1847
  return false;
1139
1848
  }
1140
1849
  isDirInMilestone.phaseCount = milestonePhaseNums.size;
1850
+ isDirInMilestone.missingExplicitVersion = missingExplicitVersion;
1141
1851
  return isDirInMilestone;
1142
1852
  }
1143
1853
 
@@ -1185,23 +1895,62 @@ function readSubdirectories(dirPath, sort = false) {
1185
1895
  }
1186
1896
  }
1187
1897
 
1898
+ /**
1899
+ * Format a Date as a fuzzy relative time string (e.g. "5 minutes ago").
1900
+ * @param {Date} date
1901
+ * @returns {string}
1902
+ */
1903
+ function timeAgo(date) {
1904
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
1905
+ if (seconds < 5) return 'just now';
1906
+ if (seconds < 60) return `${seconds} seconds ago`;
1907
+ const minutes = Math.floor(seconds / 60);
1908
+ if (minutes === 1) return '1 minute ago';
1909
+ if (minutes < 60) return `${minutes} minutes ago`;
1910
+ const hours = Math.floor(minutes / 60);
1911
+ if (hours === 1) return '1 hour ago';
1912
+ if (hours < 24) return `${hours} hours ago`;
1913
+ const days = Math.floor(hours / 24);
1914
+ if (days === 1) return '1 day ago';
1915
+ if (days < 30) return `${days} days ago`;
1916
+ const months = Math.floor(days / 30);
1917
+ if (months === 1) return '1 month ago';
1918
+ if (months < 12) return `${months} months ago`;
1919
+ const years = Math.floor(days / 365);
1920
+ if (years === 1) return '1 year ago';
1921
+ return `${years} years ago`;
1922
+ }
1923
+
1188
1924
  module.exports = {
1189
1925
  output,
1190
1926
  error,
1191
- safeReadFile,
1927
+ ERROR_REASON,
1928
+ setJsonErrorMode,
1929
+ getJsonErrorMode,
1192
1930
  loadConfig,
1193
1931
  isGitIgnored,
1194
- execGit,
1195
- normalizeMd,
1196
1932
  escapeRegex,
1197
1933
  normalizePhaseName,
1934
+ phaseMarkdownRegexSource,
1935
+ phaseMarkdownRegexSourceExact,
1198
1936
  comparePhaseNum,
1199
1937
  searchPhaseInDir,
1938
+ extractPhaseToken,
1939
+ phaseTokenMatches,
1200
1940
  findPhaseInternal,
1201
1941
  getArchivedPhaseDirs,
1202
1942
  getRoadmapPhaseInternal,
1203
1943
  resolveModelInternal,
1944
+ resolveModelForTier,
1945
+ resolveReasoningEffortInternal,
1946
+ RUNTIME_PROFILE_MAP,
1947
+ RUNTIMES_WITH_REASONING_EFFORT,
1948
+ KNOWN_RUNTIMES,
1949
+ RUNTIME_OVERRIDE_TIERS,
1950
+ resolveTierEntry,
1951
+ _resetRuntimeWarningCacheForTests,
1204
1952
  pathExistsInternal,
1953
+ gitWorktreeInfoInternal,
1205
1954
  generateSlugInternal,
1206
1955
  getMilestoneInfo,
1207
1956
  getMilestonePhaseFilter,
@@ -1211,11 +1960,14 @@ module.exports = {
1211
1960
  toPosixPath,
1212
1961
  extractOneLinerFromBody,
1213
1962
  resolveWorktreeRoot,
1963
+ // Deprecated re-exports — prefer direct import from planning-workspace.cjs
1214
1964
  withPlanningLock,
1215
1965
  findProjectRoot,
1216
1966
  detectSubRepos,
1217
1967
  reapStaleTempFiles,
1968
+ SDD_TEMP_DIR,
1218
1969
  MODEL_ALIAS_MAP,
1970
+ CONFIG_DEFAULTS,
1219
1971
  planningDir,
1220
1972
  planningRoot,
1221
1973
  planningPaths,
@@ -1227,4 +1979,7 @@ module.exports = {
1227
1979
  readSubdirectories,
1228
1980
  getAgentsDir,
1229
1981
  checkAgentsInstalled,
1982
+ timeAgo,
1983
+ pruneOrphanedWorktrees,
1984
+ inspectWorktreeHealth,
1230
1985
  };