@bhargavvc/sdd-cc 1.35.0 → 1.42.4

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 (1376) hide show
  1. package/README.ja-JP.md +54 -52
  2. package/README.ko-KR.md +47 -45
  3. package/README.md +86 -733
  4. package/README.pt-BR.md +14 -12
  5. package/README.zh-CN.md +31 -29
  6. package/agents/sdd-ai-researcher.md +2 -2
  7. package/agents/sdd-code-fixer.md +169 -17
  8. package/agents/sdd-code-reviewer.md +40 -8
  9. package/agents/sdd-codebase-mapper.md +89 -6
  10. package/agents/sdd-debug-session-manager.md +314 -0
  11. package/agents/sdd-debugger.md +147 -80
  12. package/agents/sdd-doc-classifier.md +168 -0
  13. package/agents/sdd-doc-synthesizer.md +204 -0
  14. package/agents/sdd-doc-verifier.md +20 -4
  15. package/agents/sdd-doc-writer.md +22 -9
  16. package/agents/sdd-domain-researcher.md +2 -2
  17. package/agents/sdd-eval-auditor.md +30 -3
  18. package/agents/sdd-eval-planner.md +2 -2
  19. package/agents/sdd-executor.md +203 -43
  20. package/agents/sdd-framework-selector.md +1 -1
  21. package/agents/sdd-integration-checker.md +30 -3
  22. package/agents/sdd-intel-updater.md +51 -23
  23. package/agents/sdd-nyquist-auditor.md +31 -4
  24. package/agents/sdd-pattern-mapper.md +335 -0
  25. package/agents/sdd-phase-researcher.md +195 -32
  26. package/agents/sdd-plan-checker.md +135 -24
  27. package/agents/sdd-planner.md +190 -205
  28. package/agents/sdd-project-researcher.md +7 -7
  29. package/agents/sdd-research-synthesizer.md +6 -6
  30. package/agents/sdd-roadmapper.md +19 -10
  31. package/agents/sdd-security-auditor.md +33 -6
  32. package/agents/sdd-ui-auditor.md +23 -7
  33. package/agents/sdd-ui-checker.md +16 -7
  34. package/agents/sdd-ui-researcher.md +8 -8
  35. package/agents/sdd-verifier.md +124 -27
  36. package/bin/install.js +5016 -372
  37. package/bin/sdd-sdk.js +37 -0
  38. package/commands/sdd/add-tests.md +5 -4
  39. package/commands/sdd/ai-integration-phase.md +4 -3
  40. package/commands/sdd/audit-fix.md +2 -1
  41. package/commands/sdd/audit-milestone.md +3 -2
  42. package/commands/sdd/autonomous.md +3 -3
  43. package/commands/sdd/capture.md +62 -0
  44. package/commands/sdd/cleanup.md +2 -1
  45. package/commands/sdd/code-review.md +8 -4
  46. package/commands/sdd/commit.md +39 -0
  47. package/commands/sdd/complete-milestone.md +15 -8
  48. package/commands/sdd/config.md +58 -0
  49. package/commands/sdd/debug.md +21 -155
  50. package/commands/sdd/discuss-phase.md +18 -11
  51. package/commands/sdd/docs-update.md +3 -2
  52. package/commands/sdd/eval-review.md +4 -3
  53. package/commands/sdd/execute-phase.md +5 -4
  54. package/commands/sdd/explore.md +3 -3
  55. package/commands/sdd/extract-learnings.md +23 -0
  56. package/commands/sdd/fast.md +4 -3
  57. package/commands/sdd/forensics.md +3 -2
  58. package/commands/sdd/graphify.md +199 -0
  59. package/commands/sdd/health.md +12 -3
  60. package/commands/sdd/help.md +1 -1
  61. package/commands/sdd/import.md +10 -5
  62. package/commands/sdd/inbox.md +39 -0
  63. package/commands/sdd/ingest-docs.md +42 -0
  64. package/commands/sdd/manager.md +8 -3
  65. package/commands/sdd/map-codebase.md +18 -6
  66. package/commands/sdd/milestone-summary.md +1 -1
  67. package/commands/sdd/mvp-phase.md +45 -0
  68. package/commands/sdd/new-milestone.md +4 -3
  69. package/commands/sdd/new-project.md +4 -3
  70. package/commands/sdd/ns-context.md +23 -0
  71. package/commands/sdd/ns-ideate.md +24 -0
  72. package/commands/sdd/ns-manage.md +29 -0
  73. package/commands/sdd/ns-project.md +22 -0
  74. package/commands/sdd/ns-review.md +26 -0
  75. package/commands/sdd/ns-workflow.md +28 -0
  76. package/commands/sdd/pause-work.md +6 -1
  77. package/commands/sdd/phase.md +56 -0
  78. package/commands/sdd/plan-phase.md +15 -5
  79. package/commands/sdd/plan-review-convergence.md +59 -0
  80. package/commands/sdd/pr-branch.md +2 -1
  81. package/commands/sdd/profile-user.md +1 -1
  82. package/commands/sdd/progress.md +27 -5
  83. package/commands/sdd/quick.md +128 -3
  84. package/commands/sdd/resume-work.md +2 -12
  85. package/commands/sdd/review-backlog.md +3 -2
  86. package/commands/sdd/review.md +3 -2
  87. package/commands/sdd/secure-phase.md +3 -2
  88. package/commands/sdd/settings.md +2 -9
  89. package/commands/sdd/ship.md +2 -1
  90. package/commands/sdd/sketch.md +60 -0
  91. package/commands/sdd/spec-phase.md +63 -0
  92. package/commands/sdd/spike.md +57 -0
  93. package/commands/sdd/stats.md +2 -1
  94. package/commands/sdd/surface.md +129 -0
  95. package/commands/sdd/thread.md +8 -111
  96. package/commands/sdd/ui-phase.md +3 -2
  97. package/commands/sdd/ui-review.md +3 -2
  98. package/commands/sdd/ultraplan-phase.md +34 -0
  99. package/commands/sdd/undo.md +2 -1
  100. package/commands/sdd/update.md +21 -10
  101. package/commands/sdd/validate-phase.md +3 -2
  102. package/commands/sdd/verify-work.md +5 -4
  103. package/commands/sdd/workspace.md +52 -0
  104. package/commands/sdd/workstreams.md +12 -11
  105. package/hooks/dist/sdd-check-update-worker.js +116 -0
  106. package/hooks/dist/sdd-check-update.js +13 -88
  107. package/hooks/dist/sdd-context-monitor.js +28 -1
  108. package/hooks/dist/sdd-phase-boundary.sh +23 -3
  109. package/hooks/dist/sdd-read-guard.js +21 -2
  110. package/hooks/dist/sdd-read-injection-scanner.js +152 -0
  111. package/hooks/dist/sdd-session-state.sh +38 -12
  112. package/hooks/dist/sdd-statusline.js +324 -28
  113. package/hooks/dist/sdd-update-banner.js +134 -0
  114. package/hooks/dist/sdd-validate-commit.sh +15 -5
  115. package/hooks/dist/sdd-workflow-guard.js +2 -2
  116. package/hooks/lib/git-cmd.js +150 -0
  117. package/hooks/sdd-check-update-worker.js +116 -0
  118. package/hooks/sdd-check-update.js +13 -88
  119. package/hooks/sdd-context-monitor.js +28 -1
  120. package/hooks/sdd-phase-boundary.sh +23 -3
  121. package/hooks/sdd-read-guard.js +21 -2
  122. package/hooks/sdd-read-injection-scanner.js +152 -0
  123. package/hooks/sdd-session-state.sh +38 -12
  124. package/hooks/sdd-statusline.js +324 -28
  125. package/hooks/sdd-update-banner.js +134 -0
  126. package/hooks/sdd-validate-commit.sh +15 -5
  127. package/hooks/sdd-workflow-guard.js +2 -2
  128. package/package.json +32 -7
  129. package/scripts/audit-workflow-script-paths.cjs +73 -0
  130. package/scripts/build-hooks.js +98 -4
  131. package/scripts/changeset/cli.cjs +269 -0
  132. package/scripts/changeset/github-release-notes.cjs +198 -0
  133. package/scripts/changeset/lint.cjs +110 -0
  134. package/scripts/changeset/new.cjs +137 -0
  135. package/scripts/changeset/parse.cjs +60 -0
  136. package/scripts/changeset/render.cjs +34 -0
  137. package/scripts/changeset/serialize.cjs +74 -0
  138. package/scripts/command-contract-helpers.cjs +61 -0
  139. package/scripts/diff-touches-shipped-paths.cjs +147 -0
  140. package/scripts/fix-slash-commands.cjs +106 -0
  141. package/scripts/gen-inventory-manifest.cjs +109 -0
  142. package/scripts/lint-command-contract.cjs +108 -0
  143. package/scripts/lint-descriptions.cjs +83 -0
  144. package/scripts/lint-no-source-grep-extras.cjs +81 -0
  145. package/scripts/lint-no-source-grep.cjs +174 -0
  146. package/scripts/lint-shell-command-projection-drift.cjs +57 -0
  147. package/scripts/lint-skill-deps.cjs +180 -0
  148. package/scripts/pr-template-policy.cjs +169 -0
  149. package/scripts/prompt-injection-scan.sh +2 -0
  150. package/scripts/rebrand-gsd-to-sdd.sh +4 -3
  151. package/scripts/strip-prose-atrefs.cjs +106 -0
  152. package/scripts/verify-tarball-sdk-dist.sh +69 -0
  153. package/sdd/bin/check-latest-version.cjs +104 -0
  154. package/sdd/bin/lib/active-workstream-store.cjs +85 -0
  155. package/sdd/bin/lib/adr-parser.cjs +394 -0
  156. package/sdd/bin/lib/artifacts.cjs +53 -0
  157. package/sdd/bin/lib/audit.cjs +755 -0
  158. package/sdd/bin/lib/cjs-command-router-adapter.cjs +39 -0
  159. package/sdd/bin/lib/clusters.cjs +136 -0
  160. package/sdd/bin/lib/command-aliases.generated.cjs +838 -0
  161. package/sdd/bin/lib/commands.cjs +108 -98
  162. package/sdd/bin/lib/config-schema.cjs +135 -0
  163. package/sdd/bin/lib/config.cjs +253 -68
  164. package/sdd/bin/lib/context-utilization.cjs +47 -0
  165. package/sdd/bin/lib/core.cjs +997 -607
  166. package/sdd/bin/lib/decisions.cjs +48 -0
  167. package/sdd/bin/lib/docs.cjs +36 -33
  168. package/sdd/bin/lib/drift.cjs +379 -0
  169. package/sdd/bin/lib/fallow-runner.cjs +109 -0
  170. package/sdd/bin/lib/frontmatter.cjs +19 -11
  171. package/sdd/bin/lib/gap-checker.cjs +197 -0
  172. package/sdd/bin/lib/graphify.cjs +577 -0
  173. package/sdd/bin/lib/init-command-router.cjs +70 -0
  174. package/sdd/bin/lib/init.cjs +603 -102
  175. package/sdd/bin/lib/install-profiles.cjs +572 -0
  176. package/sdd/bin/lib/installer-migration-authoring.cjs +117 -0
  177. package/sdd/bin/lib/installer-migration-report.cjs +328 -0
  178. package/sdd/bin/lib/installer-migrations/000-first-time-baseline.cjs +220 -0
  179. package/sdd/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +41 -0
  180. package/sdd/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +80 -0
  181. package/sdd/bin/lib/installer-migrations.cjs +703 -0
  182. package/sdd/bin/lib/intel.cjs +35 -52
  183. package/sdd/bin/lib/learnings.cjs +2 -1
  184. package/sdd/bin/lib/milestone.cjs +313 -283
  185. package/sdd/bin/lib/model-catalog.cjs +136 -0
  186. package/sdd/bin/lib/model-profiles.cjs +25 -70
  187. package/sdd/bin/lib/phase-command-router.cjs +96 -0
  188. package/sdd/bin/lib/phase.cjs +556 -102
  189. package/sdd/bin/lib/phases-command-router.cjs +39 -0
  190. package/sdd/bin/lib/plan-scan.cjs +138 -0
  191. package/sdd/bin/lib/planning-workspace.cjs +361 -0
  192. package/sdd/bin/lib/profile-output.cjs +109 -43
  193. package/sdd/bin/lib/profile-pipeline.cjs +1 -1
  194. package/sdd/bin/lib/review-reviewer-selection.cjs +125 -0
  195. package/sdd/bin/lib/roadmap-command-router.cjs +23 -0
  196. package/sdd/bin/lib/roadmap.cjs +278 -17
  197. package/sdd/bin/lib/runtime-homes.cjs +178 -0
  198. package/sdd/bin/lib/sdd2-import.cjs +3 -3
  199. package/sdd/bin/lib/secrets.cjs +33 -0
  200. package/sdd/bin/lib/security.cjs +8 -7
  201. package/sdd/bin/lib/shell-command-projection.cjs +548 -0
  202. package/sdd/bin/lib/state-command-router.cjs +100 -0
  203. package/sdd/bin/lib/state-document.cjs +12 -0
  204. package/sdd/bin/lib/state-document.generated.cjs +127 -0
  205. package/sdd/bin/lib/state.cjs +720 -218
  206. package/sdd/bin/lib/surface.cjs +398 -0
  207. package/sdd/bin/lib/template.cjs +4 -2
  208. package/sdd/bin/lib/uat.cjs +9 -2
  209. package/sdd/bin/lib/validate-command-router.cjs +55 -0
  210. package/sdd/bin/lib/verify-command-router.cjs +34 -0
  211. package/sdd/bin/lib/verify.cjs +464 -137
  212. package/sdd/bin/lib/workstream-inventory.cjs +159 -0
  213. package/sdd/bin/lib/workstream-name-policy.cjs +33 -0
  214. package/sdd/bin/lib/workstream.cjs +73 -195
  215. package/sdd/bin/lib/worktree-safety.cjs +563 -0
  216. package/sdd/bin/sdd-tools.cjs +386 -252
  217. package/sdd/bin/verify-reapply-patches.cjs +247 -0
  218. package/sdd/contexts/review.md +1 -0
  219. package/sdd/references/artifact-types.md +18 -0
  220. package/sdd/references/autonomous-smart-discuss.md +277 -0
  221. package/sdd/references/checkpoints.md +36 -0
  222. package/sdd/references/context-budget.md +85 -49
  223. package/sdd/references/continuation-format.md +26 -22
  224. package/sdd/references/debugger-philosophy.md +76 -0
  225. package/sdd/references/decimal-phase-calculation.md +5 -5
  226. package/sdd/references/doc-conflict-engine.md +91 -0
  227. package/sdd/references/domain-probes.md +1 -1
  228. package/sdd/references/execute-mvp-tdd.md +81 -0
  229. package/sdd/references/executor-examples.md +110 -0
  230. package/sdd/references/gate-prompts.md +2 -2
  231. package/sdd/references/git-integration.md +10 -7
  232. package/sdd/references/git-planning-commit.md +6 -4
  233. package/sdd/references/mandatory-initial-read.md +2 -0
  234. package/sdd/references/model-profiles.md +106 -6
  235. package/sdd/references/mvp-concepts.md +49 -0
  236. package/sdd/references/phase-argument-parsing.md +3 -3
  237. package/sdd/references/planner-antipatterns.md +89 -0
  238. package/sdd/references/planner-chunked.md +49 -0
  239. package/sdd/references/planner-human-verify-mode.md +57 -0
  240. package/sdd/references/planner-mvp-mode.md +53 -0
  241. package/sdd/references/planner-revision.md +1 -1
  242. package/sdd/references/planner-source-audit.md +73 -0
  243. package/sdd/references/planning-config.md +30 -13
  244. package/sdd/references/project-skills-discovery.md +19 -0
  245. package/sdd/references/scout-codebase.md +51 -0
  246. package/sdd/references/skeleton-template.md +48 -0
  247. package/sdd/references/sketch-interactivity.md +41 -0
  248. package/sdd/references/sketch-theme-system.md +94 -0
  249. package/sdd/references/sketch-tooling.md +45 -0
  250. package/sdd/references/sketch-variant-patterns.md +81 -0
  251. package/sdd/references/spidr-splitting.md +69 -0
  252. package/sdd/references/tdd.md +67 -0
  253. package/sdd/references/thinking-partner.md +1 -1
  254. package/sdd/references/universal-anti-patterns.md +4 -4
  255. package/sdd/references/user-story-template.md +58 -0
  256. package/sdd/references/verification-overrides.md +3 -3
  257. package/sdd/references/verify-mvp-mode.md +85 -0
  258. package/sdd/references/workstream-flag.md +11 -11
  259. package/sdd/references/worktree-path-safety.md +89 -0
  260. package/sdd/templates/AI-SPEC.md +1 -1
  261. package/sdd/templates/DEBUG.md +8 -3
  262. package/sdd/templates/README.md +77 -0
  263. package/sdd/templates/UAT.md +4 -4
  264. package/sdd/templates/VALIDATION.md +1 -1
  265. package/sdd/templates/claude-md.md +5 -5
  266. package/sdd/templates/config.json +16 -2
  267. package/sdd/templates/debug-subagent-prompt.md +1 -1
  268. package/sdd/templates/dev-preferences.md +1 -1
  269. package/sdd/templates/discovery.md +2 -2
  270. package/sdd/templates/phase-prompt.md +1 -1
  271. package/sdd/templates/planner-subagent-prompt.md +3 -3
  272. package/sdd/templates/project.md +1 -1
  273. package/sdd/templates/research.md +41 -1
  274. package/sdd/templates/spec.md +307 -0
  275. package/sdd/templates/state.md +9 -1
  276. package/sdd/workflows/add-backlog.md +90 -0
  277. package/sdd/workflows/add-phase.md +7 -7
  278. package/sdd/workflows/add-tests.md +12 -12
  279. package/sdd/workflows/add-todo.md +4 -4
  280. package/sdd/workflows/ai-integration-phase.md +26 -16
  281. package/sdd/workflows/analyze-dependencies.md +3 -3
  282. package/sdd/workflows/audit-fix.md +23 -3
  283. package/sdd/workflows/audit-milestone.md +37 -20
  284. package/sdd/workflows/audit-uat.md +3 -3
  285. package/sdd/workflows/autonomous.md +31 -301
  286. package/sdd/workflows/check-todos.md +6 -6
  287. package/sdd/workflows/cleanup.md +1 -1
  288. package/sdd/workflows/code-review-fix.md +20 -16
  289. package/sdd/workflows/code-review.md +114 -16
  290. package/sdd/workflows/commit.md +166 -0
  291. package/sdd/workflows/complete-milestone.md +68 -15
  292. package/sdd/workflows/debug.md +231 -0
  293. package/sdd/workflows/diagnose-issues.md +8 -6
  294. package/sdd/workflows/discovery-phase.md +3 -3
  295. package/sdd/workflows/discuss-phase/modes/advisor.md +175 -0
  296. package/sdd/workflows/discuss-phase/modes/all.md +28 -0
  297. package/sdd/workflows/discuss-phase/modes/analyze.md +44 -0
  298. package/sdd/workflows/discuss-phase/modes/auto.md +56 -0
  299. package/sdd/workflows/discuss-phase/modes/batch.md +52 -0
  300. package/sdd/workflows/discuss-phase/modes/chain.md +97 -0
  301. package/sdd/workflows/discuss-phase/modes/default.md +141 -0
  302. package/sdd/workflows/discuss-phase/modes/power.md +44 -0
  303. package/sdd/workflows/discuss-phase/modes/text.md +55 -0
  304. package/sdd/workflows/discuss-phase/templates/checkpoint.json +18 -0
  305. package/sdd/workflows/discuss-phase/templates/context.md +136 -0
  306. package/sdd/workflows/discuss-phase/templates/discussion-log.md +50 -0
  307. package/sdd/workflows/discuss-phase-assumptions.md +24 -21
  308. package/sdd/workflows/discuss-phase-power.md +3 -3
  309. package/sdd/workflows/discuss-phase.md +203 -905
  310. package/sdd/workflows/do.md +25 -21
  311. package/sdd/workflows/docs-update.md +23 -17
  312. package/sdd/workflows/edit-phase.md +294 -0
  313. package/sdd/workflows/eval-review.md +7 -7
  314. package/sdd/workflows/execute-phase/steps/codebase-drift-gate.md +81 -0
  315. package/sdd/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  316. package/sdd/workflows/execute-phase/steps/post-merge-gate.md +116 -0
  317. package/sdd/workflows/execute-phase.md +650 -252
  318. package/sdd/workflows/execute-plan.md +75 -35
  319. package/sdd/workflows/explore.md +7 -3
  320. package/sdd/workflows/extract-learnings.md +242 -0
  321. package/sdd/workflows/fast.md +13 -12
  322. package/sdd/workflows/forensics.md +19 -6
  323. package/sdd/workflows/graduation.md +195 -0
  324. package/sdd/workflows/health.md +49 -7
  325. package/sdd/workflows/help.md +340 -149
  326. package/sdd/workflows/import.md +20 -43
  327. package/sdd/workflows/inbox.md +5 -5
  328. package/sdd/workflows/ingest-docs.md +339 -0
  329. package/sdd/workflows/insert-phase.md +33 -12
  330. package/sdd/workflows/list-phase-assumptions.md +2 -2
  331. package/sdd/workflows/list-workspaces.md +2 -2
  332. package/sdd/workflows/manager.md +38 -11
  333. package/sdd/workflows/map-codebase.md +87 -23
  334. package/sdd/workflows/milestone-summary.md +8 -8
  335. package/sdd/workflows/mvp-phase.md +221 -0
  336. package/sdd/workflows/new-milestone.md +163 -23
  337. package/sdd/workflows/new-project.md +254 -53
  338. package/sdd/workflows/new-workspace.md +8 -8
  339. package/sdd/workflows/next.md +85 -18
  340. package/sdd/workflows/note.md +2 -2
  341. package/sdd/workflows/pause-work.md +13 -9
  342. package/sdd/workflows/plan-milestone-gaps.md +18 -11
  343. package/sdd/workflows/plan-phase.md +767 -94
  344. package/sdd/workflows/plan-review-convergence.md +329 -0
  345. package/sdd/workflows/plant-seed.md +146 -89
  346. package/sdd/workflows/pr-branch.md +1 -1
  347. package/sdd/workflows/profile-user.md +15 -15
  348. package/sdd/workflows/progress.md +198 -56
  349. package/sdd/workflows/quick.md +318 -54
  350. package/{commands/sdd → sdd/workflows}/reapply-patches.md +102 -23
  351. package/sdd/workflows/remove-phase.md +10 -10
  352. package/sdd/workflows/remove-workspace.md +21 -6
  353. package/sdd/workflows/resume-project.md +26 -23
  354. package/sdd/workflows/review.md +151 -20
  355. package/sdd/workflows/scan.md +5 -3
  356. package/sdd/workflows/secure-phase.md +26 -13
  357. package/sdd/workflows/settings-advanced.md +579 -0
  358. package/sdd/workflows/settings-integrations.md +281 -0
  359. package/sdd/workflows/settings.md +202 -23
  360. package/sdd/workflows/ship.md +131 -16
  361. package/sdd/workflows/sketch-wrap-up.md +285 -0
  362. package/sdd/workflows/sketch.md +360 -0
  363. package/sdd/workflows/spec-phase.md +262 -0
  364. package/sdd/workflows/spike-wrap-up.md +306 -0
  365. package/sdd/workflows/spike.md +452 -0
  366. package/sdd/workflows/stats.md +21 -2
  367. package/sdd/workflows/sync-skills.md +182 -0
  368. package/sdd/workflows/thread.md +221 -0
  369. package/sdd/workflows/transition.md +52 -30
  370. package/sdd/workflows/ui-phase.md +37 -20
  371. package/sdd/workflows/ui-review.md +12 -10
  372. package/sdd/workflows/ultraplan-phase.md +198 -0
  373. package/sdd/workflows/undo.md +9 -9
  374. package/sdd/workflows/update.md +187 -17
  375. package/sdd/workflows/validate-phase.md +12 -10
  376. package/sdd/workflows/verify-phase.md +112 -27
  377. package/sdd/workflows/verify-work.md +97 -28
  378. package/sdk/dist/cli-transport.d.ts +19 -0
  379. package/sdk/dist/cli-transport.d.ts.map +1 -0
  380. package/sdk/dist/cli-transport.js +104 -0
  381. package/sdk/dist/cli-transport.js.map +1 -0
  382. package/sdk/dist/cli.d.ts +46 -0
  383. package/sdk/dist/cli.d.ts.map +1 -0
  384. package/sdk/dist/cli.js +511 -0
  385. package/sdk/dist/cli.js.map +1 -0
  386. package/sdk/dist/config.d.ts +84 -0
  387. package/sdk/dist/config.d.ts.map +1 -0
  388. package/sdk/dist/config.js +135 -0
  389. package/sdk/dist/config.js.map +1 -0
  390. package/sdk/dist/context-engine.d.ts +49 -0
  391. package/sdk/dist/context-engine.d.ts.map +1 -0
  392. package/sdk/dist/context-engine.js +142 -0
  393. package/sdk/dist/context-engine.js.map +1 -0
  394. package/sdk/dist/context-truncation.d.ts +33 -0
  395. package/sdk/dist/context-truncation.d.ts.map +1 -0
  396. package/sdk/dist/context-truncation.js +197 -0
  397. package/sdk/dist/context-truncation.js.map +1 -0
  398. package/sdk/dist/errors.d.ts +46 -0
  399. package/sdk/dist/errors.d.ts.map +1 -0
  400. package/sdk/dist/errors.js +64 -0
  401. package/sdk/dist/errors.js.map +1 -0
  402. package/sdk/dist/event-stream.d.ts +53 -0
  403. package/sdk/dist/event-stream.d.ts.map +1 -0
  404. package/sdk/dist/event-stream.js +321 -0
  405. package/sdk/dist/event-stream.js.map +1 -0
  406. package/sdk/dist/golden/capture.d.ts +15 -0
  407. package/sdk/dist/golden/capture.d.ts.map +1 -0
  408. package/sdk/dist/golden/capture.js +67 -0
  409. package/sdk/dist/golden/capture.js.map +1 -0
  410. package/sdk/dist/golden/golden-integration-covered.d.ts +6 -0
  411. package/sdk/dist/golden/golden-integration-covered.d.ts.map +1 -0
  412. package/sdk/dist/golden/golden-integration-covered.js +30 -0
  413. package/sdk/dist/golden/golden-integration-covered.js.map +1 -0
  414. package/sdk/dist/golden/golden-mutation-covered.d.ts +7 -0
  415. package/sdk/dist/golden/golden-mutation-covered.d.ts.map +1 -0
  416. package/sdk/dist/golden/golden-mutation-covered.js +17 -0
  417. package/sdk/dist/golden/golden-mutation-covered.js.map +1 -0
  418. package/sdk/dist/golden/golden-policy.d.ts +10 -0
  419. package/sdk/dist/golden/golden-policy.d.ts.map +1 -0
  420. package/sdk/dist/golden/golden-policy.js +98 -0
  421. package/sdk/dist/golden/golden-policy.js.map +1 -0
  422. package/sdk/dist/golden/init-golden-normalize.d.ts +8 -0
  423. package/sdk/dist/golden/init-golden-normalize.d.ts.map +1 -0
  424. package/sdk/dist/golden/init-golden-normalize.js +14 -0
  425. package/sdk/dist/golden/init-golden-normalize.js.map +1 -0
  426. package/sdk/dist/golden/read-only-golden-rows.d.ts +20 -0
  427. package/sdk/dist/golden/read-only-golden-rows.d.ts.map +1 -0
  428. package/sdk/dist/golden/read-only-golden-rows.js +67 -0
  429. package/sdk/dist/golden/read-only-golden-rows.js.map +1 -0
  430. package/sdk/dist/golden/registry-canonical-commands.d.ts +6 -0
  431. package/sdk/dist/golden/registry-canonical-commands.d.ts.map +1 -0
  432. package/sdk/dist/golden/registry-canonical-commands.js +30 -0
  433. package/sdk/dist/golden/registry-canonical-commands.js.map +1 -0
  434. package/sdk/dist/index.d.ts +125 -0
  435. package/sdk/dist/index.d.ts.map +1 -0
  436. package/sdk/dist/index.js +298 -0
  437. package/sdk/dist/index.js.map +1 -0
  438. package/sdk/dist/init-runner.d.ts +90 -0
  439. package/sdk/dist/init-runner.d.ts.map +1 -0
  440. package/sdk/dist/init-runner.js +613 -0
  441. package/sdk/dist/init-runner.js.map +1 -0
  442. package/sdk/dist/logger.d.ts +50 -0
  443. package/sdk/dist/logger.d.ts.map +1 -0
  444. package/sdk/dist/logger.js +70 -0
  445. package/sdk/dist/logger.js.map +1 -0
  446. package/sdk/dist/model-catalog.d.ts +31 -0
  447. package/sdk/dist/model-catalog.d.ts.map +1 -0
  448. package/sdk/dist/model-catalog.js +31 -0
  449. package/sdk/dist/model-catalog.js.map +1 -0
  450. package/sdk/dist/phase-prompt.d.ts +72 -0
  451. package/sdk/dist/phase-prompt.d.ts.map +1 -0
  452. package/sdk/dist/phase-prompt.js +213 -0
  453. package/sdk/dist/phase-prompt.js.map +1 -0
  454. package/sdk/dist/phase-runner.d.ts +145 -0
  455. package/sdk/dist/phase-runner.d.ts.map +1 -0
  456. package/sdk/dist/phase-runner.js +1206 -0
  457. package/sdk/dist/phase-runner.js.map +1 -0
  458. package/sdk/dist/plan-parser.d.ts +55 -0
  459. package/sdk/dist/plan-parser.d.ts.map +1 -0
  460. package/sdk/dist/plan-parser.js +389 -0
  461. package/sdk/dist/plan-parser.js.map +1 -0
  462. package/sdk/dist/planning-journal.d.ts +64 -0
  463. package/sdk/dist/planning-journal.d.ts.map +1 -0
  464. package/sdk/dist/planning-journal.js +88 -0
  465. package/sdk/dist/planning-journal.js.map +1 -0
  466. package/sdk/dist/planning-runtime.d.ts +67 -0
  467. package/sdk/dist/planning-runtime.d.ts.map +1 -0
  468. package/sdk/dist/planning-runtime.js +58 -0
  469. package/sdk/dist/planning-runtime.js.map +1 -0
  470. package/sdk/dist/prompt-builder.d.ts +44 -0
  471. package/sdk/dist/prompt-builder.d.ts.map +1 -0
  472. package/sdk/dist/prompt-builder.js +180 -0
  473. package/sdk/dist/prompt-builder.js.map +1 -0
  474. package/sdk/dist/prompt-sanitizer.d.ts +35 -0
  475. package/sdk/dist/prompt-sanitizer.d.ts.map +1 -0
  476. package/sdk/dist/prompt-sanitizer.js +101 -0
  477. package/sdk/dist/prompt-sanitizer.js.map +1 -0
  478. package/sdk/dist/query/active-workstream-store.d.ts +7 -0
  479. package/sdk/dist/query/active-workstream-store.d.ts.map +1 -0
  480. package/sdk/dist/query/active-workstream-store.js +56 -0
  481. package/sdk/dist/query/active-workstream-store.js.map +1 -0
  482. package/sdk/dist/query/agent-failure-classifier.d.ts +38 -0
  483. package/sdk/dist/query/agent-failure-classifier.d.ts.map +1 -0
  484. package/sdk/dist/query/agent-failure-classifier.js +83 -0
  485. package/sdk/dist/query/agent-failure-classifier.js.map +1 -0
  486. package/sdk/dist/query/audit-open.d.ts +46 -0
  487. package/sdk/dist/query/audit-open.d.ts.map +1 -0
  488. package/sdk/dist/query/audit-open.js +662 -0
  489. package/sdk/dist/query/audit-open.js.map +1 -0
  490. package/sdk/dist/query/check-auto-mode.d.ts +13 -0
  491. package/sdk/dist/query/check-auto-mode.d.ts.map +1 -0
  492. package/sdk/dist/query/check-auto-mode.js +40 -0
  493. package/sdk/dist/query/check-auto-mode.js.map +1 -0
  494. package/sdk/dist/query/check-completion.d.ts +10 -0
  495. package/sdk/dist/query/check-completion.d.ts.map +1 -0
  496. package/sdk/dist/query/check-completion.js +157 -0
  497. package/sdk/dist/query/check-completion.js.map +1 -0
  498. package/sdk/dist/query/check-decision-coverage.d.ts +33 -0
  499. package/sdk/dist/query/check-decision-coverage.d.ts.map +1 -0
  500. package/sdk/dist/query/check-decision-coverage.js +472 -0
  501. package/sdk/dist/query/check-decision-coverage.js.map +1 -0
  502. package/sdk/dist/query/check-gates.d.ts +10 -0
  503. package/sdk/dist/query/check-gates.d.ts.map +1 -0
  504. package/sdk/dist/query/check-gates.js +89 -0
  505. package/sdk/dist/query/check-gates.js.map +1 -0
  506. package/sdk/dist/query/check-ship-ready.d.ts +10 -0
  507. package/sdk/dist/query/check-ship-ready.d.ts.map +1 -0
  508. package/sdk/dist/query/check-ship-ready.js +93 -0
  509. package/sdk/dist/query/check-ship-ready.js.map +1 -0
  510. package/sdk/dist/query/check-verification-status.d.ts +10 -0
  511. package/sdk/dist/query/check-verification-status.d.ts.map +1 -0
  512. package/sdk/dist/query/check-verification-status.js +142 -0
  513. package/sdk/dist/query/check-verification-status.js.map +1 -0
  514. package/sdk/dist/query/command-aliases.generated.d.ts +31 -0
  515. package/sdk/dist/query/command-aliases.generated.d.ts.map +1 -0
  516. package/sdk/dist/query/command-aliases.generated.js +135 -0
  517. package/sdk/dist/query/command-aliases.generated.js.map +1 -0
  518. package/sdk/dist/query/command-catalog.d.ts +9 -0
  519. package/sdk/dist/query/command-catalog.d.ts.map +1 -0
  520. package/sdk/dist/query/command-catalog.js +17 -0
  521. package/sdk/dist/query/command-catalog.js.map +1 -0
  522. package/sdk/dist/query/command-definition.d.ts +19 -0
  523. package/sdk/dist/query/command-definition.d.ts.map +1 -0
  524. package/sdk/dist/query/command-definition.js +44 -0
  525. package/sdk/dist/query/command-definition.js.map +1 -0
  526. package/sdk/dist/query/command-family-handlers.d.ts +3 -0
  527. package/sdk/dist/query/command-family-handlers.d.ts.map +1 -0
  528. package/sdk/dist/query/command-family-handlers.js +94 -0
  529. package/sdk/dist/query/command-family-handlers.js.map +1 -0
  530. package/sdk/dist/query/command-manifest.d.ts +2 -0
  531. package/sdk/dist/query/command-manifest.d.ts.map +1 -0
  532. package/sdk/dist/query/command-manifest.init.d.ts +6 -0
  533. package/sdk/dist/query/command-manifest.init.d.ts.map +1 -0
  534. package/sdk/dist/query/command-manifest.init.js +23 -0
  535. package/sdk/dist/query/command-manifest.init.js.map +1 -0
  536. package/sdk/dist/query/command-manifest.js +17 -0
  537. package/sdk/dist/query/command-manifest.js.map +1 -0
  538. package/sdk/dist/query/command-manifest.non-family.d.ts +9 -0
  539. package/sdk/dist/query/command-manifest.non-family.d.ts.map +1 -0
  540. package/sdk/dist/query/command-manifest.non-family.js +59 -0
  541. package/sdk/dist/query/command-manifest.non-family.js.map +1 -0
  542. package/sdk/dist/query/command-manifest.phase.d.ts +6 -0
  543. package/sdk/dist/query/command-manifest.phase.d.ts.map +1 -0
  544. package/sdk/dist/query/command-manifest.phase.js +15 -0
  545. package/sdk/dist/query/command-manifest.phase.js.map +1 -0
  546. package/sdk/dist/query/command-manifest.phases.d.ts +7 -0
  547. package/sdk/dist/query/command-manifest.phases.d.ts.map +1 -0
  548. package/sdk/dist/query/command-manifest.phases.js +10 -0
  549. package/sdk/dist/query/command-manifest.phases.js.map +1 -0
  550. package/sdk/dist/query/command-manifest.roadmap.d.ts +6 -0
  551. package/sdk/dist/query/command-manifest.roadmap.d.ts.map +1 -0
  552. package/sdk/dist/query/command-manifest.roadmap.js +10 -0
  553. package/sdk/dist/query/command-manifest.roadmap.js.map +1 -0
  554. package/sdk/dist/query/command-manifest.state.d.ts +9 -0
  555. package/sdk/dist/query/command-manifest.state.d.ts.map +1 -0
  556. package/sdk/dist/query/command-manifest.state.js +30 -0
  557. package/sdk/dist/query/command-manifest.state.js.map +1 -0
  558. package/sdk/dist/query/command-manifest.types.d.ts +12 -0
  559. package/sdk/dist/query/command-manifest.types.d.ts.map +1 -0
  560. package/sdk/dist/query/command-manifest.types.js +2 -0
  561. package/sdk/dist/query/command-manifest.types.js.map +1 -0
  562. package/sdk/dist/query/command-manifest.validate.d.ts +6 -0
  563. package/sdk/dist/query/command-manifest.validate.d.ts.map +1 -0
  564. package/sdk/dist/query/command-manifest.validate.js +10 -0
  565. package/sdk/dist/query/command-manifest.validate.js.map +1 -0
  566. package/sdk/dist/query/command-manifest.verify.d.ts +6 -0
  567. package/sdk/dist/query/command-manifest.verify.d.ts.map +1 -0
  568. package/sdk/dist/query/command-manifest.verify.js +14 -0
  569. package/sdk/dist/query/command-manifest.verify.js.map +1 -0
  570. package/sdk/dist/query/command-static-catalog-domain.d.ts +3 -0
  571. package/sdk/dist/query/command-static-catalog-domain.d.ts.map +1 -0
  572. package/sdk/dist/query/command-static-catalog-domain.js +116 -0
  573. package/sdk/dist/query/command-static-catalog-domain.js.map +1 -0
  574. package/sdk/dist/query/command-static-catalog-foundation.d.ts +7 -0
  575. package/sdk/dist/query/command-static-catalog-foundation.d.ts.map +1 -0
  576. package/sdk/dist/query/command-static-catalog-foundation.js +98 -0
  577. package/sdk/dist/query/command-static-catalog-foundation.js.map +1 -0
  578. package/sdk/dist/query/command-topology.d.ts +32 -0
  579. package/sdk/dist/query/command-topology.d.ts.map +1 -0
  580. package/sdk/dist/query/command-topology.js +66 -0
  581. package/sdk/dist/query/command-topology.js.map +1 -0
  582. package/sdk/dist/query/commands-list.d.ts +14 -0
  583. package/sdk/dist/query/commands-list.d.ts.map +1 -0
  584. package/sdk/dist/query/commands-list.js +18 -0
  585. package/sdk/dist/query/commands-list.js.map +1 -0
  586. package/sdk/dist/query/commit.d.ts +79 -0
  587. package/sdk/dist/query/commit.d.ts.map +1 -0
  588. package/sdk/dist/query/commit.js +340 -0
  589. package/sdk/dist/query/commit.js.map +1 -0
  590. package/sdk/dist/query/config-gates.d.ts +12 -0
  591. package/sdk/dist/query/config-gates.d.ts.map +1 -0
  592. package/sdk/dist/query/config-gates.js +66 -0
  593. package/sdk/dist/query/config-gates.js.map +1 -0
  594. package/sdk/dist/query/config-mutation.d.ts +86 -0
  595. package/sdk/dist/query/config-mutation.d.ts.map +1 -0
  596. package/sdk/dist/query/config-mutation.js +518 -0
  597. package/sdk/dist/query/config-mutation.js.map +1 -0
  598. package/sdk/dist/query/config-query.d.ts +57 -0
  599. package/sdk/dist/query/config-query.d.ts.map +1 -0
  600. package/sdk/dist/query/config-query.js +208 -0
  601. package/sdk/dist/query/config-query.js.map +1 -0
  602. package/sdk/dist/query/config-schema.d.ts +36 -0
  603. package/sdk/dist/query/config-schema.d.ts.map +1 -0
  604. package/sdk/dist/query/config-schema.js +147 -0
  605. package/sdk/dist/query/config-schema.js.map +1 -0
  606. package/sdk/dist/query/decisions.d.ts +58 -0
  607. package/sdk/dist/query/decisions.d.ts.map +1 -0
  608. package/sdk/dist/query/decisions.js +161 -0
  609. package/sdk/dist/query/decisions.js.map +1 -0
  610. package/sdk/dist/query/detect-custom-files.d.ts +11 -0
  611. package/sdk/dist/query/detect-custom-files.d.ts.map +1 -0
  612. package/sdk/dist/query/detect-custom-files.js +89 -0
  613. package/sdk/dist/query/detect-custom-files.js.map +1 -0
  614. package/sdk/dist/query/detect-phase-type.d.ts +9 -0
  615. package/sdk/dist/query/detect-phase-type.d.ts.map +1 -0
  616. package/sdk/dist/query/detect-phase-type.js +124 -0
  617. package/sdk/dist/query/detect-phase-type.js.map +1 -0
  618. package/sdk/dist/query/docs-init.d.ts +26 -0
  619. package/sdk/dist/query/docs-init.d.ts.map +1 -0
  620. package/sdk/dist/query/docs-init.js +231 -0
  621. package/sdk/dist/query/docs-init.js.map +1 -0
  622. package/sdk/dist/query/fallow-audit.d.ts +44 -0
  623. package/sdk/dist/query/fallow-audit.d.ts.map +1 -0
  624. package/sdk/dist/query/fallow-audit.js +44 -0
  625. package/sdk/dist/query/fallow-audit.js.map +1 -0
  626. package/sdk/dist/query/frontmatter-mutation.d.ts +77 -0
  627. package/sdk/dist/query/frontmatter-mutation.d.ts.map +1 -0
  628. package/sdk/dist/query/frontmatter-mutation.js +317 -0
  629. package/sdk/dist/query/frontmatter-mutation.js.map +1 -0
  630. package/sdk/dist/query/frontmatter.d.ts +93 -0
  631. package/sdk/dist/query/frontmatter.d.ts.map +1 -0
  632. package/sdk/dist/query/frontmatter.js +365 -0
  633. package/sdk/dist/query/frontmatter.js.map +1 -0
  634. package/sdk/dist/query/helpers.d.ts +191 -0
  635. package/sdk/dist/query/helpers.d.ts.map +1 -0
  636. package/sdk/dist/query/helpers.js +613 -0
  637. package/sdk/dist/query/helpers.js.map +1 -0
  638. package/sdk/dist/query/index.d.ts +8 -0
  639. package/sdk/dist/query/index.d.ts.map +1 -0
  640. package/sdk/dist/query/index.js +6 -0
  641. package/sdk/dist/query/index.js.map +1 -0
  642. package/sdk/dist/query/init-complex.d.ts +47 -0
  643. package/sdk/dist/query/init-complex.d.ts.map +1 -0
  644. package/sdk/dist/query/init-complex.js +718 -0
  645. package/sdk/dist/query/init-complex.js.map +1 -0
  646. package/sdk/dist/query/init.d.ts +106 -0
  647. package/sdk/dist/query/init.d.ts.map +1 -0
  648. package/sdk/dist/query/init.js +1159 -0
  649. package/sdk/dist/query/init.js.map +1 -0
  650. package/sdk/dist/query/intel.d.ts +43 -0
  651. package/sdk/dist/query/intel.d.ts.map +1 -0
  652. package/sdk/dist/query/intel.js +416 -0
  653. package/sdk/dist/query/intel.js.map +1 -0
  654. package/sdk/dist/query/mutation-event-decorator.d.ts +5 -0
  655. package/sdk/dist/query/mutation-event-decorator.d.ts.map +1 -0
  656. package/sdk/dist/query/mutation-event-decorator.js +28 -0
  657. package/sdk/dist/query/mutation-event-decorator.js.map +1 -0
  658. package/sdk/dist/query/mutation-event-mapper.d.ts +4 -0
  659. package/sdk/dist/query/mutation-event-mapper.d.ts.map +1 -0
  660. package/sdk/dist/query/mutation-event-mapper.js +70 -0
  661. package/sdk/dist/query/mutation-event-mapper.js.map +1 -0
  662. package/sdk/dist/query/mvp.d.ts +113 -0
  663. package/sdk/dist/query/mvp.d.ts.map +1 -0
  664. package/sdk/dist/query/mvp.js +225 -0
  665. package/sdk/dist/query/mvp.js.map +1 -0
  666. package/sdk/dist/query/phase-filesystem-adapter.d.ts +4 -0
  667. package/sdk/dist/query/phase-filesystem-adapter.d.ts.map +1 -0
  668. package/sdk/dist/query/phase-filesystem-adapter.js +33 -0
  669. package/sdk/dist/query/phase-filesystem-adapter.js.map +1 -0
  670. package/sdk/dist/query/phase-lifecycle-policy.d.ts +34 -0
  671. package/sdk/dist/query/phase-lifecycle-policy.d.ts.map +1 -0
  672. package/sdk/dist/query/phase-lifecycle-policy.js +138 -0
  673. package/sdk/dist/query/phase-lifecycle-policy.js.map +1 -0
  674. package/sdk/dist/query/phase-lifecycle.d.ts +116 -0
  675. package/sdk/dist/query/phase-lifecycle.d.ts.map +1 -0
  676. package/sdk/dist/query/phase-lifecycle.js +1486 -0
  677. package/sdk/dist/query/phase-lifecycle.js.map +1 -0
  678. package/sdk/dist/query/phase-list-queries.d.ts +18 -0
  679. package/sdk/dist/query/phase-list-queries.d.ts.map +1 -0
  680. package/sdk/dist/query/phase-list-queries.js +129 -0
  681. package/sdk/dist/query/phase-list-queries.js.map +1 -0
  682. package/sdk/dist/query/phase-ready.d.ts +9 -0
  683. package/sdk/dist/query/phase-ready.d.ts.map +1 -0
  684. package/sdk/dist/query/phase-ready.js +132 -0
  685. package/sdk/dist/query/phase-ready.js.map +1 -0
  686. package/sdk/dist/query/phase-roadmap-mutation.d.ts +13 -0
  687. package/sdk/dist/query/phase-roadmap-mutation.d.ts.map +1 -0
  688. package/sdk/dist/query/phase-roadmap-mutation.js +65 -0
  689. package/sdk/dist/query/phase-roadmap-mutation.js.map +1 -0
  690. package/sdk/dist/query/phase.d.ts +48 -0
  691. package/sdk/dist/query/phase.d.ts.map +1 -0
  692. package/sdk/dist/query/phase.js +451 -0
  693. package/sdk/dist/query/phase.js.map +1 -0
  694. package/sdk/dist/query/pipeline.d.ts +53 -0
  695. package/sdk/dist/query/pipeline.d.ts.map +1 -0
  696. package/sdk/dist/query/pipeline.js +198 -0
  697. package/sdk/dist/query/pipeline.js.map +1 -0
  698. package/sdk/dist/query/plan-scan.d.ts +14 -0
  699. package/sdk/dist/query/plan-scan.d.ts.map +1 -0
  700. package/sdk/dist/query/plan-scan.js +70 -0
  701. package/sdk/dist/query/plan-scan.js.map +1 -0
  702. package/sdk/dist/query/plan-task-structure.d.ts +9 -0
  703. package/sdk/dist/query/plan-task-structure.d.ts.map +1 -0
  704. package/sdk/dist/query/plan-task-structure.js +59 -0
  705. package/sdk/dist/query/plan-task-structure.js.map +1 -0
  706. package/sdk/dist/query/profile-extract-messages.d.ts +40 -0
  707. package/sdk/dist/query/profile-extract-messages.d.ts.map +1 -0
  708. package/sdk/dist/query/profile-extract-messages.js +195 -0
  709. package/sdk/dist/query/profile-extract-messages.js.map +1 -0
  710. package/sdk/dist/query/profile-output.d.ts +11 -0
  711. package/sdk/dist/query/profile-output.d.ts.map +1 -0
  712. package/sdk/dist/query/profile-output.js +873 -0
  713. package/sdk/dist/query/profile-output.js.map +1 -0
  714. package/sdk/dist/query/profile-questionnaire-data.d.ts +21 -0
  715. package/sdk/dist/query/profile-questionnaire-data.d.ts.map +1 -0
  716. package/sdk/dist/query/profile-questionnaire-data.js +171 -0
  717. package/sdk/dist/query/profile-questionnaire-data.js.map +1 -0
  718. package/sdk/dist/query/profile-sample.d.ts +22 -0
  719. package/sdk/dist/query/profile-sample.d.ts.map +1 -0
  720. package/sdk/dist/query/profile-sample.js +136 -0
  721. package/sdk/dist/query/profile-sample.js.map +1 -0
  722. package/sdk/dist/query/profile-scan-sessions.d.ts +49 -0
  723. package/sdk/dist/query/profile-scan-sessions.d.ts.map +1 -0
  724. package/sdk/dist/query/profile-scan-sessions.js +137 -0
  725. package/sdk/dist/query/profile-scan-sessions.js.map +1 -0
  726. package/sdk/dist/query/profile.d.ts +61 -0
  727. package/sdk/dist/query/profile.d.ts.map +1 -0
  728. package/sdk/dist/query/profile.js +307 -0
  729. package/sdk/dist/query/profile.js.map +1 -0
  730. package/sdk/dist/query/progress.d.ts +77 -0
  731. package/sdk/dist/query/progress.d.ts.map +1 -0
  732. package/sdk/dist/query/progress.js +481 -0
  733. package/sdk/dist/query/progress.js.map +1 -0
  734. package/sdk/dist/query/query-cli-adapter.d.ts +8 -0
  735. package/sdk/dist/query/query-cli-adapter.d.ts.map +1 -0
  736. package/sdk/dist/query/query-cli-adapter.js +32 -0
  737. package/sdk/dist/query/query-cli-adapter.js.map +1 -0
  738. package/sdk/dist/query/query-cli-output.d.ts +9 -0
  739. package/sdk/dist/query/query-cli-output.d.ts.map +1 -0
  740. package/sdk/dist/query/query-cli-output.js +28 -0
  741. package/sdk/dist/query/query-cli-output.js.map +1 -0
  742. package/sdk/dist/query/query-command-diagnosis.d.ts +6 -0
  743. package/sdk/dist/query/query-command-diagnosis.d.ts.map +1 -0
  744. package/sdk/dist/query/query-command-diagnosis.js +6 -0
  745. package/sdk/dist/query/query-command-diagnosis.js.map +1 -0
  746. package/sdk/dist/query/query-command-resolution-strategy.d.ts +29 -0
  747. package/sdk/dist/query/query-command-resolution-strategy.d.ts.map +1 -0
  748. package/sdk/dist/query/query-command-resolution-strategy.js +103 -0
  749. package/sdk/dist/query/query-command-resolution-strategy.js.map +1 -0
  750. package/sdk/dist/query/query-command-semantics.d.ts +7 -0
  751. package/sdk/dist/query/query-command-semantics.d.ts.map +1 -0
  752. package/sdk/dist/query/query-command-semantics.js +7 -0
  753. package/sdk/dist/query/query-command-semantics.js.map +1 -0
  754. package/sdk/dist/query/query-dispatch-contract.d.ts +21 -0
  755. package/sdk/dist/query/query-dispatch-contract.d.ts.map +1 -0
  756. package/sdk/dist/query/query-dispatch-contract.js +2 -0
  757. package/sdk/dist/query/query-dispatch-contract.js.map +1 -0
  758. package/sdk/dist/query/query-dispatch-error-mapper.d.ts +6 -0
  759. package/sdk/dist/query/query-dispatch-error-mapper.d.ts.map +1 -0
  760. package/sdk/dist/query/query-dispatch-error-mapper.js +6 -0
  761. package/sdk/dist/query/query-dispatch-error-mapper.js.map +1 -0
  762. package/sdk/dist/query/query-dispatch-formatting.d.ts +6 -0
  763. package/sdk/dist/query/query-dispatch-formatting.d.ts.map +1 -0
  764. package/sdk/dist/query/query-dispatch-formatting.js +6 -0
  765. package/sdk/dist/query/query-dispatch-formatting.js.map +1 -0
  766. package/sdk/dist/query/query-dispatch-input-validation.d.ts +6 -0
  767. package/sdk/dist/query/query-dispatch-input-validation.d.ts.map +1 -0
  768. package/sdk/dist/query/query-dispatch-input-validation.js +6 -0
  769. package/sdk/dist/query/query-dispatch-input-validation.js.map +1 -0
  770. package/sdk/dist/query/query-dispatch-observability.d.ts +2 -0
  771. package/sdk/dist/query/query-dispatch-observability.d.ts.map +1 -0
  772. package/sdk/dist/query/query-dispatch-observability.js +7 -0
  773. package/sdk/dist/query/query-dispatch-observability.js.map +1 -0
  774. package/sdk/dist/query/query-dispatch-plan.d.ts +6 -0
  775. package/sdk/dist/query/query-dispatch-plan.d.ts.map +1 -0
  776. package/sdk/dist/query/query-dispatch-plan.js +6 -0
  777. package/sdk/dist/query/query-dispatch-plan.js.map +1 -0
  778. package/sdk/dist/query/query-dispatch-result-builder.d.ts +6 -0
  779. package/sdk/dist/query/query-dispatch-result-builder.d.ts.map +1 -0
  780. package/sdk/dist/query/query-dispatch-result-builder.js +6 -0
  781. package/sdk/dist/query/query-dispatch-result-builder.js.map +1 -0
  782. package/sdk/dist/query/query-dispatch.d.ts +48 -0
  783. package/sdk/dist/query/query-dispatch.d.ts.map +1 -0
  784. package/sdk/dist/query/query-dispatch.js +175 -0
  785. package/sdk/dist/query/query-dispatch.js.map +1 -0
  786. package/sdk/dist/query/query-error-details-schema.d.ts +19 -0
  787. package/sdk/dist/query/query-error-details-schema.d.ts.map +1 -0
  788. package/sdk/dist/query/query-error-details-schema.js +10 -0
  789. package/sdk/dist/query/query-error-details-schema.js.map +1 -0
  790. package/sdk/dist/query/query-error-taxonomy.d.ts +38 -0
  791. package/sdk/dist/query/query-error-taxonomy.d.ts.map +1 -0
  792. package/sdk/dist/query/query-error-taxonomy.js +74 -0
  793. package/sdk/dist/query/query-error-taxonomy.js.map +1 -0
  794. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts +14 -0
  795. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts.map +1 -0
  796. package/sdk/dist/query/query-fallback-bridge-adapter.js +33 -0
  797. package/sdk/dist/query/query-fallback-bridge-adapter.js.map +1 -0
  798. package/sdk/dist/query/query-fallback-executor.d.ts +11 -0
  799. package/sdk/dist/query/query-fallback-executor.d.ts.map +1 -0
  800. package/sdk/dist/query/query-fallback-executor.js +31 -0
  801. package/sdk/dist/query/query-fallback-executor.js.map +1 -0
  802. package/sdk/dist/query/query-fallback-output-classifier.d.ts +6 -0
  803. package/sdk/dist/query/query-fallback-output-classifier.d.ts.map +1 -0
  804. package/sdk/dist/query/query-fallback-output-classifier.js +27 -0
  805. package/sdk/dist/query/query-fallback-output-classifier.js.map +1 -0
  806. package/sdk/dist/query/query-fallback-policy.d.ts +6 -0
  807. package/sdk/dist/query/query-fallback-policy.d.ts.map +1 -0
  808. package/sdk/dist/query/query-fallback-policy.js +7 -0
  809. package/sdk/dist/query/query-fallback-policy.js.map +1 -0
  810. package/sdk/dist/query/query-native-dispatch-adapter.d.ts +7 -0
  811. package/sdk/dist/query/query-native-dispatch-adapter.d.ts.map +1 -0
  812. package/sdk/dist/query/query-native-dispatch-adapter.js +6 -0
  813. package/sdk/dist/query/query-native-dispatch-adapter.js.map +1 -0
  814. package/sdk/dist/query/query-policy-capability.d.ts +10 -0
  815. package/sdk/dist/query/query-policy-capability.d.ts.map +1 -0
  816. package/sdk/dist/query/query-policy-capability.js +17 -0
  817. package/sdk/dist/query/query-policy-capability.js.map +1 -0
  818. package/sdk/dist/query/query-runtime-context.d.ts +19 -0
  819. package/sdk/dist/query/query-runtime-context.d.ts.map +1 -0
  820. package/sdk/dist/query/query-runtime-context.js +31 -0
  821. package/sdk/dist/query/query-runtime-context.js.map +1 -0
  822. package/sdk/dist/query/query-unknown-command-hints.d.ts +2 -0
  823. package/sdk/dist/query/query-unknown-command-hints.d.ts.map +1 -0
  824. package/sdk/dist/query/query-unknown-command-hints.js +6 -0
  825. package/sdk/dist/query/query-unknown-command-hints.js.map +1 -0
  826. package/sdk/dist/query/registry-assembly-descriptor.d.ts +12 -0
  827. package/sdk/dist/query/registry-assembly-descriptor.d.ts.map +1 -0
  828. package/sdk/dist/query/registry-assembly-descriptor.js +61 -0
  829. package/sdk/dist/query/registry-assembly-descriptor.js.map +1 -0
  830. package/sdk/dist/query/registry-assembly-invariants.d.ts +30 -0
  831. package/sdk/dist/query/registry-assembly-invariants.d.ts.map +1 -0
  832. package/sdk/dist/query/registry-assembly-invariants.js +77 -0
  833. package/sdk/dist/query/registry-assembly-invariants.js.map +1 -0
  834. package/sdk/dist/query/registry-assembly.d.ts +10 -0
  835. package/sdk/dist/query/registry-assembly.d.ts.map +1 -0
  836. package/sdk/dist/query/registry-assembly.js +53 -0
  837. package/sdk/dist/query/registry-assembly.js.map +1 -0
  838. package/sdk/dist/query/registry.d.ts +90 -0
  839. package/sdk/dist/query/registry.d.ts.map +1 -0
  840. package/sdk/dist/query/registry.js +129 -0
  841. package/sdk/dist/query/registry.js.map +1 -0
  842. package/sdk/dist/query/requirements-extract-from-plans.d.ts +9 -0
  843. package/sdk/dist/query/requirements-extract-from-plans.d.ts.map +1 -0
  844. package/sdk/dist/query/requirements-extract-from-plans.js +76 -0
  845. package/sdk/dist/query/requirements-extract-from-plans.js.map +1 -0
  846. package/sdk/dist/query/roadmap-update-plan-progress.d.ts +11 -0
  847. package/sdk/dist/query/roadmap-update-plan-progress.d.ts.map +1 -0
  848. package/sdk/dist/query/roadmap-update-plan-progress.js +124 -0
  849. package/sdk/dist/query/roadmap-update-plan-progress.js.map +1 -0
  850. package/sdk/dist/query/roadmap.d.ts +137 -0
  851. package/sdk/dist/query/roadmap.d.ts.map +1 -0
  852. package/sdk/dist/query/roadmap.js +753 -0
  853. package/sdk/dist/query/roadmap.js.map +1 -0
  854. package/sdk/dist/query/route-next-action.d.ts +9 -0
  855. package/sdk/dist/query/route-next-action.d.ts.map +1 -0
  856. package/sdk/dist/query/route-next-action.js +318 -0
  857. package/sdk/dist/query/route-next-action.js.map +1 -0
  858. package/sdk/dist/query/schema-detect.d.ts +21 -0
  859. package/sdk/dist/query/schema-detect.d.ts.map +1 -0
  860. package/sdk/dist/query/schema-detect.js +146 -0
  861. package/sdk/dist/query/schema-detect.js.map +1 -0
  862. package/sdk/dist/query/secrets.d.ts +27 -0
  863. package/sdk/dist/query/secrets.d.ts.map +1 -0
  864. package/sdk/dist/query/secrets.js +42 -0
  865. package/sdk/dist/query/secrets.js.map +1 -0
  866. package/sdk/dist/query/skill-manifest.d.ts +50 -0
  867. package/sdk/dist/query/skill-manifest.d.ts.map +1 -0
  868. package/sdk/dist/query/skill-manifest.js +171 -0
  869. package/sdk/dist/query/skill-manifest.js.map +1 -0
  870. package/sdk/dist/query/skills.d.ts +27 -0
  871. package/sdk/dist/query/skills.d.ts.map +1 -0
  872. package/sdk/dist/query/skills.js +137 -0
  873. package/sdk/dist/query/skills.js.map +1 -0
  874. package/sdk/dist/query/state-document.d.ts +14 -0
  875. package/sdk/dist/query/state-document.d.ts.map +1 -0
  876. package/sdk/dist/query/state-document.js +110 -0
  877. package/sdk/dist/query/state-document.js.map +1 -0
  878. package/sdk/dist/query/state-mutation.d.ts +224 -0
  879. package/sdk/dist/query/state-mutation.d.ts.map +1 -0
  880. package/sdk/dist/query/state-mutation.js +1539 -0
  881. package/sdk/dist/query/state-mutation.js.map +1 -0
  882. package/sdk/dist/query/state-project-load.d.ts +23 -0
  883. package/sdk/dist/query/state-project-load.d.ts.map +1 -0
  884. package/sdk/dist/query/state-project-load.js +75 -0
  885. package/sdk/dist/query/state-project-load.js.map +1 -0
  886. package/sdk/dist/query/state.d.ts +78 -0
  887. package/sdk/dist/query/state.d.ts.map +1 -0
  888. package/sdk/dist/query/state.js +430 -0
  889. package/sdk/dist/query/state.js.map +1 -0
  890. package/sdk/dist/query/summary.d.ts +18 -0
  891. package/sdk/dist/query/summary.d.ts.map +1 -0
  892. package/sdk/dist/query/summary.js +249 -0
  893. package/sdk/dist/query/summary.js.map +1 -0
  894. package/sdk/dist/query/template.d.ts +46 -0
  895. package/sdk/dist/query/template.d.ts.map +1 -0
  896. package/sdk/dist/query/template.js +210 -0
  897. package/sdk/dist/query/template.js.map +1 -0
  898. package/sdk/dist/query/uat.d.ts +34 -0
  899. package/sdk/dist/query/uat.d.ts.map +1 -0
  900. package/sdk/dist/query/uat.js +339 -0
  901. package/sdk/dist/query/uat.js.map +1 -0
  902. package/sdk/dist/query/utils.d.ts +59 -0
  903. package/sdk/dist/query/utils.d.ts.map +1 -0
  904. package/sdk/dist/query/utils.js +74 -0
  905. package/sdk/dist/query/utils.js.map +1 -0
  906. package/sdk/dist/query/validate.d.ts +67 -0
  907. package/sdk/dist/query/validate.d.ts.map +1 -0
  908. package/sdk/dist/query/validate.js +908 -0
  909. package/sdk/dist/query/validate.js.map +1 -0
  910. package/sdk/dist/query/verify.d.ts +110 -0
  911. package/sdk/dist/query/verify.d.ts.map +1 -0
  912. package/sdk/dist/query/verify.js +631 -0
  913. package/sdk/dist/query/verify.js.map +1 -0
  914. package/sdk/dist/query/websearch.d.ts +24 -0
  915. package/sdk/dist/query/websearch.d.ts.map +1 -0
  916. package/sdk/dist/query/websearch.js +68 -0
  917. package/sdk/dist/query/websearch.js.map +1 -0
  918. package/sdk/dist/query/workspace.d.ts +62 -0
  919. package/sdk/dist/query/workspace.d.ts.map +1 -0
  920. package/sdk/dist/query/workspace.js +104 -0
  921. package/sdk/dist/query/workspace.js.map +1 -0
  922. package/sdk/dist/query/workstream-inventory.d.ts +52 -0
  923. package/sdk/dist/query/workstream-inventory.d.ts.map +1 -0
  924. package/sdk/dist/query/workstream-inventory.js +141 -0
  925. package/sdk/dist/query/workstream-inventory.js.map +1 -0
  926. package/sdk/dist/query/workstream.d.ts +35 -0
  927. package/sdk/dist/query/workstream.d.ts.map +1 -0
  928. package/sdk/dist/query/workstream.js +298 -0
  929. package/sdk/dist/query/workstream.js.map +1 -0
  930. package/sdk/dist/query/worktree.d.ts +3 -0
  931. package/sdk/dist/query/worktree.d.ts.map +1 -0
  932. package/sdk/dist/query/worktree.js +36 -0
  933. package/sdk/dist/query/worktree.js.map +1 -0
  934. package/sdk/dist/query-command-executor.d.ts +22 -0
  935. package/sdk/dist/query-command-executor.d.ts.map +1 -0
  936. package/sdk/dist/query-command-executor.js +22 -0
  937. package/sdk/dist/query-command-executor.js.map +1 -0
  938. package/sdk/dist/query-execution-policy.d.ts +24 -0
  939. package/sdk/dist/query-execution-policy.d.ts.map +1 -0
  940. package/sdk/dist/query-execution-policy.js +27 -0
  941. package/sdk/dist/query-execution-policy.js.map +1 -0
  942. package/sdk/dist/query-failure-classification.d.ts +9 -0
  943. package/sdk/dist/query-failure-classification.d.ts.map +1 -0
  944. package/sdk/dist/query-failure-classification.js +32 -0
  945. package/sdk/dist/query-failure-classification.js.map +1 -0
  946. package/sdk/dist/query-hotpath-methods.d.ts +19 -0
  947. package/sdk/dist/query-hotpath-methods.d.ts.map +1 -0
  948. package/sdk/dist/query-hotpath-methods.js +34 -0
  949. package/sdk/dist/query-hotpath-methods.js.map +1 -0
  950. package/sdk/dist/query-native-direct-adapter.d.ts +20 -0
  951. package/sdk/dist/query-native-direct-adapter.d.ts.map +1 -0
  952. package/sdk/dist/query-native-direct-adapter.js +52 -0
  953. package/sdk/dist/query-native-direct-adapter.js.map +1 -0
  954. package/sdk/dist/query-native-hotpath-adapter.d.ts +15 -0
  955. package/sdk/dist/query-native-hotpath-adapter.d.ts.map +1 -0
  956. package/sdk/dist/query-native-hotpath-adapter.js +32 -0
  957. package/sdk/dist/query-native-hotpath-adapter.js.map +1 -0
  958. package/sdk/dist/query-raw-output-projection.d.ts +6 -0
  959. package/sdk/dist/query-raw-output-projection.d.ts.map +1 -0
  960. package/sdk/dist/query-raw-output-projection.js +67 -0
  961. package/sdk/dist/query-raw-output-projection.js.map +1 -0
  962. package/sdk/dist/query-runtime-bridge.d.ts +61 -0
  963. package/sdk/dist/query-runtime-bridge.d.ts.map +1 -0
  964. package/sdk/dist/query-runtime-bridge.js +144 -0
  965. package/sdk/dist/query-runtime-bridge.js.map +1 -0
  966. package/sdk/dist/query-sdd-tools-path.d.ts +2 -0
  967. package/sdk/dist/query-sdd-tools-path.d.ts.map +1 -0
  968. package/sdk/dist/query-sdd-tools-path.js +2 -0
  969. package/sdk/dist/query-sdd-tools-path.js.map +1 -0
  970. package/sdk/dist/query-sdd-tools-runtime.d.ts +20 -0
  971. package/sdk/dist/query-sdd-tools-runtime.d.ts.map +1 -0
  972. package/sdk/dist/query-sdd-tools-runtime.js +47 -0
  973. package/sdk/dist/query-sdd-tools-runtime.js.map +1 -0
  974. package/sdk/dist/query-subprocess-adapter.d.ts +18 -0
  975. package/sdk/dist/query-subprocess-adapter.d.ts.map +1 -0
  976. package/sdk/dist/query-subprocess-adapter.js +92 -0
  977. package/sdk/dist/query-subprocess-adapter.js.map +1 -0
  978. package/sdk/dist/query-tools-error-factory.d.ts +16 -0
  979. package/sdk/dist/query-tools-error-factory.d.ts.map +1 -0
  980. package/sdk/dist/query-tools-error-factory.js +33 -0
  981. package/sdk/dist/query-tools-error-factory.js.map +1 -0
  982. package/sdk/dist/research-gate.d.ts +24 -0
  983. package/sdk/dist/research-gate.d.ts.map +1 -0
  984. package/sdk/dist/research-gate.js +70 -0
  985. package/sdk/dist/research-gate.js.map +1 -0
  986. package/sdk/dist/runtime-gate.d.ts +14 -0
  987. package/sdk/dist/runtime-gate.d.ts.map +1 -0
  988. package/sdk/dist/runtime-gate.js +48 -0
  989. package/sdk/dist/runtime-gate.js.map +1 -0
  990. package/sdk/dist/sdd-tools-error.d.ts +23 -0
  991. package/sdk/dist/sdd-tools-error.d.ts.map +1 -0
  992. package/sdk/dist/sdd-tools-error.js +29 -0
  993. package/sdk/dist/sdd-tools-error.js.map +1 -0
  994. package/sdk/dist/sdd-tools.d.ts +97 -0
  995. package/sdk/dist/sdd-tools.d.ts.map +1 -0
  996. package/sdk/dist/sdd-tools.js +168 -0
  997. package/sdk/dist/sdd-tools.js.map +1 -0
  998. package/sdk/dist/sdd-transport-policy.d.ts +10 -0
  999. package/sdk/dist/sdd-transport-policy.d.ts.map +1 -0
  1000. package/sdk/dist/sdd-transport-policy.js +32 -0
  1001. package/sdk/dist/sdd-transport-policy.js.map +1 -0
  1002. package/sdk/dist/sdd-transport.d.ts +39 -0
  1003. package/sdk/dist/sdd-transport.d.ts.map +1 -0
  1004. package/sdk/dist/sdd-transport.js +78 -0
  1005. package/sdk/dist/sdd-transport.js.map +1 -0
  1006. package/sdk/dist/sdk-package-compatibility.d.ts +38 -0
  1007. package/sdk/dist/sdk-package-compatibility.d.ts.map +1 -0
  1008. package/sdk/dist/sdk-package-compatibility.js +90 -0
  1009. package/sdk/dist/sdk-package-compatibility.js.map +1 -0
  1010. package/sdk/dist/session-runner.d.ts +40 -0
  1011. package/sdk/dist/session-runner.d.ts.map +1 -0
  1012. package/sdk/dist/session-runner.js +274 -0
  1013. package/sdk/dist/session-runner.js.map +1 -0
  1014. package/sdk/dist/tool-scoping.d.ts +31 -0
  1015. package/sdk/dist/tool-scoping.d.ts.map +1 -0
  1016. package/sdk/dist/tool-scoping.js +54 -0
  1017. package/sdk/dist/tool-scoping.js.map +1 -0
  1018. package/sdk/dist/types.d.ts +794 -0
  1019. package/sdk/dist/types.d.ts.map +1 -0
  1020. package/sdk/dist/types.js +77 -0
  1021. package/sdk/dist/types.js.map +1 -0
  1022. package/sdk/dist/workstream-name-policy.d.ts +13 -0
  1023. package/sdk/dist/workstream-name-policy.d.ts.map +1 -0
  1024. package/sdk/dist/workstream-name-policy.js +24 -0
  1025. package/sdk/dist/workstream-name-policy.js.map +1 -0
  1026. package/sdk/dist/workstream-utils.d.ts +15 -0
  1027. package/sdk/dist/workstream-utils.d.ts.map +1 -0
  1028. package/sdk/dist/workstream-utils.js +21 -0
  1029. package/sdk/dist/workstream-utils.js.map +1 -0
  1030. package/sdk/dist/ws-transport.d.ts +32 -0
  1031. package/sdk/dist/ws-transport.d.ts.map +1 -0
  1032. package/sdk/dist/ws-transport.js +84 -0
  1033. package/sdk/dist/ws-transport.js.map +1 -0
  1034. package/sdk/package-lock.json +2502 -0
  1035. package/sdk/package.json +57 -0
  1036. package/sdk/prompts/templates/project.md +186 -0
  1037. package/sdk/prompts/templates/requirements.md +231 -0
  1038. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  1039. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  1040. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  1041. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  1042. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  1043. package/sdk/prompts/templates/roadmap.md +202 -0
  1044. package/sdk/prompts/templates/state.md +175 -0
  1045. package/sdk/shared/model-catalog.json +122 -0
  1046. package/sdk/src/assembled-prompts.test.ts +349 -0
  1047. package/sdk/src/bug-3591-sddtools-runtime-workstream.test.ts +179 -0
  1048. package/sdk/src/cli-transport.test.ts +388 -0
  1049. package/sdk/src/cli-transport.ts +130 -0
  1050. package/sdk/src/cli.test.ts +426 -0
  1051. package/sdk/src/cli.ts +589 -0
  1052. package/sdk/src/config.test.ts +271 -0
  1053. package/sdk/src/config.ts +218 -0
  1054. package/sdk/src/context-engine.test.ts +295 -0
  1055. package/sdk/src/context-engine.ts +170 -0
  1056. package/sdk/src/context-truncation.test.ts +163 -0
  1057. package/sdk/src/context-truncation.ts +233 -0
  1058. package/sdk/src/e2e.integration.test.ts +181 -0
  1059. package/sdk/src/errors.ts +72 -0
  1060. package/sdk/src/event-stream.test.ts +661 -0
  1061. package/sdk/src/event-stream.ts +441 -0
  1062. package/sdk/src/golden/capture.ts +95 -0
  1063. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  1064. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  1065. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  1066. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  1067. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  1068. package/sdk/src/golden/golden-mutation-covered.ts +17 -0
  1069. package/sdk/src/golden/golden-policy.test.ts +8 -0
  1070. package/sdk/src/golden/golden-policy.ts +120 -0
  1071. package/sdk/src/golden/golden.integration.test.ts +677 -0
  1072. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  1073. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  1074. package/sdk/src/golden/read-only-parity.integration.test.ts +133 -0
  1075. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  1076. package/sdk/src/index.ts +352 -0
  1077. package/sdk/src/init-e2e.integration.test.ts +138 -0
  1078. package/sdk/src/init-runner.test.ts +740 -0
  1079. package/sdk/src/init-runner.ts +734 -0
  1080. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  1081. package/sdk/src/logger.test.ts +149 -0
  1082. package/sdk/src/logger.ts +113 -0
  1083. package/sdk/src/milestone-runner.test.ts +421 -0
  1084. package/sdk/src/model-catalog.ts +70 -0
  1085. package/sdk/src/phase-prompt.test.ts +535 -0
  1086. package/sdk/src/phase-prompt.ts +259 -0
  1087. package/sdk/src/phase-runner-types.test.ts +421 -0
  1088. package/sdk/src/phase-runner.integration.test.ts +377 -0
  1089. package/sdk/src/phase-runner.test.ts +2720 -0
  1090. package/sdk/src/phase-runner.ts +1442 -0
  1091. package/sdk/src/plan-parser.test.ts +579 -0
  1092. package/sdk/src/plan-parser.ts +431 -0
  1093. package/sdk/src/planning-journal.test.ts +70 -0
  1094. package/sdk/src/planning-journal.ts +153 -0
  1095. package/sdk/src/planning-runtime.test.ts +29 -0
  1096. package/sdk/src/planning-runtime.ts +100 -0
  1097. package/sdk/src/prompt-builder.test.ts +318 -0
  1098. package/sdk/src/prompt-builder.ts +218 -0
  1099. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  1100. package/sdk/src/prompt-sanitizer.ts +116 -0
  1101. package/sdk/src/query/QUERY-HANDLERS.md +349 -0
  1102. package/sdk/src/query/active-workstream-store.ts +50 -0
  1103. package/sdk/src/query/agent-failure-classifier.test.ts +157 -0
  1104. package/sdk/src/query/agent-failure-classifier.ts +105 -0
  1105. package/sdk/src/query/audit-open.ts +722 -0
  1106. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  1107. package/sdk/src/query/check-auto-mode.ts +49 -0
  1108. package/sdk/src/query/check-completion.test.ts +113 -0
  1109. package/sdk/src/query/check-completion.ts +182 -0
  1110. package/sdk/src/query/check-decision-coverage.test.ts +519 -0
  1111. package/sdk/src/query/check-decision-coverage.ts +554 -0
  1112. package/sdk/src/query/check-gates.test.ts +103 -0
  1113. package/sdk/src/query/check-gates.ts +112 -0
  1114. package/sdk/src/query/check-ship-ready.test.ts +111 -0
  1115. package/sdk/src/query/check-ship-ready.ts +104 -0
  1116. package/sdk/src/query/check-verification-status.test.ts +143 -0
  1117. package/sdk/src/query/check-verification-status.ts +160 -0
  1118. package/sdk/src/query/command-aliases.generated.ts +156 -0
  1119. package/sdk/src/query/command-catalog.ts +31 -0
  1120. package/sdk/src/query/command-definition.test.ts +47 -0
  1121. package/sdk/src/query/command-definition.ts +70 -0
  1122. package/sdk/src/query/command-family-handlers.ts +117 -0
  1123. package/sdk/src/query/command-manifest.init.ts +24 -0
  1124. package/sdk/src/query/command-manifest.non-family.ts +85 -0
  1125. package/sdk/src/query/command-manifest.phase.ts +16 -0
  1126. package/sdk/src/query/command-manifest.phases.ts +11 -0
  1127. package/sdk/src/query/command-manifest.roadmap.ts +11 -0
  1128. package/sdk/src/query/command-manifest.state.ts +31 -0
  1129. package/sdk/src/query/command-manifest.ts +17 -0
  1130. package/sdk/src/query/command-manifest.types.ts +13 -0
  1131. package/sdk/src/query/command-manifest.validate.ts +11 -0
  1132. package/sdk/src/query/command-manifest.verify.ts +15 -0
  1133. package/sdk/src/query/command-resolution.test.ts +70 -0
  1134. package/sdk/src/query/command-seam-coverage.test.ts +118 -0
  1135. package/sdk/src/query/command-static-catalog-domain.ts +117 -0
  1136. package/sdk/src/query/command-static-catalog-foundation.ts +103 -0
  1137. package/sdk/src/query/command-topology.test.ts +28 -0
  1138. package/sdk/src/query/command-topology.ts +114 -0
  1139. package/sdk/src/query/commands-list.test.ts +36 -0
  1140. package/sdk/src/query/commands-list.ts +19 -0
  1141. package/sdk/src/query/commit.test.ts +485 -0
  1142. package/sdk/src/query/commit.ts +383 -0
  1143. package/sdk/src/query/config-gates.test.ts +89 -0
  1144. package/sdk/src/query/config-gates.ts +69 -0
  1145. package/sdk/src/query/config-mutation.test.ts +598 -0
  1146. package/sdk/src/query/config-mutation.ts +575 -0
  1147. package/sdk/src/query/config-query.test.ts +367 -0
  1148. package/sdk/src/query/config-query.ts +244 -0
  1149. package/sdk/src/query/config-schema.ts +159 -0
  1150. package/sdk/src/query/decisions.test.ts +215 -0
  1151. package/sdk/src/query/decisions.ts +192 -0
  1152. package/sdk/src/query/decomposed-handlers.test.ts +431 -0
  1153. package/sdk/src/query/detect-custom-files.test.ts +115 -0
  1154. package/sdk/src/query/detect-custom-files.ts +96 -0
  1155. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  1156. package/sdk/src/query/detect-phase-type.ts +141 -0
  1157. package/sdk/src/query/docs-init.ts +258 -0
  1158. package/sdk/src/query/fallow-audit.ts +88 -0
  1159. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  1160. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  1161. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  1162. package/sdk/src/query/frontmatter.test.ts +326 -0
  1163. package/sdk/src/query/frontmatter.ts +395 -0
  1164. package/sdk/src/query/helpers.test.ts +615 -0
  1165. package/sdk/src/query/helpers.ts +646 -0
  1166. package/sdk/src/query/index-thin-seam.test.ts +16 -0
  1167. package/sdk/src/query/index.ts +9 -0
  1168. package/sdk/src/query/init-complex.test.ts +616 -0
  1169. package/sdk/src/query/init-complex.ts +799 -0
  1170. package/sdk/src/query/init-progress-precedence.test.ts +177 -0
  1171. package/sdk/src/query/init-workstream-milestone-op.test.ts +321 -0
  1172. package/sdk/src/query/init.test.ts +792 -0
  1173. package/sdk/src/query/init.ts +1262 -0
  1174. package/sdk/src/query/intel.test.ts +90 -0
  1175. package/sdk/src/query/intel.ts +404 -0
  1176. package/sdk/src/query/mutation-event-decorator.test.ts +45 -0
  1177. package/sdk/src/query/mutation-event-decorator.ts +37 -0
  1178. package/sdk/src/query/mutation-event-mapper.test.ts +33 -0
  1179. package/sdk/src/query/mutation-event-mapper.ts +102 -0
  1180. package/sdk/src/query/mvp.test.ts +335 -0
  1181. package/sdk/src/query/mvp.ts +292 -0
  1182. package/sdk/src/query/normalize-query-command.test.ts +102 -0
  1183. package/sdk/src/query/phase-filesystem-adapter.ts +35 -0
  1184. package/sdk/src/query/phase-lifecycle-policy.ts +171 -0
  1185. package/sdk/src/query/phase-lifecycle.test.ts +1750 -0
  1186. package/sdk/src/query/phase-lifecycle.ts +1833 -0
  1187. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  1188. package/sdk/src/query/phase-list-queries.ts +152 -0
  1189. package/sdk/src/query/phase-ready.test.ts +65 -0
  1190. package/sdk/src/query/phase-ready.ts +159 -0
  1191. package/sdk/src/query/phase-roadmap-mutation.ts +77 -0
  1192. package/sdk/src/query/phase.test.ts +651 -0
  1193. package/sdk/src/query/phase.ts +550 -0
  1194. package/sdk/src/query/pipeline.test.ts +169 -0
  1195. package/sdk/src/query/pipeline.ts +243 -0
  1196. package/sdk/src/query/plan-scan.test.ts +35 -0
  1197. package/sdk/src/query/plan-scan.ts +82 -0
  1198. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  1199. package/sdk/src/query/plan-task-structure.ts +63 -0
  1200. package/sdk/src/query/policy-convergence.test.ts +28 -0
  1201. package/sdk/src/query/profile-extract-messages.ts +247 -0
  1202. package/sdk/src/query/profile-output.ts +929 -0
  1203. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  1204. package/sdk/src/query/profile-sample.ts +184 -0
  1205. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  1206. package/sdk/src/query/profile.test.ts +136 -0
  1207. package/sdk/src/query/profile.ts +337 -0
  1208. package/sdk/src/query/progress.test.ts +156 -0
  1209. package/sdk/src/query/progress.ts +566 -0
  1210. package/sdk/src/query/query-cli-adapter.test.ts +79 -0
  1211. package/sdk/src/query/query-cli-adapter.ts +39 -0
  1212. package/sdk/src/query/query-cli-output.test.ts +33 -0
  1213. package/sdk/src/query/query-cli-output.ts +35 -0
  1214. package/sdk/src/query/query-command-diagnosis.test.ts +22 -0
  1215. package/sdk/src/query/query-command-diagnosis.ts +5 -0
  1216. package/sdk/src/query/query-command-resolution-strategy.test.ts +34 -0
  1217. package/sdk/src/query/query-command-resolution-strategy.ts +121 -0
  1218. package/sdk/src/query/query-command-semantics.test.ts +22 -0
  1219. package/sdk/src/query/query-command-semantics.ts +22 -0
  1220. package/sdk/src/query/query-dispatch-contract.ts +30 -0
  1221. package/sdk/src/query/query-dispatch-error-mapper.test.ts +62 -0
  1222. package/sdk/src/query/query-dispatch-error-mapper.ts +5 -0
  1223. package/sdk/src/query/query-dispatch-formatting.test.ts +28 -0
  1224. package/sdk/src/query/query-dispatch-formatting.ts +5 -0
  1225. package/sdk/src/query/query-dispatch-input-validation.test.ts +23 -0
  1226. package/sdk/src/query/query-dispatch-input-validation.ts +5 -0
  1227. package/sdk/src/query/query-dispatch-observability.test.ts +10 -0
  1228. package/sdk/src/query/query-dispatch-observability.ts +6 -0
  1229. package/sdk/src/query/query-dispatch-plan.test.ts +25 -0
  1230. package/sdk/src/query/query-dispatch-plan.ts +5 -0
  1231. package/sdk/src/query/query-dispatch-result-builder.test.ts +16 -0
  1232. package/sdk/src/query/query-dispatch-result-builder.ts +5 -0
  1233. package/sdk/src/query/query-dispatch.test.ts +399 -0
  1234. package/sdk/src/query/query-dispatch.ts +243 -0
  1235. package/sdk/src/query/query-error-details-schema.ts +29 -0
  1236. package/sdk/src/query/query-error-taxonomy.test.ts +39 -0
  1237. package/sdk/src/query/query-error-taxonomy.ts +117 -0
  1238. package/sdk/src/query/query-fallback-bridge-adapter.test.ts +32 -0
  1239. package/sdk/src/query/query-fallback-bridge-adapter.ts +54 -0
  1240. package/sdk/src/query/query-fallback-executor.test.ts +82 -0
  1241. package/sdk/src/query/query-fallback-executor.ts +44 -0
  1242. package/sdk/src/query/query-fallback-output-classifier.test.ts +36 -0
  1243. package/sdk/src/query/query-fallback-output-classifier.ts +31 -0
  1244. package/sdk/src/query/query-fallback-policy.test.ts +13 -0
  1245. package/sdk/src/query/query-fallback-policy.ts +11 -0
  1246. package/sdk/src/query/query-native-dispatch-adapter.ts +16 -0
  1247. package/sdk/src/query/query-policy-capability.test.ts +10 -0
  1248. package/sdk/src/query/query-policy-capability.ts +26 -0
  1249. package/sdk/src/query/query-policy-snapshot.test.ts +9 -0
  1250. package/sdk/src/query/query-registry-capability.test.ts +14 -0
  1251. package/sdk/src/query/query-runtime-context.ts +44 -0
  1252. package/sdk/src/query/query-unknown-command-hints.test.ts +9 -0
  1253. package/sdk/src/query/query-unknown-command-hints.ts +5 -0
  1254. package/sdk/src/query/registry-assembly-descriptor.ts +87 -0
  1255. package/sdk/src/query/registry-assembly-invariants.ts +127 -0
  1256. package/sdk/src/query/registry-assembly.test.ts +138 -0
  1257. package/sdk/src/query/registry-assembly.ts +78 -0
  1258. package/sdk/src/query/registry.test.ts +208 -0
  1259. package/sdk/src/query/registry.ts +142 -0
  1260. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  1261. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  1262. package/sdk/src/query/roadmap-update-plan-progress.test.ts +233 -0
  1263. package/sdk/src/query/roadmap-update-plan-progress.ts +159 -0
  1264. package/sdk/src/query/roadmap.test.ts +1181 -0
  1265. package/sdk/src/query/roadmap.ts +894 -0
  1266. package/sdk/src/query/route-next-action.test.ts +61 -0
  1267. package/sdk/src/query/route-next-action.ts +345 -0
  1268. package/sdk/src/query/schema-detect.ts +189 -0
  1269. package/sdk/src/query/secrets.test.ts +66 -0
  1270. package/sdk/src/query/secrets.ts +43 -0
  1271. package/sdk/src/query/skill-manifest.test.ts +62 -0
  1272. package/sdk/src/query/skill-manifest.ts +216 -0
  1273. package/sdk/src/query/skills.test.ts +234 -0
  1274. package/sdk/src/query/skills.ts +143 -0
  1275. package/sdk/src/query/state-document.test.ts +197 -0
  1276. package/sdk/src/query/state-document.ts +129 -0
  1277. package/sdk/src/query/state-mutation.test.ts +1198 -0
  1278. package/sdk/src/query/state-mutation.ts +1718 -0
  1279. package/sdk/src/query/state-project-load.ts +80 -0
  1280. package/sdk/src/query/state.test.ts +616 -0
  1281. package/sdk/src/query/state.ts +463 -0
  1282. package/sdk/src/query/sub-repos-root.integration.test.ts +79 -0
  1283. package/sdk/src/query/summary.test.ts +95 -0
  1284. package/sdk/src/query/summary.ts +296 -0
  1285. package/sdk/src/query/template.test.ts +180 -0
  1286. package/sdk/src/query/template.ts +242 -0
  1287. package/sdk/src/query/uat.test.ts +77 -0
  1288. package/sdk/src/query/uat.ts +365 -0
  1289. package/sdk/src/query/utils.test.ts +82 -0
  1290. package/sdk/src/query/utils.ts +106 -0
  1291. package/sdk/src/query/validate.test.ts +831 -0
  1292. package/sdk/src/query/validate.ts +952 -0
  1293. package/sdk/src/query/verify.test.ts +414 -0
  1294. package/sdk/src/query/verify.ts +692 -0
  1295. package/sdk/src/query/websearch.test.ts +31 -0
  1296. package/sdk/src/query/websearch.ts +82 -0
  1297. package/sdk/src/query/workspace.test.ts +120 -0
  1298. package/sdk/src/query/workspace.ts +145 -0
  1299. package/sdk/src/query/workstream-inventory.ts +195 -0
  1300. package/sdk/src/query/workstream.test.ts +153 -0
  1301. package/sdk/src/query/workstream.ts +324 -0
  1302. package/sdk/src/query/worktree.ts +39 -0
  1303. package/sdk/src/query-command-executor.ts +31 -0
  1304. package/sdk/src/query-execution-policy.test.ts +52 -0
  1305. package/sdk/src/query-execution-policy.ts +46 -0
  1306. package/sdk/src/query-failure-classification.test.ts +23 -0
  1307. package/sdk/src/query-failure-classification.ts +42 -0
  1308. package/sdk/src/query-hotpath-methods.ts +48 -0
  1309. package/sdk/src/query-native-direct-adapter.test.ts +35 -0
  1310. package/sdk/src/query-native-direct-adapter.ts +70 -0
  1311. package/sdk/src/query-native-hotpath-adapter.test.ts +43 -0
  1312. package/sdk/src/query-native-hotpath-adapter.ts +45 -0
  1313. package/sdk/src/query-raw-output-projection.test.ts +39 -0
  1314. package/sdk/src/query-raw-output-projection.ts +74 -0
  1315. package/sdk/src/query-runtime-bridge.test.ts +150 -0
  1316. package/sdk/src/query-runtime-bridge.ts +215 -0
  1317. package/sdk/src/query-runtime-seam-coverage.test.ts +20 -0
  1318. package/sdk/src/query-sdd-tools-path.ts +1 -0
  1319. package/sdk/src/query-sdd-tools-runtime.ts +89 -0
  1320. package/sdk/src/query-subprocess-adapter.test.ts +84 -0
  1321. package/sdk/src/query-subprocess-adapter.ts +146 -0
  1322. package/sdk/src/query-tools-error-factory.test.ts +35 -0
  1323. package/sdk/src/query-tools-error-factory.ts +76 -0
  1324. package/sdk/src/research-gate.test.ts +190 -0
  1325. package/sdk/src/research-gate.ts +94 -0
  1326. package/sdk/src/runtime-bridge-options.test.ts +33 -0
  1327. package/sdk/src/runtime-gate.test.ts +84 -0
  1328. package/sdk/src/runtime-gate.ts +52 -0
  1329. package/sdk/src/sdd-tools-error.test.ts +21 -0
  1330. package/sdk/src/sdd-tools-error.ts +65 -0
  1331. package/sdk/src/sdd-tools.test.ts +472 -0
  1332. package/sdk/src/sdd-tools.ts +237 -0
  1333. package/sdk/src/sdd-transport-policy.test.ts +34 -0
  1334. package/sdk/src/sdd-transport-policy.ts +48 -0
  1335. package/sdk/src/sdd-transport.test.ts +292 -0
  1336. package/sdk/src/sdd-transport.ts +117 -0
  1337. package/sdk/src/sdk-package-compatibility.test.ts +97 -0
  1338. package/sdk/src/sdk-package-compatibility.ts +141 -0
  1339. package/sdk/src/session-runner.test.ts +164 -0
  1340. package/sdk/src/session-runner.ts +327 -0
  1341. package/sdk/src/tool-scoping.test.ts +160 -0
  1342. package/sdk/src/tool-scoping.ts +61 -0
  1343. package/sdk/src/types.ts +927 -0
  1344. package/sdk/src/workflow-agent-skills-consistency.test.ts +98 -0
  1345. package/sdk/src/workstream-name-policy.ts +24 -0
  1346. package/sdk/src/workstream-utils.ts +21 -0
  1347. package/sdk/src/ws-flag.test.ts +285 -0
  1348. package/sdk/src/ws-transport.test.ts +161 -0
  1349. package/sdk/src/ws-transport.ts +93 -0
  1350. package/sdk/tsconfig.json +20 -0
  1351. package/commands/sdd/add-backlog.md +0 -76
  1352. package/commands/sdd/add-phase.md +0 -43
  1353. package/commands/sdd/add-todo.md +0 -47
  1354. package/commands/sdd/analyze-dependencies.md +0 -34
  1355. package/commands/sdd/check-todos.md +0 -45
  1356. package/commands/sdd/code-review-fix.md +0 -52
  1357. package/commands/sdd/do.md +0 -30
  1358. package/commands/sdd/from-sdd2.md +0 -45
  1359. package/commands/sdd/insert-phase.md +0 -32
  1360. package/commands/sdd/intel.md +0 -179
  1361. package/commands/sdd/join-discord.md +0 -19
  1362. package/commands/sdd/list-phase-assumptions.md +0 -46
  1363. package/commands/sdd/list-workspaces.md +0 -19
  1364. package/commands/sdd/new-workspace.md +0 -44
  1365. package/commands/sdd/next.md +0 -26
  1366. package/commands/sdd/note.md +0 -34
  1367. package/commands/sdd/plan-milestone-gaps.md +0 -34
  1368. package/commands/sdd/plant-seed.md +0 -28
  1369. package/commands/sdd/remove-phase.md +0 -31
  1370. package/commands/sdd/remove-workspace.md +0 -26
  1371. package/commands/sdd/research-phase.md +0 -195
  1372. package/commands/sdd/scan.md +0 -26
  1373. package/commands/sdd/session-report.md +0 -19
  1374. package/commands/sdd/set-profile.md +0 -12
  1375. package/scripts/sync-upstream.sh +0 -56
  1376. package/sdd/workflows/research-phase.md +0 -82
