@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5

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 (236) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.antigravity-plugin/plugin.json +0 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  8. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  9. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  10. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  11. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  12. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  13. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  15. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  16. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  17. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  18. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  19. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  20. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  22. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  23. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  24. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  26. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  27. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  28. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  29. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  30. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  31. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  32. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  33. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  34. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  35. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  36. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  37. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  38. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  39. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  40. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  41. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  42. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  43. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  44. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  45. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  46. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  47. package/core/registry/assets.json +19 -0
  48. package/core/registry/contracts.json +2 -2
  49. package/core/registry/harnesses.json +196 -10
  50. package/core/registry/lifecycle.json +2626 -7
  51. package/core/registry/storage.json +110 -83
  52. package/core/registry/verification.json +1 -1
  53. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  54. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  55. package/core/shared/skills/do-design/SKILL.md +6 -6
  56. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  57. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  58. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  59. package/core/shared/skills/do-implement/SKILL.md +3 -2
  60. package/core/shared/skills/do-plan/SKILL.md +7 -8
  61. package/package.json +4 -2
  62. package/src/adapters/antigravity/{index.ts → index.js} +141 -105
  63. package/src/adapters/claude/{index.ts → index.js} +63 -49
  64. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  65. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  66. package/src/adapters/codex/{config.ts → config.js} +49 -49
  67. package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
  68. package/src/adapters/codex/{index.ts → index.js} +148 -105
  69. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  70. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  71. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  72. package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
  73. package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
  74. package/src/adapters/gemini/{index.ts → index.js} +96 -57
  75. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  76. package/src/adapters/{index.ts → index.js} +13 -13
  77. package/src/adapters/kiro/{index.ts → index.js} +49 -35
  78. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  79. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  80. package/src/adapters/pi/hooks.js +119 -0
  81. package/src/adapters/pi/{index.ts → index.js} +64 -39
  82. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  83. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  84. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  85. package/src/cli/commands/{install.ts → install.js} +25 -25
  86. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  87. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  88. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  89. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  90. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  91. package/src/cli/commands/{status.ts → status.js} +24 -24
  92. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  93. package/src/cli/commands/{update.ts → update.js} +20 -20
  94. package/src/cli/{index.ts → index.js} +43 -31
  95. package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
  96. package/src/cli/{shared.ts → shared.js} +40 -40
  97. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  98. package/src/helper/{git.ts → git.js} +2 -2
  99. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  100. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  101. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  102. package/src/helper/{prompt.ts → prompt.js} +5 -5
  103. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  104. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  105. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  106. package/src/helper/{toml.ts → toml.js} +48 -48
  107. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  108. package/src/install/{backup.ts → backup.js} +65 -65
  109. package/src/install/{context.ts → context.js} +15 -15
  110. package/src/install/{manifest.ts → manifest.js} +27 -27
  111. package/src/install/{mcp.ts → mcp.js} +30 -30
  112. package/src/install/{paths.ts → paths.js} +15 -15
  113. package/src/install/{targets.ts → targets.js} +10 -10
  114. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  115. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  116. package/src/lifecycle/coverage.js +57 -0
  117. package/src/lifecycle/hook-step.js +61 -0
  118. package/src/lifecycle/{index.ts → index.js} +160 -146
  119. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  120. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  121. package/src/lifecycle/{view.ts → view.js} +28 -28
  122. package/src/registry/{index.ts → index.js} +195 -66
  123. package/src/release/build-package.js +91 -0
  124. package/src/release/changelog-section.js +70 -0
  125. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  126. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  127. package/src/runtime/advice/{client.ts → client.js} +44 -44
  128. package/src/runtime/advice/{config.ts → config.js} +16 -16
  129. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  130. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  131. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  132. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  133. package/src/runtime/advice/{record.ts → record.js} +19 -19
  134. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  135. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  136. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  137. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  138. package/src/runtime/candidate.js +505 -0
  139. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  140. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  141. package/src/runtime/{claims.ts → claims.js} +44 -35
  142. package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
  143. package/src/runtime/{cli.ts → cli.js} +59 -292
  144. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  145. package/src/runtime/context/budget.js +203 -0
  146. package/src/runtime/context/compose.js +1367 -0
  147. package/src/runtime/context/delivery-store.js +102 -0
  148. package/src/runtime/context/mandatory.js +594 -0
  149. package/src/runtime/context/pointer.js +142 -0
  150. package/src/runtime/context-pack.js +1277 -0
  151. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  152. package/src/runtime/evidence-fields.js +39 -0
  153. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
  154. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  155. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  156. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  157. package/src/runtime/failure/{home.ts → home.js} +6 -6
  158. package/src/runtime/failure/{store.ts → store.js} +32 -32
  159. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  160. package/src/runtime/{freshness.ts → freshness.js} +59 -22
  161. package/src/runtime/{health.ts → health.js} +72 -72
  162. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  163. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  164. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  165. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  166. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  167. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  168. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  169. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  170. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  171. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  172. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  173. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  174. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  175. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  176. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  177. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  178. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  179. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  180. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  181. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  182. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  183. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  184. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  185. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  186. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  187. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  188. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  189. package/src/runtime/{mask.ts → mask.js} +22 -22
  190. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  191. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  192. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  193. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  194. package/src/runtime/{reach.ts → reach.js} +21 -21
  195. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  196. package/src/runtime/{readiness.ts → readiness.js} +113 -43
  197. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  198. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  199. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
  200. package/src/runtime/retrieval-record.js +223 -0
  201. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
  202. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  203. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  204. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  205. package/src/runtime/source-version.js +289 -0
  206. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  207. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  208. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  209. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  210. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  211. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  212. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  213. package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
  214. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  215. package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
  216. package/src/runtime/{task-state.ts → task-state.js} +86 -21
  217. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  218. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  219. package/src/runtime/trace/{render.ts → render.js} +7 -7
  220. package/src/runtime/trace/{views.ts → views.js} +40 -40
  221. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  222. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  223. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  224. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  225. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  226. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  227. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  228. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  229. package/src/state/{index.ts → index.js} +26 -26
  230. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  231. package/src/state/{run-lock.ts → run-lock.js} +44 -44
  232. package/core/.antigravity-plugin/hooks.json +0 -39
  233. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  234. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  235. package/core/harnesses/shared/hooks/hooks.json +0 -47
  236. package/src/runtime/context-pack.ts +0 -492
