@bhargavvc/sdd-cc 1.30.1 → 1.42.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1392) hide show
  1. package/README.ja-JP.md +165 -129
  2. package/README.ko-KR.md +161 -123
  3. package/README.md +103 -679
  4. package/README.pt-BR.md +92 -52
  5. package/README.zh-CN.md +145 -103
  6. package/agents/sdd-advisor-researcher.md +23 -0
  7. package/agents/sdd-ai-researcher.md +133 -0
  8. package/agents/sdd-code-fixer.md +668 -0
  9. package/agents/sdd-code-reviewer.md +387 -0
  10. package/agents/sdd-codebase-mapper.md +86 -3
  11. package/agents/sdd-debug-session-manager.md +314 -0
  12. package/agents/sdd-debugger.md +157 -78
  13. package/agents/sdd-doc-classifier.md +168 -0
  14. package/agents/sdd-doc-synthesizer.md +204 -0
  15. package/agents/sdd-doc-verifier.md +217 -0
  16. package/agents/sdd-doc-writer.md +615 -0
  17. package/agents/sdd-domain-researcher.md +153 -0
  18. package/agents/sdd-eval-auditor.md +191 -0
  19. package/agents/sdd-eval-planner.md +154 -0
  20. package/agents/sdd-executor.md +283 -40
  21. package/agents/sdd-framework-selector.md +160 -0
  22. package/agents/sdd-integration-checker.md +30 -3
  23. package/agents/sdd-intel-updater.md +342 -0
  24. package/agents/sdd-nyquist-auditor.md +31 -4
  25. package/agents/sdd-pattern-mapper.md +335 -0
  26. package/agents/sdd-phase-researcher.md +254 -24
  27. package/agents/sdd-plan-checker.md +223 -18
  28. package/agents/sdd-planner.md +286 -362
  29. package/agents/sdd-project-researcher.md +28 -5
  30. package/agents/sdd-research-synthesizer.md +4 -4
  31. package/agents/sdd-roadmapper.md +14 -5
  32. package/agents/sdd-security-auditor.md +155 -0
  33. package/agents/sdd-ui-auditor.md +60 -4
  34. package/agents/sdd-ui-checker.md +11 -2
  35. package/agents/sdd-ui-researcher.md +27 -4
  36. package/agents/sdd-user-profiler.md +2 -2
  37. package/agents/sdd-verifier.md +258 -41
  38. package/bin/install.js +6862 -618
  39. package/bin/sdd-sdk.js +37 -0
  40. package/commands/sdd/add-tests.md +3 -2
  41. package/commands/sdd/ai-integration-phase.md +37 -0
  42. package/commands/sdd/audit-fix.md +34 -0
  43. package/commands/sdd/audit-milestone.md +3 -2
  44. package/commands/sdd/autonomous.md +10 -5
  45. package/commands/sdd/capture.md +62 -0
  46. package/commands/sdd/cleanup.md +7 -1
  47. package/commands/sdd/code-review.md +59 -0
  48. package/commands/sdd/complete-milestone.md +11 -4
  49. package/commands/sdd/config.md +58 -0
  50. package/commands/sdd/debug.md +23 -144
  51. package/commands/sdd/discuss-phase.md +22 -10
  52. package/commands/sdd/docs-update.md +49 -0
  53. package/commands/sdd/eval-review.md +33 -0
  54. package/commands/sdd/execute-phase.md +9 -4
  55. package/commands/sdd/explore.md +27 -0
  56. package/commands/sdd/extract-learnings.md +23 -0
  57. package/commands/sdd/fast.md +2 -1
  58. package/commands/sdd/forensics.md +3 -2
  59. package/commands/sdd/graphify.md +199 -0
  60. package/commands/sdd/health.md +12 -3
  61. package/commands/sdd/help.md +3 -1
  62. package/commands/sdd/import.md +41 -0
  63. package/commands/sdd/inbox.md +39 -0
  64. package/commands/sdd/ingest-docs.md +42 -0
  65. package/commands/sdd/manager.md +9 -3
  66. package/commands/sdd/map-codebase.md +15 -3
  67. package/commands/sdd/milestone-summary.md +1 -1
  68. package/commands/sdd/mvp-phase.md +45 -0
  69. package/commands/sdd/new-milestone.md +3 -2
  70. package/commands/sdd/new-project.md +7 -2
  71. package/commands/sdd/ns-context.md +23 -0
  72. package/commands/sdd/ns-ideate.md +24 -0
  73. package/commands/sdd/ns-manage.md +29 -0
  74. package/commands/sdd/ns-project.md +22 -0
  75. package/commands/sdd/ns-review.md +26 -0
  76. package/commands/sdd/ns-workflow.md +28 -0
  77. package/commands/sdd/pause-work.md +6 -1
  78. package/commands/sdd/phase.md +56 -0
  79. package/commands/sdd/plan-phase.md +19 -4
  80. package/commands/sdd/plan-review-convergence.md +59 -0
  81. package/commands/sdd/pr-branch.md +2 -1
  82. package/commands/sdd/profile-user.md +2 -2
  83. package/commands/sdd/progress.md +27 -5
  84. package/commands/sdd/quick.md +132 -5
  85. package/commands/sdd/resume-work.md +2 -12
  86. package/commands/sdd/review-backlog.md +4 -2
  87. package/commands/sdd/review.md +7 -3
  88. package/commands/sdd/secure-phase.md +36 -0
  89. package/commands/sdd/settings.md +2 -9
  90. package/commands/sdd/ship.md +1 -0
  91. package/commands/sdd/sketch.md +60 -0
  92. package/commands/sdd/spec-phase.md +63 -0
  93. package/commands/sdd/spike.md +57 -0
  94. package/commands/sdd/stats.md +2 -1
  95. package/commands/sdd/surface.md +129 -0
  96. package/commands/sdd/thread.md +8 -111
  97. package/commands/sdd/ui-phase.md +3 -2
  98. package/commands/sdd/ui-review.md +3 -2
  99. package/commands/sdd/ultraplan-phase.md +34 -0
  100. package/commands/sdd/undo.md +35 -0
  101. package/commands/sdd/update.md +21 -10
  102. package/commands/sdd/validate-phase.md +3 -2
  103. package/commands/sdd/verify-work.md +4 -3
  104. package/commands/sdd/workspace.md +52 -0
  105. package/commands/sdd/workstreams.md +15 -8
  106. package/hooks/dist/sdd-check-update-worker.js +116 -0
  107. package/hooks/dist/sdd-check-update.js +19 -69
  108. package/hooks/dist/sdd-context-monitor.js +43 -7
  109. package/hooks/dist/sdd-phase-boundary.sh +47 -0
  110. package/hooks/dist/sdd-prompt-guard.js +1 -0
  111. package/hooks/dist/sdd-read-guard.js +101 -0
  112. package/hooks/dist/sdd-read-injection-scanner.js +152 -0
  113. package/hooks/dist/sdd-session-state.sh +59 -0
  114. package/hooks/dist/sdd-statusline.js +439 -21
  115. package/hooks/dist/sdd-update-banner.js +134 -0
  116. package/hooks/dist/sdd-validate-commit.sh +57 -0
  117. package/hooks/dist/sdd-workflow-guard.js +2 -2
  118. package/hooks/lib/git-cmd.js +150 -0
  119. package/hooks/sdd-check-update-worker.js +116 -0
  120. package/hooks/sdd-check-update.js +64 -0
  121. package/hooks/sdd-context-monitor.js +192 -0
  122. package/hooks/sdd-phase-boundary.sh +47 -0
  123. package/hooks/sdd-prompt-guard.js +97 -0
  124. package/hooks/sdd-read-guard.js +101 -0
  125. package/hooks/sdd-read-injection-scanner.js +152 -0
  126. package/hooks/sdd-session-state.sh +59 -0
  127. package/hooks/sdd-statusline.js +537 -0
  128. package/hooks/sdd-update-banner.js +134 -0
  129. package/hooks/sdd-validate-commit.sh +57 -0
  130. package/hooks/sdd-workflow-guard.js +94 -0
  131. package/package.json +34 -9
  132. package/scripts/audit-workflow-script-paths.cjs +73 -0
  133. package/scripts/build-hooks.js +114 -9
  134. package/scripts/changeset/cli.cjs +269 -0
  135. package/scripts/changeset/github-release-notes.cjs +198 -0
  136. package/scripts/changeset/lint.cjs +110 -0
  137. package/scripts/changeset/new.cjs +137 -0
  138. package/scripts/changeset/parse.cjs +60 -0
  139. package/scripts/changeset/render.cjs +34 -0
  140. package/scripts/changeset/serialize.cjs +74 -0
  141. package/scripts/command-contract-helpers.cjs +61 -0
  142. package/scripts/diff-touches-shipped-paths.cjs +147 -0
  143. package/scripts/fix-slash-commands.cjs +106 -0
  144. package/scripts/gen-inventory-manifest.cjs +109 -0
  145. package/scripts/lint-command-contract.cjs +108 -0
  146. package/scripts/lint-descriptions.cjs +83 -0
  147. package/scripts/lint-no-source-grep-extras.cjs +81 -0
  148. package/scripts/lint-no-source-grep.cjs +174 -0
  149. package/scripts/lint-shell-command-projection-drift.cjs +57 -0
  150. package/scripts/lint-skill-deps.cjs +180 -0
  151. package/scripts/pr-template-policy.cjs +169 -0
  152. package/scripts/prompt-injection-scan.sh +3 -0
  153. package/scripts/rebrand-gsd-to-sdd.sh +222 -220
  154. package/scripts/run-tests.cjs +5 -1
  155. package/scripts/strip-prose-atrefs.cjs +106 -0
  156. package/scripts/verify-tarball-sdk-dist.sh +69 -0
  157. package/sdd/bin/check-latest-version.cjs +104 -0
  158. package/sdd/bin/lib/active-workstream-store.cjs +85 -0
  159. package/sdd/bin/lib/adr-parser.cjs +394 -0
  160. package/sdd/bin/lib/artifacts.cjs +53 -0
  161. package/sdd/bin/lib/audit.cjs +755 -0
  162. package/sdd/bin/lib/cjs-command-router-adapter.cjs +39 -0
  163. package/sdd/bin/lib/clusters.cjs +135 -0
  164. package/sdd/bin/lib/command-aliases.generated.cjs +838 -0
  165. package/sdd/bin/lib/commands.cjs +179 -107
  166. package/sdd/bin/lib/config-schema.cjs +135 -0
  167. package/sdd/bin/lib/config.cjs +313 -86
  168. package/sdd/bin/lib/context-utilization.cjs +47 -0
  169. package/sdd/bin/lib/core.cjs +1146 -391
  170. package/sdd/bin/lib/decisions.cjs +48 -0
  171. package/sdd/bin/lib/docs.cjs +270 -0
  172. package/sdd/bin/lib/drift.cjs +379 -0
  173. package/sdd/bin/lib/fallow-runner.cjs +109 -0
  174. package/sdd/bin/lib/frontmatter.cjs +389 -336
  175. package/sdd/bin/lib/gap-checker.cjs +197 -0
  176. package/sdd/bin/lib/graphify.cjs +577 -0
  177. package/sdd/bin/lib/init-command-router.cjs +70 -0
  178. package/sdd/bin/lib/init.cjs +692 -97
  179. package/sdd/bin/lib/install-profiles.cjs +572 -0
  180. package/sdd/bin/lib/installer-migration-authoring.cjs +117 -0
  181. package/sdd/bin/lib/installer-migration-report.cjs +328 -0
  182. package/sdd/bin/lib/installer-migrations/000-first-time-baseline.cjs +220 -0
  183. package/sdd/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +41 -0
  184. package/sdd/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +80 -0
  185. package/sdd/bin/lib/installer-migrations.cjs +703 -0
  186. package/sdd/bin/lib/intel.cjs +643 -0
  187. package/sdd/bin/lib/learnings.cjs +379 -0
  188. package/sdd/bin/lib/milestone.cjs +313 -252
  189. package/sdd/bin/lib/model-catalog.cjs +136 -0
  190. package/sdd/bin/lib/model-profiles.cjs +25 -68
  191. package/sdd/bin/lib/phase-command-router.cjs +96 -0
  192. package/sdd/bin/lib/phase.cjs +868 -335
  193. package/sdd/bin/lib/phases-command-router.cjs +39 -0
  194. package/sdd/bin/lib/plan-scan.cjs +138 -0
  195. package/sdd/bin/lib/planning-workspace.cjs +361 -0
  196. package/sdd/bin/lib/profile-output.cjs +197 -35
  197. package/sdd/bin/lib/profile-pipeline.cjs +1 -1
  198. package/sdd/bin/lib/review-reviewer-selection.cjs +125 -0
  199. package/sdd/bin/lib/roadmap-command-router.cjs +23 -0
  200. package/sdd/bin/lib/roadmap.cjs +416 -124
  201. package/sdd/bin/lib/runtime-homes.cjs +178 -0
  202. package/sdd/bin/lib/schema-detect.cjs +238 -0
  203. package/sdd/bin/lib/sdd2-import.cjs +511 -0
  204. package/sdd/bin/lib/secrets.cjs +33 -0
  205. package/sdd/bin/lib/security.cjs +131 -9
  206. package/sdd/bin/lib/shell-command-projection.cjs +548 -0
  207. package/sdd/bin/lib/state-command-router.cjs +100 -0
  208. package/sdd/bin/lib/state-document.cjs +12 -0
  209. package/sdd/bin/lib/state-document.generated.cjs +127 -0
  210. package/sdd/bin/lib/state.cjs +1253 -367
  211. package/sdd/bin/lib/surface.cjs +398 -0
  212. package/sdd/bin/lib/template.cjs +11 -5
  213. package/sdd/bin/lib/uat.cjs +9 -2
  214. package/sdd/bin/lib/validate-command-router.cjs +55 -0
  215. package/sdd/bin/lib/verify-command-router.cjs +34 -0
  216. package/sdd/bin/lib/verify.cjs +648 -140
  217. package/sdd/bin/lib/workstream-inventory.cjs +159 -0
  218. package/sdd/bin/lib/workstream-name-policy.cjs +33 -0
  219. package/sdd/bin/lib/workstream.cjs +78 -196
  220. package/sdd/bin/lib/worktree-safety.cjs +563 -0
  221. package/sdd/bin/sdd-tools.cjs +528 -222
  222. package/sdd/bin/verify-reapply-patches.cjs +247 -0
  223. package/sdd/contexts/dev.md +21 -0
  224. package/sdd/contexts/research.md +22 -0
  225. package/sdd/contexts/review.md +23 -0
  226. package/sdd/references/agent-contracts.md +79 -0
  227. package/sdd/references/ai-evals.md +156 -0
  228. package/sdd/references/ai-frameworks.md +186 -0
  229. package/sdd/references/artifact-types.md +131 -0
  230. package/sdd/references/autonomous-smart-discuss.md +277 -0
  231. package/sdd/references/checkpoints.md +36 -0
  232. package/sdd/references/common-bug-patterns.md +114 -0
  233. package/sdd/references/context-budget.md +85 -0
  234. package/sdd/references/continuation-format.md +30 -26
  235. package/sdd/references/debugger-philosophy.md +76 -0
  236. package/sdd/references/decimal-phase-calculation.md +5 -5
  237. package/sdd/references/doc-conflict-engine.md +91 -0
  238. package/sdd/references/domain-probes.md +125 -0
  239. package/sdd/references/execute-mvp-tdd.md +81 -0
  240. package/sdd/references/executor-examples.md +110 -0
  241. package/sdd/references/few-shot-examples/plan-checker.md +73 -0
  242. package/sdd/references/few-shot-examples/verifier.md +109 -0
  243. package/sdd/references/gate-prompts.md +100 -0
  244. package/sdd/references/gates.md +70 -0
  245. package/sdd/references/git-integration.md +9 -6
  246. package/sdd/references/git-planning-commit.md +6 -4
  247. package/sdd/references/ios-scaffold.md +123 -0
  248. package/sdd/references/mandatory-initial-read.md +2 -0
  249. package/sdd/references/model-profile-resolution.md +2 -0
  250. package/sdd/references/model-profiles.md +128 -22
  251. package/sdd/references/mvp-concepts.md +49 -0
  252. package/sdd/references/phase-argument-parsing.md +3 -3
  253. package/sdd/references/planner-antipatterns.md +89 -0
  254. package/sdd/references/planner-chunked.md +49 -0
  255. package/sdd/references/planner-gap-closure.md +62 -0
  256. package/sdd/references/planner-human-verify-mode.md +57 -0
  257. package/sdd/references/planner-mvp-mode.md +53 -0
  258. package/sdd/references/planner-reviews.md +39 -0
  259. package/sdd/references/planner-revision.md +87 -0
  260. package/sdd/references/planner-source-audit.md +73 -0
  261. package/sdd/references/planning-config.md +276 -7
  262. package/sdd/references/project-skills-discovery.md +19 -0
  263. package/sdd/references/revision-loop.md +97 -0
  264. package/sdd/references/scout-codebase.md +51 -0
  265. package/sdd/references/skeleton-template.md +48 -0
  266. package/sdd/references/sketch-interactivity.md +41 -0
  267. package/sdd/references/sketch-theme-system.md +94 -0
  268. package/sdd/references/sketch-tooling.md +45 -0
  269. package/sdd/references/sketch-variant-patterns.md +81 -0
  270. package/sdd/references/spidr-splitting.md +69 -0
  271. package/sdd/references/tdd.md +67 -0
  272. package/sdd/references/thinking-models-debug.md +44 -0
  273. package/sdd/references/thinking-models-execution.md +50 -0
  274. package/sdd/references/thinking-models-planning.md +62 -0
  275. package/sdd/references/thinking-models-research.md +50 -0
  276. package/sdd/references/thinking-models-verification.md +55 -0
  277. package/sdd/references/thinking-partner.md +96 -0
  278. package/sdd/references/ui-brand.md +4 -4
  279. package/sdd/references/universal-anti-patterns.md +63 -0
  280. package/sdd/references/user-story-template.md +58 -0
  281. package/sdd/references/verification-overrides.md +227 -0
  282. package/sdd/references/verify-mvp-mode.md +85 -0
  283. package/sdd/references/workstream-flag.md +63 -10
  284. package/sdd/references/worktree-path-safety.md +89 -0
  285. package/sdd/templates/AI-SPEC.md +246 -0
  286. package/sdd/templates/DEBUG.md +7 -2
  287. package/sdd/templates/README.md +77 -0
  288. package/sdd/templates/SECURITY.md +61 -0
  289. package/sdd/templates/VALIDATION.md +3 -3
  290. package/sdd/templates/claude-md.md +27 -4
  291. package/sdd/templates/config.json +20 -2
  292. package/sdd/templates/discovery.md +2 -2
  293. package/sdd/templates/research.md +40 -0
  294. package/sdd/templates/spec.md +307 -0
  295. package/sdd/templates/state.md +10 -2
  296. package/sdd/workflows/add-backlog.md +90 -0
  297. package/sdd/workflows/add-phase.md +12 -12
  298. package/sdd/workflows/add-tests.md +6 -3
  299. package/sdd/workflows/add-todo.md +8 -6
  300. package/sdd/workflows/ai-integration-phase.md +294 -0
  301. package/sdd/workflows/analyze-dependencies.md +96 -0
  302. package/sdd/workflows/audit-fix.md +177 -0
  303. package/sdd/workflows/audit-milestone.md +35 -18
  304. package/sdd/workflows/audit-uat.md +1 -1
  305. package/sdd/workflows/autonomous.md +202 -304
  306. package/sdd/workflows/check-todos.md +12 -10
  307. package/sdd/workflows/cleanup.md +3 -1
  308. package/sdd/workflows/code-review-fix.md +501 -0
  309. package/sdd/workflows/code-review.md +613 -0
  310. package/sdd/workflows/complete-milestone.md +115 -28
  311. package/sdd/workflows/debug.md +231 -0
  312. package/sdd/workflows/diagnose-issues.md +14 -5
  313. package/sdd/workflows/discovery-phase.md +3 -1
  314. package/sdd/workflows/discuss-phase/modes/advisor.md +175 -0
  315. package/sdd/workflows/discuss-phase/modes/all.md +28 -0
  316. package/sdd/workflows/discuss-phase/modes/analyze.md +44 -0
  317. package/sdd/workflows/discuss-phase/modes/auto.md +56 -0
  318. package/sdd/workflows/discuss-phase/modes/batch.md +52 -0
  319. package/sdd/workflows/discuss-phase/modes/chain.md +97 -0
  320. package/sdd/workflows/discuss-phase/modes/default.md +141 -0
  321. package/sdd/workflows/discuss-phase/modes/power.md +44 -0
  322. package/sdd/workflows/discuss-phase/modes/text.md +55 -0
  323. package/sdd/workflows/discuss-phase/templates/checkpoint.json +18 -0
  324. package/sdd/workflows/discuss-phase/templates/context.md +136 -0
  325. package/sdd/workflows/discuss-phase/templates/discussion-log.md +50 -0
  326. package/sdd/workflows/discuss-phase-assumptions.md +41 -20
  327. package/sdd/workflows/discuss-phase-power.md +291 -0
  328. package/sdd/workflows/discuss-phase.md +242 -792
  329. package/sdd/workflows/do.md +13 -7
  330. package/sdd/workflows/docs-update.md +1161 -0
  331. package/sdd/workflows/edit-phase.md +294 -0
  332. package/sdd/workflows/eval-review.md +155 -0
  333. package/sdd/workflows/execute-phase/steps/codebase-drift-gate.md +81 -0
  334. package/sdd/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  335. package/sdd/workflows/execute-phase/steps/post-merge-gate.md +116 -0
  336. package/sdd/workflows/execute-phase.md +1062 -108
  337. package/sdd/workflows/execute-plan.md +118 -107
  338. package/sdd/workflows/explore.md +143 -0
  339. package/sdd/workflows/extract-learnings.md +242 -0
  340. package/sdd/workflows/forensics.md +17 -4
  341. package/sdd/workflows/graduation.md +195 -0
  342. package/sdd/workflows/health.md +45 -3
  343. package/sdd/workflows/help.md +265 -88
  344. package/sdd/workflows/import.md +253 -0
  345. package/sdd/workflows/inbox.md +387 -0
  346. package/sdd/workflows/ingest-docs.md +339 -0
  347. package/sdd/workflows/insert-phase.md +37 -16
  348. package/sdd/workflows/list-phase-assumptions.md +2 -2
  349. package/sdd/workflows/list-workspaces.md +3 -3
  350. package/sdd/workflows/manager.md +62 -32
  351. package/sdd/workflows/map-codebase.md +90 -24
  352. package/sdd/workflows/milestone-summary.md +6 -6
  353. package/sdd/workflows/mvp-phase.md +221 -0
  354. package/sdd/workflows/new-milestone.md +168 -20
  355. package/sdd/workflows/new-project.md +273 -47
  356. package/sdd/workflows/new-workspace.md +8 -6
  357. package/sdd/workflows/next.md +127 -4
  358. package/sdd/workflows/note.md +7 -5
  359. package/sdd/workflows/pause-work.md +79 -12
  360. package/sdd/workflows/plan-milestone-gaps.md +14 -7
  361. package/sdd/workflows/plan-phase.md +987 -62
  362. package/sdd/workflows/plan-review-convergence.md +329 -0
  363. package/sdd/workflows/plant-seed.md +145 -85
  364. package/sdd/workflows/pr-branch.md +41 -13
  365. package/sdd/workflows/profile-user.md +20 -18
  366. package/sdd/workflows/progress.md +186 -44
  367. package/sdd/workflows/quick.md +470 -58
  368. package/sdd/workflows/reapply-patches.md +390 -0
  369. package/sdd/workflows/remove-phase.md +12 -12
  370. package/sdd/workflows/remove-workspace.md +24 -7
  371. package/sdd/workflows/resume-project.md +18 -15
  372. package/sdd/workflows/review.md +242 -11
  373. package/sdd/workflows/scan.md +104 -0
  374. package/sdd/workflows/secure-phase.md +179 -0
  375. package/sdd/workflows/session-report.md +2 -2
  376. package/sdd/workflows/settings-advanced.md +579 -0
  377. package/sdd/workflows/settings-integrations.md +281 -0
  378. package/sdd/workflows/settings.md +221 -16
  379. package/sdd/workflows/ship.md +140 -13
  380. package/sdd/workflows/sketch-wrap-up.md +285 -0
  381. package/sdd/workflows/sketch.md +360 -0
  382. package/sdd/workflows/spec-phase.md +262 -0
  383. package/sdd/workflows/spike-wrap-up.md +306 -0
  384. package/sdd/workflows/spike.md +452 -0
  385. package/sdd/workflows/stats.md +20 -1
  386. package/sdd/workflows/sync-skills.md +182 -0
  387. package/sdd/workflows/thread.md +221 -0
  388. package/sdd/workflows/transition.md +44 -22
  389. package/sdd/workflows/ui-phase.md +39 -14
  390. package/sdd/workflows/ui-review.md +33 -6
  391. package/sdd/workflows/ultraplan-phase.md +198 -0
  392. package/sdd/workflows/undo.md +314 -0
  393. package/sdd/workflows/update.md +350 -29
  394. package/sdd/workflows/validate-phase.md +10 -6
  395. package/sdd/workflows/verify-phase.md +307 -18
  396. package/sdd/workflows/verify-work.md +153 -10
  397. package/sdk/dist/cli-transport.d.ts +19 -0
  398. package/sdk/dist/cli-transport.d.ts.map +1 -0
  399. package/sdk/dist/cli-transport.js +104 -0
  400. package/sdk/dist/cli-transport.js.map +1 -0
  401. package/sdk/dist/cli.d.ts +46 -0
  402. package/sdk/dist/cli.d.ts.map +1 -0
  403. package/sdk/dist/cli.js +511 -0
  404. package/sdk/dist/cli.js.map +1 -0
  405. package/sdk/dist/config.d.ts +84 -0
  406. package/sdk/dist/config.d.ts.map +1 -0
  407. package/sdk/dist/config.js +135 -0
  408. package/sdk/dist/config.js.map +1 -0
  409. package/sdk/dist/context-engine.d.ts +49 -0
  410. package/sdk/dist/context-engine.d.ts.map +1 -0
  411. package/sdk/dist/context-engine.js +142 -0
  412. package/sdk/dist/context-engine.js.map +1 -0
  413. package/sdk/dist/context-truncation.d.ts +33 -0
  414. package/sdk/dist/context-truncation.d.ts.map +1 -0
  415. package/sdk/dist/context-truncation.js +197 -0
  416. package/sdk/dist/context-truncation.js.map +1 -0
  417. package/sdk/dist/errors.d.ts +46 -0
  418. package/sdk/dist/errors.d.ts.map +1 -0
  419. package/sdk/dist/errors.js +64 -0
  420. package/sdk/dist/errors.js.map +1 -0
  421. package/sdk/dist/event-stream.d.ts +53 -0
  422. package/sdk/dist/event-stream.d.ts.map +1 -0
  423. package/sdk/dist/event-stream.js +321 -0
  424. package/sdk/dist/event-stream.js.map +1 -0
  425. package/sdk/dist/golden/capture.d.ts +15 -0
  426. package/sdk/dist/golden/capture.d.ts.map +1 -0
  427. package/sdk/dist/golden/capture.js +67 -0
  428. package/sdk/dist/golden/capture.js.map +1 -0
  429. package/sdk/dist/golden/golden-integration-covered.d.ts +6 -0
  430. package/sdk/dist/golden/golden-integration-covered.d.ts.map +1 -0
  431. package/sdk/dist/golden/golden-integration-covered.js +30 -0
  432. package/sdk/dist/golden/golden-integration-covered.js.map +1 -0
  433. package/sdk/dist/golden/golden-mutation-covered.d.ts +7 -0
  434. package/sdk/dist/golden/golden-mutation-covered.d.ts.map +1 -0
  435. package/sdk/dist/golden/golden-mutation-covered.js +17 -0
  436. package/sdk/dist/golden/golden-mutation-covered.js.map +1 -0
  437. package/sdk/dist/golden/golden-policy.d.ts +10 -0
  438. package/sdk/dist/golden/golden-policy.d.ts.map +1 -0
  439. package/sdk/dist/golden/golden-policy.js +98 -0
  440. package/sdk/dist/golden/golden-policy.js.map +1 -0
  441. package/sdk/dist/golden/init-golden-normalize.d.ts +8 -0
  442. package/sdk/dist/golden/init-golden-normalize.d.ts.map +1 -0
  443. package/sdk/dist/golden/init-golden-normalize.js +14 -0
  444. package/sdk/dist/golden/init-golden-normalize.js.map +1 -0
  445. package/sdk/dist/golden/read-only-golden-rows.d.ts +20 -0
  446. package/sdk/dist/golden/read-only-golden-rows.d.ts.map +1 -0
  447. package/sdk/dist/golden/read-only-golden-rows.js +67 -0
  448. package/sdk/dist/golden/read-only-golden-rows.js.map +1 -0
  449. package/sdk/dist/golden/registry-canonical-commands.d.ts +6 -0
  450. package/sdk/dist/golden/registry-canonical-commands.d.ts.map +1 -0
  451. package/sdk/dist/golden/registry-canonical-commands.js +30 -0
  452. package/sdk/dist/golden/registry-canonical-commands.js.map +1 -0
  453. package/sdk/dist/index.d.ts +125 -0
  454. package/sdk/dist/index.d.ts.map +1 -0
  455. package/sdk/dist/index.js +298 -0
  456. package/sdk/dist/index.js.map +1 -0
  457. package/sdk/dist/init-runner.d.ts +90 -0
  458. package/sdk/dist/init-runner.d.ts.map +1 -0
  459. package/sdk/dist/init-runner.js +613 -0
  460. package/sdk/dist/init-runner.js.map +1 -0
  461. package/sdk/dist/logger.d.ts +50 -0
  462. package/sdk/dist/logger.d.ts.map +1 -0
  463. package/sdk/dist/logger.js +70 -0
  464. package/sdk/dist/logger.js.map +1 -0
  465. package/sdk/dist/model-catalog.d.ts +31 -0
  466. package/sdk/dist/model-catalog.d.ts.map +1 -0
  467. package/sdk/dist/model-catalog.js +31 -0
  468. package/sdk/dist/model-catalog.js.map +1 -0
  469. package/sdk/dist/phase-prompt.d.ts +72 -0
  470. package/sdk/dist/phase-prompt.d.ts.map +1 -0
  471. package/sdk/dist/phase-prompt.js +213 -0
  472. package/sdk/dist/phase-prompt.js.map +1 -0
  473. package/sdk/dist/phase-runner.d.ts +145 -0
  474. package/sdk/dist/phase-runner.d.ts.map +1 -0
  475. package/sdk/dist/phase-runner.js +1206 -0
  476. package/sdk/dist/phase-runner.js.map +1 -0
  477. package/sdk/dist/plan-parser.d.ts +55 -0
  478. package/sdk/dist/plan-parser.d.ts.map +1 -0
  479. package/sdk/dist/plan-parser.js +389 -0
  480. package/sdk/dist/plan-parser.js.map +1 -0
  481. package/sdk/dist/planning-journal.d.ts +64 -0
  482. package/sdk/dist/planning-journal.d.ts.map +1 -0
  483. package/sdk/dist/planning-journal.js +88 -0
  484. package/sdk/dist/planning-journal.js.map +1 -0
  485. package/sdk/dist/planning-runtime.d.ts +67 -0
  486. package/sdk/dist/planning-runtime.d.ts.map +1 -0
  487. package/sdk/dist/planning-runtime.js +58 -0
  488. package/sdk/dist/planning-runtime.js.map +1 -0
  489. package/sdk/dist/prompt-builder.d.ts +44 -0
  490. package/sdk/dist/prompt-builder.d.ts.map +1 -0
  491. package/sdk/dist/prompt-builder.js +180 -0
  492. package/sdk/dist/prompt-builder.js.map +1 -0
  493. package/sdk/dist/prompt-sanitizer.d.ts +35 -0
  494. package/sdk/dist/prompt-sanitizer.d.ts.map +1 -0
  495. package/sdk/dist/prompt-sanitizer.js +101 -0
  496. package/sdk/dist/prompt-sanitizer.js.map +1 -0
  497. package/sdk/dist/query/active-workstream-store.d.ts +7 -0
  498. package/sdk/dist/query/active-workstream-store.d.ts.map +1 -0
  499. package/sdk/dist/query/active-workstream-store.js +56 -0
  500. package/sdk/dist/query/active-workstream-store.js.map +1 -0
  501. package/sdk/dist/query/agent-failure-classifier.d.ts +38 -0
  502. package/sdk/dist/query/agent-failure-classifier.d.ts.map +1 -0
  503. package/sdk/dist/query/agent-failure-classifier.js +83 -0
  504. package/sdk/dist/query/agent-failure-classifier.js.map +1 -0
  505. package/sdk/dist/query/audit-open.d.ts +46 -0
  506. package/sdk/dist/query/audit-open.d.ts.map +1 -0
  507. package/sdk/dist/query/audit-open.js +662 -0
  508. package/sdk/dist/query/audit-open.js.map +1 -0
  509. package/sdk/dist/query/check-auto-mode.d.ts +13 -0
  510. package/sdk/dist/query/check-auto-mode.d.ts.map +1 -0
  511. package/sdk/dist/query/check-auto-mode.js +40 -0
  512. package/sdk/dist/query/check-auto-mode.js.map +1 -0
  513. package/sdk/dist/query/check-completion.d.ts +10 -0
  514. package/sdk/dist/query/check-completion.d.ts.map +1 -0
  515. package/sdk/dist/query/check-completion.js +157 -0
  516. package/sdk/dist/query/check-completion.js.map +1 -0
  517. package/sdk/dist/query/check-decision-coverage.d.ts +33 -0
  518. package/sdk/dist/query/check-decision-coverage.d.ts.map +1 -0
  519. package/sdk/dist/query/check-decision-coverage.js +472 -0
  520. package/sdk/dist/query/check-decision-coverage.js.map +1 -0
  521. package/sdk/dist/query/check-gates.d.ts +10 -0
  522. package/sdk/dist/query/check-gates.d.ts.map +1 -0
  523. package/sdk/dist/query/check-gates.js +89 -0
  524. package/sdk/dist/query/check-gates.js.map +1 -0
  525. package/sdk/dist/query/check-ship-ready.d.ts +10 -0
  526. package/sdk/dist/query/check-ship-ready.d.ts.map +1 -0
  527. package/sdk/dist/query/check-ship-ready.js +93 -0
  528. package/sdk/dist/query/check-ship-ready.js.map +1 -0
  529. package/sdk/dist/query/check-verification-status.d.ts +10 -0
  530. package/sdk/dist/query/check-verification-status.d.ts.map +1 -0
  531. package/sdk/dist/query/check-verification-status.js +142 -0
  532. package/sdk/dist/query/check-verification-status.js.map +1 -0
  533. package/sdk/dist/query/command-aliases.generated.d.ts +31 -0
  534. package/sdk/dist/query/command-aliases.generated.d.ts.map +1 -0
  535. package/sdk/dist/query/command-aliases.generated.js +135 -0
  536. package/sdk/dist/query/command-aliases.generated.js.map +1 -0
  537. package/sdk/dist/query/command-catalog.d.ts +9 -0
  538. package/sdk/dist/query/command-catalog.d.ts.map +1 -0
  539. package/sdk/dist/query/command-catalog.js +17 -0
  540. package/sdk/dist/query/command-catalog.js.map +1 -0
  541. package/sdk/dist/query/command-definition.d.ts +19 -0
  542. package/sdk/dist/query/command-definition.d.ts.map +1 -0
  543. package/sdk/dist/query/command-definition.js +44 -0
  544. package/sdk/dist/query/command-definition.js.map +1 -0
  545. package/sdk/dist/query/command-family-handlers.d.ts +3 -0
  546. package/sdk/dist/query/command-family-handlers.d.ts.map +1 -0
  547. package/sdk/dist/query/command-family-handlers.js +94 -0
  548. package/sdk/dist/query/command-family-handlers.js.map +1 -0
  549. package/sdk/dist/query/command-manifest.d.ts +2 -0
  550. package/sdk/dist/query/command-manifest.d.ts.map +1 -0
  551. package/sdk/dist/query/command-manifest.init.d.ts +6 -0
  552. package/sdk/dist/query/command-manifest.init.d.ts.map +1 -0
  553. package/sdk/dist/query/command-manifest.init.js +23 -0
  554. package/sdk/dist/query/command-manifest.init.js.map +1 -0
  555. package/sdk/dist/query/command-manifest.js +17 -0
  556. package/sdk/dist/query/command-manifest.js.map +1 -0
  557. package/sdk/dist/query/command-manifest.non-family.d.ts +9 -0
  558. package/sdk/dist/query/command-manifest.non-family.d.ts.map +1 -0
  559. package/sdk/dist/query/command-manifest.non-family.js +59 -0
  560. package/sdk/dist/query/command-manifest.non-family.js.map +1 -0
  561. package/sdk/dist/query/command-manifest.phase.d.ts +6 -0
  562. package/sdk/dist/query/command-manifest.phase.d.ts.map +1 -0
  563. package/sdk/dist/query/command-manifest.phase.js +15 -0
  564. package/sdk/dist/query/command-manifest.phase.js.map +1 -0
  565. package/sdk/dist/query/command-manifest.phases.d.ts +7 -0
  566. package/sdk/dist/query/command-manifest.phases.d.ts.map +1 -0
  567. package/sdk/dist/query/command-manifest.phases.js +10 -0
  568. package/sdk/dist/query/command-manifest.phases.js.map +1 -0
  569. package/sdk/dist/query/command-manifest.roadmap.d.ts +6 -0
  570. package/sdk/dist/query/command-manifest.roadmap.d.ts.map +1 -0
  571. package/sdk/dist/query/command-manifest.roadmap.js +10 -0
  572. package/sdk/dist/query/command-manifest.roadmap.js.map +1 -0
  573. package/sdk/dist/query/command-manifest.state.d.ts +9 -0
  574. package/sdk/dist/query/command-manifest.state.d.ts.map +1 -0
  575. package/sdk/dist/query/command-manifest.state.js +30 -0
  576. package/sdk/dist/query/command-manifest.state.js.map +1 -0
  577. package/sdk/dist/query/command-manifest.types.d.ts +12 -0
  578. package/sdk/dist/query/command-manifest.types.d.ts.map +1 -0
  579. package/sdk/dist/query/command-manifest.types.js +2 -0
  580. package/sdk/dist/query/command-manifest.types.js.map +1 -0
  581. package/sdk/dist/query/command-manifest.validate.d.ts +6 -0
  582. package/sdk/dist/query/command-manifest.validate.d.ts.map +1 -0
  583. package/sdk/dist/query/command-manifest.validate.js +10 -0
  584. package/sdk/dist/query/command-manifest.validate.js.map +1 -0
  585. package/sdk/dist/query/command-manifest.verify.d.ts +6 -0
  586. package/sdk/dist/query/command-manifest.verify.d.ts.map +1 -0
  587. package/sdk/dist/query/command-manifest.verify.js +14 -0
  588. package/sdk/dist/query/command-manifest.verify.js.map +1 -0
  589. package/sdk/dist/query/command-static-catalog-domain.d.ts +3 -0
  590. package/sdk/dist/query/command-static-catalog-domain.d.ts.map +1 -0
  591. package/sdk/dist/query/command-static-catalog-domain.js +116 -0
  592. package/sdk/dist/query/command-static-catalog-domain.js.map +1 -0
  593. package/sdk/dist/query/command-static-catalog-foundation.d.ts +7 -0
  594. package/sdk/dist/query/command-static-catalog-foundation.d.ts.map +1 -0
  595. package/sdk/dist/query/command-static-catalog-foundation.js +98 -0
  596. package/sdk/dist/query/command-static-catalog-foundation.js.map +1 -0
  597. package/sdk/dist/query/command-topology.d.ts +32 -0
  598. package/sdk/dist/query/command-topology.d.ts.map +1 -0
  599. package/sdk/dist/query/command-topology.js +66 -0
  600. package/sdk/dist/query/command-topology.js.map +1 -0
  601. package/sdk/dist/query/commands-list.d.ts +14 -0
  602. package/sdk/dist/query/commands-list.d.ts.map +1 -0
  603. package/sdk/dist/query/commands-list.js +18 -0
  604. package/sdk/dist/query/commands-list.js.map +1 -0
  605. package/sdk/dist/query/commit.d.ts +79 -0
  606. package/sdk/dist/query/commit.d.ts.map +1 -0
  607. package/sdk/dist/query/commit.js +340 -0
  608. package/sdk/dist/query/commit.js.map +1 -0
  609. package/sdk/dist/query/config-gates.d.ts +12 -0
  610. package/sdk/dist/query/config-gates.d.ts.map +1 -0
  611. package/sdk/dist/query/config-gates.js +66 -0
  612. package/sdk/dist/query/config-gates.js.map +1 -0
  613. package/sdk/dist/query/config-mutation.d.ts +86 -0
  614. package/sdk/dist/query/config-mutation.d.ts.map +1 -0
  615. package/sdk/dist/query/config-mutation.js +518 -0
  616. package/sdk/dist/query/config-mutation.js.map +1 -0
  617. package/sdk/dist/query/config-query.d.ts +57 -0
  618. package/sdk/dist/query/config-query.d.ts.map +1 -0
  619. package/sdk/dist/query/config-query.js +208 -0
  620. package/sdk/dist/query/config-query.js.map +1 -0
  621. package/sdk/dist/query/config-schema.d.ts +36 -0
  622. package/sdk/dist/query/config-schema.d.ts.map +1 -0
  623. package/sdk/dist/query/config-schema.js +147 -0
  624. package/sdk/dist/query/config-schema.js.map +1 -0
  625. package/sdk/dist/query/decisions.d.ts +58 -0
  626. package/sdk/dist/query/decisions.d.ts.map +1 -0
  627. package/sdk/dist/query/decisions.js +161 -0
  628. package/sdk/dist/query/decisions.js.map +1 -0
  629. package/sdk/dist/query/detect-custom-files.d.ts +11 -0
  630. package/sdk/dist/query/detect-custom-files.d.ts.map +1 -0
  631. package/sdk/dist/query/detect-custom-files.js +89 -0
  632. package/sdk/dist/query/detect-custom-files.js.map +1 -0
  633. package/sdk/dist/query/detect-phase-type.d.ts +9 -0
  634. package/sdk/dist/query/detect-phase-type.d.ts.map +1 -0
  635. package/sdk/dist/query/detect-phase-type.js +124 -0
  636. package/sdk/dist/query/detect-phase-type.js.map +1 -0
  637. package/sdk/dist/query/docs-init.d.ts +26 -0
  638. package/sdk/dist/query/docs-init.d.ts.map +1 -0
  639. package/sdk/dist/query/docs-init.js +231 -0
  640. package/sdk/dist/query/docs-init.js.map +1 -0
  641. package/sdk/dist/query/fallow-audit.d.ts +44 -0
  642. package/sdk/dist/query/fallow-audit.d.ts.map +1 -0
  643. package/sdk/dist/query/fallow-audit.js +44 -0
  644. package/sdk/dist/query/fallow-audit.js.map +1 -0
  645. package/sdk/dist/query/frontmatter-mutation.d.ts +77 -0
  646. package/sdk/dist/query/frontmatter-mutation.d.ts.map +1 -0
  647. package/sdk/dist/query/frontmatter-mutation.js +317 -0
  648. package/sdk/dist/query/frontmatter-mutation.js.map +1 -0
  649. package/sdk/dist/query/frontmatter.d.ts +93 -0
  650. package/sdk/dist/query/frontmatter.d.ts.map +1 -0
  651. package/sdk/dist/query/frontmatter.js +365 -0
  652. package/sdk/dist/query/frontmatter.js.map +1 -0
  653. package/sdk/dist/query/helpers.d.ts +191 -0
  654. package/sdk/dist/query/helpers.d.ts.map +1 -0
  655. package/sdk/dist/query/helpers.js +613 -0
  656. package/sdk/dist/query/helpers.js.map +1 -0
  657. package/sdk/dist/query/index.d.ts +8 -0
  658. package/sdk/dist/query/index.d.ts.map +1 -0
  659. package/sdk/dist/query/index.js +6 -0
  660. package/sdk/dist/query/index.js.map +1 -0
  661. package/sdk/dist/query/init-complex.d.ts +47 -0
  662. package/sdk/dist/query/init-complex.d.ts.map +1 -0
  663. package/sdk/dist/query/init-complex.js +718 -0
  664. package/sdk/dist/query/init-complex.js.map +1 -0
  665. package/sdk/dist/query/init.d.ts +106 -0
  666. package/sdk/dist/query/init.d.ts.map +1 -0
  667. package/sdk/dist/query/init.js +1159 -0
  668. package/sdk/dist/query/init.js.map +1 -0
  669. package/sdk/dist/query/intel.d.ts +43 -0
  670. package/sdk/dist/query/intel.d.ts.map +1 -0
  671. package/sdk/dist/query/intel.js +416 -0
  672. package/sdk/dist/query/intel.js.map +1 -0
  673. package/sdk/dist/query/mutation-event-decorator.d.ts +5 -0
  674. package/sdk/dist/query/mutation-event-decorator.d.ts.map +1 -0
  675. package/sdk/dist/query/mutation-event-decorator.js +28 -0
  676. package/sdk/dist/query/mutation-event-decorator.js.map +1 -0
  677. package/sdk/dist/query/mutation-event-mapper.d.ts +4 -0
  678. package/sdk/dist/query/mutation-event-mapper.d.ts.map +1 -0
  679. package/sdk/dist/query/mutation-event-mapper.js +70 -0
  680. package/sdk/dist/query/mutation-event-mapper.js.map +1 -0
  681. package/sdk/dist/query/mvp.d.ts +113 -0
  682. package/sdk/dist/query/mvp.d.ts.map +1 -0
  683. package/sdk/dist/query/mvp.js +225 -0
  684. package/sdk/dist/query/mvp.js.map +1 -0
  685. package/sdk/dist/query/phase-filesystem-adapter.d.ts +4 -0
  686. package/sdk/dist/query/phase-filesystem-adapter.d.ts.map +1 -0
  687. package/sdk/dist/query/phase-filesystem-adapter.js +33 -0
  688. package/sdk/dist/query/phase-filesystem-adapter.js.map +1 -0
  689. package/sdk/dist/query/phase-lifecycle-policy.d.ts +34 -0
  690. package/sdk/dist/query/phase-lifecycle-policy.d.ts.map +1 -0
  691. package/sdk/dist/query/phase-lifecycle-policy.js +138 -0
  692. package/sdk/dist/query/phase-lifecycle-policy.js.map +1 -0
  693. package/sdk/dist/query/phase-lifecycle.d.ts +116 -0
  694. package/sdk/dist/query/phase-lifecycle.d.ts.map +1 -0
  695. package/sdk/dist/query/phase-lifecycle.js +1486 -0
  696. package/sdk/dist/query/phase-lifecycle.js.map +1 -0
  697. package/sdk/dist/query/phase-list-queries.d.ts +18 -0
  698. package/sdk/dist/query/phase-list-queries.d.ts.map +1 -0
  699. package/sdk/dist/query/phase-list-queries.js +129 -0
  700. package/sdk/dist/query/phase-list-queries.js.map +1 -0
  701. package/sdk/dist/query/phase-ready.d.ts +9 -0
  702. package/sdk/dist/query/phase-ready.d.ts.map +1 -0
  703. package/sdk/dist/query/phase-ready.js +132 -0
  704. package/sdk/dist/query/phase-ready.js.map +1 -0
  705. package/sdk/dist/query/phase-roadmap-mutation.d.ts +13 -0
  706. package/sdk/dist/query/phase-roadmap-mutation.d.ts.map +1 -0
  707. package/sdk/dist/query/phase-roadmap-mutation.js +65 -0
  708. package/sdk/dist/query/phase-roadmap-mutation.js.map +1 -0
  709. package/sdk/dist/query/phase.d.ts +48 -0
  710. package/sdk/dist/query/phase.d.ts.map +1 -0
  711. package/sdk/dist/query/phase.js +451 -0
  712. package/sdk/dist/query/phase.js.map +1 -0
  713. package/sdk/dist/query/pipeline.d.ts +53 -0
  714. package/sdk/dist/query/pipeline.d.ts.map +1 -0
  715. package/sdk/dist/query/pipeline.js +198 -0
  716. package/sdk/dist/query/pipeline.js.map +1 -0
  717. package/sdk/dist/query/plan-scan.d.ts +14 -0
  718. package/sdk/dist/query/plan-scan.d.ts.map +1 -0
  719. package/sdk/dist/query/plan-scan.js +70 -0
  720. package/sdk/dist/query/plan-scan.js.map +1 -0
  721. package/sdk/dist/query/plan-task-structure.d.ts +9 -0
  722. package/sdk/dist/query/plan-task-structure.d.ts.map +1 -0
  723. package/sdk/dist/query/plan-task-structure.js +59 -0
  724. package/sdk/dist/query/plan-task-structure.js.map +1 -0
  725. package/sdk/dist/query/profile-extract-messages.d.ts +40 -0
  726. package/sdk/dist/query/profile-extract-messages.d.ts.map +1 -0
  727. package/sdk/dist/query/profile-extract-messages.js +195 -0
  728. package/sdk/dist/query/profile-extract-messages.js.map +1 -0
  729. package/sdk/dist/query/profile-output.d.ts +11 -0
  730. package/sdk/dist/query/profile-output.d.ts.map +1 -0
  731. package/sdk/dist/query/profile-output.js +873 -0
  732. package/sdk/dist/query/profile-output.js.map +1 -0
  733. package/sdk/dist/query/profile-questionnaire-data.d.ts +21 -0
  734. package/sdk/dist/query/profile-questionnaire-data.d.ts.map +1 -0
  735. package/sdk/dist/query/profile-questionnaire-data.js +171 -0
  736. package/sdk/dist/query/profile-questionnaire-data.js.map +1 -0
  737. package/sdk/dist/query/profile-sample.d.ts +22 -0
  738. package/sdk/dist/query/profile-sample.d.ts.map +1 -0
  739. package/sdk/dist/query/profile-sample.js +136 -0
  740. package/sdk/dist/query/profile-sample.js.map +1 -0
  741. package/sdk/dist/query/profile-scan-sessions.d.ts +49 -0
  742. package/sdk/dist/query/profile-scan-sessions.d.ts.map +1 -0
  743. package/sdk/dist/query/profile-scan-sessions.js +137 -0
  744. package/sdk/dist/query/profile-scan-sessions.js.map +1 -0
  745. package/sdk/dist/query/profile.d.ts +61 -0
  746. package/sdk/dist/query/profile.d.ts.map +1 -0
  747. package/sdk/dist/query/profile.js +307 -0
  748. package/sdk/dist/query/profile.js.map +1 -0
  749. package/sdk/dist/query/progress.d.ts +77 -0
  750. package/sdk/dist/query/progress.d.ts.map +1 -0
  751. package/sdk/dist/query/progress.js +481 -0
  752. package/sdk/dist/query/progress.js.map +1 -0
  753. package/sdk/dist/query/query-cli-adapter.d.ts +8 -0
  754. package/sdk/dist/query/query-cli-adapter.d.ts.map +1 -0
  755. package/sdk/dist/query/query-cli-adapter.js +32 -0
  756. package/sdk/dist/query/query-cli-adapter.js.map +1 -0
  757. package/sdk/dist/query/query-cli-output.d.ts +9 -0
  758. package/sdk/dist/query/query-cli-output.d.ts.map +1 -0
  759. package/sdk/dist/query/query-cli-output.js +28 -0
  760. package/sdk/dist/query/query-cli-output.js.map +1 -0
  761. package/sdk/dist/query/query-command-diagnosis.d.ts +6 -0
  762. package/sdk/dist/query/query-command-diagnosis.d.ts.map +1 -0
  763. package/sdk/dist/query/query-command-diagnosis.js +6 -0
  764. package/sdk/dist/query/query-command-diagnosis.js.map +1 -0
  765. package/sdk/dist/query/query-command-resolution-strategy.d.ts +29 -0
  766. package/sdk/dist/query/query-command-resolution-strategy.d.ts.map +1 -0
  767. package/sdk/dist/query/query-command-resolution-strategy.js +103 -0
  768. package/sdk/dist/query/query-command-resolution-strategy.js.map +1 -0
  769. package/sdk/dist/query/query-command-semantics.d.ts +7 -0
  770. package/sdk/dist/query/query-command-semantics.d.ts.map +1 -0
  771. package/sdk/dist/query/query-command-semantics.js +7 -0
  772. package/sdk/dist/query/query-command-semantics.js.map +1 -0
  773. package/sdk/dist/query/query-dispatch-contract.d.ts +21 -0
  774. package/sdk/dist/query/query-dispatch-contract.d.ts.map +1 -0
  775. package/sdk/dist/query/query-dispatch-contract.js +2 -0
  776. package/sdk/dist/query/query-dispatch-contract.js.map +1 -0
  777. package/sdk/dist/query/query-dispatch-error-mapper.d.ts +6 -0
  778. package/sdk/dist/query/query-dispatch-error-mapper.d.ts.map +1 -0
  779. package/sdk/dist/query/query-dispatch-error-mapper.js +6 -0
  780. package/sdk/dist/query/query-dispatch-error-mapper.js.map +1 -0
  781. package/sdk/dist/query/query-dispatch-formatting.d.ts +6 -0
  782. package/sdk/dist/query/query-dispatch-formatting.d.ts.map +1 -0
  783. package/sdk/dist/query/query-dispatch-formatting.js +6 -0
  784. package/sdk/dist/query/query-dispatch-formatting.js.map +1 -0
  785. package/sdk/dist/query/query-dispatch-input-validation.d.ts +6 -0
  786. package/sdk/dist/query/query-dispatch-input-validation.d.ts.map +1 -0
  787. package/sdk/dist/query/query-dispatch-input-validation.js +6 -0
  788. package/sdk/dist/query/query-dispatch-input-validation.js.map +1 -0
  789. package/sdk/dist/query/query-dispatch-observability.d.ts +2 -0
  790. package/sdk/dist/query/query-dispatch-observability.d.ts.map +1 -0
  791. package/sdk/dist/query/query-dispatch-observability.js +7 -0
  792. package/sdk/dist/query/query-dispatch-observability.js.map +1 -0
  793. package/sdk/dist/query/query-dispatch-plan.d.ts +6 -0
  794. package/sdk/dist/query/query-dispatch-plan.d.ts.map +1 -0
  795. package/sdk/dist/query/query-dispatch-plan.js +6 -0
  796. package/sdk/dist/query/query-dispatch-plan.js.map +1 -0
  797. package/sdk/dist/query/query-dispatch-result-builder.d.ts +6 -0
  798. package/sdk/dist/query/query-dispatch-result-builder.d.ts.map +1 -0
  799. package/sdk/dist/query/query-dispatch-result-builder.js +6 -0
  800. package/sdk/dist/query/query-dispatch-result-builder.js.map +1 -0
  801. package/sdk/dist/query/query-dispatch.d.ts +48 -0
  802. package/sdk/dist/query/query-dispatch.d.ts.map +1 -0
  803. package/sdk/dist/query/query-dispatch.js +175 -0
  804. package/sdk/dist/query/query-dispatch.js.map +1 -0
  805. package/sdk/dist/query/query-error-details-schema.d.ts +19 -0
  806. package/sdk/dist/query/query-error-details-schema.d.ts.map +1 -0
  807. package/sdk/dist/query/query-error-details-schema.js +10 -0
  808. package/sdk/dist/query/query-error-details-schema.js.map +1 -0
  809. package/sdk/dist/query/query-error-taxonomy.d.ts +38 -0
  810. package/sdk/dist/query/query-error-taxonomy.d.ts.map +1 -0
  811. package/sdk/dist/query/query-error-taxonomy.js +74 -0
  812. package/sdk/dist/query/query-error-taxonomy.js.map +1 -0
  813. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts +14 -0
  814. package/sdk/dist/query/query-fallback-bridge-adapter.d.ts.map +1 -0
  815. package/sdk/dist/query/query-fallback-bridge-adapter.js +33 -0
  816. package/sdk/dist/query/query-fallback-bridge-adapter.js.map +1 -0
  817. package/sdk/dist/query/query-fallback-executor.d.ts +11 -0
  818. package/sdk/dist/query/query-fallback-executor.d.ts.map +1 -0
  819. package/sdk/dist/query/query-fallback-executor.js +31 -0
  820. package/sdk/dist/query/query-fallback-executor.js.map +1 -0
  821. package/sdk/dist/query/query-fallback-output-classifier.d.ts +6 -0
  822. package/sdk/dist/query/query-fallback-output-classifier.d.ts.map +1 -0
  823. package/sdk/dist/query/query-fallback-output-classifier.js +27 -0
  824. package/sdk/dist/query/query-fallback-output-classifier.js.map +1 -0
  825. package/sdk/dist/query/query-fallback-policy.d.ts +6 -0
  826. package/sdk/dist/query/query-fallback-policy.d.ts.map +1 -0
  827. package/sdk/dist/query/query-fallback-policy.js +7 -0
  828. package/sdk/dist/query/query-fallback-policy.js.map +1 -0
  829. package/sdk/dist/query/query-native-dispatch-adapter.d.ts +7 -0
  830. package/sdk/dist/query/query-native-dispatch-adapter.d.ts.map +1 -0
  831. package/sdk/dist/query/query-native-dispatch-adapter.js +6 -0
  832. package/sdk/dist/query/query-native-dispatch-adapter.js.map +1 -0
  833. package/sdk/dist/query/query-policy-capability.d.ts +10 -0
  834. package/sdk/dist/query/query-policy-capability.d.ts.map +1 -0
  835. package/sdk/dist/query/query-policy-capability.js +17 -0
  836. package/sdk/dist/query/query-policy-capability.js.map +1 -0
  837. package/sdk/dist/query/query-runtime-context.d.ts +19 -0
  838. package/sdk/dist/query/query-runtime-context.d.ts.map +1 -0
  839. package/sdk/dist/query/query-runtime-context.js +31 -0
  840. package/sdk/dist/query/query-runtime-context.js.map +1 -0
  841. package/sdk/dist/query/query-unknown-command-hints.d.ts +2 -0
  842. package/sdk/dist/query/query-unknown-command-hints.d.ts.map +1 -0
  843. package/sdk/dist/query/query-unknown-command-hints.js +6 -0
  844. package/sdk/dist/query/query-unknown-command-hints.js.map +1 -0
  845. package/sdk/dist/query/registry-assembly-descriptor.d.ts +12 -0
  846. package/sdk/dist/query/registry-assembly-descriptor.d.ts.map +1 -0
  847. package/sdk/dist/query/registry-assembly-descriptor.js +61 -0
  848. package/sdk/dist/query/registry-assembly-descriptor.js.map +1 -0
  849. package/sdk/dist/query/registry-assembly-invariants.d.ts +30 -0
  850. package/sdk/dist/query/registry-assembly-invariants.d.ts.map +1 -0
  851. package/sdk/dist/query/registry-assembly-invariants.js +77 -0
  852. package/sdk/dist/query/registry-assembly-invariants.js.map +1 -0
  853. package/sdk/dist/query/registry-assembly.d.ts +10 -0
  854. package/sdk/dist/query/registry-assembly.d.ts.map +1 -0
  855. package/sdk/dist/query/registry-assembly.js +53 -0
  856. package/sdk/dist/query/registry-assembly.js.map +1 -0
  857. package/sdk/dist/query/registry.d.ts +90 -0
  858. package/sdk/dist/query/registry.d.ts.map +1 -0
  859. package/sdk/dist/query/registry.js +129 -0
  860. package/sdk/dist/query/registry.js.map +1 -0
  861. package/sdk/dist/query/requirements-extract-from-plans.d.ts +9 -0
  862. package/sdk/dist/query/requirements-extract-from-plans.d.ts.map +1 -0
  863. package/sdk/dist/query/requirements-extract-from-plans.js +76 -0
  864. package/sdk/dist/query/requirements-extract-from-plans.js.map +1 -0
  865. package/sdk/dist/query/roadmap-update-plan-progress.d.ts +11 -0
  866. package/sdk/dist/query/roadmap-update-plan-progress.d.ts.map +1 -0
  867. package/sdk/dist/query/roadmap-update-plan-progress.js +124 -0
  868. package/sdk/dist/query/roadmap-update-plan-progress.js.map +1 -0
  869. package/sdk/dist/query/roadmap.d.ts +137 -0
  870. package/sdk/dist/query/roadmap.d.ts.map +1 -0
  871. package/sdk/dist/query/roadmap.js +753 -0
  872. package/sdk/dist/query/roadmap.js.map +1 -0
  873. package/sdk/dist/query/route-next-action.d.ts +9 -0
  874. package/sdk/dist/query/route-next-action.d.ts.map +1 -0
  875. package/sdk/dist/query/route-next-action.js +318 -0
  876. package/sdk/dist/query/route-next-action.js.map +1 -0
  877. package/sdk/dist/query/schema-detect.d.ts +21 -0
  878. package/sdk/dist/query/schema-detect.d.ts.map +1 -0
  879. package/sdk/dist/query/schema-detect.js +146 -0
  880. package/sdk/dist/query/schema-detect.js.map +1 -0
  881. package/sdk/dist/query/secrets.d.ts +27 -0
  882. package/sdk/dist/query/secrets.d.ts.map +1 -0
  883. package/sdk/dist/query/secrets.js +42 -0
  884. package/sdk/dist/query/secrets.js.map +1 -0
  885. package/sdk/dist/query/skill-manifest.d.ts +50 -0
  886. package/sdk/dist/query/skill-manifest.d.ts.map +1 -0
  887. package/sdk/dist/query/skill-manifest.js +171 -0
  888. package/sdk/dist/query/skill-manifest.js.map +1 -0
  889. package/sdk/dist/query/skills.d.ts +27 -0
  890. package/sdk/dist/query/skills.d.ts.map +1 -0
  891. package/sdk/dist/query/skills.js +137 -0
  892. package/sdk/dist/query/skills.js.map +1 -0
  893. package/sdk/dist/query/state-document.d.ts +14 -0
  894. package/sdk/dist/query/state-document.d.ts.map +1 -0
  895. package/sdk/dist/query/state-document.js +110 -0
  896. package/sdk/dist/query/state-document.js.map +1 -0
  897. package/sdk/dist/query/state-mutation.d.ts +224 -0
  898. package/sdk/dist/query/state-mutation.d.ts.map +1 -0
  899. package/sdk/dist/query/state-mutation.js +1539 -0
  900. package/sdk/dist/query/state-mutation.js.map +1 -0
  901. package/sdk/dist/query/state-project-load.d.ts +23 -0
  902. package/sdk/dist/query/state-project-load.d.ts.map +1 -0
  903. package/sdk/dist/query/state-project-load.js +75 -0
  904. package/sdk/dist/query/state-project-load.js.map +1 -0
  905. package/sdk/dist/query/state.d.ts +78 -0
  906. package/sdk/dist/query/state.d.ts.map +1 -0
  907. package/sdk/dist/query/state.js +430 -0
  908. package/sdk/dist/query/state.js.map +1 -0
  909. package/sdk/dist/query/summary.d.ts +18 -0
  910. package/sdk/dist/query/summary.d.ts.map +1 -0
  911. package/sdk/dist/query/summary.js +249 -0
  912. package/sdk/dist/query/summary.js.map +1 -0
  913. package/sdk/dist/query/template.d.ts +46 -0
  914. package/sdk/dist/query/template.d.ts.map +1 -0
  915. package/sdk/dist/query/template.js +210 -0
  916. package/sdk/dist/query/template.js.map +1 -0
  917. package/sdk/dist/query/uat.d.ts +34 -0
  918. package/sdk/dist/query/uat.d.ts.map +1 -0
  919. package/sdk/dist/query/uat.js +339 -0
  920. package/sdk/dist/query/uat.js.map +1 -0
  921. package/sdk/dist/query/utils.d.ts +59 -0
  922. package/sdk/dist/query/utils.d.ts.map +1 -0
  923. package/sdk/dist/query/utils.js +74 -0
  924. package/sdk/dist/query/utils.js.map +1 -0
  925. package/sdk/dist/query/validate.d.ts +67 -0
  926. package/sdk/dist/query/validate.d.ts.map +1 -0
  927. package/sdk/dist/query/validate.js +908 -0
  928. package/sdk/dist/query/validate.js.map +1 -0
  929. package/sdk/dist/query/verify.d.ts +110 -0
  930. package/sdk/dist/query/verify.d.ts.map +1 -0
  931. package/sdk/dist/query/verify.js +631 -0
  932. package/sdk/dist/query/verify.js.map +1 -0
  933. package/sdk/dist/query/websearch.d.ts +24 -0
  934. package/sdk/dist/query/websearch.d.ts.map +1 -0
  935. package/sdk/dist/query/websearch.js +68 -0
  936. package/sdk/dist/query/websearch.js.map +1 -0
  937. package/sdk/dist/query/workspace.d.ts +62 -0
  938. package/sdk/dist/query/workspace.d.ts.map +1 -0
  939. package/sdk/dist/query/workspace.js +104 -0
  940. package/sdk/dist/query/workspace.js.map +1 -0
  941. package/sdk/dist/query/workstream-inventory.d.ts +52 -0
  942. package/sdk/dist/query/workstream-inventory.d.ts.map +1 -0
  943. package/sdk/dist/query/workstream-inventory.js +141 -0
  944. package/sdk/dist/query/workstream-inventory.js.map +1 -0
  945. package/sdk/dist/query/workstream.d.ts +35 -0
  946. package/sdk/dist/query/workstream.d.ts.map +1 -0
  947. package/sdk/dist/query/workstream.js +298 -0
  948. package/sdk/dist/query/workstream.js.map +1 -0
  949. package/sdk/dist/query/worktree.d.ts +3 -0
  950. package/sdk/dist/query/worktree.d.ts.map +1 -0
  951. package/sdk/dist/query/worktree.js +36 -0
  952. package/sdk/dist/query/worktree.js.map +1 -0
  953. package/sdk/dist/query-command-executor.d.ts +22 -0
  954. package/sdk/dist/query-command-executor.d.ts.map +1 -0
  955. package/sdk/dist/query-command-executor.js +22 -0
  956. package/sdk/dist/query-command-executor.js.map +1 -0
  957. package/sdk/dist/query-execution-policy.d.ts +24 -0
  958. package/sdk/dist/query-execution-policy.d.ts.map +1 -0
  959. package/sdk/dist/query-execution-policy.js +27 -0
  960. package/sdk/dist/query-execution-policy.js.map +1 -0
  961. package/sdk/dist/query-failure-classification.d.ts +9 -0
  962. package/sdk/dist/query-failure-classification.d.ts.map +1 -0
  963. package/sdk/dist/query-failure-classification.js +32 -0
  964. package/sdk/dist/query-failure-classification.js.map +1 -0
  965. package/sdk/dist/query-hotpath-methods.d.ts +19 -0
  966. package/sdk/dist/query-hotpath-methods.d.ts.map +1 -0
  967. package/sdk/dist/query-hotpath-methods.js +34 -0
  968. package/sdk/dist/query-hotpath-methods.js.map +1 -0
  969. package/sdk/dist/query-native-direct-adapter.d.ts +20 -0
  970. package/sdk/dist/query-native-direct-adapter.d.ts.map +1 -0
  971. package/sdk/dist/query-native-direct-adapter.js +52 -0
  972. package/sdk/dist/query-native-direct-adapter.js.map +1 -0
  973. package/sdk/dist/query-native-hotpath-adapter.d.ts +15 -0
  974. package/sdk/dist/query-native-hotpath-adapter.d.ts.map +1 -0
  975. package/sdk/dist/query-native-hotpath-adapter.js +32 -0
  976. package/sdk/dist/query-native-hotpath-adapter.js.map +1 -0
  977. package/sdk/dist/query-raw-output-projection.d.ts +6 -0
  978. package/sdk/dist/query-raw-output-projection.d.ts.map +1 -0
  979. package/sdk/dist/query-raw-output-projection.js +67 -0
  980. package/sdk/dist/query-raw-output-projection.js.map +1 -0
  981. package/sdk/dist/query-runtime-bridge.d.ts +61 -0
  982. package/sdk/dist/query-runtime-bridge.d.ts.map +1 -0
  983. package/sdk/dist/query-runtime-bridge.js +144 -0
  984. package/sdk/dist/query-runtime-bridge.js.map +1 -0
  985. package/sdk/dist/query-sdd-tools-path.d.ts +2 -0
  986. package/sdk/dist/query-sdd-tools-path.d.ts.map +1 -0
  987. package/sdk/dist/query-sdd-tools-path.js +2 -0
  988. package/sdk/dist/query-sdd-tools-path.js.map +1 -0
  989. package/sdk/dist/query-sdd-tools-runtime.d.ts +20 -0
  990. package/sdk/dist/query-sdd-tools-runtime.d.ts.map +1 -0
  991. package/sdk/dist/query-sdd-tools-runtime.js +47 -0
  992. package/sdk/dist/query-sdd-tools-runtime.js.map +1 -0
  993. package/sdk/dist/query-subprocess-adapter.d.ts +18 -0
  994. package/sdk/dist/query-subprocess-adapter.d.ts.map +1 -0
  995. package/sdk/dist/query-subprocess-adapter.js +92 -0
  996. package/sdk/dist/query-subprocess-adapter.js.map +1 -0
  997. package/sdk/dist/query-tools-error-factory.d.ts +16 -0
  998. package/sdk/dist/query-tools-error-factory.d.ts.map +1 -0
  999. package/sdk/dist/query-tools-error-factory.js +33 -0
  1000. package/sdk/dist/query-tools-error-factory.js.map +1 -0
  1001. package/sdk/dist/research-gate.d.ts +24 -0
  1002. package/sdk/dist/research-gate.d.ts.map +1 -0
  1003. package/sdk/dist/research-gate.js +70 -0
  1004. package/sdk/dist/research-gate.js.map +1 -0
  1005. package/sdk/dist/runtime-gate.d.ts +14 -0
  1006. package/sdk/dist/runtime-gate.d.ts.map +1 -0
  1007. package/sdk/dist/runtime-gate.js +48 -0
  1008. package/sdk/dist/runtime-gate.js.map +1 -0
  1009. package/sdk/dist/sdd-tools-error.d.ts +23 -0
  1010. package/sdk/dist/sdd-tools-error.d.ts.map +1 -0
  1011. package/sdk/dist/sdd-tools-error.js +29 -0
  1012. package/sdk/dist/sdd-tools-error.js.map +1 -0
  1013. package/sdk/dist/sdd-tools.d.ts +97 -0
  1014. package/sdk/dist/sdd-tools.d.ts.map +1 -0
  1015. package/sdk/dist/sdd-tools.js +168 -0
  1016. package/sdk/dist/sdd-tools.js.map +1 -0
  1017. package/sdk/dist/sdd-transport-policy.d.ts +10 -0
  1018. package/sdk/dist/sdd-transport-policy.d.ts.map +1 -0
  1019. package/sdk/dist/sdd-transport-policy.js +32 -0
  1020. package/sdk/dist/sdd-transport-policy.js.map +1 -0
  1021. package/sdk/dist/sdd-transport.d.ts +39 -0
  1022. package/sdk/dist/sdd-transport.d.ts.map +1 -0
  1023. package/sdk/dist/sdd-transport.js +78 -0
  1024. package/sdk/dist/sdd-transport.js.map +1 -0
  1025. package/sdk/dist/sdk-package-compatibility.d.ts +38 -0
  1026. package/sdk/dist/sdk-package-compatibility.d.ts.map +1 -0
  1027. package/sdk/dist/sdk-package-compatibility.js +90 -0
  1028. package/sdk/dist/sdk-package-compatibility.js.map +1 -0
  1029. package/sdk/dist/session-runner.d.ts +40 -0
  1030. package/sdk/dist/session-runner.d.ts.map +1 -0
  1031. package/sdk/dist/session-runner.js +274 -0
  1032. package/sdk/dist/session-runner.js.map +1 -0
  1033. package/sdk/dist/tool-scoping.d.ts +31 -0
  1034. package/sdk/dist/tool-scoping.d.ts.map +1 -0
  1035. package/sdk/dist/tool-scoping.js +54 -0
  1036. package/sdk/dist/tool-scoping.js.map +1 -0
  1037. package/sdk/dist/types.d.ts +794 -0
  1038. package/sdk/dist/types.d.ts.map +1 -0
  1039. package/sdk/dist/types.js +77 -0
  1040. package/sdk/dist/types.js.map +1 -0
  1041. package/sdk/dist/workstream-name-policy.d.ts +13 -0
  1042. package/sdk/dist/workstream-name-policy.d.ts.map +1 -0
  1043. package/sdk/dist/workstream-name-policy.js +24 -0
  1044. package/sdk/dist/workstream-name-policy.js.map +1 -0
  1045. package/sdk/dist/workstream-utils.d.ts +15 -0
  1046. package/sdk/dist/workstream-utils.d.ts.map +1 -0
  1047. package/sdk/dist/workstream-utils.js +21 -0
  1048. package/sdk/dist/workstream-utils.js.map +1 -0
  1049. package/sdk/dist/ws-transport.d.ts +32 -0
  1050. package/sdk/dist/ws-transport.d.ts.map +1 -0
  1051. package/sdk/dist/ws-transport.js +84 -0
  1052. package/sdk/dist/ws-transport.js.map +1 -0
  1053. package/sdk/package-lock.json +2502 -0
  1054. package/sdk/package.json +57 -0
  1055. package/sdk/prompts/templates/project.md +186 -0
  1056. package/sdk/prompts/templates/requirements.md +231 -0
  1057. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  1058. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  1059. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  1060. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  1061. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  1062. package/sdk/prompts/templates/roadmap.md +202 -0
  1063. package/sdk/prompts/templates/state.md +175 -0
  1064. package/sdk/shared/model-catalog.json +122 -0
  1065. package/sdk/src/assembled-prompts.test.ts +349 -0
  1066. package/sdk/src/bug-3591-sddtools-runtime-workstream.test.ts +179 -0
  1067. package/sdk/src/cli-transport.test.ts +388 -0
  1068. package/sdk/src/cli-transport.ts +130 -0
  1069. package/sdk/src/cli.test.ts +426 -0
  1070. package/sdk/src/cli.ts +589 -0
  1071. package/sdk/src/config.test.ts +271 -0
  1072. package/sdk/src/config.ts +218 -0
  1073. package/sdk/src/context-engine.test.ts +295 -0
  1074. package/sdk/src/context-engine.ts +170 -0
  1075. package/sdk/src/context-truncation.test.ts +163 -0
  1076. package/sdk/src/context-truncation.ts +233 -0
  1077. package/sdk/src/e2e.integration.test.ts +181 -0
  1078. package/sdk/src/errors.ts +72 -0
  1079. package/sdk/src/event-stream.test.ts +661 -0
  1080. package/sdk/src/event-stream.ts +441 -0
  1081. package/sdk/src/golden/capture.ts +95 -0
  1082. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  1083. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  1084. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  1085. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  1086. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  1087. package/sdk/src/golden/golden-mutation-covered.ts +17 -0
  1088. package/sdk/src/golden/golden-policy.test.ts +8 -0
  1089. package/sdk/src/golden/golden-policy.ts +120 -0
  1090. package/sdk/src/golden/golden.integration.test.ts +677 -0
  1091. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  1092. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  1093. package/sdk/src/golden/read-only-parity.integration.test.ts +133 -0
  1094. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  1095. package/sdk/src/index.ts +352 -0
  1096. package/sdk/src/init-e2e.integration.test.ts +138 -0
  1097. package/sdk/src/init-runner.test.ts +740 -0
  1098. package/sdk/src/init-runner.ts +734 -0
  1099. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  1100. package/sdk/src/logger.test.ts +149 -0
  1101. package/sdk/src/logger.ts +113 -0
  1102. package/sdk/src/milestone-runner.test.ts +421 -0
  1103. package/sdk/src/model-catalog.ts +70 -0
  1104. package/sdk/src/phase-prompt.test.ts +535 -0
  1105. package/sdk/src/phase-prompt.ts +259 -0
  1106. package/sdk/src/phase-runner-types.test.ts +421 -0
  1107. package/sdk/src/phase-runner.integration.test.ts +377 -0
  1108. package/sdk/src/phase-runner.test.ts +2720 -0
  1109. package/sdk/src/phase-runner.ts +1442 -0
  1110. package/sdk/src/plan-parser.test.ts +579 -0
  1111. package/sdk/src/plan-parser.ts +431 -0
  1112. package/sdk/src/planning-journal.test.ts +70 -0
  1113. package/sdk/src/planning-journal.ts +153 -0
  1114. package/sdk/src/planning-runtime.test.ts +29 -0
  1115. package/sdk/src/planning-runtime.ts +100 -0
  1116. package/sdk/src/prompt-builder.test.ts +318 -0
  1117. package/sdk/src/prompt-builder.ts +218 -0
  1118. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  1119. package/sdk/src/prompt-sanitizer.ts +116 -0
  1120. package/sdk/src/query/QUERY-HANDLERS.md +349 -0
  1121. package/sdk/src/query/active-workstream-store.ts +50 -0
  1122. package/sdk/src/query/agent-failure-classifier.test.ts +157 -0
  1123. package/sdk/src/query/agent-failure-classifier.ts +105 -0
  1124. package/sdk/src/query/audit-open.ts +722 -0
  1125. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  1126. package/sdk/src/query/check-auto-mode.ts +49 -0
  1127. package/sdk/src/query/check-completion.test.ts +113 -0
  1128. package/sdk/src/query/check-completion.ts +182 -0
  1129. package/sdk/src/query/check-decision-coverage.test.ts +519 -0
  1130. package/sdk/src/query/check-decision-coverage.ts +554 -0
  1131. package/sdk/src/query/check-gates.test.ts +103 -0
  1132. package/sdk/src/query/check-gates.ts +112 -0
  1133. package/sdk/src/query/check-ship-ready.test.ts +111 -0
  1134. package/sdk/src/query/check-ship-ready.ts +104 -0
  1135. package/sdk/src/query/check-verification-status.test.ts +143 -0
  1136. package/sdk/src/query/check-verification-status.ts +160 -0
  1137. package/sdk/src/query/command-aliases.generated.ts +156 -0
  1138. package/sdk/src/query/command-catalog.ts +31 -0
  1139. package/sdk/src/query/command-definition.test.ts +47 -0
  1140. package/sdk/src/query/command-definition.ts +70 -0
  1141. package/sdk/src/query/command-family-handlers.ts +117 -0
  1142. package/sdk/src/query/command-manifest.init.ts +24 -0
  1143. package/sdk/src/query/command-manifest.non-family.ts +85 -0
  1144. package/sdk/src/query/command-manifest.phase.ts +16 -0
  1145. package/sdk/src/query/command-manifest.phases.ts +11 -0
  1146. package/sdk/src/query/command-manifest.roadmap.ts +11 -0
  1147. package/sdk/src/query/command-manifest.state.ts +31 -0
  1148. package/sdk/src/query/command-manifest.ts +17 -0
  1149. package/sdk/src/query/command-manifest.types.ts +13 -0
  1150. package/sdk/src/query/command-manifest.validate.ts +11 -0
  1151. package/sdk/src/query/command-manifest.verify.ts +15 -0
  1152. package/sdk/src/query/command-resolution.test.ts +70 -0
  1153. package/sdk/src/query/command-seam-coverage.test.ts +118 -0
  1154. package/sdk/src/query/command-static-catalog-domain.ts +117 -0
  1155. package/sdk/src/query/command-static-catalog-foundation.ts +103 -0
  1156. package/sdk/src/query/command-topology.test.ts +28 -0
  1157. package/sdk/src/query/command-topology.ts +114 -0
  1158. package/sdk/src/query/commands-list.test.ts +36 -0
  1159. package/sdk/src/query/commands-list.ts +19 -0
  1160. package/sdk/src/query/commit.test.ts +485 -0
  1161. package/sdk/src/query/commit.ts +383 -0
  1162. package/sdk/src/query/config-gates.test.ts +89 -0
  1163. package/sdk/src/query/config-gates.ts +69 -0
  1164. package/sdk/src/query/config-mutation.test.ts +598 -0
  1165. package/sdk/src/query/config-mutation.ts +575 -0
  1166. package/sdk/src/query/config-query.test.ts +367 -0
  1167. package/sdk/src/query/config-query.ts +244 -0
  1168. package/sdk/src/query/config-schema.ts +159 -0
  1169. package/sdk/src/query/decisions.test.ts +215 -0
  1170. package/sdk/src/query/decisions.ts +192 -0
  1171. package/sdk/src/query/decomposed-handlers.test.ts +431 -0
  1172. package/sdk/src/query/detect-custom-files.test.ts +115 -0
  1173. package/sdk/src/query/detect-custom-files.ts +96 -0
  1174. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  1175. package/sdk/src/query/detect-phase-type.ts +141 -0
  1176. package/sdk/src/query/docs-init.ts +258 -0
  1177. package/sdk/src/query/fallow-audit.ts +88 -0
  1178. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  1179. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  1180. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  1181. package/sdk/src/query/frontmatter.test.ts +326 -0
  1182. package/sdk/src/query/frontmatter.ts +395 -0
  1183. package/sdk/src/query/helpers.test.ts +615 -0
  1184. package/sdk/src/query/helpers.ts +646 -0
  1185. package/sdk/src/query/index-thin-seam.test.ts +16 -0
  1186. package/sdk/src/query/index.ts +9 -0
  1187. package/sdk/src/query/init-complex.test.ts +616 -0
  1188. package/sdk/src/query/init-complex.ts +799 -0
  1189. package/sdk/src/query/init-progress-precedence.test.ts +177 -0
  1190. package/sdk/src/query/init-workstream-milestone-op.test.ts +321 -0
  1191. package/sdk/src/query/init.test.ts +792 -0
  1192. package/sdk/src/query/init.ts +1262 -0
  1193. package/sdk/src/query/intel.test.ts +90 -0
  1194. package/sdk/src/query/intel.ts +404 -0
  1195. package/sdk/src/query/mutation-event-decorator.test.ts +45 -0
  1196. package/sdk/src/query/mutation-event-decorator.ts +37 -0
  1197. package/sdk/src/query/mutation-event-mapper.test.ts +33 -0
  1198. package/sdk/src/query/mutation-event-mapper.ts +102 -0
  1199. package/sdk/src/query/mvp.test.ts +335 -0
  1200. package/sdk/src/query/mvp.ts +292 -0
  1201. package/sdk/src/query/normalize-query-command.test.ts +102 -0
  1202. package/sdk/src/query/phase-filesystem-adapter.ts +35 -0
  1203. package/sdk/src/query/phase-lifecycle-policy.ts +171 -0
  1204. package/sdk/src/query/phase-lifecycle.test.ts +1750 -0
  1205. package/sdk/src/query/phase-lifecycle.ts +1833 -0
  1206. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  1207. package/sdk/src/query/phase-list-queries.ts +152 -0
  1208. package/sdk/src/query/phase-ready.test.ts +65 -0
  1209. package/sdk/src/query/phase-ready.ts +159 -0
  1210. package/sdk/src/query/phase-roadmap-mutation.ts +77 -0
  1211. package/sdk/src/query/phase.test.ts +651 -0
  1212. package/sdk/src/query/phase.ts +550 -0
  1213. package/sdk/src/query/pipeline.test.ts +169 -0
  1214. package/sdk/src/query/pipeline.ts +243 -0
  1215. package/sdk/src/query/plan-scan.test.ts +35 -0
  1216. package/sdk/src/query/plan-scan.ts +82 -0
  1217. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  1218. package/sdk/src/query/plan-task-structure.ts +63 -0
  1219. package/sdk/src/query/policy-convergence.test.ts +28 -0
  1220. package/sdk/src/query/profile-extract-messages.ts +247 -0
  1221. package/sdk/src/query/profile-output.ts +929 -0
  1222. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  1223. package/sdk/src/query/profile-sample.ts +184 -0
  1224. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  1225. package/sdk/src/query/profile.test.ts +136 -0
  1226. package/sdk/src/query/profile.ts +337 -0
  1227. package/sdk/src/query/progress.test.ts +156 -0
  1228. package/sdk/src/query/progress.ts +566 -0
  1229. package/sdk/src/query/query-cli-adapter.test.ts +79 -0
  1230. package/sdk/src/query/query-cli-adapter.ts +39 -0
  1231. package/sdk/src/query/query-cli-output.test.ts +33 -0
  1232. package/sdk/src/query/query-cli-output.ts +35 -0
  1233. package/sdk/src/query/query-command-diagnosis.test.ts +22 -0
  1234. package/sdk/src/query/query-command-diagnosis.ts +5 -0
  1235. package/sdk/src/query/query-command-resolution-strategy.test.ts +34 -0
  1236. package/sdk/src/query/query-command-resolution-strategy.ts +121 -0
  1237. package/sdk/src/query/query-command-semantics.test.ts +22 -0
  1238. package/sdk/src/query/query-command-semantics.ts +22 -0
  1239. package/sdk/src/query/query-dispatch-contract.ts +30 -0
  1240. package/sdk/src/query/query-dispatch-error-mapper.test.ts +62 -0
  1241. package/sdk/src/query/query-dispatch-error-mapper.ts +5 -0
  1242. package/sdk/src/query/query-dispatch-formatting.test.ts +28 -0
  1243. package/sdk/src/query/query-dispatch-formatting.ts +5 -0
  1244. package/sdk/src/query/query-dispatch-input-validation.test.ts +23 -0
  1245. package/sdk/src/query/query-dispatch-input-validation.ts +5 -0
  1246. package/sdk/src/query/query-dispatch-observability.test.ts +10 -0
  1247. package/sdk/src/query/query-dispatch-observability.ts +6 -0
  1248. package/sdk/src/query/query-dispatch-plan.test.ts +25 -0
  1249. package/sdk/src/query/query-dispatch-plan.ts +5 -0
  1250. package/sdk/src/query/query-dispatch-result-builder.test.ts +16 -0
  1251. package/sdk/src/query/query-dispatch-result-builder.ts +5 -0
  1252. package/sdk/src/query/query-dispatch.test.ts +399 -0
  1253. package/sdk/src/query/query-dispatch.ts +243 -0
  1254. package/sdk/src/query/query-error-details-schema.ts +29 -0
  1255. package/sdk/src/query/query-error-taxonomy.test.ts +39 -0
  1256. package/sdk/src/query/query-error-taxonomy.ts +117 -0
  1257. package/sdk/src/query/query-fallback-bridge-adapter.test.ts +32 -0
  1258. package/sdk/src/query/query-fallback-bridge-adapter.ts +54 -0
  1259. package/sdk/src/query/query-fallback-executor.test.ts +82 -0
  1260. package/sdk/src/query/query-fallback-executor.ts +44 -0
  1261. package/sdk/src/query/query-fallback-output-classifier.test.ts +36 -0
  1262. package/sdk/src/query/query-fallback-output-classifier.ts +31 -0
  1263. package/sdk/src/query/query-fallback-policy.test.ts +13 -0
  1264. package/sdk/src/query/query-fallback-policy.ts +11 -0
  1265. package/sdk/src/query/query-native-dispatch-adapter.ts +16 -0
  1266. package/sdk/src/query/query-policy-capability.test.ts +10 -0
  1267. package/sdk/src/query/query-policy-capability.ts +26 -0
  1268. package/sdk/src/query/query-policy-snapshot.test.ts +9 -0
  1269. package/sdk/src/query/query-registry-capability.test.ts +14 -0
  1270. package/sdk/src/query/query-runtime-context.ts +44 -0
  1271. package/sdk/src/query/query-unknown-command-hints.test.ts +9 -0
  1272. package/sdk/src/query/query-unknown-command-hints.ts +5 -0
  1273. package/sdk/src/query/registry-assembly-descriptor.ts +87 -0
  1274. package/sdk/src/query/registry-assembly-invariants.ts +127 -0
  1275. package/sdk/src/query/registry-assembly.test.ts +138 -0
  1276. package/sdk/src/query/registry-assembly.ts +78 -0
  1277. package/sdk/src/query/registry.test.ts +208 -0
  1278. package/sdk/src/query/registry.ts +142 -0
  1279. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  1280. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  1281. package/sdk/src/query/roadmap-update-plan-progress.test.ts +233 -0
  1282. package/sdk/src/query/roadmap-update-plan-progress.ts +159 -0
  1283. package/sdk/src/query/roadmap.test.ts +1181 -0
  1284. package/sdk/src/query/roadmap.ts +894 -0
  1285. package/sdk/src/query/route-next-action.test.ts +61 -0
  1286. package/sdk/src/query/route-next-action.ts +345 -0
  1287. package/sdk/src/query/schema-detect.ts +189 -0
  1288. package/sdk/src/query/secrets.test.ts +66 -0
  1289. package/sdk/src/query/secrets.ts +43 -0
  1290. package/sdk/src/query/skill-manifest.test.ts +62 -0
  1291. package/sdk/src/query/skill-manifest.ts +216 -0
  1292. package/sdk/src/query/skills.test.ts +234 -0
  1293. package/sdk/src/query/skills.ts +143 -0
  1294. package/sdk/src/query/state-document.test.ts +197 -0
  1295. package/sdk/src/query/state-document.ts +129 -0
  1296. package/sdk/src/query/state-mutation.test.ts +1198 -0
  1297. package/sdk/src/query/state-mutation.ts +1718 -0
  1298. package/sdk/src/query/state-project-load.ts +80 -0
  1299. package/sdk/src/query/state.test.ts +616 -0
  1300. package/sdk/src/query/state.ts +463 -0
  1301. package/sdk/src/query/sub-repos-root.integration.test.ts +79 -0
  1302. package/sdk/src/query/summary.test.ts +95 -0
  1303. package/sdk/src/query/summary.ts +296 -0
  1304. package/sdk/src/query/template.test.ts +180 -0
  1305. package/sdk/src/query/template.ts +242 -0
  1306. package/sdk/src/query/uat.test.ts +77 -0
  1307. package/sdk/src/query/uat.ts +365 -0
  1308. package/sdk/src/query/utils.test.ts +82 -0
  1309. package/sdk/src/query/utils.ts +106 -0
  1310. package/sdk/src/query/validate.test.ts +831 -0
  1311. package/sdk/src/query/validate.ts +952 -0
  1312. package/sdk/src/query/verify.test.ts +414 -0
  1313. package/sdk/src/query/verify.ts +692 -0
  1314. package/sdk/src/query/websearch.test.ts +31 -0
  1315. package/sdk/src/query/websearch.ts +82 -0
  1316. package/sdk/src/query/workspace.test.ts +120 -0
  1317. package/sdk/src/query/workspace.ts +145 -0
  1318. package/sdk/src/query/workstream-inventory.ts +195 -0
  1319. package/sdk/src/query/workstream.test.ts +153 -0
  1320. package/sdk/src/query/workstream.ts +324 -0
  1321. package/sdk/src/query/worktree.ts +39 -0
  1322. package/sdk/src/query-command-executor.ts +31 -0
  1323. package/sdk/src/query-execution-policy.test.ts +52 -0
  1324. package/sdk/src/query-execution-policy.ts +46 -0
  1325. package/sdk/src/query-failure-classification.test.ts +23 -0
  1326. package/sdk/src/query-failure-classification.ts +42 -0
  1327. package/sdk/src/query-hotpath-methods.ts +48 -0
  1328. package/sdk/src/query-native-direct-adapter.test.ts +35 -0
  1329. package/sdk/src/query-native-direct-adapter.ts +70 -0
  1330. package/sdk/src/query-native-hotpath-adapter.test.ts +43 -0
  1331. package/sdk/src/query-native-hotpath-adapter.ts +45 -0
  1332. package/sdk/src/query-raw-output-projection.test.ts +39 -0
  1333. package/sdk/src/query-raw-output-projection.ts +74 -0
  1334. package/sdk/src/query-runtime-bridge.test.ts +150 -0
  1335. package/sdk/src/query-runtime-bridge.ts +215 -0
  1336. package/sdk/src/query-runtime-seam-coverage.test.ts +20 -0
  1337. package/sdk/src/query-sdd-tools-path.ts +1 -0
  1338. package/sdk/src/query-sdd-tools-runtime.ts +89 -0
  1339. package/sdk/src/query-subprocess-adapter.test.ts +84 -0
  1340. package/sdk/src/query-subprocess-adapter.ts +146 -0
  1341. package/sdk/src/query-tools-error-factory.test.ts +35 -0
  1342. package/sdk/src/query-tools-error-factory.ts +76 -0
  1343. package/sdk/src/research-gate.test.ts +190 -0
  1344. package/sdk/src/research-gate.ts +94 -0
  1345. package/sdk/src/runtime-bridge-options.test.ts +33 -0
  1346. package/sdk/src/runtime-gate.test.ts +84 -0
  1347. package/sdk/src/runtime-gate.ts +52 -0
  1348. package/sdk/src/sdd-tools-error.test.ts +21 -0
  1349. package/sdk/src/sdd-tools-error.ts +65 -0
  1350. package/sdk/src/sdd-tools.test.ts +472 -0
  1351. package/sdk/src/sdd-tools.ts +237 -0
  1352. package/sdk/src/sdd-transport-policy.test.ts +34 -0
  1353. package/sdk/src/sdd-transport-policy.ts +48 -0
  1354. package/sdk/src/sdd-transport.test.ts +292 -0
  1355. package/sdk/src/sdd-transport.ts +117 -0
  1356. package/sdk/src/sdk-package-compatibility.test.ts +97 -0
  1357. package/sdk/src/sdk-package-compatibility.ts +141 -0
  1358. package/sdk/src/session-runner.test.ts +164 -0
  1359. package/sdk/src/session-runner.ts +327 -0
  1360. package/sdk/src/tool-scoping.test.ts +160 -0
  1361. package/sdk/src/tool-scoping.ts +61 -0
  1362. package/sdk/src/types.ts +927 -0
  1363. package/sdk/src/workflow-agent-skills-consistency.test.ts +98 -0
  1364. package/sdk/src/workstream-name-policy.ts +24 -0
  1365. package/sdk/src/workstream-utils.ts +21 -0
  1366. package/sdk/src/ws-flag.test.ts +285 -0
  1367. package/sdk/src/ws-transport.test.ts +161 -0
  1368. package/sdk/src/ws-transport.ts +93 -0
  1369. package/sdk/tsconfig.json +20 -0
  1370. package/commands/sdd/add-backlog.md +0 -76
  1371. package/commands/sdd/add-phase.md +0 -43
  1372. package/commands/sdd/add-todo.md +0 -47
  1373. package/commands/sdd/check-todos.md +0 -45
  1374. package/commands/sdd/do.md +0 -30
  1375. package/commands/sdd/insert-phase.md +0 -32
  1376. package/commands/sdd/join-discord.md +0 -18
  1377. package/commands/sdd/list-phase-assumptions.md +0 -46
  1378. package/commands/sdd/list-workspaces.md +0 -19
  1379. package/commands/sdd/new-workspace.md +0 -44
  1380. package/commands/sdd/next.md +0 -24
  1381. package/commands/sdd/note.md +0 -34
  1382. package/commands/sdd/plan-milestone-gaps.md +0 -34
  1383. package/commands/sdd/plant-seed.md +0 -28
  1384. package/commands/sdd/reapply-patches.md +0 -123
  1385. package/commands/sdd/remove-phase.md +0 -31
  1386. package/commands/sdd/remove-workspace.md +0 -26
  1387. package/commands/sdd/research-phase.md +0 -195
  1388. package/commands/sdd/session-report.md +0 -19
  1389. package/commands/sdd/set-profile.md +0 -12
  1390. package/scripts/sync-upstream.sh +0 -56
  1391. package/sdd/commands/sdd/workstreams.md +0 -63
  1392. package/sdd/workflows/research-phase.md +0 -82