@@ -5,37 +5,25 @@
5
5
  const fs = require('fs');
6
6
  const os = require('os');
7
7
  const path = require('path');
8
- const crypto = require('crypto');
9
- const { execSync, execFileSync, spawnSync } = require('child_process');
10
- const { MODEL_PROFILES } = require('./model-profiles.cjs');
11
-
12
- const WORKSTREAM_SESSION_ENV_KEYS = [
13
- 'SDD_SESSION_KEY',
14
- 'CODEX_THREAD_ID',
15
- 'CLAUDE_SESSION_ID',
16
- 'CLAUDE_CODE_SSE_PORT',
17
- 'OPENCODE_SESSION_ID',
18
- 'GEMINI_SESSION_ID',
19
- 'CURSOR_SESSION_ID',
20
- 'WINDSURF_SESSION_ID',
21
- 'TERM_SESSION_ID',
22
- 'WT_SESSION',
23
- 'TMUX_PANE',
24
- 'ZELLIJ_SESSION_NAME',
25
- ];
26
-
27
- let cachedControllingTtyToken = null;
28
- let didProbeControllingTtyToken = false;
29
-
30
- // Track all .planning/.lock files held by this process so they can be removed
31
- // on exit. process.on('exit') fires even on process.exit(1), unlike try/finally
32
- // which is skipped when error() calls process.exit(1) inside a locked region (#1916).
33
- const _heldPlanningLocks = new Set();
34
- process.on('exit', () => {
35
- for (const lockPath of _heldPlanningLocks) {
36
- try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
37
- }
38
- });
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');
39
27
 
