codex-orchestrator 0.1.42 → 0.1.44
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 +32 -0
- package/dist/src/cli.js +66 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config/schema.d.ts +1 -1
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +1 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/runner/acceptance-proof-loop.d.ts +7 -0
- package/dist/src/runner/acceptance-proof-loop.d.ts.map +1 -1
- package/dist/src/runner/acceptance-proof-loop.js +28 -3
- package/dist/src/runner/acceptance-proof-loop.js.map +1 -1
- package/dist/src/runner/acceptance-proof-runner.d.ts +1 -0
- package/dist/src/runner/acceptance-proof-runner.d.ts.map +1 -1
- package/dist/src/runner/acceptance-proof-runner.js +20 -2
- package/dist/src/runner/acceptance-proof-runner.js.map +1 -1
- package/dist/src/runner/acceptance-proof.d.ts +8 -0
- package/dist/src/runner/acceptance-proof.d.ts.map +1 -1
- package/dist/src/runner/acceptance-proof.js +116 -40
- package/dist/src/runner/acceptance-proof.js.map +1 -1
- package/dist/src/runner/agent-attempt.d.ts.map +1 -1
- package/dist/src/runner/agent-attempt.js +10 -0
- package/dist/src/runner/agent-attempt.js.map +1 -1
- package/dist/src/runner/completion-report.d.ts +12 -0
- package/dist/src/runner/completion-report.d.ts.map +1 -1
- package/dist/src/runner/completion-report.js +27 -3
- package/dist/src/runner/completion-report.js.map +1 -1
- package/dist/src/runner/durable-run-summary.d.ts +5 -0
- package/dist/src/runner/durable-run-summary.d.ts.map +1 -1
- package/dist/src/runner/durable-run-summary.js +25 -0
- package/dist/src/runner/durable-run-summary.js.map +1 -1
- package/dist/src/runner/handoff-evidence.d.ts +4 -0
- package/dist/src/runner/handoff-evidence.d.ts.map +1 -1
- package/dist/src/runner/handoff-evidence.js +135 -21
- package/dist/src/runner/handoff-evidence.js.map +1 -1
- package/dist/src/runner/local-execution-session.d.ts +24 -1
- package/dist/src/runner/local-execution-session.d.ts.map +1 -1
- package/dist/src/runner/local-execution-session.js +686 -29
- package/dist/src/runner/local-execution-session.js.map +1 -1
- package/dist/src/runner/review-gates.d.ts +2 -0
- package/dist/src/runner/review-gates.d.ts.map +1 -1
- package/dist/src/runner/review-gates.js +50 -11
- package/dist/src/runner/review-gates.js.map +1 -1
- package/dist/src/runner/rework-policy.d.ts +12 -1
- package/dist/src/runner/rework-policy.d.ts.map +1 -1
- package/dist/src/runner/rework-policy.js +52 -8
- package/dist/src/runner/rework-policy.js.map +1 -1
- package/dist/src/runner/runner-handoff-decision.d.ts +2 -1
- package/dist/src/runner/runner-handoff-decision.d.ts.map +1 -1
- package/dist/src/runner/runner-handoff-decision.js +10 -1
- package/dist/src/runner/runner-handoff-decision.js.map +1 -1
- package/dist/src/runner/scoped-auto-command.d.ts +2 -1
- package/dist/src/runner/scoped-auto-command.d.ts.map +1 -1
- package/dist/src/runner/scoped-auto-command.js +10 -3
- package/dist/src/runner/scoped-auto-command.js.map +1 -1
- package/dist/src/setup/project-config.d.ts.map +1 -1
- package/dist/src/setup/project-config.js +1 -0
- package/dist/src/setup/project-config.js.map +1 -1
- package/docs/deep-dive.md +76 -11
- package/package.json +1 -1
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
5
|
import { mergeArtifacts, runConfiguredChecks } from './command-utils.js';
|
|
4
|
-
import {
|
|
6
|
+
import { readScopedCompletionReportDetailed, } from './completion-report.js';
|
|
7
|
+
import { sessionPromptPath, writeDurablePrompt } from './prompt.js';
|
|
8
|
+
import { sessionLogPath } from './run-log.js';
|
|
5
9
|
import { evaluateReviewGates, shouldApplyVisualProofGate } from './review-gates.js';
|
|
6
10
|
import { validateChangedPaths, validateCompletionReportSafety, validateNoAgentOwnedGitPublication, } from './safety.js';
|
|
7
11
|
import { evaluateScopeIsolation } from './scope-isolation-policy.js';
|
|
8
12
|
import { runRunnerVisualProofAdapter } from './visual-proof-runner.js';
|
|
9
13
|
import { runAcceptanceProofAdapter, } from './acceptance-proof-runner.js';
|
|
10
14
|
import { runAcceptanceProofLoopAttempt } from './acceptance-proof-loop.js';
|
|
11
|
-
import { MISSING_COMPLETION_REPORT_REASON, OPTIONAL_FIGMA_MCP_FAILURE_REASON, REQUIRED_FIGMA_MCP_FAILURE_REASON, } from './rework-policy.js';
|
|
15
|
+
import { INCOMPLETE_AFTER_PROGRESS_REASON, MISSING_COMPLETION_REPORT_REASON, OPTIONAL_FIGMA_MCP_FAILURE_REASON, REQUIRED_FIGMA_MCP_FAILURE_REASON, } from './rework-policy.js';
|
|
16
|
+
import { cleanupSessionCodexHome, sessionCodexHomePath } from './session-home.js';
|
|
12
17
|
export async function runLocalExecutionSession(input) {
|
|
13
18
|
const phaseResults = [];
|
|
14
19
|
for (const phaseId of input.phases) {
|
|
@@ -34,26 +39,43 @@ export async function runLocalExecutionSession(input) {
|
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
41
|
export async function runImplementationPublishabilityCheck(input) {
|
|
42
|
+
const repairAttempts = [];
|
|
37
43
|
const publicationViolations = input.config.runner.allowAgentLocalCommits
|
|
38
44
|
? []
|
|
39
45
|
: validateNoAgentOwnedGitPublication(input.beforeHead, input.afterHead);
|
|
40
46
|
if (publicationViolations.length > 0) {
|
|
41
47
|
return blocked(publicationViolations.map((violation) => violation.message));
|
|
42
48
|
}
|
|
43
|
-
if (input.codexResult.exitCode !== 0) {
|
|
44
|
-
return blocked([formatCodexExitReason(input.codexResult)]);
|
|
45
|
-
}
|
|
46
49
|
let report;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const idleTimeout = isIdleTimeoutResult(input.codexResult);
|
|
51
|
+
if (input.codexResult.exitCode !== 0) {
|
|
52
|
+
const figmaReason = formatFigmaMcpFailureReason(input.codexResult);
|
|
53
|
+
if (figmaReason) {
|
|
54
|
+
return blocked([figmaReason]);
|
|
55
|
+
}
|
|
56
|
+
if (!idleTimeout) {
|
|
57
|
+
return blocked([formatCodexExitReason(input.codexResult)]);
|
|
58
|
+
}
|
|
59
|
+
const reportResult = await readScopedCompletionReportDetailed(input.reportPath);
|
|
60
|
+
const preparedReport = await resolveInitialReportRead(input, reportResult, {
|
|
61
|
+
idleTimeout,
|
|
62
|
+
repairAttempts,
|
|
63
|
+
});
|
|
64
|
+
if (preparedReport.kind === 'blocked') {
|
|
65
|
+
return preparedReport.result;
|
|
51
66
|
}
|
|
52
|
-
report =
|
|
67
|
+
report = preparedReport.report;
|
|
53
68
|
}
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
69
|
+
else {
|
|
70
|
+
const reportResult = await readScopedCompletionReportDetailed(input.reportPath);
|
|
71
|
+
const preparedReport = await resolveInitialReportRead(input, reportResult, {
|
|
72
|
+
idleTimeout,
|
|
73
|
+
repairAttempts,
|
|
74
|
+
});
|
|
75
|
+
if (preparedReport.kind === 'blocked') {
|
|
76
|
+
return preparedReport.result;
|
|
77
|
+
}
|
|
78
|
+
report = preparedReport.report;
|
|
57
79
|
}
|
|
58
80
|
if (report.status === 'needs-promotion') {
|
|
59
81
|
return { status: 'promotion-requested', report };
|
|
@@ -76,6 +98,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
76
98
|
skippedChecks: applicableSkippedChecks(input, report, changeSet.changedPaths),
|
|
77
99
|
residualRisks: [...report.residualRisks, ...localSession.phaseResults.flatMap((phase) => phase.residualRisks)],
|
|
78
100
|
commits: changeSet.commits,
|
|
101
|
+
...repairAttemptsField(repairAttempts),
|
|
79
102
|
};
|
|
80
103
|
}
|
|
81
104
|
report = {
|
|
@@ -102,6 +125,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
102
125
|
skippedChecks: report.skippedChecks,
|
|
103
126
|
residualRisks: report.residualRisks,
|
|
104
127
|
commits: changeSet.commits,
|
|
128
|
+
...repairAttemptsField(repairAttempts),
|
|
105
129
|
};
|
|
106
130
|
}
|
|
107
131
|
const scopeIsolation = evaluateScopeIsolation({
|
|
@@ -118,6 +142,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
118
142
|
skippedChecks: report.skippedChecks,
|
|
119
143
|
residualRisks: report.residualRisks,
|
|
120
144
|
commits: changeSet.commits,
|
|
145
|
+
...repairAttemptsField(repairAttempts),
|
|
121
146
|
};
|
|
122
147
|
}
|
|
123
148
|
const violations = [
|
|
@@ -133,6 +158,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
133
158
|
skippedChecks: report.skippedChecks,
|
|
134
159
|
residualRisks: report.residualRisks,
|
|
135
160
|
commits: changeSet.commits,
|
|
161
|
+
...repairAttemptsField(repairAttempts),
|
|
136
162
|
};
|
|
137
163
|
}
|
|
138
164
|
const validationBeforeChecks = report.validation.length;
|
|
@@ -174,6 +200,21 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
174
200
|
});
|
|
175
201
|
}
|
|
176
202
|
: undefined,
|
|
203
|
+
executeAdaptiveProofRepair: input.acceptanceProof
|
|
204
|
+
? async ({ schemaErrors }) => runAcceptanceProofAdapter({
|
|
205
|
+
config: input.config,
|
|
206
|
+
issue: input.issue,
|
|
207
|
+
targetRoot: input.acceptanceProof.targetRoot,
|
|
208
|
+
worktreePath: input.worktreePath,
|
|
209
|
+
changedFiles,
|
|
210
|
+
implementationReport: report,
|
|
211
|
+
codexAdapter: input.acceptanceProof.codexAdapter,
|
|
212
|
+
sessionId: input.acceptanceProof.sessionId,
|
|
213
|
+
branchName: input.acceptanceProof.branchName,
|
|
214
|
+
workflowPromptText: input.acceptanceProof.workflowPromptText,
|
|
215
|
+
repairSchemaErrors: schemaErrors,
|
|
216
|
+
})
|
|
217
|
+
: undefined,
|
|
177
218
|
executeCommandProof: async () => {
|
|
178
219
|
const result = await runRunnerVisualProofAdapter({
|
|
179
220
|
config: input.config,
|
|
@@ -230,6 +271,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
230
271
|
residualRisks,
|
|
231
272
|
commits: changeSet.commits,
|
|
232
273
|
acceptanceProofAttempt,
|
|
274
|
+
...repairAttemptsField(repairAttempts),
|
|
233
275
|
};
|
|
234
276
|
}
|
|
235
277
|
}
|
|
@@ -248,6 +290,7 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
248
290
|
residualRisks,
|
|
249
291
|
commits: changeSet.commits,
|
|
250
292
|
acceptanceProofAttempt,
|
|
293
|
+
...repairAttemptsField(repairAttempts),
|
|
251
294
|
};
|
|
252
295
|
}
|
|
253
296
|
const reviewGate = evaluateReviewGates({
|
|
@@ -259,23 +302,46 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
259
302
|
report,
|
|
260
303
|
worktreePath: input.worktreePath,
|
|
261
304
|
});
|
|
305
|
+
let reviewGateWarnings = reviewGate.warnings;
|
|
262
306
|
if (!reviewGate.ok) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
const evidenceRepair = await maybeRunEvidenceReportRepair(input, {
|
|
308
|
+
report,
|
|
309
|
+
reviewGate,
|
|
266
310
|
changedFiles,
|
|
311
|
+
changeSet,
|
|
267
312
|
validation,
|
|
268
|
-
skippedChecks: report.skippedChecks,
|
|
269
313
|
residualRisks,
|
|
270
|
-
commits: changeSet.commits,
|
|
271
314
|
acceptanceProofAttempt,
|
|
272
|
-
|
|
315
|
+
repairAttempts,
|
|
316
|
+
});
|
|
317
|
+
if (evidenceRepair.kind === 'publish-ready') {
|
|
318
|
+
report = evidenceRepair.report;
|
|
319
|
+
validation = evidenceRepair.validation;
|
|
320
|
+
reviewGateWarnings = evidenceRepair.reviewWarnings;
|
|
321
|
+
}
|
|
322
|
+
else if (evidenceRepair.kind === 'blocked') {
|
|
323
|
+
return evidenceRepair.result;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
return {
|
|
327
|
+
status: 'blocked',
|
|
328
|
+
reasons: reviewGate.reasons,
|
|
329
|
+
blockers: reviewGate.blockers,
|
|
330
|
+
changedFiles,
|
|
331
|
+
validation,
|
|
332
|
+
skippedChecks: report.skippedChecks,
|
|
333
|
+
residualRisks,
|
|
334
|
+
commits: changeSet.commits,
|
|
335
|
+
acceptanceProofAttempt,
|
|
336
|
+
...repairAttemptsField(repairAttempts),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
273
339
|
}
|
|
274
|
-
if (
|
|
275
|
-
residualRisks.push(...
|
|
340
|
+
if (reviewGateWarnings.length > 0) {
|
|
341
|
+
residualRisks.push(...reviewGateWarnings);
|
|
276
342
|
report = {
|
|
277
343
|
...report,
|
|
278
|
-
residualRisks: [...report.residualRisks, ...
|
|
344
|
+
residualRisks: [...report.residualRisks, ...reviewGateWarnings],
|
|
279
345
|
};
|
|
280
346
|
}
|
|
281
347
|
if (!(await input.git.isWorktreeClean(input.worktreePath))) {
|
|
@@ -295,8 +361,574 @@ export async function runImplementationPublishabilityCheck(input) {
|
|
|
295
361
|
residualRisks,
|
|
296
362
|
commits: changeSet.commits,
|
|
297
363
|
acceptanceProofAttempt,
|
|
364
|
+
...repairAttemptsField(repairAttempts),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async function resolveInitialReportRead(input, reportResult, state) {
|
|
368
|
+
if (reportResult.kind === 'valid') {
|
|
369
|
+
return { kind: 'valid', report: reportResult.report };
|
|
370
|
+
}
|
|
371
|
+
const repair = state.idleTimeout
|
|
372
|
+
? { kind: 'not-eligible' }
|
|
373
|
+
: await maybeRunCompletionReportRepair(input, reportResult, state.repairAttempts);
|
|
374
|
+
if (repair.kind === 'valid') {
|
|
375
|
+
return repair;
|
|
376
|
+
}
|
|
377
|
+
if (repair.kind === 'blocked') {
|
|
378
|
+
return repair;
|
|
379
|
+
}
|
|
380
|
+
if (reportResult.kind === 'missing') {
|
|
381
|
+
if (!state.idleTimeout) {
|
|
382
|
+
return {
|
|
383
|
+
kind: 'blocked',
|
|
384
|
+
result: blocked([MISSING_COMPLETION_REPORT_REASON], {
|
|
385
|
+
blockers: [runnerBlocker('missing-completion-report', MISSING_COMPLETION_REPORT_REASON, 'completion-report', 'completion-report')],
|
|
386
|
+
repairAttempts: state.repairAttempts,
|
|
387
|
+
}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const changeSet = repair.changeSet ?? await input.git.collectSessionChangeSet({
|
|
391
|
+
worktreePath: input.worktreePath,
|
|
392
|
+
baseHead: input.beforeHead,
|
|
393
|
+
});
|
|
394
|
+
const changedFiles = changeSet.changedPaths;
|
|
395
|
+
if (changedFiles.length === 0) {
|
|
396
|
+
return {
|
|
397
|
+
kind: 'blocked',
|
|
398
|
+
result: blocked(['Codex completed without file changes'], { repairAttempts: state.repairAttempts }),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const changedPathViolations = validateChangedPaths(changedFiles, input.config);
|
|
402
|
+
if (changedPathViolations.length > 0) {
|
|
403
|
+
return {
|
|
404
|
+
kind: 'blocked',
|
|
405
|
+
result: blocked(changedPathViolations.map((violation) => violation.message), {
|
|
406
|
+
changedFiles,
|
|
407
|
+
commits: changeSet.commits,
|
|
408
|
+
repairAttempts: state.repairAttempts,
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const scopeIsolation = evaluateScopeIsolation({
|
|
413
|
+
config: input.config,
|
|
414
|
+
issue: input.issue,
|
|
415
|
+
changedFiles,
|
|
416
|
+
});
|
|
417
|
+
if (scopeIsolation.blockers.length > 0) {
|
|
418
|
+
return {
|
|
419
|
+
kind: 'blocked',
|
|
420
|
+
result: blocked(scopeIsolation.blockers, {
|
|
421
|
+
changedFiles,
|
|
422
|
+
commits: changeSet.commits,
|
|
423
|
+
repairAttempts: state.repairAttempts,
|
|
424
|
+
}),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
kind: 'blocked',
|
|
429
|
+
result: blocked([INCOMPLETE_AFTER_PROGRESS_REASON], {
|
|
430
|
+
changedFiles,
|
|
431
|
+
validation: [],
|
|
432
|
+
commits: changeSet.commits,
|
|
433
|
+
repairAttempts: state.repairAttempts,
|
|
434
|
+
blockers: [runnerBlocker('incomplete-after-progress', INCOMPLETE_AFTER_PROGRESS_REASON, 'recovery', 'implementation-rework')],
|
|
435
|
+
}),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
kind: 'blocked',
|
|
440
|
+
result: blocked([reportResult.message], {
|
|
441
|
+
blockers: [runnerBlocker('invalid-completion-report', reportResult.message, 'completion-report', 'completion-report')],
|
|
442
|
+
repairAttempts: state.repairAttempts,
|
|
443
|
+
}),
|
|
298
444
|
};
|
|
299
445
|
}
|
|
446
|
+
async function maybeRunCompletionReportRepair(input, reportIssue, repairAttempts) {
|
|
447
|
+
const changeSet = await input.git.collectSessionChangeSet({
|
|
448
|
+
worktreePath: input.worktreePath,
|
|
449
|
+
baseHead: input.beforeHead,
|
|
450
|
+
});
|
|
451
|
+
const changedFiles = changeSet.changedPaths;
|
|
452
|
+
if (changedFiles.length === 0) {
|
|
453
|
+
return { kind: 'not-eligible', changeSet };
|
|
454
|
+
}
|
|
455
|
+
const changedPathViolations = validateChangedPaths(changedFiles, input.config);
|
|
456
|
+
if (changedPathViolations.length > 0) {
|
|
457
|
+
return {
|
|
458
|
+
kind: 'blocked',
|
|
459
|
+
result: blocked(changedPathViolations.map((violation) => violation.message), {
|
|
460
|
+
changedFiles,
|
|
461
|
+
commits: changeSet.commits,
|
|
462
|
+
}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const scopeIsolation = evaluateScopeIsolation({
|
|
466
|
+
config: input.config,
|
|
467
|
+
issue: input.issue,
|
|
468
|
+
changedFiles,
|
|
469
|
+
});
|
|
470
|
+
if (scopeIsolation.blockers.length > 0) {
|
|
471
|
+
return {
|
|
472
|
+
kind: 'blocked',
|
|
473
|
+
result: blocked(scopeIsolation.blockers, {
|
|
474
|
+
changedFiles,
|
|
475
|
+
commits: changeSet.commits,
|
|
476
|
+
}),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (!input.reportRepair || !input.git.getHead) {
|
|
480
|
+
return { kind: 'not-eligible', changeSet };
|
|
481
|
+
}
|
|
482
|
+
const repairSessionId = `${input.reportRepair.sessionId}-completion-report-repair`;
|
|
483
|
+
const promptPath = sessionPromptPath({
|
|
484
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
485
|
+
config: input.config,
|
|
486
|
+
issueNumber: input.issue.number,
|
|
487
|
+
sessionId: repairSessionId,
|
|
488
|
+
});
|
|
489
|
+
const logPath = sessionLogPath({
|
|
490
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
491
|
+
config: input.config,
|
|
492
|
+
issueNumber: input.issue.number,
|
|
493
|
+
sessionId: repairSessionId,
|
|
494
|
+
});
|
|
495
|
+
const repairAttemptBase = {
|
|
496
|
+
kind: 'completion-report',
|
|
497
|
+
sessionId: repairSessionId,
|
|
498
|
+
promptPath,
|
|
499
|
+
reportPath: input.reportPath,
|
|
500
|
+
logPath,
|
|
501
|
+
};
|
|
502
|
+
const preRepairHead = await input.git.getHead(input.worktreePath);
|
|
503
|
+
const protectedBefore = await captureRepairProtectedFingerprint(input, changeSet);
|
|
504
|
+
const promptText = buildCompletionReportRepairPrompt({
|
|
505
|
+
input,
|
|
506
|
+
reportIssue,
|
|
507
|
+
changedFiles,
|
|
508
|
+
});
|
|
509
|
+
await writeDurablePrompt({
|
|
510
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
511
|
+
config: input.config,
|
|
512
|
+
issueNumber: input.issue.number,
|
|
513
|
+
sessionId: repairSessionId,
|
|
514
|
+
promptText,
|
|
515
|
+
});
|
|
516
|
+
const isolatedHomePath = sessionCodexHomePath({
|
|
517
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
518
|
+
sessionId: repairSessionId,
|
|
519
|
+
});
|
|
520
|
+
let repairResult;
|
|
521
|
+
try {
|
|
522
|
+
repairResult = await input.reportRepair.codexAdapter.run({
|
|
523
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
524
|
+
config: input.config,
|
|
525
|
+
worktreePath: input.worktreePath,
|
|
526
|
+
promptPath,
|
|
527
|
+
promptText,
|
|
528
|
+
reportPath: input.reportPath,
|
|
529
|
+
isolatedHomePath,
|
|
530
|
+
issueNumber: input.issue.number,
|
|
531
|
+
sessionId: repairSessionId,
|
|
532
|
+
branchName: input.reportRepair.branchName,
|
|
533
|
+
phase: 'scoped-issue',
|
|
534
|
+
logPath,
|
|
535
|
+
env: {
|
|
536
|
+
CODEX_ORCHESTRATOR_REPAIR_MODE: 'completion-report',
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
await cleanupSessionCodexHome(isolatedHomePath);
|
|
542
|
+
}
|
|
543
|
+
if (repairResult.exitCode !== 0) {
|
|
544
|
+
const reason = `Completion report repair failed: ${formatCodexExitReason(repairResult)}`;
|
|
545
|
+
const blockers = [runnerBlocker('invalid-completion-report', reason, 'completion-report', 'none')];
|
|
546
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
547
|
+
return {
|
|
548
|
+
kind: 'blocked',
|
|
549
|
+
result: blocked([reason], {
|
|
550
|
+
changedFiles,
|
|
551
|
+
commits: changeSet.commits,
|
|
552
|
+
blockers,
|
|
553
|
+
repairAttempts,
|
|
554
|
+
}),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const postRepairHead = await input.git.getHead(input.worktreePath);
|
|
558
|
+
if (postRepairHead !== preRepairHead) {
|
|
559
|
+
const reason = 'Completion report repair created or moved HEAD; repair sessions must not create local commits.';
|
|
560
|
+
const blockers = [runnerBlocker('publication-violation', reason, 'safety', 'none')];
|
|
561
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
562
|
+
return {
|
|
563
|
+
kind: 'blocked',
|
|
564
|
+
result: blocked([reason], {
|
|
565
|
+
changedFiles,
|
|
566
|
+
commits: changeSet.commits,
|
|
567
|
+
blockers,
|
|
568
|
+
repairAttempts,
|
|
569
|
+
}),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const postChangeSet = await input.git.collectSessionChangeSet({
|
|
573
|
+
worktreePath: input.worktreePath,
|
|
574
|
+
baseHead: input.beforeHead,
|
|
575
|
+
});
|
|
576
|
+
const protectedAfter = await captureRepairProtectedFingerprint(input, postChangeSet);
|
|
577
|
+
if (protectedAfter !== protectedBefore) {
|
|
578
|
+
const reason = 'Completion report repair changed protected worktree content; repair may only write the completion report JSON.';
|
|
579
|
+
const blockers = [runnerBlocker('destructive-or-production-action', reason, 'safety', 'none')];
|
|
580
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
581
|
+
return {
|
|
582
|
+
kind: 'blocked',
|
|
583
|
+
result: blocked([reason], {
|
|
584
|
+
changedFiles: postChangeSet.changedPaths,
|
|
585
|
+
commits: postChangeSet.commits,
|
|
586
|
+
blockers,
|
|
587
|
+
repairAttempts,
|
|
588
|
+
}),
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const repairedReport = await readScopedCompletionReportDetailed(input.reportPath);
|
|
592
|
+
if (repairedReport.kind === 'missing') {
|
|
593
|
+
const reason = `${MISSING_COMPLETION_REPORT_REASON} Completion report repair did not write CODEX_ORCHESTRATOR_REPORT_FILE.`;
|
|
594
|
+
const blockers = [runnerBlocker('missing-completion-report', reason, 'completion-report', 'none')];
|
|
595
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
596
|
+
return {
|
|
597
|
+
kind: 'blocked',
|
|
598
|
+
result: blocked([reason], {
|
|
599
|
+
changedFiles,
|
|
600
|
+
commits: changeSet.commits,
|
|
601
|
+
blockers,
|
|
602
|
+
repairAttempts,
|
|
603
|
+
}),
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (repairedReport.kind === 'invalid') {
|
|
607
|
+
const reason = `Completion report repair wrote an invalid report: ${repairedReport.message}`;
|
|
608
|
+
const blockers = [runnerBlocker('invalid-completion-report', reason, 'completion-report', 'none')];
|
|
609
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
610
|
+
return {
|
|
611
|
+
kind: 'blocked',
|
|
612
|
+
result: blocked([reason], {
|
|
613
|
+
changedFiles,
|
|
614
|
+
commits: changeSet.commits,
|
|
615
|
+
blockers,
|
|
616
|
+
repairAttempts,
|
|
617
|
+
}),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
repairAttempts.push({ ...repairAttemptBase, status: 'passed', reason: 'Completion report repair wrote a valid scoped report.' });
|
|
621
|
+
return { kind: 'valid', report: repairedReport.report };
|
|
622
|
+
}
|
|
623
|
+
async function maybeRunEvidenceReportRepair(input, context) {
|
|
624
|
+
const repairableBlockers = (context.reviewGate.blockers ?? [])
|
|
625
|
+
.filter((blocker) => blocker.repair === 'evidence'
|
|
626
|
+
&& (blocker.key === 'missing-quality-gate-evidence' || blocker.key === 'risk-routing-policy'));
|
|
627
|
+
if (repairableBlockers.length === 0 || !input.reportRepair || !input.git.getHead) {
|
|
628
|
+
return { kind: 'not-eligible' };
|
|
629
|
+
}
|
|
630
|
+
const repairSessionId = `${input.reportRepair.sessionId}-evidence-repair`;
|
|
631
|
+
const promptPath = sessionPromptPath({
|
|
632
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
633
|
+
config: input.config,
|
|
634
|
+
issueNumber: input.issue.number,
|
|
635
|
+
sessionId: repairSessionId,
|
|
636
|
+
});
|
|
637
|
+
const logPath = sessionLogPath({
|
|
638
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
639
|
+
config: input.config,
|
|
640
|
+
issueNumber: input.issue.number,
|
|
641
|
+
sessionId: repairSessionId,
|
|
642
|
+
});
|
|
643
|
+
const repairAttemptBase = {
|
|
644
|
+
kind: 'evidence',
|
|
645
|
+
sessionId: repairSessionId,
|
|
646
|
+
promptPath,
|
|
647
|
+
reportPath: input.reportPath,
|
|
648
|
+
logPath,
|
|
649
|
+
};
|
|
650
|
+
const preRepairHead = await input.git.getHead(input.worktreePath);
|
|
651
|
+
const protectedBefore = await captureRepairProtectedFingerprint(input, context.changeSet);
|
|
652
|
+
const promptText = buildEvidenceReportRepairPrompt({
|
|
653
|
+
input,
|
|
654
|
+
changedFiles: context.changedFiles,
|
|
655
|
+
reviewGate: context.reviewGate,
|
|
656
|
+
validation: context.validation,
|
|
657
|
+
});
|
|
658
|
+
await writeDurablePrompt({
|
|
659
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
660
|
+
config: input.config,
|
|
661
|
+
issueNumber: input.issue.number,
|
|
662
|
+
sessionId: repairSessionId,
|
|
663
|
+
promptText,
|
|
664
|
+
});
|
|
665
|
+
const isolatedHomePath = sessionCodexHomePath({
|
|
666
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
667
|
+
sessionId: repairSessionId,
|
|
668
|
+
});
|
|
669
|
+
let repairResult;
|
|
670
|
+
try {
|
|
671
|
+
repairResult = await input.reportRepair.codexAdapter.run({
|
|
672
|
+
targetRoot: input.reportRepair.targetRoot,
|
|
673
|
+
config: input.config,
|
|
674
|
+
worktreePath: input.worktreePath,
|
|
675
|
+
promptPath,
|
|
676
|
+
promptText,
|
|
677
|
+
reportPath: input.reportPath,
|
|
678
|
+
isolatedHomePath,
|
|
679
|
+
issueNumber: input.issue.number,
|
|
680
|
+
sessionId: repairSessionId,
|
|
681
|
+
branchName: input.reportRepair.branchName,
|
|
682
|
+
phase: 'scoped-issue',
|
|
683
|
+
logPath,
|
|
684
|
+
env: {
|
|
685
|
+
CODEX_ORCHESTRATOR_REPAIR_MODE: 'evidence',
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
await cleanupSessionCodexHome(isolatedHomePath);
|
|
691
|
+
}
|
|
692
|
+
if (repairResult.exitCode !== 0) {
|
|
693
|
+
const reason = `Evidence repair failed: ${formatCodexExitReason(repairResult)}`;
|
|
694
|
+
const blockers = [runnerBlocker('missing-quality-gate-evidence', reason, 'review-gate', 'none')];
|
|
695
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
696
|
+
return {
|
|
697
|
+
kind: 'blocked',
|
|
698
|
+
result: blocked([reason], {
|
|
699
|
+
changedFiles: context.changedFiles,
|
|
700
|
+
validation: context.validation,
|
|
701
|
+
skippedChecks: context.report.skippedChecks,
|
|
702
|
+
residualRisks: context.residualRisks,
|
|
703
|
+
commits: context.changeSet.commits,
|
|
704
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
705
|
+
blockers,
|
|
706
|
+
repairAttempts: context.repairAttempts,
|
|
707
|
+
}),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
const postRepairHead = await input.git.getHead(input.worktreePath);
|
|
711
|
+
if (postRepairHead !== preRepairHead) {
|
|
712
|
+
const reason = 'Evidence repair created or moved HEAD; repair sessions must not create local commits.';
|
|
713
|
+
const blockers = [runnerBlocker('publication-violation', reason, 'safety', 'none')];
|
|
714
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
715
|
+
return {
|
|
716
|
+
kind: 'blocked',
|
|
717
|
+
result: blocked([reason], {
|
|
718
|
+
changedFiles: context.changedFiles,
|
|
719
|
+
validation: context.validation,
|
|
720
|
+
skippedChecks: context.report.skippedChecks,
|
|
721
|
+
residualRisks: context.residualRisks,
|
|
722
|
+
commits: context.changeSet.commits,
|
|
723
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
724
|
+
blockers,
|
|
725
|
+
repairAttempts: context.repairAttempts,
|
|
726
|
+
}),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
const postChangeSet = await input.git.collectSessionChangeSet({
|
|
730
|
+
worktreePath: input.worktreePath,
|
|
731
|
+
baseHead: input.beforeHead,
|
|
732
|
+
});
|
|
733
|
+
const protectedAfter = await captureRepairProtectedFingerprint(input, postChangeSet);
|
|
734
|
+
if (protectedAfter !== protectedBefore) {
|
|
735
|
+
const reason = 'Evidence repair changed protected worktree content; repair may only write corrected completion report evidence.';
|
|
736
|
+
const blockers = [runnerBlocker('destructive-or-production-action', reason, 'safety', 'none')];
|
|
737
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
738
|
+
return {
|
|
739
|
+
kind: 'blocked',
|
|
740
|
+
result: blocked([reason], {
|
|
741
|
+
changedFiles: postChangeSet.changedPaths,
|
|
742
|
+
validation: context.validation,
|
|
743
|
+
skippedChecks: context.report.skippedChecks,
|
|
744
|
+
residualRisks: context.residualRisks,
|
|
745
|
+
commits: postChangeSet.commits,
|
|
746
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
747
|
+
blockers,
|
|
748
|
+
repairAttempts: context.repairAttempts,
|
|
749
|
+
}),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const repairedRead = await readScopedCompletionReportDetailed(input.reportPath);
|
|
753
|
+
if (repairedRead.kind !== 'valid') {
|
|
754
|
+
const reason = repairedRead.kind === 'missing'
|
|
755
|
+
? `${MISSING_COMPLETION_REPORT_REASON} Evidence repair did not write CODEX_ORCHESTRATOR_REPORT_FILE.`
|
|
756
|
+
: `Evidence repair wrote an invalid report: ${repairedRead.message}`;
|
|
757
|
+
const blockers = [runnerBlocker(repairedRead.kind === 'missing' ? 'missing-completion-report' : 'invalid-completion-report', reason, 'completion-report', 'none')];
|
|
758
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
759
|
+
return {
|
|
760
|
+
kind: 'blocked',
|
|
761
|
+
result: blocked([reason], {
|
|
762
|
+
changedFiles: context.changedFiles,
|
|
763
|
+
validation: context.validation,
|
|
764
|
+
skippedChecks: context.report.skippedChecks,
|
|
765
|
+
residualRisks: context.residualRisks,
|
|
766
|
+
commits: context.changeSet.commits,
|
|
767
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
768
|
+
blockers,
|
|
769
|
+
repairAttempts: context.repairAttempts,
|
|
770
|
+
}),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (repairedRead.report.status !== context.report.status) {
|
|
774
|
+
const reason = 'Evidence repair changed completion report status; repair may only correct review-gate evidence.';
|
|
775
|
+
const blockers = [runnerBlocker('missing-quality-gate-evidence', reason, 'review-gate', 'none')];
|
|
776
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
777
|
+
return {
|
|
778
|
+
kind: 'blocked',
|
|
779
|
+
result: blocked([reason], {
|
|
780
|
+
changedFiles: context.changedFiles,
|
|
781
|
+
validation: context.validation,
|
|
782
|
+
skippedChecks: context.report.skippedChecks,
|
|
783
|
+
residualRisks: context.residualRisks,
|
|
784
|
+
commits: context.changeSet.commits,
|
|
785
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
786
|
+
blockers,
|
|
787
|
+
repairAttempts: context.repairAttempts,
|
|
788
|
+
}),
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const externalValidation = context.validation.slice(context.report.validation.length);
|
|
792
|
+
const repairedReport = {
|
|
793
|
+
...context.report,
|
|
794
|
+
...repairedRead.report,
|
|
795
|
+
skippedChecks: applicableSkippedChecks(input, repairedRead.report, context.changedFiles),
|
|
796
|
+
artifacts: mergeArtifacts(repairedRead.report.artifacts, context.report.artifacts),
|
|
797
|
+
};
|
|
798
|
+
const repairedValidation = [...repairedReport.validation, ...externalValidation];
|
|
799
|
+
const repairedReviewGate = evaluateReviewGates({
|
|
800
|
+
config: input.config,
|
|
801
|
+
issue: input.issue,
|
|
802
|
+
changedFiles: context.changedFiles,
|
|
803
|
+
validation: repairedValidation,
|
|
804
|
+
skippedChecks: repairedReport.skippedChecks,
|
|
805
|
+
report: repairedReport,
|
|
806
|
+
worktreePath: input.worktreePath,
|
|
807
|
+
});
|
|
808
|
+
if (!repairedReviewGate.ok) {
|
|
809
|
+
const reason = `Evidence repair did not satisfy review gates: ${repairedReviewGate.reasons.join('; ')}`;
|
|
810
|
+
const blockers = repairedReviewGate.blockers ?? repairableBlockers;
|
|
811
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'blocked', reason, blockers });
|
|
812
|
+
return {
|
|
813
|
+
kind: 'blocked',
|
|
814
|
+
result: blocked(repairedReviewGate.reasons, {
|
|
815
|
+
changedFiles: context.changedFiles,
|
|
816
|
+
validation: repairedValidation,
|
|
817
|
+
skippedChecks: repairedReport.skippedChecks,
|
|
818
|
+
residualRisks: context.residualRisks,
|
|
819
|
+
commits: context.changeSet.commits,
|
|
820
|
+
acceptanceProofAttempt: context.acceptanceProofAttempt,
|
|
821
|
+
blockers,
|
|
822
|
+
repairAttempts: context.repairAttempts,
|
|
823
|
+
}),
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
context.repairAttempts.push({ ...repairAttemptBase, status: 'passed', reason: 'Evidence repair wrote review-gate evidence that passed rerun review gates.' });
|
|
827
|
+
return {
|
|
828
|
+
kind: 'publish-ready',
|
|
829
|
+
report: repairedReport,
|
|
830
|
+
validation: repairedValidation,
|
|
831
|
+
reviewWarnings: repairedReviewGate.warnings,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
async function captureRepairProtectedFingerprint(input, changeSet) {
|
|
835
|
+
const reportRelativePath = relativeReportPathInsideWorktree(input);
|
|
836
|
+
const entries = [];
|
|
837
|
+
for (const changedPath of changeSet.changedPaths) {
|
|
838
|
+
if (reportRelativePath && changedPath === reportRelativePath) {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
const absolutePath = join(input.worktreePath, changedPath);
|
|
842
|
+
try {
|
|
843
|
+
const content = await readFile(absolutePath);
|
|
844
|
+
entries.push({
|
|
845
|
+
path: changedPath,
|
|
846
|
+
state: 'file',
|
|
847
|
+
sha256: createHash('sha256').update(content).digest('hex'),
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
852
|
+
entries.push({ path: changedPath, state: 'missing' });
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return JSON.stringify(entries);
|
|
859
|
+
}
|
|
860
|
+
function relativeReportPathInsideWorktree(input) {
|
|
861
|
+
const worktreePath = resolve(input.worktreePath);
|
|
862
|
+
const reportPath = resolve(input.reportPath);
|
|
863
|
+
const relativePath = relative(worktreePath, reportPath);
|
|
864
|
+
if (relativePath === '' || relativePath.startsWith('..') || isAbsolute(relativePath)) {
|
|
865
|
+
return undefined;
|
|
866
|
+
}
|
|
867
|
+
return relativePath.split(/[\\/]+/u).join('/');
|
|
868
|
+
}
|
|
869
|
+
function buildCompletionReportRepairPrompt(input) {
|
|
870
|
+
const issue = input.input.issue;
|
|
871
|
+
const reportIssueText = input.reportIssue.kind === 'missing'
|
|
872
|
+
? 'The completion report is missing.'
|
|
873
|
+
: [
|
|
874
|
+
input.reportIssue.message,
|
|
875
|
+
...input.reportIssue.errors,
|
|
876
|
+
input.reportIssue.rawContent ? `Raw invalid report content (truncated):\n${input.reportIssue.rawContent}` : undefined,
|
|
877
|
+
].filter(Boolean).join('\n');
|
|
878
|
+
return [
|
|
879
|
+
'# Completion Report Repair',
|
|
880
|
+
'',
|
|
881
|
+
'Repair only the completion report JSON at CODEX_ORCHESTRATOR_REPORT_FILE.',
|
|
882
|
+
'Do not edit product files. Do not edit GitHub. Do not create commits.',
|
|
883
|
+
'Use the changed files, issue text, schema errors/raw content, and validation/check evidence to write the corrected scoped completion report.',
|
|
884
|
+
'Final response raw JSON only.',
|
|
885
|
+
'',
|
|
886
|
+
`Issue #${issue.number}: ${issue.title}`,
|
|
887
|
+
issue.body,
|
|
888
|
+
'',
|
|
889
|
+
'Changed files:',
|
|
890
|
+
...input.changedFiles.map((file) => `- ${file}`),
|
|
891
|
+
'',
|
|
892
|
+
'Report problem:',
|
|
893
|
+
reportIssueText,
|
|
894
|
+
'',
|
|
895
|
+
'Original workflow prompt:',
|
|
896
|
+
input.input.reportRepair?.workflowPromptText ?? '',
|
|
897
|
+
].join('\n');
|
|
898
|
+
}
|
|
899
|
+
function buildEvidenceReportRepairPrompt(input) {
|
|
900
|
+
const issue = input.input.issue;
|
|
901
|
+
return [
|
|
902
|
+
'# Evidence Repair',
|
|
903
|
+
'',
|
|
904
|
+
'Repair only missing review-gate evidence in the completion report JSON at CODEX_ORCHESTRATOR_REPORT_FILE.',
|
|
905
|
+
'Do not edit product files. Do not edit GitHub. Do not create commits.',
|
|
906
|
+
'Only correct completion report JSON evidence fields such as validation, skippedChecks, residualRisks, and reviewHandoff.',
|
|
907
|
+
'You may run read-only review commands or review skills if needed, but final publication still depends on the runner rerunning gates.',
|
|
908
|
+
'Final response raw JSON only.',
|
|
909
|
+
'',
|
|
910
|
+
`Issue #${issue.number}: ${issue.title}`,
|
|
911
|
+
issue.body,
|
|
912
|
+
'',
|
|
913
|
+
'Changed files:',
|
|
914
|
+
...input.changedFiles.map((file) => `- ${file}`),
|
|
915
|
+
'',
|
|
916
|
+
'Review gate reasons to repair:',
|
|
917
|
+
...input.reviewGate.reasons.map((reason) => `- ${reason}`),
|
|
918
|
+
'',
|
|
919
|
+
'Typed blocker keys:',
|
|
920
|
+
...(input.reviewGate.blockers ?? []).map((blocker) => `- ${blocker.key}: ${blocker.reason}`),
|
|
921
|
+
'',
|
|
922
|
+
'Current validation evidence:',
|
|
923
|
+
...input.validation.map((line) => `- ${line.command}: ${line.status} - ${line.summary}`),
|
|
924
|
+
'',
|
|
925
|
+
'Original workflow prompt:',
|
|
926
|
+
input.input.reportRepair?.workflowPromptText ?? '',
|
|
927
|
+
].join('\n');
|
|
928
|
+
}
|
|
929
|
+
function runnerBlocker(key, reason, source, repair) {
|
|
930
|
+
return { key, reason, source, repair };
|
|
931
|
+
}
|
|
300
932
|
function applicableSkippedChecks(input, report, changedFiles) {
|
|
301
933
|
if (shouldApplyVisualProofGate({ config: input.config, issue: input.issue, changedFiles })) {
|
|
302
934
|
return report.skippedChecks;
|
|
@@ -337,15 +969,31 @@ function localSessionBlockReasons(phaseResults) {
|
|
|
337
969
|
];
|
|
338
970
|
});
|
|
339
971
|
}
|
|
340
|
-
function blocked(reasons) {
|
|
341
|
-
|
|
972
|
+
function blocked(reasons, options = {}) {
|
|
973
|
+
const result = {
|
|
342
974
|
status: 'blocked',
|
|
343
975
|
reasons,
|
|
344
|
-
changedFiles: [],
|
|
345
|
-
skippedChecks: [],
|
|
346
|
-
residualRisks: [],
|
|
347
|
-
commits: [],
|
|
976
|
+
changedFiles: options.changedFiles ?? [],
|
|
977
|
+
skippedChecks: options.skippedChecks ?? [],
|
|
978
|
+
residualRisks: options.residualRisks ?? [],
|
|
979
|
+
commits: options.commits ?? [],
|
|
348
980
|
};
|
|
981
|
+
if (options.validation) {
|
|
982
|
+
result.validation = options.validation;
|
|
983
|
+
}
|
|
984
|
+
if (options.blockers && options.blockers.length > 0) {
|
|
985
|
+
result.blockers = options.blockers;
|
|
986
|
+
}
|
|
987
|
+
if (options.repairAttempts && options.repairAttempts.length > 0) {
|
|
988
|
+
result.repairAttempts = options.repairAttempts;
|
|
989
|
+
}
|
|
990
|
+
if (options.acceptanceProofAttempt) {
|
|
991
|
+
result.acceptanceProofAttempt = options.acceptanceProofAttempt;
|
|
992
|
+
}
|
|
993
|
+
return result;
|
|
994
|
+
}
|
|
995
|
+
function repairAttemptsField(repairAttempts) {
|
|
996
|
+
return repairAttempts.length > 0 ? { repairAttempts } : {};
|
|
349
997
|
}
|
|
350
998
|
function formatCodexExitReason(result) {
|
|
351
999
|
const figmaReason = formatFigmaMcpFailureReason(result);
|
|
@@ -366,6 +1014,15 @@ function formatCodexExitReason(result) {
|
|
|
366
1014
|
? `Codex exited with code ${result.exitCode}: ${truncate(detail, 600)}`
|
|
367
1015
|
: `Codex exited with code ${result.exitCode}`;
|
|
368
1016
|
}
|
|
1017
|
+
function isIdleTimeoutResult(result) {
|
|
1018
|
+
if (result.exitCode !== 124) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
return `${result.stderr}\n${result.stdout}`
|
|
1022
|
+
.split(/\r?\n/u)
|
|
1023
|
+
.map((line) => line.trim())
|
|
1024
|
+
.some((line) => /^Command idle timed out after [1-9][0-9]*ms\.$/u.test(line));
|
|
1025
|
+
}
|
|
369
1026
|
function formatFigmaMcpFailureReason(result) {
|
|
370
1027
|
if (result.exitCode === 0 || result.figmaMcp?.enabled !== true) {
|
|
371
1028
|
return undefined;
|