@gong-ym/ai-spec-auto 0.2.13 → 0.2.15

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 (631) hide show
  1. package/.agents/commands/README.md +33 -33
  2. package/.agents/commands/claude/spec-start-review.md +88 -88
  3. package/.agents/commands/codex/spec-continue.md +74 -74
  4. package/.agents/commands/codex/spec-orchestrate.md +35 -35
  5. package/.agents/commands/codex/spec-start-review.md +88 -88
  6. package/.agents/commands/codex/spec-start.md +67 -67
  7. package/.agents/commands/codex/spec-status.md +22 -22
  8. package/.agents/commands/codex/spec-stop.md +29 -29
  9. package/.agents/commands/codex/spec-update.md +40 -40
  10. package/.agents/commands/common/branch-review.md +117 -117
  11. package/.agents/commands/common/project-init.md +25 -25
  12. package/.agents/commands/common/spec-continue.md +74 -74
  13. package/.agents/commands/common/spec-orchestrate.md +35 -35
  14. package/.agents/commands/common/spec-start-review.md +82 -82
  15. package/.agents/commands/common/spec-start.md +67 -67
  16. package/.agents/commands/common/spec-status.md +22 -22
  17. package/.agents/commands/common/spec-stop.md +29 -29
  18. package/.agents/commands/common/spec-update.md +60 -40
  19. package/.agents/commands/cursor/opsx-apply.md +55 -55
  20. package/.agents/commands/cursor/opsx-archive.md +48 -48
  21. package/.agents/commands/cursor/opsx-explore.md +45 -45
  22. package/.agents/commands/cursor/opsx-propose.md +59 -59
  23. package/.agents/commands/cursor/spec-continue.md +63 -63
  24. package/.agents/commands/cursor/spec-orchestrate.md +53 -53
  25. package/.agents/commands/cursor/spec-start-review.md +78 -78
  26. package/.agents/commands/cursor/spec-start.md +59 -59
  27. package/.agents/commands/cursor/spec-status.md +30 -30
  28. package/.agents/commands/cursor/spec-stop.md +29 -29
  29. package/.agents/commands/cursor/spec-update.md +41 -41
  30. package/.agents/flows/FRONTMATTER.md +263 -263
  31. package/.agents/flows/RUN_OUTPUT.md +263 -263
  32. package/.agents/flows/common/README.md +29 -29
  33. package/.agents/flows/common/bugfix-to-verification.md +95 -95
  34. package/.agents/flows/common/change-to-architecture-review.md +89 -89
  35. package/.agents/flows/common/change-to-release.md +94 -94
  36. package/.agents/flows/common/prd-to-delivery.md +184 -184
  37. package/.agents/flows/common/requirement-to-observability.md +97 -97
  38. package/.agents/orchestration/README.md +22 -22
  39. package/.agents/orchestration/expert-dispatch-spec.md +155 -155
  40. package/.agents/orchestration/expert-executor-spec.md +84 -84
  41. package/.agents/orchestration/expert-runtime-action-spec.md +73 -73
  42. package/.agents/orchestration/runtime-state-handoff-spec.md +264 -264
  43. package/.agents/orchestration/task-anchor-spec.md +212 -212
  44. package/.agents/orchestration/task-orchestrator-adapter-payload.md +153 -153
  45. package/.agents/orchestration/task-orchestrator-bootstrap-payload.md +145 -145
  46. package/.agents/orchestration/task-orchestrator-output-extractor-spec.md +93 -93
  47. package/.agents/orchestration/task-orchestrator-run-plan-template.md +312 -312
  48. package/.agents/orchestration/task-orchestrator-runtime-hooks.md +214 -214
  49. package/.agents/registry/README.md +63 -63
  50. package/.agents/registry/flows.json +125 -125
  51. package/.agents/registry/profiles.json +101 -101
  52. package/.agents/registry/roles.json +1265 -1265
  53. package/.agents/registry/rules.json +148 -148
  54. package/.agents/registry/scenario-packages.json +123 -123
  55. package/.agents/registry/skills.json +130 -130
  56. package/.agents/roles/INDEX.md +346 -346
  57. package/.agents/roles/common/README.md +76 -76
  58. package/.agents/roles/common/archive-change.md +80 -80
  59. package/.agents/roles/common/backend-implementer.md +92 -92
  60. package/.agents/roles/common/code-guardian.md +151 -151
  61. package/.agents/roles/common/frontend-implementer.md +146 -146
  62. package/.agents/roles/common/requirement-analyst.md +138 -138
  63. package/.agents/roles/common/task-orchestrator-routing.md +301 -301
  64. package/.agents/roles/common/task-orchestrator.md +224 -224
  65. package/.agents/roles/common/tooling-implementer.md +92 -92
  66. package/.agents/roles/domains/README.md +35 -35
  67. package/.agents/roles/domains/delivery/README.md +11 -11
  68. package/.agents/roles/domains/delivery/container-specialist.md +50 -50
  69. package/.agents/roles/domains/delivery/deployment-specialist.md +50 -50
  70. package/.agents/roles/domains/delivery/pipeline-specialist.md +50 -50
  71. package/.agents/roles/domains/demand-design/README.md +16 -16
  72. package/.agents/roles/domains/demand-design/api-contract-specialist.md +52 -52
  73. package/.agents/roles/domains/demand-design/design-collaborator.md +58 -58
  74. package/.agents/roles/domains/documentation/README.md +11 -11
  75. package/.agents/roles/domains/documentation/api-doc-specialist.md +50 -50
  76. package/.agents/roles/domains/documentation/component-doc-specialist.md +49 -49
  77. package/.agents/roles/domains/documentation/technical-writing-specialist.md +48 -48
  78. package/.agents/roles/domains/engineering/README.md +17 -17
  79. package/.agents/roles/domains/engineering/architecture-advisor.md +53 -53
  80. package/.agents/roles/domains/engineering/build-specialist.md +51 -51
  81. package/.agents/roles/domains/engineering/dependency-governor.md +52 -52
  82. package/.agents/roles/domains/governance/README.md +17 -17
  83. package/.agents/roles/domains/governance/api-governance-specialist.md +51 -51
  84. package/.agents/roles/domains/governance/lint-policy-specialist.md +49 -49
  85. package/.agents/roles/domains/governance/route-governance-specialist.md +52 -52
  86. package/.agents/roles/domains/observability/README.md +11 -11
  87. package/.agents/roles/domains/observability/error-tracker.md +50 -50
  88. package/.agents/roles/domains/observability/event-instrumentation-specialist.md +51 -51
  89. package/.agents/roles/domains/observability/rum-analyst.md +50 -50
  90. package/.agents/roles/domains/performance/README.md +11 -11
  91. package/.agents/roles/domains/performance/asset-optimizer.md +50 -50
  92. package/.agents/roles/domains/performance/performance-auditor.md +56 -56
  93. package/.agents/roles/domains/performance/vitals-analyst.md +50 -50
  94. package/.agents/roles/domains/security-a11y/README.md +11 -11
  95. package/.agents/roles/domains/security-a11y/a11y-auditor.md +50 -50
  96. package/.agents/roles/domains/security-a11y/aria-specialist.md +51 -51
  97. package/.agents/roles/domains/security-a11y/security-reviewer.md +49 -49
  98. package/.agents/roles/domains/testing/README.md +12 -12
  99. package/.agents/roles/domains/testing/coverage-analyst.md +50 -50
  100. package/.agents/roles/domains/testing/e2e-test-specialist.md +51 -51
  101. package/.agents/roles/domains/testing/unit-test-specialist.md +56 -56
  102. package/.agents/roles/domains/testing/verification-reviewer.md +67 -67
  103. package/.agents/rules/README.md +87 -87
  104. package/.agents/rules/common/02-/347/274/226/347/240/201/350/247/204/350/214/203.md +45 -45
  105. package/.agents/rules/common/08-/351/200/232/347/224/250/347/272/246/346/235/237.md +63 -63
  106. package/.agents/rules/common/10-/346/226/207/346/241/243/350/247/204/350/214/203.md +101 -101
  107. package/.agents/rules/common/12-Superpowers/346/211/247/350/241/214/350/247/204/350/214/203.md +46 -46
  108. package/.agents/rules/common/14-/345/256/241/350/256/241/346/261/207/346/212/245/350/247/204/350/214/203.md +107 -107
  109. package/.agents/rules/common/15-visual-gate-wait.md +90 -90
  110. package/.agents/rules/profiles/nestjs/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +27 -27
  111. package/.agents/rules/profiles/nestjs/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +20 -20
  112. package/.agents/rules/profiles/nestjs/04-/346/250/241/345/235/227/347/273/223/346/236/204/350/247/204/350/214/203.md +24 -24
  113. package/.agents/rules/profiles/nestjs/05-/346/216/245/345/217/243/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +24 -24
  114. package/.agents/rules/profiles/nestjs/06-/346/225/260/346/215/256/350/256/277/351/227/256/350/247/204/350/214/203.md +24 -24
  115. package/.agents/rules/profiles/nestjs/07-/351/205/215/347/275/256/344/270/216/350/277/220/350/241/214/346/227/266/350/247/204/350/214/203.md +20 -20
  116. package/.agents/rules/profiles/nestjs/09-/345/274/202/345/270/270/344/270/216/346/227/245/345/277/227/350/247/204/350/214/203.md +20 -20
  117. package/.agents/rules/profiles/nestjs/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +24 -24
  118. package/.agents/rules/profiles/nestjs/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +20 -20
  119. package/.agents/rules/profiles/node-tooling/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +30 -30
  120. package/.agents/rules/profiles/node-tooling/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +37 -37
  121. package/.agents/rules/profiles/node-tooling/04-CLI/344/270/216/346/250/241/345/235/227/350/247/204/350/214/203.md +42 -42
  122. package/.agents/rules/profiles/node-tooling/05-Contract/344/270/216Schema/350/247/204/350/214/203.md +42 -42
  123. package/.agents/rules/profiles/node-tooling/06-/350/277/220/350/241/214/346/227/266/346/226/207/344/273/266/350/247/204/350/214/203.md +30 -30
  124. package/.agents/rules/profiles/node-tooling/07-/346/227/245/345/277/227/344/270/216/351/224/231/350/257/257/345/244/204/347/220/206/350/247/204/350/214/203.md +60 -60
  125. package/.agents/rules/profiles/node-tooling/09-/350/204/232/346/234/254/344/270/216/345/205/245/345/217/243/350/247/204/350/214/203.md +45 -45
  126. package/.agents/rules/profiles/node-tooling/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +41 -41
  127. package/.agents/rules/profiles/node-tooling/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +55 -55
  128. package/.agents/rules/profiles/react/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +29 -29
  129. package/.agents/rules/profiles/react/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +104 -104
  130. package/.agents/rules/profiles/react/04-/347/273/204/344/273/266/350/247/204/350/214/203.md +46 -46
  131. package/.agents/rules/profiles/react/05-API/350/247/204/350/214/203.md +67 -67
  132. package/.agents/rules/profiles/react/06-/350/267/257/347/224/261/350/247/204/350/214/203.md +54 -54
  133. package/.agents/rules/profiles/react/07-/347/212/266/346/200/201/347/256/241/347/220/206.md +226 -226
  134. package/.agents/rules/profiles/react/09-/346/240/267/345/274/217/350/247/204/350/214/203.md +71 -71
  135. package/.agents/rules/profiles/react/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +80 -80
  136. package/.agents/rules/profiles/react/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +159 -159
  137. package/.agents/rules/profiles/springboot/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +31 -31
  138. package/.agents/rules/profiles/springboot/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +37 -37
  139. package/.agents/rules/profiles/springboot/04-/345/210/206/345/261/202/350/247/204/350/214/203.md +33 -33
  140. package/.agents/rules/profiles/springboot/05-/346/216/245/345/217/243/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +51 -51
  141. package/.agents/rules/profiles/springboot/06-/346/225/260/346/215/256/350/256/277/351/227/256/350/247/204/350/214/203.md +34 -34
  142. package/.agents/rules/profiles/springboot/07-/351/205/215/347/275/256/344/270/216/350/277/220/350/241/214/346/227/266/350/247/204/350/214/203.md +38 -38
  143. package/.agents/rules/profiles/springboot/09-/345/274/202/345/270/270/344/270/216/346/227/245/345/277/227/350/247/204/350/214/203.md +48 -48
  144. package/.agents/rules/profiles/springboot/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +43 -43
  145. package/.agents/rules/profiles/springboot/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +48 -48
  146. package/.agents/rules/profiles/vue/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +47 -47
  147. package/.agents/rules/profiles/vue/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +106 -106
  148. package/.agents/rules/profiles/vue/04-/347/273/204/344/273/266/350/247/204/350/214/203.md +61 -61
  149. package/.agents/rules/profiles/vue/05-API/350/247/204/350/214/203.md +67 -67
  150. package/.agents/rules/profiles/vue/06-/350/267/257/347/224/261/350/247/204/350/214/203.md +69 -69
  151. package/.agents/rules/profiles/vue/07-/347/212/266/346/200/201/347/256/241/347/220/206.md +93 -93
  152. package/.agents/rules/profiles/vue/09-/346/240/267/345/274/217/350/247/204/350/214/203.md +67 -67
  153. package/.agents/rules/profiles/vue/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +80 -80
  154. package/.agents/rules/profiles/vue/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +159 -159
  155. package/.agents/skills/README.md +171 -171
  156. package/.agents/skills/common/archive-change/SKILL.md +180 -180
  157. package/.agents/skills/common/branch-code-reviewer/SKILL.md +533 -459
  158. package/.agents/skills/common/branch-code-reviewer/references/business-risk-guide.md +293 -293
  159. package/.agents/skills/common/branch-code-reviewer/references/html-template-guide.md +121 -121
  160. package/.agents/skills/common/config-and-secret-scan/SKILL.md +99 -99
  161. package/.agents/skills/common/create-proposal/SKILL.md +192 -192
  162. package/.agents/skills/common/create-proposal/evals/evals.json +16 -16
  163. package/.agents/skills/common/create-proposal/evals/train_queries.json +18 -18
  164. package/.agents/skills/common/create-proposal/evals/validation_queries.json +18 -18
  165. package/.agents/skills/common/create-proposal/references/interaction-spec-template.md +42 -42
  166. package/.agents/skills/common/create-test/SKILL.md +292 -292
  167. package/.agents/skills/common/dependency-impact-graph/SKILL.md +80 -80
  168. package/.agents/skills/common/execute-task/SKILL.md +206 -206
  169. package/.agents/skills/common/execute-task/evals/evals.json +16 -16
  170. package/.agents/skills/common/execute-task/evals/train_queries.json +18 -18
  171. package/.agents/skills/common/execute-task/evals/validation_queries.json +18 -18
  172. package/.agents/skills/common/find-skills/SKILL.md +144 -144
  173. package/.agents/skills/common/install-ai-spec-auto/SKILL.md +260 -260
  174. package/.agents/skills/common/install-ai-spec-auto/evals/evals.json +17 -17
  175. package/.agents/skills/common/install-ai-spec-auto/evals/train_queries.json +18 -18
  176. package/.agents/skills/common/install-ai-spec-auto/evals/validation_queries.json +18 -18
  177. package/.agents/skills/common/project-init/SKILL.md +178 -178
  178. package/.agents/skills/common/project-init/evals/evals.json +16 -16
  179. package/.agents/skills/common/project-init/evals/train_queries.json +18 -18
  180. package/.agents/skills/common/project-init/evals/validation_queries.json +18 -18
  181. package/.agents/skills/common/project-init/references/custom-rule-generation.md +89 -89
  182. package/.agents/skills/common/project-init/references/deep-scan-rules.md +67 -67
  183. package/.agents/skills/common/project-init/references/output-contracts.md +71 -71
  184. package/.agents/skills/common/project-init/references/repo-fact-gathering.md +83 -83
  185. package/.agents/skills/common/project-init/references/scope-resolution.md +76 -76
  186. package/.agents/skills/common/project-init/scripts/inspect-project.js +112 -112
  187. package/.agents/skills/common/skill-creator/LICENSE.txt +201 -201
  188. package/.agents/skills/common/skill-creator/SKILL.md +370 -370
  189. package/.agents/skills/common/skill-creator/evals/evals.json +16 -16
  190. package/.agents/skills/common/skill-creator/evals/train_queries.json +18 -18
  191. package/.agents/skills/common/skill-creator/evals/validation_queries.json +18 -18
  192. package/.agents/skills/common/skill-creator/references/output-patterns.md +82 -82
  193. package/.agents/skills/common/skill-creator/references/workflows.md +27 -27
  194. package/.agents/skills/common/skill-creator/scripts/init_skill.py +209 -209
  195. package/.agents/skills/common/skill-creator/scripts/package_skill.py +110 -110
  196. package/.agents/skills/common/skill-creator/scripts/quick_validate.py +51 -51
  197. package/.agents/skills/common/skill-optimizer/SKILL.md +102 -102
  198. package/.agents/skills/common/skill-optimizer/evals/evals.json +16 -16
  199. package/.agents/skills/common/skill-optimizer/evals/train_queries.json +18 -18
  200. package/.agents/skills/common/skill-optimizer/evals/validation_queries.json +18 -18
  201. package/.agents/skills/common/skill-optimizer/references/design-patterns.md +26 -26
  202. package/.agents/skills/common/skill-optimizer/references/review-checklist.md +22 -22
  203. package/.agents/skills/common/using-superpowers/SKILL.md +151 -151
  204. package/.agents/skills/common/wait-for-gate-signal/SKILL.md +85 -85
  205. package/.agents/skills/domains/README.md +19 -19
  206. package/.agents/skills/domains/ui-ux-pro-max/SKILL.md +58 -58
  207. package/.agents/skills/domains/web/design-analysis/SKILL.md +89 -89
  208. package/.agents/skills/domains/web/design-analysis/rules/analysis-order.md +61 -61
  209. package/.agents/skills/domains/web/design-analysis/rules/analysis-priorities.md +136 -136
  210. package/.agents/skills/domains/web/design-analysis/rules/checklist-common-misses.md +107 -107
  211. package/.agents/skills/domains/web/design-analysis/rules/implementation-common-errors.md +204 -204
  212. package/.agents/skills/domains/web/design-analysis/rules/implementation-guidelines.md +211 -211
  213. package/.agents/skills/domains/web/design-analysis/rules/output-analysis-checklist.md +247 -247
  214. package/.agents/skills/domains/web/design-analysis/rules/tools-design-guidelines.md +108 -108
  215. package/.agents/skills/domains/web/design-analysis/rules/workflow-element-extraction.md +162 -162
  216. package/.agents/skills/domains/web/design-analysis/rules/workflow-layout-map.md +131 -131
  217. package/.agents/skills/domains/web/design-analysis/rules/workflow-output-checklist.md +70 -70
  218. package/.agents/skills/domains/web/design-analysis/rules/workflow-style-summary.md +91 -91
  219. package/.agents/skills/domains/web/route-permission-map/SKILL.md +103 -103
  220. package/.agents/skills/domains/web/ui-verification/SKILL.md +114 -114
  221. package/.agents/skills/domains/web/ui-verification/evals/evals.json +16 -16
  222. package/.agents/skills/domains/web/ui-verification/evals/train_queries.json +18 -18
  223. package/.agents/skills/domains/web/ui-verification/evals/validation_queries.json +18 -18
  224. package/.agents/skills/domains/web/ui-verification/rules/comparison-content-image.md +34 -34
  225. package/.agents/skills/domains/web/ui-verification/rules/comparison-content-text.md +30 -30
  226. package/.agents/skills/domains/web/ui-verification/rules/comparison-hierarchy.md +33 -33
  227. package/.agents/skills/domains/web/ui-verification/rules/comparison-layout.md +35 -35
  228. package/.agents/skills/domains/web/ui-verification/rules/errors-alignment.md +42 -42
  229. package/.agents/skills/domains/web/ui-verification/rules/errors-button-dimensions.md +28 -28
  230. package/.agents/skills/domains/web/ui-verification/rules/errors-button-position.md +25 -25
  231. package/.agents/skills/domains/web/ui-verification/rules/errors-css-priority.md +50 -50
  232. package/.agents/skills/domains/web/ui-verification/rules/errors-flex-column-width.md +46 -46
  233. package/.agents/skills/domains/web/ui-verification/rules/errors-flex-layout.md +46 -46
  234. package/.agents/skills/domains/web/ui-verification/rules/errors-grid-container-width.md +44 -44
  235. package/.agents/skills/domains/web/ui-verification/rules/errors-page-container-width.md +39 -39
  236. package/.agents/skills/domains/web/ui-verification/rules/tools-browser-navigation.md +53 -53
  237. package/.agents/skills/domains/web/ui-verification/rules/tools-design-guidelines.md +53 -53
  238. package/.agents/skills/domains/web/ui-verification/rules/workflow-checklist.md +27 -27
  239. package/.agents/skills/domains/web/ui-verification/rules/workflow-problem-list.md +56 -56
  240. package/.agents/skills/domains/web/ui-verification/rules/workflow-reflection.md +44 -44
  241. package/.agents/skills/domains/web/ui-verification/rules/writing-alignment.md +44 -44
  242. package/.agents/skills/domains/web/ui-verification/rules/writing-element-completeness.md +63 -63
  243. package/.agents/skills/domains/web/ui-verification/rules/writing-list-layout.md +75 -75
  244. package/.agents/skills/domains/web/ui-verification/rules/writing-page-container-width.md +37 -37
  245. package/.agents/skills/domains/web/web-design-guidelines/SKILL.md +40 -40
  246. package/.agents/skills/profiles/nestjs/README.md +4 -4
  247. package/.agents/skills/profiles/node-tooling/README.md +9 -9
  248. package/.agents/skills/profiles/react/create-api/SKILL.md +145 -145
  249. package/.agents/skills/profiles/react/create-component/SKILL.md +160 -160
  250. package/.agents/skills/profiles/react/create-route/SKILL.md +168 -168
  251. package/.agents/skills/profiles/react/create-store/SKILL.md +262 -262
  252. package/.agents/skills/profiles/react/theme-variables/SKILL.md +82 -82
  253. package/.agents/skills/profiles/react/vercel-composition-patterns/AGENTS.md +899 -899
  254. package/.agents/skills/profiles/react/vercel-composition-patterns/SKILL.md +81 -81
  255. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -100
  256. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/architecture-compound-components.md +112 -112
  257. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -87
  258. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -100
  259. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-context-interface.md +191 -191
  260. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -113
  261. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-lift-state.md +125 -125
  262. package/.agents/skills/profiles/react/vercel-react-best-practices/AGENTS.md +2934 -2934
  263. package/.agents/skills/profiles/react/vercel-react-best-practices/SKILL.md +136 -136
  264. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -55
  265. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-init-once.md +42 -42
  266. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-use-latest.md +39 -39
  267. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-api-routes.md +38 -38
  268. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-defer-await.md +80 -80
  269. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-dependencies.md +51 -51
  270. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-parallel.md +28 -28
  271. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -99
  272. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -59
  273. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-conditional.md +31 -31
  274. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -49
  275. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -35
  276. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-preload.md +50 -50
  277. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-event-listeners.md +74 -74
  278. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -71
  279. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -48
  280. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-swr-dedup.md +56 -56
  281. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -107
  282. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-function-results.md +80 -80
  283. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-property-access.md +28 -28
  284. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-storage.md +70 -70
  285. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-combine-iterations.md +32 -32
  286. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-early-exit.md +50 -50
  287. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -45
  288. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-index-maps.md +37 -37
  289. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-length-check-first.md +49 -49
  290. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-min-max-loop.md +82 -82
  291. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -24
  292. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -57
  293. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-activity.md +26 -26
  294. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -47
  295. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -40
  296. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -38
  297. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -46
  298. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -82
  299. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -30
  300. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -28
  301. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -75
  302. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -39
  303. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-dependencies.md +45 -45
  304. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -40
  305. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-derived-state.md +29 -29
  306. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -74
  307. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -58
  308. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -38
  309. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-memo.md +44 -44
  310. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -45
  311. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -35
  312. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-transitions.md +40 -40
  313. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -73
  314. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -73
  315. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-auth-actions.md +96 -96
  316. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-cache-lru.md +41 -41
  317. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-cache-react.md +76 -76
  318. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-dedup-props.md +65 -65
  319. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -83
  320. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-serialization.md +38 -38
  321. package/.agents/skills/profiles/springboot/README.md +10 -10
  322. package/.agents/skills/profiles/vue/create-api/SKILL.md +105 -105
  323. package/.agents/skills/profiles/vue/create-component/SKILL.md +76 -76
  324. package/.agents/skills/profiles/vue/create-route/SKILL.md +141 -141
  325. package/.agents/skills/profiles/vue/create-store/SKILL.md +97 -97
  326. package/.agents/skills/profiles/vue/create-view/SKILL.md +81 -81
  327. package/.agents/skills/profiles/vue/theme-variables/SKILL.md +73 -73
  328. package/.agents/skills/profiles/vue/vue-best-practices/SKILL.md +166 -166
  329. package/.agents/skills/profiles/vue/vue-best-practices/references/animation-class-based-technique.md +254 -254
  330. package/.agents/skills/profiles/vue/vue-best-practices/references/animation-state-driven-technique.md +291 -291
  331. package/.agents/skills/profiles/vue/vue-best-practices/references/component-async.md +97 -97
  332. package/.agents/skills/profiles/vue/vue-best-practices/references/component-data-flow.md +307 -307
  333. package/.agents/skills/profiles/vue/vue-best-practices/references/component-fallthrough-attrs.md +174 -174
  334. package/.agents/skills/profiles/vue/vue-best-practices/references/component-keep-alive.md +137 -137
  335. package/.agents/skills/profiles/vue/vue-best-practices/references/component-slots.md +216 -216
  336. package/.agents/skills/profiles/vue/vue-best-practices/references/component-suspense.md +228 -228
  337. package/.agents/skills/profiles/vue/vue-best-practices/references/component-teleport.md +108 -108
  338. package/.agents/skills/profiles/vue/vue-best-practices/references/component-transition-group.md +128 -128
  339. package/.agents/skills/profiles/vue/vue-best-practices/references/component-transition.md +125 -125
  340. package/.agents/skills/profiles/vue/vue-best-practices/references/composables.md +290 -290
  341. package/.agents/skills/profiles/vue/vue-best-practices/references/directives.md +162 -162
  342. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -159
  343. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -182
  344. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-virtualize-large-lists.md +187 -187
  345. package/.agents/skills/profiles/vue/vue-best-practices/references/plugins.md +166 -166
  346. package/.agents/skills/profiles/vue/vue-best-practices/references/reactivity.md +344 -344
  347. package/.agents/skills/profiles/vue/vue-best-practices/references/render-functions.md +201 -201
  348. package/.agents/skills/profiles/vue/vue-best-practices/references/sfc.md +310 -310
  349. package/.agents/skills/profiles/vue/vue-best-practices/references/state-management.md +135 -135
  350. package/.agents/skills/profiles/vue/vue-best-practices/references/updated-hook-performance.md +187 -187
  351. package/.agents/templates/common/README.md +23 -23
  352. package/.agents/templates/common/bugfix.md +22 -22
  353. package/.agents/templates/common/create-expert-package.md +458 -458
  354. package/.agents/templates/common/mock-page.md +28 -28
  355. package/.agents/templates/common/new-component.md +25 -25
  356. package/.agents/templates/common/new-page.md +31 -31
  357. package/.cursor/mcp.json +35 -35
  358. package/.qoder/mcp.json +26 -26
  359. package/bin/archive-change.js +560 -474
  360. package/bin/check-command.js +62 -62
  361. package/bin/cli.js +0 -0
  362. package/bin/command-template-renderer.js +40 -40
  363. package/bin/context-command.js +102 -102
  364. package/bin/demo-runtime-smoke.js +760 -760
  365. package/bin/execution-semantics.js +821 -821
  366. package/bin/executor-command.js +93 -93
  367. package/bin/expert-dispatch.js +334 -334
  368. package/bin/expert-executor.js +1148 -1148
  369. package/bin/guard-command.js +52 -52
  370. package/bin/hub-command.js +876 -876
  371. package/bin/ide-command.js +242 -242
  372. package/bin/init-command.js +193 -193
  373. package/bin/install-workflow.js +35 -3
  374. package/bin/manifest-export.js +34 -34
  375. package/bin/profile-registry.js +90 -90
  376. package/bin/protocol-workflow.js +452 -446
  377. package/bin/repair-command.js +161 -161
  378. package/bin/repo-map.js +177 -177
  379. package/bin/report-command.js +236 -236
  380. package/bin/runtime-bootstrap.js +428 -428
  381. package/bin/runtime-embedded.js +101 -101
  382. package/bin/runtime-fallback.js +106 -106
  383. package/bin/runtime-launcher.js +116 -116
  384. package/bin/runtime-paths.js +177 -177
  385. package/bin/runtime-registry.js +289 -289
  386. package/bin/runtime-state.js +2541 -2541
  387. package/bin/scan.js +96 -96
  388. package/bin/self-upgrade.js +206 -206
  389. package/bin/skill-spec-validator.js +457 -457
  390. package/bin/spec-command.js +366 -366
  391. package/bin/superpowers.js +384 -384
  392. package/bin/sync-command.js +59 -59
  393. package/bin/sync.js +1904 -1904
  394. package/bin/task-orchestrator-adapter.js +341 -341
  395. package/bin/task-orchestrator-extractor.js +274 -274
  396. package/bin/task-orchestrator-runner.js +1208 -1208
  397. package/bin/telemetry/README.md +66 -66
  398. package/bin/telemetry/aspect.js +153 -153
  399. package/bin/telemetry/collect.js +67 -67
  400. package/bin/telemetry/config.js +114 -114
  401. package/bin/telemetry/defaults.json +5 -5
  402. package/bin/telemetry/healthcheck.js +195 -195
  403. package/bin/telemetry/identity.js +53 -53
  404. package/bin/telemetry/index.js +25 -25
  405. package/bin/telemetry/reporter.js +83 -83
  406. package/bin/telemetry/safe.js +39 -39
  407. package/bin/validate-registry.js +740 -740
  408. package/bin/visual-bridge-config.js +117 -117
  409. package/bin/visual-bridge.js +287 -287
  410. package/bin/visual-command.js +432 -432
  411. package/bin/worktree-command.js +194 -194
  412. package/configs/common/.editorconfig +15 -15
  413. package/configs/common/.husky/commit-msg +4 -4
  414. package/configs/common/.husky/pre-commit +4 -4
  415. package/configs/common/.lintstagedrc +11 -11
  416. package/configs/common/.prettierignore +11 -11
  417. package/configs/common/.prettierrc.json +11 -11
  418. package/configs/common/.stylelintignore +14 -14
  419. package/configs/common/.stylelintrc.json +21 -21
  420. package/configs/common/commitlint.config.js +3 -3
  421. package/configs/profiles/nestjs/.gitkeep +1 -1
  422. package/configs/profiles/node-tooling/.gitkeep +1 -1
  423. package/configs/profiles/react/.eslintignore +6 -6
  424. package/configs/profiles/react/.eslintrc.js +16 -16
  425. package/configs/profiles/react/.stylelintrc.json +18 -18
  426. package/configs/profiles/springboot/.gitkeep +1 -1
  427. package/configs/profiles/vue/.eslintignore +6 -6
  428. package/configs/profiles/vue/.eslintrc.cjs +17 -17
  429. package/contracts/README.md +28 -28
  430. package/contracts/fixtures/asset-package.fixture.json +26 -26
  431. package/contracts/fixtures/asset-usage-feedback.fixture.json +14 -14
  432. package/contracts/fixtures/evidence-report.fixture.json +28 -28
  433. package/contracts/fixtures/manifest.fixture.json +20 -20
  434. package/contracts/fixtures/run-event.fixture.json +15 -15
  435. package/contracts/schemas/asset-package.schema.json +76 -76
  436. package/contracts/schemas/asset-usage-feedback.schema.json +57 -57
  437. package/contracts/schemas/evidence-report.schema.json +60 -60
  438. package/contracts/schemas/manifest.schema.json +63 -63
  439. package/contracts/schemas/run-event.schema.json +72 -72
  440. package/install.ps1 +35 -35
  441. package/install.sh +17 -17
  442. package/internal/ai-protocol-workflow.js +5824 -5600
  443. package/internal/hub-client.js +98 -98
  444. package/internal/hub-sync-selection.js +69 -69
  445. package/internal/visual-hooks/README.md +481 -481
  446. package/internal/visual-hooks/config-loader.js +218 -218
  447. package/internal/visual-hooks/control-puller.js +206 -206
  448. package/internal/visual-hooks/gate-signal.js +150 -150
  449. package/internal/visual-hooks/inbox-consumer.js +469 -469
  450. package/internal/visual-hooks/index.js +197 -197
  451. package/internal/visual-hooks/push-client.js +189 -189
  452. package/internal/visual-hooks/receipt-pusher.js +176 -176
  453. package/internal/visual-hooks/runtime-state-pusher.js +128 -128
  454. package/openspec/config.yaml.template +52 -52
  455. package/openspec/schemas/expert-delivery/schema.yaml +68 -68
  456. package/openspec/schemas/expert-delivery/templates/checklist.md +39 -39
  457. package/openspec/schemas/expert-delivery/templates/design.md +61 -61
  458. package/openspec/schemas/expert-delivery/templates/iterations.md +25 -25
  459. package/openspec/schemas/expert-delivery/templates/proposal.md +45 -45
  460. package/openspec/schemas/expert-delivery/templates/spec.md +29 -29
  461. package/openspec/schemas/expert-delivery/templates/tasks.md +24 -24
  462. package/package.json +1 -1
  463. package/scripts/acceptance-zero-intrusion.sh +168 -168
  464. package/scripts/hub-sync-assets.config.example.json +296 -296
  465. package/scripts/hub-sync-assets.js +2038 -2038
  466. package/scripts/local-verify.sh +280 -280
  467. package/scripts/post-publish-auto-fix-check.js +404 -404
  468. package/scripts/post-publish-verify.sh +175 -175
  469. package/scripts/setup-cursor-manual-test.sh +107 -107
  470. package/scripts/setup-cursor-spec-archive-test.sh +111 -111
  471. package/scripts/setup-visual-integration.sh +225 -225
  472. package/scripts/test-integration.sh +176 -176
  473. package/scripts/update-test-project.sh +93 -93
  474. package/scripts/upload-four-web.sh +57 -57
  475. package/scripts/verify-install-ps1-bom.js +26 -26
  476. package/src/agent/agent-context.js +259 -259
  477. package/src/agent/agent-profile.js +185 -185
  478. package/src/agent/agent-templates.js +161 -161
  479. package/src/agent/agent-types.js +108 -108
  480. package/src/agent/collaboration-protocol.js +333 -333
  481. package/src/agent/conflict-handler.js +364 -364
  482. package/src/agent/file-permission.js +121 -121
  483. package/src/agent/index.js +38 -38
  484. package/src/agent/permission-audit.js +151 -151
  485. package/src/agent/review-repair-loop.js +270 -270
  486. package/src/agent/tool-permission.js +101 -101
  487. package/src/asset/asset-dependency.js +322 -322
  488. package/src/asset/asset-feedback.js +350 -350
  489. package/src/asset/asset-fork.js +300 -300
  490. package/src/asset/asset-install.js +278 -278
  491. package/src/asset/asset-installer.js +497 -497
  492. package/src/asset/asset-lifecycle.js +324 -324
  493. package/src/asset/asset-manager.js +245 -245
  494. package/src/asset/asset-package-manager.js +349 -349
  495. package/src/asset/asset-package.js +186 -186
  496. package/src/asset/asset-quality.js +262 -262
  497. package/src/asset/asset-registry.js +387 -387
  498. package/src/asset/asset-version.js +293 -293
  499. package/src/asset/index.js +86 -86
  500. package/src/cache/agent-profile-cache.js +59 -59
  501. package/src/cache/asset-cache.js +63 -63
  502. package/src/cache/global-cache.js +61 -61
  503. package/src/cache/manifest-cache.js +30 -30
  504. package/src/check/check-service.js +32 -32
  505. package/src/config/config-layer.js +343 -343
  506. package/src/config/config-loader.js +60 -60
  507. package/src/config/defaults.js +49 -49
  508. package/src/connectors/hub/asset-package.js +72 -72
  509. package/src/connectors/hub/asset-usage-feedback.js +46 -46
  510. package/src/connectors/hub/hub-connector.js +44 -44
  511. package/src/connectors/hub/index.js +21 -21
  512. package/src/connectors/visual/evidence-report.js +49 -49
  513. package/src/connectors/visual/index.js +15 -15
  514. package/src/connectors/visual/queue.js +41 -41
  515. package/src/connectors/visual/run-event.js +81 -81
  516. package/src/connectors/visual/visual-connector.js +77 -77
  517. package/src/context/context-budget.js +59 -59
  518. package/src/context/context-builder.js +285 -285
  519. package/src/context/context-loader.js +116 -116
  520. package/src/context/context-planner.js +158 -158
  521. package/src/context/types.js +96 -96
  522. package/src/contracts/index.js +63 -63
  523. package/src/executor/executor-registry.js +78 -78
  524. package/src/executor/executor-result-parser.js +44 -44
  525. package/src/executor/executor-runner.js +141 -141
  526. package/src/executor/executor-selector.js +139 -139
  527. package/src/executor/executor-timeout.js +36 -36
  528. package/src/executor/providers/base-provider-utils.js +189 -189
  529. package/src/executor/providers/claude-code-executor-provider.js +128 -128
  530. package/src/executor/providers/codex-executor-provider.js +126 -126
  531. package/src/executor/providers/cursor-executor-provider.js +99 -99
  532. package/src/executor/types.js +137 -137
  533. package/src/git/branch-manager.js +71 -71
  534. package/src/git/dirty-checker.js +43 -43
  535. package/src/git/dirty-strategy-handler.js +29 -29
  536. package/src/git/git-command.js +37 -37
  537. package/src/git/git-repository-detector.js +45 -45
  538. package/src/git/multi-repo-worktree-planner.js +88 -88
  539. package/src/git/policy.js +19 -19
  540. package/src/git/strategies/block-dirty-strategy.js +34 -34
  541. package/src/git/strategies/ignore-dirty-strategy.js +33 -33
  542. package/src/git/strategies/patch-snapshot-strategy.js +53 -53
  543. package/src/git/strategies/wip-commit-strategy.js +38 -38
  544. package/src/git/types.js +71 -71
  545. package/src/git/worktree-manager.js +85 -85
  546. package/src/governance/asset-review.js +351 -351
  547. package/src/governance/audit-log.js +368 -368
  548. package/src/governance/gray-release.js +312 -312
  549. package/src/governance/index.js +31 -31
  550. package/src/governance/policy-types.js +56 -56
  551. package/src/governance/rbac-types.js +171 -171
  552. package/src/governance/rbac.js +382 -382
  553. package/src/governance/rollback.js +360 -360
  554. package/src/governance/security-policy.js +354 -354
  555. package/src/hook/hook-config-writer.js +125 -125
  556. package/src/hub/hub-client.js +186 -186
  557. package/src/hub/hub-config.js +39 -39
  558. package/src/hub/project-facts.js +31 -31
  559. package/src/hub/runtime-feedback-reporter.js +55 -55
  560. package/src/ide/adapters/adapter-protocol.js +385 -385
  561. package/src/ide/adapters/claude-adapter.js +419 -419
  562. package/src/ide/adapters/codex-adapter.js +60 -60
  563. package/src/ide/adapters/cursor-adapter.js +484 -484
  564. package/src/ide/adapters/index.js +24 -24
  565. package/src/ide/anchors/markdown-anchor-writer.js +152 -152
  566. package/src/ide/ide-service.js +270 -270
  567. package/src/ide/ide-types.js +94 -94
  568. package/src/ide/links/link-mode-resolver.js +160 -160
  569. package/src/ide/registry/ide-registry-builder.js +165 -165
  570. package/src/incident/incident-writer.js +47 -47
  571. package/src/incident/types.js +22 -22
  572. package/src/init/ide-linker.js +126 -126
  573. package/src/init/ide-pointer-injector.js +75 -75
  574. package/src/init/init-applier.js +197 -197
  575. package/src/init/init-plan.js +294 -294
  576. package/src/init/init-service.js +65 -65
  577. package/src/init/manifest-installer.js +302 -302
  578. package/src/init/types.js +26 -26
  579. package/src/project/config-writer.js +83 -83
  580. package/src/project/context-index-writer.js +82 -82
  581. package/src/project/json-utils.js +72 -72
  582. package/src/project/local-state-writer.js +50 -50
  583. package/src/project/lock-file-writer.js +98 -98
  584. package/src/project/manifest-writer.js +126 -126
  585. package/src/project/policy-config-writer.js +91 -91
  586. package/src/project/project-config-writer.js +74 -74
  587. package/src/project/project-files.js +39 -39
  588. package/src/project/registry-index-writer.js +43 -43
  589. package/src/project/workspace-config-writer.js +63 -63
  590. package/src/run/index.js +11 -11
  591. package/src/run/run-id.js +32 -32
  592. package/src/run/run-service.js +269 -269
  593. package/src/run/run-store.js +80 -80
  594. package/src/scanner/aggregator/detection-aggregator.js +23 -23
  595. package/src/scanner/boundary/boundary-resolver.js +229 -229
  596. package/src/scanner/detectors/detector-registry.js +44 -44
  597. package/src/scanner/detectors/fastapi-detector.js +46 -46
  598. package/src/scanner/detectors/go-detector.js +46 -46
  599. package/src/scanner/detectors/nestjs-detector.js +57 -57
  600. package/src/scanner/detectors/nextjs-detector.js +52 -52
  601. package/src/scanner/detectors/react-vite-detector.js +52 -52
  602. package/src/scanner/detectors/react-webpack-detector.js +57 -57
  603. package/src/scanner/detectors/springboot-detector.js +46 -46
  604. package/src/scanner/detectors/springcloud-detector.js +46 -46
  605. package/src/scanner/detectors/springmvc-detector.js +46 -46
  606. package/src/scanner/detectors/vue-vite-detector.js +52 -52
  607. package/src/scanner/engine.js +72 -72
  608. package/src/scanner/facts/fact-extractor.js +211 -211
  609. package/src/scanner/types.js +30 -30
  610. package/src/security/asset-tamper-checker.js +188 -188
  611. package/src/security/checksum.js +40 -40
  612. package/src/spec/spec-writer.js +302 -302
  613. package/src/state-machine/circuit-breaker.js +112 -112
  614. package/src/state-machine/escape-hatch.js +49 -49
  615. package/src/state-machine/stage-runner.js +281 -281
  616. package/src/state-machine/state-machine.js +24 -24
  617. package/src/state-machine/transition-guard.js +36 -36
  618. package/src/state-machine/types.js +37 -37
  619. package/src/sync/sync-service.js +192 -192
  620. package/src/visual/agent-visual.js +142 -142
  621. package/src/visual/event-gateway.js +357 -357
  622. package/src/visual/event-mapper.js +128 -128
  623. package/src/visual/hook-dashboard.js +216 -216
  624. package/src/visual/index.js +27 -27
  625. package/src/visual/metrics.js +287 -287
  626. package/src/visual/privacy-filter.js +100 -100
  627. package/src/visual/risk-board.js +252 -252
  628. package/src/visual/timeline.js +245 -245
  629. package/src/visual/visual-client.js +94 -94
  630. package/src/visual/visual-config.js +40 -40
  631. package/src/visual/visual-reporter.js +88 -88
