@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
@@ -2,26 +2,62 @@
2
2
 
3
3
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
4
 
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
5
8
  const { EvidenceLedger } = require('./evidence-ledger.js');
6
9
  const { ClaimsManager } = require('./claims.js');
7
10
  const { ResearchRequestStore } = require('./research-request.js');
8
- const { finishRuntime, usageError } = require('./cli-result.js');
9
- const { resolveActiveFeature } = require('./feature-resolve.js');
11
+ const { finishRuntime, usageError, EXIT } = require('./cli-result.js');
12
+ const nativeConsoleLog = console.log;
13
+ const { resolveFeature } = require('./task-scope.js');
10
14
  const { readRegister } = require('./decision-register.js');
15
+ const compose = require('./context/compose.js');
16
+ const {
17
+ FIELD_CLASSES,
18
+ buildPackItem,
19
+ buildObjectiveItem,
20
+ buildConstraintItem,
21
+ buildAcceptanceCriteriaItem,
22
+ buildVerificationRequirementItem,
23
+ buildScopeExcludedItem,
24
+ buildClaimConflictItem,
25
+ buildBlockingGapItem,
26
+ buildResearchRequestItem,
27
+ buildDecisionReasonItem,
28
+ buildDecisionLiveItem,
29
+ buildClaimSupportedItem,
30
+ buildClaimHypothesisItem,
31
+ buildPointerItem,
32
+ buildRelevantFileItem,
33
+ buildEvidenceSummaryItem,
34
+ buildStructuralContextItem,
35
+ computeEnvelopes,
36
+ linearFit,
37
+ groupOmissions,
38
+ computePackDigest,
39
+ formatOverflowMessage,
40
+ formatMarkdown,
41
+ formatJson,
42
+ } = require('./context/compose.js');
43
+ const {
44
+ extractMandatory,
45
+ parseMandatoryFile,
46
+ applyMandatoryOverride,
47
+ } = require('./context/mandatory.js');
11
48
 
12
49
  const NO_DECISIONS = Object.freeze({ available: false, live: [], liveCount: 0 });
13
50
 
14
- const TASK_CLASS_BUDGETS = Object.freeze({
15
- feature: 225000,
16
- 'dependency-change': 85000,
17
- bug: 71000,
18
- refactor: 71000,
19
- documentation: 57000,
20
- operations: 45000,
21
- review: 42000,
22
- research: 35302,
23
- 'trivial-edit': 30000,
24
- });
51
+ const { TASK_CLASS_BUDGETS, resolveBudget, getRegisteredHarnessIds, getRegisteredHarnesses } = require('./context/budget.js');
52
+ const { createDeliveryEntry, readDeliveryEntry, recordDelivery, makeTokenRecord } = require('./context/delivery-store.js');
53
+ const { boundedAcquire } = require('./task-state.js');
54
+ const pointerTools = require('./context/pointer.js');
55
+ const { arbitrate, emitContext } = require('./context/arbitrate.js');
56
+ const { updateTaskState } = require('./task-state.js');
57
+ const { taskRecord } = require('./task-scope.js');
58
+
59
+ let correctiveTrimFallbackCount = 0;
60
+ function getCorrectiveTrimFallbackCount() { return correctiveTrimFallbackCount; }
25
61
 
26
62
  /**
27
63
  * The live decisions of the feature the caller is working on (IC-012). The feature is resolved
@@ -34,7 +70,7 @@ const TASK_CLASS_BUDGETS = Object.freeze({
34
70
  */
