@besales/ops-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +328 -0
- package/bin/build-check-context.mjs +67 -0
- package/bin/build-execution-ledger.mjs +54 -0
- package/bin/estimate-llm-input.mjs +160 -0
- package/bin/guard-task.mjs +384 -0
- package/bin/hash-task-artifacts.mjs +44 -0
- package/bin/init-project.mjs +49 -0
- package/bin/intake-execution-feedback.mjs +207 -0
- package/bin/intake-feedback.test.mjs +73 -0
- package/bin/learning-loop.mjs +658 -0
- package/bin/learning-loop.test.mjs +175 -0
- package/bin/lib/bootstrap-utils.mjs +542 -0
- package/bin/lib/bootstrap-utils.test.mjs +156 -0
- package/bin/lib/check-context-utils.mjs +1448 -0
- package/bin/lib/check-context-utils.test.mjs +497 -0
- package/bin/lib/execution-ledger-utils.mjs +162 -0
- package/bin/lib/execution-ledger-utils.test.mjs +74 -0
- package/bin/lib/llm-input-pack-utils.mjs +663 -0
- package/bin/lib/llm-input-pack-utils.test.mjs +262 -0
- package/bin/lib/project-config.mjs +229 -0
- package/bin/lib/project-config.test.mjs +102 -0
- package/bin/lib/task-manifest-utils.mjs +512 -0
- package/bin/lib/task-manifest-utils.test.mjs +218 -0
- package/bin/lib/task-metrics-utils.mjs +63 -0
- package/bin/lib/task-metrics-utils.test.mjs +40 -0
- package/bin/lib/test-setup.mjs +37 -0
- package/bin/new-task.mjs +42 -0
- package/bin/ops-agent.mjs +81 -0
- package/bin/preflight.mjs +56 -0
- package/bin/providers/external-cli-checker.mjs +190 -0
- package/bin/providers/openai-checker.mjs +62 -0
- package/bin/quality-gates.mjs +92 -0
- package/bin/run-check.mjs +559 -0
- package/bin/run-plan-check-loop.mjs +392 -0
- package/bin/run-verify.mjs +627 -0
- package/bin/self-lint.mjs +88 -0
- package/bin/supervisor-turn.mjs +146 -0
- package/bin/supervisor-turn.test.mjs +72 -0
- package/bin/task-manifest.mjs +57 -0
- package/bin/task-metrics.mjs +48 -0
- package/bin/transition.mjs +94 -0
- package/bin/validate-check-artifacts.mjs +418 -0
- package/config/default-agents.json +100 -0
- package/package.json +28 -0
- package/playbooks/checker-context.md +9 -0
- package/playbooks/complexity-performance.md +13 -0
- package/playbooks/production-rollout.md +9 -0
- package/playbooks/source-sync-provider.md +9 -0
- package/playbooks/ui-acceptance.md +9 -0
- package/prompts/checker.md +170 -0
- package/prompts/executor.md +54 -0
- package/prompts/planner.md +128 -0
- package/prompts/researcher.md +44 -0
- package/prompts/supervisor.md +337 -0
- package/prompts/verifier.md +128 -0
- package/templates/brief.md +15 -0
- package/templates/check-resolution.md +69 -0
- package/templates/check-result.json +32 -0
- package/templates/check.md +46 -0
- package/templates/execution-feedback.md +25 -0
- package/templates/execution.md +101 -0
- package/templates/human-gate-summary.md +49 -0
- package/templates/orchestration-log.md +8 -0
- package/templates/plan.md +86 -0
- package/templates/research.md +13 -0
- package/templates/retrospective.md +48 -0
- package/templates/status.md +53 -0
- package/templates/verify-result.json +19 -0
- package/templates/verify.md +41 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
normalizeHeading,
|
|
4
|
+
parseMarkdownSections,
|
|
5
|
+
readRelevantPlaybooks,
|
|
6
|
+
readJsonFile,
|
|
7
|
+
readTaskFile,
|
|
8
|
+
renderRelevantPlaybooks,
|
|
9
|
+
} from './check-context-utils.mjs';
|
|
10
|
+
|
|
11
|
+
export const LLM_CONTEXT_MODES = ['fast', 'standard', 'strict'];
|
|
12
|
+
export const LLM_CONTEXT_CAPS = {
|
|
13
|
+
fast: 8000,
|
|
14
|
+
standard: 20000,
|
|
15
|
+
strict: 45000,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const TOKEN_ESTIMATE_CHARS_PER_TOKEN = 1.8;
|
|
19
|
+
const PACK_CAP_SAFETY_MULTIPLIER = 1.15;
|
|
20
|
+
const MEMORY_MAX_CHARS = {
|
|
21
|
+
fast: 3000,
|
|
22
|
+
standard: 3500,
|
|
23
|
+
strict: Infinity,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const STRICT_TRIGGER_PATTERNS = [
|
|
27
|
+
'auth-security',
|
|
28
|
+
'prisma-schema',
|
|
29
|
+
'production-runtime',
|
|
30
|
+
'source-sync-provider',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const CHECK_RELEVANT_SECTIONS = [
|
|
34
|
+
'цель',
|
|
35
|
+
'goal',
|
|
36
|
+
'опора на research',
|
|
37
|
+
'допущения',
|
|
38
|
+
'assumptions',
|
|
39
|
+
'затронутые модули и файлы',
|
|
40
|
+
'risk tier and execution budget',
|
|
41
|
+
'ui acceptance scenarios',
|
|
42
|
+
'complexity / performance budget',
|
|
43
|
+
'production rollout gate',
|
|
44
|
+
'source sync / provider gate',
|
|
45
|
+
'checker context hints',
|
|
46
|
+
'шаги реализации',
|
|
47
|
+
'риски и открытые вопросы',
|
|
48
|
+
'verification plan',
|
|
49
|
+
'план проверки',
|
|
50
|
+
'что требует human approval',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const VERIFY_PLAN_SECTIONS = [
|
|
54
|
+
'цель',
|
|
55
|
+
'goal',
|
|
56
|
+
'допущения',
|
|
57
|
+
'затронутые модули и файлы',
|
|
58
|
+
'risk tier and execution budget',
|
|
59
|
+
'ui acceptance scenarios',
|
|
60
|
+
'complexity / performance budget',
|
|
61
|
+
'production rollout gate',
|
|
62
|
+
'source sync / provider gate',
|
|
63
|
+
'шаги реализации',
|
|
64
|
+
'риски и открытые вопросы',
|
|
65
|
+
'verification plan',
|
|
66
|
+
'план проверки',
|
|
67
|
+
'что требует human approval',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const VERIFY_EXECUTION_SECTIONS = [
|
|
71
|
+
'краткое summary',
|
|
72
|
+
'risk tier / execution budget used',
|
|
73
|
+
'planned item coverage',
|
|
74
|
+
'preflight evidence',
|
|
75
|
+
'execution ledger',
|
|
76
|
+
'измененные файлы',
|
|
77
|
+
'запущенные команды',
|
|
78
|
+
'micro-verify evidence',
|
|
79
|
+
'slice-verify evidence',
|
|
80
|
+
'ui acceptance evidence',
|
|
81
|
+
'complexity / performance evidence',
|
|
82
|
+
'production rollout evidence',
|
|
83
|
+
'source sync / provider evidence',
|
|
84
|
+
'отклонения от плана',
|
|
85
|
+
'explicit non-actions',
|
|
86
|
+
'блокеры',
|
|
87
|
+
'follow-up notes',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const SECTION_ALIASES = new Map([
|
|
91
|
+
['goal', ['goal', 'цель']],
|
|
92
|
+
['scope', ['scope', 'затронутые модули и файлы', 'affected modules and files']],
|
|
93
|
+
['success criteria', ['success criteria', 'acceptance criteria', 'критерии успеха']],
|
|
94
|
+
['findings', ['findings', 'finding', 'опора на research', 'опора на findings из research']],
|
|
95
|
+
['evidence', ['evidence', 'repo evidence', 'доказательства']],
|
|
96
|
+
['repo', ['repo', 'repository', 'repo context']],
|
|
97
|
+
['architecture', ['architecture', 'архитектура']],
|
|
98
|
+
['standards', ['standards', 'global standards alignment', 'standards alignment']],
|
|
99
|
+
['допущения', ['допущения', 'assumptions']],
|
|
100
|
+
['assumptions', ['assumptions', 'допущения']],
|
|
101
|
+
['risk tier and execution budget', ['risk tier and execution budget', 'execution budget']],
|
|
102
|
+
['ui acceptance scenarios', ['ui acceptance scenarios', 'ui acceptance', 'ui scenarios']],
|
|
103
|
+
['complexity / performance budget', ['complexity / performance budget', 'complexity performance budget', 'performance budget', 'complexity budget']],
|
|
104
|
+
['production rollout gate', ['production rollout gate', 'production rollout', 'rollout gate', 'deployment gate']],
|
|
105
|
+
['source sync / provider gate', ['source sync / provider gate', 'source sync provider gate', 'source sync gate', 'provider gate']],
|
|
106
|
+
['checker context hints', ['checker context hints', 'checker context']],
|
|
107
|
+
['шаги реализации', ['шаги реализации', 'implementation steps', 'implementation slices', 'реализация']],
|
|
108
|
+
['риски и открытые вопросы', ['риски и открытые вопросы', 'known risks', 'open questions', 'risks']],
|
|
109
|
+
['verification plan', ['verification plan', 'verification and replay plan', 'план проверки']],
|
|
110
|
+
['план проверки', ['план проверки', 'verification plan']],
|
|
111
|
+
['что требует human approval', ['что требует human approval', 'human approval']],
|
|
112
|
+
['краткое summary', ['краткое summary', 'summary']],
|
|
113
|
+
['risk tier / execution budget used', ['risk tier / execution budget used', 'execution budget used']],
|
|
114
|
+
['planned item coverage', ['planned item coverage']],
|
|
115
|
+
['preflight evidence', ['preflight evidence']],
|
|
116
|
+
['execution ledger', ['execution ledger']],
|
|
117
|
+
['измененные файлы', ['измененные файлы', 'changed files']],
|
|
118
|
+
['запущенные команды', ['запущенные команды', 'commands run']],
|
|
119
|
+
['micro-verify evidence', ['micro-verify evidence']],
|
|
120
|
+
['slice-verify evidence', ['slice-verify evidence']],
|
|
121
|
+
['ui acceptance evidence', ['ui acceptance evidence']],
|
|
122
|
+
['complexity / performance evidence', ['complexity / performance evidence', 'performance evidence']],
|
|
123
|
+
['production rollout evidence', ['production rollout evidence', 'rollout evidence']],
|
|
124
|
+
['source sync / provider evidence', ['source sync / provider evidence', 'source sync evidence', 'provider evidence']],
|
|
125
|
+
['отклонения от плана', ['отклонения от плана', 'deviations from plan']],
|
|
126
|
+
['explicit non-actions', ['explicit non-actions']],
|
|
127
|
+
['блокеры', ['блокеры', 'blockers']],
|
|
128
|
+
['follow-up notes', ['follow-up notes']],
|
|
129
|
+
['verdict', ['verdict']],
|
|
130
|
+
['residual risks', ['residual risks']],
|
|
131
|
+
['recommended next step', ['recommended next step']],
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const PROTECTED_SECTIONS = new Set([
|
|
135
|
+
'verification plan',
|
|
136
|
+
'план проверки',
|
|
137
|
+
'ui acceptance scenarios',
|
|
138
|
+
'complexity / performance budget',
|
|
139
|
+
'production rollout gate',
|
|
140
|
+
'source sync / provider gate',
|
|
141
|
+
'измененные файлы',
|
|
142
|
+
'changed files',
|
|
143
|
+
'запущенные команды',
|
|
144
|
+
'commands run',
|
|
145
|
+
'micro-verify evidence',
|
|
146
|
+
'slice-verify evidence',
|
|
147
|
+
'ui acceptance evidence',
|
|
148
|
+
'complexity / performance evidence',
|
|
149
|
+
'production rollout evidence',
|
|
150
|
+
'source sync / provider evidence',
|
|
151
|
+
'блокеры',
|
|
152
|
+
'blockers',
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
export function resolveLlmContextMode({ requestedMode, riskTriggers = [] } = {}) {
|
|
156
|
+
const requested = normalizeLlmContextMode(requestedMode);
|
|
157
|
+
if (requested) {
|
|
158
|
+
return requested;
|
|
159
|
+
}
|
|
160
|
+
return riskTriggers.some((trigger) => STRICT_TRIGGER_PATTERNS.includes(trigger))
|
|
161
|
+
? 'strict'
|
|
162
|
+
: 'standard';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function normalizeLlmContextMode(value) {
|
|
166
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
167
|
+
return LLM_CONTEXT_MODES.includes(normalized) ? normalized : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function nextLlmContextMode(mode) {
|
|
171
|
+
if (mode === 'fast') {
|
|
172
|
+
return 'standard';
|
|
173
|
+
}
|
|
174
|
+
if (mode === 'standard') {
|
|
175
|
+
return 'strict';
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function estimateTokens(value) {
|
|
181
|
+
return Math.ceil(String(value || '').length / TOKEN_ESTIMATE_CHARS_PER_TOKEN);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function estimatePayload(value) {
|
|
185
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
186
|
+
return {
|
|
187
|
+
bytes: Buffer.byteLength(serialized, 'utf8'),
|
|
188
|
+
estimatedTokens: estimateTokens(serialized),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function buildCheckerLlmInputPack({
|
|
193
|
+
taskDir,
|
|
194
|
+
taskId,
|
|
195
|
+
checkerPromptSha,
|
|
196
|
+
cacheKey,
|
|
197
|
+
checkContext,
|
|
198
|
+
checkEvidence,
|
|
199
|
+
checkerContextPack,
|
|
200
|
+
taskManifest,
|
|
201
|
+
projectMemory,
|
|
202
|
+
mode = 'standard',
|
|
203
|
+
}) {
|
|
204
|
+
const selectedMode = normalizeLlmContextMode(mode) || 'standard';
|
|
205
|
+
const artifacts = selectedMode === 'strict'
|
|
206
|
+
? readArtifacts(taskDir, ['brief.md', 'research.md', 'plan.md', 'status.md', 'feedback.md', 'execution-feedback.md'], 'full')
|
|
207
|
+
: {
|
|
208
|
+
'brief.md': compactArtifact(taskDir, 'brief.md', selectedMode, ['goal', 'scope', 'success criteria']),
|
|
209
|
+
'research.md': compactArtifact(taskDir, 'research.md', selectedMode, ['findings', 'evidence', 'repo', 'architecture', 'standards']),
|
|
210
|
+
'plan.md': compactArtifact(taskDir, 'plan.md', selectedMode, CHECK_RELEVANT_SECTIONS),
|
|
211
|
+
'status.md': compactStatus(readTaskFile(taskDir, 'status.md')),
|
|
212
|
+
'feedback.md': compactArtifact(taskDir, 'feedback.md', selectedMode, ['feedback event', 'classification', 'supervisor decision']),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const input = {
|
|
216
|
+
taskId,
|
|
217
|
+
checkerPromptSha,
|
|
218
|
+
cacheKey,
|
|
219
|
+
checkContext: selectedMode === 'strict' ? checkContext : compactCheckContext(checkContext, selectedMode),
|
|
220
|
+
llmInputPolicy: {
|
|
221
|
+
stage: 'check',
|
|
222
|
+
mode: selectedMode,
|
|
223
|
+
capTokens: LLM_CONTEXT_CAPS[selectedMode],
|
|
224
|
+
fullContextAvailableViaStrict: selectedMode !== 'strict',
|
|
225
|
+
contextInsufficientFallback: selectedMode === 'strict' ? 'stop_and_report' : `rerun_${nextLlmContextMode(selectedMode)}`,
|
|
226
|
+
},
|
|
227
|
+
checkEvidence: compactGeneratedMarkdown('check-evidence.md', checkEvidence, selectedMode, { fast: 2800, standard: 4000 }),
|
|
228
|
+
checkerContextPack: compactGeneratedMarkdown('checker-context-pack.md', checkerContextPack, selectedMode, { fast: 3300, standard: 4600 }),
|
|
229
|
+
relevantPlaybooks: selectedMode === 'strict'
|
|
230
|
+
? renderRelevantPlaybooks(readRelevantPlaybooks(checkContext.riskTriggers || []), { mode: 'strict' })
|
|
231
|
+
: renderRelevantPlaybooks(readRelevantPlaybooks(checkContext.riskTriggers || []), { mode: 'compact' }),
|
|
232
|
+
taskManifest,
|
|
233
|
+
projectMemory: compactProjectMemory(projectMemory, selectedMode),
|
|
234
|
+
taskArtifacts: artifacts,
|
|
235
|
+
outputContract: {
|
|
236
|
+
checkMarkdown: 'Markdown for check.md',
|
|
237
|
+
checkResultJson: {
|
|
238
|
+
taskId,
|
|
239
|
+
stage: 'Check',
|
|
240
|
+
checkerProvider: 'filled by runner',
|
|
241
|
+
checkerModel: 'filled by runner',
|
|
242
|
+
planSha: checkContext.planSha,
|
|
243
|
+
memorySha: checkContext.memorySha,
|
|
244
|
+
riskProfile: checkContext.riskProfile,
|
|
245
|
+
verdict: 'return_to_plan | ready_for_human_gate | human_arbitration_required | context_insufficient | checker_failed',
|
|
246
|
+
failureReason: null,
|
|
247
|
+
findings: [],
|
|
248
|
+
readyForHumanGate: false,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return withPackMetadata(input, selectedMode);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function buildVerifierLlmInputPack({
|
|
257
|
+
taskDir,
|
|
258
|
+
taskId,
|
|
259
|
+
planSha,
|
|
260
|
+
executionSha,
|
|
261
|
+
verifier,
|
|
262
|
+
mode = 'standard',
|
|
263
|
+
}) {
|
|
264
|
+
const selectedMode = normalizeLlmContextMode(mode) || 'standard';
|
|
265
|
+
const taskArtifacts = selectedMode === 'strict'
|
|
266
|
+
? readArtifacts(taskDir, [
|
|
267
|
+
'brief.md',
|
|
268
|
+
'research.md',
|
|
269
|
+
'plan.md',
|
|
270
|
+
'task-manifest.json',
|
|
271
|
+
'check.result.json',
|
|
272
|
+
'check.md',
|
|
273
|
+
'check-resolution.md',
|
|
274
|
+
'human-gate-summary.md',
|
|
275
|
+
'execution.md',
|
|
276
|
+
'execution-ledger.json',
|
|
277
|
+
'verify.md',
|
|
278
|
+
'status.md',
|
|
279
|
+
'feedback.md',
|
|
280
|
+
'execution-feedback.md',
|
|
281
|
+
'orchestration-log.md',
|
|
282
|
+
], 'full')
|
|
283
|
+
: {
|
|
284
|
+
'brief.md': compactArtifact(taskDir, 'brief.md', selectedMode, ['goal', 'scope', 'success criteria']),
|
|
285
|
+
'research.md': compactArtifact(taskDir, 'research.md', selectedMode, ['findings', 'evidence', 'repo']),
|
|
286
|
+
'plan.md': compactArtifact(taskDir, 'plan.md', selectedMode, VERIFY_PLAN_SECTIONS),
|
|
287
|
+
'task-manifest.json': readTaskFile(taskDir, 'task-manifest.json'),
|
|
288
|
+
'check.result.json': readTaskFile(taskDir, 'check.result.json'),
|
|
289
|
+
'check.md': compactCheckMarkdown({
|
|
290
|
+
checkMarkdown: readTaskFile(taskDir, 'check.md'),
|
|
291
|
+
checkResult: readOptionalJson(taskDir, 'check.result.json'),
|
|
292
|
+
mode: selectedMode,
|
|
293
|
+
}),
|
|
294
|
+
'check-resolution.md': truncateMiddle(readTaskFile(taskDir, 'check-resolution.md'), charLimitForMode(selectedMode, 1500, 3500)),
|
|
295
|
+
'human-gate-summary.md': truncateMiddle(readTaskFile(taskDir, 'human-gate-summary.md'), charLimitForMode(selectedMode, 1200, 2500)),
|
|
296
|
+
'execution.md': compactArtifact(taskDir, 'execution.md', selectedMode, VERIFY_EXECUTION_SECTIONS),
|
|
297
|
+
'execution-ledger.json': readTaskFile(taskDir, 'execution-ledger.json'),
|
|
298
|
+
'verify.md': compactArtifact(taskDir, 'verify.md', selectedMode, ['verdict', 'findings', 'residual risks', 'recommended next step']),
|
|
299
|
+
'status.md': compactStatus(readTaskFile(taskDir, 'status.md')),
|
|
300
|
+
'feedback.md': compactArtifact(taskDir, 'feedback.md', selectedMode, ['feedback event', 'classification', 'supervisor decision']),
|
|
301
|
+
'orchestration-log.md': compactOrchestrationLog(readTaskFile(taskDir, 'orchestration-log.md'), selectedMode),
|
|
302
|
+
};
|
|
303
|
+
const riskTriggers = readOptionalJson(taskDir, 'task-manifest.json')?.context?.riskTriggers || [];
|
|
304
|
+
|
|
305
|
+
const input = {
|
|
306
|
+
taskId,
|
|
307
|
+
planSha,
|
|
308
|
+
executionSha,
|
|
309
|
+
verifier,
|
|
310
|
+
llmInputPolicy: {
|
|
311
|
+
stage: 'verify',
|
|
312
|
+
mode: selectedMode,
|
|
313
|
+
capTokens: LLM_CONTEXT_CAPS[selectedMode],
|
|
314
|
+
fullContextAvailableViaStrict: selectedMode !== 'strict',
|
|
315
|
+
contextInsufficientFallback: selectedMode === 'strict' ? 'stop_and_report' : `rerun_${nextLlmContextMode(selectedMode)}`,
|
|
316
|
+
},
|
|
317
|
+
relevantPlaybooks: selectedMode === 'strict'
|
|
318
|
+
? renderRelevantPlaybooks(readRelevantPlaybooks(riskTriggers), { mode: 'strict' })
|
|
319
|
+
: renderRelevantPlaybooks(readRelevantPlaybooks(riskTriggers), { mode: 'compact' }),
|
|
320
|
+
taskArtifacts,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return withPackMetadata(input, selectedMode);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function isContextInsufficientResult(result) {
|
|
327
|
+
return result?.verdict === 'context_insufficient'
|
|
328
|
+
|| result?.failureReason === 'context_insufficient';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function buildContextModeSequence(initialMode) {
|
|
332
|
+
const modes = [normalizeLlmContextMode(initialMode) || 'standard'];
|
|
333
|
+
let current = modes[0];
|
|
334
|
+
while (current && current !== 'strict') {
|
|
335
|
+
current = nextLlmContextMode(current);
|
|
336
|
+
if (current) {
|
|
337
|
+
modes.push(current);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return modes;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function summarizePackForConsole(pack) {
|
|
344
|
+
return [
|
|
345
|
+
`- llmInputMode: ${pack.meta.mode}`,
|
|
346
|
+
`- estimatedInputTokens: ${pack.meta.estimatedTokens}`,
|
|
347
|
+
`- capTokens: ${pack.meta.capTokens}`,
|
|
348
|
+
`- compactedArtifacts: ${pack.meta.compactedArtifacts.join(', ') || 'none'}`,
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function withPackMetadata(input, mode) {
|
|
353
|
+
const serialized = JSON.stringify(input, null, 2);
|
|
354
|
+
const compactedArtifacts = Object.entries(input.taskArtifacts || {})
|
|
355
|
+
.filter(([, value]) => isCompactedArtifact(value))
|
|
356
|
+
.map(([fileName]) => fileName);
|
|
357
|
+
return {
|
|
358
|
+
input,
|
|
359
|
+
meta: {
|
|
360
|
+
mode,
|
|
361
|
+
capTokens: LLM_CONTEXT_CAPS[mode],
|
|
362
|
+
bytes: Buffer.byteLength(serialized, 'utf8'),
|
|
363
|
+
estimatedTokens: estimateTokens(serialized),
|
|
364
|
+
compactedArtifacts,
|
|
365
|
+
overCap: estimateTokens(serialized) * PACK_CAP_SAFETY_MULTIPLIER > LLM_CONTEXT_CAPS[mode],
|
|
366
|
+
safetyMultiplier: PACK_CAP_SAFETY_MULTIPLIER,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function readArtifacts(taskDir, fileNames, delivery) {
|
|
372
|
+
return Object.fromEntries(fileNames.map((fileName) => [
|
|
373
|
+
fileName,
|
|
374
|
+
delivery === 'full'
|
|
375
|
+
? readTaskFile(taskDir, fileName)
|
|
376
|
+
: compactArtifact(taskDir, fileName, 'standard', []),
|
|
377
|
+
]));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function compactArtifact(taskDir, fileName, mode, sectionNames) {
|
|
381
|
+
const content = readTaskFile(taskDir, fileName);
|
|
382
|
+
if (!content.trim()) {
|
|
383
|
+
return '';
|
|
384
|
+
}
|
|
385
|
+
const maxChars = charLimitForMode(mode, 2500, 7000);
|
|
386
|
+
const compacted = compactMarkdownSections(content, sectionNames, maxChars);
|
|
387
|
+
return markCompacted(fileName, content, compacted);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function compactMarkdownSections(content, sectionNames, maxChars) {
|
|
391
|
+
const sections = parseMarkdownSections(content);
|
|
392
|
+
if (sections.size === 0 || sectionNames.length === 0) {
|
|
393
|
+
return truncateMiddle(content, maxChars);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const wanted = expandSectionAliases(sectionNames);
|
|
397
|
+
const included = [];
|
|
398
|
+
const title = content.split('\n').find((line) => /^#\s+/.test(line)) || '';
|
|
399
|
+
if (title) {
|
|
400
|
+
included.push(title);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for (const [sectionTitle, body] of sections.entries()) {
|
|
404
|
+
const normalized = normalizeHeading(sectionTitle);
|
|
405
|
+
const shouldInclude = wanted.has(normalized);
|
|
406
|
+
if (shouldInclude) {
|
|
407
|
+
included.push(compactSection(sectionTitle, body, maxChars));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (included.length <= (title ? 1 : 0)) {
|
|
412
|
+
return truncateMiddle(content, maxChars);
|
|
413
|
+
}
|
|
414
|
+
return limitSectionsByPriority(included, maxChars);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function compactStatus(status) {
|
|
418
|
+
const sections = parseMarkdownSections(status || '');
|
|
419
|
+
const wanted = [
|
|
420
|
+
'текущий этап',
|
|
421
|
+
'current stage',
|
|
422
|
+
'текущий owner',
|
|
423
|
+
'latest routing decision',
|
|
424
|
+
'latest check verdict',
|
|
425
|
+
'latest check result',
|
|
426
|
+
'latest check resolution',
|
|
427
|
+
'human gate summary',
|
|
428
|
+
'последнее действие supervisor',
|
|
429
|
+
'следующий шаг',
|
|
430
|
+
'нужен ли сейчас human approval',
|
|
431
|
+
].map(normalizeHeading);
|
|
432
|
+
const result = [];
|
|
433
|
+
for (const [title, body] of sections.entries()) {
|
|
434
|
+
if (wanted.includes(normalizeHeading(title))) {
|
|
435
|
+
result.push(`## ${title}\n${body.trim()}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return markCompacted('status.md', status, result.join('\n\n') || truncateMiddle(status, 1500));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function compactCheckMarkdown({ checkMarkdown, checkResult, mode }) {
|
|
442
|
+
const findings = Array.isArray(checkResult?.findings) ? checkResult.findings : [];
|
|
443
|
+
const lines = [
|
|
444
|
+
'# Check compact excerpt',
|
|
445
|
+
'',
|
|
446
|
+
`Verdict: ${checkResult?.verdict || 'unknown'}`,
|
|
447
|
+
`Findings: ${findings.length}`,
|
|
448
|
+
'',
|
|
449
|
+
compactMarkdownSections(checkMarkdown || '', ['итоговая оценка', 'summary'], charLimitForMode(mode, 800, 1200)),
|
|
450
|
+
'',
|
|
451
|
+
...findings.map((finding) => [
|
|
452
|
+
`## ${finding.id || 'finding'}`,
|
|
453
|
+
`- Severity: ${finding.severity || 'unknown'}`,
|
|
454
|
+
`- Category: ${finding.claimCategory || 'unknown'}`,
|
|
455
|
+
`- Affected artifacts: ${truncateEnd(JSON.stringify(finding.affectedArtifacts || finding.affectedPlanSections || []), 250)}`,
|
|
456
|
+
'- Evidence refs:',
|
|
457
|
+
...formatRefs(finding.evidenceRefs || [], 220),
|
|
458
|
+
`- Claim: ${truncateEnd(finding.claim || '', 700)}`,
|
|
459
|
+
`- Expected correction: ${truncateEnd(finding.expectedCorrection || '', 700)}`,
|
|
460
|
+
].join('\n')),
|
|
461
|
+
];
|
|
462
|
+
const compacted = lines.join('\n').trim() || truncateMiddle(checkMarkdown, charLimitForMode(mode, 1200, 3000));
|
|
463
|
+
return markCompacted('check.md', checkMarkdown, compacted);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function compactOrchestrationLog(log, mode) {
|
|
467
|
+
if (!log.trim()) {
|
|
468
|
+
return '';
|
|
469
|
+
}
|
|
470
|
+
const lines = log.split('\n').filter(Boolean);
|
|
471
|
+
const eventLines = lines.filter((line) => /^-\s+`/.test(line));
|
|
472
|
+
const lastCount = mode === 'fast' ? 20 : 40;
|
|
473
|
+
const counters = {
|
|
474
|
+
returnToPlan: countMatches(log, /return_to_plan/g),
|
|
475
|
+
returnToExecute: countMatches(log, /return_to_execute/g),
|
|
476
|
+
contextInsufficient: countMatches(log, /context_insufficient/g),
|
|
477
|
+
deterministicPreverify: countMatches(log, /deterministic pre-verify/gi),
|
|
478
|
+
checkerFailed: countMatches(log, /checker_failed/g),
|
|
479
|
+
verifierFailed: countMatches(log, /verifier_failed/g),
|
|
480
|
+
};
|
|
481
|
+
const compacted = [
|
|
482
|
+
'# Orchestration Log Compact',
|
|
483
|
+
'',
|
|
484
|
+
'## Counters',
|
|
485
|
+
'',
|
|
486
|
+
...Object.entries(counters).map(([key, value]) => `- ${key}: ${value}`),
|
|
487
|
+
'',
|
|
488
|
+
`## Last ${lastCount} Events`,
|
|
489
|
+
'',
|
|
490
|
+
...eventLines.slice(-lastCount),
|
|
491
|
+
].join('\n');
|
|
492
|
+
return markCompacted('orchestration-log.md', log, compacted);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function compactProjectMemory(projectMemory, mode) {
|
|
496
|
+
if (mode === 'strict') {
|
|
497
|
+
return projectMemory;
|
|
498
|
+
}
|
|
499
|
+
const maxChars = MEMORY_MAX_CHARS[mode] ?? MEMORY_MAX_CHARS.standard;
|
|
500
|
+
let used = 0;
|
|
501
|
+
const compacted = [];
|
|
502
|
+
for (const item of projectMemory || []) {
|
|
503
|
+
const budgetLeft = maxChars - used;
|
|
504
|
+
if (budgetLeft <= 0) {
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
const content = truncateEnd(item.content || '', Math.min(budgetLeft, mode === 'fast' ? 800 : 1400));
|
|
508
|
+
used += content.length;
|
|
509
|
+
compacted.push({
|
|
510
|
+
...item,
|
|
511
|
+
content,
|
|
512
|
+
compacted: content !== item.content,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return compacted;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function compactGeneratedMarkdown(fileName, content, mode, limits) {
|
|
519
|
+
if (mode === 'strict' || !content) {
|
|
520
|
+
return content;
|
|
521
|
+
}
|
|
522
|
+
const limit = mode === 'fast' ? limits.fast : limits.standard;
|
|
523
|
+
return markCompacted(fileName, content, truncateMiddle(content, limit));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function compactCheckContext(checkContext, mode) {
|
|
527
|
+
const referencedLimit = mode === 'fast' ? 8 : 16;
|
|
528
|
+
return {
|
|
529
|
+
taskId: checkContext.taskId,
|
|
530
|
+
stage: checkContext.stage,
|
|
531
|
+
planSha: checkContext.planSha,
|
|
532
|
+
planFingerprintVersion: checkContext.planFingerprintVersion,
|
|
533
|
+
memorySha: checkContext.memorySha,
|
|
534
|
+
riskProfile: checkContext.riskProfile,
|
|
535
|
+
riskTriggers: checkContext.riskTriggers,
|
|
536
|
+
checkerContextPackFile: checkContext.checkerContextPackFile,
|
|
537
|
+
checkerContextPackSha: checkContext.checkerContextPackSha,
|
|
538
|
+
evidenceFile: checkContext.evidenceFile,
|
|
539
|
+
referencedFiles: (checkContext.referencedFiles || []).slice(0, referencedLimit),
|
|
540
|
+
contextBudgetTokens: checkContext.contextBudgetTokens,
|
|
541
|
+
maxRepoReads: checkContext.maxRepoReads,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function markCompacted(fileName, original, compacted) {
|
|
546
|
+
if (!original || original === compacted) {
|
|
547
|
+
return compacted;
|
|
548
|
+
}
|
|
549
|
+
return [
|
|
550
|
+
`<!-- compacted:${fileName} originalBytes=${Buffer.byteLength(original, 'utf8')} compactBytes=${Buffer.byteLength(compacted, 'utf8')} -->`,
|
|
551
|
+
compacted,
|
|
552
|
+
].join('\n');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function isCompactedArtifact(value) {
|
|
556
|
+
return typeof value === 'string' && value.startsWith('<!-- compacted:');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function truncateMiddle(value, maxChars) {
|
|
560
|
+
const text = String(value || '').trim();
|
|
561
|
+
if (text.length <= maxChars) {
|
|
562
|
+
return text;
|
|
563
|
+
}
|
|
564
|
+
const head = Math.floor(maxChars * 0.65);
|
|
565
|
+
const tail = Math.max(0, maxChars - head - 120);
|
|
566
|
+
return [
|
|
567
|
+
text.slice(0, head).trimEnd(),
|
|
568
|
+
'',
|
|
569
|
+
`<!-- truncated ${text.length - head - tail} chars from middle for token budget -->`,
|
|
570
|
+
'',
|
|
571
|
+
text.slice(text.length - tail).trimStart(),
|
|
572
|
+
].join('\n');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function truncateEnd(value, maxChars) {
|
|
576
|
+
const text = String(value || '').trim();
|
|
577
|
+
if (text.length <= maxChars) {
|
|
578
|
+
return text;
|
|
579
|
+
}
|
|
580
|
+
return `${text.slice(0, Math.max(0, maxChars - 40)).trimEnd()} ... [truncated]`;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function expandSectionAliases(sectionNames) {
|
|
584
|
+
const wanted = new Set();
|
|
585
|
+
for (const name of sectionNames) {
|
|
586
|
+
const normalized = normalizeHeading(name);
|
|
587
|
+
wanted.add(normalized);
|
|
588
|
+
for (const alias of SECTION_ALIASES.get(normalized) || []) {
|
|
589
|
+
wanted.add(normalizeHeading(alias));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return wanted;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function compactSection(sectionTitle, body, maxChars) {
|
|
596
|
+
const header = `## ${sectionTitle}`;
|
|
597
|
+
const trimmed = String(body || '').trim();
|
|
598
|
+
const sectionBudget = isProtectedSection(sectionTitle)
|
|
599
|
+
? Math.max(1400, Math.floor(maxChars * 0.65))
|
|
600
|
+
: Math.min(Math.max(1200, Math.floor(maxChars / 2)), maxChars);
|
|
601
|
+
return `${header}\n${truncateEnd(trimmed, sectionBudget)}`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function limitSectionsByPriority(sections, maxChars) {
|
|
605
|
+
const joined = sections.join('\n\n');
|
|
606
|
+
if (joined.length <= maxChars) {
|
|
607
|
+
return joined;
|
|
608
|
+
}
|
|
609
|
+
const protectedSections = sections.filter((section) => isProtectedSection(section));
|
|
610
|
+
const regularSections = sections.filter((section) => !isProtectedSection(section));
|
|
611
|
+
const kept = [];
|
|
612
|
+
const droppedProtected = [];
|
|
613
|
+
let used = 0;
|
|
614
|
+
for (const section of [...protectedSections, ...regularSections]) {
|
|
615
|
+
const nextSize = section.length + (kept.length ? 2 : 0);
|
|
616
|
+
if (used + nextSize <= maxChars) {
|
|
617
|
+
kept.push(section);
|
|
618
|
+
used += nextSize;
|
|
619
|
+
} else if (isProtectedSection(section)) {
|
|
620
|
+
droppedProtected.push(extractSectionTitle(section));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const droppedMarkers = droppedProtected.map((title) => `<!-- dropped-protected:${title} -->`);
|
|
624
|
+
const result = [...droppedMarkers, ...kept].join('\n\n');
|
|
625
|
+
if (result.length <= maxChars) {
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
return truncateEnd(result, maxChars);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function extractSectionTitle(section) {
|
|
632
|
+
const match = /^##\s+(.+?)\s*$/m.exec(String(section || ''));
|
|
633
|
+
return match ? normalizeHeading(match[1]) : 'unknown';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function formatRefs(refs, maxChars) {
|
|
637
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
638
|
+
return [' - none'];
|
|
639
|
+
}
|
|
640
|
+
return refs.map((ref) => ` - ${truncateEnd(JSON.stringify(ref), maxChars)}`);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function isProtectedSection(value) {
|
|
644
|
+
const normalized = extractSectionTitle(value);
|
|
645
|
+
return PROTECTED_SECTIONS.has(normalized);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function charLimitForMode(mode, fastChars, standardChars) {
|
|
649
|
+
return mode === 'fast' ? fastChars : standardChars;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function readOptionalJson(taskDir, fileName) {
|
|
653
|
+
const filePath = path.join(taskDir, fileName);
|
|
654
|
+
try {
|
|
655
|
+
return readJsonFile(filePath);
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function countMatches(value, pattern) {
|
|
662
|
+
return (String(value || '').match(pattern) || []).length;
|
|
663
|
+
}
|