40
28
  // ─── Path helpers ────────────────────────────────────────────────────────────
41
29
 
@@ -119,7 +107,9 @@ function findProjectRoot(startDir) {
119
107
  if (fs.existsSync(parentPlanning) && fs.statSync(parentPlanning).isDirectory()) {
120
108
  const configPath = path.join(parentPlanning, 'config.json');
121
109
  try {
122
- 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);
123
113
  const subRepos = config.sub_repos || config.planning?.sub_repos || [];
124
114
 
125
115
  // Check explicit sub_repos list
@@ -159,14 +149,25 @@ function findProjectRoot(startDir) {
159
149
  * @param {number} opts.maxAgeMs - max age in ms before removal (default: 5 min)
160
150
  * @param {boolean} opts.dirsOnly - if true, only remove directories (default: false)
161
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
+
162
163
  function reapStaleTempFiles(prefix = 'sdd-', { maxAgeMs = 5 * 60 * 1000, dirsOnly = false } = {}) {
163
164
  try {
164
- const tmpDir = require('os').tmpdir();
165
+ ensureSddTempDir();
165
166
  const now = Date.now();
166
- const entries = fs.readdirSync(tmpDir);
167
+ const entries = fs.readdirSync(SDD_TEMP_DIR);
167
168
  for (const entry of entries) {
168
169
  if (!entry.startsWith(prefix)) continue;
169
- const fullPath = path.join(tmpDir, entry);
170
+ const fullPath = path.join(SDD_TEMP_DIR, entry);
170
171
  try {
171
172
  const stat = fs.statSync(fullPath);
172
173
  if (now - stat.mtimeMs > maxAgeMs) {
@@ -195,8 +196,9 @@ function output(result, raw, rawValue) {
195
196
  // Write to tmpfile and output the path prefixed with @file: so callers can detect it.
196
197
  if (json.length > 50000) {
197
198
  reapStaleTempFiles();
198
- const tmpPath = path.join(require('os').tmpdir(), `sdd-${Date.now()}.json`);
199
- fs.writeFileSync(tmpPath, json, 'utf-8');
199
+ ensureSddTempDir();
200
+ const tmpPath = path.join(SDD_TEMP_DIR, `sdd-${Date.now()}.json`);
201
+ platformWriteSync(tmpPath, json);
200
202
  data = '@file:' + tmpPath;
201
203
  } else {
202
204
  data = json;
@@ -209,21 +211,73 @@ function output(result, raw, rawValue) {
209
211
  fs.writeSync(1, data);
210
212
  }
211
213
 
212
- function error(message) {
213
- fs.writeSync(2, 'Error: ' + message + '\n');
214
- process.exit(1);
215
- }
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
+ });
216
248
 
217
- // ─── File & Config utilities ──────────────────────────────────────────────────
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; }
218
261
 
219
- function safeReadFile(filePath) {
220
- try {
221
- return fs.readFileSync(filePath, 'utf-8');
222
- } catch {
223
- return null;
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');
224
275
  }
276
+ process.exit(1);
225
277
  }
226
278
 
279
+ // ─── File & Config utilities ──────────────────────────────────────────────────
280
+
227
281
  /**
228
282
  * Canonical config defaults. Single source of truth — imported by config.cjs and verify.cjs.
229
283
  */
@@ -251,79 +305,189 @@ const CONFIG_DEFAULTS = {
251
305
  phase_naming: 'sequential', // 'sequential' (default, auto-increment) or 'custom' (arbitrary string IDs)
252
306
  project_code: null, // optional short prefix for phase dirs (e.g., 'CK' → 'CK-01-foundation')
253
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
254
312
  };
255
313
 
256
- function loadConfig(cwd) {
257
- const configPath = path.join(planningDir(cwd), 'config.json');
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
+ }
332
+ }
333
+ return result;
334
+ }
335
+
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');
258
365
  const defaults = CONFIG_DEFAULTS;
259
366
 
260
367
  try {
261
- const raw = fs.readFileSync(configPath, 'utf-8');
262
- 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);
263
373
 
264
374
  // Migrate deprecated "depth" key to "granularity" with value mapping
265
- if ('depth' in parsed && !('granularity' in parsed)) {
375
+ if ('depth' in fileData && !('granularity' in fileData)) {
266
376
  const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
267
- parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
268
- delete parsed.depth;
269
- 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 */ }
270
380
  }
271
381
 
272
382
  // Auto-detect and sync sub_repos: scan for child directories with .git
273
383
  let configDirty = false;
274
384
 
275
- // Migrate legacy "multiRepo: true" boolean → sub_repos array
276
- 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) {
277
389
  const detected = detectSubRepos(cwd);
278
390
  if (detected.length > 0) {
279
- parsed.sub_repos = detected;
280
- if (!parsed.planning) parsed.planning = {};
281
- parsed.planning.commit_docs = false;
282
- 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;
283
395
  configDirty = true;
284
396
  }
285
397
  }
286
398
 
287
- // Keep sub_repos in sync with actual filesystem
288
- 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 || [];
289
427
  if (Array.isArray(currentSubRepos) && currentSubRepos.length > 0) {
290
428
  const detected = detectSubRepos(cwd);
291
429
  if (detected.length > 0) {
292
430
  const sorted = [...currentSubRepos].sort();
293
431
  if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
294
- parsed.sub_repos = detected;
432
+ if (!fileData.planning) fileData.planning = {};
433
+ fileData.planning.sub_repos = detected;
295
434
  configDirty = true;
296
435
  }
297
436
  }
298
437
  }
299
438
 
300
- // 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.
301
441
  if (configDirty) {
302
- try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch {}
442
+ try { platformWriteSync(configPath, JSON.stringify(fileData, null, 2)); } catch {}
303
443
  }
304
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
+
305
449
  // Warn about unrecognized top-level keys so users don't silently lose config.
306
450
  // Derived from config-set's VALID_CONFIG_KEYS (canonical source) plus internal-only
307
451
  // keys that loadConfig handles but config-set doesn't expose. This avoids maintaining
308
452
  // a hardcoded duplicate that drifts when new config keys are added.
309
- const { VALID_CONFIG_KEYS } = require('./config.cjs');
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');
310
457
  const KNOWN_TOP_LEVEL = new Set([
311
458
  // Extract top-level key names from dot-notation paths (e.g., 'workflow.research' → 'workflow')
312
459
  ...[...VALID_CONFIG_KEYS].map(k => k.split('.')[0]),
313
- // Section containers that hold nested sub-keys
314
- 'git', 'workflow', 'planning', 'hooks', 'features',
460
+ // Dynamic-pattern top-level containers (e.g. review, model_profile_overrides)
461
+ ...DYNAMIC_KEY_PATTERNS.map(p => p.topLevel),
315
462
  // Internal keys loadConfig reads but config-set doesn't expose
316
- 'model_overrides', 'agent_skills', 'context_window', 'resolve_model_ids',
463
+ 'model_overrides', 'context_window', 'resolve_model_ids', 'claude_md_path',
317
464
  // Deprecated keys (still accepted for migration, not in config-set)
318
- 'depth', 'multiRepo',
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',
319
469
  ]);
320
470
  const unknownKeys = Object.keys(parsed).filter(k => !KNOWN_TOP_LEVEL.has(k));
321
471
  if (unknownKeys.length > 0) {
322
- process.stderr.write(
323
- `sdd-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} these will be ignored\n`
324
- );
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
+ }
325
482
  }
326
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
+
327
491
  const get = (key, nested) => {
328
492
  if (parsed[key] !== undefined) return parsed[key];
329
493
  if (nested && parsed[nested.section] && parsed[nested.section][nested.field] !== undefined) {
@@ -359,11 +523,17 @@ function loadConfig(cwd) {
359
523
  plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
360
524
  verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
361
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,
362
527
  parallelization,
363
528
  brave_search: get('brave_search') ?? defaults.brave_search,
364
529
  firecrawl: get('firecrawl') ?? defaults.firecrawl,
365
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,
366
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',
367
537
  sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
368
538
  resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
369
539
  context_window: get('context_window') ?? defaults.context_window,
@@ -371,20 +541,53 @@ function loadConfig(cwd) {
371
541
  project_code: get('project_code') ?? defaults.project_code,
372
542
  subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
373
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,
374
567
  agent_skills: parsed.agent_skills || {},
375
568
  manager: parsed.manager || {},
376
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,
377
572
  };
378
573
  } catch {
379
574
  // Fall back to ~/.sdd/defaults.json only for truly pre-project contexts (#1683)
380
- // If .planning/ exists, the project is initialized — just missing config.json
381
- if (fs.existsSync(planningDir(cwd))) {
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 });
583
+ }
382
584
  return defaults;
383
585
  }
384
586
  try {
385
587
  const home = process.env.SDD_HOME || os.homedir();
386
588
  const globalDefaultsPath = path.join(home, '.sdd', 'defaults.json');
387
- const raw = fs.readFileSync(globalDefaultsPath, 'utf-8');
589
+ const raw = platformReadSync(globalDefaultsPath);
590
+ if (raw === null) throw new Error('missing');
388
591
  const globalDefaults = JSON.parse(raw);
389
592
  return {
390
593
  ...defaults,
@@ -394,12 +597,17 @@ function loadConfig(cwd) {
394
597
  plan_checker: globalDefaults.plan_checker ?? defaults.plan_checker,
395
598
  verifier: globalDefaults.verifier ?? defaults.verifier,
396
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,
397
603
  parallelization: globalDefaults.parallelization ?? defaults.parallelization,
398
604
  text_mode: globalDefaults.text_mode ?? defaults.text_mode,
399
605
  resolve_model_ids: globalDefaults.resolve_model_ids ?? defaults.resolve_model_ids,
400
606
  context_window: globalDefaults.context_window ?? defaults.context_window,
401
607
  subagent_timeout: globalDefaults.subagent_timeout ?? defaults.subagent_timeout,
402
608
  model_overrides: globalDefaults.model_overrides || null,
609
+ models: globalDefaults.models || null,
610
+ dynamic_routing: globalDefaults.dynamic_routing || null,
403
611
  agent_skills: globalDefaults.agent_skills || {},
404
612
  response_language: globalDefaults.response_language || null,
405
613
  };
@@ -411,151 +619,26 @@ function loadConfig(cwd) {
411
619
 
412
620
  // ─── Git utilities ────────────────────────────────────────────────────────────
413
621
 
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();
626
+
414
627
  const _gitIgnoredCache = new Map();
415
628
 
416
629
  function isGitIgnored(cwd, targetPath) {
417
630
  const key = cwd + '::' + targetPath;
418
631
  if (_gitIgnoredCache.has(key)) return _gitIgnoredCache.get(key);
419
- try {
420
- // --no-index checks .gitignore rules regardless of whether the file is tracked.
421
- // Without it, git check-ignore returns "not ignored" for tracked files even when
422
- // .gitignore explicitly lists them a common source of confusion when .planning/
423
- // was committed before being added to .gitignore.
424
- // Use execFileSync (array args) to prevent shell interpretation of special characters
425
- // in file paths avoids command injection via crafted path names.
426
- execFileSync('git', ['check-ignore', '-q', '--no-index', '--', targetPath], {
427
- cwd,
428
- stdio: 'pipe',
429
- });
430
- _gitIgnoredCache.set(key, true);
431
- return true;
432
- } catch {
433
- _gitIgnoredCache.set(key, false);
434
- return false;
435
- }
436
- }
437
-
438
- // ─── Markdown normalization ─────────────────────────────────────────────────
439
-
440
- /**
441
- * Normalize markdown to fix common markdownlint violations.
442
- * Applied at write points so SDD-generated .planning/ files are IDE-friendly.
443
- *
444
- * Rules enforced:
445
- * MD022 — Blank lines around headings
446
- * MD031 — Blank lines around fenced code blocks
447
- * MD032 — Blank lines around lists
448
- * MD012 — No multiple consecutive blank lines (collapsed to 2 max)
449
- * MD047 — Files end with a single newline
450
- */
451
- function normalizeMd(content) {
452
- if (!content || typeof content !== 'string') return content;
453
-
454
- // Normalize line endings to LF for consistent processing
455
- let text = content.replace(/\r\n/g, '\n');
456
-
457
- const lines = text.split('\n');
458
- const result = [];
459
-
460
- // Pre-compute fence state in a single O(n) pass instead of O(n^2) per-line scanning
461
- const fenceRegex = /^```/;
462
- const insideFence = new Array(lines.length);
463
- let fenceOpen = false;
464
- for (let i = 0; i < lines.length; i++) {
465
- if (fenceRegex.test(lines[i].trimEnd())) {
466
- if (fenceOpen) {
467
- // This is a closing fence — mark as NOT inside (it's the boundary)
468
- insideFence[i] = false;
469
- fenceOpen = false;
470
- } else {
471
- // This is an opening fence
472
- insideFence[i] = false;
473
- fenceOpen = true;
474
- }
475
- } else {
476
- insideFence[i] = fenceOpen;
477
- }
478
- }
479
-
480
- for (let i = 0; i < lines.length; i++) {
481
- const line = lines[i];
482
- const prev = i > 0 ? lines[i - 1] : '';
483
- const prevTrimmed = prev.trimEnd();
484
- const trimmed = line.trimEnd();
485
- const isFenceLine = fenceRegex.test(trimmed);
486
-
487
- // MD022: Blank line before headings (skip first line and frontmatter delimiters)
488
- if (/^#{1,6}\s/.test(trimmed) && i > 0 && prevTrimmed !== '' && prevTrimmed !== '---') {
489
- result.push('');
490
- }
491
-
492
- // MD031: Blank line before fenced code blocks (opening fences only)
493
- if (isFenceLine && i > 0 && prevTrimmed !== '' && !insideFence[i] && (i === 0 || !insideFence[i - 1] || isFenceLine)) {
494
- // Only add blank before opening fences (not closing ones)
495
- if (i === 0 || !insideFence[i - 1]) {
496
- result.push('');
497
- }
498
- }
499
-
500
- // MD032: Blank line before lists (- item, * item, N. item, - [ ] item)
501
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 &&
502
- prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) &&
503
- prevTrimmed !== '---') {
504
- result.push('');
505
- }
506
-
507
- result.push(line);
508
-
509
- // MD022: Blank line after headings
510
- if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1) {
511
- const next = lines[i + 1];
512
- if (next !== undefined && next.trimEnd() !== '') {
513
- result.push('');
514
- }
515
- }
516
-
517
- // MD031: Blank line after closing fenced code blocks
518
- if (/^```\s*$/.test(trimmed) && i > 0 && insideFence[i - 1] && i < lines.length - 1) {
519
- const next = lines[i + 1];
520
- if (next !== undefined && next.trimEnd() !== '') {
521
- result.push('');
522
- }
523
- }
524
-
525
- // MD032: Blank line after last list item in a block
526
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i < lines.length - 1) {
527
- const next = lines[i + 1];
528
- if (next !== undefined && next.trimEnd() !== '' &&
529
- !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(next) &&
530
- !/^\s/.test(next)) {
531
- // Only add blank line if next line is not a continuation/indented line
532
- result.push('');
533
- }
534
- }
535
- }
536
-
537
- text = result.join('\n');
538
-
539
- // MD012: Collapse 3+ consecutive blank lines to 2
540
- text = text.replace(/\n{3,}/g, '\n\n');
541
-
542
- // MD047: Ensure file ends with exactly one newline
543
- text = text.replace(/\n*$/, '\n');
544
-
545
- return text;
546
- }
547
-
548
- function execGit(cwd, args) {
549
- const result = spawnSync('git', args, {
550
- cwd,
551
- stdio: 'pipe',
552
- encoding: 'utf-8',
553
- });
554
- return {
555
- exitCode: result.status ?? 1,
556
- stdout: (result.stdout ?? '').toString().trim(),
557
- stderr: (result.stderr ?? '').toString().trim(),
558
- };
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;
559
642
  }
560
643
 
561
644
  // ─── Common path helpers ──────────────────────────────────────────────────────
@@ -566,330 +649,58 @@ function execGit(cwd, args) {
566
649
  * Returns the main worktree path, or cwd if not in a worktree.
567
650
  */
568
651
  function resolveWorktreeRoot(cwd) {
569
- // If the current directory already has its own .planning/, respect it.
570
- // This handles linked worktrees with independent planning state (e.g., Conductor workspaces).
571
- if (fs.existsSync(path.join(cwd, '.planning'))) {
572
- return cwd;
573
- }
574
-
575
- // Check if we're in a linked worktree
576
- const gitDir = execGit(cwd, ['rev-parse', '--git-dir']);
577
- const commonDir = execGit(cwd, ['rev-parse', '--git-common-dir']);
578
-
579
- if (gitDir.exitCode !== 0 || commonDir.exitCode !== 0) return cwd;
580
-
581
- // In a linked worktree, .git is a file pointing to .git/worktrees/<name>
582
- // and git-common-dir points to the main repo's .git directory
583
- const gitDirResolved = path.resolve(cwd, gitDir.stdout);
584
- const commonDirResolved = path.resolve(cwd, commonDir.stdout);
585
-
586
- if (gitDirResolved !== commonDirResolved) {
587
- // We're in a linked worktree — resolve main worktree root
588
- // The common dir is the main repo's .git, so its parent is the main worktree root
589
- return path.dirname(commonDirResolved);
590
- }
591
-
592
- return cwd;
593
- }
594
-
595
- /**
596
- * Acquire a file-based lock for .planning/ writes.
597
- * Prevents concurrent worktrees from corrupting shared planning files.
598
- * Lock is auto-released after the callback completes.
599
- */
600
- function withPlanningLock(cwd, fn) {
601
- const lockPath = path.join(planningDir(cwd), '.lock');
602
- const lockTimeout = 10000; // 10 seconds
603
- const retryDelay = 100;
604
- const start = Date.now();
605
-
606
- // Ensure .planning/ exists
607
- try { fs.mkdirSync(planningDir(cwd), { recursive: true }); } catch { /* ok */ }
608
-
609
- while (Date.now() - start < lockTimeout) {
610
- try {
611
- // Atomic create — fails if file exists
612
- fs.writeFileSync(lockPath, JSON.stringify({
613
- pid: process.pid,
614
- cwd,
615
- acquired: new Date().toISOString(),
616
- }), { flag: 'wx' });
617
-
618
- // Register for exit-time cleanup so process.exit(1) inside a locked region
619
- // cannot leave a stale lock file (#1916).
620
- _heldPlanningLocks.add(lockPath);
621
-
622
- // Lock acquired — run the function
623
- try {
624
- return fn();
625
- } finally {
626
- _heldPlanningLocks.delete(lockPath);
627
- try { fs.unlinkSync(lockPath); } catch { /* already released */ }
628
- }
629
- } catch (err) {
630
- if (err.code === 'EEXIST') {
631
- // Lock exists — check if stale (>30s old)
632
- try {
633
- const stat = fs.statSync(lockPath);
634
- if (Date.now() - stat.mtimeMs > 30000) {
635
- fs.unlinkSync(lockPath);
636
- continue; // retry
637
- }
638
- } catch { continue; }
639
-
640
- // Wait and retry (cross-platform, no shell dependency)
641
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
642
- continue;
643
- }
644
- throw err;
645
- }
646
- }
647
- // Timeout — force acquire (stale lock recovery)
648
- try { fs.unlinkSync(lockPath); } catch { /* ok */ }
649
- 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;
650
659
  }
651
660
 
652
661
  /**
653
- * Get the .planning directory path, project- and workstream-aware.
654
- *
655
- * Resolution order:
656
- * 1. If SDD_PROJECT is set (env var or explicit `project` arg), routes to
657
- * `.planning/{project}/` — supports multi-project workspaces where several
658
- * independent projects share a single `.planning/` root directory (e.g.,
659
- * an Obsidian vault or monorepo knowledge base used as a command center).
660
- * 2. If SDD_WORKSTREAM is set, routes to `.planning/workstreams/{ws}/`.
661
- * 3. 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.
662
665
  *
663
- * SDD_PROJECT and SDD_WORKSTREAM can be combined:
664
- * `.planning/{project}/workstreams/{ws}/`
665
- *
666
- * @param {string} cwd - project root
667
- * @param {string} [ws] - explicit workstream name; if omitted, checks SDD_WORKSTREAM env var
668
- * @param {string} [project] - explicit project name; if omitted, checks SDD_PROJECT env var
666
+ * @param {string} porcelain - raw output from git worktree list --porcelain
667
+ * @returns {{ path: string, branch: string }[]}
669
668
  */
670
- function planningDir(cwd, ws, project) {
671
- if (project === undefined) project = process.env.SDD_PROJECT || null;
672
- if (ws === undefined) ws = process.env.SDD_WORKSTREAM || null;
673
-
674
- // Reject path separators and traversal components in project/workstream names
675
- const BAD_SEGMENT = /[/\\]|\.\./;
676
- if (project && BAD_SEGMENT.test(project)) {
677
- throw new Error(`SDD_PROJECT contains invalid path characters: ${project}`);
678
- }
679
- if (ws && BAD_SEGMENT.test(ws)) {
680
- throw new Error(`SDD_WORKSTREAM contains invalid path characters: ${ws}`);
681
- }
682
-
683
- let base = path.join(cwd, '.planning');
684
- if (project) base = path.join(base, project);
685
- if (ws) base = path.join(base, 'workstreams', ws);
686
- return base;
687
- }
688
-
689
- /** Always returns the root .planning/ path, ignoring workstreams and projects. For shared resources. */
690
- function planningRoot(cwd) {
691
- return path.join(cwd, '.planning');
669
+ function parseWorktreePorcelain(porcelain) {
670
+ return parseWorktreePorcelainPolicy(porcelain);
692
671
  }
693
672
 
694
673
  /**
695
- * Get common .planning file paths, project-and-workstream-aware.
674
+ * Clear stale worktree metadata references via `git worktree prune`.
696
675
  *
697
- * All paths route through planningDir(cwd, ws), which honors the SDD_PROJECT
698
- * env var and active workstream. This matches loadConfig() above (line 256),
699
- * which has always read config.json via planningDir(cwd). Previously project
700
- * and config were resolved against the unrouted .planning/ root, which broke
701
- * `sdd-tools config-get` in multi-project layouts (the CRUD writers and the
702
- * reader pointed at different files).
703
- */
704
- function planningPaths(cwd, ws) {
705
- const base = planningDir(cwd, ws);
706
- return {
707
- planning: base,
708
- state: path.join(base, 'STATE.md'),
709
- roadmap: path.join(base, 'ROADMAP.md'),
710
- project: path.join(base, 'PROJECT.md'),
711
- config: path.join(base, 'config.json'),
712
- phases: path.join(base, 'phases'),
713
- requirements: path.join(base, 'REQUIREMENTS.md'),
714
- };
715
- }
716
-
717
- // ─── Active Workstream Detection ─────────────────────────────────────────────
718
-
719
- function sanitizeWorkstreamSessionToken(value) {
720
- if (value === null || value === undefined) return null;
721
- const token = String(value).trim().replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
722
- return token ? token.slice(0, 160) : null;
723
- }
724
-
725
- function probeControllingTtyToken() {
726
- if (didProbeControllingTtyToken) return cachedControllingTtyToken;
727
- didProbeControllingTtyToken = true;
728
-
729
- // `tty` reads stdin. When stdin is already non-interactive, spawning it only
730
- // adds avoidable failures on the routing hot path and cannot reveal a stable token.
731
- if (!(process.stdin && process.stdin.isTTY)) {
732
- return cachedControllingTtyToken;
733
- }
734
-
735
- try {
736
- const ttyPath = execFileSync('tty', [], {
737
- encoding: 'utf-8',
738
- stdio: ['inherit', 'pipe', 'ignore'],
739
- }).trim();
740
- if (ttyPath && ttyPath !== 'not a tty') {
741
- const token = sanitizeWorkstreamSessionToken(ttyPath.replace(/^\/dev\//, ''));
742
- if (token) cachedControllingTtyToken = `tty-${token}`;
743
- }
744
- } catch {}
745
-
746
- return cachedControllingTtyToken;
747
- }
748
-
749
- function getControllingTtyToken() {
750
- for (const envKey of ['TTY', 'SSH_TTY']) {
751
- const token = sanitizeWorkstreamSessionToken(process.env[envKey]);
752
- if (token) return `tty-${token.replace(/^dev_/, '')}`;
753
- }
754
-
755
- return probeControllingTtyToken();
756
- }
757
-
758
- /**
759
- * Resolve a deterministic session key for workstream-local routing.
676
+ * Destructive linked-worktree removal is disabled by default for safety.
760
677
  *
761
- * Order:
762
- * 1. Explicit runtime/session env vars (`SDD_SESSION_KEY`, `CODEX_THREAD_ID`, etc.)
763
- * 2. Terminal identity exposed via `TTY` or `SSH_TTY`
764
- * 3. One best-effort `tty` probe when stdin is interactive
765
- * 4. `null`, which tells callers to use the legacy shared pointer fallback
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)
766
681
  */
767
- function getWorkstreamSessionKey() {
768
- for (const envKey of WORKSTREAM_SESSION_ENV_KEYS) {
769
- const raw = process.env[envKey];
770
- const token = sanitizeWorkstreamSessionToken(raw);
771
- if (token) return `${envKey.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${token}`;
772
- }
773
-
774
- return getControllingTtyToken();
775
- }
776
-
777
- function getSessionScopedWorkstreamFile(cwd) {
778
- const sessionKey = getWorkstreamSessionKey();
779
- if (!sessionKey) return null;
780
-
781
- // Use realpathSync.native so the hash is derived from the canonical filesystem
782
- // path. On Windows, path.resolve returns whatever case the caller supplied,
783
- // while realpathSync.native returns the case the OS recorded — they differ on
784
- // case-insensitive NTFS, producing different hashes and different tmpdir slots.
785
- // Fall back to path.resolve when the directory does not yet exist.
786
- let planningAbs;
682
+ function pruneOrphanedWorktrees(repoRoot) {
787
683
  try {
788
- planningAbs = fs.realpathSync.native(planningRoot(cwd));
789
- } catch {
790
- planningAbs = path.resolve(planningRoot(cwd));
791
- }
792
- const projectId = crypto
793
- .createHash('sha1')
794
- .update(planningAbs)
795
- .digest('hex')
796
- .slice(0, 16);
797
-
798
- const dirPath = path.join(os.tmpdir(), 'sdd-workstream-sessions', projectId);
799
- return {
800
- sessionKey,
801
- dirPath,
802
- filePath: path.join(dirPath, sessionKey),
803
- };
804
- }
805
-
806
- function clearActiveWorkstreamPointer(filePath, cleanupDirPath) {
807
- try { fs.unlinkSync(filePath); } catch {}
808
-
809
- // Session-scoped pointers for a repo share one tmp directory. Only remove it
810
- // when it is empty so clearing or self-healing one session never deletes siblings.
811
- // Explicitly check remaining entries rather than relying on rmdirSync throwing
812
- // ENOTEMPTY — that error is not raised reliably on Windows.
813
- if (cleanupDirPath) {
814
- try {
815
- const remaining = fs.readdirSync(cleanupDirPath);
816
- if (remaining.length === 0) {
817
- fs.rmdirSync(cleanupDirPath);
818
- }
819
- } catch {}
820
- }
821
- }
822
-
823
- /**
824
- * Pointer files are self-healing: invalid names or deleted-workstream pointers
825
- * are removed on read so the session falls back to `null` instead of carrying
826
- * silent stale state forward. Session-scoped callers may also prune an empty
827
- * per-project tmp directory; shared `.planning/active-workstream` callers do not.
828
- */
829
- function readActiveWorkstreamPointer(filePath, cwd, cleanupDirPath = null) {
830
- try {
831
- const name = fs.readFileSync(filePath, 'utf-8').trim();
832
- if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
833
- clearActiveWorkstreamPointer(filePath, cleanupDirPath);
834
- return null;
835
- }
836
- const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
837
- if (!fs.existsSync(wsDir)) {
838
- clearActiveWorkstreamPointer(filePath, cleanupDirPath);
839
- return null;
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
+ );
840
698
  }
841
- return name;
842
- } catch {
843
- return null;
844
- }
845
- }
846
-
847
- /**
848
- * Get the active workstream name.
849
- *
850
- * Resolution priority:
851
- * 1. Session-scoped pointer (tmpdir) when the runtime exposes a stable session key
852
- * 2. Legacy shared `.planning/active-workstream` file when no session key is available
853
- *
854
- * The shared file is intentionally ignored when a session key exists so multiple
855
- * concurrent sessions do not overwrite each other's active workstream.
856
- */
857
- function getActiveWorkstream(cwd) {
858
- const sessionScoped = getSessionScopedWorkstreamFile(cwd);
859
- if (sessionScoped) {
860
- return readActiveWorkstreamPointer(sessionScoped.filePath, cwd, sessionScoped.dirPath);
861
- }
862
-
863
- const sharedFilePath = path.join(planningRoot(cwd), 'active-workstream');
864
- return readActiveWorkstreamPointer(sharedFilePath, cwd);
699
+ } catch { /* never crash the caller */ }
700
+ return [];
865
701
  }
866
702
 
867
- /**
868
- * Set the active workstream. Pass null to clear.
869
- *
870
- * When a stable session key is available, this updates a tmpdir-backed
871
- * session-scoped pointer. Otherwise it falls back to the legacy shared
872
- * `.planning/active-workstream` file for backward compatibility.
873
- */
874
- function setActiveWorkstream(cwd, name) {
875
- const sessionScoped = getSessionScopedWorkstreamFile(cwd);
876
- const filePath = sessionScoped
877
- ? sessionScoped.filePath
878
- : path.join(planningRoot(cwd), 'active-workstream');
879
-
880
- if (!name) {
881
- clearActiveWorkstreamPointer(filePath, sessionScoped ? sessionScoped.dirPath : null);
882
- return;
883
- }
884
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
885
- throw new Error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
886
- }
887
-
888
- if (sessionScoped) {
889
- fs.mkdirSync(sessionScoped.dirPath, { recursive: true });
890
- }
891
- fs.writeFileSync(filePath, name + '\n', 'utf-8');
892
- }
703
+ // ─── Planning workspace (pathing + active workstream + lock) moved to planning-workspace.cjs ───
893
704
 
894
705
  // ─── Phase utilities ──────────────────────────────────────────────────────────
895
706
 
@@ -916,6 +727,50 @@ function normalizePhaseName(phase) {
916
727
  return str;
917
728
  }
918
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
+
919
774
  function comparePhaseNum(a, b) {
920
775
  // Strip optional project_code prefix before comparing (e.g., 'CK-01-name' → '01-name')
921
776
  const sa = String(a).replace(/^[A-Z]{1,6}-/, '');
@@ -982,6 +837,17 @@ function phaseTokenMatches(dirName, normalized) {
982
837
  return false;
983
838
  }
984
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
+
985
851
  function searchPhaseInDir(baseDir, relBase, normalized) {
986
852
  try {
987
853
  const dirs = readSubdirectories(baseDir, true);
@@ -1002,11 +868,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
1002
868
  const summaries = unsortedSummaries.sort();
1003
869
 
1004
870
  const completedPlanIds = new Set(
1005
- 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
+ })
1006
876
  );
1007
877
  const incompletePlans = plans.filter(p => {
1008
878
  const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
1009
- return !completedPlanIds.has(planId);
879
+ const canonical = extractCanonicalPlanId(p);
880
+ return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
1010
881
  });
1011
882
 
1012
883
  return {
@@ -1135,8 +1006,8 @@ function extractCurrentMilestone(content, cwd) {
1135
1006
  let version = null;
1136
1007
  try {
1137
1008
  const statePath = path.join(planningDir(cwd), 'STATE.md');
1138
- if (fs.existsSync(statePath)) {
1139
- const stateRaw = fs.readFileSync(statePath, 'utf-8');
1009
+ const stateRaw = platformReadSync(statePath);
1010
+ if (stateRaw !== null) {
1140
1011
  const milestoneMatch = stateRaw.match(/^milestone:\s*(.+)/m);
1141
1012
  if (milestoneMatch) {
1142
1013
  version = milestoneMatch[1].trim();
@@ -1168,21 +1039,42 @@ function extractCurrentMilestone(content, cwd) {
1168
1039
 
1169
1040
  const sectionStart = sectionMatch.index;
1170
1041
 
1171
- // 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.
1172
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.
1173
1046
  const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
1174
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.
1175
1051
  const nextMilestonePattern = new RegExp(
1176
- `^#{1,${headingLevel}}\\s+(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
1177
- 'mi'
1052
+ `^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
1053
+ 'i'
1178
1054
  );
1179
- const nextMatch = restContent.match(nextMilestonePattern);
1180
1055
 
1181
- let sectionEnd;
1182
- if (nextMatch) {
1183
- sectionEnd = sectionStart + sectionMatch[0].length + nextMatch.index;
1184
- } else {
1185
- 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;
1186
1078
  }
1187
1079
 
1188
1080
  // Return everything before the current milestone section (non-milestone content
@@ -1221,10 +1113,16 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
1221
1113
  if (!fs.existsSync(roadmapPath)) return null;
1222
1114
 
1223
1115
  try {
1224
- const content = extractCurrentMilestone(fs.readFileSync(roadmapPath, 'utf-8'), cwd);
1225
- const escapedPhase = escapeRegex(phaseNum.toString());
1226
- // Match both numeric (Phase 1:) and custom (Phase PROJ-42:) headers
1227
- 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
+ );
1228
1126
  const headerMatch = content.match(phasePattern);
1229
1127
  if (!headerMatch) return null;
1230
1128
 
@@ -1315,43 +1213,202 @@ function checkAgentsInstalled() {
1315
1213
 
1316
1214
  // ─── Model alias resolution ───────────────────────────────────────────────────
1317
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
+
1318
1277
  /**
1319
- * Map short model aliases to full model IDs.
1320
- * Updated each release to match current model versions.
1321
- * 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`.
1322
1298
  */
1323
- const MODEL_ALIAS_MAP = {
1324
- 'opus': 'claude-opus-4-6',
1325
- 'sonnet': 'claude-sonnet-4-6',
1326
- 'haiku': 'claude-haiku-4-5',
1327
- };
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
+ }
1328
1329
 
1329
1330
  function resolveModelInternal(cwd, agentType) {
1330
1331
  const config = loadConfig(cwd);
1331
1332
 
1332
- // Check per-agent override first — always respected regardless of resolve_model_ids.
1333
+ // 1. Per-agent override — always respected; highest precedence.
1333
1334
  // Users who set fully-qualified model IDs (e.g., "openai/gpt-5.4") get exactly that.
1334
1335
  const override = config.model_overrides?.[agentType];
1335
1336
  if (override) {
1336
1337
  return override;
1337
1338
  }
1338
1339
 
1339
- // resolve_model_ids: "omit" return empty string so the runtime uses its configured
1340
- // default model. For non-Claude runtimes (OpenCode, Codex, etc.) that don't recognize
1341
- // 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.
1342
1391
  if (config.resolve_model_ids === 'omit') {
1343
1392
  return '';
1344
1393
  }
1345
1394
 
1346
- // Fall back to profile lookup
1347
- const profile = String(config.model_profile || 'balanced').toLowerCase();
1348
- const agentModels = MODEL_PROFILES[agentType];
1349
- if (!agentModels) return 'sonnet';
1350
- if (profile === 'inherit') return 'inherit';
1351
- const alias = agentModels[profile] || agentModels['balanced'] || 'sonnet';
1352
-
1353
- // resolve_model_ids: true map alias to full Claude model ID
1354
- // 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.
1355
1412
  if (config.resolve_model_ids) {
1356
1413
  return MODEL_ALIAS_MAP[alias] || alias;
1357
1414
  }
@@ -1359,6 +1416,156 @@ function resolveModelInternal(cwd, agentType) {
1359
1416
  return alias;
1360
1417
  }
1361
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
+
1362
1569
  // ─── Summary body helpers ─────────────────────────────────────────────────
1363
1570
 
1364
1571
  /**
@@ -1369,11 +1576,28 @@ function resolveModelInternal(cwd, agentType) {
1369
1576
  */
1370
1577
  function extractOneLinerFromBody(content) {
1371
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');
1372
1581
  // Strip frontmatter first
1373
- const body = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
1374
- // Find the first **...** line after a # heading
1375
- const match = body.match(/^#[^\n]*\n+\*\*([^*]+)\*\*/m);
1376
- 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;
1377
1601
  }
1378
1602
 
1379
1603
  // ─── Misc utilities ───────────────────────────────────────────────────────────
@@ -1388,6 +1612,48 @@ function pathExistsInternal(cwd, targetPath) {
1388
1612
  }
1389
1613
  }
1390
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
+
1391
1657
  function generateSlugInternal(text) {
1392
1658
  if (!text) return null;
1393
1659
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 60);
@@ -1395,7 +1661,52 @@ function generateSlugInternal(text) {
1395
1661
 
1396
1662
  function getMilestoneInfo(cwd) {
1397
1663
  try {
1398
- 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
+ }
1399
1710
 
1400
1711
  // First: check for list-format roadmaps using 🚧 (in-progress) marker
1401
1712
  // e.g. "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
@@ -1408,11 +1719,14 @@ function getMilestoneInfo(cwd) {
1408
1719
  };
1409
1720
  }
1410
1721
 
1411
- // 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.
1412
1726
  const cleaned = stripShippedMilestones(roadmap);
1413
- // Extract version and name from the same ## heading for consistency
1727
+ // Negative lookahead skips headings that contain (shipped milestone marker).
1414
1728
  // Supports 2+ segment versions: v1.2, v1.2.1, v2.0.1, etc.
1415
- const headingMatch = cleaned.match(/## .*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
1729
+ const headingMatch = cleaned.match(/## (?!.*✅).*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
1416
1730
  if (headingMatch) {
1417
1731
  return {
1418
1732
  version: 'v' + headingMatch[1],
@@ -1435,10 +1749,63 @@ function getMilestoneInfo(cwd) {
1435
1749
  * to the current milestone based on ROADMAP.md phase headings.
1436
1750
  * If no ROADMAP exists or no phases are listed, returns a pass-all filter.
1437
1751
  */
1438
- function getMilestonePhaseFilter(cwd) {
1752
+ function getMilestonePhaseFilter(cwd, versionOverride) {
1439
1753
  const milestonePhaseNums = new Set();
1754
+ let missingExplicitVersion = false;
1440
1755
  try {
1441
- 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
+
1442
1809
  // Match both numeric phases (Phase 1:) and custom IDs (Phase PROJ-42:)
1443
1810
  const phasePattern = /#{2,4}\s*Phase\s+([\w][\w.-]*)\s*:/gi;
1444
1811
  let m;
@@ -1450,11 +1817,12 @@ function getMilestonePhaseFilter(cwd) {
1450
1817
  if (milestonePhaseNums.size === 0) {
1451
1818
  const passAll = () => true;
1452
1819
  passAll.phaseCount = 0;
1820
+ passAll.missingExplicitVersion = missingExplicitVersion;
1453
1821
  return passAll;
1454
1822
  }
1455
1823
 
1456
1824
  const normalized = new Set(
1457
- [...milestonePhaseNums].map(n => (n.replace(/^0+/, '') || '0').toLowerCase())
1825
+ [...milestonePhaseNums].map(n => (n.replace(/^0+(?=\d)/, '') || '0').toLowerCase())
1458
1826
  );
1459
1827
 
1460
1828
  function isDirInMilestone(dirName) {
@@ -1464,9 +1832,22 @@ function getMilestonePhaseFilter(cwd) {
1464
1832
  // Try custom ID match (e.g. PROJ-42-description → PROJ-42)
1465
1833
  const customMatch = dirName.match(/^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*)/);
1466
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
+ }
1467
1847
  return false;
1468
1848
  }
1469
1849
  isDirInMilestone.phaseCount = milestonePhaseNums.size;
1850
+ isDirInMilestone.missingExplicitVersion = missingExplicitVersion;
1470
1851
  return isDirInMilestone;
1471
1852
  }
1472
1853
 
@@ -1514,48 +1895,44 @@ function readSubdirectories(dirPath, sort = false) {
1514
1895
  }
1515
1896
  }
1516
1897
 
1517
- // ─── Atomic file writes ───────────────────────────────────────────────────────
1518
-
1519
1898
  /**
1520
- * Write a file atomically using write-to-temp-then-rename.
1521
- *
1522
- * On POSIX systems, `fs.renameSync` is atomic when the source and destination
1523
- * are on the same filesystem. This prevents a process killed mid-write from
1524
- * leaving a truncated file that is unparseable on next read.
1525
- *
1526
- * The temp file is placed alongside the target so it is guaranteed to be on
1527
- * the same filesystem (required for rename atomicity). The PID is embedded in
1528
- * the temp file name so concurrent writers use distinct paths.
1529
- *
1530
- * If `renameSync` fails (e.g. cross-device move), the function falls back to a
1531
- * direct `writeFileSync` so callers always get a best-effort write.
1532
- *
1533
- * @param {string} filePath Absolute path to write.
1534
- * @param {string|Buffer} content File content.
1535
- * @param {string} [encoding='utf-8'] Encoding passed to writeFileSync.
1899
+ * Format a Date as a fuzzy relative time string (e.g. "5 minutes ago").
1900
+ * @param {Date} date
1901
+ * @returns {string}
1536
1902
  */
1537
- function atomicWriteFileSync(filePath, content, encoding = 'utf-8') {
1538
- const tmpPath = filePath + '.tmp.' + process.pid;
1539
- try {
1540
- fs.writeFileSync(tmpPath, content, encoding);
1541
- fs.renameSync(tmpPath, filePath);
1542
- } catch (renameErr) {
1543
- // Clean up the temp file if rename failed, then fall back to direct write.
1544
- try { fs.unlinkSync(tmpPath); } catch { /* already gone or never created */ }
1545
- fs.writeFileSync(filePath, content, encoding);
1546
- }
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`;
1547
1922
  }
1548
1923
 
1549
1924
  module.exports = {
1550
1925
  output,
1551
1926
  error,
1552
- safeReadFile,
1927
+ ERROR_REASON,
1928
+ setJsonErrorMode,
1929
+ getJsonErrorMode,
1553
1930
  loadConfig,
1554
1931
  isGitIgnored,
1555
- execGit,
1556
- normalizeMd,
1557
1932
  escapeRegex,
1558
1933
  normalizePhaseName,
1934
+ phaseMarkdownRegexSource,
1935
+ phaseMarkdownRegexSourceExact,
1559
1936
  comparePhaseNum,
1560
1937
  searchPhaseInDir,
1561
1938
  extractPhaseToken,
@@ -1564,7 +1941,16 @@ module.exports = {
1564
1941
  getArchivedPhaseDirs,
1565
1942
  getRoadmapPhaseInternal,
1566
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,
1567
1952
  pathExistsInternal,
1953
+ gitWorktreeInfoInternal,
1568
1954
  generateSlugInternal,
1569
1955
  getMilestoneInfo,
1570
1956
  getMilestonePhaseFilter,
@@ -1574,10 +1960,12 @@ module.exports = {
1574
1960
  toPosixPath,
1575
1961
  extractOneLinerFromBody,
1576
1962
  resolveWorktreeRoot,
1963
+ // Deprecated re-exports — prefer direct import from planning-workspace.cjs
1577
1964
  withPlanningLock,
1578
1965
  findProjectRoot,
1579
1966
  detectSubRepos,
1580
1967
  reapStaleTempFiles,
1968
+ SDD_TEMP_DIR,
1581
1969
  MODEL_ALIAS_MAP,
1582
1970
  CONFIG_DEFAULTS,
1583
1971
  planningDir,
@@ -1591,5 +1979,7 @@ module.exports = {
1591
1979
  readSubdirectories,
1592
1980
  getAgentsDir,
1593
1981
  checkAgentsInstalled,
1594
- atomicWriteFileSync,
1982
+ timeAgo,
1983
+ pruneOrphanedWorktrees,
1984
+ inspectWorktreeHealth,
1595
1985
  };