codex-orchestrator 0.1.40 → 0.1.42
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 +22 -0
- package/dist/src/cli.js +9 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config/schema.d.ts +6 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +31 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/git/worktree.d.ts +3 -1
- package/dist/src/git/worktree.d.ts.map +1 -1
- package/dist/src/git/worktree.js +51 -17
- package/dist/src/git/worktree.js.map +1 -1
- package/dist/src/github/gh-issue-adapter.d.ts +2 -0
- package/dist/src/github/gh-issue-adapter.d.ts.map +1 -1
- package/dist/src/github/gh-issue-adapter.js +7 -1
- package/dist/src/github/gh-issue-adapter.js.map +1 -1
- package/dist/src/github/issues.d.ts +3 -0
- package/dist/src/github/issues.d.ts.map +1 -1
- package/dist/src/github/issues.js +7 -1
- package/dist/src/github/issues.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/runner/acceptance-proof-loop.d.ts +115 -0
- package/dist/src/runner/acceptance-proof-loop.d.ts.map +1 -0
- package/dist/src/runner/acceptance-proof-loop.js +401 -0
- package/dist/src/runner/acceptance-proof-loop.js.map +1 -0
- package/dist/src/runner/acceptance-proof-runner.d.ts +2 -20
- package/dist/src/runner/acceptance-proof-runner.d.ts.map +1 -1
- package/dist/src/runner/acceptance-proof-runner.js +9 -103
- package/dist/src/runner/acceptance-proof-runner.js.map +1 -1
- package/dist/src/runner/agent-attempt.d.ts +105 -0
- package/dist/src/runner/agent-attempt.d.ts.map +1 -0
- package/dist/src/runner/agent-attempt.js +235 -0
- package/dist/src/runner/agent-attempt.js.map +1 -0
- package/dist/src/runner/auto-visual-proof-command.d.ts.map +1 -1
- package/dist/src/runner/auto-visual-proof-command.js +6 -12
- package/dist/src/runner/auto-visual-proof-command.js.map +1 -1
- package/dist/src/runner/command-utils.d.ts +6 -2
- package/dist/src/runner/command-utils.d.ts.map +1 -1
- package/dist/src/runner/command-utils.js +34 -1
- package/dist/src/runner/command-utils.js.map +1 -1
- package/dist/src/runner/daemon-command.d.ts +3 -1
- package/dist/src/runner/daemon-command.d.ts.map +1 -1
- package/dist/src/runner/daemon-command.js +37 -0
- package/dist/src/runner/daemon-command.js.map +1 -1
- package/dist/src/runner/durable-run-summary.d.ts +19 -0
- package/dist/src/runner/durable-run-summary.d.ts.map +1 -1
- package/dist/src/runner/durable-run-summary.js +107 -2
- package/dist/src/runner/durable-run-summary.js.map +1 -1
- package/dist/src/runner/handoff-evidence.d.ts +1 -0
- package/dist/src/runner/handoff-evidence.d.ts.map +1 -1
- package/dist/src/runner/handoff-evidence.js +2 -2
- package/dist/src/runner/handoff-evidence.js.map +1 -1
- package/dist/src/runner/hygiene-cleanup.d.ts +15 -0
- package/dist/src/runner/hygiene-cleanup.d.ts.map +1 -0
- package/dist/src/runner/hygiene-cleanup.js +25 -0
- package/dist/src/runner/hygiene-cleanup.js.map +1 -0
- package/dist/src/runner/issue-tree.d.ts +1 -1
- package/dist/src/runner/issue-tree.d.ts.map +1 -1
- package/dist/src/runner/issue-tree.js +15 -6
- package/dist/src/runner/issue-tree.js.map +1 -1
- package/dist/src/runner/local-execution-session.d.ts.map +1 -1
- package/dist/src/runner/local-execution-session.js +84 -127
- package/dist/src/runner/local-execution-session.js.map +1 -1
- package/dist/src/runner/plan-auto-command.d.ts +3 -0
- package/dist/src/runner/plan-auto-command.d.ts.map +1 -1
- package/dist/src/runner/plan-auto-command.js +319 -310
- package/dist/src/runner/plan-auto-command.js.map +1 -1
- package/dist/src/runner/plan-auto-recovery.d.ts +83 -0
- package/dist/src/runner/plan-auto-recovery.d.ts.map +1 -0
- package/dist/src/runner/plan-auto-recovery.js +303 -0
- package/dist/src/runner/plan-auto-recovery.js.map +1 -0
- package/dist/src/runner/review-gate-policy.d.ts +7 -1
- package/dist/src/runner/review-gate-policy.d.ts.map +1 -1
- package/dist/src/runner/review-gate-policy.js +11 -123
- package/dist/src/runner/review-gate-policy.js.map +1 -1
- package/dist/src/runner/rework-policy.js +1 -1
- package/dist/src/runner/rework-policy.js.map +1 -1
- package/dist/src/runner/runner-handoff-decision.d.ts.map +1 -1
- package/dist/src/runner/runner-handoff-decision.js +22 -4
- package/dist/src/runner/runner-handoff-decision.js.map +1 -1
- package/dist/src/runner/scoped-auto-command.d.ts.map +1 -1
- package/dist/src/runner/scoped-auto-command.js +104 -221
- package/dist/src/runner/scoped-auto-command.js.map +1 -1
- package/dist/src/runner/scoped-recovery.d.ts +13 -0
- package/dist/src/runner/scoped-recovery.d.ts.map +1 -1
- package/dist/src/runner/scoped-recovery.js +7 -7
- package/dist/src/runner/scoped-recovery.js.map +1 -1
- package/dist/src/runner/terminal-outcome.d.ts +58 -0
- package/dist/src/runner/terminal-outcome.d.ts.map +1 -0
- package/dist/src/runner/terminal-outcome.js +73 -0
- package/dist/src/runner/terminal-outcome.js.map +1 -0
- package/dist/src/runner/visual-proof-runner.d.ts +2 -0
- package/dist/src/runner/visual-proof-runner.d.ts.map +1 -1
- package/dist/src/runner/visual-proof-runner.js +30 -8
- package/dist/src/runner/visual-proof-runner.js.map +1 -1
- package/dist/src/setup/setup-command.js +1 -0
- package/dist/src/setup/setup-command.js.map +1 -1
- package/docs/deep-dive.md +26 -0
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { hostname as osHostname } from 'node:os';
|
|
2
3
|
import { dirname, join, resolve } from 'node:path';
|
|
3
4
|
import { CodexCommandAdapter } from '../codex/command-adapter.js';
|
|
4
5
|
import { resolveBaseBranch } from '../git/base-branch.js';
|
|
5
6
|
import { GitMergeConflictError, GitWorktreeManager, renderBranchTemplate } from '../git/worktree.js';
|
|
6
7
|
import { GhCliIssueAdapter } from '../github/gh-issue-adapter.js';
|
|
7
8
|
import { GhCliPullRequestAdapter } from '../github/gh-pull-request-adapter.js';
|
|
8
|
-
import { verifyPullRequestRefs } from '../github/pull-requests.js';
|
|
9
9
|
import { defaultShellCommandExecutor } from '../process/command.js';
|
|
10
10
|
import { formatSessionTimestamp, readRunnerConfig, runConfiguredChecks, } from './command-utils.js';
|
|
11
11
|
import { buildChildBlockedReport, buildChildReviewReport, buildIssueTreePullRequestBody, buildIssueTreeReviewReport, buildParentBlockedReport, } from './handoff-evidence.js';
|
|
@@ -15,15 +15,16 @@ import { writeContextSnapshot } from './context-snapshot.js';
|
|
|
15
15
|
import { readPlanAutoCompletionReport, } from './completion-report.js';
|
|
16
16
|
import { claimIssue, discoverIssueWork } from './issue-state-machine.js';
|
|
17
17
|
import { collectExecutableChildBatches, persistAutonomousChildNode, readAutonomousChildNodes, topologicalPlanNodes, } from './issue-tree.js';
|
|
18
|
-
import { runImplementationPublishabilityCheck } from './local-execution-session.js';
|
|
19
18
|
import { RunnerLifecycleEventStore } from './lifecycle-events.js';
|
|
20
19
|
import { RunnerStateStore } from './local-state.js';
|
|
20
|
+
import { classifyPlanAutoBlockedChildRecovery, classifyPlanAutoCompletedChildRecovery, classifyPlanAutoParentRecovery, } from './plan-auto-recovery.js';
|
|
21
21
|
import { buildIssueTreeChildPrompt, buildPlanAutoPrompt, sessionPromptPath, sessionReportPath, writeDurablePrompt, } from './prompt.js';
|
|
22
|
-
import {
|
|
22
|
+
import { runAgentAttemptLoop } from './agent-attempt.js';
|
|
23
23
|
import { evaluateParentRiskRoutingGate } from './review-gates.js';
|
|
24
24
|
import { buildBlockedHandoffEvidence, buildPromotionAsBlockedHandoffEvidence, buildReviewReadyHandoffEvidence, } from './runner-handoff-decision.js';
|
|
25
25
|
import { sessionLogPath } from './run-log.js';
|
|
26
26
|
import { cleanupSessionCodexHome, sessionCodexHomePath } from './session-home.js';
|
|
27
|
+
import { finishBlockedTerminalOutcome, finishReviewReadyCommentTerminalOutcome, finishReviewReadyTerminalOutcome, } from './terminal-outcome.js';
|
|
27
28
|
class ChildExecutionBlockedError extends Error {
|
|
28
29
|
durableRunSummary;
|
|
29
30
|
freshContextReview;
|
|
@@ -51,16 +52,36 @@ export async function runPlanAutoCommand(options) {
|
|
|
51
52
|
if (!parentIssue) {
|
|
52
53
|
throw new Error(`Issue #${options.issueNumber} was not found`);
|
|
53
54
|
}
|
|
55
|
+
const branchName = renderBranchTemplate(config.branches.issueTree, { parentIssueNumber: options.issueNumber });
|
|
56
|
+
const worktreePath = join(targetRoot, config.runner.workspaceRoot, `tree-${options.issueNumber}`);
|
|
57
|
+
const store = new RunnerStateStore(targetRoot, config);
|
|
58
|
+
const parentRecovery = await classifyPlanAutoParentRecovery({
|
|
59
|
+
targetRoot,
|
|
60
|
+
config,
|
|
61
|
+
parentIssue,
|
|
62
|
+
branchName,
|
|
63
|
+
worktreePath,
|
|
64
|
+
baseSha: resolvedBase.sha,
|
|
65
|
+
state: await store.load(),
|
|
66
|
+
git,
|
|
67
|
+
now,
|
|
68
|
+
hostname: options.hostname,
|
|
69
|
+
processProbe: options.processProbe,
|
|
70
|
+
});
|
|
54
71
|
const decision = discoverIssueWork([parentIssue], config)[0];
|
|
55
|
-
|
|
72
|
+
const parentLabels = new Set(parentIssue.labels.map((label) => label.name));
|
|
73
|
+
const alreadyRunningPlanParent = decision?.kind === 'skipped'
|
|
74
|
+
&& decision.reasonCode === 'already-running'
|
|
75
|
+
&& parentIssue.state === 'OPEN'
|
|
76
|
+
&& parentLabels.has(config.github.labels.planAuto.name)
|
|
77
|
+
&& !parentLabels.has(config.github.labels.child.name);
|
|
78
|
+
if (!decision || !((decision.kind === 'eligible' && decision.mode === 'plan-parent') || alreadyRunningPlanParent)) {
|
|
56
79
|
const reason = decision?.kind === 'skipped' ? decision.reason : 'not agent:plan-auto';
|
|
57
80
|
throw new Error(`Issue #${options.issueNumber} is not eligible for agent:plan-auto planning: ${reason}`);
|
|
58
81
|
}
|
|
59
82
|
const workflowPrompts = await readPlanWorkflowPrompts(targetRoot, config);
|
|
60
83
|
const issueTreeWorkflowPrompt = await readIssueTreeWorkflowPrompt(targetRoot, config);
|
|
61
84
|
const acceptanceProofWorkflowPrompt = await readAcceptanceProofWorkflowPrompt(targetRoot, config);
|
|
62
|
-
const branchName = renderBranchTemplate(config.branches.issueTree, { parentIssueNumber: options.issueNumber });
|
|
63
|
-
const worktreePath = join(targetRoot, config.runner.workspaceRoot, `tree-${options.issueNumber}`);
|
|
64
85
|
let promptPath = '';
|
|
65
86
|
let reportPath = '';
|
|
66
87
|
let logPath = '';
|
|
@@ -68,19 +89,38 @@ export async function runPlanAutoCommand(options) {
|
|
|
68
89
|
let parentSnapshotPath = '';
|
|
69
90
|
const childIssues = [];
|
|
70
91
|
let pullRequest;
|
|
71
|
-
let store;
|
|
72
92
|
const executionResults = [];
|
|
73
93
|
const events = new RunnerLifecycleEventStore(targetRoot, config);
|
|
74
94
|
const buildBase = () => baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues, sessionId, parentSnapshotPath);
|
|
75
|
-
|
|
95
|
+
if (parentRecovery.kind === 'hard-block') {
|
|
96
|
+
return finishPlanBlocked(issueAdapter, config, events, buildBase(), [parentRecovery.reason], childIssues, parentRecovery.marker);
|
|
97
|
+
}
|
|
98
|
+
if (alreadyRunningPlanParent && parentRecovery.kind !== 'resume-parent') {
|
|
99
|
+
return finishPlanBlocked(issueAdapter, config, events, buildBase(), ['parent recovery requires runner-owned metadata for running parent'], childIssues, `plan-auto-recovery-blocked parent=${options.issueNumber} reason=missing-runner-owned-metadata`);
|
|
100
|
+
}
|
|
101
|
+
if (!alreadyRunningPlanParent) {
|
|
102
|
+
await claimIssue(issueAdapter, config, options.issueNumber, 'plan-parent', now);
|
|
103
|
+
}
|
|
76
104
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
if (parentRecovery.kind === 'resume-parent') {
|
|
106
|
+
await git.ensureIssueWorktree({
|
|
107
|
+
targetRoot,
|
|
108
|
+
workspacePath: worktreePath,
|
|
109
|
+
branchName,
|
|
110
|
+
baseBranch: resolvedBase.sha,
|
|
111
|
+
requiredBaseSha: resolvedBase.sha,
|
|
112
|
+
allowResume: true,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
await git.createIssueWorktree({
|
|
117
|
+
targetRoot,
|
|
118
|
+
workspacePath: worktreePath,
|
|
119
|
+
branchName,
|
|
120
|
+
baseBranch: resolvedBase.sha,
|
|
121
|
+
requiredBaseSha: resolvedBase.sha,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
84
124
|
sessionId = `plan-${options.issueNumber}-${formatSessionTimestamp(now)}`;
|
|
85
125
|
promptPath = sessionPromptPath({ targetRoot, config, issueNumber: options.issueNumber, sessionId });
|
|
86
126
|
reportPath = sessionReportPath({ targetRoot, config, issueNumber: options.issueNumber, sessionId });
|
|
@@ -132,7 +172,6 @@ export async function runPlanAutoCommand(options) {
|
|
|
132
172
|
summary: 'Starting parent planning Codex session.',
|
|
133
173
|
artifacts: sessionArtifacts(promptPath, reportPath, logPath, snapshot.path),
|
|
134
174
|
});
|
|
135
|
-
store = new RunnerStateStore(targetRoot, config);
|
|
136
175
|
await store.upsertRun({
|
|
137
176
|
issueNumber: options.issueNumber,
|
|
138
177
|
mode: 'plan-parent',
|
|
@@ -145,6 +184,10 @@ export async function runPlanAutoCommand(options) {
|
|
|
145
184
|
retryCount: 0,
|
|
146
185
|
createdAt: now.toISOString(),
|
|
147
186
|
updatedAt: now.toISOString(),
|
|
187
|
+
ownerPid: process.pid,
|
|
188
|
+
host: (options.hostname ?? osHostname)(),
|
|
189
|
+
leaseUpdatedAt: now.toISOString(),
|
|
190
|
+
baseSha: resolvedBase.sha,
|
|
148
191
|
});
|
|
149
192
|
const beforeHead = await git.getHead(worktreePath);
|
|
150
193
|
let codexResult;
|
|
@@ -198,7 +241,60 @@ export async function runPlanAutoCommand(options) {
|
|
|
198
241
|
childIssues.push(persisted);
|
|
199
242
|
}
|
|
200
243
|
const childNodes = await readAutonomousChildNodes(issueAdapter, config, options.issueNumber, childIssues);
|
|
201
|
-
const
|
|
244
|
+
const childRecoveryState = await store.load();
|
|
245
|
+
const childRecoveryDecisions = await Promise.all(childNodes.map(async (child) => {
|
|
246
|
+
const completed = await classifyPlanAutoCompletedChildRecovery({
|
|
247
|
+
targetRoot,
|
|
248
|
+
config,
|
|
249
|
+
parentIssueNumber: options.issueNumber,
|
|
250
|
+
parentBranchName: branchName,
|
|
251
|
+
child,
|
|
252
|
+
state: childRecoveryState,
|
|
253
|
+
git,
|
|
254
|
+
});
|
|
255
|
+
return completed.kind === 'execute-child'
|
|
256
|
+
? classifyPlanAutoBlockedChildRecovery({
|
|
257
|
+
targetRoot,
|
|
258
|
+
config,
|
|
259
|
+
parentIssueNumber: options.issueNumber,
|
|
260
|
+
child,
|
|
261
|
+
state: childRecoveryState,
|
|
262
|
+
git,
|
|
263
|
+
})
|
|
264
|
+
: completed;
|
|
265
|
+
}));
|
|
266
|
+
const recoveredStableIds = [];
|
|
267
|
+
const resumableBlockedStableIds = [];
|
|
268
|
+
const childRecoveryByStableId = new Map();
|
|
269
|
+
for (const decision of childRecoveryDecisions) {
|
|
270
|
+
if (decision.kind === 'hard-block') {
|
|
271
|
+
return finishPlanBlocked(issueAdapter, config, events, buildBase(), [decision.reason], childIssues, decision.marker);
|
|
272
|
+
}
|
|
273
|
+
if (decision.kind === 'recovered-completed-child') {
|
|
274
|
+
recoveredStableIds.push(decision.child.metadata.stableId);
|
|
275
|
+
executionResults.push({
|
|
276
|
+
child: decision.child,
|
|
277
|
+
branchName: decision.branchName,
|
|
278
|
+
worktreePath: decision.worktreePath,
|
|
279
|
+
promptPath: decision.promptPath,
|
|
280
|
+
reportPath: decision.reportPath,
|
|
281
|
+
logPath: decision.logPath,
|
|
282
|
+
changedFiles: decision.changedFiles,
|
|
283
|
+
validation: decision.validation,
|
|
284
|
+
artifacts: [],
|
|
285
|
+
commits: [],
|
|
286
|
+
skippedChecks: decision.skippedChecks,
|
|
287
|
+
residualRisks: decision.residualRisks,
|
|
288
|
+
durableRunSummary: decision.durableRunSummary,
|
|
289
|
+
recovered: true,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else if (decision.kind === 'resume-child-rework') {
|
|
293
|
+
resumableBlockedStableIds.push(decision.child.metadata.stableId);
|
|
294
|
+
childRecoveryByStableId.set(decision.child.metadata.stableId, decision);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const batches = collectExecutableChildBatches(childNodes, config, recoveredStableIds, resumableBlockedStableIds);
|
|
202
298
|
if (!batches.ok) {
|
|
203
299
|
return finishPlanBlocked(issueAdapter, config, events, base, batches.errors, childIssues);
|
|
204
300
|
}
|
|
@@ -218,6 +314,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
218
314
|
acceptanceProofWorkflowPrompt,
|
|
219
315
|
now,
|
|
220
316
|
events,
|
|
317
|
+
recovery: childRecoveryByStableId.get(child.metadata.stableId),
|
|
221
318
|
})));
|
|
222
319
|
const failures = [];
|
|
223
320
|
for (const [index, result] of settled.entries()) {
|
|
@@ -268,7 +365,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
268
365
|
executionResults.push(childResult);
|
|
269
366
|
}
|
|
270
367
|
}
|
|
271
|
-
const finalValidation = await runConfiguredChecks(config, worktreePath, shellExecutor, []);
|
|
368
|
+
const finalValidation = await runConfiguredChecks(config, worktreePath, shellExecutor, [], { phase: 'parent-integration' });
|
|
272
369
|
const failedFinalValidation = finalValidation.filter((line) => line.status === 'failed');
|
|
273
370
|
if (failedFinalValidation.length > 0) {
|
|
274
371
|
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, 'Parent integration configured checks failed before publication.', worktreePath);
|
|
@@ -277,37 +374,44 @@ export async function runPlanAutoCommand(options) {
|
|
|
277
374
|
...failedFinalValidation.map((line) => `${line.command}: ${line.status} - ${line.summary}`),
|
|
278
375
|
], childIssues);
|
|
279
376
|
}
|
|
280
|
-
await
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
377
|
+
const handoff = await finishReviewReadyTerminalOutcome({
|
|
378
|
+
issueNumber: options.issueNumber,
|
|
379
|
+
config,
|
|
380
|
+
branchName,
|
|
381
|
+
baseBranch: resolvedBase.prBaseBranch,
|
|
382
|
+
worktreePath,
|
|
383
|
+
git,
|
|
384
|
+
pullRequestAdapter,
|
|
385
|
+
issueAdapter,
|
|
386
|
+
pullRequest: {
|
|
387
|
+
title: renderParentTemplate(config.pullRequests.issueTreeTitle, options.issueNumber),
|
|
388
|
+
body: buildIssueTreePullRequestBody({
|
|
389
|
+
parentIssueNumber: options.issueNumber,
|
|
390
|
+
childIssues,
|
|
391
|
+
childResults: executionResults,
|
|
392
|
+
finalValidation,
|
|
393
|
+
sizeRisk: report.sizeRisk,
|
|
394
|
+
parentReviewHandoff: report.parentReviewHandoff,
|
|
395
|
+
riskRoutingWarnings,
|
|
396
|
+
}),
|
|
397
|
+
},
|
|
398
|
+
findExistingPullRequest: false,
|
|
399
|
+
reportComment: (verifiedPullRequest) => buildIssueTreeReviewReport({
|
|
284
400
|
parentIssueNumber: options.issueNumber,
|
|
285
|
-
|
|
401
|
+
pullRequest: verifiedPullRequest,
|
|
402
|
+
batches: batches.batches,
|
|
286
403
|
childResults: executionResults,
|
|
287
404
|
finalValidation,
|
|
288
405
|
sizeRisk: report.sizeRisk,
|
|
289
406
|
parentReviewHandoff: report.parentReviewHandoff,
|
|
290
407
|
riskRoutingWarnings,
|
|
291
408
|
}),
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
parentIssueNumber: options.issueNumber,
|
|
298
|
-
pullRequest,
|
|
299
|
-
batches: batches.batches,
|
|
300
|
-
childResults: executionResults,
|
|
301
|
-
finalValidation,
|
|
302
|
-
sizeRisk: report.sizeRisk,
|
|
303
|
-
parentReviewHandoff: report.parentReviewHandoff,
|
|
304
|
-
riskRoutingWarnings,
|
|
409
|
+
onPullRequestReady: (verifiedPullRequest) => {
|
|
410
|
+
pullRequest = verifiedPullRequest;
|
|
411
|
+
},
|
|
412
|
+
beforeIssueMutation: () => markCompletedChildrenReviewReady(issueAdapter, config, store, options.issueNumber, executionResults),
|
|
413
|
+
afterComment: () => store.removeRun(options.issueNumber),
|
|
305
414
|
});
|
|
306
|
-
await markCompletedChildrenReviewReady(issueAdapter, config, store, options.issueNumber, executionResults);
|
|
307
|
-
await issueAdapter.removeLabels(options.issueNumber, [config.github.labels.running.name]);
|
|
308
|
-
await issueAdapter.addLabels(options.issueNumber, [config.github.labels.review.name]);
|
|
309
|
-
await issueAdapter.postComment(options.issueNumber, reportComment);
|
|
310
|
-
await store.removeRun(options.issueNumber);
|
|
311
415
|
await safeAppendEvent(events, {
|
|
312
416
|
issueNumber: options.issueNumber,
|
|
313
417
|
mode: 'plan-parent',
|
|
@@ -317,14 +421,14 @@ export async function runPlanAutoCommand(options) {
|
|
|
317
421
|
summary: 'Issue-tree execution completed draft PR handoff.',
|
|
318
422
|
artifacts: [
|
|
319
423
|
...sessionArtifacts(promptPath, reportPath, logPath, parentSnapshotPath),
|
|
320
|
-
{ kind: 'pr', url: pullRequest.url, description: 'Draft pull request' },
|
|
424
|
+
{ kind: 'pr', url: handoff.pullRequest.url, description: 'Draft pull request' },
|
|
321
425
|
],
|
|
322
426
|
});
|
|
323
427
|
return {
|
|
324
428
|
...buildBase(),
|
|
325
429
|
status: 'review-ready',
|
|
326
|
-
pullRequest,
|
|
327
|
-
reportComment,
|
|
430
|
+
pullRequest: handoff.pullRequest,
|
|
431
|
+
reportComment: handoff.reportComment,
|
|
328
432
|
riskRoutingWarnings,
|
|
329
433
|
};
|
|
330
434
|
}
|
|
@@ -333,7 +437,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
333
437
|
if (pullRequest) {
|
|
334
438
|
throw new Error(`Issue-tree execution failed after draft PR creation (${pullRequest.url}); not marking parent blocked: ${message}`);
|
|
335
439
|
}
|
|
336
|
-
if (
|
|
440
|
+
if (executionResults.length > 0) {
|
|
337
441
|
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, `Parent publication failed before review handoff: ${message}`, worktreePath);
|
|
338
442
|
}
|
|
339
443
|
return finishPlanBlocked(issueAdapter, config, events, buildBase(), [message], childIssues);
|
|
@@ -381,217 +485,95 @@ async function executeChild(input) {
|
|
|
381
485
|
const childIssueNumber = input.child.issue.number;
|
|
382
486
|
const branchName = `codex/tree-${input.parentIssue.number}-issue-${childIssueNumber}`;
|
|
383
487
|
const worktreePath = join(input.targetRoot, input.config.runner.workspaceRoot, `tree-${input.parentIssue.number}-issue-${childIssueNumber}`);
|
|
384
|
-
const store = new RunnerStateStore(input.targetRoot, input.config);
|
|
385
488
|
let sessionId = '';
|
|
386
489
|
let promptPath = '';
|
|
387
490
|
let reportPath = '';
|
|
388
491
|
let logPath = '';
|
|
389
492
|
let snapshotPath = '';
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
workspacePath: worktreePath,
|
|
393
|
-
branchName,
|
|
394
|
-
baseBranch: input.parentBranchName,
|
|
395
|
-
});
|
|
396
|
-
await input.issueAdapter.addLabels(childIssueNumber, [input.config.github.labels.running.name]);
|
|
397
|
-
await input.issueAdapter.postComment(childIssueNumber, `codex-orchestrator: claimed #${childIssueNumber} for tree-child autonomous work under #${input.parentIssue.number} at ${input.now.toISOString()}.`);
|
|
398
|
-
let publishability;
|
|
399
|
-
let rework;
|
|
400
|
-
const reworkAttempts = [];
|
|
401
|
-
const maxReworkAttempts = Math.max(input.config.loopPolicy.rework.maxAttempts, input.config.reviewGates.acceptanceProof.maxIterations - 1);
|
|
402
|
-
for (let attempt = 0; attempt <= maxReworkAttempts; attempt++) {
|
|
403
|
-
const attemptNow = new Date(input.now.getTime() + attempt);
|
|
404
|
-
sessionId = `tree-${input.parentIssue.number}-issue-${childIssueNumber}-${formatSessionTimestamp(attemptNow)}${attempt === 0 ? '' : `-attempt-${attempt}`}`;
|
|
405
|
-
promptPath = sessionPromptPath({
|
|
406
|
-
targetRoot: input.targetRoot,
|
|
407
|
-
config: input.config,
|
|
408
|
-
issueNumber: childIssueNumber,
|
|
409
|
-
sessionId,
|
|
410
|
-
});
|
|
411
|
-
reportPath = sessionReportPath({
|
|
412
|
-
targetRoot: input.targetRoot,
|
|
413
|
-
config: input.config,
|
|
414
|
-
issueNumber: childIssueNumber,
|
|
415
|
-
sessionId,
|
|
416
|
-
});
|
|
417
|
-
logPath = sessionLogPath({
|
|
418
|
-
targetRoot: input.targetRoot,
|
|
419
|
-
config: input.config,
|
|
420
|
-
issueNumber: childIssueNumber,
|
|
421
|
-
sessionId,
|
|
422
|
-
});
|
|
423
|
-
const isolatedHomePath = sessionCodexHomePath({ targetRoot: input.targetRoot, sessionId });
|
|
424
|
-
await mkdir(dirname(reportPath), { recursive: true });
|
|
425
|
-
await mkdir(isolatedHomePath, { recursive: true });
|
|
426
|
-
const promptText = buildIssueTreeChildPrompt({
|
|
427
|
-
parentIssue: input.parentIssue,
|
|
428
|
-
childIssue: input.child.issue,
|
|
429
|
-
config: input.config,
|
|
430
|
-
workflowPromptText: input.issueTreeWorkflowPrompt,
|
|
431
|
-
childMetadata: input.child.metadata,
|
|
432
|
-
dependencyIssues: input.dependencyIssues,
|
|
433
|
-
promptPath,
|
|
434
|
-
reportPath,
|
|
435
|
-
branchName,
|
|
436
|
-
worktreePath,
|
|
437
|
-
});
|
|
438
|
-
const attemptPromptText = rework
|
|
439
|
-
? `${promptText}\n\n## Rework Request\nThis is an automatic rework attempt (#${rework.attempt}). Continue from the current worktree state; do not start over.\nThe previous attempt was blocked for these reasons:\n${rework.blockedReasons.map((reason) => `- ${reason}`).join('\n')}\n${rework.disableOptionalFigmaMcp ? 'Optional Figma MCP failed in the previous attempt. Continue without optional Figma MCP access unless the issue explicitly requires Figma.\n' : ''}Address the blockers, then produce a fresh completion report JSON for the runner.`
|
|
440
|
-
: promptText;
|
|
441
|
-
await writeDurablePrompt({
|
|
442
|
-
targetRoot: input.targetRoot,
|
|
443
|
-
config: input.config,
|
|
444
|
-
issueNumber: childIssueNumber,
|
|
445
|
-
sessionId,
|
|
446
|
-
promptText: attemptPromptText,
|
|
447
|
-
});
|
|
448
|
-
const snapshot = await writeContextSnapshot({
|
|
493
|
+
if (input.recovery) {
|
|
494
|
+
await input.git.ensureIssueWorktree({
|
|
449
495
|
targetRoot: input.targetRoot,
|
|
450
|
-
|
|
451
|
-
issue: input.child.issue,
|
|
452
|
-
mode: 'tree-child',
|
|
453
|
-
phase: 'tree-child',
|
|
454
|
-
decision: rework
|
|
455
|
-
? `tree-child rework attempt #${rework.attempt} under parent #${input.parentIssue.number}`
|
|
456
|
-
: `child issue execution under parent #${input.parentIssue.number}`,
|
|
457
|
-
sessionId,
|
|
458
|
-
worktreePath,
|
|
459
|
-
promptPath,
|
|
460
|
-
reportPath,
|
|
461
|
-
logPath,
|
|
496
|
+
workspacePath: worktreePath,
|
|
462
497
|
branchName,
|
|
463
498
|
baseBranch: input.parentBranchName,
|
|
464
|
-
|
|
465
|
-
blockedBy: [],
|
|
466
|
-
createdAt: attemptNow,
|
|
499
|
+
allowResume: true,
|
|
467
500
|
});
|
|
468
|
-
|
|
469
|
-
await
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
501
|
+
await input.issueAdapter.removeLabels(childIssueNumber, [input.config.github.labels.blocked.name]);
|
|
502
|
+
await input.issueAdapter.addLabels(childIssueNumber, [input.config.github.labels.running.name]);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
await input.git.createIssueWorktree({
|
|
506
|
+
targetRoot: input.targetRoot,
|
|
473
507
|
workspacePath: worktreePath,
|
|
474
|
-
sessionId,
|
|
475
508
|
branchName,
|
|
476
|
-
|
|
477
|
-
reportPath,
|
|
478
|
-
logPath,
|
|
479
|
-
retryCount: attempt,
|
|
480
|
-
createdAt: input.now.toISOString(),
|
|
481
|
-
updatedAt: attemptNow.toISOString(),
|
|
482
|
-
attemptStartedAt: attemptNow.toISOString(),
|
|
483
|
-
snapshotPath,
|
|
484
|
-
});
|
|
485
|
-
await safeAppendEvent(input.events, {
|
|
486
|
-
timestamp: attemptNow,
|
|
487
|
-
issueNumber: childIssueNumber,
|
|
488
|
-
parentIssueNumber: input.parentIssue.number,
|
|
489
|
-
mode: 'tree-child',
|
|
490
|
-
sessionId,
|
|
491
|
-
phase: 'tree-child',
|
|
492
|
-
status: 'started',
|
|
493
|
-
summary: rework ? `Starting tree-child rework attempt #${rework.attempt}.` : 'Starting tree-child Codex session.',
|
|
494
|
-
artifacts: sessionArtifacts(promptPath, reportPath, logPath, snapshotPath),
|
|
509
|
+
baseBranch: input.parentBranchName,
|
|
495
510
|
});
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
511
|
+
await input.issueAdapter.addLabels(childIssueNumber, [input.config.github.labels.running.name]);
|
|
512
|
+
await input.issueAdapter.postComment(childIssueNumber, `codex-orchestrator: claimed #${childIssueNumber} for tree-child autonomous work under #${input.parentIssue.number} at ${input.now.toISOString()}.`);
|
|
513
|
+
}
|
|
514
|
+
const attemptLoop = await runAgentAttemptLoop({
|
|
515
|
+
targetRoot: input.targetRoot,
|
|
516
|
+
config: input.config,
|
|
517
|
+
issue: input.child.issue,
|
|
518
|
+
issueNumber: childIssueNumber,
|
|
519
|
+
parentIssueNumber: input.parentIssue.number,
|
|
520
|
+
mode: 'tree-child',
|
|
521
|
+
phase: 'tree-child',
|
|
522
|
+
branchName,
|
|
523
|
+
worktreePath,
|
|
524
|
+
baseBranch: input.parentBranchName,
|
|
525
|
+
createdAt: input.now,
|
|
526
|
+
firstAttempt: input.recovery ? input.recovery.rework.attempt : 0,
|
|
527
|
+
initialRework: input.recovery?.rework,
|
|
528
|
+
buildSessionId: ({ attempt, attemptNow }) => `tree-${input.parentIssue.number}-issue-${childIssueNumber}-${formatSessionTimestamp(attemptNow)}${attempt === 0 ? '' : `-attempt-${attempt}`}`,
|
|
529
|
+
buildPrompt: ({ promptPath: attemptPromptPath, reportPath: attemptReportPath, rework }) => {
|
|
530
|
+
const promptText = buildIssueTreeChildPrompt({
|
|
531
|
+
parentIssue: input.parentIssue,
|
|
532
|
+
childIssue: input.child.issue,
|
|
501
533
|
config: input.config,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
issueNumber: childIssueNumber,
|
|
508
|
-
sessionId,
|
|
534
|
+
workflowPromptText: input.issueTreeWorkflowPrompt,
|
|
535
|
+
childMetadata: input.child.metadata,
|
|
536
|
+
dependencyIssues: input.dependencyIssues,
|
|
537
|
+
promptPath: attemptPromptPath,
|
|
538
|
+
reportPath: attemptReportPath,
|
|
509
539
|
branchName,
|
|
510
|
-
|
|
511
|
-
logPath,
|
|
512
|
-
disableOptionalFigmaMcp: rework?.disableOptionalFigmaMcp,
|
|
540
|
+
worktreePath,
|
|
513
541
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
542
|
+
return rework
|
|
543
|
+
? `${promptText}\n\n## Rework Request\nThis is an automatic rework attempt (#${rework.attempt}). Continue from the current worktree state; do not start over.\nThe previous attempt was blocked for these reasons:\n${rework.blockedReasons.map((reason) => `- ${reason}`).join('\n')}\n${rework.disableOptionalFigmaMcp ? 'Optional Figma MCP failed in the previous attempt. Continue without optional Figma MCP access unless the issue explicitly requires Figma.\n' : ''}Address the blockers, then produce a fresh completion report JSON for the runner.`
|
|
544
|
+
: promptText;
|
|
545
|
+
},
|
|
546
|
+
buildSnapshotDecision: ({ rework }) => rework
|
|
547
|
+
? `tree-child rework attempt #${rework.attempt} under parent #${input.parentIssue.number}`
|
|
548
|
+
: `child issue execution under parent #${input.parentIssue.number}`,
|
|
549
|
+
startedSummary: ({ rework }) => rework ? `Starting tree-child rework attempt #${rework.attempt}.` : 'Starting tree-child Codex session.',
|
|
550
|
+
reworkScheduledSummary: ({ nextAttempt }) => `Runner scheduled tree-child rework attempt #${nextAttempt}.`,
|
|
551
|
+
missingPublishabilityMessage: 'Runner internal error: missing child publishability result',
|
|
552
|
+
codexAdapter: input.codexAdapter,
|
|
553
|
+
git: input.git,
|
|
554
|
+
shellExecutor: input.shellExecutor,
|
|
555
|
+
commitMessage: `Codex: implement issue #${childIssueNumber} for parent #${input.parentIssue.number}`,
|
|
556
|
+
events: input.events,
|
|
557
|
+
acceptanceProof: {
|
|
522
558
|
targetRoot: input.targetRoot,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
sessionId,
|
|
542
|
-
event,
|
|
543
|
-
}),
|
|
544
|
-
},
|
|
545
|
-
});
|
|
546
|
-
if (publishability.status === 'blocked') {
|
|
547
|
-
const reworkDecision = decideImplementationRework({
|
|
548
|
-
reasons: publishability.reasons,
|
|
549
|
-
config: input.config,
|
|
550
|
-
attempt,
|
|
551
|
-
});
|
|
552
|
-
reworkAttempts.push({
|
|
553
|
-
attempt,
|
|
554
|
-
maxAttempts: 'maxAttempts' in reworkDecision ? reworkDecision.maxAttempts : undefined,
|
|
555
|
-
decisionKind: reworkDecision.kind,
|
|
556
|
-
reasons: publishability.reasons,
|
|
557
|
-
promptPath,
|
|
558
|
-
reportPath,
|
|
559
|
-
logPath,
|
|
560
|
-
snapshotPath,
|
|
561
|
-
});
|
|
562
|
-
if (reworkDecision.kind === 'retry') {
|
|
563
|
-
rework = reworkDecision.rework;
|
|
564
|
-
await safeAppendEvent(input.events, {
|
|
565
|
-
timestamp: attemptNow,
|
|
566
|
-
issueNumber: childIssueNumber,
|
|
567
|
-
parentIssueNumber: input.parentIssue.number,
|
|
568
|
-
mode: 'tree-child',
|
|
569
|
-
sessionId,
|
|
570
|
-
phase: 'tree-child',
|
|
571
|
-
status: 'needs-rework',
|
|
572
|
-
summary: `Runner scheduled tree-child rework attempt #${reworkDecision.nextAttempt}.`,
|
|
573
|
-
artifacts: sessionArtifacts(promptPath, reportPath, logPath, snapshotPath),
|
|
574
|
-
});
|
|
575
|
-
await mkdir(isolatedHomePath, { recursive: true });
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
await safeAppendEvent(input.events, {
|
|
579
|
-
timestamp: attemptNow,
|
|
580
|
-
issueNumber: childIssueNumber,
|
|
581
|
-
parentIssueNumber: input.parentIssue.number,
|
|
582
|
-
mode: 'tree-child',
|
|
583
|
-
sessionId,
|
|
584
|
-
phase: 'tree-child',
|
|
585
|
-
status: 'blocked',
|
|
586
|
-
summary: `Runner rework decision: ${reworkDecision.kind}.`,
|
|
587
|
-
artifacts: sessionArtifacts(promptPath, reportPath, logPath, snapshotPath),
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
if (!publishability) {
|
|
593
|
-
throw new Error('Runner internal error: missing child publishability result');
|
|
594
|
-
}
|
|
559
|
+
workflowPromptText: input.acceptanceProofWorkflowPrompt,
|
|
560
|
+
codexAdapter: input.codexAdapter,
|
|
561
|
+
},
|
|
562
|
+
onAcceptanceProofAttemptEvent: ({ sessionId: attemptSessionId, event }) => appendAcceptanceProofEvent({
|
|
563
|
+
events: input.events,
|
|
564
|
+
issueNumber: childIssueNumber,
|
|
565
|
+
parentIssueNumber: input.parentIssue.number,
|
|
566
|
+
sessionId: attemptSessionId,
|
|
567
|
+
event,
|
|
568
|
+
}),
|
|
569
|
+
});
|
|
570
|
+
const publishability = attemptLoop.publishability;
|
|
571
|
+
sessionId = attemptLoop.sessionId;
|
|
572
|
+
promptPath = attemptLoop.promptPath;
|
|
573
|
+
reportPath = attemptLoop.reportPath;
|
|
574
|
+
logPath = attemptLoop.logPath;
|
|
575
|
+
snapshotPath = attemptLoop.snapshotPath;
|
|
576
|
+
const reworkAttempts = attemptLoop.reworkAttempts;
|
|
595
577
|
if (publishability.status === 'promotion-requested') {
|
|
596
578
|
const promotion = publishability.report.promotion;
|
|
597
579
|
const evidence = buildPromotionAsBlockedHandoffEvidence({
|
|
@@ -730,31 +712,37 @@ async function blockFailedBatch(issueAdapter, config, parentIssueNumber, failure
|
|
|
730
712
|
if (!failure.child) {
|
|
731
713
|
continue;
|
|
732
714
|
}
|
|
733
|
-
await
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
715
|
+
await finishBlockedTerminalOutcome({
|
|
716
|
+
issueNumber: failure.child.issue.number,
|
|
717
|
+
config,
|
|
718
|
+
issueAdapter,
|
|
719
|
+
reportComment: buildChildBlockedReport({
|
|
720
|
+
parentIssueNumber,
|
|
721
|
+
childIssueNumber: failure.child.issue.number,
|
|
722
|
+
reasons: [failure.message],
|
|
723
|
+
details: ['Worktree preserved for maintainer inspection.'],
|
|
724
|
+
freshContextReview: failure.freshContextReview,
|
|
725
|
+
durableRunSummary: failure.durableRunSummary,
|
|
726
|
+
reworkAttempts: failure.reworkAttempts,
|
|
727
|
+
acceptanceProof: failure.acceptanceProof,
|
|
728
|
+
}),
|
|
729
|
+
});
|
|
745
730
|
}
|
|
746
731
|
for (const result of successfulUnmerged) {
|
|
747
|
-
await
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
732
|
+
await finishBlockedTerminalOutcome({
|
|
733
|
+
issueNumber: result.child.issue.number,
|
|
734
|
+
config,
|
|
735
|
+
issueAdapter,
|
|
736
|
+
reportComment: buildChildBlockedReport({
|
|
737
|
+
parentIssueNumber,
|
|
738
|
+
childIssueNumber: result.child.issue.number,
|
|
739
|
+
reasons: ['A sibling child failed before the batch merge; this child branch was not merged.'],
|
|
740
|
+
branchName: result.branchName,
|
|
741
|
+
worktreePath: result.worktreePath,
|
|
742
|
+
durableRunSummary: result.durableRunSummary,
|
|
743
|
+
acceptanceProof: result.acceptanceProof,
|
|
744
|
+
}),
|
|
745
|
+
});
|
|
758
746
|
}
|
|
759
747
|
}
|
|
760
748
|
async function handleMergeConflict(git, issueAdapter, config, parentIssueNumber, error, childResult, batchResults) {
|
|
@@ -766,68 +754,89 @@ async function handleMergeConflict(git, issueAdapter, config, parentIssueNumber,
|
|
|
766
754
|
abortMessage = abortError instanceof Error ? abortError.message : 'merge abort failed';
|
|
767
755
|
}
|
|
768
756
|
for (const result of batchResults) {
|
|
769
|
-
await
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
result.child.issue.number
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
757
|
+
await finishBlockedTerminalOutcome({
|
|
758
|
+
issueNumber: result.child.issue.number,
|
|
759
|
+
config,
|
|
760
|
+
issueAdapter,
|
|
761
|
+
reportComment: buildChildBlockedReport({
|
|
762
|
+
parentIssueNumber,
|
|
763
|
+
childIssueNumber: result.child.issue.number,
|
|
764
|
+
reasons: [
|
|
765
|
+
result.child.issue.number === childResult.child.issue.number
|
|
766
|
+
? `Merge conflict from branch ${childResult.branchName}`
|
|
767
|
+
: `A sibling merge conflict stopped the batch before publication: ${childResult.branchName}`,
|
|
768
|
+
],
|
|
769
|
+
details: [
|
|
770
|
+
`- Parent worktree: ${error.worktreePath}`,
|
|
771
|
+
`- Child worktree: ${result.worktreePath}`,
|
|
772
|
+
`- Child branch preserved: ${result.branchName}`,
|
|
773
|
+
abortMessage ? `- Merge abort also failed: ${abortMessage}` : '- Merge abort completed.',
|
|
774
|
+
],
|
|
775
|
+
batchChildren: batchResults.map((batchResult) => ({
|
|
776
|
+
issueNumber: batchResult.child.issue.number,
|
|
777
|
+
branchName: batchResult.branchName,
|
|
778
|
+
})),
|
|
779
|
+
gitOutput: error.stderr || error.stdout || 'no git output',
|
|
780
|
+
durableRunSummary: result.durableRunSummary,
|
|
781
|
+
acceptanceProof: result.acceptanceProof,
|
|
782
|
+
}),
|
|
783
|
+
});
|
|
793
784
|
}
|
|
794
785
|
}
|
|
795
786
|
async function markCompletedChildrenReviewReady(issueAdapter, config, store, parentIssueNumber, childResults) {
|
|
796
787
|
for (const childResult of childResults) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
788
|
+
if (childResult.recovered) {
|
|
789
|
+
await store.removeRun(childResult.child.issue.number);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
await finishReviewReadyCommentTerminalOutcome({
|
|
793
|
+
issueNumber: childResult.child.issue.number,
|
|
794
|
+
config,
|
|
795
|
+
issueAdapter,
|
|
796
|
+
reportComment: buildChildReviewReport({ parentIssueNumber, result: childResult }),
|
|
797
|
+
afterTerminalMutation: () => store.removeRun(childResult.child.issue.number),
|
|
798
|
+
});
|
|
801
799
|
}
|
|
802
800
|
}
|
|
803
801
|
async function blockCompletedChildren(issueAdapter, config, store, parentIssueNumber, childResults, reason, parentWorktreePath) {
|
|
804
802
|
for (const result of childResults) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
803
|
+
if (result.recovered) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
await finishBlockedTerminalOutcome({
|
|
807
|
+
issueNumber: result.child.issue.number,
|
|
808
|
+
config,
|
|
809
|
+
issueAdapter,
|
|
810
|
+
reportComment: buildChildBlockedReport({
|
|
811
|
+
parentIssueNumber,
|
|
812
|
+
childIssueNumber: result.child.issue.number,
|
|
813
|
+
reasons: [reason],
|
|
814
|
+
details: [
|
|
815
|
+
`- Merged child branch was not published: ${result.branchName}`,
|
|
816
|
+
`- Parent worktree preserved: ${parentWorktreePath}`,
|
|
817
|
+
],
|
|
818
|
+
durableRunSummary: result.durableRunSummary,
|
|
819
|
+
acceptanceProof: result.acceptanceProof,
|
|
820
|
+
}),
|
|
821
|
+
afterTerminalMutation: () => store.removeRun(result.child.issue.number),
|
|
822
|
+
});
|
|
819
823
|
}
|
|
820
824
|
}
|
|
821
|
-
async function finishPlanBlocked(issueAdapter, config, events, result, reasons, mutatedChildren) {
|
|
822
|
-
const
|
|
825
|
+
async function finishPlanBlocked(issueAdapter, config, events, result, reasons, mutatedChildren, commentMarker) {
|
|
826
|
+
const reportBody = buildParentBlockedReport({
|
|
823
827
|
parentIssueNumber: result.parentIssueNumber,
|
|
824
828
|
reasons,
|
|
825
829
|
logPath: result.logPath,
|
|
826
830
|
mutatedChildren,
|
|
827
831
|
});
|
|
828
|
-
|
|
829
|
-
await
|
|
830
|
-
|
|
832
|
+
const reportComment = commentMarker ? `<!-- codex-orchestrator:${commentMarker} -->\n${reportBody}` : reportBody;
|
|
833
|
+
await finishBlockedTerminalOutcome({
|
|
834
|
+
issueNumber: result.parentIssueNumber,
|
|
835
|
+
config,
|
|
836
|
+
issueAdapter,
|
|
837
|
+
reportComment,
|
|
838
|
+
skipCommentIfIncludes: commentMarker,
|
|
839
|
+
});
|
|
831
840
|
await safeAppendEvent(events, {
|
|
832
841
|
issueNumber: result.parentIssueNumber,
|
|
833
842
|
mode: 'plan-parent',
|