@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
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
8
+ const { execFileSync } = require('node:child_process');
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ const treeStateMemo = new Map ();
26
+
27
+ function clearTreeStateCache() {
28
+ treeStateMemo.clear();
29
+ }
30
+
31
+ /**
32
+ * Finds the top of the git work tree and root's relative prefix below it using filesystem reads only.
33
+ */
34
+ function findWorkTreeTop(root ) {
35
+ let current = path.resolve(root);
36
+ while (true) {
37
+ try {
38
+ if (fs.existsSync(path.join(current, '.git'))) {
39
+ const rel = path.relative(current, path.resolve(root));
40
+ const prefix = rel && rel !== '.' ? (rel.split(path.sep).join('/') + '/') : '';
41
+ return { top: current, prefix };
42
+ }
43
+ } catch {
44
+ break;
45
+ }
46
+ const parent = path.dirname(current);
47
+ if (parent === current) break;
48
+ current = parent;
49
+ }
50
+ return { top: null, prefix: null };
51
+ }
52
+
53
+ function defaultGitRunner(args , cwd ) {
54
+ try {
55
+ return execFileSync('git', args, {
56
+ cwd,
57
+ encoding: 'utf8',
58
+ timeout: 2000,
59
+ maxBuffer: 256 * 1024 * 1024,
60
+ stdio: ['ignore', 'pipe', 'ignore'],
61
+ });
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Measures the repository's HEAD commit, dirty paths and tree fingerprint with one git status call.
69
+ */
70
+ function measureTreeState(root , runner ) {
71
+ const absRoot = path.resolve(root);
72
+ if (!runner && treeStateMemo.has(absRoot)) {
73
+ return treeStateMemo.get(absRoot) ;
74
+ }
75
+
76
+ const { top: workTreeTop, prefix } = findWorkTreeTop(absRoot);
77
+ if (!workTreeTop) {
78
+ const unmeasured = {
79
+ gitCommit: null,
80
+ dirty: null,
81
+ prefix: null,
82
+ fingerprint: null,
83
+ };
84
+ if (!runner) treeStateMemo.set(absRoot, unmeasured);
85
+ return unmeasured;
86
+ }
87
+
88
+ const args = ['-C', absRoot, 'status', '--porcelain=v2', '--branch', '-z', '--untracked-files=all', '--ignored=matching'];
89
+ const raw = runner ? runner(args, absRoot) : defaultGitRunner(args, absRoot);
90
+
91
+ if (raw === null || raw === undefined) {
92
+ const commit = runner
93
+ ? runner(['-C', absRoot, 'rev-parse', 'HEAD'], absRoot)
94
+ : defaultGitRunner(['-C', absRoot, 'rev-parse', 'HEAD'], absRoot);
95
+ const failed = {
96
+ gitCommit: commit?.trim() || null,
97
+ dirty: null,
98
+ prefix,
99
+ fingerprint: null,
100
+ };
101
+ if (!runner) treeStateMemo.set(absRoot, failed);
102
+ return failed;
103
+ }
104
+
105
+ const tokens = raw.split('\0');
106
+ let gitCommit = null;
107
+ const dirty = new Set ();
108
+ const entryRecords = [];
109
+
110
+ for (let i = 0; i < tokens.length; i++) {
111
+ const token = tokens[i];
112
+ if (!token) continue;
113
+
114
+ if (token.startsWith('# branch.oid ')) {
115
+ const oid = token.slice('# branch.oid '.length).trim();
116
+ if (oid && oid !== '(initial)') {
117
+ gitCommit = oid;
118
+ }
119
+ continue;
120
+ }
121
+ if (token.startsWith('# ')) {
122
+ continue;
123
+ }
124
+
125
+ if (token.startsWith('! ')) {
126
+ const ignoredPath = token.slice(2);
127
+ if (prefix && (ignoredPath === prefix.slice(0, -1) || ignoredPath === `${prefix.slice(0, -1)}/`)) {
128
+ const ignored = { gitCommit, dirty: null, prefix, fingerprint: null };
129
+ if (!runner) treeStateMemo.set(absRoot, ignored);
130
+ return ignored;
131
+ }
132
+ continue;
133
+ }
134
+
135
+ let dirtyPath = null;
136
+ let entryText = token;
137
+
138
+ if (token.startsWith('1 ')) {
139
+ const parts = token.split(' ');
140
+ dirtyPath = parts.slice(8).join(' ');
141
+ dirty.add(dirtyPath);
142
+ } else if (token.startsWith('2 ')) {
143
+ const parts = token.split(' ');
144
+ dirtyPath = parts.slice(9).join(' ');
145
+ dirty.add(dirtyPath);
146
+ if (i + 1 < tokens.length && tokens[i + 1]) {
147
+ const origPath = tokens[i + 1];
148
+ dirty.add(origPath);
149
+ entryText = `${token}\0${origPath}`;
150
+ i++;
151
+ }
152
+ } else if (token.startsWith('u ')) {
153
+ const parts = token.split(' ');
154
+ dirtyPath = parts.slice(10).join(' ');
155
+ dirty.add(dirtyPath);
156
+ } else if (token.startsWith('? ')) {
157
+ dirtyPath = token.slice(2);
158
+ dirty.add(dirtyPath);
159
+ }
160
+
161
+ if (dirtyPath !== null) {
162
+ const statePath = `${prefix}.doflow/state/`;
163
+ const normalizedDirtyPath = dirtyPath.split(path.sep).join('/');
164
+ const shouldFingerprint = !normalizedDirtyPath.startsWith(statePath);
165
+ if (shouldFingerprint && /^(?:1|2|u) \S+ S\.M /.test(token)) {
166
+ const nestedRoot = path.resolve(workTreeTop, dirtyPath);
167
+ const nested = measureNestedRepository(nestedRoot);
168
+ if (nested === null) {
169
+ const unknown = { gitCommit, dirty: null, prefix, fingerprint: null };
170
+ if (!runner) treeStateMemo.set(absRoot, unknown);
171
+ return unknown;
172
+ }
173
+ entryRecords.push(`${entryText}:nested:${nested}`);
174
+ continue;
175
+ }
176
+ if (shouldFingerprint && token.startsWith('? ')) {
177
+ const nestedRoot = path.resolve(workTreeTop, dirtyPath.replace(/\/$/, ''));
178
+ if (fs.existsSync(path.join(nestedRoot, '.git'))) {
179
+ const nested = measureNestedRepository(nestedRoot);
180
+ if (nested === null) {
181
+ const unknown = { gitCommit, dirty: null, prefix, fingerprint: null };
182
+ if (!runner) treeStateMemo.set(absRoot, unknown);
183
+ return unknown;
184
+ }
185
+ entryRecords.push(`${entryText}:nested:${nested}`);
186
+ continue;
187
+ }
188
+ }
189
+ if (!shouldFingerprint) continue;
190
+ let statTerm = 'absent';
191
+ try {
192
+ const absPath = path.resolve(workTreeTop, dirtyPath);
193
+ const st = fs.statSync(absPath);
194
+ statTerm = `${st.size}:${st.mtimeMs}`;
195
+ } catch {
196
+ statTerm = 'absent';
197
+ }
198
+ entryRecords.push(`${entryText}:${statTerm}`);
199
+ }
200
+ }
201
+
202
+ if (!gitCommit) {
203
+ const uncommitted = {
204
+ gitCommit: null,
205
+ dirty: null,
206
+ prefix,
207
+ fingerprint: null,
208
+ };
209
+ if (!runner) treeStateMemo.set(absRoot, uncommitted);
210
+ return uncommitted;
211
+ }
212
+
213
+ const hash = crypto.createHash('sha256');
214
+ hash.update(gitCommit);
215
+ hash.update('\0');
216
+ hash.update(entryRecords.sort().join('\0'));
217
+ const fingerprint = `sha256:${hash.digest('hex')}`;
218
+
219
+ const state = {
220
+ gitCommit,
221
+ dirty,
222
+ prefix,
223
+ fingerprint,
224
+ };
225
+ if (!runner) treeStateMemo.set(absRoot, state);
226
+ return state;
227
+ }
228
+
229
+ function measureNestedRepository(root ) {
230
+ try {
231
+ const gitEntry = path.join(root, '.git');
232
+ const gitStat = fs.statSync(gitEntry);
233
+ const gitDir = gitStat.isFile()
234
+ ? path.resolve(root, fs.readFileSync(gitEntry, 'utf8').trim().replace(/^gitdir:\s*/, ''))
235
+ : gitEntry;
236
+ const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
237
+ const headRef = head.startsWith('ref: ')
238
+ ? fs.readFileSync(path.join(gitDir, head.slice('ref: '.length)), 'utf8').trim()
239
+ : head;
240
+ const index = fs.statSync(path.join(gitDir, 'index'));
241
+ return `${head}:${headRef}:${index.mtimeMs}:${index.size}`;
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+
247
+ function fileHash(root , locator ) {
248
+ if (!locator?.file) return null;
249
+ try {
250
+ const absolute = path.resolve(root, locator.file);
251
+ if (!fs.statSync(absolute).isFile()) return null;
252
+ return `sha256:${crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex')}`;
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Derives source version { gitCommit, fileHash, isDirty } for a given locator and tree state.
260
+ */
261
+ function sourceVersionFor(root , locator , state ) {
262
+ const hash = fileHash(root, locator);
263
+ if (!locator?.file || !state || !state.dirty) {
264
+ return {
265
+ gitCommit: state?.gitCommit ?? null,
266
+ fileHash: hash,
267
+ isDirty: null,
268
+ };
269
+ }
270
+
271
+ const prefix = state.prefix || '';
272
+ if (path.isAbsolute(locator.file)) {
273
+ return { gitCommit: state.gitCommit ?? null, fileHash: hash, isDirty: null };
274
+ }
275
+ const candidate = path.posix.normalize(`${prefix}${locator.file.split(path.sep).join('/')}`).replace(/^\.\//, '');
276
+ const isDirty = state.dirty.has(candidate);
277
+
278
+ return {
279
+ gitCommit: state.gitCommit ?? null,
280
+ fileHash: hash,
281
+ isDirty,
282
+ };
283
+ }
284
+
285
+ module.exports = {
286
+ findWorkTreeTop,
287
+ measureTreeState,
288
+ sourceVersionFor,
289
+ clearTreeStateCache,
290
+ };
@@ -0,0 +1,217 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { acquireLock } = require('./task-state.js');
9
+ const { loadStorageRegistry } = require('./storage/registry.js');
10
+ const { REPO_ROOT } = require('../helper/repo-root.js');
11
+
12
+ // Where a task's records live (115 IC-004) and how records a linked worktree wrote before that rule
13
+ // reach the main checkout (IC-006): one locked sweep per redirected call.
14
+ //
15
+ // A linked, non-sandbox worktree reads and writes every per-task record under the main checkout's
16
+ // `.doflow/state`, whatever folders the worktree holds: a conditional rule flipped mid-run when
17
+ // do-brainstorm created the feature folder in the worktree and split one task's records across two
18
+ // stores. A DoFlow sandbox (`.doflow-worktree-base`) keeps its own store, and `--global` is $HOME.
19
+
20
+ /** This module's path as `core/registry/storage.json` names it in a store's `writers`. */
21
+ const ADOPTER = 'src/runtime/state-root.js';
22
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
23
+
24
+ /**
25
+ * IC-004: the root a task-store verb or `orchestrate` reads and writes its records in.
26
+ * @param {Object} o parsed CLI options (`global`, `positional`)
27
+ * @param {Object|null} checkouts a `listCheckouts` result, or null when none was listed
28
+ * @returns {{root: string, redirected: boolean}}
29
+ */
30
+ function taskStateRoot(o , checkouts ) {
31
+ if (!o.global && checkouts?.ok && checkouts.isLinked && !checkouts.sandbox && checkouts.main) {
32
+ return { root: checkouts.main, redirected: true };
33
+ }
34
+ return { root: o.global ? os.homedir() : path.resolve(o.positional?.[0] || '.'), redirected: false };
35
+ }
36
+
37
+ /**
38
+ * The project-state stores a worktree's records are adopted from: the ones that declare this module
39
+ * among their `writers` in `core/registry/storage.json`, so the list is the registry's, not a copy.
40
+ * @param {Object} [registry] a loaded storage registry (default: the shipped one)
41
+ * @returns {Array<{id: string, path: string, match: RegExp}>}
42
+ */
43
+ function adoptedStores(registry = loadStorageRegistry({ repoRoot: REPO_ROOT })) {
44
+ return registry.stores
45
+ .filter((store ) => store.root === 'project-state' && Array.isArray(store.writers) && store.writers.includes(ADOPTER))
46
+ .map((store ) => ({ id: store.id, path: store.path, match: new RegExp(store.match) }));
47
+ }
48
+
49
+ /** Whether `name` is a namespace directory of a store (a feature slug), not a `<file>.lock` directory. */
50
+ function isNamespaceDir(name ) {
51
+ return SAFE_ID.test(name) && !name.includes('..') && !name.endsWith('.lock');
52
+ }
53
+
54
+ /** The record files of one store directory of the worktree, by the store's exact `match`: flat files
55
+ * and the files of each `<slug>/` directory, as paths relative to the store. */
56
+ function storeFiles(dir , match , fsImpl ) {
57
+ const out = [];
58
+ let entries ;
59
+ try { entries = fsImpl.readdirSync(dir, { withFileTypes: true }); } catch { return out; }
60
+ for (const entry of entries) {
61
+ // A link is listed as it stands, file or directory, so the sweep refuses it instead of skipping it.
62
+ if (entry.isSymbolicLink() && (match.test(entry.name) || isNamespaceDir(entry.name))) out.push(entry.name);
63
+ else if (entry.isFile() && match.test(entry.name)) out.push(entry.name);
64
+ else if (entry.isDirectory() && isNamespaceDir(entry.name)) {
65
+ let inner ;
66
+ try { inner = fsImpl.readdirSync(path.join(dir, entry.name), { withFileTypes: true }); } catch { continue; }
67
+ for (const file of inner) if ((file.isFile() || file.isSymbolicLink()) && match.test(file.name)) out.push(path.join(entry.name, file.name));
68
+ }
69
+ }
70
+ return out.sort();
71
+ }
72
+
73
+ /** The first link among `root`'s own path components down to `file` (the root itself is the
74
+ * checkout and is not owned here), or null. A component that does not exist yet is not a link. */
75
+ function linkOnTheWay(root , file , fsImpl ) {
76
+ let at = root;
77
+ for (const part of path.relative(root, file).split(path.sep)) {
78
+ at = path.join(at, part);
79
+ let stat ;
80
+ try { stat = fsImpl.lstatSync(at); } catch { return null; }
81
+ if (stat.isSymbolicLink()) return at;
82
+ }
83
+ return null;
84
+ }
85
+
86
+ /** The task id a flat record file names: `<taskId>.json` or the claims file `<taskId>_claims.json`. */
87
+ function taskIdOf(name ) {
88
+ const base = name.slice(0, -'.json'.length);
89
+ return base.endsWith('_claims') ? base.slice(0, -'_claims'.length) : base;
90
+ }
91
+
92
+ /** The feature a flat record names (`featureSlug` on a run, `slug` on a readiness record), or null. */
93
+ function ownSlugOf(bytes ) {
94
+ try {
95
+ const data = JSON.parse(bytes.toString('utf8'));
96
+ const named = [data?.featureSlug, data?.slug].find((v) => typeof v === 'string' && SAFE_ID.test(v) && !v.includes('..'));
97
+ return named || null;
98
+ } catch { return null; }
99
+ }
100
+
101
+ /** Writes `bytes` to `to` through a temporary file beside it and a rename, so a reader never sees a
102
+ * partial record. The one write path for a record copied between stores or checkouts. */
103
+ function placeRecord(to , bytes , fsImpl = fs) {
104
+ const tmp = `${to}.${process.pid}.tmp`;
105
+ fsImpl.writeFileSync(tmp, bytes);
106
+ fsImpl.renameSync(tmp, to);
107
+ }
108
+
109
+ function realOrNull(p , fsImpl ) {
110
+ try { return fsImpl.realpathSync(p); } catch { return null; }
111
+ }
112
+
113
+ /**
114
+ * IC-006 (115 C-3, D-1, D-4): moves every record a linked worktree holds in its own `.doflow/state`
115
+ * to the main checkout's, before the verb reads any of them. One sweep per redirected call, over
116
+ * every adopted store, whatever task the call names.
117
+ *
118
+ * Nothing is swept when the two state directories are one directory (a worktree whose
119
+ * `.doflow/state` links to the main checkout's). Per file: a link on either side's own path, or a
120
+ * source and target that are one file (`dev:ino`), refuses that file; otherwise the target's lock and
121
+ * then the source's are taken, the source is re-read under them (gone means another sweep moved it),
122
+ * and it goes to the same relative path, except that a flat record of a task id other than its
123
+ * feature's slug goes under `<store>/<slug>/` (the slug it names, else the call's `recordSlug`, so it
124
+ * lands where every reader looks). Equal bytes at the target delete the source; no target means a
125
+ * copy to a temporary file, a rename into place, a re-read compared with the source, and only then
126
+ * the source's deletion; other bytes leave both and are reported. Files already moved stay moved.
127
+ * @param {Object} options
128
+ * @param {string} options.mainRoot the main checkout
129
+ * @param {string} options.worktreeRoot the linked worktree the verb runs in
130
+ * @param {string|null|(() => string|null)} [options.recordSlug] the call's feature slug, asked for only
131
+ * when a flat record names no feature of its own
132
+ * @param {Object} [options.registry] storage registry (default: the shipped one)
133
+ * @param {Object} [options.fsImpl]
134
+ * @returns {{ok: boolean, adopted: string[], removed: string[], error?: 'store-conflict',
135
+ * conflicts?: Array<{worktree: string, main: string, reason: 'differs'|'link'|'same-file'}>}}
136
+ */
137
+ function sweepWorktreeRecords({ mainRoot, worktreeRoot, recordSlug = null, registry, fsImpl = fs } ) {
138
+ const done = { ok: true, adopted: [] , removed: [] };
139
+ const conflicts = [];
140
+ const fromState = path.join(worktreeRoot, '.doflow', 'state');
141
+ const toState = path.join(mainRoot, '.doflow', 'state');
142
+ const fromReal = realOrNull(fromState, fsImpl);
143
+ if (fromReal === null || fromReal === realOrNull(toState, fsImpl)) return done;
144
+ let callSlug ;
145
+ const slugOfCall = () => {
146
+ if (callSlug === undefined) {
147
+ let value = null;
148
+ try { value = typeof recordSlug === 'function' ? recordSlug() : recordSlug; } catch { /* no feature */ }
149
+ callSlug = typeof value === 'string' && SAFE_ID.test(value) && !value.includes('..') ? value : null;
150
+ }
151
+ return callSlug;
152
+ };
153
+
154
+ for (const store of adoptedStores(registry)) {
155
+ const fromDir = path.join(fromState, store.path);
156
+ let dirStat ;
157
+ try { dirStat = fsImpl.lstatSync(fromDir); } catch { continue; }
158
+ if (dirStat.isSymbolicLink()) {
159
+ conflicts.push({ worktree: fromDir, main: path.join(toState, store.path), reason: 'link' });
160
+ continue;
161
+ }
162
+ if (!dirStat.isDirectory()) continue;
163
+ for (const rel of storeFiles(fromDir, store.match, fsImpl)) {
164
+ const from = path.join(fromDir, rel);
165
+ if (linkOnTheWay(worktreeRoot, from, fsImpl)) {
166
+ conflicts.push({ worktree: from, main: path.join(toState, store.path, rel), reason: 'link' });
167
+ continue;
168
+ }
169
+ let toRel = rel;
170
+ if (!rel.includes(path.sep)) {
171
+ let bytes = null;
172
+ try { bytes = fsImpl.readFileSync(from); } catch { continue; }
173
+ const slug = ownSlugOf(bytes ) || slugOfCall();
174
+ if (slug && slug !== taskIdOf(rel)) toRel = path.join(slug, rel);
175
+ }
176
+ const to = path.join(toState, store.path, toRel);
177
+ if (linkOnTheWay(mainRoot, to, fsImpl)) { conflicts.push({ worktree: from, main: to, reason: 'link' }); continue; }
178
+ try {
179
+ const a = fsImpl.lstatSync(from);
180
+ const b = fsImpl.lstatSync(to);
181
+ if (a.dev === b.dev && a.ino === b.ino) { conflicts.push({ worktree: from, main: to, reason: 'same-file' }); continue; }
182
+ } catch { /* one side is absent: not one file */ }
183
+
184
+ fsImpl.mkdirSync(path.dirname(to), { recursive: true });
185
+ const releaseTarget = acquireLock(fsImpl, to);
186
+ try {
187
+ const releaseSource = acquireLock(fsImpl, from);
188
+ try {
189
+ let bytes ;
190
+ try { bytes = fsImpl.readFileSync(from); } catch (error ) {
191
+ if (error.code === 'ENOENT') continue; // another sweep moved it
192
+ throw error;
193
+ }
194
+ let there = null;
195
+ try { there = fsImpl.readFileSync(to); } catch (error ) { if (error.code !== 'ENOENT') throw error; }
196
+ if (there !== null) {
197
+ if (!bytes.equals(there)) { conflicts.push({ worktree: from, main: to, reason: 'differs' }); continue; }
198
+ fsImpl.unlinkSync(from);
199
+ done.removed.push(from);
200
+ continue;
201
+ }
202
+ placeRecord(to, bytes, fsImpl);
203
+ if (!bytes.equals(fsImpl.readFileSync(to))) { conflicts.push({ worktree: from, main: to, reason: 'differs' }); continue; }
204
+ fsImpl.unlinkSync(from);
205
+ done.adopted.push(to);
206
+ } finally {
207
+ releaseSource();
208
+ }
209
+ } finally {
210
+ releaseTarget();
211
+ }
212
+ }
213
+ }
214
+ return conflicts.length ? { ...done, ok: false, error: 'store-conflict', conflicts } : done;
215
+ }
216
+
217
+ module.exports = { taskStateRoot, sweepWorktreeRecords, placeRecord, adoptedStores, ADOPTER };
@@ -5,7 +5,7 @@
5
5
  const path = require('node:path');
6
6
  const fs = require('node:fs');
7
7
  const { loadStorageRegistry } = require('./registry.js');
8
- const { resolveRoots, readPruneSetting } = require('./roots.js');
8
+ const { resolveRoots, createPruneSettingsResolver } = require('./roots.js');
9
9
  const { runPass } = require('./engine.js');
10
10
  const { upkeepAfterVerb } = require('./upkeep.js');
11
11
  const { REPO_ROOT } = require('../../helper/repo-root.js');
@@ -29,10 +29,15 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
29
29
  return;
30
30
  }
31
31
  const roots = resolveRoots({ registry, projectRoot: global ? null : path.resolve(projectRoot), global, env });
32
- const projectSetting = readPruneSetting({ registry, roots, rootId: 'project-state', fsImpl });
33
- const userSetting = readPruneSetting({ registry, roots, rootId: 'user-state', fsImpl });
34
- const setting = Object.fromEntries(Object.keys(roots).map(rootId => [rootId,
35
- readPruneSetting({ registry, roots, rootId, fsImpl })]));
32
+ let settingsView = null;
33
+ const resolvePruneSetting = createPruneSettingsResolver({ roots, fsImpl, onView: (view ) => { settingsView = view; } });
34
+ const projectSetting = resolvePruneSetting('project-state');
35
+ const userSetting = resolvePruneSetting('user-state');
36
+ let deprecationNotes = [];
37
+ try {
38
+ if (settingsView) deprecationNotes = settingsView.notes.filter((note ) => note.code === 'deprecated' && note.key === 'storage.prune');
39
+ } catch { /* a settings fault does not change storage's fail-closed report */ }
40
+ const setting = Object.fromEntries(Object.keys(roots).map(rootId => [rootId, resolvePruneSetting(rootId)]));
36
41
  const mode = action === 'prune' ? 'prune' : 'report';
37
42
  const result = runPass({ registry, roots, mode, setting, now: Date.now(), env, fsImpl });
38
43
  const removed = result.stores.reduce((sum , row ) => sum + row.removed, 0);
@@ -42,6 +47,7 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
42
47
  // IC-013: an explicitly set DOFLOW_RETENTION_HOURS still removes settled lifecycle events.
43
48
  console.log(removed === 0 ? `storage: pruning is off (${decision}); nothing removed`
44
49
  : `storage: pruning is off (${decision}); DOFLOW_RETENTION_HOURS still applied to lifecycle events: removed ${removed} ${entries}`);
50
+ for (const note of deprecationNotes) console.log(`note: ${note.text}`);
45
51
  return;
46
52
  }
47
53
  if (json) {
@@ -53,6 +59,7 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
53
59
  return;
54
60
  }
55
61
  console.log(`Settings: project ${projectSetting.setting}${projectSetting.decidedBy ? ` (${projectSetting.decidedBy})` : ' (default)'}; user ${userSetting.setting}${userSetting.decidedBy ? ` (${userSetting.decidedBy})` : ' (default)'}`);
62
+ for (const note of deprecationNotes) console.log(`note: ${note.text}`);
56
63
  for (const row of result.stores) {
57
64
  const names = row.selected.slice(0, 20).map((entry ) => entry.name);
58
65
  const more = row.selected.length > 20 ? `\n ... and ${row.selected.length - 20} more` : '';
@@ -7,7 +7,7 @@ const path = require('node:path');
7
7
  const { randomBytes } = require('node:crypto');
8
8
  const { formatBound } = require('./registry.js');
9
9
  const { resolveStorePath } = require('./roots.js');
10
- const { selectCandidates, recordStillCandidate, protectedLogs, removableRules, ruleOrder, projectRoot } = require('./rules.js');
10
+ const { selectCandidates, recordStillCandidate, protectedLogs, unfinishedRuns, removableRules, ruleOrder, projectRoot } = require('./rules.js');
11
11
  const { retainLocked } = require('../lifecycle/store-upkeep.js');
12
12
  const { parseWindow, selectExpired } = require('../lifecycle/retention.js');
13
13
  const { readEvents } = require('../lifecycle/event-store.js');
@@ -192,7 +192,7 @@ function runPass({registry,roots,stores,mode,setting={},now=Date.now(),budgetMs,
192
192
  row.selected=selection.files.map((name )=>({name,reason:'settled',bytes:stat(fsImpl,path.join(location,name))?.size || 0}));
193
193
  } else {
194
194
  let output;
195
- try { output=retainLocked(project,{fsImpl,now:new Date(now),window,maxFiles:Math.max(0,cap-removals),shouldStop}); }
195
+ try { output=retainLocked(project,{fsImpl,now:new Date(now),window,maxFiles:Math.max(0,cap-removals),shouldStop,staleTmpMs:registry.settings.staleTmpHours*3600000}); }
196
196
  catch(error ) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
197
197
  row.selected=output.selected.map(({name,bytes} )=>({name,reason:'settled',bytes}));
198
198
  row.removed=output.removed; removals+=row.removed;
@@ -222,6 +222,13 @@ function runPass({registry,roots,stores,mode,setting={},now=Date.now(),budgetMs,
222
222
  row.protected=candidates.filter((c )=>protectedSet.has(c.name)).length;
223
223
  candidates=candidates.filter((c )=>!protectedSet.has(c.name));
224
224
  }
225
+ if (store.protect==='unfinished-run' && candidates.length) {
226
+ // Fails closed: a file whose run cannot be shown terminal is kept, never reported as a failure.
227
+ const kept=unfinishedRuns({roots,fsImpl,names:candidates.map((c )=>c.name),shouldStop});
228
+ if (kept.incomplete) { result.partial=true; break; }
229
+ row.protected=candidates.filter((c )=>kept.names.has(c.name)).length;
230
+ candidates=candidates.filter((c )=>!kept.names.has(c.name));
231
+ }
225
232
  const orderedCandidates=store.entry==='record' ? removalOrder(candidates)
226
233
  : [...candidates].sort((a,b)=>a.mtimeMs-b.mtimeMs || a.name.localeCompare(b.name));
227
234
  if (shouldStop()) { result.partial=true; break; }
@@ -18,6 +18,9 @@ const RULES = new Map([
18
18
  ['existing', []],
19
19
  ['control', []],
20
20
  ]);
21
+ // open-work keeps logs an unfinished task's record names; unfinished-run keeps a worker-model file
22
+ // while its worker run or dispatch is not terminal or cannot be resolved (row 120 IC-005).
23
+ const PROTECTS = new Set(['open-work', 'unfinished-run']);
21
24
  const MOMENTS = new Set(['verb', 'session', 'verify', 'install', 'lifecycle-verb', 'uninstall']);
22
25
  const STORE_ID = /^[a-z][a-z0-9-]*$/;
23
26
 
@@ -70,8 +73,18 @@ function validateStorageRegistry(registry )
70
73
  for (const field of RULES.get(store.rule) || []) if (!positiveInteger(store.bound[field])) error(errors, `${where}.bound.${field}`, 'must be a positive integer');
71
74
  if (['existing', 'control'].includes(store.rule) && (typeof store.bound.text !== 'string' || !store.bound.text)) error(errors, `${where}.bound.text`, 'must be a non-empty string');
72
75
  if (store.rule === 'existing' && store.bound.value !== undefined && !positiveInteger(store.bound.value)) error(errors, `${where}.bound.value`, 'must be a positive integer when present');
76
+ if (store.rule === 'count-and-age' && store.bound.either !== undefined && typeof store.bound.either !== 'boolean') error(errors, `${where}.bound.either`, 'must be a boolean when present');
77
+ const allowed = new Set([
78
+ ...(RULES.get(store.rule) || []),
79
+ ...(['existing', 'control'].includes(store.rule) ? ['text'] : []),
80
+ ...(store.rule === 'existing' ? ['value'] : []),
81
+ ...(store.rule === 'count-and-age' ? ['either'] : []),
82
+ ]);
83
+ for (const key of Object.keys(store.bound)) {
84
+ if (!allowed.has(key)) error(errors, `${where}.bound`, `unknown key '${key}'`);
85
+ }
73
86
  }
74
- if (store.protect !== null && store.protect !== 'open-work') error(errors, `${where}.protect`, 'must be open-work or null');
87
+ if (store.protect !== null && !PROTECTS.has(store.protect)) error(errors, `${where}.protect`, 'must be open-work, unfinished-run or null');
75
88
  if (!Array.isArray(store.moments) || store.moments.some((moment ) => !MOMENTS.has(moment)) || new Set(store.moments).size !== store.moments.length) error(errors, `${where}.moments`, 'must be a unique subset of known moments');
76
89
  if (store.rule === 'existing' && store.moments?.length) error(errors, `${where}.moments`, 'must be empty for existing stores');
77
90
  if (!Array.isArray(store.writers) || store.writers.some((writer ) => typeof writer !== 'string' || !writer)) error(errors, `${where}.writers`, 'must be an array of repository-relative paths');