@glrs-dev/cli 2.4.1 → 2.6.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/CHANGELOG.md +34 -0
- package/dist/{chunk-HQUCVJ4G.js → chunk-FBXSGZAA.js} +4 -0
- package/dist/chunk-J3FXSHMA.js +263 -0
- package/dist/{chunk-5ZVUFNCP.js → chunk-S6N5E2GG.js} +8 -1
- package/dist/{chunk-2VMFXAJH.js → chunk-UO7WHIKY.js} +18 -5
- package/dist/cli.js +10 -3
- package/dist/commands/autopilot-tui.d.ts +11 -1
- package/dist/commands/autopilot-tui.js +2 -1
- package/dist/commands/autopilot.d.ts +2 -0
- package/dist/commands/autopilot.js +62 -21
- package/dist/commands/debrief.d.ts +2 -0
- package/dist/commands/debrief.js +1 -1
- package/dist/commands/loop.d.ts +2 -0
- package/dist/commands/loop.js +33 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.d.ts +9 -0
- package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.js +33 -15
- package/dist/node_modules/@glrs-dev/adapter-opencode/package.json +1 -1
- package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-EVLBKHUZ.js +7 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/{changeset-generator-DG3MVWVV.js → changeset-generator-HAHYSSUR.js} +2 -2
- package/dist/node_modules/@glrs-dev/autopilot/dist/{chunk-VITL2Z45.js → chunk-2X3CWH47.js} +578 -62
- package/dist/node_modules/@glrs-dev/autopilot/dist/{chunk-Q4ULU6ER.js → chunk-2ZQ6SBV3.js} +4 -2
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-6JZQLIRP.js +781 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/{chunk-E7PWTRFO.js → chunk-AWRK6S6G.js} +2 -2
- package/dist/node_modules/@glrs-dev/autopilot/dist/{chunk-M2ZVBPWL.js → chunk-BLEIZHET.js} +1 -1
- package/dist/node_modules/@glrs-dev/autopilot/dist/{chunk-7OSEI5TF.js → chunk-GXXCEGDD.js} +3 -1
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-S34HOCZ4.js +44 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/index.d.ts +159 -9
- package/dist/node_modules/@glrs-dev/autopilot/dist/index.js +115 -35
- package/dist/node_modules/@glrs-dev/autopilot/dist/{logger-UITJGIZE.js → logger-3XLFMXLN.js} +1 -1
- package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-YLCVJGPV.js +9 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-4SQYV5FC.js +17 -0
- package/dist/node_modules/@glrs-dev/autopilot/package.json +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/agents-md-writer.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/architecture-advisor.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-searcher.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/docs-maintainer.md +0 -8
- package/dist/vendor/harness-opencode/dist/agents/prompts/gap-analyzer.md +1 -3
- package/dist/vendor/harness-opencode/dist/agents/prompts/lib-reader.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan-reviewer.md +0 -2
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/prime.md +78 -262
- package/dist/vendor/harness-opencode/dist/agents/prompts/research.md +5 -14
- package/dist/vendor/harness-opencode/dist/agents/prompts/scoper.md +7 -2
- package/dist/vendor/harness-opencode/dist/autopilot/strategies/default.md +29 -0
- package/dist/vendor/harness-opencode/dist/index.js +112 -82
- package/dist/vendor/harness-opencode/package.json +1 -1
- package/package.json +1 -1
- package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-LCT6LIH7.js +0 -7
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-ZNJWARTM.js +0 -449
- package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-XKL3NHUA.js +0 -8
- package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-D3RPJR2J.js +0 -14
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveModel
|
|
3
|
+
} from "./chunk-S34HOCZ4.js";
|
|
1
4
|
import {
|
|
2
5
|
childLogger,
|
|
3
6
|
createAutopilotLogger
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2ZQ6SBV3.js";
|
|
5
8
|
import {
|
|
6
9
|
detectSpecPhases,
|
|
7
10
|
filterUncheckedSpecPhases,
|
|
@@ -12,7 +15,7 @@ import {
|
|
|
12
15
|
readSpecGoal,
|
|
13
16
|
validateMainSpec,
|
|
14
17
|
validatePhaseSpec
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-GXXCEGDD.js";
|
|
16
19
|
|
|
17
20
|
// src/loop-session.ts
|
|
18
21
|
import * as fs7 from "fs";
|
|
@@ -348,6 +351,7 @@ var MAX_ITERATIONS_PER_PHASE_BY_TIER = {
|
|
|
348
351
|
"autopilot-execute": 10,
|
|
349
352
|
fast: 10
|
|
350
353
|
};
|
|
354
|
+
var MAX_ITERATIONS_PER_ITEM = 5;
|
|
351
355
|
var KILL_SWITCH_PATH = ".agent/autopilot-disable";
|
|
352
356
|
var SENTINEL_TAG = "<autopilot-done>";
|
|
353
357
|
var STATUS_INTERVAL_MS = (() => {
|
|
@@ -462,7 +466,10 @@ function formatSlackMessage(event) {
|
|
|
462
466
|
function isSlackWebhookUrl(url) {
|
|
463
467
|
return url.includes("hooks.slack.com/");
|
|
464
468
|
}
|
|
465
|
-
async function notifyWebhook(url, event) {
|
|
469
|
+
async function notifyWebhook(url, event, allowedEvents) {
|
|
470
|
+
if (allowedEvents && !allowedEvents.includes(event.event)) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
466
473
|
try {
|
|
467
474
|
const body = isSlackWebhookUrl(url) ? JSON.stringify(formatSlackMessage(event)) : JSON.stringify(event);
|
|
468
475
|
const response = await fetch(url, {
|
|
@@ -712,13 +719,36 @@ async function getHeadSha(cwd) {
|
|
|
712
719
|
return "HEAD";
|
|
713
720
|
}
|
|
714
721
|
}
|
|
722
|
+
async function amendCommitWithPrefix(cwd, prefix) {
|
|
723
|
+
try {
|
|
724
|
+
const { stdout: subject } = await execFile2("git", ["log", "-1", "--pretty=%s"], { cwd });
|
|
725
|
+
const currentSubject = subject.trim();
|
|
726
|
+
if (!currentSubject || currentSubject.startsWith(prefix)) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
const newSubject = `${prefix} ${currentSubject}`;
|
|
730
|
+
await execFile2("git", ["commit", "--amend", "-m", newSubject], { cwd });
|
|
731
|
+
return true;
|
|
732
|
+
} catch {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
715
736
|
async function runRalphLoop(opts) {
|
|
716
737
|
process.env["GLRS_AUTOPILOT_HEADLESS"] = "1";
|
|
717
738
|
const maxIterations = opts.maxIterations ?? MAX_ITERATIONS;
|
|
718
739
|
const timeoutMs = opts.timeoutMs ?? TIMEOUT_MS;
|
|
719
740
|
const tier = opts.agentName === "autopilot-fast" ? "autopilot-execute" : "deep";
|
|
720
|
-
const
|
|
741
|
+
const cfgObj = opts.config;
|
|
742
|
+
const cfgStallMs = cfgObj?.stall_timeout;
|
|
743
|
+
const stallMs = opts.stallMs ?? cfgStallMs ?? STALL_MS_BY_TIER[tier];
|
|
721
744
|
const struggleThreshold = opts.struggleThreshold ?? STRUGGLE_THRESHOLD;
|
|
745
|
+
const autoCommit = cfgObj?.auto_commit ?? true;
|
|
746
|
+
const commitPrefix = cfgObj?.commit_prefix;
|
|
747
|
+
const cfgNotifyUrl = cfgObj?.notify_url;
|
|
748
|
+
const cfgNotifyEvents = cfgObj?.notify_events;
|
|
749
|
+
const resolvedNotifyUrl = opts.notifyUrl ?? cfgNotifyUrl;
|
|
750
|
+
const resolvedNotifyEvents = cfgNotifyEvents;
|
|
751
|
+
const finalNotifyEvents = opts.notifyEvents ?? resolvedNotifyEvents;
|
|
722
752
|
if (!opts.adapter) {
|
|
723
753
|
throw new Error("runRalphLoop: adapter is required");
|
|
724
754
|
}
|
|
@@ -727,8 +757,8 @@ async function runRalphLoop(opts) {
|
|
|
727
757
|
const struggle = new StruggleDetector(struggleThreshold);
|
|
728
758
|
const startTime = Date.now();
|
|
729
759
|
const notify = (event) => {
|
|
730
|
-
if (
|
|
731
|
-
notifyWebhook(
|
|
760
|
+
if (resolvedNotifyUrl) {
|
|
761
|
+
notifyWebhook(resolvedNotifyUrl, event, finalNotifyEvents).catch(() => {
|
|
732
762
|
});
|
|
733
763
|
}
|
|
734
764
|
};
|
|
@@ -773,7 +803,7 @@ async function runRalphLoop(opts) {
|
|
|
773
803
|
log.info({ file: autopilotLog.logFilePath }, `Logging to ${autopilotLog.logFilePath}`);
|
|
774
804
|
}
|
|
775
805
|
log.info({ cwd: opts.cwd, maxIterations, timeoutMs }, `Starting agent (${adapter.name})`);
|
|
776
|
-
const handle = await adapter.start({ cwd: opts.cwd });
|
|
806
|
+
const handle = await adapter.start({ cwd: opts.cwd, agents: opts.agentOverrides });
|
|
777
807
|
log.info({ agentId: handle.id }, "Agent ready");
|
|
778
808
|
const abort = new AbortController();
|
|
779
809
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -808,13 +838,14 @@ async function runRalphLoop(opts) {
|
|
|
808
838
|
try {
|
|
809
839
|
const agentName = opts.agentName ?? "autopilot-prime";
|
|
810
840
|
const tierLabel = agentName === "autopilot-fast" ? "autopilot-execute tier" : "deep tier";
|
|
811
|
-
sessionId = await adapter.createSession(handle, { agentName });
|
|
841
|
+
sessionId = await adapter.createSession(handle, { agentName, model: opts.model });
|
|
812
842
|
log.info({ sessionId, agentName, tier: tierLabel }, `Session created with ${agentName} (${tierLabel})`);
|
|
843
|
+
const statusFileEnabled = opts.config?.status_file !== false;
|
|
813
844
|
heartbeat = createStatusHeartbeat({
|
|
814
845
|
logger: statusLog,
|
|
815
846
|
intervalMs: STATUS_INTERVAL_MS,
|
|
816
847
|
pollCost: async () => adapter.getSessionCost(handle, sessionId),
|
|
817
|
-
statusFilePath: join4(opts.cwd, ".agent", "autopilot-status.json")
|
|
848
|
+
statusFilePath: statusFileEnabled ? join4(opts.cwd, ".agent", "autopilot-status.json") : void 0
|
|
818
849
|
});
|
|
819
850
|
heartbeat.start();
|
|
820
851
|
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
@@ -832,6 +863,7 @@ async function runRalphLoop(opts) {
|
|
|
832
863
|
iterations: iteration - 1,
|
|
833
864
|
message: `Kill switch active (.agent/autopilot-disable exists). Stopping after ${iteration - 1} iteration(s).`,
|
|
834
865
|
sessionId,
|
|
866
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
835
867
|
agentHandle: transferHandle()
|
|
836
868
|
};
|
|
837
869
|
}
|
|
@@ -849,6 +881,7 @@ async function runRalphLoop(opts) {
|
|
|
849
881
|
iterations: iteration - 1,
|
|
850
882
|
message: `Total timeout (${timeoutMs}ms) exceeded after ${iteration - 1} iteration(s).`,
|
|
851
883
|
sessionId,
|
|
884
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
852
885
|
agentHandle: transferHandle()
|
|
853
886
|
};
|
|
854
887
|
}
|
|
@@ -1011,6 +1044,7 @@ async function runRalphLoop(opts) {
|
|
|
1011
1044
|
iterations: iteration,
|
|
1012
1045
|
message: `Aborted after ${iteration} iteration(s) (total timeout exceeded).`,
|
|
1013
1046
|
sessionId,
|
|
1047
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1014
1048
|
agentHandle: transferHandle()
|
|
1015
1049
|
};
|
|
1016
1050
|
}
|
|
@@ -1028,6 +1062,7 @@ async function runRalphLoop(opts) {
|
|
|
1028
1062
|
iterations: iteration,
|
|
1029
1063
|
message: `Iteration ${iteration} stalled for ${result.stallMs}ms with no idle signal.`,
|
|
1030
1064
|
sessionId,
|
|
1065
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1031
1066
|
agentHandle: transferHandle()
|
|
1032
1067
|
};
|
|
1033
1068
|
}
|
|
@@ -1093,6 +1128,7 @@ async function runRalphLoop(opts) {
|
|
|
1093
1128
|
iterations: iteration,
|
|
1094
1129
|
message: `Error in iteration ${iteration}: ${result.message}`,
|
|
1095
1130
|
sessionId,
|
|
1131
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1096
1132
|
agentHandle: transferHandle()
|
|
1097
1133
|
};
|
|
1098
1134
|
}
|
|
@@ -1118,6 +1154,7 @@ async function runRalphLoop(opts) {
|
|
|
1118
1154
|
iterations: iteration,
|
|
1119
1155
|
message: `Agent emitted <autopilot-done> at iteration ${iteration}.`,
|
|
1120
1156
|
sessionId,
|
|
1157
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1121
1158
|
agentHandle: transferHandle()
|
|
1122
1159
|
};
|
|
1123
1160
|
}
|
|
@@ -1196,6 +1233,10 @@ async function runRalphLoop(opts) {
|
|
|
1196
1233
|
commitSubject = logOut.trim().replace(/^[0-9a-f]+ /, "");
|
|
1197
1234
|
} catch {
|
|
1198
1235
|
}
|
|
1236
|
+
if (commitSubject && commitPrefix) {
|
|
1237
|
+
amendCommitWithPrefix(opts.cwd, commitPrefix).catch(() => {
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1199
1240
|
if (commitSubject) {
|
|
1200
1241
|
log.info(`${lanePrefix}commit: ${commitSubject}`);
|
|
1201
1242
|
}
|
|
@@ -1289,6 +1330,7 @@ async function runRalphLoop(opts) {
|
|
|
1289
1330
|
iterations: iteration,
|
|
1290
1331
|
message: `Agent made no filesystem progress for ${struggleThreshold} consecutive iteration(s). Stopping at iteration ${iteration}.`,
|
|
1291
1332
|
sessionId,
|
|
1333
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1292
1334
|
agentHandle: transferHandle()
|
|
1293
1335
|
};
|
|
1294
1336
|
}
|
|
@@ -1306,6 +1348,7 @@ async function runRalphLoop(opts) {
|
|
|
1306
1348
|
iterations: maxIterations,
|
|
1307
1349
|
message: `Reached maximum iterations (${maxIterations}). Stopping.`,
|
|
1308
1350
|
sessionId,
|
|
1351
|
+
cumulativeCostUsd: heartbeat?.getState().cumulativeCostUsd,
|
|
1309
1352
|
agentHandle: transferHandle()
|
|
1310
1353
|
};
|
|
1311
1354
|
} finally {
|
|
@@ -1318,12 +1361,20 @@ async function runRalphLoop(opts) {
|
|
|
1318
1361
|
try {
|
|
1319
1362
|
const { stdout: porcelain } = await execFile2("git", ["status", "--porcelain"], { cwd: opts.cwd });
|
|
1320
1363
|
if (porcelain.trim().length > 0) {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1364
|
+
if (autoCommit) {
|
|
1365
|
+
log.info({ signal: interruptedSignal }, "Committing WIP before exit");
|
|
1366
|
+
try {
|
|
1367
|
+
await execFile2("git", ["add", "-A"], { cwd: opts.cwd });
|
|
1368
|
+
const commitMsg = commitPrefix ? `${commitPrefix} [WIP] autopilot interrupted` : "[WIP] autopilot interrupted";
|
|
1369
|
+
await execFile2("git", ["commit", "-m", commitMsg], { cwd: opts.cwd });
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "WIP commit failed (hooks may have rejected)");
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
log.warn(
|
|
1375
|
+
{ signal: interruptedSignal },
|
|
1376
|
+
"Pending changes left unstaged (auto_commit: false)"
|
|
1377
|
+
);
|
|
1327
1378
|
}
|
|
1328
1379
|
}
|
|
1329
1380
|
} catch (err) {
|
|
@@ -1387,6 +1438,28 @@ function atomicWriteFileSync(target, content) {
|
|
|
1387
1438
|
fs5.writeFileSync(tmp, content, "utf-8");
|
|
1388
1439
|
fs5.renameSync(tmp, target);
|
|
1389
1440
|
}
|
|
1441
|
+
function markItemUnchecked(planDir, phaseFile, itemId) {
|
|
1442
|
+
const phasePath = path4.join(planDir, "spec", phaseFile);
|
|
1443
|
+
try {
|
|
1444
|
+
const content = fs5.readFileSync(phasePath, "utf-8");
|
|
1445
|
+
const raw = yamlParse(content);
|
|
1446
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
1447
|
+
const obj = raw;
|
|
1448
|
+
if (!Array.isArray(obj["items"])) return;
|
|
1449
|
+
const items = obj["items"];
|
|
1450
|
+
let found = false;
|
|
1451
|
+
for (const item of items) {
|
|
1452
|
+
if (item["id"] === itemId) {
|
|
1453
|
+
item["checked"] = false;
|
|
1454
|
+
found = true;
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (!found) return;
|
|
1459
|
+
atomicWriteFileSync(phasePath, yamlStringify(raw));
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1390
1463
|
function markPhaseCompleted(planDir, phaseFile) {
|
|
1391
1464
|
const mainPath = path4.join(planDir, "spec", "main.yaml");
|
|
1392
1465
|
try {
|
|
@@ -1652,6 +1725,22 @@ import { execFile as execFileCb6 } from "child_process";
|
|
|
1652
1725
|
import { promisify as promisify6 } from "util";
|
|
1653
1726
|
var execFileDefault3 = promisify6(execFileCb6);
|
|
1654
1727
|
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1728
|
+
function getTimeoutForProofType(proofType, customTimeoutMs) {
|
|
1729
|
+
if (!proofType) return customTimeoutMs;
|
|
1730
|
+
switch (proofType) {
|
|
1731
|
+
case "unit_test":
|
|
1732
|
+
return 30 * 1e3;
|
|
1733
|
+
case "api_check":
|
|
1734
|
+
return 10 * 1e3;
|
|
1735
|
+
case "structural":
|
|
1736
|
+
case "typecheck":
|
|
1737
|
+
return 60 * 1e3;
|
|
1738
|
+
case "e2e":
|
|
1739
|
+
return 120 * 1e3;
|
|
1740
|
+
default:
|
|
1741
|
+
return customTimeoutMs;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1655
1744
|
async function runVerifyCommands(items, cwd, opts = {}) {
|
|
1656
1745
|
const execFile4 = opts._deps?.execFile ?? execFileDefault3;
|
|
1657
1746
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -1659,11 +1748,12 @@ async function runVerifyCommands(items, cwd, opts = {}) {
|
|
|
1659
1748
|
for (const item of items) {
|
|
1660
1749
|
const command = item.verify?.trim();
|
|
1661
1750
|
if (!command) continue;
|
|
1751
|
+
const itemTimeoutMs = getTimeoutForProofType(item.proof_type, timeoutMs);
|
|
1662
1752
|
const start = Date.now();
|
|
1663
1753
|
try {
|
|
1664
1754
|
const { stdout, stderr } = await execFile4("/bin/sh", ["-c", command], {
|
|
1665
1755
|
cwd,
|
|
1666
|
-
signal: AbortSignal.timeout(
|
|
1756
|
+
signal: AbortSignal.timeout(itemTimeoutMs),
|
|
1667
1757
|
// Capture as much output as the command produces — verify
|
|
1668
1758
|
// commands are typically test runs; truncating their output
|
|
1669
1759
|
// would hide the failure detail we want in the next prompt.
|
|
@@ -1683,7 +1773,7 @@ async function runVerifyCommands(items, cwd, opts = {}) {
|
|
|
1683
1773
|
const stdout = e.stdout !== void 0 ? typeof e.stdout === "string" ? e.stdout : e.stdout.toString() : "";
|
|
1684
1774
|
let stderr = e.stderr !== void 0 ? typeof e.stderr === "string" ? e.stderr : e.stderr.toString() : "";
|
|
1685
1775
|
if (isTimeout) {
|
|
1686
|
-
stderr = (stderr ? stderr + "\n" : "") + `[verify-runner] command timed out after ${Math.round(
|
|
1776
|
+
stderr = (stderr ? stderr + "\n" : "") + `[verify-runner] command timed out after ${Math.round(itemTimeoutMs / 1e3)}s`;
|
|
1687
1777
|
} else if (!stderr && e.message) {
|
|
1688
1778
|
stderr = e.message;
|
|
1689
1779
|
}
|
|
@@ -1700,6 +1790,37 @@ async function runVerifyCommands(items, cwd, opts = {}) {
|
|
|
1700
1790
|
return results;
|
|
1701
1791
|
}
|
|
1702
1792
|
|
|
1793
|
+
// src/hook-runner.ts
|
|
1794
|
+
import { execFile as execFileCb7 } from "child_process";
|
|
1795
|
+
import { promisify as promisify7 } from "util";
|
|
1796
|
+
var execFileDefault4 = promisify7(execFileCb7);
|
|
1797
|
+
async function runHook(cmd, cwd, timeoutMs, opts = {}) {
|
|
1798
|
+
if (!cmd?.trim()) {
|
|
1799
|
+
return { ok: true, output: "" };
|
|
1800
|
+
}
|
|
1801
|
+
const execFile4 = opts._deps?.execFile ?? execFileDefault4;
|
|
1802
|
+
const timeout = opts.timeoutMs ?? timeoutMs;
|
|
1803
|
+
try {
|
|
1804
|
+
const { stdout, stderr } = await execFile4("/bin/sh", ["-c", cmd], {
|
|
1805
|
+
cwd,
|
|
1806
|
+
signal: AbortSignal.timeout(timeout),
|
|
1807
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1808
|
+
});
|
|
1809
|
+
const combinedOutput = (typeof stdout === "string" ? stdout : String(stdout ?? "")) + (typeof stderr === "string" ? stderr : String(stderr ?? ""));
|
|
1810
|
+
return { ok: true, output: combinedOutput };
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
const e = err;
|
|
1813
|
+
const isTimeout = e.name === "AbortError" || e.code === "ABORT_ERR";
|
|
1814
|
+
let stderr = e.stderr !== void 0 ? typeof e.stderr === "string" ? e.stderr : e.stderr.toString() : "";
|
|
1815
|
+
if (isTimeout) {
|
|
1816
|
+
stderr = (stderr ? stderr + "\n" : "") + `[hook-runner] command timed out after ${Math.round(timeout / 1e3)}s`;
|
|
1817
|
+
} else if (!stderr && e.message) {
|
|
1818
|
+
stderr = e.message;
|
|
1819
|
+
}
|
|
1820
|
+
return { ok: false, output: stderr };
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1703
1824
|
// src/plan-validator.ts
|
|
1704
1825
|
import * as fs6 from "fs";
|
|
1705
1826
|
import * as path6 from "path";
|
|
@@ -1900,7 +2021,76 @@ function checkItemsSoft(content, file, warnings) {
|
|
|
1900
2021
|
}
|
|
1901
2022
|
}
|
|
1902
2023
|
|
|
2024
|
+
// src/phase-config.ts
|
|
2025
|
+
function deepMerge(...objects) {
|
|
2026
|
+
const result = {};
|
|
2027
|
+
for (const obj of objects) {
|
|
2028
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
for (const key in obj) {
|
|
2032
|
+
const srcValue = obj[key];
|
|
2033
|
+
if (srcValue && typeof srcValue === "object" && !Array.isArray(srcValue) && !(srcValue instanceof Date)) {
|
|
2034
|
+
const targetValue = result[key];
|
|
2035
|
+
if (targetValue && typeof targetValue === "object" && !Array.isArray(targetValue) && !(targetValue instanceof Date)) {
|
|
2036
|
+
result[key] = deepMerge(
|
|
2037
|
+
targetValue,
|
|
2038
|
+
srcValue
|
|
2039
|
+
);
|
|
2040
|
+
} else {
|
|
2041
|
+
result[key] = srcValue;
|
|
2042
|
+
}
|
|
2043
|
+
} else {
|
|
2044
|
+
result[key] = srcValue;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return result;
|
|
2049
|
+
}
|
|
2050
|
+
function resolvePhaseConfig(baseConfig, phaseName) {
|
|
2051
|
+
if (!baseConfig || typeof baseConfig !== "object" || Array.isArray(baseConfig)) {
|
|
2052
|
+
return {};
|
|
2053
|
+
}
|
|
2054
|
+
const base = baseConfig;
|
|
2055
|
+
const phases = base.phases;
|
|
2056
|
+
if (!phases || typeof phases !== "object") {
|
|
2057
|
+
return base;
|
|
2058
|
+
}
|
|
2059
|
+
const phaseOverride = phases[phaseName];
|
|
2060
|
+
if (!phaseOverride || typeof phaseOverride !== "object" || Array.isArray(phaseOverride)) {
|
|
2061
|
+
return base;
|
|
2062
|
+
}
|
|
2063
|
+
return deepMerge(base, phaseOverride);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1903
2066
|
// src/loop-session.ts
|
|
2067
|
+
function extractVerifyConfig(config) {
|
|
2068
|
+
const cfgObj = config;
|
|
2069
|
+
const strategy = cfgObj?.verify ?? "after_phase";
|
|
2070
|
+
const timeoutMs = cfgObj?.verify_timeout ?? 5 * 60 * 1e3;
|
|
2071
|
+
const retryOnFailure = cfgObj?.verify_retry ?? true;
|
|
2072
|
+
return { strategy, timeoutMs, retryOnFailure };
|
|
2073
|
+
}
|
|
2074
|
+
function extractHooksConfig(config) {
|
|
2075
|
+
const cfgObj = config;
|
|
2076
|
+
const hooks = cfgObj?.hooks;
|
|
2077
|
+
return {
|
|
2078
|
+
pre_phase: hooks?.pre_phase,
|
|
2079
|
+
post_phase: hooks?.post_phase,
|
|
2080
|
+
post_run: hooks?.post_run,
|
|
2081
|
+
on_error: hooks?.on_error
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
function uncheckItemsInMarkdown(content, itemIds) {
|
|
2085
|
+
if (itemIds.length === 0) return content;
|
|
2086
|
+
let result = content;
|
|
2087
|
+
for (const itemId of itemIds) {
|
|
2088
|
+
const escaped = itemId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2089
|
+
const re = new RegExp(`^(- )\\[[xX]\\](\\s+id:\\s+${escaped}\\b)`, "m");
|
|
2090
|
+
result = result.replace(re, "$1[ ]$2");
|
|
2091
|
+
}
|
|
2092
|
+
return result;
|
|
2093
|
+
}
|
|
1904
2094
|
function extractSection(content, sectionName) {
|
|
1905
2095
|
const re = new RegExp(
|
|
1906
2096
|
`^## ${sectionName}\\s*\\n([\\s\\S]*?)(?=^## |$)`,
|
|
@@ -2029,14 +2219,40 @@ async function runLoopSession(opts) {
|
|
|
2029
2219
|
})();
|
|
2030
2220
|
if (!isDirectory) {
|
|
2031
2221
|
const prompt = `Work the plan at ${opts.planPath}. Complete all items in ## Acceptance criteria. Mark items done as they complete.`;
|
|
2032
|
-
|
|
2222
|
+
const adapterName = opts.adapter?.name;
|
|
2223
|
+
const singleFileCfgObj = opts.config;
|
|
2224
|
+
const models = singleFileCfgObj?.models;
|
|
2225
|
+
const executionSpecifier = models?.execution;
|
|
2226
|
+
const executionModel = executionSpecifier ? resolveModel(executionSpecifier, adapterName ?? "opencode") : void 0;
|
|
2227
|
+
const singleFileStallMs = singleFileCfgObj?.stall_timeout ?? STALL_MS_BY_TIER[opts.fast ? "autopilot-execute" : "deep"];
|
|
2228
|
+
const singleFileAdapters = opts.config?.adapters;
|
|
2229
|
+
const singleFileOcAdapter = singleFileAdapters?.opencode;
|
|
2230
|
+
const singleFileAgentOverrides = singleFileOcAdapter?.agents;
|
|
2231
|
+
return _runRalphLoop({
|
|
2232
|
+
prompt,
|
|
2233
|
+
cwd: opts.cwd,
|
|
2234
|
+
agentName: opts.fast ? "autopilot-fast" : void 0,
|
|
2235
|
+
model: executionModel,
|
|
2236
|
+
stallMs: singleFileStallMs,
|
|
2237
|
+
config: opts.config,
|
|
2238
|
+
agentOverrides: singleFileAgentOverrides,
|
|
2239
|
+
logger: opts.logger,
|
|
2240
|
+
emitter,
|
|
2241
|
+
adapter: opts.adapter
|
|
2242
|
+
});
|
|
2033
2243
|
}
|
|
2034
2244
|
const log = opts.logger ? opts.logger.root.child({ component: "autopilot.orchestrator" }) : pino(
|
|
2035
2245
|
{ level: "info", timestamp: pino.stdTimeFunctions.isoTime },
|
|
2036
2246
|
pino.destination({ fd: 2, sync: false })
|
|
2037
2247
|
).child({ component: "autopilot.orchestrator" });
|
|
2038
2248
|
const tier = opts.fast ? "autopilot-execute" : "deep";
|
|
2039
|
-
const
|
|
2249
|
+
const cfgObj = opts.config;
|
|
2250
|
+
const cfgMaxIterPerPhase = cfgObj?.max_iterations_per_phase;
|
|
2251
|
+
const maxIterationsPerPhase = opts.maxIterationsPerPhase ?? cfgMaxIterPerPhase ?? MAX_ITERATIONS_PER_PHASE_BY_TIER[tier];
|
|
2252
|
+
const cfgStallMs = cfgObj?.stall_timeout;
|
|
2253
|
+
const stallMs = cfgStallMs ?? STALL_MS_BY_TIER[tier];
|
|
2254
|
+
const verifyConfig = extractVerifyConfig(opts.config);
|
|
2255
|
+
const hooksConfig = extractHooksConfig(opts.config);
|
|
2040
2256
|
const mainMdPath = path7.join(opts.planPath, "main.md");
|
|
2041
2257
|
const useYamlSpec = hasSpec(opts.planPath);
|
|
2042
2258
|
let goal;
|
|
@@ -2153,11 +2369,17 @@ async function runLoopSession(opts) {
|
|
|
2153
2369
|
iterations: 0,
|
|
2154
2370
|
message: "No phases to execute."
|
|
2155
2371
|
};
|
|
2156
|
-
const
|
|
2372
|
+
const cfgMaxIterPerItem = opts.config?.max_iterations_per_item;
|
|
2373
|
+
const maxIterationsPerItem = cfgMaxIterPerItem ?? MAX_ITERATIONS_PER_ITEM;
|
|
2157
2374
|
const runItemsForPhase = async (args) => {
|
|
2158
2375
|
const { phaseFile, phasePath, laneId, runCwd, useParallel: useParallel2 } = args;
|
|
2159
2376
|
const phaseContent = args.readFileSync(phasePath);
|
|
2160
|
-
const
|
|
2377
|
+
const allItems = useYamlSpec ? parseSpecItems(phasePath) : parseItems(phaseContent);
|
|
2378
|
+
const items = allItems.filter((it) => !it.checked);
|
|
2379
|
+
log.info(
|
|
2380
|
+
{ phase: phaseFile, total: allItems.length, unchecked: items.length, checked: allItems.length - items.length },
|
|
2381
|
+
`phase items: ${items.length} unchecked of ${allItems.length} total`
|
|
2382
|
+
);
|
|
2161
2383
|
if (items.length === 0) {
|
|
2162
2384
|
const prompt = `You are executing one phase of a multi-file plan. Work through every unchecked item in order. Check each box as you complete it. Commit when the phase is done.
|
|
2163
2385
|
|
|
@@ -2171,11 +2393,20 @@ ${constraints}
|
|
|
2171
2393
|
${phaseContent}
|
|
2172
2394
|
|
|
2173
2395
|
Do not work on items from other phases. Do not ask questions.`;
|
|
2396
|
+
const adapterName = args.adapter?.name;
|
|
2397
|
+
const cfgObj2 = args.config;
|
|
2398
|
+
const models = cfgObj2?.models;
|
|
2399
|
+
const executionSpecifier = models?.execution;
|
|
2400
|
+
const executionModel = executionSpecifier ? resolveModel(executionSpecifier, adapterName ?? "opencode") : void 0;
|
|
2174
2401
|
return args.runRalphLoop({
|
|
2175
2402
|
prompt,
|
|
2176
2403
|
cwd: runCwd,
|
|
2177
2404
|
agentName: "autopilot-fast",
|
|
2405
|
+
model: executionModel,
|
|
2178
2406
|
maxIterations: maxIterationsPerPhase,
|
|
2407
|
+
...args.stallMs ? { stallMs: args.stallMs } : {},
|
|
2408
|
+
config: args.config,
|
|
2409
|
+
agentOverrides: args.agentOverrides,
|
|
2179
2410
|
laneId: useParallel2 ? laneId : void 0,
|
|
2180
2411
|
logger: args.logger,
|
|
2181
2412
|
emitter: args.emitter,
|
|
@@ -2185,7 +2416,7 @@ Do not work on items from other phases. Do not ask questions.`;
|
|
|
2185
2416
|
const perItemCap = Math.max(
|
|
2186
2417
|
1,
|
|
2187
2418
|
Math.min(
|
|
2188
|
-
|
|
2419
|
+
maxIterationsPerItem,
|
|
2189
2420
|
Math.ceil(maxIterationsPerPhase / items.length)
|
|
2190
2421
|
)
|
|
2191
2422
|
);
|
|
@@ -2199,7 +2430,48 @@ Do not work on items from other phases. Do not ask questions.`;
|
|
|
2199
2430
|
for (const item of items) {
|
|
2200
2431
|
const filesList = item.files.map((f) => `${f.path}${f.isNew ? " (CREATE)" : " (EDIT)"}`).join(", ");
|
|
2201
2432
|
const verify = item.verify?.trim() || "(no verify command declared)";
|
|
2202
|
-
const
|
|
2433
|
+
const enriched = item;
|
|
2434
|
+
let enrichmentBlock = "";
|
|
2435
|
+
if (enriched.mirror || enriched.context || enriched.conventions || enriched.proof) {
|
|
2436
|
+
const parts = [];
|
|
2437
|
+
if (typeof enriched.mirror === "string" && enriched.mirror.trim()) {
|
|
2438
|
+
parts.push(`Pattern reference (read this file for the pattern to follow):
|
|
2439
|
+
${enriched.mirror}`);
|
|
2440
|
+
}
|
|
2441
|
+
if (typeof enriched.context === "string" && enriched.context.trim()) {
|
|
2442
|
+
parts.push(`Code context:
|
|
2443
|
+
${enriched.context}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (typeof enriched.conventions === "string" && enriched.conventions.trim()) {
|
|
2446
|
+
parts.push(`Conventions: ${enriched.conventions}`);
|
|
2447
|
+
}
|
|
2448
|
+
if (typeof enriched.proof === "string" && enriched.proof.trim()) {
|
|
2449
|
+
const proofType = typeof enriched.proof_type === "string" ? ` (${enriched.proof_type})` : "";
|
|
2450
|
+
parts.push(`Acceptance proof${proofType}: ${enriched.proof}`);
|
|
2451
|
+
}
|
|
2452
|
+
enrichmentBlock = `
|
|
2453
|
+
## Enrichment context
|
|
2454
|
+
|
|
2455
|
+
${parts.join("\n\n")}
|
|
2456
|
+
|
|
2457
|
+
`;
|
|
2458
|
+
}
|
|
2459
|
+
const phaseConfigForItem = resolvePhaseConfig(
|
|
2460
|
+
args.config,
|
|
2461
|
+
phaseFile.replace(/\.(md|ya?ml)$/, "")
|
|
2462
|
+
);
|
|
2463
|
+
const executionStyle = phaseConfigForItem?.execution_style;
|
|
2464
|
+
const isTddMode = executionStyle !== "direct";
|
|
2465
|
+
let itemPrompt;
|
|
2466
|
+
if (isTddMode) {
|
|
2467
|
+
itemPrompt = `You are executing ONE item of a multi-item phase using TDD (test-driven development). Follow the red-green-refactor cycle strictly:
|
|
2468
|
+
|
|
2469
|
+
1. RED: Write a failing test/proof first. Run verify to confirm it fails.
|
|
2470
|
+
2. GREEN: Implement the minimal code to make the test pass. Run verify to confirm it passes.
|
|
2471
|
+
3. REFACTOR: Clean up the code if needed, re-run verify to ensure it still passes.
|
|
2472
|
+
4. MARK: Mark the item checkbox and commit only after the verify command passes.
|
|
2473
|
+
|
|
2474
|
+
Complete only this item, mark its checkbox in ${phaseFile}, commit, and stop. Do not work on other items.
|
|
2203
2475
|
|
|
2204
2476
|
## Overall goal
|
|
2205
2477
|
${goal}
|
|
@@ -2213,7 +2485,7 @@ ${constraints}
|
|
|
2213
2485
|
files: ${filesList || "(none declared)"}
|
|
2214
2486
|
verify: ${verify}
|
|
2215
2487
|
|
|
2216
|
-
|
|
2488
|
+
` + enrichmentBlock + `## Structured context
|
|
2217
2489
|
|
|
2218
2490
|
Files you may touch (ONLY these):
|
|
2219
2491
|
` + (item.files.length > 0 ? item.files.map((f) => ` - ${f.path} (${f.isNew ? "CREATE" : "EDIT"})`).join("\n") : " (none declared \u2014 confine edits to the phase's natural scope)") + `
|
|
@@ -2221,16 +2493,61 @@ Files you may touch (ONLY these):
|
|
|
2221
2493
|
Verify command (must exit 0):
|
|
2222
2494
|
- ${verify}
|
|
2223
2495
|
|
|
2496
|
+
TDD workflow:
|
|
2497
|
+
1. Write a test that fails (RED phase)
|
|
2498
|
+
2. Implement to make it pass (GREEN phase)
|
|
2499
|
+
3. Refactor if needed, re-verify (REFACTOR phase)
|
|
2500
|
+
4. Mark checkbox when verify passes
|
|
2501
|
+
|
|
2224
2502
|
Non-goals:
|
|
2503
|
+
- Do NOT skip the RED phase \u2014 always write the test first.
|
|
2225
2504
|
- Do NOT modify files outside the list above.
|
|
2226
2505
|
- Do NOT work on items other than ${item.id}.
|
|
2227
2506
|
|
|
2228
2507
|
When done: mark the checkbox for item ${item.id} in ${phaseFile} as [x], commit, and emit the autopilot-done sentinel.`;
|
|
2508
|
+
} else {
|
|
2509
|
+
itemPrompt = `You are executing ONE item of a multi-item phase. Complete only this item, mark its checkbox in ${phaseFile}, commit, and stop. Do not work on other items.
|
|
2510
|
+
|
|
2511
|
+
## Overall goal
|
|
2512
|
+
${goal}
|
|
2513
|
+
|
|
2514
|
+
## Constraints
|
|
2515
|
+
${constraints}
|
|
2516
|
+
|
|
2517
|
+
## Your item
|
|
2518
|
+
- [ ] id: ${item.id}
|
|
2519
|
+
intent: ${item.intent}
|
|
2520
|
+
files: ${filesList || "(none declared)"}
|
|
2521
|
+
verify: ${verify}
|
|
2522
|
+
|
|
2523
|
+
` + enrichmentBlock + `## Structured context
|
|
2524
|
+
|
|
2525
|
+
Files you may touch (ONLY these):
|
|
2526
|
+
` + (item.files.length > 0 ? item.files.map((f) => ` - ${f.path} (${f.isNew ? "CREATE" : "EDIT"})`).join("\n") : " (none declared \u2014 confine edits to the phase's natural scope)") + `
|
|
2527
|
+
|
|
2528
|
+
Verify command (must exit 0):
|
|
2529
|
+
- ${verify}
|
|
2530
|
+
|
|
2531
|
+
Non-goals:
|
|
2532
|
+
- Do NOT modify files outside the list above.
|
|
2533
|
+
- Do NOT work on items other than ${item.id}.
|
|
2534
|
+
|
|
2535
|
+
When done: mark the checkbox for item ${item.id} in ${phaseFile} as [x], commit, and emit the autopilot-done sentinel.`;
|
|
2536
|
+
}
|
|
2537
|
+
const adapterName = args.adapter?.name;
|
|
2538
|
+
const cfgObj2 = args.config;
|
|
2539
|
+
const models = cfgObj2?.models;
|
|
2540
|
+
const executionSpecifier = models?.execution;
|
|
2541
|
+
const executionModel = executionSpecifier ? resolveModel(executionSpecifier, adapterName ?? "opencode") : void 0;
|
|
2229
2542
|
const itemResult = await args.runRalphLoop({
|
|
2230
2543
|
prompt: itemPrompt,
|
|
2231
2544
|
cwd: runCwd,
|
|
2232
2545
|
agentName: "autopilot-fast",
|
|
2546
|
+
model: executionModel,
|
|
2233
2547
|
maxIterations: perItemCap,
|
|
2548
|
+
...args.stallMs ? { stallMs: args.stallMs } : {},
|
|
2549
|
+
config: args.config,
|
|
2550
|
+
agentOverrides: args.agentOverrides,
|
|
2234
2551
|
laneId: useParallel2 ? laneId : void 0,
|
|
2235
2552
|
logger: args.logger,
|
|
2236
2553
|
emitter: args.emitter,
|
|
@@ -2261,6 +2578,48 @@ When done: mark the checkbox for item ${item.id} in ${phaseFile} as [x], commit,
|
|
|
2261
2578
|
message: `Item ${item.id} failed: ${itemResult.message}`
|
|
2262
2579
|
};
|
|
2263
2580
|
}
|
|
2581
|
+
if (args.verifyConfig?.strategy === "after_item" && item.verify?.trim()) {
|
|
2582
|
+
const itemVerifyResult = await _runVerifyCommands([item], runCwd, {
|
|
2583
|
+
timeoutMs: args.verifyConfig.timeoutMs
|
|
2584
|
+
});
|
|
2585
|
+
if (itemVerifyResult.length > 0 && !itemVerifyResult[0].passed) {
|
|
2586
|
+
const failed = itemVerifyResult[0];
|
|
2587
|
+
log.warn(
|
|
2588
|
+
{
|
|
2589
|
+
phase: phaseFile,
|
|
2590
|
+
itemId: failed.itemId,
|
|
2591
|
+
command: failed.command,
|
|
2592
|
+
stderr: failed.stderr.slice(0, 500)
|
|
2593
|
+
},
|
|
2594
|
+
"per-item verify failed"
|
|
2595
|
+
);
|
|
2596
|
+
if (useYamlSpec && args.planPath) {
|
|
2597
|
+
markItemUnchecked(args.planPath, phaseFile, item.id);
|
|
2598
|
+
args.logger?.root.child({ component: "autopilot.per-item-verify" }).warn(
|
|
2599
|
+
{ phase: phaseFile, itemId: item.id },
|
|
2600
|
+
"unchecked item that failed verify \u2014 will retry next iteration"
|
|
2601
|
+
);
|
|
2602
|
+
} else if (args.writeFileSync) {
|
|
2603
|
+
const currentPhaseContent = args.readFileSync(phasePath);
|
|
2604
|
+
const uncheckContent = uncheckItemsInMarkdown(currentPhaseContent, [item.id]);
|
|
2605
|
+
if (uncheckContent !== currentPhaseContent) {
|
|
2606
|
+
args.writeFileSync(phasePath, uncheckContent);
|
|
2607
|
+
args.logger?.root.child({ component: "autopilot.per-item-verify" }).warn(
|
|
2608
|
+
{ phase: phaseFile, itemId: item.id },
|
|
2609
|
+
"unchecked item that failed verify \u2014 will retry next iteration"
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (args.verifyConfig.retryOnFailure) {
|
|
2614
|
+
return {
|
|
2615
|
+
exitReason: "sentinel",
|
|
2616
|
+
iterations: cumulativeIterations,
|
|
2617
|
+
cumulativeCostUsd: cumulativeCost,
|
|
2618
|
+
message: `Item ${item.id} verify failed: ${failed.stderr.split("\n")[0]}`
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2264
2623
|
}
|
|
2265
2624
|
return {
|
|
2266
2625
|
...lastItemResult,
|
|
@@ -2286,7 +2645,17 @@ When done: mark the checkbox for item ${item.id} in ${phaseFile} as [x], commit,
|
|
|
2286
2645
|
current: phasesCompleted + 1,
|
|
2287
2646
|
total: uncheckedPhases.length
|
|
2288
2647
|
});
|
|
2648
|
+
const phaseName = phaseFile.replace(/\.(md|ya?ml)$/, "");
|
|
2649
|
+
const phaseConfig = resolvePhaseConfig(
|
|
2650
|
+
opts.config,
|
|
2651
|
+
phaseName
|
|
2652
|
+
);
|
|
2653
|
+
const phaseOcAdapter = phaseConfig.adapters?.opencode;
|
|
2654
|
+
const baseAdapters = opts.config?.adapters;
|
|
2655
|
+
const baseOcAdapter = baseAdapters?.opencode;
|
|
2656
|
+
const phaseAgentOverrides = phaseOcAdapter?.agents ?? baseOcAdapter?.agents;
|
|
2289
2657
|
let result;
|
|
2658
|
+
const phaseVerifyConfig = extractVerifyConfig(phaseConfig);
|
|
2290
2659
|
if (opts.fast) {
|
|
2291
2660
|
result = await runItemsForPhase({
|
|
2292
2661
|
phaseFile,
|
|
@@ -2295,10 +2664,16 @@ When done: mark the checkbox for item ${item.id} in ${phaseFile} as [x], commit,
|
|
|
2295
2664
|
runCwd,
|
|
2296
2665
|
runRalphLoop: _runRalphLoop,
|
|
2297
2666
|
readFileSync: _readFileSync,
|
|
2667
|
+
writeFileSync: _writeFileSync,
|
|
2668
|
+
planPath: opts.planPath,
|
|
2298
2669
|
useParallel,
|
|
2670
|
+
stallMs,
|
|
2299
2671
|
logger: opts.logger,
|
|
2300
2672
|
emitter,
|
|
2301
|
-
adapter: opts.adapter
|
|
2673
|
+
adapter: opts.adapter,
|
|
2674
|
+
config: opts.config,
|
|
2675
|
+
agentOverrides: phaseAgentOverrides,
|
|
2676
|
+
verifyConfig: phaseVerifyConfig
|
|
2302
2677
|
});
|
|
2303
2678
|
} else {
|
|
2304
2679
|
const retrySection = retryContext ? `
|
|
@@ -2321,11 +2696,20 @@ ${constraints}
|
|
|
2321
2696
|
${phaseContent}
|
|
2322
2697
|
` + retrySection + `
|
|
2323
2698
|
Do not work on items from other phases. Do not ask questions \u2014 pick sensible defaults and note decisions in ## Open questions.`;
|
|
2699
|
+
const adapterName = opts.adapter?.name;
|
|
2700
|
+
const cfgObj2 = opts.config;
|
|
2701
|
+
const models = cfgObj2?.models;
|
|
2702
|
+
const executionSpecifier = models?.execution;
|
|
2703
|
+
const executionModel = executionSpecifier ? resolveModel(executionSpecifier, adapterName ?? "opencode") : void 0;
|
|
2324
2704
|
result = await _runRalphLoop({
|
|
2325
2705
|
prompt,
|
|
2326
2706
|
cwd: runCwd,
|
|
2327
2707
|
agentName: void 0,
|
|
2708
|
+
model: executionModel,
|
|
2328
2709
|
maxIterations: maxIterationsPerPhase,
|
|
2710
|
+
stallMs,
|
|
2711
|
+
config: opts.config,
|
|
2712
|
+
agentOverrides: phaseAgentOverrides,
|
|
2329
2713
|
laneId: useParallel ? laneId : void 0,
|
|
2330
2714
|
logger: opts.logger,
|
|
2331
2715
|
emitter,
|
|
@@ -2340,9 +2724,17 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2340
2724
|
const updatedPhaseContent = _readFileSync(phasePath);
|
|
2341
2725
|
let phaseComplete = useYamlSpec ? (() => {
|
|
2342
2726
|
const yamlItems = parseSpecItems(phasePath);
|
|
2727
|
+
const checkedCount = yamlItems.filter((i) => i.checked).length;
|
|
2728
|
+
log.info(
|
|
2729
|
+
{ phase: phaseFile, total: yamlItems.length, checked: checkedCount },
|
|
2730
|
+
`phase completion check: ${checkedCount}/${yamlItems.length} items checked`
|
|
2731
|
+
);
|
|
2343
2732
|
return yamlItems.length > 0 && yamlItems.every((i) => i.checked);
|
|
2344
2733
|
})() : isPhaseComplete(updatedPhaseContent);
|
|
2345
|
-
|
|
2734
|
+
const verifyConfig2 = extractVerifyConfig(opts.config);
|
|
2735
|
+
const shouldSkipVerify = verifyConfig2.strategy === "skip";
|
|
2736
|
+
const isAfterItemMode = verifyConfig2.strategy === "after_item" && opts.fast;
|
|
2737
|
+
if (phaseComplete && !shouldSkipVerify && !isAfterItemMode) {
|
|
2346
2738
|
const items = useYamlSpec ? parseSpecItems(phasePath) : parseItems(updatedPhaseContent);
|
|
2347
2739
|
const itemsWithVerify = items.filter((it) => it.verify?.trim());
|
|
2348
2740
|
if (itemsWithVerify.length > 0) {
|
|
@@ -2356,7 +2748,9 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2356
2748
|
phase: phaseFile,
|
|
2357
2749
|
itemCount: itemsWithVerify.length
|
|
2358
2750
|
});
|
|
2359
|
-
const phaseVerify = await _runVerifyCommands(itemsWithVerify, runCwd
|
|
2751
|
+
const phaseVerify = await _runVerifyCommands(itemsWithVerify, runCwd, {
|
|
2752
|
+
timeoutMs: verifyConfig2.timeoutMs
|
|
2753
|
+
});
|
|
2360
2754
|
verifyResults.push({ phaseFile, results: phaseVerify });
|
|
2361
2755
|
const failed = phaseVerify.filter((r) => !r.passed);
|
|
2362
2756
|
for (const r of phaseVerify) {
|
|
@@ -2371,6 +2765,7 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2371
2765
|
});
|
|
2372
2766
|
}
|
|
2373
2767
|
if (failed.length > 0) {
|
|
2768
|
+
const failedItemIds = failed.map((f) => f.itemId);
|
|
2374
2769
|
for (const f of failed) {
|
|
2375
2770
|
log.warn(
|
|
2376
2771
|
{
|
|
@@ -2382,6 +2777,20 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2382
2777
|
"verify command failed"
|
|
2383
2778
|
);
|
|
2384
2779
|
}
|
|
2780
|
+
for (const itemId of failedItemIds) {
|
|
2781
|
+
if (useYamlSpec) {
|
|
2782
|
+
markItemUnchecked(opts.planPath, phaseFile, itemId);
|
|
2783
|
+
} else {
|
|
2784
|
+
const uncheckContent = uncheckItemsInMarkdown(updatedPhaseContent, [itemId]);
|
|
2785
|
+
if (uncheckContent !== updatedPhaseContent) {
|
|
2786
|
+
_writeFileSync(phasePath, uncheckContent);
|
|
2787
|
+
log.warn(
|
|
2788
|
+
{ phase: phaseFile, itemId },
|
|
2789
|
+
"unchecked item that failed verify \u2014 will retry next iteration"
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2385
2794
|
phaseComplete = false;
|
|
2386
2795
|
verifyFailureSummary = failed.map((f) => `- \`${f.command}\` failed (item ${f.itemId}): ${f.stderr.split("\n").slice(-3).join(" ").slice(0, 200)}`).join("\n");
|
|
2387
2796
|
} else {
|
|
@@ -2401,13 +2810,16 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2401
2810
|
}
|
|
2402
2811
|
if (!phaseComplete && !SUCCESS_REASONS.has(result.exitReason)) {
|
|
2403
2812
|
log.warn({ phase: phaseFile, exitReason: result.exitReason }, "phase failed");
|
|
2404
|
-
|
|
2813
|
+
const rollbackConfig = cfgObj?.rollback_on_failure ?? "soft";
|
|
2814
|
+
if (rollbackConfig !== "off" && opts.fast && preHeadSha && preHeadSha !== "HEAD") {
|
|
2405
2815
|
const ok = await resetSoft(runCwd, preHeadSha, {
|
|
2406
2816
|
onWarn: (m) => log.warn(m)
|
|
2407
2817
|
});
|
|
2408
2818
|
if (ok) {
|
|
2409
2819
|
log.info({ ref: preHeadSha.slice(0, 8) }, "soft-reset to pre-phase state");
|
|
2410
2820
|
}
|
|
2821
|
+
} else if (rollbackConfig === "off" && opts.fast && preHeadSha && preHeadSha !== "HEAD") {
|
|
2822
|
+
log.info({ ref: preHeadSha.slice(0, 8) }, "rollback disabled by config \u2014 keeping phase changes");
|
|
2411
2823
|
}
|
|
2412
2824
|
emitter?.emitEvent({
|
|
2413
2825
|
type: "phase:done",
|
|
@@ -2418,6 +2830,12 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2418
2830
|
iterations: result.iterations,
|
|
2419
2831
|
costUsd: costThisPhase
|
|
2420
2832
|
});
|
|
2833
|
+
const phaseHooksConfig = extractHooksConfig(phaseConfig);
|
|
2834
|
+
const effectiveOnErrorHook = phaseHooksConfig.on_error ?? hooksConfig.on_error;
|
|
2835
|
+
if (effectiveOnErrorHook) {
|
|
2836
|
+
runHook(effectiveOnErrorHook, runCwd, verifyConfig2.timeoutMs).catch(() => {
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2421
2839
|
return {
|
|
2422
2840
|
phaseFile,
|
|
2423
2841
|
laneId,
|
|
@@ -2434,13 +2852,16 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2434
2852
|
{ phase: phaseFile, max: maxIterationsPerPhase },
|
|
2435
2853
|
"phase budget exhausted \u2014 moving on"
|
|
2436
2854
|
);
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2855
|
+
const checkpointEnabled = cfgObj?.checkpoint !== false;
|
|
2856
|
+
if (checkpointEnabled) {
|
|
2857
|
+
writeCheckpoint(opts.cwd, {
|
|
2858
|
+
planPath: opts.planPath,
|
|
2859
|
+
completedPhases: [...completedPhasesAcc],
|
|
2860
|
+
totalCostUsd,
|
|
2861
|
+
totalIterations,
|
|
2862
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2444
2865
|
}
|
|
2445
2866
|
emitter?.emitEvent({
|
|
2446
2867
|
type: "phase:done",
|
|
@@ -2463,7 +2884,7 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2463
2884
|
verifyFailures: verifyFailureSummary
|
|
2464
2885
|
};
|
|
2465
2886
|
};
|
|
2466
|
-
const recordPhaseCompletion = (phaseFile, result) => {
|
|
2887
|
+
const recordPhaseCompletion = async (phaseFile, result, phaseHooksConfig) => {
|
|
2467
2888
|
phasesCompleted++;
|
|
2468
2889
|
completedPhasesAcc.push(phaseFile);
|
|
2469
2890
|
if (useYamlSpec) {
|
|
@@ -2472,13 +2893,16 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2472
2893
|
const updatedMain = markPhaseChecked(_readFileSync(mainMdPath), phaseFile);
|
|
2473
2894
|
_writeFileSync(mainMdPath, updatedMain);
|
|
2474
2895
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2896
|
+
const checkpointEnabled = cfgObj?.checkpoint !== false;
|
|
2897
|
+
if (checkpointEnabled) {
|
|
2898
|
+
writeCheckpoint(opts.cwd, {
|
|
2899
|
+
planPath: opts.planPath,
|
|
2900
|
+
completedPhases: [...completedPhasesAcc],
|
|
2901
|
+
totalCostUsd,
|
|
2902
|
+
totalIterations,
|
|
2903
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2482
2906
|
log.info(
|
|
2483
2907
|
{
|
|
2484
2908
|
phase: phaseFile,
|
|
@@ -2489,18 +2913,28 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2489
2913
|
},
|
|
2490
2914
|
"phase complete"
|
|
2491
2915
|
);
|
|
2916
|
+
const effectivePostPhaseHook = phaseHooksConfig?.post_phase ?? hooksConfig.post_phase;
|
|
2917
|
+
if (effectivePostPhaseHook) {
|
|
2918
|
+
const hookResult = await runHook(effectivePostPhaseHook, opts.cwd, verifyConfig.timeoutMs);
|
|
2919
|
+
if (!hookResult.ok) {
|
|
2920
|
+
log.warn({ phase: phaseFile, output: hookResult.output }, "post_phase hook failed");
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2492
2923
|
};
|
|
2493
2924
|
if (!useParallel) {
|
|
2494
2925
|
for (const phaseFile of uncheckedPhases) {
|
|
2495
2926
|
if (opts.signal?.aborted) {
|
|
2496
2927
|
log.info({ completed: phasesCompleted, remaining: uncheckedPhases.length }, "abort signal received \u2014 writing checkpoint and stopping");
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2928
|
+
const checkpointEnabled = cfgObj?.checkpoint !== false;
|
|
2929
|
+
if (checkpointEnabled) {
|
|
2930
|
+
writeCheckpoint(opts.cwd, {
|
|
2931
|
+
planPath: opts.planPath,
|
|
2932
|
+
completedPhases: [...completedPhasesAcc],
|
|
2933
|
+
totalCostUsd,
|
|
2934
|
+
totalIterations,
|
|
2935
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2504
2938
|
return {
|
|
2505
2939
|
exitReason: "aborted",
|
|
2506
2940
|
iterations: totalIterations,
|
|
@@ -2508,13 +2942,20 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2508
2942
|
message: `Aborted after ${phasesCompleted}/${uncheckedPhases.length} phases completed`
|
|
2509
2943
|
};
|
|
2510
2944
|
}
|
|
2511
|
-
const
|
|
2945
|
+
const phaseName = phaseFile.replace(/\.(md|ya?ml)$/, "");
|
|
2946
|
+
const phaseConfig = resolvePhaseConfig(
|
|
2947
|
+
opts.config,
|
|
2948
|
+
phaseName
|
|
2949
|
+
);
|
|
2950
|
+
const phaseHooksConfig = extractHooksConfig(phaseConfig);
|
|
2951
|
+
const verifyRetryConfig = extractVerifyConfig(opts.config);
|
|
2952
|
+
const effectiveMaxRetries = verifyRetryConfig.retryOnFailure ? opts.maxPhaseRetries ?? (opts._deps ? 1 : 3) : 1;
|
|
2512
2953
|
let attempt = 0;
|
|
2513
2954
|
let phaseSuccess = false;
|
|
2514
2955
|
let lastVerifyFailures;
|
|
2515
|
-
while (attempt <
|
|
2956
|
+
while (attempt < effectiveMaxRetries && !phaseSuccess) {
|
|
2516
2957
|
attempt++;
|
|
2517
|
-
const isEscalation = attempt ===
|
|
2958
|
+
const isEscalation = attempt === effectiveMaxRetries && attempt > 1 && opts.fast;
|
|
2518
2959
|
if (attempt > 1) {
|
|
2519
2960
|
log.info(
|
|
2520
2961
|
{ phase: phaseFile, attempt, escalate: isEscalation },
|
|
@@ -2533,13 +2974,25 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2533
2974
|
if (isEscalation) {
|
|
2534
2975
|
opts.fast = false;
|
|
2535
2976
|
}
|
|
2977
|
+
const effectivePrePhaseHook = phaseHooksConfig.pre_phase ?? hooksConfig.pre_phase;
|
|
2978
|
+
if (effectivePrePhaseHook) {
|
|
2979
|
+
const hookResult = await runHook(effectivePrePhaseHook, opts.cwd, verifyConfig.timeoutMs);
|
|
2980
|
+
if (!hookResult.ok) {
|
|
2981
|
+
log.warn({ phase: phaseFile, output: hookResult.output }, "pre_phase hook failed \u2014 skipping phase");
|
|
2982
|
+
if (isEscalation) {
|
|
2983
|
+
opts.fast = originalFast;
|
|
2984
|
+
}
|
|
2985
|
+
phaseSuccess = false;
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2536
2989
|
const r = await runPhaseInner(phaseFile, "lane-1", opts.cwd, lastVerifyFailures);
|
|
2537
2990
|
if (isEscalation) {
|
|
2538
2991
|
opts.fast = originalFast;
|
|
2539
2992
|
}
|
|
2540
2993
|
lastVerifyFailures = r.verifyFailures;
|
|
2541
2994
|
if (r.phaseComplete) {
|
|
2542
|
-
recordPhaseCompletion(phaseFile, r.phaseLoopResult);
|
|
2995
|
+
await recordPhaseCompletion(phaseFile, r.phaseLoopResult, phaseHooksConfig);
|
|
2543
2996
|
phaseSuccess = true;
|
|
2544
2997
|
break;
|
|
2545
2998
|
}
|
|
@@ -2551,7 +3004,7 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2551
3004
|
message: `${r.phaseLoopResult.message} (phase ${phaseFile}, ${phasesCompleted}/${uncheckedPhases.length} phases completed, total $${totalCostUsd.toFixed(2)})`
|
|
2552
3005
|
};
|
|
2553
3006
|
}
|
|
2554
|
-
if (attempt >=
|
|
3007
|
+
if (attempt >= effectiveMaxRetries) {
|
|
2555
3008
|
log.warn({ phase: phaseFile, attempts: attempt }, "phase exhausted retries \u2014 moving on");
|
|
2556
3009
|
}
|
|
2557
3010
|
}
|
|
@@ -2601,6 +3054,33 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2601
3054
|
log.warn({ phase: phaseFile, err: msg }, "worktree create failed \u2014 falling back to main cwd");
|
|
2602
3055
|
}
|
|
2603
3056
|
const runCwd = handle?.path ?? opts.cwd;
|
|
3057
|
+
const phaseName = phaseFile.replace(/\.(md|ya?ml)$/, "");
|
|
3058
|
+
const phaseConfig = resolvePhaseConfig(
|
|
3059
|
+
opts.config,
|
|
3060
|
+
phaseName
|
|
3061
|
+
);
|
|
3062
|
+
const phaseHooksConfig = extractHooksConfig(phaseConfig);
|
|
3063
|
+
const effectivePrePhaseHook = phaseHooksConfig.pre_phase ?? hooksConfig.pre_phase;
|
|
3064
|
+
if (effectivePrePhaseHook) {
|
|
3065
|
+
const hookResult = await runHook(effectivePrePhaseHook, runCwd, verifyConfig.timeoutMs);
|
|
3066
|
+
if (!hookResult.ok) {
|
|
3067
|
+
log.warn({ phase: phaseFile, output: hookResult.output }, "pre_phase hook failed \u2014 skipping phase");
|
|
3068
|
+
return {
|
|
3069
|
+
phaseFile,
|
|
3070
|
+
laneId,
|
|
3071
|
+
ok: false,
|
|
3072
|
+
fatal: false,
|
|
3073
|
+
iterations: 0,
|
|
3074
|
+
costUsd: 0,
|
|
3075
|
+
phaseLoopResult: {
|
|
3076
|
+
exitReason: "error",
|
|
3077
|
+
iterations: 0,
|
|
3078
|
+
message: "pre_phase hook failed"
|
|
3079
|
+
},
|
|
3080
|
+
phaseComplete: false
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
2604
3084
|
const result = await runPhaseInner(phaseFile, laneId, runCwd);
|
|
2605
3085
|
if (handle) {
|
|
2606
3086
|
handles.set(phaseFile, handle);
|
|
@@ -2629,12 +3109,18 @@ Do not work on items from other phases. Do not ask questions \u2014 pick sensibl
|
|
|
2629
3109
|
});
|
|
2630
3110
|
for (const r of lanesResult.results) {
|
|
2631
3111
|
if (r.ok) {
|
|
2632
|
-
|
|
3112
|
+
const phaseName = r.phaseFile.replace(/\.(md|ya?ml)$/, "");
|
|
3113
|
+
const phaseConfig = resolvePhaseConfig(
|
|
3114
|
+
opts.config,
|
|
3115
|
+
phaseName
|
|
3116
|
+
);
|
|
3117
|
+
const phaseHooksConfig = extractHooksConfig(phaseConfig);
|
|
3118
|
+
await recordPhaseCompletion(r.phaseFile, {
|
|
2633
3119
|
exitReason: "sentinel",
|
|
2634
3120
|
iterations: r.iterations,
|
|
2635
3121
|
message: "completed via parallel lane",
|
|
2636
3122
|
cumulativeCostUsd: r.costUsd
|
|
2637
|
-
});
|
|
3123
|
+
}, phaseHooksConfig);
|
|
2638
3124
|
}
|
|
2639
3125
|
}
|
|
2640
3126
|
const fatalResult = lanesResult.results.find((r) => r.fatal);
|
|
@@ -2678,19 +3164,49 @@ ${constraints}
|
|
|
2678
3164
|
${finalMainContent}
|
|
2679
3165
|
|
|
2680
3166
|
Only work on the unchecked items in main.md's acceptance criteria. Phase items are already done. Do not ask questions.`;
|
|
2681
|
-
const
|
|
3167
|
+
const adapterName = opts.adapter?.name;
|
|
3168
|
+
const cfgObj2 = opts.config;
|
|
3169
|
+
const models = cfgObj2?.models;
|
|
3170
|
+
const executionSpecifier = models?.execution;
|
|
3171
|
+
const executionModel = executionSpecifier ? resolveModel(executionSpecifier, adapterName ?? "opencode") : void 0;
|
|
3172
|
+
const crossAdapters = opts.config?.adapters;
|
|
3173
|
+
const crossOcAdapter = crossAdapters?.opencode;
|
|
3174
|
+
const crossCuttingAgentOverrides = crossOcAdapter?.agents;
|
|
3175
|
+
const crossResult = await _runRalphLoop({
|
|
3176
|
+
prompt: crossCuttingPrompt,
|
|
3177
|
+
cwd: opts.cwd,
|
|
3178
|
+
agentName: opts.fast ? "autopilot-fast" : void 0,
|
|
3179
|
+
model: executionModel,
|
|
3180
|
+
stallMs,
|
|
3181
|
+
config: opts.config,
|
|
3182
|
+
agentOverrides: crossCuttingAgentOverrides,
|
|
3183
|
+
logger: opts.logger,
|
|
3184
|
+
emitter,
|
|
3185
|
+
adapter: opts.adapter
|
|
3186
|
+
});
|
|
2682
3187
|
totalIterations += crossResult.iterations;
|
|
2683
3188
|
totalCostUsd += crossResult.cumulativeCostUsd ?? 0;
|
|
2684
3189
|
lastResult = crossResult;
|
|
2685
3190
|
}
|
|
2686
3191
|
}
|
|
2687
3192
|
log.info({ completed: phasesCompleted, total: uncheckedPhases.length, iterations: totalIterations, cost: totalCostUsd.toFixed(2) }, "all phases done");
|
|
3193
|
+
if (hooksConfig.post_run) {
|
|
3194
|
+
const hookResult = await runHook(hooksConfig.post_run, opts.cwd, verifyConfig.timeoutMs);
|
|
3195
|
+
if (!hookResult.ok) {
|
|
3196
|
+
log.warn({ output: hookResult.output }, "post_run hook failed");
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
2688
3199
|
deleteCheckpoint(opts.cwd);
|
|
2689
3200
|
let changesetPath;
|
|
2690
|
-
|
|
3201
|
+
const changesetEnabled = cfgObj?.changeset !== false;
|
|
3202
|
+
if (changesetEnabled && !opts._deps && phasesCompleted === uncheckedPhases.length && uncheckedPhases.length > 0) {
|
|
2691
3203
|
try {
|
|
2692
|
-
const { generateChangeset } = await import("./changeset-generator-
|
|
2693
|
-
const
|
|
3204
|
+
const { generateChangeset } = await import("./changeset-generator-HAHYSSUR.js");
|
|
3205
|
+
const changesetOpts = {
|
|
3206
|
+
packageName: cfgObj?.changeset_package,
|
|
3207
|
+
bumpLevel: cfgObj?.changeset_bump
|
|
3208
|
+
};
|
|
3209
|
+
const cs = await generateChangeset(opts.planPath, opts.cwd, changesetOpts);
|
|
2694
3210
|
changesetPath = cs.path;
|
|
2695
3211
|
log.info(
|
|
2696
3212
|
{ path: cs.path, bumpLevel: cs.bumpLevel },
|
|
@@ -2704,7 +3220,7 @@ Only work on the unchecked items in main.md's acceptance criteria. Phase items a
|
|
|
2704
3220
|
let prUrl;
|
|
2705
3221
|
if (!opts._deps && opts.ship && phasesCompleted === uncheckedPhases.length && uncheckedPhases.length > 0) {
|
|
2706
3222
|
try {
|
|
2707
|
-
const { autoShip } = await import("./auto-ship-
|
|
3223
|
+
const { autoShip } = await import("./auto-ship-EVLBKHUZ.js");
|
|
2708
3224
|
const shipResult = await autoShip({
|
|
2709
3225
|
planPath: opts.planPath,
|
|
2710
3226
|
repoRoot: opts.cwd
|