@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,209 @@
1
+ # emission.jq — one-pass renderer of the prompt segments and the hook response (078 IC-002, IC-004
2
+ # step 7). renderEmission() in src/runtime/context/arbitrate.ts is the reference; both must give
3
+ # expected.text, bytes, refused and dropped of test/fixtures/emission/render-corpus.json byte for
4
+ # byte (IC-022, RK7).
5
+ #
6
+ # Invocation (IC-004 step 7), one process:
7
+ # jq -n -r -f emission.jq --argjson plan "$PLAN" \
8
+ # --rawfile git <git-context.json> --rawfile summary <claimed summary> \
9
+ # --rawfile grant <claimed grant> --arg warning "$WARNING" --arg nudge "$NUDGE"
10
+ #
11
+ # All four names are always bound (jq refuses an undefined $name); emission.sh binds $warning with
12
+ # --arg, because the warning claim renames the file and reads it before the render. A file that does not
13
+ # exist is passed as /dev/null and its name listed in plan.absent; an empty existing file is not
14
+ # absent (an empty warning still renders, an empty git capture is "Not a git repository.").
15
+ #
16
+ # plan:
17
+ # actions IC-001 actions by segment; only git, summary, warning, grant and nudge render here
18
+ # maxBytes positive integer, the IC-002 byte budget
19
+ # harness the harness name (DOFLOW_AGENT); sessionTitle is added on every harness but
20
+ # "codex", whose envelope rejects unknown fields (DEC-030)
21
+ # title true when a title may be set (the payload carries no session_title)
22
+ # absent names among git, summary, grant, warning whose file does not exist (default [])
23
+ # summaryBytes the claimed summary file's full size, which can exceed the bounded read; it
24
+ # defaults to the UTF-8 size of $summary
25
+ # mode "response" (default) prints the hook response; "render" prints
26
+ # {text, bytes, refused, dropped, segments}, the renderEmission() result, for parity;
27
+ # "hook" (emission.sh, run with -r) prints one first line holding the dropped
28
+ # segment names joined with tabs (possibly empty), a second line holding the
29
+ # response digest (empty unless plan.digest), then the hook response
30
+ # digest hook mode: true computes response_digest (rider R1, 091); other modes ignore it
31
+ #
32
+ # Response bytes (jq's default pretty print): emission is
33
+ # {"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": ...}}
34
+ # with sessionTitle on every harness but Codex (DEC-030), when plan.title holds and the git action built a title
35
+ # ("{branch?unknown} — {sha?unknown}", or "no-git" outside a repository; none when the capture
36
+ # file is missing). Nothing rendered prints {} and a newline. Any error exits non-zero and the
37
+ # caller answers with the silent response.
38
+ #
39
+ # Counting (design R1): characters are code points (jq string length and slices), budgets are
40
+ # UTF-8 bytes (utf8bytelength). Portability: jq 1.6+ built with Oniguruma; utf8bytelength is the
41
+ # one builtin newer than 1.5. No index/rindex on strings (byte offsets in 1.6) and no 1.7-only
42
+ # builtin (trim, ltrim, rtrim, abs, pick, toarray, have_decnum). Every regex is anchored
43
+ # with \A or \z, never ^ or $, which Oniguruma reads per line.
44
+
45
+ def summary_cap: 4000;
46
+ def summary_read: 65536;
47
+
48
+ # Trailing newlines removed, as $(...) does.
49
+ def rstrip_nl: sub("\n+\\z"; "");
50
+ def no_cr: split("\r") | join("");
51
+
52
+ # `jq -r '<field> // empty'` in a $(...): null and false are empty, other scalars print as JSON.
53
+ def jq_field:
54
+ if . == null or . == false then ""
55
+ elif type == "string" then rstrip_nl
56
+ else tojson | rstrip_nl
57
+ end;
58
+
59
+ # A summary or grant record: \r removed, trailing newlines dropped, frontmatter keys from the lines
60
+ # between the first two `---` lines (first match wins, spaces after `key:` stripped), body from the
61
+ # first non-empty line after the second `---`, trailing newlines dropped. Fewer than two `---`
62
+ # lines give an empty body.
63
+ def parse_record:
64
+ (no_cr | rstrip_nl | split("\n"))
65
+ | reduce .[] as $line ({fences: 0, started: false, fields: {}, body: []};
66
+ if .fences < 2 and $line == "---" then .fences += 1
67
+ elif .fences == 1 then
68
+ ($line | split(":")) as $parts
69
+ | if ($parts | length) > 1 and ($parts[0] | length) > 0 and (.fields | has($parts[0]) | not)
70
+ then .fields[$parts[0]] = ($parts[1:] | join(":") | sub("\\A +"; ""))
71
+ else . end
72
+ elif .fences >= 2 and (.started or $line != "") then .started = true | .body += [$line]
73
+ else . end)
74
+ | {fields: .fields, body: (.body | join("\n") | rstrip_nl)};
75
+
76
+ # A bracketed header value: `]` removed, then empty when it is "unknown".
77
+ def known: (. // "") | split("]") | join("") | if . == "unknown" then "" else . end;
78
+
79
+ def header_tail:
80
+ (.compacted_at | known) as $at | (.branch | known) as $branch
81
+ | (if $at != "" then ", compacted \($at)" else "" end)
82
+ + (if $branch != "" then " on branch \($branch)" else "" end);
83
+
84
+ # The git capture as an object; anything unparseable or not an object reads as {}.
85
+ def capture($raw):
86
+ (try ($raw | fromjson) catch null) | if type == "object" then . else {} end;
87
+
88
+ def is_repo: (.is_git_repo | jq_field) == "true";
89
+
90
+ def render_git($raw; $missing):
91
+ if $missing then "Git context unavailable for this session."
92
+ else capture($raw)
93
+ | if is_repo | not then "Not a git repository."
94
+ else
95
+ ((if (.commits | type) == "array" then .commits else [] end)
96
+ | map(jq_field) | map(select(. != "")) | .[0:5] | join(" · ")) as $commits
97
+ | (.stash_count | jq_field) as $stash
98
+ | "Git context — branch: \(.branch | jq_field | if . == "" then "unknown" else . end) | \(.sha | jq_field | if . == "" then "unknown" else . end)\n"
99
+ + "Last commits: \(if $commits == "" then "none" else $commits end)\n"
100
+ + "Uncommitted files: \(.uncommitted_count | jq_field | if . == "" then "0" else . end)"
101
+ + (if ($stash | test("\\A[0-9]+\\z")) and ($stash | tonumber) > 0 then " | Stashed: \($stash)" else "" end)
102
+ end
103
+ end;
104
+
105
+ def git_title($raw; $missing):
106
+ if $missing then null
107
+ else capture($raw)
108
+ | if is_repo then
109
+ "\(.branch | jq_field | if . == "" then "unknown" else . end) — \(.sha | jq_field | if . == "" then "unknown" else . end)"
110
+ else "no-git" end
111
+ end;
112
+
113
+ # null when the body is empty: an empty summary renders nothing.
114
+ def render_summary($size):
115
+ parse_record
116
+ | if .body == "" then null
117
+ else (.body | length) as $n
118
+ | (if $n > summary_cap
119
+ then .body[0:summary_cap] + "\n[summary truncated: first \(summary_cap) of \(if $size > summary_read then "at least " else "" end)\($n) characters]"
120
+ else .body end) as $shown
121
+ | "\n\n[Prior session summary\(.fields | header_tail)]\n\($shown)"
122
+ end;
123
+
124
+ def render_grant: "[Granted summary from lineage \(.fields.from_lineage // "")\(.fields | header_tail)]\n\(.body)";
125
+
126
+ def render_pointer($max): "[Granted summary \(.fields.summary_id // "") withheld: \(.body | utf8bytelength) bytes exceed the \($max)-byte context budget]";
127
+
128
+ def join_order: ["git", "summary", "warning", "grant", "nudge"];
129
+
130
+ # IC-002 join: summary drops its leading blank line when nothing precedes it; grant and nudge gain
131
+ # one when something does.
132
+ def join_segments:
133
+ . as $p
134
+ | reduce (join_order[] | select($p[.] != null)) as $s ("";
135
+ if $s == "summary" then (if . == "" then . + ($p[$s] | .[2:]) else . + $p[$s] end)
136
+ elif $s == "grant" or $s == "nudge" then (if . == "" then "" else . + "\n\n" end) + $p[$s]
137
+ else . + $p[$s] end);
138
+
139
+ def render($plan; $in):
140
+ ($plan.actions // {}) as $a
141
+ | $plan.maxBytes as $max
142
+ | if ($max | type) != "number" or $max <= 0 or ($max | floor) != $max then error("plan.maxBytes must be a positive integer") else . end
143
+ | ($plan.absent // []) as $absent
144
+ | def present($k): ($absent | map(select(. == $k)) | length) == 0;
145
+ ({}
146
+ | if $a.git == "include" then .git = render_git($in.git; present("git") | not) else . end
147
+ | if $a.summary == "truncate" and present("summary")
148
+ then ($in.summary | render_summary($plan.summaryBytes // ($in.summary | utf8bytelength))) as $s
149
+ | if $s != null then .summary = $s else . end
150
+ else . end
151
+ | if $a.warning == "include" and present("warning") then .warning = "\n[Prior session warning: \($in.warning | rstrip_nl)]" else . end
152
+ | if $a.nudge == "include" and ($in.nudge | type) == "string" and $in.nudge != "" then .nudge = $in.nudge else . end
153
+ ) as $optional
154
+ | (if ($a.grant == "whole" or $a.grant == "pointer") and present("grant")
155
+ then ($in.grant | parse_record) as $r
156
+ | ($r | render_grant) as $whole
157
+ | if $a.grant == "whole" and ($whole | utf8bytelength) <= $max
158
+ then {selected: {grant: $whole}, refused: false}
159
+ else {selected: {grant: ($r | render_pointer($max))}, refused: true} end
160
+ else {selected: {}, refused: false} end)
161
+ | . + {dropped: []}
162
+ | reduce ("git", "summary", "warning", "nudge") as $s (.;
163
+ if $optional[$s] == null then .
164
+ elif (.selected + {($s): $optional[$s]} | join_segments | utf8bytelength) <= $max then .selected[$s] = $optional[$s]
165
+ else .dropped += [$s] end)
166
+ | (.selected | join_segments) as $text
167
+ | {
168
+ text: (if $text == "" then null else $text end),
169
+ bytes: ($text | utf8bytelength),
170
+ refused: .refused,
171
+ dropped: .dropped,
172
+ segments: [join_order[] as $s | select(.selected[$s] != null) | $s]
173
+ };
174
+
175
+ def response($plan; $in; $result):
176
+ if $result.text == null then {}
177
+ else
178
+ (if $plan.harness != "codex" and $plan.title == true and ($plan.actions // {}).git == "include"
179
+ then git_title($in.git; ($plan.absent // []) | map(select(. == "git")) | length > 0) else null end) as $title
180
+ | {"hookSpecificOutput": ({"hookEventName": "UserPromptSubmit", "additionalContext": $result.text}
181
+ + (if $title != null and $title != "" then {"sessionTitle": $title} else {} end))}
182
+ end;
183
+
184
+ # Rider R1 (091): the response exactly as jq's default pretty print writes it (indent 2, insertion
185
+ # order, the same string escaping as tojson), without the trailing newline. The response shape is
186
+ # fixed: {} or one hookSpecificOutput object of string values.
187
+ def pretty_response:
188
+ if . == {} then "{}"
189
+ else "{\n \"hookSpecificOutput\": {\n"
190
+ + ([.hookSpecificOutput | to_entries[] | " \(.key | tojson): \(.value | tojson)"] | join(",\n"))
191
+ + "\n }\n}"
192
+ end;
193
+
194
+ # 8 lowercase hex of h = (h*31 + c) mod 2^32 over the code points of the pretty response. Every step
195
+ # stays below 2^38, exact in doubles; mod and the nibbles use floor, not %, which casts to int on old jq.
196
+ def response_digest:
197
+ (pretty_response | explode | reduce .[] as $c (0; (. * 31 + $c) | . - ((. / 4294967296) | floor) * 4294967296)) as $h
198
+ | [268435456, 16777216, 1048576, 65536, 4096, 256, 16, 1]
199
+ | map(($h / . | floor) - (($h / . / 16) | floor) * 16)
200
+ | map("0123456789abcdef"[.:. + 1]) | join("");
201
+
202
+ {git: $git, summary: $summary, grant: $grant, warning: $warning, nudge: $nudge} as $in
203
+ | render($plan; $in) as $result
204
+ | ($plan.mode // "response") as $mode
205
+ | if $mode == "render" then $result
206
+ elif $mode == "hook" then
207
+ response($plan; $in; $result) as $response
208
+ | ($result.dropped | join("\t")), (if $plan.digest == true then $response | response_digest else "" end), $response
209
+ else response($plan; $in; $result) end
@@ -0,0 +1,383 @@
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
+ # emission.sh — Canonical Policy Library: the hook emission adapter and the emission ledger
4
+ # (feature 078, IC-004, IC-015; DEC-015, DEC-016, DEC-017).
5
+ #
6
+ # The prompt path's emission, from the state bits to the response bytes. arbitrate() in
7
+ # src/runtime/context/arbitrate.js is the reference for the rules; this file reads the compiled
8
+ # table (emission-rules.sh), claims through summary-store.sh and renders with one jq process running
9
+ # emission.jq. It starts no Node or doflow-run process.
10
+ #
11
+ # Sourcing this file defines functions only: no option, no variable, no trap, no output. bash 3.2
12
+ # safe and safe under the caller's `set -euo pipefail`. The caller has sourced lib.sh (which sources
13
+ # identity.sh), summary-store.sh and emission-rules.sh; a missing one is a fault.
14
+ #
15
+ # Store, under SESSION_DIR (lib.sh; session-env/sessions; empty with no config home, which prints
16
+ # the silent response with reason silent:no-home):
17
+ # <unitId>/injected one-shot bootstrap slot, created exclusively (set -C) by the first caller
18
+ # <unitId>/emitted the IC-015 ledger: <ISO-8601 UTC>\t<kind>\t<key>\t<outcome>\t<caller>, one
19
+ # line per (kind, key, outcome), appended only when that triple is absent
20
+ #
21
+ # Faults: any claim or render failure prints the silent response ({} and a newline) and records
22
+ # `doflow_capture_failure user-prompt-submit emission-<point>`, point one of deps, unit, slot,
23
+ # summary, grant, warning, render; a failed ledger append records emission-ledger and keeps the
24
+ # rendered response. A fault after the slot claim releases the slot this call created, so the next
25
+ # prompt bootstraps again (DEC-038); a fault in a later claim renames the summary, grant and warning
26
+ # already claimed back into place (`mv -n`, never over a newer file). A claim that fails because its
27
+ # source vanished (another caller won it) is no fault: that segment counts as absent and the
28
+ # bootstrap continues. A claimed summary or grant whose render fails is not restored.
29
+ #
30
+ # Ledger outcomes besides refused:mandatory-over-budget: `optional-dropped <key> over-budget`, one
31
+ # per optional segment the budget dropped (key: the summary id for summary, else the segment name;
32
+ # DEC-041). A dropped nudge leaves the session unmarked, so a later prompt can nudge.
33
+
34
+ # _doflow_em_capture <point>: one failure-capture line, never on stdout or stderr.
35
+ _doflow_em_capture() {
36
+ local dir
37
+ _DOFLOW_SRC=${BASH_SOURCE[0]//\\//}; case $_DOFLOW_SRC in */*) dir=${_DOFLOW_SRC%/*} ;; *) dir=. ;; esac
38
+ if declare -F doflow_capture_failure >/dev/null 2>&1; then
39
+ ( doflow_capture_failure user-prompt-submit "emission-$1" ) >/dev/null 2>&1 || true
40
+ else
41
+ ( . "$dir/capture-failure.sh" && doflow_capture_failure user-prompt-submit "emission-$1" ) >/dev/null 2>&1 || true
42
+ fi
43
+ return 0
44
+ }
45
+
46
+ # _doflow_em_restore <claimed> <original>: rename a claimed file back unless a newer one took its
47
+ # place, in which case the claimed copy is the older and is removed.
48
+ _doflow_em_restore() {
49
+ [ -n "$1" ] && [ -e "$1" ] || return 0
50
+ mv -n "$1" "$2" 2>/dev/null || true
51
+ [ ! -e "$1" ] || rm -f "$1" 2>/dev/null || true
52
+ return 0
53
+ }
54
+
55
+ # _doflow_em_release: undo this call's claims before a fault response, from the caller's locals
56
+ # (made, dir, sumfile, sumsrc, claimed_grant, grantsrc, warnclaimed, warnfile). The slot goes last.
57
+ _doflow_em_release() {
58
+ [ "$sumfile" = /dev/null ] || _doflow_em_restore "$sumfile" "$sumsrc"
59
+ [ -z "$claimed_grant" ] || _doflow_em_restore "$claimed_grant" "$grantsrc"
60
+ [ -z "$warnclaimed" ] || _doflow_em_restore "$warnclaimed" "$warnfile"
61
+ sumfile=/dev/null claimed_grant="" warnclaimed=""
62
+ if [ "$made" = 1 ]; then rm -f "$dir/injected" 2>/dev/null || true; made=0; fi
63
+ return 0
64
+ }
65
+
66
+ # _doflow_em_measure <response> <digest>: for the response printed as `printf '%s\n'`, DF_EMIT_BYTES
67
+ # (its byte length under LC_ALL=C, newline included) and DF_EMIT_DIGEST (the renderer's digest
68
+ # header line when it is 8 lowercase hex, else empty). Rider R1 (091): metadata for the hook line
69
+ # only; builtins only, no response byte leaves this function, and nothing reaches stdout.
70
+ _doflow_em_measure() {
71
+ local LC_ALL=C
72
+ DF_EMIT_BYTES=$(( ${#1} + 1 ))
73
+ DF_EMIT_DIGEST=""
74
+ case "${2:-}" in
75
+ [0123456789abcdef][0123456789abcdef][0123456789abcdef][0123456789abcdef][0123456789abcdef][0123456789abcdef][0123456789abcdef][0123456789abcdef]) DF_EMIT_DIGEST=$2 ;;
76
+ esac
77
+ return 0
78
+ }
79
+
80
+ # _doflow_em_silent <point>: the fault response.
81
+ _doflow_em_silent() {
82
+ _doflow_em_capture "$1"
83
+ DF_EMIT_REASON="fault:$1"
84
+ printf '{}\n'
85
+ return 0
86
+ }
87
+
88
+ # _doflow_em_seen <ledger file> <kind> <key> <outcome>: 0 when the triple is already recorded.
89
+ _doflow_em_seen() {
90
+ local ts kind key outcome caller
91
+ [ -f "$1" ] || return 1
92
+ {
93
+ while IFS=$'\t' read -r ts kind key outcome caller || [ -n "$ts" ]; do
94
+ if [ "$kind" = "$2" ] && [ "$key" = "$3" ] && [ "$outcome" = "$4" ]; then return 0; fi
95
+ ts=""
96
+ done
97
+ } 2>/dev/null <"$1"
98
+ return 1
99
+ }
100
+
101
+ # _doflow_em_ledger_add <unit dir> <kind> <key> <outcome> <caller>: append one IC-015 line unless
102
+ # (kind, key, outcome) is already recorded. Returns 1 when the line could not be written.
103
+ _doflow_em_ledger_add() {
104
+ local file="$1/emitted" ts
105
+ if _doflow_em_seen "$file" "$2" "$3" "$4"; then return 0; fi
106
+ mkdir -p "$1" 2>/dev/null || return 1
107
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) || return 1
108
+ printf '%s\t%s\t%s\t%s\t%s\n' "$ts" "$2" "$3" "$4" "$5" >>"$file" 2>/dev/null || return 1
109
+ return 0
110
+ }
111
+
112
+ # _doflow_em_rule <event.segment> <vector>: prints nothing; sets DF_ACTION (skip on any failure).
113
+ _doflow_em_rule() {
114
+ DF_ACTION=skip
115
+ doflow_emission_rule "$1" "$2" || DF_ACTION=skip
116
+ return 0
117
+ }
118
+
119
+ # _doflow_em_actions: the prompt segments' actions from the caller's state locals (role, kind,
120
+ # bootstrapped, subagent, own, grant, refused, warning, nudge_state), vectors in IC-002 order.
121
+ _doflow_em_actions() {
122
+ _doflow_em_rule prompt.slot "$bootstrapped"; a_slot=$DF_ACTION
123
+ _doflow_em_rule prompt.git "$role:$bootstrapped"; a_git=$DF_ACTION
124
+ _doflow_em_rule prompt.summary "$role:$kind:$subagent:$bootstrapped:$own"; a_summary=$DF_ACTION
125
+ _doflow_em_rule prompt.warning "$role:$subagent:$bootstrapped:$warning"; a_warning=$DF_ACTION
126
+ _doflow_em_rule prompt.grant "$subagent:$grant:$refused"; a_grant=$DF_ACTION
127
+ _doflow_em_rule prompt.nudge "$role:$nudge_state"; a_nudge=$DF_ACTION
128
+ return 0
129
+ }
130
+
131
+ # doflow_emit_prompt <session_id> <cwd> <payload_title> <subagent> <nudge_text>
132
+ # IC-004 steps 2 to 8 for one UserPromptSubmit event; prints the response on stdout and returns 0.
133
+ # session_id the payload session_id; empty prints nothing (the 0fcd973 early exit)
134
+ # cwd the payload cwd
135
+ # payload_title the payload session_title; non-empty suppresses sessionTitle
136
+ # subagent non-empty when the payload is a native subagent prompt (agent_id or agent_type)
137
+ # nudge_text the nudge message when the classifier earned it, else empty; the session is
138
+ # marked nudged here, only once the nudge rendered (DEC-041)
139
+ # Reads DOFLOW_AGENT (harness; sessionTitle on every harness but codex, DEC-030),
140
+ # DOFLOW_CONTEXT_MAX_BYTES (budget, default DF_RULES_MAX_BYTES_DEFAULT or 16384), SESSION_DIR and the
141
+ # launcher variables through doflow_identity. Sets DF_ID_* (doflow_identity), DF_EMIT_REASON (emit,
142
+ # refused:mandatory-over-budget, silent:already-bootstrapped, silent:unchanged, silent:no-home or
143
+ # fault:<point>), DF_EMIT_BYTES and DF_EMIT_DIGEST (_doflow_em_measure for emit and refused:*, else 0
144
+ # and empty) and DF_EMIT_NUDGE (rendered or empty); when the nudge was rendered it calls
145
+ # `nudge_mark <session> nudged` and, when contract.sh is loaded, `doflow_decide advisory prompt-nudge`.
146
+ doflow_emit_prompt() {
147
+ local session="${1:-}" cwd="${2:-}" payload_title="${3:-}" nudge="${5:-}"
148
+ local subagent=false nudge_state=none role kind bootstrapped=false own=none grant=none refused=false
149
+ local warning=false legacy=false a_slot a_git a_summary a_warning a_grant a_nudge
150
+ local sessions dir unit max harness here renderer project="" warnfile="" warntext="" had_c=0 made=0
151
+ local gitfile sumfile=/dev/null grantfile=/dev/null claimed_grant="" absent="" plan out
152
+ local refusal=refused:mandatory-over-budget title=false sumsrc="" sumid="" grantsrc="" warnclaimed=""
153
+ local dropped rest seg key digest=false
154
+ DF_EMIT_REASON=""
155
+ DF_EMIT_BYTES=0
156
+ DF_EMIT_DIGEST=""
157
+ DF_EMIT_NUDGE=""
158
+ [ -n "$session" ] || return 0
159
+ [ -z "${4:-}" ] || subagent=true
160
+ [ -z "$nudge" ] || nudge_state=nudge
161
+ if ! declare -F doflow_identity doflow_emission_rule doflow_summary_peek doflow_summary_claim \
162
+ doflow_grant_peek doflow_grant_claim doflow_summary_adopt_legacy doflow_summary_legacy_present ensure_project_dir >/dev/null 2>&1; then
163
+ _doflow_em_silent deps
164
+ return 0
165
+ fi
166
+ _DOFLOW_SRC=${BASH_SOURCE[0]//\\//}; case $_DOFLOW_SRC in */*) here=${_DOFLOW_SRC%/*} ;; *) here=. ;; esac
167
+ # The renderer beside the entry policy when it set _DOFLOW_POLDIR, else beside this file.
168
+ renderer=$here/emission.jq
169
+ [ -z "${_DOFLOW_POLDIR:-}" ] || [ ! -f "$_DOFLOW_POLDIR/emission.jq" ] || renderer=$_DOFLOW_POLDIR/emission.jq
170
+
171
+ # Step 2: identity, and the unit whose slot and ledger this event uses. The session id names
172
+ # git-context.json's directory for every lineage kind, so it is a path segment or a fault (DEC-042).
173
+ if ! [[ $session =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]; then
174
+ _doflow_em_silent unit
175
+ return 0
176
+ fi
177
+ doflow_identity "$session" "$cwd"
178
+ role=$DF_ID_ROLE
179
+ kind=$DF_ID_KIND
180
+ unit=$DF_ID_UNIT
181
+ if ! [[ $unit =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]; then
182
+ _doflow_em_silent unit
183
+ return 0
184
+ fi
185
+ # IC-010: lib.sh's SESSION_DIR is empty with no config home; there is no store, so nothing is
186
+ # claimed, rendered or recorded (src/runtime/context/arbitrate.js answers silent:no-home too).
187
+ sessions="${SESSION_DIR:-}"
188
+ if [ -z "$sessions" ]; then
189
+ DF_EMIT_REASON=silent:no-home
190
+ printf '{}\n'
191
+ return 0
192
+ fi
193
+ dir="$sessions/$unit"
194
+ max="${DOFLOW_CONTEXT_MAX_BYTES:-}"
195
+ [[ $max =~ ^[1-9][0-9]{0,9}$ ]] || max="${DF_RULES_MAX_BYTES_DEFAULT:-16384}"
196
+ harness="${DOFLOW_AGENT:-}"
197
+ [[ $harness =~ ^[A-Za-z0-9._-]{0,40}$ ]] || harness=unknown
198
+
199
+ # Step 3: state bits. The project directory, the legacy file, the warning and the owner summary
200
+ # only matter before the bootstrap, so a later prompt reads none of them.
201
+ if [ -e "$dir/injected" ] || [ -L "$dir/injected" ]; then bootstrapped=true; fi
202
+ if [ "$bootstrapped" = false ]; then
203
+ if [ -n "$cwd" ]; then project=$(ensure_project_dir "$cwd" 2>/dev/null) || project=""; fi
204
+ if [ -n "$project" ]; then
205
+ if doflow_summary_legacy_present "$cwd"; then legacy=true; fi
206
+ warnfile="$project/uncommitted-warning.txt"
207
+ if [ -f "$warnfile" ]; then warning=true; fi
208
+ fi
209
+ doflow_summary_peek "$DF_ID_WORKTREE" "$DF_ID_RUN" "$DF_ID_LINEAGE" "$session"
210
+ own=$DF_SUMMARY_OWN
211
+ fi
212
+ # Grants are addressed to launched units only: local, solo and main lineages are shared by
213
+ # unrelated sessions (DEC-039).
214
+ if [ "$kind" = launched ]; then
215
+ doflow_grant_peek "$DF_ID_RUN" "$DF_ID_LINEAGE" "$max"
216
+ grant=$DF_GRANT
217
+ fi
218
+ if [ "$grant" = over ] && _doflow_em_seen "$dir/emitted" grant "$DF_GRANT_ID" "$refusal"; then refused=true; fi
219
+
220
+ # Step 4: the legacy adoption, then the owner summary bit again.
221
+ _doflow_em_rule legacy-adopt.legacy "$role:$kind:$subagent:$bootstrapped:$legacy"
222
+ if [ "$DF_ACTION" = adopt ]; then
223
+ doflow_summary_adopt_legacy "$cwd" "$DF_ID_WORKTREE" || true
224
+ doflow_summary_peek "$DF_ID_WORKTREE" "$DF_ID_RUN" "$DF_ID_LINEAGE" "$session"
225
+ own=$DF_SUMMARY_OWN
226
+ fi
227
+
228
+ # Step 5: one lookup per segment.
229
+ _doflow_em_actions
230
+
231
+ # Step 6: claims in the order slot, summary, grant, warning. EEXIST on the slot means another
232
+ # caller bootstrapped this unit: the segments are looked up again as bootstrapped, which makes
233
+ # every bootstrap-only one skip.
234
+ if [ "$a_slot" = claim ]; then
235
+ if ! mkdir -p "$dir" 2>/dev/null; then
236
+ _doflow_em_silent slot
237
+ return 0
238
+ fi
239
+ case $- in *C*) had_c=1 ;; esac
240
+ set -C
241
+ { : >"$dir/injected"; } 2>/dev/null && made=1
242
+ [ "$had_c" = 1 ] || set +C
243
+ if [ "$made" = 0 ]; then
244
+ if [ -e "$dir/injected" ] || [ -L "$dir/injected" ]; then
245
+ bootstrapped=true
246
+ _doflow_em_actions
247
+ else
248
+ _doflow_em_silent slot
249
+ return 0
250
+ fi
251
+ fi
252
+ fi
253
+ # A lost claim (the source gone, or no longer a pending summary) makes that segment absent and the
254
+ # segments are looked up again; only a failure with the source still in place is a fault.
255
+ if [ "$a_summary" = truncate ]; then
256
+ if doflow_summary_claim "$DF_ID_WORKTREE" "$DF_ID_RUN" "$DF_ID_LINEAGE" "$unit" "$session"; then
257
+ sumfile=$DF_SUMMARY_CLAIMED
258
+ sumsrc=$DF_SUMMARY_SCOPE/summary.md
259
+ sumid=$DF_SUMMARY_SID
260
+ else
261
+ doflow_summary_peek "$DF_ID_WORKTREE" "$DF_ID_RUN" "$DF_ID_LINEAGE" "$session"
262
+ if [ "$DF_SUMMARY_OWN" = pending ]; then
263
+ _doflow_em_release
264
+ _doflow_em_silent summary
265
+ return 0
266
+ fi
267
+ own=none
268
+ _doflow_em_actions
269
+ fi
270
+ fi
271
+ if [ "$a_grant" = whole ]; then
272
+ grantsrc=$DF_GRANT_FILE
273
+ if doflow_grant_claim "$DF_ID_RUN" "$DF_ID_LINEAGE" "$unit" "$max"; then
274
+ grantfile=$DF_GRANT_CLAIMED
275
+ claimed_grant=$grantfile
276
+ grantsrc=$DF_GRANT_FILE
277
+ elif [ -n "$grantsrc" ] && [ -f "$grantsrc" ]; then
278
+ _doflow_em_release
279
+ _doflow_em_silent grant
280
+ return 0
281
+ else
282
+ grant=none
283
+ _doflow_em_actions
284
+ fi
285
+ elif [ "$a_grant" = pointer ]; then
286
+ # An over-budget grant is never claimed: it renders as its pointer from where it lies.
287
+ grantfile=$DF_GRANT_FILE
288
+ fi
289
+ # The warning is claimed by rename, so two first prompts deliver it once (DEC-040); read -d ''
290
+ # returns non-zero at EOF, which is accepted only once the claimed file is known to be readable.
291
+ if [ "$a_warning" = include ]; then
292
+ if mv "$warnfile" "$warnfile.claimed.$unit" 2>/dev/null; then
293
+ warnclaimed=$warnfile.claimed.$unit
294
+ if [ -r "$warnclaimed" ] && [ -f "$warnclaimed" ]; then
295
+ IFS= read -r -d '' warntext 2>/dev/null <"$warnclaimed" || true
296
+ else
297
+ _doflow_em_release
298
+ _doflow_em_silent warning
299
+ return 0
300
+ fi
301
+ elif [ -e "$warnfile" ] || [ -L "$warnfile" ]; then
302
+ _doflow_em_release
303
+ _doflow_em_silent warning
304
+ return 0
305
+ else
306
+ warning=false
307
+ _doflow_em_actions
308
+ fi
309
+ fi
310
+
311
+ # Nothing that renders: silence without a jq process.
312
+ case "$a_git $a_summary $a_warning $a_grant $a_nudge" in
313
+ *include* | *truncate* | *whole* | *pointer*) ;;
314
+ *)
315
+ if [ "$bootstrapped" = true ]; then DF_EMIT_REASON=silent:already-bootstrapped; else DF_EMIT_REASON=silent:unchanged; fi
316
+ printf '{}\n'
317
+ return 0
318
+ ;;
319
+ esac
320
+
321
+ # Step 7: one jq process. A missing file is /dev/null and named in plan.absent.
322
+ gitfile="$sessions/$session/git-context.json"
323
+ if [ ! -f "$gitfile" ]; then gitfile=/dev/null; absent='"git"'; fi
324
+ [ "$sumfile" != /dev/null ] || absent="${absent:+$absent,}\"summary\""
325
+ [ "$grantfile" != /dev/null ] || absent="${absent:+$absent,}\"grant\""
326
+ [ "$a_warning" = include ] || absent="${absent:+$absent,}\"warning\""
327
+ [ -n "$payload_title" ] || title=true
328
+ plan="{\"actions\":{\"git\":\"$a_git\",\"summary\":\"$a_summary\",\"warning\":\"$a_warning\",\"grant\":\"$a_grant\",\"nudge\":\"$a_nudge\"},\"maxBytes\":$max,\"harness\":\"$harness\",\"title\":$title,\"absent\":[$absent]"
329
+ [ "$sumfile" = /dev/null ] || plan="$plan,\"summaryBytes\":${DF_SUMMARY_BYTES:-0}"
330
+ # Rider R1: the renderer hashes the response only when the bridge will write it (telemetry on, fd set).
331
+ [ "${DOFLOW_TELEMETRY:-}" = off ] || [ -z "${DOFLOW_DECISION_FD:-}" ] || digest=true
332
+ plan="$plan,\"digest\":$digest,\"mode\":\"hook\"}"
333
+ # Hook mode: the first line is the dropped segment names, tab-joined (possibly empty); the second
334
+ # is the response digest (empty unless plan.digest).
335
+ out=$(jq -n -r -f "$renderer" --argjson plan "$plan" --rawfile git "$gitfile" \
336
+ --rawfile summary "$sumfile" --rawfile grant "$grantfile" --arg warning "$warntext" \
337
+ --arg nudge "$nudge" 2>/dev/null) || out=""
338
+ [ "$sumfile" = /dev/null ] || rm -f "$sumfile" 2>/dev/null || true
339
+ [ -z "$claimed_grant" ] || rm -f "$claimed_grant" 2>/dev/null || true
340
+ [ -z "$warnclaimed" ] || rm -f "$warnclaimed" 2>/dev/null || true
341
+ sumfile=/dev/null claimed_grant="" warnclaimed=""
342
+ case "$out" in *$'\n'*$'\n'?*) ;; *) out="" ;; esac
343
+ if [ -z "$out" ]; then
344
+ _doflow_em_release
345
+ _doflow_em_silent render
346
+ return 0
347
+ fi
348
+ dropped=${out%%$'\n'*}
349
+ out=${out#*$'\n'}
350
+ digest=${out%%$'\n'*}
351
+ out=${out#*$'\n'}
352
+
353
+ # Step 8: the refusal ledger line (key: the grant's summary id), one optional-dropped line per
354
+ # dropped segment, then the nudge record.
355
+ DF_EMIT_REASON=emit
356
+ if [ "$a_grant" = pointer ]; then
357
+ DF_EMIT_REASON=$refusal
358
+ _doflow_em_ledger_add "$dir" grant "$DF_GRANT_ID" "$refusal" hook || _doflow_em_capture ledger
359
+ fi
360
+ rest=$dropped
361
+ while [ -n "$rest" ]; do
362
+ seg=${rest%%$'\t'*}
363
+ case "$rest" in *$'\t'*) rest=${rest#*$'\t'} ;; *) rest="" ;; esac
364
+ [ -n "$seg" ] || continue
365
+ key=$seg
366
+ [ "$seg" != summary ] || [ -z "$sumid" ] || key=$sumid
367
+ [ "$seg" != grant ] || [ -z "${DF_GRANT_ID:-}" ] || key=$DF_GRANT_ID
368
+ _doflow_em_ledger_add "$dir" optional-dropped "$key" over-budget hook || _doflow_em_capture ledger
369
+ done
370
+ if [ "$a_nudge" = include ]; then
371
+ case $'\t'"$dropped"$'\t' in
372
+ *$'\t'nudge$'\t'*) ;;
373
+ *)
374
+ DF_EMIT_NUDGE=rendered
375
+ if declare -F nudge_mark >/dev/null 2>&1; then nudge_mark "$session" nudged || true; fi
376
+ if declare -F doflow_decide >/dev/null 2>&1; then doflow_decide advisory prompt-nudge || true; fi
377
+ ;;
378
+ esac
379
+ fi
380
+ _doflow_em_measure "$out" "$digest"
381
+ printf '%s\n' "$out"
382
+ return 0
383
+ }