@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
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bash
2
+ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; then jq() { command jq -b "$@"; }; fi ;; esac # native jq.exe ends lines with CRLF unless -b
3
+ _DOFLOW_SRC=${0//\\//}; case $_DOFLOW_SRC in */*) _DOFLOW_POLDIR=${_DOFLOW_SRC%/*} ;; *) _DOFLOW_POLDIR=. ;; esac
2
4
  # stop-check.sh — Canonical Policy Library: Stop hook
3
5
  #
4
6
  # Two responsibilities:
@@ -24,14 +26,8 @@
24
26
  #
25
27
  # Canonical Policy Script Contract (design.md §4):
26
28
  # env DOFLOW_PROJECT_DIR, DOFLOW_AGENT (both optional here)
27
- # stdin a Stop-shaped JSON payload. session_id and transcript path field
28
- # names both vary by harness (transcript_path vs. Antigravity's
29
- # camelCase transcriptPath) — this script reads the union of known
30
- # field names rather than assuming one, since a native front door
31
- # (Claude/Codex/Kiro) execs this script directly with no payload
32
- # translation of its own.
33
- # exit 0 = allow the stop, non-zero = deny (block the stop) with the
34
- # reason on stderr.
29
+ # stdin envelope v1 from a native front door via the shared lifecycle bridge.
30
+ # exit exit 2 + doflow_decide deny = deny; other faults per IC-011.
35
31
  #
36
32
  # Depends on lib.sh (require_jq, json_field, ensure_session_dir,
37
33
  # run_with_timeout) being discoverable next to this script when it runs — see
@@ -48,18 +44,49 @@
48
44
 
49
45
  set -euo pipefail
50
46
  # shellcheck source=lib.sh
51
- source "$(dirname "$0")/lib.sh"
52
- require_jq
53
-
47
+ source "$_DOFLOW_POLDIR/lib.sh"
54
48
  INPUT=$(cat)
55
- SESSION_ID=$(json_field "$INPUT" ".session_id")
49
+ # Decision records stay off every stream unless a bridge supplied the fd.
50
+ POLICY_DIR=$_DOFLOW_POLDIR
51
+ if [ -f "$POLICY_DIR/contract.sh" ]; then
52
+ # shellcheck source=contract.sh
53
+ source "$POLICY_DIR/contract.sh"
54
+ doflow_decision_init stop-check 1
55
+ else
56
+ doflow_decision_init() { :; }
57
+ doflow_decide() { :; }
58
+ ( . "$POLICY_DIR/capture-failure.sh"; doflow_capture_failure stop-check contract-missing ) >/dev/null 2>&1 || true
59
+ exit 0
60
+ fi
61
+ if ! command -v jq >/dev/null 2>&1; then
62
+ doflow_decide error dependency-missing:jq
63
+ ( . "$POLICY_DIR/capture-failure.sh"; doflow_capture_failure stop-check dependency-missing:jq ) >/dev/null 2>&1 || true
64
+ fi
65
+ require_jq
66
+ SESSION_ID="" ENVELOPE_CLASS=missing
67
+ { IFS= read -r -d '' ENVELOPE_CLASS; IFS= read -r -d '' SESSION_ID; } < <(
68
+ printf '%s' "$INPUT" | jq -j 'if ((. | type) != "object") or ((.doflow_event | type) != "object") or ((.doflow_event | has("version")) | not) then "missing" elif ((.doflow_event.version | type) == "number") and .doflow_event.version == 1 then "v1" else "unsupported" end, "\u0000", (.session_id // "" | if type == "string" then rtrimstr("\n") else tojson end), "\u0000"' 2>/dev/null
69
+ ) || true
70
+ ENVELOPE_CLASS=${ENVELOPE_CLASS:-missing}
71
+ if [ ! -f "$POLICY_DIR/contract.sh" ]; then ENVELOPE_CLASS=missing; fi
72
+ if [ "$ENVELOPE_CLASS" = unsupported ] || [ "$ENVELOPE_CLASS" = missing ]; then
73
+ code=envelope-version
74
+ [ "$ENVELOPE_CLASS" = missing ] && code=envelope-missing
75
+ [ -f "$POLICY_DIR/contract.sh" ] || code=contract-missing
76
+ doflow_decide unsupported "$code"
77
+ if [ -f "$POLICY_DIR/contract.sh" ]; then
78
+ ( . "$_DOFLOW_POLDIR/capture-failure.sh"; doflow_capture_failure stop-check "$code" ) >/dev/null 2>&1 || true
79
+ fi
80
+ exit 0
81
+ fi
56
82
 
57
83
  TRANSCRIPT_PATH=$(json_field "$INPUT" ".transcript_path")
58
84
  [ -z "$TRANSCRIPT_PATH" ] && TRANSCRIPT_PATH=$(json_field "$INPUT" ".transcriptPath")
59
85
 
60
86
  # ── 1. Lint dispatch (only when we have a session-scoped edited-files queue) ──
61
87
 
62
- if [ -n "$SESSION_ID" ]; then
88
+ # With no config home (SESSION_DIR empty) there is no queue to read; the stub check below still runs.
89
+ if [ -n "$SESSION_ID" ] && [ -n "$SESSION_DIR" ]; then
63
90
  SESSION_PATH=$(ensure_session_dir "$SESSION_ID")
64
91
  EDITED_FILES="$SESSION_PATH/edited-files.txt"
65
92
  PROC_FILE="$SESSION_PATH/edited-files.txt.proc"
@@ -79,12 +106,14 @@ if [ -n "$SESSION_ID" ]; then
79
106
  # The resolver for the leak-scan verb. Absent install -> the scan is
80
107
  # skipped, like every other tool below: this hook has never required
81
108
  # anything to be present.
109
+ # HOME may be unset (an absolute XDG_CONFIG_HOME alone gives a config home): its candidates drop out.
82
110
  DOFLOW_RUN=""
111
+ claude_dir="${CLAUDE_CONFIG_DIR:-${HOME:+$HOME/.claude}}"
83
112
  for candidate in \
84
- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/scripts/doflow/bin/doflow-run" \
85
- "$HOME/.doflow/scripts/doflow/bin/doflow-run"
113
+ "${claude_dir:+$claude_dir/scripts/doflow/bin/doflow-run}" \
114
+ "${HOME:+$HOME/.doflow/scripts/doflow/bin/doflow-run}"
86
115
  do
87
- [ -x "$candidate" ] && { DOFLOW_RUN="$candidate"; break; }
116
+ [ -n "$candidate" ] && [ -x "$candidate" ] && { DOFLOW_RUN="$candidate"; break; }
88
117
  done
89
118
 
90
119
  while IFS= read -r path; do
@@ -107,7 +136,7 @@ if [ -n "$SESSION_ID" ]; then
107
136
  # before the {} reply is invalid hook output)
108
137
  if [ ${#py_files[@]} -gt 0 ]; then
109
138
  if command -v ruff &>/dev/null; then
110
- nohup ruff format "${py_files[@]}" </dev/null >/dev/null 2>&1 &
139
+ nohup ruff format "${py_files[@]}" </dev/null >/dev/null 2>&1 3>&- 5>&- &
111
140
  run_with_timeout 2 -- ruff check "${py_files[@]}" >&2 || true
112
141
  fi
113
142
  fi
@@ -115,7 +144,7 @@ if [ -n "$SESSION_ID" ]; then
115
144
  # JS/TS: async fix only (eslint --fix rarely needs the caller's immediate attention)
116
145
  if [ ${#js_files[@]} -gt 0 ]; then
117
146
  if command -v eslint &>/dev/null; then
118
- nohup eslint --fix "${js_files[@]}" </dev/null >/dev/null 2>&1 &
147
+ nohup eslint --fix "${js_files[@]}" </dev/null >/dev/null 2>&1 3>&- 5>&- &
119
148
  fi
120
149
  fi
121
150
 
@@ -129,7 +158,7 @@ if [ -n "$SESSION_ID" ]; then
129
158
  # Java: async spotlessApply (slow — only if gradlew exists in cwd)
130
159
  if [ "$java_has_files" = "true" ]; then
131
160
  if [ -x "./gradlew" ]; then
132
- nohup ./gradlew spotlessApply </dev/null >/dev/null 2>&1 &
161
+ nohup ./gradlew spotlessApply </dev/null >/dev/null 2>&1 3>&- 5>&- &
133
162
  fi
134
163
  fi
135
164
 
@@ -143,6 +172,7 @@ if [ -n "$SESSION_ID" ]; then
143
172
  if [ -n "$DOFLOW_RUN" ]; then
144
173
  leak_out="$(run_with_timeout 2 -- "$DOFLOW_RUN" leak-scan "${leak_args[@]}" 2>/dev/null || true)"
145
174
  if [ -n "$leak_out" ] && printf '%s' "$leak_out" | grep -q ':[0-9]'; then
175
+ doflow_decide advisory internal-identifiers-found
146
176
  printf 'doflow: internal identifiers found in shipped files (warning, not a block):\n%s\n' \
147
177
  "$leak_out" >&2
148
178
  fi
@@ -161,9 +191,10 @@ fi
161
191
 
162
192
  # The payload's own last_assistant_message is the response being stopped on. A present key
163
193
  # (even null or empty) is authoritative: the transcript is only read when the key is absent.
164
- # jq filter for the transcript fallback: one JSON object per line, assistant text from three
194
+ # jq filter for the transcript fallback: one JSON object per line, assistant text from four
165
195
  # shapes — Claude Code (.message with text blocks), Codex rollout (response_item .payload with
166
- # output_text blocks) and the flat {role,content} line. A partial or foreign line yields "".
196
+ # output_text blocks), Copilot CLI events.jsonl (type assistant.message, .data.content a string) and
197
+ # the flat {role,content} line. A partial or foreign line yields "".
167
198
  read -r -d '' LAST_ASSISTANT_JQ <<'JQ' || true
168
199
  def text: if type == "string" then .
169
200
  elif type == "array" then [ .[]? | objects | select(.type == "text" or .type == "output_text") | .text | strings ] | join("\n")
@@ -171,6 +202,7 @@ def text: if type == "string" then .
171
202
  [ inputs | fromjson? | objects
172
203
  | if ((.message | objects | .role) // null) == "assistant" then .message.content
173
204
  elif .type == "response_item" and ((.payload | objects | .role) // null) == "assistant" then .payload.content
205
+ elif .type == "assistant.message" then .data.content
174
206
  elif .role == "assistant" then .content
175
207
  else empty end
176
208
  | text | select(length > 0) ] | last // ""
@@ -248,16 +280,22 @@ if grep -qiE -- "$STUB_PATTERN" <<<"$STUB_TEXT" 2>/dev/null; then
248
280
  # Block a given message once: the continuation that follows is judged on new text, and a
249
281
  # harness with no stop_hook_active would otherwise be blocked on this one forever. State is
250
282
  # per session, or per transcript when the harness sends no session_id; any failure here
251
- # leaves the block in force only for this call.
283
+ # leaves the block in force only for this call. With no config home there is no state store, so
284
+ # this policy cannot remember a block and allows the stop.
285
+ [ -n "$SESSION_DIR" ] || exit 0
252
286
  GUARD_KEY="$SESSION_ID"
253
287
  [ -z "$GUARD_KEY" ] && GUARD_KEY="t$(printf '%s' "$TRANSCRIPT_PATH" | cksum | cut -d' ' -f1)"
254
- GUARD_FILE="$(ensure_session_dir "$GUARD_KEY" 2>/dev/null)/stop-last-blocked" || GUARD_FILE=""
288
+ GUARD_FILE=""
289
+ if [ -n "$SESSION_DIR" ]; then
290
+ GUARD_FILE="$(ensure_session_dir "$GUARD_KEY" 2>/dev/null)/stop-last-blocked" || GUARD_FILE=""
291
+ fi
255
292
  MESSAGE_SUM=$(printf '%s' "$LAST_ASSISTANT_CONTENT" | cksum 2>/dev/null) || MESSAGE_SUM=""
256
293
  if [ -n "$MESSAGE_SUM" ] && [ -n "$GUARD_FILE" ] && [ "$(cat "$GUARD_FILE" 2>/dev/null)" = "$MESSAGE_SUM" ]; then
257
294
  exit 0
258
295
  fi
259
296
  [ -n "$MESSAGE_SUM" ] && [ -n "$GUARD_FILE" ] && { printf '%s\n' "$MESSAGE_SUM" > "$GUARD_FILE"; } 2>/dev/null || true
260
297
  echo "[stop-check] The last response contains an unfinished-work marker (TODO/FIXME/stub comment or a not-implemented raise/throw)." >&2
298
+ doflow_decide deny unfinished-work
261
299
  exit 2
262
300
  fi
263
301
 
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env bash
2
+ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; then jq() { command jq -b "$@"; }; fi ;; esac # native jq.exe ends lines with CRLF unless -b
3
+ _DOFLOW_SRC=${0//\\//}; case $_DOFLOW_SRC in */*) _DOFLOW_POLDIR=${_DOFLOW_SRC%/*} ;; *) _DOFLOW_POLDIR=. ;; esac
2
4
  # subagent-audit.sh — Canonical Policy Library: SubagentStart + SubagentStop hook
3
5
  # (pure observability, no deny path)
4
6
  #
5
7
  # 022-hooks-remaining-duplication: claude and codex's copies were byte-identical — moved here as
6
- # the single canonical copy. Gemini has no SubagentStart/SubagentStop-equivalent event.
8
+ # the single canonical copy.
7
9
  #
8
10
  # Appends {timestamp, session_id, event, agent_type, agent_id} to a session-scoped audit log.
9
11
  # Today there is zero record of which of the installed agents/ specialists actually run, how often, or
@@ -18,14 +20,45 @@
18
20
 
19
21
  set -euo pipefail
20
22
  # shellcheck source=lib.sh
21
- source "$(dirname "$0")/lib.sh"
22
- require_jq
23
-
23
+ source "$_DOFLOW_POLDIR/lib.sh"
24
24
  INPUT=$(cat)
25
- SESSION_ID=$(json_field "$INPUT" ".session_id")
25
+ POLICY_DIR=$_DOFLOW_POLDIR
26
+ if [ -f "$POLICY_DIR/contract.sh" ]; then
27
+ # shellcheck source=contract.sh
28
+ source "$POLICY_DIR/contract.sh"
29
+ doflow_decision_init subagent-audit 1
30
+ else
31
+ doflow_decision_init() { :; }
32
+ doflow_decide() { :; }
33
+ ( . "$POLICY_DIR/capture-failure.sh"; doflow_capture_failure subagent-audit contract-missing ) >/dev/null 2>&1 || true
34
+ exit 0
35
+ fi
36
+ if ! command -v jq >/dev/null 2>&1; then
37
+ doflow_decide error dependency-missing:jq
38
+ ( . "$POLICY_DIR/capture-failure.sh"; doflow_capture_failure subagent-audit dependency-missing:jq ) >/dev/null 2>&1 || true
39
+ fi
40
+ require_jq
41
+ SESSION_ID="" ENVELOPE_CLASS=missing
42
+ { IFS= read -r -d '' ENVELOPE_CLASS; IFS= read -r -d '' SESSION_ID; } < <(
43
+ printf '%s' "$INPUT" | jq -j 'if ((. | type) != "object") or ((.doflow_event | type) != "object") or ((.doflow_event | has("version")) | not) then "missing" elif ((.doflow_event.version | type) == "number") and .doflow_event.version == 1 then "v1" else "unsupported" end, "\u0000", (.session_id // "" | if type == "string" then rtrimstr("\n") else tojson end), "\u0000"' 2>/dev/null
44
+ ) || true
45
+ ENVELOPE_CLASS=${ENVELOPE_CLASS:-missing}
46
+ if [ ! -f "$POLICY_DIR/contract.sh" ]; then ENVELOPE_CLASS=missing; fi
47
+ if [ "$ENVELOPE_CLASS" = unsupported ] || [ "$ENVELOPE_CLASS" = missing ]; then
48
+ code=envelope-version
49
+ [ "$ENVELOPE_CLASS" = missing ] && code=envelope-missing
50
+ [ -f "$POLICY_DIR/contract.sh" ] || code=contract-missing
51
+ doflow_decide unsupported "$code"
52
+ if [ -f "$POLICY_DIR/contract.sh" ]; then
53
+ ( . "$_DOFLOW_POLDIR/capture-failure.sh"; doflow_capture_failure subagent-audit "$code" ) >/dev/null 2>&1 || true
54
+ fi
55
+ exit 0
56
+ fi
26
57
 
27
58
  # Guard: if we can't determine the session, nothing to do
28
59
  [[ -z "$SESSION_ID" ]] && exit 0
60
+ # No config home (lib.sh leaves the state paths empty): no session store to write (086 IC-010).
61
+ [[ -n "$SESSION_DIR" ]] || exit 0
29
62
 
30
63
  EVENT=$(json_field "$INPUT" ".hook_event_name")
31
64
  AGENT_TYPE=$(json_field "$INPUT" ".agent_type")
@@ -41,6 +74,6 @@ jq -nc \
41
74
  --arg agent_type "$AGENT_TYPE" \
42
75
  --arg agent_id "$AGENT_ID" \
43
76
  '{timestamp: $ts, session_id: $session, event: $event, agent_type: $agent_type, agent_id: $agent_id}' \
44
- >> "$SESSION_PATH/subagent-audit.log" 2>/dev/null || true
77
+ >> "$SESSION_PATH/subagent-audit.log" 2>/dev/null || doflow_decide error audit-write-failed
45
78
 
46
79
  exit 0
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env bash
2
+ # summary-store.sh — Canonical Policy Library: scoped compact summaries, grants and the legacy
3
+ # adopter (feature 078, IC-010, IC-011 store write, IC-012, IC-014; DEC-003, DEC-012, DEC-014).
4
+ #
5
+ # Layout under STATE_DIR (lib.sh: <config home>/doflow/session-env; empty with no config home, and
6
+ # then there is no store: peeks find nothing, writes, claims and adopts return 1):
7
+ # summaries/<worktreeKey>/<run>/<lineage>/summary.md one pending summary per scope
8
+ # summaries/<worktreeKey>/<run>/<lineage>/.summary.XXXXXX write temporary
9
+ # summaries/<worktreeKey>/<run>/<lineage>/.claimed.<unitId> claim temporary
10
+ # grants/<run>/<lineage>/<summaryId>.md grant addressed to a recipient
11
+ # grants/<run>/<lineage>/.claimed.<unitId>.<summaryId> claim temporary
12
+ # worktreeKey is cwd_hash of the identity worktree (DEC-027), so the caller must have sourced
13
+ # lib.sh, which defines cwd_hash, STATE_DIR and PROJECTS_DIR.
14
+ #
15
+ # Sourcing this file defines functions only: no option, no variable, no trap, no output. bash 3.2
16
+ # safe and safe under the caller's `set -euo pipefail`. Peek functions always return 0; write,
17
+ # claim and adopt return 0 when they did their work and 1 otherwise, so call them from `if` or
18
+ # with `||`. Results are DF_SUMMARY_* and DF_GRANT_* variables. A claimed file is left in place for
19
+ # the caller to pass to emission.jq (--rawfile summary or grant) and to remove after rendering.
20
+
21
+ # The id pattern of IC-007 (DEC-024) and the summary id pattern of IC-010.
22
+ _doflow_ss_id_ok() { [[ ${1:-} =~ ^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$ ]]; }
23
+ _doflow_ss_summary_id_ok() { [[ ${1:-} =~ ^s-[0-9]{8}T[0-9]{6}Z-[A-Za-z0-9]{6}$ ]]; }
24
+ # A unit id (L.<run>.<lineage>.<attempt> or a harness session id) is one path segment.
25
+ _doflow_ss_unit_ok() { [[ ${1:-} =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]; }
26
+
27
+ # _doflow_ss_root: _DF_SS_ROOT = lib.sh's STATE_DIR; returns 1 when it is empty (no config home).
28
+ _doflow_ss_root() {
29
+ _DF_SS_ROOT="${STATE_DIR:-}"
30
+ [ -n "$_DF_SS_ROOT" ]
31
+ }
32
+
33
+ # _doflow_ss_key <dir>: _DF_SS_KEY = cwd_hash <dir>, remembered for the last directory asked.
34
+ _doflow_ss_key() {
35
+ if [ -n "${_DF_SS_KEY_FOR:-}" ] && [ "$_DF_SS_KEY_FOR" = "$1" ] && [ -n "${_DF_SS_KEY:-}" ]; then
36
+ return 0
37
+ fi
38
+ _DF_SS_KEY=""
39
+ _DF_SS_KEY_FOR=""
40
+ [ -n "$1" ] || return 1
41
+ declare -F cwd_hash >/dev/null 2>&1 || return 1
42
+ _DF_SS_KEY=$(cwd_hash "$1" 2>/dev/null) || _DF_SS_KEY=""
43
+ [[ $_DF_SS_KEY =~ ^[0-9a-f]{16}$ ]] || { _DF_SS_KEY=""; return 1; }
44
+ _DF_SS_KEY_FOR=$1
45
+ return 0
46
+ }
47
+
48
+ # doflow_summary_scope <worktree> <run> <lineage>
49
+ # DF_SUMMARY_SCOPE = summaries/<worktreeKey>/<run>/<lineage>, or empty (return 1) when run or
50
+ # lineage is not an id, or the lineage is `solo`, which has no scope directory.
51
+ doflow_summary_scope() {
52
+ DF_SUMMARY_SCOPE=""
53
+ _doflow_ss_id_ok "${2:-}" && _doflow_ss_id_ok "${3:-}" && [ "$3" != solo ] || return 1
54
+ _doflow_ss_key "${1:-}" || return 1
55
+ _doflow_ss_root || return 1
56
+ DF_SUMMARY_SCOPE="$_DF_SS_ROOT/summaries/$_DF_SS_KEY/$2/$3"
57
+ return 0
58
+ }
59
+
60
+ # _doflow_ss_head <file>: reads the frontmatter the way emission.jq's parse_record does (\r removed,
61
+ # fields between the first two `---` lines, first match wins, leading spaces of a value dropped),
62
+ # never more than 65536 bytes. Sets _DF_SS_SESSION, _DF_SS_SID, and for the grant header
63
+ # _DF_SS_FROM (from_lineage), _DF_SS_AT (compacted_at) and _DF_SS_BRANCH (branch).
64
+ _doflow_ss_head() {
65
+ local LC_ALL=C line="" left=65536 fences=0 key value seen=" "
66
+ _DF_SS_SESSION=""
67
+ _DF_SS_SID=""
68
+ _DF_SS_FROM=""
69
+ _DF_SS_AT=""
70
+ _DF_SS_BRANCH=""
71
+ {
72
+ while [ "$left" -gt 0 ] && [ "$fences" -lt 2 ]; do
73
+ IFS= read -r -n "$left" line || [ -n "$line" ] || break
74
+ left=$((left - ${#line} - 1))
75
+ line=${line//$'\r'/}
76
+ if [ "$line" = "---" ]; then
77
+ fences=$((fences + 1))
78
+ elif [ "$fences" = 1 ] && [[ $line == ?*:* ]]; then
79
+ key=${line%%:*}
80
+ value=${line#*:}
81
+ while [ "${value# }" != "$value" ]; do value=${value# }; done
82
+ # The first match of a key wins: a wanted key is recorded in $seen once it is read.
83
+ case "$key" in
84
+ session_id | summary_id | from_lineage | compacted_at | branch)
85
+ case "$seen" in *" $key "*) key="" ;; esac ;;
86
+ *) key="" ;;
87
+ esac
88
+ case "$key" in
89
+ session_id) _DF_SS_SESSION=$value ;;
90
+ summary_id) _DF_SS_SID=$value ;;
91
+ from_lineage) _DF_SS_FROM=$value ;;
92
+ compacted_at) _DF_SS_AT=$value ;;
93
+ branch) _DF_SS_BRANCH=$value ;;
94
+ esac
95
+ [ -z "$key" ] || seen="$seen$key "
96
+ fi
97
+ line=""
98
+ done
99
+ } 2>/dev/null <"$1" || true
100
+ return 0
101
+ }
102
+
103
+ # _doflow_ss_body_bytes <file>: _DF_SS_BODY = the UTF-8 byte length of the record body exactly as
104
+ # parse_record yields it (\r removed, from the first non-empty line after the second `---`,
105
+ # trailing newlines dropped). The caller has bounded the file's size.
106
+ _doflow_ss_body_bytes() {
107
+ local LC_ALL=C line="" fences=0 started=0 gap=0
108
+ _DF_SS_BODY=0
109
+ {
110
+ while IFS= read -r line || [ -n "$line" ]; do
111
+ line=${line//$'\r'/}
112
+ if [ "$fences" -lt 2 ]; then
113
+ [ "$line" != "---" ] || fences=$((fences + 1))
114
+ elif [ -z "$line" ]; then
115
+ [ "$started" = 0 ] || gap=$((gap + 1))
116
+ elif [ "$started" = 0 ]; then
117
+ started=1
118
+ _DF_SS_BODY=${#line}
119
+ else
120
+ _DF_SS_BODY=$((_DF_SS_BODY + gap + 1 + ${#line}))
121
+ gap=0
122
+ fi
123
+ line=""
124
+ done
125
+ } 2>/dev/null <"$1" || true
126
+ return 0
127
+ }
128
+
129
+ # _doflow_ss_size <file>: _DF_SS_SIZE = the file's size in bytes, or empty.
130
+ _doflow_ss_size() {
131
+ _DF_SS_SIZE=$(wc -c 2>/dev/null <"$1") || _DF_SS_SIZE=""
132
+ _DF_SS_SIZE=${_DF_SS_SIZE//[!0-9]/}
133
+ return 0
134
+ }
135
+
136
+ # doflow_summary_write <worktree> <run> <lineage> <attempt> <compacted_at> <session_id> <trigger>
137
+ # <branch> <role> <body>
138
+ # IC-011 step 3: mkdir -p the scope, mktemp .summary.XXXXXX in it, write the IC-010 record, mv it to
139
+ # summary.md (a newer compaction replaces the pending one). compacted_at is UTC ISO-8601 to the
140
+ # second (YYYY-MM-DDTHH:MM:SSZ). Sets DF_SUMMARY_ID and DF_SUMMARY_PATH. Returns 1 with nothing left
141
+ # behind on an invalid argument, an empty body or a failed step.
142
+ doflow_summary_write() {
143
+ local worktree="${1:-}" run="${2:-}" lineage="${3:-}" attempt="${4:-}" at="${5:-}"
144
+ local session="${6:-}" trigger="${7:-}" branch="${8:-}" role="${9:-}" body="${10:-}"
145
+ local tmp="" stamp suffix nl=$'\n' cr=$'\r'
146
+ DF_SUMMARY_ID=""
147
+ DF_SUMMARY_PATH=""
148
+ [ -n "$body" ] || return 1
149
+ [[ $at =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] || return 1
150
+ [ -z "$attempt" ] || [[ $attempt =~ ^[1-9][0-9]{0,3}$ ]] || return 1
151
+ doflow_summary_scope "$worktree" "$run" "$lineage" || return 1
152
+ # A frontmatter value is one line: an embedded line break would forge a key.
153
+ session=${session//[$nl$cr]/ }; trigger=${trigger//[$nl$cr]/ }; branch=${branch//[$nl$cr]/ }
154
+ role=${role//[$nl$cr]/ }; worktree=${worktree//[$nl$cr]/ }
155
+ mkdir -p "$DF_SUMMARY_SCOPE" 2>/dev/null || return 1
156
+ tmp=$(mktemp "$DF_SUMMARY_SCOPE/.summary.XXXXXX" 2>/dev/null) || return 1
157
+ suffix=${tmp##*.summary.}
158
+ stamp=${at//[-:]/}
159
+ DF_SUMMARY_ID="s-$stamp-$suffix"
160
+ if ! _doflow_ss_summary_id_ok "$DF_SUMMARY_ID" \
161
+ || ! printf -- '---\ncompacted_at: %s\nsession_id: %s\ntrigger: %s\nbranch: %s\nrole: %s\nsummary_id: %s\nrun: %s\nlineage: %s\nattempt:%s\nworktree: %s\n---\n\n%s\n' \
162
+ "$at" "${session:-unknown}" "${trigger:-unknown}" "${branch:-unknown}" "${role:-main}" \
163
+ "$DF_SUMMARY_ID" "$run" "$lineage" "${attempt:+ $attempt}" "$worktree" "$body" >"$tmp" 2>/dev/null \
164
+ || ! mv -f "$tmp" "$DF_SUMMARY_SCOPE/summary.md" 2>/dev/null; then
165
+ rm -f "$tmp" 2>/dev/null || true
166
+ DF_SUMMARY_ID=""
167
+ return 1
168
+ fi
169
+ DF_SUMMARY_PATH="$DF_SUMMARY_SCOPE/summary.md"
170
+ return 0
171
+ }
172
+
173
+ # doflow_summary_peek <worktree> <run> <lineage> <session_id>
174
+ # IC-012 owner steps 1 and 2, for the state vector: DF_SUMMARY_OWN is `none` (no regular,
175
+ # non-symlink summary.md in the scope), `own-write` (its frontmatter session_id is this session's)
176
+ # or `pending`; DF_SUMMARY_FILE is its path and DF_SUMMARY_SID its frontmatter summary_id (possibly
177
+ # empty) when not `none`. Always returns 0.
178
+ doflow_summary_peek() {
179
+ local f
180
+ DF_SUMMARY_OWN=none
181
+ DF_SUMMARY_FILE=""
182
+ DF_SUMMARY_SID=""
183
+ doflow_summary_scope "${1:-}" "${2:-}" "${3:-}" || return 0
184
+ f="$DF_SUMMARY_SCOPE/summary.md"
185
+ [ -f "$f" ] && [ ! -L "$f" ] || return 0
186
+ _doflow_ss_head "$f"
187
+ DF_SUMMARY_FILE=$f
188
+ DF_SUMMARY_SID=$_DF_SS_SID
189
+ if [ -n "${4:-}" ] && [ "$_DF_SS_SESSION" = "$4" ]; then DF_SUMMARY_OWN=own-write; else DF_SUMMARY_OWN=pending; fi
190
+ return 0
191
+ }
192
+
193
+ # doflow_summary_claim <worktree> <run> <lineage> <unit_id> <session_id>
194
+ # IC-012 owner steps 1 to 3: peek, then rename summary.md to .claimed.<unitId>; a summary written
195
+ # after the rename is a new summary.md and survives. The claimed copy is cut to its first 65536
196
+ # bytes. Sets DF_SUMMARY_OWN (as peek, `none` after a lost rename), DF_SUMMARY_CLAIMED (the claimed
197
+ # path, for --rawfile summary) and DF_SUMMARY_BYTES (its full size before the cut, for
198
+ # plan.summaryBytes). Returns 0 when this caller claimed it; the caller removes the claimed file.
199
+ doflow_summary_claim() {
200
+ local unit="${4:-}" claimed
201
+ DF_SUMMARY_CLAIMED=""
202
+ DF_SUMMARY_BYTES=0
203
+ doflow_summary_peek "${1:-}" "${2:-}" "${3:-}" "${5:-}"
204
+ [ "$DF_SUMMARY_OWN" = pending ] || return 1
205
+ _doflow_ss_unit_ok "$unit" || return 1
206
+ claimed="$DF_SUMMARY_SCOPE/.claimed.$unit"
207
+ if ! mv "$DF_SUMMARY_FILE" "$claimed" 2>/dev/null; then
208
+ DF_SUMMARY_OWN=none
209
+ return 1
210
+ fi
211
+ _doflow_ss_size "$claimed"
212
+ DF_SUMMARY_BYTES=${_DF_SS_SIZE:-0}
213
+ if [ "$DF_SUMMARY_BYTES" -gt 65536 ]; then
214
+ # The cut copy is private to this user, as the record it replaces (L1-11).
215
+ if ! { (umask 077; head -c 65536 "$claimed" >"$claimed.read") 2>/dev/null && mv -f "$claimed.read" "$claimed" 2>/dev/null; }; then
216
+ rm -f "$claimed" "$claimed.read" 2>/dev/null || true
217
+ return 1
218
+ fi
219
+ fi
220
+ DF_SUMMARY_CLAIMED=$claimed
221
+ return 0
222
+ }
223
+
224
+ # _doflow_ss_known <value>: _DF_SS_KNOWN = emission.jq's `known`: `]` removed, then empty when it
225
+ # is "unknown".
226
+ _doflow_ss_known() {
227
+ _DF_SS_KNOWN=${1//]/}
228
+ [ "$_DF_SS_KNOWN" != unknown ] || _DF_SS_KNOWN=""
229
+ return 0
230
+ }
231
+
232
+ # doflow_grant_peek <run> <lineage> <max_bytes>
233
+ # IC-012 grant steps 1 and 2: the oldest grants/<run>/<lineage>/<summaryId>.md by name among regular,
234
+ # non-symlink files whose name matches the summary id pattern. DF_GRANT is `none`, `fits` or `over`.
235
+ # It fits exactly when emission.jq would render it whole: the UTF-8 bytes of render_grant's segment
236
+ # "[Granted summary from lineage <from_lineage><header_tail>]\n<body>" are at most max_bytes, so a
237
+ # body that fits under a header that does not is `over` and is never claimed. DF_GRANT_FILE,
238
+ # DF_GRANT_ID and DF_GRANT_BYTES (the body's bytes, for the pointer; empty when the file is too large
239
+ # to be worth reading) describe it. Always returns 0.
240
+ doflow_grant_peek() {
241
+ local LC_ALL=C run="${1:-}" lineage="${2:-}" max="${3:-}" dir f name header tail=""
242
+ DF_GRANT=none
243
+ DF_GRANT_FILE=""
244
+ DF_GRANT_ID=""
245
+ DF_GRANT_BYTES=""
246
+ _doflow_ss_id_ok "$run" && _doflow_ss_id_ok "$lineage" || return 0
247
+ [[ $max =~ ^[1-9][0-9]{0,9}$ ]] || return 0
248
+ _doflow_ss_root || return 0
249
+ dir="$_DF_SS_ROOT/grants/$run/$lineage"
250
+ [ -d "$dir" ] && [ ! -L "$dir" ] || return 0
251
+ # Glob expansion is sorted (C collation here), and summary ids sort by time.
252
+ for f in "$dir"/s-*.md; do
253
+ name=${f##*/}
254
+ name=${name%.md}
255
+ _doflow_ss_summary_id_ok "$name" || continue
256
+ [ -f "$f" ] && [ ! -L "$f" ] || continue
257
+ DF_GRANT_FILE=$f
258
+ DF_GRANT_ID=$name
259
+ break
260
+ done
261
+ [ -n "$DF_GRANT_FILE" ] || return 0
262
+ _doflow_ss_size "$DF_GRANT_FILE"
263
+ # A frontmatter needs far less than 65536 bytes: anything larger than that plus the budget is over.
264
+ if [ -n "$_DF_SS_SIZE" ] && [ "$_DF_SS_SIZE" -le $((max + 65536)) ]; then
265
+ _doflow_ss_body_bytes "$DF_GRANT_FILE"
266
+ DF_GRANT_BYTES=$_DF_SS_BODY
267
+ # render_grant's header, from the same bounded head pass parse_record's fields come from.
268
+ _doflow_ss_head "$DF_GRANT_FILE"
269
+ _doflow_ss_known "$_DF_SS_AT"
270
+ [ -z "$_DF_SS_KNOWN" ] || tail=", compacted $_DF_SS_KNOWN"
271
+ _doflow_ss_known "$_DF_SS_BRANCH"
272
+ [ -z "$_DF_SS_KNOWN" ] || tail="$tail on branch $_DF_SS_KNOWN"
273
+ header="[Granted summary from lineage $_DF_SS_FROM$tail]"
274
+ if [ $((${#header} + 1 + DF_GRANT_BYTES)) -le "$max" ]; then DF_GRANT=fits; else DF_GRANT=over; fi
275
+ else
276
+ DF_GRANT=over
277
+ fi
278
+ return 0
279
+ }
280
+
281
+ # doflow_grant_claim <run> <lineage> <unit_id> <max_bytes>
282
+ # IC-012 grant step 3: peek; when the oldest grant fits, rename it to
283
+ # .claimed.<unitId>.<summaryId> and set DF_GRANT_CLAIMED (for --rawfile grant); the caller removes
284
+ # it after rendering. An `over` grant stays in place (DF_GRANT_FILE renders the pointer). One grant
285
+ # per event. Returns 0 only when this caller claimed a grant.
286
+ doflow_grant_claim() {
287
+ local unit="${3:-}" claimed
288
+ DF_GRANT_CLAIMED=""
289
+ doflow_grant_peek "${1:-}" "${2:-}" "${4:-}"
290
+ [ "$DF_GRANT" = fits ] || return 1
291
+ _doflow_ss_unit_ok "$unit" || return 1
292
+ claimed="${DF_GRANT_FILE%/*}/.claimed.$unit.$DF_GRANT_ID"
293
+ if ! mv "$DF_GRANT_FILE" "$claimed" 2>/dev/null; then
294
+ DF_GRANT=none
295
+ return 1
296
+ fi
297
+ DF_GRANT_CLAIMED=$claimed
298
+ return 0
299
+ }
300
+
301
+ # doflow_summary_adopt_legacy <cwd> <worktree> [check]
302
+ # IC-014, on action `adopt` only: the pre-078 project summary under projects/<cwd_hash cwd>/, a
303
+ # regular file and not a symlink, moves once into summaries/<worktreeKey>/local/main/summary.md
304
+ # with its frontmatter kept. When a summary is already pending there it is newer by construction
305
+ # and the old file is removed instead. The legacy file is first renamed to <old>.adopting.<pid>, so
306
+ # only one adopter proceeds (a lost rename means another adopter won and returns 0, DEC-043); then
307
+ # a hard link that refuses to replace, and the .adopting file is unlinked last, so a summary written
308
+ # meanwhile is never overwritten. Without hard links, `mv -n` is the fallback, taken only when the
309
+ # target is absent and verified afterwards. Nothing writes the old path. Returns 0 when a legacy
310
+ # file was adopted or retired, or another adopter took it. With a third argument `check` it only tests that the
311
+ # legacy file exists and changes nothing (doflow_summary_legacy_present). This function is the one
312
+ # place that names the legacy file (G38.4).
313
+ doflow_summary_adopt_legacy() {
314
+ local cwd="${1:-}" worktree="${2:-}" projects old new taken
315
+ [ -n "$cwd" ] || return 1
316
+ [ -n "$worktree" ] || worktree=$cwd
317
+ _doflow_ss_key "$cwd" || return 1
318
+ _doflow_ss_root || return 1
319
+ projects="${PROJECTS_DIR:-$_DF_SS_ROOT/projects}"
320
+ old="$projects/$_DF_SS_KEY/last-compact-summary.md"
321
+ [ -f "$old" ] && [ ! -L "$old" ] || return 1
322
+ [ "${3:-}" != check ] || return 0
323
+ doflow_summary_scope "$worktree" local main || return 1
324
+ new="$DF_SUMMARY_SCOPE/summary.md"
325
+ mkdir -p "$DF_SUMMARY_SCOPE" 2>/dev/null || return 1
326
+ taken="$old.adopting.$$"
327
+ mv "$old" "$taken" 2>/dev/null || return 0
328
+ # A link that refuses to replace; it fails when a summary is pending, which retires the legacy one.
329
+ if ! ln "$taken" "$new" 2>/dev/null && [ ! -e "$new" ] && [ ! -L "$new" ]; then
330
+ # No hard links on this filesystem: a rename that refuses to replace, then a check that $new is
331
+ # the adopted file (mv -n leaves the source in place when the target appeared meanwhile).
332
+ # The old path is never written again, so a failed rename leaves the .adopting file in place.
333
+ mv -n "$taken" "$new" 2>/dev/null || true
334
+ if [ -e "$taken" ] && [ ! -e "$new" ] && [ ! -L "$new" ]; then return 1; fi
335
+ fi
336
+ rm -f "$taken" 2>/dev/null || true
337
+ return 0
338
+ }
339
+
340
+ # doflow_summary_legacy_present <cwd>
341
+ # Returns 0 when the pre-078 project summary under projects/<cwd_hash cwd>/ exists as a regular file
342
+ # and not a symlink, and 1 otherwise. Runs adopt_legacy's check mode in a subshell, so it sets no
343
+ # variable, prints nothing and changes nothing.
344
+ doflow_summary_legacy_present() {
345
+ [ -n "${1:-}" ] || return 1
346
+ (doflow_summary_adopt_legacy "$1" "" check)
347
+ }