@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

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 (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  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 +4 -1
  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-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -93,11 +93,18 @@ function classify(bodyLines , kinds , canonicalPointer
93
93
  return { comments, moved, pointer };
94
94
  }
95
95
 
96
- function writeAtomic(fsImpl , file , content ) {
96
+ function writeAtomic(fsImpl , file , content , unchangedSince ) {
97
97
  fsImpl.mkdirSync(path.dirname(file), { recursive: true });
98
98
  const tmp = `${file}.${process.pid}.tmp`;
99
99
  try {
100
100
  fsImpl.writeFileSync(tmp, content, 'utf8');
101
+ // The text `content` was built from is compared with the file just before the rename, so an edit
102
+ // made since it was read is kept instead of replaced (FU-1jbmm3). This narrows the window to the
103
+ // gap between this read and the rename; it cannot close it, which would take a lock on the
104
+ // artifact itself that its editors would also have to take.
105
+ if (unchangedSince !== undefined && fsImpl.readFileSync(file, 'utf8') !== unchangedSince) {
106
+ throw new Error(`${path.basename(file)} changed while it was being compacted`);
107
+ }
101
108
  fsImpl.renameSync(tmp, file);
102
109
  } catch (error ) {
103
110
  try { fsImpl.rmSync(tmp, { force: true }); } catch { /* nothing to clean */ }
@@ -189,7 +196,7 @@ function compactArtifact({ fsImpl, featureDir, file, date }
189
196
  const body = [blank];
190
197
  for (const c of comments) body.push(...c, blank);
191
198
  body.push(pointerLine, end === lines.length ? '' : blank);
192
- writeAtomic(fsImpl, file, [...lines.slice(0, start + 1), ...body, ...lines.slice(end)].join('\n'));
199
+ writeAtomic(fsImpl, file, [...lines.slice(0, start + 1), ...body, ...lines.slice(end)].join('\n'), text);
193
200
  return { artifact: name, path: rel, archive: archiveRel, lines: moved.length };
194
201
  }
195
202
 
@@ -204,7 +211,10 @@ function compactArtifact({ fsImpl, featureDir, file, date }
204
211
  * @param {string} [options.date] YYYY-MM-DD stamped on the archive chunk; defaults to today (UTC)
205
212
  * @param {Object} [options.fsImpl] fs implementation (tests substitute one)
206
213
  *
207
- * Not locked: concurrent callers can interleave archive appends. Call it through
214
+ * Not locked: concurrent callers can interleave archive appends, and an artifact edited between its
215
+ * read and its rewrite is detected just before the rename and left as the editor wrote it (the
216
+ * artifact is reported in `failed`; the moved block is already in the archive and a later run does
217
+ * not copy it twice), but an edit landing in the last instant before the rename is not. Call it through
208
218
  * `compactDecisions` / `runDecision` in decision-register.js, which hold the register lock.
209
219
  *
210
220
  * Every artifact is attempted. One that cannot be compacted is left as it was and reported in
@@ -8,7 +8,7 @@ const { readReadinessRecord, newerVersionOf, RECORD_VERSION } = require('./readi
8
8
  const { listCheckouts } = require('./checkouts.js');
9
9
  const { readTaskState } = require('./task-state.js');
10
10
  const { resolveActiveFeature } = require('./feature-resolve.js');
11
- const { isSafeSlug } = require('./task-scope.js');
11
+ const { isSafeSlug, flatRecordIsOurs, readRegularFileText, MAX_RECORD_FILE_BYTES } = require('./task-scope.js');
12
12
  const { REPO_ROOT } = require('../helper/repo-root.js');
13
13
 
14
14
  // The implementation gate: which stage of a workflow needs a READY readiness record before it is
@@ -19,6 +19,15 @@ const { REPO_ROOT } = require('../helper/repo-root.js');
19
19
  // orchestrator, `verify` and the edit hook (which prints the same bytes) say the same thing.
20
20
 
21
21
  const GATE = 'doflow gate readiness-before-implementation';
22
+ const RUN_RECORD_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
23
+ const RUN_NAMESPACE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
24
+ /** Where a checkout keeps its run records, relative to the checkout; the one place the path is named. */
25
+ const RUN_STORE_REL = path.join('.doflow', 'state', 'orchestration');
26
+
27
+ /** The run store of the checkout at `root`. */
28
+ function runStoreDir(root ) {
29
+ return path.join(root, RUN_STORE_REL);
30
+ }
22
31
 
23
32
  /** What clears a record a newer DoFlow wrote; `readiness` here refuses to overwrite it. */
24
33
  const NEWER_NEXT = 'run this command with the DoFlow that wrote the record, or upgrade DoFlow here.';
@@ -55,13 +64,14 @@ function nextCommand({ taskId, slug, template } ) {
55
64
 
56
65
  /**
57
66
  * The refusal for one check code.
58
- * @param {'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'} code
67
+ * @param {'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'|'store-conflict'|'ambiguous-run'} code
59
68
  * @param {Object} options
60
69
  * @param {string} options.taskId
61
70
  * @param {string|null} [options.slug]
62
71
  * @param {string} options.template the template the gated stage needs
63
72
  * @param {Object|null} [options.record] the record read, for `not-ready` and `wrong-template`
64
- * @param {Array<string>} [options.candidates] the files found, for `ambiguous`
73
+ * @param {Array<string>} [options.candidates] the files found, for `ambiguous`; the worktree's then the
74
+ * main checkout's, for `store-conflict`
65
75
  * @param {string|null} [options.detail] why the record cannot be used, for `unusable`
66
76
  * @param {string|null} [options.file] the record's file, for `unusable` when a newer DoFlow wrote it
67
77
  * @param {number|null} [options.newerVersion] that newer record version; `readiness` would refuse to
@@ -86,6 +96,8 @@ function refusalText(code , { taskId, slug = null, template, record = null,
86
96
  return `${GATE}: task '${taskId}' has a readiness record that cannot be used (${detail}). ${tail}`;
87
97
  case 'ambiguous':
88
98
  return `${GATE}: task '${taskId}' has records in more than one other checkout (${candidates.join(', ')}). Next: run the command from the checkout that holds the one you mean, or run ${next} here. Nothing was changed.`;
99
+ case 'store-conflict':
100
+ return `${GATE}: task '${taskId}' has a readiness record in this worktree (${candidates[0]}) that differs from the main checkout's (${candidates[1]}). Next: keep one of the two and remove the other, then re-run. Nothing was changed.`;
89
101
  case 'ambiguous-run':
90
102
  // A readiness record written here cannot say which run is meant, so it is not offered.
91
103
  return `${GATE}: task '${taskId}' has runs in more than one other checkout (${candidates.join(', ')}). Next: run verify from the checkout that holds the run you mean. Nothing was changed.`;
@@ -95,8 +107,8 @@ function refusalText(code , { taskId, slug = null, template, record = null,
95
107
  }
96
108
 
97
109
  /**
98
- * Which harnesses run this gate before each source edit and which meet it first at handoff, from the
99
- * gate's own mapping in lifecycle.json (`pre-implementation-gate`): `supported`, or `different`
110
+ * Which harnesses run this gate's advisory hook before each source edit and which meet the runtime refusal first at
111
+ * handoff, from the gate's own mapping in lifecycle.json (`pre-implementation-gate`): `supported`, or `different`
100
112
  * with no prerequisite, runs it; a prerequisite (such as Codex's hook review) is named, since until
101
113
  * it is met nothing was checked; `unavailable` has no hook layer for it. Empty when the registry
102
114
  * cannot be read.
@@ -122,8 +134,8 @@ function harnessHookNote({ repoRoot = REPO_ROOT } = {}) {
122
134
  const parts = [];
123
135
  if (runs.length) parts.push(`${runs.join(', ')} ${runs.length === 1 ? 'runs' : 'run'} this check before each source edit`);
124
136
  for (const [key, ids] of gated) parts.push(`${ids.join(', ')} ${ids.length === 1 ? 'runs' : 'run'} it only after ${key}`);
125
- if (none.length) parts.push(`${none.join(', ')} ${none.length === 1 ? 'has' : 'have'} no hook layer, so this refusal is ${none.length === 1 ? 'its' : 'their'} first check`);
126
- return `Edit-time check: ${parts.join('; ')}.`;
137
+ if (none.length) parts.push(`${none.join(', ')} ${none.length === 1 ? 'has' : 'have'} no hook layer, so this refusal is ${none.length === 1 ? 'its' : 'their'} only check`);
138
+ return `Edit-time check: ${parts.join('; ')}. The hook only advises and never refuses an edit; this refusal is the runtime's.`;
127
139
  } catch {
128
140
  return '';
129
141
  }
@@ -168,35 +180,269 @@ function runSlugOf(run , root ) {
168
180
  return run && isSafeSlug(run.taskId) && isDir(folderOf(root, run.taskId)) ? run.taskId : null;
169
181
  }
170
182
 
183
+ /** Whether a store directory can be a run namespace rather than a lock directory. */
184
+ function isRunNamespaceDir(name ) {
185
+ return RUN_NAMESPACE_PATTERN.test(name) && !name.includes('..') && !name.endsWith('.lock');
186
+ }
187
+
188
+ /** The run record files under one checkout's store: flat `<id>.json` and `<slug>/<id>.json`, by
189
+ * path, without reading them. The one listing `findRunFile` and `orchestrationRuns` share.
190
+ * With `listing` (IC-001, the list action), a symbolic link is never followed: a linked run file is
191
+ * kept with `linked: true` so it can be reported as unreadable, and a linked namespace directory is
192
+ * not entered, so nothing outside the store root is read or named. */
193
+ function runFilesAt(root , { listing = false } = {}) {
194
+ const store = runStoreDir(root);
195
+ const files = [];
196
+ const take = (entry ) => entry.isFile() || entry.isSymbolicLink();
197
+ let entries ;
198
+ try { entries = fs.readdirSync(store, { withFileTypes: true }); } catch { return []; }
199
+ for (const entry of entries) {
200
+ if (take(entry) && RUN_RECORD_PATTERN.test(entry.name)) {
201
+ files.push({ file: path.join(store, entry.name), namespace: null, linked: entry.isSymbolicLink() });
202
+ } else if ((entry.isDirectory() || (!listing && entry.isSymbolicLink())) && isRunNamespaceDir(entry.name)) {
203
+ let nested ;
204
+ try { nested = fs.readdirSync(path.join(store, entry.name), { withFileTypes: true }); } catch { continue; }
205
+ for (const item of nested) {
206
+ if (take(item) && RUN_RECORD_PATTERN.test(item.name)) {
207
+ files.push({ file: path.join(store, entry.name, item.name), namespace: entry.name, linked: entry.isSymbolicLink() || item.isSymbolicLink() });
208
+ }
209
+ }
210
+ }
211
+ }
212
+ return files.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
213
+ }
214
+
215
+ /** The largest run file the list action and `readRunRecord` read; a bigger one is reported as `io` /
216
+ * refused (a journal is a few KiB). */
217
+ const MAX_RUN_FILE_BYTES = MAX_RECORD_FILE_BYTES;
218
+
219
+ /**
220
+ * One run's record, read the way the list action reads it (FU-16c2wz): no link is followed on the
221
+ * way from the store folder `storeDir` to the file or at the file, and the file must be a regular
222
+ * file of at most `MAX_RUN_FILE_BYTES`, so a FIFO or a device cannot block the read and a link
223
+ * cannot make it read outside the store. A run file that is not there is `null`; one that must not
224
+ * be read throws `store-unreadable` naming the file, and nothing of it is read.
225
+ * @param {Object} fsImpl
226
+ * @param {string} file the run file, under `storeDir`
227
+ * @param {string} storeDir the run store folder it belongs to
228
+ * @returns {Object|null} the parsed record
229
+ */
230
+ function readRunRecord(fsImpl , file , storeDir ) {
231
+ if (typeof fsImpl.lstatSync !== 'function' || typeof fsImpl.openSync !== 'function') return readTaskState(fsImpl, file);
232
+ const text = readRunText(fsImpl, file, storeDir);
233
+ return text === null ? null : readTaskState({ existsSync: () => true, readFileSync: () => text }, file);
234
+ }
235
+
236
+ /** `readRunRecord`'s guarded read without the parse: the run file's text, `null` when it is not
237
+ * there, `store-unreadable` when it must not be read. */
238
+ function readRunText(fsImpl , file , storeDir ) {
239
+ const relative = path.relative(storeDir, file);
240
+ const parts = relative === '' || relative.startsWith('..') || path.isAbsolute(relative) ? [] : relative.split(path.sep);
241
+ const refuse = () => {
242
+ const failure = new Error(`Run file '${file}' is not a regular file inside the run store (a link, FIFO, device or folder, or larger than ${MAX_RUN_FILE_BYTES} bytes). Nothing was read.`);
243
+ failure.code = 'store-unreadable';
244
+ throw failure;
245
+ };
246
+ let at = storeDir;
247
+ for (const part of parts.slice(0, -1)) {
248
+ at = path.join(at, part);
249
+ let stat ;
250
+ try { stat = fsImpl.lstatSync(at); } catch (error ) {
251
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null;
252
+ throw error;
253
+ }
254
+ if (stat.isSymbolicLink()) refuse();
255
+ }
256
+ let stat ;
257
+ try { stat = fsImpl.lstatSync(file); } catch (error ) {
258
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null;
259
+ throw error;
260
+ }
261
+ if (!stat.isFile()) refuse();
262
+ const text = readRegularFileText(file, fsImpl);
263
+ if (text === null) refuse();
264
+ return text;
265
+ }
266
+
267
+ /** The run files under one checkout's store, read, with each malformed or unreadable file isolated. */
268
+ function runEntriesAt(root , origin ) {
269
+ return runFilesAt(root, { listing: true }).map(({ file, namespace, linked }) => {
270
+ let record = null;
271
+ let error = null;
272
+ const bytes = linked ? null : readRegularFileText(file);
273
+ if (bytes === null) error = 'io';
274
+ else {
275
+ try { record = JSON.parse(bytes); } catch { error = 'unparseable'; }
276
+ }
277
+ return {
278
+ file,
279
+ root,
280
+ origin,
281
+ slug: namespace || runSlugOf(record, root),
282
+ record,
283
+ error,
284
+ };
285
+ });
286
+ }
287
+
288
+ /**
289
+ * All orchestration journals in the current store and the other checkouts, when Git can list them.
290
+ * @param {{stateRoot: string, exec?: Function}} options
291
+ * @returns {{ok: true, entries: Array<Object>}|{ok: false, error: 'cannot-list-checkouts'}}
292
+ */
293
+ function orchestrationRuns({ stateRoot, exec } ) {
294
+ const current = runEntriesAt(stateRoot, 'current');
295
+ const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
296
+ if (!checkouts.ok && checkouts.reason !== 'not-a-git-repository') {
297
+ return { ok: false, error: 'cannot-list-checkouts' };
298
+ }
299
+ if (!checkouts.ok || checkouts.sandbox) return { ok: true, entries: current };
300
+
301
+ const entries = [...current];
302
+ for (const root of checkouts.others) entries.push(...runEntriesAt(root, 'other'));
303
+ return { ok: true, entries };
304
+ }
305
+
306
+ /** Whether a flat record names no feature (no `featureSlug`, no `slug`): such a run is any feature's
307
+ * (`flatRecordIsOurs`), so a namespaced copy of the same id in the same store shadows it. */
308
+ function namesNoFeature(record ) {
309
+ return Boolean(record) && typeof record === 'object'
310
+ && ![record.featureSlug, record.slug].some((value) => typeof value === 'string' && value !== '');
311
+ }
312
+
313
+ /**
314
+ * IC-001/IC-003: one entry per logical run, the copy `status` reads, with `findRunFile`'s
315
+ * precedence. In one store, the namespaced `<slug>/<id>.json` wins over a flat `<id>.json` that
316
+ * names that feature (when the slug is the id, the flat file is the run's only path and wins).
317
+ * A flat file that names no feature, or that cannot be read or is a link, is keyed as `findRunFile`
318
+ * would first read it, the id as its slug, and stays its own entry. Across stores, the current
319
+ * store's copy of a key wins over every other checkout's; a current flat file `status` reads for
320
+ * any feature without a namespaced copy here (one naming no feature, or a link, which status refuses)
321
+ * also shadows the other checkouts' copies of that id. With no current copy, each other checkout
322
+ * that holds one keeps its entry (more than one is the case `status` refuses as ambiguous). A
323
+ * losing copy is dropped: `status` never reads it for this run.
324
+ * @param {Array<Object>} entries `orchestrationRuns(...).entries`
325
+ * @returns {Array<Object>} the winning entries, in input order
326
+ */
327
+ function selectRunEntries(entries ) {
328
+ const stem = (entry ) => path.basename(entry.file, '.json');
329
+ const namespaceOf = (entry ) => {
330
+ const parts = path.relative(runStoreDir(entry.root), entry.file).split(path.sep);
331
+ return parts.length > 1 ? parts[0] : null;
332
+ };
333
+ const named = (entry ) => entry.record && !namesNoFeature(entry.record) && !entry.error;
334
+ const linked = (entry ) => {
335
+ try { return fs.lstatSync(entry.file).isSymbolicLink(); } catch { return false; }
336
+ };
337
+ const keyOf = (entry ) => {
338
+ const ns = namespaceOf(entry);
339
+ if (ns !== null) return `${ns}\0${stem(entry)}`;
340
+ if (named(entry) && entry.slug) return `${entry.slug}\0${stem(entry)}`;
341
+ return `${stem(entry)}\0${stem(entry)}`;
342
+ };
343
+ const rank = (entry ) => {
344
+ const ns = namespaceOf(entry);
345
+ if (ns === null) return named(entry) && entry.slug !== stem(entry) ? 1 : 0;
346
+ return ns === stem(entry) ? 2 : 0;
347
+ };
348
+ // Ids a current flat file answers for any feature: status reads it before any other checkout.
349
+ const wildcards = new Set(entries.filter((entry) => entry.origin === 'current' && namespaceOf(entry) === null
350
+ && ((entry.record && namesNoFeature(entry.record)) || linked(entry))).map(stem));
351
+ const perRoot = new Map ();
352
+ for (const entry of entries) {
353
+ const local = `${entry.root}\0${keyOf(entry)}`;
354
+ const held = perRoot.get(local);
355
+ if (!held || rank(entry) < rank(held)) perRoot.set(local, entry);
356
+ }
357
+ const best = new Map ();
358
+ for (const entry of perRoot.values()) {
359
+ const key = keyOf(entry);
360
+ const held = best.get(key);
361
+ if (!held) best.set(key, [entry]);
362
+ else if (held[0].origin === 'current') continue;
363
+ else if (entry.origin === 'current') best.set(key, [entry]);
364
+ else held.push(entry);
365
+ }
366
+ const winners = new Set ();
367
+ for (const list of best.values()) {
368
+ for (const entry of list) {
369
+ if (entry.origin !== 'current' && wildcards.has(stem(entry))) continue;
370
+ winners.add(entry);
371
+ }
372
+ }
373
+ return entries.filter((entry) => winners.has(entry));
374
+ }
375
+
171
376
  /**
172
377
  * A task's run file: in this checkout, else in exactly one other checkout whose run belongs to the
173
- * same feature. Task ids repeat across features, so another checkout's run with the same id but
174
- * another feature (or none) is not this task's and is never read or updated from here.
378
+ * same feature. The run of a task id other than its feature's slug is `orchestration/<slug>/<id>.json`
379
+ * (115 C-1); a flat run of that id is read in its place only when it is this feature's or names none
380
+ * (D-3). Task ids repeat across features, so another checkout's run with the same id but another
381
+ * feature (or none) is not this task's and is never read or updated from here.
175
382
  * @param {{stateRoot: string, taskId: string, slug: string|null, exec?: Function}} options
176
383
  * @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
177
384
  * origin: 'current'|'other'|null, candidates: Array<string>}}
178
385
  */
179
- function findRunFile({ stateRoot, taskId, slug, exec } ) {
180
- const rel = path.join('.doflow', 'state', 'orchestration', `${taskId}.json`);
386
+ function findRunFile({ stateRoot, taskId, slug, exec, anyNamespace = false } ) {
387
+ const found = findRunFileFor({ stateRoot, taskId, slug, exec });
388
+ return anyNamespace && found.status === 'missing' ? findNamespacedRun({ stateRoot, taskId, exec }) : found;
389
+ }
390
+
391
+ /**
392
+ * FU-wrnnaq: the run of `taskId` under whichever feature namespace holds it, for a caller that has
393
+ * no feature to name (`status` run from another branch). This is the run the list action shows for
394
+ * the id: this checkout's namespaced run first, else the other checkouts'; exactly one is the run,
395
+ * more than one is `ambiguous` (the feature must then be named), none is `missing`. `slug` is the
396
+ * namespace the run is in, for the caller to read it by.
397
+ * @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
398
+ * origin: 'current'|'other'|null, candidates: Array<string>, slug?: string, byFeature?: true}}
399
+ */
400
+ function findNamespacedRun({ stateRoot, taskId, exec } ) {
401
+ const name = `${taskId}.json`;
181
402
  const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] };
182
- if (fs.existsSync(path.join(stateRoot, rel))) {
183
- return { status: 'found', file: path.join(stateRoot, rel), root: stateRoot, origin: 'current', candidates: [] };
403
+ const inRoot = (root ) => runFilesAt(root).filter((entry) => entry.namespace !== null && path.basename(entry.file) === name);
404
+ const settle = (entries , root , origin ) => (
405
+ entries.length === 1
406
+ ? { status: 'found', file: entries[0].file, root, origin, candidates: [], slug: entries[0].namespace }
407
+ : { ...missing, status: 'ambiguous', byFeature: true, candidates: entries.map((entry) => entry.file) });
408
+ const here = inRoot(stateRoot);
409
+ if (here.length > 0) return settle(here, stateRoot, 'current');
410
+ const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
411
+ if (!checkouts.ok || checkouts.sandbox) return missing;
412
+ const matches = checkouts.others.flatMap((root ) => inRoot(root).map((entry) => ({ ...entry, root })));
413
+ if (matches.length === 0) return missing;
414
+ return settle(matches, matches[0].root, 'other');
415
+ }
416
+
417
+ function findRunFileFor({ stateRoot, taskId, slug, exec } ) {
418
+ const store = RUN_STORE_REL;
419
+ const flat = path.join(store, `${taskId}.json`);
420
+ const namespaced = slug && slug !== taskId ? path.join(store, slug, `${taskId}.json`) : null;
421
+ const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] };
422
+ const here = runFilesAt(stateRoot);
423
+ const localNamespaced = namespaced && here.find((entry) => path.relative(stateRoot, entry.file) === namespaced);
424
+ const localFlat = here.find((entry) => path.relative(stateRoot, entry.file) === flat);
425
+ if (localNamespaced) return { status: 'found', file: localNamespaced.file, root: stateRoot, origin: 'current', candidates: [] };
426
+ if (localFlat && (!namespaced || flatRecordIsOurs(localFlat.file, slug, fs, true))) {
427
+ return { status: 'found', file: localFlat.file, root: stateRoot, origin: 'current', candidates: [] };
184
428
  }
185
429
  if (!slug) return missing;
186
430
  const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
187
431
  if (!checkouts.ok || checkouts.sandbox) return missing;
188
- const matches = checkouts.others.filter((root ) => {
189
- const file = path.join(root, rel);
190
- if (!fs.existsSync(file)) return false;
191
- try {
192
- return runSlugOf(JSON.parse(fs.readFileSync(file, 'utf8')), root) === slug;
193
- } catch {
194
- return false;
195
- }
196
- });
432
+ const matches = [];
433
+ for (const root of checkouts.others) {
434
+ const files = runFilesAt(root);
435
+ const found = [namespaced, flat].filter(Boolean).map((rel ) => files.find((entry) => path.relative(root, entry.file) === rel)).find((entry ) => {
436
+ if (!entry) return false;
437
+ // The same safe read as every other run read: a linked run or namespace folder, a FIFO or a
438
+ // device in another checkout is never opened, and is not a match.
439
+ try { return runSlugOf(JSON.parse(readRunText(fs, entry.file, runStoreDir(root)) ), root) === slug; } catch { return false; }
440
+ });
441
+ if (found) matches.push({ file: found.file, root });
442
+ }
197
443
  if (matches.length === 0) return missing;
198
- if (matches.length > 1) return { ...missing, status: 'ambiguous', candidates: matches.map((root ) => path.join(root, rel)) };
199
- return { status: 'found', file: path.join(matches[0], rel), root: matches[0], origin: 'other', candidates: [] };
444
+ if (matches.length > 1) return { ...missing, status: 'ambiguous', candidates: matches.map((m) => m.file) };
445
+ return { status: 'found', file: matches[0].file, root: matches[0].root, origin: 'other', candidates: [] };
200
446
  }
201
447
 
202
448
  /** The slug a refusal's next command names: one whose feature folder exists in this checkout or in
@@ -231,7 +477,7 @@ function unusableDetail(record , now ) {
231
477
  * @param {string} options.template
232
478
  * @param {Date} [options.now]
233
479
  * @param {Function} [options.exec]
234
- * @returns {{ok: boolean, code: 'ready'|'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous',
480
+ * @returns {{ok: boolean, code: 'ready'|'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'|'store-conflict',
235
481
  * record: Object|null, file: string|null, origin: string|null, candidates: Array<string>,
236
482
  * detail: string|null, nextSlug: string|null, message: string|null}}
237
483
  */
@@ -254,6 +500,7 @@ function checkReadiness({ stateRoot, taskId, slug = null, template, now = new Da
254
500
  }),
255
501
  });
256
502
  if (read.status === 'ambiguous') return result('ambiguous');
503
+ if (read.status === 'conflict') return result('store-conflict');
257
504
  if (read.status === 'missing') return result('missing');
258
505
  const detail = read.status === 'unreadable' ? read.detail : unusableDetail(read.record, now);
259
506
  if (detail) return result('unusable', detail);
@@ -284,7 +531,9 @@ function verifyReadinessCheck({ stateRoot, taskId, slug = null, now = new Date()
284
531
  };
285
532
  }
286
533
  if (found.status === 'missing') return { applies: false, reason: 'no-run' };
287
- const run = readTaskState(fs, found.file);
534
+ // Reads only through the run store's safe reader; a run file that must not be read is refused by name.
535
+ const run = readRunRecord(fs, found.file, runStoreDir(found.root));
536
+ if (!run) return { applies: false, reason: 'no-run' };
288
537
  if (run.state === 'COMPLETED' || run.state === 'REJECTED') return { applies: false, reason: 'run-finished' };
289
538
  const stageNodes = (run.program || []).filter((n ) => n.type === 'stage');
290
539
  const stage = gatedStage(stageNodes);
@@ -309,6 +558,7 @@ function verifyReadinessCheck({ stateRoot, taskId, slug = null, now = new Date()
309
558
  }
310
559
 
311
560
  module.exports = {
561
+ unusableDetail,
312
562
  gatedStage,
313
563
  heldClasses,
314
564
  refusalText,
@@ -317,5 +567,10 @@ module.exports = {
317
567
  verifyReadinessCheck,
318
568
  featureSlugFor,
319
569
  runSlugOf,
570
+ orchestrationRuns,
571
+ selectRunEntries,
320
572
  findRunFile,
573
+ RUN_STORE_REL,
574
+ runStoreDir,
575
+ readRunRecord,
321
576
  };