@@ -0,0 +1,1277 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
8
+ const { EvidenceLedger } = require('./evidence-ledger.js');
9
+ const { ClaimsManager } = require('./claims.js');
10
+ const { ResearchRequestStore } = require('./research-request.js');
11
+ const { finishRuntime, usageError, EXIT } = require('./cli-result.js');
12
+ const nativeConsoleLog = console.log;
13
+ const { resolveFeature } = require('./task-scope.js');
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');
48
+
49
+ const NO_DECISIONS = Object.freeze({ available: false, live: [], liveCount: 0 });
50
+
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 { updateTaskState } = require('./task-state.js');
56
+ const { taskStoreDir } = require('./task-scope.js');
57
+
58
+ let correctiveTrimFallbackCount = 0;
59
+ function getCorrectiveTrimFallbackCount() { return correctiveTrimFallbackCount; }
60
+
61
+ /**
62
+ * The live decisions of the feature the caller is working on (IC-012). The feature is resolved
63
+ * from the branch, or from `slug` when the task id is not the feature slug. A feature with no
64
+ * register or an unresolvable feature reads as "not available"; an unreadable register does too,
65
+ * with a `reason`. Neither throws: a context pack is a read, and a missing register must not stop
66
+ * a stage from getting its evidence.
67
+ * @param {{projectRoot:string, slug?:string|null}} options
68
+ * @returns {{available:boolean, live:Array<Object>, liveCount:number, reason?:string}}
69
+ */
70
+ function loadLiveDecisions({ projectRoot, slug = null } ) {
71
+ try {
72
+ const feature = resolveFeature({ projectRoot, slug });
73
+ if (feature.error) return { ...NO_DECISIONS };
74
+ let register;
75
+ try {
76
+ register = readRegister(feature.featureDir);
77
+ } catch (error ) {
78
+ // The register exists but cannot be trusted. Stay unavailable, but say why, so a stage
79
+ // sees the corruption instead of reading "no decisions".
80
+ return { ...NO_DECISIONS, reason: `register unreadable: ${error.message}` };
81
+ }
82
+ if (!register) return { ...NO_DECISIONS };
83
+ const live = register.decisions
84
+ .filter((d ) => d.status === 'live')
85
+ .sort((a , b ) => (a.topic < b.topic ? -1 : a.topic > b.topic ? 1 : 0))
86
+ .map(({ id, topic, statement, decidedBy, stage } ) => ({ id, topic, statement, decidedBy, stage }));
87
+ return { available: true, live, liveCount: live.length };
88
+ } catch {
89
+ return { ...NO_DECISIONS };
90
+ }
91
+ }
92
+
93
+ function hashSourceFiles(root , paths ) {
94
+ const result = {};
95
+ for (const source of [...new Set(paths)].sort()) {
96
+ if (typeof source !== 'string' || source === '') continue;
97
+ const absolute = path.resolve(root, source);
98
+ const relative = path.relative(path.resolve(root), absolute);
99
+ if (!relative || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) continue;
100
+ try {
101
+ result[relative.split(path.sep).join('/')] = `sha256:${crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex')}`;
102
+ } catch { /* unavailable source paths cannot be represented as verified sources */ }
103
+ }
104
+ return result;
105
+ }
106
+
107
+ function sourcesChanged(root , sources ) {
108
+ for (const [source, digest] of Object.entries(sources || {})) {
109
+ const current = hashSourceFiles(root, [source])[source];
110
+ if (current !== digest) return true;
111
+ }
112
+ return false;
113
+ }
114
+
115
+ function classifyStoreFailure(error ) {
116
+ if (error?.code === 'store-locked' || error?.message === 'store-locked') return 'store-locked';
117
+ if (error?.code === 'store-unreadable') return 'store-unreadable';
118
+ if (['EACCES', 'EROFS', 'EPERM', 'ENOSPC'].includes(error?.code)) return 'store-unwritable';
119
+ return 'store-unwritable';
120
+ }
121
+
122
+ function profileRetention(harnessId , profiles ) {
123
+ if (!harnessId) return null;
124
+ const harnesses = profiles || getRegisteredHarnesses();
125
+ return harnesses.find((item ) => item.id === harnessId)?.contextProfile?.retention || null;
126
+ }
127
+
128
+ function appendPointerExpansion({ projectRoot, taskId, slug, key, expansion, fsImpl = fs } ) {
129
+ const dir = taskStoreDir({ projectRoot, store: 'delivery', taskId, slug });
130
+ const file = path.join(dir, `${taskId}.json`);
131
+ updateTaskState({
132
+ fsImpl, file,
133
+ acquire: (impl , target ) => boundedAcquire(impl, target, { retries: 3, waitMs: 5 }),
134
+ build(disk ) {
135
+ const deliveries = Array.isArray(disk?.deliveries) ? disk.deliveries : [];
136
+ let found = false;
137
+ const updated = deliveries.map((entry ) => {
138
+ if (entry.key !== key) return entry;
139
+ found = true;
140
+ return { ...entry, expansions: [...(entry.expansions || []), expansion] };
141
+ });
142
+ if (!found) {
143
+ const error = new Error('delivery entry disappeared');
144
+ error.code = 'entry-evicted';
145
+ throw error;
146
+ }
147
+ return { taskId, deliveries: updated };
148
+ },
149
+ });
150
+ }
151
+
152
+ class ContextPackCompiler {
153
+
154
+ /**
155
+ * @param {Object} [defaultOptions]
156
+ * @param {number} [defaultOptions.maxFiles=15]
157
+ * @param {number} [defaultOptions.maxClaims=20]
158
+ * @param {number} [defaultOptions.maxEvidenceItems=25]
159
+ */
160
+ constructor(defaultOptions = {}) {
161
+ this.options = {
162
+ maxFiles: defaultOptions.maxFiles || 15,
163
+ maxClaims: defaultOptions.maxClaims || 20,
164
+ maxEvidenceItems: defaultOptions.maxEvidenceItems || 25,
165
+ maxResearchRequests: defaultOptions.maxResearchRequests || 20,
166
+ maxStructuralItems: defaultOptions.maxStructuralItems || 5,
167
+ enableCorrective: defaultOptions.enableCorrective !== false,
168
+ selfRefWidth: defaultOptions.selfRefWidth ?? null,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Compiles a ContextPack from task evidence and claims through compose.ts.
174
+ * @param {Object} params
175
+ * @param {Object} [budgetOverrides={}]
176
+ * @returns {Object} ContextPack
177
+ */
178
+ compileContextPack(params , budgetOverrides = {}) {
179
+ const {
180
+ taskId = 'default',
181
+ taskClass = 'feature',
182
+ objective: paramObjective,
183
+ constraints: paramConstraints = [],
184
+ acceptanceCriteria: paramAc = [],
185
+ verificationRequirements: paramVr = [],
186
+ scopeExcluded: paramScope = [],
187
+ blockingGaps: paramGaps = [],
188
+ evidenceLedger,
189
+ claimsManager,
190
+ researchRequests = [],
191
+ decisions = NO_DECISIONS,
192
+ mandatory: paramMandatory,
193
+ budget: paramBudget,
194
+ maxBytes,
195
+ harness,
196
+ bare = false,
197
+ mode: paramMode,
198
+ noRecord = false,
199
+ reason,
200
+ since,
201
+ expand,
202
+ pointers: suppliedPointers = [],
203
+ expansion = null,
204
+ } = params;
205
+
206
+ const limits = { ...this.options, ...budgetOverrides };
207
+ const contentLimits = {
208
+ maxFiles: limits.maxFiles,
209
+ maxClaims: limits.maxClaims,
210
+ maxEvidenceItems: limits.maxEvidenceItems,
211
+ maxResearchRequests: limits.maxResearchRequests,
212
+ maxStructuralItems: limits.maxStructuralItems,
213
+ };
214
+
215
+ const effectiveTaskClass = (!taskClass || typeof taskClass !== 'string' || taskClass.trim() === '') ? 'feature' : taskClass.trim();
216
+ const resolvedBudget = paramBudget || resolveBudget({ taskClass: effectiveTaskClass, maxBytes, harnessFlag: harness });
217
+
218
+ // Determine mandatory fields and provenance
219
+ const mandatoryFields = paramMandatory?.fields || {};
220
+ const mandatoryProv = paramMandatory?.provenance || {};
221
+
222
+ const hasParamObjective = paramObjective !== undefined && paramObjective !== null;
223
+ const objective = hasParamObjective
224
+ ? paramObjective
225
+ : (mandatoryFields.objective || '');
226
+
227
+ const acSource = (paramAc.length > 0 ? paramAc : (mandatoryFields.acceptanceCriteria || []));
228
+ const constraintsSource = (paramConstraints.length > 0 ? paramConstraints : (mandatoryFields.constraints || []));
229
+ const vrSource = (paramVr.length > 0 ? paramVr : (mandatoryFields.verificationRequirements || []));
230
+ const scopeSource = (paramScope.length > 0 ? paramScope : (mandatoryFields.scopeExcluded || []));
231
+ const gapsSource = (paramGaps.length > 0 ? paramGaps : (mandatoryFields.blockingGaps || []));
232
+
233
+ const mandatoryProvenance = {
234
+ objective: hasParamObjective
235
+ ? { supplied: Boolean(objective), source: 'caller', locator: null, digest: null, maturity: null, reason: objective ? null : 'caller-supplied-empty', gaps: [] }
236
+ : (mandatoryProv.objective || { supplied: Boolean(objective), source: objective ? 'caller' : 'none', locator: null, digest: null, maturity: null, reason: objective ? null : 'none-in-artifact', gaps: [] }),
237
+ 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: [] },
238
+ 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: [] },
239
+ 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: [] },
240
+ 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: [] },
241
+ 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: [] },
242
+ };
243
+
244
+ // Claims
245
+ let rawClaims = [];
246
+ if (claimsManager) {
247
+ rawClaims = claimsManager.getClaims(taskId);
248
+ } else if (params.claims) {
249
+ if (Array.isArray(params.claims)) {
250
+ rawClaims = params.claims;
251
+ } else {
252
+ const cList = (params.claims.conflicts || []).map((c ) => ({ ...c, status: 'conflicted' }));
253
+ const sList = (params.claims.supported || []).map((s ) => ({ ...s, status: s.status || 'supported' }));
254
+ const hList = (params.claims.hypotheses || []).map((h ) => ({ ...h, status: h.status || 'hypothesis' }));
255
+ rawClaims = [...cList, ...sList, ...hList];
256
+ }
257
+ }
258
+ const conflictedClaims = [];
259
+ const supportedClaims = [];
260
+ const activeHypotheses = [];
261
+
262
+ for (const claim of rawClaims) {
263
+ if (claim.status === 'conflicted' || (Array.isArray(claim.contradictingEvidence) && claim.contradictingEvidence.length > 0)) {
264
+ conflictedClaims.push(claim);
265
+ } else if (claim.status === 'supported') {
266
+ supportedClaims.push({
267
+ ...claim,
268
+ evidenceIds: Array.isArray(claim.evidenceIds)
269
+ ? claim.evidenceIds
270
+ : (Array.isArray(claim.supportingEvidence) ? claim.supportingEvidence : []),
271
+ });
272
+ } else if (claim.status === 'hypothesis') {
273
+ activeHypotheses.push(claim);
274
+ }
275
+ }
276
+
277
+ // Evidence
278
+ const rawEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId }) : (params.evidence || []);
279
+ const relevantFileSet = new Set ();
280
+ const structuralNodes = [];
281
+ const freshEvidenceItems = [];
282
+ const nonFreshOmissions = [];
283
+
284
+ for (const ev of rawEvidence) {
285
+ if (ev.status === 'superseded') {
286
+ nonFreshOmissions.push({
287
+ id: ev.id,
288
+ field: 'evidenceSummary',
289
+ reason: 'superseded',
290
+ });
291
+ } else if (ev.freshness?.status === 'FRESH') {
292
+ if (ev.locator?.file) relevantFileSet.add(ev.locator.file);
293
+ if (ev.kind === 'structural' && ev.content) structuralNodes.push(ev.content);
294
+ freshEvidenceItems.push({
295
+ id: ev.id,
296
+ kind: ev.kind,
297
+ locator: ev.locator,
298
+ summary: typeof ev.content === 'string' ? ev.content.slice(0, 120) : null,
299
+ freshness: 'FRESH',
300
+ });
301
+ } else {
302
+ const status = ev.freshness?.status === 'STALE' ? 'STALE' : 'UNKNOWN';
303
+ nonFreshOmissions.push({
304
+ id: ev.id,
305
+ field: 'evidenceSummary',
306
+ reason: 'not-fresh',
307
+ freshness: status,
308
+ });
309
+ }
310
+ }
311
+ const relevantFiles = params.relevantFiles
312
+ ? Array.from(new Set([...params.relevantFiles, ...relevantFileSet]))
313
+ : Array.from(relevantFileSet);
314
+
315
+ // Build items
316
+ const items = [];
317
+ if (objective) items.push(buildObjectiveItem(objective));
318
+
319
+ const blockingUnanswered = researchRequests.filter((r ) => r.blocking && r.status !== 'ANSWERED');
320
+ const otherRequests = researchRequests.filter((r ) => !(r.blocking && r.status !== 'ANSWERED'));
321
+
322
+ if (!bare) {
323
+ acSource.forEach((ac , i ) => items.push(buildAcceptanceCriteriaItem(ac, i)));
324
+ constraintsSource.forEach((c , i ) => items.push(buildConstraintItem(c, i)));
325
+ vrSource.forEach((vr , i ) => items.push(buildVerificationRequirementItem(vr, i)));
326
+ scopeSource.forEach((se , i ) => items.push(buildScopeExcludedItem(se, i)));
327
+ gapsSource.forEach((bg , i ) => items.push(buildBlockingGapItem(bg, i)));
328
+ conflictedClaims.forEach((c ) => items.push(buildClaimConflictItem(c)));
329
+
330
+ blockingUnanswered.forEach((r ) => items.push(buildResearchRequestItem(r)));
331
+ if (decisions.reason) items.push(buildDecisionReasonItem(decisions.reason));
332
+
333
+ if (decisions.available && Array.isArray(decisions.live)) {
334
+ decisions.live.forEach((d ) => items.push(buildDecisionLiveItem(d)));
335
+ }
336
+ supportedClaims.forEach((c ) => items.push(buildClaimSupportedItem(c)));
337
+ otherRequests.forEach((r ) => items.push(buildResearchRequestItem(r)));
338
+ activeHypotheses.forEach((h ) => items.push(buildClaimHypothesisItem(h)));
339
+ suppliedPointers.forEach((p ) => items.push(buildPointerItem(p)));
340
+ relevantFiles.forEach((f ) => items.push(buildRelevantFileItem(f)));
341
+ freshEvidenceItems.forEach((ev ) => items.push(buildEvidenceSummaryItem(ev)));
342
+ structuralNodes.forEach((sn , i ) => items.push(buildStructuralContextItem(sn, i)));
343
+ }
344
+
345
+ const hasMandatoryItems = {
346
+ objective: Boolean(objective),
347
+ acceptanceCriteria: !bare && acSource.length > 0,
348
+ constraints: !bare && constraintsSource.length > 0,
349
+ verificationRequirements: !bare && vrSource.length > 0,
350
+ scopeExcluded: !bare && scopeSource.length > 0,
351
+ blockingGaps: !bare && (gapsSource.length > 0 || blockingUnanswered.length > 0),
352
+ 'claims.conflicts': !bare && conflictedClaims.length > 0,
353
+ researchRequests: !bare && blockingUnanswered.length > 0,
354
+ };
355
+
356
+ const empty = rawEvidence.length === 0
357
+ && rawClaims.length === 0
358
+ && researchRequests.length === 0
359
+ && (!decisions || decisions.liveCount === 0);
360
+
361
+ const enableCorrective = budgetOverrides.enableCorrective !== undefined
362
+ ? budgetOverrides.enableCorrective
363
+ : (this.options.enableCorrective !== false);
364
+ const registeredHarnessIds = getRegisteredHarnessIds();
365
+ const reservedHarnessWidth = Math.max(...registeredHarnessIds.map((id ) => id.length), 0);
366
+ const selfRefWidth = Math.max(budgetOverrides.selfRefWidth ?? this.options.selfRefWidth ?? 0, reservedHarnessWidth);
367
+ const longestHarnessId = registeredHarnessIds.reduce((longest , id ) => id.length > longest.length ? id : longest, 'unstated');
368
+ const envelopeBudget = {
369
+ ...resolvedBudget,
370
+ harness: { ...resolvedBudget.harness, id: longestHarnessId },
371
+ };
372
+
373
+ const { envelopeMd, envelopeJson } = computeEnvelopes({
374
+ taskId,
375
+ taskClass: effectiveTaskClass,
376
+ budget: envelopeBudget,
377
+ mandatory: paramMandatory,
378
+ empty,
379
+ mode: bare ? 'bare' : (paramMode || 'full'),
380
+ hasMandatoryItems,
381
+ limits,
382
+ selfRefWidth,
383
+ recordingExpected: params.recordingExpected === true,
384
+ // Reserve the longest registered identity and delivery form for parity.
385
+ form: 'markdown',
386
+ harnessId: longestHarnessId,
387
+ });
388
+
389
+ const hasMandatoryRequests = !bare && blockingUnanswered.length > 0;
390
+ let deltaInfo = null;
391
+ let unchangedOmissions = [];
392
+ let removedIds = [];
393
+ let fitItems = items;
394
+ if (params.deltaSinceEntry && paramMode === 'delta') {
395
+ const oldItems = params.deltaSinceEntry.items || {};
396
+ const currentIds = new Set(items.map((item ) => item.id));
397
+ const added = [];
398
+ const changed = [];
399
+ const unchanged = [];
400
+ fitItems = [];
401
+ for (const item of items) {
402
+ const oldDigest = oldItems[item.id];
403
+ if (item.cls === 'mandatory') {
404
+ if (!oldDigest) added.push(item.id);
405
+ else if (oldDigest !== item.digest) changed.push(item.id);
406
+ else unchanged.push(item.id);
407
+ fitItems.push(item);
408
+ } else if (!oldDigest) {
409
+ added.push(item.id); fitItems.push({ ...item, deltaForced: true });
410
+ } else if (oldDigest === item.digest) {
411
+ unchanged.push(item.id);
412
+ unchangedOmissions.push({ id: item.id, field: item.field, reason: 'unchanged' });
413
+ } else {
414
+ changed.push(item.id); fitItems.push({ ...item, deltaForced: true });
415
+ }
416
+ }
417
+ removedIds = Object.keys(oldItems).filter((id ) => !currentIds.has(id));
418
+ deltaInfo = { since, added, changed, removed: removedIds, unchanged };
419
+ }
420
+
421
+ const fit = linearFit({
422
+ items: fitItems,
423
+ budget: resolvedBudget.maxBytes,
424
+ taskId,
425
+ taskClass: effectiveTaskClass,
426
+ envelopeMd,
427
+ envelopeJson,
428
+ initialOmissions: bare ? [] : [...nonFreshOmissions, ...unchangedOmissions],
429
+ sectionHeadingsJson: {
430
+ 'decisions.live': 5,
431
+ 'claims.supported': 5,
432
+ 'researchRequests (others)': hasMandatoryRequests ? 0 : 3,
433
+ 'claims.hypotheses': 4,
434
+ pointers: 3,
435
+ relevantFiles: 3,
436
+ evidenceSummary: 3,
437
+ structuralContext: 3,
438
+ },
439
+ itemCapLimits: {
440
+ relevantFiles: limits.maxFiles ?? 15,
441
+ 'claims.supported': limits.maxClaims ?? 20,
442
+ 'claims.hypotheses': limits.maxClaims ?? 20,
443
+ evidenceSummary: limits.maxEvidenceItems ?? 25,
444
+ 'researchRequests (others)': Math.max(0, (limits.maxResearchRequests ?? 20) - blockingUnanswered.length),
445
+ structuralContext: limits.maxStructuralItems ?? 5,
446
+ },
447
+ });
448
+
449
+ const assemblePack = (keptItems , omissionsList , isOverflow = false, overflowCause = null) => {
450
+ const keptAc = keptItems.filter((i ) => i.field === 'acceptanceCriteria').map((i ) => i.value);
451
+ const keptConstraints = keptItems.filter((i ) => i.field === 'constraints').map((i ) => i.value);
452
+ const keptVr = keptItems.filter((i ) => i.field === 'verificationRequirements').map((i ) => i.value);
453
+ const keptScope = keptItems.filter((i ) => i.field === 'scopeExcluded').map((i ) => i.value);
454
+ const keptGaps = keptItems.filter((i ) => i.field === 'blockingGaps').map((i ) => i.value);
455
+ const keptConflicts = keptItems.filter((i ) => i.field === 'claims.conflicts').map((i ) => i.value);
456
+ const keptLiveDecisions = keptItems.filter((i ) => i.field === 'decisions.live').map((i ) => i.value);
457
+ const keptSupported = keptItems.filter((i ) => i.field === 'claims.supported').map((i ) => i.value);
458
+ const keptRequests = keptItems.filter((i ) => i.field.startsWith('researchRequests')).map((i ) => i.value);
459
+ const keptHypotheses = keptItems.filter((i ) => i.field === 'claims.hypotheses').map((i ) => i.value);
460
+ const keptPointers = keptItems.filter((i ) => i.field === 'pointers').map((i ) => i.value);
461
+ const keptFiles = keptItems.filter((i ) => i.field === 'relevantFiles').map((i ) => i.value);
462
+ const keptEvidence = keptItems.filter((i ) => i.field === 'evidenceSummary').map((i ) => i.value);
463
+ const keptStructural = keptItems.filter((i ) => i.field === 'structuralContext').map((i ) => i.value);
464
+
465
+ const omissions = groupOmissions(omissionsList);
466
+ const packDigest = computePackDigest(keptItems);
467
+
468
+ const cause = overflowCause || (isOverflow ? (fit.cause || 'mandatory') : null);
469
+ const packTotalBytes = fit.totalBytes;
470
+ const packMandatoryBytes = fit.mandatoryBytesByForm?.[params.form === 'markdown' ? 'markdown' : 'json'] ?? fit.mandatoryBytes;
471
+ const packOptionalBytes = packTotalBytes - packMandatoryBytes;
472
+
473
+ const packed = {
474
+ version: 1,
475
+ status: isOverflow ? 'OVERFLOW' : (empty ? 'EMPTY' : 'OK'),
476
+ exitCode: isOverflow ? EXIT.OVERFLOW : (empty ? EXIT.EMPTY : EXIT.OK),
477
+ taskId,
478
+ taskClass: effectiveTaskClass,
479
+ compiledAt: new Date().toISOString(),
480
+ mode: bare ? 'bare' : (paramMode || 'full'),
481
+ budget: resolvedBudget,
482
+ overflow: isOverflow ? {
483
+ cause,
484
+ mandatoryBytes: packMandatoryBytes,
485
+ budgetBytes: resolvedBudget.maxBytes,
486
+ excessBytes: Math.max(0, packTotalBytes - resolvedBudget.maxBytes),
487
+ action: 'split-or-new-session',
488
+ } : null,
489
+ overflowAction: isOverflow ? {
490
+ message: formatOverflowMessage({
491
+ cause,
492
+ mandatoryBytes: packMandatoryBytes,
493
+ totalBytes: packTotalBytes,
494
+ budgetBytes: resolvedBudget.maxBytes,
495
+ source: resolvedBudget.source,
496
+ }),
497
+ } : null,
498
+ toolLimitNotice: null,
499
+ objective,
500
+ acceptanceCriteria: keptAc,
501
+ constraints: keptConstraints,
502
+ verificationRequirements: keptVr,
503
+ scopeExcluded: keptScope,
504
+ blockingGaps: keptGaps,
505
+ claims: {
506
+ conflicts: keptConflicts,
507
+ supported: keptSupported,
508
+ hypotheses: keptHypotheses,
509
+ },
510
+ researchRequests: keptRequests,
511
+ mandatory: mandatoryProvenance,
512
+ decisions: {
513
+ available: Boolean(decisions && decisions.available),
514
+ live: keptLiveDecisions,
515
+ liveCount: keptLiveDecisions.length,
516
+ ...(decisions?.reason ? { reason: decisions.reason } : {}),
517
+ },
518
+ pointers: keptPointers,
519
+ ...(expansion ? { expansion } : {}),
520
+ relevantFiles: keptFiles,
521
+ evidenceCount: keptEvidence.length,
522
+ evidenceSummary: keptEvidence,
523
+ structuralContext: keptStructural,
524
+ budgetEnforcement: {
525
+ totalFiles: relevantFiles.length,
526
+ totalSupportedClaims: supportedClaims.length,
527
+ totalActiveHypotheses: activeHypotheses.length,
528
+ limits: contentLimits,
529
+ },
530
+ empty,
531
+ omissions: bare ? { total: 0, groups: [] } : omissions,
532
+ size: {
533
+ totalBytes: packTotalBytes,
534
+ mandatoryBytes: packMandatoryBytes,
535
+ optionalBytes: packOptionalBytes,
536
+ fittedOn: fit.fittedOn,
537
+ },
538
+ accounting: {
539
+ tokens: makeTokenRecord(packTotalBytes),
540
+ },
541
+ delivery: {
542
+ key: params.recordingExpected ? `${packDigest}.${params.form || 'json'}.${params.harness?.id || 'unstated'}` : null,
543
+ packDigest,
544
+ recorded: params.recordingExpected === true,
545
+ reason: null,
546
+ since: since || null,
547
+ deltaRefused: params.deltaRefused || null,
548
+ },
549
+ delta: deltaInfo,
550
+ packEnd: {
551
+ mode: bare ? 'bare' : (paramMode || 'full'),
552
+ objective: objective ? 1 : 0,
553
+ acceptanceCriteria: keptAc.length,
554
+ constraints: keptConstraints.length,
555
+ verificationRequirements: keptVr.length,
556
+ scopeExcluded: keptScope.length,
557
+ conflicts: keptConflicts.length,
558
+ blockingGaps: keptGaps.length,
559
+ omitted: bare ? 0 : omissions.total,
560
+ bytes: packTotalBytes,
561
+ digest: packDigest.slice(0, 19),
562
+ },
563
+ };
564
+ Object.defineProperties(packed, {
565
+ deliveryItems: { value: keptItems.map((item ) => ({ ...item, cls: item.cls || item.class })) },
566
+ deliveryOmissions: { value: bare ? [] : omissionsList.map((item ) => ({ ...item })) },
567
+ deliveryAllItems: { value: items.map((item ) => ({ id: item.id, digest: item.digest, cls: item.cls || item.class })) },
568
+ trimOptional: { value: () => {
569
+ const currentItems = (packed ).deliveryItems ;
570
+ let trimIndex = -1;
571
+ for (let i = currentItems.length - 1; i >= 0; i -= 1) {
572
+ if (currentItems[i].cls === 'optional' && !currentItems[i].deltaForced) { trimIndex = i; break; }
573
+ }
574
+ if (trimIndex < 0) return null;
575
+ const dropped = currentItems[trimIndex];
576
+ const nextItems = currentItems.filter((_ , i ) => i !== trimIndex);
577
+ const nextOmissions = [...(packed ).deliveryOmissions, {
578
+ id: dropped.id, field: dropped.field, reason: 'budget', ...(dropped.freshness ? { freshness: dropped.freshness } : {}),
579
+ }];
580
+ return assemblePack(nextItems, nextOmissions, false, null);
581
+ } },
582
+ });
583
+ return packed;
584
+ };
585
+
586
+ let pack = assemblePack(fit.kept, fit.omissions, fit.overflow, fit.cause);
587
+
588
+ if (enableCorrective && pack.status === 'OK') {
589
+ let currentKept = [...fit.kept];
590
+ let currentOmissions = [...fit.omissions];
591
+ let fallbackCounted = false;
592
+
593
+ while (pack.status === 'OK') {
594
+ const mdOut = formatMarkdown(pack, { throwOnOverBudget: false });
595
+ const jsonOut = formatJson(pack, { throwOnOverBudget: false });
596
+ const mdLen = Buffer.byteLength(mdOut, 'utf8');
597
+ const jsonLen = Buffer.byteLength(jsonOut, 'utf8');
598
+
599
+ if (mdLen <= resolvedBudget.maxBytes && jsonLen <= resolvedBudget.maxBytes) {
600
+ pack.size.fittedOn = jsonLen >= mdLen ? 'json' : 'markdown';
601
+ break;
602
+ }
603
+
604
+ let lastOptIdx = -1;
605
+ for (let idx = currentKept.length - 1; idx >= 0; idx--) {
606
+ const it = currentKept[idx];
607
+ if ((it.cls === 'optional' || it.class === 'optional') && !it.deltaForced) {
608
+ lastOptIdx = idx;
609
+ break;
610
+ }
611
+ }
612
+
613
+ if (lastOptIdx === -1) {
614
+ const mandatoryOnlyKept = currentKept.filter((i ) => i.cls === 'mandatory' || i.class === 'mandatory' || i.deltaForced);
615
+ const allOptOmitted = [
616
+ ...(bare ? [] : nonFreshOmissions),
617
+ ...items.filter((i ) => (i.cls === 'optional' || i.class === 'optional') && !i.deltaForced).map((i ) => ({
618
+ id: i.id,
619
+ field: i.field,
620
+ reason: 'mandatory-overflow',
621
+ ...(i.freshness ? { freshness: i.freshness } : {}),
622
+ })),
623
+ ];
624
+ const cause = Math.max(fit.mandatoryBytes, mdLen, jsonLen) > resolvedBudget.maxBytes ? 'mandatory' : 'accounting';
625
+ pack = assemblePack(mandatoryOnlyKept, allOptOmitted, true, cause);
626
+ break;
627
+ }
628
+
629
+ if (!fallbackCounted) {
630
+ correctiveTrimFallbackCount += 1;
631
+ fallbackCounted = true;
632
+ }
633
+ const [dropped] = currentKept.splice(lastOptIdx, 1);
634
+ currentOmissions.push({
635
+ id: dropped.id,
636
+ field: dropped.field,
637
+ reason: 'budget',
638
+ ...(dropped.freshness ? { freshness: dropped.freshness } : {}),
639
+ });
640
+ pack = assemblePack(currentKept, currentOmissions, false, null);
641
+ }
642
+ }
643
+
644
+ const fittedOutput = params.form === 'markdown'
645
+ ? formatMarkdown(pack, { throwOnOverBudget: false })
646
+ : formatJson(pack, { throwOnOverBudget: false });
647
+ pack.size.totalBytes = Buffer.byteLength(fittedOutput, 'utf8');
648
+ pack.size.optionalBytes = pack.size.totalBytes - pack.size.mandatoryBytes;
649
+ pack.packEnd.bytes = pack.size.totalBytes;
650
+ pack.accounting.tokens = makeTokenRecord(pack.size.totalBytes);
651
+
652
+ return pack;
653
+ }
654
+
655
+ /**
656
+ * Formats a ContextPack into markdown per IC-005.
657
+ * @param {Object} pack
658
+ * @param {Object} [options]
659
+ * @returns {string}
660
+ */
661
+ formatMarkdown(pack , options = {}) {
662
+ return formatMarkdown(pack, options);
663
+ }
664
+
665
+ /**
666
+ * Formats a ContextPack into JSON per IC-004.
667
+ * @param {Object} pack
668
+ * @param {Object} [options]
669
+ * @returns {string}
670
+ */
671
+ formatJson(pack , options = {}) {
672
+ return formatJson(pack, options);
673
+ }
674
+
675
+ /**
676
+ * Compiles bounded context pack payload with mandatory/optional partitioning (IC-001).
677
+ * @param {Object} options
678
+ * @param {string} options.taskId
679
+ * @param {string} [options.taskClass='feature']
680
+ * @param {number} [options.maxBytes]
681
+ * @param {string} [options.objective='']
682
+ * @param {Object} [options.mandatory]
683
+ * @param {Array<string>} [options.mandatory.acceptanceCriteria]
684
+ * @param {Array<string>} [options.mandatory.constraints]
685
+ * @param {string} [options.mandatory.verificationPlan]
686
+ * @param {Object} [options.optional]
687
+ * @param {Array<string>} [options.optional.hypotheses]
688
+ * @param {Array<string>} [options.optional.narrative]
689
+ * @param {Array<string>} [options.optional.unverifiedClaims]
690
+ * @returns {Object} ContextPackResult
691
+ */
692
+ compileBoundedContextPack(options = {}) {
693
+ const {
694
+ taskId = 'default',
695
+ taskClass = 'feature',
696
+ maxBytes,
697
+ objective = '',
698
+ mandatory = {},
699
+ optional = {},
700
+ } = options;
701
+
702
+ const resolvedBudget = resolveBudget({ taskClass, maxBytes });
703
+ const budgetBytes = resolvedBudget.maxBytes;
704
+
705
+ const acceptanceCriteria = Array.isArray(mandatory.acceptanceCriteria) ? [...mandatory.acceptanceCriteria] : [];
706
+ const constraints = Array.isArray(mandatory.constraints) ? [...mandatory.constraints] : [];
707
+ const verificationPlan = typeof mandatory.verificationPlan === 'string' ? mandatory.verificationPlan : '';
708
+
709
+ // Build mandatory section payload
710
+ let mandatoryMd = `## ContextPack: [${taskClass.toUpperCase()}] ${taskId}\n`;
711
+ if (objective) {
712
+ mandatoryMd += `**Objective:** ${objective}\n\n`;
713
+ }
714
+ if (acceptanceCriteria.length > 0) {
715
+ mandatoryMd += `### Acceptance Criteria\n`;
716
+ for (const ac of acceptanceCriteria) {
717
+ mandatoryMd += `- [ ] ${ac}\n`;
718
+ }
719
+ mandatoryMd += '\n';
720
+ }
721
+ if (constraints.length > 0) {
722
+ mandatoryMd += `### Constraints\n`;
723
+ for (const c of constraints) {
724
+ mandatoryMd += `- ! ${c}\n`;
725
+ }
726
+ mandatoryMd += '\n';
727
+ }
728
+ if (verificationPlan) {
729
+ mandatoryMd += `### Verification Plan\n${verificationPlan}\n\n`;
730
+ }
731
+
732
+ const mandatoryBytes = Buffer.byteLength(mandatoryMd, 'utf8');
733
+
734
+ const hypotheses = Array.isArray(optional.hypotheses) ? [...optional.hypotheses] : [];
735
+ const unverifiedClaims = Array.isArray(optional.unverifiedClaims) ? [...optional.unverifiedClaims] : [];
736
+ const narrative = Array.isArray(optional.narrative) ? [...optional.narrative] : [];
737
+
738
+ const items = [];
739
+ hypotheses.forEach((h , idx ) => {
740
+ const line = `- ? ${h}\n`;
741
+ items.push({
742
+ id: `hypothesis-${idx + 1}`,
743
+ field: 'hypotheses',
744
+ value: h,
745
+ class: 'optional',
746
+ cls: 'optional',
747
+ priority: 1,
748
+ md: line,
749
+ mdBytes: Buffer.byteLength(line, 'utf8'),
750
+ jsonBytes: 0,
751
+ });
752
+ });
753
+
754
+ unverifiedClaims.forEach((c , idx ) => {
755
+ const line = `- ? ${c}\n`;
756
+ items.push({
757
+ id: `claim-${idx + 1}`,
758
+ field: 'unverifiedClaims',
759
+ value: c,
760
+ class: 'optional',
761
+ cls: 'optional',
762
+ priority: 2,
763
+ md: line,
764
+ mdBytes: Buffer.byteLength(line, 'utf8'),
765
+ jsonBytes: 0,
766
+ });
767
+ });
768
+
769
+ narrative.forEach((n , idx ) => {
770
+ const line = `- ${n}\n`;
771
+ items.push({
772
+ id: `narrative-${idx + 1}`,
773
+ field: 'narrative',
774
+ value: n,
775
+ class: 'optional',
776
+ cls: 'optional',
777
+ priority: 3,
778
+ md: line,
779
+ mdBytes: Buffer.byteLength(line, 'utf8'),
780
+ jsonBytes: 0,
781
+ });
782
+ });
783
+
784
+ const fit = compose.linearFit({
785
+ items,
786
+ budget: budgetBytes,
787
+ taskId,
788
+ taskClass,
789
+ envelopeMd: mandatoryBytes,
790
+ envelopeJson: mandatoryBytes,
791
+ includeOmissionCosts: false,
792
+ sectionHeadingsMd: {
793
+ hypotheses: Buffer.byteLength('### Active Hypotheses (Unverified)\n\n', 'utf8'),
794
+ unverifiedClaims: Buffer.byteLength('### Unverified Claims\n\n', 'utf8'),
795
+ narrative: Buffer.byteLength('### Background Narrative\n\n', 'utf8'),
796
+ },
797
+ sectionHeadingsJson: {},
798
+ itemCapLimits: {},
799
+ });
800
+
801
+ if (fit.overflow) {
802
+ const omissions = items.map((i ) => ({ id: i.id, reason: 'budget-cap-exceeded' }));
803
+ return {
804
+ taskId,
805
+ taskClass,
806
+ totalBytes: mandatoryBytes,
807
+ budgetBytes,
808
+ overflow: true,
809
+ mandatoryBytes,
810
+ optionalBytes: 0,
811
+ omissions,
812
+ payload: mandatoryMd,
813
+ error: `Mandatory context (${mandatoryBytes} B) exceeds budget (${budgetBytes} B)`,
814
+ };
815
+ }
816
+
817
+ const keptHypotheses = fit.kept.filter((i ) => i.field === 'hypotheses').map((i ) => i.value);
818
+ const keptClaims = fit.kept.filter((i ) => i.field === 'unverifiedClaims').map((i ) => i.value);
819
+ const keptNarrative = fit.kept.filter((i ) => i.field === 'narrative').map((i ) => i.value);
820
+
821
+ let payload = mandatoryMd;
822
+ if (keptHypotheses.length > 0) {
823
+ payload += `### Active Hypotheses (Unverified)\n`;
824
+ for (const h of keptHypotheses) payload += `- ? ${h}\n`;
825
+ payload += '\n';
826
+ }
827
+ if (keptClaims.length > 0) {
828
+ payload += `### Unverified Claims\n`;
829
+ for (const c of keptClaims) payload += `- ? ${c}\n`;
830
+ payload += '\n';
831
+ }
832
+ if (keptNarrative.length > 0) {
833
+ payload += `### Background Narrative\n`;
834
+ for (const n of keptNarrative) payload += `- ${n}\n`;
835
+ payload += '\n';
836
+ }
837
+
838
+ const totalBytes = Buffer.byteLength(payload, 'utf8');
839
+ const optionalBytes = totalBytes - mandatoryBytes;
840
+ const omissions = fit.omissions.map((o ) => ({ id: o.id, reason: 'budget-cap-exceeded' }));
841
+
842
+ return {
843
+ taskId,
844
+ taskClass,
845
+ totalBytes,
846
+ budgetBytes,
847
+ overflow: false,
848
+ mandatoryBytes,
849
+ optionalBytes,
850
+ omissions,
851
+ payload,
852
+ };
853
+ }
854
+
855
+ /**
856
+ * Alias for compileBoundedContextPack.
857
+ */
858
+ compileBoundedPayload(options = {}) {
859
+ return this.compileBoundedContextPack(options);
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Handles `doflow context-pack` — compile the evidence and claims recorded for a task into the
865
+ * context block a stage is handed.
866
+ *
867
+ * Exits 1 on a pack with nothing in it. An empty pack is not evidence that a task needs no
868
+ * context; it is evidence that nothing was recorded, and reporting it as success is the
869
+ * empty-contract defect in another costume.
870
+ *
871
+ * @param {Object} options
872
+ * @param {string} options.taskId
873
+ * @param {string} [options.taskClass]
874
+ * @param {string} [options.objective]
875
+ * @param {boolean} [options.json=false]
876
+ * @param {string} [options.stateRoot]
877
+ * @param {string} [options.slug] feature slug, when the task id is not the feature's own
878
+ * @returns {number} exit code
879
+ */
880
+ function handleContextPackCommand({
881
+ taskId,
882
+ taskClass = 'feature',
883
+ objective,
884
+ json = false,
885
+ stateRoot,
886
+ slug,
887
+ mandatoryFile,
888
+ harness = null,
889
+ maxBytes,
890
+ bare = false,
891
+ noRecord = false,
892
+ reason = null,
893
+ since = null,
894
+ expand = null,
895
+ profiles = null,
896
+ } = {}) {
897
+ const root = stateRoot || process.cwd();
898
+ const effectiveTaskClass = (!taskClass || typeof taskClass !== 'string' || taskClass.trim() === '') ? 'feature' : taskClass.trim();
899
+
900
+ // IC-001 Usage errors
901
+ if (!taskId || typeof taskId !== 'string' || taskId.trim() === '') {
902
+ return usageError('context-pack', '--task-id is required', json);
903
+ }
904
+ if (bare && expand) {
905
+ return usageError('context-pack', '--bare cannot be combined with --expand', json);
906
+ }
907
+ if (since && !/^sha256:[0-9a-f]{64}$/.test(since)) {
908
+ return usageError('context-pack', "--since must be 'sha256:' followed by 64 lowercase hex", json);
909
+ }
910
+ if (expand && (!since || !reason)) {
911
+ return usageError('context-pack', '--expand requires both --since and --reason', json);
912
+ }
913
+ if (harness) {
914
+ const validIds = getRegisteredHarnessIds();
915
+ if (!validIds.includes(harness)) {
916
+ return usageError('context-pack', `Unknown harness '${harness}'. Valid harnesses: ${validIds.join(', ')}`, json);
917
+ }
918
+ }
919
+
920
+ let parsedMaxBytes ;
921
+ let baseBudget = null;
922
+ if (maxBytes !== undefined && maxBytes !== null) {
923
+ const parsed = typeof maxBytes === 'number' ? maxBytes : parseInt(String(maxBytes).trim(), 10);
924
+ if (!Number.isInteger(parsed) || parsed <= 0 || (typeof maxBytes === 'string' && !/^[1-9]\d*$/.test(maxBytes.trim()))) {
925
+ return usageError('context-pack', '--max-bytes must be a positive integer', json);
926
+ }
927
+ parsedMaxBytes = parsed;
928
+
929
+ baseBudget = resolveBudget({ taskClass: effectiveTaskClass, harnessFlag: harness, profiles });
930
+ if (parsedMaxBytes > baseBudget.maxBytes && !reason) {
931
+ return usageError('context-pack', `--max-bytes (${parsedMaxBytes}) exceeds resolved budget (${baseBudget.maxBytes}) and requires --reason`, json);
932
+ }
933
+ }
934
+
935
+ const budget = resolveBudget({ taskClass: effectiveTaskClass, maxBytes: parsedMaxBytes, harnessFlag: harness, profiles });
936
+
937
+ const feature = resolveFeature({ projectRoot: root, slug: slug || null });
938
+
939
+ let sinceEntry = null;
940
+ let deltaRefused = null;
941
+ let deltaExpansions = [];
942
+ if (parsedMaxBytes !== undefined) {
943
+ if (!baseBudget) {
944
+ baseBudget = resolveBudget({ taskClass: effectiveTaskClass, harnessFlag: harness, profiles });
945
+ }
946
+ if (parsedMaxBytes > baseBudget.maxBytes) {
947
+ deltaExpansions.push({ at: new Date().toISOString(), kind: 'cap-raised', target: null, reason, bytesAdded: null });
948
+ }
949
+ }
950
+ let retention = profileRetention(budget.harness?.id || null, profiles);
951
+ if (since && !expand) {
952
+ sinceEntry = readDeliveryEntry({ projectRoot: root, taskId, slug, packDigest: since });
953
+ if (!sinceEntry) deltaRefused = 'since-unknown';
954
+ else if (sourcesChanged(root, sinceEntry.sources || {})) deltaRefused = 'sources-changed';
955
+ else if (!budget.harness?.id) deltaRefused = 'harness-unstated';
956
+ else if (retention?.value !== 'supported') deltaRefused = 'retention-unproven';
957
+ if (deltaRefused) {
958
+ deltaExpansions.push({ at: new Date().toISOString(), kind: 'delta-widened', target: null, reason: deltaRefused, bytesAdded: null });
959
+ }
960
+ }
961
+
962
+ if (expand) {
963
+ const prior = readDeliveryEntry({ projectRoot: root, taskId, slug, packDigest: since });
964
+ if (!prior) return usageError('context-pack', `Unknown digest '${since}'`, json);
965
+ const pointer = (prior.pointers || []).find((candidate ) => candidate.id === expand);
966
+ if (!pointer) return usageError('context-pack', `Unknown pointer '${expand}' in digest '${since}'`, json);
967
+ const current = pointerTools.readPointerSlice({ projectRoot: root, pointer });
968
+ if (!current) return usageError('context-pack', `Unknown pointer '${expand}' in digest '${since}'`, json);
969
+ const compiler = new ContextPackCompiler();
970
+ const pack = compiler.compileContextPack({ taskId, taskClass: effectiveTaskClass, objective: '', mandatory: { fields: {}, provenance: {} }, budget,
971
+ mode: 'expand', expansion: { id: expand, path: pointer.path, content: current.slice, targetChanged: current.digest !== pointer.digest } });
972
+ pack.status = 'OK';
973
+ pack.exitCode = EXIT.OK;
974
+ pack.mode = 'expand';
975
+ pack.delivery = { key: prior.key, packDigest: since, recorded: false, reason: noRecord ? 'no-record' : null,
976
+ since, deltaRefused: null, expansionRecorded: false };
977
+ pack.packEnd.mode = 'expand';
978
+ pack.packEnd.digest = since.slice(7, 19);
979
+ const sliceBytes = Buffer.byteLength(current.slice, 'utf8');
980
+ let output = '';
981
+ let emitted = 0;
982
+ for (let render = 0; render < 3; render += 1) {
983
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
984
+ emitted = Buffer.byteLength(output, 'utf8');
985
+ const tokenValue = Math.ceil(emitted / 4);
986
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
987
+ pack.accounting.tokens = makeTokenRecord(emitted);
988
+ }
989
+ if (emitted > budget.maxBytes) {
990
+ 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);
991
+ }
992
+ let expansionRecorded = false;
993
+ let recordReason = noRecord ? 'no-record' : null;
994
+ if (!noRecord) {
995
+ const expansionRecord = { at: new Date().toISOString(), kind: 'pointer-followed', target: expand, reason,
996
+ bytesAdded: sliceBytes, targetChanged: current.digest !== pointer.digest };
997
+ try {
998
+ appendPointerExpansion({ projectRoot: root, taskId, slug, key: prior.key, expansion: expansionRecord });
999
+ expansionRecorded = true;
1000
+ recordReason = null;
1001
+ } catch (error ) {
1002
+ recordReason = error?.code === 'entry-evicted' ? 'entry-evicted' : classifyStoreFailure(error);
1003
+ }
1004
+ }
1005
+ pack.delivery.recorded = expansionRecorded;
1006
+ pack.delivery.reason = recordReason;
1007
+ pack.delivery.expansionRecorded = expansionRecorded;
1008
+ pack.expansionRecorded = expansionRecorded;
1009
+ for (let render = 0; render < 3; render += 1) {
1010
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1011
+ emitted = Buffer.byteLength(output, 'utf8');
1012
+ const tokenValue = Math.ceil(emitted / 4);
1013
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
1014
+ pack.accounting.tokens = makeTokenRecord(emitted);
1015
+ }
1016
+ if (console.log !== nativeConsoleLog) console.log(output);
1017
+ else process.stdout.write(output);
1018
+ return finishRuntime(EXIT.OK);
1019
+ }
1020
+
1021
+ let reqText = null;
1022
+ let reqPath = 'requirement.md';
1023
+ if (!feature.error && feature.featureDir) {
1024
+ reqPath = feature.paths?.requirement
1025
+ ? path.resolve(feature.repoRoot || root, feature.paths.requirement)
1026
+ : path.join(feature.featureDir, 'intention', 'requirement.md');
1027
+ try {
1028
+ if (fs.existsSync(reqPath)) {
1029
+ reqText = fs.readFileSync(reqPath, 'utf8');
1030
+ }
1031
+ } catch {
1032
+ reqText = null;
1033
+ }
1034
+ }
1035
+
1036
+ let taskStories = [];
1037
+ if (!feature.error && feature.featureDir && taskId) {
1038
+ let planPath = null;
1039
+ if (feature.paths?.plan) {
1040
+ planPath = path.resolve(feature.repoRoot || root, feature.paths.plan);
1041
+ } else {
1042
+ const nestedPlan = path.join(feature.featureDir, 'plan', 'plan.md');
1043
+ const flatPlan = path.join(feature.featureDir, 'plan.md');
1044
+ if (fs.existsSync(nestedPlan)) planPath = nestedPlan;
1045
+ else if (fs.existsSync(flatPlan)) planPath = flatPlan;
1046
+ }
1047
+ if (planPath && fs.existsSync(planPath)) {
1048
+ try {
1049
+ const planText = fs.readFileSync(planPath, 'utf8');
1050
+ const { parsePlan } = require('./scaffold/artifacts.js');
1051
+ const parsedPlan = parsePlan(planText);
1052
+ const matchedTask = (parsedPlan.tasks || []).find(
1053
+ (t ) => t.id === taskId || t.id.toLowerCase() === taskId.toLowerCase()
1054
+ );
1055
+ if (matchedTask && Array.isArray(matchedTask.stories)) {
1056
+ taskStories = matchedTask.stories;
1057
+ }
1058
+ } catch {
1059
+ taskStories = [];
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ let mandatory = extractMandatory({
1065
+ text: reqText,
1066
+ path: reqPath,
1067
+ featureError: feature.error ? (feature.message || feature.error) : null,
1068
+ taskStories,
1069
+ });
1070
+
1071
+ if (mandatoryFile) {
1072
+ try {
1073
+ let fileContent ;
1074
+ if (mandatoryFile === '-') {
1075
+ fileContent = fs.readFileSync(0, 'utf8');
1076
+ } else {
1077
+ if (!fs.existsSync(mandatoryFile)) {
1078
+ const err = new Error(`--mandatory-file '${mandatoryFile}' does not exist`);
1079
+ err.code = 'USAGE_ERROR';
1080
+ throw err;
1081
+ }
1082
+ fileContent = fs.readFileSync(mandatoryFile, 'utf8');
1083
+ }
1084
+ const override = parseMandatoryFile(fileContent, mandatoryFile);
1085
+ mandatory = applyMandatoryOverride(mandatory, override);
1086
+ } catch (err ) {
1087
+ return usageError('context-pack', err.message, json, err);
1088
+ }
1089
+ }
1090
+
1091
+ const ledger = new EvidenceLedger({ repoRoot: root, slug });
1092
+ ledger.load(taskId);
1093
+ const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: root, slug });
1094
+ claims.load(taskId);
1095
+ claims.evaluateAll();
1096
+
1097
+ const researchRequests = new ResearchRequestStore({ projectRoot: root, slug }).list(taskId);
1098
+
1099
+ const compiler = new ContextPackCompiler();
1100
+ let pack = compiler.compileContextPack({
1101
+ taskId,
1102
+ taskClass: effectiveTaskClass,
1103
+ objective: objective !== undefined && objective !== null ? objective : undefined,
1104
+ mandatory,
1105
+ budget,
1106
+ harness,
1107
+ maxBytes: parsedMaxBytes,
1108
+ bare,
1109
+ noRecord,
1110
+ recordingExpected: !noRecord,
1111
+ form: json ? 'json' : 'markdown',
1112
+ reason,
1113
+ since,
1114
+ mode: since && !deltaRefused && !expand ? 'delta' : undefined,
1115
+ deltaSinceEntry: since && !deltaRefused && !expand ? sinceEntry : null,
1116
+ deltaRefused,
1117
+ expand,
1118
+ pointers: pointerTools.collectPointers({ projectRoot: root, feature }),
1119
+ evidenceLedger: ledger,
1120
+ claimsManager: claims,
1121
+ researchRequests,
1122
+ decisions: loadLiveDecisions({ projectRoot: root, slug: slug || null }),
1123
+ });
1124
+
1125
+ let deliverySources = null;
1126
+ let deliveryRetention = null;
1127
+ const form = json ? 'json' : 'markdown';
1128
+ let deliveryRecorded = !noRecord;
1129
+ let deliveryReason = noRecord ? 'no-record' : null;
1130
+ pack.delivery.recorded = deliveryRecorded;
1131
+ pack.delivery.reason = deliveryReason;
1132
+ pack.delivery.key = noRecord ? null : `${pack.delivery.packDigest}.${form}.${budget.harness?.id || 'unstated'}`;
1133
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1134
+ if (pack.status === 'OVERFLOW') {
1135
+ pack = { ...pack, deliveryOmissions: pack.deliveryOmissions.map((item ) => item.reason === 'budget' ? { ...item, reason: 'mandatory-overflow' } : item) };
1136
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1137
+ }
1138
+ if (!noRecord) {
1139
+ deliverySources = hashSourceFiles(root, pack.relevantFiles || []);
1140
+ deliveryRetention = retention?.value === 'supported' || retention?.value === 'unsupported'
1141
+ ? { status: retention.value, basis: `declared:${retention.source || 'profile'}` }
1142
+ : { status: 'unknown', basis: retention?.unverified || 'contextProfile.retention is unverified' };
1143
+ }
1144
+
1145
+ const renderTrimmed = () => {
1146
+ while (true) {
1147
+ let mdOutput = '';
1148
+ let jsonOutput = '';
1149
+ let mdBytes = 0;
1150
+ let jsonBytes = 0;
1151
+ let stable = false;
1152
+ for (let render = 0; render < 3; render += 1) {
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
+ const emitted = json ? jsonBytes : mdBytes;
1158
+ const priorBytes = pack.size.totalBytes;
1159
+ const priorOptionalBytes = pack.size.optionalBytes;
1160
+ const tokens = makeTokenRecord(emitted);
1161
+ const priorValue = pack.accounting.tokens?.estimated?.value;
1162
+ pack.size.totalBytes = emitted;
1163
+ pack.size.optionalBytes = emitted - pack.size.mandatoryBytes;
1164
+ pack.accounting.tokens = tokens;
1165
+ if (priorValue === tokens.estimated.value && priorBytes === emitted && priorOptionalBytes === pack.size.optionalBytes) { stable = true; break; }
1166
+ }
1167
+ if (!stable) {
1168
+ mdOutput = compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1169
+ jsonOutput = compiler.formatJson(pack, { throwOnOverBudget: false });
1170
+ mdBytes = Buffer.byteLength(mdOutput, 'utf8');
1171
+ jsonBytes = Buffer.byteLength(jsonOutput, 'utf8');
1172
+ pack.size.totalBytes = json ? jsonBytes : mdBytes;
1173
+ pack.size.optionalBytes = pack.size.totalBytes - pack.size.mandatoryBytes;
1174
+ pack.accounting.tokens = makeTokenRecord(pack.size.totalBytes);
1175
+ }
1176
+ if (pack.status !== 'OK' || (mdBytes <= budget.maxBytes && jsonBytes <= budget.maxBytes)) {
1177
+ const output = json ? jsonOutput : mdOutput;
1178
+ const bytes = json ? jsonBytes : mdBytes;
1179
+ pack.packEnd.bytes = bytes;
1180
+ pack.size.totalBytes = bytes;
1181
+ pack.size.optionalBytes = bytes - pack.size.mandatoryBytes;
1182
+ pack.accounting.tokens = makeTokenRecord(bytes);
1183
+ return { output, bytes };
1184
+ }
1185
+ const trimmed = (pack ).trimOptional?.();
1186
+ if (trimmed) {
1187
+ pack = trimmed;
1188
+ pack.delivery.recorded = deliveryRecorded;
1189
+ pack.delivery.reason = deliveryReason;
1190
+ pack.delivery.key = deliveryRecorded ? `${pack.delivery.packDigest}.${form}.${budget.harness?.id || 'unstated'}` : null;
1191
+ pack.accounting.tokens = makeTokenRecord(json ? jsonBytes : mdBytes);
1192
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1193
+ continue;
1194
+ }
1195
+ const cause = pack.size.mandatoryBytes > budget.maxBytes ? 'mandatory' : 'accounting';
1196
+ pack.status = 'OVERFLOW';
1197
+ pack.exitCode = EXIT.OVERFLOW;
1198
+ if (cause === 'mandatory') {
1199
+ pack = { ...pack, deliveryOmissions: pack.deliveryOmissions.map((item ) => item.reason === 'budget' ? { ...item, reason: 'mandatory-overflow' } : item) };
1200
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, !noRecord);
1201
+ }
1202
+ pack.overflow = { cause, mandatoryBytes: pack.size.mandatoryBytes, budgetBytes: budget.maxBytes,
1203
+ excessBytes: Math.max(0, Math.max(mdBytes, jsonBytes) - budget.maxBytes), action: 'split-or-new-session' };
1204
+ pack.overflowAction = { message: cause === 'accounting'
1205
+ ? `The omission record brings the pack to ${Math.max(mdBytes, jsonBytes)} B against a ${budget.maxBytes} B budget.`
1206
+ : `It is ${pack.size.mandatoryBytes} B against a ${budget.maxBytes} B budget (${budget.source}).` };
1207
+ let output = '';
1208
+ let bytes = 0;
1209
+ for (let render = 0; render < 3; render += 1) {
1210
+ output = json ? compiler.formatJson(pack, { throwOnOverBudget: false }) : compiler.formatMarkdown(pack, { throwOnOverBudget: false });
1211
+ bytes = Buffer.byteLength(output, 'utf8');
1212
+ const tokenValue = Math.ceil(bytes / 4);
1213
+ if (pack.accounting.tokens?.estimated?.value === tokenValue) break;
1214
+ pack.accounting.tokens = makeTokenRecord(bytes);
1215
+ }
1216
+ pack.packEnd.bytes = bytes;
1217
+ pack.size.totalBytes = bytes;
1218
+ return { output, bytes };
1219
+ }
1220
+ };
1221
+ let rendered = renderTrimmed();
1222
+ let output = rendered.output;
1223
+ let emittedBytes = rendered.bytes;
1224
+ if (!noRecord) {
1225
+ const entry = createDeliveryEntry({ pack: { ...pack, deliveryOmissions: pack.deliveryOmissions }, form, harness: budget.harness?.id || null,
1226
+ sources: deliverySources, expansions: deltaExpansions, retention: deliveryRetention });
1227
+ entry.bytes = emittedBytes;
1228
+ entry.tokens = makeTokenRecord(emittedBytes);
1229
+ try {
1230
+ const result = recordDelivery({ projectRoot: root, taskId, slug, entry, acquire: boundedAcquire });
1231
+ pack.delivery.key = result?.key || entry.key;
1232
+ deliveryRecorded = true;
1233
+ deliveryReason = null;
1234
+ pack.delivery.recorded = deliveryRecorded;
1235
+ pack.delivery.reason = deliveryReason;
1236
+ } catch (error ) {
1237
+ deliveryRecorded = false;
1238
+ deliveryReason = classifyStoreFailure(error);
1239
+ pack.delivery.key = null;
1240
+ pack.delivery.recorded = deliveryRecorded;
1241
+ pack.delivery.reason = deliveryReason;
1242
+ }
1243
+ pack.omissions = pack.mode === 'bare' ? pack.omissions : compose.groupOmissions(pack.deliveryOmissions, pack.delivery.recorded);
1244
+ rendered = renderTrimmed();
1245
+ output = rendered.output;
1246
+ emittedBytes = rendered.bytes;
1247
+ }
1248
+ if (pack.status === 'OVERFLOW') {
1249
+ pack.overflow.excessBytes = Math.max(0, emittedBytes - budget.maxBytes);
1250
+ pack.overflowAction.message = formatOverflowMessage({
1251
+ cause: pack.overflow.cause,
1252
+ mandatoryBytes: pack.size.mandatoryBytes,
1253
+ totalBytes: emittedBytes,
1254
+ budgetBytes: budget.maxBytes,
1255
+ source: budget.source,
1256
+ });
1257
+ rendered = renderTrimmed();
1258
+ output = rendered.output;
1259
+ emittedBytes = rendered.bytes;
1260
+ pack.packEnd.bytes = emittedBytes;
1261
+ pack.size.totalBytes = emittedBytes;
1262
+ }
1263
+ if (console.log !== nativeConsoleLog) {
1264
+ console.log(output);
1265
+ } else {
1266
+ process.stdout.write(output);
1267
+ }
1268
+ return finishRuntime(pack.exitCode);
1269
+ }
1270
+
1271
+ module.exports = {
1272
+ ContextPackCompiler,
1273
+ TASK_CLASS_BUDGETS,
1274
+ handleContextPackCommand,
1275
+ loadLiveDecisions,
1276
+ getCorrectiveTrimFallbackCount,
1277
+ };