@@ -1,2541 +1,2541 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { spawnSync } = require('child_process');
5
- const {
6
- resolveRuntimePaths,
7
- getExistingPath,
8
- getCandidatePaths,
9
- shouldPersistHistory,
10
- shouldPersistCheckpoints,
11
- } = require('./runtime-paths');
12
- const { syncRepoMap } = require('./repo-map');
13
-
14
- function printUsage() {
15
- console.log(`Usage:
16
- ai-spec-auto runtime-state init --run-plan <file> [options]
17
- ai-spec-auto runtime-state bootstrap --payload <file> [options]
18
- ai-spec-auto runtime-state bootstrap --stdin [options]
19
- ai-spec-auto runtime-state handoff --to-role <role> [options]
20
- ai-spec-auto runtime-state approve [options]
21
- ai-spec-auto runtime-state pause [options]
22
- ai-spec-auto runtime-state resume [options]
23
- ai-spec-auto runtime-state restore --checkpoint <file> [options]
24
- ai-spec-auto runtime-state gate-blocked [options]
25
- ai-spec-auto runtime-state complete [options]
26
- ai-spec-auto runtime-state fail [options]
27
- ai-spec-auto runtime-state cancel [options]
28
- ai-spec-auto runtime-state status [options]
29
-
30
- Options:
31
- --target <dir> Target project directory (default: .)
32
- --run-plan <file> Path to run-plan JSON file
33
- --task-anchor <file> Optional path to task-anchor JSON file
34
- --payload <file> Path to task-orchestrator bootstrap payload JSON file
35
- --stdin Read bootstrap payload JSON from stdin
36
- --run-id <id> Override generated run id
37
- --to-role <role> Target role for handoff update
38
- --next-role <role> Next role after current handoff
39
- --from-role <role> Explicit source role override
40
- --gate <id> Expected approval gate id
41
- --checkpoint <file> Restore source checkpoint JSON file
42
- --pending-gate <id> Pending approval gate id
43
- --clear-pending-gate Clear current pending gate
44
- --blocked-by-role <id> Role that raised the current gate
45
- --resume-to-role <id> Role to resume into after approval
46
- --required-user-action <text>
47
- Explicit user action required by the gate
48
- --blocked-reason <text> Human-readable reason for the gate
49
- --message <text> Event message override
50
- --error <text> Failure detail appended to errors list
51
- --event-type <type> Event type override (default: role-handoff)
52
- --status <status> planned | running | paused | waiting-confirm | waiting-approval | blocked | success | failed | cancelled
53
- --trigger-source <src> Trigger source (default: ide-skill)
54
- --entry <entry> Entry role (default: task-orchestrator)
55
- --raw-input <text> Raw user input override
56
- --change-id <id> Change id override
57
- --change-impact <kind> patch | scope-delta | re-scope | archive-fix | followup-patch
58
- --reconcile-strategy <strategy>
59
- in-place | rewind-to-requirement | rewind-to-frontend | rewind-to-guardian | suggest-new-change | followup-patch
60
- --reopen-reason <text> Human-readable reopen / repair reason
61
- --parent-change-id <id> Parent change id for follow-up patch runs
62
- --artifacts-to-update <items>
63
- Comma-separated artifact hints to update incrementally
64
- --json Print JSON result
65
- --pretty Print readable summary (default)
66
- --help Show this help
67
-
68
- Environment:
69
- AI_SPEC_PERSIST_CHECKPOINTS=1
70
- Persist .ai-spec/checkpoints/<run-id>/*.json for restore/debug
71
- `);
72
- }
73
-
74
- function parseArgs(argv) {
75
- const args = [...argv];
76
- const command = args.shift();
77
- const options = {
78
- target: '.',
79
- triggerSource: 'ide-skill',
80
- entry: 'task-orchestrator',
81
- pretty: true,
82
- json: false,
83
- };
84
-
85
- while (args.length > 0) {
86
- const arg = args.shift();
87
- switch (arg) {
88
- case '--target':
89
- options.target = args.shift();
90
- break;
91
- case '--run-plan':
92
- options.runPlan = args.shift();
93
- break;
94
- case '--task-anchor':
95
- case '--anchor':
96
- options.taskAnchor = args.shift();
97
- break;
98
- case '--payload':
99
- options.payload = args.shift();
100
- break;
101
- case '--stdin':
102
- options.stdin = true;
103
- break;
104
- case '--run-id':
105
- options.runId = args.shift();
106
- break;
107
- case '--to-role':
108
- options.toRole = args.shift();
109
- break;
110
- case '--next-role':
111
- options.nextRole = args.shift();
112
- break;
113
- case '--from-role':
114
- options.fromRole = args.shift();
115
- break;
116
- case '--gate':
117
- options.gate = args.shift();
118
- break;
119
- case '--checkpoint':
120
- options.checkpoint = args.shift();
121
- break;
122
- case '--pending-gate':
123
- options.pendingGate = args.shift();
124
- break;
125
- case '--clear-pending-gate':
126
- options.clearPendingGate = true;
127
- break;
128
- case '--blocked-by-role':
129
- options.blockedByRole = args.shift();
130
- break;
131
- case '--resume-to-role':
132
- options.resumeToRole = args.shift();
133
- break;
134
- case '--required-user-action':
135
- options.requiredUserAction = args.shift();
136
- break;
137
- case '--blocked-reason':
138
- options.blockedReason = args.shift();
139
- break;
140
- case '--message':
141
- options.message = args.shift();
142
- break;
143
- case '--error':
144
- options.error = args.shift();
145
- break;
146
- case '--event-type':
147
- options.eventType = args.shift();
148
- break;
149
- case '--status':
150
- options.status = args.shift();
151
- break;
152
- case '--trigger-source':
153
- options.triggerSource = args.shift();
154
- break;
155
- case '--entry':
156
- options.entry = args.shift();
157
- break;
158
- case '--raw-input':
159
- options.rawInput = args.shift();
160
- break;
161
- case '--change-id':
162
- options.changeId = args.shift();
163
- break;
164
- case '--change-impact':
165
- options.changeImpact = args.shift();
166
- break;
167
- case '--reconcile-strategy':
168
- options.reconcileStrategy = args.shift();
169
- break;
170
- case '--reopen-reason':
171
- options.reopenReason = args.shift();
172
- break;
173
- case '--parent-change-id':
174
- options.parentChangeId = args.shift();
175
- break;
176
- case '--artifacts-to-update':
177
- options.artifactsToUpdate = String(args.shift() || '')
178
- .split(',')
179
- .map((item) => item.trim())
180
- .filter(Boolean);
181
- break;
182
- case '--json':
183
- options.json = true;
184
- options.pretty = false;
185
- break;
186
- case '--pretty':
187
- options.pretty = true;
188
- options.json = false;
189
- break;
190
- case '--help':
191
- case '-h':
192
- options.help = true;
193
- break;
194
- default:
195
- throw new Error(`Unknown argument: ${arg}`);
196
- }
197
- }
198
-
199
- return { command, options };
200
- }
201
-
202
- const DEFAULT_RUN_MODE = 'auto';
203
- const DEFAULT_REVIEW_POLICY = 'none';
204
- const RUN_MODES = new Set(['auto', 'suggest', 'manual']);
205
- const REVIEW_POLICIES = new Set(['none', 'main-flow-blocking']);
206
-
207
- function normalizeRunMode(value) {
208
- const normalized = String(value || '').trim().toLowerCase();
209
- return RUN_MODES.has(normalized) ? normalized : DEFAULT_RUN_MODE;
210
- }
211
-
212
- function normalizeReviewPolicy(value) {
213
- const normalized = String(value || '').trim().toLowerCase();
214
- return REVIEW_POLICIES.has(normalized) ? normalized : DEFAULT_REVIEW_POLICY;
215
- }
216
-
217
- function buildEffectiveApprovalGates(flowId, gates, reviewPolicy) {
218
- const normalizedPolicy = normalizeReviewPolicy(reviewPolicy);
219
- const deduped = Array.isArray(gates)
220
- ? [...new Set(gates.map((item) => String(item || '').trim()).filter(Boolean))]
221
- : [];
222
- if (flowId !== 'prd-to-delivery' || normalizedPolicy !== 'main-flow-blocking') {
223
- return deduped;
224
- }
225
- const supportedMainFlowGates = new Set(['before-implementation', 'before-guardian', 'before-archive']);
226
- const shouldInjectMainFlowGates = deduped.length === 0 || deduped.every((gate) => supportedMainFlowGates.has(gate));
227
- if (!shouldInjectMainFlowGates) {
228
- return deduped;
229
- }
230
-
231
- const ordered = [];
232
- for (const gate of ['before-implementation', 'before-guardian', 'before-archive']) {
233
- if (!ordered.includes(gate)) {
234
- ordered.push(gate);
235
- }
236
- }
237
- for (const gate of deduped) {
238
- if (!ordered.includes(gate)) {
239
- ordered.push(gate);
240
- }
241
- }
242
- return ordered;
243
- }
244
-
245
- function readJsonFile(filePath, label) {
246
- const raw = fs.readFileSync(filePath, 'utf8');
247
- try {
248
- return JSON.parse(raw);
249
- } catch (error) {
250
- throw new Error(`${label} is not valid JSON: ${filePath}`);
251
- }
252
- }
253
-
254
- function readJsonFromStdin(label) {
255
- const raw = fs.readFileSync(0, 'utf8');
256
- if (!raw.trim()) {
257
- throw new Error(`${label} stdin is empty`);
258
- }
259
- try {
260
- return JSON.parse(raw);
261
- } catch (error) {
262
- throw new Error(`${label} stdin is not valid JSON`);
263
- }
264
- }
265
-
266
- function assertRunPlan(runPlan, filePath) {
267
- if (!runPlan || typeof runPlan !== 'object') {
268
- throw new Error(`Invalid run-plan object: ${filePath}`);
269
- }
270
- if (runPlan.kind !== 'run-plan') {
271
- throw new Error(`Expected kind "run-plan" but got "${runPlan.kind || 'undefined'}": ${filePath}`);
272
- }
273
- if (!runPlan.flow || !runPlan.flow.id) {
274
- throw new Error(`run-plan is missing flow.id: ${filePath}`);
275
- }
276
- if (!runPlan.plan || !runPlan.plan.first_handoff) {
277
- throw new Error(`run-plan is missing plan.first_handoff: ${filePath}`);
278
- }
279
- }
280
-
281
- function pad2(value) {
282
- return String(value).padStart(2, '0');
283
- }
284
-
285
- function createRunId(now = new Date()) {
286
- const y = now.getFullYear();
287
- const m = pad2(now.getMonth() + 1);
288
- const d = pad2(now.getDate());
289
- const hh = pad2(now.getHours());
290
- const mm = pad2(now.getMinutes());
291
- const ss = pad2(now.getSeconds());
292
- const rand = Math.random().toString(36).slice(2, 6);
293
- return `run_${y}${m}${d}_${hh}${mm}${ss}_${rand}`;
294
- }
295
-
296
- function slugifyValue(value) {
297
- return String(value || '')
298
- .toLowerCase()
299
- .replace(/[^a-z0-9]+/g, '-')
300
- .replace(/^-+|-+$/g, '')
301
- .replace(/-{2,}/g, '-');
302
- }
303
-
304
- function deriveChangeId({ explicitChangeId, rawInput, taskType, runId }) {
305
- const normalizedExplicit = slugifyValue(explicitChangeId);
306
- if (normalizedExplicit) {
307
- return normalizedExplicit;
308
- }
309
-
310
- const normalizedInput = slugifyValue(rawInput);
311
- if (normalizedInput) {
312
- return normalizedInput.slice(0, 64);
313
- }
314
-
315
- const normalizedTaskType = slugifyValue(taskType) || 'change';
316
- const normalizedRunId = slugifyValue(runId) || 'run';
317
- return `${normalizedTaskType}-${normalizedRunId}`.slice(0, 96);
318
- }
319
-
320
- const FLOW_APPROVAL_RESUME_ROLE_HINTS = {
321
- 'prd-to-delivery': {
322
- 'requirement-analyst': 'frontend-implementer',
323
- 'frontend-implementer': 'code-guardian',
324
- 'code-guardian': 'archive-change',
325
- 'archive-change': 'archive-change',
326
- },
327
- };
328
-
329
- function inferApprovalResumeRole(state, options = {}) {
330
- if (options.toRole || options.nextRole) {
331
- return options.toRole || options.nextRole;
332
- }
333
-
334
- const gateResumeRole = state.gate_context?.resume_to_role || null;
335
- if (gateResumeRole) {
336
- return gateResumeRole;
337
- }
338
-
339
- const anchorNextRole = state.anchor?.stage?.next_role || null;
340
- if (anchorNextRole) {
341
- return anchorNextRole;
342
- }
343
-
344
- const flowId = state.flow?.id || null;
345
- const currentRole = state.current_role || null;
346
- const hintedRole = flowId && currentRole
347
- ? FLOW_APPROVAL_RESUME_ROLE_HINTS[flowId]?.[currentRole] || null
348
- : null;
349
- if (hintedRole) {
350
- return hintedRole;
351
- }
352
-
353
- return state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null;
354
- }
355
-
356
- const CHECKPOINT_EVENTS = new Set([
357
- 'bootstrap',
358
- 'handoff',
359
- 'gate-blocked',
360
- 'approve',
361
- 'pause',
362
- 'complete',
363
- 'fail',
364
- 'cancel',
365
- ]);
366
-
367
- function buildGateContext(state, options = {}, fallbackGate = null) {
368
- const gateId = options.gateId || options.gate || options.pendingGate || fallbackGate || state?.pending_gate || null;
369
- if (!gateId) {
370
- return null;
371
- }
372
-
373
- return {
374
- gate_id: gateId,
375
- blocked_by_role: options.blockedByRole || options.fromRole || state?.current_role || null,
376
- resume_to_role: options.resumeToRole || options.nextRole || options.toRole || inferApprovalResumeRole(state || {}, options) || null,
377
- required_user_action: options.requiredUserAction || state?.gate_context?.required_user_action || null,
378
- blocked_reason: options.blockedReason || state?.gate_context?.blocked_reason || null,
379
- };
380
- }
381
-
382
- function buildIncrementalUpdateState(state, options = {}, defaults = {}) {
383
- const previous = state?.incremental_update && typeof state.incremental_update === 'object'
384
- ? state.incremental_update
385
- : {};
386
- const next = {
387
- change_context: options.changeContext || defaults.changeContext || previous.change_context || null,
388
- route_decision: options.routeDecision || defaults.routeDecision || previous.route_decision || null,
389
- trace_mode: options.traceMode || defaults.traceMode || previous.trace_mode || null,
390
- change_impact: options.changeImpact || defaults.changeImpact || previous.change_impact || null,
391
- reconcile_strategy: options.reconcileStrategy || defaults.reconcileStrategy || previous.reconcile_strategy || null,
392
- artifacts_to_update: Array.isArray(options.artifactsToUpdate)
393
- ? options.artifactsToUpdate
394
- : Array.isArray(defaults.artifactsToUpdate)
395
- ? defaults.artifactsToUpdate
396
- : Array.isArray(previous.artifacts_to_update)
397
- ? previous.artifacts_to_update
398
- : [],
399
- reopen_reason: options.reopenReason || defaults.reopenReason || previous.reopen_reason || null,
400
- parent_change_id: options.parentChangeId || defaults.parentChangeId || previous.parent_change_id || null,
401
- target_role: options.toRole || options.nextRole || defaults.targetRole || previous.target_role || null,
402
- handoff_gate: options.handoffGate || defaults.handoffGate || previous.handoff_gate || null,
403
- updated_at: defaults.updatedAt || previous.updated_at || null,
404
- };
405
-
406
- if (
407
- !next.change_context &&
408
- !next.route_decision &&
409
- !next.trace_mode &&
410
- !next.change_impact &&
411
- !next.reconcile_strategy &&
412
- next.artifacts_to_update.length === 0 &&
413
- !next.reopen_reason &&
414
- !next.parent_change_id &&
415
- !next.target_role &&
416
- !next.handoff_gate
417
- ) {
418
- return null;
419
- }
420
-
421
- return next;
422
- }
423
-
424
- function buildDefaultAutoFixState() {
425
- return {
426
- attempts: 0,
427
- max_attempts: 1,
428
- active: false,
429
- last_failed_steps: [],
430
- };
431
- }
432
-
433
- function normalizeAutoFixStep(step) {
434
- if (!step || typeof step !== 'object') {
435
- return null;
436
- }
437
-
438
- return {
439
- name: typeof step.name === 'string' && step.name.trim() ? step.name.trim() : 'unknown',
440
- status: typeof step.status === 'string' && step.status.trim() ? step.status.trim() : null,
441
- command: typeof step.command === 'string' && step.command.trim() ? step.command.trim() : null,
442
- exit_code: typeof step.exit_code === 'number' ? step.exit_code : null,
443
- reason: typeof step.reason === 'string' && step.reason.trim() ? step.reason.trim() : null,
444
- error: typeof step.error === 'string' && step.error.trim() ? step.error.trim() : null,
445
- stdout_excerpt: typeof step.stdout_excerpt === 'string' && step.stdout_excerpt.trim() ? step.stdout_excerpt.trim() : null,
446
- stderr_excerpt: typeof step.stderr_excerpt === 'string' && step.stderr_excerpt.trim() ? step.stderr_excerpt.trim() : null,
447
- };
448
- }
449
-
450
- function normalizeAutoFixState(value) {
451
- const defaults = buildDefaultAutoFixState();
452
- const merged = value && typeof value === 'object'
453
- ? { ...defaults, ...value }
454
- : defaults;
455
- const maxAttempts = Number.isFinite(Number(merged.max_attempts))
456
- ? Math.max(1, Number(merged.max_attempts))
457
- : defaults.max_attempts;
458
- const attempts = Number.isFinite(Number(merged.attempts))
459
- ? Math.max(0, Math.min(Number(merged.attempts), maxAttempts))
460
- : defaults.attempts;
461
- const lastFailedSteps = Array.isArray(merged.last_failed_steps)
462
- ? merged.last_failed_steps
463
- .map((item) => normalizeAutoFixStep(item))
464
- .filter(Boolean)
465
- : [];
466
-
467
- return {
468
- attempts,
469
- max_attempts: maxAttempts,
470
- active: Boolean(merged.active),
471
- last_failed_steps: lastFailedSteps,
472
- };
473
- }
474
-
475
- function buildNextAutoFixState(state, options = {}, defaults = {}) {
476
- const current = normalizeAutoFixState(state?.auto_fix);
477
- if (options.autoFixData && typeof options.autoFixData === 'object') {
478
- return normalizeAutoFixState({
479
- ...current,
480
- ...options.autoFixData,
481
- });
482
- }
483
-
484
- if (defaults && typeof defaults === 'object' && Object.keys(defaults).length > 0) {
485
- return normalizeAutoFixState({
486
- ...current,
487
- ...defaults,
488
- });
489
- }
490
-
491
- return current;
492
- }
493
-
494
- function buildCheckpointMetadata(state, eventName, relPath, timestamp) {
495
- return {
496
- sequence: (Number(state?.checkpoint_count) || 0) + 1,
497
- event: eventName,
498
- at: timestamp,
499
- file: relPath,
500
- };
501
- }
502
-
503
- const MICRO_TASK_TYPES = new Set([
504
- 'page-development',
505
- 'component-development',
506
- 'bugfix',
507
- 'bug-fix',
508
- 'problem-fix',
509
- 'issue-fix',
510
- 'style-update',
511
- 'route-update',
512
- ]);
513
-
514
- const MICRO_INPUT_PATTERNS = [
515
- /mock/i,
516
- /mock数据/,
517
- /示例数据/,
518
- /静态/,
519
- /单页/,
520
- /单一页面/,
521
- /简单页面/,
522
- /简单组件/,
523
- /列表页面/,
524
- /登录页面/,
525
- /注册页面/,
526
- /商品列表页面/,
527
- /原型/,
528
- ];
529
-
530
- const STANDARD_INPUT_PATTERNS = [
531
- /重构/,
532
- /权限/,
533
- /支付/,
534
- /认证/,
535
- /oauth/i,
536
- /短信/,
537
- /多步骤/,
538
- /多页面/,
539
- /复杂/,
540
- /真实接口/,
541
- /核心模块/,
542
- /状态联动/,
543
- /合规/,
544
- /安全/,
545
- ];
546
-
547
- const HIGH_RISK_INPUT_PATTERNS = [
548
- /支付/,
549
- /认证/,
550
- /oauth/i,
551
- /短信/,
552
- /权限/,
553
- /安全/,
554
- /合规/,
555
- /风控/,
556
- /收款/,
557
- /交易/,
558
- ];
559
-
560
- const DEFERRED_DETAIL_PATTERNS = [
561
- /先不说/,
562
- /先不提供/,
563
- /暂不说/,
564
- /暂不提供/,
565
- /暂未确定/,
566
- /未明确/,
567
- /待定/,
568
- /后续再说/,
569
- /后面再说/,
570
- ];
571
-
572
- function inferRiskLevel({ explicitRiskLevel, rawInput, taskType, deliveryProfile }) {
573
- const normalizedExplicit = String(explicitRiskLevel || '').trim().toLowerCase();
574
- if (normalizedExplicit === 'low' || normalizedExplicit === 'medium' || normalizedExplicit === 'high') {
575
- return normalizedExplicit;
576
- }
577
-
578
- let score = 0;
579
- const input = String(rawInput || '');
580
- const normalizedTaskType = String(taskType || '').trim().toLowerCase();
581
-
582
- if (deliveryProfile === 'standard') {
583
- score += 1;
584
- }
585
-
586
- if (normalizedTaskType.includes('payment') || normalizedTaskType.includes('auth') || normalizedTaskType.includes('security')) {
587
- score += 2;
588
- }
589
-
590
- for (const pattern of HIGH_RISK_INPUT_PATTERNS) {
591
- if (pattern.test(input)) {
592
- score += 2;
593
- break;
594
- }
595
- }
596
-
597
- for (const pattern of DEFERRED_DETAIL_PATTERNS) {
598
- if (pattern.test(input)) {
599
- score += 2;
600
- break;
601
- }
602
- }
603
-
604
- if (score >= 4) {
605
- return 'high';
606
- }
607
- if (score >= 2) {
608
- return 'medium';
609
- }
610
- return 'low';
611
- }
612
-
613
- function inferDeliveryProfile({ explicitProfile, flowId, taskType, rawInput, riskLevel }) {
614
- const normalizedExplicit = String(explicitProfile || '').trim().toLowerCase();
615
- if (normalizedExplicit === 'micro' || normalizedExplicit === 'standard') {
616
- return normalizedExplicit;
617
- }
618
-
619
- let score = 0;
620
-
621
- if (MICRO_TASK_TYPES.has(String(taskType || '').trim().toLowerCase())) {
622
- score += 1;
623
- }
624
-
625
- const input = String(rawInput || '');
626
- for (const pattern of MICRO_INPUT_PATTERNS) {
627
- if (pattern.test(input)) {
628
- score += 2;
629
- break;
630
- }
631
- }
632
-
633
- for (const pattern of STANDARD_INPUT_PATTERNS) {
634
- if (pattern.test(input)) {
635
- score -= 2;
636
- break;
637
- }
638
- }
639
-
640
- const normalizedRisk = String(riskLevel || '').trim().toLowerCase();
641
- if (normalizedRisk === 'low') {
642
- score += 1;
643
- } else if (normalizedRisk === 'high') {
644
- score -= 2;
645
- }
646
-
647
- if (flowId && flowId !== 'prd-to-delivery') {
648
- score -= 1;
649
- }
650
-
651
- return score >= 2 ? 'micro' : 'standard';
652
- }
653
-
654
- function inferArtifactProfile({ explicitProfile, deliveryProfile }) {
655
- const normalizedExplicit = String(explicitProfile || '').trim().toLowerCase();
656
- if (normalizedExplicit === 'compact' || normalizedExplicit === 'full') {
657
- return normalizedExplicit;
658
- }
659
-
660
- return deliveryProfile === 'micro' ? 'compact' : 'full';
661
- }
662
-
663
- function inferComplexity({ explicitComplexity, deliveryProfile, riskLevel }) {
664
- const normalizedExplicit = String(explicitComplexity || '').trim().toLowerCase();
665
- if (normalizedExplicit === 'low' || normalizedExplicit === 'medium' || normalizedExplicit === 'high') {
666
- return normalizedExplicit;
667
- }
668
-
669
- const normalizedRisk = String(riskLevel || '').trim().toLowerCase();
670
- if (normalizedRisk === 'high') {
671
- return 'high';
672
- }
673
- if (normalizedRisk === 'medium') {
674
- return 'medium';
675
- }
676
-
677
- return deliveryProfile === 'micro' ? 'low' : 'medium';
678
- }
679
-
680
- function normalizeSpecsArtifactPath(relPath) {
681
- const value = String(relPath || '').trim();
682
- if (!value) {
683
- return null;
684
- }
685
-
686
- const normalized = value.replace(/[\\/]+$/, '');
687
- if (/[\\/]specs$/.test(normalized)) {
688
- return normalized;
689
- }
690
-
691
- const match = normalized.match(/^(.*[\\/]specs)(?:[\\/].+)?$/);
692
- return match ? match[1] : normalized;
693
- }
694
-
695
- function buildDefaultArtifacts(changeId, options = {}) {
696
- const flowId = String(options.flowId || '').trim();
697
- const runId = String(options.runId || '').trim();
698
- const traceMode = String(options.traceMode || '').trim();
699
-
700
- if ((flowId === 'bugfix-to-verification' || traceMode === 'direct-fix') && runId) {
701
- const historyDir = `.ai-spec/history/${runId}`;
702
- return {
703
- proposal: null,
704
- specs: null,
705
- design: null,
706
- tasks: null,
707
- bugfix: `${historyDir}/bugfix.md`,
708
- implementation_notes: `${historyDir}/implementation-notes.md`,
709
- checklist: `${historyDir}/checklist.md`,
710
- iterations: `${historyDir}/iterations.md`,
711
- additional: [],
712
- };
713
- }
714
-
715
- if (!changeId) {
716
- return {
717
- proposal: null,
718
- specs: null,
719
- design: null,
720
- tasks: null,
721
- bugfix: null,
722
- implementation_notes: null,
723
- checklist: null,
724
- iterations: null,
725
- additional: [],
726
- };
727
- }
728
-
729
- const baseDir = `openspec/changes/${changeId}`;
730
- return {
731
- proposal: `${baseDir}/proposal.md`,
732
- specs: `${baseDir}/specs`,
733
- design: `${baseDir}/design.md`,
734
- tasks: `${baseDir}/tasks.md`,
735
- bugfix: null,
736
- implementation_notes: null,
737
- checklist: `${baseDir}/checklist.md`,
738
- iterations: `${baseDir}/iterations.md`,
739
- additional: [],
740
- };
741
- }
742
-
743
- function mergeArtifacts(baseArtifacts, inferredArtifacts) {
744
- const proposal = inferredArtifacts?.proposal || baseArtifacts?.proposal || null;
745
- const specs = normalizeSpecsArtifactPath(inferredArtifacts?.specs || baseArtifacts?.specs || null);
746
- const design = inferredArtifacts?.design || baseArtifacts?.design || null;
747
- const tasks = inferredArtifacts?.tasks || baseArtifacts?.tasks || null;
748
- const bugfix = inferredArtifacts?.bugfix || baseArtifacts?.bugfix || null;
749
- const implementationNotes = inferredArtifacts?.implementation_notes || baseArtifacts?.implementation_notes || null;
750
- const checklist = inferredArtifacts?.checklist || baseArtifacts?.checklist || null;
751
- const iterations = inferredArtifacts?.iterations || baseArtifacts?.iterations || null;
752
- const primaryArtifacts = new Set(
753
- [proposal, specs, design, tasks, bugfix, implementationNotes, checklist, iterations]
754
- .map((item) => (typeof item === 'string' ? item.trim().replace(/[\\/]+$/, '') : null))
755
- .filter(Boolean),
756
- );
757
- const additional = [
758
- ...(Array.isArray(baseArtifacts?.additional) ? baseArtifacts.additional : []),
759
- ...(Array.isArray(inferredArtifacts?.additional) ? inferredArtifacts.additional : []),
760
- ]
761
- .map((item) => String(item || '').trim())
762
- .filter(Boolean)
763
- .map((item) => item.replace(/[\\/]+$/, ''))
764
- .filter((item) => !primaryArtifacts.has(item));
765
-
766
- const merged = {
767
- proposal,
768
- specs,
769
- design,
770
- tasks,
771
- bugfix,
772
- implementation_notes: implementationNotes,
773
- checklist,
774
- iterations,
775
- additional: Array.from(new Set(additional.filter(Boolean))),
776
- };
777
-
778
- if (merged.additional.length === 0) {
779
- delete merged.additional;
780
- }
781
-
782
- return merged;
783
- }
784
-
785
- function inferArtifacts(artifacts) {
786
- const normalized = {
787
- proposal: null,
788
- specs: null,
789
- design: null,
790
- tasks: null,
791
- bugfix: null,
792
- implementation_notes: null,
793
- checklist: null,
794
- iterations: null,
795
- additional: [],
796
- };
797
-
798
- if (!artifacts) {
799
- return normalized;
800
- }
801
-
802
- if (artifacts && typeof artifacts === 'object' && !Array.isArray(artifacts)) {
803
- const directKeys = ['proposal', 'specs', 'design', 'tasks', 'bugfix', 'implementation_notes', 'checklist', 'iterations'];
804
- for (const key of directKeys) {
805
- if (typeof artifacts[key] === 'string' && artifacts[key].trim()) {
806
- normalized[key] = key === 'specs'
807
- ? normalizeSpecsArtifactPath(artifacts[key])
808
- : artifacts[key];
809
- }
810
- }
811
-
812
- const additional = artifacts.additional;
813
- if (typeof additional === 'string' && additional.trim()) {
814
- normalized.additional.push(additional);
815
- } else if (Array.isArray(additional)) {
816
- normalized.additional.push(...additional.filter((item) => typeof item === 'string' && item.trim()));
817
- }
818
-
819
- if (normalized.additional.length === 0) {
820
- delete normalized.additional;
821
- }
822
-
823
- return normalized;
824
- }
825
-
826
- if (!Array.isArray(artifacts)) {
827
- return normalized;
828
- }
829
-
830
- for (const item of artifacts) {
831
- if (typeof item !== 'string') {
832
- continue;
833
- }
834
- if (item.endsWith('/proposal.md')) {
835
- normalized.proposal = item;
836
- continue;
837
- }
838
- if (/[\\/]specs(?:[\\/].+)?$/.test(item)) {
839
- normalized.specs = normalizeSpecsArtifactPath(item);
840
- continue;
841
- }
842
- if (item.endsWith('/design.md')) {
843
- normalized.design = item;
844
- continue;
845
- }
846
- if (item.endsWith('/tasks.md')) {
847
- normalized.tasks = item;
848
- continue;
849
- }
850
- if (item.endsWith('/bugfix.md')) {
851
- normalized.bugfix = item;
852
- continue;
853
- }
854
- if (item.endsWith('/implementation-notes.md')) {
855
- normalized.implementation_notes = item;
856
- continue;
857
- }
858
- if (item.endsWith('/checklist.md')) {
859
- normalized.checklist = item;
860
- continue;
861
- }
862
- if (item.endsWith('/iterations.md')) {
863
- normalized.iterations = item;
864
- continue;
865
- }
866
- normalized.additional.push(item);
867
- }
868
-
869
- if (normalized.additional.length === 0) {
870
- delete normalized.additional;
871
- }
872
-
873
- return normalized;
874
- }
875
-
876
- function sanitizeAnchor(taskAnchor) {
877
- if (!taskAnchor || typeof taskAnchor !== 'object') {
878
- return null;
879
- }
880
- return {
881
- kind: taskAnchor.kind || 'task-anchor',
882
- task: taskAnchor.task || null,
883
- stage: taskAnchor.stage || null,
884
- constraints: taskAnchor.constraints || null,
885
- artifacts: taskAnchor.artifacts || null,
886
- expected_output: taskAnchor.expected_output || [],
887
- };
888
- }
889
-
890
- function normalizeBootstrapPayload(payload, sourceLabel) {
891
- if (!payload || typeof payload !== 'object') {
892
- throw new Error(`Invalid bootstrap payload: ${sourceLabel}`);
893
- }
894
-
895
- if (payload.kind === 'run-plan') {
896
- return {
897
- runPlan: payload,
898
- taskAnchor: null,
899
- };
900
- }
901
-
902
- const runPlan = payload.run_plan || payload.runPlan || null;
903
- const taskAnchor = payload.task_anchor || payload.taskAnchor || null;
904
-
905
- if (!runPlan) {
906
- throw new Error(`Bootstrap payload is missing run_plan: ${sourceLabel}`);
907
- }
908
-
909
- return { runPlan, taskAnchor };
910
- }
911
-
912
- function buildRunState({ runPlan, taskAnchor, options, now, source }) {
913
- const runId = options.runId || runPlan.run_id || createRunId(now);
914
- const createdAt = now.toISOString();
915
- const runMode = normalizeRunMode(runPlan.mode);
916
- const reviewPolicy = normalizeReviewPolicy(runPlan.review_policy || runPlan.plan?.review_policy || null);
917
- const rawInput =
918
- options.rawInput ||
919
- runPlan.task?.raw_input ||
920
- taskAnchor?.task?.raw_goal ||
921
- null;
922
- const changeId = deriveChangeId({
923
- explicitChangeId: options.changeId || runPlan.task?.change_id || taskAnchor?.task?.change_id || null,
924
- rawInput,
925
- taskType: runPlan.task?.type || null,
926
- runId,
927
- });
928
- const deliveryProfile = inferDeliveryProfile({
929
- explicitProfile: runPlan.delivery_profile || runPlan.flow?.delivery_profile || runPlan.plan?.delivery_profile || null,
930
- flowId: runPlan.flow?.id || null,
931
- taskType: runPlan.task?.type || null,
932
- rawInput,
933
- riskLevel: runPlan.task?.risk_level || null,
934
- });
935
- const riskLevel = inferRiskLevel({
936
- explicitRiskLevel: runPlan.task?.risk_level || null,
937
- rawInput,
938
- taskType: runPlan.task?.type || null,
939
- deliveryProfile,
940
- });
941
- const artifactProfile = inferArtifactProfile({
942
- explicitProfile: runPlan.artifact_profile || runPlan.plan?.artifact_profile || null,
943
- deliveryProfile,
944
- });
945
- const complexity = inferComplexity({
946
- explicitComplexity: runPlan.complexity || runPlan.task?.complexity || null,
947
- deliveryProfile,
948
- riskLevel,
949
- });
950
- const changeContext = options.changeContext || runPlan.task?.change_context || null;
951
- const routeDecision = options.routeDecision || runPlan.task?.route_decision || null;
952
- const traceMode = options.traceMode || runPlan.task?.trace_mode || null;
953
- const artifacts = mergeArtifacts(buildDefaultArtifacts(changeId, {
954
- flowId: runPlan.flow?.id || null,
955
- runId,
956
- traceMode,
957
- }), inferArtifacts(runPlan.artifacts));
958
- const currentRole = runPlan.plan?.first_handoff || null;
959
- const approvalGates = buildEffectiveApprovalGates(
960
- runPlan.flow?.id || null,
961
- Array.isArray(runPlan.plan?.approval_gates) ? runPlan.plan.approval_gates : [],
962
- reviewPolicy,
963
- );
964
- const normalizedRunPlanStatus = String(runPlan.status || '').trim().toLowerCase();
965
- const initialStatus = options.status
966
- || (runMode === 'suggest'
967
- ? (normalizedRunPlanStatus && normalizedRunPlanStatus !== 'planned' ? runPlan.status : 'waiting-confirm')
968
- : runPlan.status || 'planned');
969
- const pendingGate =
970
- options.pendingGate ||
971
- runPlan.pending_gate ||
972
- runPlan.plan?.pending_gate ||
973
- null;
974
- const sanitizedAnchor = sanitizeAnchor(taskAnchor);
975
- const anchor = sanitizedAnchor
976
- ? {
977
- ...sanitizedAnchor,
978
- task: {
979
- ...(sanitizedAnchor.task || {}),
980
- change_id: sanitizedAnchor.task?.change_id || changeId,
981
- },
982
- artifacts: mergeArtifacts(
983
- buildDefaultArtifacts(changeId, {
984
- flowId: runPlan.flow?.id || null,
985
- runId,
986
- traceMode,
987
- }),
988
- inferArtifacts(sanitizedAnchor.artifacts || artifacts),
989
- ),
990
- }
991
- : null;
992
- const initMessage = source?.bootstrapPayload
993
- ? 'runtime-state initialized from task-orchestrator bootstrap payload'
994
- : 'runtime-state initialized from run-plan';
995
- const initialGateContext = runPlan.gate_context && typeof runPlan.gate_context === 'object'
996
- ? {
997
- gate_id: runPlan.gate_context.gate_id || null,
998
- blocked_by_role: runPlan.gate_context.blocked_by_role || null,
999
- resume_to_role: runPlan.gate_context.resume_to_role || currentRole,
1000
- required_user_action: runPlan.gate_context.required_user_action || null,
1001
- blocked_reason: runPlan.gate_context.blocked_reason || null,
1002
- }
1003
- : null;
1004
- const suggestGateContext = runMode === 'suggest' && initialStatus === 'waiting-confirm' && !pendingGate
1005
- ? {
1006
- gate_id: 'start-review',
1007
- blocked_by_role: 'task-orchestrator',
1008
- resume_to_role: currentRole,
1009
- required_user_action: '请先确认建议执行计划,再启动第一位专家。',
1010
- blocked_reason: '当前以 suggest(建议)模式启动,首轮 run-plan 需要先经过人工确认。',
1011
- }
1012
- : null;
1013
-
1014
- return {
1015
- schema_version: 1,
1016
- kind: 'run-state',
1017
- run_id: runId,
1018
- mode: runMode,
1019
- review_policy: reviewPolicy,
1020
- delivery_profile: deliveryProfile,
1021
- artifact_profile: artifactProfile,
1022
- complexity,
1023
- status: initialStatus,
1024
- trigger: {
1025
- source: options.triggerSource,
1026
- entry: options.entry,
1027
- raw_input: rawInput,
1028
- latest_user_input: rawInput,
1029
- latest_input_at: rawInput ? createdAt : null,
1030
- },
1031
- task: {
1032
- change_id: changeId,
1033
- parent_change_id: options.parentChangeId || runPlan.task?.parent_change_id || taskAnchor?.task?.parent_change_id || null,
1034
- input_kind: runPlan.task?.input_kind || taskAnchor?.task?.input_kind || 'unknown',
1035
- risk_level: riskLevel,
1036
- type: runPlan.task?.type || null,
1037
- complexity,
1038
- change_context: changeContext,
1039
- route_decision: routeDecision,
1040
- trace_mode: traceMode,
1041
- change_impact: options.changeImpact || runPlan.task?.change_impact || null,
1042
- },
1043
- flow: {
1044
- id: runPlan.flow?.id || null,
1045
- name: runPlan.flow?.name || null,
1046
- source: runPlan.flow?.source || null,
1047
- delivery_profile: deliveryProfile,
1048
- artifact_profile: artifactProfile,
1049
- },
1050
- plan: {
1051
- required_roles: runPlan.plan?.required_roles || [],
1052
- activated_optional_roles: runPlan.plan?.activated_optional_roles || [],
1053
- skipped_optional_roles: runPlan.plan?.skipped_optional_roles || [],
1054
- approval_gates: approvalGates,
1055
- first_handoff: currentRole,
1056
- delivery_profile: deliveryProfile,
1057
- artifact_profile: artifactProfile,
1058
- review_policy: reviewPolicy,
1059
- },
1060
- current_role: currentRole,
1061
- pending_input_update: false,
1062
- pending_gate: pendingGate,
1063
- gate_context: pendingGate
1064
- ? buildGateContext(null, options, pendingGate)
1065
- : initialGateContext || suggestGateContext,
1066
- incremental_update: buildIncrementalUpdateState(null, options, {
1067
- changeContext,
1068
- routeDecision,
1069
- traceMode,
1070
- changeImpact: options.changeImpact || runPlan.task?.change_impact || null,
1071
- reconcileStrategy: options.reconcileStrategy || runPlan.task?.reconcile_strategy || null,
1072
- artifactsToUpdate: options.artifactsToUpdate || runPlan.task?.artifacts_to_update || [],
1073
- reopenReason: options.reopenReason || runPlan.task?.reopen_reason || null,
1074
- parentChangeId: options.parentChangeId || runPlan.task?.parent_change_id || null,
1075
- updatedAt: createdAt,
1076
- }),
1077
- artifacts,
1078
- verification: null,
1079
- auto_fix: buildDefaultAutoFixState(),
1080
- last_checkpoint: null,
1081
- checkpoint_count: 0,
1082
- assumptions: Array.isArray(runPlan.assumptions) ? runPlan.assumptions : [],
1083
- missing_inputs: runPlan.missing_inputs || [],
1084
- warnings: runPlan.warnings || [],
1085
- errors: runPlan.errors || [],
1086
- input_updates: [],
1087
- anchor,
1088
- events: [
1089
- {
1090
- at: createdAt,
1091
- type: 'run-created',
1092
- status: options.status || runPlan.status || 'planned',
1093
- message: initMessage,
1094
- },
1095
- ],
1096
- timestamps: {
1097
- created_at: createdAt,
1098
- updated_at: createdAt,
1099
- },
1100
- };
1101
- }
1102
-
1103
- function listMissingOpenSpecArtifacts(targetDir, state, artifactKeys) {
1104
- const artifactMap = mergeArtifacts(
1105
- buildDefaultArtifacts(state.task?.change_id || state.anchor?.task?.change_id || null),
1106
- inferArtifacts(state.artifacts || null),
1107
- );
1108
- const missing = [];
1109
-
1110
- for (const key of artifactKeys) {
1111
- const relPath = artifactMap[key];
1112
- if (!relPath) {
1113
- missing.push(`artifact:${key}`);
1114
- continue;
1115
- }
1116
-
1117
- const absolutePath = path.join(targetDir, relPath);
1118
- if (!fs.existsSync(absolutePath)) {
1119
- missing.push(relPath);
1120
- }
1121
- }
1122
-
1123
- return missing;
1124
- }
1125
-
1126
- function assertRequiredOpenSpecArtifacts(targetDir, state, action, toRole) {
1127
- if (state.flow?.id !== 'prd-to-delivery') {
1128
- return;
1129
- }
1130
-
1131
- if (!state.task?.change_id) {
1132
- throw new Error(`Cannot ${action} prd-to-delivery run without task.change_id`);
1133
- }
1134
-
1135
- let requiredArtifacts = [];
1136
- if (action === 'handoff' && toRole === 'frontend-implementer') {
1137
- requiredArtifacts = ['proposal', 'specs', 'design', 'tasks'];
1138
- } else if (action === 'complete') {
1139
- requiredArtifacts = ['proposal', 'specs', 'design', 'tasks', 'checklist', 'iterations'];
1140
- }
1141
-
1142
- if (requiredArtifacts.length === 0) {
1143
- return;
1144
- }
1145
-
1146
- const missingArtifacts = listMissingOpenSpecArtifacts(targetDir, state, requiredArtifacts);
1147
- if (missingArtifacts.length > 0) {
1148
- throw new Error(
1149
- `Cannot ${action} prd-to-delivery run; missing required OpenSpec artifacts: ${missingArtifacts.join(', ')}`,
1150
- );
1151
- }
1152
- }
1153
-
1154
- function ensureDir(dirPath) {
1155
- fs.mkdirSync(dirPath, { recursive: true });
1156
- }
1157
-
1158
- function writeJsonFile(filePath, value) {
1159
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
1160
- }
1161
-
1162
- function readRunStateFile(filePath) {
1163
- const state = readJsonFile(filePath, 'run-state');
1164
- if (!state || typeof state !== 'object' || state.kind !== 'run-state') {
1165
- throw new Error(`Invalid run-state object: ${filePath}`);
1166
- }
1167
- if (!state.run_id) {
1168
- throw new Error(`run-state is missing run_id: ${filePath}`);
1169
- }
1170
- return state;
1171
- }
1172
-
1173
- function loadTaskAnchor(taskAnchorPath, taskAnchorData = null) {
1174
- if (taskAnchorData) {
1175
- return taskAnchorData;
1176
- }
1177
- return taskAnchorPath ? readJsonFile(taskAnchorPath, 'task-anchor') : null;
1178
- }
1179
-
1180
- function maybeAttachCheckpoint(targetDir, state, checkpointEvent) {
1181
- if (!shouldPersistCheckpoints() || !checkpointEvent || !CHECKPOINT_EVENTS.has(checkpointEvent)) {
1182
- return state;
1183
- }
1184
-
1185
- const runtimePaths = resolveRuntimePaths(targetDir);
1186
- const checkpointDir = path.join(runtimePaths.checkpointsDir.path, state.run_id);
1187
- ensureDir(checkpointDir);
1188
-
1189
- const timestamp = state.timestamps?.updated_at || new Date().toISOString();
1190
- const sequence = (Number(state.checkpoint_count) || 0) + 1;
1191
- const checkpointFileName = `${String(sequence).padStart(3, '0')}-${checkpointEvent}.json`;
1192
- const checkpointPath = path.join(checkpointDir, checkpointFileName);
1193
- const checkpointRelPath = path.relative(targetDir, checkpointPath);
1194
- const metadata = buildCheckpointMetadata(state, checkpointEvent, checkpointRelPath, timestamp);
1195
- const nextState = {
1196
- ...state,
1197
- checkpoint_count: sequence,
1198
- last_checkpoint: metadata,
1199
- };
1200
-
1201
- writeJsonFile(checkpointPath, {
1202
- schema_version: 1,
1203
- kind: 'runtime-checkpoint',
1204
- run_id: state.run_id,
1205
- sequence,
1206
- event: checkpointEvent,
1207
- created_at: timestamp,
1208
- state: nextState,
1209
- });
1210
-
1211
- return nextState;
1212
- }
1213
-
1214
- function saveUpdatedRunState({
1215
- targetDir,
1216
- historyRunPath,
1217
- currentRunPath,
1218
- syncCurrent,
1219
- forceSyncCurrent = false,
1220
- state,
1221
- checkpointEvent = null,
1222
- }) {
1223
- syncRepoMap(targetDir);
1224
- const nextState = maybeAttachCheckpoint(targetDir, state, checkpointEvent);
1225
-
1226
- if (historyRunPath) {
1227
- writeJsonFile(historyRunPath, nextState);
1228
- }
1229
- if (syncCurrent || forceSyncCurrent) {
1230
- writeJsonFile(currentRunPath, nextState);
1231
- }
1232
-
1233
- try {
1234
- const bridgePath = path.join(__dirname, 'visual-bridge.js');
1235
- if (fs.existsSync(bridgePath)) {
1236
- const child = spawnSync(process.execPath, [bridgePath, 'push-current', '--target', targetDir, '--event-name', checkpointEvent || 'runtime-state-updated', '--json'], {
1237
- encoding: 'utf8',
1238
- stdio: ['ignore', 'pipe', 'pipe'],
1239
- });
1240
- if (child.status !== 0 && process.env.AI_SPEC_VISUAL_BRIDGE_DEBUG === '1') {
1241
- console.warn(`visual bridge skipped: ${child.stderr || child.stdout || 'unknown error'}`);
1242
- }
1243
- }
1244
- } catch (error) {
1245
- if (process.env.AI_SPEC_VISUAL_BRIDGE_DEBUG === '1') {
1246
- console.warn(`visual bridge error: ${error.message}`);
1247
- }
1248
- }
1249
-
1250
- return nextState;
1251
- }
1252
-
1253
- function recordRunInputUpdate(options) {
1254
- if (!options.userInput || !String(options.userInput).trim()) {
1255
- throw new Error('Missing required argument: userInput');
1256
- }
1257
-
1258
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1259
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1260
-
1261
- if (['success', 'failed', 'cancelled'].includes(String(state.status || '').toLowerCase())) {
1262
- throw new Error(`Cannot update terminal run: ${state.run_id}`);
1263
- }
1264
-
1265
- const now = new Date();
1266
- const userInput = String(options.userInput).trim();
1267
- const update = {
1268
- at: now.toISOString(),
1269
- text: userInput,
1270
- source: options.source || 'protocol-update',
1271
- change_context: options.changeContext || null,
1272
- route_decision: options.routeDecision || null,
1273
- trace_mode: options.traceMode || null,
1274
- change_impact: options.changeImpact || null,
1275
- reconcile_strategy: options.reconcileStrategy || null,
1276
- artifacts_to_update: Array.isArray(options.artifactsToUpdate) ? options.artifactsToUpdate : [],
1277
- reopen_reason: options.reopenReason || null,
1278
- parent_change_id: options.parentChangeId || null,
1279
- target_role: options.toRole || options.nextRole || null,
1280
- handoff_gate: options.handoffGate || null,
1281
- };
1282
-
1283
- const nextInputUpdates = [...(Array.isArray(state.input_updates) ? state.input_updates : []), update].slice(-20);
1284
- const event = buildStateEvent({
1285
- state,
1286
- options: {
1287
- ...options,
1288
- toRole: state.current_role || state.plan?.first_handoff || null,
1289
- clearPendingGate: false,
1290
- message: `user input updated: ${userInput}`,
1291
- },
1292
- now,
1293
- defaults: {
1294
- status: state.status || 'running',
1295
- eventType: 'user-input-updated',
1296
- message: `user input updated: ${userInput}`,
1297
- pendingGate: state.pending_gate ?? null,
1298
- },
1299
- });
1300
-
1301
- const updatedState = {
1302
- ...state,
1303
- pending_input_update: true,
1304
- trigger: {
1305
- ...(state.trigger || {}),
1306
- latest_user_input: userInput,
1307
- latest_input_at: now.toISOString(),
1308
- latest_change_context: options.changeContext || null,
1309
- latest_route_decision: options.routeDecision || null,
1310
- latest_trace_mode: options.traceMode || null,
1311
- latest_change_impact: options.changeImpact || null,
1312
- latest_reconcile_strategy: options.reconcileStrategy || null,
1313
- },
1314
- incremental_update: buildIncrementalUpdateState(state, options, {
1315
- updatedAt: now.toISOString(),
1316
- }),
1317
- input_updates: nextInputUpdates,
1318
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1319
- timestamps: {
1320
- ...(state.timestamps || {}),
1321
- updated_at: now.toISOString(),
1322
- },
1323
- };
1324
-
1325
- const persistedState = saveUpdatedRunState({
1326
- targetDir,
1327
- historyRunPath,
1328
- currentRunPath,
1329
- syncCurrent,
1330
- state: updatedState,
1331
- checkpointEvent: 'pause',
1332
- });
1333
-
1334
- return {
1335
- status: 'success',
1336
- target: targetDir,
1337
- artifacts: {
1338
- current_run: syncCurrent ? currentRunPath : null,
1339
- run_history: historyRunPath,
1340
- },
1341
- state: persistedState,
1342
- update,
1343
- };
1344
- }
1345
-
1346
- function writeRunState({ targetDir, runPlan, taskAnchor, options, source }) {
1347
- const now = new Date();
1348
- const state = buildRunState({ runPlan, taskAnchor, options, now, source });
1349
- const runtimePaths = resolveRuntimePaths(targetDir);
1350
- const persistHistory = shouldPersistHistory();
1351
- if (persistHistory) {
1352
- ensureDir(runtimePaths.runsDir.path);
1353
- }
1354
- ensureDir(path.dirname(runtimePaths.currentRun.path));
1355
-
1356
- const currentRunPath = runtimePaths.currentRun.path;
1357
- const historyRunPath = persistHistory
1358
- ? path.join(runtimePaths.runsDir.path, `${state.run_id}.json`)
1359
- : null;
1360
-
1361
- const persistedState = saveUpdatedRunState({
1362
- targetDir,
1363
- historyRunPath,
1364
- currentRunPath,
1365
- syncCurrent: true,
1366
- forceSyncCurrent: true,
1367
- state,
1368
- checkpointEvent: 'bootstrap',
1369
- });
1370
-
1371
- return {
1372
- status: 'success',
1373
- target: targetDir,
1374
- artifacts: {
1375
- current_run: currentRunPath,
1376
- run_history: historyRunPath,
1377
- },
1378
- state: persistedState,
1379
- source: {
1380
- run_plan: source.runPlan || null,
1381
- task_anchor: source.taskAnchor || null,
1382
- bootstrap_payload: source.bootstrapPayload || null,
1383
- },
1384
- };
1385
- }
1386
-
1387
- function resolveRunStatePaths(targetDir, runId) {
1388
- const runtimePaths = resolveRuntimePaths(targetDir);
1389
- const aiSpecDir = runtimePaths.aiSpecDir.path;
1390
- const currentRunPath = runtimePaths.currentRun.path;
1391
- let historyRunPath = null;
1392
- let state = null;
1393
- const currentState = fs.existsSync(currentRunPath)
1394
- ? readRunStateFile(currentRunPath)
1395
- : null;
1396
-
1397
- if (runId) {
1398
- if (currentState && currentState.run_id === runId) {
1399
- state = currentState;
1400
- }
1401
- for (const candidateDir of getCandidatePaths(runtimePaths.runsDir)) {
1402
- const candidatePath = path.join(candidateDir, `${runId}.json`);
1403
- if (fs.existsSync(candidatePath)) {
1404
- historyRunPath = candidatePath;
1405
- if (!state) {
1406
- state = readRunStateFile(historyRunPath);
1407
- }
1408
- break;
1409
- }
1410
- }
1411
- if (!state) {
1412
- throw new Error(`run-state history file not found for run_id: ${runId}`);
1413
- }
1414
- } else {
1415
- if (!fs.existsSync(currentRunPath)) {
1416
- throw new Error(`current run-state file not found: ${currentRunPath}`);
1417
- }
1418
- state = currentState;
1419
- const candidateHistory = getExistingPath({
1420
- path: path.join(runtimePaths.runsDir.path, `${state.run_id}.json`),
1421
- legacyPaths: getCandidatePaths(runtimePaths.runsDir).map((dirPath) => path.join(dirPath, `${state.run_id}.json`)).slice(1),
1422
- });
1423
- historyRunPath = fs.existsSync(candidateHistory) ? candidateHistory : null;
1424
- }
1425
-
1426
- return {
1427
- aiSpecDir,
1428
- currentRunPath,
1429
- historyRunPath,
1430
- state,
1431
- syncCurrent: Boolean(currentState && currentState.run_id === state.run_id),
1432
- };
1433
- }
1434
-
1435
- function buildHandoffEvent({ state, options, now }) {
1436
- const fromRole = options.fromRole || state.current_role || state.plan?.first_handoff || null;
1437
- const toRole = options.toRole;
1438
- const eventType = options.eventType || 'role-handoff';
1439
- const message =
1440
- options.message ||
1441
- `handoff from ${fromRole || 'unknown'} to ${toRole}`;
1442
-
1443
- return {
1444
- at: now.toISOString(),
1445
- type: eventType,
1446
- status: options.status || state.status || 'running',
1447
- from_role: fromRole,
1448
- to_role: toRole,
1449
- pending_gate:
1450
- options.clearPendingGate ? null :
1451
- (Object.prototype.hasOwnProperty.call(options, 'pendingGate') ? options.pendingGate || null : state.pending_gate || null),
1452
- gate_context: options.clearPendingGate ? null : buildGateContext(state, options),
1453
- message,
1454
- };
1455
- }
1456
-
1457
- function buildStateEvent({ state, options, now, defaults = {} }) {
1458
- const fromRole = options.fromRole || defaults.fromRole || state.current_role || state.plan?.first_handoff || null;
1459
- const toRole = options.toRole || defaults.toRole || null;
1460
- const pendingGate = options.clearPendingGate
1461
- ? null
1462
- : (Object.prototype.hasOwnProperty.call(options, 'pendingGate')
1463
- ? options.pendingGate || null
1464
- : defaults.pendingGate ?? state.pending_gate ?? null);
1465
- const status = options.status || defaults.status || state.status || 'running';
1466
- const eventType = options.eventType || defaults.eventType || 'state-updated';
1467
- const message = options.message || defaults.message || eventType;
1468
-
1469
- return {
1470
- at: now.toISOString(),
1471
- type: eventType,
1472
- status,
1473
- from_role: fromRole,
1474
- to_role: toRole,
1475
- pending_gate: pendingGate,
1476
- gate_context: pendingGate ? buildGateContext(state, options, pendingGate) : null,
1477
- message,
1478
- };
1479
- }
1480
-
1481
- function shouldClearPendingGateForHandoff(state, options = {}) {
1482
- if (Object.prototype.hasOwnProperty.call(options, 'clearPendingGate')) {
1483
- return Boolean(options.clearPendingGate);
1484
- }
1485
-
1486
- if (Object.prototype.hasOwnProperty.call(options, 'pendingGate')) {
1487
- return false;
1488
- }
1489
-
1490
- return Boolean(state?.pending_gate && state?.pending_input_update);
1491
- }
1492
-
1493
- function updateAnchorForRole(existingAnchor, taskAnchor, toRole, nextRole) {
1494
- const sanitizedAnchor = taskAnchor ? sanitizeAnchor(taskAnchor) : existingAnchor || null;
1495
- if (!sanitizedAnchor) {
1496
- return null;
1497
- }
1498
- return {
1499
- ...sanitizedAnchor,
1500
- stage: {
1501
- ...(sanitizedAnchor.stage || {}),
1502
- current_role: toRole ?? sanitizedAnchor.stage?.current_role ?? null,
1503
- next_role: nextRole ?? sanitizedAnchor.stage?.next_role ?? null,
1504
- },
1505
- };
1506
- }
1507
-
1508
- function handoffRunState(options) {
1509
- if (!options.toRole) {
1510
- throw new Error('Missing required argument: --to-role <role>');
1511
- }
1512
-
1513
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1514
- const taskAnchorPath = options.taskAnchor
1515
- ? path.resolve(process.cwd(), options.taskAnchor)
1516
- : null;
1517
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1518
- assertRequiredOpenSpecArtifacts(targetDir, state, 'handoff', options.toRole);
1519
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1520
- const sanitizedAnchor = updateAnchorForRole(
1521
- state.anchor || null,
1522
- taskAnchor,
1523
- options.toRole,
1524
- options.nextRole,
1525
- );
1526
- const now = new Date();
1527
- const clearPendingGate = shouldClearPendingGateForHandoff(state, options);
1528
- const event = buildHandoffEvent({
1529
- state,
1530
- options: {
1531
- ...options,
1532
- clearPendingGate,
1533
- },
1534
- now,
1535
- });
1536
- const updatedState = {
1537
- ...state,
1538
- status: options.status || 'running',
1539
- current_role: options.toRole,
1540
- pending_input_update: false,
1541
- pending_gate: clearPendingGate
1542
- ? null
1543
- : (Object.prototype.hasOwnProperty.call(options, 'pendingGate') ? options.pendingGate || null : state.pending_gate || null),
1544
- gate_context: clearPendingGate
1545
- ? null
1546
- : buildGateContext(state, options),
1547
- verification: options.verificationData || state.verification || null,
1548
- auto_fix: buildNextAutoFixState(state, options),
1549
- anchor: sanitizedAnchor,
1550
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1551
- timestamps: {
1552
- ...(state.timestamps || {}),
1553
- updated_at: now.toISOString(),
1554
- },
1555
- };
1556
-
1557
- const persistedState = saveUpdatedRunState({
1558
- targetDir,
1559
- historyRunPath,
1560
- currentRunPath,
1561
- syncCurrent,
1562
- state: updatedState,
1563
- checkpointEvent: 'handoff',
1564
- });
1565
-
1566
- return {
1567
- status: 'success',
1568
- target: targetDir,
1569
- artifacts: {
1570
- current_run: syncCurrent ? currentRunPath : null,
1571
- run_history: historyRunPath,
1572
- },
1573
- state: persistedState,
1574
- source: {
1575
- task_anchor: taskAnchorPath,
1576
- },
1577
- handoff: {
1578
- from_role: event.from_role || null,
1579
- to_role: options.toRole,
1580
- next_role: options.nextRole || null,
1581
- },
1582
- };
1583
- }
1584
-
1585
- function approveRunState(options) {
1586
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1587
- const taskAnchorPath = options.taskAnchor
1588
- ? path.resolve(process.cwd(), options.taskAnchor)
1589
- : null;
1590
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1591
- const activeGate = state.pending_gate || null;
1592
- const requestedGate = options.gate || activeGate;
1593
-
1594
- if (!activeGate) {
1595
- throw new Error('No pending approval gate found');
1596
- }
1597
- if (options.gate && activeGate && options.gate !== activeGate) {
1598
- throw new Error(`Pending gate mismatch: current is "${activeGate}", requested "${options.gate}"`);
1599
- }
1600
-
1601
- const toRole = inferApprovalResumeRole(state, options);
1602
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1603
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1604
- const now = new Date();
1605
- const event = buildStateEvent({
1606
- state,
1607
- options: { ...options, toRole, clearPendingGate: true },
1608
- now,
1609
- defaults: {
1610
- status: 'running',
1611
- eventType: 'gate-cleared',
1612
- message: `approval cleared for ${requestedGate}`,
1613
- pendingGate: null,
1614
- },
1615
- });
1616
-
1617
- const updatedState = {
1618
- ...state,
1619
- status: options.status || 'running',
1620
- current_role: toRole,
1621
- pending_input_update: false,
1622
- pending_gate: null,
1623
- gate_context: null,
1624
- incremental_update: buildIncrementalUpdateState(state, options, {
1625
- updatedAt: now.toISOString(),
1626
- }),
1627
- auto_fix: buildNextAutoFixState(state, options),
1628
- anchor,
1629
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1630
- timestamps: {
1631
- ...(state.timestamps || {}),
1632
- updated_at: now.toISOString(),
1633
- },
1634
- };
1635
-
1636
- const persistedState = saveUpdatedRunState({
1637
- targetDir,
1638
- historyRunPath,
1639
- currentRunPath,
1640
- syncCurrent,
1641
- state: updatedState,
1642
- checkpointEvent: 'approve',
1643
- });
1644
-
1645
- return {
1646
- status: 'success',
1647
- target: targetDir,
1648
- artifacts: {
1649
- current_run: syncCurrent ? currentRunPath : null,
1650
- run_history: historyRunPath,
1651
- },
1652
- state: persistedState,
1653
- source: {
1654
- task_anchor: taskAnchorPath,
1655
- gate: requestedGate,
1656
- },
1657
- handoff: {
1658
- from_role: event.from_role || null,
1659
- to_role: toRole,
1660
- next_role: options.nextRole || null,
1661
- },
1662
- };
1663
- }
1664
-
1665
- function pauseRunState(options) {
1666
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1667
- const taskAnchorPath = options.taskAnchor
1668
- ? path.resolve(process.cwd(), options.taskAnchor)
1669
- : null;
1670
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1671
- if (['success', 'failed', 'cancelled'].includes(String(state.status || '').toLowerCase())) {
1672
- throw new Error(`Cannot pause terminal run: ${state.run_id}`);
1673
- }
1674
-
1675
- const toRole = options.toRole || state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null;
1676
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1677
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1678
- const now = new Date();
1679
- const event = buildStateEvent({
1680
- state,
1681
- options: { ...options, toRole, clearPendingGate: false },
1682
- now,
1683
- defaults: {
1684
- status: 'paused',
1685
- eventType: 'run-paused',
1686
- message: options.message || 'run paused',
1687
- pendingGate: state.pending_gate || null,
1688
- },
1689
- });
1690
-
1691
- const updatedState = {
1692
- ...state,
1693
- status: options.status || 'paused',
1694
- current_role: toRole,
1695
- pending_input_update: false,
1696
- pending_gate: options.clearPendingGate ? null : state.pending_gate || null,
1697
- gate_context: options.clearPendingGate ? null : state.gate_context || null,
1698
- incremental_update: buildIncrementalUpdateState(state, options, {
1699
- updatedAt: now.toISOString(),
1700
- }),
1701
- auto_fix: buildNextAutoFixState(state, options),
1702
- anchor,
1703
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1704
- timestamps: {
1705
- ...(state.timestamps || {}),
1706
- updated_at: now.toISOString(),
1707
- },
1708
- };
1709
-
1710
- const persistedState = saveUpdatedRunState({
1711
- targetDir,
1712
- historyRunPath,
1713
- currentRunPath,
1714
- syncCurrent,
1715
- state: updatedState,
1716
- });
1717
-
1718
- return {
1719
- status: 'success',
1720
- target: targetDir,
1721
- artifacts: {
1722
- current_run: syncCurrent ? currentRunPath : null,
1723
- run_history: historyRunPath,
1724
- },
1725
- state: persistedState,
1726
- source: {
1727
- task_anchor: taskAnchorPath,
1728
- },
1729
- };
1730
- }
1731
-
1732
- function resumeRunState(options) {
1733
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1734
- const taskAnchorPath = options.taskAnchor
1735
- ? path.resolve(process.cwd(), options.taskAnchor)
1736
- : null;
1737
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1738
- const toRole = state.pending_gate
1739
- ? inferApprovalResumeRole(state, options)
1740
- : (options.toRole || state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null);
1741
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1742
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1743
- const now = new Date();
1744
- const event = buildStateEvent({
1745
- state,
1746
- options: { ...options, toRole, clearPendingGate: true },
1747
- now,
1748
- defaults: {
1749
- status: 'running',
1750
- eventType: 'run-resumed',
1751
- message: `resumed run at ${toRole || 'unknown'}`,
1752
- pendingGate: null,
1753
- },
1754
- });
1755
-
1756
- const updatedState = {
1757
- ...state,
1758
- status: options.status || 'running',
1759
- current_role: toRole,
1760
- pending_input_update: false,
1761
- pending_gate: options.clearPendingGate === false ? state.pending_gate || null : null,
1762
- gate_context: options.clearPendingGate === false ? state.gate_context || null : null,
1763
- incremental_update: buildIncrementalUpdateState(state, options, {
1764
- updatedAt: now.toISOString(),
1765
- }),
1766
- auto_fix: buildNextAutoFixState(state, options),
1767
- anchor,
1768
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1769
- timestamps: {
1770
- ...(state.timestamps || {}),
1771
- updated_at: now.toISOString(),
1772
- },
1773
- };
1774
-
1775
- const persistedState = saveUpdatedRunState({
1776
- targetDir,
1777
- historyRunPath,
1778
- currentRunPath,
1779
- syncCurrent,
1780
- state: updatedState,
1781
- });
1782
-
1783
- return {
1784
- status: 'success',
1785
- target: targetDir,
1786
- artifacts: {
1787
- current_run: syncCurrent ? currentRunPath : null,
1788
- run_history: historyRunPath,
1789
- },
1790
- state: persistedState,
1791
- source: {
1792
- task_anchor: taskAnchorPath,
1793
- },
1794
- };
1795
- }
1796
-
1797
- function restoreRunState(options) {
1798
- if (!options.checkpoint) {
1799
- throw new Error('Missing required argument: --checkpoint <file>');
1800
- }
1801
-
1802
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1803
- const checkpointPath = path.resolve(process.cwd(), options.checkpoint);
1804
- const checkpoint = readJsonFile(checkpointPath, 'runtime checkpoint');
1805
-
1806
- if (checkpoint.kind !== 'runtime-checkpoint' || !checkpoint.state || checkpoint.state.kind !== 'run-state') {
1807
- throw new Error(`Invalid runtime checkpoint: ${checkpointPath}`);
1808
- }
1809
-
1810
- const runId = options.runId || checkpoint.run_id || checkpoint.state.run_id || null;
1811
- if (!runId) {
1812
- throw new Error(`Checkpoint is missing run_id: ${checkpointPath}`);
1813
- }
1814
- if (checkpoint.run_id && checkpoint.run_id !== runId) {
1815
- throw new Error(`Checkpoint run_id mismatch: expected ${runId}, got ${checkpoint.run_id}`);
1816
- }
1817
-
1818
- const { currentRunPath, historyRunPath } = resolveRunStatePaths(targetDir, runId);
1819
- const currentRun = fs.existsSync(currentRunPath) ? readRunStateFile(currentRunPath) : null;
1820
- if (currentRun && currentRun.run_id !== runId) {
1821
- throw new Error(`Restore only supports the current active run; current run is ${currentRun.run_id}, requested ${runId}`);
1822
- }
1823
-
1824
- const now = new Date();
1825
- const restoredState = {
1826
- ...checkpoint.state,
1827
- events: [
1828
- ...(Array.isArray(checkpoint.state.events) ? checkpoint.state.events : []),
1829
- {
1830
- at: now.toISOString(),
1831
- type: 'run-restored',
1832
- status: checkpoint.state.status || 'running',
1833
- from_role: checkpoint.state.current_role || null,
1834
- to_role: checkpoint.state.current_role || null,
1835
- pending_gate: checkpoint.state.pending_gate || null,
1836
- message: `restored from checkpoint ${path.relative(targetDir, checkpointPath)}`,
1837
- },
1838
- ],
1839
- timestamps: {
1840
- ...(checkpoint.state.timestamps || {}),
1841
- updated_at: now.toISOString(),
1842
- },
1843
- };
1844
-
1845
- const persistedState = saveUpdatedRunState({
1846
- targetDir,
1847
- historyRunPath,
1848
- currentRunPath,
1849
- syncCurrent: true,
1850
- forceSyncCurrent: true,
1851
- state: restoredState,
1852
- });
1853
-
1854
- return {
1855
- status: 'success',
1856
- target: targetDir,
1857
- artifacts: {
1858
- current_run: currentRunPath,
1859
- run_history: historyRunPath,
1860
- checkpoint: checkpointPath,
1861
- },
1862
- state: persistedState,
1863
- source: {
1864
- checkpoint: checkpointPath,
1865
- },
1866
- };
1867
- }
1868
-
1869
- function statusRunState(options) {
1870
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1871
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1872
- const events = Array.isArray(state.events) ? state.events : [];
1873
- const lastEvent = events.length > 0 ? events[events.length - 1] : null;
1874
-
1875
- return {
1876
- status: 'success',
1877
- target: targetDir,
1878
- artifacts: {
1879
- current_run: syncCurrent ? currentRunPath : null,
1880
- run_history: historyRunPath,
1881
- },
1882
- summary: {
1883
- run_id: state.run_id,
1884
- mode: state.mode || null,
1885
- delivery_profile: state.delivery_profile || null,
1886
- artifact_profile: state.artifact_profile || null,
1887
- complexity: state.complexity || state.task?.complexity || null,
1888
- status: state.status || null,
1889
- flow_id: state.flow?.id || null,
1890
- current_role: state.current_role || null,
1891
- pending_input_update: Boolean(state.pending_input_update),
1892
- input_update_count: Array.isArray(state.input_updates) ? state.input_updates.length : 0,
1893
- pending_gate: state.pending_gate || null,
1894
- gate_context: state.gate_context || null,
1895
- incremental_update: state.incremental_update || null,
1896
- auto_fix: normalizeAutoFixState(state.auto_fix),
1897
- checkpoint_count: Number(state.checkpoint_count) || 0,
1898
- last_checkpoint: state.last_checkpoint || null,
1899
- updated_at: state.timestamps?.updated_at || null,
1900
- last_event: lastEvent,
1901
- },
1902
- state,
1903
- };
1904
- }
1905
-
1906
- function gateBlockedRunState(options) {
1907
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1908
- const taskAnchorPath = options.taskAnchor
1909
- ? path.resolve(process.cwd(), options.taskAnchor)
1910
- : null;
1911
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1912
- const requestedGate = options.gate || options.pendingGate || state.pending_gate || null;
1913
- const nextStatus = options.status || (requestedGate ? 'waiting-approval' : 'blocked');
1914
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1915
- const toRole = options.toRole || state.current_role || null;
1916
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1917
- const now = new Date();
1918
- const event = buildStateEvent({
1919
- state,
1920
- options: { ...options, pendingGate: requestedGate, toRole },
1921
- now,
1922
- defaults: {
1923
- status: nextStatus,
1924
- eventType: 'gate-blocked',
1925
- message: requestedGate
1926
- ? `waiting for ${requestedGate} approval`
1927
- : 'run blocked',
1928
- pendingGate: requestedGate,
1929
- },
1930
- });
1931
-
1932
- const updatedState = {
1933
- ...state,
1934
- status: nextStatus,
1935
- current_role: toRole,
1936
- pending_input_update: false,
1937
- pending_gate: requestedGate,
1938
- gate_context: buildGateContext(state, options, requestedGate),
1939
- incremental_update: buildIncrementalUpdateState(state, options, {
1940
- updatedAt: now.toISOString(),
1941
- }),
1942
- verification: options.verificationData || state.verification || null,
1943
- auto_fix: buildNextAutoFixState(state, options),
1944
- anchor,
1945
- events: [...(Array.isArray(state.events) ? state.events : []), event],
1946
- timestamps: {
1947
- ...(state.timestamps || {}),
1948
- updated_at: now.toISOString(),
1949
- },
1950
- };
1951
-
1952
- const persistedState = saveUpdatedRunState({
1953
- targetDir,
1954
- historyRunPath,
1955
- currentRunPath,
1956
- syncCurrent,
1957
- state: updatedState,
1958
- checkpointEvent: 'gate-blocked',
1959
- });
1960
-
1961
- return {
1962
- status: 'success',
1963
- target: targetDir,
1964
- artifacts: {
1965
- current_run: syncCurrent ? currentRunPath : null,
1966
- run_history: historyRunPath,
1967
- },
1968
- state: persistedState,
1969
- source: {
1970
- task_anchor: taskAnchorPath,
1971
- gate: requestedGate,
1972
- },
1973
- };
1974
- }
1975
-
1976
- function completeRunState(options) {
1977
- const targetDir = path.resolve(process.cwd(), options.target || '.');
1978
- const taskAnchorPath = options.taskAnchor
1979
- ? path.resolve(process.cwd(), options.taskAnchor)
1980
- : null;
1981
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1982
- if (options.skipArtifactCheck !== true) {
1983
- assertRequiredOpenSpecArtifacts(targetDir, state, 'complete', options.toRole || state.current_role || null);
1984
- }
1985
- const toRole = options.toRole || state.current_role || null;
1986
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1987
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1988
- const nextArtifacts = options.artifactsData
1989
- ? mergeArtifacts(
1990
- mergeArtifacts(buildDefaultArtifacts(state.task?.change_id || state.anchor?.task?.change_id || null), inferArtifacts(state.artifacts || null)),
1991
- inferArtifacts(options.artifactsData),
1992
- )
1993
- : state.artifacts;
1994
- const now = new Date();
1995
- const event = buildStateEvent({
1996
- state,
1997
- options: { ...options, toRole, clearPendingGate: true },
1998
- now,
1999
- defaults: {
2000
- status: 'success',
2001
- eventType: 'run-completed',
2002
- message: 'run completed',
2003
- pendingGate: null,
2004
- },
2005
- });
2006
-
2007
- const updatedState = {
2008
- ...state,
2009
- status: options.status || 'success',
2010
- current_role: toRole,
2011
- pending_input_update: false,
2012
- pending_gate: null,
2013
- gate_context: null,
2014
- incremental_update: buildIncrementalUpdateState(state, options, {
2015
- updatedAt: now.toISOString(),
2016
- }),
2017
- artifacts: nextArtifacts,
2018
- auto_fix: buildNextAutoFixState(state, options, { active: false }),
2019
- anchor,
2020
- events: [...(Array.isArray(state.events) ? state.events : []), event],
2021
- timestamps: {
2022
- ...(state.timestamps || {}),
2023
- updated_at: now.toISOString(),
2024
- finished_at: now.toISOString(),
2025
- },
2026
- };
2027
-
2028
- const persistedState = saveUpdatedRunState({
2029
- targetDir,
2030
- historyRunPath,
2031
- currentRunPath,
2032
- syncCurrent,
2033
- state: updatedState,
2034
- checkpointEvent: 'complete',
2035
- });
2036
-
2037
- return {
2038
- status: 'success',
2039
- target: targetDir,
2040
- artifacts: {
2041
- current_run: syncCurrent ? currentRunPath : null,
2042
- run_history: historyRunPath,
2043
- },
2044
- state: persistedState,
2045
- source: {
2046
- task_anchor: taskAnchorPath,
2047
- },
2048
- };
2049
- }
2050
-
2051
- function failRunState(options) {
2052
- const targetDir = path.resolve(process.cwd(), options.target || '.');
2053
- const taskAnchorPath = options.taskAnchor
2054
- ? path.resolve(process.cwd(), options.taskAnchor)
2055
- : null;
2056
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
2057
- const toRole = options.toRole || state.current_role || null;
2058
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
2059
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
2060
- const now = new Date();
2061
- const errorMessage = options.error || options.message || 'run failed';
2062
- const event = buildStateEvent({
2063
- state,
2064
- options: { ...options, toRole, clearPendingGate: true, message: errorMessage },
2065
- now,
2066
- defaults: {
2067
- status: 'failed',
2068
- eventType: 'run-failed',
2069
- message: errorMessage,
2070
- pendingGate: null,
2071
- },
2072
- });
2073
-
2074
- const updatedErrors = [...(Array.isArray(state.errors) ? state.errors : [])];
2075
- if (errorMessage) {
2076
- updatedErrors.push(errorMessage);
2077
- }
2078
-
2079
- const updatedState = {
2080
- ...state,
2081
- status: options.status || 'failed',
2082
- current_role: toRole,
2083
- pending_input_update: false,
2084
- pending_gate: null,
2085
- gate_context: null,
2086
- incremental_update: buildIncrementalUpdateState(state, options, {
2087
- updatedAt: now.toISOString(),
2088
- }),
2089
- auto_fix: buildNextAutoFixState(state, options, { active: false }),
2090
- anchor,
2091
- errors: updatedErrors,
2092
- events: [...(Array.isArray(state.events) ? state.events : []), event],
2093
- timestamps: {
2094
- ...(state.timestamps || {}),
2095
- updated_at: now.toISOString(),
2096
- finished_at: now.toISOString(),
2097
- },
2098
- };
2099
-
2100
- const persistedState = saveUpdatedRunState({
2101
- targetDir,
2102
- historyRunPath,
2103
- currentRunPath,
2104
- syncCurrent,
2105
- state: updatedState,
2106
- checkpointEvent: 'fail',
2107
- });
2108
-
2109
- return {
2110
- status: 'success',
2111
- target: targetDir,
2112
- artifacts: {
2113
- current_run: syncCurrent ? currentRunPath : null,
2114
- run_history: historyRunPath,
2115
- },
2116
- state: persistedState,
2117
- source: {
2118
- task_anchor: taskAnchorPath,
2119
- error: options.error || null,
2120
- },
2121
- };
2122
- }
2123
-
2124
- function cancelRunState(options) {
2125
- const targetDir = path.resolve(process.cwd(), options.target || '.');
2126
- const taskAnchorPath = options.taskAnchor
2127
- ? path.resolve(process.cwd(), options.taskAnchor)
2128
- : null;
2129
- const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
2130
- const toRole = options.toRole || state.current_role || null;
2131
- const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
2132
- const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
2133
- const now = new Date();
2134
- const cancelMessage = options.message || 'run cancelled';
2135
- const event = buildStateEvent({
2136
- state,
2137
- options: { ...options, toRole, clearPendingGate: true, message: cancelMessage },
2138
- now,
2139
- defaults: {
2140
- status: 'cancelled',
2141
- eventType: 'run-cancelled',
2142
- message: cancelMessage,
2143
- pendingGate: null,
2144
- },
2145
- });
2146
-
2147
- const updatedState = {
2148
- ...state,
2149
- status: options.status || 'cancelled',
2150
- current_role: toRole,
2151
- pending_input_update: false,
2152
- pending_gate: null,
2153
- gate_context: null,
2154
- incremental_update: buildIncrementalUpdateState(state, options, {
2155
- updatedAt: now.toISOString(),
2156
- }),
2157
- auto_fix: buildNextAutoFixState(state, options, { active: false }),
2158
- anchor,
2159
- events: [...(Array.isArray(state.events) ? state.events : []), event],
2160
- timestamps: {
2161
- ...(state.timestamps || {}),
2162
- updated_at: now.toISOString(),
2163
- finished_at: now.toISOString(),
2164
- },
2165
- };
2166
-
2167
- const persistedState = saveUpdatedRunState({
2168
- targetDir,
2169
- historyRunPath,
2170
- currentRunPath,
2171
- syncCurrent,
2172
- state: updatedState,
2173
- checkpointEvent: 'cancel',
2174
- });
2175
-
2176
- return {
2177
- status: 'success',
2178
- target: targetDir,
2179
- artifacts: {
2180
- current_run: syncCurrent ? currentRunPath : null,
2181
- run_history: historyRunPath,
2182
- },
2183
- state: persistedState,
2184
- source: {
2185
- task_anchor: taskAnchorPath,
2186
- },
2187
- };
2188
- }
2189
-
2190
- function initRunState(options) {
2191
- const targetDir = path.resolve(process.cwd(), options.target || '.');
2192
- const runPlanPath = path.resolve(process.cwd(), options.runPlan);
2193
- const taskAnchorPath = options.taskAnchor
2194
- ? path.resolve(process.cwd(), options.taskAnchor)
2195
- : null;
2196
-
2197
- const runPlan = readJsonFile(runPlanPath, 'run-plan');
2198
- assertRunPlan(runPlan, runPlanPath);
2199
-
2200
- const taskAnchor = taskAnchorPath
2201
- ? readJsonFile(taskAnchorPath, 'task-anchor')
2202
- : null;
2203
-
2204
- return writeRunState({
2205
- targetDir,
2206
- runPlan,
2207
- taskAnchor,
2208
- options,
2209
- source: {
2210
- runPlan: runPlanPath,
2211
- taskAnchor: taskAnchorPath,
2212
- bootstrapPayload: null,
2213
- },
2214
- });
2215
- }
2216
-
2217
- function bootstrapRunState(options) {
2218
- const targetDir = path.resolve(process.cwd(), options.target || '.');
2219
- const inputCount = [
2220
- Boolean(options.payload),
2221
- Boolean(options.stdin),
2222
- Boolean(options.payloadData),
2223
- ].filter(Boolean).length;
2224
- const hasInput = inputCount > 0;
2225
-
2226
- if (!hasInput) {
2227
- throw new Error('Missing bootstrap input: use --payload <file> or --stdin');
2228
- }
2229
- if (inputCount > 1) {
2230
- throw new Error('Use only one bootstrap input: --payload <file>, --stdin, or payloadData');
2231
- }
2232
-
2233
- const payloadSource = options.payloadData
2234
- ? 'memory-payload'
2235
- : options.payload
2236
- ? path.resolve(process.cwd(), options.payload)
2237
- : 'stdin';
2238
- const payload = options.payloadData
2239
- ? options.payloadData
2240
- : options.payload
2241
- ? readJsonFile(payloadSource, 'bootstrap payload')
2242
- : readJsonFromStdin('bootstrap payload');
2243
-
2244
- const { runPlan, taskAnchor } = normalizeBootstrapPayload(payload, payloadSource);
2245
- assertRunPlan(runPlan, payloadSource);
2246
-
2247
- return writeRunState({
2248
- targetDir,
2249
- runPlan,
2250
- taskAnchor,
2251
- options,
2252
- source: {
2253
- runPlan: payloadSource,
2254
- taskAnchor: payloadSource,
2255
- bootstrapPayload: payloadSource,
2256
- },
2257
- });
2258
- }
2259
-
2260
- function printPretty(result, action = 'init') {
2261
- if (action === 'handoff') {
2262
- console.log('run-state updated');
2263
- } else if (action === 'approve') {
2264
- console.log('run-state approved');
2265
- } else if (action === 'resume') {
2266
- console.log('run-state resumed');
2267
- } else if (action === 'pause') {
2268
- console.log('run-state paused');
2269
- } else if (action === 'restore') {
2270
- console.log('run-state restored');
2271
- } else if (action === 'gate-blocked') {
2272
- console.log('run-state blocked');
2273
- } else if (action === 'status') {
2274
- console.log('run-state status');
2275
- } else if (action === 'complete') {
2276
- console.log('run-state completed');
2277
- } else if (action === 'fail') {
2278
- console.log('run-state failed');
2279
- } else if (action === 'cancel') {
2280
- console.log('run-state cancelled');
2281
- } else {
2282
- console.log('run-state initialized');
2283
- }
2284
- console.log(` target: ${result.target}`);
2285
- console.log(` run_id: ${result.state.run_id}`);
2286
- console.log(` current: ${result.artifacts.current_run}`);
2287
- if (result.artifacts.run_history) {
2288
- console.log(` history: ${result.artifacts.run_history}`);
2289
- }
2290
- console.log(` mode: ${result.state.mode || 'n/a'}`);
2291
- console.log(` review_policy: ${result.state.review_policy || 'n/a'}`);
2292
- console.log(` delivery_profile: ${result.state.delivery_profile || 'n/a'}`);
2293
- console.log(` artifact_profile: ${result.state.artifact_profile || 'n/a'}`);
2294
- console.log(` complexity: ${result.state.complexity || result.state.task?.complexity || 'n/a'}`);
2295
- console.log(` checkpoints: ${Number(result.state.checkpoint_count) || 0}`);
2296
- if (result.state.last_checkpoint?.file) {
2297
- console.log(` last_checkpoint: ${result.state.last_checkpoint.file}`);
2298
- }
2299
- if (action === 'status') {
2300
- console.log(` status: ${result.state.status || 'n/a'}`);
2301
- console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2302
- console.log(` pending_gate: ${result.state.pending_gate || 'n/a'}`);
2303
- if (result.state.gate_context?.required_user_action) {
2304
- console.log(` required_user_action: ${result.state.gate_context.required_user_action}`);
2305
- }
2306
- } else if (action === 'handoff') {
2307
- console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2308
- console.log(` from_role: ${result.handoff?.from_role || 'n/a'}`);
2309
- console.log(` to_role: ${result.handoff?.to_role || 'n/a'}`);
2310
- } else if (
2311
- action === 'pause' ||
2312
- action === 'approve' ||
2313
- action === 'resume' ||
2314
- action === 'restore' ||
2315
- action === 'gate-blocked' ||
2316
- action === 'complete' ||
2317
- action === 'fail'
2318
- ) {
2319
- console.log(` status: ${result.state.status || 'n/a'}`);
2320
- console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2321
- console.log(` pending_gate: ${result.state.pending_gate || 'n/a'}`);
2322
- } else {
2323
- console.log(` first_handoff: ${result.state.plan.first_handoff || 'n/a'}`);
2324
- }
2325
- if (result.source.bootstrap_payload) {
2326
- console.log(` bootstrap_payload: ${result.source.bootstrap_payload}`);
2327
- }
2328
- }
2329
-
2330
- function main(argv = process.argv.slice(2)) {
2331
- const { command, options } = parseArgs(argv);
2332
-
2333
- if (!command || options.help || command === '--help' || command === '-h' || command === 'help') {
2334
- printUsage();
2335
- return 0;
2336
- }
2337
-
2338
- if (command === 'init') {
2339
- if (!options.runPlan) {
2340
- throw new Error('Missing required argument: --run-plan <file>');
2341
- }
2342
-
2343
- const result = initRunState(options);
2344
-
2345
- if (options.json) {
2346
- console.log(JSON.stringify(result, null, 2));
2347
- } else {
2348
- printPretty(result, 'init');
2349
- }
2350
-
2351
- return 0;
2352
- }
2353
-
2354
- if (command === 'bootstrap') {
2355
- const result = bootstrapRunState(options);
2356
-
2357
- if (options.json) {
2358
- console.log(JSON.stringify(result, null, 2));
2359
- } else {
2360
- printPretty(result, 'bootstrap');
2361
- }
2362
-
2363
- return 0;
2364
- }
2365
-
2366
- if (command === 'handoff') {
2367
- const result = handoffRunState(options);
2368
-
2369
- if (options.json) {
2370
- console.log(JSON.stringify(result, null, 2));
2371
- } else {
2372
- printPretty(result, 'handoff');
2373
- }
2374
-
2375
- return 0;
2376
- }
2377
-
2378
- if (command === 'approve') {
2379
- const result = approveRunState(options);
2380
-
2381
- if (options.json) {
2382
- console.log(JSON.stringify(result, null, 2));
2383
- } else {
2384
- printPretty(result, 'approve');
2385
- }
2386
-
2387
- return 0;
2388
- }
2389
-
2390
- if (command === 'pause') {
2391
- const result = pauseRunState(options);
2392
-
2393
- if (options.json) {
2394
- console.log(JSON.stringify(result, null, 2));
2395
- } else {
2396
- printPretty(result, 'pause');
2397
- }
2398
-
2399
- return 0;
2400
- }
2401
-
2402
- if (command === 'resume') {
2403
- const result = resumeRunState(options);
2404
-
2405
- if (options.json) {
2406
- console.log(JSON.stringify(result, null, 2));
2407
- } else {
2408
- printPretty(result, 'resume');
2409
- }
2410
-
2411
- return 0;
2412
- }
2413
-
2414
- if (command === 'restore') {
2415
- const result = restoreRunState(options);
2416
-
2417
- if (options.json) {
2418
- console.log(JSON.stringify(result, null, 2));
2419
- } else {
2420
- printPretty(result, 'restore');
2421
- }
2422
-
2423
- return 0;
2424
- }
2425
-
2426
- if (command === 'gate-blocked') {
2427
- const result = gateBlockedRunState(options);
2428
-
2429
- if (options.json) {
2430
- console.log(JSON.stringify(result, null, 2));
2431
- } else {
2432
- printPretty(result, 'gate-blocked');
2433
- }
2434
-
2435
- return 0;
2436
- }
2437
-
2438
- if (command === 'status') {
2439
- const result = statusRunState(options);
2440
-
2441
- if (options.json) {
2442
- console.log(JSON.stringify(result, null, 2));
2443
- } else {
2444
- printPretty(result, 'status');
2445
- }
2446
-
2447
- return 0;
2448
- }
2449
-
2450
- if (command === 'complete') {
2451
- const result = completeRunState(options);
2452
-
2453
- if (options.json) {
2454
- console.log(JSON.stringify(result, null, 2));
2455
- } else {
2456
- printPretty(result, 'complete');
2457
- }
2458
-
2459
- return 0;
2460
- }
2461
-
2462
- if (command === 'fail') {
2463
- const result = failRunState(options);
2464
-
2465
- if (options.json) {
2466
- console.log(JSON.stringify(result, null, 2));
2467
- } else {
2468
- printPretty(result, 'fail');
2469
- }
2470
-
2471
- return 0;
2472
- }
2473
-
2474
- if (command === 'cancel') {
2475
- const result = cancelRunState(options);
2476
-
2477
- if (options.json) {
2478
- console.log(JSON.stringify(result, null, 2));
2479
- } else {
2480
- printPretty(result, 'cancel');
2481
- }
2482
-
2483
- return 0;
2484
- }
2485
-
2486
- if (
2487
- command !== 'init' &&
2488
- command !== 'bootstrap' &&
2489
- command !== 'handoff' &&
2490
- command !== 'approve' &&
2491
- command !== 'pause' &&
2492
- command !== 'resume' &&
2493
- command !== 'restore' &&
2494
- command !== 'gate-blocked' &&
2495
- command !== 'status' &&
2496
- command !== 'complete' &&
2497
- command !== 'fail' &&
2498
- command !== 'cancel'
2499
- ) {
2500
- throw new Error(`Unsupported runtime-state command: ${command}`);
2501
- }
2502
- }
2503
-
2504
- if (require.main === module) {
2505
- try {
2506
- const exitCode = main();
2507
- process.exit(exitCode);
2508
- } catch (error) {
2509
- console.error(`runtime-state error: ${error.message}`);
2510
- process.exit(1);
2511
- }
2512
- }
2513
-
2514
- module.exports = {
2515
- main,
2516
- parseArgs,
2517
- createRunId,
2518
- normalizeSpecsArtifactPath,
2519
- inferDeliveryProfile,
2520
- inferArtifactProfile,
2521
- inferComplexity,
2522
- inferRiskLevel,
2523
- inferArtifacts,
2524
- buildRunState,
2525
- recordRunInputUpdate,
2526
- readRunStateFile,
2527
- resolveRunStatePaths,
2528
- initRunState,
2529
- bootstrapRunState,
2530
- normalizeBootstrapPayload,
2531
- handoffRunState,
2532
- approveRunState,
2533
- pauseRunState,
2534
- resumeRunState,
2535
- restoreRunState,
2536
- gateBlockedRunState,
2537
- statusRunState,
2538
- completeRunState,
2539
- failRunState,
2540
- cancelRunState,
2541
- };
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { spawnSync } = require('child_process');
5
+ const {
6
+ resolveRuntimePaths,
7
+ getExistingPath,
8
+ getCandidatePaths,
9
+ shouldPersistHistory,
10
+ shouldPersistCheckpoints,
11
+ } = require('./runtime-paths');
12
+ const { syncRepoMap } = require('./repo-map');
13
+
14
+ function printUsage() {
15
+ console.log(`Usage:
16
+ ai-spec-auto runtime-state init --run-plan <file> [options]
17
+ ai-spec-auto runtime-state bootstrap --payload <file> [options]
18
+ ai-spec-auto runtime-state bootstrap --stdin [options]
19
+ ai-spec-auto runtime-state handoff --to-role <role> [options]
20
+ ai-spec-auto runtime-state approve [options]
21
+ ai-spec-auto runtime-state pause [options]
22
+ ai-spec-auto runtime-state resume [options]
23
+ ai-spec-auto runtime-state restore --checkpoint <file> [options]
24
+ ai-spec-auto runtime-state gate-blocked [options]
25
+ ai-spec-auto runtime-state complete [options]
26
+ ai-spec-auto runtime-state fail [options]
27
+ ai-spec-auto runtime-state cancel [options]
28
+ ai-spec-auto runtime-state status [options]
29
+
30
+ Options:
31
+ --target <dir> Target project directory (default: .)
32
+ --run-plan <file> Path to run-plan JSON file
33
+ --task-anchor <file> Optional path to task-anchor JSON file
34
+ --payload <file> Path to task-orchestrator bootstrap payload JSON file
35
+ --stdin Read bootstrap payload JSON from stdin
36
+ --run-id <id> Override generated run id
37
+ --to-role <role> Target role for handoff update
38
+ --next-role <role> Next role after current handoff
39
+ --from-role <role> Explicit source role override
40
+ --gate <id> Expected approval gate id
41
+ --checkpoint <file> Restore source checkpoint JSON file
42
+ --pending-gate <id> Pending approval gate id
43
+ --clear-pending-gate Clear current pending gate
44
+ --blocked-by-role <id> Role that raised the current gate
45
+ --resume-to-role <id> Role to resume into after approval
46
+ --required-user-action <text>
47
+ Explicit user action required by the gate
48
+ --blocked-reason <text> Human-readable reason for the gate
49
+ --message <text> Event message override
50
+ --error <text> Failure detail appended to errors list
51
+ --event-type <type> Event type override (default: role-handoff)
52
+ --status <status> planned | running | paused | waiting-confirm | waiting-approval | blocked | success | failed | cancelled
53
+ --trigger-source <src> Trigger source (default: ide-skill)
54
+ --entry <entry> Entry role (default: task-orchestrator)
55
+ --raw-input <text> Raw user input override
56
+ --change-id <id> Change id override
57
+ --change-impact <kind> patch | scope-delta | re-scope | archive-fix | followup-patch
58
+ --reconcile-strategy <strategy>
59
+ in-place | rewind-to-requirement | rewind-to-frontend | rewind-to-guardian | suggest-new-change | followup-patch
60
+ --reopen-reason <text> Human-readable reopen / repair reason
61
+ --parent-change-id <id> Parent change id for follow-up patch runs
62
+ --artifacts-to-update <items>
63
+ Comma-separated artifact hints to update incrementally
64
+ --json Print JSON result
65
+ --pretty Print readable summary (default)
66
+ --help Show this help
67
+
68
+ Environment:
69
+ AI_SPEC_PERSIST_CHECKPOINTS=1
70
+ Persist .ai-spec/checkpoints/<run-id>/*.json for restore/debug
71
+ `);
72
+ }
73
+
74
+ function parseArgs(argv) {
75
+ const args = [...argv];
76
+ const command = args.shift();
77
+ const options = {
78
+ target: '.',
79
+ triggerSource: 'ide-skill',
80
+ entry: 'task-orchestrator',
81
+ pretty: true,
82
+ json: false,
83
+ };
84
+
85
+ while (args.length > 0) {
86
+ const arg = args.shift();
87
+ switch (arg) {
88
+ case '--target':
89
+ options.target = args.shift();
90
+ break;
91
+ case '--run-plan':
92
+ options.runPlan = args.shift();
93
+ break;
94
+ case '--task-anchor':
95
+ case '--anchor':
96
+ options.taskAnchor = args.shift();
97
+ break;
98
+ case '--payload':
99
+ options.payload = args.shift();
100
+ break;
101
+ case '--stdin':
102
+ options.stdin = true;
103
+ break;
104
+ case '--run-id':
105
+ options.runId = args.shift();
106
+ break;
107
+ case '--to-role':
108
+ options.toRole = args.shift();
109
+ break;
110
+ case '--next-role':
111
+ options.nextRole = args.shift();
112
+ break;
113
+ case '--from-role':
114
+ options.fromRole = args.shift();
115
+ break;
116
+ case '--gate':
117
+ options.gate = args.shift();
118
+ break;
119
+ case '--checkpoint':
120
+ options.checkpoint = args.shift();
121
+ break;
122
+ case '--pending-gate':
123
+ options.pendingGate = args.shift();
124
+ break;
125
+ case '--clear-pending-gate':
126
+ options.clearPendingGate = true;
127
+ break;
128
+ case '--blocked-by-role':
129
+ options.blockedByRole = args.shift();
130
+ break;
131
+ case '--resume-to-role':
132
+ options.resumeToRole = args.shift();
133
+ break;
134
+ case '--required-user-action':
135
+ options.requiredUserAction = args.shift();
136
+ break;
137
+ case '--blocked-reason':
138
+ options.blockedReason = args.shift();
139
+ break;
140
+ case '--message':
141
+ options.message = args.shift();
142
+ break;
143
+ case '--error':
144
+ options.error = args.shift();
145
+ break;
146
+ case '--event-type':
147
+ options.eventType = args.shift();
148
+ break;
149
+ case '--status':
150
+ options.status = args.shift();
151
+ break;
152
+ case '--trigger-source':
153
+ options.triggerSource = args.shift();
154
+ break;
155
+ case '--entry':
156
+ options.entry = args.shift();
157
+ break;
158
+ case '--raw-input':
159
+ options.rawInput = args.shift();
160
+ break;
161
+ case '--change-id':
162
+ options.changeId = args.shift();
163
+ break;
164
+ case '--change-impact':
165
+ options.changeImpact = args.shift();
166
+ break;
167
+ case '--reconcile-strategy':
168
+ options.reconcileStrategy = args.shift();
169
+ break;
170
+ case '--reopen-reason':
171
+ options.reopenReason = args.shift();
172
+ break;
173
+ case '--parent-change-id':
174
+ options.parentChangeId = args.shift();
175
+ break;
176
+ case '--artifacts-to-update':
177
+ options.artifactsToUpdate = String(args.shift() || '')
178
+ .split(',')
179
+ .map((item) => item.trim())
180
+ .filter(Boolean);
181
+ break;
182
+ case '--json':
183
+ options.json = true;
184
+ options.pretty = false;
185
+ break;
186
+ case '--pretty':
187
+ options.pretty = true;
188
+ options.json = false;
189
+ break;
190
+ case '--help':
191
+ case '-h':
192
+ options.help = true;
193
+ break;
194
+ default:
195
+ throw new Error(`Unknown argument: ${arg}`);
196
+ }
197
+ }
198
+
199
+ return { command, options };
200
+ }
201
+
202
+ const DEFAULT_RUN_MODE = 'auto';
203
+ const DEFAULT_REVIEW_POLICY = 'none';
204
+ const RUN_MODES = new Set(['auto', 'suggest', 'manual']);
205
+ const REVIEW_POLICIES = new Set(['none', 'main-flow-blocking']);
206
+
207
+ function normalizeRunMode(value) {
208
+ const normalized = String(value || '').trim().toLowerCase();
209
+ return RUN_MODES.has(normalized) ? normalized : DEFAULT_RUN_MODE;
210
+ }
211
+
212
+ function normalizeReviewPolicy(value) {
213
+ const normalized = String(value || '').trim().toLowerCase();
214
+ return REVIEW_POLICIES.has(normalized) ? normalized : DEFAULT_REVIEW_POLICY;
215
+ }
216
+
217
+ function buildEffectiveApprovalGates(flowId, gates, reviewPolicy) {
218
+ const normalizedPolicy = normalizeReviewPolicy(reviewPolicy);
219
+ const deduped = Array.isArray(gates)
220
+ ? [...new Set(gates.map((item) => String(item || '').trim()).filter(Boolean))]
221
+ : [];
222
+ if (flowId !== 'prd-to-delivery' || normalizedPolicy !== 'main-flow-blocking') {
223
+ return deduped;
224
+ }
225
+ const supportedMainFlowGates = new Set(['before-implementation', 'before-guardian', 'before-archive']);
226
+ const shouldInjectMainFlowGates = deduped.length === 0 || deduped.every((gate) => supportedMainFlowGates.has(gate));
227
+ if (!shouldInjectMainFlowGates) {
228
+ return deduped;
229
+ }
230
+
231
+ const ordered = [];
232
+ for (const gate of ['before-implementation', 'before-guardian', 'before-archive']) {
233
+ if (!ordered.includes(gate)) {
234
+ ordered.push(gate);
235
+ }
236
+ }
237
+ for (const gate of deduped) {
238
+ if (!ordered.includes(gate)) {
239
+ ordered.push(gate);
240
+ }
241
+ }
242
+ return ordered;
243
+ }
244
+
245
+ function readJsonFile(filePath, label) {
246
+ const raw = fs.readFileSync(filePath, 'utf8');
247
+ try {
248
+ return JSON.parse(raw);
249
+ } catch (error) {
250
+ throw new Error(`${label} is not valid JSON: ${filePath}`);
251
+ }
252
+ }
253
+
254
+ function readJsonFromStdin(label) {
255
+ const raw = fs.readFileSync(0, 'utf8');
256
+ if (!raw.trim()) {
257
+ throw new Error(`${label} stdin is empty`);
258
+ }
259
+ try {
260
+ return JSON.parse(raw);
261
+ } catch (error) {
262
+ throw new Error(`${label} stdin is not valid JSON`);
263
+ }
264
+ }
265
+
266
+ function assertRunPlan(runPlan, filePath) {
267
+ if (!runPlan || typeof runPlan !== 'object') {
268
+ throw new Error(`Invalid run-plan object: ${filePath}`);
269
+ }
270
+ if (runPlan.kind !== 'run-plan') {
271
+ throw new Error(`Expected kind "run-plan" but got "${runPlan.kind || 'undefined'}": ${filePath}`);
272
+ }
273
+ if (!runPlan.flow || !runPlan.flow.id) {
274
+ throw new Error(`run-plan is missing flow.id: ${filePath}`);
275
+ }
276
+ if (!runPlan.plan || !runPlan.plan.first_handoff) {
277
+ throw new Error(`run-plan is missing plan.first_handoff: ${filePath}`);
278
+ }
279
+ }
280
+
281
+ function pad2(value) {
282
+ return String(value).padStart(2, '0');
283
+ }
284
+
285
+ function createRunId(now = new Date()) {
286
+ const y = now.getFullYear();
287
+ const m = pad2(now.getMonth() + 1);
288
+ const d = pad2(now.getDate());
289
+ const hh = pad2(now.getHours());
290
+ const mm = pad2(now.getMinutes());
291
+ const ss = pad2(now.getSeconds());
292
+ const rand = Math.random().toString(36).slice(2, 6);
293
+ return `run_${y}${m}${d}_${hh}${mm}${ss}_${rand}`;
294
+ }
295
+
296
+ function slugifyValue(value) {
297
+ return String(value || '')
298
+ .toLowerCase()
299
+ .replace(/[^a-z0-9]+/g, '-')
300
+ .replace(/^-+|-+$/g, '')
301
+ .replace(/-{2,}/g, '-');
302
+ }
303
+
304
+ function deriveChangeId({ explicitChangeId, rawInput, taskType, runId }) {
305
+ const normalizedExplicit = slugifyValue(explicitChangeId);
306
+ if (normalizedExplicit) {
307
+ return normalizedExplicit;
308
+ }
309
+
310
+ const normalizedInput = slugifyValue(rawInput);
311
+ if (normalizedInput) {
312
+ return normalizedInput.slice(0, 64);
313
+ }
314
+
315
+ const normalizedTaskType = slugifyValue(taskType) || 'change';
316
+ const normalizedRunId = slugifyValue(runId) || 'run';
317
+ return `${normalizedTaskType}-${normalizedRunId}`.slice(0, 96);
318
+ }
319
+
320
+ const FLOW_APPROVAL_RESUME_ROLE_HINTS = {
321
+ 'prd-to-delivery': {
322
+ 'requirement-analyst': 'frontend-implementer',
323
+ 'frontend-implementer': 'code-guardian',
324
+ 'code-guardian': 'archive-change',
325
+ 'archive-change': 'archive-change',
326
+ },
327
+ };
328
+
329
+ function inferApprovalResumeRole(state, options = {}) {
330
+ if (options.toRole || options.nextRole) {
331
+ return options.toRole || options.nextRole;
332
+ }
333
+
334
+ const gateResumeRole = state.gate_context?.resume_to_role || null;
335
+ if (gateResumeRole) {
336
+ return gateResumeRole;
337
+ }
338
+
339
+ const anchorNextRole = state.anchor?.stage?.next_role || null;
340
+ if (anchorNextRole) {
341
+ return anchorNextRole;
342
+ }
343
+
344
+ const flowId = state.flow?.id || null;
345
+ const currentRole = state.current_role || null;
346
+ const hintedRole = flowId && currentRole
347
+ ? FLOW_APPROVAL_RESUME_ROLE_HINTS[flowId]?.[currentRole] || null
348
+ : null;
349
+ if (hintedRole) {
350
+ return hintedRole;
351
+ }
352
+
353
+ return state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null;
354
+ }
355
+
356
+ const CHECKPOINT_EVENTS = new Set([
357
+ 'bootstrap',
358
+ 'handoff',
359
+ 'gate-blocked',
360
+ 'approve',
361
+ 'pause',
362
+ 'complete',
363
+ 'fail',
364
+ 'cancel',
365
+ ]);
366
+
367
+ function buildGateContext(state, options = {}, fallbackGate = null) {
368
+ const gateId = options.gateId || options.gate || options.pendingGate || fallbackGate || state?.pending_gate || null;
369
+ if (!gateId) {
370
+ return null;
371
+ }
372
+
373
+ return {
374
+ gate_id: gateId,
375
+ blocked_by_role: options.blockedByRole || options.fromRole || state?.current_role || null,
376
+ resume_to_role: options.resumeToRole || options.nextRole || options.toRole || inferApprovalResumeRole(state || {}, options) || null,
377
+ required_user_action: options.requiredUserAction || state?.gate_context?.required_user_action || null,
378
+ blocked_reason: options.blockedReason || state?.gate_context?.blocked_reason || null,
379
+ };
380
+ }
381
+
382
+ function buildIncrementalUpdateState(state, options = {}, defaults = {}) {
383
+ const previous = state?.incremental_update && typeof state.incremental_update === 'object'
384
+ ? state.incremental_update
385
+ : {};
386
+ const next = {
387
+ change_context: options.changeContext || defaults.changeContext || previous.change_context || null,
388
+ route_decision: options.routeDecision || defaults.routeDecision || previous.route_decision || null,
389
+ trace_mode: options.traceMode || defaults.traceMode || previous.trace_mode || null,
390
+ change_impact: options.changeImpact || defaults.changeImpact || previous.change_impact || null,
391
+ reconcile_strategy: options.reconcileStrategy || defaults.reconcileStrategy || previous.reconcile_strategy || null,
392
+ artifacts_to_update: Array.isArray(options.artifactsToUpdate)
393
+ ? options.artifactsToUpdate
394
+ : Array.isArray(defaults.artifactsToUpdate)
395
+ ? defaults.artifactsToUpdate
396
+ : Array.isArray(previous.artifacts_to_update)
397
+ ? previous.artifacts_to_update
398
+ : [],
399
+ reopen_reason: options.reopenReason || defaults.reopenReason || previous.reopen_reason || null,
400
+ parent_change_id: options.parentChangeId || defaults.parentChangeId || previous.parent_change_id || null,
401
+ target_role: options.toRole || options.nextRole || defaults.targetRole || previous.target_role || null,
402
+ handoff_gate: options.handoffGate || defaults.handoffGate || previous.handoff_gate || null,
403
+ updated_at: defaults.updatedAt || previous.updated_at || null,
404
+ };
405
+
406
+ if (
407
+ !next.change_context &&
408
+ !next.route_decision &&
409
+ !next.trace_mode &&
410
+ !next.change_impact &&
411
+ !next.reconcile_strategy &&
412
+ next.artifacts_to_update.length === 0 &&
413
+ !next.reopen_reason &&
414
+ !next.parent_change_id &&
415
+ !next.target_role &&
416
+ !next.handoff_gate
417
+ ) {
418
+ return null;
419
+ }
420
+
421
+ return next;
422
+ }
423
+
424
+ function buildDefaultAutoFixState() {
425
+ return {
426
+ attempts: 0,
427
+ max_attempts: 1,
428
+ active: false,
429
+ last_failed_steps: [],
430
+ };
431
+ }
432
+
433
+ function normalizeAutoFixStep(step) {
434
+ if (!step || typeof step !== 'object') {
435
+ return null;
436
+ }
437
+
438
+ return {
439
+ name: typeof step.name === 'string' && step.name.trim() ? step.name.trim() : 'unknown',
440
+ status: typeof step.status === 'string' && step.status.trim() ? step.status.trim() : null,
441
+ command: typeof step.command === 'string' && step.command.trim() ? step.command.trim() : null,
442
+ exit_code: typeof step.exit_code === 'number' ? step.exit_code : null,
443
+ reason: typeof step.reason === 'string' && step.reason.trim() ? step.reason.trim() : null,
444
+ error: typeof step.error === 'string' && step.error.trim() ? step.error.trim() : null,
445
+ stdout_excerpt: typeof step.stdout_excerpt === 'string' && step.stdout_excerpt.trim() ? step.stdout_excerpt.trim() : null,
446
+ stderr_excerpt: typeof step.stderr_excerpt === 'string' && step.stderr_excerpt.trim() ? step.stderr_excerpt.trim() : null,
447
+ };
448
+ }
449
+
450
+ function normalizeAutoFixState(value) {
451
+ const defaults = buildDefaultAutoFixState();
452
+ const merged = value && typeof value === 'object'
453
+ ? { ...defaults, ...value }
454
+ : defaults;
455
+ const maxAttempts = Number.isFinite(Number(merged.max_attempts))
456
+ ? Math.max(1, Number(merged.max_attempts))
457
+ : defaults.max_attempts;
458
+ const attempts = Number.isFinite(Number(merged.attempts))
459
+ ? Math.max(0, Math.min(Number(merged.attempts), maxAttempts))
460
+ : defaults.attempts;
461
+ const lastFailedSteps = Array.isArray(merged.last_failed_steps)
462
+ ? merged.last_failed_steps
463
+ .map((item) => normalizeAutoFixStep(item))
464
+ .filter(Boolean)
465
+ : [];
466
+
467
+ return {
468
+ attempts,
469
+ max_attempts: maxAttempts,
470
+ active: Boolean(merged.active),
471
+ last_failed_steps: lastFailedSteps,
472
+ };
473
+ }
474
+
475
+ function buildNextAutoFixState(state, options = {}, defaults = {}) {
476
+ const current = normalizeAutoFixState(state?.auto_fix);
477
+ if (options.autoFixData && typeof options.autoFixData === 'object') {
478
+ return normalizeAutoFixState({
479
+ ...current,
480
+ ...options.autoFixData,
481
+ });
482
+ }
483
+
484
+ if (defaults && typeof defaults === 'object' && Object.keys(defaults).length > 0) {
485
+ return normalizeAutoFixState({
486
+ ...current,
487
+ ...defaults,
488
+ });
489
+ }
490
+
491
+ return current;
492
+ }
493
+
494
+ function buildCheckpointMetadata(state, eventName, relPath, timestamp) {
495
+ return {
496
+ sequence: (Number(state?.checkpoint_count) || 0) + 1,
497
+ event: eventName,
498
+ at: timestamp,
499
+ file: relPath,
500
+ };
501
+ }
502
+
503
+ const MICRO_TASK_TYPES = new Set([
504
+ 'page-development',
505
+ 'component-development',
506
+ 'bugfix',
507
+ 'bug-fix',
508
+ 'problem-fix',
509
+ 'issue-fix',
510
+ 'style-update',
511
+ 'route-update',
512
+ ]);
513
+
514
+ const MICRO_INPUT_PATTERNS = [
515
+ /mock/i,
516
+ /mock数据/,
517
+ /示例数据/,
518
+ /静态/,
519
+ /单页/,
520
+ /单一页面/,
521
+ /简单页面/,
522
+ /简单组件/,
523
+ /列表页面/,
524
+ /登录页面/,
525
+ /注册页面/,
526
+ /商品列表页面/,
527
+ /原型/,
528
+ ];
529
+
530
+ const STANDARD_INPUT_PATTERNS = [
531
+ /重构/,
532
+ /权限/,
533
+ /支付/,
534
+ /认证/,
535
+ /oauth/i,
536
+ /短信/,
537
+ /多步骤/,
538
+ /多页面/,
539
+ /复杂/,
540
+ /真实接口/,
541
+ /核心模块/,
542
+ /状态联动/,
543
+ /合规/,
544
+ /安全/,
545
+ ];
546
+
547
+ const HIGH_RISK_INPUT_PATTERNS = [
548
+ /支付/,
549
+ /认证/,
550
+ /oauth/i,
551
+ /短信/,
552
+ /权限/,
553
+ /安全/,
554
+ /合规/,
555
+ /风控/,
556
+ /收款/,
557
+ /交易/,
558
+ ];
559
+
560
+ const DEFERRED_DETAIL_PATTERNS = [
561
+ /先不说/,
562
+ /先不提供/,
563
+ /暂不说/,
564
+ /暂不提供/,
565
+ /暂未确定/,
566
+ /未明确/,
567
+ /待定/,
568
+ /后续再说/,
569
+ /后面再说/,
570
+ ];
571
+
572
+ function inferRiskLevel({ explicitRiskLevel, rawInput, taskType, deliveryProfile }) {
573
+ const normalizedExplicit = String(explicitRiskLevel || '').trim().toLowerCase();
574
+ if (normalizedExplicit === 'low' || normalizedExplicit === 'medium' || normalizedExplicit === 'high') {
575
+ return normalizedExplicit;
576
+ }
577
+
578
+ let score = 0;
579
+ const input = String(rawInput || '');
580
+ const normalizedTaskType = String(taskType || '').trim().toLowerCase();
581
+
582
+ if (deliveryProfile === 'standard') {
583
+ score += 1;
584
+ }
585
+
586
+ if (normalizedTaskType.includes('payment') || normalizedTaskType.includes('auth') || normalizedTaskType.includes('security')) {
587
+ score += 2;
588
+ }
589
+
590
+ for (const pattern of HIGH_RISK_INPUT_PATTERNS) {
591
+ if (pattern.test(input)) {
592
+ score += 2;
593
+ break;
594
+ }
595
+ }
596
+
597
+ for (const pattern of DEFERRED_DETAIL_PATTERNS) {
598
+ if (pattern.test(input)) {
599
+ score += 2;
600
+ break;
601
+ }
602
+ }
603
+
604
+ if (score >= 4) {
605
+ return 'high';
606
+ }
607
+ if (score >= 2) {
608
+ return 'medium';
609
+ }
610
+ return 'low';
611
+ }
612
+
613
+ function inferDeliveryProfile({ explicitProfile, flowId, taskType, rawInput, riskLevel }) {
614
+ const normalizedExplicit = String(explicitProfile || '').trim().toLowerCase();
615
+ if (normalizedExplicit === 'micro' || normalizedExplicit === 'standard') {
616
+ return normalizedExplicit;
617
+ }
618
+
619
+ let score = 0;
620
+
621
+ if (MICRO_TASK_TYPES.has(String(taskType || '').trim().toLowerCase())) {
622
+ score += 1;
623
+ }
624
+
625
+ const input = String(rawInput || '');
626
+ for (const pattern of MICRO_INPUT_PATTERNS) {
627
+ if (pattern.test(input)) {
628
+ score += 2;
629
+ break;
630
+ }
631
+ }
632
+
633
+ for (const pattern of STANDARD_INPUT_PATTERNS) {
634
+ if (pattern.test(input)) {
635
+ score -= 2;
636
+ break;
637
+ }
638
+ }
639
+
640
+ const normalizedRisk = String(riskLevel || '').trim().toLowerCase();
641
+ if (normalizedRisk === 'low') {
642
+ score += 1;
643
+ } else if (normalizedRisk === 'high') {
644
+ score -= 2;
645
+ }
646
+
647
+ if (flowId && flowId !== 'prd-to-delivery') {
648
+ score -= 1;
649
+ }
650
+
651
+ return score >= 2 ? 'micro' : 'standard';
652
+ }
653
+
654
+ function inferArtifactProfile({ explicitProfile, deliveryProfile }) {
655
+ const normalizedExplicit = String(explicitProfile || '').trim().toLowerCase();
656
+ if (normalizedExplicit === 'compact' || normalizedExplicit === 'full') {
657
+ return normalizedExplicit;
658
+ }
659
+
660
+ return deliveryProfile === 'micro' ? 'compact' : 'full';
661
+ }
662
+
663
+ function inferComplexity({ explicitComplexity, deliveryProfile, riskLevel }) {
664
+ const normalizedExplicit = String(explicitComplexity || '').trim().toLowerCase();
665
+ if (normalizedExplicit === 'low' || normalizedExplicit === 'medium' || normalizedExplicit === 'high') {
666
+ return normalizedExplicit;
667
+ }
668
+
669
+ const normalizedRisk = String(riskLevel || '').trim().toLowerCase();
670
+ if (normalizedRisk === 'high') {
671
+ return 'high';
672
+ }
673
+ if (normalizedRisk === 'medium') {
674
+ return 'medium';
675
+ }
676
+
677
+ return deliveryProfile === 'micro' ? 'low' : 'medium';
678
+ }
679
+
680
+ function normalizeSpecsArtifactPath(relPath) {
681
+ const value = String(relPath || '').trim();
682
+ if (!value) {
683
+ return null;
684
+ }
685
+
686
+ const normalized = value.replace(/[\\/]+$/, '');
687
+ if (/[\\/]specs$/.test(normalized)) {
688
+ return normalized;
689
+ }
690
+
691
+ const match = normalized.match(/^(.*[\\/]specs)(?:[\\/].+)?$/);
692
+ return match ? match[1] : normalized;
693
+ }
694
+
695
+ function buildDefaultArtifacts(changeId, options = {}) {
696
+ const flowId = String(options.flowId || '').trim();
697
+ const runId = String(options.runId || '').trim();
698
+ const traceMode = String(options.traceMode || '').trim();
699
+
700
+ if ((flowId === 'bugfix-to-verification' || traceMode === 'direct-fix') && runId) {
701
+ const historyDir = `.ai-spec/history/${runId}`;
702
+ return {
703
+ proposal: null,
704
+ specs: null,
705
+ design: null,
706
+ tasks: null,
707
+ bugfix: `${historyDir}/bugfix.md`,
708
+ implementation_notes: `${historyDir}/implementation-notes.md`,
709
+ checklist: `${historyDir}/checklist.md`,
710
+ iterations: `${historyDir}/iterations.md`,
711
+ additional: [],
712
+ };
713
+ }
714
+
715
+ if (!changeId) {
716
+ return {
717
+ proposal: null,
718
+ specs: null,
719
+ design: null,
720
+ tasks: null,
721
+ bugfix: null,
722
+ implementation_notes: null,
723
+ checklist: null,
724
+ iterations: null,
725
+ additional: [],
726
+ };
727
+ }
728
+
729
+ const baseDir = `openspec/changes/${changeId}`;
730
+ return {
731
+ proposal: `${baseDir}/proposal.md`,
732
+ specs: `${baseDir}/specs`,
733
+ design: `${baseDir}/design.md`,
734
+ tasks: `${baseDir}/tasks.md`,
735
+ bugfix: null,
736
+ implementation_notes: null,
737
+ checklist: `${baseDir}/checklist.md`,
738
+ iterations: `${baseDir}/iterations.md`,
739
+ additional: [],
740
+ };
741
+ }
742
+
743
+ function mergeArtifacts(baseArtifacts, inferredArtifacts) {
744
+ const proposal = inferredArtifacts?.proposal || baseArtifacts?.proposal || null;
745
+ const specs = normalizeSpecsArtifactPath(inferredArtifacts?.specs || baseArtifacts?.specs || null);
746
+ const design = inferredArtifacts?.design || baseArtifacts?.design || null;
747
+ const tasks = inferredArtifacts?.tasks || baseArtifacts?.tasks || null;
748
+ const bugfix = inferredArtifacts?.bugfix || baseArtifacts?.bugfix || null;
749
+ const implementationNotes = inferredArtifacts?.implementation_notes || baseArtifacts?.implementation_notes || null;
750
+ const checklist = inferredArtifacts?.checklist || baseArtifacts?.checklist || null;
751
+ const iterations = inferredArtifacts?.iterations || baseArtifacts?.iterations || null;
752
+ const primaryArtifacts = new Set(
753
+ [proposal, specs, design, tasks, bugfix, implementationNotes, checklist, iterations]
754
+ .map((item) => (typeof item === 'string' ? item.trim().replace(/[\\/]+$/, '') : null))
755
+ .filter(Boolean),
756
+ );
757
+ const additional = [
758
+ ...(Array.isArray(baseArtifacts?.additional) ? baseArtifacts.additional : []),
759
+ ...(Array.isArray(inferredArtifacts?.additional) ? inferredArtifacts.additional : []),
760
+ ]
761
+ .map((item) => String(item || '').trim())
762
+ .filter(Boolean)
763
+ .map((item) => item.replace(/[\\/]+$/, ''))
764
+ .filter((item) => !primaryArtifacts.has(item));
765
+
766
+ const merged = {
767
+ proposal,
768
+ specs,
769
+ design,
770
+ tasks,
771
+ bugfix,
772
+ implementation_notes: implementationNotes,
773
+ checklist,
774
+ iterations,
775
+ additional: Array.from(new Set(additional.filter(Boolean))),
776
+ };
777
+
778
+ if (merged.additional.length === 0) {
779
+ delete merged.additional;
780
+ }
781
+
782
+ return merged;
783
+ }
784
+
785
+ function inferArtifacts(artifacts) {
786
+ const normalized = {
787
+ proposal: null,
788
+ specs: null,
789
+ design: null,
790
+ tasks: null,
791
+ bugfix: null,
792
+ implementation_notes: null,
793
+ checklist: null,
794
+ iterations: null,
795
+ additional: [],
796
+ };
797
+
798
+ if (!artifacts) {
799
+ return normalized;
800
+ }
801
+
802
+ if (artifacts && typeof artifacts === 'object' && !Array.isArray(artifacts)) {
803
+ const directKeys = ['proposal', 'specs', 'design', 'tasks', 'bugfix', 'implementation_notes', 'checklist', 'iterations'];
804
+ for (const key of directKeys) {
805
+ if (typeof artifacts[key] === 'string' && artifacts[key].trim()) {
806
+ normalized[key] = key === 'specs'
807
+ ? normalizeSpecsArtifactPath(artifacts[key])
808
+ : artifacts[key];
809
+ }
810
+ }
811
+
812
+ const additional = artifacts.additional;
813
+ if (typeof additional === 'string' && additional.trim()) {
814
+ normalized.additional.push(additional);
815
+ } else if (Array.isArray(additional)) {
816
+ normalized.additional.push(...additional.filter((item) => typeof item === 'string' && item.trim()));
817
+ }
818
+
819
+ if (normalized.additional.length === 0) {
820
+ delete normalized.additional;
821
+ }
822
+
823
+ return normalized;
824
+ }
825
+
826
+ if (!Array.isArray(artifacts)) {
827
+ return normalized;
828
+ }
829
+
830
+ for (const item of artifacts) {
831
+ if (typeof item !== 'string') {
832
+ continue;
833
+ }
834
+ if (item.endsWith('/proposal.md')) {
835
+ normalized.proposal = item;
836
+ continue;
837
+ }
838
+ if (/[\\/]specs(?:[\\/].+)?$/.test(item)) {
839
+ normalized.specs = normalizeSpecsArtifactPath(item);
840
+ continue;
841
+ }
842
+ if (item.endsWith('/design.md')) {
843
+ normalized.design = item;
844
+ continue;
845
+ }
846
+ if (item.endsWith('/tasks.md')) {
847
+ normalized.tasks = item;
848
+ continue;
849
+ }
850
+ if (item.endsWith('/bugfix.md')) {
851
+ normalized.bugfix = item;
852
+ continue;
853
+ }
854
+ if (item.endsWith('/implementation-notes.md')) {
855
+ normalized.implementation_notes = item;
856
+ continue;
857
+ }
858
+ if (item.endsWith('/checklist.md')) {
859
+ normalized.checklist = item;
860
+ continue;
861
+ }
862
+ if (item.endsWith('/iterations.md')) {
863
+ normalized.iterations = item;
864
+ continue;
865
+ }
866
+ normalized.additional.push(item);
867
+ }
868
+
869
+ if (normalized.additional.length === 0) {
870
+ delete normalized.additional;
871
+ }
872
+
873
+ return normalized;
874
+ }
875
+
876
+ function sanitizeAnchor(taskAnchor) {
877
+ if (!taskAnchor || typeof taskAnchor !== 'object') {
878
+ return null;
879
+ }
880
+ return {
881
+ kind: taskAnchor.kind || 'task-anchor',
882
+ task: taskAnchor.task || null,
883
+ stage: taskAnchor.stage || null,
884
+ constraints: taskAnchor.constraints || null,
885
+ artifacts: taskAnchor.artifacts || null,
886
+ expected_output: taskAnchor.expected_output || [],
887
+ };
888
+ }
889
+
890
+ function normalizeBootstrapPayload(payload, sourceLabel) {
891
+ if (!payload || typeof payload !== 'object') {
892
+ throw new Error(`Invalid bootstrap payload: ${sourceLabel}`);
893
+ }
894
+
895
+ if (payload.kind === 'run-plan') {
896
+ return {
897
+ runPlan: payload,
898
+ taskAnchor: null,
899
+ };
900
+ }
901
+
902
+ const runPlan = payload.run_plan || payload.runPlan || null;
903
+ const taskAnchor = payload.task_anchor || payload.taskAnchor || null;
904
+
905
+ if (!runPlan) {
906
+ throw new Error(`Bootstrap payload is missing run_plan: ${sourceLabel}`);
907
+ }
908
+
909
+ return { runPlan, taskAnchor };
910
+ }
911
+
912
+ function buildRunState({ runPlan, taskAnchor, options, now, source }) {
913
+ const runId = options.runId || runPlan.run_id || createRunId(now);
914
+ const createdAt = now.toISOString();
915
+ const runMode = normalizeRunMode(runPlan.mode);
916
+ const reviewPolicy = normalizeReviewPolicy(runPlan.review_policy || runPlan.plan?.review_policy || null);
917
+ const rawInput =
918
+ options.rawInput ||
919
+ runPlan.task?.raw_input ||
920
+ taskAnchor?.task?.raw_goal ||
921
+ null;
922
+ const changeId = deriveChangeId({
923
+ explicitChangeId: options.changeId || runPlan.task?.change_id || taskAnchor?.task?.change_id || null,
924
+ rawInput,
925
+ taskType: runPlan.task?.type || null,
926
+ runId,
927
+ });
928
+ const deliveryProfile = inferDeliveryProfile({
929
+ explicitProfile: runPlan.delivery_profile || runPlan.flow?.delivery_profile || runPlan.plan?.delivery_profile || null,
930
+ flowId: runPlan.flow?.id || null,
931
+ taskType: runPlan.task?.type || null,
932
+ rawInput,
933
+ riskLevel: runPlan.task?.risk_level || null,
934
+ });
935
+ const riskLevel = inferRiskLevel({
936
+ explicitRiskLevel: runPlan.task?.risk_level || null,
937
+ rawInput,
938
+ taskType: runPlan.task?.type || null,
939
+ deliveryProfile,
940
+ });
941
+ const artifactProfile = inferArtifactProfile({
942
+ explicitProfile: runPlan.artifact_profile || runPlan.plan?.artifact_profile || null,
943
+ deliveryProfile,
944
+ });
945
+ const complexity = inferComplexity({
946
+ explicitComplexity: runPlan.complexity || runPlan.task?.complexity || null,
947
+ deliveryProfile,
948
+ riskLevel,
949
+ });
950
+ const changeContext = options.changeContext || runPlan.task?.change_context || null;
951
+ const routeDecision = options.routeDecision || runPlan.task?.route_decision || null;
952
+ const traceMode = options.traceMode || runPlan.task?.trace_mode || null;
953
+ const artifacts = mergeArtifacts(buildDefaultArtifacts(changeId, {
954
+ flowId: runPlan.flow?.id || null,
955
+ runId,
956
+ traceMode,
957
+ }), inferArtifacts(runPlan.artifacts));
958
+ const currentRole = runPlan.plan?.first_handoff || null;
959
+ const approvalGates = buildEffectiveApprovalGates(
960
+ runPlan.flow?.id || null,
961
+ Array.isArray(runPlan.plan?.approval_gates) ? runPlan.plan.approval_gates : [],
962
+ reviewPolicy,
963
+ );
964
+ const normalizedRunPlanStatus = String(runPlan.status || '').trim().toLowerCase();
965
+ const initialStatus = options.status
966
+ || (runMode === 'suggest'
967
+ ? (normalizedRunPlanStatus && normalizedRunPlanStatus !== 'planned' ? runPlan.status : 'waiting-confirm')
968
+ : runPlan.status || 'planned');
969
+ const pendingGate =
970
+ options.pendingGate ||
971
+ runPlan.pending_gate ||
972
+ runPlan.plan?.pending_gate ||
973
+ null;
974
+ const sanitizedAnchor = sanitizeAnchor(taskAnchor);
975
+ const anchor = sanitizedAnchor
976
+ ? {
977
+ ...sanitizedAnchor,
978
+ task: {
979
+ ...(sanitizedAnchor.task || {}),
980
+ change_id: sanitizedAnchor.task?.change_id || changeId,
981
+ },
982
+ artifacts: mergeArtifacts(
983
+ buildDefaultArtifacts(changeId, {
984
+ flowId: runPlan.flow?.id || null,
985
+ runId,
986
+ traceMode,
987
+ }),
988
+ inferArtifacts(sanitizedAnchor.artifacts || artifacts),
989
+ ),
990
+ }
991
+ : null;
992
+ const initMessage = source?.bootstrapPayload
993
+ ? 'runtime-state initialized from task-orchestrator bootstrap payload'
994
+ : 'runtime-state initialized from run-plan';
995
+ const initialGateContext = runPlan.gate_context && typeof runPlan.gate_context === 'object'
996
+ ? {
997
+ gate_id: runPlan.gate_context.gate_id || null,
998
+ blocked_by_role: runPlan.gate_context.blocked_by_role || null,
999
+ resume_to_role: runPlan.gate_context.resume_to_role || currentRole,
1000
+ required_user_action: runPlan.gate_context.required_user_action || null,
1001
+ blocked_reason: runPlan.gate_context.blocked_reason || null,
1002
+ }
1003
+ : null;
1004
+ const suggestGateContext = runMode === 'suggest' && initialStatus === 'waiting-confirm' && !pendingGate
1005
+ ? {
1006
+ gate_id: 'start-review',
1007
+ blocked_by_role: 'task-orchestrator',
1008
+ resume_to_role: currentRole,
1009
+ required_user_action: '请先确认建议执行计划,再启动第一位专家。',
1010
+ blocked_reason: '当前以 suggest(建议)模式启动,首轮 run-plan 需要先经过人工确认。',
1011
+ }
1012
+ : null;
1013
+
1014
+ return {
1015
+ schema_version: 1,
1016
+ kind: 'run-state',
1017
+ run_id: runId,
1018
+ mode: runMode,
1019
+ review_policy: reviewPolicy,
1020
+ delivery_profile: deliveryProfile,
1021
+ artifact_profile: artifactProfile,
1022
+ complexity,
1023
+ status: initialStatus,
1024
+ trigger: {
1025
+ source: options.triggerSource,
1026
+ entry: options.entry,
1027
+ raw_input: rawInput,
1028
+ latest_user_input: rawInput,
1029
+ latest_input_at: rawInput ? createdAt : null,
1030
+ },
1031
+ task: {
1032
+ change_id: changeId,
1033
+ parent_change_id: options.parentChangeId || runPlan.task?.parent_change_id || taskAnchor?.task?.parent_change_id || null,
1034
+ input_kind: runPlan.task?.input_kind || taskAnchor?.task?.input_kind || 'unknown',
1035
+ risk_level: riskLevel,
1036
+ type: runPlan.task?.type || null,
1037
+ complexity,
1038
+ change_context: changeContext,
1039
+ route_decision: routeDecision,
1040
+ trace_mode: traceMode,
1041
+ change_impact: options.changeImpact || runPlan.task?.change_impact || null,
1042
+ },
1043
+ flow: {
1044
+ id: runPlan.flow?.id || null,
1045
+ name: runPlan.flow?.name || null,
1046
+ source: runPlan.flow?.source || null,
1047
+ delivery_profile: deliveryProfile,
1048
+ artifact_profile: artifactProfile,
1049
+ },
1050
+ plan: {
1051
+ required_roles: runPlan.plan?.required_roles || [],
1052
+ activated_optional_roles: runPlan.plan?.activated_optional_roles || [],
1053
+ skipped_optional_roles: runPlan.plan?.skipped_optional_roles || [],
1054
+ approval_gates: approvalGates,
1055
+ first_handoff: currentRole,
1056
+ delivery_profile: deliveryProfile,
1057
+ artifact_profile: artifactProfile,
1058
+ review_policy: reviewPolicy,
1059
+ },
1060
+ current_role: currentRole,
1061
+ pending_input_update: false,
1062
+ pending_gate: pendingGate,
1063
+ gate_context: pendingGate
1064
+ ? buildGateContext(null, options, pendingGate)
1065
+ : initialGateContext || suggestGateContext,
1066
+ incremental_update: buildIncrementalUpdateState(null, options, {
1067
+ changeContext,
1068
+ routeDecision,
1069
+ traceMode,
1070
+ changeImpact: options.changeImpact || runPlan.task?.change_impact || null,
1071
+ reconcileStrategy: options.reconcileStrategy || runPlan.task?.reconcile_strategy || null,
1072
+ artifactsToUpdate: options.artifactsToUpdate || runPlan.task?.artifacts_to_update || [],
1073
+ reopenReason: options.reopenReason || runPlan.task?.reopen_reason || null,
1074
+ parentChangeId: options.parentChangeId || runPlan.task?.parent_change_id || null,
1075
+ updatedAt: createdAt,
1076
+ }),
1077
+ artifacts,
1078
+ verification: null,
1079
+ auto_fix: buildDefaultAutoFixState(),
1080
+ last_checkpoint: null,
1081
+ checkpoint_count: 0,
1082
+ assumptions: Array.isArray(runPlan.assumptions) ? runPlan.assumptions : [],
1083
+ missing_inputs: runPlan.missing_inputs || [],
1084
+ warnings: runPlan.warnings || [],
1085
+ errors: runPlan.errors || [],
1086
+ input_updates: [],
1087
+ anchor,
1088
+ events: [
1089
+ {
1090
+ at: createdAt,
1091
+ type: 'run-created',
1092
+ status: options.status || runPlan.status || 'planned',
1093
+ message: initMessage,
1094
+ },
1095
+ ],
1096
+ timestamps: {
1097
+ created_at: createdAt,
1098
+ updated_at: createdAt,
1099
+ },
1100
+ };
1101
+ }
1102
+
1103
+ function listMissingOpenSpecArtifacts(targetDir, state, artifactKeys) {
1104
+ const artifactMap = mergeArtifacts(
1105
+ buildDefaultArtifacts(state.task?.change_id || state.anchor?.task?.change_id || null),
1106
+ inferArtifacts(state.artifacts || null),
1107
+ );
1108
+ const missing = [];
1109
+
1110
+ for (const key of artifactKeys) {
1111
+ const relPath = artifactMap[key];
1112
+ if (!relPath) {
1113
+ missing.push(`artifact:${key}`);
1114
+ continue;
1115
+ }
1116
+
1117
+ const absolutePath = path.join(targetDir, relPath);
1118
+ if (!fs.existsSync(absolutePath)) {
1119
+ missing.push(relPath);
1120
+ }
1121
+ }
1122
+
1123
+ return missing;
1124
+ }
1125
+
1126
+ function assertRequiredOpenSpecArtifacts(targetDir, state, action, toRole) {
1127
+ if (state.flow?.id !== 'prd-to-delivery') {
1128
+ return;
1129
+ }
1130
+
1131
+ if (!state.task?.change_id) {
1132
+ throw new Error(`Cannot ${action} prd-to-delivery run without task.change_id`);
1133
+ }
1134
+
1135
+ let requiredArtifacts = [];
1136
+ if (action === 'handoff' && toRole === 'frontend-implementer') {
1137
+ requiredArtifacts = ['proposal', 'specs', 'design', 'tasks'];
1138
+ } else if (action === 'complete') {
1139
+ requiredArtifacts = ['proposal', 'specs', 'design', 'tasks', 'checklist', 'iterations'];
1140
+ }
1141
+
1142
+ if (requiredArtifacts.length === 0) {
1143
+ return;
1144
+ }
1145
+
1146
+ const missingArtifacts = listMissingOpenSpecArtifacts(targetDir, state, requiredArtifacts);
1147
+ if (missingArtifacts.length > 0) {
1148
+ throw new Error(
1149
+ `Cannot ${action} prd-to-delivery run; missing required OpenSpec artifacts: ${missingArtifacts.join(', ')}`,
1150
+ );
1151
+ }
1152
+ }
1153
+
1154
+ function ensureDir(dirPath) {
1155
+ fs.mkdirSync(dirPath, { recursive: true });
1156
+ }
1157
+
1158
+ function writeJsonFile(filePath, value) {
1159
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
1160
+ }
1161
+
1162
+ function readRunStateFile(filePath) {
1163
+ const state = readJsonFile(filePath, 'run-state');
1164
+ if (!state || typeof state !== 'object' || state.kind !== 'run-state') {
1165
+ throw new Error(`Invalid run-state object: ${filePath}`);
1166
+ }
1167
+ if (!state.run_id) {
1168
+ throw new Error(`run-state is missing run_id: ${filePath}`);
1169
+ }
1170
+ return state;
1171
+ }
1172
+
1173
+ function loadTaskAnchor(taskAnchorPath, taskAnchorData = null) {
1174
+ if (taskAnchorData) {
1175
+ return taskAnchorData;
1176
+ }
1177
+ return taskAnchorPath ? readJsonFile(taskAnchorPath, 'task-anchor') : null;
1178
+ }
1179
+
1180
+ function maybeAttachCheckpoint(targetDir, state, checkpointEvent) {
1181
+ if (!shouldPersistCheckpoints() || !checkpointEvent || !CHECKPOINT_EVENTS.has(checkpointEvent)) {
1182
+ return state;
1183
+ }
1184
+
1185
+ const runtimePaths = resolveRuntimePaths(targetDir);
1186
+ const checkpointDir = path.join(runtimePaths.checkpointsDir.path, state.run_id);
1187
+ ensureDir(checkpointDir);
1188
+
1189
+ const timestamp = state.timestamps?.updated_at || new Date().toISOString();
1190
+ const sequence = (Number(state.checkpoint_count) || 0) + 1;
1191
+ const checkpointFileName = `${String(sequence).padStart(3, '0')}-${checkpointEvent}.json`;
1192
+ const checkpointPath = path.join(checkpointDir, checkpointFileName);
1193
+ const checkpointRelPath = path.relative(targetDir, checkpointPath);
1194
+ const metadata = buildCheckpointMetadata(state, checkpointEvent, checkpointRelPath, timestamp);
1195
+ const nextState = {
1196
+ ...state,
1197
+ checkpoint_count: sequence,
1198
+ last_checkpoint: metadata,
1199
+ };
1200
+
1201
+ writeJsonFile(checkpointPath, {
1202
+ schema_version: 1,
1203
+ kind: 'runtime-checkpoint',
1204
+ run_id: state.run_id,
1205
+ sequence,
1206
+ event: checkpointEvent,
1207
+ created_at: timestamp,
1208
+ state: nextState,
1209
+ });
1210
+
1211
+ return nextState;
1212
+ }
1213
+
1214
+ function saveUpdatedRunState({
1215
+ targetDir,
1216
+ historyRunPath,
1217
+ currentRunPath,
1218
+ syncCurrent,
1219
+ forceSyncCurrent = false,
1220
+ state,
1221
+ checkpointEvent = null,
1222
+ }) {
1223
+ syncRepoMap(targetDir);
1224
+ const nextState = maybeAttachCheckpoint(targetDir, state, checkpointEvent);
1225
+
1226
+ if (historyRunPath) {
1227
+ writeJsonFile(historyRunPath, nextState);
1228
+ }
1229
+ if (syncCurrent || forceSyncCurrent) {
1230
+ writeJsonFile(currentRunPath, nextState);
1231
+ }
1232
+
1233
+ try {
1234
+ const bridgePath = path.join(__dirname, 'visual-bridge.js');
1235
+ if (fs.existsSync(bridgePath)) {
1236
+ const child = spawnSync(process.execPath, [bridgePath, 'push-current', '--target', targetDir, '--event-name', checkpointEvent || 'runtime-state-updated', '--json'], {
1237
+ encoding: 'utf8',
1238
+ stdio: ['ignore', 'pipe', 'pipe'],
1239
+ });
1240
+ if (child.status !== 0 && process.env.AI_SPEC_VISUAL_BRIDGE_DEBUG === '1') {
1241
+ console.warn(`visual bridge skipped: ${child.stderr || child.stdout || 'unknown error'}`);
1242
+ }
1243
+ }
1244
+ } catch (error) {
1245
+ if (process.env.AI_SPEC_VISUAL_BRIDGE_DEBUG === '1') {
1246
+ console.warn(`visual bridge error: ${error.message}`);
1247
+ }
1248
+ }
1249
+
1250
+ return nextState;
1251
+ }
1252
+
1253
+ function recordRunInputUpdate(options) {
1254
+ if (!options.userInput || !String(options.userInput).trim()) {
1255
+ throw new Error('Missing required argument: userInput');
1256
+ }
1257
+
1258
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1259
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1260
+
1261
+ if (['success', 'failed', 'cancelled'].includes(String(state.status || '').toLowerCase())) {
1262
+ throw new Error(`Cannot update terminal run: ${state.run_id}`);
1263
+ }
1264
+
1265
+ const now = new Date();
1266
+ const userInput = String(options.userInput).trim();
1267
+ const update = {
1268
+ at: now.toISOString(),
1269
+ text: userInput,
1270
+ source: options.source || 'protocol-update',
1271
+ change_context: options.changeContext || null,
1272
+ route_decision: options.routeDecision || null,
1273
+ trace_mode: options.traceMode || null,
1274
+ change_impact: options.changeImpact || null,
1275
+ reconcile_strategy: options.reconcileStrategy || null,
1276
+ artifacts_to_update: Array.isArray(options.artifactsToUpdate) ? options.artifactsToUpdate : [],
1277
+ reopen_reason: options.reopenReason || null,
1278
+ parent_change_id: options.parentChangeId || null,
1279
+ target_role: options.toRole || options.nextRole || null,
1280
+ handoff_gate: options.handoffGate || null,
1281
+ };
1282
+
1283
+ const nextInputUpdates = [...(Array.isArray(state.input_updates) ? state.input_updates : []), update].slice(-20);
1284
+ const event = buildStateEvent({
1285
+ state,
1286
+ options: {
1287
+ ...options,
1288
+ toRole: state.current_role || state.plan?.first_handoff || null,
1289
+ clearPendingGate: false,
1290
+ message: `user input updated: ${userInput}`,
1291
+ },
1292
+ now,
1293
+ defaults: {
1294
+ status: state.status || 'running',
1295
+ eventType: 'user-input-updated',
1296
+ message: `user input updated: ${userInput}`,
1297
+ pendingGate: state.pending_gate ?? null,
1298
+ },
1299
+ });
1300
+
1301
+ const updatedState = {
1302
+ ...state,
1303
+ pending_input_update: true,
1304
+ trigger: {
1305
+ ...(state.trigger || {}),
1306
+ latest_user_input: userInput,
1307
+ latest_input_at: now.toISOString(),
1308
+ latest_change_context: options.changeContext || null,
1309
+ latest_route_decision: options.routeDecision || null,
1310
+ latest_trace_mode: options.traceMode || null,
1311
+ latest_change_impact: options.changeImpact || null,
1312
+ latest_reconcile_strategy: options.reconcileStrategy || null,
1313
+ },
1314
+ incremental_update: buildIncrementalUpdateState(state, options, {
1315
+ updatedAt: now.toISOString(),
1316
+ }),
1317
+ input_updates: nextInputUpdates,
1318
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1319
+ timestamps: {
1320
+ ...(state.timestamps || {}),
1321
+ updated_at: now.toISOString(),
1322
+ },
1323
+ };
1324
+
1325
+ const persistedState = saveUpdatedRunState({
1326
+ targetDir,
1327
+ historyRunPath,
1328
+ currentRunPath,
1329
+ syncCurrent,
1330
+ state: updatedState,
1331
+ checkpointEvent: 'pause',
1332
+ });
1333
+
1334
+ return {
1335
+ status: 'success',
1336
+ target: targetDir,
1337
+ artifacts: {
1338
+ current_run: syncCurrent ? currentRunPath : null,
1339
+ run_history: historyRunPath,
1340
+ },
1341
+ state: persistedState,
1342
+ update,
1343
+ };
1344
+ }
1345
+
1346
+ function writeRunState({ targetDir, runPlan, taskAnchor, options, source }) {
1347
+ const now = new Date();
1348
+ const state = buildRunState({ runPlan, taskAnchor, options, now, source });
1349
+ const runtimePaths = resolveRuntimePaths(targetDir);
1350
+ const persistHistory = shouldPersistHistory();
1351
+ if (persistHistory) {
1352
+ ensureDir(runtimePaths.runsDir.path);
1353
+ }
1354
+ ensureDir(path.dirname(runtimePaths.currentRun.path));
1355
+
1356
+ const currentRunPath = runtimePaths.currentRun.path;
1357
+ const historyRunPath = persistHistory
1358
+ ? path.join(runtimePaths.runsDir.path, `${state.run_id}.json`)
1359
+ : null;
1360
+
1361
+ const persistedState = saveUpdatedRunState({
1362
+ targetDir,
1363
+ historyRunPath,
1364
+ currentRunPath,
1365
+ syncCurrent: true,
1366
+ forceSyncCurrent: true,
1367
+ state,
1368
+ checkpointEvent: 'bootstrap',
1369
+ });
1370
+
1371
+ return {
1372
+ status: 'success',
1373
+ target: targetDir,
1374
+ artifacts: {
1375
+ current_run: currentRunPath,
1376
+ run_history: historyRunPath,
1377
+ },
1378
+ state: persistedState,
1379
+ source: {
1380
+ run_plan: source.runPlan || null,
1381
+ task_anchor: source.taskAnchor || null,
1382
+ bootstrap_payload: source.bootstrapPayload || null,
1383
+ },
1384
+ };
1385
+ }
1386
+
1387
+ function resolveRunStatePaths(targetDir, runId) {
1388
+ const runtimePaths = resolveRuntimePaths(targetDir);
1389
+ const aiSpecDir = runtimePaths.aiSpecDir.path;
1390
+ const currentRunPath = runtimePaths.currentRun.path;
1391
+ let historyRunPath = null;
1392
+ let state = null;
1393
+ const currentState = fs.existsSync(currentRunPath)
1394
+ ? readRunStateFile(currentRunPath)
1395
+ : null;
1396
+
1397
+ if (runId) {
1398
+ if (currentState && currentState.run_id === runId) {
1399
+ state = currentState;
1400
+ }
1401
+ for (const candidateDir of getCandidatePaths(runtimePaths.runsDir)) {
1402
+ const candidatePath = path.join(candidateDir, `${runId}.json`);
1403
+ if (fs.existsSync(candidatePath)) {
1404
+ historyRunPath = candidatePath;
1405
+ if (!state) {
1406
+ state = readRunStateFile(historyRunPath);
1407
+ }
1408
+ break;
1409
+ }
1410
+ }
1411
+ if (!state) {
1412
+ throw new Error(`run-state history file not found for run_id: ${runId}`);
1413
+ }
1414
+ } else {
1415
+ if (!fs.existsSync(currentRunPath)) {
1416
+ throw new Error(`current run-state file not found: ${currentRunPath}`);
1417
+ }
1418
+ state = currentState;
1419
+ const candidateHistory = getExistingPath({
1420
+ path: path.join(runtimePaths.runsDir.path, `${state.run_id}.json`),
1421
+ legacyPaths: getCandidatePaths(runtimePaths.runsDir).map((dirPath) => path.join(dirPath, `${state.run_id}.json`)).slice(1),
1422
+ });
1423
+ historyRunPath = fs.existsSync(candidateHistory) ? candidateHistory : null;
1424
+ }
1425
+
1426
+ return {
1427
+ aiSpecDir,
1428
+ currentRunPath,
1429
+ historyRunPath,
1430
+ state,
1431
+ syncCurrent: Boolean(currentState && currentState.run_id === state.run_id),
1432
+ };
1433
+ }
1434
+
1435
+ function buildHandoffEvent({ state, options, now }) {
1436
+ const fromRole = options.fromRole || state.current_role || state.plan?.first_handoff || null;
1437
+ const toRole = options.toRole;
1438
+ const eventType = options.eventType || 'role-handoff';
1439
+ const message =
1440
+ options.message ||
1441
+ `handoff from ${fromRole || 'unknown'} to ${toRole}`;
1442
+
1443
+ return {
1444
+ at: now.toISOString(),
1445
+ type: eventType,
1446
+ status: options.status || state.status || 'running',
1447
+ from_role: fromRole,
1448
+ to_role: toRole,
1449
+ pending_gate:
1450
+ options.clearPendingGate ? null :
1451
+ (Object.prototype.hasOwnProperty.call(options, 'pendingGate') ? options.pendingGate || null : state.pending_gate || null),
1452
+ gate_context: options.clearPendingGate ? null : buildGateContext(state, options),
1453
+ message,
1454
+ };
1455
+ }
1456
+
1457
+ function buildStateEvent({ state, options, now, defaults = {} }) {
1458
+ const fromRole = options.fromRole || defaults.fromRole || state.current_role || state.plan?.first_handoff || null;
1459
+ const toRole = options.toRole || defaults.toRole || null;
1460
+ const pendingGate = options.clearPendingGate
1461
+ ? null
1462
+ : (Object.prototype.hasOwnProperty.call(options, 'pendingGate')
1463
+ ? options.pendingGate || null
1464
+ : defaults.pendingGate ?? state.pending_gate ?? null);
1465
+ const status = options.status || defaults.status || state.status || 'running';
1466
+ const eventType = options.eventType || defaults.eventType || 'state-updated';
1467
+ const message = options.message || defaults.message || eventType;
1468
+
1469
+ return {
1470
+ at: now.toISOString(),
1471
+ type: eventType,
1472
+ status,
1473
+ from_role: fromRole,
1474
+ to_role: toRole,
1475
+ pending_gate: pendingGate,
1476
+ gate_context: pendingGate ? buildGateContext(state, options, pendingGate) : null,
1477
+ message,
1478
+ };
1479
+ }
1480
+
1481
+ function shouldClearPendingGateForHandoff(state, options = {}) {
1482
+ if (Object.prototype.hasOwnProperty.call(options, 'clearPendingGate')) {
1483
+ return Boolean(options.clearPendingGate);
1484
+ }
1485
+
1486
+ if (Object.prototype.hasOwnProperty.call(options, 'pendingGate')) {
1487
+ return false;
1488
+ }
1489
+
1490
+ return Boolean(state?.pending_gate && state?.pending_input_update);
1491
+ }
1492
+
1493
+ function updateAnchorForRole(existingAnchor, taskAnchor, toRole, nextRole) {
1494
+ const sanitizedAnchor = taskAnchor ? sanitizeAnchor(taskAnchor) : existingAnchor || null;
1495
+ if (!sanitizedAnchor) {
1496
+ return null;
1497
+ }
1498
+ return {
1499
+ ...sanitizedAnchor,
1500
+ stage: {
1501
+ ...(sanitizedAnchor.stage || {}),
1502
+ current_role: toRole ?? sanitizedAnchor.stage?.current_role ?? null,
1503
+ next_role: nextRole ?? sanitizedAnchor.stage?.next_role ?? null,
1504
+ },
1505
+ };
1506
+ }
1507
+
1508
+ function handoffRunState(options) {
1509
+ if (!options.toRole) {
1510
+ throw new Error('Missing required argument: --to-role <role>');
1511
+ }
1512
+
1513
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1514
+ const taskAnchorPath = options.taskAnchor
1515
+ ? path.resolve(process.cwd(), options.taskAnchor)
1516
+ : null;
1517
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1518
+ assertRequiredOpenSpecArtifacts(targetDir, state, 'handoff', options.toRole);
1519
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1520
+ const sanitizedAnchor = updateAnchorForRole(
1521
+ state.anchor || null,
1522
+ taskAnchor,
1523
+ options.toRole,
1524
+ options.nextRole,
1525
+ );
1526
+ const now = new Date();
1527
+ const clearPendingGate = shouldClearPendingGateForHandoff(state, options);
1528
+ const event = buildHandoffEvent({
1529
+ state,
1530
+ options: {
1531
+ ...options,
1532
+ clearPendingGate,
1533
+ },
1534
+ now,
1535
+ });
1536
+ const updatedState = {
1537
+ ...state,
1538
+ status: options.status || 'running',
1539
+ current_role: options.toRole,
1540
+ pending_input_update: false,
1541
+ pending_gate: clearPendingGate
1542
+ ? null
1543
+ : (Object.prototype.hasOwnProperty.call(options, 'pendingGate') ? options.pendingGate || null : state.pending_gate || null),
1544
+ gate_context: clearPendingGate
1545
+ ? null
1546
+ : buildGateContext(state, options),
1547
+ verification: options.verificationData || state.verification || null,
1548
+ auto_fix: buildNextAutoFixState(state, options),
1549
+ anchor: sanitizedAnchor,
1550
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1551
+ timestamps: {
1552
+ ...(state.timestamps || {}),
1553
+ updated_at: now.toISOString(),
1554
+ },
1555
+ };
1556
+
1557
+ const persistedState = saveUpdatedRunState({
1558
+ targetDir,
1559
+ historyRunPath,
1560
+ currentRunPath,
1561
+ syncCurrent,
1562
+ state: updatedState,
1563
+ checkpointEvent: 'handoff',
1564
+ });
1565
+
1566
+ return {
1567
+ status: 'success',
1568
+ target: targetDir,
1569
+ artifacts: {
1570
+ current_run: syncCurrent ? currentRunPath : null,
1571
+ run_history: historyRunPath,
1572
+ },
1573
+ state: persistedState,
1574
+ source: {
1575
+ task_anchor: taskAnchorPath,
1576
+ },
1577
+ handoff: {
1578
+ from_role: event.from_role || null,
1579
+ to_role: options.toRole,
1580
+ next_role: options.nextRole || null,
1581
+ },
1582
+ };
1583
+ }
1584
+
1585
+ function approveRunState(options) {
1586
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1587
+ const taskAnchorPath = options.taskAnchor
1588
+ ? path.resolve(process.cwd(), options.taskAnchor)
1589
+ : null;
1590
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1591
+ const activeGate = state.pending_gate || null;
1592
+ const requestedGate = options.gate || activeGate;
1593
+
1594
+ if (!activeGate) {
1595
+ throw new Error('No pending approval gate found');
1596
+ }
1597
+ if (options.gate && activeGate && options.gate !== activeGate) {
1598
+ throw new Error(`Pending gate mismatch: current is "${activeGate}", requested "${options.gate}"`);
1599
+ }
1600
+
1601
+ const toRole = inferApprovalResumeRole(state, options);
1602
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1603
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1604
+ const now = new Date();
1605
+ const event = buildStateEvent({
1606
+ state,
1607
+ options: { ...options, toRole, clearPendingGate: true },
1608
+ now,
1609
+ defaults: {
1610
+ status: 'running',
1611
+ eventType: 'gate-cleared',
1612
+ message: `approval cleared for ${requestedGate}`,
1613
+ pendingGate: null,
1614
+ },
1615
+ });
1616
+
1617
+ const updatedState = {
1618
+ ...state,
1619
+ status: options.status || 'running',
1620
+ current_role: toRole,
1621
+ pending_input_update: false,
1622
+ pending_gate: null,
1623
+ gate_context: null,
1624
+ incremental_update: buildIncrementalUpdateState(state, options, {
1625
+ updatedAt: now.toISOString(),
1626
+ }),
1627
+ auto_fix: buildNextAutoFixState(state, options),
1628
+ anchor,
1629
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1630
+ timestamps: {
1631
+ ...(state.timestamps || {}),
1632
+ updated_at: now.toISOString(),
1633
+ },
1634
+ };
1635
+
1636
+ const persistedState = saveUpdatedRunState({
1637
+ targetDir,
1638
+ historyRunPath,
1639
+ currentRunPath,
1640
+ syncCurrent,
1641
+ state: updatedState,
1642
+ checkpointEvent: 'approve',
1643
+ });
1644
+
1645
+ return {
1646
+ status: 'success',
1647
+ target: targetDir,
1648
+ artifacts: {
1649
+ current_run: syncCurrent ? currentRunPath : null,
1650
+ run_history: historyRunPath,
1651
+ },
1652
+ state: persistedState,
1653
+ source: {
1654
+ task_anchor: taskAnchorPath,
1655
+ gate: requestedGate,
1656
+ },
1657
+ handoff: {
1658
+ from_role: event.from_role || null,
1659
+ to_role: toRole,
1660
+ next_role: options.nextRole || null,
1661
+ },
1662
+ };
1663
+ }
1664
+
1665
+ function pauseRunState(options) {
1666
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1667
+ const taskAnchorPath = options.taskAnchor
1668
+ ? path.resolve(process.cwd(), options.taskAnchor)
1669
+ : null;
1670
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1671
+ if (['success', 'failed', 'cancelled'].includes(String(state.status || '').toLowerCase())) {
1672
+ throw new Error(`Cannot pause terminal run: ${state.run_id}`);
1673
+ }
1674
+
1675
+ const toRole = options.toRole || state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null;
1676
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1677
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1678
+ const now = new Date();
1679
+ const event = buildStateEvent({
1680
+ state,
1681
+ options: { ...options, toRole, clearPendingGate: false },
1682
+ now,
1683
+ defaults: {
1684
+ status: 'paused',
1685
+ eventType: 'run-paused',
1686
+ message: options.message || 'run paused',
1687
+ pendingGate: state.pending_gate || null,
1688
+ },
1689
+ });
1690
+
1691
+ const updatedState = {
1692
+ ...state,
1693
+ status: options.status || 'paused',
1694
+ current_role: toRole,
1695
+ pending_input_update: false,
1696
+ pending_gate: options.clearPendingGate ? null : state.pending_gate || null,
1697
+ gate_context: options.clearPendingGate ? null : state.gate_context || null,
1698
+ incremental_update: buildIncrementalUpdateState(state, options, {
1699
+ updatedAt: now.toISOString(),
1700
+ }),
1701
+ auto_fix: buildNextAutoFixState(state, options),
1702
+ anchor,
1703
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1704
+ timestamps: {
1705
+ ...(state.timestamps || {}),
1706
+ updated_at: now.toISOString(),
1707
+ },
1708
+ };
1709
+
1710
+ const persistedState = saveUpdatedRunState({
1711
+ targetDir,
1712
+ historyRunPath,
1713
+ currentRunPath,
1714
+ syncCurrent,
1715
+ state: updatedState,
1716
+ });
1717
+
1718
+ return {
1719
+ status: 'success',
1720
+ target: targetDir,
1721
+ artifacts: {
1722
+ current_run: syncCurrent ? currentRunPath : null,
1723
+ run_history: historyRunPath,
1724
+ },
1725
+ state: persistedState,
1726
+ source: {
1727
+ task_anchor: taskAnchorPath,
1728
+ },
1729
+ };
1730
+ }
1731
+
1732
+ function resumeRunState(options) {
1733
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1734
+ const taskAnchorPath = options.taskAnchor
1735
+ ? path.resolve(process.cwd(), options.taskAnchor)
1736
+ : null;
1737
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1738
+ const toRole = state.pending_gate
1739
+ ? inferApprovalResumeRole(state, options)
1740
+ : (options.toRole || state.current_role || state.anchor?.stage?.current_role || state.plan?.first_handoff || null);
1741
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1742
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1743
+ const now = new Date();
1744
+ const event = buildStateEvent({
1745
+ state,
1746
+ options: { ...options, toRole, clearPendingGate: true },
1747
+ now,
1748
+ defaults: {
1749
+ status: 'running',
1750
+ eventType: 'run-resumed',
1751
+ message: `resumed run at ${toRole || 'unknown'}`,
1752
+ pendingGate: null,
1753
+ },
1754
+ });
1755
+
1756
+ const updatedState = {
1757
+ ...state,
1758
+ status: options.status || 'running',
1759
+ current_role: toRole,
1760
+ pending_input_update: false,
1761
+ pending_gate: options.clearPendingGate === false ? state.pending_gate || null : null,
1762
+ gate_context: options.clearPendingGate === false ? state.gate_context || null : null,
1763
+ incremental_update: buildIncrementalUpdateState(state, options, {
1764
+ updatedAt: now.toISOString(),
1765
+ }),
1766
+ auto_fix: buildNextAutoFixState(state, options),
1767
+ anchor,
1768
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1769
+ timestamps: {
1770
+ ...(state.timestamps || {}),
1771
+ updated_at: now.toISOString(),
1772
+ },
1773
+ };
1774
+
1775
+ const persistedState = saveUpdatedRunState({
1776
+ targetDir,
1777
+ historyRunPath,
1778
+ currentRunPath,
1779
+ syncCurrent,
1780
+ state: updatedState,
1781
+ });
1782
+
1783
+ return {
1784
+ status: 'success',
1785
+ target: targetDir,
1786
+ artifacts: {
1787
+ current_run: syncCurrent ? currentRunPath : null,
1788
+ run_history: historyRunPath,
1789
+ },
1790
+ state: persistedState,
1791
+ source: {
1792
+ task_anchor: taskAnchorPath,
1793
+ },
1794
+ };
1795
+ }
1796
+
1797
+ function restoreRunState(options) {
1798
+ if (!options.checkpoint) {
1799
+ throw new Error('Missing required argument: --checkpoint <file>');
1800
+ }
1801
+
1802
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1803
+ const checkpointPath = path.resolve(process.cwd(), options.checkpoint);
1804
+ const checkpoint = readJsonFile(checkpointPath, 'runtime checkpoint');
1805
+
1806
+ if (checkpoint.kind !== 'runtime-checkpoint' || !checkpoint.state || checkpoint.state.kind !== 'run-state') {
1807
+ throw new Error(`Invalid runtime checkpoint: ${checkpointPath}`);
1808
+ }
1809
+
1810
+ const runId = options.runId || checkpoint.run_id || checkpoint.state.run_id || null;
1811
+ if (!runId) {
1812
+ throw new Error(`Checkpoint is missing run_id: ${checkpointPath}`);
1813
+ }
1814
+ if (checkpoint.run_id && checkpoint.run_id !== runId) {
1815
+ throw new Error(`Checkpoint run_id mismatch: expected ${runId}, got ${checkpoint.run_id}`);
1816
+ }
1817
+
1818
+ const { currentRunPath, historyRunPath } = resolveRunStatePaths(targetDir, runId);
1819
+ const currentRun = fs.existsSync(currentRunPath) ? readRunStateFile(currentRunPath) : null;
1820
+ if (currentRun && currentRun.run_id !== runId) {
1821
+ throw new Error(`Restore only supports the current active run; current run is ${currentRun.run_id}, requested ${runId}`);
1822
+ }
1823
+
1824
+ const now = new Date();
1825
+ const restoredState = {
1826
+ ...checkpoint.state,
1827
+ events: [
1828
+ ...(Array.isArray(checkpoint.state.events) ? checkpoint.state.events : []),
1829
+ {
1830
+ at: now.toISOString(),
1831
+ type: 'run-restored',
1832
+ status: checkpoint.state.status || 'running',
1833
+ from_role: checkpoint.state.current_role || null,
1834
+ to_role: checkpoint.state.current_role || null,
1835
+ pending_gate: checkpoint.state.pending_gate || null,
1836
+ message: `restored from checkpoint ${path.relative(targetDir, checkpointPath)}`,
1837
+ },
1838
+ ],
1839
+ timestamps: {
1840
+ ...(checkpoint.state.timestamps || {}),
1841
+ updated_at: now.toISOString(),
1842
+ },
1843
+ };
1844
+
1845
+ const persistedState = saveUpdatedRunState({
1846
+ targetDir,
1847
+ historyRunPath,
1848
+ currentRunPath,
1849
+ syncCurrent: true,
1850
+ forceSyncCurrent: true,
1851
+ state: restoredState,
1852
+ });
1853
+
1854
+ return {
1855
+ status: 'success',
1856
+ target: targetDir,
1857
+ artifacts: {
1858
+ current_run: currentRunPath,
1859
+ run_history: historyRunPath,
1860
+ checkpoint: checkpointPath,
1861
+ },
1862
+ state: persistedState,
1863
+ source: {
1864
+ checkpoint: checkpointPath,
1865
+ },
1866
+ };
1867
+ }
1868
+
1869
+ function statusRunState(options) {
1870
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1871
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1872
+ const events = Array.isArray(state.events) ? state.events : [];
1873
+ const lastEvent = events.length > 0 ? events[events.length - 1] : null;
1874
+
1875
+ return {
1876
+ status: 'success',
1877
+ target: targetDir,
1878
+ artifacts: {
1879
+ current_run: syncCurrent ? currentRunPath : null,
1880
+ run_history: historyRunPath,
1881
+ },
1882
+ summary: {
1883
+ run_id: state.run_id,
1884
+ mode: state.mode || null,
1885
+ delivery_profile: state.delivery_profile || null,
1886
+ artifact_profile: state.artifact_profile || null,
1887
+ complexity: state.complexity || state.task?.complexity || null,
1888
+ status: state.status || null,
1889
+ flow_id: state.flow?.id || null,
1890
+ current_role: state.current_role || null,
1891
+ pending_input_update: Boolean(state.pending_input_update),
1892
+ input_update_count: Array.isArray(state.input_updates) ? state.input_updates.length : 0,
1893
+ pending_gate: state.pending_gate || null,
1894
+ gate_context: state.gate_context || null,
1895
+ incremental_update: state.incremental_update || null,
1896
+ auto_fix: normalizeAutoFixState(state.auto_fix),
1897
+ checkpoint_count: Number(state.checkpoint_count) || 0,
1898
+ last_checkpoint: state.last_checkpoint || null,
1899
+ updated_at: state.timestamps?.updated_at || null,
1900
+ last_event: lastEvent,
1901
+ },
1902
+ state,
1903
+ };
1904
+ }
1905
+
1906
+ function gateBlockedRunState(options) {
1907
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1908
+ const taskAnchorPath = options.taskAnchor
1909
+ ? path.resolve(process.cwd(), options.taskAnchor)
1910
+ : null;
1911
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1912
+ const requestedGate = options.gate || options.pendingGate || state.pending_gate || null;
1913
+ const nextStatus = options.status || (requestedGate ? 'waiting-approval' : 'blocked');
1914
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1915
+ const toRole = options.toRole || state.current_role || null;
1916
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1917
+ const now = new Date();
1918
+ const event = buildStateEvent({
1919
+ state,
1920
+ options: { ...options, pendingGate: requestedGate, toRole },
1921
+ now,
1922
+ defaults: {
1923
+ status: nextStatus,
1924
+ eventType: 'gate-blocked',
1925
+ message: requestedGate
1926
+ ? `waiting for ${requestedGate} approval`
1927
+ : 'run blocked',
1928
+ pendingGate: requestedGate,
1929
+ },
1930
+ });
1931
+
1932
+ const updatedState = {
1933
+ ...state,
1934
+ status: nextStatus,
1935
+ current_role: toRole,
1936
+ pending_input_update: false,
1937
+ pending_gate: requestedGate,
1938
+ gate_context: buildGateContext(state, options, requestedGate),
1939
+ incremental_update: buildIncrementalUpdateState(state, options, {
1940
+ updatedAt: now.toISOString(),
1941
+ }),
1942
+ verification: options.verificationData || state.verification || null,
1943
+ auto_fix: buildNextAutoFixState(state, options),
1944
+ anchor,
1945
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
1946
+ timestamps: {
1947
+ ...(state.timestamps || {}),
1948
+ updated_at: now.toISOString(),
1949
+ },
1950
+ };
1951
+
1952
+ const persistedState = saveUpdatedRunState({
1953
+ targetDir,
1954
+ historyRunPath,
1955
+ currentRunPath,
1956
+ syncCurrent,
1957
+ state: updatedState,
1958
+ checkpointEvent: 'gate-blocked',
1959
+ });
1960
+
1961
+ return {
1962
+ status: 'success',
1963
+ target: targetDir,
1964
+ artifacts: {
1965
+ current_run: syncCurrent ? currentRunPath : null,
1966
+ run_history: historyRunPath,
1967
+ },
1968
+ state: persistedState,
1969
+ source: {
1970
+ task_anchor: taskAnchorPath,
1971
+ gate: requestedGate,
1972
+ },
1973
+ };
1974
+ }
1975
+
1976
+ function completeRunState(options) {
1977
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
1978
+ const taskAnchorPath = options.taskAnchor
1979
+ ? path.resolve(process.cwd(), options.taskAnchor)
1980
+ : null;
1981
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
1982
+ if (options.skipArtifactCheck !== true) {
1983
+ assertRequiredOpenSpecArtifacts(targetDir, state, 'complete', options.toRole || state.current_role || null);
1984
+ }
1985
+ const toRole = options.toRole || state.current_role || null;
1986
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
1987
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
1988
+ const nextArtifacts = options.artifactsData
1989
+ ? mergeArtifacts(
1990
+ mergeArtifacts(buildDefaultArtifacts(state.task?.change_id || state.anchor?.task?.change_id || null), inferArtifacts(state.artifacts || null)),
1991
+ inferArtifacts(options.artifactsData),
1992
+ )
1993
+ : state.artifacts;
1994
+ const now = new Date();
1995
+ const event = buildStateEvent({
1996
+ state,
1997
+ options: { ...options, toRole, clearPendingGate: true },
1998
+ now,
1999
+ defaults: {
2000
+ status: 'success',
2001
+ eventType: 'run-completed',
2002
+ message: 'run completed',
2003
+ pendingGate: null,
2004
+ },
2005
+ });
2006
+
2007
+ const updatedState = {
2008
+ ...state,
2009
+ status: options.status || 'success',
2010
+ current_role: toRole,
2011
+ pending_input_update: false,
2012
+ pending_gate: null,
2013
+ gate_context: null,
2014
+ incremental_update: buildIncrementalUpdateState(state, options, {
2015
+ updatedAt: now.toISOString(),
2016
+ }),
2017
+ artifacts: nextArtifacts,
2018
+ auto_fix: buildNextAutoFixState(state, options, { active: false }),
2019
+ anchor,
2020
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
2021
+ timestamps: {
2022
+ ...(state.timestamps || {}),
2023
+ updated_at: now.toISOString(),
2024
+ finished_at: now.toISOString(),
2025
+ },
2026
+ };
2027
+
2028
+ const persistedState = saveUpdatedRunState({
2029
+ targetDir,
2030
+ historyRunPath,
2031
+ currentRunPath,
2032
+ syncCurrent,
2033
+ state: updatedState,
2034
+ checkpointEvent: 'complete',
2035
+ });
2036
+
2037
+ return {
2038
+ status: 'success',
2039
+ target: targetDir,
2040
+ artifacts: {
2041
+ current_run: syncCurrent ? currentRunPath : null,
2042
+ run_history: historyRunPath,
2043
+ },
2044
+ state: persistedState,
2045
+ source: {
2046
+ task_anchor: taskAnchorPath,
2047
+ },
2048
+ };
2049
+ }
2050
+
2051
+ function failRunState(options) {
2052
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
2053
+ const taskAnchorPath = options.taskAnchor
2054
+ ? path.resolve(process.cwd(), options.taskAnchor)
2055
+ : null;
2056
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
2057
+ const toRole = options.toRole || state.current_role || null;
2058
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
2059
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
2060
+ const now = new Date();
2061
+ const errorMessage = options.error || options.message || 'run failed';
2062
+ const event = buildStateEvent({
2063
+ state,
2064
+ options: { ...options, toRole, clearPendingGate: true, message: errorMessage },
2065
+ now,
2066
+ defaults: {
2067
+ status: 'failed',
2068
+ eventType: 'run-failed',
2069
+ message: errorMessage,
2070
+ pendingGate: null,
2071
+ },
2072
+ });
2073
+
2074
+ const updatedErrors = [...(Array.isArray(state.errors) ? state.errors : [])];
2075
+ if (errorMessage) {
2076
+ updatedErrors.push(errorMessage);
2077
+ }
2078
+
2079
+ const updatedState = {
2080
+ ...state,
2081
+ status: options.status || 'failed',
2082
+ current_role: toRole,
2083
+ pending_input_update: false,
2084
+ pending_gate: null,
2085
+ gate_context: null,
2086
+ incremental_update: buildIncrementalUpdateState(state, options, {
2087
+ updatedAt: now.toISOString(),
2088
+ }),
2089
+ auto_fix: buildNextAutoFixState(state, options, { active: false }),
2090
+ anchor,
2091
+ errors: updatedErrors,
2092
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
2093
+ timestamps: {
2094
+ ...(state.timestamps || {}),
2095
+ updated_at: now.toISOString(),
2096
+ finished_at: now.toISOString(),
2097
+ },
2098
+ };
2099
+
2100
+ const persistedState = saveUpdatedRunState({
2101
+ targetDir,
2102
+ historyRunPath,
2103
+ currentRunPath,
2104
+ syncCurrent,
2105
+ state: updatedState,
2106
+ checkpointEvent: 'fail',
2107
+ });
2108
+
2109
+ return {
2110
+ status: 'success',
2111
+ target: targetDir,
2112
+ artifacts: {
2113
+ current_run: syncCurrent ? currentRunPath : null,
2114
+ run_history: historyRunPath,
2115
+ },
2116
+ state: persistedState,
2117
+ source: {
2118
+ task_anchor: taskAnchorPath,
2119
+ error: options.error || null,
2120
+ },
2121
+ };
2122
+ }
2123
+
2124
+ function cancelRunState(options) {
2125
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
2126
+ const taskAnchorPath = options.taskAnchor
2127
+ ? path.resolve(process.cwd(), options.taskAnchor)
2128
+ : null;
2129
+ const { currentRunPath, historyRunPath, state, syncCurrent } = resolveRunStatePaths(targetDir, options.runId);
2130
+ const toRole = options.toRole || state.current_role || null;
2131
+ const taskAnchor = loadTaskAnchor(taskAnchorPath, options.taskAnchorData || null);
2132
+ const anchor = updateAnchorForRole(state.anchor || null, taskAnchor, toRole, options.nextRole);
2133
+ const now = new Date();
2134
+ const cancelMessage = options.message || 'run cancelled';
2135
+ const event = buildStateEvent({
2136
+ state,
2137
+ options: { ...options, toRole, clearPendingGate: true, message: cancelMessage },
2138
+ now,
2139
+ defaults: {
2140
+ status: 'cancelled',
2141
+ eventType: 'run-cancelled',
2142
+ message: cancelMessage,
2143
+ pendingGate: null,
2144
+ },
2145
+ });
2146
+
2147
+ const updatedState = {
2148
+ ...state,
2149
+ status: options.status || 'cancelled',
2150
+ current_role: toRole,
2151
+ pending_input_update: false,
2152
+ pending_gate: null,
2153
+ gate_context: null,
2154
+ incremental_update: buildIncrementalUpdateState(state, options, {
2155
+ updatedAt: now.toISOString(),
2156
+ }),
2157
+ auto_fix: buildNextAutoFixState(state, options, { active: false }),
2158
+ anchor,
2159
+ events: [...(Array.isArray(state.events) ? state.events : []), event],
2160
+ timestamps: {
2161
+ ...(state.timestamps || {}),
2162
+ updated_at: now.toISOString(),
2163
+ finished_at: now.toISOString(),
2164
+ },
2165
+ };
2166
+
2167
+ const persistedState = saveUpdatedRunState({
2168
+ targetDir,
2169
+ historyRunPath,
2170
+ currentRunPath,
2171
+ syncCurrent,
2172
+ state: updatedState,
2173
+ checkpointEvent: 'cancel',
2174
+ });
2175
+
2176
+ return {
2177
+ status: 'success',
2178
+ target: targetDir,
2179
+ artifacts: {
2180
+ current_run: syncCurrent ? currentRunPath : null,
2181
+ run_history: historyRunPath,
2182
+ },
2183
+ state: persistedState,
2184
+ source: {
2185
+ task_anchor: taskAnchorPath,
2186
+ },
2187
+ };
2188
+ }
2189
+
2190
+ function initRunState(options) {
2191
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
2192
+ const runPlanPath = path.resolve(process.cwd(), options.runPlan);
2193
+ const taskAnchorPath = options.taskAnchor
2194
+ ? path.resolve(process.cwd(), options.taskAnchor)
2195
+ : null;
2196
+
2197
+ const runPlan = readJsonFile(runPlanPath, 'run-plan');
2198
+ assertRunPlan(runPlan, runPlanPath);
2199
+
2200
+ const taskAnchor = taskAnchorPath
2201
+ ? readJsonFile(taskAnchorPath, 'task-anchor')
2202
+ : null;
2203
+
2204
+ return writeRunState({
2205
+ targetDir,
2206
+ runPlan,
2207
+ taskAnchor,
2208
+ options,
2209
+ source: {
2210
+ runPlan: runPlanPath,
2211
+ taskAnchor: taskAnchorPath,
2212
+ bootstrapPayload: null,
2213
+ },
2214
+ });
2215
+ }
2216
+
2217
+ function bootstrapRunState(options) {
2218
+ const targetDir = path.resolve(process.cwd(), options.target || '.');
2219
+ const inputCount = [
2220
+ Boolean(options.payload),
2221
+ Boolean(options.stdin),
2222
+ Boolean(options.payloadData),
2223
+ ].filter(Boolean).length;
2224
+ const hasInput = inputCount > 0;
2225
+
2226
+ if (!hasInput) {
2227
+ throw new Error('Missing bootstrap input: use --payload <file> or --stdin');
2228
+ }
2229
+ if (inputCount > 1) {
2230
+ throw new Error('Use only one bootstrap input: --payload <file>, --stdin, or payloadData');
2231
+ }
2232
+
2233
+ const payloadSource = options.payloadData
2234
+ ? 'memory-payload'
2235
+ : options.payload
2236
+ ? path.resolve(process.cwd(), options.payload)
2237
+ : 'stdin';
2238
+ const payload = options.payloadData
2239
+ ? options.payloadData
2240
+ : options.payload
2241
+ ? readJsonFile(payloadSource, 'bootstrap payload')
2242
+ : readJsonFromStdin('bootstrap payload');
2243
+
2244
+ const { runPlan, taskAnchor } = normalizeBootstrapPayload(payload, payloadSource);
2245
+ assertRunPlan(runPlan, payloadSource);
2246
+
2247
+ return writeRunState({
2248
+ targetDir,
2249
+ runPlan,
2250
+ taskAnchor,
2251
+ options,
2252
+ source: {
2253
+ runPlan: payloadSource,
2254
+ taskAnchor: payloadSource,
2255
+ bootstrapPayload: payloadSource,
2256
+ },
2257
+ });
2258
+ }
2259
+
2260
+ function printPretty(result, action = 'init') {
2261
+ if (action === 'handoff') {
2262
+ console.log('run-state updated');
2263
+ } else if (action === 'approve') {
2264
+ console.log('run-state approved');
2265
+ } else if (action === 'resume') {
2266
+ console.log('run-state resumed');
2267
+ } else if (action === 'pause') {
2268
+ console.log('run-state paused');
2269
+ } else if (action === 'restore') {
2270
+ console.log('run-state restored');
2271
+ } else if (action === 'gate-blocked') {
2272
+ console.log('run-state blocked');
2273
+ } else if (action === 'status') {
2274
+ console.log('run-state status');
2275
+ } else if (action === 'complete') {
2276
+ console.log('run-state completed');
2277
+ } else if (action === 'fail') {
2278
+ console.log('run-state failed');
2279
+ } else if (action === 'cancel') {
2280
+ console.log('run-state cancelled');
2281
+ } else {
2282
+ console.log('run-state initialized');
2283
+ }
2284
+ console.log(` target: ${result.target}`);
2285
+ console.log(` run_id: ${result.state.run_id}`);
2286
+ console.log(` current: ${result.artifacts.current_run}`);
2287
+ if (result.artifacts.run_history) {
2288
+ console.log(` history: ${result.artifacts.run_history}`);
2289
+ }
2290
+ console.log(` mode: ${result.state.mode || 'n/a'}`);
2291
+ console.log(` review_policy: ${result.state.review_policy || 'n/a'}`);
2292
+ console.log(` delivery_profile: ${result.state.delivery_profile || 'n/a'}`);
2293
+ console.log(` artifact_profile: ${result.state.artifact_profile || 'n/a'}`);
2294
+ console.log(` complexity: ${result.state.complexity || result.state.task?.complexity || 'n/a'}`);
2295
+ console.log(` checkpoints: ${Number(result.state.checkpoint_count) || 0}`);
2296
+ if (result.state.last_checkpoint?.file) {
2297
+ console.log(` last_checkpoint: ${result.state.last_checkpoint.file}`);
2298
+ }
2299
+ if (action === 'status') {
2300
+ console.log(` status: ${result.state.status || 'n/a'}`);
2301
+ console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2302
+ console.log(` pending_gate: ${result.state.pending_gate || 'n/a'}`);
2303
+ if (result.state.gate_context?.required_user_action) {
2304
+ console.log(` required_user_action: ${result.state.gate_context.required_user_action}`);
2305
+ }
2306
+ } else if (action === 'handoff') {
2307
+ console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2308
+ console.log(` from_role: ${result.handoff?.from_role || 'n/a'}`);
2309
+ console.log(` to_role: ${result.handoff?.to_role || 'n/a'}`);
2310
+ } else if (
2311
+ action === 'pause' ||
2312
+ action === 'approve' ||
2313
+ action === 'resume' ||
2314
+ action === 'restore' ||
2315
+ action === 'gate-blocked' ||
2316
+ action === 'complete' ||
2317
+ action === 'fail'
2318
+ ) {
2319
+ console.log(` status: ${result.state.status || 'n/a'}`);
2320
+ console.log(` current_role: ${result.state.current_role || 'n/a'}`);
2321
+ console.log(` pending_gate: ${result.state.pending_gate || 'n/a'}`);
2322
+ } else {
2323
+ console.log(` first_handoff: ${result.state.plan.first_handoff || 'n/a'}`);
2324
+ }
2325
+ if (result.source.bootstrap_payload) {
2326
+ console.log(` bootstrap_payload: ${result.source.bootstrap_payload}`);
2327
+ }
2328
+ }
2329
+
2330
+ function main(argv = process.argv.slice(2)) {
2331
+ const { command, options } = parseArgs(argv);
2332
+
2333
+ if (!command || options.help || command === '--help' || command === '-h' || command === 'help') {
2334
+ printUsage();
2335
+ return 0;
2336
+ }
2337
+
2338
+ if (command === 'init') {
2339
+ if (!options.runPlan) {
2340
+ throw new Error('Missing required argument: --run-plan <file>');
2341
+ }
2342
+
2343
+ const result = initRunState(options);
2344
+
2345
+ if (options.json) {
2346
+ console.log(JSON.stringify(result, null, 2));
2347
+ } else {
2348
+ printPretty(result, 'init');
2349
+ }
2350
+
2351
+ return 0;
2352
+ }
2353
+
2354
+ if (command === 'bootstrap') {
2355
+ const result = bootstrapRunState(options);
2356
+
2357
+ if (options.json) {
2358
+ console.log(JSON.stringify(result, null, 2));
2359
+ } else {
2360
+ printPretty(result, 'bootstrap');
2361
+ }
2362
+
2363
+ return 0;
2364
+ }
2365
+
2366
+ if (command === 'handoff') {
2367
+ const result = handoffRunState(options);
2368
+
2369
+ if (options.json) {
2370
+ console.log(JSON.stringify(result, null, 2));
2371
+ } else {
2372
+ printPretty(result, 'handoff');
2373
+ }
2374
+
2375
+ return 0;
2376
+ }
2377
+
2378
+ if (command === 'approve') {
2379
+ const result = approveRunState(options);
2380
+
2381
+ if (options.json) {
2382
+ console.log(JSON.stringify(result, null, 2));
2383
+ } else {
2384
+ printPretty(result, 'approve');
2385
+ }
2386
+
2387
+ return 0;
2388
+ }
2389
+
2390
+ if (command === 'pause') {
2391
+ const result = pauseRunState(options);
2392
+
2393
+ if (options.json) {
2394
+ console.log(JSON.stringify(result, null, 2));
2395
+ } else {
2396
+ printPretty(result, 'pause');
2397
+ }
2398
+
2399
+ return 0;
2400
+ }
2401
+
2402
+ if (command === 'resume') {
2403
+ const result = resumeRunState(options);
2404
+
2405
+ if (options.json) {
2406
+ console.log(JSON.stringify(result, null, 2));
2407
+ } else {
2408
+ printPretty(result, 'resume');
2409
+ }
2410
+
2411
+ return 0;
2412
+ }
2413
+
2414
+ if (command === 'restore') {
2415
+ const result = restoreRunState(options);
2416
+
2417
+ if (options.json) {
2418
+ console.log(JSON.stringify(result, null, 2));
2419
+ } else {
2420
+ printPretty(result, 'restore');
2421
+ }
2422
+
2423
+ return 0;
2424
+ }
2425
+
2426
+ if (command === 'gate-blocked') {
2427
+ const result = gateBlockedRunState(options);
2428
+
2429
+ if (options.json) {
2430
+ console.log(JSON.stringify(result, null, 2));
2431
+ } else {
2432
+ printPretty(result, 'gate-blocked');
2433
+ }
2434
+
2435
+ return 0;
2436
+ }
2437
+
2438
+ if (command === 'status') {
2439
+ const result = statusRunState(options);
2440
+
2441
+ if (options.json) {
2442
+ console.log(JSON.stringify(result, null, 2));
2443
+ } else {
2444
+ printPretty(result, 'status');
2445
+ }
2446
+
2447
+ return 0;
2448
+ }
2449
+
2450
+ if (command === 'complete') {
2451
+ const result = completeRunState(options);
2452
+
2453
+ if (options.json) {
2454
+ console.log(JSON.stringify(result, null, 2));
2455
+ } else {
2456
+ printPretty(result, 'complete');
2457
+ }
2458
+
2459
+ return 0;
2460
+ }
2461
+
2462
+ if (command === 'fail') {
2463
+ const result = failRunState(options);
2464
+
2465
+ if (options.json) {
2466
+ console.log(JSON.stringify(result, null, 2));
2467
+ } else {
2468
+ printPretty(result, 'fail');
2469
+ }
2470
+
2471
+ return 0;
2472
+ }
2473
+
2474
+ if (command === 'cancel') {
2475
+ const result = cancelRunState(options);
2476
+
2477
+ if (options.json) {
2478
+ console.log(JSON.stringify(result, null, 2));
2479
+ } else {
2480
+ printPretty(result, 'cancel');
2481
+ }
2482
+
2483
+ return 0;
2484
+ }
2485
+
2486
+ if (
2487
+ command !== 'init' &&
2488
+ command !== 'bootstrap' &&
2489
+ command !== 'handoff' &&
2490
+ command !== 'approve' &&
2491
+ command !== 'pause' &&
2492
+ command !== 'resume' &&
2493
+ command !== 'restore' &&
2494
+ command !== 'gate-blocked' &&
2495
+ command !== 'status' &&
2496
+ command !== 'complete' &&
2497
+ command !== 'fail' &&
2498
+ command !== 'cancel'
2499
+ ) {
2500
+ throw new Error(`Unsupported runtime-state command: ${command}`);
2501
+ }
2502
+ }
2503
+
2504
+ if (require.main === module) {
2505
+ try {
2506
+ const exitCode = main();
2507
+ process.exit(exitCode);
2508
+ } catch (error) {
2509
+ console.error(`runtime-state error: ${error.message}`);
2510
+ process.exit(1);
2511
+ }
2512
+ }
2513
+
2514
+ module.exports = {
2515
+ main,
2516
+ parseArgs,
2517
+ createRunId,
2518
+ normalizeSpecsArtifactPath,
2519
+ inferDeliveryProfile,
2520
+ inferArtifactProfile,
2521
+ inferComplexity,
2522
+ inferRiskLevel,
2523
+ inferArtifacts,
2524
+ buildRunState,
2525
+ recordRunInputUpdate,
2526
+ readRunStateFile,
2527
+ resolveRunStatePaths,
2528
+ initRunState,
2529
+ bootstrapRunState,
2530
+ normalizeBootstrapPayload,
2531
+ handoffRunState,
2532
+ approveRunState,
2533
+ pauseRunState,
2534
+ resumeRunState,
2535
+ restoreRunState,
2536
+ gateBlockedRunState,
2537
+ statusRunState,
2538
+ completeRunState,
2539
+ failRunState,
2540
+ cancelRunState,
2541
+ };