codex-orchestrator 0.1.19 → 0.1.21
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/README.md +132 -33
- package/dist/src/codex/command-adapter.d.ts +3 -0
- package/dist/src/codex/command-adapter.d.ts.map +1 -1
- package/dist/src/codex/command-adapter.js +18 -7
- package/dist/src/codex/command-adapter.js.map +1 -1
- package/dist/src/config/schema.d.ts +3 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +3 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/git/worktree.d.ts +17 -0
- package/dist/src/git/worktree.d.ts.map +1 -1
- package/dist/src/git/worktree.js +35 -2
- package/dist/src/git/worktree.js.map +1 -1
- package/dist/src/github/gh-issue-adapter.js +6 -3
- package/dist/src/github/gh-issue-adapter.js.map +1 -1
- package/dist/src/github/issues.d.ts +1 -1
- package/dist/src/github/issues.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/path-policy.d.ts +27 -0
- package/dist/src/path-policy.d.ts.map +1 -0
- package/dist/src/path-policy.js +83 -0
- package/dist/src/path-policy.js.map +1 -0
- package/dist/src/process/command.d.ts +3 -0
- package/dist/src/process/command.d.ts.map +1 -1
- package/dist/src/process/command.js +59 -9
- package/dist/src/process/command.js.map +1 -1
- package/dist/src/runner/command-utils.d.ts +5 -0
- package/dist/src/runner/command-utils.d.ts.map +1 -1
- package/dist/src/runner/command-utils.js +46 -1
- package/dist/src/runner/command-utils.js.map +1 -1
- package/dist/src/runner/completion-report.d.ts +54 -0
- package/dist/src/runner/completion-report.d.ts.map +1 -0
- package/dist/src/runner/completion-report.js +220 -0
- package/dist/src/runner/completion-report.js.map +1 -0
- package/dist/src/runner/handoff-evidence.d.ts +105 -0
- package/dist/src/runner/handoff-evidence.d.ts.map +1 -0
- package/dist/src/runner/handoff-evidence.js +224 -0
- package/dist/src/runner/handoff-evidence.js.map +1 -0
- package/dist/src/runner/issue-tree.d.ts +4 -1
- package/dist/src/runner/issue-tree.d.ts.map +1 -1
- package/dist/src/runner/issue-tree.js +75 -0
- package/dist/src/runner/issue-tree.js.map +1 -1
- package/dist/src/runner/local-execution-session.d.ts +79 -0
- package/dist/src/runner/local-execution-session.d.ts.map +1 -0
- package/dist/src/runner/local-execution-session.js +236 -0
- package/dist/src/runner/local-execution-session.js.map +1 -0
- package/dist/src/runner/local-state.d.ts +1 -0
- package/dist/src/runner/local-state.d.ts.map +1 -1
- package/dist/src/runner/local-state.js +2 -1
- package/dist/src/runner/local-state.js.map +1 -1
- package/dist/src/runner/plan-auto-command.d.ts +1 -0
- package/dist/src/runner/plan-auto-command.d.ts.map +1 -1
- package/dist/src/runner/plan-auto-command.js +143 -302
- package/dist/src/runner/plan-auto-command.js.map +1 -1
- package/dist/src/runner/prompt.d.ts +2 -53
- package/dist/src/runner/prompt.d.ts.map +1 -1
- package/dist/src/runner/prompt.js +20 -265
- package/dist/src/runner/prompt.js.map +1 -1
- package/dist/src/runner/review-gate-policy.d.ts +24 -0
- package/dist/src/runner/review-gate-policy.d.ts.map +1 -0
- package/dist/src/runner/review-gate-policy.js +104 -0
- package/dist/src/runner/review-gate-policy.js.map +1 -0
- package/dist/src/runner/review-gates.d.ts +4 -9
- package/dist/src/runner/review-gates.d.ts.map +1 -1
- package/dist/src/runner/review-gates.js +17 -63
- package/dist/src/runner/review-gates.js.map +1 -1
- package/dist/src/runner/run-log.d.ts +18 -0
- package/dist/src/runner/run-log.d.ts.map +1 -0
- package/dist/src/runner/run-log.js +53 -0
- package/dist/src/runner/run-log.js.map +1 -0
- package/dist/src/runner/safety.d.ts +1 -1
- package/dist/src/runner/safety.d.ts.map +1 -1
- package/dist/src/runner/safety.js +5 -20
- package/dist/src/runner/safety.js.map +1 -1
- package/dist/src/runner/scoped-auto-command.d.ts +4 -0
- package/dist/src/runner/scoped-auto-command.d.ts.map +1 -1
- package/dist/src/runner/scoped-auto-command.js +70 -172
- package/dist/src/runner/scoped-auto-command.js.map +1 -1
- package/dist/src/runner/visual-proof-runner.d.ts +3 -8
- package/dist/src/runner/visual-proof-runner.d.ts.map +1 -1
- package/dist/src/runner/visual-proof-runner.js +71 -17
- package/dist/src/runner/visual-proof-runner.js.map +1 -1
- package/dist/src/setup/project-config.d.ts.map +1 -1
- package/dist/src/setup/project-config.js +5 -0
- package/dist/src/setup/project-config.js.map +1 -1
- package/dist/src/setup/setup-command.d.ts.map +1 -1
- package/dist/src/setup/setup-command.js +42 -0
- package/dist/src/setup/setup-command.js.map +1 -1
- package/package.json +2 -1
|
@@ -5,15 +5,16 @@ import { GitMergeConflictError, GitWorktreeManager, renderBranchTemplate } from
|
|
|
5
5
|
import { GhCliIssueAdapter } from '../github/gh-issue-adapter.js';
|
|
6
6
|
import { GhCliPullRequestAdapter } from '../github/gh-pull-request-adapter.js';
|
|
7
7
|
import { defaultShellCommandExecutor } from '../process/command.js';
|
|
8
|
-
import {
|
|
8
|
+
import { formatSessionTimestamp, readRunnerConfig, runConfiguredChecks, } from './command-utils.js';
|
|
9
|
+
import { buildChildBlockedReport, buildChildReviewReport, buildIssueTreePullRequestBody, buildIssueTreeReviewReport, buildParentBlockedReport, } from './handoff-evidence.js';
|
|
10
|
+
import { readPlanAutoCompletionReport, } from './completion-report.js';
|
|
9
11
|
import { claimIssue, discoverIssueWork } from './issue-state-machine.js';
|
|
10
|
-
import { collectExecutableChildBatches,
|
|
12
|
+
import { collectExecutableChildBatches, persistAutonomousChildNode, readAutonomousChildNodes, topologicalPlanNodes, } from './issue-tree.js';
|
|
13
|
+
import { runImplementationPublishabilityCheck } from './local-execution-session.js';
|
|
11
14
|
import { RunnerStateStore } from './local-state.js';
|
|
12
|
-
import { buildIssueTreeChildPrompt, buildPlanAutoPrompt,
|
|
13
|
-
import {
|
|
14
|
-
import { validateChangedPaths, validateCompletionReportSafety, validateNoAgentOwnedGitPublication, } from './safety.js';
|
|
15
|
+
import { buildIssueTreeChildPrompt, buildPlanAutoPrompt, sessionPromptPath, sessionReportPath, writeDurablePrompt, } from './prompt.js';
|
|
16
|
+
import { sessionLogPath } from './run-log.js';
|
|
15
17
|
import { cleanupSessionCodexHome, sessionCodexHomePath } from './session-home.js';
|
|
16
|
-
import { runRunnerVisualProof } from './visual-proof-runner.js';
|
|
17
18
|
export async function runPlanAutoCommand(options) {
|
|
18
19
|
const targetRoot = resolve(options.targetRoot);
|
|
19
20
|
const now = options.now ?? new Date();
|
|
@@ -38,8 +39,11 @@ export async function runPlanAutoCommand(options) {
|
|
|
38
39
|
const worktreePath = join(targetRoot, config.runner.workspaceRoot, `tree-${options.issueNumber}`);
|
|
39
40
|
let promptPath = '';
|
|
40
41
|
let reportPath = '';
|
|
42
|
+
let logPath = '';
|
|
41
43
|
const childIssues = [];
|
|
42
44
|
let pullRequest;
|
|
45
|
+
let store;
|
|
46
|
+
const executionResults = [];
|
|
43
47
|
await claimIssue(issueAdapter, config, options.issueNumber, 'plan-parent', now);
|
|
44
48
|
try {
|
|
45
49
|
await git.createIssueWorktree({
|
|
@@ -51,6 +55,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
51
55
|
const sessionId = `plan-${options.issueNumber}-${formatSessionTimestamp(now)}`;
|
|
52
56
|
promptPath = sessionPromptPath({ targetRoot, config, issueNumber: options.issueNumber, sessionId });
|
|
53
57
|
reportPath = sessionReportPath({ targetRoot, config, issueNumber: options.issueNumber, sessionId });
|
|
58
|
+
logPath = sessionLogPath({ targetRoot, config, issueNumber: options.issueNumber, sessionId });
|
|
54
59
|
const isolatedHomePath = sessionCodexHomePath({ targetRoot, sessionId });
|
|
55
60
|
await mkdir(dirname(reportPath), { recursive: true });
|
|
56
61
|
await mkdir(isolatedHomePath, { recursive: true });
|
|
@@ -70,7 +75,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
70
75
|
sessionId,
|
|
71
76
|
promptText,
|
|
72
77
|
});
|
|
73
|
-
|
|
78
|
+
store = new RunnerStateStore(targetRoot, config);
|
|
74
79
|
await store.upsertRun({
|
|
75
80
|
issueNumber: options.issueNumber,
|
|
76
81
|
mode: 'plan-parent',
|
|
@@ -79,6 +84,7 @@ export async function runPlanAutoCommand(options) {
|
|
|
79
84
|
branchName,
|
|
80
85
|
promptPath,
|
|
81
86
|
reportPath,
|
|
87
|
+
logPath,
|
|
82
88
|
retryCount: 0,
|
|
83
89
|
createdAt: now.toISOString(),
|
|
84
90
|
updatedAt: now.toISOString(),
|
|
@@ -97,13 +103,14 @@ export async function runPlanAutoCommand(options) {
|
|
|
97
103
|
issueNumber: options.issueNumber,
|
|
98
104
|
sessionId,
|
|
99
105
|
branchName,
|
|
106
|
+
logPath,
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
finally {
|
|
103
110
|
await cleanupSessionCodexHome(isolatedHomePath);
|
|
104
111
|
}
|
|
105
112
|
const afterHead = await git.getHead(worktreePath);
|
|
106
|
-
const base = baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, childIssues);
|
|
113
|
+
const base = baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues);
|
|
107
114
|
if (beforeHead !== afterHead) {
|
|
108
115
|
return finishPlanBlocked(issueAdapter, config, base, ['Planning session changed git HEAD; planning must not commit.'], []);
|
|
109
116
|
}
|
|
@@ -124,15 +131,14 @@ export async function runPlanAutoCommand(options) {
|
|
|
124
131
|
body: report.parent.body,
|
|
125
132
|
});
|
|
126
133
|
for (const node of topologicalPlanNodes(report.graph)) {
|
|
127
|
-
const persisted = await
|
|
134
|
+
const persisted = await persistAutonomousChildNode(issueAdapter, config, options.issueNumber, node);
|
|
128
135
|
childIssues.push(persisted);
|
|
129
136
|
}
|
|
130
|
-
const childNodes = await
|
|
137
|
+
const childNodes = await readAutonomousChildNodes(issueAdapter, config, options.issueNumber, childIssues);
|
|
131
138
|
const batches = collectExecutableChildBatches(childNodes, config);
|
|
132
139
|
if (!batches.ok) {
|
|
133
140
|
return finishPlanBlocked(issueAdapter, config, base, batches.errors, childIssues);
|
|
134
141
|
}
|
|
135
|
-
const executionResults = [];
|
|
136
142
|
for (const batch of batches.batches) {
|
|
137
143
|
const settled = await Promise.allSettled(batch.map((child) => executeChild({
|
|
138
144
|
targetRoot,
|
|
@@ -161,7 +167,8 @@ export async function runPlanAutoCommand(options) {
|
|
|
161
167
|
if (failures.length > 0) {
|
|
162
168
|
const successful = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
163
169
|
await blockFailedBatch(issueAdapter, config, options.issueNumber, failures, successful);
|
|
164
|
-
|
|
170
|
+
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, 'A later child batch failed before parent publication.', worktreePath);
|
|
171
|
+
return finishPlanBlocked(issueAdapter, config, baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues), [
|
|
165
172
|
'Child batch failed before merge; no child from the failed batch was merged, pushed, or published.',
|
|
166
173
|
...failures.map((failure) => failure.child ? `#${failure.child.issue.number}: ${failure.message}` : failure.message),
|
|
167
174
|
], childIssues);
|
|
@@ -179,7 +186,8 @@ export async function runPlanAutoCommand(options) {
|
|
|
179
186
|
catch (error) {
|
|
180
187
|
if (error instanceof GitMergeConflictError) {
|
|
181
188
|
await handleMergeConflict(git, issueAdapter, config, options.issueNumber, error, childResult, orderedBatchResults);
|
|
182
|
-
|
|
189
|
+
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, `A later merge conflict stopped parent publication before ${branchName} was pushed.`, worktreePath);
|
|
190
|
+
return finishPlanBlocked(issueAdapter, config, baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues), [
|
|
183
191
|
`Merge conflict while merging #${childResult.child.issue.number} from ${childResult.branchName}.`,
|
|
184
192
|
error.stderr || error.stdout,
|
|
185
193
|
], childIssues);
|
|
@@ -189,28 +197,44 @@ export async function runPlanAutoCommand(options) {
|
|
|
189
197
|
}
|
|
190
198
|
for (const childResult of orderedBatchResults) {
|
|
191
199
|
await git.removeWorktree({ targetRoot, worktreePath: childResult.worktreePath });
|
|
192
|
-
await issueAdapter.removeLabels(childResult.child.issue.number, [config.github.labels.running.name]);
|
|
193
|
-
await issueAdapter.addLabels(childResult.child.issue.number, [config.github.labels.review.name]);
|
|
194
|
-
await issueAdapter.postComment(childResult.child.issue.number, buildChildReviewReport(options.issueNumber, childResult));
|
|
195
|
-
await store.removeRun(childResult.child.issue.number);
|
|
196
200
|
executionResults.push(childResult);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
const finalValidation = await runConfiguredChecks(config, worktreePath, shellExecutor, []);
|
|
204
|
+
const failedFinalValidation = finalValidation.filter((line) => line.status === 'failed');
|
|
205
|
+
if (failedFinalValidation.length > 0) {
|
|
206
|
+
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, 'Parent integration configured checks failed before publication.', worktreePath);
|
|
207
|
+
return finishPlanBlocked(issueAdapter, config, baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues), [
|
|
208
|
+
'One or more configured checks failed.',
|
|
209
|
+
...failedFinalValidation.map((line) => `${line.command}: ${line.status} - ${line.summary}`),
|
|
210
|
+
], childIssues);
|
|
211
|
+
}
|
|
200
212
|
await git.pushBranch({ worktreePath, branchName });
|
|
201
213
|
pullRequest = await pullRequestAdapter.createDraftPullRequest({
|
|
202
214
|
title: renderParentTemplate(config.pullRequests.issueTreeTitle, options.issueNumber),
|
|
203
|
-
body: buildIssueTreePullRequestBody(
|
|
215
|
+
body: buildIssueTreePullRequestBody({
|
|
216
|
+
parentIssueNumber: options.issueNumber,
|
|
217
|
+
childIssues,
|
|
218
|
+
childResults: executionResults,
|
|
219
|
+
finalValidation,
|
|
220
|
+
}),
|
|
204
221
|
headBranch: branchName,
|
|
205
222
|
baseBranch: config.branches.base,
|
|
206
223
|
});
|
|
207
|
-
const reportComment = buildIssueTreeReviewReport(
|
|
224
|
+
const reportComment = buildIssueTreeReviewReport({
|
|
225
|
+
parentIssueNumber: options.issueNumber,
|
|
226
|
+
pullRequest,
|
|
227
|
+
batches: batches.batches,
|
|
228
|
+
childResults: executionResults,
|
|
229
|
+
finalValidation,
|
|
230
|
+
});
|
|
231
|
+
await markCompletedChildrenReviewReady(issueAdapter, config, store, options.issueNumber, executionResults);
|
|
208
232
|
await issueAdapter.removeLabels(options.issueNumber, [config.github.labels.running.name]);
|
|
209
233
|
await issueAdapter.addLabels(options.issueNumber, [config.github.labels.review.name]);
|
|
210
234
|
await issueAdapter.postComment(options.issueNumber, reportComment);
|
|
211
235
|
await store.removeRun(options.issueNumber);
|
|
212
236
|
return {
|
|
213
|
-
...baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, childIssues),
|
|
237
|
+
...baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues),
|
|
214
238
|
status: 'review-ready',
|
|
215
239
|
pullRequest,
|
|
216
240
|
reportComment,
|
|
@@ -221,46 +245,11 @@ export async function runPlanAutoCommand(options) {
|
|
|
221
245
|
if (pullRequest) {
|
|
222
246
|
throw new Error(`Issue-tree execution failed after draft PR creation (${pullRequest.url}); not marking parent blocked: ${message}`);
|
|
223
247
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
async function persistChildNode(issueAdapter, config, parentIssueNumber, node) {
|
|
228
|
-
const body = buildChildBody(node, parentIssueNumber);
|
|
229
|
-
const childLabel = config.github.labels.child.name;
|
|
230
|
-
const autoLabel = config.github.labels.auto.name;
|
|
231
|
-
let issue;
|
|
232
|
-
if (node.issueNumber !== undefined) {
|
|
233
|
-
const existing = await issueAdapter.getIssue(node.issueNumber);
|
|
234
|
-
if (!existing) {
|
|
235
|
-
throw new Error(`Existing issue #${node.issueNumber} was not found`);
|
|
236
|
-
}
|
|
237
|
-
if (!isAutonomousChildOfParent(existing, config, parentIssueNumber)) {
|
|
238
|
-
throw new Error(`Existing issue #${node.issueNumber} is not an autonomous child of #${parentIssueNumber}; refusing to update arbitrary issue.`);
|
|
239
|
-
}
|
|
240
|
-
issue = await issueAdapter.updateIssue(node.issueNumber, {
|
|
241
|
-
title: node.title,
|
|
242
|
-
body,
|
|
243
|
-
addLabels: [childLabel],
|
|
244
|
-
removeLabels: node.afkHitl === 'hitl' ? [autoLabel] : undefined,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
issue = await issueAdapter.createIssue({
|
|
249
|
-
title: node.title,
|
|
250
|
-
body,
|
|
251
|
-
labels: [childLabel],
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
if (!isAutonomousChildOfParent(issue, config, parentIssueNumber)) {
|
|
255
|
-
throw new Error(`Child issue #${issue.number} was not persisted with the autonomous marker for #${parentIssueNumber}.`);
|
|
256
|
-
}
|
|
257
|
-
if (node.afkHitl === 'afk') {
|
|
258
|
-
issue = await issueAdapter.updateIssue(issue.number, { addLabels: [autoLabel] });
|
|
259
|
-
if (!isAutonomousChildOfParent(issue, config, parentIssueNumber)) {
|
|
260
|
-
throw new Error(`Child issue #${issue.number} lost the autonomous marker for #${parentIssueNumber} while enabling agent:auto.`);
|
|
248
|
+
if (store && executionResults.length > 0) {
|
|
249
|
+
await blockCompletedChildren(issueAdapter, config, store, options.issueNumber, executionResults, `Parent publication failed before review handoff: ${message}`, worktreePath);
|
|
261
250
|
}
|
|
251
|
+
return finishPlanBlocked(issueAdapter, config, baseResult(options.issueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues), [message], childIssues);
|
|
262
252
|
}
|
|
263
|
-
return issue;
|
|
264
253
|
}
|
|
265
254
|
async function readPlanReport(reportPath) {
|
|
266
255
|
try {
|
|
@@ -297,27 +286,6 @@ async function readWorkflowPrompt(targetRoot, promptPath, promptName) {
|
|
|
297
286
|
throw error;
|
|
298
287
|
}
|
|
299
288
|
}
|
|
300
|
-
async function readExecutableChildren(issueAdapter, config, parentIssueNumber, childIssues) {
|
|
301
|
-
const nodes = [];
|
|
302
|
-
const errors = [];
|
|
303
|
-
for (const child of childIssues) {
|
|
304
|
-
const current = await issueAdapter.getIssue(child.number);
|
|
305
|
-
if (!current) {
|
|
306
|
-
errors.push(`Child issue #${child.number} was not found during execution readback`);
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
const parsed = parseAutonomousChildMetadata(current, config, parentIssueNumber);
|
|
310
|
-
if (!parsed.ok) {
|
|
311
|
-
errors.push(...parsed.errors);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
nodes.push(parsed.node);
|
|
315
|
-
}
|
|
316
|
-
if (errors.length > 0) {
|
|
317
|
-
throw new Error(errors.join('; '));
|
|
318
|
-
}
|
|
319
|
-
return nodes;
|
|
320
|
-
}
|
|
321
289
|
async function executeChild(input) {
|
|
322
290
|
const childIssueNumber = input.child.issue.number;
|
|
323
291
|
const branchName = `codex/tree-${input.parentIssue.number}-issue-${childIssueNumber}`;
|
|
@@ -335,6 +303,12 @@ async function executeChild(input) {
|
|
|
335
303
|
issueNumber: childIssueNumber,
|
|
336
304
|
sessionId,
|
|
337
305
|
});
|
|
306
|
+
const logPath = sessionLogPath({
|
|
307
|
+
targetRoot: input.targetRoot,
|
|
308
|
+
config: input.config,
|
|
309
|
+
issueNumber: childIssueNumber,
|
|
310
|
+
sessionId,
|
|
311
|
+
});
|
|
338
312
|
const isolatedHomePath = sessionCodexHomePath({ targetRoot: input.targetRoot, sessionId });
|
|
339
313
|
const store = new RunnerStateStore(input.targetRoot, input.config);
|
|
340
314
|
await input.git.createIssueWorktree({
|
|
@@ -375,6 +349,7 @@ async function executeChild(input) {
|
|
|
375
349
|
branchName,
|
|
376
350
|
promptPath,
|
|
377
351
|
reportPath,
|
|
352
|
+
logPath,
|
|
378
353
|
retryCount: 0,
|
|
379
354
|
createdAt: input.now.toISOString(),
|
|
380
355
|
updatedAt: input.now.toISOString(),
|
|
@@ -393,119 +368,51 @@ async function executeChild(input) {
|
|
|
393
368
|
issueNumber: childIssueNumber,
|
|
394
369
|
sessionId,
|
|
395
370
|
branchName,
|
|
371
|
+
logPath,
|
|
396
372
|
});
|
|
397
373
|
}
|
|
398
374
|
finally {
|
|
399
375
|
await cleanupSessionCodexHome(isolatedHomePath);
|
|
400
376
|
}
|
|
401
377
|
const afterHead = await input.git.getHead(worktreePath);
|
|
402
|
-
const
|
|
403
|
-
if (publicationViolations.length > 0) {
|
|
404
|
-
throw new Error(publicationViolations.map((violation) => violation.message).join('; '));
|
|
405
|
-
}
|
|
406
|
-
if (codexResult.exitCode !== 0) {
|
|
407
|
-
throw new Error(`Codex exited with code ${codexResult.exitCode}: ${codexResult.stderr || codexResult.stdout}`);
|
|
408
|
-
}
|
|
409
|
-
const report = await readRequiredScopedReport(reportPath);
|
|
410
|
-
if (report.status === 'needs-promotion') {
|
|
411
|
-
const promotion = report.promotion;
|
|
412
|
-
throw new Error([
|
|
413
|
-
'Child requested promotion instead of completing issue-tree work.',
|
|
414
|
-
promotion ? `Reason: ${promotion.reason}` : undefined,
|
|
415
|
-
promotion ? `Evidence: ${promotion.evidence.join(', ')}` : undefined,
|
|
416
|
-
].filter(Boolean).join(' '));
|
|
417
|
-
}
|
|
418
|
-
let changedFiles = await input.git.listChangedFiles(worktreePath);
|
|
419
|
-
if (changedFiles.length === 0) {
|
|
420
|
-
throw new Error('Codex completed without file changes');
|
|
421
|
-
}
|
|
422
|
-
const violations = [
|
|
423
|
-
...validateChangedPaths(changedFiles, input.config),
|
|
424
|
-
...validateCompletionReportSafety(report),
|
|
425
|
-
];
|
|
426
|
-
if (violations.length > 0) {
|
|
427
|
-
throw new Error(violations.map((violation) => violation.message).join('; '));
|
|
428
|
-
}
|
|
429
|
-
let validation = await runConfiguredChecks(input.config, worktreePath, input.shellExecutor, report.validation);
|
|
430
|
-
const runnerVisualProof = await runRunnerVisualProof({
|
|
378
|
+
const publishability = await runImplementationPublishabilityCheck({
|
|
431
379
|
config: input.config,
|
|
432
380
|
issue: input.child.issue,
|
|
433
|
-
issueNumber: childIssueNumber,
|
|
434
381
|
worktreePath,
|
|
435
|
-
|
|
436
|
-
|
|
382
|
+
reportPath,
|
|
383
|
+
beforeHead,
|
|
384
|
+
afterHead,
|
|
385
|
+
codexResult,
|
|
386
|
+
git: input.git,
|
|
437
387
|
shellExecutor: input.shellExecutor,
|
|
388
|
+
commitMessage: `Codex: implement issue #${childIssueNumber} for parent #${input.parentIssue.number}`,
|
|
438
389
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
residualRisks.push('One or more configured checks failed.');
|
|
390
|
+
if (publishability.status === 'promotion-requested') {
|
|
391
|
+
const promotion = publishability.report.promotion;
|
|
392
|
+
throw new Error([
|
|
393
|
+
'Child requested promotion instead of completing issue-tree work.',
|
|
394
|
+
promotion ? `Reason: ${promotion.reason}` : undefined,
|
|
395
|
+
promotion ? `Evidence: ${promotion.evidence.join(', ')}` : undefined,
|
|
396
|
+
].filter(Boolean).join(' '));
|
|
447
397
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
issue: input.child.issue,
|
|
451
|
-
changedFiles,
|
|
452
|
-
validation,
|
|
453
|
-
skippedChecks: report.skippedChecks,
|
|
454
|
-
report: { ...report, artifacts },
|
|
455
|
-
worktreePath,
|
|
456
|
-
});
|
|
457
|
-
if (!reviewGate.ok) {
|
|
458
|
-
throw new Error(reviewGate.reasons.join('; '));
|
|
398
|
+
if (publishability.status === 'blocked') {
|
|
399
|
+
throw new Error(publishability.reasons.join('; '));
|
|
459
400
|
}
|
|
460
|
-
await input.git.commitAll({
|
|
461
|
-
worktreePath,
|
|
462
|
-
message: `Codex: implement issue #${childIssueNumber} for parent #${input.parentIssue.number}`,
|
|
463
|
-
});
|
|
464
401
|
return {
|
|
465
402
|
child: input.child,
|
|
466
403
|
branchName,
|
|
467
404
|
worktreePath,
|
|
468
405
|
promptPath,
|
|
469
406
|
reportPath,
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
407
|
+
logPath,
|
|
408
|
+
changedFiles: publishability.changedFiles,
|
|
409
|
+
validation: publishability.validation,
|
|
410
|
+
artifacts: publishability.artifacts,
|
|
411
|
+
commits: publishability.commits,
|
|
412
|
+
skippedChecks: publishability.skippedChecks,
|
|
413
|
+
residualRisks: publishability.residualRisks,
|
|
475
414
|
};
|
|
476
415
|
}
|
|
477
|
-
async function readRequiredScopedReport(reportPath) {
|
|
478
|
-
const reportRead = await readScopedCompletionReport(reportPath);
|
|
479
|
-
if (reportRead.kind === 'missing') {
|
|
480
|
-
throw new Error('Codex did not write CODEX_ORCHESTRATOR_REPORT_FILE; runner cannot prove child safety contract.');
|
|
481
|
-
}
|
|
482
|
-
return reportRead.report;
|
|
483
|
-
}
|
|
484
|
-
async function runConfiguredChecks(config, worktreePath, shellExecutor, reportValidation) {
|
|
485
|
-
const lines = [...reportValidation];
|
|
486
|
-
for (const [name, command] of Object.entries(config.checks)) {
|
|
487
|
-
const result = await shellExecutor(command, { cwd: worktreePath });
|
|
488
|
-
lines.push({
|
|
489
|
-
command,
|
|
490
|
-
status: result.exitCode === 0 ? 'passed' : 'failed',
|
|
491
|
-
summary: `${name}: ${result.exitCode === 0 ? 'passed' : result.stderr || result.stdout || `exit ${result.exitCode}`}`,
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
return lines;
|
|
495
|
-
}
|
|
496
|
-
function mergeArtifacts(existing, additions) {
|
|
497
|
-
const seen = new Set(existing.map((artifact) => artifact.url ?? artifact.path ?? artifact.description));
|
|
498
|
-
const merged = [...existing];
|
|
499
|
-
for (const artifact of additions) {
|
|
500
|
-
const key = artifact.url ?? artifact.path ?? artifact.description;
|
|
501
|
-
if (seen.has(key)) {
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
seen.add(key);
|
|
505
|
-
merged.push(artifact);
|
|
506
|
-
}
|
|
507
|
-
return merged;
|
|
508
|
-
}
|
|
509
416
|
async function blockFailedBatch(issueAdapter, config, parentIssueNumber, failures, successfulUnmerged) {
|
|
510
417
|
for (const failure of failures) {
|
|
511
418
|
if (!failure.child) {
|
|
@@ -513,23 +420,23 @@ async function blockFailedBatch(issueAdapter, config, parentIssueNumber, failure
|
|
|
513
420
|
}
|
|
514
421
|
await issueAdapter.removeLabels(failure.child.issue.number, [config.github.labels.running.name]);
|
|
515
422
|
await issueAdapter.addLabels(failure.child.issue.number, [config.github.labels.blocked.name]);
|
|
516
|
-
await issueAdapter.postComment(failure.child.issue.number,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
'Worktree preserved for maintainer inspection.',
|
|
521
|
-
|
|
423
|
+
await issueAdapter.postComment(failure.child.issue.number, buildChildBlockedReport({
|
|
424
|
+
parentIssueNumber,
|
|
425
|
+
childIssueNumber: failure.child.issue.number,
|
|
426
|
+
reasons: [failure.message],
|
|
427
|
+
details: ['Worktree preserved for maintainer inspection.'],
|
|
428
|
+
}));
|
|
522
429
|
}
|
|
523
430
|
for (const result of successfulUnmerged) {
|
|
524
431
|
await issueAdapter.removeLabels(result.child.issue.number, [config.github.labels.running.name]);
|
|
525
432
|
await issueAdapter.addLabels(result.child.issue.number, [config.github.labels.blocked.name]);
|
|
526
|
-
await issueAdapter.postComment(result.child.issue.number,
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
'
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
433
|
+
await issueAdapter.postComment(result.child.issue.number, buildChildBlockedReport({
|
|
434
|
+
parentIssueNumber,
|
|
435
|
+
childIssueNumber: result.child.issue.number,
|
|
436
|
+
reasons: ['A sibling child failed before the batch merge; this child branch was not merged.'],
|
|
437
|
+
branchName: result.branchName,
|
|
438
|
+
worktreePath: result.worktreePath,
|
|
439
|
+
}));
|
|
533
440
|
}
|
|
534
441
|
}
|
|
535
442
|
async function handleMergeConflict(git, issueAdapter, config, parentIssueNumber, error, childResult, batchResults) {
|
|
@@ -543,51 +450,64 @@ async function handleMergeConflict(git, issueAdapter, config, parentIssueNumber,
|
|
|
543
450
|
for (const result of batchResults) {
|
|
544
451
|
await issueAdapter.removeLabels(result.child.issue.number, [config.github.labels.running.name]);
|
|
545
452
|
await issueAdapter.addLabels(result.child.issue.number, [config.github.labels.blocked.name]);
|
|
546
|
-
await issueAdapter.postComment(result.child.issue.number,
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
453
|
+
await issueAdapter.postComment(result.child.issue.number, buildChildBlockedReport({
|
|
454
|
+
parentIssueNumber,
|
|
455
|
+
childIssueNumber: result.child.issue.number,
|
|
456
|
+
reasons: [
|
|
457
|
+
result.child.issue.number === childResult.child.issue.number
|
|
458
|
+
? `Merge conflict from branch ${childResult.branchName}`
|
|
459
|
+
: `A sibling merge conflict stopped the batch before publication: ${childResult.branchName}`,
|
|
460
|
+
],
|
|
461
|
+
details: [
|
|
462
|
+
`- Parent worktree: ${error.worktreePath}`,
|
|
463
|
+
`- Child worktree: ${result.worktreePath}`,
|
|
464
|
+
`- Child branch preserved: ${result.branchName}`,
|
|
465
|
+
abortMessage ? `- Merge abort also failed: ${abortMessage}` : '- Merge abort completed.',
|
|
466
|
+
],
|
|
467
|
+
batchChildren: batchResults.map((batchResult) => ({
|
|
468
|
+
issueNumber: batchResult.child.issue.number,
|
|
469
|
+
branchName: batchResult.branchName,
|
|
470
|
+
})),
|
|
471
|
+
gitOutput: error.stderr || error.stdout || 'no git output',
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function markCompletedChildrenReviewReady(issueAdapter, config, store, parentIssueNumber, childResults) {
|
|
476
|
+
for (const childResult of childResults) {
|
|
477
|
+
await issueAdapter.removeLabels(childResult.child.issue.number, [config.github.labels.running.name]);
|
|
478
|
+
await issueAdapter.addLabels(childResult.child.issue.number, [config.github.labels.review.name]);
|
|
479
|
+
await issueAdapter.postComment(childResult.child.issue.number, buildChildReviewReport({ parentIssueNumber, result: childResult }));
|
|
480
|
+
await store.removeRun(childResult.child.issue.number);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async function blockCompletedChildren(issueAdapter, config, store, parentIssueNumber, childResults, reason, parentWorktreePath) {
|
|
484
|
+
for (const result of childResults) {
|
|
485
|
+
await issueAdapter.removeLabels(result.child.issue.number, [config.github.labels.running.name]);
|
|
486
|
+
await issueAdapter.addLabels(result.child.issue.number, [config.github.labels.blocked.name]);
|
|
487
|
+
await issueAdapter.postComment(result.child.issue.number, buildChildBlockedReport({
|
|
488
|
+
parentIssueNumber,
|
|
489
|
+
childIssueNumber: result.child.issue.number,
|
|
490
|
+
reasons: [reason],
|
|
491
|
+
details: [
|
|
492
|
+
`- Merged child branch was not published: ${result.branchName}`,
|
|
493
|
+
`- Parent worktree preserved: ${parentWorktreePath}`,
|
|
494
|
+
],
|
|
495
|
+
}));
|
|
496
|
+
await store.removeRun(result.child.issue.number);
|
|
561
497
|
}
|
|
562
498
|
}
|
|
563
499
|
async function finishPlanBlocked(issueAdapter, config, result, reasons, mutatedChildren) {
|
|
564
|
-
const reportComment =
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
].join('\n');
|
|
500
|
+
const reportComment = buildParentBlockedReport({
|
|
501
|
+
parentIssueNumber: result.parentIssueNumber,
|
|
502
|
+
reasons,
|
|
503
|
+
logPath: result.logPath,
|
|
504
|
+
mutatedChildren,
|
|
505
|
+
});
|
|
571
506
|
await issueAdapter.removeLabels(result.parentIssueNumber, [config.github.labels.running.name]);
|
|
572
507
|
await issueAdapter.addLabels(result.parentIssueNumber, [config.github.labels.blocked.name]);
|
|
573
508
|
await issueAdapter.postComment(result.parentIssueNumber, reportComment);
|
|
574
509
|
return { ...result, status: 'blocked', reportComment };
|
|
575
510
|
}
|
|
576
|
-
function buildChildBody(node, parentIssueNumber) {
|
|
577
|
-
return [
|
|
578
|
-
ensureAutonomousChildBody(node.body, parentIssueNumber),
|
|
579
|
-
'',
|
|
580
|
-
'## codex-orchestrator metadata',
|
|
581
|
-
`Stable ID: ${node.stableId}`,
|
|
582
|
-
`AFK/HITL: ${node.afkHitl}`,
|
|
583
|
-
`Depends on: ${node.dependsOn.length > 0 ? node.dependsOn.join(', ') : 'none'}`,
|
|
584
|
-
'Ownership:',
|
|
585
|
-
...bulletList(node.ownershipScope),
|
|
586
|
-
'Spec gate: wave-level',
|
|
587
|
-
'Verification:',
|
|
588
|
-
...bulletList(node.verification),
|
|
589
|
-
].join('\n');
|
|
590
|
-
}
|
|
591
511
|
function dependencyIssuesFor(child, allChildren) {
|
|
592
512
|
const issuesByStableId = new Map(allChildren.map((node) => [node.metadata.stableId, node.issue]));
|
|
593
513
|
return child.metadata.dependsOn.flatMap((stableId) => {
|
|
@@ -595,89 +515,10 @@ function dependencyIssuesFor(child, allChildren) {
|
|
|
595
515
|
return issue ? [issue] : [];
|
|
596
516
|
});
|
|
597
517
|
}
|
|
598
|
-
function buildChildReviewReport(parentIssueNumber, result) {
|
|
599
|
-
return [
|
|
600
|
-
`codex-orchestrator child review report for #${result.child.issue.number}`,
|
|
601
|
-
`Parent issue: #${parentIssueNumber}`,
|
|
602
|
-
`Integration branch: ${result.branchName}`,
|
|
603
|
-
'Changes',
|
|
604
|
-
...bulletList(result.changedFiles),
|
|
605
|
-
'Validation',
|
|
606
|
-
...result.validation.map((line) => `- ${line.command}: ${line.status} - ${line.summary}`),
|
|
607
|
-
'Proof Artifacts',
|
|
608
|
-
...renderProofArtifacts(result.artifacts),
|
|
609
|
-
'Skipped Checks',
|
|
610
|
-
...bulletList(result.skippedChecks),
|
|
611
|
-
'Residual Risks',
|
|
612
|
-
...bulletList(result.residualRisks),
|
|
613
|
-
].join('\n');
|
|
614
|
-
}
|
|
615
|
-
function buildIssueTreeReviewReport(parentIssueNumber, pullRequest, batches, childResults, finalValidation) {
|
|
616
|
-
return [
|
|
617
|
-
`codex-orchestrator issue-tree review report for #${parentIssueNumber}`,
|
|
618
|
-
'Pull Request',
|
|
619
|
-
`- ${pullRequest.url}`,
|
|
620
|
-
'Execution Batches',
|
|
621
|
-
...batches.map((batch, index) => `- Batch ${index + 1}: ${batch.map((child) => `#${child.issue.number}`).join(', ')}`),
|
|
622
|
-
'Child Issues',
|
|
623
|
-
...childResults.map((result) => `- #${result.child.issue.number} ${result.child.issue.title}: ${result.branchName}`),
|
|
624
|
-
'Validation',
|
|
625
|
-
...childResults.flatMap((result) => result.validation.map((line) => `- #${result.child.issue.number} ${line.command}: ${line.status} - ${line.summary}`)),
|
|
626
|
-
...finalValidation.map((line) => `- final ${line.command}: ${line.status} - ${line.summary}`),
|
|
627
|
-
'Skipped Checks',
|
|
628
|
-
...bulletList(childResults.flatMap((result) => result.skippedChecks)),
|
|
629
|
-
'Proof Artifacts',
|
|
630
|
-
...childResults.flatMap((result) => renderProofArtifacts(result.artifacts).map((line) => `- #${result.child.issue.number} ${line.replace(/^- /, '')}`)),
|
|
631
|
-
'Residual Risks',
|
|
632
|
-
...bulletList(childResults.flatMap((result) => result.residualRisks)),
|
|
633
|
-
].join('\n');
|
|
634
|
-
}
|
|
635
|
-
function buildIssueTreePullRequestBody(parentIssueNumber, childIssues, childResults, finalValidation) {
|
|
636
|
-
return [
|
|
637
|
-
`Parent issue: #${parentIssueNumber}`,
|
|
638
|
-
'',
|
|
639
|
-
'Child issues:',
|
|
640
|
-
...childIssues.map((issue) => `- #${issue.number} ${issue.title}`),
|
|
641
|
-
'',
|
|
642
|
-
'Changed files:',
|
|
643
|
-
...childResults.flatMap((result) => [
|
|
644
|
-
`- #${result.child.issue.number}:`,
|
|
645
|
-
...result.changedFiles.map((file) => ` - ${file}`),
|
|
646
|
-
]),
|
|
647
|
-
'',
|
|
648
|
-
'Validation:',
|
|
649
|
-
...childResults.flatMap((result) => result.validation.map((line) => `- #${result.child.issue.number} ${line.command}: ${line.status} - ${line.summary}`)),
|
|
650
|
-
...finalValidation.map((line) => `- final ${line.command}: ${line.status} - ${line.summary}`),
|
|
651
|
-
'',
|
|
652
|
-
'Skipped checks:',
|
|
653
|
-
...bulletList(childResults.flatMap((result) => result.skippedChecks)),
|
|
654
|
-
'',
|
|
655
|
-
'Proof artifacts:',
|
|
656
|
-
...childResults.flatMap((result) => renderProofArtifacts(result.artifacts).map((line) => `- #${result.child.issue.number} ${line.replace(/^- /, '')}`)),
|
|
657
|
-
'',
|
|
658
|
-
'Residual risks:',
|
|
659
|
-
...bulletList(childResults.flatMap((result) => result.residualRisks)),
|
|
660
|
-
'',
|
|
661
|
-
'Merge summary:',
|
|
662
|
-
...childResults.map((result) => `- ${result.branchName} merged for #${result.child.issue.number}`),
|
|
663
|
-
'',
|
|
664
|
-
'Auto-merge is disabled.',
|
|
665
|
-
].join('\n');
|
|
666
|
-
}
|
|
667
518
|
function renderParentTemplate(template, parentIssueNumber) {
|
|
668
519
|
return template.replaceAll('${parentIssueNumber}', String(parentIssueNumber));
|
|
669
520
|
}
|
|
670
|
-
function
|
|
671
|
-
|
|
672
|
-
return ['- none'];
|
|
673
|
-
}
|
|
674
|
-
return artifacts.map((artifact) => {
|
|
675
|
-
const target = artifact.url ?? artifact.path ?? 'missing-target';
|
|
676
|
-
const label = `${artifact.type}: ${artifact.description}`;
|
|
677
|
-
return artifact.type === 'screenshot' ? `- ![${label.replace(/[\[\]]/g, '')}](${target})` : `- ${label}: ${target}`;
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
function baseResult(parentIssueNumber, branchName, worktreePath, promptPath, reportPath, childIssues) {
|
|
681
|
-
return { parentIssueNumber, branchName, worktreePath, promptPath, reportPath, childIssues };
|
|
521
|
+
function baseResult(parentIssueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues) {
|
|
522
|
+
return { parentIssueNumber, branchName, worktreePath, promptPath, reportPath, logPath, childIssues };
|
|
682
523
|
}
|
|
683
524
|
//# sourceMappingURL=plan-auto-command.js.map
|