@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,392 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import {
|
|
5
|
+
appendOrchestrationLog,
|
|
6
|
+
getFlag,
|
|
7
|
+
parseCliArgs,
|
|
8
|
+
pipelineRoot,
|
|
9
|
+
readAgentsConfig,
|
|
10
|
+
readJsonFile,
|
|
11
|
+
readTaskFile,
|
|
12
|
+
repoRoot,
|
|
13
|
+
resolveTaskDir,
|
|
14
|
+
parseMarkdownSections,
|
|
15
|
+
updateStatus,
|
|
16
|
+
writeTaskFile,
|
|
17
|
+
} from './lib/check-context-utils.mjs';
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const args = parseCliArgs(process.argv.slice(2));
|
|
21
|
+
const taskArg = args.positional[0];
|
|
22
|
+
if (!taskArg) {
|
|
23
|
+
fail('Usage: node ops/agent-pipeline/bin/run-plan-check-loop.mjs <TASK-id-or-task-path> [--checker-provider openai] [--checker-model model]');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const taskDir = resolveTaskDir(taskArg);
|
|
28
|
+
const taskId = path.basename(taskDir);
|
|
29
|
+
const config = readAgentsConfig();
|
|
30
|
+
let iteration = 0;
|
|
31
|
+
let lastResult = null;
|
|
32
|
+
const maxIterations = resolveMaxIterations(taskDir, config);
|
|
33
|
+
|
|
34
|
+
while (iteration < maxIterations) {
|
|
35
|
+
iteration += 1;
|
|
36
|
+
const startedAt = Date.now();
|
|
37
|
+
const runResult = runCheck(taskArg, args);
|
|
38
|
+
lastResult = readCheckResult(taskDir);
|
|
39
|
+
const elapsedMs = Date.now() - startedAt;
|
|
40
|
+
|
|
41
|
+
appendIterationLog({
|
|
42
|
+
taskDir,
|
|
43
|
+
taskId,
|
|
44
|
+
iteration,
|
|
45
|
+
result: lastResult,
|
|
46
|
+
elapsedMs,
|
|
47
|
+
maxIterations,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
routeAfterCheck({ taskDir, result: lastResult, iteration, maxIterations });
|
|
51
|
+
|
|
52
|
+
if (lastResult.verdict === 'ready_for_human_gate') {
|
|
53
|
+
console.log(`Plan/Check loop ready for Human Gate after ${iteration} iteration(s).`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (lastResult.verdict === 'human_arbitration_required') {
|
|
57
|
+
fail(`Plan/Check loop routed to Human Arbitration after ${iteration} iteration(s).`);
|
|
58
|
+
}
|
|
59
|
+
if (lastResult.verdict === 'checker_failed') {
|
|
60
|
+
fail(`Checker failed with ${lastResult.failureReason}. Stage routed according to failure reason.`);
|
|
61
|
+
}
|
|
62
|
+
if (lastResult.verdict === 'return_to_plan') {
|
|
63
|
+
if (!fs.existsSync(path.join(taskDir, 'check-resolution.md'))) {
|
|
64
|
+
fail('Checker returned return_to_plan. Planner must update plan.md and write check-resolution.md before the loop can continue.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (runResult.status !== 0 && lastResult.verdict !== 'return_to_plan') {
|
|
69
|
+
fail(`run-check exited with ${runResult.status}.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateStatus(taskDir, {
|
|
74
|
+
stage: 'Human Arbitration',
|
|
75
|
+
routingDecision: `max_iterations_reached:${maxIterations}`,
|
|
76
|
+
checkVerdict: lastResult?.verdict || 'unknown',
|
|
77
|
+
supervisorAction: 'Plan/Check loop reached max iterations.',
|
|
78
|
+
nextStep: 'Human Arbitration: resolve Planner/Checker non-convergence.',
|
|
79
|
+
humanApproval: 'yes',
|
|
80
|
+
});
|
|
81
|
+
appendOrchestrationLog(taskDir, {
|
|
82
|
+
event: 'plan_check_max_iterations',
|
|
83
|
+
taskId,
|
|
84
|
+
maxIterations,
|
|
85
|
+
verdict: lastResult?.verdict || null,
|
|
86
|
+
});
|
|
87
|
+
fail(`Plan/Check loop reached max iterations (${maxIterations}).`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
fail(error.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runCheck(taskArg, args) {
|
|
94
|
+
const runCheckPath = path.join(pipelineRoot, 'bin', 'run-check.mjs');
|
|
95
|
+
const childArgs = [runCheckPath, taskArg];
|
|
96
|
+
for (const flagName of ['checker-provider', 'checker-model']) {
|
|
97
|
+
const value = getFlag(args, flagName);
|
|
98
|
+
if (value) {
|
|
99
|
+
childArgs.push(`--${flagName}`, value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = spawnSync(process.execPath, childArgs, {
|
|
104
|
+
cwd: repoRoot,
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
});
|
|
107
|
+
if (result.stdout) {
|
|
108
|
+
process.stdout.write(result.stdout);
|
|
109
|
+
}
|
|
110
|
+
if (result.stderr) {
|
|
111
|
+
process.stderr.write(result.stderr);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readCheckResult(taskDir) {
|
|
117
|
+
const resultPath = path.join(taskDir, 'check.result.json');
|
|
118
|
+
if (!fs.existsSync(resultPath)) {
|
|
119
|
+
throw new Error('run-check did not produce check.result.json.');
|
|
120
|
+
}
|
|
121
|
+
return readJsonFile(resultPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveMaxIterations(taskDir, config) {
|
|
125
|
+
const contextPath = path.join(taskDir, 'check-context.json');
|
|
126
|
+
const riskProfile = fs.existsSync(contextPath)
|
|
127
|
+
? readJsonFile(contextPath).riskProfile
|
|
128
|
+
: config.planCheckLoop?.defaultRiskProfile || 'medium';
|
|
129
|
+
return config.planCheckLoop?.maxIterations?.[riskProfile] || 3;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function appendIterationLog({ taskDir, taskId, iteration, result, elapsedMs, maxIterations }) {
|
|
133
|
+
const expectedOutputTokens = 3000;
|
|
134
|
+
const estimatedInputTokens = readJsonFile(path.join(taskDir, 'check-context.json')).contextBudgetTokens;
|
|
135
|
+
const resolutionCounts = readResolutionCounts(taskDir);
|
|
136
|
+
const estimatedTotalTokens = iteration * (estimatedInputTokens + expectedOutputTokens);
|
|
137
|
+
const derivedBudgetTokens = maxIterations * (estimatedInputTokens + expectedOutputTokens);
|
|
138
|
+
appendOrchestrationLog(taskDir, {
|
|
139
|
+
event: 'plan_check_iteration',
|
|
140
|
+
taskId,
|
|
141
|
+
iteration,
|
|
142
|
+
maxIterations,
|
|
143
|
+
riskProfile: result.riskProfile,
|
|
144
|
+
planSha: result.planSha,
|
|
145
|
+
memorySha: result.memorySha,
|
|
146
|
+
checkerProvider: result.checkerProvider,
|
|
147
|
+
checkerModel: result.checkerModel,
|
|
148
|
+
verdict: result.verdict,
|
|
149
|
+
failureReason: result.failureReason,
|
|
150
|
+
blockingFindings: result.blockingFindings,
|
|
151
|
+
acceptedCount: resolutionCounts.accepted,
|
|
152
|
+
partiallyAcceptedCount: resolutionCounts.partiallyAccepted,
|
|
153
|
+
rejectedWithEvidence: resolutionCounts.rejectedWithEvidence,
|
|
154
|
+
needsResearch: resolutionCounts.needsResearch,
|
|
155
|
+
needsHumanTriage: 0,
|
|
156
|
+
needsHumanDecision: resolutionCounts.needsHumanDecision,
|
|
157
|
+
elapsedMs,
|
|
158
|
+
estimatedInputTokens,
|
|
159
|
+
estimatedOutputTokens,
|
|
160
|
+
estimatedTotalTokens,
|
|
161
|
+
derivedBudgetTokens,
|
|
162
|
+
derivedBudgetWarning: estimatedTotalTokens >= derivedBudgetTokens,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readResolutionCounts(taskDir) {
|
|
167
|
+
const defaults = {
|
|
168
|
+
accepted: 0,
|
|
169
|
+
partiallyAccepted: 0,
|
|
170
|
+
rejectedWithEvidence: 0,
|
|
171
|
+
needsResearch: 0,
|
|
172
|
+
needsHumanDecision: 0,
|
|
173
|
+
};
|
|
174
|
+
const resolutionPath = path.join(taskDir, 'check-resolution.md');
|
|
175
|
+
if (!fs.existsSync(resolutionPath)) {
|
|
176
|
+
return defaults;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const markdown = readTaskFile(taskDir, 'check-resolution.md');
|
|
180
|
+
const markerIndex = markdown.indexOf('## Structured Resolution');
|
|
181
|
+
if (markerIndex === -1) {
|
|
182
|
+
return defaults;
|
|
183
|
+
}
|
|
184
|
+
const match = /```json\s*([\s\S]*?)```/.exec(markdown.slice(markerIndex));
|
|
185
|
+
if (!match) {
|
|
186
|
+
return defaults;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const resolution = JSON.parse(match[1]);
|
|
191
|
+
for (const response of resolution.responses || []) {
|
|
192
|
+
if (response.decision === 'accepted') {
|
|
193
|
+
defaults.accepted += 1;
|
|
194
|
+
}
|
|
195
|
+
if (response.decision === 'partially_accepted') {
|
|
196
|
+
defaults.partiallyAccepted += 1;
|
|
197
|
+
}
|
|
198
|
+
if (response.decision === 'rejected' && Array.isArray(response.evidenceRefs) && response.evidenceRefs.length > 0) {
|
|
199
|
+
defaults.rejectedWithEvidence += 1;
|
|
200
|
+
}
|
|
201
|
+
if (response.decision === 'needs_research') {
|
|
202
|
+
defaults.needsResearch += 1;
|
|
203
|
+
}
|
|
204
|
+
if (response.decision === 'needs_human_decision') {
|
|
205
|
+
defaults.needsHumanDecision += 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
return defaults;
|
|
210
|
+
}
|
|
211
|
+
return defaults;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function routeAfterCheck({ taskDir, result, iteration, maxIterations }) {
|
|
215
|
+
if (result.verdict === 'ready_for_human_gate') {
|
|
216
|
+
const summary = buildHumanGateSummary(taskDir, result);
|
|
217
|
+
writeTaskFile(taskDir, 'human-gate-summary.md', summary);
|
|
218
|
+
updateStatus(taskDir, {
|
|
219
|
+
stage: 'Human Gate',
|
|
220
|
+
routingDecision: 'ready_for_human_gate',
|
|
221
|
+
planSha: `\`${result.planSha}\``,
|
|
222
|
+
memorySha: `\`${result.memorySha}\``,
|
|
223
|
+
checkVerdict: '`ready_for_human_gate`',
|
|
224
|
+
checkResult: '- `check.result.json`: current',
|
|
225
|
+
humanGateSummary: '- `human-gate-summary.md`: present',
|
|
226
|
+
supervisorAction: `Plan/Check loop converged at iteration ${iteration}/${maxIterations}.`,
|
|
227
|
+
nextStep: 'Ask human approval before Execute.',
|
|
228
|
+
humanApproval: 'yes',
|
|
229
|
+
});
|
|
230
|
+
console.log('\n--- Human Gate Summary ---\n');
|
|
231
|
+
console.log(summary);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (result.verdict === 'return_to_plan') {
|
|
236
|
+
updateStatus(taskDir, {
|
|
237
|
+
stage: 'Plan',
|
|
238
|
+
routingDecision: 'return_to_plan',
|
|
239
|
+
planSha: `\`${result.planSha}\``,
|
|
240
|
+
memorySha: `\`${result.memorySha}\``,
|
|
241
|
+
checkVerdict: '`return_to_plan`',
|
|
242
|
+
checkResult: '- `check.result.json`: current',
|
|
243
|
+
checkResolution: '- `check-resolution.md`: missing | present',
|
|
244
|
+
supervisorAction: 'Checker returned blockers; Planner must resolve each blocking finding.',
|
|
245
|
+
nextStep: 'Planner updates artifacts and writes check-resolution.md, then fresh Check.',
|
|
246
|
+
humanApproval: 'no',
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (result.verdict === 'human_arbitration_required') {
|
|
252
|
+
updateStatus(taskDir, {
|
|
253
|
+
stage: 'Human Arbitration',
|
|
254
|
+
routingDecision: 'human_arbitration_required',
|
|
255
|
+
checkVerdict: '`human_arbitration_required`',
|
|
256
|
+
supervisorAction: 'Checker routed unresolved tradeoff to Human Arbitration.',
|
|
257
|
+
nextStep: 'Human decides unresolved finding or risk appetite.',
|
|
258
|
+
humanApproval: 'yes',
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (result.verdict === 'checker_failed') {
|
|
264
|
+
updateStatus(taskDir, {
|
|
265
|
+
stage: 'Check',
|
|
266
|
+
routingDecision: `checker_failed:${result.failureReason}`,
|
|
267
|
+
checkVerdict: '`checker_failed`',
|
|
268
|
+
supervisorAction: `Checker failed with ${result.failureReason}.`,
|
|
269
|
+
nextStep: nextStepForFailure(result.failureReason),
|
|
270
|
+
humanApproval: result.failureReason === 'context_overflow' || result.failureReason === 'repo_read_limit_exceeded' ? 'yes' : 'no',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildHumanGateSummary(taskDir, result) {
|
|
276
|
+
const taskId = path.basename(taskDir);
|
|
277
|
+
const briefSections = parseMarkdownSections(readTaskFile(taskDir, 'brief.md'));
|
|
278
|
+
const planSections = parseMarkdownSections(readTaskFile(taskDir, 'plan.md'));
|
|
279
|
+
const problem = firstNonEmpty([
|
|
280
|
+
briefSections.get('Задача'),
|
|
281
|
+
briefSections.get('Task'),
|
|
282
|
+
briefSections.get('Зачем это нужно'),
|
|
283
|
+
briefSections.get('Why'),
|
|
284
|
+
]);
|
|
285
|
+
const planSummary = firstNonEmpty([
|
|
286
|
+
planSections.get('Goal'),
|
|
287
|
+
planSections.get('Цель'),
|
|
288
|
+
planSections.get('Implementation Steps'),
|
|
289
|
+
planSections.get('Шаги реализации'),
|
|
290
|
+
]);
|
|
291
|
+
const executionApproach = firstNonEmpty([
|
|
292
|
+
planSections.get('Dependency Order'),
|
|
293
|
+
planSections.get('Implementation Steps'),
|
|
294
|
+
planSections.get('Verification and replay plan'),
|
|
295
|
+
]);
|
|
296
|
+
const humanRequirements = firstNonEmpty([
|
|
297
|
+
planSections.get('Verification and replay plan'),
|
|
298
|
+
planSections.get('Known Risks'),
|
|
299
|
+
planSections.get('Constraints'),
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
return [
|
|
303
|
+
'# Human Gate Summary',
|
|
304
|
+
'',
|
|
305
|
+
'## Task',
|
|
306
|
+
'',
|
|
307
|
+
`\`${taskId}\``,
|
|
308
|
+
'',
|
|
309
|
+
'## Decision Needed',
|
|
310
|
+
'',
|
|
311
|
+
'Approve moving from `Human Gate` to `Execute`, or ask a clarifying question before approval.',
|
|
312
|
+
'',
|
|
313
|
+
'## Problem We Are Solving',
|
|
314
|
+
'',
|
|
315
|
+
truncateMarkdown(problem, 900),
|
|
316
|
+
'',
|
|
317
|
+
'## Approved Plan Summary',
|
|
318
|
+
'',
|
|
319
|
+
truncateMarkdown(planSummary, 1200),
|
|
320
|
+
'',
|
|
321
|
+
'## Execution Approach',
|
|
322
|
+
'',
|
|
323
|
+
truncateMarkdown(executionApproach, 1200),
|
|
324
|
+
'',
|
|
325
|
+
'## Human Gate Requirements',
|
|
326
|
+
'',
|
|
327
|
+
truncateMarkdown(humanRequirements, 1200),
|
|
328
|
+
'',
|
|
329
|
+
'## What Approval Allows',
|
|
330
|
+
'',
|
|
331
|
+
'- Executor may start implementation according to `plan.md`.',
|
|
332
|
+
'- Executor may touch only modules and artifacts covered by the approved plan.',
|
|
333
|
+
'- Executor may run local verification commands required by the plan.',
|
|
334
|
+
'',
|
|
335
|
+
'## What Remains Forbidden',
|
|
336
|
+
'',
|
|
337
|
+
'- Do not run non-local replay/backfill without separate Human Gate approval.',
|
|
338
|
+
'- Do not apply Prisma schema changes outside local development without explicit approval.',
|
|
339
|
+
'- Do not expand scope beyond `brief.md` / `plan.md` without returning to Brief/Plan and fresh Check.',
|
|
340
|
+
'',
|
|
341
|
+
'## Check Result',
|
|
342
|
+
'',
|
|
343
|
+
`- Verdict: \`${result.verdict}\``,
|
|
344
|
+
`- Blocking findings: \`${result.blockingFindings}\``,
|
|
345
|
+
`- Plan SHA: \`${result.planSha}\``,
|
|
346
|
+
`- Memory SHA: \`${result.memorySha}\``,
|
|
347
|
+
'',
|
|
348
|
+
'## Next Step',
|
|
349
|
+
'',
|
|
350
|
+
'Human approves Execute or asks a clarifying question.',
|
|
351
|
+
].join('\n');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function firstNonEmpty(values) {
|
|
355
|
+
for (const value of values) {
|
|
356
|
+
if (typeof value === 'string' && value.trim()) {
|
|
357
|
+
return value.trim();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return 'n/a';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function truncateMarkdown(value, maxLength) {
|
|
364
|
+
const normalized = String(value || 'n/a').trim();
|
|
365
|
+
if (normalized.length <= maxLength) {
|
|
366
|
+
return normalized;
|
|
367
|
+
}
|
|
368
|
+
return `${normalized.slice(0, maxLength).trimEnd()}\n\n...`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function nextStepForFailure(failureReason) {
|
|
372
|
+
if (failureReason === 'provider_unavailable' || failureReason === 'timeout') {
|
|
373
|
+
return 'Retry once or use secondary checker provider.';
|
|
374
|
+
}
|
|
375
|
+
if (failureReason === 'invalid_json' || failureReason === 'schema_validation_failed') {
|
|
376
|
+
return 'Rerun checker once with schema repair instruction, then Human Arbitration if repeated.';
|
|
377
|
+
}
|
|
378
|
+
if (failureReason === 'context_overflow' || failureReason === 'repo_read_limit_exceeded') {
|
|
379
|
+
return 'Shrink context or ask human to approve deeper check budget.';
|
|
380
|
+
}
|
|
381
|
+
if (failureReason === 'memory_snapshot_mismatch') {
|
|
382
|
+
return 'Rebuild check context with fresh memorySha.';
|
|
383
|
+
}
|
|
384
|
+
return 'Inspect checker failure and decide retry or Human Arbitration.';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function fail(message) {
|
|
388
|
+
console.error(`Error: ${message}`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
main();
|