35
71
  function loadLiveDecisions({ projectRoot, slug = null } ) {
36
72
  try {
37
- const feature = resolveActiveFeature({ projectRoot, slug });
73
+ const feature = resolveFeature({ projectRoot, slug });
38
74
  if (feature.error) return { ...NO_DECISIONS };
39
75
  let register;
40
76
  try {
@@ -55,6 +91,66 @@ function loadLiveDecisions({ projectRoot, slug = null } ) {
55
91
  }
56
92
  }
57
93
 
94
+ function hashSourceFiles(root , paths ) {
95
+ const result = {};
96
+ for (const source of [...new Set(paths)].sort()) {
97
+ if (typeof source !== 'string' || source === '') continue;
98
+ const absolute = path.resolve(root, source);
99
+ const relative = path.relative(path.resolve(root), absolute);
100
+ if (!relative || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) continue;
101
+ try {
102
+ result[relative.split(path.sep).join('/')] = `sha256:${crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex')}`;
103
+ } catch { /* unavailable source paths cannot be represented as verified sources */ }
104
+ }
105
+ return result;
106
+ }
107
+
108
+ function sourcesChanged(root , sources ) {
109
+ for (const [source, digest] of Object.entries(sources || {})) {
110
+ const current = hashSourceFiles(root, [source])[source];
111
+ if (current !== digest) return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ function classifyStoreFailure(error ) {
117
+ if (error?.code === 'store-locked' || error?.message === 'store-locked') return 'store-locked';
118
+ // Another writer reclaimed the lock mid-write; nothing was recorded and the store is intact.
119
+ if (error?.code === 'lock-lost') return 'lock-lost';
120
+ if (error?.code === 'store-unreadable') return 'store-unreadable';
121
+ if (['EACCES', 'EROFS', 'EPERM', 'ENOSPC'].includes(error?.code)) return 'store-unwritable';
122
+ return 'store-unwritable';
123
+ }
124
+
125
+ function profileRetention(harnessId , profiles ) {
126
+ if (!harnessId) return null;
127
+ const harnesses = profiles || getRegisteredHarnesses();
128
+ return harnesses.find((item ) => item.id === harnessId)?.contextProfile?.retention || null;
129
+ }
130
+
131
+ function appendPointerExpansion({ projectRoot, taskId, slug, key, expansion, fsImpl = fs } ) {
132
+ const { file, seed } = taskRecord({ projectRoot, store: 'delivery', taskId, slug, fsImpl });
133
+ updateTaskState({
134
+ fsImpl, file, prepare: seed,
135
+ acquire: (impl , target ) => boundedAcquire(impl, target, { retries: 3, waitMs: 5 }),
136
+ build(disk ) {
137
+ const deliveries = Array.isArray(disk?.deliveries) ? disk.deliveries : [];
138
+ let found = false;
139
+ const updated = deliveries.map((entry ) => {
140
+ if (entry.key !== key) return entry;
141
+ found = true;
142
+ return { ...entry, expansions: [...(entry.expansions || []), expansion] };
143
+ });
144
+ if (!found) {
145
+ const error = new Error('delivery entry disappeared');
146
+ error.code = 'entry-evicted';
147
+ throw error;
148
+ }
149
+ return { taskId, deliveries: updated };
150
+ },
151
+ });
152
+ }
153
+
58
154
  class ContextPackCompiler {
59
155
 
60
156
  /**
@@ -69,189 +165,517 @@ class ContextPackCompiler {
69
165
  maxClaims: defaultOptions.maxClaims || 20,
70
166
  maxEvidenceItems: defaultOptions.maxEvidenceItems || 25,
71
167
  maxResearchRequests: defaultOptions.maxResearchRequests || 20,
168
+ maxStructuralItems: defaultOptions.maxStructuralItems || 5,
169
+ enableCorrective: defaultOptions.enableCorrective !== false,
170
+ selfRefWidth: defaultOptions.selfRefWidth ?? null,
72
171
  };
73
172
  }
74
173
 
75
174
  /**
76
- * Compiles a ContextPack from task evidence and claims.
175
+ * Compiles a ContextPack from task evidence and claims through compose.ts.
77
176
  * @param {Object} params
78
- * @param {string} params.taskId
79
- * @param {string} [params.taskClass='feature']
80
- * @param {string} [params.objective='']
81
- * @param {Array<string>} [params.constraints=[]]
82
- * @param {Array<string>} [params.acceptanceCriteria=[]]
83
- * @param {Object} params.evidenceLedger
84
- * @param {Object} params.claimsManager
85
177
  * @param {Object} [budgetOverrides={}]
86
178
  * @returns {Object} ContextPack
87
179
  */
88
180
  compileContextPack(params , budgetOverrides = {}) {
89
181
  const {
90
- taskId,
182
+ taskId = 'default',
91
183
  taskClass = 'feature',
92
- objective = '',
93
- constraints = [],
94
- acceptanceCriteria = [],
184
+ objective: paramObjective,
185
+ constraints: paramConstraints = [],
186
+ acceptanceCriteria: paramAc = [],
187
+ verificationRequirements: paramVr = [],
188
+ scopeExcluded: paramScope = [],
189
+ blockingGaps: paramGaps = [],
95
190
  evidenceLedger,
96
191
  claimsManager,
97
192
  researchRequests = [],
98
193
  decisions = NO_DECISIONS,
194
+ mandatory: paramMandatory,
195
+ budget: paramBudget,
196
+ maxBytes,
197
+ harness,
198
+ bare = false,
199
+ mode: paramMode,
200
+ noRecord = false,
201
+ reason,
202
+ since,
203
+ expand,
204
+ pointers: suppliedPointers = [],
205
+ expansion = null,
99
206
  } = params;
100
207
 
101
208
  const limits = { ...this.options, ...budgetOverrides };
209
+ const contentLimits = {
210
+ maxFiles: limits.maxFiles,
211
+ maxClaims: limits.maxClaims,
212
+ maxEvidenceItems: limits.maxEvidenceItems,
213
+ maxResearchRequests: limits.maxResearchRequests,
214
+ maxStructuralItems: limits.maxStructuralItems,
215
+ };
216
+
217
+ const effectiveTaskClass = (!taskClass || typeof taskClass !== 'string' || taskClass.trim() === '') ? 'feature' : taskClass.trim();
218
+ const resolvedBudget = paramBudget || resolveBudget({ taskClass: effectiveTaskClass, maxBytes, harnessFlag: harness });
219
+
220
+ // Determine mandatory fields and provenance
221
+ const mandatoryFields = paramMandatory?.fields || {};
222
+ const mandatoryProv = paramMandatory?.provenance || {};
102
223
 
103
- const rawClaims = claimsManager ? claimsManager.getClaims(taskId) : [];
104
- const rawEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId }) : [];
224
+ const hasParamObjective = paramObjective !== undefined && paramObjective !== null;
225
+ const objective = hasParamObjective
226
+ ? paramObjective
227
+ : (mandatoryFields.objective || '');
105
228
 
106
- // Filter claims
107
- const supportedClaims = [];
108
- const activeHypotheses = [];
109
- const conflictedClaims = [];
229
+ const acSource = (paramAc.length > 0 ? paramAc : (mandatoryFields.acceptanceCriteria || []));
230
+ const constraintsSource = (paramConstraints.length > 0 ? paramConstraints : (mandatoryFields.constraints || []));
231
+ const vrSource = (paramVr.length > 0 ? paramVr : (mandatoryFields.verificationRequirements || []));
232
+ const scopeSource = (paramScope.length > 0 ? paramScope : (mandatoryFields.scopeExcluded || []));
233
+ const gapsSource = (paramGaps.length > 0 ? paramGaps : (mandatoryFields.blockingGaps || []));
234
+
235
+ const mandatoryProvenance = {
236
+ objective: hasParamObjective
237
+ ? { supplied: Boolean(objective), source: 'caller', locator: null, digest: null, maturity: null, reason: objective ? null : 'caller-supplied-empty', gaps: [] }
238
+ : (mandatoryProv.objective || { supplied: Boolean(objective), source: objective ? 'caller' : 'none', locator: null, digest: null, maturity: null, reason: objective ? null : 'none-in-artifact', gaps: [] }),
239
+ acceptanceCriteria: mandatoryProv.acceptanceCriteria || { supplied: acSource.length > 0, source: acSource.length > 0 ? 'requirement' : 'none', locator: null, digest: null, maturity: null, reason: acSource.length > 0 ? null : 'artifact-absent', gaps: [] },
240
+ constraints: mandatoryProv.constraints || { supplied: constraintsSource.length > 0, source: constraintsSource.length > 0 ? 'requirement' : 'none', locator: null, digest: null, maturity: null, reason: constraintsSource.length > 0 ? null : 'artifact-absent', gaps: [] },
241
+ verificationRequirements: mandatoryProv.verificationRequirements || { supplied: vrSource.length > 0, source: vrSource.length > 0 ? 'requirement' : 'none', locator: null, digest: null, maturity: null, reason: vrSource.length > 0 ? null : 'artifact-absent', gaps: [] },
242
+ scopeExcluded: mandatoryProv.scopeExcluded || { supplied: scopeSource.length > 0, source: scopeSource.length > 0 ? 'requirement' : 'none', locator: null, digest: null, maturity: null, reason: scopeSource.length > 0 ? null : 'artifact-absent', gaps: [] },
243
+ blockingGaps: mandatoryProv.blockingGaps || { supplied: gapsSource.length > 0, source: gapsSource.length > 0 ? 'requirement' : 'none', locator: null, digest: null, maturity: null, reason: gapsSource.length > 0 ? null : 'artifact-absent', gaps: [] },
244
+ };
245
+
246
+ // Claims
247
+ let rawClaims = [];
248
+ if (claimsManager) {
249
+ rawClaims = claimsManager.getClaims(taskId);
250
+ } else if (params.claims) {
251
+ if (Array.isArray(params.claims)) {
252
+ rawClaims = params.claims;
253
+ } else {
254
+ const cList = (params.claims.conflicts || []).map((c ) => ({ ...c, status: 'conflicted' }));
255
+ const sList = (params.claims.supported || []).map((s ) => ({ ...s, status: s.status || 'supported' }));
256
+ const hList = (params.claims.hypotheses || []).map((h ) => ({ ...h, status: h.status || 'hypothesis' }));
257
+ rawClaims = [...cList, ...sList, ...hList];
258
+ }
259
+ }
260
+ const conflictedClaims = [];
261
+ const supportedClaims = [];
262
+ const activeHypotheses = [];
110
263
 
111
264
  for (const claim of rawClaims) {
112
- if (claim.status === 'supported') {
113
- if (supportedClaims.length < limits.maxClaims) {
114
- supportedClaims.push({
115
- id: claim.id,
116
- statement: claim.statement,
117
- evidenceIds: claim.supportingEvidence,
118
- });
119
- }
120
- } else if (claim.status === 'hypothesis') {
121
- activeHypotheses.push({
122
- id: claim.id,
123
- statement: claim.statement,
124
- });
125
- } else if (claim.status === 'conflicted') {
126
- conflictedClaims.push({
127
- id: claim.id,
128
- statement: claim.statement,
129
- supportingEvidence: claim.supportingEvidence,
130
- contradictingEvidence: claim.contradictingEvidence,
265
+ if (claim.status === 'conflicted' || (Array.isArray(claim.contradictingEvidence) && claim.contradictingEvidence.length > 0)) {
266
+ conflictedClaims.push(claim);
267
+ } else if (claim.status === 'supported') {
268
+ supportedClaims.push({
269
+ ...claim,
270
+ evidenceIds: Array.isArray(claim.evidenceIds)
271
+ ? claim.evidenceIds
272
+ : (Array.isArray(claim.supportingEvidence) ? claim.supportingEvidence : []),
131
273
  });
274
+ } else if (claim.status === 'hypothesis') {
275
+ activeHypotheses.push(claim);
132
276
  }
133
277
  }
134
278
 
135
- // Extract relevant files and structural context from fresh evidence
136
- const relevantFileSet = new Set();
137
- const structuralNodes = [];
138
- const freshEvidenceItems = [];
279
+ // Evidence
280
+ const rawEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId }) : (params.evidence || []);
281
+ const relevantFileSet = new Set ();
282
+ const structuralNodes = [];
283
+ const freshEvidenceItems = [];
284
+ const nonFreshOmissions = [];
139
285
 
140
286
  for (const ev of rawEvidence) {
141
- if (ev.freshness?.status === 'FRESH') {
142
- if (ev.locator?.file) {
143
- relevantFileSet.add(ev.locator.file);
144
- }
145
- if (ev.kind === 'structural' && ev.content) {
146
- structuralNodes.push(ev.content);
147
- }
148
- if (freshEvidenceItems.length < limits.maxEvidenceItems) {
149
- freshEvidenceItems.push({
150
- id: ev.id,
151
- kind: ev.kind,
152
- locator: ev.locator,
153
- summary: typeof ev.content === 'string' ? ev.content.slice(0, 120) : null,
154
- });
155
- }
287
+ if (ev.status === 'superseded') {
288
+ nonFreshOmissions.push({
289
+ id: ev.id,
290
+ field: 'evidenceSummary',
291
+ reason: 'superseded',
292
+ });
293
+ } else if (ev.freshness?.status === 'FRESH') {
294
+ if (ev.locator?.file) relevantFileSet.add(ev.locator.file);
295
+ if (ev.kind === 'structural' && ev.content) structuralNodes.push(ev.content);
296
+ freshEvidenceItems.push({
297
+ id: ev.id,
298
+ kind: ev.kind,
299
+ locator: ev.locator,
300
+ summary: typeof ev.content === 'string' ? ev.content.slice(0, 120) : null,
301
+ freshness: 'FRESH',
302
+ });
303
+ } else {
304
+ const status = ev.freshness?.status === 'STALE' ? 'STALE' : 'UNKNOWN';
305
+ nonFreshOmissions.push({
306
+ id: ev.id,
307
+ field: 'evidenceSummary',
308
+ reason: 'not-fresh',
309
+ freshness: status,
310
+ });
156
311
  }
157
312
  }
313
+ const relevantFiles = params.relevantFiles
314
+ ? Array.from(new Set([...params.relevantFiles, ...relevantFileSet]))
315
+ : Array.from(relevantFileSet);
158
316
 
159
- const relevantFiles = Array.from(relevantFileSet).slice(0, limits.maxFiles);
317
+ // Build items
318
+ const items = [];
319
+ if (objective) items.push(buildObjectiveItem(objective));
160
320
 
161
- return {
162
- version: 1,
163
- taskId: taskId || 'default',
164
- taskClass,
165
- compiledAt: new Date().toISOString(),
166
- objective,
167
- constraints: [...constraints],
168
- // Ported from `evidence/context_pack.py` (plan task B.3): the only field the Python pack
169
- // carried that this one did not. What the task must satisfy belongs beside the evidence
170
- // gathered to satisfy it, otherwise the verification engine has to re-read the requirement
171
- // to find out what it is verifying against.
172
- verificationRequirements: [...acceptanceCriteria],
173
- claims: {
174
- supported: supportedClaims,
175
- hypotheses: activeHypotheses,
176
- conflicts: conflictedClaims,
177
- },
178
- relevantFiles,
179
- structuralContext: structuralNodes.slice(0, 5),
180
- evidenceCount: freshEvidenceItems.length,
181
- evidenceSummary: freshEvidenceItems,
182
- researchRequests: [...researchRequests]
183
- .sort((a, b) => Number(b.blocking && b.status !== 'ANSWERED')
184
- - Number(a.blocking && a.status !== 'ANSWERED'))
185
- .slice(0, limits.maxResearchRequests).map(r => ({
186
- id: r.id, stageId: r.stageId, question: r.question, status: r.status,
187
- blocking: r.blocking, claimId: r.claimId, evidenceIds: r.evidenceIds, gap: r.gap,
188
- })),
189
- decisions: {
190
- available: decisions.available, live: [...decisions.live], liveCount: decisions.liveCount,
191
- ...(decisions.reason ? { reason: decisions.reason } : {}),
192
- },
193
- budgetEnforcement: {
194
- totalFiles: relevantFiles.length,
195
- totalSupportedClaims: supportedClaims.length,
196
- totalActiveHypotheses: activeHypotheses.length,
197
- limits,
198
- },
199
- };
200
- }
321
+ const blockingUnanswered = researchRequests.filter((r ) => r.blocking && r.status !== 'ANSWERED');
322
+ const otherRequests = researchRequests.filter((r ) => !(r.blocking && r.status !== 'ANSWERED'));
201
323
 
202
- /**
203
- * Formats a ContextPack into a concise markdown context block.
204
- * @param {Object} pack
205
- * @returns {string}
206
- */
207
- formatMarkdown(pack ) {
208
- let md = `## ContextPack: [${pack.taskClass.toUpperCase()}] ${pack.taskId}\n`;
209
- if (pack.objective) {
210
- md += `**Objective:** ${pack.objective}\n\n`;
211
- }
324
+ if (!bare) {
325
+ acSource.forEach((ac , i ) => items.push(buildAcceptanceCriteriaItem(ac, i)));
326
+ constraintsSource.forEach((c , i ) => items.push(buildConstraintItem(c, i)));
327
+ vrSource.forEach((vr , i ) => items.push(buildVerificationRequirementItem(vr, i)));
328
+ scopeSource.forEach((se , i ) => items.push(buildScopeExcludedItem(se, i)));
329
+ gapsSource.forEach((bg , i ) => items.push(buildBlockingGapItem(bg, i)));
330
+ conflictedClaims.forEach((c ) => items.push(buildClaimConflictItem(c)));
212
331
 
213
- if (pack.claims.supported.length > 0) {
214
- md += `### Supported Facts & Invariants\n`;
215
- for (const c of pack.claims.supported) {
216
- md += `- ✓ ${c.statement} *(ev: ${c.evidenceIds.join(', ')})*\n`;
217
- }
218
- md += '\n';
219
- }
332
+ blockingUnanswered.forEach((r ) => items.push(buildResearchRequestItem(r)));
333
+ if (decisions.reason) items.push(buildDecisionReasonItem(decisions.reason));
220
334
 
221
- if (pack.claims.hypotheses.length > 0) {
222
- md += `### Active Hypotheses (Unverified)\n`;
223
- for (const h of pack.claims.hypotheses) {
224
- md += `- ? ${h.statement}\n`;
335
+ if (decisions.available && Array.isArray(decisions.live)) {
336
+ decisions.live.forEach((d ) => items.push(buildDecisionLiveItem(d)));
225
337
  }
226
- md += '\n';
338
+ supportedClaims.forEach((c ) => items.push(buildClaimSupportedItem(c)));
339
+ otherRequests.forEach((r ) => items.push(buildResearchRequestItem(r)));
340
+ activeHypotheses.forEach((h ) => items.push(buildClaimHypothesisItem(h)));
341
+ suppliedPointers.forEach((p ) => items.push(buildPointerItem(p)));
342
+ relevantFiles.forEach((f ) => items.push(buildRelevantFileItem(f)));
343
+ freshEvidenceItems.forEach((ev ) => items.push(buildEvidenceSummaryItem(ev)));
344
+ structuralNodes.forEach((sn , i ) => items.push(buildStructuralContextItem(sn, i)));
227
345
  }
228
346
 
229
- if (pack.researchRequests.length > 0) {
230
- md += `### Feature Research Requests\n`;
231
- for (const r of pack.researchRequests) {
232
- md += `- ${r.status} ${r.blocking ? '(blocking)' : '(nonblocking)'} ${r.stageId}: ${r.question}`;
233
- if (r.gap) md += ` — ${r.gap}`;
234
- if (r.claimId) md += ` (claim: ${r.claimId})`;
235
- md += '\n';
347
+ const hasMandatoryItems = {
348
+ objective: Boolean(objective),
349
+ acceptanceCriteria: !bare && acSource.length > 0,
350
+ constraints: !bare && constraintsSource.length > 0,
351
+ verificationRequirements: !bare && vrSource.length > 0,
352
+ scopeExcluded: !bare && scopeSource.length > 0,
353
+ blockingGaps: !bare && (gapsSource.length > 0 || blockingUnanswered.length > 0),
354
+ 'claims.conflicts': !bare && conflictedClaims.length > 0,
355
+ researchRequests: !bare && blockingUnanswered.length > 0,
356
+ };
357
+
358
+ const empty = rawEvidence.length === 0
359
+ && rawClaims.length === 0
360
+ && researchRequests.length === 0
361
+ && (!decisions || decisions.liveCount === 0);
362
+
363
+ const enableCorrective = budgetOverrides.enableCorrective !== undefined
364
+ ? budgetOverrides.enableCorrective
365
+ : (this.options.enableCorrective !== false);
366
+ const registeredHarnessIds = getRegisteredHarnessIds();
367
+ const reservedHarnessWidth = Math.max(...registeredHarnessIds.map((id ) => id.length), 0);
368
+ const selfRefWidth = Math.max(budgetOverrides.selfRefWidth ?? this.options.selfRefWidth ?? 0, reservedHarnessWidth);
369
+ const longestHarnessId = registeredHarnessIds.reduce((longest , id ) => id.length > longest.length ? id : longest, 'unstated');
370
+ const envelopeBudget = {
371
+ maxBytes: resolvedBudget.maxBytes,
372
+ source: resolvedBudget.source,
373
+ harness: { ...resolvedBudget.harness, id: longestHarnessId },
374
+ capacity: { value: null, basis: 'unknown:values belong to 085 (A-19)' },
375
+ toolResultLimitBytes: null,
376
+ ...(resolvedBudget.startupTokens ? { startupTokens: resolvedBudget.startupTokens } : {}),
377
+ };
378
+
379
+ const { envelopeMd, envelopeJson } = computeEnvelopes({
380
+ taskId,
381
+ taskClass: effectiveTaskClass,
382
+ budget: envelopeBudget,
383
+ mandatory: paramMandatory,
384
+ empty,
385
+ mode: bare ? 'bare' : (paramMode || 'full'),
386
+ hasMandatoryItems,
387
+ limits,
388
+ selfRefWidth,
389
+ recordingExpected: params.recordingExpected === true,
390
+ // Reserve the longest registered identity and delivery form for parity.
391
+ form: 'markdown',
392
+ harnessId: longestHarnessId,
393
+ });
394
+
395
+ const hasMandatoryRequests = !bare && blockingUnanswered.length > 0;
396
+ let deltaInfo = null;
397
+ let unchangedOmissions = [];
398
+ let removedIds = [];
399
+ let fitItems = items;
400
+ if (params.deltaSinceEntry && paramMode === 'delta') {
401
+ const oldItems = params.deltaSinceEntry.items || {};
402
+ const currentIds = new Set(items.map((item ) => item.id));
403
+ const added = [];
404
+ const changed = [];
405
+ const unchanged = [];
406
+ fitItems = [];
407
+ for (const item of items) {
408
+ const oldDigest = oldItems[item.id];
409
+ if (item.cls === 'mandatory') {
410
+ if (!oldDigest) added.push(item.id);
411
+ else if (oldDigest !== item.digest) changed.push(item.id);
412
+ else unchanged.push(item.id);
413
+ fitItems.push(item);
414
+ } else if (!oldDigest) {
415
+ added.push(item.id); fitItems.push({ ...item, deltaForced: true });
416
+ } else if (oldDigest === item.digest) {
417
+ unchanged.push(item.id);
418
+ unchangedOmissions.push({ id: item.id, field: item.field, reason: 'unchanged' });
419
+ } else {
420
+ changed.push(item.id); fitItems.push({ ...item, deltaForced: true });
421
+ }
236
422
  }
237
- md += '\n';
423
+ removedIds = Object.keys(oldItems).filter((id ) => !currentIds.has(id));
424
+ deltaInfo = { since, added, changed, removed: removedIds, unchanged };
238
425
  }
239
426
 
240
- if (pack.decisions && pack.decisions.liveCount > 0) {
241
- md += `### Live decisions\n`;
242
- for (const d of pack.decisions.live) md += `- ${d.id} (${d.topic}): ${d.statement}\n`;
243
- md += '\n';
244
- }
427
+ const fit = linearFit({
428
+ items: fitItems,
429
+ budget: resolvedBudget.maxBytes,
430
+ taskId,
431
+ taskClass: effectiveTaskClass,
432
+ envelopeMd,
433
+ envelopeJson,
434
+ initialOmissions: bare ? [] : [...nonFreshOmissions, ...unchangedOmissions],
435
+ sectionHeadingsJson: {
436
+ 'decisions.live': 5,
437
+ 'claims.supported': 5,
438
+ 'researchRequests (others)': hasMandatoryRequests ? 0 : 3,
439
+ 'claims.hypotheses': 4,
440
+ pointers: 3,
441
+ relevantFiles: 3,
442
+ evidenceSummary: 3,
443
+ structuralContext: 3,
444
+ },
445
+ itemCapLimits: {
446
+ relevantFiles: limits.maxFiles ?? 15,
447
+ 'claims.supported': limits.maxClaims ?? 20,
448
+ 'claims.hypotheses': limits.maxClaims ?? 20,
449
+ evidenceSummary: limits.maxEvidenceItems ?? 25,
450
+ 'researchRequests (others)': Math.max(0, (limits.maxResearchRequests ?? 20) - blockingUnanswered.length),
451
+ structuralContext: limits.maxStructuralItems ?? 5,
452
+ },
453
+ });
454
+
455
+ const assemblePack = (keptItems , omissionsList , isOverflow = false, overflowCause = null) => {
456
+ const keptAc = keptItems.filter((i ) => i.field === 'acceptanceCriteria').map((i ) => i.value);
457
+ const keptConstraints = keptItems.filter((i ) => i.field === 'constraints').map((i ) => i.value);
458
+ const keptVr = keptItems.filter((i ) => i.field === 'verificationRequirements').map((i ) => i.value);
459
+ const keptScope = keptItems.filter((i ) => i.field === 'scopeExcluded').map((i ) => i.value);
460
+ const keptGaps = keptItems.filter((i ) => i.field === 'blockingGaps').map((i ) => i.value);
461
+ const keptConflicts = keptItems.filter((i ) => i.field === 'claims.conflicts').map((i ) => i.value);
462
+ const keptLiveDecisions = keptItems.filter((i ) => i.field === 'decisions.live').map((i ) => i.value);
463
+ const keptSupported = keptItems.filter((i ) => i.field === 'claims.supported').map((i ) => i.value);
464
+ const keptRequests = keptItems.filter((i ) => i.field.startsWith('researchRequests')).map((i ) => i.value);
465
+ const keptHypotheses = keptItems.filter((i ) => i.field === 'claims.hypotheses').map((i ) => i.value);
466
+ const keptPointers = keptItems.filter((i ) => i.field === 'pointers').map((i ) => i.value);
467
+ const keptFiles = keptItems.filter((i ) => i.field === 'relevantFiles').map((i ) => i.value);
468
+ const keptEvidence = keptItems.filter((i ) => i.field === 'evidenceSummary').map((i ) => i.value);
469
+ const keptStructural = keptItems.filter((i ) => i.field === 'structuralContext').map((i ) => i.value);
470
+
471
+ const omissions = groupOmissions(omissionsList);
472
+ const packDigest = computePackDigest(keptItems);
473
+
474
+ const cause = overflowCause || (isOverflow ? (fit.cause || 'mandatory') : null);
475
+ const packTotalBytes = fit.totalBytes;
476
+ const packMandatoryBytes = fit.mandatoryBytesByForm?.[params.form === 'markdown' ? 'markdown' : 'json'] ?? fit.mandatoryBytes;
477
+ const packOptionalBytes = packTotalBytes - packMandatoryBytes;
478
+
479
+ const packed = {
480
+ version: 1,
481
+ status: isOverflow ? 'OVERFLOW' : (empty ? 'EMPTY' : 'OK'),
482
+ exitCode: isOverflow ? EXIT.OVERFLOW : (empty ? EXIT.EMPTY : EXIT.OK),
483
+ taskId,
484
+ taskClass: effectiveTaskClass,
485
+ compiledAt: new Date().toISOString(),
486
+ mode: bare ? 'bare' : (paramMode || 'full'),
487
+ budget: envelopeBudget,
488
+ overflow: isOverflow ? {
489
+ cause,
490
+ mandatoryBytes: packMandatoryBytes,
491
+ budgetBytes: resolvedBudget.maxBytes,
492
+ excessBytes: Math.max(0, packTotalBytes - resolvedBudget.maxBytes),
493
+ action: 'split-or-new-session',
494
+ } : null,
495
+ overflowAction: isOverflow ? {
496
+ message: formatOverflowMessage({
497
+ cause,
498
+ mandatoryBytes: packMandatoryBytes,
499
+ totalBytes: packTotalBytes,
500
+ budgetBytes: resolvedBudget.maxBytes,
501
+ source: resolvedBudget.source,
502
+ }),
503
+ } : null,
504
+ toolLimitNotice: null,
505
+ objective,
506
+ acceptanceCriteria: keptAc,
507
+ constraints: keptConstraints,
508
+ verificationRequirements: keptVr,
509
+ scopeExcluded: keptScope,
510
+ blockingGaps: keptGaps,
511
+ claims: {
512
+ conflicts: keptConflicts,
513
+ supported: keptSupported,
514
+ hypotheses: keptHypotheses,
515
+ },
516
+ researchRequests: keptRequests,
517
+ mandatory: mandatoryProvenance,
518
+ decisions: {
519
+ available: Boolean(decisions && decisions.available),
520
+ live: keptLiveDecisions,
521
+ liveCount: keptLiveDecisions.length,
522
+ ...(decisions?.reason ? { reason: decisions.reason } : {}),
523
+ },
524
+ pointers: keptPointers,
525
+ ...(expansion ? { expansion } : {}),
526
+ relevantFiles: keptFiles,
527
+ evidenceCount: keptEvidence.length,
528
+ evidenceSummary: keptEvidence,
529
+ structuralContext: keptStructural,
530
+ budgetEnforcement: {
531
+ totalFiles: relevantFiles.length,
532
+ totalSupportedClaims: supportedClaims.length,
533
+ totalActiveHypotheses: activeHypotheses.length,
534
+ limits: contentLimits,
535
+ },
536
+ empty,
537
+ omissions: bare ? { total: 0, groups: [] } : omissions,
538
+ size: {
539
+ totalBytes: packTotalBytes,
540
+ mandatoryBytes: packMandatoryBytes,
541
+ optionalBytes: packOptionalBytes,
542
+ fittedOn: fit.fittedOn,
543
+ },
544
+ accounting: {
545
+ tokens: makeTokenRecord(packTotalBytes),
546
+ },
547
+ delivery: {
548
+ key: params.recordingExpected ? `${packDigest}.${params.form || 'json'}.${params.harness?.id || 'unstated'}` : null,
549
+ packDigest,
550
+ recorded: params.recordingExpected === true,
551
+ reason: null,
552
+ since: since || null,
553
+ deltaRefused: params.deltaRefused || null,
554
+ },
555
+ delta: deltaInfo,
556
+ packEnd: {
557
+ mode: bare ? 'bare' : (paramMode || 'full'),
558
+ objective: objective ? 1 : 0,
559
+ acceptanceCriteria: keptAc.length,
560
+ constraints: keptConstraints.length,
561
+ verificationRequirements: keptVr.length,
562
+ scopeExcluded: keptScope.length,
563
+ conflicts: keptConflicts.length,
564
+ blockingGaps: keptGaps.length,
565
+ omitted: bare ? 0 : omissions.total,
566
+ bytes: packTotalBytes,
567
+ digest: packDigest.slice(0, 19),
568
+ },
569
+ };
570
+ Object.defineProperties(packed, {
571
+ deliveryItems: { value: keptItems.map((item ) => ({ ...item, cls: item.cls || item.class })) },
572
+ deliveryOmissions: { value: bare ? [] : omissionsList.map((item ) => ({ ...item })) },
573
+ deliveryAllItems: { value: items.map((item ) => ({ id: item.id, digest: item.digest, cls: item.cls || item.class })) },
574
+ trimOptional: { value: () => {
575
+ const currentItems = (packed ).deliveryItems ;
576
+ let trimIndex = -1;
577
+ for (let i = currentItems.length - 1; i >= 0; i -= 1) {
578
+ if (currentItems[i].cls === 'optional' && !currentItems[i].deltaForced) { trimIndex = i; break; }
579
+ }
580
+ if (trimIndex < 0) return null;
581
+ const dropped = currentItems[trimIndex];
582
+ const nextItems = currentItems.filter((_ , i ) => i !== trimIndex);
583
+ const nextOmissions = [...(packed ).deliveryOmissions, {
584
+ id: dropped.id, field: dropped.field, reason: 'budget', ...(dropped.freshness ? { freshness: dropped.freshness } : {}),
585
+ }];
586
+ return assemblePack(nextItems, nextOmissions, false, null);
587
+ } },
588
+ });
589
+ return packed;
590
+ };
591
+
592
+ let pack = assemblePack(fit.kept, fit.omissions, fit.overflow, fit.cause);
593
+
594
+ if (enableCorrective && pack.status === 'OK') {
595
+ let currentKept = [...fit.kept];
596
+ let currentOmissions = [...fit.omissions];
597
+ let fallbackCounted = false;
598
+
599
+ while (pack.status === 'OK') {
600
+ const mdOut = formatMarkdown(pack, { throwOnOverBudget: false });
601
+ const jsonOut = formatJson(pack, { throwOnOverBudget: false });
602
+ const mdLen = Buffer.byteLength(mdOut, 'utf8');
603
+ const jsonLen = Buffer.byteLength(jsonOut, 'utf8');
245
604
 
246
- if (pack.relevantFiles.length > 0) {
247
- md += `### Relevant File Locators\n`;
248
- for (const f of pack.relevantFiles) {
249
- md += `- \`${f}\`\n`;
605
+ if (mdLen <= resolvedBudget.maxBytes && jsonLen <= resolvedBudget.maxBytes) {
606
+ pack.size.fittedOn = jsonLen >= mdLen ? 'json' : 'markdown';
607
+ break;
608
+ }
609
+
610
+ let lastOptIdx = -1;
611
+ for (let idx = currentKept.length - 1; idx >= 0; idx--) {
612
+ const it = currentKept[idx];
613
+ if ((it.cls === 'optional' || it.class === 'optional') && !it.deltaForced) {
614
+ lastOptIdx = idx;
615
+ break;
616
+ }
617
+ }
618
+
619
+ if (lastOptIdx === -1) {
620
+ const mandatoryOnlyKept = currentKept.filter((i ) => i.cls === 'mandatory' || i.class === 'mandatory' || i.deltaForced);
621
+ const allOptOmitted = [
622
+ ...(bare ? [] : nonFreshOmissions),
623
+ ...items.filter((i ) => (i.cls === 'optional' || i.class === 'optional') && !i.deltaForced).map((i ) => ({
624
+ id: i.id,
625
+ field: i.field,
626
+ reason: 'mandatory-overflow',
627
+ ...(i.freshness ? { freshness: i.freshness } : {}),
628
+ })),
629
+ ];
630
+ const cause = Math.max(fit.mandatoryBytes, mdLen, jsonLen) > resolvedBudget.maxBytes ? 'mandatory' : 'accounting';
631
+ pack = assemblePack(mandatoryOnlyKept, allOptOmitted, true, cause);
632
+ break;
633
+ }
634
+
635
+ if (!fallbackCounted) {
636
+ correctiveTrimFallbackCount += 1;
637
+ fallbackCounted = true;
638
+ }
639
+ const [dropped] = currentKept.splice(lastOptIdx, 1);
640
+ currentOmissions.push({
641
+ id: dropped.id,
642
+ field: dropped.field,
643
+ reason: 'budget',
644
+ ...(dropped.freshness ? { freshness: dropped.freshness } : {}),
645
+ });
646
+ pack = assemblePack(currentKept, currentOmissions, false, null);
250
647
  }
251
- md += '\n';
252
648
  }
253
649
 
254
- return md;
650
+ const fittedOutput = params.form === 'markdown'
651
+ ? formatMarkdown(pack, { throwOnOverBudget: false })
652
+ : formatJson(pack, { throwOnOverBudget: false });
653
+ pack.size.totalBytes = Buffer.byteLength(fittedOutput, 'utf8');
654
+ pack.size.optionalBytes = pack.size.totalBytes - pack.size.mandatoryBytes;
655
+ pack.packEnd.bytes = pack.size.totalBytes;
656
+ pack.accounting.tokens = makeTokenRecord(pack.size.totalBytes);
657
+
658
+ return pack;
659
+ }
660
+
661
+ /**
662
+ * Formats a ContextPack into markdown per IC-005.
663
+ * @param {Object} pack
664
+ * @param {Object} [options]
665
+ * @returns {string}
666
+ */
667
+ formatMarkdown(pack , options = {}) {
668
+ return formatMarkdown(pack, options);
669
+ }
670
+
671
+ /**
672
+ * Formats a ContextPack into JSON per IC-004.
673
+ * @param {Object} pack
674
+ * @param {Object} [options]
675
+ * @returns {string}
676
+ */
677
+ formatJson(pack , options = {}) {
678
+ return formatJson(pack, options);
255
679
  }
256
680
 
257
681
  /**
@@ -281,9 +705,8 @@ class ContextPackCompiler {
281
705
  optional = {},
282
706
  } = options;
283
707
 
284
- const budgetBytes = Number.isInteger(maxBytes) && maxBytes > 0
285
- ? maxBytes
286
- : (TASK_CLASS_BUDGETS[taskClass] || 225000);
708
+ const resolvedBudget = resolveBudget({ taskClass, maxBytes });
709
+ const budgetBytes = resolvedBudget.maxBytes;
287
710
 
288
711
  const acceptanceCriteria = Array.isArray(mandatory.acceptanceCriteria) ? [...mandatory.acceptanceCriteria] : [];
289
712
  const constraints = Array.isArray(mandatory.constraints) ? [...mandatory.constraints] : [];
@@ -314,17 +737,75 @@ class ContextPackCompiler {
314
737
 
315
738
  const mandatoryBytes = Buffer.byteLength(mandatoryMd, 'utf8');
316
739
 
317
- // Rule 1: Mandatory content is never truncated; if mandatoryBytes > budgetBytes, returns overflow: true.
318
- if (mandatoryBytes > budgetBytes) {
319
- const omissions = [];
320
- const narrativeList = Array.isArray(optional.narrative) ? optional.narrative : [];
321
- const claimsList = Array.isArray(optional.unverifiedClaims) ? optional.unverifiedClaims : [];
322
- const hypList = Array.isArray(optional.hypotheses) ? optional.hypotheses : [];
740
+ const hypotheses = Array.isArray(optional.hypotheses) ? [...optional.hypotheses] : [];
741
+ const unverifiedClaims = Array.isArray(optional.unverifiedClaims) ? [...optional.unverifiedClaims] : [];
742
+ const narrative = Array.isArray(optional.narrative) ? [...optional.narrative] : [];
743
+
744
+ const items = [];
745
+ hypotheses.forEach((h , idx ) => {
746
+ const line = `- ? ${h}\n`;
747
+ items.push({
748
+ id: `hypothesis-${idx + 1}`,
749
+ field: 'hypotheses',
750
+ value: h,
751
+ class: 'optional',
752
+ cls: 'optional',
753
+ priority: 1,
754
+ md: line,
755
+ mdBytes: Buffer.byteLength(line, 'utf8'),
756
+ jsonBytes: 0,
757
+ });
758
+ });
323
759
 
324
- for (let i = 0; i < narrativeList.length; i++) omissions.push({ id: `narrative-${i + 1}`, reason: 'budget-cap-exceeded' });
325
- for (let i = 0; i < claimsList.length; i++) omissions.push({ id: `claim-${i + 1}`, reason: 'budget-cap-exceeded' });
326
- for (let i = 0; i < hypList.length; i++) omissions.push({ id: `hypothesis-${i + 1}`, reason: 'budget-cap-exceeded' });
760
+ unverifiedClaims.forEach((c , idx ) => {
761
+ const line = `- ? ${c}\n`;
762
+ items.push({
763
+ id: `claim-${idx + 1}`,
764
+ field: 'unverifiedClaims',
765
+ value: c,
766
+ class: 'optional',
767
+ cls: 'optional',
768
+ priority: 2,
769
+ md: line,
770
+ mdBytes: Buffer.byteLength(line, 'utf8'),
771
+ jsonBytes: 0,
772
+ });
773
+ });
327
774
 
775
+ narrative.forEach((n , idx ) => {
776
+ const line = `- ${n}\n`;
777
+ items.push({
778
+ id: `narrative-${idx + 1}`,
779
+ field: 'narrative',
780
+ value: n,
781
+ class: 'optional',
782
+ cls: 'optional',
783
+ priority: 3,
784
+ md: line,
785
+ mdBytes: Buffer.byteLength(line, 'utf8'),
786
+ jsonBytes: 0,
787
+ });
788
+ });
789
+
790
+ const fit = compose.linearFit({
791
+ items,
792
+ budget: budgetBytes,
793
+ taskId,
794
+ taskClass,
795
+ envelopeMd: mandatoryBytes,
796
+ envelopeJson: mandatoryBytes,
797
+ includeOmissionCosts: false,
798
+ sectionHeadingsMd: {
799
+ hypotheses: Buffer.byteLength('### Active Hypotheses (Unverified)\n\n', 'utf8'),
800
+ unverifiedClaims: Buffer.byteLength('### Unverified Claims\n\n', 'utf8'),
801
+ narrative: Buffer.byteLength('### Background Narrative\n\n', 'utf8'),
802
+ },
803
+ sectionHeadingsJson: {},
804
+ itemCapLimits: {},
805
+ });
806
+
807
+ if (fit.overflow) {
808
+ const omissions = items.map((i ) => ({ id: i.id, reason: 'budget-cap-exceeded' }));
328
809
  return {
329
810
  taskId,
330
811
  taskClass,
@@ -339,74 +820,30 @@ class ContextPackCompiler {
339
820
  };
340
821
  }
341
822
 
342
- // Rule 2: Optional content is trimmed in reverse priority order when mandatoryBytes + optionalBytes > budgetBytes.
343
- // Priority order: 1. hypotheses, 2. unverifiedClaims, 3. narrative.
344
- // Trimming in reverse priority: narrative first, then claims, then hypotheses.
345
- const hypotheses = Array.isArray(optional.hypotheses) ? [...optional.hypotheses] : [];
346
- const unverifiedClaims = Array.isArray(optional.unverifiedClaims) ? [...optional.unverifiedClaims] : [];
347
- const narrative = Array.isArray(optional.narrative) ? [...optional.narrative] : [];
348
-
349
- const keptHypotheses = [];
350
- const keptClaims = [];
351
- const keptNarrative = [];
352
- const omissions = [];
353
-
354
- const buildPayload = (hList , cList , nList ) => {
355
- let md = mandatoryMd;
356
- if (hList.length > 0) {
357
- md += `### Active Hypotheses (Unverified)\n`;
358
- for (const h of hList) md += `- ? ${h}\n`;
359
- md += '\n';
360
- }
361
- if (cList.length > 0) {
362
- md += `### Unverified Claims\n`;
363
- for (const c of cList) md += `- ? ${c}\n`;
364
- md += '\n';
365
- }
366
- if (nList.length > 0) {
367
- md += `### Background Narrative\n`;
368
- for (const n of nList) md += `- ${n}\n`;
369
- md += '\n';
370
- }
371
- return md;
372
- };
823
+ const keptHypotheses = fit.kept.filter((i ) => i.field === 'hypotheses').map((i ) => i.value);
824
+ const keptClaims = fit.kept.filter((i ) => i.field === 'unverifiedClaims').map((i ) => i.value);
825
+ const keptNarrative = fit.kept.filter((i ) => i.field === 'narrative').map((i ) => i.value);
373
826
 
374
- // Forward trial fitting: hypotheses first
375
- for (let i = 0; i < hypotheses.length; i++) {
376
- const trial = [...keptHypotheses, hypotheses[i]];
377
- const trialMd = buildPayload(trial, [], []);
378
- if (Buffer.byteLength(trialMd, 'utf8') <= budgetBytes) {
379
- keptHypotheses.push(hypotheses[i]);
380
- } else {
381
- omissions.push({ id: `hypothesis-${i + 1}`, reason: 'budget-cap-exceeded' });
382
- }
827
+ let payload = mandatoryMd;
828
+ if (keptHypotheses.length > 0) {
829
+ payload += `### Active Hypotheses (Unverified)\n`;
830
+ for (const h of keptHypotheses) payload += `- ? ${h}\n`;
831
+ payload += '\n';
383
832
  }
384
-
385
- // unverifiedClaims next
386
- for (let i = 0; i < unverifiedClaims.length; i++) {
387
- const trial = [...keptClaims, unverifiedClaims[i]];
388
- const trialMd = buildPayload(keptHypotheses, trial, []);
389
- if (Buffer.byteLength(trialMd, 'utf8') <= budgetBytes) {
390
- keptClaims.push(unverifiedClaims[i]);
391
- } else {
392
- omissions.push({ id: `claim-${i + 1}`, reason: 'budget-cap-exceeded' });
393
- }
833
+ if (keptClaims.length > 0) {
834
+ payload += `### Unverified Claims\n`;
835
+ for (const c of keptClaims) payload += `- ? ${c}\n`;
836
+ payload += '\n';
394
837
  }
395
-
396
- // narrative next
397
- for (let i = 0; i < narrative.length; i++) {
398
- const trial = [...keptNarrative, narrative[i]];
399
- const trialMd = buildPayload(keptHypotheses, keptClaims, trial);
400
- if (Buffer.byteLength(trialMd, 'utf8') <= budgetBytes) {
401
- keptNarrative.push(narrative[i]);
402
- } else {
403
- omissions.push({ id: `narrative-${i + 1}`, reason: 'budget-cap-exceeded' });
404
- }
838
+ if (keptNarrative.length > 0) {
839
+ payload += `### Background Narrative\n`;
840
+ for (const n of keptNarrative) payload += `- ${n}\n`;
841
+ payload += '\n';
405
842
  }
406
843
 
407
- const payload = buildPayload(keptHypotheses, keptClaims, keptNarrative);
408
844
  const totalBytes = Buffer.byteLength(payload, 'utf8');
409
845
  const optionalBytes = totalBytes - mandatoryBytes;
846
+ const omissions = fit.omissions.map((o ) => ({ id: o.id, reason: 'budget-cap-exceeded' }));
410
847
 
411
848
  return {
412
849
  taskId,
@@ -443,50 +880,474 @@ class ContextPackCompiler {
443
880
  * @param {string} [options.objective]
444
881
  * @param {boolean} [options.json=false]
445
882
  * @param {string} [options.stateRoot]
883
+ * @param {string} [options.sourceRoot] the checkout the verb runs in (default stateRoot)
446
884
  * @param {string} [options.slug] feature slug, when the task id is not the feature's own
885
+ * @param {string|null} [options.explicitSlug] the `--slug` the caller typed, if any (default `slug`).
886
+ * Only this selects a run for the stage-pack link: a branch-derived `slug` still keys the delivery
887
+ * store, but the branch name alone never picks a run (docs/specs/orchestrator-context-modes.md).
447
888
  * @returns {number} exit code
448
889
  */
449
- function handleContextPackCommand({ taskId, taskClass, objective, json = false, stateRoot, slug } = {}) {
890
+ function handleContextPackCommand({
891
+ taskId,
892
+ taskClass = 'feature',
893
+ objective,
894
+ json = false,
895
+ stateRoot,
896
+ sourceRoot,
897
+ slug,
898
+ explicitSlug,
899
+ mandatoryFile,
900
+ harness = null,
901
+ maxBytes,
902
+ bare = false,
903
+ noRecord = false,
904
+ reason = null,
905
+ since = null,
906
+ expand = null,
907
+ profiles = null,
908
+ } = {}) {
450
909
  const root = stateRoot || process.cwd();
910
+ // 115 IC-004/IC-007: records live under `root`; the feature folder, its plan and pointers, and the
911
+ // source files a delivery hashes are read in the checkout the verb runs in.
912
+ const source = sourceRoot || root;
913
+ const effectiveTaskClass = (!taskClass || typeof taskClass !== 'string' || taskClass.trim() === '') ? 'feature' : taskClass.trim();
914
+
915
+ // IC-001 Usage errors
916
+ if (!taskId || typeof taskId !== 'string' || taskId.trim() === '') {
917
+ return usageError('context-pack', '--task-id is required', json);
918
+ }
919
+ if (bare && expand) {
920
+ return usageError('context-pack', '--bare cannot be combined with --expand', json);
921
+ }
922
+ if (since && !/^sha256:[0-9a-f]{64}$/.test(since)) {
923
+ return usageError('context-pack', "--since must be 'sha256:' followed by 64 lowercase hex", json);
924
+ }
925
+ if (expand && (!since || !reason)) {
926
+ return usageError('context-pack', '--expand requires both --since and --reason', json);
927
+ }
928
+ if (harness) {
929
+ const validIds = getRegisteredHarnessIds();
930
+ if (!validIds.includes(harness)) {
931
+ return usageError('context-pack', `Unknown harness '${harness}'. Valid harnesses: ${validIds.join(', ')}`, json);
932
+ }
933
+ }
934
+
935
+ let parsedMaxBytes ;
936
+ let baseBudget = null;
937
+ if (maxBytes !== undefined && maxBytes !== null) {
938
+ const parsed = typeof maxBytes === 'number' ? maxBytes : parseInt(String(maxBytes).trim(), 10);
939
+ if (!Number.isInteger(parsed) || parsed <= 0 || (typeof maxBytes === 'string' && !/^[1-9]\d*$/.test(maxBytes.trim()))) {
940
+ return usageError('context-pack', '--max-bytes must be a positive integer', json);
941
+ }
942
+ parsedMaxBytes = parsed;
943
+
944
+ baseBudget = resolveBudget({ taskClass: effectiveTaskClass, harnessFlag: harness, profiles });
945
+ if (parsedMaxBytes > baseBudget.maxBytes && !reason) {
946
+ return usageError('context-pack', `--max-bytes (${parsedMaxBytes}) exceeds resolved budget (${baseBudget.maxBytes}) and requires --reason`, json);
947
+ }
948
+ }
949
+
950
+ const budget = resolveBudget({ taskClass: effectiveTaskClass, maxBytes: parsedMaxBytes, harnessFlag: harness, profiles });
951
+
952
+ const feature = resolveFeature({ projectRoot: source, slug: slug || null });
953
+
954
+ let deltaExpansions = [];
955
+ if (parsedMaxBytes !== undefined) {
956
+ if (!baseBudget) {
957
+ baseBudget = resolveBudget({ taskClass: effectiveTaskClass, harnessFlag: harness, profiles });
958
+ }
959
+ if (parsedMaxBytes > baseBudget.maxBytes) {
960
+ deltaExpansions.push({ at: new Date().toISOString(), kind: 'cap-raised', target: null, reason, bytesAdded: null });
961
+ }
962
+ }
963
+ const basis = resolveDeltaBasis({ root, source, taskId, slug, since: expand ? null : since, budget, profiles });
964
+ const { sinceEntry, deltaRefused, retention } = basis;
965
+ if (basis.expansion) deltaExpansions.push(basis.expansion);
966
+
967
+ if (expand) {
968
+ const prior = readDeliveryEntry({ projectRoot: root, taskId, slug, packDigest: since });
969
+ if (!prior) return usageError('context-pack', `Unknown digest '${since}'`, json);
970
+ const pointer = (prior.pointers || []).find((candidate ) => candidate.id === expand);
971
+ if (!pointer) return usageError('context-pack', `Unknown pointer '${expand}' in digest '${since}'`, json);
972
+ const current = pointerTools.readPointerSlice({ projectRoot: source, pointer });
973
+ if (!current) return usageError('context-pack', `Unknown pointer '${expand}' in digest '${since}'`, json);
974
+ const compiler = new ContextPackCompiler();
975
+ const pack = compiler.compileContextPack({ taskId, taskClass: effectiveTaskClass, objective: '', mandatory: { fields: {}, provenance: {} }, budget,
976
+ mode: 'expand', expansion: { id: expand, path: pointer.path, content: current.slice, targetChanged: current.digest !== pointer.digest } });
977
+ pack.status = 'OK';
978
+ pack.exitCode = EXIT.OK;
979
+ pack.mode = 'expand';
980
+ pack.delivery = { key: prior.key, packDigest: since, recorded: false, reason: noRecord ? 'no-record' : null,
981
+ since, deltaRefused: null, expansionRecorded: false };
982
+ pack.packEnd.mode = 'expand';
983
+ pack.packEnd.digest = since.slice(7, 19);
984
+ const sliceBytes = Buffer.byteLength(current.slice, 'utf8');
985
+ let output = '';
986
+ let emitted = 0;
987
+ for (let render = 0; render < 3; render += 1) {
988
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
989
+ emitted = Buffer.byteLength(output, 'utf8');
990
+ const tokenValue = Math.ceil(emitted / 4);
991
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
992
+ pack.accounting.tokens = makeTokenRecord(emitted);
993
+ }
994
+ if (emitted > budget.maxBytes) {
995
+ return usageError('context-pack', `slice is ${emitted} B, over the ${budget.maxBytes} B cap (${budget.source}); re-run with --max-bytes ${emitted} --reason "${reason}"`, json);
996
+ }
997
+ let expansionRecorded = false;
998
+ let recordReason = noRecord ? 'no-record' : null;
999
+ if (!noRecord) {
1000
+ const expansionRecord = { at: new Date().toISOString(), kind: 'pointer-followed', target: expand, reason,
1001
+ bytesAdded: sliceBytes, targetChanged: current.digest !== pointer.digest };
1002
+ try {
1003
+ appendPointerExpansion({ projectRoot: root, taskId, slug, key: prior.key, expansion: expansionRecord });
1004
+ expansionRecorded = true;
1005
+ recordReason = null;
1006
+ } catch (error ) {
1007
+ recordReason = error?.code === 'entry-evicted' ? 'entry-evicted' : classifyStoreFailure(error);
1008
+ }
1009
+ }
1010
+ pack.delivery.recorded = expansionRecorded;
1011
+ pack.delivery.reason = recordReason;
1012
+ pack.delivery.expansionRecorded = expansionRecorded;
1013
+ pack.expansionRecorded = expansionRecorded;
1014
+ for (let render = 0; render < 3; render += 1) {
1015
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1016
+ emitted = Buffer.byteLength(output, 'utf8');
1017
+ const tokenValue = Math.ceil(emitted / 4);
1018
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
1019
+ pack.accounting.tokens = makeTokenRecord(emitted);
1020
+ }
1021
+ if (console.log !== nativeConsoleLog) console.log(output);
1022
+ else process.stdout.write(output);
1023
+ return finishRuntime(EXIT.OK);
1024
+ }
1025
+
1026
+ let mandatory = gatherMandatory({ root: source, taskId, feature });
1027
+
1028
+ if (mandatoryFile) {
1029
+ try {
1030
+ let fileContent ;
1031
+ if (mandatoryFile === '-') {
1032
+ fileContent = fs.readFileSync(0, 'utf8');
1033
+ } else {
1034
+ if (!fs.existsSync(mandatoryFile)) {
1035
+ const err = new Error(`--mandatory-file '${mandatoryFile}' does not exist`);
1036
+ err.code = 'USAGE_ERROR';
1037
+ throw err;
1038
+ }
1039
+ fileContent = fs.readFileSync(mandatoryFile, 'utf8');
1040
+ }
1041
+ const override = parseMandatoryFile(fileContent, mandatoryFile);
1042
+ mandatory = applyMandatoryOverride(mandatory, override);
1043
+ } catch (err ) {
1044
+ return usageError('context-pack', err.message, json, err);
1045
+ }
1046
+ }
1047
+
1048
+ const { pack, output } = deliverPack({
1049
+ root, source, taskId, slug, effectiveTaskClass, objective, mandatory, budget, harness, parsedMaxBytes, bare, noRecord,
1050
+ json, reason, since, sinceEntry, deltaRefused, deltaExpansions, expand, retention, feature,
1051
+ });
1052
+ // Issue #9 stage 3: a recorded delivery is named on the run stage active now (best effort, never fails the verb).
1053
+ if (pack.delivery?.recorded === true) {
1054
+ try {
1055
+ require('./workflow-orchestrator.js').recordStagePack({ stateRoot: root, sourceRoot: source, taskId, slug: (explicitSlug === undefined ? slug : explicitSlug) || null, digest: pack.delivery.packDigest,
1056
+ checkout: !feature.error && feature.paths?.repo_root ? { repoRoot: feature.paths.repo_root, branch: feature.paths.branch || null } : null });
1057
+ } catch { /* an unrecorded link is unknown, not a failure */ }
1058
+ }
1059
+ if (console.log !== nativeConsoleLog) {
1060
+ console.log(output);
1061
+ } else {
1062
+ process.stdout.write(output);
1063
+ }
1064
+ return finishRuntime(pack.exitCode);
1065
+ }
1066
+
1067
+ /**
1068
+ * The context-pack verb's compile, fit, render and delivery-record steps for a full or delta pack,
1069
+ * with no output: `handleContextPackCommand()` prints the result and `packForEmission()` hands it
1070
+ * to `emitContext()`. Returns the final pack and its rendered text in the requested form.
1071
+ */
1072
+ function deliverPack({
1073
+ root, source = root, taskId, slug, effectiveTaskClass, objective, mandatory, budget, harness, parsedMaxBytes, bare, noRecord,
1074
+ json, reason, since, sinceEntry, deltaRefused, deltaExpansions, expand, retention, feature,
1075
+ } ) {
451
1076
  const ledger = new EvidenceLedger({ repoRoot: root, slug });
452
1077
  ledger.load(taskId);
453
1078
  const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: root, slug });
454
1079
  claims.load(taskId);
455
1080
  claims.evaluateAll();
1081
+
456
1082
  const researchRequests = new ResearchRequestStore({ projectRoot: root, slug }).list(taskId);
457
1083
 
458
1084
  const compiler = new ContextPackCompiler();
459
- const pack = compiler.compileContextPack({
1085
+ let pack = compiler.compileContextPack({
460
1086
  taskId,
461
- // The library's own default. Passed through rather than substituted here so there is one
462
- // place that decides what an unstated class means for a *label* — which is all it is in a
463
- // pack, unlike readiness where it selects the contract.
464
- ...(taskClass ? { taskClass } : {}),
465
- objective: objective || '',
1087
+ taskClass: effectiveTaskClass,
1088
+ objective: objective !== undefined && objective !== null ? objective : undefined,
1089
+ mandatory,
1090
+ budget,
1091
+ harness,
1092
+ maxBytes: parsedMaxBytes,
1093
+ bare,
1094
+ noRecord,
1095
+ recordingExpected: !noRecord,
1096
+ form: json ? 'json' : 'markdown',
1097
+ reason,
1098
+ since,
1099
+ mode: since && !deltaRefused && !expand ? 'delta' : undefined,
1100
+ deltaSinceEntry: since && !deltaRefused && !expand ? sinceEntry : null,
1101
+ deltaRefused,
1102
+ expand,
1103
+ pointers: pointerTools.collectPointers({ projectRoot: source, feature }),
466
1104
  evidenceLedger: ledger,
467
1105
  claimsManager: claims,
468
1106
  researchRequests,
469
- decisions: loadLiveDecisions({ projectRoot: root, slug: slug || null }),
1107
+ decisions: loadLiveDecisions({ projectRoot: source, slug: slug || null }),
470
1108
  });
471
1109
 
472
- const empty = pack.evidenceCount === 0
473
- && pack.claims.supported.length === 0
474
- && pack.claims.hypotheses.length === 0
475
- && pack.claims.conflicts.length === 0
476
- && pack.researchRequests.length === 0
477
- && pack.decisions.liveCount === 0;
478
-
479
- if (json) console.log(JSON.stringify({ ...pack, empty }, null, 2));
480
- else {
481
- console.log(compiler.formatMarkdown(pack));
482
- if (empty) console.log(`_No evidence or claims are recorded for task '${taskId}'; this pack states nothing._\n`);
1110
+ let deliverySources = null;
1111
+ let deliveryRetention = null;
1112
+ const form = json ? 'json' : 'markdown';
1113
+ let deliveryRecorded = !noRecord;
1114
+ let deliveryReason = noRecord ? 'no-record' : null;
1115
+ pack.delivery.recorded = deliveryRecorded;
1116
+ pack.delivery.reason = deliveryReason;
1117
+ pack.delivery.key = noRecord ? null : `${pack.delivery.packDigest}.${form}.${budget.harness?.id || 'unstated'}`;
1118
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1119
+ if (pack.status === 'OVERFLOW') {
1120
+ pack = { ...pack, deliveryOmissions: pack.deliveryOmissions.map((item ) => item.reason === 'budget' ? { ...item, reason: 'mandatory-overflow' } : item) };
1121
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1122
+ }
1123
+ if (!noRecord) {
1124
+ deliverySources = hashSourceFiles(source, pack.relevantFiles || []);
1125
+ deliveryRetention = retention?.value === 'supported' || retention?.value === 'unsupported'
1126
+ ? { status: retention.value, basis: `declared:${retention.source || 'profile'}` }
1127
+ : { status: 'unknown', basis: retention?.unverified || 'contextProfile.retention is unverified' };
1128
+ }
1129
+
1130
+ const renderTrimmed = () => {
1131
+ while (true) {
1132
+ let mdOutput = '';
1133
+ let jsonOutput = '';
1134
+ let mdBytes = 0;
1135
+ let jsonBytes = 0;
1136
+ let stable = false;
1137
+ for (let render = 0; render < 3; render += 1) {
1138
+ mdOutput = compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1139
+ jsonOutput = compiler.formatJson(pack, { throwOnOverBudget: false });
1140
+ mdBytes = Buffer.byteLength(mdOutput, 'utf8');
1141
+ jsonBytes = Buffer.byteLength(jsonOutput, 'utf8');
1142
+ const emitted = json ? jsonBytes : mdBytes;
1143
+ const priorBytes = pack.size.totalBytes;
1144
+ const priorOptionalBytes = pack.size.optionalBytes;
1145
+ const tokens = makeTokenRecord(emitted);
1146
+ const priorValue = pack.accounting.tokens?.estimated?.value;
1147
+ pack.size.totalBytes = emitted;
1148
+ pack.size.optionalBytes = emitted - pack.size.mandatoryBytes;
1149
+ pack.accounting.tokens = tokens;
1150
+ if (priorValue === tokens.estimated.value && priorBytes === emitted && priorOptionalBytes === pack.size.optionalBytes) { stable = true; break; }
1151
+ }
1152
+ if (!stable) {
1153
+ mdOutput = compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1154
+ jsonOutput = compiler.formatJson(pack, { throwOnOverBudget: false });
1155
+ mdBytes = Buffer.byteLength(mdOutput, 'utf8');
1156
+ jsonBytes = Buffer.byteLength(jsonOutput, 'utf8');
1157
+ pack.size.totalBytes = json ? jsonBytes : mdBytes;
1158
+ pack.size.optionalBytes = pack.size.totalBytes - pack.size.mandatoryBytes;
1159
+ pack.accounting.tokens = makeTokenRecord(pack.size.totalBytes);
1160
+ }
1161
+ if (pack.status !== 'OK' || (mdBytes <= budget.maxBytes && jsonBytes <= budget.maxBytes)) {
1162
+ const output = json ? jsonOutput : mdOutput;
1163
+ const bytes = json ? jsonBytes : mdBytes;
1164
+ pack.packEnd.bytes = bytes;
1165
+ pack.size.totalBytes = bytes;
1166
+ pack.size.optionalBytes = bytes - pack.size.mandatoryBytes;
1167
+ pack.accounting.tokens = makeTokenRecord(bytes);
1168
+ return { output, bytes };
1169
+ }
1170
+ const trimmed = (pack ).trimOptional?.();
1171
+ if (trimmed) {
1172
+ pack = trimmed;
1173
+ pack.delivery.recorded = deliveryRecorded;
1174
+ pack.delivery.reason = deliveryReason;
1175
+ pack.delivery.key = deliveryRecorded ? `${pack.delivery.packDigest}.${form}.${budget.harness?.id || 'unstated'}` : null;
1176
+ pack.accounting.tokens = makeTokenRecord(json ? jsonBytes : mdBytes);
1177
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1178
+ continue;
1179
+ }
1180
+ const cause = pack.size.mandatoryBytes > budget.maxBytes ? 'mandatory' : 'accounting';
1181
+ pack.status = 'OVERFLOW';
1182
+ pack.exitCode = EXIT.OVERFLOW;
1183
+ if (cause === 'mandatory') {
1184
+ pack = { ...pack, deliveryOmissions: pack.deliveryOmissions.map((item ) => item.reason === 'budget' ? { ...item, reason: 'mandatory-overflow' } : item) };
1185
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1186
+ }
1187
+ pack.overflow = { cause, mandatoryBytes: pack.size.mandatoryBytes, budgetBytes: budget.maxBytes,
1188
+ excessBytes: Math.max(0, Math.max(mdBytes, jsonBytes) - budget.maxBytes), action: 'split-or-new-session' };
1189
+ pack.overflowAction = { message: cause === 'accounting'
1190
+ ? `The omission record brings the pack to ${Math.max(mdBytes, jsonBytes)} B against a ${budget.maxBytes} B budget.`
1191
+ : `It is ${pack.size.mandatoryBytes} B against a ${budget.maxBytes} B budget (${budget.source}).` };
1192
+ let output = '';
1193
+ let bytes = 0;
1194
+ for (let render = 0; render < 3; render += 1) {
1195
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1196
+ bytes = Buffer.byteLength(output, 'utf8');
1197
+ const tokenValue = Math.ceil(bytes / 4);
1198
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
1199
+ pack.accounting.tokens = makeTokenRecord(bytes);
1200
+ }
1201
+ pack.packEnd.bytes = bytes;
1202
+ pack.size.totalBytes = bytes;
1203
+ return { output, bytes };
1204
+ }
1205
+ };
1206
+ let rendered = renderTrimmed();
1207
+ let output = rendered.output;
1208
+ let emittedBytes = rendered.bytes;
1209
+ if (!noRecord) {
1210
+ const entry = createDeliveryEntry({ pack: { ...pack, deliveryOmissions: pack.deliveryOmissions }, form, harness: budget.harness?.id || null,
1211
+ sources: deliverySources, expansions: deltaExpansions, retention: deliveryRetention });
1212
+ entry.bytes = emittedBytes;
1213
+ entry.tokens = makeTokenRecord(emittedBytes);
1214
+ try {
1215
+ const result = recordDelivery({ projectRoot: root, taskId, slug, entry, acquire: boundedAcquire });
1216
+ pack.delivery.key = result?.key || entry.key;
1217
+ deliveryRecorded = true;
1218
+ deliveryReason = null;
1219
+ pack.delivery.recorded = deliveryRecorded;
1220
+ pack.delivery.reason = deliveryReason;
1221
+ } catch (error ) {
1222
+ deliveryRecorded = false;
1223
+ deliveryReason = classifyStoreFailure(error);
1224
+ pack.delivery.key = null;
1225
+ pack.delivery.recorded = deliveryRecorded;
1226
+ pack.delivery.reason = deliveryReason;
1227
+ }
1228
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, pack.delivery.recorded);
1229
+ rendered = renderTrimmed();
1230
+ output = rendered.output;
1231
+ emittedBytes = rendered.bytes;
1232
+ }
1233
+ if (pack.status === 'OVERFLOW') {
1234
+ pack.overflow.excessBytes = Math.max(0, emittedBytes - budget.maxBytes);
1235
+ pack.overflowAction.message = formatOverflowMessage({
1236
+ cause: pack.overflow.cause,
1237
+ mandatoryBytes: pack.size.mandatoryBytes,
1238
+ totalBytes: emittedBytes,
1239
+ budgetBytes: budget.maxBytes,
1240
+ source: budget.source,
1241
+ });
1242
+ rendered = renderTrimmed();
1243
+ output = rendered.output;
1244
+ emittedBytes = rendered.bytes;
1245
+ pack.packEnd.bytes = emittedBytes;
1246
+ pack.size.totalBytes = emittedBytes;
1247
+ }
1248
+ return { pack, output };
1249
+ }
1250
+
1251
+ /** The delta basis of a `since` digest: its delivery entry, or why a delta was refused (widened to full). */
1252
+ function resolveDeltaBasis({ root, source = root, taskId, slug, since, budget, profiles } ) {
1253
+ const retention = profileRetention(budget.harness?.id || null, profiles);
1254
+ let sinceEntry = null;
1255
+ let deltaRefused = null;
1256
+ let expansion = null;
1257
+ if (since) {
1258
+ sinceEntry = readDeliveryEntry({ projectRoot: root, taskId, slug, packDigest: since });
1259
+ if (!sinceEntry) deltaRefused = 'since-unknown';
1260
+ else if (sourcesChanged(source, sinceEntry.sources || {})) deltaRefused = 'sources-changed';
1261
+ else if (!budget.harness?.id) deltaRefused = 'harness-unstated';
1262
+ else if (retention?.value !== 'supported') deltaRefused = 'retention-unproven';
1263
+ if (deltaRefused) {
1264
+ expansion = { at: new Date().toISOString(), kind: 'delta-widened', target: null, reason: deltaRefused, bytesAdded: null };
1265
+ }
483
1266
  }
484
- return finishRuntime(empty ? 1 : 0);
1267
+ return { sinceEntry, deltaRefused, retention, expansion };
1268
+ }
1269
+
1270
+ /** The mandatory fields of a task from its feature's requirement.md and the task's plan stories. */
1271
+ function gatherMandatory({ root, taskId, feature } ) {
1272
+ let reqText = null;
1273
+ let reqPath = 'requirement.md';
1274
+ if (!feature.error && feature.featureDir) {
1275
+ reqPath = feature.paths?.requirement
1276
+ ? path.resolve(feature.repoRoot || root, feature.paths.requirement)
1277
+ : path.join(feature.featureDir, 'intention', 'requirement.md');
1278
+ try {
1279
+ if (fs.existsSync(reqPath)) {
1280
+ reqText = fs.readFileSync(reqPath, 'utf8');
1281
+ }
1282
+ } catch {
1283
+ reqText = null;
1284
+ }
1285
+ }
1286
+
1287
+ let taskStories = [];
1288
+ if (!feature.error && feature.featureDir && taskId) {
1289
+ let planPath = null;
1290
+ if (feature.paths?.plan) {
1291
+ planPath = path.resolve(feature.repoRoot || root, feature.paths.plan);
1292
+ } else {
1293
+ const nestedPlan = path.join(feature.featureDir, 'plan', 'plan.md');
1294
+ const flatPlan = path.join(feature.featureDir, 'plan.md');
1295
+ if (fs.existsSync(nestedPlan)) planPath = nestedPlan;
1296
+ else if (fs.existsSync(flatPlan)) planPath = flatPlan;
1297
+ }
1298
+ if (planPath && fs.existsSync(planPath)) {
1299
+ try {
1300
+ const planText = fs.readFileSync(planPath, 'utf8');
1301
+ const { parsePlan } = require('./scaffold/artifacts.js');
1302
+ const parsedPlan = parsePlan(planText);
1303
+ const matchedTask = (parsedPlan.tasks || []).find(
1304
+ (t ) => t.id === taskId || t.id.toLowerCase() === taskId.toLowerCase()
1305
+ );
1306
+ if (matchedTask && Array.isArray(matchedTask.stories)) {
1307
+ taskStories = matchedTask.stories;
1308
+ }
1309
+ } catch {
1310
+ taskStories = [];
1311
+ }
1312
+ }
1313
+ }
1314
+
1315
+ return extractMandatory({
1316
+ text: reqText,
1317
+ path: reqPath,
1318
+ featureError: feature.error ? (feature.message || feature.error) : null,
1319
+ taskStories,
1320
+ });
1321
+ }
1322
+
1323
+ /**
1324
+ * 078 IC-001: the context pack `emitContext()` delivers on `launch` and `delta`, compiled by
1325
+ * compileContextPack() through the verb's own steps, with `maxBytes` as the budget and `since` the
1326
+ * unit's last delivered pack digest (a delta when its entry qualifies, else widened to full).
1327
+ * `noRecord` (peek) leaves the delivery store untouched. Rendered as markdown.
1328
+ */
1329
+ function packForEmission({ root, stateRoot = root, taskId = 'default', taskClass = 'feature', slug = null, harness = null, maxBytes, since = null, noRecord = false, env = process.env } ) {
1330
+ const budget = resolveBudget({ taskClass, maxBytes, harnessFlag: harness, env });
1331
+ const feature = resolveFeature({ projectRoot: root, slug: slug || null });
1332
+ // 115 C-2: the delivery record goes where `context-pack` puts it, under the state root; the feature
1333
+ // folder, the plan and the hashed source files are read in `root`, the checkout emitting.
1334
+ const basis = resolveDeltaBasis({ root: stateRoot, source: root, taskId, slug, since, budget, profiles: null });
1335
+ return deliverPack({
1336
+ root: stateRoot, source: root, taskId, slug, effectiveTaskClass: taskClass, objective: undefined, mandatory: gatherMandatory({ root, taskId, feature }),
1337
+ budget, harness, parsedMaxBytes: maxBytes, bare: false, noRecord, json: false, reason: null, since,
1338
+ sinceEntry: basis.sinceEntry, deltaRefused: basis.deltaRefused, deltaExpansions: basis.expansion ? [basis.expansion] : [],
1339
+ expand: null, retention: basis.retention, feature,
1340
+ });
485
1341
  }
486
1342
 
487
1343
  module.exports = {
1344
+ classifyStoreFailure,
488
1345
  ContextPackCompiler,
1346
+ arbitrate,
1347
+ emitContext,
489
1348
  TASK_CLASS_BUDGETS,
490
1349
  handleContextPackCommand,
491
1350
  loadLiveDecisions,
1351
+ packForEmission,
1352
+ getCorrectiveTrimFallbackCount,
492
1353
  };