@@ -4,34 +4,45 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, normalizeMd, planningDir, planningPaths, output, error } = require('./core.cjs');
7
+ const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, output, error } = require('./core.cjs');
8
+ const { platformWriteSync, platformReadSync, platformEnsureDir } = require('./shell-command-projection.cjs');
9
+ const { planningDir, planningPaths } = require('./planning-workspace.cjs');
8
10
  const { extractFrontmatter, reconstructFrontmatter } = require('./frontmatter.cjs');
11
+ const scanPhasePlans = require('./plan-scan.cjs');
12
+ const {
13
+ computeProgressPercent,
14
+ normalizeProgressNumbers,
15
+ normalizeStateStatus,
16
+ shouldPreserveExistingProgress,
17
+ stateExtractField,
18
+ stateReplaceField,
19
+ } = require('./state-document.cjs');
20
+
21
+ // Cache disk scan results from buildStateFrontmatter per cwd per process (#1967).
22
+ // Avoids re-reading N+1 directories on every state write when the phase structure
23
+ // hasn't changed within the same sdd-tools invocation.
24
+ const _diskScanCache = new Map();
9
25
 
10
26
  /** Shorthand — every state command needs this path */
11
27
  function getStatePath(cwd) {
12
28
  return planningPaths(cwd).state;
13
29
  }
