@khoavu882/doflow 2.0.0-beta.4 → 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.
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +24 -1
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +83 -47
- package/src/adapters/claude/index.js +15 -1
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +52 -9
- package/src/adapters/copy-tree.js +4 -4
- package/src/adapters/gemini/hooks.js +57 -16
- package/src/adapters/gemini/index.js +57 -18
- package/src/adapters/kiro/index.js +15 -1
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/index.js +34 -9
- package/src/cli/commands/install.js +1 -1
- package/src/cli/commands/update.js +1 -1
- package/src/cli/index.js +17 -5
- package/src/cli/runtime-commands.js +51 -12
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/index.js +28 -14
- package/src/registry/index.js +136 -7
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/claims.js +14 -5
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +35 -268
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1029 -244
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +7 -0
- package/src/runtime/freshness.js +44 -7
- package/src/runtime/outcome.js +1 -1
- package/src/runtime/readiness.js +83 -13
- package/src/runtime/retrieval-plan.js +422 -73
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/artifacts.js +3 -0
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/task-attempts.js +1 -1
- package/src/runtime/task-scope.js +28 -3
- package/src/runtime/task-state.js +72 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
|
@@ -2,26 +2,61 @@
|
|
|
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
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 { updateTaskState } = require('./task-state.js');
|
|
56
|
+
const { taskStoreDir } = require('./task-scope.js');
|
|
57
|
+
|
|
58
|
+
let correctiveTrimFallbackCount = 0;
|
|
59
|
+
function getCorrectiveTrimFallbackCount() { return correctiveTrimFallbackCount; }
|
|
25
60
|
|
|
26
61
|
/**
|
|
27
62
|
* The live decisions of the feature the caller is working on (IC-012). The feature is resolved
|
|
@@ -34,7 +69,7 @@ const TASK_CLASS_BUDGETS = Object.freeze({
|
|
|
34
69
|
*/
|
|
35
70
|
function loadLiveDecisions({ projectRoot, slug = null } ) {
|
|
36
71
|
try {
|
|
37
|
-
const feature =
|
|
72
|
+
const feature = resolveFeature({ projectRoot, slug });
|
|
38
73
|
if (feature.error) return { ...NO_DECISIONS };
|
|
39
74
|
let register;
|
|
40
75
|
try {
|
|
@@ -55,6 +90,65 @@ function loadLiveDecisions({ projectRoot, slug = null } ) {
|
|
|
55
90
|
}
|
|
56
91
|
}
|
|
57
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
|
+
|
|
58
152
|
class ContextPackCompiler {
|
|
59
153
|
|
|
60
154
|
/**
|
|
@@ -69,189 +163,513 @@ class ContextPackCompiler {
|
|
|
69
163
|
maxClaims: defaultOptions.maxClaims || 20,
|
|
70
164
|
maxEvidenceItems: defaultOptions.maxEvidenceItems || 25,
|
|
71
165
|
maxResearchRequests: defaultOptions.maxResearchRequests || 20,
|
|
166
|
+
maxStructuralItems: defaultOptions.maxStructuralItems || 5,
|
|
167
|
+
enableCorrective: defaultOptions.enableCorrective !== false,
|
|
168
|
+
selfRefWidth: defaultOptions.selfRefWidth ?? null,
|
|
72
169
|
};
|
|
73
170
|
}
|
|
74
171
|
|
|
75
172
|
/**
|
|
76
|
-
* Compiles a ContextPack from task evidence and claims.
|
|
173
|
+
* Compiles a ContextPack from task evidence and claims through compose.ts.
|
|
77
174
|
* @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
175
|
* @param {Object} [budgetOverrides={}]
|
|
86
176
|
* @returns {Object} ContextPack
|
|
87
177
|
*/
|
|
88
178
|
compileContextPack(params , budgetOverrides = {}) {
|
|
89
179
|
const {
|
|
90
|
-
taskId,
|
|
180
|
+
taskId = 'default',
|
|
91
181
|
taskClass = 'feature',
|
|
92
|
-
objective
|
|
93
|
-
constraints = [],
|
|
94
|
-
acceptanceCriteria = [],
|
|
182
|
+
objective: paramObjective,
|
|
183
|
+
constraints: paramConstraints = [],
|
|
184
|
+
acceptanceCriteria: paramAc = [],
|
|
185
|
+
verificationRequirements: paramVr = [],
|
|
186
|
+
scopeExcluded: paramScope = [],
|
|
187
|
+
blockingGaps: paramGaps = [],
|
|
95
188
|
evidenceLedger,
|
|
96
189
|
claimsManager,
|
|
97
190
|
researchRequests = [],
|
|
98
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,
|
|
99
204
|
} = params;
|
|
100
205
|
|
|
101
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
|
+
};
|
|
102
214
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
215
|
+
const effectiveTaskClass = (!taskClass || typeof taskClass !== 'string' || taskClass.trim() === '') ? 'feature' : taskClass.trim();
|
|
216
|
+
const resolvedBudget = paramBudget || resolveBudget({ taskClass: effectiveTaskClass, maxBytes, harnessFlag: harness });
|
|
105
217
|
|
|
106
|
-
//
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
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 = [];
|
|
110
261
|
|
|
111
262
|
for (const claim of rawClaims) {
|
|
112
|
-
if (claim.status === '
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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,
|
|
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 : []),
|
|
131
271
|
});
|
|
272
|
+
} else if (claim.status === 'hypothesis') {
|
|
273
|
+
activeHypotheses.push(claim);
|
|
132
274
|
}
|
|
133
275
|
}
|
|
134
276
|
|
|
135
|
-
//
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
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 = [];
|
|
139
283
|
|
|
140
284
|
for (const ev of rawEvidence) {
|
|
141
|
-
if (ev.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
});
|
|
156
309
|
}
|
|
157
310
|
}
|
|
311
|
+
const relevantFiles = params.relevantFiles
|
|
312
|
+
? Array.from(new Set([...params.relevantFiles, ...relevantFileSet]))
|
|
313
|
+
: Array.from(relevantFileSet);
|
|
158
314
|
|
|
159
|
-
|
|
315
|
+
// Build items
|
|
316
|
+
const items = [];
|
|
317
|
+
if (objective) items.push(buildObjectiveItem(objective));
|
|
160
318
|
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
}
|
|
319
|
+
const blockingUnanswered = researchRequests.filter((r ) => r.blocking && r.status !== 'ANSWERED');
|
|
320
|
+
const otherRequests = researchRequests.filter((r ) => !(r.blocking && r.status !== 'ANSWERED'));
|
|
201
321
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (pack.objective) {
|
|
210
|
-
md += `**Objective:** ${pack.objective}\n\n`;
|
|
211
|
-
}
|
|
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)));
|
|
212
329
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
for (const c of pack.claims.supported) {
|
|
216
|
-
md += `- ✓ ${c.statement} *(ev: ${c.evidenceIds.join(', ')})*\n`;
|
|
217
|
-
}
|
|
218
|
-
md += '\n';
|
|
219
|
-
}
|
|
330
|
+
blockingUnanswered.forEach((r ) => items.push(buildResearchRequestItem(r)));
|
|
331
|
+
if (decisions.reason) items.push(buildDecisionReasonItem(decisions.reason));
|
|
220
332
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
for (const h of pack.claims.hypotheses) {
|
|
224
|
-
md += `- ? ${h.statement}\n`;
|
|
333
|
+
if (decisions.available && Array.isArray(decisions.live)) {
|
|
334
|
+
decisions.live.forEach((d ) => items.push(buildDecisionLiveItem(d)));
|
|
225
335
|
}
|
|
226
|
-
|
|
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)));
|
|
227
343
|
}
|
|
228
344
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
}
|
|
236
416
|
}
|
|
237
|
-
|
|
417
|
+
removedIds = Object.keys(oldItems).filter((id ) => !currentIds.has(id));
|
|
418
|
+
deltaInfo = { since, added, changed, removed: removedIds, unchanged };
|
|
238
419
|
}
|
|
239
420
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
}
|
|
245
628
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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);
|
|
250
641
|
}
|
|
251
|
-
md += '\n';
|
|
252
642
|
}
|
|
253
643
|
|
|
254
|
-
|
|
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);
|
|
255
673
|
}
|
|
256
674
|
|
|
257
675
|
/**
|
|
@@ -281,9 +699,8 @@ class ContextPackCompiler {
|
|
|
281
699
|
optional = {},
|
|
282
700
|
} = options;
|
|
283
701
|
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
: (TASK_CLASS_BUDGETS[taskClass] || 225000);
|
|
702
|
+
const resolvedBudget = resolveBudget({ taskClass, maxBytes });
|
|
703
|
+
const budgetBytes = resolvedBudget.maxBytes;
|
|
287
704
|
|
|
288
705
|
const acceptanceCriteria = Array.isArray(mandatory.acceptanceCriteria) ? [...mandatory.acceptanceCriteria] : [];
|
|
289
706
|
const constraints = Array.isArray(mandatory.constraints) ? [...mandatory.constraints] : [];
|
|
@@ -314,17 +731,75 @@ class ContextPackCompiler {
|
|
|
314
731
|
|
|
315
732
|
const mandatoryBytes = Buffer.byteLength(mandatoryMd, 'utf8');
|
|
316
733
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
+
});
|
|
323
768
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
+
});
|
|
327
800
|
|
|
801
|
+
if (fit.overflow) {
|
|
802
|
+
const omissions = items.map((i ) => ({ id: i.id, reason: 'budget-cap-exceeded' }));
|
|
328
803
|
return {
|
|
329
804
|
taskId,
|
|
330
805
|
taskClass,
|
|
@@ -339,74 +814,30 @@ class ContextPackCompiler {
|
|
|
339
814
|
};
|
|
340
815
|
}
|
|
341
816
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
};
|
|
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);
|
|
373
820
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
keptHypotheses.push(hypotheses[i]);
|
|
380
|
-
} else {
|
|
381
|
-
omissions.push({ id: `hypothesis-${i + 1}`, reason: 'budget-cap-exceeded' });
|
|
382
|
-
}
|
|
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';
|
|
383
826
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
}
|
|
827
|
+
if (keptClaims.length > 0) {
|
|
828
|
+
payload += `### Unverified Claims\n`;
|
|
829
|
+
for (const c of keptClaims) payload += `- ? ${c}\n`;
|
|
830
|
+
payload += '\n';
|
|
394
831
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
}
|
|
832
|
+
if (keptNarrative.length > 0) {
|
|
833
|
+
payload += `### Background Narrative\n`;
|
|
834
|
+
for (const n of keptNarrative) payload += `- ${n}\n`;
|
|
835
|
+
payload += '\n';
|
|
405
836
|
}
|
|
406
837
|
|
|
407
|
-
const payload = buildPayload(keptHypotheses, keptClaims, keptNarrative);
|
|
408
838
|
const totalBytes = Buffer.byteLength(payload, 'utf8');
|
|
409
839
|
const optionalBytes = totalBytes - mandatoryBytes;
|
|
840
|
+
const omissions = fit.omissions.map((o ) => ({ id: o.id, reason: 'budget-cap-exceeded' }));
|
|
410
841
|
|
|
411
842
|
return {
|
|
412
843
|
taskId,
|
|
@@ -446,42 +877,395 @@ class ContextPackCompiler {
|
|
|
446
877
|
* @param {string} [options.slug] feature slug, when the task id is not the feature's own
|
|
447
878
|
* @returns {number} exit code
|
|
448
879
|
*/
|
|
449
|
-
function handleContextPackCommand({
|
|
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
|
+
} = {}) {
|
|
450
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
|
+
|
|
451
1091
|
const ledger = new EvidenceLedger({ repoRoot: root, slug });
|
|
452
1092
|
ledger.load(taskId);
|
|
453
1093
|
const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: root, slug });
|
|
454
1094
|
claims.load(taskId);
|
|
455
1095
|
claims.evaluateAll();
|
|
1096
|
+
|
|
456
1097
|
const researchRequests = new ResearchRequestStore({ projectRoot: root, slug }).list(taskId);
|
|
457
1098
|
|
|
458
1099
|
const compiler = new ContextPackCompiler();
|
|
459
|
-
|
|
1100
|
+
let pack = compiler.compileContextPack({
|
|
460
1101
|
taskId,
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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 }),
|
|
466
1119
|
evidenceLedger: ledger,
|
|
467
1120
|
claimsManager: claims,
|
|
468
1121
|
researchRequests,
|
|
469
1122
|
decisions: loadLiveDecisions({ projectRoot: root, slug: slug || null }),
|
|
470
1123
|
});
|
|
471
1124
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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);
|
|
483
1267
|
}
|
|
484
|
-
return finishRuntime(
|
|
1268
|
+
return finishRuntime(pack.exitCode);
|
|
485
1269
|
}
|
|
486
1270
|
|
|
487
1271
|
module.exports = {
|
|
@@ -489,4 +1273,5 @@ module.exports = {
|
|
|
489
1273
|
TASK_CLASS_BUDGETS,
|
|
490
1274
|
handleContextPackCommand,
|
|
491
1275
|
loadLiveDecisions,
|
|
1276
|
+
getCorrectiveTrimFallbackCount,
|
|
492
1277
|
};
|