@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -0,0 +1,655 @@
1
+ 'use strict';
2
+
3
+ // Scoped compact summaries and grants (078 IC-010, IC-012, IC-013 in process; DEC-002, DEC-012).
4
+ // Run directly by Node native type-stripping.
5
+ //
6
+ // The TypeScript twin of `policies/summary-store.sh`: the same layout, record format, rename
7
+ // claims, 65536-byte bounded reads and grant-fits rule, so a summary written by either side is
8
+ // claimed by the other. Layout under stateDir() (`<config home>/doflow/session-env`; with no config home
9
+ // there is no store and every read finds nothing):
10
+ // summaries/<worktreeKey>/<run>/<lineage>/summary.md one pending summary per scope
11
+ // summaries/<worktreeKey>/<run>/<lineage>/.summary.XXXXXX write temporary
12
+ // summaries/<worktreeKey>/<run>/<lineage>/.claimed.<unitId> claim temporary
13
+ // grants/<run>/<lineage>/<summaryId>.md grant addressed to a recipient
14
+ // grants/<run>/<lineage>/.grant.XXXXXX grant write temporary
15
+ // grants/<run>/<lineage>/.claimed.<unitId>.<summaryId> claim temporary
16
+ // Records are read as bytes (latin1 strings, one character per byte), as the bash store does under
17
+ // LC_ALL=C, so byte counts match it on any input, valid UTF-8 or not.
18
+
19
+ const crypto = require('node:crypto');
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+ const { ID_PATTERN, cwdHash, stateDir } = require('./identity.js');
23
+
24
+
25
+
26
+ const SUMMARY_ID_PATTERN = /^s-[0-9]{8}T[0-9]{6}Z-[A-Za-z0-9]{6}$/;
27
+ /** A unit id (L.<run>.<lineage>.<attempt> or a harness session id) is one path segment. */
28
+ const UNIT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
29
+ const MAX_BYTES_PATTERN = /^[1-9][0-9]{0,9}$/;
30
+ const READ_BOUND = 65536;
31
+ const DEFAULT_GRANT_MAX_BYTES = 16384;
32
+ const SUFFIX_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
33
+
34
+ /** IC-013 exits: not found, legacy or already granted 1, usage 2, over budget 3. */
35
+ const GRANT_EXIT = {
36
+ 'summary-not-found': 1,
37
+ 'summary-legacy': 1,
38
+ 'grant-exists': 1,
39
+ usage: 2,
40
+ 'over-budget': 3,
41
+ } ;
42
+
43
+
44
+ class SummaryStoreError extends Error {
45
+ code ;
46
+ exitCode ;
47
+ constructor(code , message ) {
48
+ super(message);
49
+ this.name = 'SummaryStoreError';
50
+ this.code = code;
51
+ this.exitCode = GRANT_EXIT[code];
52
+ }
53
+ }
54
+
55
+ function idOk(value ) {
56
+ return typeof value === 'string' && ID_PATTERN.test(value);
57
+ }
58
+
59
+ function regularFile(file ) {
60
+ try {
61
+ return fs.lstatSync(file).isFile();
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /** The file's first `limit` bytes (all of it when limit is omitted) as a latin1 string, or null. */
68
+ function readBytes(file , limit ) {
69
+ try {
70
+ if (limit === undefined) return fs.readFileSync(file).toString('latin1');
71
+ const fd = fs.openSync(file, 'r');
72
+ try {
73
+ const buffer = Buffer.alloc(limit);
74
+ let length = 0;
75
+ while (length < limit) {
76
+ const n = fs.readSync(fd, buffer, length, limit - length, null);
77
+ if (n === 0) break;
78
+ length += n;
79
+ }
80
+ return buffer.subarray(0, length).toString('latin1');
81
+ } finally {
82
+ fs.closeSync(fd);
83
+ }
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ function fileSize(file ) {
90
+ try {
91
+ return fs.statSync(file).size;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * emission.jq's parse_record over a byte string: `\r` removed, trailing newlines dropped, fields
99
+ * from the lines between the first two `---` lines (first match wins, leading spaces of a value
100
+ * dropped), body from the first non-empty line after the second `---`, trailing newlines dropped.
101
+ */
102
+ function parseRecord(raw ) {
103
+ const lines = raw.replace(/\r/g, '').replace(/\n+$/, '').split('\n');
104
+ const fields = Object.create(null);
105
+ const body = [];
106
+ let fences = 0;
107
+ let started = false;
108
+ for (const line of lines) {
109
+ if (fences < 2 && line === '---') { fences += 1; continue; }
110
+ if (fences === 1) {
111
+ const colon = line.indexOf(':');
112
+ if (colon > 0) {
113
+ const key = line.slice(0, colon);
114
+ if (!(key in fields)) fields[key] = line.slice(colon + 1).replace(/^ */, '');
115
+ }
116
+ } else if (fences >= 2 && (started || line !== '')) {
117
+ started = true;
118
+ body.push(line);
119
+ }
120
+ }
121
+ return { fields, body: body.join('\n').replace(/\n+$/, '') };
122
+ }
123
+
124
+ /** The frontmatter fields of a record from its first 65536 bytes (the bash store's bounded head read). */
125
+ function readHead(file ) {
126
+ const raw = readBytes(file, READ_BOUND);
127
+ return raw === null ? Object.create(null) : parseRecord(raw).fields;
128
+ }
129
+
130
+ /** emission.jq's `known`: `]` removed, then empty when it is "unknown". */
131
+ function known(value ) {
132
+ const v = (value ?? '').replace(/\]/g, '');
133
+ return v === 'unknown' ? '' : v;
134
+ }
135
+
136
+ /** render_grant's header line, `]` included, over byte strings. */
137
+ function grantHeader(fromLineage , compactedAt , branch ) {
138
+ const at = known(compactedAt);
139
+ const br = known(branch);
140
+ return `[Granted summary from lineage ${fromLineage ?? ''}${at ? `, compacted ${at}` : ''}${br ? ` on branch ${br}` : ''}]`;
141
+ }
142
+
143
+ /** A frontmatter value is one line: an embedded line break would forge a key. */
144
+ function oneLine(value ) {
145
+ return value.replace(/[\r\n]/g, ' ');
146
+ }
147
+
148
+ function utf8(value ) {
149
+ return Buffer.from(value, 'latin1').toString('utf8');
150
+ }
151
+
152
+ /** `mktemp <dir>/<prefix>XXXXXX`: an exclusively created 0600 file with a six-character suffix. */
153
+ function makeTemp(dir , prefix ) {
154
+ for (let tries = 0; tries < 100; tries++) {
155
+ const bytes = crypto.randomBytes(6);
156
+ let suffix = '';
157
+ for (const b of bytes) suffix += SUFFIX_CHARS[b % SUFFIX_CHARS.length];
158
+ const file = path.join(dir, `${prefix}${suffix}`);
159
+ try {
160
+ fs.closeSync(fs.openSync(file, 'wx', 0o600));
161
+ return { file, suffix };
162
+ } catch (error ) {
163
+ if (error?.code !== 'EEXIST') throw error;
164
+ }
165
+ }
166
+ throw new Error(`cannot create a temporary file in ${dir}`);
167
+ }
168
+
169
+ function removeQuietly(file ) {
170
+ try {
171
+ fs.rmSync(file, { force: true });
172
+ } catch {
173
+ // Best effort, as `rm -f ... || true`.
174
+ }
175
+ }
176
+
177
+ /**
178
+ * `doflow_summary_scope`: summaries/<worktreeKey>/<run>/<lineage> under stateDir(), or null when run
179
+ * or lineage is not an id, the lineage is `solo` (no scope directory), the worktree is empty or there
180
+ * is no config home.
181
+ */
182
+ function summaryScope(worktree , run , lineage , env = process.env) {
183
+ if (!idOk(run) || !idOk(lineage) || lineage === 'solo' || !worktree) return null;
184
+ const root = stateDir(env);
185
+ return root === null ? null : path.join(root, 'summaries', cwdHash(worktree), run, lineage);
186
+ }
187
+
188
+
189
+
190
+ /**
191
+ * `doflow_summary_peek` (IC-012 owner steps 1 and 2): `none` when the scope holds no regular,
192
+ * non-symlink summary.md, `own-write` when its frontmatter session_id is this session's, else
193
+ * `pending`; file is its path when not `none`.
194
+ */
195
+ function peekSummary(worktree , run , lineage , sessionId , env = process.env) {
196
+ const scope = summaryScope(worktree, run, lineage, env);
197
+ if (scope === null) return { own: 'none', file: null, scope: null };
198
+ const file = path.join(scope, 'summary.md');
199
+ if (!regularFile(file)) return { own: 'none', file: null, scope };
200
+ const session = readHead(file).session_id ?? '';
201
+ const own = sessionId && utf8(session) === sessionId ? 'own-write' : 'pending';
202
+ return { own, file, scope };
203
+ }
204
+
205
+ /**
206
+ * `doflow_summary_claim` (IC-012 owner steps 1 to 3): peek, then rename summary.md to
207
+ * `.claimed.<unitId>`; a summary written after the rename is a new summary.md and survives. The
208
+ * claimed copy is cut to its first 65536 bytes. claimed is set only when this caller claimed it;
209
+ * bytes is the claimed file's full size before the cut; origin is the summary.md it came from. The
210
+ * caller removes the claimed file, or gives it back with releaseClaim().
211
+ */
212
+ function claimSummary(worktree , run , lineage , unitId , sessionId , env = process.env) {
213
+ const peek = peekSummary(worktree, run, lineage, sessionId, env);
214
+ if (peek.own !== 'pending' || peek.file === null || peek.scope === null) return { own: peek.own, claimed: null, bytes: 0 };
215
+ if (!UNIT_PATTERN.test(unitId)) return { own: peek.own, claimed: null, bytes: 0 };
216
+ const claimed = path.join(peek.scope, `.claimed.${unitId}`);
217
+ try {
218
+ fs.renameSync(peek.file, claimed);
219
+ } catch {
220
+ return { own: 'none', claimed: null, bytes: 0 };
221
+ }
222
+ const bytes = fileSize(claimed) ?? 0;
223
+ if (bytes > READ_BOUND) {
224
+ try {
225
+ fs.truncateSync(claimed, READ_BOUND);
226
+ } catch {
227
+ removeQuietly(claimed);
228
+ return { own: peek.own, claimed: null, bytes };
229
+ }
230
+ }
231
+ return { own: peek.own, claimed, bytes, origin: peek.file };
232
+ }
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+ /**
245
+ * `doflow_grant_peek` (IC-012 grant steps 1 and 2): the oldest grants/<run>/<lineage>/<summaryId>.md
246
+ * by name among regular, non-symlink files whose name matches the summary id pattern. It fits
247
+ * exactly when the UTF-8 bytes of render_grant's segment "<header>\n<body>" are at most maxBytes.
248
+ */
249
+ function peekGrant(run , lineage , maxBytes , env = process.env) {
250
+ const none = { grant: 'none', file: null, id: null, bytes: null };
251
+ if (!idOk(run) || !idOk(lineage)) return none;
252
+ const max = String(maxBytes);
253
+ if (!MAX_BYTES_PATTERN.test(max)) return none;
254
+ const root = stateDir(env);
255
+ if (root === null) return none;
256
+ const dir = path.join(root, 'grants', run, lineage);
257
+ let names ;
258
+ try {
259
+ if (!fs.lstatSync(dir).isDirectory()) return none;
260
+ names = fs.readdirSync(dir);
261
+ } catch {
262
+ return none;
263
+ }
264
+ // The glob `s-*.md` in C collation: byte order, which for these ASCII names is code-unit order.
265
+ const candidates = names.filter((n) => n.startsWith('s-') && n.endsWith('.md')).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
266
+ let file = null;
267
+ let id = null;
268
+ for (const name of candidates) {
269
+ const stem = name.slice(0, -3);
270
+ if (!SUMMARY_ID_PATTERN.test(stem)) continue;
271
+ const f = path.join(dir, name);
272
+ if (!regularFile(f)) continue;
273
+ file = f;
274
+ id = stem;
275
+ break;
276
+ }
277
+ if (file === null) return none;
278
+ const size = fileSize(file);
279
+ // A frontmatter needs far less than 65536 bytes: anything larger than that plus the budget is over.
280
+ if (size === null || size > Number(max) + READ_BOUND) return { grant: 'over', file, id, bytes: null };
281
+ const raw = readBytes(file);
282
+ if (raw === null) return { grant: 'over', file, id, bytes: null };
283
+ const bytes = parseRecord(raw).body.length;
284
+ const head = readHead(file);
285
+ const header = grantHeader(head.from_lineage, head.compacted_at, head.branch);
286
+ return { grant: header.length + 1 + bytes <= Number(max) ? 'fits' : 'over', file, id, bytes };
287
+ }
288
+
289
+ /**
290
+ * `doflow_grant_claim` (IC-012 grant step 3) for one grant the caller already peeked and fitted
291
+ * (DEC-034): rename exactly grants/<run>/<lineage>/<summaryId>.md to
292
+ * `.claimed.<unitId>.<summaryId>`. A grant that vanished, is no longer a regular file, or a bad id
293
+ * is `none` with claimed null; no other grant is ever taken in its place. The caller removes the
294
+ * claimed file after rendering, or puts it back with releaseClaim().
295
+ */
296
+ function claimGrant(run , lineage , unitId , summaryId , env = process.env) {
297
+ const none = { grant: 'none' , id: null, claimed: null, origin: null };
298
+ if (!idOk(run) || !idOk(lineage) || !UNIT_PATTERN.test(unitId) || !SUMMARY_ID_PATTERN.test(summaryId)) return none;
299
+ const root = stateDir(env);
300
+ if (root === null) return none;
301
+ const origin = path.join(root, 'grants', run, lineage, `${summaryId}.md`);
302
+ if (!regularFile(origin)) return none;
303
+ const claimed = path.join(path.dirname(origin), `.claimed.${unitId}.${summaryId}`);
304
+ try {
305
+ fs.renameSync(origin, claimed);
306
+ } catch {
307
+ return none;
308
+ }
309
+ return { grant: 'fits', id: summaryId, claimed, origin };
310
+ }
311
+
312
+ /**
313
+ * Give a claimed summary or grant back to the name it was claimed from (DEC-038), never replacing
314
+ * a file that appeared there since: a hard link that refuses to replace, then an unlink of the
315
+ * claimed name. True when the content is back under `origin`. Without hard links, or when a newer
316
+ * file holds the name, the claimed file is left where it is.
317
+ */
318
+ function releaseClaim(claimed , origin ) {
319
+ try {
320
+ fs.linkSync(claimed, origin);
321
+ } catch {
322
+ return false;
323
+ }
324
+ removeQuietly(claimed);
325
+ return true;
326
+ }
327
+
328
+ // ── IC-014: the pre-078 project summary (DEC-043) ───────────────────────────────────────────────
329
+
330
+ /**
331
+ * The pre-078 project summary under projects/<cwdHash cwd>/ when it exists as a regular file and
332
+ * not a symlink, else null. This and adoptLegacy() are the TypeScript side's only use of the name.
333
+ */
334
+ function legacySummary(cwd , env = process.env) {
335
+ const root = stateDir(env);
336
+ if (!cwd || root === null) return null;
337
+ const old = path.join(root, 'projects', cwdHash(cwd), 'last-compact-summary.md');
338
+ return regularFile(old) ? old : null;
339
+ }
340
+
341
+ /**
342
+ * `doflow_summary_adopt_legacy` (IC-014, action `adopt` only): move the legacy summary once into
343
+ * summaries/<worktreeKey>/local/main/summary.md, frontmatter kept. The legacy file is first claimed
344
+ * by a rename to `.adopting.<pid>`, so of two concurrent adopters exactly one proceeds; the loser
345
+ * returns false and changes nothing. The claimed file is then hard-linked to summary.md, which
346
+ * refuses to replace: a summary already pending there is newer by construction and the legacy copy
347
+ * is retired instead. Only when the link fails with summary.md absent (no hard links on this
348
+ * filesystem) is it renamed into place after a re-check; that fallback is check-then-rename and so
349
+ * can replace a summary written in the same instant (the ceiling it accepts). The `.adopting` name
350
+ * is unlinked last. True when this call adopted or retired the legacy file.
351
+ */
352
+ function adoptLegacy(cwd , worktree , env = process.env) {
353
+ const old = legacySummary(cwd, env);
354
+ if (old === null) return false;
355
+ const scope = summaryScope(worktree || cwd, 'local', 'main', env);
356
+ if (scope === null) return false;
357
+ try {
358
+ fs.mkdirSync(scope, { recursive: true });
359
+ } catch {
360
+ return false;
361
+ }
362
+ const adopting = `${old}.adopting.${process.pid}`;
363
+ try {
364
+ fs.renameSync(old, adopting);
365
+ } catch {
366
+ return false;
367
+ }
368
+ const target = path.join(scope, 'summary.md');
369
+ try {
370
+ fs.linkSync(adopting, target);
371
+ } catch {
372
+ if (!present(target)) {
373
+ try {
374
+ fs.renameSync(adopting, target);
375
+ return true;
376
+ } catch {
377
+ // Neither a link nor a rename: the file is left under its .adopting name for a later
378
+ // cleanup; the retired legacy path is never written again (G38.4).
379
+ return false;
380
+ }
381
+ }
382
+ }
383
+ removeQuietly(adopting);
384
+ return true;
385
+ }
386
+
387
+ function present(file ) {
388
+ try {
389
+ fs.lstatSync(file);
390
+ return true;
391
+ } catch {
392
+ return false;
393
+ }
394
+ }
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+ /**
414
+ * IC-013 in process: copy one summary's body into grants/<toRun>/<toLineage>/<summaryId>.md,
415
+ * written atomically and refusing when that file exists; the owner's summary is untouched. Every
416
+ * refusal happens before any write and throws a SummaryStoreError whose exitCode is the verb's exit.
417
+ */
418
+ function grantSummary(options ) {
419
+ const env = options.env ?? process.env;
420
+ const fromWorktree = options.fromWorktree ?? (env.DOFLOW_WORKTREE || process.cwd());
421
+ const fromRun = options.fromRun ?? (env.DOFLOW_RUN_ID || 'local');
422
+ const fromLineage = options.fromLineage ?? (env.DOFLOW_LINEAGE_ID || 'main');
423
+ const max = String(options.maxBytes ?? DEFAULT_GRANT_MAX_BYTES);
424
+ const wanted = options.summary;
425
+
426
+ if (typeof wanted !== 'string' || (wanted !== 'pending' && !SUMMARY_ID_PATTERN.test(wanted))) {
427
+ throw new SummaryStoreError('usage', '--summary must be `pending` or a summary id');
428
+ }
429
+ if (!idOk(options.toRun) || !idOk(options.toLineage)) throw new SummaryStoreError('usage', '--to-run and --to-lineage must be ids');
430
+ // DEC-039: grants are read only by launched identities, so a recipient no launched unit can be
431
+ // (lineage solo, or the default main scope local/main) would reach unrelated sessions instead.
432
+ if (options.toLineage === 'solo') throw new SummaryStoreError('usage', '--to-lineage solo is not a launched recipient');
433
+ if (options.toRun === 'local' && options.toLineage === 'main') {
434
+ throw new SummaryStoreError('usage', '--to-run local --to-lineage main is the default main scope, not a launched recipient');
435
+ }
436
+ if (!MAX_BYTES_PATTERN.test(max)) throw new SummaryStoreError('usage', '--max-bytes must be a positive integer');
437
+ const root = stateDir(env);
438
+ if (root === null) throw new SummaryStoreError('usage', 'no config home resolves (set HOME or an absolute XDG_CONFIG_HOME), so there is no session store to grant into');
439
+ const scope = summaryScope(fromWorktree, fromRun, fromLineage, env);
440
+ if (scope === null) throw new SummaryStoreError('usage', 'the source scope needs a worktree, a run id and a non-solo lineage id');
441
+
442
+ const source = path.join(scope, 'summary.md');
443
+ if (!regularFile(source)) throw new SummaryStoreError('summary-not-found', `no pending summary in ${scope}`);
444
+ const head = readHead(source);
445
+ const summaryId = head.summary_id;
446
+ if (summaryId === undefined || !SUMMARY_ID_PATTERN.test(summaryId)) {
447
+ throw new SummaryStoreError('summary-legacy', 'the pending summary has no summary_id and cannot be granted');
448
+ }
449
+ if (wanted !== 'pending' && wanted !== summaryId) throw new SummaryStoreError('summary-not-found', `summary ${wanted} is not pending in ${scope}`);
450
+
451
+ // Byte strings throughout, so the header counted here is the one peekGrant() counts on the copy.
452
+ const compactedAt = head.compacted_at ?? 'unknown';
453
+ const branch = head.branch ?? 'unknown';
454
+ const header = grantHeader(fromLineage, compactedAt, branch);
455
+ const size = fileSize(source);
456
+ const raw = size === null || size > Number(max) + READ_BOUND ? null : readBytes(source);
457
+ const body = raw === null ? null : parseRecord(raw).body;
458
+ if (body === '') throw new SummaryStoreError('summary-not-found', 'the pending summary has an empty body');
459
+ if (body === null || header.length + 1 + body.length > Number(max)) {
460
+ throw new SummaryStoreError('over-budget', `the granted summary exceeds the ${max}-byte budget`);
461
+ }
462
+
463
+ const dir = path.join(root, 'grants', options.toRun, options.toLineage);
464
+ const target = path.join(dir, `${summaryId}.md`);
465
+ if (fs.existsSync(target) || isLink(target)) throw new SummaryStoreError('grant-exists', `grant ${summaryId} already present for ${options.toRun}/${options.toLineage}`);
466
+
467
+ const grantedAt = (options.now ?? new Date()).toISOString().replace(/\.[0-9]{3}Z$/, 'Z');
468
+ const record = '---\n'
469
+ + `summary_id: ${summaryId}\n`
470
+ + `granted_at: ${grantedAt}\n`
471
+ + `from_worktree: ${Buffer.from(oneLine(fromWorktree), 'utf8').toString('latin1')}\n`
472
+ + `from_run: ${fromRun}\n`
473
+ + `from_lineage: ${fromLineage}\n`
474
+ + `to_run: ${options.toRun}\n`
475
+ + `to_lineage: ${options.toLineage}\n`
476
+ + `compacted_at: ${compactedAt}\n`
477
+ + `branch: ${branch}\n`
478
+ + `bytes: ${body.length}\n`
479
+ + '---\n\n'
480
+ + `${body}\n`;
481
+ fs.mkdirSync(dir, { recursive: true });
482
+ const { file: tmp } = makeTemp(dir, '.grant.');
483
+ try {
484
+ fs.writeFileSync(tmp, Buffer.from(record, 'latin1'));
485
+ publishNoReplace(tmp, target, summaryId, options.toRun, options.toLineage);
486
+ } finally {
487
+ removeQuietly(tmp);
488
+ }
489
+ return { summaryId, path: target, bytes: body.length };
490
+ }
491
+
492
+ function isLink(file ) {
493
+ try {
494
+ return fs.lstatSync(file).isSymbolicLink();
495
+ } catch {
496
+ return false;
497
+ }
498
+ }
499
+
500
+ /** A hard link refuses to replace an existing grant; without hard links, re-check and rename. */
501
+ function publishNoReplace(tmp , target , summaryId , toRun , toLineage ) {
502
+ try {
503
+ fs.linkSync(tmp, target);
504
+ return;
505
+ } catch (error ) {
506
+ if (error?.code === 'EEXIST') throw new SummaryStoreError('grant-exists', `grant ${summaryId} already present for ${toRun}/${toLineage}`);
507
+ }
508
+ if (fs.existsSync(target) || isLink(target)) throw new SummaryStoreError('grant-exists', `grant ${summaryId} already present for ${toRun}/${toLineage}`);
509
+ fs.renameSync(tmp, target);
510
+ }
511
+
512
+ // ── 080 IC-014: the run scope's persisted context, read only (DEC-010) ──────────────────────────
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+ /** A recorded header value as UTF-8, or null when absent, empty or `unknown` (emission.jq's `known`). */
534
+ function recorded(value ) {
535
+ const v = known(value);
536
+ return v ? utf8(v) : null;
537
+ }
538
+
539
+ function listNames(dir ) {
540
+ try {
541
+ if (!fs.lstatSync(dir).isDirectory()) return [];
542
+ return fs.readdirSync(dir).sort((a , b ) => (a < b ? -1 : a > b ? 1 : 0));
543
+ } catch {
544
+ return [];
545
+ }
546
+ }
547
+
548
+ /**
549
+ * IC-014: the pending summary, leftover `.claimed.<unitId>` copies of the summary scope, and the
550
+ * grants addressed to (run, lineage), each with what its header records. Headers are read with the
551
+ * bounded readHead(); nothing is renamed, removed or written. Empty when summaryScope() is null.
552
+ * 078 headers record no commit today, so `commit` is null unless a header carries a `commit` field.
553
+ */
554
+ function scopeContext(worktree , run , lineage , env = process.env) {
555
+ const empty = { pending: null, claimed: [], grants: [] };
556
+ const scope = summaryScope(worktree, run, lineage, env);
557
+ if (scope === null) return empty;
558
+
559
+ let pending = null;
560
+ const claimed = [];
561
+ for (const name of listNames(scope)) {
562
+ const file = path.join(scope, name);
563
+ if (name === 'summary.md') {
564
+ if (!regularFile(file)) continue;
565
+ const head = readHead(file);
566
+ const id = head.summary_id;
567
+ pending = {
568
+ path: file,
569
+ summaryId: id !== undefined && SUMMARY_ID_PATTERN.test(id) ? id : null,
570
+ branch: recorded(head.branch),
571
+ commit: recorded(head.commit),
572
+ worktree: recorded(head.worktree),
573
+ compactedAt: recorded(head.compacted_at),
574
+ };
575
+ } else if (name.startsWith('.claimed.')) {
576
+ const unitId = name.slice('.claimed.'.length);
577
+ if (UNIT_PATTERN.test(unitId) && regularFile(file)) claimed.push({ path: file, unitId });
578
+ }
579
+ }
580
+
581
+ const grants = [];
582
+ const root = stateDir(env);
583
+ const dir = path.join(root ?? '', 'grants', run, lineage);
584
+ for (const name of root === null ? [] : listNames(dir)) {
585
+ if (!name.endsWith('.md')) continue;
586
+ const summaryId = name.slice(0, -3);
587
+ const file = path.join(dir, name);
588
+ if (!SUMMARY_ID_PATTERN.test(summaryId) || !regularFile(file)) continue;
589
+ const head = readHead(file);
590
+ grants.push({
591
+ path: file,
592
+ summaryId,
593
+ branch: recorded(head.branch),
594
+ commit: recorded(head.commit),
595
+ fromWorktree: recorded(head.from_worktree),
596
+ compactedAt: recorded(head.compacted_at),
597
+ });
598
+ }
599
+ return { pending, claimed, grants };
600
+ }
601
+
602
+
603
+
604
+
605
+ /**
606
+ * IC-014's comparison, pure: the caller probes the current branch (null when HEAD is detached or
607
+ * unreadable) and commit outside any lock. Both branches known: compare them. Both sides detached,
608
+ * with a recorded and a current commit: compare commits (critic A m9). Otherwise `branch-unknown`.
609
+ * A recorded worktree that is not the current one adds `worktree-differs`. Leftover claimed copies
610
+ * are always `claimed-not-consumed`. Returns every stale item, pending first, then claimed, then grants.
611
+ */
612
+ function compareScopeContext(context , current ) {
613
+ const reasonsFor = (branch , commit , worktree ) => {
614
+ const reasons = [];
615
+ if (branch !== null && current.branch) {
616
+ if (branch !== current.branch) reasons.push('branch-differs');
617
+ } else if (branch === null && !current.branch && commit !== null && current.commit) {
618
+ if (commit !== current.commit) reasons.push('commit-differs');
619
+ } else {
620
+ reasons.push('branch-unknown');
621
+ }
622
+ if (worktree !== null && worktree !== current.worktree) reasons.push('worktree-differs');
623
+ return reasons;
624
+ };
625
+ const stale = [];
626
+ const p = context.pending;
627
+ if (p !== null) {
628
+ const reasons = reasonsFor(p.branch, p.commit, p.worktree);
629
+ if (reasons.length) stale.push({ kind: 'summary', path: p.path, reasons });
630
+ }
631
+ for (const c of context.claimed) stale.push({ kind: 'claimed-summary', path: c.path, reasons: ['claimed-not-consumed'] });
632
+ for (const g of context.grants) {
633
+ const reasons = reasonsFor(g.branch, g.commit, g.fromWorktree);
634
+ if (reasons.length) stale.push({ kind: 'grant', path: g.path, reasons });
635
+ }
636
+ return stale;
637
+ }
638
+
639
+ module.exports = {
640
+ DEFAULT_GRANT_MAX_BYTES,
641
+ GRANT_EXIT,
642
+ SUMMARY_ID_PATTERN,
643
+ SummaryStoreError,
644
+ adoptLegacy,
645
+ claimGrant,
646
+ claimSummary,
647
+ compareScopeContext,
648
+ grantSummary,
649
+ legacySummary,
650
+ peekGrant,
651
+ peekSummary,
652
+ releaseClaim,
653
+ scopeContext,
654
+ summaryScope,
655
+ };