14
30
 
15
- // Shared helper: extract a field value from STATE.md content.
16
- // Supports both **Field:** bold and plain Field: format.
17
- function stateExtractField(content, fieldName) {
18
- const escaped = escapeRegex(fieldName);
19
- const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*\\s*(.+)`, 'i');
20
- const boldMatch = content.match(boldPattern);
21
- if (boldMatch) return boldMatch[1].trim();
22
- const plainPattern = new RegExp(`^${escaped}:\\s*(.+)`, 'im');
23
- const plainMatch = content.match(plainPattern);
24
- return plainMatch ? plainMatch[1].trim() : null;
25
- }
31
+ // Track all lock files held by this process so they can be removed on exit.
32
+ // process.on('exit') fires even on process.exit(1), unlike try/finally which is
33
+ // skipped when error() calls process.exit(1) inside a locked region (#1916).
34
+ const _heldStateLocks = new Set();
35
+ process.on('exit', () => {
36
+ for (const lockPath of _heldStateLocks) {
37
+ try { require('fs').unlinkSync(lockPath); } catch { /* already gone */ }
38
+ }
39
+ });
26
40
 
27
41
  function cmdStateLoad(cwd, raw) {
28
42
  const config = loadConfig(cwd);
29
43
  const planDir = planningPaths(cwd).planning;
30
44
 
31
- let stateRaw = '';
32
- try {
33
- stateRaw = fs.readFileSync(path.join(planDir, 'STATE.md'), 'utf-8');
34
- } catch { /* intentionally empty */ }
45
+ const stateRaw = platformReadSync(path.join(planDir, 'STATE.md')) || '';
35
46
 
36
47
  const configExists = fs.existsSync(path.join(planDir, 'config.json'));
37
48
  const roadmapExists = fs.existsSync(path.join(planDir, 'ROADMAP.md'));
@@ -71,8 +82,12 @@ function cmdStateLoad(cwd, raw) {
71
82
 
72
83
  function cmdStateGet(cwd, section, raw) {
73
84
  const statePath = planningPaths(cwd).state;
74
- try {
75
- const content = fs.readFileSync(statePath, 'utf-8');
85
+ const content = platformReadSync(statePath);
86
+ if (content === null) {
87
+ error('STATE.md not found');
88
+ return;
89
+ }
90
+ {
76
91
 
77
92
  if (!section) {
78
93
  output({ content }, raw, content);
@@ -107,8 +122,6 @@ function cmdStateGet(cwd, section, raw) {
107
122
  }
108
123
 
109
124
  output({ error: `Section or field "${section}" not found` }, raw, '');
110
- } catch {
111
- error('STATE.md not found');
112
125
  }
113
126
  }
114
127
 
@@ -141,29 +154,21 @@ function cmdStatePatch(cwd, patches, raw) {
141
154
 
142
155
  const statePath = planningPaths(cwd).state;
143
156
  try {
144
- let content = fs.readFileSync(statePath, 'utf-8');
145
157
  const results = { updated: [], failed: [] };
146
158
 
147
- for (const [field, value] of Object.entries(patches)) {
148
- const fieldEscaped = escapeRegex(field);
149
- // Try **Field:** bold format first, then plain Field: format
150
- const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
151
- const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
152
-
153
- if (boldPattern.test(content)) {
154
- content = content.replace(boldPattern, (_match, prefix) => `${prefix}${value}`);
155
- results.updated.push(field);
156
- } else if (plainPattern.test(content)) {
157
- content = content.replace(plainPattern, (_match, prefix) => `${prefix}${value}`);
158
- results.updated.push(field);
159
- } else {
160
- results.failed.push(field);
159
+ // Use atomic read-modify-write to prevent lost updates from concurrent agents
160
+ readModifyWriteStateMd(statePath, (content) => {
161
+ for (const [field, value] of Object.entries(patches)) {
162
+ const result = stateReplaceField(content, field, value);
163
+ if (result) {
164
+ content = result;
165
+ results.updated.push(field);
166
+ } else {
167
+ results.failed.push(field);
168
+ }
161
169
  }
162
- }
163
-
164
- if (results.updated.length > 0) {
165
- writeStateMd(statePath, content, cwd);
166
- }
170
+ return content;
171
+ }, cwd);
167
172
 
168
173
  output(results, raw, results.updated.length > 0 ? 'true' : 'false');
169
174
  } catch {
@@ -185,18 +190,24 @@ function cmdStateUpdate(cwd, field, value) {
185
190
 
186
191
  const statePath = planningPaths(cwd).state;
187
192
  try {
188
- let content = fs.readFileSync(statePath, 'utf-8');
189
- const fieldEscaped = escapeRegex(field);
190
- // Try **Field:** bold format first, then plain Field: format
191
- const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
192
- const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
193
- if (boldPattern.test(content)) {
194
- content = content.replace(boldPattern, (_match, prefix) => `${prefix}${value}`);
195
- writeStateMd(statePath, content, cwd);
196
- output({ updated: true });
197
- } else if (plainPattern.test(content)) {
198
- content = content.replace(plainPattern, (_match, prefix) => `${prefix}${value}`);
199
- writeStateMd(statePath, content, cwd);
193
+ let updated = false;
194
+ const shouldResync = ['Progress', 'Total Plans in Phase', 'Total Phases'].includes(field);
195
+ // Preserve curated progress for body-only updates, but allow fields that
196
+ // directly project into progress.* frontmatter to rebuild after mutation.
197
+ readModifyWriteStateMd(statePath, (content) => {
198
+ const body = stripFrontmatter(content);
199
+ const result = stateReplaceField(body, field, value);
200
+ if (result) {
201
+ updated = true;
202
+ const existingFm = extractFrontmatter(content);
203
+ if (Object.keys(existingFm).length > 0) {
204
+ return `---\n${reconstructFrontmatter(existingFm)}\n---\n\n${result}`;
205
+ }
206
+ return result;
207
+ }
208
+ return content;
209
+ }, cwd, { resync: shouldResync });
210
+ if (updated) {
200
211
  output({ updated: true });
201
212
  } else {
202
213
  output({ updated: false, reason: `Field "${field}" not found in STATE.md` });
@@ -207,21 +218,6 @@ function cmdStateUpdate(cwd, field, value) {
207
218
  }
208
219
 
209
220
  // ─── State Progression Engine ────────────────────────────────────────────────
210
- // stateExtractField is defined above (shared helper) — do not duplicate.
211
-
212
- function stateReplaceField(content, fieldName, newValue) {
213
- const escaped = escapeRegex(fieldName);
214
- // Try **Field:** bold format first, then plain Field: format
215
- const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
216
- if (boldPattern.test(content)) {
217
- return content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
218
- }
219
- const plainPattern = new RegExp(`(^${escaped}:\\s*)(.*)`, 'im');
220
- if (plainPattern.test(content)) {
221
- return content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
222
- }
223
- return null;
224
- }
225
221
 
226
222
  /**
227
223
  * Replace a STATE.md field with fallback field name support.
@@ -236,6 +232,12 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
236
232
  result = stateReplaceField(content, fallback, value);
237
233
  if (result) return result;
238
234
  }
235
+ // Neither pattern matched — field may have been reformatted or removed.
236
+ // Log diagnostic so template drift is detected early rather than silently swallowed.
237
+ process.stderr.write(
238
+ `[sdd-tools] WARNING: STATE.md field "${primary}"${fallback ? ` (fallback: "${fallback}")` : ''} not found — update skipped. ` +
239
+ `This may indicate STATE.md was externally modified or uses an unexpected format.\n`
240
+ );
239
241
  return content;
240
242
  }
241
243
 
@@ -262,62 +264,74 @@ function updateCurrentPositionFields(content, fields) {
262
264
  posBody = posBody.replace(/^Plan:.*$/m, `Plan: ${fields.plan}`);
263
265
  }
264
266
 
265
- return content.replace(posPattern, `${posMatch[1]}${posBody}`);
267
+ return content.replace(posPattern, () => `${posMatch[1]}${posBody}`);
266
268
  }
267
269
 
268
270
  function cmdStateAdvancePlan(cwd, raw) {
269
271
  const statePath = planningPaths(cwd).state;
270
272
  if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; }
271
273
 
272
- let content = fs.readFileSync(statePath, 'utf-8');
273
274
  const today = new Date().toISOString().split('T')[0];
275
+ let result = null;
276
+
277
+ readModifyWriteStateMd(statePath, (content) => {
278
+ // Try legacy separate fields first, then compound "Plan: X of Y" format
279
+ const legacyPlan = stateExtractField(content, 'Current Plan');
280
+ const legacyTotal = stateExtractField(content, 'Total Plans in Phase');
281
+ const planField = stateExtractField(content, 'Plan');
282
+
283
+ let currentPlan, totalPlans;
284
+ let useCompoundFormat = false;
285
+
286
+ if (legacyPlan && legacyTotal) {
287
+ currentPlan = parseInt(legacyPlan, 10);
288
+ totalPlans = parseInt(legacyTotal, 10);
289
+ } else if (planField) {
290
+ // Compound format: "2 of 6 in current phase" or "2 of 6"
291
+ currentPlan = parseInt(planField, 10);
292
+ const ofMatch = planField.match(/of\s+(\d+)/);
293
+ totalPlans = ofMatch ? parseInt(ofMatch[1], 10) : NaN;
294
+ useCompoundFormat = true;
295
+ }
274
296
 
275
- // Try legacy separate fields first, then compound "Plan: X of Y" format
276
- const legacyPlan = stateExtractField(content, 'Current Plan');
277
- const legacyTotal = stateExtractField(content, 'Total Plans in Phase');
278
- const planField = stateExtractField(content, 'Plan');
279
-
280
- let currentPlan, totalPlans;
281
- let useCompoundFormat = false;
297
+ if (isNaN(currentPlan) || isNaN(totalPlans)) {
298
+ result = { error: true };
299
+ return content;
300
+ }
282
301
 
283
- if (legacyPlan && legacyTotal) {
284
- currentPlan = parseInt(legacyPlan, 10);
285
- totalPlans = parseInt(legacyTotal, 10);
286
- } else if (planField) {
287
- // Compound format: "2 of 6 in current phase" or "2 of 6"
288
- currentPlan = parseInt(planField, 10);
289
- const ofMatch = planField.match(/of\s+(\d+)/);
290
- totalPlans = ofMatch ? parseInt(ofMatch[1], 10) : NaN;
291
- useCompoundFormat = true;
292
- }
302
+ if (currentPlan >= totalPlans) {
303
+ content = stateReplaceFieldWithFallback(content, 'Status', null, 'Phase complete — ready for verification');
304
+ content = stateReplaceFieldWithFallback(content, 'Last Activity', 'Last activity', today);
305
+ content = updateCurrentPositionFields(content, { status: 'Phase complete — ready for verification', lastActivity: today });
306
+ result = { advanced: false, reason: 'last_plan', current_plan: currentPlan, total_plans: totalPlans, status: 'ready_for_verification' };
307
+ } else {
308
+ const newPlan = currentPlan + 1;
309
+ let planDisplayValue;
310
+ if (useCompoundFormat) {
311
+ // Preserve compound format: "X of Y in current phase" → replace X only
312
+ planDisplayValue = planField.replace(/^\d+/, String(newPlan));
313
+ content = stateReplaceField(content, 'Plan', planDisplayValue) || content;
314
+ } else {
315
+ planDisplayValue = `${newPlan} of ${totalPlans}`;
316
+ content = stateReplaceField(content, 'Current Plan', String(newPlan)) || content;
317
+ }
318
+ content = stateReplaceFieldWithFallback(content, 'Status', null, 'Ready to execute');
319
+ content = stateReplaceFieldWithFallback(content, 'Last Activity', 'Last activity', today);
320
+ content = updateCurrentPositionFields(content, { status: 'Ready to execute', lastActivity: today, plan: planDisplayValue });
321
+ result = { advanced: true, previous_plan: currentPlan, current_plan: newPlan, total_plans: totalPlans };
322
+ }
323
+ return content;
324
+ }, cwd);
293
325
 
294
- if (isNaN(currentPlan) || isNaN(totalPlans)) {
326
+ if (!result || result.error) {
295
327
  output({ error: 'Cannot parse Current Plan or Total Plans in Phase from STATE.md' }, raw);
296
328
  return;
297
329
  }
298
330
 
299
- if (currentPlan >= totalPlans) {
300
- content = stateReplaceFieldWithFallback(content, 'Status', null, 'Phase complete — ready for verification');
301
- content = stateReplaceFieldWithFallback(content, 'Last Activity', 'Last activity', today);
302
- content = updateCurrentPositionFields(content, { status: 'Phase complete — ready for verification', lastActivity: today });
303
- writeStateMd(statePath, content, cwd);
304
- output({ advanced: false, reason: 'last_plan', current_plan: currentPlan, total_plans: totalPlans, status: 'ready_for_verification' }, raw, 'false');
331
+ if (result.advanced === false) {
332
+ output(result, raw, 'false');
305
333
  } else {
306
- const newPlan = currentPlan + 1;
307
- let planDisplayValue;
308
- if (useCompoundFormat) {
309
- // Preserve compound format: "X of Y in current phase" → replace X only
310
- planDisplayValue = planField.replace(/^\d+/, String(newPlan));
311
- content = stateReplaceField(content, 'Plan', planDisplayValue) || content;
312
- } else {
313
- planDisplayValue = `${newPlan} of ${totalPlans}`;
314
- content = stateReplaceField(content, 'Current Plan', String(newPlan)) || content;
315
- }
316
- content = stateReplaceFieldWithFallback(content, 'Status', null, 'Ready to execute');
317
- content = stateReplaceFieldWithFallback(content, 'Last Activity', 'Last activity', today);
318
- content = updateCurrentPositionFields(content, { status: 'Ready to execute', lastActivity: today, plan: planDisplayValue });
319
- writeStateMd(statePath, content, cwd);
320
- output({ advanced: true, previous_plan: currentPlan, current_plan: newPlan, total_plans: totalPlans }, raw, 'true');
334
+ output(result, raw, 'true');
321
335
  }
322
336
  }
323
337
 
@@ -325,7 +339,6 @@ function cmdStateRecordMetric(cwd, options, raw) {
325
339
  const statePath = planningPaths(cwd).state;
326
340
  if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; }
327
341
 
328
- let content = fs.readFileSync(statePath, 'utf-8');
329
342
  const { phase, plan, duration, tasks, files } = options;
330
343
 
331
344
  if (!phase || !plan || !duration) {
@@ -333,35 +346,55 @@ function cmdStateRecordMetric(cwd, options, raw) {
333
346
  return;
334
347
  }
335
348
 
336
- // Find Performance Metrics section and its table
337
- const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n)([\s\S]*?)(?=\n##|\n$|$)/i;
338
- const metricsMatch = content.match(metricsPattern);
349
+ let recorded = false;
350
+ let created = false;
351
+ readModifyWriteStateMd(statePath, (content) => {
352
+ // Find Performance Metrics section and its table
353
+ const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n)([\s\S]*?)(?=\n##|\n$|$)/i;
354
+ const metricsMatch = content.match(metricsPattern);
339
355
 
340
- if (metricsMatch) {
341
- let tableBody = metricsMatch[2].trimEnd();
342
356
  const newRow = `| Phase ${phase} P${plan} | ${duration} | ${tasks || '-'} tasks | ${files || '-'} files |`;
343
357
 
344
- if (tableBody.trim() === '' || tableBody.includes('None yet')) {
345
- tableBody = newRow;
346
- } else {
347
- tableBody = tableBody + '\n' + newRow;
358
+ if (metricsMatch) {
359
+ let tableBody = metricsMatch[2].trimEnd();
360
+
361
+ if (tableBody.trim() === '' || tableBody.includes('None yet')) {
362
+ tableBody = newRow;
363
+ } else {
364
+ tableBody = tableBody + '\n' + newRow;
365
+ }
366
+
367
+ recorded = true;
368
+ return content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
348
369
  }
349
370
 
350
- content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
351
- writeStateMd(statePath, content, cwd);
352
- output({ recorded: true, phase, plan, duration }, raw, 'true');
353
- } else {
354
- output({ recorded: false, reason: 'Performance Metrics section not found in STATE.md' }, raw, 'false');
355
- }
371
+ // Section absent DWIM: auto-create canonical ## Performance Metrics scaffold,
372
+ // then append the row. Matches state begin-phase / advance-plan DWIM behavior.
373
+ const scaffold = [
374
+ '',
375
+ '## Performance Metrics',
376
+ '',
377
+ '| Phase | Plan | Duration | Notes |',
378
+ '|-------|------|----------|-------|',
379
+ newRow,
380
+ '',
381
+ ].join('\n');
382
+ recorded = true;
383
+ created = true;
384
+ return content.trimEnd() + '\n' + scaffold;
385
+ }, cwd);
386
+
387
+ // Auto-create fallback guarantees recorded === true; no else branch needed.
388
+ const result = { recorded: true, phase, plan, duration };
389
+ if (created) result.created = true;
390
+ output(result, raw, 'true');
356
391
  }
357
392
 
358
393
  function cmdStateUpdateProgress(cwd, raw) {
359
394
  const statePath = planningPaths(cwd).state;
360
395
  if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; }
361
396
 
362
- let content = fs.readFileSync(statePath, 'utf-8');
363
-
364
- // Count summaries across current milestone phases only
397
+ // Count summaries across current milestone phases only (outside lock — read-only)
365
398
  const phasesDir = planningPaths(cwd).phases;
366
399
  let totalPlans = 0;
367
400
  let totalSummaries = 0;
@@ -372,9 +405,9 @@ function cmdStateUpdateProgress(cwd, raw) {
372
405
  .filter(e => e.isDirectory()).map(e => e.name)
373
406
  .filter(isDirInMilestone);
374
407
  for (const dir of phaseDirs) {
375
- const files = fs.readdirSync(path.join(phasesDir, dir));
376
- totalPlans += files.filter(f => f.match(/-PLAN\.md$/i)).length;
377
- totalSummaries += files.filter(f => f.match(/-SUMMARY\.md$/i)).length;
408
+ const { planCount, summaryCount } = scanPhasePlans(path.join(phasesDir, dir));
409
+ totalPlans += planCount;
410
+ totalSummaries += summaryCount;
378
411
  }
379
412
  }
380
413
 
@@ -384,17 +417,26 @@ function cmdStateUpdateProgress(cwd, raw) {
384
417
  const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
385
418
  const progressStr = `[${bar}] ${percent}%`;
386
419
 
387
- // Try **Progress:** bold format first, then plain Progress: format
388
- const boldProgressPattern = /(\*\*Progress:\*\*\s*).*/i;
389
- const plainProgressPattern = /^(Progress:\s*).*/im;
390
- if (boldProgressPattern.test(content)) {
391
- content = content.replace(boldProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
392
- writeStateMd(statePath, content, cwd);
393
- output({ updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr }, raw, progressStr);
394
- } else if (plainProgressPattern.test(content)) {
395
- content = content.replace(plainProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
396
- writeStateMd(statePath, content, cwd);
397
- output({ updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr }, raw, progressStr);
420
+ let updated = false;
421
+ const _totalPlans = totalPlans;
422
+ const _totalSummaries = totalSummaries;
423
+
424
+ readModifyWriteStateMd(statePath, (content) => {
425
+ // Try **Progress:** bold format first, then plain Progress: format
426
+ const boldProgressPattern = /(\*\*Progress:\*\*\s*).*/i;
427
+ const plainProgressPattern = /^(Progress:\s*).*/im;
428
+ if (boldProgressPattern.test(content)) {
429
+ updated = true;
430
+ return content.replace(boldProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
431
+ } else if (plainProgressPattern.test(content)) {
432
+ updated = true;
433
+ return content.replace(plainProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
434
+ }
435
+ return content;
436
+ }, cwd);
437
+
438
+ if (updated) {
439
+ output({ updated: true, percent, completed: _totalSummaries, total: _totalPlans, bar: progressStr }, raw, progressStr);
398
440
  } else {
399
441
  output({ updated: false, reason: 'Progress field not found in STATE.md' }, raw, 'false');
400
442
  }
@@ -418,24 +460,42 @@ function cmdStateAddDecision(cwd, options, raw) {
418
460
 
419
461
  if (!summaryText) { output({ error: 'summary required' }, raw); return; }
420
462
 
421
- let content = fs.readFileSync(statePath, 'utf-8');
422
463
  const entry = `- [Phase ${phase || '?'}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ''}`;
464
+ let added = false;
465
+ let created = false;
466
+
467
+ readModifyWriteStateMd(statePath, (content) => {
468
+ // Find Decisions section (various heading patterns)
469
+ const sectionPattern = /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
470
+ const match = content.match(sectionPattern);
471
+
472
+ if (match) {
473
+ let sectionBody = match[2];
474
+ // Remove placeholders
475
+ sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, '').replace(/No decisions yet\.?\s*\n?/gi, '');
476
+ sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
477
+ added = true;
478
+ return content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
479
+ }
423
480
 
424
- // Find Decisions section (various heading patterns)
425
- const sectionPattern = /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
426
- const match = content.match(sectionPattern);
427
-
428
- if (match) {
429
- let sectionBody = match[2];
430
- // Remove placeholders
431
- sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, '').replace(/No decisions yet\.?\s*\n?/gi, '');
432
- sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
433
- content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
434
- writeStateMd(statePath, content, cwd);
435
- output({ added: true, decision: entry }, raw, 'true');
436
- } else {
437
- output({ added: false, reason: 'Decisions section not found in STATE.md' }, raw, 'false');
438
- }
481
+ // Section absent DWIM: auto-create canonical ## Decisions scaffold,
482
+ // then append the entry. Matches state begin-phase / advance-plan DWIM behavior.
483
+ const scaffold = [
484
+ '',
485
+ '## Decisions',
486
+ '',
487
+ entry,
488
+ '',
489
+ ].join('\n');
490
+ added = true;
491
+ created = true;
492
+ return content.trimEnd() + '\n' + scaffold;
493
+ }, cwd);
494
+
495
+ // Auto-create fallback guarantees added === true; no else branch needed.
496
+ const result = { added: true, decision: entry };
497
+ if (created) result.created = true;
498
+ output(result, raw, 'true');
439
499
  }
440
500
 
441
501
  function cmdStateAddBlocker(cwd, text, raw) {
@@ -453,22 +513,39 @@ function cmdStateAddBlocker(cwd, text, raw) {
453
513
 
454
514
  if (!blockerText) { output({ error: 'text required' }, raw); return; }
455
515
 
456
- let content = fs.readFileSync(statePath, 'utf-8');
457
516
  const entry = `- ${blockerText}`;
517
+ let added = false;
518
+ let created = false;
519
+
520
+ readModifyWriteStateMd(statePath, (content) => {
521
+ const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
522
+ const match = content.match(sectionPattern);
523
+
524
+ if (match) {
525
+ let sectionBody = match[2];
526
+ sectionBody = sectionBody.replace(/None\.?\s*\n?/gi, '').replace(/None yet\.?\s*\n?/gi, '');
527
+ sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
528
+ added = true;
529
+ return content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
530
+ }
458
531
 
459
- const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
460
- const match = content.match(sectionPattern);
461
-
462
- if (match) {
463
- let sectionBody = match[2];
464
- sectionBody = sectionBody.replace(/None\.?\s*\n?/gi, '').replace(/None yet\.?\s*\n?/gi, '');
465
- sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
466
- content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
467
- writeStateMd(statePath, content, cwd);
468
- output({ added: true, blocker: blockerText }, raw, 'true');
469
- } else {
470
- output({ added: false, reason: 'Blockers section not found in STATE.md' }, raw, 'false');
471
- }
532
+ // Section absent — DWIM: auto-create canonical ### Blockers scaffold.
533
+ const scaffold = [
534
+ '',
535
+ '### Blockers',
536
+ '',
537
+ entry,
538
+ '',
539
+ ].join('\n');
540
+ added = true;
541
+ created = true;
542
+ return content.trimEnd() + '\n' + scaffold;
543
+ }, cwd);
544
+
545
+ // Auto-create fallback guarantees added === true; no else branch needed.
546
+ const result = { added: true, blocker: blockerText };
547
+ if (created) result.created = true;
548
+ output(result, raw, 'true');
472
549
  }
473
550
 
474
551
  function cmdStateResolveBlocker(cwd, text, raw) {
@@ -476,27 +553,33 @@ function cmdStateResolveBlocker(cwd, text, raw) {
476
553
  if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; }
477
554
  if (!text) { output({ error: 'text required' }, raw); return; }
478
555
 
479
- let content = fs.readFileSync(statePath, 'utf-8');
556
+ let resolved = false;
557
+
558
+ readModifyWriteStateMd(statePath, (content) => {
559
+ const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
560
+ const match = content.match(sectionPattern);
561
+
562
+ if (match) {
563
+ const sectionBody = match[2];
564
+ const lines = sectionBody.split('\n');
565
+ const filtered = lines.filter(line => {
566
+ if (!line.startsWith('- ')) return true;
567
+ return !line.toLowerCase().includes(text.toLowerCase());
568
+ });
480
569
 
481
- const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
482
- const match = content.match(sectionPattern);
483
-
484
- if (match) {
485
- const sectionBody = match[2];
486
- const lines = sectionBody.split('\n');
487
- const filtered = lines.filter(line => {
488
- if (!line.startsWith('- ')) return true;
489
- return !line.toLowerCase().includes(text.toLowerCase());
490
- });
491
-
492
- let newBody = filtered.join('\n');
493
- // If section is now empty, add placeholder
494
- if (!newBody.trim() || !newBody.includes('- ')) {
495
- newBody = 'None\n';
570
+ let newBody = filtered.join('\n');
571
+ // If section is now empty, add placeholder
572
+ if (!newBody.trim() || !newBody.includes('- ')) {
573
+ newBody = 'None\n';
574
+ }
575
+
576
+ resolved = true;
577
+ return content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
496
578
  }
579
+ return content;
580
+ }, cwd);
497
581
 
498
- content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
499
- writeStateMd(statePath, content, cwd);
582
+ if (resolved) {
500
583
  output({ resolved: true, blocker: text }, raw, 'true');
501
584
  } else {
502
585
  output({ resolved: false, reason: 'Blockers section not found in STATE.md' }, raw, 'false');
@@ -507,31 +590,33 @@ function cmdStateRecordSession(cwd, options, raw) {
507
590
  const statePath = planningPaths(cwd).state;
508
591
  if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; }
509
592
 
510
- let content = fs.readFileSync(statePath, 'utf-8');
511
593
  const now = new Date().toISOString();
512
594
  const updated = [];
513
595
 
514
- // Update Last session / Last Date
515
- let result = stateReplaceField(content, 'Last session', now);
516
- if (result) { content = result; updated.push('Last session'); }
517
- result = stateReplaceField(content, 'Last Date', now);
518
- if (result) { content = result; updated.push('Last Date'); }
596
+ readModifyWriteStateMd(statePath, (content) => {
597
+ // Update Last session / Last Date
598
+ let result = stateReplaceField(content, 'Last session', now);
599
+ if (result) { content = result; updated.push('Last session'); }
600
+ result = stateReplaceField(content, 'Last Date', now);
601
+ if (result) { content = result; updated.push('Last Date'); }
602
+
603
+ // Update Stopped at
604
+ if (options.stopped_at) {
605
+ result = stateReplaceField(content, 'Stopped At', options.stopped_at);
606
+ if (!result) result = stateReplaceField(content, 'Stopped at', options.stopped_at);
607
+ if (result) { content = result; updated.push('Stopped At'); }
608
+ }
519
609
 
520
- // Update Stopped at
521
- if (options.stopped_at) {
522
- result = stateReplaceField(content, 'Stopped At', options.stopped_at);
523
- if (!result) result = stateReplaceField(content, 'Stopped at', options.stopped_at);
524
- if (result) { content = result; updated.push('Stopped At'); }
525
- }
610
+ // Update Resume file
611
+ const resumeFile = options.resume_file || 'None';
612
+ result = stateReplaceField(content, 'Resume File', resumeFile);
613
+ if (!result) result = stateReplaceField(content, 'Resume file', resumeFile);
614
+ if (result) { content = result; updated.push('Resume File'); }
526
615
 
527
- // Update Resume file
528
- const resumeFile = options.resume_file || 'None';
529
- result = stateReplaceField(content, 'Resume File', resumeFile);
530
- if (!result) result = stateReplaceField(content, 'Resume file', resumeFile);
531
- if (result) { content = result; updated.push('Resume File'); }
616
+ return content;
617
+ }, cwd);
532
618
 
533
619
  if (updated.length > 0) {
534
- writeStateMd(statePath, content, cwd);
535
620
  output({ recorded: true, updated }, raw, 'true');
536
621
  } else {
537
622
  output({ recorded: false, reason: 'No session fields found in STATE.md' }, raw, 'false');
@@ -548,17 +633,36 @@ function cmdStateSnapshot(cwd, raw) {
548
633
 
549
634
  const content = fs.readFileSync(statePath, 'utf-8');
550
635
 
551
- // Extract basic fields
552
- const currentPhase = stateExtractField(content, 'Current Phase');
553
- const currentPhaseName = stateExtractField(content, 'Current Phase Name');
554
- const totalPhasesRaw = stateExtractField(content, 'Total Phases');
555
- const currentPlan = stateExtractField(content, 'Current Plan');
556
- const totalPlansRaw = stateExtractField(content, 'Total Plans in Phase');
557
- const status = stateExtractField(content, 'Status');
558
- const progressRaw = stateExtractField(content, 'Progress');
559
- const lastActivity = stateExtractField(content, 'Last Activity');
560
- const lastActivityDesc = stateExtractField(content, 'Last Activity Description');
561
- const pausedAt = stateExtractField(content, 'Paused At');
636
+ // Bug #3265: prefer YAML frontmatter for canonical scalar fields so that a
637
+ // body table cell containing **Status:** Y cannot shadow the authoritative
638
+ // frontmatter value. Mirrors the fix in sdk/src/query/state.ts.
639
+ const fm = extractFrontmatter(content);
640
+ const body = stripFrontmatter(content);
641
+
642
+ // Helper: return frontmatter scalar value when present and non-empty.
643
+ // Accepts strings, numbers, and booleans — coercing non-string primitives to
644
+ // their string representation so callers always receive string | null.
645
+ // Returns null for missing, null/undefined, or empty-after-trim values so
646
+ // the caller falls back to body extraction.
647
+ const fmScalar = (key) => {
648
+ const v = fm[key];
649
+ if (v === null || v === undefined) return null;
650
+ if (typeof v === 'string') return v.trim() || null;
651
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
652
+ return null;
653
+ };
654
+
655
+ // Extract basic fields — frontmatter keys take precedence over body
656
+ const currentPhase = fmScalar('current_phase') ?? stateExtractField(body, 'Current Phase');
657
+ const currentPhaseName = fmScalar('current_phase_name') ?? stateExtractField(body, 'Current Phase Name');
658
+ const totalPhasesRaw = fmScalar('total_phases') ?? stateExtractField(body, 'Total Phases');
659
+ const currentPlan = fmScalar('current_plan') ?? stateExtractField(body, 'Current Plan');
660
+ const totalPlansRaw = fmScalar('total_plans_in_phase') ?? stateExtractField(body, 'Total Plans in Phase');
661
+ const status = fmScalar('status') ?? stateExtractField(body, 'Status');
662
+ const progressRaw = fmScalar('progress') ?? stateExtractField(body, 'Progress');
663
+ const lastActivity = fmScalar('last_activity') ?? stateExtractField(body, 'Last Activity');
664
+ const lastActivityDesc = fmScalar('last_activity_desc') ?? stateExtractField(body, 'Last Activity Description');
665
+ const pausedAt = fmScalar('paused_at') ?? stateExtractField(body, 'Paused At');
562
666
 
563
667
  // Parse numeric fields
564
668
  const totalPhases = totalPhasesRaw ? parseInt(totalPhasesRaw, 10) : null;
@@ -567,7 +671,7 @@ function cmdStateSnapshot(cwd, raw) {
567
671
 
568
672
  // Extract decisions table
569
673
  const decisions = [];
570
- const decisionsMatch = content.match(/##\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n([\s\S]*?)(?=\n##|\n$|$)/i);
674
+ const decisionsMatch = body.match(/##\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n([\s\S]*?)(?=\n##|\n$|$)/i);
571
675
  if (decisionsMatch) {
572
676
  const tableBody = decisionsMatch[1];
573
677
  const rows = tableBody.trim().split('\n').filter(r => r.includes('|'));
@@ -585,7 +689,7 @@ function cmdStateSnapshot(cwd, raw) {
585
689
 
586
690
  // Extract blockers list
587
691
  const blockers = [];
588
- const blockersMatch = content.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i);
692
+ const blockersMatch = body.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i);
589
693
  if (blockersMatch) {
590
694
  const blockersSection = blockersMatch[1];
591
695
  const items = blockersSection.match(/^-\s+(.+)$/gm) || [];
@@ -601,7 +705,7 @@ function cmdStateSnapshot(cwd, raw) {
601
705
  resume_file: null,
602
706
  };
603
707
 
604
- const sessionMatch = content.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
708
+ const sessionMatch = body.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
605
709
  if (sessionMatch) {
606
710
  const sessionSection = sessionMatch[1];
607
711
  const lastDateMatch = sessionSection.match(/\*\*Last Date:\*\*\s*(.+)/i)
@@ -651,7 +755,13 @@ function buildStateFrontmatter(bodyContent, cwd) {
651
755
  const status = stateExtractField(bodyContent, 'Status');
652
756
  const progressRaw = stateExtractField(bodyContent, 'Progress');
653
757
  const lastActivity = stateExtractField(bodyContent, 'Last Activity');
654
- const stoppedAt = stateExtractField(bodyContent, 'Stopped At') || stateExtractField(bodyContent, 'Stopped at');
758
+ // Bug #2444: scope Stopped At extraction to the ## Session section so that
759
+ // historical "Stopped at:" prose elsewhere in the body (e.g. in a
760
+ // Session Continuity Archive section) never overwrites the current value.
761
+ // Fall back to full-body search only when no ## Session section exists.
762
+ const sessionSectionMatch = bodyContent.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
763
+ const sessionBodyScope = sessionSectionMatch ? sessionSectionMatch[1] : bodyContent;
764
+ const stoppedAt = stateExtractField(sessionBodyScope, 'Stopped At') || stateExtractField(sessionBodyScope, 'Stopped at');
655
765
  const pausedAt = stateExtractField(bodyContent, 'Paused At');
656
766
 
657
767
  let milestone = null;
@@ -673,56 +783,79 @@ function buildStateFrontmatter(bodyContent, cwd) {
673
783
  try {
674
784
  const phasesDir = planningPaths(cwd).phases;
675
785
  if (fs.existsSync(phasesDir)) {
676
- const isDirInMilestone = getMilestonePhaseFilter(cwd);
677
- const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
678
- .filter(e => e.isDirectory()).map(e => e.name)
679
- .filter(isDirInMilestone);
680
- let diskTotalPlans = 0;
681
- let diskTotalSummaries = 0;
682
- let diskCompletedPhases = 0;
683
-
684
- for (const dir of phaseDirs) {
685
- const files = fs.readdirSync(path.join(phasesDir, dir));
686
- const plans = files.filter(f => f.match(/-PLAN\.md$/i)).length;
687
- const summaries = files.filter(f => f.match(/-SUMMARY\.md$/i)).length;
688
- diskTotalPlans += plans;
689
- diskTotalSummaries += summaries;
690
- if (plans > 0 && summaries >= plans) diskCompletedPhases++;
786
+ // Use cached disk scan when available — avoids N+1 readdirSync calls
787
+ // on repeated buildStateFrontmatter invocations within the same process (#1967)
788
+ let cached = _diskScanCache.get(cwd);
789
+ if (!cached) {
790
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
791
+ const allMatchingDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
792
+ .filter(e => e.isDirectory()).map(e => e.name)
793
+ .filter(isDirInMilestone);
794
+
795
+ // Bug #2445: when stale phase dirs from a prior milestone remain in
796
+ // .planning/phases/ alongside new dirs with the same phase number,
797
+ // de-duplicate by normalized phase number keeping the most recently
798
+ // modified dir. This prevents double-counting (e.g. two "Phase 1" dirs).
799
+ const seenPhaseNums = new Map(); // normalizedNum -> dirName
800
+ for (const dir of allMatchingDirs) {
801
+ const m = dir.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
802
+ const key = m ? m[1].toLowerCase() : dir;
803
+ if (!seenPhaseNums.has(key)) {
804
+ seenPhaseNums.set(key, dir);
805
+ } else {
806
+ // Keep the dir that is newer on disk (more likely current milestone)
807
+ try {
808
+ const existing = path.join(phasesDir, seenPhaseNums.get(key));
809
+ const candidate = path.join(phasesDir, dir);
810
+ if (fs.statSync(candidate).mtimeMs > fs.statSync(existing).mtimeMs) {
811
+ seenPhaseNums.set(key, dir);
812
+ }
813
+ } catch { /* keep existing on stat error */ }
814
+ }
815
+ }
816
+ const phaseDirs = [...seenPhaseNums.values()];
817
+
818
+ let diskTotalPlans = 0;
819
+ let diskTotalSummaries = 0;
820
+ let diskCompletedPhases = 0;
821
+
822
+ for (const dir of phaseDirs) {
823
+ const phaseDir = path.join(phasesDir, dir);
824
+ const { planCount, summaryCount, completed } = scanPhasePlans(phaseDir);
825
+ diskTotalPlans += planCount;
826
+ diskTotalSummaries += summaryCount;
827
+ if (completed) diskCompletedPhases++;
828
+ }
829
+ cached = {
830
+ totalPhases: isDirInMilestone.phaseCount > 0
831
+ ? Math.max(phaseDirs.length, isDirInMilestone.phaseCount)
832
+ : phaseDirs.length,
833
+ completedPhases: diskCompletedPhases,
834
+ totalPlans: diskTotalPlans,
835
+ completedPlans: diskTotalSummaries,
836
+ };
837
+ _diskScanCache.set(cwd, cached);
691
838
  }
692
- totalPhases = isDirInMilestone.phaseCount > 0
693
- ? Math.max(phaseDirs.length, isDirInMilestone.phaseCount)
694
- : phaseDirs.length;
695
- completedPhases = diskCompletedPhases;
696
- totalPlans = diskTotalPlans;
697
- completedPlans = diskTotalSummaries;
839
+ totalPhases = cached.totalPhases;
840
+ completedPhases = cached.completedPhases;
841
+ totalPlans = cached.totalPlans;
842
+ completedPlans = cached.completedPlans;
698
843
  }
699
844
  } catch { /* intentionally empty */ }
700
845
  }
701
846
 
702
- let progressPercent = null;
703
- if (progressRaw) {
847
+ // Derive percent from disk counts when available (ground truth).
848
+ // Uses min(plan_fraction, phase_fraction) via computeProgressPercent so that
849
+ // ROADMAP-declared-but-unrealized future phases cap the reported completion
850
+ // instead of a false 100% from plan-only coverage (#3242 Bug B).
851
+ // Falls back to the body Progress: field only when no plan files exist on disk.
852
+ let progressPercent = computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases);
853
+ if (progressPercent === null && progressRaw) {
704
854
  const pctMatch = progressRaw.match(/(\d+)%/);
705
855
  if (pctMatch) progressPercent = parseInt(pctMatch[1], 10);
706
856
  }
707
857
 
708
- // Normalize status to one of: planning, discussing, executing, verifying, paused, completed, unknown
709
- let normalizedStatus = status || 'unknown';
710
- const statusLower = (status || '').toLowerCase();
711
- if (statusLower.includes('paused') || statusLower.includes('stopped') || pausedAt) {
712
- normalizedStatus = 'paused';
713
- } else if (statusLower.includes('executing') || statusLower.includes('in progress')) {
714
- normalizedStatus = 'executing';
715
- } else if (statusLower.includes('planning') || statusLower.includes('ready to plan')) {
716
- normalizedStatus = 'planning';
717
- } else if (statusLower.includes('discussing')) {
718
- normalizedStatus = 'discussing';
719
- } else if (statusLower.includes('verif')) {
720
- normalizedStatus = 'verifying';
721
- } else if (statusLower.includes('complete') || statusLower.includes('done')) {
722
- normalizedStatus = 'completed';
723
- } else if (statusLower.includes('ready to execute')) {
724
- normalizedStatus = 'executing';
725
- }
858
+ const normalizedStatus = normalizeStateStatus(status, pausedAt);
726
859
 
727
860
  const fm = { sdd_state_version: '1.0' };
728
861
 
@@ -781,55 +914,117 @@ function syncStateFrontmatter(content, cwd) {
781
914
  }
782
915
 
783
916
  /**
784
- * Write STATE.md with synchronized YAML frontmatter.
785
- * All STATE.md writes should use this instead of raw writeFileSync.
786
- * Uses a simple lockfile to prevent parallel agents from overwriting
787
- * each other's changes (race condition with read-modify-write cycle).
917
+ * Acquire a lockfile for STATE.md operations.
918
+ * Returns the lock path for later release.
788
919
  */
789
- function writeStateMd(statePath, content, cwd) {
790
- const synced = syncStateFrontmatter(content, cwd);
920
+ function acquireStateLock(statePath) {
791
921
  const lockPath = statePath + '.lock';
792
922
  const maxRetries = 10;
793
923
  const retryDelay = 200; // ms
794
924
 
795
- // Acquire lock (spin with backoff)
796
925
  for (let i = 0; i < maxRetries; i++) {
797
926
  try {
798
- // O_EXCL fails if file already exists — atomic lock
799
927
  const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
800
928
  fs.writeSync(fd, String(process.pid));
801
929
  fs.closeSync(fd);
802
- break;
930
+ // Register for exit-time cleanup so process.exit(1) inside a locked region
931
+ // cannot leave a stale lock file (#1916).
932
+ _heldStateLocks.add(lockPath);
933
+ return lockPath;
803
934
  } catch (err) {
804
935
  if (err.code === 'EEXIST') {
805
- // Check for stale lock (> 10s old)
806
936
  try {
807
937
  const stat = fs.statSync(lockPath);
808
938
  if (Date.now() - stat.mtimeMs > 10000) {
809
939
  fs.unlinkSync(lockPath);
810
- continue; // retry immediately after clearing stale lock
940
+ continue;
811
941
  }
812
942
  } catch { /* lock was released between check — retry */ }
813
943
 
814
944
  if (i === maxRetries - 1) {
815
- // Last resort: write anyway rather than losing data
816
945
  try { fs.unlinkSync(lockPath); } catch {}
817
- break;
946
+ return lockPath;
818
947
  }
819
- // Spin-wait with small jitter
820
948
  const jitter = Math.floor(Math.random() * 50);
821
- const start = Date.now();
822
- while (Date.now() - start < retryDelay + jitter) { /* busy wait */ }
949
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, retryDelay + jitter);
823
950
  continue;
824
951
  }
825
- break; // non-EEXIST error — proceed without lock
952
+ return lockPath; // non-EEXIST error — proceed without lock
826
953
  }
827
954
  }
955
+ return statePath + '.lock';
956
+ }
957
+
958
+ function releaseStateLock(lockPath) {
959
+ _heldStateLocks.delete(lockPath);
960
+ try { fs.unlinkSync(lockPath); } catch { /* lock already gone */ }
961
+ }
828
962
 
963
+ /**
964
+ * Write STATE.md with synchronized YAML frontmatter.
965
+ * All STATE.md writes should use this instead of raw writeFileSync.
966
+ * Uses a simple lockfile to prevent parallel agents from overwriting
967
+ * each other's changes (race condition with read-modify-write cycle).
968
+ */
969
+ function writeStateMd(statePath, content, cwd) {
970
+ // Invalidate disk scan cache before computing new frontmatter — the write
971
+ // may create new PLAN/SUMMARY files that buildStateFrontmatter must see.
972
+ // Safe for any calling pattern, not just short-lived CLI processes (#1967).
973
+ if (cwd) _diskScanCache.delete(cwd);
974
+ const synced = syncStateFrontmatter(content, cwd);
975
+ const lockPath = acquireStateLock(statePath);
829
976
  try {
830
- fs.writeFileSync(statePath, normalizeMd(synced), 'utf-8');
977
+ platformWriteSync(statePath, synced);
831
978
  } finally {
832
- try { fs.unlinkSync(lockPath); } catch { /* lock already gone */ }
979
+ releaseStateLock(lockPath);
980
+ }
981
+ }
982
+
983
+ /**
984
+ * Atomic read-modify-write for STATE.md.
985
+ * Holds the lock across the entire read -> transform -> write cycle,
986
+ * preventing the lost-update problem where two agents read the same
987
+ * content and the second write clobbers the first.
988
+ *
989
+ * @param {string} statePath
990
+ * @param {function} transformFn - (content: string) => string
991
+ * @param {string} cwd
992
+ * @param {{ resync?: boolean }} [options]
993
+ * resync: when true (default) rebuilds the entire frontmatter from disk after
994
+ * the transform. Pass { resync: false } for body-only updates (e.g. state.update
995
+ * on a single field) that must not trample manually-curated cross-milestone
996
+ * progress.* counters in the frontmatter (#3242 Bug A).
997
+ * When resync is false, syncStateFrontmatter still runs to maintain/create the
998
+ * frontmatter block, but any existing progress.* sub-keys are preserved from
999
+ * the pre-transform file rather than being rebuilt from disk.
1000
+ */
1001
+ function readModifyWriteStateMd(statePath, transformFn, cwd, options) {
1002
+ const resync = !options || options.resync !== false;
1003
+ const lockPath = acquireStateLock(statePath);
1004
+ try {
1005
+ const content = platformReadSync(statePath) || '';
1006
+ // Snapshot the existing progress block BEFORE the transform so we can
1007
+ // restore it when resync is false.
1008
+ const preFm = resync ? null : extractFrontmatter(content);
1009
+ const modified = transformFn(content);
1010
+ let synced = syncStateFrontmatter(modified, cwd);
1011
+
1012
+ if (!resync && preFm && preFm.progress) {
1013
+ // Re-apply the curated progress block that syncStateFrontmatter just
1014
+ // overwrote with disk-derived values. Only restore keys that were present
1015
+ // in the snapshot — this preserves any new non-progress frontmatter fields
1016
+ // (e.g., status, current_phase) that syncStateFrontmatter legitimately
1017
+ // derived from the updated body.
1018
+ const postFm = extractFrontmatter(synced);
1019
+ postFm.progress = preFm.progress;
1020
+ const yamlStr = reconstructFrontmatter(postFm);
1021
+ const body = stripFrontmatter(synced);
1022
+ synced = `---\n${yamlStr}\n---\n\n${body}`;
1023
+ }
1024
+
1025
+ platformWriteSync(statePath, synced);
1026
+ } finally {
1027
+ releaseStateLock(lockPath);
833
1028
  }
834
1029
  }
835
1030
 
@@ -841,16 +1036,33 @@ function cmdStateJson(cwd, raw) {
841
1036
  }
842
1037
 
843
1038
  const content = fs.readFileSync(statePath, 'utf-8');
844
- const fm = extractFrontmatter(content);
1039
+ const existingFm = extractFrontmatter(content);
1040
+ const body = stripFrontmatter(content);
845
1041
 
846
- if (!fm || Object.keys(fm).length === 0) {
847
- const body = stripFrontmatter(content);
848
- const built = buildStateFrontmatter(body, cwd);
849
- output(built, raw, JSON.stringify(built, null, 2));
850
- return;
1042
+ // Always rebuild from body + disk so progress counters reflect current state.
1043
+ // Returning cached frontmatter directly causes stale percent/completed_plans
1044
+ // when SUMMARY files were added after the last STATE.md write (#1589).
1045
+ const built = buildStateFrontmatter(body, cwd);
1046
+
1047
+ // Preserve frontmatter-only fields that cannot be recovered from the body.
1048
+ if (existingFm && existingFm.stopped_at && !built.stopped_at) {
1049
+ built.stopped_at = existingFm.stopped_at;
1050
+ }
1051
+ if (existingFm && existingFm.paused_at && !built.paused_at) {
1052
+ built.paused_at = existingFm.paused_at;
1053
+ }
1054
+ // Preserve existing status when body-derived status is 'unknown' (same logic as syncStateFrontmatter).
1055
+ if (built.status === 'unknown' && existingFm && existingFm.status && existingFm.status !== 'unknown') {
1056
+ built.status = existingFm.status;
1057
+ }
1058
+ // Preserve curated cross-milestone aggregates when local disk scanning sees
1059
+ // only a narrower realized subset (#3242 Bug A). Stale lower counters still
1060
+ // rebuild from disk because they do not exceed the derived scan.
1061
+ if (existingFm && shouldPreserveExistingProgress(existingFm.progress, built.progress)) {
1062
+ built.progress = normalizeProgressNumbers(existingFm.progress);
851
1063
  }
852
1064
 
853
- output(fm, raw, JSON.stringify(fm, null, 2));
1065
+ output(built, raw, JSON.stringify(built, null, 2));
854
1066
  }
855
1067
 
856
1068
  /**
@@ -866,96 +1078,121 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
866
1078
  return;
867
1079
  }
868
1080
 
869
- let content = fs.readFileSync(statePath, 'utf-8');
870
1081
  const today = new Date().toISOString().split('T')[0];
871
1082
  const updated = [];
872
1083
 
873
- // Update Status field
874
- const statusValue = `Executing Phase ${phaseNumber}`;
875
- let result = stateReplaceField(content, 'Status', statusValue);
876
- if (result) { content = result; updated.push('Status'); }
877
-
878
- // Update Last Activity
879
- result = stateReplaceField(content, 'Last Activity', today);
880
- if (result) { content = result; updated.push('Last Activity'); }
881
-
882
- // Update Last Activity Description if it exists
883
- const activityDesc = `Phase ${phaseNumber} execution started`;
884
- result = stateReplaceField(content, 'Last Activity Description', activityDesc);
885
- if (result) { content = result; updated.push('Last Activity Description'); }
1084
+ readModifyWriteStateMd(statePath, (content) => {
1085
+ // Idempotency guard (#3127): if the phase is already mid-flight, do NOT
1086
+ // overwrite execution-progress fields (Current Plan, plan body line,
1087
+ // Last Activity Description). Only update fields that are safe to
1088
+ // refresh on resume (Last Activity date, Status if inconsistent).
1089
+ // A phase is considered mid-flight when Status contains 'Executing Phase N'
1090
+ // for the current phase number.
1091
+ const currentStatus = stateExtractField(content, 'Status') || '';
1092
+ const isAlreadyExecuting = new RegExp(`Executing Phase\\s+${escapeRegex(String(phaseNumber))}\\b`, 'i').test(currentStatus);
1093
+
1094
+ // Update Status field
1095
+ const statusValue = `Executing Phase ${phaseNumber}`;
1096
+ let result = stateReplaceField(content, 'Status', statusValue);
1097
+ if (result) { content = result; updated.push('Status'); }
1098
+
1099
+ // Update Last Activity (safe to update on resume — tracks when execute-phase ran)
1100
+ result = stateReplaceField(content, 'Last Activity', today);
1101
+ if (result) { content = result; updated.push('Last Activity'); }
1102
+
1103
+ if (!isAlreadyExecuting) {
1104
+ // First-time execution: set all progress fields
1105
+
1106
+ // Update Last Activity Description
1107
+ const activityDesc = `Phase ${phaseNumber} execution started`;
1108
+ result = stateReplaceField(content, 'Last Activity Description', activityDesc);
1109
+ if (result) { content = result; updated.push('Last Activity Description'); }
1110
+
1111
+ // Update Current Phase
1112
+ result = stateReplaceField(content, 'Current Phase', String(phaseNumber));
1113
+ if (result) { content = result; updated.push('Current Phase'); }
1114
+
1115
+ // Update Current Phase Name
1116
+ if (phaseName) {
1117
+ result = stateReplaceField(content, 'Current Phase Name', phaseName);
1118
+ if (result) { content = result; updated.push('Current Phase Name'); }
1119
+ }
886
1120
 
887
- // Update Current Phase
888
- result = stateReplaceField(content, 'Current Phase', String(phaseNumber));
889
- if (result) { content = result; updated.push('Current Phase'); }
1121
+ // Update Current Plan to 1 (starting from the first plan)
1122
+ result = stateReplaceField(content, 'Current Plan', '1');
1123
+ if (result) { content = result; updated.push('Current Plan'); }
890
1124
 
891
- // Update Current Phase Name
892
- if (phaseName) {
893
- result = stateReplaceField(content, 'Current Phase Name', phaseName);
894
- if (result) { content = result; updated.push('Current Phase Name'); }
895
- }
1125
+ // Update Total Plans in Phase
1126
+ if (planCount) {
1127
+ result = stateReplaceField(content, 'Total Plans in Phase', String(planCount));
1128
+ if (result) { content = result; updated.push('Total Plans in Phase'); }
1129
+ }
896
1130
 
897
- // Update Current Plan to 1 (starting from the first plan)
898
- result = stateReplaceField(content, 'Current Plan', '1');
899
- if (result) { content = result; updated.push('Current Plan'); }
1131
+ // Update **Current focus:** body text line (#1104)
1132
+ const focusLabel = phaseName ? `Phase ${phaseNumber} — ${phaseName}` : `Phase ${phaseNumber}`;
1133
+ const focusPattern = /(\*\*Current focus:\*\*\s*).*/i;
1134
+ if (focusPattern.test(content)) {
1135
+ content = content.replace(focusPattern, (_match, prefix) => `${prefix}${focusLabel}`);
1136
+ updated.push('Current focus');
1137
+ }
900
1138
 
901
- // Update Total Plans in Phase
902
- if (planCount) {
903
- result = stateReplaceField(content, 'Total Plans in Phase', String(planCount));
904
- if (result) { content = result; updated.push('Total Plans in Phase'); }
905
- }
1139
+ // Update ## Current Position section (#1104, #1365)
1140
+ const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
1141
+ const positionMatch = content.match(positionPattern);
1142
+ if (positionMatch) {
1143
+ const header = positionMatch[1];
1144
+ let posBody = positionMatch[2];
1145
+
1146
+ // Update or insert Phase line
1147
+ const newPhase = `Phase: ${phaseNumber}${phaseName ? ` (${phaseName})` : ''} — EXECUTING`;
1148
+ if (/^Phase:/m.test(posBody)) {
1149
+ posBody = posBody.replace(/^Phase:.*$/m, newPhase);
1150
+ } else {
1151
+ posBody = newPhase + '\n' + posBody;
1152
+ }
906
1153
 
907
- // Update **Current focus:** body text line (#1104)
908
- const focusLabel = phaseName ? `Phase ${phaseNumber} ${phaseName}` : `Phase ${phaseNumber}`;
909
- const focusPattern = /(\*\*Current focus:\*\*\s*).*/i;
910
- if (focusPattern.test(content)) {
911
- content = content.replace(focusPattern, (_match, prefix) => `${prefix}${focusLabel}`);
912
- updated.push('Current focus');
913
- }
1154
+ // Update or insert Plan line
1155
+ const newPlan = `Plan: 1 of ${planCount || '?'}`;
1156
+ if (/^Plan:/m.test(posBody)) {
1157
+ posBody = posBody.replace(/^Plan:.*$/m, newPlan);
1158
+ } else {
1159
+ posBody = posBody.replace(/^(Phase:.*$)/m, `$1\n${newPlan}`);
1160
+ }
914
1161
 
915
- // Update ## Current Position section (#1104, #1365)
916
- // Update individual fields within Current Position instead of replacing the
917
- // entire section, so that Status, Last activity, and Progress are preserved.
918
- const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
919
- const positionMatch = content.match(positionPattern);
920
- if (positionMatch) {
921
- const header = positionMatch[1];
922
- let posBody = positionMatch[2];
1162
+ // Update Status line if present
1163
+ const newStatus = `Status: Executing Phase ${phaseNumber}`;
1164
+ if (/^Status:/m.test(posBody)) {
1165
+ posBody = posBody.replace(/^Status:.*$/m, newStatus);
1166
+ }
923
1167
 
924
- // Update or insert Phase line
925
- const newPhase = `Phase: ${phaseNumber}${phaseName ? ` (${phaseName})` : ''} — EXECUTING`;
926
- if (/^Phase:/m.test(posBody)) {
927
- posBody = posBody.replace(/^Phase:.*$/m, newPhase);
928
- } else {
929
- posBody = newPhase + '\n' + posBody;
930
- }
1168
+ // Update Last activity line if present
1169
+ const newActivity = `Last activity: ${today} -- Phase ${phaseNumber} execution started`;
1170
+ if (/^Last activity:/im.test(posBody)) {
1171
+ posBody = posBody.replace(/^Last activity:.*$/im, newActivity);
1172
+ }
931
1173
 
932
- // Update or insert Plan line
933
- const newPlan = `Plan: 1 of ${planCount || '?'}`;
934
- if (/^Plan:/m.test(posBody)) {
935
- posBody = posBody.replace(/^Plan:.*$/m, newPlan);
1174
+ content = content.replace(positionPattern, () => `${header}${posBody}`);
1175
+ updated.push('Current Position');
1176
+ }
936
1177
  } else {
937
- posBody = posBody.replace(/^(Phase:.*$)/m, `$1\n${newPlan}`);
938
- }
939
-
940
- // Update Status line if present
941
- const newStatus = `Status: Executing Phase ${phaseNumber}`;
942
- if (/^Status:/m.test(posBody)) {
943
- posBody = posBody.replace(/^Status:.*$/m, newStatus);
944
- }
945
-
946
- // Update Last activity line if present
947
- const newActivity = `Last activity: ${today} -- Phase ${phaseNumber} execution started`;
948
- if (/^Last activity:/im.test(posBody)) {
949
- posBody = posBody.replace(/^Last activity:.*$/im, newActivity);
1178
+ // Resume path: only update Last activity timestamp in Current Position
1179
+ // (do not touch Plan:, stopped_at, progress.percent, or plan counter)
1180
+ const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
1181
+ const positionMatch = content.match(positionPattern);
1182
+ if (positionMatch) {
1183
+ const header = positionMatch[1];
1184
+ let posBody = positionMatch[2];
1185
+ const resumeActivity = `Last activity: ${today} -- Phase ${phaseNumber} execution resumed (wave continue)`;
1186
+ if (/^Last activity:/im.test(posBody)) {
1187
+ posBody = posBody.replace(/^Last activity:.*$/im, resumeActivity);
1188
+ content = content.replace(positionPattern, () => `${header}${posBody}`);
1189
+ updated.push('Last activity (resume)');
1190
+ }
1191
+ }
950
1192
  }
951
1193
 
952
- content = content.replace(positionPattern, `${header}${posBody}`);
953
- updated.push('Current Position');
954
- }
955
-
956
- if (updated.length > 0) {
957
- writeStateMd(statePath, content, cwd);
958
- }
1194
+ return content;
1195
+ }, cwd);
959
1196
 
960
1197
  output({ updated, phase: phaseNumber, phase_name: phaseName || null, plan_count: planCount || null }, raw, updated.length > 0 ? 'true' : 'false');
961
1198
  }
@@ -980,8 +1217,8 @@ function cmdSignalWaiting(cwd, type, question, options, phase, raw) {
980
1217
  };
981
1218
 
982
1219
  try {
983
- fs.mkdirSync(sddDir, { recursive: true });
984
- fs.writeFileSync(waitingPath, JSON.stringify(signal, null, 2), 'utf-8');
1220
+ platformEnsureDir(sddDir);
1221
+ platformWriteSync(waitingPath, JSON.stringify(signal, null, 2));
985
1222
  output({ signaled: true, path: waitingPath }, raw, 'true');
986
1223
  } catch (e) {
987
1224
  output({ signaled: false, error: e.message }, raw, 'false');
@@ -1007,11 +1244,654 @@ function cmdSignalResume(cwd, raw) {
1007
1244
  output({ resumed: true, removed }, raw, removed ? 'true' : 'false');
1008
1245
  }
1009
1246
 
1247
+ // ─── Gate Functions (STATE.md consistency enforcement) ────────────────────────
1248
+
1249
+ /**
1250
+ * Update the ## Performance Metrics section in STATE.md content.
1251
+ * Increments Velocity totals and upserts a By Phase table row.
1252
+ * Returns modified content string.
1253
+ */
1254
+ function updatePerformanceMetricsSection(content, cwd, phaseNum, planCount, summaryCount) {
1255
+ // Update Velocity: Total plans completed
1256
+ const totalMatch = content.match(/Total plans completed:\s*(\d+|\[N\])/);
1257
+ const prevTotal = totalMatch && totalMatch[1] !== '[N]' ? parseInt(totalMatch[1], 10) : 0;
1258
+ const newTotal = prevTotal + summaryCount;
1259
+ content = content.replace(
1260
+ /Total plans completed:\s*(\d+|\[N\])/,
1261
+ `Total plans completed: ${newTotal}`
1262
+ );
1263
+
1264
+ // Update By Phase table — upsert row for this phase
1265
+ const byPhaseTablePattern = /(\|\s*Phase\s*\|\s*Plans\s*\|\s*Total\s*\|\s*Avg\/Plan\s*\|[ \t]*\n\|(?:[- :\t]+\|)+[ \t]*\n)((?:[ \t]*\|[^\n]*\n)*)(?=\n|$)/i;
1266
+ const byPhaseMatch = content.match(byPhaseTablePattern);
1267
+ if (byPhaseMatch) {
1268
+ let tableBody = byPhaseMatch[2].trim();
1269
+ const phaseRowPattern = new RegExp(`^\\|\\s*${escapeRegex(String(phaseNum))}\\s*\\|.*$`, 'm');
1270
+ const newRow = `| ${phaseNum} | ${summaryCount} | - | - |`;
1271
+
1272
+ if (phaseRowPattern.test(tableBody)) {
1273
+ // Update existing row
1274
+ tableBody = tableBody.replace(phaseRowPattern, newRow);
1275
+ } else {
1276
+ // Remove placeholder row and add new row
1277
+ tableBody = tableBody.replace(/^\|\s*-\s*\|\s*-\s*\|\s*-\s*\|\s*-\s*\|$/m, '').trim();
1278
+ tableBody = tableBody ? tableBody + '\n' + newRow : newRow;
1279
+ }
1280
+
1281
+ content = content.replace(byPhaseTablePattern, (_match, tableHeader) => `${tableHeader}${tableBody}\n`);
1282
+ }
1283
+
1284
+ return content;
1285
+ }
1286
+
1287
+ /**
1288
+ * Gate 3a: Record state after plan-phase completes.
1289
+ * Updates Status to "Ready to execute", Total Plans, Last Activity.
1290
+ */
1291
+ function cmdStatePlannedPhase(cwd, phaseNumber, planCount, raw) {
1292
+ const statePath = planningPaths(cwd).state;
1293
+ if (!fs.existsSync(statePath)) {
1294
+ output({ error: 'STATE.md not found' }, raw);
1295
+ return;
1296
+ }
1297
+
1298
+ let content = fs.readFileSync(statePath, 'utf-8');
1299
+ const today = new Date().toISOString().split('T')[0];
1300
+ const updated = [];
1301
+
1302
+ // Update Status
1303
+ let result = stateReplaceField(content, 'Status', 'Ready to execute');
1304
+ if (result) { content = result; updated.push('Status'); }
1305
+
1306
+ // Update Total Plans in Phase
1307
+ if (planCount !== null && planCount !== undefined) {
1308
+ result = stateReplaceField(content, 'Total Plans in Phase', String(planCount));
1309
+ if (result) { content = result; updated.push('Total Plans in Phase'); }
1310
+ }
1311
+
1312
+ // Update Last Activity
1313
+ result = stateReplaceField(content, 'Last Activity', today);
1314
+ if (result) { content = result; updated.push('Last Activity'); }
1315
+
1316
+ // Update Last Activity Description
1317
+ result = stateReplaceField(content, 'Last Activity Description', `Phase ${phaseNumber} planning complete — ${planCount || '?'} plans ready`);
1318
+ if (result) { content = result; updated.push('Last Activity Description'); }
1319
+
1320
+ // Update Current Position section
1321
+ content = updateCurrentPositionFields(content, {
1322
+ status: 'Ready to execute',
1323
+ lastActivity: `${today} -- Phase ${phaseNumber} planning complete`,
1324
+ });
1325
+
1326
+ if (updated.length > 0) {
1327
+ writeStateMd(statePath, content, cwd);
1328
+ }
1329
+
1330
+ output({ updated, phase: phaseNumber, plan_count: planCount }, raw, updated.length > 0 ? 'true' : 'false');
1331
+ }
1332
+
1333
+ /**
1334
+ * Bug #2630: reset STATE.md for a new milestone cycle.
1335
+ * Stomps frontmatter milestone/milestone_name/status/progress AND rewrites
1336
+ * the Current Position body. Preserves Accumulated Context.
1337
+ * Symmetric with the SDK `stateMilestoneSwitch` handler.
1338
+ */
1339
+ function cmdStateMilestoneSwitch(cwd, version, name, raw) {
1340
+ if (!version || !String(version).trim()) {
1341
+ output({ error: 'milestone required (--milestone <vX.Y>)' }, raw);
1342
+ return;
1343
+ }
1344
+ const resolvedName = (name && String(name).trim()) || 'milestone';
1345
+ const statePath = planningPaths(cwd).state;
1346
+ const today = new Date().toISOString().split('T')[0];
1347
+
1348
+ const lockPath = acquireStateLock(statePath);
1349
+ try {
1350
+ const content = platformReadSync(statePath) || '';
1351
+ const existingFm = extractFrontmatter(content);
1352
+ const body = stripFrontmatter(content);
1353
+
1354
+ const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
1355
+ const resetPositionBody =
1356
+ `\nPhase: Not started (defining requirements)\n` +
1357
+ `Plan: —\n` +
1358
+ `Status: Defining requirements\n` +
1359
+ `Last activity: ${today} — Milestone ${version} started\n\n`;
1360
+ let newBody;
1361
+ if (positionPattern.test(body)) {
1362
+ newBody = body.replace(positionPattern, (_m, header) => `${header}${resetPositionBody}`);
1363
+ } else {
1364
+ const preface = body.trim().length > 0 ? body : '# Project State\n';
1365
+ newBody = `${preface.trimEnd()}\n\n## Current Position\n${resetPositionBody}`;
1366
+ }
1367
+
1368
+ const fm = {
1369
+ sdd_state_version: existingFm.sdd_state_version || '1.0',
1370
+ milestone: version,
1371
+ milestone_name: resolvedName,
1372
+ status: 'planning',
1373
+ last_updated: new Date().toISOString(),
1374
+ last_activity: today,
1375
+ progress: {
1376
+ total_phases: 0,
1377
+ completed_phases: 0,
1378
+ total_plans: 0,
1379
+ completed_plans: 0,
1380
+ percent: 0,
1381
+ },
1382
+ };
1383
+
1384
+ const yamlStr = reconstructFrontmatter(fm);
1385
+ const assembled = `---\n${yamlStr}\n---\n\n${newBody.replace(/^\n+/, '')}`;
1386
+ platformWriteSync(statePath, assembled);
1387
+ output(
1388
+ { switched: true, version, name: resolvedName, status: 'planning' },
1389
+ raw,
1390
+ 'true',
1391
+ );
1392
+ } finally {
1393
+ releaseStateLock(lockPath);
1394
+ }
1395
+ }
1396
+
1397
+ /**
1398
+ * Gate 1: Validate STATE.md against filesystem.
1399
+ * Returns { valid, warnings, drift } JSON.
1400
+ */
1401
+ function cmdStateValidate(cwd, raw) {
1402
+ const statePath = planningPaths(cwd).state;
1403
+ if (!fs.existsSync(statePath)) {
1404
+ output({ error: 'STATE.md not found' }, raw);
1405
+ return;
1406
+ }
1407
+
1408
+ const content = fs.readFileSync(statePath, 'utf-8');
1409
+ const warnings = [];
1410
+ const drift = {};
1411
+
1412
+ const status = stateExtractField(content, 'Status') || '';
1413
+ const currentPhase = stateExtractField(content, 'Current Phase');
1414
+ const totalPlansRaw = stateExtractField(content, 'Total Plans in Phase');
1415
+ const totalPlansInPhase = totalPlansRaw ? parseInt(totalPlansRaw, 10) : null;
1416
+
1417
+ const phasesDir = planningPaths(cwd).phases;
1418
+
1419
+ // Scan disk for current phase
1420
+ if (currentPhase && fs.existsSync(phasesDir)) {
1421
+ const normalized = currentPhase.replace(/\s+of\s+\d+.*/, '').trim();
1422
+ try {
1423
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
1424
+ const phaseDir = entries.find(e => e.isDirectory() && e.name.startsWith(normalized.replace(/^0+/, '').padStart(2, '0')));
1425
+ if (phaseDir) {
1426
+ const phaseDirPath = path.join(phasesDir, phaseDir.name);
1427
+ const { planCount: diskPlans, summaryCount: diskSummaries } = scanPhasePlans(phaseDirPath);
1428
+
1429
+ // Check plan count mismatch
1430
+ if (totalPlansInPhase !== null && diskPlans !== totalPlansInPhase) {
1431
+ warnings.push(`Plan count mismatch: STATE.md says ${totalPlansInPhase} plans, disk has ${diskPlans}`);
1432
+ drift.plan_count = { state: totalPlansInPhase, disk: diskPlans };
1433
+ }
1434
+
1435
+ // Check for VERIFICATION.md
1436
+ const files = fs.readdirSync(phaseDirPath);
1437
+ const verificationFiles = files.filter(f => f.includes('VERIFICATION') && f.endsWith('.md'));
1438
+ for (const vf of verificationFiles) {
1439
+ try {
1440
+ const vContent = fs.readFileSync(path.join(phaseDirPath, vf), 'utf-8');
1441
+ if (/status:\s*passed/i.test(vContent) && /executing/i.test(status)) {
1442
+ warnings.push(`Status drift: STATE.md says "${status}" but ${vf} shows verification passed — phase may be complete`);
1443
+ drift.verification_status = { state_status: status, verification: 'passed' };
1444
+ }
1445
+ } catch { /* intentionally empty */ }
1446
+ }
1447
+
1448
+ // Check if all plans have summaries but status still says executing
1449
+ if (diskPlans > 0 && diskSummaries >= diskPlans && /executing/i.test(status)) {
1450
+ // Only warn if no verification exists (if verification passed, the above warning covers it)
1451
+ if (verificationFiles.length === 0) {
1452
+ warnings.push(`All ${diskPlans} plans have summaries but status is still "${status}" — phase may be ready for verification`);
1453
+ }
1454
+ }
1455
+ }
1456
+ } catch { /* intentionally empty */ }
1457
+ }
1458
+
1459
+ const valid = warnings.length === 0;
1460
+ output({ valid, warnings, drift }, raw);
1461
+ }
1462
+
1463
+ /**
1464
+ * Gate 2: Sync STATE.md from filesystem ground truth.
1465
+ * Scans phase dirs, reconstructs counters, progress, metrics.
1466
+ * Supports --verify for dry-run mode.
1467
+ */
1468
+ function cmdStateSync(cwd, options, raw) {
1469
+ const statePath = planningPaths(cwd).state;
1470
+ if (!fs.existsSync(statePath)) {
1471
+ output({ error: 'STATE.md not found' }, raw);
1472
+ return;
1473
+ }
1474
+
1475
+ const verify = options && options.verify;
1476
+ const content = fs.readFileSync(statePath, 'utf-8');
1477
+ const changes = [];
1478
+ let modified = content;
1479
+ const today = new Date().toISOString().split('T')[0];
1480
+
1481
+ const phasesDir = planningPaths(cwd).phases;
1482
+ if (!fs.existsSync(phasesDir)) {
1483
+ output({ synced: true, changes: [], dry_run: !!verify }, raw);
1484
+ return;
1485
+ }
1486
+
1487
+ // Scan all phases
1488
+ let entries;
1489
+ try {
1490
+ entries = fs.readdirSync(phasesDir, { withFileTypes: true })
1491
+ .filter(e => e.isDirectory())
1492
+ .map(e => e.name)
1493
+ .sort();
1494
+ } catch {
1495
+ output({ synced: true, changes: [], dry_run: !!verify }, raw);
1496
+ return;
1497
+ }
1498
+
1499
+ let totalDiskPlans = 0;
1500
+ let totalDiskSummaries = 0;
1501
+ let diskCompletedPhases = 0;
1502
+ let highestIncompletePhase = null;
1503
+ let highestIncompletePhaseNum = null;
1504
+ let highestIncompletePhaseplanCount = 0;
1505
+ let highestIncompletePhaseSummaryCount = 0;
1506
+
1507
+ for (const dir of entries) {
1508
+ const dirPath = path.join(phasesDir, dir);
1509
+ const { planCount: plans, summaryCount: summaries, completed } = scanPhasePlans(dirPath);
1510
+ totalDiskPlans += plans;
1511
+ totalDiskSummaries += summaries;
1512
+ if (completed) diskCompletedPhases++;
1513
+
1514
+ // Track the highest phase with incomplete plans (or any plans)
1515
+ const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
1516
+ if (phaseMatch && plans > 0) {
1517
+ if (summaries < plans) {
1518
+ // Incomplete phase — this is likely the current one
1519
+ highestIncompletePhase = dir;
1520
+ highestIncompletePhaseNum = phaseMatch[1];
1521
+ highestIncompletePhaseplanCount = plans;
1522
+ highestIncompletePhaseSummaryCount = summaries;
1523
+ } else if (!highestIncompletePhase) {
1524
+ // All complete, track as potential current
1525
+ highestIncompletePhase = dir;
1526
+ highestIncompletePhaseNum = phaseMatch[1];
1527
+ highestIncompletePhaseplanCount = plans;
1528
+ highestIncompletePhaseSummaryCount = summaries;
1529
+ }
1530
+ }
1531
+ }
1532
+
1533
+ // Determine total phases from ROADMAP (may be larger than realized disk dirs).
1534
+ // Mirrors the logic in buildStateFrontmatter so both report consistent percents (#3242 Bug B).
1535
+ let syncTotalPhases = null;
1536
+ try {
1537
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
1538
+ if (isDirInMilestone.phaseCount > 0) {
1539
+ syncTotalPhases = Math.max(entries.length, isDirInMilestone.phaseCount);
1540
+ } else {
1541
+ syncTotalPhases = entries.length;
1542
+ }
1543
+ } catch { /* intentionally empty */ }
1544
+
1545
+ // Sync Total Plans in Phase
1546
+ if (highestIncompletePhase) {
1547
+ const currentPlansField = stateExtractField(modified, 'Total Plans in Phase');
1548
+ if (currentPlansField && parseInt(currentPlansField, 10) !== highestIncompletePhaseplanCount) {
1549
+ changes.push(`Total Plans in Phase: ${currentPlansField} -> ${highestIncompletePhaseplanCount}`);
1550
+ const result = stateReplaceField(modified, 'Total Plans in Phase', String(highestIncompletePhaseplanCount));
1551
+ if (result) modified = result;
1552
+ }
1553
+ }
1554
+
1555
+ // Sync Progress — use shared helper so formula stays in one place (#3242 Bug B).
1556
+ // computeProgressPercent applies min(plan_fraction, phase_fraction) so unrealised
1557
+ // ROADMAP phases cap the reported percent rather than allowing a false 100%.
1558
+ const percent = (() => {
1559
+ const p = computeProgressPercent(totalDiskSummaries, totalDiskPlans, diskCompletedPhases, syncTotalPhases);
1560
+ return p !== null ? p : 0;
1561
+ })();
1562
+ const currentProgress = stateExtractField(modified, 'Progress');
1563
+ if (currentProgress) {
1564
+ const currentPercent = parseInt(currentProgress.replace(/[^\d]/g, ''), 10);
1565
+ if (currentPercent !== percent) {
1566
+ const barWidth = 10;
1567
+ const filled = Math.round(percent / 100 * barWidth);
1568
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
1569
+ const progressStr = `[${bar}] ${percent}%`;
1570
+ changes.push(`Progress: ${currentProgress} -> ${progressStr}`);
1571
+ const result = stateReplaceField(modified, 'Progress', progressStr);
1572
+ if (result) modified = result;
1573
+ }
1574
+ }
1575
+
1576
+ // Sync Last Activity
1577
+ const result = stateReplaceField(modified, 'Last Activity', today);
1578
+ if (result) {
1579
+ const oldActivity = stateExtractField(modified, 'Last Activity');
1580
+ if (oldActivity !== today) {
1581
+ changes.push(`Last Activity: ${oldActivity} -> ${today}`);
1582
+ }
1583
+ modified = result;
1584
+ }
1585
+
1586
+ if (verify) {
1587
+ output({ synced: false, changes, dry_run: true }, raw);
1588
+ return;
1589
+ }
1590
+
1591
+ if (changes.length > 0 || modified !== content) {
1592
+ writeStateMd(statePath, modified, cwd);
1593
+ }
1594
+
1595
+ output({ synced: true, changes, dry_run: false }, raw);
1596
+ }
1597
+
1598
+ /**
1599
+ * Prune old entries from STATE.md sections that grow unboundedly (#1970).
1600
+ * Moves decisions, recently-completed summaries, and resolved blockers
1601
+ * older than keepRecent phases to STATE-ARCHIVE.md.
1602
+ *
1603
+ * Options:
1604
+ * keepRecent: number of recent phases to retain (default: 3)
1605
+ * dryRun: if true, return what would be pruned without modifying STATE.md
1606
+ */
1607
+ function cmdStatePrune(cwd, options, raw) {
1608
+ const silent = !!options.silent;
1609
+ const emit = silent ? () => {} : (result, r, v) => output(result, r, v);
1610
+ const statePath = planningPaths(cwd).state;
1611
+ if (!fs.existsSync(statePath)) { emit({ error: 'STATE.md not found' }, raw); return; }
1612
+
1613
+ const keepRecent = parseInt(options.keepRecent, 10) || 3;
1614
+ const dryRun = !!options.dryRun;
1615
+ const currentPhaseRaw = stateExtractField(fs.readFileSync(statePath, 'utf-8'), 'Current Phase');
1616
+ const currentPhase = parseInt(currentPhaseRaw, 10) || 0;
1617
+ const cutoff = currentPhase - keepRecent;
1618
+
1619
+ if (cutoff <= 0) {
1620
+ emit({ pruned: false, reason: `Only ${currentPhase} phases — nothing to prune with --keep-recent ${keepRecent}` }, raw, 'false');
1621
+ return;
1622
+ }
1623
+
1624
+ const archivePath = path.join(path.dirname(statePath), 'STATE-ARCHIVE.md');
1625
+ const archived = [];
1626
+
1627
+ // Shared pruning logic applied to both dry-run and real passes.
1628
+ // Returns { newContent, archivedSections }.
1629
+ function prunePass(content) {
1630
+ const sections = [];
1631
+
1632
+ // Prune Decisions section: entries like "- [Phase N]: ..."
1633
+ const decisionPattern = /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
1634
+ const decMatch = content.match(decisionPattern);
1635
+ if (decMatch) {
1636
+ const lines = decMatch[2].split('\n');
1637
+ const keep = [];
1638
+ const archive = [];
1639
+ for (const line of lines) {
1640
+ const phaseMatch = line.match(/^\s*-\s*\[Phase\s+(\d+)/i);
1641
+ if (phaseMatch && parseInt(phaseMatch[1], 10) <= cutoff) {
1642
+ archive.push(line);
1643
+ } else {
1644
+ keep.push(line);
1645
+ }
1646
+ }
1647
+ if (archive.length > 0) {
1648
+ sections.push({ section: 'Decisions', count: archive.length, lines: archive });
1649
+ content = content.replace(decisionPattern, (_m, header) => `${header}${keep.join('\n')}`);
1650
+ }
1651
+ }
1652
+
1653
+ // Prune Recently Completed section: entries mentioning phase numbers
1654
+ const recentPattern = /(###?\s*Recently Completed\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
1655
+ const recMatch = content.match(recentPattern);
1656
+ if (recMatch) {
1657
+ const lines = recMatch[2].split('\n');
1658
+ const keep = [];
1659
+ const archive = [];
1660
+ for (const line of lines) {
1661
+ const phaseMatch = line.match(/Phase\s+(\d+)/i);
1662
+ if (phaseMatch && parseInt(phaseMatch[1], 10) <= cutoff) {
1663
+ archive.push(line);
1664
+ } else {
1665
+ keep.push(line);
1666
+ }
1667
+ }
1668
+ if (archive.length > 0) {
1669
+ sections.push({ section: 'Recently Completed', count: archive.length, lines: archive });
1670
+ content = content.replace(recentPattern, (_m, header) => `${header}${keep.join('\n')}`);
1671
+ }
1672
+ }
1673
+
1674
+ // Prune resolved blockers: lines marked as resolved (strikethrough ~~text~~
1675
+ // or "[RESOLVED]" prefix) with a phase reference older than cutoff
1676
+ const blockersPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Blockers\s*&\s*Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
1677
+ const blockersMatch = content.match(blockersPattern);
1678
+ if (blockersMatch) {
1679
+ const lines = blockersMatch[2].split('\n');
1680
+ const keep = [];
1681
+ const archive = [];
1682
+ for (const line of lines) {
1683
+ const isResolved = /~~.*~~|\[RESOLVED\]/i.test(line);
1684
+ const phaseMatch = line.match(/Phase\s+(\d+)/i);
1685
+ if (isResolved && phaseMatch && parseInt(phaseMatch[1], 10) <= cutoff) {
1686
+ archive.push(line);
1687
+ } else {
1688
+ keep.push(line);
1689
+ }
1690
+ }
1691
+ if (archive.length > 0) {
1692
+ sections.push({ section: 'Blockers (resolved)', count: archive.length, lines: archive });
1693
+ content = content.replace(blockersPattern, (_m, header) => `${header}${keep.join('\n')}`);
1694
+ }
1695
+ }
1696
+
1697
+ // Prune Performance Metrics table rows: keep only rows for phases > cutoff.
1698
+ // Preserves header rows (| Phase | ... and |---|...) and any prose around the table.
1699
+ const metricsPattern = /(###?\s*Performance Metrics\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
1700
+ const metricsMatch = content.match(metricsPattern);
1701
+ if (metricsMatch) {
1702
+ const sectionLines = metricsMatch[2].split('\n');
1703
+ const keep = [];
1704
+ const archive = [];
1705
+ for (const line of sectionLines) {
1706
+ // Table data row: starts with | followed by a number (phase)
1707
+ const tableRowMatch = line.match(/^\|\s*(\d+)\s*\|/);
1708
+ if (tableRowMatch) {
1709
+ const rowPhase = parseInt(tableRowMatch[1], 10);
1710
+ if (rowPhase <= cutoff) {
1711
+ archive.push(line);
1712
+ } else {
1713
+ keep.push(line);
1714
+ }
1715
+ } else {
1716
+ // Header row, separator row, or prose — always keep
1717
+ keep.push(line);
1718
+ }
1719
+ }
1720
+ if (archive.length > 0) {
1721
+ sections.push({ section: 'Performance Metrics', count: archive.length, lines: archive });
1722
+ content = content.replace(metricsPattern, (_m, header) => `${header}${keep.join('\n')}`);
1723
+ }
1724
+ }
1725
+
1726
+ return { newContent: content, archivedSections: sections };
1727
+ }
1728
+
1729
+ if (dryRun) {
1730
+ // Dry-run: compute what would be pruned without writing anything
1731
+ const content = fs.readFileSync(statePath, 'utf-8');
1732
+ const result = prunePass(content);
1733
+ const totalPruned = result.archivedSections.reduce((sum, s) => sum + s.count, 0);
1734
+ emit({
1735
+ pruned: false,
1736
+ dry_run: true,
1737
+ cutoff_phase: cutoff,
1738
+ keep_recent: keepRecent,
1739
+ sections: result.archivedSections.map(s => ({ section: s.section, entries_would_archive: s.count })),
1740
+ total_would_archive: totalPruned,
1741
+ note: totalPruned > 0 ? 'Run without --dry-run to actually prune' : 'Nothing to prune',
1742
+ }, raw, totalPruned > 0 ? 'true' : 'false');
1743
+ return;
1744
+ }
1745
+
1746
+ readModifyWriteStateMd(statePath, (content) => {
1747
+ const result = prunePass(content);
1748
+ archived.push(...result.archivedSections);
1749
+ return result.newContent;
1750
+ }, cwd);
1751
+
1752
+ // Write archived entries to STATE-ARCHIVE.md
1753
+ if (archived.length > 0) {
1754
+ const timestamp = new Date().toISOString().split('T')[0];
1755
+ let archiveContent = platformReadSync(archivePath);
1756
+ if (archiveContent === null) {
1757
+ archiveContent = '# STATE Archive\n\nPruned entries from STATE.md. Recoverable but no longer loaded into agent context.\n\n';
1758
+ }
1759
+ archiveContent += `## Pruned ${timestamp} (phases 1-${cutoff}, kept recent ${keepRecent})\n\n`;
1760
+ for (const section of archived) {
1761
+ archiveContent += `### ${section.section}\n\n${section.lines.join('\n')}\n\n`;
1762
+ }
1763
+ platformWriteSync(archivePath, archiveContent);
1764
+ }
1765
+
1766
+ const totalPruned = archived.reduce((sum, s) => sum + s.count, 0);
1767
+ emit({
1768
+ pruned: totalPruned > 0,
1769
+ cutoff_phase: cutoff,
1770
+ keep_recent: keepRecent,
1771
+ sections: archived.map(s => ({ section: s.section, entries_archived: s.count })),
1772
+ total_archived: totalPruned,
1773
+ archive_file: totalPruned > 0 ? 'STATE-ARCHIVE.md' : null,
1774
+ }, raw, totalPruned > 0 ? 'true' : 'false');
1775
+ }
1776
+
1777
+ /**
1778
+ * Mark the current phase as COMPLETE in STATE.md.
1779
+ * Updates Status, Last Activity, and the Current Position section to reflect
1780
+ * that the phase execution is finished and the project is ready for the next phase.
1781
+ * Implements the `sdd state complete-phase` subcommand (issue #2735).
1782
+ */
1783
+ function resolvePhaseIdForCompletePhase(content, overridePhase) {
1784
+ const candidate = overridePhase ||
1785
+ stateExtractField(content, 'Current Phase') ||
1786
+ stateExtractField(content, 'Phase') ||
1787
+ '';
1788
+
1789
+ // Accept canonical phase token only (e.g. 3, 03, 3A, 3.3, 10.2)
1790
+ const phaseMatch = String(candidate).match(/(\d+[A-Z]?(?:\.\d+)*)/i);
1791
+ return phaseMatch ? phaseMatch[1] : null;
1792
+ }
1793
+
1794
+ function cmdStateCompletePhase(cwd, raw, overridePhase) {
1795
+ const statePath = planningPaths(cwd).state;
1796
+ if (!fs.existsSync(statePath)) {
1797
+ output({ error: 'STATE.md not found' }, raw);
1798
+ return;
1799
+ }
1800
+
1801
+ const content = fs.readFileSync(statePath, 'utf-8');
1802
+ const resolvedPhase = resolvePhaseIdForCompletePhase(content, overridePhase);
1803
+ if (!resolvedPhase || /^phase$/i.test(resolvedPhase)) {
1804
+ output({ error: 'Unable to resolve current phase. Pass an explicit phase: state complete-phase --phase <N>' }, raw);
1805
+ return;
1806
+ }
1807
+
1808
+ // Idempotency guard (#3489). If STATE.md's canonical `Current Phase` field
1809
+ // already names a phase distinct from the one we are being asked to mark
1810
+ // complete, the project has advanced past the requested phase (e.g. a
1811
+ // follow-up phase was inserted, or the next phase began). Re-running
1812
+ // `state complete-phase --phase <N>` in that situation previously rolled
1813
+ // STATE.md back to <N>'s moment-of-completion — silently clobbering Status,
1814
+ // Last Activity, Last Activity Description, and the Current Position body.
1815
+ // The handler is now a no-op in that case so re-invocation from downstream
1816
+ // workflows cannot regress the project state.
1817
+ const existingCurrentPhaseRaw = stateExtractField(content, 'Current Phase') || '';
1818
+ const existingCurrentPhaseMatch = String(existingCurrentPhaseRaw).match(/(\d+[A-Z]?(?:\.\d+)*)/i);
1819
+ const existingCurrentPhase = existingCurrentPhaseMatch ? existingCurrentPhaseMatch[1] : null;
1820
+ if (existingCurrentPhase && existingCurrentPhase !== resolvedPhase) {
1821
+ output(
1822
+ { updated: [], phase: resolvedPhase, idempotent: true, note: 'phase already superseded; no-op' },
1823
+ raw,
1824
+ 'false',
1825
+ );
1826
+ return;
1827
+ }
1828
+
1829
+ const today = new Date().toISOString().split('T')[0];
1830
+ const updated = [];
1831
+
1832
+ readModifyWriteStateMd(statePath, (content) => {
1833
+ const currentPhase = resolvedPhase;
1834
+
1835
+ // Update Status field
1836
+ const statusValue = `Phase ${currentPhase} complete`;
1837
+ let result = stateReplaceField(content, 'Status', statusValue);
1838
+ if (result) { content = result; updated.push('Status'); }
1839
+
1840
+ // Update Last Activity date
1841
+ result = stateReplaceField(content, 'Last Activity', today);
1842
+ if (result) { content = result; updated.push('Last Activity'); }
1843
+
1844
+ // Update Last Activity Description
1845
+ const activityDesc = `Phase ${currentPhase} marked complete`;
1846
+ result = stateReplaceField(content, 'Last Activity Description', activityDesc);
1847
+ if (result) { content = result; updated.push('Last Activity Description'); }
1848
+
1849
+ // Update ## Current Position section
1850
+ const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
1851
+ const positionMatch = content.match(positionPattern);
1852
+ if (positionMatch) {
1853
+ const header = positionMatch[1];
1854
+ let posBody = positionMatch[2];
1855
+
1856
+ // Update Phase line to show COMPLETE
1857
+ const newPhase = `Phase: ${currentPhase} — COMPLETE`;
1858
+ if (/^Phase:/m.test(posBody)) {
1859
+ posBody = posBody.replace(/^Phase:.*$/m, newPhase);
1860
+ }
1861
+
1862
+ // Update Status line if present
1863
+ const newStatus = `Status: Phase ${currentPhase} complete`;
1864
+ if (/^Status:/m.test(posBody)) {
1865
+ posBody = posBody.replace(/^Status:.*$/m, newStatus);
1866
+ }
1867
+
1868
+ // Update Last activity line if present
1869
+ const newActivity = `Last activity: ${today} -- Phase ${currentPhase} marked complete`;
1870
+ if (/^Last activity:/im.test(posBody)) {
1871
+ posBody = posBody.replace(/^Last activity:.*$/im, newActivity);
1872
+ }
1873
+
1874
+ content = content.replace(positionPattern, () => `${header}${posBody}`);
1875
+ updated.push('Current Position');
1876
+ }
1877
+
1878
+ return content;
1879
+ }, cwd);
1880
+
1881
+ output(
1882
+ { updated, phase: resolvedPhase },
1883
+ raw,
1884
+ updated.length > 0 ? 'true' : 'false',
1885
+ );
1886
+ }
1887
+
1010
1888
  module.exports = {
1011
1889
  stateExtractField,
1012
1890
  stateReplaceField,
1013
1891
  stateReplaceFieldWithFallback,
1014
1892
  writeStateMd,
1893
+ readModifyWriteStateMd,
1894
+ updatePerformanceMetricsSection,
1015
1895
  cmdStateLoad,
1016
1896
  cmdStateGet,
1017
1897
  cmdStatePatch,
@@ -1026,6 +1906,12 @@ module.exports = {
1026
1906
  cmdStateSnapshot,
1027
1907
  cmdStateJson,
1028
1908
  cmdStateBeginPhase,
1909
+ cmdStatePlannedPhase,
1910
+ cmdStateCompletePhase,
1911
+ cmdStateValidate,
1912
+ cmdStateSync,
1913
+ cmdStatePrune,
1914
+ cmdStateMilestoneSwitch,
1029
1915
  cmdSignalWaiting,
1030
1916
  cmdSignalResume,
1031
1917
  };