@khoavu882/doflow 2.0.0-beta.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,767 @@
1
+ 'use strict';
2
+
3
+ // Emission authority (078 IC-001 decision half, IC-002). Run directly by Node native type-stripping.
4
+ //
5
+ // The IC-002 rule table is data (DEC-005): `arbitrate()` reads it and nothing else, with no I/O, no
6
+ // clock and no environment, so two callers holding the same request reach the same decision
7
+ // (NFR-003). The compiled hook table (IC-003) is generated from EMISSION_RULES, and the hook's
8
+ // `emission.jq` must reproduce renderEmission() byte for byte over
9
+ // test/fixtures/emission/render-corpus.json (IC-022). Characters are counted as code points with
10
+ // Array.from(), never UTF-16 units (design R1); budgets are UTF-8 bytes.
11
+
12
+ // The IC-007 reader whose SessionIdentity a request carries. In-process callers (emitContext(),
13
+ // the launcher) resolve identity with it before they arbitrate; arbitrate() itself only reads the
14
+ // resolved `role` and `lineageKind`.
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+ const { cwdHash, resolveIdentity, stateDir } = require('./identity.js');
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+ /** The fields of an IC-007 SessionIdentity (resolveIdentity()) that the rules read. */
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+ /** One arm: per input (vector order) the values it matches, or '*' for every value of the domain. */
62
+
63
+
64
+
65
+ /** Every input's finite domain, as the strings a vector carries. Booleans are 'false' and 'true'. */
66
+ const INPUT_DOMAINS = Object.freeze({
67
+ role: Object.freeze(['main', 'worker']),
68
+ lineageKind: Object.freeze(['default-main', 'launched', 'solo']),
69
+ bootstrapped: Object.freeze(['false', 'true']),
70
+ subagent: Object.freeze(['false', 'true']),
71
+ ownSummary: Object.freeze(['none', 'pending', 'own-write']),
72
+ legacy: Object.freeze(['false', 'true']),
73
+ grant: Object.freeze(['none', 'fits', 'over']),
74
+ refusedSeen: Object.freeze(['false', 'true']),
75
+ warning: Object.freeze(['false', 'true']),
76
+ nudge: Object.freeze(['nudge', 'suppress', 'none']),
77
+ });
78
+
79
+ const EVENTS = ['launch', 'prompt', 'delta', 'legacy-adopt', 'compact-store', 'compact-meta', 'compact-instructions'];
80
+
81
+ function rule(event , segment , inputs , cls , arms ) {
82
+ return { event, segment, inputs, arms, class: cls };
83
+ }
84
+
85
+ /** IC-002, row for row. Any combination no arm matches is `skip`. */
86
+ const EMISSION_RULES = deepFreeze([
87
+ rule('prompt', 'slot', ['bootstrapped'], 'n/a', [
88
+ { when: [['false']], action: 'claim' },
89
+ ]),
90
+ rule('prompt', 'git', ['role', 'bootstrapped'], 'optional', [
91
+ { when: [['main'], ['false']], action: 'include' },
92
+ ]),
93
+ rule('prompt', 'summary', ['role', 'lineageKind', 'subagent', 'bootstrapped', 'ownSummary'], 'optional', [
94
+ { when: [['main'], ['default-main', 'launched'], ['false'], ['false'], ['pending']], action: 'truncate' },
95
+ { when: [['worker'], ['launched'], ['false'], ['false'], ['pending']], action: 'truncate' },
96
+ ]),
97
+ rule('prompt', 'warning', ['role', 'subagent', 'bootstrapped', 'warning'], 'optional', [
98
+ { when: [['main'], ['false'], ['false'], ['true']], action: 'include' },
99
+ ]),
100
+ rule('prompt', 'grant', ['subagent', 'grant', 'refusedSeen'], 'mandatory', [
101
+ { when: [['false'], ['fits'], '*'], action: 'whole' },
102
+ { when: [['false'], ['over'], ['false']], action: 'pointer' },
103
+ ]),
104
+ rule('prompt', 'nudge', ['role', 'nudge'], 'optional', [
105
+ { when: [['main'], ['nudge']], action: 'include' },
106
+ ]),
107
+ rule('legacy-adopt', 'legacy', ['role', 'lineageKind', 'subagent', 'bootstrapped', 'legacy'], 'n/a', [
108
+ { when: [['main'], ['default-main'], ['false'], ['false'], ['true']], action: 'adopt' },
109
+ ]),
110
+ rule('launch', 'slot', ['bootstrapped'], 'n/a', [
111
+ { when: [['false']], action: 'claim' },
112
+ ]),
113
+ rule('launch', 'pack', ['bootstrapped'], 'per-item', [
114
+ { when: [['false']], action: 'include' },
115
+ ]),
116
+ rule('launch', 'summary', ['role', 'bootstrapped', 'ownSummary'], 'optional', [
117
+ { when: [['main', 'worker'], ['false'], ['pending']], action: 'truncate' },
118
+ ]),
119
+ rule('launch', 'grant', ['grant', 'refusedSeen'], 'mandatory', [
120
+ { when: [['fits'], '*'], action: 'whole' },
121
+ { when: [['over'], ['false']], action: 'pointer' },
122
+ ]),
123
+ rule('delta', 'pack', ['bootstrapped'], 'per-item', [
124
+ { when: [['true']], action: 'include' },
125
+ ]),
126
+ rule('compact-store', 'summary', ['role', 'lineageKind'], 'n/a', [
127
+ { when: [['main'], ['default-main']], action: 'write' },
128
+ { when: [['main', 'worker'], ['launched']], action: 'write' },
129
+ ]),
130
+ rule('compact-meta', 'meta', ['role', 'lineageKind'], 'n/a', [
131
+ { when: [['main'], ['default-main']], action: 'write' },
132
+ ]),
133
+ rule('compact-instructions', 'instructions', ['role'], 'optional', [
134
+ { when: [['main', 'worker']], action: 'include' },
135
+ ]),
136
+ ]);
137
+
138
+ function deepFreeze (value ) {
139
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
140
+ Object.freeze(value);
141
+ for (const child of Object.values(value )) deepFreeze(child);
142
+ }
143
+ return value;
144
+ }
145
+
146
+ /** The action a rule gives one vector (input values in the rule's own order). */
147
+ function ruleAction(r , values ) {
148
+ if (values.length !== r.inputs.length) throw new TypeError(`${r.event}.${r.segment}: expected ${r.inputs.length} input values`);
149
+ for (const arm of r.arms) {
150
+ if (arm.when.every((allowed, i) => allowed === '*' || allowed.includes(values[i]))) return arm.action;
151
+ }
152
+ return 'skip';
153
+ }
154
+
155
+ function inputValue(request , name ) {
156
+ const raw = name === 'role' || name === 'lineageKind' ? request.identity[name] : request.state[name];
157
+ const value = typeof raw === 'boolean' ? String(raw) : raw;
158
+ if (typeof value !== 'string' || !INPUT_DOMAINS[name].includes(value)) {
159
+ throw new TypeError(`arbitrate: ${name} must be one of ${INPUT_DOMAINS[name].join(', ')}`);
160
+ }
161
+ return value;
162
+ }
163
+
164
+ /** A rule's vector for a request: its input values in IC-002 vector order (IC-003 joins them with ':'). */
165
+ function ruleVector(r , request ) {
166
+ return r.inputs.map((name) => inputValue(request, name));
167
+ }
168
+
169
+ const TEXT_ACTIONS = ['include', 'truncate', 'whole', 'pointer'];
170
+ /** IC-004 claim order, with the legacy adoption (IC-004 step 4) ahead of the prompt claims. */
171
+ const CLAIM_ORDER = [
172
+ ['legacy', 'adopt'], ['slot', 'claim'], ['summary', 'truncate'], ['grant', 'whole'], ['warning', 'include'],
173
+ ];
174
+ const REFUSED = 'refused:mandatory-over-budget';
175
+
176
+ /** IC-001: the decision for one request. Pure and total over the IC-002 domain; outside it, a TypeError. */
177
+ function arbitrate(request ) {
178
+ if (!request || !EVENTS.includes(request.event)) throw new TypeError(`arbitrate: event must be one of ${EVENTS.join(', ')}`);
179
+ if (!request.identity || !request.state) throw new TypeError('arbitrate: identity and state are required');
180
+
181
+ const actions = {};
182
+ const classes = {};
183
+ for (const r of EMISSION_RULES) {
184
+ if (r.event !== request.event) continue;
185
+ actions[r.segment] = ruleAction(r, ruleVector(r, request));
186
+ classes[r.segment] = r.class;
187
+ }
188
+ const claims = CLAIM_ORDER.filter(([segment, action]) => actions[segment] === action).map(([segment]) => segment);
189
+ const pointed = Object.keys(actions).some((s) => actions[s ] === 'pointer' && classes[s ] === 'mandatory');
190
+ const emit = Object.values(actions).some((a) => TEXT_ACTIONS.includes(a ));
191
+ const acts = Object.values(actions).some((a) => a !== 'skip');
192
+ const hasSlot = actions.slot !== undefined;
193
+
194
+ let reason ;
195
+ if (pointed) reason = REFUSED;
196
+ else if (emit) reason = 'emit';
197
+ else if (acts) reason = 'act';
198
+ else if (hasSlot && request.state.bootstrapped) reason = 'silent:already-bootstrapped';
199
+ else reason = 'silent:unchanged';
200
+
201
+ // The grant's summary id is not part of the finite vector; '@grant' names the grant the caller
202
+ // measured, and the caller writes its summary_id as the ledger key (IC-015).
203
+ const ledger = pointed ? { kind: 'grant', key: '@grant', outcome: REFUSED } : null;
204
+ return { emit, reason, actions, claims, ledger };
205
+ }
206
+
207
+ // ── Renderer (IC-002 templates, join order and budget fill) ──────────────────────────────────
208
+
209
+ const SUMMARY_CAP = 4000;
210
+ const SUMMARY_READ = 65536;
211
+ const COMMIT_SEPARATOR = ' · ';
212
+ const JOIN_ORDER = ['git', 'summary', 'warning', 'grant', 'nudge'] ;
213
+ const OPTIONAL_PRIORITY = ['git', 'summary', 'warning', 'nudge'] ;
214
+
215
+
216
+ /**
217
+ * Raw renderer inputs, the bytes the hook hands `emission.jq` through `--rawfile` and `--arg`.
218
+ * `null` or absent means the file does not exist. `summaryBytes` is the claimed file's full size,
219
+ * which says whether the bounded read saw all of it; it defaults to the UTF-8 size of `summary`.
220
+ */
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+ /** UTF-8 bytes of a string, counted per code point. */
245
+ function utf8Bytes(text ) {
246
+ let bytes = 0;
247
+ for (const ch of text) {
248
+ const cp = ch.codePointAt(0) ;
249
+ bytes += cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
250
+ }
251
+ return bytes;
252
+ }
253
+
254
+
255
+
256
+ /**
257
+ * A summary or grant record as today's prompt policy reads it: `\r` removed, trailing newlines
258
+ * dropped, frontmatter keys from the lines between the first two `---` lines (first match wins,
259
+ * `key:` then spaces stripped), body the lines after the second `---` from the first non-empty
260
+ * line, trailing newlines dropped. A record with fewer than two `---` lines has an empty body.
261
+ */
262
+ function parseRecord(raw ) {
263
+ const lines = raw.replace(/\r/g, '').replace(/\n+$/, '').split('\n');
264
+ const fields = Object.create(null);
265
+ const body = [];
266
+ let fences = 0;
267
+ let started = false;
268
+ for (const line of lines) {
269
+ if (fences < 2 && line === '---') { fences += 1; continue; }
270
+ if (fences === 1) {
271
+ const colon = line.indexOf(':');
272
+ if (colon > 0) {
273
+ const key = line.slice(0, colon);
274
+ if (!(key in fields)) fields[key] = line.slice(colon + 1).replace(/^ */, '');
275
+ }
276
+ } else if (fences >= 2 && (started || line !== '')) {
277
+ started = true;
278
+ body.push(line);
279
+ }
280
+ }
281
+ return { fields, body: body.join('\n').replace(/\n+$/, '') };
282
+ }
283
+
284
+ /** A bracketed header value: `]` removed, then absent when empty or `unknown`. */
285
+ function known(value ) {
286
+ const v = (value ?? '').replace(/\]/g, '');
287
+ return v === 'unknown' ? '' : v;
288
+ }
289
+
290
+ function headerTail(fields ) {
291
+ const at = known(fields.compacted_at);
292
+ const branch = known(fields.branch);
293
+ return `${at ? `, compacted ${at}` : ''}${branch ? ` on branch ${branch}` : ''}`;
294
+ }
295
+
296
+ /** `jq -r '<field> // empty'` in a `$(...)`: null and false are empty, strings lose trailing newlines. */
297
+ function jqField(value ) {
298
+ if (value === null || value === undefined || value === false) return '';
299
+ return (typeof value === 'string' ? value : JSON.stringify(value)).replace(/\n+$/, '');
300
+ }
301
+
302
+ function renderGit(raw ) {
303
+ if (raw === null || raw === undefined) return 'Git context unavailable for this session.';
304
+ let capture ;
305
+ try { capture = JSON.parse(raw); } catch { capture = null; }
306
+ if (!capture || typeof capture !== 'object' || Array.isArray(capture)) capture = {};
307
+ if (jqField(capture.is_git_repo) !== 'true') return 'Not a git repository.';
308
+ const commits = (Array.isArray(capture.commits) ? capture.commits : [])
309
+ .map(jqField).filter((c ) => c !== '').slice(0, 5).join(COMMIT_SEPARATOR);
310
+ const stash = jqField(capture.stash_count);
311
+ let text = `Git context — branch: ${jqField(capture.branch) || 'unknown'} | ${jqField(capture.sha) || 'unknown'}\n`
312
+ + `Last commits: ${commits || 'none'}\n`
313
+ + `Uncommitted files: ${jqField(capture.uncommitted_count) || '0'}`;
314
+ if (/^[0-9]+$/.test(stash) && Number(stash) > 0) text += ` | Stashed: ${stash}`;
315
+ return text;
316
+ }
317
+
318
+ function renderSummary(raw , size ) {
319
+ const { fields, body } = parseRecord(raw);
320
+ if (body === '') return null;
321
+ const points = Array.from(body);
322
+ let shown = body;
323
+ if (points.length > SUMMARY_CAP) {
324
+ const more = size > SUMMARY_READ ? 'at least ' : '';
325
+ shown = `${points.slice(0, SUMMARY_CAP).join('')}\n[summary truncated: first ${SUMMARY_CAP} of ${more}${points.length} characters]`;
326
+ }
327
+ return `\n\n[Prior session summary${headerTail(fields)}]\n${shown}`;
328
+ }
329
+
330
+ function renderGrant(record ) {
331
+ return `[Granted summary from lineage ${record.fields.from_lineage ?? ''}${headerTail(record.fields)}]\n${record.body}`;
332
+ }
333
+
334
+ function renderPointer(record , maxBytes ) {
335
+ return `[Granted summary ${record.fields.summary_id ?? ''} withheld: ${utf8Bytes(record.body)} bytes exceed the ${maxBytes}-byte context budget]`;
336
+ }
337
+
338
+ /** IC-002 join: summary drops its leading blank line when nothing precedes it; grant and nudge gain one when something does. */
339
+ function joinSegments(pieces ) {
340
+ let out = '';
341
+ for (const segment of JOIN_ORDER) {
342
+ const piece = pieces[segment];
343
+ if (piece === undefined) continue;
344
+ if (segment === 'summary') out += out === '' ? piece.slice(2) : piece;
345
+ else if (segment === 'grant' || segment === 'nudge') out += (out === '' ? '' : '\n\n') + piece;
346
+ else out += piece;
347
+ }
348
+ return out;
349
+ }
350
+
351
+ /**
352
+ * IC-002: render the text segments a decision names, then fill the budget. Mandatory content (the
353
+ * grant) is measured first and is never cut: when it alone exceeds `maxBytes` it becomes its
354
+ * pointer and the render is refused. Optional segments are then added in priority order git,
355
+ * summary, warning, nudge while the joined text stays within `maxBytes`; one that does not fit is
356
+ * dropped whole and named. Only the prompt segments render here; `pack` is the context pack's own.
357
+ */
358
+ function renderEmission(request ) {
359
+ const { actions, maxBytes, inputs } = request;
360
+ if (!Number.isInteger(maxBytes) || maxBytes <= 0) throw new TypeError('renderEmission: maxBytes must be a positive integer');
361
+ const optional = {};
362
+ if (actions.git === 'include') optional.git = renderGit(inputs.git);
363
+ if (actions.summary === 'truncate' && typeof inputs.summary === 'string') {
364
+ const piece = renderSummary(inputs.summary, inputs.summaryBytes ?? utf8Bytes(inputs.summary));
365
+ if (piece !== null) optional.summary = piece;
366
+ }
367
+ if (actions.warning === 'include' && typeof inputs.warning === 'string') {
368
+ optional.warning = `\n[Prior session warning: ${inputs.warning.replace(/\n+$/, '')}]`;
369
+ }
370
+ if (actions.nudge === 'include' && typeof inputs.nudge === 'string' && inputs.nudge !== '') optional.nudge = inputs.nudge;
371
+
372
+ const selected = {};
373
+ let refused = false;
374
+ if ((actions.grant === 'whole' || actions.grant === 'pointer') && typeof inputs.grant === 'string') {
375
+ const record = parseRecord(inputs.grant);
376
+ const whole = renderGrant(record);
377
+ if (actions.grant === 'whole' && utf8Bytes(whole) <= maxBytes) {
378
+ selected.grant = whole;
379
+ } else {
380
+ selected.grant = renderPointer(record, maxBytes);
381
+ refused = true;
382
+ }
383
+ }
384
+
385
+ const dropped = [];
386
+ for (const segment of OPTIONAL_PRIORITY) {
387
+ const piece = optional[segment];
388
+ if (piece === undefined) continue;
389
+ if (utf8Bytes(joinSegments({ ...selected, [segment]: piece })) <= maxBytes) selected[segment] = piece;
390
+ else dropped.push(segment);
391
+ }
392
+
393
+ const text = joinSegments(selected);
394
+ return {
395
+ text: text === '' ? null : text,
396
+ bytes: utf8Bytes(text),
397
+ refused,
398
+ dropped,
399
+ segments: JOIN_ORDER.filter((segment) => selected[segment] !== undefined),
400
+ };
401
+ }
402
+
403
+ // ── emitContext() (IC-001 emit half, IC-012 TypeScript claim, IC-015) ─────────────────────────
404
+ //
405
+ // The launcher-side caller of arbitrate(). It gathers the state bits from the store exactly as
406
+ // policies/emission.sh does, decides, takes the one-shot tokens (the `injected` slot with an
407
+ // exclusive create, then the summary and grant renames through summary-store.ts), renders, and
408
+ // appends the IC-015 ledger line with caller `launcher`. The store modules and the context pack
409
+ // are required inside the function: arbitrate.ts is also loaded alone by the rule compiler.
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+ const UNIT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
445
+ const MAX_BYTES_PATTERN = /^[1-9][0-9]{0,9}$/;
446
+ const PACK_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
447
+ /** emission-rules.sh DF_RULES_MAX_BYTES_DEFAULT. */
448
+ const DEFAULT_MAX_BYTES = 16384;
449
+ const EMIT_EVENTS = ['launch', 'prompt', 'delta'];
450
+
451
+ function usageError(message ) {
452
+ const error = new Error(message);
453
+ error.code = 'USAGE_ERROR';
454
+ return error;
455
+ }
456
+
457
+ /** IC-001 budget: the flag, else DOFLOW_CONTEXT_MAX_BYTES, else the hook's default, as emission.sh. */
458
+ function emitBudget(maxBytes , env ) {
459
+ if (maxBytes !== undefined) {
460
+ if (!Number.isInteger(maxBytes) || maxBytes <= 0) throw usageError('emitContext: maxBytes must be a positive integer');
461
+ return { maxBytes, source: 'flag' };
462
+ }
463
+ const raw = env.DOFLOW_CONTEXT_MAX_BYTES || '';
464
+ if (MAX_BYTES_PATTERN.test(raw)) return { maxBytes: Number(raw), source: 'env' };
465
+ return { maxBytes: DEFAULT_MAX_BYTES, source: 'default' };
466
+ }
467
+
468
+ function present(file ) {
469
+ try {
470
+ fs.lstatSync(file);
471
+ return true;
472
+ } catch {
473
+ return false;
474
+ }
475
+ }
476
+
477
+ /** `[ -f file ]`: a regular file, following a symlink. */
478
+ function isFile(file ) {
479
+ try {
480
+ return fs.statSync(file).isFile();
481
+ } catch {
482
+ return false;
483
+ }
484
+ }
485
+
486
+ function readText(file ) {
487
+ if (file === null) return null;
488
+ try {
489
+ return fs.readFileSync(file, 'utf8');
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+
495
+ /** The IC-015 ledger as [ts, kind, key, outcome, caller] rows; an absent file is empty. */
496
+ function readLedger(file ) {
497
+ const raw = readText(file);
498
+ if (raw === null) return [];
499
+ return raw.split('\n').filter((line ) => line !== '').map((line ) => line.split('\t'));
500
+ }
501
+
502
+ function ledgerSeen(rows , kind , key , outcome ) {
503
+ return rows.some((row) => row[1] === kind && row[2] === key && row[3] === outcome);
504
+ }
505
+
506
+ /** The unit's last delivered pack digest, from a line either caller wrote. */
507
+ function lastPackKey(rows ) {
508
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
509
+ if (rows[i][1] === 'pack' && rows[i][3] === 'emitted' && PACK_DIGEST_PATTERN.test(rows[i][2] ?? '')) return rows[i][2];
510
+ }
511
+ return null;
512
+ }
513
+
514
+ /** `_doflow_em_ledger_add` with caller `launcher`: one line per (kind, key, outcome). True when written. */
515
+ function ledgerAdd(dir , kind , key , outcome ) {
516
+ const file = path.join(dir, 'emitted');
517
+ if (ledgerSeen(readLedger(file), kind, key, outcome)) return false;
518
+ fs.mkdirSync(dir, { recursive: true });
519
+ const at = new Date().toISOString().replace(/\.[0-9]{3}Z$/, 'Z');
520
+ fs.appendFileSync(file, `${at}\t${kind}\t${key}\t${outcome}\tlauncher\n`);
521
+ return true;
522
+ }
523
+
524
+ /**
525
+ * IC-001: one emission for a launch, a prompt or a delta. Throws a USAGE_ERROR for an unknown
526
+ * event, a missing cwd, a unit with no id, or a `launch` without a launched identity (IC-007).
527
+ */
528
+ function emitContext(options ) {
529
+ const store = require('./summary-store.js');
530
+ const env = options.env ?? process.env;
531
+ const { event, cwd } = options;
532
+ if (!EMIT_EVENTS.includes(event)) throw usageError(`emitContext: event must be one of ${EMIT_EVENTS.join(', ')}`);
533
+ if (typeof cwd !== 'string' || cwd === '') throw usageError('emitContext: cwd is required');
534
+ const sessionId = options.sessionId ?? '';
535
+ // DEC-032, DEC-042: a session id names store paths (the unit, the git capture), so one that is not
536
+ // a single safe path segment gets the silent answer for every lineage kind, before any read.
537
+ if (sessionId !== '' && !UNIT_PATTERN.test(sessionId)) {
538
+ return { decision: { emit: false, reason: 'fault:unit', actions: {}, claims: [], ledger: null }, text: null, pack: null, committed: false };
539
+ }
540
+ const identity = options.identity ?? resolveIdentity(env, { cwd, sessionId });
541
+ if (event === 'launch' && identity.lineageKind !== 'launched') {
542
+ throw usageError('emitContext: launch requires a launched identity (DOFLOW_RUN_ID, DOFLOW_LINEAGE_ID and DOFLOW_ATTEMPT)');
543
+ }
544
+ const unit = identity.unitId;
545
+ if (typeof unit !== 'string' || !UNIT_PATTERN.test(unit)) throw usageError('emitContext: the unit has no id (no launched identity and no session id)');
546
+ const peek = options.peek === true;
547
+ const budget = emitBudget(options.maxBytes, env);
548
+ const max = budget.maxBytes;
549
+ const root = stateDir(env);
550
+ // IC-010: with no config home there is no session store, so no bootstrap slot, ledger or claim to keep.
551
+ if (root === null) return { decision: { emit: false, reason: 'silent:no-home', actions: {}, claims: [], ledger: null }, text: null, pack: null, committed: false };
552
+ const dir = path.join(root, 'sessions', unit);
553
+ const slot = path.join(dir, 'injected');
554
+ const ledger = readLedger(path.join(dir, 'emitted'));
555
+ const { run, lineage, worktree } = identity;
556
+
557
+ // emission.sh step 3. The warning, the legacy file and the owner summary only matter before the
558
+ // bootstrap. Grants are read only by launched identities (DEC-039).
559
+ let bootstrapped = present(slot);
560
+ const warnFile = path.join(root, 'projects', cwdHash(cwd), 'uncommitted-warning.txt');
561
+ let warning = false;
562
+ let legacy = false;
563
+ let own = 'none';
564
+ if (!bootstrapped) {
565
+ warning = isFile(warnFile);
566
+ legacy = store.legacySummary(cwd, env) !== null;
567
+ own = store.peekSummary(worktree, run, lineage, sessionId, env).own;
568
+ }
569
+ const grant = identity.lineageKind === 'launched'
570
+ ? store.peekGrant(run, lineage, max, env)
571
+ : { grant: 'none', file: null, id: null, bytes: null };
572
+ const refusedSeen = grant.grant === 'over' && grant.id !== null && ledgerSeen(ledger, 'grant', grant.id, REFUSED);
573
+ const state = (b ) => ({ bootstrapped: b, subagent: false, ownSummary: own, legacy, grant: grant.grant, refusedSeen, warning, nudge: options.nudge ?? 'none' });
574
+
575
+ // Step 4 (IC-014, DEC-043): the legacy adoption, then the owner summary bit again. A peek takes
576
+ // nothing, so it reads the legacy file where it lies as the summary the prompt would adopt.
577
+ let legacyFile = null;
578
+ if (event === 'prompt' && arbitrate({ event: 'legacy-adopt', identity, budget, state: state(bootstrapped) }).actions.legacy === 'adopt') {
579
+ if (peek) {
580
+ if (own === 'none') {
581
+ legacyFile = store.legacySummary(cwd, env);
582
+ if (legacyFile !== null) own = 'pending';
583
+ }
584
+ } else {
585
+ store.adoptLegacy(cwd, worktree, env);
586
+ own = store.peekSummary(worktree, run, lineage, sessionId, env).own;
587
+ }
588
+ }
589
+ const decide = (b ) => arbitrate({ event, identity, budget, state: state(b) });
590
+ let decision = decide(bootstrapped);
591
+
592
+ // Step 6, the slot first: EEXIST means another caller bootstrapped this unit.
593
+ let tookSlot = false;
594
+ if (decision.actions.slot === 'claim' && !peek) {
595
+ fs.mkdirSync(dir, { recursive: true });
596
+ try {
597
+ fs.closeSync(fs.openSync(slot, 'wx'));
598
+ tookSlot = true;
599
+ } catch (error ) {
600
+ if (error?.code !== 'EEXIST') throw error;
601
+ bootstrapped = true;
602
+ decision = decide(true);
603
+ }
604
+ }
605
+ const releaseSlot = () => {
606
+ if (tookSlot) fs.rmSync(slot, { force: true });
607
+ };
608
+
609
+ // Every claimed summary or grant, with the name it came from: removed only once the text is
610
+ // returned, and given back (with the slot) when anything after the claim throws (DEC-038).
611
+ const claimed = [];
612
+ try {
613
+ // The pack, before any other claim: a mandatory overflow refuses the whole event and gives back
614
+ // the slot this call took, so the next caller can still bootstrap the unit.
615
+ let pack = null;
616
+ let packText = null;
617
+ if (decision.actions.pack === 'include') {
618
+ const { packForEmission } = require('../context-pack.js');
619
+ const since = lastPackKey(ledger);
620
+ const compiled = packForEmission({ root: cwd, stateRoot: options.stateRoot ?? cwd, taskId: options.taskId, slug: options.slug, harness: options.harness ?? null, maxBytes: max, since, noRecord: peek, env });
621
+ const digest = compiled.pack.delivery.packDigest;
622
+ pack = { packDigest: digest, form: 'markdown', deltaRefused: compiled.pack.delivery.deltaRefused ?? null };
623
+ if (compiled.pack.status === 'OVERFLOW') {
624
+ releaseSlot();
625
+ return {
626
+ decision: { ...decision, emit: false, reason: REFUSED },
627
+ text: compiled.pack.overflowAction?.message ?? null,
628
+ pack,
629
+ committed: false,
630
+ };
631
+ }
632
+ const delta = compiled.pack.delta;
633
+ const unchanged = since !== null && (digest === since
634
+ || (delta !== null && delta !== undefined && delta.added.length === 0 && delta.changed.length === 0 && delta.removed.length === 0));
635
+ if (!unchanged) packText = compiled.output;
636
+ }
637
+
638
+ // DEC-034: one byte budget per emission. The pack is measured first; the prompt segments get
639
+ // what is left after it and the blank line that joins them. The grant is fitted to that
640
+ // remainder before it is claimed, so a grant that no longer fits becomes its pointer and is
641
+ // never consumed; the claim then takes exactly the grant that was fitted (L1-05).
642
+ const room = packText === null ? max : max - utf8Bytes(packText) - 2;
643
+ if ((decision.actions.grant === 'whole' || decision.actions.grant === 'pointer') && grant.file !== null) {
644
+ const raw = readText(grant.file);
645
+ if (raw !== null) {
646
+ const record = parseRecord(raw);
647
+ if (decision.actions.grant === 'whole' && utf8Bytes(renderGrant(record)) > room) {
648
+ decision = { ...decision, actions: { ...decision.actions, grant: 'pointer' }, ledger: { kind: 'grant', key: '@grant', outcome: REFUSED } };
649
+ }
650
+ if (decision.actions.grant === 'pointer' && (room < 1 || utf8Bytes(renderPointer(record, room)) > room)) {
651
+ // Not even the pointer fits: the event is refused, as a pack overflow is, and the slot goes back.
652
+ releaseSlot();
653
+ return {
654
+ decision: { ...decision, emit: false, reason: REFUSED },
655
+ text: renderPointer(record, max),
656
+ pack,
657
+ committed: false,
658
+ };
659
+ }
660
+ }
661
+ }
662
+
663
+ // The remaining claims in IC-004 order (summary, grant, warning); peek reads in place.
664
+ const inputs = {};
665
+ let tookClaim = false;
666
+ let grantKey = grant.id;
667
+ let summaryKey = null;
668
+ let removeWarning = false;
669
+ if (decision.actions.summary === 'truncate') {
670
+ if (peek) {
671
+ const file = legacyFile ?? store.peekSummary(worktree, run, lineage, sessionId, env).file;
672
+ if (file !== null) {
673
+ const raw = fs.readFileSync(file);
674
+ inputs.summary = raw.subarray(0, SUMMARY_READ).toString('utf8');
675
+ inputs.summaryBytes = raw.length;
676
+ }
677
+ } else {
678
+ const c = store.claimSummary(worktree, run, lineage, unit, sessionId, env);
679
+ if (c.claimed !== null) {
680
+ tookClaim = true;
681
+ claimed.push({ file: c.claimed, origin: c.origin });
682
+ inputs.summary = readText(c.claimed);
683
+ inputs.summaryBytes = c.bytes;
684
+ }
685
+ }
686
+ if (typeof inputs.summary === 'string') {
687
+ const id = parseRecord(inputs.summary).fields.summary_id;
688
+ if (id !== undefined && store.SUMMARY_ID_PATTERN.test(id)) summaryKey = id;
689
+ }
690
+ }
691
+ if (decision.actions.grant === 'whole') {
692
+ if (peek) inputs.grant = readText(grant.file);
693
+ else if (grant.id !== null) {
694
+ const c = store.claimGrant(run, lineage, unit, grant.id, env);
695
+ if (c.claimed !== null) {
696
+ tookClaim = true;
697
+ claimed.push({ file: c.claimed, origin: c.origin });
698
+ grantKey = c.id;
699
+ inputs.grant = readText(c.claimed);
700
+ }
701
+ }
702
+ } else if (decision.actions.grant === 'pointer') {
703
+ // An over-budget grant is never claimed: it renders as its pointer from where it lies.
704
+ inputs.grant = readText(grant.file);
705
+ }
706
+ if (decision.actions.warning === 'include') {
707
+ inputs.warning = readText(warnFile);
708
+ removeWarning = !peek && inputs.warning !== null;
709
+ }
710
+ if (decision.actions.git === 'include' && sessionId !== '') inputs.git = readText(path.join(root, 'sessions', sessionId, 'git-context.json'));
711
+
712
+ const rendered = room >= 1
713
+ ? renderEmission({ actions: decision.actions, maxBytes: room, inputs })
714
+ : { text: null, bytes: 0, refused: false, dropped: [], segments: [] };
715
+ const text = [packText, rendered.text].filter((piece) => piece !== null && piece !== '').join('\n\n') || null;
716
+
717
+ // Step 8: the ledger lines (pack delivered; grant refused, keyed by its summary id; each optional
718
+ // segment the budget dropped, keyed by its summary or grant id, else its name; DEC-041).
719
+ let wrote = false;
720
+ if (!peek) {
721
+ if (packText !== null && pack !== null) wrote = ledgerAdd(dir, 'pack', pack.packDigest, 'emitted') || wrote;
722
+ if (decision.actions.grant === 'pointer' && grant.id !== null) wrote = ledgerAdd(dir, 'grant', grant.id, REFUSED) || wrote;
723
+ for (const segment of rendered.dropped) {
724
+ const key = (segment === 'summary' ? summaryKey : segment === 'grant' ? grantKey : null) ?? segment;
725
+ wrote = ledgerAdd(dir, 'optional-dropped', key, 'over-budget') || wrote;
726
+ }
727
+ }
728
+
729
+ let reason ;
730
+ if (rendered.refused || decision.reason === REFUSED) reason = REFUSED;
731
+ else if (text !== null) reason = 'emit';
732
+ else if (bootstrapped && event !== 'delta') reason = 'silent:already-bootstrapped';
733
+ else reason = 'silent:unchanged';
734
+ const hint = decision.ledger && decision.ledger.key === '@grant' && grant.id !== null ? { ...decision.ledger, key: grant.id } : decision.ledger;
735
+ const result = {
736
+ decision: { ...decision, emit: text !== null, reason, ledger: hint },
737
+ text,
738
+ pack,
739
+ committed: !peek && (tookSlot || tookClaim || removeWarning || wrote),
740
+ };
741
+
742
+ // The text is ready: only now are the claimed files and the delivered warning consumed.
743
+ for (const c of claimed.splice(0)) fs.rmSync(c.file, { force: true });
744
+ if (removeWarning) fs.rmSync(warnFile, { force: true });
745
+ return result;
746
+ } catch (error) {
747
+ for (const c of claimed) store.releaseClaim(c.file, c.origin);
748
+ try {
749
+ releaseSlot();
750
+ } catch {
751
+ // The original error is the one to report.
752
+ }
753
+ throw error;
754
+ }
755
+ }
756
+
757
+ module.exports = {
758
+ EMISSION_RULES,
759
+ INPUT_DOMAINS,
760
+ arbitrate,
761
+ emitContext,
762
+ renderEmission,
763
+ resolveIdentity,
764
+ ruleAction,
765
+ ruleVector,
766
+ utf8Bytes,
767
+ };