issue-flow 0.6.0 → 0.7.1
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 +3 -1
- package/dist/{analyze-VVG7UABS.js → analyze-R25HDGBJ.js} +10 -6
- package/dist/analyze-R25HDGBJ.js.map +1 -0
- package/dist/chunk-23WOEBGN.js +725 -0
- package/dist/chunk-23WOEBGN.js.map +1 -0
- package/dist/{chunk-4HT4EVYV.js → chunk-2AVZZAC6.js} +181 -13
- package/dist/chunk-2AVZZAC6.js.map +1 -0
- package/dist/{chunk-FPGHTTVV.js → chunk-ASMCLXQ2.js} +12 -7
- package/dist/chunk-ASMCLXQ2.js.map +1 -0
- package/dist/{chunk-2CSHLHYP.js → chunk-EHVKXFFN.js} +2 -2
- package/dist/{chunk-JTSAL3TI.js → chunk-EM43T462.js} +18 -5
- package/dist/chunk-EM43T462.js.map +1 -0
- package/dist/{chunk-KWGRBNUG.js → chunk-HJ7JT777.js} +26 -7
- package/dist/chunk-HJ7JT777.js.map +1 -0
- package/dist/{chunk-WZDCTAWZ.js → chunk-JNEHVQH5.js} +39 -2
- package/dist/chunk-JNEHVQH5.js.map +1 -0
- package/dist/{chunk-SVOWWZQJ.js → chunk-RN2FAMUU.js} +5 -280
- package/dist/chunk-RN2FAMUU.js.map +1 -0
- package/dist/chunk-TCOS2KFB.js +283 -0
- package/dist/chunk-TCOS2KFB.js.map +1 -0
- package/dist/chunk-WHIBTNYI.js +72 -0
- package/dist/chunk-WHIBTNYI.js.map +1 -0
- package/dist/{chunk-7W5VGYXW.js → chunk-YGEWIEAI.js} +45 -69
- package/dist/chunk-YGEWIEAI.js.map +1 -0
- package/dist/{chunk-YM3G5BLR.js → chunk-YLXPLHEL.js} +2 -2
- package/dist/{chunk-EHX4J5G5.js → chunk-ZAEBDO3Q.js} +12 -7
- package/dist/chunk-ZAEBDO3Q.js.map +1 -0
- package/dist/cli.js +65 -20
- package/dist/cli.js.map +1 -1
- package/dist/{config-B564P45C.js → config-SJAQMFWB.js} +11 -3
- package/dist/execute-VZ7SHLP4.js +13 -0
- package/dist/{generate-IN62ARBS.js → generate-LUK3XTIP.js} +7 -5
- package/dist/{generate-IN62ARBS.js.map → generate-LUK3XTIP.js.map} +1 -1
- package/dist/init-5KXCG36P.js +9 -0
- package/dist/plan-R34SAWRK.js +15 -0
- package/dist/pr-P4BDQXUQ.js +15 -0
- package/dist/pr-review-TOSBE6ZS.js +15 -0
- package/dist/prd-RYLULM75.js +15 -0
- package/dist/review-ZSC5ELQA.js +13 -0
- package/dist/review-ZSC5ELQA.js.map +1 -0
- package/dist/{run-GPBICAD6.js → run-SDRQDMSZ.js} +150 -55
- package/dist/run-SDRQDMSZ.js.map +1 -0
- package/package.json +1 -1
- package/prompts/execute.md +17 -5
- package/prompts/plan.md +1 -0
- package/prompts/pr-review.md +144 -0
- package/prompts/review.md +2 -0
- package/dist/analyze-VVG7UABS.js.map +0 -1
- package/dist/chunk-4HT4EVYV.js.map +0 -1
- package/dist/chunk-7W5VGYXW.js.map +0 -1
- package/dist/chunk-EHX4J5G5.js.map +0 -1
- package/dist/chunk-FPGHTTVV.js.map +0 -1
- package/dist/chunk-JTSAL3TI.js.map +0 -1
- package/dist/chunk-KWGRBNUG.js.map +0 -1
- package/dist/chunk-SVOWWZQJ.js.map +0 -1
- package/dist/chunk-WZDCTAWZ.js.map +0 -1
- package/dist/execute-PNOFNLZG.js +0 -12
- package/dist/init-5242XSYC.js +0 -9
- package/dist/plan-6KLONWPR.js +0 -14
- package/dist/pr-JLJLMFN3.js +0 -14
- package/dist/prd-TVJDYZXH.js +0 -14
- package/dist/review-FRV4ZMS2.js +0 -12
- package/dist/run-GPBICAD6.js.map +0 -1
- /package/dist/{chunk-2CSHLHYP.js.map → chunk-EHVKXFFN.js.map} +0 -0
- /package/dist/{chunk-YM3G5BLR.js.map → chunk-YLXPLHEL.js.map} +0 -0
- /package/dist/{config-B564P45C.js.map → config-SJAQMFWB.js.map} +0 -0
- /package/dist/{execute-PNOFNLZG.js.map → execute-VZ7SHLP4.js.map} +0 -0
- /package/dist/{init-5242XSYC.js.map → init-5KXCG36P.js.map} +0 -0
- /package/dist/{plan-6KLONWPR.js.map → plan-R34SAWRK.js.map} +0 -0
- /package/dist/{pr-JLJLMFN3.js.map → pr-P4BDQXUQ.js.map} +0 -0
- /package/dist/{prd-TVJDYZXH.js.map → pr-review-TOSBE6ZS.js.map} +0 -0
- /package/dist/{review-FRV4ZMS2.js.map → prd-RYLULM75.js.map} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ElapsedTimer,
|
|
4
|
+
createSpinner,
|
|
5
|
+
formatDuration,
|
|
6
|
+
getIcons,
|
|
7
|
+
getOutputCallback,
|
|
8
|
+
getSessionPublisher,
|
|
9
|
+
isVerbose,
|
|
10
|
+
isoNow,
|
|
11
|
+
useColor
|
|
12
|
+
} from "./chunk-JNEHVQH5.js";
|
|
13
|
+
|
|
14
|
+
// src/core/headless.ts
|
|
15
|
+
import { createInterface } from "readline";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { execa } from "execa";
|
|
18
|
+
function getToolContext(name, input) {
|
|
19
|
+
switch (name) {
|
|
20
|
+
case "Read":
|
|
21
|
+
return shortPath(input.file_path);
|
|
22
|
+
case "Write":
|
|
23
|
+
case "Edit":
|
|
24
|
+
return shortPath(input.file_path);
|
|
25
|
+
case "Glob":
|
|
26
|
+
return input.pattern ?? "";
|
|
27
|
+
case "Grep":
|
|
28
|
+
return input.pattern ?? "";
|
|
29
|
+
case "Bash": {
|
|
30
|
+
const cmd = input.command ?? "";
|
|
31
|
+
return cmd.length > 60 ? `${cmd.substring(0, 57)}...` : cmd;
|
|
32
|
+
}
|
|
33
|
+
default:
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function shortPath(filePath) {
|
|
38
|
+
if (!filePath) return "";
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
if (filePath.startsWith(cwd)) {
|
|
41
|
+
return filePath.substring(cwd.length + 1);
|
|
42
|
+
}
|
|
43
|
+
const parts = filePath.split("/");
|
|
44
|
+
if (parts.length > 2) {
|
|
45
|
+
return `.../${parts.slice(-2).join("/")}`;
|
|
46
|
+
}
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
function printStreamEvent(line, state, onOutput) {
|
|
50
|
+
let event;
|
|
51
|
+
try {
|
|
52
|
+
event = JSON.parse(line);
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const icons = getIcons();
|
|
57
|
+
const colored = useColor();
|
|
58
|
+
const emit = onOutput ?? ((msg) => process.stderr.write(`${msg}
|
|
59
|
+
`));
|
|
60
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
61
|
+
state.turnCount++;
|
|
62
|
+
for (const block of event.message.content) {
|
|
63
|
+
if (block.type === "text" && block.text) {
|
|
64
|
+
const lines = block.text.split("\n");
|
|
65
|
+
for (const textLine of lines) {
|
|
66
|
+
if (!textLine.trim()) continue;
|
|
67
|
+
const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
68
|
+
const text = colored ? chalk.dim(textLine) : textLine;
|
|
69
|
+
emit(`${prefix}${text}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (block.type === "tool_use" && block.name) {
|
|
73
|
+
const context = block.input ? getToolContext(block.name, block.input) : "";
|
|
74
|
+
getSessionPublisher().publish({
|
|
75
|
+
type: "activity",
|
|
76
|
+
at: isoNow(),
|
|
77
|
+
tool: block.name,
|
|
78
|
+
detail: context || void 0
|
|
79
|
+
});
|
|
80
|
+
const toolName = block.name.padEnd(12);
|
|
81
|
+
const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
82
|
+
const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;
|
|
83
|
+
const toolLabel = colored ? chalk.cyan(toolName) : toolName;
|
|
84
|
+
const contextText = context ? colored ? chalk.dim(context) : context : "";
|
|
85
|
+
emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function runHeadlessVerbose(prompt, maxTurns, timeout, allowedTools, statusMessage, onOutput) {
|
|
91
|
+
const icons = getIcons();
|
|
92
|
+
const colored = useColor();
|
|
93
|
+
const emit = onOutput ?? ((msg) => process.stderr.write(`${msg}
|
|
94
|
+
`));
|
|
95
|
+
if (statusMessage) {
|
|
96
|
+
const msg = colored ? chalk.blue(`${icons.start} ${statusMessage}`) : `${icons.start} ${statusMessage}`;
|
|
97
|
+
emit(msg);
|
|
98
|
+
}
|
|
99
|
+
const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;
|
|
100
|
+
emit(connectorLine);
|
|
101
|
+
const startTime = Date.now();
|
|
102
|
+
const args = [
|
|
103
|
+
"-p",
|
|
104
|
+
prompt,
|
|
105
|
+
"--output-format",
|
|
106
|
+
"stream-json",
|
|
107
|
+
"--verbose",
|
|
108
|
+
"--max-turns",
|
|
109
|
+
String(maxTurns)
|
|
110
|
+
];
|
|
111
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
112
|
+
for (const tool of allowedTools) {
|
|
113
|
+
args.push("--allowedTools", tool);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const subprocess = execa("claude", args, {
|
|
117
|
+
stdin: "ignore",
|
|
118
|
+
reject: false,
|
|
119
|
+
timeout,
|
|
120
|
+
stripFinalNewline: false
|
|
121
|
+
});
|
|
122
|
+
let resultText = "";
|
|
123
|
+
let isError = false;
|
|
124
|
+
let costData = null;
|
|
125
|
+
const state = { turnCount: 0 };
|
|
126
|
+
if (subprocess.stdout) {
|
|
127
|
+
const rl = createInterface({ input: subprocess.stdout });
|
|
128
|
+
for await (const line of rl) {
|
|
129
|
+
printStreamEvent(line, state, onOutput);
|
|
130
|
+
try {
|
|
131
|
+
const event = JSON.parse(line);
|
|
132
|
+
if (event.type === "result") {
|
|
133
|
+
resultText = event.result ?? "";
|
|
134
|
+
isError = event.is_error === true;
|
|
135
|
+
if (event.total_cost_usd != null && event.usage) {
|
|
136
|
+
costData = {
|
|
137
|
+
inputTokens: event.usage.input_tokens,
|
|
138
|
+
outputTokens: event.usage.output_tokens
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const proc = await subprocess;
|
|
147
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1e3);
|
|
148
|
+
const durationStr = formatDuration(elapsedSec);
|
|
149
|
+
const doneLine = colored ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`) : ` ${icons.connector} Done in ${durationStr}`;
|
|
150
|
+
emit(doneLine);
|
|
151
|
+
emit(connectorLine);
|
|
152
|
+
if (proc.exitCode !== 0 && !resultText) {
|
|
153
|
+
const stderr = proc.stderr?.toString() ?? "";
|
|
154
|
+
const isTimeout = proc.exitCode === 143 || "signalName" in proc && proc.signalName === "SIGTERM";
|
|
155
|
+
const errorMsg = isTimeout ? `Headless invocation timed out after ${timeout}ms` : stderr || `claude exited with code ${proc.exitCode}`;
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
result: "",
|
|
159
|
+
cost: null,
|
|
160
|
+
error: errorMsg
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
success: !isError,
|
|
165
|
+
result: resultText,
|
|
166
|
+
cost: costData,
|
|
167
|
+
error: isError ? resultText : null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function runHeadless(options) {
|
|
171
|
+
const {
|
|
172
|
+
prompt,
|
|
173
|
+
maxTurns = 10,
|
|
174
|
+
timeout = 3e5,
|
|
175
|
+
outputFormat = "json",
|
|
176
|
+
allowedTools,
|
|
177
|
+
statusMessage,
|
|
178
|
+
onOutput
|
|
179
|
+
} = options;
|
|
180
|
+
if (isVerbose()) {
|
|
181
|
+
const effectiveOnOutput = onOutput ?? getOutputCallback();
|
|
182
|
+
return runHeadlessVerbose(
|
|
183
|
+
prompt,
|
|
184
|
+
maxTurns,
|
|
185
|
+
timeout,
|
|
186
|
+
allowedTools,
|
|
187
|
+
statusMessage,
|
|
188
|
+
effectiveOnOutput
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const spinner = statusMessage ? createSpinner(statusMessage).start() : null;
|
|
192
|
+
let timer = null;
|
|
193
|
+
if (spinner) {
|
|
194
|
+
timer = new ElapsedTimer((elapsed) => {
|
|
195
|
+
spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;
|
|
196
|
+
}).start();
|
|
197
|
+
}
|
|
198
|
+
const args = [
|
|
199
|
+
"-p",
|
|
200
|
+
prompt,
|
|
201
|
+
"--output-format",
|
|
202
|
+
outputFormat,
|
|
203
|
+
"--max-turns",
|
|
204
|
+
String(maxTurns)
|
|
205
|
+
];
|
|
206
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
207
|
+
for (const tool of allowedTools) {
|
|
208
|
+
args.push("--allowedTools", tool);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const proc = await execa("claude", args, {
|
|
213
|
+
stdin: "ignore",
|
|
214
|
+
reject: false,
|
|
215
|
+
timeout,
|
|
216
|
+
stripFinalNewline: false
|
|
217
|
+
});
|
|
218
|
+
const stdout = proc.stdout?.toString() ?? "";
|
|
219
|
+
const stderr = proc.stderr?.toString() ?? "";
|
|
220
|
+
if (proc.exitCode !== 0) {
|
|
221
|
+
const elapsed2 = timer?.stop() ?? 0;
|
|
222
|
+
const dur2 = useColor() ? chalk.dim(` (${formatDuration(elapsed2)})`) : ` (${formatDuration(elapsed2)})`;
|
|
223
|
+
spinner?.fail(`${statusMessage}${dur2}`);
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
result: "",
|
|
227
|
+
cost: null,
|
|
228
|
+
error: stderr || stdout || `claude exited with code ${proc.exitCode}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const elapsed = timer?.stop() ?? 0;
|
|
232
|
+
const dur = useColor() ? chalk.dim(` (${formatDuration(elapsed)})`) : ` (${formatDuration(elapsed)})`;
|
|
233
|
+
spinner?.succeed(`${statusMessage}${dur}`);
|
|
234
|
+
if (outputFormat === "json") {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(stdout);
|
|
237
|
+
return {
|
|
238
|
+
success: true,
|
|
239
|
+
result: parsed.result ?? stdout,
|
|
240
|
+
cost: parsed.cost_usd != null ? { inputTokens: parsed.num_input_tokens, outputTokens: parsed.num_output_tokens } : null,
|
|
241
|
+
error: null
|
|
242
|
+
};
|
|
243
|
+
} catch {
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
result: stdout,
|
|
247
|
+
cost: null,
|
|
248
|
+
error: null
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
result: stdout,
|
|
255
|
+
cost: null,
|
|
256
|
+
error: null
|
|
257
|
+
};
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
260
|
+
const catchElapsed = timer?.stop() ?? 0;
|
|
261
|
+
const catchDur = useColor() ? chalk.dim(` (${formatDuration(catchElapsed)})`) : ` (${formatDuration(catchElapsed)})`;
|
|
262
|
+
spinner?.fail(`${statusMessage}${catchDur}`);
|
|
263
|
+
if (message.includes("timed out") || message.includes("ETIMEDOUT")) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
result: "",
|
|
267
|
+
cost: null,
|
|
268
|
+
error: `Headless invocation timed out after ${timeout}ms`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
result: "",
|
|
274
|
+
cost: null,
|
|
275
|
+
error: message
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
runHeadless
|
|
282
|
+
};
|
|
283
|
+
//# sourceMappingURL=chunk-TCOS2KFB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/headless.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { createSpinner, ElapsedTimer, formatDuration, getIcons, useColor } from '../ui/logger.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { isoNow } from './state-manager.js';\nimport { getOutputCallback, isVerbose } from './verbose.js';\n\nexport interface HeadlessOptions {\n prompt: string;\n maxTurns?: number;\n timeout?: number;\n outputFormat?: 'json' | 'text' | 'stream-json';\n allowedTools?: string[];\n /** Status message displayed as spinner (non-verbose) or header (verbose). */\n statusMessage?: string;\n /** Optional callback for routing verbose output (e.g., through listr2 task.output). When provided, verbose stream events are sent here instead of directly to stderr. */\n onOutput?: (line: string) => void;\n}\n\nexport interface HeadlessCost {\n inputTokens?: number;\n outputTokens?: number;\n}\n\nexport interface HeadlessResult {\n success: boolean;\n result: string;\n cost: HeadlessCost | null;\n error: string | null;\n}\n\n/* ── verbose stream formatting ──────────────────────────────────────────── */\n\n/**\n * Extract a short context string from a tool_use input object.\n */\nfunction getToolContext(name: string, input: Record<string, unknown>): string {\n switch (name) {\n case 'Read':\n return shortPath(input.file_path as string);\n case 'Write':\n case 'Edit':\n return shortPath(input.file_path as string);\n case 'Glob':\n return (input.pattern as string) ?? '';\n case 'Grep':\n return (input.pattern as string) ?? '';\n case 'Bash': {\n const cmd = (input.command as string) ?? '';\n return cmd.length > 60 ? `${cmd.substring(0, 57)}...` : cmd;\n }\n default:\n return '';\n }\n}\n\n/**\n * Shorten a file path to be relative-friendly.\n */\nfunction shortPath(filePath: string | undefined): string {\n if (!filePath) return '';\n const cwd = process.cwd();\n if (filePath.startsWith(cwd)) {\n return filePath.substring(cwd.length + 1);\n }\n // Show last 2 segments for absolute paths\n const parts = filePath.split('/');\n if (parts.length > 2) {\n return `.../${parts.slice(-2).join('/')}`;\n }\n return filePath;\n}\n\n/**\n * Print a formatted stream event line to stderr.\n */\nfunction printStreamEvent(\n line: string,\n state: { turnCount: number },\n onOutput?: (line: string) => void,\n): void {\n let event: {\n type?: string;\n subtype?: string;\n message?: {\n content?: { type: string; text?: string; name?: string; input?: Record<string, unknown> }[];\n };\n };\n\n try {\n event = JSON.parse(line);\n } catch {\n return;\n }\n\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n if (event.type === 'assistant' && event.message?.content) {\n state.turnCount++;\n\n for (const block of event.message.content) {\n if (block.type === 'text' && block.text) {\n // Wrap text with connector prefix\n const lines = block.text.split('\\n');\n for (const textLine of lines) {\n if (!textLine.trim()) continue;\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const text = colored ? chalk.dim(textLine) : textLine;\n emit(`${prefix}${text}`);\n }\n }\n\n if (block.type === 'tool_use' && block.name) {\n const context = block.input ? getToolContext(block.name, block.input) : '';\n getSessionPublisher().publish({\n type: 'activity',\n at: isoNow(),\n tool: block.name,\n detail: context || undefined,\n });\n const toolName = block.name.padEnd(12);\n\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;\n const toolLabel = colored ? chalk.cyan(toolName) : toolName;\n const contextText = context ? (colored ? chalk.dim(context) : context) : '';\n\n emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);\n }\n }\n }\n}\n\n/* ── verbose execution ──────────────────────────────────────────────────── */\n\n/**\n * Run headless in verbose mode using stream-json to display real-time progress.\n */\nasync function runHeadlessVerbose(\n prompt: string,\n maxTurns: number,\n timeout: number,\n allowedTools?: string[],\n statusMessage?: string,\n onOutput?: (line: string) => void,\n): Promise<HeadlessResult> {\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n // Print header\n if (statusMessage) {\n const msg = colored\n ? chalk.blue(`${icons.start} ${statusMessage}`)\n : `${icons.start} ${statusMessage}`;\n emit(msg);\n }\n\n // Print opening connector\n const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;\n emit(connectorLine);\n\n const startTime = Date.now();\n\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n 'stream-json',\n '--verbose',\n '--max-turns',\n String(maxTurns),\n ];\n\n if (allowedTools && allowedTools.length > 0) {\n for (const tool of allowedTools) {\n args.push('--allowedTools', tool);\n }\n }\n\n const subprocess = execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n\n let resultText = '';\n let isError = false;\n let costData: HeadlessCost | null = null;\n const state = { turnCount: 0 };\n\n if (subprocess.stdout) {\n const rl = createInterface({ input: subprocess.stdout });\n for await (const line of rl) {\n printStreamEvent(line, state, onOutput);\n\n try {\n const event = JSON.parse(line);\n if (event.type === 'result') {\n resultText = event.result ?? '';\n isError = event.is_error === true;\n if (event.total_cost_usd != null && event.usage) {\n costData = {\n inputTokens: event.usage.input_tokens,\n outputTokens: event.usage.output_tokens,\n };\n }\n }\n } catch {\n // ignore malformed lines\n }\n }\n }\n\n // Wait for the process to finish\n const proc = await subprocess;\n\n // Close connector with elapsed time\n const elapsedSec = Math.floor((Date.now() - startTime) / 1000);\n const durationStr = formatDuration(elapsedSec);\n const doneLine = colored\n ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`)\n : ` ${icons.connector} Done in ${durationStr}`;\n emit(doneLine);\n emit(connectorLine);\n\n if (proc.exitCode !== 0 && !resultText) {\n const stderr = proc.stderr?.toString() ?? '';\n const isTimeout =\n proc.exitCode === 143 || ('signalName' in proc && proc.signalName === 'SIGTERM');\n const errorMsg = isTimeout\n ? `Headless invocation timed out after ${timeout}ms`\n : stderr || `claude exited with code ${proc.exitCode}`;\n return {\n success: false,\n result: '',\n cost: null,\n error: errorMsg,\n };\n }\n\n return {\n success: !isError,\n result: resultText,\n cost: costData,\n error: isError ? resultText : null,\n };\n}\n\n/* ── standard execution ─────────────────────────────────────────────────── */\n\n/**\n * Invoke Claude Code in headless mode via `claude -p`.\n *\n * Each invocation is an isolated session — no context is shared between calls.\n * Output is parsed as JSON when outputFormat is 'json' (default).\n *\n * When verbose mode is active, uses stream-json to display real-time progress.\n * Otherwise, shows a spinner while waiting.\n */\nexport async function runHeadless(options: HeadlessOptions): Promise<HeadlessResult> {\n const {\n prompt,\n maxTurns = 10,\n timeout = 300_000,\n outputFormat = 'json',\n allowedTools,\n statusMessage,\n onOutput,\n } = options;\n\n if (isVerbose()) {\n // Use explicit onOutput, fall back to global output callback, or default to stderr\n const effectiveOnOutput = onOutput ?? getOutputCallback();\n return runHeadlessVerbose(\n prompt,\n maxTurns,\n timeout,\n allowedTools,\n statusMessage,\n effectiveOnOutput,\n );\n }\n\n // Non-verbose: use spinner with elapsed timer\n const spinner = statusMessage ? createSpinner(statusMessage).start() : null;\n\n let timer: ElapsedTimer | null = null;\n if (spinner) {\n timer = new ElapsedTimer((elapsed) => {\n spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;\n }).start();\n }\n\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n outputFormat,\n '--max-turns',\n String(maxTurns),\n ];\n\n if (allowedTools && allowedTools.length > 0) {\n for (const tool of allowedTools) {\n args.push('--allowedTools', tool);\n }\n }\n\n try {\n const proc = await execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n\n const stdout = proc.stdout?.toString() ?? '';\n const stderr = proc.stderr?.toString() ?? '';\n\n if (proc.exitCode !== 0) {\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.fail(`${statusMessage}${dur}`);\n return {\n success: false,\n result: '',\n cost: null,\n error: stderr || stdout || `claude exited with code ${proc.exitCode}`,\n };\n }\n\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.succeed(`${statusMessage}${dur}`);\n\n if (outputFormat === 'json') {\n try {\n const parsed = JSON.parse(stdout);\n return {\n success: true,\n result: parsed.result ?? stdout,\n cost:\n parsed.cost_usd != null\n ? { inputTokens: parsed.num_input_tokens, outputTokens: parsed.num_output_tokens }\n : null,\n error: null,\n };\n } catch {\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n }\n }\n\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const catchElapsed = timer?.stop() ?? 0;\n const catchDur = useColor()\n ? chalk.dim(` (${formatDuration(catchElapsed)})`)\n : ` (${formatDuration(catchElapsed)})`;\n spinner?.fail(`${statusMessage}${catchDur}`);\n\n if (message.includes('timed out') || message.includes('ETIMEDOUT')) {\n return {\n success: false,\n result: '',\n cost: null,\n error: `Headless invocation timed out after ${timeout}ms`,\n };\n }\n\n return {\n success: false,\n result: '',\n cost: null,\n error: message,\n };\n }\n}\n"],"mappings":"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","names":["elapsed","dur"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getBaseBranch,
|
|
4
|
+
getCommitsSince,
|
|
5
|
+
getCurrentBranch,
|
|
6
|
+
run
|
|
7
|
+
} from "./chunk-2AVZZAC6.js";
|
|
8
|
+
import {
|
|
9
|
+
NullPublisher,
|
|
10
|
+
isoNow
|
|
11
|
+
} from "./chunk-JNEHVQH5.js";
|
|
12
|
+
|
|
13
|
+
// src/core/session-git.ts
|
|
14
|
+
async function listPullRequests(branch) {
|
|
15
|
+
if (!branch) return [];
|
|
16
|
+
const result = await run("gh", [
|
|
17
|
+
"pr",
|
|
18
|
+
"list",
|
|
19
|
+
"--head",
|
|
20
|
+
branch,
|
|
21
|
+
"--state",
|
|
22
|
+
"all",
|
|
23
|
+
"--json",
|
|
24
|
+
"number,url,title",
|
|
25
|
+
"--limit",
|
|
26
|
+
"10"
|
|
27
|
+
]);
|
|
28
|
+
if (result.exitCode !== 0) return [];
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(result.stdout || "[]");
|
|
31
|
+
if (!Array.isArray(parsed)) return [];
|
|
32
|
+
return parsed.filter(
|
|
33
|
+
(pr) => typeof pr === "object" && pr !== null && typeof pr.number === "number" && typeof pr.url === "string" && typeof pr.title === "string"
|
|
34
|
+
).map((pr) => ({ number: pr.number, url: pr.url, title: pr.title }));
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var defaultSources = {
|
|
40
|
+
currentBranch: getCurrentBranch,
|
|
41
|
+
baseBranch: getBaseBranch,
|
|
42
|
+
commitsSince: getCommitsSince,
|
|
43
|
+
pullRequests: listPullRequests,
|
|
44
|
+
now: isoNow
|
|
45
|
+
};
|
|
46
|
+
async function publishGitState(publisher, sources = {}) {
|
|
47
|
+
if (publisher instanceof NullPublisher) return;
|
|
48
|
+
const src = { ...defaultSources, ...sources };
|
|
49
|
+
try {
|
|
50
|
+
const branch = await src.currentBranch();
|
|
51
|
+
const baseBranch = await src.baseBranch();
|
|
52
|
+
const [commits, pullRequests] = await Promise.all([
|
|
53
|
+
src.commitsSince(baseBranch),
|
|
54
|
+
src.pullRequests(branch)
|
|
55
|
+
]);
|
|
56
|
+
publisher.publish({
|
|
57
|
+
type: "git:update",
|
|
58
|
+
at: src.now(),
|
|
59
|
+
branch,
|
|
60
|
+
baseBranch,
|
|
61
|
+
commits,
|
|
62
|
+
pullRequests
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
listPullRequests,
|
|
70
|
+
publishGitState
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=chunk-WHIBTNYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/session-git.ts"],"sourcesContent":["import { getBaseBranch, getCommitsSince, getCurrentBranch } from '../utils/git.js';\nimport { run } from '../utils/shell.js';\nimport {\n NullPublisher,\n type SessionCommit,\n type SessionPublisher,\n type SessionPullRequest,\n} from './session-state.js';\nimport { isoNow } from './state-manager.js';\n\n/**\n * Low-frequency enrichment of the session snapshot with git commits and pull\n * requests. Called only at phase boundaries (run.ts) and at the end of each\n * iteration (engine.ts) — never per HTTP request: the server (US-006) serves\n * the in-memory snapshot as-is.\n *\n * Like every monitoring surface, this must never affect the pipeline: with\n * the NullPublisher installed it returns before spawning any subprocess, and\n * every failure is swallowed.\n */\n\n/** Data sources, injectable for tests. */\nexport interface GitStateSources {\n currentBranch(): Promise<string>;\n baseBranch(): Promise<string>;\n commitsSince(base: string): Promise<SessionCommit[]>;\n pullRequests(branch: string): Promise<SessionPullRequest[]>;\n now(): string;\n}\n\n/**\n * List PRs whose head is the given branch via the GitHub CLI. Never throws;\n * returns [] when gh is unavailable, unauthenticated or returns bad JSON.\n */\nexport async function listPullRequests(branch: string): Promise<SessionPullRequest[]> {\n if (!branch) return [];\n\n const result = await run('gh', [\n 'pr',\n 'list',\n '--head',\n branch,\n '--state',\n 'all',\n '--json',\n 'number,url,title',\n '--limit',\n '10',\n ]);\n if (result.exitCode !== 0) return [];\n\n try {\n const parsed: unknown = JSON.parse(result.stdout || '[]');\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter(\n (pr): pr is { number: number; url: string; title: string } =>\n typeof pr === 'object' &&\n pr !== null &&\n typeof (pr as { number?: unknown }).number === 'number' &&\n typeof (pr as { url?: unknown }).url === 'string' &&\n typeof (pr as { title?: unknown }).title === 'string',\n )\n .map((pr) => ({ number: pr.number, url: pr.url, title: pr.title }));\n } catch {\n return [];\n }\n}\n\nconst defaultSources: GitStateSources = {\n currentBranch: getCurrentBranch,\n baseBranch: getBaseBranch,\n commitsSince: getCommitsSince,\n pullRequests: listPullRequests,\n now: isoNow,\n};\n\n/**\n * Gather branch, base branch, commits and PRs, then publish a single\n * git:update event. Never throws.\n */\nexport async function publishGitState(\n publisher: SessionPublisher,\n sources: Partial<GitStateSources> = {},\n): Promise<void> {\n if (publisher instanceof NullPublisher) return;\n const src: GitStateSources = { ...defaultSources, ...sources };\n\n try {\n const branch = await src.currentBranch();\n const baseBranch = await src.baseBranch();\n const [commits, pullRequests] = await Promise.all([\n src.commitsSince(baseBranch),\n src.pullRequests(branch),\n ]);\n\n publisher.publish({\n type: 'git:update',\n at: src.now(),\n branch,\n baseBranch,\n commits,\n pullRequests,\n });\n } catch {\n // Monitoring enrichment must never propagate errors to the pipeline.\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAkCA,eAAsB,iBAAiB,QAA+C;AACpF,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,SAAS,MAAM,IAAI,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,OAAO,aAAa,EAAG,QAAO,CAAC;AAEnC,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,OAAO,UAAU,IAAI;AACxD,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,WAAO,OACJ;AAAA,MACC,CAAC,OACC,OAAO,OAAO,YACd,OAAO,QACP,OAAQ,GAA4B,WAAW,YAC/C,OAAQ,GAAyB,QAAQ,YACzC,OAAQ,GAA2B,UAAU;AAAA,IACjD,EACC,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,KAAK,GAAG,KAAK,OAAO,GAAG,MAAM,EAAE;AAAA,EACtE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,IAAM,iBAAkC;AAAA,EACtC,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,cAAc;AAAA,EACd,KAAK;AACP;AAMA,eAAsB,gBACpB,WACA,UAAoC,CAAC,GACtB;AACf,MAAI,qBAAqB,cAAe;AACxC,QAAM,MAAuB,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAE7D,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,cAAc;AACvC,UAAM,aAAa,MAAM,IAAI,WAAW;AACxC,UAAM,CAAC,SAAS,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,MAChD,IAAI,aAAa,UAAU;AAAA,MAC3B,IAAI,aAAa,MAAM;AAAA,IACzB,CAAC;AAED,cAAU,QAAQ;AAAA,MAChB,MAAM;AAAA,MACN,IAAI,IAAI,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
@@ -4,21 +4,19 @@ import {
|
|
|
4
4
|
retryDelaySeconds,
|
|
5
5
|
sleep
|
|
6
6
|
} from "./chunk-HDV3YDTG.js";
|
|
7
|
+
import {
|
|
8
|
+
publishGitState
|
|
9
|
+
} from "./chunk-WHIBTNYI.js";
|
|
7
10
|
import {
|
|
8
11
|
applyPlaceholders,
|
|
9
12
|
loadPrompt
|
|
10
13
|
} from "./chunk-SLHZPB3C.js";
|
|
11
14
|
import {
|
|
12
15
|
createConfig,
|
|
13
|
-
getBaseBranch,
|
|
14
|
-
getCommitsSince,
|
|
15
|
-
getCurrentBranch,
|
|
16
16
|
resolvePaths,
|
|
17
|
-
run,
|
|
18
17
|
validateDependencies
|
|
19
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2AVZZAC6.js";
|
|
20
19
|
import {
|
|
21
|
-
NullPublisher,
|
|
22
20
|
allStoriesPass,
|
|
23
21
|
clearLastError,
|
|
24
22
|
formatDuration,
|
|
@@ -27,6 +25,7 @@ import {
|
|
|
27
25
|
getSessionPublisher,
|
|
28
26
|
getStoryUpdateCallback,
|
|
29
27
|
getTermWidth,
|
|
28
|
+
hasPendingCorrection,
|
|
30
29
|
initializeState,
|
|
31
30
|
isVerbose,
|
|
32
31
|
isoNow,
|
|
@@ -43,7 +42,7 @@ import {
|
|
|
43
42
|
trimErrorMessage,
|
|
44
43
|
useColor,
|
|
45
44
|
useUnicode
|
|
46
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-JNEHVQH5.js";
|
|
47
46
|
|
|
48
47
|
// src/core/engine.ts
|
|
49
48
|
import { existsSync } from "fs";
|
|
@@ -214,6 +213,38 @@ function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan,
|
|
|
214
213
|
console.log("");
|
|
215
214
|
printBox(boxLines);
|
|
216
215
|
}
|
|
216
|
+
function buildRunSummaryLines(info) {
|
|
217
|
+
const lines = [
|
|
218
|
+
` Branch: ${info.branchName}${info.noBranch ? " (current)" : ""}`,
|
|
219
|
+
` Stories: ${info.storyCount}`,
|
|
220
|
+
` Duration: ${formatDuration(info.elapsedSeconds)}`
|
|
221
|
+
];
|
|
222
|
+
if (!info.noBranch) {
|
|
223
|
+
lines.push(` PR: ${info.prUrl}`);
|
|
224
|
+
}
|
|
225
|
+
const review = info.prReview;
|
|
226
|
+
if (review) {
|
|
227
|
+
const verdict = review.recommendation ?? (review.requestedChanges ? "REQUEST_CHANGES" : "unknown");
|
|
228
|
+
lines.push(` Review: ${verdict}`);
|
|
229
|
+
if (review.reportPath !== null) {
|
|
230
|
+
lines.push(` Report: ${review.reportPath}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return lines;
|
|
234
|
+
}
|
|
235
|
+
function printRunSummary(info) {
|
|
236
|
+
console.log("");
|
|
237
|
+
if (info.prReview?.requestedChanges) {
|
|
238
|
+
printWarning(
|
|
239
|
+
`Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`
|
|
240
|
+
);
|
|
241
|
+
} else {
|
|
242
|
+
printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);
|
|
243
|
+
}
|
|
244
|
+
for (const line of buildRunSummaryLines(info)) {
|
|
245
|
+
console.log(line);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
217
248
|
|
|
218
249
|
// src/core/executor.ts
|
|
219
250
|
import { execa } from "execa";
|
|
@@ -242,61 +273,6 @@ ${stderr}` : "");
|
|
|
242
273
|
};
|
|
243
274
|
}
|
|
244
275
|
|
|
245
|
-
// src/core/session-git.ts
|
|
246
|
-
async function listPullRequests(branch) {
|
|
247
|
-
if (!branch) return [];
|
|
248
|
-
const result = await run("gh", [
|
|
249
|
-
"pr",
|
|
250
|
-
"list",
|
|
251
|
-
"--head",
|
|
252
|
-
branch,
|
|
253
|
-
"--state",
|
|
254
|
-
"all",
|
|
255
|
-
"--json",
|
|
256
|
-
"number,url,title",
|
|
257
|
-
"--limit",
|
|
258
|
-
"10"
|
|
259
|
-
]);
|
|
260
|
-
if (result.exitCode !== 0) return [];
|
|
261
|
-
try {
|
|
262
|
-
const parsed = JSON.parse(result.stdout || "[]");
|
|
263
|
-
if (!Array.isArray(parsed)) return [];
|
|
264
|
-
return parsed.filter(
|
|
265
|
-
(pr) => typeof pr === "object" && pr !== null && typeof pr.number === "number" && typeof pr.url === "string" && typeof pr.title === "string"
|
|
266
|
-
).map((pr) => ({ number: pr.number, url: pr.url, title: pr.title }));
|
|
267
|
-
} catch {
|
|
268
|
-
return [];
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
var defaultSources = {
|
|
272
|
-
currentBranch: getCurrentBranch,
|
|
273
|
-
baseBranch: getBaseBranch,
|
|
274
|
-
commitsSince: getCommitsSince,
|
|
275
|
-
pullRequests: listPullRequests,
|
|
276
|
-
now: isoNow
|
|
277
|
-
};
|
|
278
|
-
async function publishGitState(publisher, sources = {}) {
|
|
279
|
-
if (publisher instanceof NullPublisher) return;
|
|
280
|
-
const src = { ...defaultSources, ...sources };
|
|
281
|
-
try {
|
|
282
|
-
const branch = await src.currentBranch();
|
|
283
|
-
const baseBranch = await src.baseBranch();
|
|
284
|
-
const [commits, pullRequests] = await Promise.all([
|
|
285
|
-
src.commitsSince(baseBranch),
|
|
286
|
-
src.pullRequests(branch)
|
|
287
|
-
]);
|
|
288
|
-
publisher.publish({
|
|
289
|
-
type: "git:update",
|
|
290
|
-
at: src.now(),
|
|
291
|
-
branch,
|
|
292
|
-
baseBranch,
|
|
293
|
-
commits,
|
|
294
|
-
pullRequests
|
|
295
|
-
});
|
|
296
|
-
} catch {
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
276
|
// src/core/engine.ts
|
|
301
277
|
function emitLog(message) {
|
|
302
278
|
const cb = getOutputCallback();
|
|
@@ -370,7 +346,7 @@ async function runEngine(config, paths) {
|
|
|
370
346
|
let plan = await loadTaskPlan(paths.prdFile);
|
|
371
347
|
plan = initializeState(plan);
|
|
372
348
|
await saveTaskPlan(paths.prdFile, plan);
|
|
373
|
-
if (plan.issueStatus === "completed" && allStoriesPass(plan)) {
|
|
349
|
+
if (plan.issueStatus === "completed" && allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
374
350
|
emitLog(`Issue already marked complete in ${paths.prdFile}`);
|
|
375
351
|
return 0;
|
|
376
352
|
}
|
|
@@ -386,7 +362,7 @@ async function runEngine(config, paths) {
|
|
|
386
362
|
);
|
|
387
363
|
await saveTaskPlan(paths.prdFile, plan);
|
|
388
364
|
}
|
|
389
|
-
if (allStoriesPass(plan)) {
|
|
365
|
+
if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
390
366
|
emitLog("All user stories already pass. Marking issue as completed.");
|
|
391
367
|
plan = markIssueCompleted(plan);
|
|
392
368
|
await saveTaskPlan(paths.prdFile, plan);
|
|
@@ -493,7 +469,7 @@ async function runEngine(config, paths) {
|
|
|
493
469
|
await publishGitState(getSessionPublisher());
|
|
494
470
|
if (result.output.includes("<promise>COMPLETE</promise>")) {
|
|
495
471
|
plan = await loadTaskPlan(paths.prdFile);
|
|
496
|
-
if (allStoriesPass(plan)) {
|
|
472
|
+
if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
497
473
|
plan = markIssueCompleted(plan);
|
|
498
474
|
await saveTaskPlan(paths.prdFile, plan);
|
|
499
475
|
const elapsed2 = Math.floor((Date.now() - startTime) / 1e3);
|
|
@@ -503,12 +479,12 @@ async function runEngine(config, paths) {
|
|
|
503
479
|
plan = setLastError(
|
|
504
480
|
plan,
|
|
505
481
|
"invalid_completion_signal",
|
|
506
|
-
"Claude returned <promise>COMPLETE</promise> before every story had passes=true."
|
|
482
|
+
hasPendingCorrection(plan) ? "Claude returned <promise>COMPLETE</promise> while lastReviewFindings was still set." : "Claude returned <promise>COMPLETE</promise> before every story had passes=true."
|
|
507
483
|
);
|
|
508
484
|
await saveTaskPlan(paths.prdFile, plan);
|
|
509
485
|
emitLog("");
|
|
510
486
|
printWarning(
|
|
511
|
-
"Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing."
|
|
487
|
+
hasPendingCorrection(plan) ? "Claude returned a completion signal, but lastReviewFindings is still set. Ignoring completion and continuing." : "Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing."
|
|
512
488
|
);
|
|
513
489
|
}
|
|
514
490
|
if (isVerbose()) {
|
|
@@ -562,7 +538,7 @@ async function runExecute(positionalMaxIter, options) {
|
|
|
562
538
|
}
|
|
563
539
|
|
|
564
540
|
export {
|
|
565
|
-
|
|
541
|
+
printRunSummary,
|
|
566
542
|
runExecute
|
|
567
543
|
};
|
|
568
|
-
//# sourceMappingURL=chunk-
|
|
544
|
+
//# sourceMappingURL=chunk-YGEWIEAI.js.map
|