@tarcisiopgs/lisa 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +114 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -638,9 +638,11 @@ ${readmeBlock}${hookBlock}
|
|
|
638
638
|
{
|
|
639
639
|
"repoPath": "<absolute path to the chosen repo>",
|
|
640
640
|
"branch": "<your English branch name>",
|
|
641
|
-
"prTitle": "<PR title in English, conventional commit format>"
|
|
641
|
+
"prTitle": "<PR title in English, conventional commit format>",
|
|
642
|
+
"prBody": "<English summary of what was implemented and why, 2-5 sentences>"
|
|
642
643
|
}
|
|
643
644
|
\`\`\`
|
|
645
|
+
The \`prBody\` should describe WHAT was changed and WHY, not just repeat the title. Mention key files modified, new behavior added, or bugs fixed. Write in English.
|
|
644
646
|
Do NOT commit this file.
|
|
645
647
|
|
|
646
648
|
## Rules
|
|
@@ -699,8 +701,9 @@ ${testBlock}${readmeBlock}${hookBlock}
|
|
|
699
701
|
|
|
700
702
|
4. **Write manifest**: Create \`.lisa-manifest.json\` in the **current directory** with JSON:
|
|
701
703
|
\`\`\`json
|
|
702
|
-
{"branch": "<final English branch name>", "prTitle": "<English PR title, conventional commit format>"}
|
|
704
|
+
{"branch": "<final English branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<English summary of what was implemented and why, 2-5 sentences>"}
|
|
703
705
|
\`\`\`
|
|
706
|
+
The \`prBody\` should describe WHAT was changed and WHY, not just repeat the title. Mention key files modified, new behavior added, or bugs fixed. Write in English.
|
|
704
707
|
Do NOT commit this file.
|
|
705
708
|
|
|
706
709
|
## Rules
|
|
@@ -768,8 +771,9 @@ ${testBlock}${readmeBlock}${hookBlock}
|
|
|
768
771
|
|
|
769
772
|
6. **Write manifest**: Before finishing, create \`${manifestPath}\` with JSON:
|
|
770
773
|
\`\`\`json
|
|
771
|
-
{"repoPath": "<absolute path to this repo>", "branch": "<branch name>", "prTitle": "<English PR title, conventional commit format>"}
|
|
774
|
+
{"repoPath": "<absolute path to this repo>", "branch": "<branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<English summary of what was implemented and why, 2-5 sentences>"}
|
|
772
775
|
\`\`\`
|
|
776
|
+
The \`prBody\` should describe WHAT was changed and WHY, not just repeat the title. Mention key files modified, new behavior added, or bugs fixed. Write in English.
|
|
773
777
|
Do NOT commit this file.
|
|
774
778
|
|
|
775
779
|
## Rules
|
|
@@ -1715,6 +1719,50 @@ function createSource(name) {
|
|
|
1715
1719
|
return factory();
|
|
1716
1720
|
}
|
|
1717
1721
|
|
|
1722
|
+
// src/terminal.ts
|
|
1723
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1724
|
+
var SPINNER_INTERVAL_MS = 80;
|
|
1725
|
+
var spinnerTimer = null;
|
|
1726
|
+
var spinnerFrame = 0;
|
|
1727
|
+
function isTTY() {
|
|
1728
|
+
return process.stdout.isTTY === true;
|
|
1729
|
+
}
|
|
1730
|
+
function writeOSC(title) {
|
|
1731
|
+
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
1732
|
+
}
|
|
1733
|
+
function setTitle(title) {
|
|
1734
|
+
if (!isTTY()) return;
|
|
1735
|
+
writeOSC(title);
|
|
1736
|
+
}
|
|
1737
|
+
function startSpinner(message) {
|
|
1738
|
+
if (!isTTY()) return;
|
|
1739
|
+
stopSpinner();
|
|
1740
|
+
spinnerFrame = 0;
|
|
1741
|
+
writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
|
|
1742
|
+
spinnerTimer = setInterval(() => {
|
|
1743
|
+
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
1744
|
+
writeOSC(`${SPINNER_FRAMES[spinnerFrame]} Lisa \u2014 ${message}`);
|
|
1745
|
+
}, SPINNER_INTERVAL_MS);
|
|
1746
|
+
}
|
|
1747
|
+
function stopSpinner(message) {
|
|
1748
|
+
if (spinnerTimer) {
|
|
1749
|
+
clearInterval(spinnerTimer);
|
|
1750
|
+
spinnerTimer = null;
|
|
1751
|
+
}
|
|
1752
|
+
if (!isTTY()) return;
|
|
1753
|
+
if (message) {
|
|
1754
|
+
writeOSC(message);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
function notify() {
|
|
1758
|
+
if (!isTTY()) return;
|
|
1759
|
+
process.stdout.write("\x07");
|
|
1760
|
+
}
|
|
1761
|
+
function resetTitle() {
|
|
1762
|
+
if (!isTTY()) return;
|
|
1763
|
+
writeOSC("");
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1718
1766
|
// src/worktree.ts
|
|
1719
1767
|
import { appendFileSync as appendFileSync5, existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1720
1768
|
import { join as join6, resolve as resolve4 } from "path";
|
|
@@ -1851,10 +1899,15 @@ function resolveModels(config2) {
|
|
|
1851
1899
|
if (config2.models && config2.models.length > 0) return config2.models;
|
|
1852
1900
|
return [config2.provider];
|
|
1853
1901
|
}
|
|
1854
|
-
function buildPrBody(
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1902
|
+
function buildPrBody(providerUsed, description) {
|
|
1903
|
+
const lines = [];
|
|
1904
|
+
if (description) {
|
|
1905
|
+
lines.push(description, "");
|
|
1906
|
+
}
|
|
1907
|
+
lines.push(
|
|
1908
|
+
`Implemented by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerUsed}**.`
|
|
1909
|
+
);
|
|
1910
|
+
return lines.join("\n");
|
|
1858
1911
|
}
|
|
1859
1912
|
var PR_TITLE_FILE = ".pr-title";
|
|
1860
1913
|
function readPrTitle(cwd) {
|
|
@@ -1946,6 +1999,8 @@ function installSignalHandlers() {
|
|
|
1946
1999
|
process.exit(1);
|
|
1947
2000
|
}
|
|
1948
2001
|
shuttingDown = true;
|
|
2002
|
+
stopSpinner();
|
|
2003
|
+
resetTitle();
|
|
1949
2004
|
warn(`Received ${signal}. Reverting active issue...`);
|
|
1950
2005
|
if (activeCleanup) {
|
|
1951
2006
|
const { issueId, previousStatus, source } = activeCleanup;
|
|
@@ -2022,12 +2077,14 @@ async function runLoop(config2, opts) {
|
|
|
2022
2077
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
2023
2078
|
const logFile = resolve5(config2.logs.dir, `session_${session}_${timestamp2}.log`);
|
|
2024
2079
|
divider(session);
|
|
2080
|
+
startSpinner("fetching issue...");
|
|
2025
2081
|
if (opts.issueId) {
|
|
2026
2082
|
log(`Fetching issue '${opts.issueId}' from ${config2.source}...`);
|
|
2027
2083
|
} else {
|
|
2028
2084
|
log(`Fetching next '${config2.source_config.label}' issue from ${config2.source}...`);
|
|
2029
2085
|
}
|
|
2030
2086
|
if (opts.dryRun) {
|
|
2087
|
+
stopSpinner();
|
|
2031
2088
|
if (opts.issueId) {
|
|
2032
2089
|
log(`[dry-run] Would fetch issue '${opts.issueId}' from ${config2.source}`);
|
|
2033
2090
|
} else {
|
|
@@ -2044,11 +2101,14 @@ async function runLoop(config2, opts) {
|
|
|
2044
2101
|
try {
|
|
2045
2102
|
issue = opts.issueId ? await source.fetchIssueById(opts.issueId) : await source.fetchNextIssue(config2.source_config);
|
|
2046
2103
|
} catch (err) {
|
|
2104
|
+
stopSpinner();
|
|
2047
2105
|
error(`Failed to fetch issues: ${err instanceof Error ? err.message : String(err)}`);
|
|
2048
2106
|
if (opts.once) break;
|
|
2107
|
+
setTitle("Lisa \u2014 cooling down...");
|
|
2049
2108
|
await sleep(config2.loop.cooldown * 1e3);
|
|
2050
2109
|
continue;
|
|
2051
2110
|
}
|
|
2111
|
+
stopSpinner();
|
|
2052
2112
|
if (!issue) {
|
|
2053
2113
|
if (opts.issueId) {
|
|
2054
2114
|
error(`Issue '${opts.issueId}' not found.`);
|
|
@@ -2058,6 +2118,7 @@ async function runLoop(config2, opts) {
|
|
|
2058
2118
|
break;
|
|
2059
2119
|
}
|
|
2060
2120
|
ok(`Picked up: ${issue.id} \u2014 ${issue.title}`);
|
|
2121
|
+
setTitle(`Lisa \u2014 ${issue.id}`);
|
|
2061
2122
|
const previousStatus = config2.source_config.pick_from;
|
|
2062
2123
|
try {
|
|
2063
2124
|
const inProgress = config2.source_config.in_progress;
|
|
@@ -2071,6 +2132,7 @@ async function runLoop(config2, opts) {
|
|
|
2071
2132
|
try {
|
|
2072
2133
|
sessionResult = config2.workflow === "worktree" ? await runWorktreeSession(config2, issue, logFile, session, models) : await runBranchSession(config2, issue, logFile, session, models);
|
|
2073
2134
|
} catch (err) {
|
|
2135
|
+
stopSpinner();
|
|
2074
2136
|
error(
|
|
2075
2137
|
`Unhandled error in session for ${issue.id}: ${err instanceof Error ? err.message : String(err)}`
|
|
2076
2138
|
);
|
|
@@ -2083,8 +2145,10 @@ async function runLoop(config2, opts) {
|
|
|
2083
2145
|
);
|
|
2084
2146
|
}
|
|
2085
2147
|
activeCleanup = null;
|
|
2148
|
+
notify();
|
|
2086
2149
|
if (opts.once) break;
|
|
2087
2150
|
log(`Cooling down ${config2.loop.cooldown}s before next issue...`);
|
|
2151
|
+
setTitle("Lisa \u2014 cooling down...");
|
|
2088
2152
|
await sleep(config2.loop.cooldown * 1e3);
|
|
2089
2153
|
continue;
|
|
2090
2154
|
}
|
|
@@ -2100,11 +2164,13 @@ async function runLoop(config2, opts) {
|
|
|
2100
2164
|
);
|
|
2101
2165
|
}
|
|
2102
2166
|
activeCleanup = null;
|
|
2167
|
+
notify();
|
|
2103
2168
|
if (opts.once) {
|
|
2104
2169
|
log("Single iteration mode. Exiting.");
|
|
2105
2170
|
break;
|
|
2106
2171
|
}
|
|
2107
2172
|
log(`Cooling down ${config2.loop.cooldown}s before next issue...`);
|
|
2173
|
+
setTitle("Lisa \u2014 cooling down...");
|
|
2108
2174
|
await sleep(config2.loop.cooldown * 1e3);
|
|
2109
2175
|
continue;
|
|
2110
2176
|
}
|
|
@@ -2122,11 +2188,13 @@ async function runLoop(config2, opts) {
|
|
|
2122
2188
|
);
|
|
2123
2189
|
}
|
|
2124
2190
|
activeCleanup = null;
|
|
2191
|
+
notify();
|
|
2125
2192
|
if (opts.once) {
|
|
2126
2193
|
log("Single iteration mode. Exiting.");
|
|
2127
2194
|
break;
|
|
2128
2195
|
}
|
|
2129
2196
|
log(`Cooling down ${config2.loop.cooldown}s before next issue...`);
|
|
2197
|
+
setTitle("Lisa \u2014 cooling down...");
|
|
2130
2198
|
await sleep(config2.loop.cooldown * 1e3);
|
|
2131
2199
|
continue;
|
|
2132
2200
|
}
|
|
@@ -2150,13 +2218,17 @@ async function runLoop(config2, opts) {
|
|
|
2150
2218
|
error(`Failed to complete issue: ${err instanceof Error ? err.message : String(err)}`);
|
|
2151
2219
|
}
|
|
2152
2220
|
activeCleanup = null;
|
|
2221
|
+
stopSpinner(`\u2713 Lisa \u2014 ${issue.id} \u2014 PR created`);
|
|
2222
|
+
notify();
|
|
2153
2223
|
if (opts.once) {
|
|
2154
2224
|
log("Single iteration mode. Exiting.");
|
|
2155
2225
|
break;
|
|
2156
2226
|
}
|
|
2157
2227
|
log(`Cooling down ${config2.loop.cooldown}s before next issue...`);
|
|
2228
|
+
setTitle("Lisa \u2014 cooling down...");
|
|
2158
2229
|
await sleep(config2.loop.cooldown * 1e3);
|
|
2159
2230
|
}
|
|
2231
|
+
resetTitle();
|
|
2160
2232
|
ok(`lisa finished. ${session} session(s) run.`);
|
|
2161
2233
|
}
|
|
2162
2234
|
function logAttemptHistory(result) {
|
|
@@ -2205,11 +2277,13 @@ async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
|
2205
2277
|
const repoPath = determineRepoPath(config2.repos, issue, workspace) ?? workspace;
|
|
2206
2278
|
const defaultBranch = resolveBaseBranch(config2, repoPath);
|
|
2207
2279
|
const branchName = generateBranchName(issue.id, issue.title);
|
|
2280
|
+
startSpinner(`${issue.id} \u2014 creating worktree...`);
|
|
2208
2281
|
log(`Creating worktree for ${branchName} (base: ${defaultBranch})...`);
|
|
2209
2282
|
let worktreePath;
|
|
2210
2283
|
try {
|
|
2211
2284
|
worktreePath = await createWorktree(repoPath, branchName, defaultBranch);
|
|
2212
2285
|
} catch (err) {
|
|
2286
|
+
stopSpinner();
|
|
2213
2287
|
error(`Failed to create worktree: ${err instanceof Error ? err.message : String(err)}`);
|
|
2214
2288
|
return {
|
|
2215
2289
|
success: false,
|
|
@@ -2224,10 +2298,13 @@ async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
|
2224
2298
|
}
|
|
2225
2299
|
};
|
|
2226
2300
|
}
|
|
2301
|
+
stopSpinner();
|
|
2227
2302
|
ok(`Worktree created at ${worktreePath}`);
|
|
2228
2303
|
const repo = findRepoConfig(config2, issue);
|
|
2229
2304
|
if (repo?.lifecycle) {
|
|
2305
|
+
startSpinner(`${issue.id} \u2014 starting resources...`);
|
|
2230
2306
|
const started = await startResources(repo, worktreePath);
|
|
2307
|
+
stopSpinner();
|
|
2231
2308
|
if (!started) {
|
|
2232
2309
|
error(`Lifecycle startup failed for ${issue.id}. Aborting session.`);
|
|
2233
2310
|
await cleanupWorktree(repoPath, worktreePath);
|
|
@@ -2250,6 +2327,7 @@ async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
|
2250
2327
|
log(`Detected test runner: ${testRunner}`);
|
|
2251
2328
|
}
|
|
2252
2329
|
const prompt = buildImplementPrompt(issue, config2, testRunner);
|
|
2330
|
+
startSpinner(`${issue.id} \u2014 implementing...`);
|
|
2253
2331
|
log(`Implementing in worktree... (log: ${logFile})`);
|
|
2254
2332
|
initLogFile(logFile);
|
|
2255
2333
|
const result = await runWithFallback(models, prompt, {
|
|
@@ -2259,6 +2337,7 @@ async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
|
2259
2337
|
issueId: issue.id,
|
|
2260
2338
|
overseer: config2.overseer
|
|
2261
2339
|
});
|
|
2340
|
+
stopSpinner();
|
|
2262
2341
|
try {
|
|
2263
2342
|
appendFileSync6(
|
|
2264
2343
|
logFile,
|
|
@@ -2279,7 +2358,9 @@ ${result.output}
|
|
|
2279
2358
|
await cleanupWorktree(repoPath, worktreePath);
|
|
2280
2359
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2281
2360
|
}
|
|
2361
|
+
startSpinner(`${issue.id} \u2014 validating tests...`);
|
|
2282
2362
|
const testsPassed = await runTestValidation(worktreePath);
|
|
2363
|
+
stopSpinner();
|
|
2283
2364
|
if (!testsPassed) {
|
|
2284
2365
|
error(`Tests failed for ${issue.id}. Blocking PR creation.`);
|
|
2285
2366
|
await cleanupWorktree(repoPath, worktreePath);
|
|
@@ -2299,6 +2380,7 @@ ${result.output}
|
|
|
2299
2380
|
);
|
|
2300
2381
|
}
|
|
2301
2382
|
}
|
|
2383
|
+
startSpinner(`${issue.id} \u2014 pushing...`);
|
|
2302
2384
|
const pushResult = await pushWithRecovery({
|
|
2303
2385
|
branch: effectiveBranch,
|
|
2304
2386
|
cwd: worktreePath,
|
|
@@ -2308,13 +2390,16 @@ ${result.output}
|
|
|
2308
2390
|
issueId: issue.id,
|
|
2309
2391
|
overseer: config2.overseer
|
|
2310
2392
|
});
|
|
2393
|
+
stopSpinner();
|
|
2311
2394
|
if (!pushResult.success) {
|
|
2312
2395
|
error(`Failed to push branch to remote: ${pushResult.error}`);
|
|
2313
2396
|
cleanupManifest(worktreePath);
|
|
2314
2397
|
await cleanupWorktree(repoPath, worktreePath);
|
|
2315
2398
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2316
2399
|
}
|
|
2400
|
+
startSpinner(`${issue.id} \u2014 creating PR...`);
|
|
2317
2401
|
const prTitle = manifest?.prTitle ?? readPrTitle(worktreePath) ?? issue.title;
|
|
2402
|
+
const prBody = manifest?.prBody;
|
|
2318
2403
|
cleanupPrTitle(worktreePath);
|
|
2319
2404
|
cleanupManifest(worktreePath);
|
|
2320
2405
|
const prUrls = [];
|
|
@@ -2327,7 +2412,7 @@ ${result.output}
|
|
|
2327
2412
|
head: effectiveBranch,
|
|
2328
2413
|
base: defaultBranch,
|
|
2329
2414
|
title: prTitle,
|
|
2330
|
-
body: buildPrBody(
|
|
2415
|
+
body: buildPrBody(result.providerUsed, prBody)
|
|
2331
2416
|
},
|
|
2332
2417
|
config2.github
|
|
2333
2418
|
);
|
|
@@ -2336,6 +2421,7 @@ ${result.output}
|
|
|
2336
2421
|
} catch (err) {
|
|
2337
2422
|
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2338
2423
|
}
|
|
2424
|
+
stopSpinner();
|
|
2339
2425
|
await cleanupWorktree(repoPath, worktreePath);
|
|
2340
2426
|
ok(`Session ${session} complete for ${issue.id}`);
|
|
2341
2427
|
return { success: true, providerUsed: result.providerUsed, prUrls, fallback: result };
|
|
@@ -2344,6 +2430,7 @@ async function runWorktreeMultiRepoSession(config2, issue, logFile, session, mod
|
|
|
2344
2430
|
const workspace = resolve5(config2.workspace);
|
|
2345
2431
|
cleanupManifest(workspace);
|
|
2346
2432
|
const prompt = buildWorktreeMultiRepoPrompt(issue, config2);
|
|
2433
|
+
startSpinner(`${issue.id} \u2014 implementing...`);
|
|
2347
2434
|
log(`Multi-repo worktree session for ${issue.id} (agent selects repo and branch name)`);
|
|
2348
2435
|
log(`Implementing (agent selects repo)... (log: ${logFile})`);
|
|
2349
2436
|
initLogFile(logFile);
|
|
@@ -2354,6 +2441,7 @@ async function runWorktreeMultiRepoSession(config2, issue, logFile, session, mod
|
|
|
2354
2441
|
issueId: issue.id,
|
|
2355
2442
|
overseer: config2.overseer
|
|
2356
2443
|
});
|
|
2444
|
+
stopSpinner();
|
|
2357
2445
|
try {
|
|
2358
2446
|
appendFileSync6(
|
|
2359
2447
|
logFile,
|
|
@@ -2387,13 +2475,16 @@ ${result.output}
|
|
|
2387
2475
|
if (!hasWorktree) {
|
|
2388
2476
|
warn(`Worktree not found at ${worktreePath} \u2014 using repo root for git operations`);
|
|
2389
2477
|
}
|
|
2478
|
+
startSpinner(`${issue.id} \u2014 validating tests...`);
|
|
2390
2479
|
const testsPassed = await runTestValidation(effectiveCwd);
|
|
2480
|
+
stopSpinner();
|
|
2391
2481
|
if (!testsPassed) {
|
|
2392
2482
|
error(`Tests failed for ${issue.id}. Blocking PR creation.`);
|
|
2393
2483
|
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2394
2484
|
cleanupManifest(workspace);
|
|
2395
2485
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2396
2486
|
}
|
|
2487
|
+
startSpinner(`${issue.id} \u2014 pushing...`);
|
|
2397
2488
|
const pushResult = await pushWithRecovery({
|
|
2398
2489
|
branch: manifest.branch,
|
|
2399
2490
|
cwd: effectiveCwd,
|
|
@@ -2403,13 +2494,16 @@ ${result.output}
|
|
|
2403
2494
|
issueId: issue.id,
|
|
2404
2495
|
overseer: config2.overseer
|
|
2405
2496
|
});
|
|
2497
|
+
stopSpinner();
|
|
2406
2498
|
if (!pushResult.success) {
|
|
2407
2499
|
error(`Failed to push branch to remote: ${pushResult.error}`);
|
|
2408
2500
|
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2409
2501
|
cleanupManifest(workspace);
|
|
2410
2502
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2411
2503
|
}
|
|
2504
|
+
startSpinner(`${issue.id} \u2014 creating PR...`);
|
|
2412
2505
|
const prTitle = manifest.prTitle ?? issue.title;
|
|
2506
|
+
const prBody = manifest.prBody;
|
|
2413
2507
|
const prUrls = [];
|
|
2414
2508
|
try {
|
|
2415
2509
|
const repoInfo = await getRepoInfo(effectiveCwd);
|
|
@@ -2420,7 +2514,7 @@ ${result.output}
|
|
|
2420
2514
|
head: manifest.branch,
|
|
2421
2515
|
base: baseBranch,
|
|
2422
2516
|
title: prTitle,
|
|
2423
|
-
body: buildPrBody(
|
|
2517
|
+
body: buildPrBody(result.providerUsed, prBody)
|
|
2424
2518
|
},
|
|
2425
2519
|
config2.github
|
|
2426
2520
|
);
|
|
@@ -2429,6 +2523,7 @@ ${result.output}
|
|
|
2429
2523
|
} catch (err) {
|
|
2430
2524
|
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2431
2525
|
}
|
|
2526
|
+
stopSpinner();
|
|
2432
2527
|
cleanupManifest(workspace);
|
|
2433
2528
|
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2434
2529
|
ok(`Session ${session} complete for ${issue.id}`);
|
|
@@ -2444,8 +2539,10 @@ async function runBranchSession(config2, issue, logFile, session, models) {
|
|
|
2444
2539
|
const prompt = buildImplementPrompt(issue, config2, testRunner);
|
|
2445
2540
|
const repo = findRepoConfig(config2, issue);
|
|
2446
2541
|
if (repo?.lifecycle) {
|
|
2542
|
+
startSpinner(`${issue.id} \u2014 starting resources...`);
|
|
2447
2543
|
const cwd = resolve5(workspace, repo.path);
|
|
2448
2544
|
const started = await startResources(repo, cwd);
|
|
2545
|
+
stopSpinner();
|
|
2449
2546
|
if (!started) {
|
|
2450
2547
|
error(`Lifecycle startup failed for ${issue.id}. Aborting session.`);
|
|
2451
2548
|
return {
|
|
@@ -2462,6 +2559,7 @@ async function runBranchSession(config2, issue, logFile, session, models) {
|
|
|
2462
2559
|
};
|
|
2463
2560
|
}
|
|
2464
2561
|
}
|
|
2562
|
+
startSpinner(`${issue.id} \u2014 implementing...`);
|
|
2465
2563
|
log(`Implementing... (log: ${logFile})`);
|
|
2466
2564
|
initLogFile(logFile);
|
|
2467
2565
|
const result = await runWithFallback(models, prompt, {
|
|
@@ -2471,6 +2569,7 @@ async function runBranchSession(config2, issue, logFile, session, models) {
|
|
|
2471
2569
|
issueId: issue.id,
|
|
2472
2570
|
overseer: config2.overseer
|
|
2473
2571
|
});
|
|
2572
|
+
stopSpinner();
|
|
2474
2573
|
try {
|
|
2475
2574
|
appendFileSync6(
|
|
2476
2575
|
logFile,
|
|
@@ -2490,7 +2589,9 @@ ${result.output}
|
|
|
2490
2589
|
error(`Session ${session} failed for ${issue.id}. Check ${logFile}`);
|
|
2491
2590
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2492
2591
|
}
|
|
2592
|
+
startSpinner(`${issue.id} \u2014 validating tests...`);
|
|
2493
2593
|
const testsPassed = await runTestValidation(workspace);
|
|
2594
|
+
stopSpinner();
|
|
2494
2595
|
if (!testsPassed) {
|
|
2495
2596
|
error(`Tests failed for ${issue.id}. Blocking PR creation.`);
|
|
2496
2597
|
cleanupManifest(workspace);
|
|
@@ -2513,7 +2614,9 @@ ${result.output}
|
|
|
2513
2614
|
ok(`Session ${session} complete for ${issue.id}`);
|
|
2514
2615
|
return { success: true, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2515
2616
|
}
|
|
2617
|
+
startSpinner(`${issue.id} \u2014 creating PR...`);
|
|
2516
2618
|
const prTitle = manifest?.prTitle ?? readPrTitle(workspace) ?? issue.title;
|
|
2619
|
+
const prBody = manifest?.prBody;
|
|
2517
2620
|
cleanupPrTitle(workspace);
|
|
2518
2621
|
const prUrls = [];
|
|
2519
2622
|
for (const { repoPath, branch } of detected) {
|
|
@@ -2528,7 +2631,7 @@ ${result.output}
|
|
|
2528
2631
|
head: branch,
|
|
2529
2632
|
base: baseBranch,
|
|
2530
2633
|
title: prTitle,
|
|
2531
|
-
body: buildPrBody(
|
|
2634
|
+
body: buildPrBody(result.providerUsed, prBody)
|
|
2532
2635
|
},
|
|
2533
2636
|
config2.github
|
|
2534
2637
|
);
|
|
@@ -2538,6 +2641,7 @@ ${result.output}
|
|
|
2538
2641
|
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2539
2642
|
}
|
|
2540
2643
|
}
|
|
2644
|
+
stopSpinner();
|
|
2541
2645
|
ok(`Session ${session} complete for ${issue.id}`);
|
|
2542
2646
|
return { success: true, providerUsed: result.providerUsed, prUrls, fallback: result };
|
|
2543
2647
|
}
|