@khoavu882/doflow 2.0.0-beta.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -13,23 +13,36 @@ const UNCHECKED = /^\s*- \[ \] /m;
13
13
  const UNCHECKED_TASK = /^\s*- \[ \] (\S+)/gm;
14
14
  const SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15
15
 
16
- function recordStorePaths(fsImpl ) {
16
+ /** The record extension a store declares in its `match` pattern: `.jsonl` for a JSON-lines store, else `.json`. */
17
+ function recordExtension(match ) {
18
+ return typeof match === 'string' && /\\\.jsonl\$$/.test(match) ? '.jsonl' : '.json';
19
+ }
20
+
21
+ function workRecordStores(fsImpl ) {
17
22
  try {
18
23
  const registry = loadStorageRegistry({ repoRoot: REPO_ROOT, fsImpl });
19
- return new Map(registry.stores
20
- .filter((store ) => store.root === 'project-state' && store.entry === 'record' && store.rule === 'work')
21
- .map((store ) => [store.id, store.path]));
24
+ return registry.stores.filter((store ) => store.root === 'project-state' && store.entry === 'record' && store.rule === 'work');
22
25
  } catch {
23
- return new Map();
26
+ return [];
24
27
  }
25
28
  }
26
29
 
30
+ function recordStorePaths(fsImpl ) {
31
+ return new Map(workRecordStores(fsImpl).map((store ) => [store.id, store.path]));
32
+ }
33
+
34
+ function recordStoreExtensions(fsImpl ) {
35
+ return new Map(workRecordStores(fsImpl).map((store ) => [store.id, recordExtension(store.match)]));
36
+ }
37
+
27
38
  function createWorkCache({ projectRoot, fsImpl = fs } ) {
28
39
  return {
29
40
  projectRoot: path.resolve(projectRoot),
30
41
  fsImpl,
31
42
  recordStorePaths: recordStorePaths(fsImpl),
43
+ recordExtensions: recordStoreExtensions(fsImpl),
32
44
  runNames: null,
45
+ runLocations: null,
33
46
  runListError: false,
34
47
  featureNames: null,
35
48
  featureListError: false,
@@ -44,7 +57,7 @@ function createWorkCache({ projectRoot, fsImpl = fs }
44
57
  * taken at selection. A run reopened or a plan task unchecked after selection keeps the record.
45
58
  */
46
59
  function refreshedWorkCache(cache ) {
47
- return { ...cache, runNames: null, runListError: false, featureNames: null, featureListError: false,
60
+ return { ...cache, runNames: null, runLocations: null, runListError: false, featureNames: null, featureListError: false,
48
61
  runs: new Map(), plans: new Map() };
49
62
  }
50
63
 
@@ -63,6 +76,16 @@ function safeSegment(value ) {
63
76
  return typeof value === 'string' && SAFE_SEGMENT.test(value) && !value.includes('..');
64
77
  }
65
78
 
79
+ /** Whether `name` can be a run namespace directory (a feature slug), not a `<file>.lock` directory. */
80
+ function isRunNamespaceDir(name ) {
81
+ return safeSegment(name) && !name.endsWith('.lock');
82
+ }
83
+
84
+ /**
85
+ * Every run id the orchestration store holds, flat or under a `<slug>/` namespace (115 C-1: a task id
86
+ * other than its feature's slug lives in `orchestration/<slug>/`). `cache.runLocations` maps each id
87
+ * to the directories that hold it ('' is the flat store), namespaces sorted, flat last.
88
+ */
66
89
  function listRunNames(cache , shouldStop ) {
67
90
  if (shouldStop?.()) return { names: [], incomplete: true };
68
91
  if (cache.runNames !== null) return { names: cache.runNames, incomplete: false };
@@ -71,35 +94,115 @@ function listRunNames(cache , shouldStop )
71
94
  try { entries = cache.fsImpl.readdirSync(dir, { withFileTypes: true }); }
72
95
  catch (error ) {
73
96
  if (error?.code === 'ENOENT') entries = [];
74
- else { cache.runNames = []; cache.runListError = true; return { names: cache.runNames, incomplete: false }; }
97
+ else { cache.runNames = []; cache.runLocations = new Map(); cache.runListError = true; return { names: cache.runNames, incomplete: false }; }
75
98
  }
76
99
  if (shouldStop?.()) return { names: [], incomplete: true };
77
- const names = [];
100
+ const locations = new Map ();
101
+ const add = (id , where ) => {
102
+ if (!locations.has(id)) locations.set(id, []);
103
+ (locations.get(id) ).push(where);
104
+ };
105
+ const namespaces = [];
78
106
  for (const entry of entries) {
79
107
  if (shouldStop?.()) return { names: [], incomplete: true };
80
- if (entry.name.endsWith('.json')) names.push(entry.name.slice(0, -5));
108
+ if (entry.name.endsWith('.json')) add(entry.name.slice(0, -5), '');
109
+ else if (entry.isDirectory() && isRunNamespaceDir(entry.name)) namespaces.push(entry.name);
110
+ }
111
+ for (const namespace of namespaces.sort()) {
112
+ let inner ;
113
+ try { inner = cache.fsImpl.readdirSync(path.join(dir, namespace), { withFileTypes: true }); }
114
+ catch (error ) {
115
+ if (error?.code === 'ENOENT') continue;
116
+ cache.runNames = []; cache.runLocations = new Map(); cache.runListError = true;
117
+ return { names: cache.runNames, incomplete: false };
118
+ }
119
+ for (const file of inner) {
120
+ if (shouldStop?.()) return { names: [], incomplete: true };
121
+ if (file.name.endsWith('.json')) add(file.name.slice(0, -5), namespace);
122
+ }
81
123
  }
82
124
  if (shouldStop?.()) return { names: [], incomplete: true };
83
- cache.runNames = names;
84
- return { names, incomplete: false };
125
+ // Namespaces come first in the order listed; the flat copy (a legacy or seed source) is last.
126
+ for (const where of locations.values()) where.sort((x, y) => (x === '' ? 1 : y === '' ? -1 : x < y ? -1 : x > y ? 1 : 0));
127
+ cache.runNames = [...locations.keys()];
128
+ cache.runLocations = locations;
129
+ return { names: cache.runNames, incomplete: false };
85
130
  }
86
131
 
87
- function readRun(cache , runId ) {
88
- if (cache.runs.has(runId)) return cache.runs.get(runId);
89
- const file = path.join(stateRoot(cache.projectRoot), 'orchestration', `${runId}.json`);
132
+ function readRunAt(cache , runId , where ) {
133
+ // The flat copy keeps its bare id as the key: the pruner reads that snapshot back by run id.
134
+ const key = where ? `${where}/${runId}` : runId;
135
+ if (cache.runs.has(key)) return cache.runs.get(key);
136
+ const file = path.join(stateRoot(cache.projectRoot), 'orchestration', ...(where ? [where] : []), `${runId}.json`);
90
137
  let result ;
91
138
  try {
92
- const value = JSON.parse(cache.fsImpl.readFileSync(file, 'utf8'));
139
+ // A namespaced copy is a file base never read (N-4): a link, a FIFO or a device there reads as
140
+ // unreadable (the record is kept) and never blocks. The flat copy keeps its plain read (FU-d88kp0).
141
+ const text = where && typeof cache.fsImpl.lstatSync === 'function' && typeof cache.fsImpl.openSync === 'function'
142
+ ? require('../task-scope.js').readRegularFileText(file, cache.fsImpl) : cache.fsImpl.readFileSync(file, 'utf8');
143
+ if (text === null) throw new Error('not a regular file');
144
+ const value = JSON.parse(text);
93
145
  result = value && typeof value === 'object' && !Array.isArray(value) && RUN_STATES.has(value.state)
94
- ? { ok: true, value }
95
- : { ok: false };
146
+ ? { ok: true, value, where }
147
+ : { ok: false, where };
96
148
  } catch {
97
- result = { ok: false };
149
+ result = { ok: false, where };
98
150
  }
99
- cache.runs.set(runId, result);
151
+ cache.runs.set(key, result);
100
152
  return result;
101
153
  }
102
154
 
155
+ /**
156
+ * Whether a namespaced copy of a run keeps the record open (C-1, N-1). A namespaced copy may only
157
+ * keep a record: a record is judged as 135ce230 judged it, by the flat run copy alone, and a
158
+ * namespaced copy never makes a record finished. The copies a record owns are its own namespace's
159
+ * (a namespaced record) or the namespace named by its own `featureSlug`/`slug` (a flat record); it
160
+ * never owns another feature's copy. The run is chosen as a flat run is: the exact id, the record's
161
+ * namespace, then the longest delimited prefix. An owned copy that is RUNNING or AWAITING_GATE
162
+ * holds the record open and one that cannot be read keeps it undecidable. A flat record that names
163
+ * no namespace of its own and could be claimed by two or more namespaced copies is undecidable (fail
164
+ * closed), not guessed.
165
+ */
166
+ function namespacedHold(cache , taskId , namespace , recordValue , shouldStop ) {
167
+ const recordNs = safeSegment(namespace) ? namespace : null;
168
+ const named = recordNs ? null : (stringField(recordValue, 'featureSlug') || stringField(recordValue, 'slug'));
169
+ const own = recordNs || (named !== null && isRunNamespaceDir(named) ? named : null);
170
+ const locations = cache.runLocations || new Map();
171
+ const choose = (accept ) => {
172
+ let exact = false, byNamespace = false, prefix = null;
173
+ for (const [candidate, held] of locations) {
174
+ if (shouldStop?.()) return undefined;
175
+ if (!held.some((w ) => w !== '' && accept(w))) continue;
176
+ if (candidate === taskId) exact = true;
177
+ else if (recordNs && candidate === recordNs) byNamespace = true;
178
+ else if ((taskId.startsWith(`${candidate}-`) || taskId.startsWith(`${candidate}.`))
179
+ && (!prefix || candidate.length > prefix.length || (candidate.length === prefix.length && candidate < prefix))) prefix = candidate;
180
+ }
181
+ return exact ? taskId : byNamespace ? recordNs : prefix;
182
+ };
183
+ if (own) {
184
+ const id = choose((w ) => w === own);
185
+ if (id === undefined || shouldStop?.()) return { incomplete: true };
186
+ if (id) {
187
+ const read = readRunAt(cache, id, own);
188
+ if (shouldStop?.()) return { incomplete: true };
189
+ if (!read.ok) return { unreadable: true, id };
190
+ if (RUN_OPEN_STATES.includes(read.value.state)) return { open: true, id };
191
+ }
192
+ }
193
+ if (!recordNs && !own) {
194
+ const id = choose(() => true);
195
+ if (id === undefined || shouldStop?.()) return { incomplete: true };
196
+ if (id && (locations.get(id) || []).filter((w ) => w !== '').length > 1) return { unreadable: true, id };
197
+ }
198
+ return {};
199
+ }
200
+
201
+ /** The flat run copy `runId`, the only copy a record is judged by (135ce230). */
202
+ function readRun(cache , runId ) {
203
+ return readRunAt(cache, runId, '');
204
+ }
205
+
103
206
  function resolveRun(cache , taskId , namespace , shouldStop ) {
104
207
  const listed = listRunNames(cache, shouldStop);
105
208
  if (listed.incomplete) return { id: null, unreadable: false, incomplete: true };
@@ -107,6 +210,7 @@ function resolveRun(cache , taskId , namespace , shouldStop
107
210
  let exact = false, namespaced = false, prefix = null;
108
211
  for (const candidate of listed.names) {
109
212
  if (shouldStop?.()) return { id: null, unreadable: false, incomplete: true };
213
+ if (!(cache.runLocations.get(candidate) || []).includes('')) continue;
110
214
  if (candidate === taskId) exact = true;
111
215
  else if (safeSegment(namespace) && candidate === namespace) namespaced = true;
112
216
  else if ((taskId.startsWith(`${candidate}-`) || taskId.startsWith(`${candidate}.`))
@@ -120,16 +224,26 @@ function resolveRun(cache , taskId , namespace , shouldStop
120
224
  return read.ok ? { id, value: read.value, unreadable: false } : { id, value: null, unreadable: true };
121
225
  }
122
226
 
123
- function readRecord({ projectRoot, storePath, recordId, namespace, mtimeMs, fsImpl } ) {
227
+ function parseJsonLines(text ) {
228
+ const lines = text.split('\n').filter((line ) => line.trim() !== '');
229
+ if (lines.length === 0) throw new Error('empty JSON-lines record');
230
+ const values = lines.map((line ) => JSON.parse(line));
231
+ return values[values.length - 1];
232
+ }
233
+
234
+ function readRecord({ projectRoot, storePath, recordId, namespace, mtimeMs, fsImpl, extension = '.json' } ) {
124
235
  const storeSegments = typeof storePath === 'string' && storePath !== '' ? storePath.split('/') : [];
125
236
  if (typeof storePath !== 'string' || storeSegments.some((segment ) => !safeSegment(segment))
126
237
  || !safeSegment(recordId)
127
238
  || (namespace !== null && namespace !== undefined && !safeSegment(namespace))) {
128
239
  return { ok: false };
129
240
  }
130
- const file = path.join(stateRoot(projectRoot), ...storeSegments, ...(namespace ? [namespace] : []), `${recordId}.json`);
241
+ const file = path.join(stateRoot(projectRoot), ...storeSegments, ...(namespace ? [namespace] : []), `${recordId}${extension}`);
131
242
  try {
132
- const value = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
243
+ const text = fsImpl.readFileSync(file, 'utf8');
244
+ // A `.jsonl` record is one JSON value per line, appended over time: every line must parse (fail
245
+ // closed otherwise) and the last one is the record's current value.
246
+ const value = extension === '.jsonl' ? parseJsonLines(text) : JSON.parse(text);
133
247
  const mtime = mtimeMs === undefined ? fsImpl.statSync(file).mtimeMs : mtimeMs;
134
248
  if (!Number.isFinite(mtime)) return { ok: false };
135
249
  return { ok: true, value, mtimeMs: mtime };
@@ -226,6 +340,33 @@ function planWithUncheckedTask(cache , taskId , shouldStop
226
340
  return { found: false, unreadable: false };
227
341
  }
228
342
 
343
+ /** IC-028: why a task-attempts ledger is open work, or null when it holds neither an active or
344
+ * uncleared cancelled attempt nor a run whose control is other than `running`. A cancel a resume has
345
+ * cleared (the same `clearedCancels` the resume report uses) is settled work, not open work. */
346
+ function openLedgerReason(ledger ) {
347
+ const tasks = ledger && typeof ledger.tasks === 'object' && ledger.tasks ? Object.entries (ledger.tasks) : [];
348
+ let cleared = null;
349
+ for (const [, entry] of tasks) {
350
+ const attempts = entry && Array.isArray(entry.attempts) ? entry.attempts : [];
351
+ for (const a of attempts) {
352
+ if (!a) continue;
353
+ if (a.state === 'active') return 'attempt-open';
354
+ if (a.state !== 'cancelled') continue;
355
+ // Loaded only when a cancelled attempt exists: task-run is a heavy module for a storage pass.
356
+ // Paired by the attempt object, not by (task, token): a malformed ledger can repeat or omit
357
+ // tokens, and an uncleared cancel must never be taken for a cleared one.
358
+ if (cleared === null) cleared = new Set(require('../task-run.js').clearedCancels(ledger).map((c ) => c.attempt));
359
+ if (!cleared.has(a)) return 'attempt-open';
360
+ }
361
+ }
362
+ const runs = ledger && typeof ledger.runs === 'object' && ledger.runs ? Object.values (ledger.runs) : [];
363
+ if (runs.some((run ) => !run || run.control !== 'running')) return 'run-control-open';
364
+ return null;
365
+ }
366
+
367
+ /** IC-037: the dispatch run states that settle a dispatch store; any other run state is open. */
368
+ const DISPATCH_RUN_SETTLED = Object.freeze(['completed', 'cancelled']);
369
+
229
370
  function answer(state , reason , run , feature , since = null) {
230
371
  return { state, since, reason, run, feature };
231
372
  }
@@ -234,6 +375,47 @@ function incompleteAnswer(run = null, feature = null) {
234
375
  return { ...answer('undecidable', 'pass-budget-exhausted', run, feature), incomplete: true };
235
376
  }
236
377
 
378
+ /**
379
+ * A loop run record (`roadmap/runs/<runId>.json`) is open while its `closedAt` is null (the loop
380
+ * resumes any such run), while it is the newest record in `runs/`, while a merge intent in
381
+ * `roadmap/intents/` names its `runId`, or while `roadmap/lease.json` names it. Any other record is
382
+ * finished as of its mtime. An unreadable source is never called finished.
383
+ */
384
+ function roadmapRunStatus(project , fileSystem , runId , mtimeMs ) {
385
+ if (!safeSegment(runId)) return answer('undecidable', 'record-unreadable', null, null);
386
+ const dir = path.join(stateRoot(project), 'roadmap');
387
+ const unreadable = () => answer('undecidable', 'record-unreadable', runId, null);
388
+ let record ;
389
+ try { record = JSON.parse(fileSystem.readFileSync(path.join(dir, 'runs', `${runId}.json`), 'utf8')); }
390
+ catch { return unreadable(); }
391
+ if (!record || typeof record !== 'object' || !Object.hasOwn(record, 'closedAt')) return unreadable();
392
+ if (record.closedAt === null) return answer('open', 'roadmap-run-open', runId, null);
393
+ const list = (folder ) => {
394
+ try { return fileSystem.readdirSync(folder); } catch (error ) { return error.code === 'ENOENT' ? [] : null; }
395
+ };
396
+ const runs = list(path.join(dir, 'runs'));
397
+ if (runs === null) return unreadable();
398
+ let newest = null;
399
+ for (const name of runs) {
400
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/.test(name)) continue;
401
+ let stat ;
402
+ try { stat = fileSystem.statSync(path.join(dir, 'runs', name)); } catch { return unreadable(); }
403
+ if (!newest || stat.mtimeMs > newest.mtimeMs || (stat.mtimeMs === newest.mtimeMs && name > newest.name)) newest = { name, mtimeMs: stat.mtimeMs };
404
+ }
405
+ const open = answer('open', 'roadmap-run-open', runId, null);
406
+ if (newest?.name === `${runId}.json`) return open;
407
+ const intents = list(path.join(dir, 'intents'));
408
+ if (intents === null) return unreadable();
409
+ for (const name of intents) {
410
+ if (!name.endsWith('.json')) continue;
411
+ try { if (JSON.parse(fileSystem.readFileSync(path.join(dir, 'intents', name), 'utf8'))?.runId === runId) return open; }
412
+ catch { return unreadable(); }
413
+ }
414
+ try { if (JSON.parse(fileSystem.readFileSync(path.join(dir, 'lease.json'), 'utf8'))?.runId === runId) return open; }
415
+ catch (error ) { if (error.code !== 'ENOENT') return unreadable(); }
416
+ return answer('finished', 'roadmap-run-finished', runId, null, Number.isFinite(mtimeMs) ? mtimeMs : null);
417
+ }
418
+
237
419
  /**
238
420
  * The one definition of whether a per-task record is still open work (IC-004).
239
421
  * Reads fail closed: an unreadable run, record, or relevant plan is never called finished. An
@@ -243,11 +425,23 @@ function incompleteAnswer(run = null, feature = null) {
243
425
  function workStatus({
244
426
  projectRoot, store, taskId, namespace = null, mtimeMs, cache, fsImpl, shouldStop,
245
427
  } ) {
428
+ const recordId = taskId;
429
+ const workId = typeof taskId === 'string' ? taskId.replace(/_claims$/, '') : taskId;
430
+
431
+ // Roadmap archive location is authoritative: open intents never age out, while archived
432
+ // intents use their own mtime and do not depend on the feature plan remaining on disk.
433
+ if (store === 'roadmap' && namespace === 'intents')
434
+ return answer('open', 'roadmap-intent-open', workId, null);
435
+ if (store === 'roadmap' && namespace === 'done')
436
+ return answer('finished', 'roadmap-intent-done', workId, null, Number.isFinite(mtimeMs) ? mtimeMs : null);
437
+ // The tripwire's remembered event ids are tiny and bounded by those ids: kept.
438
+ if (store === 'roadmap' && namespace === 'tripwire')
439
+ return answer('open', 'roadmap-tripwire-kept', workId, null);
440
+
246
441
  const project = path.resolve(projectRoot);
247
442
  const fileSystem = fsImpl || cache?.fsImpl || fs;
443
+ if (store === 'roadmap' && namespace === 'runs') return roadmapRunStatus(project, fileSystem, workId, mtimeMs);
248
444
  const pass = cacheFor({ projectRoot: project, fsImpl: fileSystem, cache });
249
- const recordId = taskId;
250
- const workId = typeof taskId === 'string' ? taskId.replace(/_claims$/, '') : taskId;
251
445
 
252
446
  if (shouldStop?.()) return incompleteAnswer();
253
447
  if (!safeSegment(workId)) return answer('undecidable', 'record-unreadable', null, null);
@@ -274,7 +468,8 @@ function workStatus({
274
468
  record = { ok: false };
275
469
  }
276
470
  } else {
277
- record = readRecord({ projectRoot: project, storePath, recordId, namespace, mtimeMs, fsImpl: fileSystem });
471
+ record = readRecord({ projectRoot: project, storePath, recordId, namespace, mtimeMs, fsImpl: fileSystem,
472
+ extension: pass.recordExtensions?.get(store) || '.json' });
278
473
  }
279
474
  if (shouldStop?.()) return incompleteAnswer(resolvedRun.id);
280
475
  if (!record.ok) return answer('undecidable', 'record-unreadable', resolvedRun.id, null);
@@ -284,6 +479,30 @@ function workStatus({
284
479
  return answer('open', 'run-open', resolvedRun.id, null);
285
480
  }
286
481
 
482
+ // N-1: a namespaced copy of a run may keep the record open or undecidable, never finished.
483
+ const hold = namespacedHold(pass, workId, namespace, record.value, shouldStop);
484
+ if (hold.incomplete || shouldStop?.()) return incompleteAnswer(resolvedRun.id);
485
+ if (hold.open) return answer('open', 'run-open', hold.id, null);
486
+ if (hold.unreadable) return answer('undecidable', 'run-unreadable', hold.id, null);
487
+
488
+ // IC-028: a task-attempts ledger stays open while any attempt is active or cancelled and not cleared
489
+ // by a resume, or any run is not `running`, so a paused or cancelled run whose boxes are all ticked
490
+ // is never pruned.
491
+ if (store === 'task-attempts') {
492
+ const ledgerOpen = openLedgerReason(record.value);
493
+ if (ledgerOpen) return answer('open', ledgerOpen, resolvedRun.id, null);
494
+ }
495
+
496
+ // IC-037 (mirrors IC-028): a dispatch store stays open while its run is not `completed` or
497
+ // `cancelled`, so a paused, blocked or resume-required run on a fully ticked plan keeps the
498
+ // record that names its kept worktrees.
499
+ if (store === 'dispatch') {
500
+ const dispatchRun = record.value && typeof record.value === 'object' ? record.value.run : null;
501
+ if (dispatchRun !== null && dispatchRun !== undefined && !DISPATCH_RUN_SETTLED.includes(dispatchRun?.state)) {
502
+ return answer('open', 'dispatch-run-open', resolvedRun.id, null);
503
+ }
504
+ }
505
+
287
506
  // W5: feature metadata is ordered; folder-name inference is the final fallback.
288
507
  let feature = stringField(run, 'featureSlug')
289
508
  || stringField(record.value, 'featureSlug')
@@ -323,4 +542,4 @@ function workStatus({
323
542
  return answer('finished', run ? 'run-finished' : 'no-run', resolvedRun.id, feature, since);
324
543
  }
325
544
 
326
- module.exports = { workStatus, createWorkCache, refreshedWorkCache, RUN_OPEN_STATES };
545
+ module.exports = { workStatus, recordExtension, createWorkCache, refreshedWorkCache, RUN_OPEN_STATES };