@open-tomato/rafa 0.40.0 → 0.41.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/README.md +1 -1
- package/dist/cli.js +2760 -1506
- package/dist/continue-criteria.md +31 -0
- package/dist/continue-decision-prompt.md +94 -0
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-334y39tn.js → index-3jpz9nr3.js} +63 -8
- package/dist/{index-8veaj0t9.js → index-cd0wzc12.js} +76 -6
- package/dist/{index-j7kjj0dd.js → index-snf04fpt.js} +256 -530
- package/dist/{index-zcbj07aq.js → index-wjsarzf5.js} +2090 -725
- package/dist/{index-krpmxpkh.js → index-zg0qsb5b.js} +29 -14
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/package.json +2 -2
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
escapeBlockerText,
|
|
5
5
|
findNextTask,
|
|
6
6
|
insertTrackerTask,
|
|
7
|
+
listOpenTasks,
|
|
8
|
+
listTrackerTasks,
|
|
7
9
|
loadPlanPrerequisites,
|
|
8
10
|
malformedPrerequisiteLines,
|
|
9
11
|
parsePlan,
|
|
@@ -11,10 +13,11 @@ import {
|
|
|
11
13
|
prerequisitesPathForPlan,
|
|
12
14
|
renderInjection,
|
|
13
15
|
runPreflight,
|
|
16
|
+
splitBlockerComment,
|
|
14
17
|
trackerPathFor,
|
|
15
18
|
updateTrackerLine,
|
|
16
19
|
writeTrackerBlocker
|
|
17
|
-
} from "./index-
|
|
20
|
+
} from "./index-zg0qsb5b.js";
|
|
18
21
|
import {
|
|
19
22
|
ACROSS_STORES_ORDER,
|
|
20
23
|
CORE_ADAPTER_REGISTRY,
|
|
@@ -106,10 +109,12 @@ import {
|
|
|
106
109
|
runClaude,
|
|
107
110
|
runClaudeCaptured,
|
|
108
111
|
runsDir,
|
|
112
|
+
samePlan,
|
|
109
113
|
scanLayout,
|
|
110
114
|
selectEffortStore,
|
|
111
115
|
selectSync,
|
|
112
116
|
selfAndAncestors,
|
|
117
|
+
sessionDecisions,
|
|
113
118
|
sessionFilePath,
|
|
114
119
|
sessionPhase,
|
|
115
120
|
spawnClaude,
|
|
@@ -129,7 +134,7 @@ import {
|
|
|
129
134
|
writeNames,
|
|
130
135
|
writePositionFile,
|
|
131
136
|
writeSqliteStore
|
|
132
|
-
} from "./index-
|
|
137
|
+
} from "./index-cd0wzc12.js";
|
|
133
138
|
import {
|
|
134
139
|
CHANGE_LEVELS,
|
|
135
140
|
CONFIG_DEFAULTS,
|
|
@@ -146,6 +151,7 @@ import {
|
|
|
146
151
|
collisionMessage,
|
|
147
152
|
configFilePath,
|
|
148
153
|
findTierItem,
|
|
154
|
+
isLoopRetryCount,
|
|
149
155
|
optionalPrerequisite,
|
|
150
156
|
parseConfigText,
|
|
151
157
|
parseReport,
|
|
@@ -158,7 +164,7 @@ import {
|
|
|
158
164
|
requiredPrerequisite,
|
|
159
165
|
resolveTiers,
|
|
160
166
|
stampPrompt
|
|
161
|
-
} from "./index-
|
|
167
|
+
} from "./index-3jpz9nr3.js";
|
|
162
168
|
import {
|
|
163
169
|
promotable
|
|
164
170
|
} from "./index-d6d0942q.js";
|
|
@@ -13832,7 +13838,7 @@ var CHECKOUT_ROSTER_TIMEOUT_MS = 30000;
|
|
|
13832
13838
|
var DESCRIBE_LINE = `bun ${RAFA_ENTRY} describe --output=json`;
|
|
13833
13839
|
async function coreRoster() {
|
|
13834
13840
|
const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
|
|
13835
|
-
import("./index-
|
|
13841
|
+
import("./index-snf04fpt.js"),
|
|
13836
13842
|
import("./describe-pwhh0vws.js")
|
|
13837
13843
|
]);
|
|
13838
13844
|
return describeRegistry(CORE_REGISTRY, version);
|
|
@@ -19475,7 +19481,7 @@ import {
|
|
|
19475
19481
|
chmodSync,
|
|
19476
19482
|
copyFileSync,
|
|
19477
19483
|
existsSync as existsSync19,
|
|
19478
|
-
mkdirSync as
|
|
19484
|
+
mkdirSync as mkdirSync10,
|
|
19479
19485
|
readdirSync as readdirSync11,
|
|
19480
19486
|
readFileSync as readFileSync26,
|
|
19481
19487
|
realpathSync as realpathSync2,
|
|
@@ -19484,16 +19490,206 @@ import {
|
|
|
19484
19490
|
statSync as statSync11,
|
|
19485
19491
|
symlinkSync
|
|
19486
19492
|
} from "fs";
|
|
19487
|
-
import { basename as basename7, delimiter as delimiter3, dirname as dirname10, join as
|
|
19493
|
+
import { basename as basename7, delimiter as delimiter3, dirname as dirname10, join as join21, relative as relative6, resolve as resolve16 } from "path";
|
|
19488
19494
|
|
|
19489
19495
|
// src/start/runtime.ts
|
|
19490
19496
|
import { existsSync as existsSync18, readdirSync as readdirSync10, realpathSync, statSync as statSync10 } from "fs";
|
|
19491
19497
|
import { homedir as homedir5 } from "os";
|
|
19492
|
-
import { isAbsolute as isAbsolute4, join as
|
|
19498
|
+
import { isAbsolute as isAbsolute4, join as join20, relative as relative5, resolve as resolve15, sep } from "path";
|
|
19493
19499
|
|
|
19494
19500
|
// src/start/session.ts
|
|
19495
19501
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
19496
19502
|
import { relative as relative4 } from "path";
|
|
19503
|
+
|
|
19504
|
+
// src/loop/events-file.ts
|
|
19505
|
+
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
19506
|
+
|
|
19507
|
+
// src/start/loop-events.ts
|
|
19508
|
+
import { appendFileSync, mkdirSync as mkdirSync9 } from "fs";
|
|
19509
|
+
import { join as join19 } from "path";
|
|
19510
|
+
var MINUTE_MS = 60000;
|
|
19511
|
+
var TOKENS_PER_K = 1000;
|
|
19512
|
+
function quoted2(text) {
|
|
19513
|
+
return `"${oneLine(text).replaceAll('"', "\\\"")}"`;
|
|
19514
|
+
}
|
|
19515
|
+
function taskKind(position, verb) {
|
|
19516
|
+
return padKind(`task ${position.index}/${position.total} ${verb}`);
|
|
19517
|
+
}
|
|
19518
|
+
function minutes(durationMs) {
|
|
19519
|
+
return `${Math.max(1, Math.round(durationMs / MINUTE_MS))}m`;
|
|
19520
|
+
}
|
|
19521
|
+
function summaryOf2(event) {
|
|
19522
|
+
switch (event.kind) {
|
|
19523
|
+
case "task-start":
|
|
19524
|
+
return `${taskKind(event.position, "start")}${quoted2(event.text)}`;
|
|
19525
|
+
case "task-done":
|
|
19526
|
+
return event.tokens === null ? `${taskKind(event.position, "done")}${minutes(event.durationMs)}` : `${taskKind(event.position, "done")}${minutes(event.durationMs)} ${Math.round(event.tokens / TOKENS_PER_K)}k tokens`;
|
|
19527
|
+
case "task-blocked":
|
|
19528
|
+
return `${taskKind(event.position, "blocked")}${oneLine(event.reason)}`;
|
|
19529
|
+
case "wrap-up":
|
|
19530
|
+
return `${padKind("wrap-up")}${event.phase}`;
|
|
19531
|
+
case "pr":
|
|
19532
|
+
return `pr #${event.number} opened`;
|
|
19533
|
+
case "no-pr":
|
|
19534
|
+
return `${padKind("no pr")}${oneLine(event.reason)}`;
|
|
19535
|
+
case "halt":
|
|
19536
|
+
return `${padKind("halt")}${oneLine(event.reason)}`;
|
|
19537
|
+
case "retry":
|
|
19538
|
+
return `${padKind(`retry ${event.attempt}/${event.of}`)}${oneLine(event.reason)}`;
|
|
19539
|
+
case "decision":
|
|
19540
|
+
return `${padKind(`decision ${event.strategy}`)}line ${event.line}: ${oneLine(event.reason)}`;
|
|
19541
|
+
case "decision-needed":
|
|
19542
|
+
return `${padKind("decision needed")}line ${event.line} ${quoted2(event.task)}`;
|
|
19543
|
+
case "passed-over":
|
|
19544
|
+
return `${padKind("passed over")}${passedOverList(event.tasks)}`;
|
|
19545
|
+
case "inherited":
|
|
19546
|
+
return `${padKind("inherited")}${oneLine(`${event.file} > ${event.name}`)}`;
|
|
19547
|
+
case "error":
|
|
19548
|
+
return `${padKind("error")}${firstLine4(event.message)}`;
|
|
19549
|
+
}
|
|
19550
|
+
}
|
|
19551
|
+
function passedOverList(tasks) {
|
|
19552
|
+
const noun = tasks.length === 1 ? "task" : "tasks";
|
|
19553
|
+
return `${tasks.length} ${noun}: ${tasks.map((task) => `line ${task.line} (${task.strategy})`).join(", ")}`;
|
|
19554
|
+
}
|
|
19555
|
+
function firstLine4(message) {
|
|
19556
|
+
return message.split(`
|
|
19557
|
+
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
19558
|
+
}
|
|
19559
|
+
var EVENTS_EXTENSION = ".events.ndjson";
|
|
19560
|
+
var bound = null;
|
|
19561
|
+
function eventsFilePath(root, sessionId) {
|
|
19562
|
+
if (!isSessionId(sessionId)) {
|
|
19563
|
+
throw new Error(`events file: unusable session id ${JSON.stringify(sessionId)}`);
|
|
19564
|
+
}
|
|
19565
|
+
return join19(runsDir(root), `${sessionId}${EVENTS_EXTENSION}`);
|
|
19566
|
+
}
|
|
19567
|
+
function bindEventsFile(root, sessionId, warn = (line) => {
|
|
19568
|
+
process.stderr.write(line);
|
|
19569
|
+
}) {
|
|
19570
|
+
const file = eventsFilePath(root, sessionId);
|
|
19571
|
+
try {
|
|
19572
|
+
mkdirSync9(runsDir(root), { recursive: true });
|
|
19573
|
+
} catch {}
|
|
19574
|
+
bound = { file, warn, warned: false };
|
|
19575
|
+
return file;
|
|
19576
|
+
}
|
|
19577
|
+
function unbindEventsFile() {
|
|
19578
|
+
bound = null;
|
|
19579
|
+
}
|
|
19580
|
+
function appendEventLine(line) {
|
|
19581
|
+
if (bound === null)
|
|
19582
|
+
return;
|
|
19583
|
+
try {
|
|
19584
|
+
appendFileSync(bound.file, `${JSON.stringify(line)}
|
|
19585
|
+
`);
|
|
19586
|
+
} catch (error) {
|
|
19587
|
+
if (bound.warned)
|
|
19588
|
+
return;
|
|
19589
|
+
bound = { ...bound, warned: true };
|
|
19590
|
+
bound.warn(`rafa: cannot write the events file ${bound.file}: ${messageOf(error)}
|
|
19591
|
+
`);
|
|
19592
|
+
}
|
|
19593
|
+
}
|
|
19594
|
+
function emitLoopEvent(event, now = () => new Date) {
|
|
19595
|
+
const { kind, ...data } = event;
|
|
19596
|
+
const line = { name: kind, summary: summaryOf2(event), data, ts: now().toISOString() };
|
|
19597
|
+
appendEventLine(line);
|
|
19598
|
+
activeOutput().emit({ type: "event", ...line });
|
|
19599
|
+
}
|
|
19600
|
+
async function unlessText(read) {
|
|
19601
|
+
return activeOutputMode() === "text" ? null : read();
|
|
19602
|
+
}
|
|
19603
|
+
function taskPosition(trackerContent, lineNum) {
|
|
19604
|
+
const { tasks } = parsePlan(trackerContent);
|
|
19605
|
+
return { index: tasks.findIndex((task) => task.lineNum === lineNum) + 1, total: tasks.length };
|
|
19606
|
+
}
|
|
19607
|
+
async function taskTokens(checkout, sessionId, home) {
|
|
19608
|
+
try {
|
|
19609
|
+
const { usage } = await readSessionLog(join19(sessionLogDir(checkout, home), `${sessionId}.jsonl`));
|
|
19610
|
+
return usage.inputTokens + usage.cacheCreationInputTokens + usage.outputTokens;
|
|
19611
|
+
} catch {
|
|
19612
|
+
return null;
|
|
19613
|
+
}
|
|
19614
|
+
}
|
|
19615
|
+
|
|
19616
|
+
// src/loop/events-file.ts
|
|
19617
|
+
var NEWLINE = 10;
|
|
19618
|
+
function eventsFileOf(root, record) {
|
|
19619
|
+
return eventsFilePath(root, record.sessionId);
|
|
19620
|
+
}
|
|
19621
|
+
function isPlainObject(value) {
|
|
19622
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19623
|
+
}
|
|
19624
|
+
function parseEventLine(line) {
|
|
19625
|
+
let value;
|
|
19626
|
+
try {
|
|
19627
|
+
value = JSON.parse(line);
|
|
19628
|
+
} catch {
|
|
19629
|
+
return null;
|
|
19630
|
+
}
|
|
19631
|
+
if (!isPlainObject(value))
|
|
19632
|
+
return null;
|
|
19633
|
+
const { name: name2, summary, data, ts } = value;
|
|
19634
|
+
if (typeof name2 !== "string" || typeof summary !== "string" || typeof ts !== "string")
|
|
19635
|
+
return null;
|
|
19636
|
+
if (!isPlainObject(data))
|
|
19637
|
+
return null;
|
|
19638
|
+
return Object.freeze({ name: name2, summary, data: Object.freeze({ ...data }), ts });
|
|
19639
|
+
}
|
|
19640
|
+
function bytesFrom(fd, offset) {
|
|
19641
|
+
const length = fstatSync(fd).size - offset;
|
|
19642
|
+
if (length <= 0)
|
|
19643
|
+
return null;
|
|
19644
|
+
const buffer = Buffer.alloc(length);
|
|
19645
|
+
let filled = 0;
|
|
19646
|
+
while (filled < length) {
|
|
19647
|
+
const read = readSync(fd, buffer, filled, length - filled, offset + filled);
|
|
19648
|
+
if (read === 0)
|
|
19649
|
+
break;
|
|
19650
|
+
filled += read;
|
|
19651
|
+
}
|
|
19652
|
+
return buffer.subarray(0, filled);
|
|
19653
|
+
}
|
|
19654
|
+
function readTail(file, offset) {
|
|
19655
|
+
let fd;
|
|
19656
|
+
try {
|
|
19657
|
+
fd = openSync(file, "r");
|
|
19658
|
+
} catch (error) {
|
|
19659
|
+
if (errorCode(error) === "ENOENT")
|
|
19660
|
+
return "absent";
|
|
19661
|
+
throw error;
|
|
19662
|
+
}
|
|
19663
|
+
try {
|
|
19664
|
+
return bytesFrom(fd, offset);
|
|
19665
|
+
} finally {
|
|
19666
|
+
closeSync(fd);
|
|
19667
|
+
}
|
|
19668
|
+
}
|
|
19669
|
+
function readEventsFrom(file, offset) {
|
|
19670
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
19671
|
+
throw new RangeError(`events file: unusable offset ${String(offset)}`);
|
|
19672
|
+
}
|
|
19673
|
+
const tail = readTail(file, offset);
|
|
19674
|
+
if (tail === "absent")
|
|
19675
|
+
return Object.freeze({ kind: "absent" });
|
|
19676
|
+
const end = tail?.lastIndexOf(NEWLINE) ?? -1;
|
|
19677
|
+
if (tail === null || end < 0) {
|
|
19678
|
+
return Object.freeze({ kind: "read", events: Object.freeze([]), malformed: 0, offset });
|
|
19679
|
+
}
|
|
19680
|
+
const lines = tail.subarray(0, end).toString("utf8").split(`
|
|
19681
|
+
`);
|
|
19682
|
+
const parsed2 = lines.map(parseEventLine);
|
|
19683
|
+
const events = parsed2.filter((event) => event !== null);
|
|
19684
|
+
return Object.freeze({
|
|
19685
|
+
kind: "read",
|
|
19686
|
+
events: Object.freeze(events),
|
|
19687
|
+
malformed: parsed2.length - events.length,
|
|
19688
|
+
offset: offset + end + 1
|
|
19689
|
+
});
|
|
19690
|
+
}
|
|
19691
|
+
|
|
19692
|
+
// src/start/session.ts
|
|
19497
19693
|
var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
|
|
19498
19694
|
function recordLabel(repoRoot, sessionId) {
|
|
19499
19695
|
return relative4(repoRoot, sessionFilePath(repoRoot, sessionId));
|
|
@@ -19552,6 +19748,7 @@ function sessionHandle(repoRoot, sessionId) {
|
|
|
19552
19748
|
pullRequestStarted: () => change("the pull-request phase", { phase: "pull-request" }),
|
|
19553
19749
|
ciStarted: () => change("the ci phase", { phase: "ci" }),
|
|
19554
19750
|
repairStarted: () => change("the repair phase", { phase: "repair" }),
|
|
19751
|
+
decisionsChanged: (list) => change("the pass-over list", { decisions: list }),
|
|
19555
19752
|
finished: () => {
|
|
19556
19753
|
reachedEnd = true;
|
|
19557
19754
|
},
|
|
@@ -19607,6 +19804,37 @@ function openRunSession(options) {
|
|
|
19607
19804
|
}
|
|
19608
19805
|
return sessionHandle(repoRoot, draft.sessionId);
|
|
19609
19806
|
}
|
|
19807
|
+
function sameRunPlace(repoRoot, record, plan) {
|
|
19808
|
+
if (record.branch !== plan.branch)
|
|
19809
|
+
return false;
|
|
19810
|
+
if (record.worktree === undefined)
|
|
19811
|
+
return true;
|
|
19812
|
+
return plan.checkout !== repoRoot && record.worktree === plan.checkout;
|
|
19813
|
+
}
|
|
19814
|
+
var ENDED_STATES = new Set(["stopped", "done"]);
|
|
19815
|
+
function endedRunsNewestFirst(repoRoot, plan, seams) {
|
|
19816
|
+
const wanted = { planStub: plan.planStub, plan: relative4(repoRoot, plan.planPath) };
|
|
19817
|
+
return readSessions(repoRoot, seams).filter((record) => ENDED_STATES.has(record.state) && samePlan(record, wanted) && sameRunPlace(repoRoot, record, plan)).reverse();
|
|
19818
|
+
}
|
|
19819
|
+
function readPreviousPassOver(repoRoot, plan, seams = {}) {
|
|
19820
|
+
const newest = endedRunsNewestFirst(repoRoot, plan, seams)[0];
|
|
19821
|
+
return newest === undefined ? [] : sessionDecisions(newest);
|
|
19822
|
+
}
|
|
19823
|
+
var TASK_EVENTS = new Set(["task-start", "decision", "decision-needed"]);
|
|
19824
|
+
function neededOf(data) {
|
|
19825
|
+
const { task, line } = data;
|
|
19826
|
+
return typeof task === "string" && typeof line === "number" ? { task, line } : null;
|
|
19827
|
+
}
|
|
19828
|
+
function readPreviousDecisionNeeded(repoRoot, plan, seams = {}) {
|
|
19829
|
+
for (const record of endedRunsNewestFirst(repoRoot, plan, seams)) {
|
|
19830
|
+
const read = readEventsFrom(eventsFileOf(repoRoot, record), 0);
|
|
19831
|
+
const last = read.kind === "read" ? read.events.filter((event) => TASK_EVENTS.has(event.name)).at(-1) : undefined;
|
|
19832
|
+
if (last === undefined)
|
|
19833
|
+
continue;
|
|
19834
|
+
return last.name === "decision-needed" ? neededOf(last.data) : null;
|
|
19835
|
+
}
|
|
19836
|
+
return null;
|
|
19837
|
+
}
|
|
19610
19838
|
|
|
19611
19839
|
// src/start/run-config.ts
|
|
19612
19840
|
function refuseEffortDirRun(env) {
|
|
@@ -19684,7 +19912,7 @@ function announcePlanIssues(planContent) {
|
|
|
19684
19912
|
|
|
19685
19913
|
// src/start/runtime.ts
|
|
19686
19914
|
var RUNTIME_FLAG = "--runtime";
|
|
19687
|
-
var RUNTIME_SUBDIR =
|
|
19915
|
+
var RUNTIME_SUBDIR = join20(".rafa", "runtime");
|
|
19688
19916
|
var RUNTIME_ENTRY = "cli.js";
|
|
19689
19917
|
var SOURCE_DIR = "src";
|
|
19690
19918
|
var RUNTIME_COMMAND = ["start"];
|
|
@@ -19711,7 +19939,7 @@ function resolveRuntime(value, places) {
|
|
|
19711
19939
|
throw refusal2([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
|
|
19712
19940
|
}
|
|
19713
19941
|
const isVersion = VERSION_SHAPE2.test(value);
|
|
19714
|
-
const named3 = isVersion ?
|
|
19942
|
+
const named3 = isVersion ? join20(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve15(places.cwd, value);
|
|
19715
19943
|
refuseInsideSource(value, named3, places);
|
|
19716
19944
|
const entry = entryOf2(value, named3, isVersion, places.home);
|
|
19717
19945
|
const real = realpathSync(entry);
|
|
@@ -19722,7 +19950,7 @@ function entryOf2(value, named3, isVersion, home) {
|
|
|
19722
19950
|
if (isVersion) {
|
|
19723
19951
|
if (isFile2(named3))
|
|
19724
19952
|
return named3;
|
|
19725
|
-
const root =
|
|
19953
|
+
const root = join20(home, RUNTIME_SUBDIR);
|
|
19726
19954
|
const installed = installedVersions(root);
|
|
19727
19955
|
throw refusal2([
|
|
19728
19956
|
`\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named3} is no file.`,
|
|
@@ -19731,7 +19959,7 @@ function entryOf2(value, named3, isVersion, home) {
|
|
|
19731
19959
|
}
|
|
19732
19960
|
if (isFile2(named3))
|
|
19733
19961
|
return named3;
|
|
19734
|
-
const inside =
|
|
19962
|
+
const inside = join20(named3, RUNTIME_ENTRY);
|
|
19735
19963
|
if (isDirectory(named3) && isFile2(inside))
|
|
19736
19964
|
return inside;
|
|
19737
19965
|
throw refusal2([
|
|
@@ -19749,7 +19977,7 @@ function refuseInsideSource(value, path3, places) {
|
|
|
19749
19977
|
]);
|
|
19750
19978
|
}
|
|
19751
19979
|
function sourceDirs({ cwd, root }) {
|
|
19752
|
-
const dirs = [
|
|
19980
|
+
const dirs = [join20(cwd, SOURCE_DIR), join20(root, SOURCE_DIR)];
|
|
19753
19981
|
const real = dirs.filter((dir) => existsSync18(dir)).map((dir) => realpathSync(dir));
|
|
19754
19982
|
return [...new Set([...dirs, ...real])];
|
|
19755
19983
|
}
|
|
@@ -19759,7 +19987,7 @@ function isInside2(path3, dir) {
|
|
|
19759
19987
|
}
|
|
19760
19988
|
function installedVersions(root) {
|
|
19761
19989
|
try {
|
|
19762
|
-
return readdirSync10(root).filter((name2) => isFile2(
|
|
19990
|
+
return readdirSync10(root).filter((name2) => isFile2(join20(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
|
|
19763
19991
|
} catch {
|
|
19764
19992
|
return [];
|
|
19765
19993
|
}
|
|
@@ -19953,19 +20181,19 @@ function runtimeRefusalLines(runtimeDir, version2) {
|
|
|
19953
20181
|
}
|
|
19954
20182
|
function listTrackers(repoRoot, planDir) {
|
|
19955
20183
|
const dir = resolve16(repoRoot, planDir);
|
|
19956
|
-
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) =>
|
|
20184
|
+
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) => join21(dir, entry)).filter((path3) => statSync11(path3).isFile()).map((path3) => relative6(repoRoot, path3));
|
|
19957
20185
|
}
|
|
19958
20186
|
function openTrackers(repoRoot, planDir) {
|
|
19959
20187
|
return listTrackers(repoRoot, planDir).flatMap((path3) => {
|
|
19960
|
-
const task = findNextTask(readFileSync26(
|
|
20188
|
+
const task = findNextTask(readFileSync26(join21(repoRoot, path3), "utf8"));
|
|
19961
20189
|
return task ? [{ path: path3, task }] : [];
|
|
19962
20190
|
});
|
|
19963
20191
|
}
|
|
19964
20192
|
function readManifestVersion2(repoRoot) {
|
|
19965
|
-
const manifest = JSON.parse(readFileSync26(
|
|
20193
|
+
const manifest = JSON.parse(readFileSync26(join21(repoRoot, "package.json"), "utf8"));
|
|
19966
20194
|
const record = typeof manifest === "object" && manifest !== null ? manifest : {};
|
|
19967
20195
|
if (record["name"] !== name) {
|
|
19968
|
-
throw new Error(`${
|
|
20196
|
+
throw new Error(`${join21(repoRoot, "package.json")} names ${JSON.stringify(record["name"])}, not ${name}: this is no rafa checkout`);
|
|
19969
20197
|
}
|
|
19970
20198
|
const version2 = record["version"];
|
|
19971
20199
|
if (typeof version2 !== "string" || !VERSION_SHAPE2.test(version2)) {
|
|
@@ -19974,12 +20202,12 @@ function readManifestVersion2(repoRoot) {
|
|
|
19974
20202
|
return version2;
|
|
19975
20203
|
}
|
|
19976
20204
|
function copyTree(from, to) {
|
|
19977
|
-
|
|
20205
|
+
mkdirSync10(to, { recursive: true });
|
|
19978
20206
|
let copied = 0;
|
|
19979
20207
|
const entries = readdirSync11(from, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
19980
20208
|
for (const entry of entries) {
|
|
19981
|
-
const source =
|
|
19982
|
-
const target =
|
|
20209
|
+
const source = join21(from, entry.name);
|
|
20210
|
+
const target = join21(to, entry.name);
|
|
19983
20211
|
if (entry.isDirectory()) {
|
|
19984
20212
|
copied += copyTree(source, target);
|
|
19985
20213
|
} else if (entry.isFile()) {
|
|
@@ -20020,7 +20248,7 @@ function swapIn(staging, to) {
|
|
|
20020
20248
|
rmSync2(outgoing, { recursive: true, force: true });
|
|
20021
20249
|
}
|
|
20022
20250
|
function relink(linkPath, target) {
|
|
20023
|
-
|
|
20251
|
+
mkdirSync10(dirname10(linkPath), { recursive: true });
|
|
20024
20252
|
const temporary = temporaryName(linkPath);
|
|
20025
20253
|
rmSync2(temporary, { force: true });
|
|
20026
20254
|
try {
|
|
@@ -20074,8 +20302,8 @@ function installRuntime(seams, options = {}) {
|
|
|
20074
20302
|
}
|
|
20075
20303
|
function runInstall(seams, options) {
|
|
20076
20304
|
const { repoRoot, home, log } = seams;
|
|
20077
|
-
const runtimeRoot =
|
|
20078
|
-
const linkPath =
|
|
20305
|
+
const runtimeRoot = join21(home, RUNTIME_SUBDIR);
|
|
20306
|
+
const linkPath = join21(home, RAFA_BIN_DIR, LINK_NAME);
|
|
20079
20307
|
log(`repo root: ${repoRoot}`);
|
|
20080
20308
|
log(`runtime root: ${runtimeRoot}`);
|
|
20081
20309
|
log(`link: ${linkPath}`);
|
|
@@ -20085,8 +20313,8 @@ function runInstall(seams, options) {
|
|
|
20085
20313
|
const open = inStage("trackers", () => openTrackers(repoRoot, planDir));
|
|
20086
20314
|
if (open.length > 0)
|
|
20087
20315
|
return { kind: "refused", reason: "trackers", planDir, trackers: open };
|
|
20088
|
-
const distDir =
|
|
20089
|
-
const runtimeDir =
|
|
20316
|
+
const distDir = join21(repoRoot, "dist");
|
|
20317
|
+
const runtimeDir = join21(runtimeRoot, version2);
|
|
20090
20318
|
const installed = existsSync19(runtimeDir);
|
|
20091
20319
|
if (installed && options.force !== true) {
|
|
20092
20320
|
return { kind: "refused", reason: "runtime-exists", version: version2, runtimeDir };
|
|
@@ -20095,13 +20323,13 @@ function runInstall(seams, options) {
|
|
|
20095
20323
|
const code = inStage("build", () => seams.build(repoRoot));
|
|
20096
20324
|
if (code !== 0)
|
|
20097
20325
|
throw new StageFailure("build", `the build exited ${code}`);
|
|
20098
|
-
if (!existsSync19(
|
|
20099
|
-
throw new StageFailure("build", `the build exited 0 and left no ${
|
|
20326
|
+
if (!existsSync19(join21(distDir, RUNTIME_ENTRY))) {
|
|
20327
|
+
throw new StageFailure("build", `the build exited 0 and left no ${join21(distDir, RUNTIME_ENTRY)}`);
|
|
20100
20328
|
}
|
|
20101
20329
|
log(installed ? `copying ${distDir} over ${runtimeDir}, replacing it whole` : `copying ${distDir} into ${runtimeDir}`);
|
|
20102
20330
|
const copied = inStage("copy", () => replaceTree(distDir, runtimeDir));
|
|
20103
20331
|
log(`copied ${copied} file(s)`);
|
|
20104
|
-
const resolved = inStage("link", () => relink(linkPath,
|
|
20332
|
+
const resolved = inStage("link", () => relink(linkPath, join21(runtimeDir, RUNTIME_ENTRY)));
|
|
20105
20333
|
log(`${linkPath} resolves to ${resolved}`);
|
|
20106
20334
|
return { kind: "done", version: version2, runtimeDir, linkPath, resolved, copied };
|
|
20107
20335
|
}
|
|
@@ -20135,7 +20363,7 @@ function replaceFile(source, target) {
|
|
|
20135
20363
|
}
|
|
20136
20364
|
}
|
|
20137
20365
|
function temporaryName(path3) {
|
|
20138
|
-
return
|
|
20366
|
+
return join21(dirname10(path3), `.${basename7(path3)}.snapshot-${process.pid}`);
|
|
20139
20367
|
}
|
|
20140
20368
|
|
|
20141
20369
|
// src/commands/self-update.ts
|
|
@@ -20244,12 +20472,12 @@ var self_update_default = createSelfUpdateCommand();
|
|
|
20244
20472
|
|
|
20245
20473
|
// src/commands/plan/list.ts
|
|
20246
20474
|
import { existsSync as existsSync20, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
20247
|
-
import { join as
|
|
20475
|
+
import { join as join22 } from "path";
|
|
20248
20476
|
var USAGE5 = "rafa plan list";
|
|
20249
20477
|
var OPEN_HINT = "Type it bare: rafa plan list --open";
|
|
20250
20478
|
function listing(dir, stub) {
|
|
20251
|
-
const plan =
|
|
20252
|
-
const trackerPath =
|
|
20479
|
+
const plan = join22(dir, planFileName(stub, false));
|
|
20480
|
+
const trackerPath = join22(dir, planFileName(stub, true));
|
|
20253
20481
|
const tracker = isFile(trackerPath) ? trackerPath : null;
|
|
20254
20482
|
const planModel = parsePlan(readFileSync27(plan, "utf8"));
|
|
20255
20483
|
const counted4 = tracker === null ? planModel : parsePlan(readFileSync27(tracker, "utf8"));
|
|
@@ -20259,7 +20487,7 @@ function listPlans(plans) {
|
|
|
20259
20487
|
const dir = plans.path;
|
|
20260
20488
|
if (!existsSync20(dir))
|
|
20261
20489
|
return { dir, plans: [] };
|
|
20262
|
-
const stubs = readdirSync12(dir).filter((name2) => isFile(
|
|
20490
|
+
const stubs = readdirSync12(dir).filter((name2) => isFile(join22(dir, name2))).map((name2) => stubOfPlanFile(name2)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
|
|
20263
20491
|
return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
|
|
20264
20492
|
}
|
|
20265
20493
|
function isOpen(plan) {
|
|
@@ -20442,7 +20670,7 @@ async function openNextRelations(context, seams) {
|
|
|
20442
20670
|
// src/commands/loop/loop-sessions.ts
|
|
20443
20671
|
import { execFileSync } from "child_process";
|
|
20444
20672
|
import { readFileSync as readFileSync28 } from "fs";
|
|
20445
|
-
import { join as
|
|
20673
|
+
import { join as join23, relative as relative7 } from "path";
|
|
20446
20674
|
|
|
20447
20675
|
// src/effort/store/task-finishes.ts
|
|
20448
20676
|
import { existsSync as existsSync21 } from "fs";
|
|
@@ -20458,8 +20686,8 @@ function readTaskFinishes(repoRoot, query) {
|
|
|
20458
20686
|
const path3 = sqliteStorePath(repoRoot);
|
|
20459
20687
|
if (!existsSync21(path3))
|
|
20460
20688
|
return [];
|
|
20461
|
-
const
|
|
20462
|
-
const rows = withSqliteStore(path3, "read", false, (db) => db.query(SELECT_FINISHES).all(...
|
|
20689
|
+
const bound2 = [query.planStub, query.since, query.planStub, query.since];
|
|
20690
|
+
const rows = withSqliteStore(path3, "read", false, (db) => db.query(SELECT_FINISHES).all(...bound2));
|
|
20463
20691
|
return rows.map((row) => row.collected_at);
|
|
20464
20692
|
}
|
|
20465
20693
|
|
|
@@ -20594,7 +20822,7 @@ function writeSession(root, record, change) {
|
|
|
20594
20822
|
}
|
|
20595
20823
|
}
|
|
20596
20824
|
function readSessionChecklist(root, record) {
|
|
20597
|
-
const plan =
|
|
20825
|
+
const plan = join23(root, record.plan);
|
|
20598
20826
|
const file = [trackerPathFor(plan), plan].find((path3) => isFile(path3));
|
|
20599
20827
|
if (file === undefined)
|
|
20600
20828
|
return null;
|
|
@@ -20630,11 +20858,11 @@ function readSessionFinishes(root, record) {
|
|
|
20630
20858
|
function formatDuration(seconds) {
|
|
20631
20859
|
if (seconds < 60)
|
|
20632
20860
|
return "under a minute";
|
|
20633
|
-
const
|
|
20634
|
-
const hours = Math.floor(
|
|
20635
|
-
const rest =
|
|
20861
|
+
const minutes2 = Math.round(seconds / 60);
|
|
20862
|
+
const hours = Math.floor(minutes2 / 60);
|
|
20863
|
+
const rest = minutes2 % 60;
|
|
20636
20864
|
if (hours === 0)
|
|
20637
|
-
return `${
|
|
20865
|
+
return `${minutes2}m`;
|
|
20638
20866
|
return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
|
|
20639
20867
|
}
|
|
20640
20868
|
function etaLine(eta) {
|
|
@@ -20753,7 +20981,7 @@ function readBoardBody(body) {
|
|
|
20753
20981
|
|
|
20754
20982
|
// src/board/codeowners.ts
|
|
20755
20983
|
import { readFileSync as readFileSync29, statSync as statSync12 } from "fs";
|
|
20756
|
-
import { join as
|
|
20984
|
+
import { join as join24 } from "path";
|
|
20757
20985
|
var CODEOWNERS_LOCATIONS = Object.freeze([
|
|
20758
20986
|
".github/CODEOWNERS",
|
|
20759
20987
|
"CODEOWNERS",
|
|
@@ -20805,7 +21033,7 @@ function isFile3(path3) {
|
|
|
20805
21033
|
}
|
|
20806
21034
|
}
|
|
20807
21035
|
function findCodeowners(root) {
|
|
20808
|
-
return CODEOWNERS_LOCATIONS.find((location) => isFile3(
|
|
21036
|
+
return CODEOWNERS_LOCATIONS.find((location) => isFile3(join24(root, location))) ?? null;
|
|
20809
21037
|
}
|
|
20810
21038
|
function readCodeowners(root) {
|
|
20811
21039
|
const path3 = findCodeowners(root);
|
|
@@ -20813,7 +21041,7 @@ function readCodeowners(root) {
|
|
|
20813
21041
|
return null;
|
|
20814
21042
|
let text;
|
|
20815
21043
|
try {
|
|
20816
|
-
text = readFileSync29(
|
|
21044
|
+
text = readFileSync29(join24(root, path3), "utf8");
|
|
20817
21045
|
} catch (error) {
|
|
20818
21046
|
const why = error instanceof Error ? error.message : String(error);
|
|
20819
21047
|
throw new Error(`Cannot read ${path3}: ${why}`, { cause: error });
|
|
@@ -21588,7 +21816,7 @@ function pickerFor(followed, seams) {
|
|
|
21588
21816
|
|
|
21589
21817
|
// src/commands/pr/merge-cleanup.ts
|
|
21590
21818
|
import { existsSync as existsSync22, readFileSync as readFileSync30 } from "fs";
|
|
21591
|
-
import { join as
|
|
21819
|
+
import { join as join25 } from "path";
|
|
21592
21820
|
|
|
21593
21821
|
// src/release/enabled.ts
|
|
21594
21822
|
import { statSync as statSync13 } from "fs";
|
|
@@ -21773,11 +22001,11 @@ function settleWaitingOn(place, git) {
|
|
|
21773
22001
|
return { base: place.base, fragments: reading2.fragments.length, version: reading2.version };
|
|
21774
22002
|
}
|
|
21775
22003
|
function followUpsFor(place, git) {
|
|
21776
|
-
const facts = readPackageFacts(textOf2(
|
|
22004
|
+
const facts = readPackageFacts(textOf2(join25(place.root, "package.json")));
|
|
21777
22005
|
return readFollowUps({
|
|
21778
22006
|
version: facts.version,
|
|
21779
22007
|
rafaCheckout: facts.rafaCheckout,
|
|
21780
|
-
runtimeInstalled: facts.version !== null && existsSync22(
|
|
22008
|
+
runtimeInstalled: facts.version !== null && existsSync22(join25(place.home, RUNTIME_SUBDIR, facts.version)),
|
|
21781
22009
|
settle: settleWaitingOn(place, git),
|
|
21782
22010
|
liveLoopBranches: liveLoopBranchesOf(place.root, place.isAlive)
|
|
21783
22011
|
});
|
|
@@ -23722,19 +23950,19 @@ var ready_default = createIssueReadyCommand();
|
|
|
23722
23950
|
import fs from "fs";
|
|
23723
23951
|
import { homedir as homedir10 } from "os";
|
|
23724
23952
|
import path4 from "path";
|
|
23725
|
-
import { fileURLToPath as
|
|
23953
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
23726
23954
|
|
|
23727
23955
|
// src/notices/run.ts
|
|
23728
23956
|
import { homedir as homedir6 } from "os";
|
|
23729
23957
|
|
|
23730
23958
|
// src/notices/notices.ts
|
|
23731
|
-
import { mkdirSync as
|
|
23732
|
-
import { dirname as dirname11, join as
|
|
23959
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync31, writeFileSync as writeFileSync11 } from "fs";
|
|
23960
|
+
import { dirname as dirname11, join as join26 } from "path";
|
|
23733
23961
|
var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
|
|
23734
23962
|
var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
|
|
23735
23963
|
var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
|
|
23736
23964
|
function noticesPath(home) {
|
|
23737
|
-
return
|
|
23965
|
+
return join26(home, ".rafa", "notices.json");
|
|
23738
23966
|
}
|
|
23739
23967
|
function isNoticeId(value) {
|
|
23740
23968
|
return typeof value === "string" && NOTICE_IDS.includes(value);
|
|
@@ -23761,7 +23989,7 @@ function readDismissed(home) {
|
|
|
23761
23989
|
function writeDismissed(home, ids) {
|
|
23762
23990
|
const all = NOTICE_IDS.filter((id2) => ids.includes(id2) || readDismissed(home).includes(id2));
|
|
23763
23991
|
const path3 = noticesPath(home);
|
|
23764
|
-
|
|
23992
|
+
mkdirSync11(dirname11(path3), { recursive: true });
|
|
23765
23993
|
writeFileSync11(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
|
|
23766
23994
|
`);
|
|
23767
23995
|
}
|
|
@@ -23879,7 +24107,7 @@ function realDirOf(path3) {
|
|
|
23879
24107
|
return null;
|
|
23880
24108
|
}
|
|
23881
24109
|
}
|
|
23882
|
-
function
|
|
24110
|
+
function firstLine5(git, args) {
|
|
23883
24111
|
const result = git(args);
|
|
23884
24112
|
if (!result.ok)
|
|
23885
24113
|
return null;
|
|
@@ -23892,13 +24120,13 @@ function readCheckout2(checkout, seams = {}) {
|
|
|
23892
24120
|
if (realDir === null)
|
|
23893
24121
|
return { kind: "missing" };
|
|
23894
24122
|
const git = (seams.git ?? createGitRunner)(checkout);
|
|
23895
|
-
const toplevel =
|
|
24123
|
+
const toplevel = firstLine5(git, ["rev-parse", "--show-toplevel"]);
|
|
23896
24124
|
if (toplevel === null || toplevel !== realDir)
|
|
23897
24125
|
return { kind: "foreign", toplevel };
|
|
23898
24126
|
return {
|
|
23899
24127
|
kind: "read",
|
|
23900
|
-
branch:
|
|
23901
|
-
head:
|
|
24128
|
+
branch: firstLine5(git, ["symbolic-ref", "--quiet", "--short", "HEAD"]),
|
|
24129
|
+
head: firstLine5(git, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])
|
|
23902
24130
|
};
|
|
23903
24131
|
}
|
|
23904
24132
|
function compareCheckout(expected2, reading2) {
|
|
@@ -23968,7 +24196,7 @@ function guardCheckout(expected2, seams = {}) {
|
|
|
23968
24196
|
const move = compareCheckout(expected2, reading2);
|
|
23969
24197
|
if (move === null)
|
|
23970
24198
|
return { held: true };
|
|
23971
|
-
const branchTip = move === "branch" ?
|
|
24199
|
+
const branchTip = move === "branch" ? firstLine5((seams.git ?? createGitRunner)(expected2.checkout), ["rev-parse", "--verify", "--quiet", `refs/heads/${expected2.branch}^{commit}`]) : null;
|
|
23972
24200
|
return { held: false, move, reading: reading2, lines: checkoutMovedLines({ expected: expected2, move, reading: reading2, branchTip }) };
|
|
23973
24201
|
}
|
|
23974
24202
|
|
|
@@ -24048,7 +24276,7 @@ function haltIfWrapUpMoved(options, seams = {}) {
|
|
|
24048
24276
|
// src/project/roots.ts
|
|
24049
24277
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
24050
24278
|
import { accessSync, constants, readFileSync as readFileSync32, statSync as statSync14 } from "fs";
|
|
24051
|
-
import { isAbsolute as isAbsolute5, join as
|
|
24279
|
+
import { isAbsolute as isAbsolute5, join as join27, resolve as resolve18 } from "path";
|
|
24052
24280
|
var REFUSED_ROOTS = Object.freeze([
|
|
24053
24281
|
{ path: "/", why: "is the filesystem root" },
|
|
24054
24282
|
{ path: "/var", why: "holds system state" },
|
|
@@ -24180,8 +24408,8 @@ function readManifest(file, fs) {
|
|
|
24180
24408
|
}
|
|
24181
24409
|
}
|
|
24182
24410
|
function readDir(dir, fs) {
|
|
24183
|
-
const manifest = readManifest(
|
|
24184
|
-
const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(
|
|
24411
|
+
const manifest = readManifest(join27(dir, PACKAGE_JSON), fs);
|
|
24412
|
+
const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(join27(dir, name2)));
|
|
24185
24413
|
const markers = manifest.marks ? [PACKAGE_JSON, ...files] : files;
|
|
24186
24414
|
return { ...manifest, dir, markers };
|
|
24187
24415
|
}
|
|
@@ -24546,6 +24774,12 @@ var NO_REPORT_HOLD = "no report: the session wrote no rafa:report block the loop
|
|
|
24546
24774
|
var BACKGROUND_WAIT_HOLD = "no report: the session ended its turn waiting on a command left running in the background:";
|
|
24547
24775
|
var NO_COMMIT_HOLD = "no commit: the task changed no tracked file";
|
|
24548
24776
|
var NOTHING_REPORTED_OR_COMMITTED = "the session wrote no report block and changed no tracked file";
|
|
24777
|
+
function isNothingLeftBehindHold(hold) {
|
|
24778
|
+
return hold === NO_REPORT_HOLD || hold === NO_COMMIT_HOLD || hold === NOTHING_REPORTED_OR_COMMITTED || hold.startsWith(`${BACKGROUND_WAIT_HOLD} `);
|
|
24779
|
+
}
|
|
24780
|
+
function heldOnNothingLeftBehind(finished) {
|
|
24781
|
+
return finished.attempt.outcome !== "failed" && finished.holds.length > 0 && finished.holds.every(isNothingLeftBehindHold);
|
|
24782
|
+
}
|
|
24549
24783
|
function indentBlock(text) {
|
|
24550
24784
|
return text.split(`
|
|
24551
24785
|
`).map((line) => ` ${line}`).join(`
|
|
@@ -24636,6 +24870,571 @@ function finishCleanExit(options) {
|
|
|
24636
24870
|
};
|
|
24637
24871
|
}
|
|
24638
24872
|
|
|
24873
|
+
// src/start/decision-parse.ts
|
|
24874
|
+
var DECISION_KIND = "decision";
|
|
24875
|
+
var DECISION_STRATEGIES = ["retry", "stop", "jump", "defer"];
|
|
24876
|
+
var UNREADABLE_PREFIX = "the decision could not be read, so the run stops: ";
|
|
24877
|
+
function isMapping3(value) {
|
|
24878
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24879
|
+
}
|
|
24880
|
+
function fieldOf(mapping, key) {
|
|
24881
|
+
return Object.entries(mapping).find(([own]) => own === key)?.[1];
|
|
24882
|
+
}
|
|
24883
|
+
function describeValue3(value) {
|
|
24884
|
+
if (typeof value === "string")
|
|
24885
|
+
return JSON.stringify(value);
|
|
24886
|
+
if (value === null || value === undefined)
|
|
24887
|
+
return "nothing";
|
|
24888
|
+
if (Array.isArray(value))
|
|
24889
|
+
return "a list";
|
|
24890
|
+
if (typeof value === "object")
|
|
24891
|
+
return "a mapping";
|
|
24892
|
+
return `the ${typeof value} ${String(value)}`;
|
|
24893
|
+
}
|
|
24894
|
+
function messageOf7(error) {
|
|
24895
|
+
return error instanceof Error ? error.message : String(error);
|
|
24896
|
+
}
|
|
24897
|
+
function unreadable(why) {
|
|
24898
|
+
return { strategy: "stop", reason: `${UNREADABLE_PREFIX}${why}` };
|
|
24899
|
+
}
|
|
24900
|
+
function isStrategy(value) {
|
|
24901
|
+
return DECISION_STRATEGIES.includes(value);
|
|
24902
|
+
}
|
|
24903
|
+
function textField(document, key, needs = "") {
|
|
24904
|
+
const raw = fieldOf(document, key);
|
|
24905
|
+
if (raw === undefined || raw === null)
|
|
24906
|
+
return { problem: `${key} is missing${needs}` };
|
|
24907
|
+
if (typeof raw !== "string")
|
|
24908
|
+
return { problem: `${key} is ${describeValue3(raw)}, expected text` };
|
|
24909
|
+
const value = raw.trim();
|
|
24910
|
+
return value === "" ? { problem: `${key} is blank${needs}` } : { value };
|
|
24911
|
+
}
|
|
24912
|
+
function afterField(document) {
|
|
24913
|
+
const raw = fieldOf(document, "after");
|
|
24914
|
+
if (raw === undefined || raw === null)
|
|
24915
|
+
return { problem: "after is missing, and defer needs one" };
|
|
24916
|
+
return typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1 ? { value: raw } : { problem: `after is ${describeValue3(raw)}, expected a line number from 1` };
|
|
24917
|
+
}
|
|
24918
|
+
function readDecision(document) {
|
|
24919
|
+
const strategy = fieldOf(document, "strategy");
|
|
24920
|
+
if (strategy === undefined || strategy === null)
|
|
24921
|
+
return unreadable("strategy is missing");
|
|
24922
|
+
if (!isStrategy(strategy)) {
|
|
24923
|
+
return unreadable(`strategy is ${describeValue3(strategy)}, expected one of: ${DECISION_STRATEGIES.join(", ")}`);
|
|
24924
|
+
}
|
|
24925
|
+
const reason = textField(document, "reason");
|
|
24926
|
+
if ("problem" in reason)
|
|
24927
|
+
return unreadable(reason.problem);
|
|
24928
|
+
if (strategy === "retry") {
|
|
24929
|
+
const approach = textField(document, "approach", ", and retry needs one");
|
|
24930
|
+
return "problem" in approach ? unreadable(approach.problem) : { strategy, reason: reason.value, approach: approach.value };
|
|
24931
|
+
}
|
|
24932
|
+
if (strategy === "defer") {
|
|
24933
|
+
const after = afterField(document);
|
|
24934
|
+
return "problem" in after ? unreadable(after.problem) : { strategy, reason: reason.value, after: after.value };
|
|
24935
|
+
}
|
|
24936
|
+
return { strategy, reason: reason.value };
|
|
24937
|
+
}
|
|
24938
|
+
function parseDecision(output) {
|
|
24939
|
+
const block = readRafaBlocks(output).filter((each) => each.kind === DECISION_KIND).at(-1);
|
|
24940
|
+
if (block === undefined)
|
|
24941
|
+
return unreadable("the output holds no rafa:decision block");
|
|
24942
|
+
const at = `the rafa:decision block at line ${String(block.span.first)}`;
|
|
24943
|
+
if (!block.closed)
|
|
24944
|
+
return unreadable(`${at} is never closed`);
|
|
24945
|
+
let document;
|
|
24946
|
+
try {
|
|
24947
|
+
document = Bun.YAML.parse(block.body);
|
|
24948
|
+
} catch (error) {
|
|
24949
|
+
return unreadable(`${at} is not valid YAML (${messageOf7(error)})`);
|
|
24950
|
+
}
|
|
24951
|
+
if (!isMapping3(document))
|
|
24952
|
+
return unreadable(`${at} holds ${describeValue3(document)}, not a mapping`);
|
|
24953
|
+
return readDecision(document);
|
|
24954
|
+
}
|
|
24955
|
+
|
|
24956
|
+
// src/start/decision-prompt.ts
|
|
24957
|
+
import { existsSync as existsSync26, readFileSync as readFileSync35 } from "fs";
|
|
24958
|
+
import { dirname as dirname14, join as join30, resolve as resolve19 } from "path";
|
|
24959
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
24960
|
+
|
|
24961
|
+
// src/epic/verify-plan.ts
|
|
24962
|
+
import { existsSync as existsSync25, readFileSync as readFileSync34 } from "fs";
|
|
24963
|
+
import { dirname as dirname13, join as join29 } from "path";
|
|
24964
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
24965
|
+
|
|
24966
|
+
// src/board/epic-template.ts
|
|
24967
|
+
import { existsSync as existsSync24, readFileSync as readFileSync33 } from "fs";
|
|
24968
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
24969
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24970
|
+
var MODULE_DIR2 = dirname12(fileURLToPath2(import.meta.url));
|
|
24971
|
+
var EPIC_TEMPLATE_FILE = "epic.md";
|
|
24972
|
+
var CRITERIA_PLACEHOLDERS = Object.freeze([
|
|
24973
|
+
"- Placeholder: one line per thing that makes this epic finished," + " written so a check against main can tell whether it holds."
|
|
24974
|
+
]);
|
|
24975
|
+
var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
|
|
24976
|
+
function epicTemplateSource(moduleDir = MODULE_DIR2) {
|
|
24977
|
+
return join28(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
|
|
24978
|
+
}
|
|
24979
|
+
function readEpicTemplate(moduleDir = MODULE_DIR2) {
|
|
24980
|
+
const source = epicTemplateSource(moduleDir);
|
|
24981
|
+
if (!existsSync24(source)) {
|
|
24982
|
+
throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
|
|
24983
|
+
}
|
|
24984
|
+
return readFileSync33(source, "utf8");
|
|
24985
|
+
}
|
|
24986
|
+
function placeholderCriteria(criteria) {
|
|
24987
|
+
if (criteria === null)
|
|
24988
|
+
return Object.freeze([]);
|
|
24989
|
+
const held = criteria.split(/\r\n?|\n/u).map((line) => line.trim()).filter((line) => CRITERIA_PLACEHOLDERS.includes(line));
|
|
24990
|
+
return Object.freeze(held.map((text) => Object.freeze({ text, reason: PLACEHOLDER_REASON })));
|
|
24991
|
+
}
|
|
24992
|
+
function renderEpicBody(template = readEpicTemplate()) {
|
|
24993
|
+
const read = readEpicBody(template);
|
|
24994
|
+
if (read.criteria === null) {
|
|
24995
|
+
throw new Error("epic template: the template carries no acceptance criteria section with a placeholder under it");
|
|
24996
|
+
}
|
|
24997
|
+
const missing = CRITERIA_PLACEHOLDERS.length - placeholderCriteria(read.criteria).length;
|
|
24998
|
+
if (missing > 0) {
|
|
24999
|
+
throw new Error(`epic template: the acceptance criteria section has lost ${String(missing)} of its placeholder lines`);
|
|
25000
|
+
}
|
|
25001
|
+
if (read.lines.length > 0) {
|
|
25002
|
+
throw new Error(`epic template: the template carries ${String(read.lines.length)} checklist lines; a new epic has none`);
|
|
25003
|
+
}
|
|
25004
|
+
return template;
|
|
25005
|
+
}
|
|
25006
|
+
|
|
25007
|
+
// src/epic/verify-plan.ts
|
|
25008
|
+
var MODULE_DIR3 = dirname13(fileURLToPath3(import.meta.url));
|
|
25009
|
+
var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
|
|
25010
|
+
var VERIFY_BLOCK_FENCE = "rafa:verify";
|
|
25011
|
+
var VERIFY_PROMPT_SLOTS = ["EPIC_NUMBER", "EPIC_TITLE", "CRITERIA"];
|
|
25012
|
+
var SKIPPED_REASON = "the verification plan did not answer it";
|
|
25013
|
+
var FENCE4 = /^\s*(?:`{3,}|~{3,})/u;
|
|
25014
|
+
var LIST_ITEM2 = /^( {0,3})(?:[-*+]|\d{1,9}[.)])(?:\s|$)/u;
|
|
25015
|
+
var INDENTED2 = /^\s/u;
|
|
25016
|
+
var MIN_FENCE_LENGTH = 3;
|
|
25017
|
+
var VERIFY_KIND = VERIFY_BLOCK_FENCE.slice("rafa:".length);
|
|
25018
|
+
var SLOT_PATTERN = new RegExp(`\\{(${VERIFY_PROMPT_SLOTS.join("|")})\\}`, "gu");
|
|
25019
|
+
var ANSWER_KEYS = ["check", "uncheckable"];
|
|
25020
|
+
function opensCriterion(text, state) {
|
|
25021
|
+
const item = LIST_ITEM2.exec(text);
|
|
25022
|
+
if (item !== null) {
|
|
25023
|
+
const indent = item[1]?.length ?? 0;
|
|
25024
|
+
state.topIndent ??= indent;
|
|
25025
|
+
return indent <= state.topIndent;
|
|
25026
|
+
}
|
|
25027
|
+
return state.afterBlank && !INDENTED2.test(text);
|
|
25028
|
+
}
|
|
25029
|
+
function splitCriteria(criteria) {
|
|
25030
|
+
if (criteria === null)
|
|
25031
|
+
return Object.freeze([]);
|
|
25032
|
+
const gathered = [];
|
|
25033
|
+
const state = { fenced: false, afterBlank: false, topIndent: null };
|
|
25034
|
+
for (const text of criteria.split(/\r\n?|\n/u)) {
|
|
25035
|
+
const insideFence = state.fenced;
|
|
25036
|
+
if (FENCE4.test(text))
|
|
25037
|
+
state.fenced = !state.fenced;
|
|
25038
|
+
if (!insideFence && text.trim() === "") {
|
|
25039
|
+
state.afterBlank = true;
|
|
25040
|
+
gathered.at(-1)?.push(text);
|
|
25041
|
+
continue;
|
|
25042
|
+
}
|
|
25043
|
+
const current = gathered.at(-1);
|
|
25044
|
+
const opens = !insideFence && opensCriterion(text, state);
|
|
25045
|
+
if (current === undefined || opens) {
|
|
25046
|
+
gathered.push([text]);
|
|
25047
|
+
} else {
|
|
25048
|
+
current.push(text);
|
|
25049
|
+
}
|
|
25050
|
+
state.afterBlank = false;
|
|
25051
|
+
}
|
|
25052
|
+
const numbered2 = gathered.map((lines, index) => {
|
|
25053
|
+
const text = lines.join(`
|
|
25054
|
+
`).trim();
|
|
25055
|
+
return Object.freeze({ number: index + 1, text, placeholder: CRITERIA_PLACEHOLDERS.includes(text) });
|
|
25056
|
+
});
|
|
25057
|
+
return Object.freeze(numbered2);
|
|
25058
|
+
}
|
|
25059
|
+
function criteriaToAsk(criteria) {
|
|
25060
|
+
return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
|
|
25061
|
+
}
|
|
25062
|
+
function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
|
|
25063
|
+
return [join29(moduleDir, VERIFY_PROMPT_FILE), join29(dirname13(moduleDir), VERIFY_PROMPT_FILE)];
|
|
25064
|
+
}
|
|
25065
|
+
function readVerifyPrompt(moduleDir = MODULE_DIR3) {
|
|
25066
|
+
const candidates = verifyPromptCandidates(moduleDir);
|
|
25067
|
+
const found = candidates.find((candidate) => existsSync25(candidate));
|
|
25068
|
+
if (found === undefined) {
|
|
25069
|
+
throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
|
|
25070
|
+
}
|
|
25071
|
+
return readFileSync34(found, "utf8");
|
|
25072
|
+
}
|
|
25073
|
+
function fenceFor(text) {
|
|
25074
|
+
const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
|
|
25075
|
+
return "`".repeat(Math.max(MIN_FENCE_LENGTH, longestRun + 1));
|
|
25076
|
+
}
|
|
25077
|
+
function renderCriterion(criterion) {
|
|
25078
|
+
const fence = fenceFor(criterion.text);
|
|
25079
|
+
return [`### Criterion ${String(criterion.number)}`, "", `${fence}text`, criterion.text, fence].join(`
|
|
25080
|
+
`);
|
|
25081
|
+
}
|
|
25082
|
+
function buildVerifyPrompt(template, input) {
|
|
25083
|
+
const missing = VERIFY_PROMPT_SLOTS.find((slot) => !template.includes(`{${slot}}`));
|
|
25084
|
+
if (missing !== undefined) {
|
|
25085
|
+
throw new Error(`epic verify prompt: the template carries no {${missing}} slot`);
|
|
25086
|
+
}
|
|
25087
|
+
const values = {
|
|
25088
|
+
EPIC_NUMBER: String(input.epic),
|
|
25089
|
+
EPIC_TITLE: input.title.replace(/\s+/gu, " ").trim(),
|
|
25090
|
+
CRITERIA: criteriaToAsk(input.criteria).map(renderCriterion).join(`
|
|
25091
|
+
|
|
25092
|
+
`)
|
|
25093
|
+
};
|
|
25094
|
+
return template.replace(SLOT_PATTERN, (_whole, slot) => values[slot]);
|
|
25095
|
+
}
|
|
25096
|
+
function isMapping4(value) {
|
|
25097
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25098
|
+
}
|
|
25099
|
+
function fieldOf2(mapping, key) {
|
|
25100
|
+
return Object.entries(mapping).find(([own]) => own === key)?.[1];
|
|
25101
|
+
}
|
|
25102
|
+
function describeValue4(value) {
|
|
25103
|
+
if (typeof value === "string")
|
|
25104
|
+
return JSON.stringify(value);
|
|
25105
|
+
if (value === null || value === undefined)
|
|
25106
|
+
return "nothing";
|
|
25107
|
+
if (Array.isArray(value))
|
|
25108
|
+
return "a list";
|
|
25109
|
+
if (typeof value === "object")
|
|
25110
|
+
return "a mapping";
|
|
25111
|
+
return `the ${typeof value} ${String(value)}`;
|
|
25112
|
+
}
|
|
25113
|
+
function messageOf8(error) {
|
|
25114
|
+
return error instanceof Error ? error.message : String(error);
|
|
25115
|
+
}
|
|
25116
|
+
function absent2(reason, block, text) {
|
|
25117
|
+
return Object.freeze({ present: false, reason, block, text });
|
|
25118
|
+
}
|
|
25119
|
+
function readEntry(item, field4, asked) {
|
|
25120
|
+
if (!isMapping4(item))
|
|
25121
|
+
return { problem: `${field4} is ${describeValue4(item)}, not a mapping`, number: null };
|
|
25122
|
+
const number = fieldOf2(item, "criterion");
|
|
25123
|
+
if (typeof number !== "number" || !Number.isInteger(number) || number < 1) {
|
|
25124
|
+
return { problem: `${field4}.criterion is ${describeValue4(number)}, not a criterion number from 1`, number: null };
|
|
25125
|
+
}
|
|
25126
|
+
if (!asked.has(number)) {
|
|
25127
|
+
return { problem: `${field4}.criterion ${String(number)} is no criterion the plan was asked about`, number: null };
|
|
25128
|
+
}
|
|
25129
|
+
const carried = ANSWER_KEYS.filter((key2) => fieldOf2(item, key2) !== undefined);
|
|
25130
|
+
const [key] = carried;
|
|
25131
|
+
if (carried.length !== 1 || key === undefined) {
|
|
25132
|
+
const count = carried.length === 0 ? "neither" : "both";
|
|
25133
|
+
return { problem: `${field4} carries ${count} check and uncheckable`, number };
|
|
25134
|
+
}
|
|
25135
|
+
const value = fieldOf2(item, key);
|
|
25136
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
25137
|
+
return { problem: `${field4}.${key} is ${describeValue4(value)}, not a non-blank string`, number };
|
|
25138
|
+
}
|
|
25139
|
+
return { answer: { number, key, text: value.trim() } };
|
|
25140
|
+
}
|
|
25141
|
+
function verdictFor(criterion, answer4, problem) {
|
|
25142
|
+
if (criterion.placeholder) {
|
|
25143
|
+
return { kind: "uncheckable", criterion, reason: PLACEHOLDER_REASON, source: "placeholder" };
|
|
25144
|
+
}
|
|
25145
|
+
if (answer4?.key === "check")
|
|
25146
|
+
return { kind: "check", criterion, check: answer4.text };
|
|
25147
|
+
if (answer4?.key === "uncheckable")
|
|
25148
|
+
return { kind: "uncheckable", criterion, reason: answer4.text, source: "answer" };
|
|
25149
|
+
const reason = problem === undefined ? SKIPPED_REASON : `${SKIPPED_REASON}: its entry was dropped, as ${problem}`;
|
|
25150
|
+
return { kind: "uncheckable", criterion, reason, source: "skipped" };
|
|
25151
|
+
}
|
|
25152
|
+
function readVerdicts(items2, criteria, block) {
|
|
25153
|
+
const asked = new Set(criteriaToAsk(criteria).map((criterion) => criterion.number));
|
|
25154
|
+
const answers = new Map;
|
|
25155
|
+
const problems = new Map;
|
|
25156
|
+
const issues = [];
|
|
25157
|
+
for (const [index, item] of items2.entries()) {
|
|
25158
|
+
const field4 = `criteria[${String(index)}]`;
|
|
25159
|
+
const reading2 = readEntry(item, field4, asked);
|
|
25160
|
+
if ("problem" in reading2) {
|
|
25161
|
+
issues.push(Object.freeze({ field: field4, text: `${reading2.problem}; dropped` }));
|
|
25162
|
+
if (reading2.number !== null && !problems.has(reading2.number))
|
|
25163
|
+
problems.set(reading2.number, reading2.problem);
|
|
25164
|
+
continue;
|
|
25165
|
+
}
|
|
25166
|
+
if (answers.has(reading2.answer.number)) {
|
|
25167
|
+
const text = `${field4} answers criterion ${String(reading2.answer.number)} a second time; dropped`;
|
|
25168
|
+
issues.push(Object.freeze({ field: field4, text }));
|
|
25169
|
+
continue;
|
|
25170
|
+
}
|
|
25171
|
+
answers.set(reading2.answer.number, reading2.answer);
|
|
25172
|
+
}
|
|
25173
|
+
const verdicts = criteria.map((criterion) => Object.freeze(verdictFor(criterion, answers.get(criterion.number), problems.get(criterion.number))));
|
|
25174
|
+
return Object.freeze({
|
|
25175
|
+
present: true,
|
|
25176
|
+
verdicts: Object.freeze(verdicts),
|
|
25177
|
+
block,
|
|
25178
|
+
issues: Object.freeze(issues)
|
|
25179
|
+
});
|
|
25180
|
+
}
|
|
25181
|
+
function parseVerifyPlan(output, criteria) {
|
|
25182
|
+
const block = readRafaBlocks(output).filter((each) => each.kind === VERIFY_KIND).at(-1);
|
|
25183
|
+
if (block === undefined) {
|
|
25184
|
+
return absent2("no-block", null, `the session output holds no ${VERIFY_BLOCK_FENCE} block`);
|
|
25185
|
+
}
|
|
25186
|
+
const at = `${VERIFY_BLOCK_FENCE} block at line ${String(block.span.first)}`;
|
|
25187
|
+
if (!block.closed) {
|
|
25188
|
+
return absent2("unclosed-block", block, `${at} is never closed, so it is not read`);
|
|
25189
|
+
}
|
|
25190
|
+
let document;
|
|
25191
|
+
try {
|
|
25192
|
+
document = Bun.YAML.parse(block.body);
|
|
25193
|
+
} catch (error) {
|
|
25194
|
+
return absent2("malformed-block", block, `${at} is not valid YAML (${messageOf8(error)})`);
|
|
25195
|
+
}
|
|
25196
|
+
if (!isMapping4(document)) {
|
|
25197
|
+
return absent2("malformed-block", block, `${at} holds ${describeValue4(document)}, not a mapping`);
|
|
25198
|
+
}
|
|
25199
|
+
const listed = fieldOf2(document, "criteria") ?? [];
|
|
25200
|
+
if (!Array.isArray(listed)) {
|
|
25201
|
+
return absent2("malformed-block", block, `${at} has criteria ${describeValue4(listed)}, not a list`);
|
|
25202
|
+
}
|
|
25203
|
+
return readVerdicts(listed, criteria, block);
|
|
25204
|
+
}
|
|
25205
|
+
|
|
25206
|
+
// src/start/decision-prompt.ts
|
|
25207
|
+
var MODULE_DIR4 = dirname14(fileURLToPath4(import.meta.url));
|
|
25208
|
+
var DECISION_PROMPT_FILE = "continue-decision-prompt.md";
|
|
25209
|
+
var CONTINUE_CRITERIA_FILE = "continue-criteria.md";
|
|
25210
|
+
var PROJECT_CRITERIA_HEADING = "### Project criteria";
|
|
25211
|
+
var DECISION_PROMPT_SLOTS = [
|
|
25212
|
+
"plan",
|
|
25213
|
+
"line",
|
|
25214
|
+
"task",
|
|
25215
|
+
"holds",
|
|
25216
|
+
"retriesLeft",
|
|
25217
|
+
"openTasks",
|
|
25218
|
+
"criteria"
|
|
25219
|
+
];
|
|
25220
|
+
var SLOT_PATTERN2 = new RegExp(`\\{\\{(${DECISION_PROMPT_SLOTS.join("|")})\\}\\}`, "gu");
|
|
25221
|
+
var RETRY_SECTION = /^<!-- (retry|no-retry) -->\n([\s\S]*?)^<!-- \/\1 -->\n/gmu;
|
|
25222
|
+
function chooseRetrySections(template, retriesLeft) {
|
|
25223
|
+
const kept = retriesLeft > 0 ? "retry" : "no-retry";
|
|
25224
|
+
return template.replace(RETRY_SECTION, (_whole, section, body) => section === kept ? body : "");
|
|
25225
|
+
}
|
|
25226
|
+
var NO_HOLDS = "(no reason was recorded)";
|
|
25227
|
+
var NO_OPEN_TASKS = "(no open task is left)";
|
|
25228
|
+
function bundledCandidates(file, moduleDir = MODULE_DIR4) {
|
|
25229
|
+
return [join30(moduleDir, file), join30(dirname14(moduleDir), file)];
|
|
25230
|
+
}
|
|
25231
|
+
function readBundledFile(file, moduleDir = MODULE_DIR4) {
|
|
25232
|
+
const candidates = bundledCandidates(file, moduleDir);
|
|
25233
|
+
const found = candidates.find((candidate) => existsSync26(candidate));
|
|
25234
|
+
if (found === undefined) {
|
|
25235
|
+
throw new Error(`loop continue: ${file} is missing: no file at ${candidates.join(" or ")}`);
|
|
25236
|
+
}
|
|
25237
|
+
return readFileSync35(found, "utf8");
|
|
25238
|
+
}
|
|
25239
|
+
function readProjectFile(path3) {
|
|
25240
|
+
if (!existsSync26(path3))
|
|
25241
|
+
return { text: null };
|
|
25242
|
+
try {
|
|
25243
|
+
return { text: readFileSync35(path3, "utf8") };
|
|
25244
|
+
} catch (error) {
|
|
25245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25246
|
+
return { refusal: `loop.continue.criteria cannot be read at ${path3}: ${message}` };
|
|
25247
|
+
}
|
|
25248
|
+
}
|
|
25249
|
+
function resolveContinueCriteria(settings, moduleDir = MODULE_DIR4) {
|
|
25250
|
+
const path3 = resolve19(settings.root, settings.path);
|
|
25251
|
+
const read = readProjectFile(path3);
|
|
25252
|
+
if ("refusal" in read)
|
|
25253
|
+
return { ok: false, refusal: read.refusal };
|
|
25254
|
+
const projectText = read.text?.trim() ?? "";
|
|
25255
|
+
const project = read.text === null ? null : path3;
|
|
25256
|
+
if (settings.mode === "replace") {
|
|
25257
|
+
if (read.text === null) {
|
|
25258
|
+
return {
|
|
25259
|
+
ok: false,
|
|
25260
|
+
refusal: `loop.continue.criteriaMode is replace, and loop.continue.criteria names no file at ${path3}`
|
|
25261
|
+
};
|
|
25262
|
+
}
|
|
25263
|
+
if (projectText === "") {
|
|
25264
|
+
return { ok: false, refusal: `loop.continue.criteriaMode is replace, and ${path3} holds no criteria` };
|
|
25265
|
+
}
|
|
25266
|
+
return { ok: true, criteria: projectText, project };
|
|
25267
|
+
}
|
|
25268
|
+
const base = readBundledFile(CONTINUE_CRITERIA_FILE, moduleDir).trimEnd();
|
|
25269
|
+
const criteria = projectText === "" ? base : `${base}
|
|
25270
|
+
|
|
25271
|
+
${PROJECT_CRITERIA_HEADING}
|
|
25272
|
+
|
|
25273
|
+
${projectText}`;
|
|
25274
|
+
return { ok: true, criteria, project };
|
|
25275
|
+
}
|
|
25276
|
+
function fenced(text) {
|
|
25277
|
+
const fence = fenceFor(text);
|
|
25278
|
+
return `${fence}text
|
|
25279
|
+
${text}
|
|
25280
|
+
${fence}`;
|
|
25281
|
+
}
|
|
25282
|
+
function shownLine(lineNum) {
|
|
25283
|
+
return String(lineNum + 1);
|
|
25284
|
+
}
|
|
25285
|
+
function renderOpenTasks(tasks) {
|
|
25286
|
+
if (tasks.length === 0)
|
|
25287
|
+
return NO_OPEN_TASKS;
|
|
25288
|
+
return fenced(tasks.map((task) => `line ${shownLine(task.lineNum)}: ${task.task}`).join(`
|
|
25289
|
+
`));
|
|
25290
|
+
}
|
|
25291
|
+
function renderDecisionPrompt(template, input) {
|
|
25292
|
+
const missing = DECISION_PROMPT_SLOTS.find((slot) => !template.includes(`{{${slot}}}`));
|
|
25293
|
+
if (missing !== undefined) {
|
|
25294
|
+
throw new Error(`loop continue: the decision prompt carries no {{${missing}}} slot`);
|
|
25295
|
+
}
|
|
25296
|
+
const values = {
|
|
25297
|
+
plan: input.plan,
|
|
25298
|
+
line: shownLine(input.task.lineNum),
|
|
25299
|
+
task: fenced(input.task.task),
|
|
25300
|
+
holds: input.holds.length === 0 ? NO_HOLDS : fenced(input.holds.join(`
|
|
25301
|
+
`)),
|
|
25302
|
+
retriesLeft: String(input.retriesLeft),
|
|
25303
|
+
openTasks: renderOpenTasks(input.openTasks),
|
|
25304
|
+
criteria: input.criteria
|
|
25305
|
+
};
|
|
25306
|
+
return chooseRetrySections(template, input.retriesLeft).replace(SLOT_PATTERN2, (_whole, slot) => values[slot]);
|
|
25307
|
+
}
|
|
25308
|
+
function buildDecisionPrompt(input, moduleDir = MODULE_DIR4) {
|
|
25309
|
+
return renderDecisionPrompt(readBundledFile(DECISION_PROMPT_FILE, moduleDir), input);
|
|
25310
|
+
}
|
|
25311
|
+
|
|
25312
|
+
// src/start/continue-args.ts
|
|
25313
|
+
var CONTINUE_FLAG = "--continue";
|
|
25314
|
+
var DECIDE_FLAG = "--decide";
|
|
25315
|
+
var APPROACH_FLAG = "--approach";
|
|
25316
|
+
var AFTER_FLAG = "--after";
|
|
25317
|
+
var FORCE_WRAP_UP_FLAG = "--force-wrap-up";
|
|
25318
|
+
var DIRECTIVE_REASON = `named on the line with ${DECIDE_FLAG}`;
|
|
25319
|
+
var CONTINUE_OFF = Object.freeze({ on: false, directive: null, forceWrapUp: false });
|
|
25320
|
+
var LINE_NUMBER = /^[1-9][0-9]*$/;
|
|
25321
|
+
function refuse10(reason, hint) {
|
|
25322
|
+
throw new CommandExit(1, [`\u274C Refusing ${reason}: ${hint}`, NOTHING_DISPATCHED].join(`
|
|
25323
|
+
`));
|
|
25324
|
+
}
|
|
25325
|
+
function flagWords(args) {
|
|
25326
|
+
const end = args.indexOf("--");
|
|
25327
|
+
return end === -1 ? args : args.slice(0, end);
|
|
25328
|
+
}
|
|
25329
|
+
function lastValue(words, flag, shape) {
|
|
25330
|
+
if (words.includes(flag))
|
|
25331
|
+
refuse10(`a bare ${flag}`, `it takes a value as ${flag}=<${shape}>.`);
|
|
25332
|
+
const prefix = `${flag}=`;
|
|
25333
|
+
return [...words].reverse().find((word) => word.startsWith(prefix))?.slice(prefix.length);
|
|
25334
|
+
}
|
|
25335
|
+
function isStrategy2(value) {
|
|
25336
|
+
return DECISION_STRATEGIES.includes(value);
|
|
25337
|
+
}
|
|
25338
|
+
function readStrategy(words) {
|
|
25339
|
+
const typed = lastValue(words, DECIDE_FLAG, "strategy");
|
|
25340
|
+
if (typed === undefined)
|
|
25341
|
+
return;
|
|
25342
|
+
if (!isStrategy2(typed)) {
|
|
25343
|
+
refuse10(`${DECIDE_FLAG}=${typed}`, `it takes retry, stop, jump or defer as ${DECIDE_FLAG}=<strategy>.`);
|
|
25344
|
+
}
|
|
25345
|
+
return typed;
|
|
25346
|
+
}
|
|
25347
|
+
function readAfter(words) {
|
|
25348
|
+
const typed = lastValue(words, AFTER_FLAG, "line");
|
|
25349
|
+
if (typed === undefined)
|
|
25350
|
+
return;
|
|
25351
|
+
if (!LINE_NUMBER.test(typed)) {
|
|
25352
|
+
refuse10(`${AFTER_FLAG}=${typed}`, "it takes a tracker line number from 1, as the decision prompt shows lines.");
|
|
25353
|
+
}
|
|
25354
|
+
return Number(typed);
|
|
25355
|
+
}
|
|
25356
|
+
function directiveOf(strategy, approach, after) {
|
|
25357
|
+
if (approach !== undefined && strategy !== "retry") {
|
|
25358
|
+
refuse10(APPROACH_FLAG, `${APPROACH_FLAG} is read only beside ${DECIDE_FLAG}=retry.`);
|
|
25359
|
+
}
|
|
25360
|
+
if (after !== undefined && strategy !== "defer") {
|
|
25361
|
+
refuse10(AFTER_FLAG, `${AFTER_FLAG} is read only beside ${DECIDE_FLAG}=defer.`);
|
|
25362
|
+
}
|
|
25363
|
+
if (strategy === undefined)
|
|
25364
|
+
return null;
|
|
25365
|
+
if (strategy === "retry") {
|
|
25366
|
+
const text = approach?.trim() ?? "";
|
|
25367
|
+
if (text === "")
|
|
25368
|
+
refuse10(`${DECIDE_FLAG}=retry`, `${DECIDE_FLAG}=retry needs ${APPROACH_FLAG}=<text>, the new approach.`);
|
|
25369
|
+
return { strategy, reason: DIRECTIVE_REASON, approach: text };
|
|
25370
|
+
}
|
|
25371
|
+
if (strategy === "defer") {
|
|
25372
|
+
if (after === undefined)
|
|
25373
|
+
refuse10(`${DECIDE_FLAG}=defer`, `${DECIDE_FLAG}=defer needs ${AFTER_FLAG}=<line>, the task to run first.`);
|
|
25374
|
+
return { strategy, reason: DIRECTIVE_REASON, after };
|
|
25375
|
+
}
|
|
25376
|
+
return { strategy, reason: DIRECTIVE_REASON };
|
|
25377
|
+
}
|
|
25378
|
+
function readContinueArgs(args) {
|
|
25379
|
+
const words = flagWords(args);
|
|
25380
|
+
const on = words.includes(CONTINUE_FLAG);
|
|
25381
|
+
const forceWrapUp = words.includes(FORCE_WRAP_UP_FLAG);
|
|
25382
|
+
const strategy = readStrategy(words);
|
|
25383
|
+
const approach = lastValue(words, APPROACH_FLAG, "text");
|
|
25384
|
+
const after = readAfter(words);
|
|
25385
|
+
if (!on) {
|
|
25386
|
+
if (strategy !== undefined)
|
|
25387
|
+
refuse10(DECIDE_FLAG, `${DECIDE_FLAG} needs ${CONTINUE_FLAG}, the run it decides for.`);
|
|
25388
|
+
if (forceWrapUp)
|
|
25389
|
+
refuse10(FORCE_WRAP_UP_FLAG, `${FORCE_WRAP_UP_FLAG} needs ${CONTINUE_FLAG}, the run that passes tasks over.`);
|
|
25390
|
+
}
|
|
25391
|
+
const directive = directiveOf(strategy, approach, after);
|
|
25392
|
+
return on ? { on, directive, forceWrapUp } : CONTINUE_OFF;
|
|
25393
|
+
}
|
|
25394
|
+
function configuredRetries(config, continueOn) {
|
|
25395
|
+
return continueOn ? config.loopRetriesOnContinue : config.loopRetries;
|
|
25396
|
+
}
|
|
25397
|
+
function refuseRetryWithoutBudget(continueArgs, retries) {
|
|
25398
|
+
if (continueArgs.directive?.strategy !== "retry" || retries !== false)
|
|
25399
|
+
return;
|
|
25400
|
+
refuse10(`${DECIDE_FLAG}=retry`, "the run has no retry to spend: loop.retriesOnContinue is false and --retry names no count. Name one with --retry=<n>, or decide another strategy.");
|
|
25401
|
+
}
|
|
25402
|
+
function refuseUnusableCriteria(continueArgs, root, config) {
|
|
25403
|
+
if (!continueArgs.on)
|
|
25404
|
+
return;
|
|
25405
|
+
const reading2 = resolveContinueCriteria({
|
|
25406
|
+
root,
|
|
25407
|
+
path: config.loopContinueCriteria,
|
|
25408
|
+
mode: config.loopContinueCriteriaMode
|
|
25409
|
+
});
|
|
25410
|
+
if (reading2.ok)
|
|
25411
|
+
return;
|
|
25412
|
+
refuse10(CONTINUE_FLAG, `${reading2.refusal}. Write the criteria there, or set loop.continue.criteriaMode: extend.`);
|
|
25413
|
+
}
|
|
25414
|
+
|
|
25415
|
+
// src/start/continue-exits.ts
|
|
25416
|
+
var DECISION_STOP_EXIT = 20;
|
|
25417
|
+
var DECISION_NEEDED_EXIT = 21;
|
|
25418
|
+
var PASSED_OVER_EXIT = 22;
|
|
25419
|
+
var CONTINUE_EXITS = Object.freeze([
|
|
25420
|
+
DECISION_STOP_EXIT,
|
|
25421
|
+
DECISION_NEEDED_EXIT,
|
|
25422
|
+
PASSED_OVER_EXIT
|
|
25423
|
+
]);
|
|
25424
|
+
|
|
25425
|
+
class LoopEnd extends CommandExit {
|
|
25426
|
+
constructor(exitCode, message) {
|
|
25427
|
+
super(exitCode, message);
|
|
25428
|
+
this.name = "LoopEnd";
|
|
25429
|
+
}
|
|
25430
|
+
}
|
|
25431
|
+
|
|
25432
|
+
// src/start/continue-run.ts
|
|
25433
|
+
import { existsSync as existsSync33, readFileSync as readFileSync40 } from "fs";
|
|
25434
|
+
|
|
25435
|
+
// src/start/decision-session.ts
|
|
25436
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
25437
|
+
|
|
24639
25438
|
// src/start/dispatch.ts
|
|
24640
25439
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
24641
25440
|
|
|
@@ -24708,13 +25507,13 @@ function bodyLines2(body) {
|
|
|
24708
25507
|
function bodySections(body) {
|
|
24709
25508
|
const lines = bodyLines2(body);
|
|
24710
25509
|
const headings = [];
|
|
24711
|
-
let
|
|
25510
|
+
let fenced2 = false;
|
|
24712
25511
|
lines.forEach((line, index) => {
|
|
24713
25512
|
if (FENCE_LINE6.test(line)) {
|
|
24714
|
-
|
|
25513
|
+
fenced2 = !fenced2;
|
|
24715
25514
|
return;
|
|
24716
25515
|
}
|
|
24717
|
-
if (
|
|
25516
|
+
if (fenced2)
|
|
24718
25517
|
return;
|
|
24719
25518
|
const match = HEADING_LINE2.exec(line);
|
|
24720
25519
|
if (match === null)
|
|
@@ -24740,7 +25539,7 @@ function bodySections(body) {
|
|
|
24740
25539
|
}
|
|
24741
25540
|
function numberedRuns(body) {
|
|
24742
25541
|
const runs = [];
|
|
24743
|
-
let
|
|
25542
|
+
let fenced2 = false;
|
|
24744
25543
|
let steps = 0;
|
|
24745
25544
|
let line = 0;
|
|
24746
25545
|
const close = () => {
|
|
@@ -24750,10 +25549,10 @@ function numberedRuns(body) {
|
|
|
24750
25549
|
};
|
|
24751
25550
|
bodyLines2(body).forEach((text, index) => {
|
|
24752
25551
|
if (FENCE_LINE6.test(text)) {
|
|
24753
|
-
|
|
25552
|
+
fenced2 = !fenced2;
|
|
24754
25553
|
return;
|
|
24755
25554
|
}
|
|
24756
|
-
if (
|
|
25555
|
+
if (fenced2 || text.trim() === "")
|
|
24757
25556
|
return;
|
|
24758
25557
|
if (NUMBERED_ITEM.test(text)) {
|
|
24759
25558
|
if (steps === 0)
|
|
@@ -24835,13 +25634,13 @@ function firstSection(sections2, heading) {
|
|
|
24835
25634
|
}
|
|
24836
25635
|
function withoutFences(text) {
|
|
24837
25636
|
const kept = [];
|
|
24838
|
-
let
|
|
25637
|
+
let fenced2 = false;
|
|
24839
25638
|
for (const line of bodyLines2(text)) {
|
|
24840
25639
|
if (FENCE_LINE6.test(line)) {
|
|
24841
|
-
|
|
25640
|
+
fenced2 = !fenced2;
|
|
24842
25641
|
continue;
|
|
24843
25642
|
}
|
|
24844
|
-
if (!
|
|
25643
|
+
if (!fenced2)
|
|
24845
25644
|
kept.push(line);
|
|
24846
25645
|
}
|
|
24847
25646
|
return kept;
|
|
@@ -24894,13 +25693,13 @@ function extractedDate(body) {
|
|
|
24894
25693
|
return match?.[1] ?? null;
|
|
24895
25694
|
}
|
|
24896
25695
|
function errorString(body) {
|
|
24897
|
-
let
|
|
25696
|
+
let fenced2 = false;
|
|
24898
25697
|
for (const line of bodyLines2(body)) {
|
|
24899
25698
|
if (FENCE_LINE6.test(line)) {
|
|
24900
|
-
|
|
25699
|
+
fenced2 = !fenced2;
|
|
24901
25700
|
continue;
|
|
24902
25701
|
}
|
|
24903
|
-
if (
|
|
25702
|
+
if (fenced2) {
|
|
24904
25703
|
if (ERROR_WORDS.test(line))
|
|
24905
25704
|
return line.trim();
|
|
24906
25705
|
continue;
|
|
@@ -25046,7 +25845,7 @@ function reportLessons(record, context, now) {
|
|
|
25046
25845
|
|
|
25047
25846
|
// src/effort/store/absences.ts
|
|
25048
25847
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
25049
|
-
import { existsSync as
|
|
25848
|
+
import { existsSync as existsSync27 } from "fs";
|
|
25050
25849
|
var ABSENCE_REASONS = {
|
|
25051
25850
|
"no-block": true,
|
|
25052
25851
|
"unclosed-block": true,
|
|
@@ -25106,7 +25905,7 @@ function writeReportAbsence(repoRoot, write, seams = {}) {
|
|
|
25106
25905
|
write.outcome,
|
|
25107
25906
|
collectedAt
|
|
25108
25907
|
];
|
|
25109
|
-
const appended3 = withSqliteStore(path3, "write", !
|
|
25908
|
+
const appended3 = withSqliteStore(path3, "write", !existsSync27(path3), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
|
|
25110
25909
|
return { path: path3, appended: appended3, skipped: 1 - appended3 };
|
|
25111
25910
|
}
|
|
25112
25911
|
|
|
@@ -25369,8 +26168,8 @@ function renderLessonsSection(lessons) {
|
|
|
25369
26168
|
}
|
|
25370
26169
|
|
|
25371
26170
|
// src/utils/progress.ts
|
|
25372
|
-
import { existsSync as
|
|
25373
|
-
import { join as
|
|
26171
|
+
import { existsSync as existsSync28, writeFileSync as writeFileSync12 } from "fs";
|
|
26172
|
+
import { join as join31 } from "path";
|
|
25374
26173
|
var PROGRESS_FILE_NAME = "progress.txt";
|
|
25375
26174
|
var PROGRESS_CAP_BYTES = 16000;
|
|
25376
26175
|
var LISTED_FIELDS = [
|
|
@@ -25412,7 +26211,7 @@ var ESCAPED_CODE_POINTS = new Set([
|
|
|
25412
26211
|
8233
|
|
25413
26212
|
]);
|
|
25414
26213
|
function progressFilePath(repoRoot) {
|
|
25415
|
-
return
|
|
26214
|
+
return join31(repoRoot, PROGRESS_FILE_NAME);
|
|
25416
26215
|
}
|
|
25417
26216
|
function escapeValue(value) {
|
|
25418
26217
|
let escaped = "";
|
|
@@ -25473,7 +26272,7 @@ function renderProgressText(findings, capBytes = PROGRESS_CAP_BYTES) {
|
|
|
25473
26272
|
}
|
|
25474
26273
|
function readProgressFindings(repoRoot, planStub2) {
|
|
25475
26274
|
const path3 = sqliteStorePath(repoRoot);
|
|
25476
|
-
if (!
|
|
26275
|
+
if (!existsSync28(path3))
|
|
25477
26276
|
return [];
|
|
25478
26277
|
const rows = withSqliteStore(path3, "read", false, (db) => {
|
|
25479
26278
|
const query = db.query(SELECT_FINDINGS2);
|
|
@@ -25499,7 +26298,7 @@ function writeProgress(repoRoot, planStub2, dir = repoRoot) {
|
|
|
25499
26298
|
}
|
|
25500
26299
|
|
|
25501
26300
|
// src/inventory/search/rank.ts
|
|
25502
|
-
import { readFileSync as
|
|
26301
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
25503
26302
|
var SEARCH_LIMIT = 12;
|
|
25504
26303
|
var MIN_WORD_LENGTH = 2;
|
|
25505
26304
|
var MIN_PREFIX_LENGTH = 4;
|
|
@@ -25621,7 +26420,7 @@ function rankCandidates(question, candidates, limit = SEARCH_LIMIT) {
|
|
|
25621
26420
|
return [];
|
|
25622
26421
|
return candidates.map((candidate) => scoreCandidate(candidate, words)).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
25623
26422
|
}
|
|
25624
|
-
function readRankCandidate(record, read = (path3) =>
|
|
26423
|
+
function readRankCandidate(record, read = (path3) => readFileSync36(path3, "utf8")) {
|
|
25625
26424
|
let text;
|
|
25626
26425
|
try {
|
|
25627
26426
|
text = read(record.path);
|
|
@@ -25700,7 +26499,7 @@ function renderSkillIndex(resolution) {
|
|
|
25700
26499
|
// src/task/resolve-skills.ts
|
|
25701
26500
|
var RANK_FLOOR = 0;
|
|
25702
26501
|
var TAG_LIMIT = 3;
|
|
25703
|
-
function
|
|
26502
|
+
function summaryOf3(row) {
|
|
25704
26503
|
const { summary } = row;
|
|
25705
26504
|
return typeof summary === "string" ? summary : "";
|
|
25706
26505
|
}
|
|
@@ -25709,7 +26508,7 @@ function offered(item) {
|
|
|
25709
26508
|
name: item.name,
|
|
25710
26509
|
source: item.winner.source,
|
|
25711
26510
|
path: item.winner.path,
|
|
25712
|
-
description:
|
|
26511
|
+
description: summaryOf3(item.winner)
|
|
25713
26512
|
};
|
|
25714
26513
|
}
|
|
25715
26514
|
function asRecord(row) {
|
|
@@ -25822,14 +26621,14 @@ function taskInputFor(taskInfo, text, declaration, planContent) {
|
|
|
25822
26621
|
stageContext: stageContextOf(taskInfo, planContent)
|
|
25823
26622
|
};
|
|
25824
26623
|
}
|
|
25825
|
-
function
|
|
26624
|
+
function messageOf9(error) {
|
|
25826
26625
|
return error instanceof Error ? error.message : String(error);
|
|
25827
26626
|
}
|
|
25828
26627
|
async function pullBundle(learning, repoRoot) {
|
|
25829
26628
|
try {
|
|
25830
26629
|
return await taskLearningAdapter(learning, repoRoot).pullBlessed();
|
|
25831
26630
|
} catch (error) {
|
|
25832
|
-
activeOutput().warn(` Lessons: none handed to this task: the \`${learning.kind}\` learning adapter answered no blessed set: ${
|
|
26631
|
+
activeOutput().warn(` Lessons: none handed to this task: the \`${learning.kind}\` learning adapter answered no blessed set: ${messageOf9(error)}`);
|
|
25833
26632
|
activeOutput().warn(" The task is not failed for it: it is dispatched without a lessons section.");
|
|
25834
26633
|
return null;
|
|
25835
26634
|
}
|
|
@@ -25870,17 +26669,17 @@ function renderInheritedSection(failures) {
|
|
|
25870
26669
|
|
|
25871
26670
|
// src/start/preflight.ts
|
|
25872
26671
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
25873
|
-
import { existsSync as
|
|
26672
|
+
import { existsSync as existsSync31, readFileSync as readFileSync38 } from "fs";
|
|
25874
26673
|
import { homedir as homedir7 } from "os";
|
|
25875
|
-
import { basename as basename8, join as
|
|
26674
|
+
import { basename as basename8, join as join32 } from "path";
|
|
25876
26675
|
|
|
25877
26676
|
// src/effort/store/schema-report.ts
|
|
25878
|
-
import { existsSync as
|
|
26677
|
+
import { existsSync as existsSync29 } from "fs";
|
|
25879
26678
|
import { Database } from "bun:sqlite";
|
|
25880
26679
|
var NO_NEXT_STEP = "none";
|
|
25881
26680
|
function readSchemaReport(path3, options = {}) {
|
|
25882
26681
|
guardTestProcess(path3);
|
|
25883
|
-
if (!
|
|
26682
|
+
if (!existsSync29(path3))
|
|
25884
26683
|
return absentReport(path3);
|
|
25885
26684
|
const catalogue = sqliteCatalogue(options.migrations ?? SQLITE_MIGRATIONS);
|
|
25886
26685
|
const store = readReadOnly(path3);
|
|
@@ -25988,11 +26787,11 @@ function developmentApplied(report2) {
|
|
|
25988
26787
|
}
|
|
25989
26788
|
|
|
25990
26789
|
// src/preflight/first-dispatch.ts
|
|
25991
|
-
import { readFileSync as
|
|
26790
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
25992
26791
|
var TICKED_TASK = /^- \[[xX]\] (.+)/;
|
|
25993
26792
|
function readIfReadable(path3) {
|
|
25994
26793
|
try {
|
|
25995
|
-
return
|
|
26794
|
+
return readFileSync37(path3, "utf8");
|
|
25996
26795
|
} catch {
|
|
25997
26796
|
return null;
|
|
25998
26797
|
}
|
|
@@ -26019,7 +26818,7 @@ function isFirstDispatch(planPath) {
|
|
|
26019
26818
|
}
|
|
26020
26819
|
|
|
26021
26820
|
// src/claims/device.ts
|
|
26022
|
-
import { existsSync as
|
|
26821
|
+
import { existsSync as existsSync30 } from "fs";
|
|
26023
26822
|
var NO_CLAIMANT = "no claim can name this device";
|
|
26024
26823
|
function ndjsonReason() {
|
|
26025
26824
|
return "this project's effort store is NDJSON (store: ndjson), which records no store id," + ` so ${NO_CLAIMANT}. Next safe step: ${MOVE_TO_SQLITE}`;
|
|
@@ -26034,7 +26833,7 @@ function readDeviceStoreId(repoRoot, config) {
|
|
|
26034
26833
|
if (config.store === "ndjson")
|
|
26035
26834
|
return { ok: false, cause: "ndjson", reason: ndjsonReason() };
|
|
26036
26835
|
const path3 = sqliteStorePath(repoRoot);
|
|
26037
|
-
if (!
|
|
26836
|
+
if (!existsSync30(path3))
|
|
26038
26837
|
return { ok: false, cause: "absent", reason: absentReason(path3) };
|
|
26039
26838
|
const meta = withSqliteStore(path3, "read", false, (db) => readStoreMeta(db));
|
|
26040
26839
|
return meta === null ? { ok: false, cause: "unminted", reason: unmintedReason(path3) } : { ok: true, storeId: meta.storeId };
|
|
@@ -26453,11 +27252,11 @@ function knownMissingNotice(lines) {
|
|
|
26453
27252
|
}
|
|
26454
27253
|
function agentSourcePath(planPath) {
|
|
26455
27254
|
const tracker = trackerPathFor(planPath);
|
|
26456
|
-
return
|
|
27255
|
+
return existsSync31(tracker) ? tracker : planPath;
|
|
26457
27256
|
}
|
|
26458
27257
|
function readIfReadable2(path3) {
|
|
26459
27258
|
try {
|
|
26460
|
-
return
|
|
27259
|
+
return readFileSync38(path3, "utf8");
|
|
26461
27260
|
} catch {
|
|
26462
27261
|
return null;
|
|
26463
27262
|
}
|
|
@@ -26592,7 +27391,7 @@ function announceReminders(planPath, reminders) {
|
|
|
26592
27391
|
function announceUnknownMigrations(repoRoot) {
|
|
26593
27392
|
const output = activeOutput();
|
|
26594
27393
|
try {
|
|
26595
|
-
const report2 = readSchemaReport(
|
|
27394
|
+
const report2 = readSchemaReport(join32(effortStoreDir(repoRoot), SQLITE_STORE_FILE_NAME));
|
|
26596
27395
|
for (const migration of unknownAdditive(report2))
|
|
26597
27396
|
output.warn(unknownAdditiveWarning(migration));
|
|
26598
27397
|
} catch (error) {
|
|
@@ -26669,6 +27468,294 @@ function withStamp(prompt) {
|
|
|
26669
27468
|
return activePlanStub === null ? prompt : stampPrompt(activePlanStub, prompt);
|
|
26670
27469
|
}
|
|
26671
27470
|
|
|
27471
|
+
// src/start/type-step.ts
|
|
27472
|
+
import { existsSync as existsSync32, mkdtempSync, realpathSync as realpathSync4, rmSync as rmSync4, symlinkSync as symlinkSync2, writeFileSync as writeFileSync13 } from "fs";
|
|
27473
|
+
import { tmpdir } from "os";
|
|
27474
|
+
import { dirname as dirname15, join as join33, relative as relative8, resolve as resolve20, sep as sep2 } from "path";
|
|
27475
|
+
var TEST_FILE_SUFFIX = ".test.ts";
|
|
27476
|
+
var TSC_FLAGS = ["--noEmit", "--pretty", "false"];
|
|
27477
|
+
var ERRORS_LISTED = 10;
|
|
27478
|
+
var NODE_MODULES = "node_modules";
|
|
27479
|
+
var TSC_BIN = [".bin", "tsc"];
|
|
27480
|
+
var SCRATCH_NAME = "tsconfig.json";
|
|
27481
|
+
var FILE_ERROR = /^(.+)\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
|
27482
|
+
var GLOBAL_ERROR = /^error (TS\d+): (.*)$/;
|
|
27483
|
+
var NOTHING_RAN = { ran: false, red: false, interrupted: false, blocker: null };
|
|
27484
|
+
var GREEN = { ran: true, red: false, interrupted: false, blocker: null };
|
|
27485
|
+
var INTERRUPTED = { ran: true, red: false, interrupted: true, blocker: null };
|
|
27486
|
+
async function runTsc(options) {
|
|
27487
|
+
const proc = Bun.spawn([...options.argv], {
|
|
27488
|
+
cwd: options.cwd,
|
|
27489
|
+
stdin: "ignore",
|
|
27490
|
+
stdout: "pipe",
|
|
27491
|
+
stderr: "pipe"
|
|
27492
|
+
});
|
|
27493
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
27494
|
+
new Response(proc.stdout).text(),
|
|
27495
|
+
new Response(proc.stderr).text(),
|
|
27496
|
+
proc.exited
|
|
27497
|
+
]);
|
|
27498
|
+
return { exitCode, stdout, stderr };
|
|
27499
|
+
}
|
|
27500
|
+
function typeLabel(task) {
|
|
27501
|
+
return `type step after "${task}"`;
|
|
27502
|
+
}
|
|
27503
|
+
function pathsOf(status) {
|
|
27504
|
+
return /^[RC]/.test(status) ? 2 : 1;
|
|
27505
|
+
}
|
|
27506
|
+
function readTypeFiles(git, base) {
|
|
27507
|
+
const result = git(["diff", "--name-status", "-z", "--find-renames", "--diff-filter=d", base, "HEAD"]);
|
|
27508
|
+
if (!result.ok) {
|
|
27509
|
+
activeOutput().warn(`\u26A0\uFE0F git diff from ${base} did not answer (${gitSaid(result) || "nothing said"}); the type step runs nothing.`);
|
|
27510
|
+
return null;
|
|
27511
|
+
}
|
|
27512
|
+
const fields = result.stdout.split("\x00").filter((field4) => field4 !== "");
|
|
27513
|
+
const files = [];
|
|
27514
|
+
for (let index = 0;index < fields.length; ) {
|
|
27515
|
+
const status = fields[index] ?? "";
|
|
27516
|
+
const count = pathsOf(status);
|
|
27517
|
+
const paths = fields.slice(index + 1, index + 1 + count);
|
|
27518
|
+
index += 1 + count;
|
|
27519
|
+
const head = paths[paths.length - 1];
|
|
27520
|
+
if (head === undefined || !head.endsWith(TEST_FILE_SUFFIX))
|
|
27521
|
+
continue;
|
|
27522
|
+
files.push({ head, base: status.startsWith("A") ? null : paths[0] ?? null });
|
|
27523
|
+
}
|
|
27524
|
+
return files;
|
|
27525
|
+
}
|
|
27526
|
+
function realPath(path3) {
|
|
27527
|
+
try {
|
|
27528
|
+
return realpathSync4(path3);
|
|
27529
|
+
} catch {
|
|
27530
|
+
return resolve20(path3);
|
|
27531
|
+
}
|
|
27532
|
+
}
|
|
27533
|
+
function walkStop(git) {
|
|
27534
|
+
const result = git(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
27535
|
+
const common = result.stdout.trim();
|
|
27536
|
+
return result.ok && common !== "" ? dirname15(realPath(common)) : null;
|
|
27537
|
+
}
|
|
27538
|
+
function isUnder(dir, root) {
|
|
27539
|
+
return dir === root || dir.startsWith(root.endsWith(sep2) ? root : `${root}${sep2}`);
|
|
27540
|
+
}
|
|
27541
|
+
function findNodeModules(checkout, git) {
|
|
27542
|
+
const start = realPath(checkout);
|
|
27543
|
+
const stop = walkStop(git);
|
|
27544
|
+
const top = stop !== null && isUnder(start, stop) ? stop : start;
|
|
27545
|
+
for (let dir = start;; dir = dirname15(dir)) {
|
|
27546
|
+
const modules = join33(dir, NODE_MODULES);
|
|
27547
|
+
if (existsSync32(join33(modules, ...TSC_BIN)))
|
|
27548
|
+
return modules;
|
|
27549
|
+
if (dir === top || dirname15(dir) === dir)
|
|
27550
|
+
return null;
|
|
27551
|
+
}
|
|
27552
|
+
}
|
|
27553
|
+
function scratchTsconfigFields(tree, files, modules) {
|
|
27554
|
+
const root = resolve20(tree);
|
|
27555
|
+
return {
|
|
27556
|
+
extends: join33(root, "tsconfig.json"),
|
|
27557
|
+
compilerOptions: { typeRoots: [join33(resolve20(modules), "@types")] },
|
|
27558
|
+
files: files.map((file) => join33(root, file)),
|
|
27559
|
+
include: []
|
|
27560
|
+
};
|
|
27561
|
+
}
|
|
27562
|
+
function scratchTsconfig(tree, files, modules) {
|
|
27563
|
+
return `${JSON.stringify(scratchTsconfigFields(tree, files, modules), null, 2)}
|
|
27564
|
+
`;
|
|
27565
|
+
}
|
|
27566
|
+
function tscArgv(modules, scratch) {
|
|
27567
|
+
return [join33(modules, ...TSC_BIN), "-p", scratch, ...TSC_FLAGS];
|
|
27568
|
+
}
|
|
27569
|
+
function modulesFor(checkout, git) {
|
|
27570
|
+
return findNodeModules(checkout, git) ?? join33(resolve20(checkout), NODE_MODULES);
|
|
27571
|
+
}
|
|
27572
|
+
function readTypeCheckRecipe(checkout, git) {
|
|
27573
|
+
if (!existsSync32(join33(checkout, "tsconfig.json")))
|
|
27574
|
+
return null;
|
|
27575
|
+
const modules = findNodeModules(checkout, git);
|
|
27576
|
+
return modules === null ? null : { checkout, modules };
|
|
27577
|
+
}
|
|
27578
|
+
function parseTscOutput(stdout, cwd) {
|
|
27579
|
+
const errors = [];
|
|
27580
|
+
for (const line of stdout.split(`
|
|
27581
|
+
`)) {
|
|
27582
|
+
const atFile = FILE_ERROR.exec(line);
|
|
27583
|
+
const global = atFile === null ? GLOBAL_ERROR.exec(line) : null;
|
|
27584
|
+
const last = errors[errors.length - 1];
|
|
27585
|
+
if (atFile !== null) {
|
|
27586
|
+
const [, file = "", row = "0", column = "0", code = "", message = ""] = atFile;
|
|
27587
|
+
errors.push({ file: relative8(cwd, resolve20(cwd, file)), line: Number(row), column: Number(column), code, message });
|
|
27588
|
+
} else if (global !== null) {
|
|
27589
|
+
errors.push({ file: null, line: 0, column: 0, code: global[1] ?? "", message: global[2] ?? "" });
|
|
27590
|
+
} else if (line.startsWith(" ") && last !== undefined) {
|
|
27591
|
+
errors[errors.length - 1] = { ...last, message: `${last.message}
|
|
27592
|
+
${line.trimEnd()}` };
|
|
27593
|
+
}
|
|
27594
|
+
}
|
|
27595
|
+
return errors;
|
|
27596
|
+
}
|
|
27597
|
+
function identity(error) {
|
|
27598
|
+
return `${error.code} ${error.message}`;
|
|
27599
|
+
}
|
|
27600
|
+
function newErrors(head, base) {
|
|
27601
|
+
const left = new Map;
|
|
27602
|
+
for (const error of base) {
|
|
27603
|
+
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
27604
|
+
left.set(key, (left.get(key) ?? 0) + 1);
|
|
27605
|
+
}
|
|
27606
|
+
return head.filter((error) => {
|
|
27607
|
+
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
27608
|
+
const count = left.get(key) ?? 0;
|
|
27609
|
+
if (count === 0)
|
|
27610
|
+
return true;
|
|
27611
|
+
left.set(key, count - 1);
|
|
27612
|
+
return false;
|
|
27613
|
+
});
|
|
27614
|
+
}
|
|
27615
|
+
function errorLine(error) {
|
|
27616
|
+
const first = error.message.split(`
|
|
27617
|
+
`)[0] ?? "";
|
|
27618
|
+
return `${error.file ?? "(no file)"}:${error.line}:${error.column} ${error.code} ${first}`;
|
|
27619
|
+
}
|
|
27620
|
+
function listed(errors) {
|
|
27621
|
+
const shown2 = errors.slice(0, ERRORS_LISTED).map(errorLine);
|
|
27622
|
+
return errors.length > ERRORS_LISTED ? [...shown2, `...and ${errors.length - ERRORS_LISTED} more`] : shown2;
|
|
27623
|
+
}
|
|
27624
|
+
function typeBlockerText(label, base, errors) {
|
|
27625
|
+
const files = [...new Set(errors.map((error) => error.file ?? ""))];
|
|
27626
|
+
const shown2 = listed(errors).join("; ").replace(/\.$/u, "");
|
|
27627
|
+
return `The runner's ${label} found type errors in the task's test files that ${base} did not hold. ` + `New errors: ${shown2}. ` + `Fix them, then check ${files.join(" ")} with tsc ${TSC_FLAGS.join(" ")} -p over a tsconfig outside the checkout ` + "that extends its tsconfig.json by absolute path and lists them under files.";
|
|
27628
|
+
}
|
|
27629
|
+
function brokenBlockerText(label, run, said) {
|
|
27630
|
+
return `The runner's ${label} could not type-check the task's test files: tsc exited ${run.exitCode}${said === undefined ? " and printed no error." : `: ${said}`}`;
|
|
27631
|
+
}
|
|
27632
|
+
function configError(errors, scratch, cwd) {
|
|
27633
|
+
const config = relative8(cwd, scratch);
|
|
27634
|
+
return errors.find((error) => error.file === null || error.file === config);
|
|
27635
|
+
}
|
|
27636
|
+
function firstLine6(run) {
|
|
27637
|
+
return `${run.stdout}
|
|
27638
|
+
${run.stderr}`.split(`
|
|
27639
|
+
`).map((line) => line.trim()).find((line) => line !== "");
|
|
27640
|
+
}
|
|
27641
|
+
async function checkTree(input, modules, tree, files) {
|
|
27642
|
+
const scratchDir = mkdtempSync(join33(tmpdir(), "rafa-type-step-"));
|
|
27643
|
+
const scratch = join33(scratchDir, SCRATCH_NAME);
|
|
27644
|
+
try {
|
|
27645
|
+
writeFileSync13(scratch, scratchTsconfig(tree, files, modules), "utf8");
|
|
27646
|
+
let run;
|
|
27647
|
+
try {
|
|
27648
|
+
run = await (input.runTypes ?? runTsc)({ cwd: tree, argv: tscArgv(modules, scratch) });
|
|
27649
|
+
} catch (error) {
|
|
27650
|
+
return { kind: "threw", why: messageOf(error) };
|
|
27651
|
+
}
|
|
27652
|
+
if (run.exitCode === input.stopCode || input.isInterrupted?.() === true)
|
|
27653
|
+
return { kind: "interrupted" };
|
|
27654
|
+
const errors = parseTscOutput(run.stdout, tree);
|
|
27655
|
+
const config = configError(errors, scratch, tree);
|
|
27656
|
+
if (config !== undefined)
|
|
27657
|
+
return { kind: "broken", run, said: `error ${config.code}: ${config.message.split(`
|
|
27658
|
+
`)[0] ?? ""}` };
|
|
27659
|
+
if (run.exitCode !== 0 && errors.length === 0)
|
|
27660
|
+
return { kind: "broken", run, said: firstLine6(run) };
|
|
27661
|
+
const wanted = new Set(files);
|
|
27662
|
+
return { kind: "read", errors: errors.filter((error) => error.file !== null && wanted.has(error.file)) };
|
|
27663
|
+
} finally {
|
|
27664
|
+
rmSync4(scratchDir, { recursive: true, force: true });
|
|
27665
|
+
}
|
|
27666
|
+
}
|
|
27667
|
+
async function inBaseTree(input, label, modules, check) {
|
|
27668
|
+
const parent = mkdtempSync(join33(tmpdir(), "rafa-type-base-"));
|
|
27669
|
+
const tree = join33(parent, "tree");
|
|
27670
|
+
try {
|
|
27671
|
+
const added = input.git(["worktree", "add", "--detach", tree, input.base]);
|
|
27672
|
+
if (!added.ok) {
|
|
27673
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check out ${input.base} (${gitSaid(added) || "nothing said"}); it reads no error as new.`);
|
|
27674
|
+
return null;
|
|
27675
|
+
}
|
|
27676
|
+
if (existsSync32(modules) && !existsSync32(join33(tree, NODE_MODULES)))
|
|
27677
|
+
symlinkSync2(modules, join33(tree, NODE_MODULES));
|
|
27678
|
+
if (!existsSync32(join33(tree, "tsconfig.json")))
|
|
27679
|
+
return { kind: "read", errors: [] };
|
|
27680
|
+
return await check(tree);
|
|
27681
|
+
} finally {
|
|
27682
|
+
if (existsSync32(tree)) {
|
|
27683
|
+
const removed = input.git(["worktree", "remove", "--force", tree]);
|
|
27684
|
+
if (!removed.ok)
|
|
27685
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not remove its worktree ${tree} (${gitSaid(removed) || "nothing said"}).`);
|
|
27686
|
+
}
|
|
27687
|
+
rmSync4(parent, { recursive: true, force: true });
|
|
27688
|
+
}
|
|
27689
|
+
}
|
|
27690
|
+
async function baseErrors(input, label, modules, files) {
|
|
27691
|
+
const pairs = files.filter((file) => file.base !== null);
|
|
27692
|
+
if (pairs.length === 0)
|
|
27693
|
+
return { errors: [] };
|
|
27694
|
+
const run = await inBaseTree(input, label, modules, (tree) => checkTree(input, modules, tree, pairs.map((file) => file.base)));
|
|
27695
|
+
if (run === null)
|
|
27696
|
+
return { outcome: GREEN };
|
|
27697
|
+
if (run.kind === "interrupted")
|
|
27698
|
+
return { outcome: interrupted(label) };
|
|
27699
|
+
if (run.kind !== "read") {
|
|
27700
|
+
const why = run.kind === "threw" ? run.why : brokenBlockerText(label, run.run, run.said);
|
|
27701
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check the files at ${input.base} (${why}); it reads no error as new.`);
|
|
27702
|
+
return { outcome: GREEN };
|
|
27703
|
+
}
|
|
27704
|
+
const headOf = new Map(pairs.map((file) => [file.base, file.head]));
|
|
27705
|
+
return { errors: run.errors.map((error) => ({ ...error, file: headOf.get(error.file ?? "") ?? error.file })) };
|
|
27706
|
+
}
|
|
27707
|
+
function interrupted(label) {
|
|
27708
|
+
activeOutput().info(`\u23F9 The ${label} was interrupted by SIGINT: read as a stop, not as errors.`);
|
|
27709
|
+
return INTERRUPTED;
|
|
27710
|
+
}
|
|
27711
|
+
function filesToCheck(input, label) {
|
|
27712
|
+
if (!existsSync32(join33(input.checkout, "tsconfig.json"))) {
|
|
27713
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: no tsconfig.json at the checkout root; nothing to type-check.`);
|
|
27714
|
+
return null;
|
|
27715
|
+
}
|
|
27716
|
+
const files = readTypeFiles(input.git, input.base);
|
|
27717
|
+
if (files === null)
|
|
27718
|
+
return null;
|
|
27719
|
+
if (files.length > 0)
|
|
27720
|
+
return files;
|
|
27721
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: the task changed no ${TEST_FILE_SUFFIX} file; nothing to type-check.`);
|
|
27722
|
+
return null;
|
|
27723
|
+
}
|
|
27724
|
+
async function runTypeStep(input) {
|
|
27725
|
+
const label = typeLabel(input.task);
|
|
27726
|
+
const files = filesToCheck(input, label);
|
|
27727
|
+
if (files === null)
|
|
27728
|
+
return NOTHING_RAN;
|
|
27729
|
+
const modules = modulesFor(input.checkout, input.git);
|
|
27730
|
+
const head = await checkTree(input, modules, input.checkout, files.map((file) => file.head));
|
|
27731
|
+
if (head.kind === "threw") {
|
|
27732
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not run tsc (${head.why}); the run goes on without it.`);
|
|
27733
|
+
return NOTHING_RAN;
|
|
27734
|
+
}
|
|
27735
|
+
if (head.kind === "interrupted")
|
|
27736
|
+
return interrupted(label);
|
|
27737
|
+
if (head.kind === "broken") {
|
|
27738
|
+
const blocker = brokenBlockerText(label, head.run, head.said);
|
|
27739
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: ${blocker}`);
|
|
27740
|
+
return { ran: true, red: true, interrupted: false, blocker };
|
|
27741
|
+
}
|
|
27742
|
+
const ran = `\uD83D\uDD0E ${label}: tsc over ${files.length} test file(s)`;
|
|
27743
|
+
if (head.errors.length === 0) {
|
|
27744
|
+
activeOutput().info(`${ran}: no type error.`);
|
|
27745
|
+
return GREEN;
|
|
27746
|
+
}
|
|
27747
|
+
const base = await baseErrors(input, label, modules, files.filter((file) => head.errors.some((error) => error.file === file.head)));
|
|
27748
|
+
if ("outcome" in base)
|
|
27749
|
+
return base.outcome;
|
|
27750
|
+
const added = newErrors(head.errors, base.errors);
|
|
27751
|
+
activeOutput().info(`${ran}: ${head.errors.length} error(s), ${head.errors.length - added.length} already held at ${input.base}, ${added.length} not held.`);
|
|
27752
|
+
for (const line of listed(added))
|
|
27753
|
+
activeOutput().info(` ${line}`);
|
|
27754
|
+
if (added.length === 0)
|
|
27755
|
+
return GREEN;
|
|
27756
|
+
return { ran: true, red: true, interrupted: false, blocker: typeBlockerText(label, input.base, added) };
|
|
27757
|
+
}
|
|
27758
|
+
|
|
26672
27759
|
// src/start/task-gate-lines.ts
|
|
26673
27760
|
var BASE_PROMPT_PREFIX = "The base commit of this task is ";
|
|
26674
27761
|
var COMMIT_NAME = /^[0-9a-f]{7,64}$/;
|
|
@@ -26696,9 +27783,11 @@ function readAlwaysRunFiles(git, globs) {
|
|
|
26696
27783
|
}
|
|
26697
27784
|
return alwaysRunFiles(globs, result.stdout.split("\x00").filter((path3) => path3 !== ""));
|
|
26698
27785
|
}
|
|
27786
|
+
function shellWord(word) {
|
|
27787
|
+
return SHELL_SAFE_PATH.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
27788
|
+
}
|
|
26699
27789
|
function testArgument(path3) {
|
|
26700
|
-
|
|
26701
|
-
return SHELL_SAFE_PATH.test(prefixed) ? prefixed : `'${prefixed.replaceAll("'", "'\\''")}'`;
|
|
27790
|
+
return shellWord(`./${path3}`);
|
|
26702
27791
|
}
|
|
26703
27792
|
function alwaysRunLines(files) {
|
|
26704
27793
|
if (files.length === 0)
|
|
@@ -26706,6 +27795,17 @@ function alwaysRunLines(files) {
|
|
|
26706
27795
|
const args = files.map(testArgument).join(" ");
|
|
26707
27796
|
return [`Also run \`bun test ${args}\`: these are the \`tests.alwaysRun\` files, content sweeps no changed file selects.`];
|
|
26708
27797
|
}
|
|
27798
|
+
var TYPE_CHECK_FILE = "<test file>";
|
|
27799
|
+
var TYPE_CHECK_SCRATCH = "<that tsconfig.json>";
|
|
27800
|
+
function typeCheckLines(recipe) {
|
|
27801
|
+
if (recipe === null)
|
|
27802
|
+
return [];
|
|
27803
|
+
const scratch = JSON.stringify(scratchTsconfigFields(recipe.checkout, [TYPE_CHECK_FILE], recipe.modules));
|
|
27804
|
+
const run = tscArgv(recipe.modules, TYPE_CHECK_SCRATCH).map((word) => word === TYPE_CHECK_SCRATCH ? word : shellWord(word)).join(" ");
|
|
27805
|
+
return [
|
|
27806
|
+
`Before you report done, type-check the \`*${TEST_FILE_SUFFIX}\` files your change adds or edits, if any, as the runner's type step will:` + ` write \`${scratch}\` to \`tsconfig.json\` in a new directory outside the checkout,` + ` one \`files\` entry per such file with \`${TYPE_CHECK_FILE}\` replaced by its path relative to the checkout, such as \`src/x${TEST_FILE_SUFFIX}\`` + ` (the entry already opens with the checkout's path), then run \`${run}\` in the checkout.` + " In a test file your change adds, fix every error it reports." + ` In one your change edits, fix the errors on the lines your change touches, which \`git diff <base> -- ${TYPE_CHECK_FILE}\` names` + " with `<base>` the base commit above, and leave the others: the file held them before this task."
|
|
27807
|
+
];
|
|
27808
|
+
}
|
|
26709
27809
|
|
|
26710
27810
|
// src/start/dispatch.ts
|
|
26711
27811
|
var SESSION_ID_FLAG2 = "--session-id";
|
|
@@ -26722,13 +27822,14 @@ var NO_TASK_SECTIONS = { skills: "", lessons: "" };
|
|
|
26722
27822
|
function sectionLines(sections2) {
|
|
26723
27823
|
return [sections2.skills, sections2.lessons, sections2.inherited].filter((section) => section.trim().length > 0).flatMap((section) => [section, ""]);
|
|
26724
27824
|
}
|
|
26725
|
-
function buildTaskPrompt(taskText, promptContent, planText, knownMissing = [], blocker = null, sections2 = NO_TASK_SECTIONS, base = null, inherited = [], alwaysRun = []) {
|
|
27825
|
+
function buildTaskPrompt(taskText, promptContent, planText, knownMissing = [], blocker = null, sections2 = NO_TASK_SECTIONS, base = null, inherited = [], alwaysRun = [], typeCheck = null) {
|
|
26726
27826
|
return [
|
|
26727
27827
|
`Your scoped task is: ${taskText}`,
|
|
26728
27828
|
"Consider tasks listed above this one in the plan checklist as completed. Do not re-evaluate or re-do them. Focus only on the scoped task.",
|
|
26729
27829
|
...blockerLines(blocker),
|
|
26730
27830
|
...baseLines(base),
|
|
26731
27831
|
...alwaysRunLines(alwaysRun),
|
|
27832
|
+
...typeCheckLines(typeCheck),
|
|
26732
27833
|
"",
|
|
26733
27834
|
...sectionLines({ ...sections2, inherited: renderInheritedSection(inherited) }),
|
|
26734
27835
|
promptContent,
|
|
@@ -26772,7 +27873,7 @@ async function dispatchTask(options) {
|
|
|
26772
27873
|
const resolution = options.serving === null ? EMPTY_RESOLUTION : resolveSessionTiers(options.serving);
|
|
26773
27874
|
const task = taskInputFor(taskInfo, taskText, declaration, options.planContent);
|
|
26774
27875
|
const handed = await handOut(options.handout, task, resolution, options.repoRoot);
|
|
26775
|
-
const prompt = withStamp(buildTaskPrompt(taskText, options.promptContent, injection.text, options.knownMissing, taskInfo.blocker ?? null, { skills: renderSkillsSection(handed.skills), lessons: renderLessonsSection(handed.lessons) }, options.base, options.inherited, options.alwaysRun));
|
|
27876
|
+
const prompt = withStamp(buildTaskPrompt(taskText, options.promptContent, injection.text, options.knownMissing, taskInfo.blocker ?? null, { skills: renderSkillsSection(handed.skills), lessons: renderLessonsSection(handed.lessons) }, options.base, options.inherited, options.alwaysRun, options.typeCheck ?? null));
|
|
26776
27877
|
const served = serveForSession(options.serving, resolution);
|
|
26777
27878
|
const record = {
|
|
26778
27879
|
taskText,
|
|
@@ -26805,7 +27906,7 @@ function serveForSession(serving, resolution) {
|
|
|
26805
27906
|
activeOutput().warn(` ${skipped2.message}`);
|
|
26806
27907
|
return served.flags;
|
|
26807
27908
|
}
|
|
26808
|
-
function
|
|
27909
|
+
function messageOf10(error) {
|
|
26809
27910
|
return error instanceof Error ? error.message : String(error);
|
|
26810
27911
|
}
|
|
26811
27912
|
function renderProgressForDispatch(repoRoot, planStub2, checkout = repoRoot) {
|
|
@@ -26818,7 +27919,7 @@ function renderProgressForDispatch(repoRoot, planStub2, checkout = repoRoot) {
|
|
|
26818
27919
|
return true;
|
|
26819
27920
|
} catch (error) {
|
|
26820
27921
|
activeOutput().error(`
|
|
26821
|
-
\u274C progress.txt could not be rendered from the findings store: ${
|
|
27922
|
+
\u274C progress.txt could not be rendered from the findings store: ${messageOf10(error)}`);
|
|
26822
27923
|
activeOutput().error(" Nothing was dispatched. Make the store readable, then run again.");
|
|
26823
27924
|
return false;
|
|
26824
27925
|
}
|
|
@@ -26842,7 +27943,7 @@ async function pushTaskLessons(options, record) {
|
|
|
26842
27943
|
const rules = result.decisions.map((decision) => decision.rule).join(", ");
|
|
26843
27944
|
activeOutput().info(` Pushed ${count} lesson(s) to the \`${learning.kind}\` learning adapter: ${rules}.`);
|
|
26844
27945
|
} catch (error) {
|
|
26845
|
-
activeOutput().warn(` ${count} lesson(s) of session ${dispatch.sessionId} were not pushed to the \`${learning.kind}\` learning adapter: ${
|
|
27946
|
+
activeOutput().warn(` ${count} lesson(s) of session ${dispatch.sessionId} were not pushed to the \`${learning.kind}\` learning adapter: ${messageOf10(error)}`);
|
|
26846
27947
|
activeOutput().warn(" The task is not failed for it: its report is stored, findings and all.");
|
|
26847
27948
|
}
|
|
26848
27949
|
}
|
|
@@ -26876,7 +27977,7 @@ async function storeTaskReport(options) {
|
|
|
26876
27977
|
activeOutput().warn(` ${warning}`);
|
|
26877
27978
|
} catch (error) {
|
|
26878
27979
|
activeOutput().error(`
|
|
26879
|
-
\u274C The report of session ${dispatch.sessionId} was not stored: ${
|
|
27980
|
+
\u274C The report of session ${dispatch.sessionId} was not stored: ${messageOf10(error)}`);
|
|
26880
27981
|
activeOutput().error(" The session printed it above as it ran.");
|
|
26881
27982
|
return false;
|
|
26882
27983
|
}
|
|
@@ -26884,101 +27985,570 @@ async function storeTaskReport(options) {
|
|
|
26884
27985
|
return true;
|
|
26885
27986
|
}
|
|
26886
27987
|
|
|
26887
|
-
// src/start/
|
|
26888
|
-
|
|
26889
|
-
|
|
26890
|
-
|
|
26891
|
-
var TOKENS_PER_K = 1000;
|
|
26892
|
-
function quoted2(text) {
|
|
26893
|
-
return `"${oneLine(text).replaceAll('"', "\\\"")}"`;
|
|
27988
|
+
// src/start/decision-session.ts
|
|
27989
|
+
var DECISION_TOOLS = Object.freeze(["Read", "Grep", "Glob"]);
|
|
27990
|
+
function decisionFlags(sessionId) {
|
|
27991
|
+
return [SESSION_ID_FLAG2, sessionId, "--tools", DECISION_TOOLS.join(",")];
|
|
26894
27992
|
}
|
|
26895
|
-
function
|
|
26896
|
-
|
|
27993
|
+
function runDecisionSession(input) {
|
|
27994
|
+
const id2 = (input.sessionId ?? randomUUID9)();
|
|
27995
|
+
return runClaudeCaptured(withStamp(input.prompt), input.settingSources, decisionFlags(id2), input.spawn, [], { cwd: input.checkout });
|
|
26897
27996
|
}
|
|
26898
|
-
|
|
26899
|
-
|
|
27997
|
+
|
|
27998
|
+
// src/start/pass-over.ts
|
|
27999
|
+
var EMPTY_PASS_OVER = Object.freeze([]);
|
|
28000
|
+
function taskIdentity(text) {
|
|
28001
|
+
return stripTaskDeclaration(splitBlockerComment(text.trim()).text).trim();
|
|
26900
28002
|
}
|
|
26901
|
-
function
|
|
26902
|
-
|
|
26903
|
-
case "task-start":
|
|
26904
|
-
return `${taskKind(event.position, "start")}${quoted2(event.text)}`;
|
|
26905
|
-
case "task-done":
|
|
26906
|
-
return event.tokens === null ? `${taskKind(event.position, "done")}${minutes(event.durationMs)}` : `${taskKind(event.position, "done")}${minutes(event.durationMs)} ${Math.round(event.tokens / TOKENS_PER_K)}k tokens`;
|
|
26907
|
-
case "task-blocked":
|
|
26908
|
-
return `${taskKind(event.position, "blocked")}${oneLine(event.reason)}`;
|
|
26909
|
-
case "wrap-up":
|
|
26910
|
-
return `${padKind("wrap-up")}${event.phase}`;
|
|
26911
|
-
case "pr":
|
|
26912
|
-
return `pr #${event.number} opened`;
|
|
26913
|
-
case "no-pr":
|
|
26914
|
-
return `${padKind("no pr")}${oneLine(event.reason)}`;
|
|
26915
|
-
case "halt":
|
|
26916
|
-
return `${padKind("halt")}${oneLine(event.reason)}`;
|
|
26917
|
-
case "inherited":
|
|
26918
|
-
return `${padKind("inherited")}${oneLine(`${event.file} > ${event.name}`)}`;
|
|
26919
|
-
case "error":
|
|
26920
|
-
return `${padKind("error")}${firstLine5(event.message)}`;
|
|
26921
|
-
}
|
|
28003
|
+
function ordinalOf(ref8) {
|
|
28004
|
+
return ref8.ordinal ?? 1;
|
|
26922
28005
|
}
|
|
26923
|
-
function
|
|
26924
|
-
return
|
|
26925
|
-
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
28006
|
+
function sameTask(a, b) {
|
|
28007
|
+
return a.task === b.task && ordinalOf(a) === ordinalOf(b);
|
|
26926
28008
|
}
|
|
26927
|
-
|
|
26928
|
-
|
|
26929
|
-
|
|
26930
|
-
|
|
26931
|
-
|
|
28009
|
+
function taskRefIn(task, trackerContent) {
|
|
28010
|
+
const identity2 = taskIdentity(task.task);
|
|
28011
|
+
const copies = listTrackerTasks(trackerContent).filter((line) => line.lineNum <= task.lineNum && taskIdentity(line.task) === identity2);
|
|
28012
|
+
return { lineNum: task.lineNum, task: identity2, ordinal: Math.max(1, copies.length) };
|
|
28013
|
+
}
|
|
28014
|
+
function locate(ref8, tasks) {
|
|
28015
|
+
const copies = tasks.filter((task) => taskIdentity(task.task) === ref8.task);
|
|
28016
|
+
return copies[ordinalOf(ref8) - 1] ?? null;
|
|
28017
|
+
}
|
|
28018
|
+
function movedTo(ref8, task) {
|
|
28019
|
+
return { lineNum: task.lineNum, task: ref8.task, ordinal: ordinalOf(ref8) };
|
|
28020
|
+
}
|
|
28021
|
+
function locateOpen(ref8, tasks) {
|
|
28022
|
+
const found = locate(ref8, tasks);
|
|
28023
|
+
return found === null || found.status === "done" ? null : found;
|
|
28024
|
+
}
|
|
28025
|
+
function stillPassedOver(entry, tasks) {
|
|
28026
|
+
const task = locateOpen(entry.task, tasks);
|
|
28027
|
+
if (task === null)
|
|
28028
|
+
return null;
|
|
28029
|
+
const moved = { ...entry, task: movedTo(entry.task, task) };
|
|
28030
|
+
if (entry.after === undefined)
|
|
28031
|
+
return moved;
|
|
28032
|
+
const after = locateOpen(entry.after, tasks);
|
|
28033
|
+
return after === null ? null : { ...moved, after: movedTo(entry.after, after) };
|
|
28034
|
+
}
|
|
28035
|
+
function addDecision(list, task, decision, trackerContent) {
|
|
28036
|
+
const ref8 = taskRefIn(task, trackerContent);
|
|
28037
|
+
const others = list.filter((entry) => !sameTask(entry.task, ref8));
|
|
28038
|
+
const { strategy, reason } = decision;
|
|
28039
|
+
if (strategy === "jump")
|
|
28040
|
+
return [...others, { task: ref8, strategy, reason }];
|
|
28041
|
+
if (strategy !== "defer" || decision.after === undefined)
|
|
28042
|
+
return others;
|
|
28043
|
+
const afterLine = decision.after - 1;
|
|
28044
|
+
const target = listOpenTasks(trackerContent).find((open) => open.lineNum === afterLine && open.lineNum !== task.lineNum);
|
|
28045
|
+
return target === undefined ? others : [...others, { task: ref8, strategy, reason, after: taskRefIn(target, trackerContent) }];
|
|
28046
|
+
}
|
|
28047
|
+
function markDone(list, task, trackerContent) {
|
|
28048
|
+
const done = taskRefIn(task, trackerContent);
|
|
28049
|
+
const waitsOnDone = (entry) => entry.after !== undefined && sameTask(entry.after, done);
|
|
28050
|
+
return list.filter((entry) => !sameTask(entry.task, done) && !waitsOnDone(entry));
|
|
28051
|
+
}
|
|
28052
|
+
function skippedLines(list, trackerContent) {
|
|
28053
|
+
return new Set(remaining(list, trackerContent).map((entry) => entry.task.lineNum));
|
|
28054
|
+
}
|
|
28055
|
+
function remaining(list, trackerContent) {
|
|
28056
|
+
if (list.length === 0)
|
|
28057
|
+
return EMPTY_PASS_OVER;
|
|
28058
|
+
const tasks = listTrackerTasks(trackerContent);
|
|
28059
|
+
return list.flatMap((entry) => stillPassedOver(entry, tasks) ?? []);
|
|
28060
|
+
}
|
|
28061
|
+
function seedFrom(list, trackerContent) {
|
|
28062
|
+
if (list.length === 0)
|
|
28063
|
+
return EMPTY_PASS_OVER;
|
|
28064
|
+
const tasks = listTrackerTasks(trackerContent);
|
|
28065
|
+
const isBlocked = (entry) => locate(entry.task, tasks)?.status === "blocked";
|
|
28066
|
+
return list.filter(isBlocked).flatMap((entry) => stillPassedOver(entry, tasks) ?? []);
|
|
28067
|
+
}
|
|
28068
|
+
|
|
28069
|
+
// src/start/suite-blocker.ts
|
|
28070
|
+
import { readFileSync as readFileSync39, writeFileSync as writeFileSync14 } from "fs";
|
|
28071
|
+
function failingFiles(failures) {
|
|
28072
|
+
const counts = new Map;
|
|
28073
|
+
for (const failure3 of failures)
|
|
28074
|
+
counts.set(failure3.file, (counts.get(failure3.file) ?? 0) + 1);
|
|
28075
|
+
return [...counts.entries()];
|
|
28076
|
+
}
|
|
28077
|
+
var UNHANDLED_NAMED = 10;
|
|
28078
|
+
function unhandledName(error) {
|
|
28079
|
+
const file = error.file ?? "a file Bun did not name";
|
|
28080
|
+
return error.firstLine === null ? `${file} threw an error Bun printed no line for` : `${file} threw "${error.firstLine}"`;
|
|
28081
|
+
}
|
|
28082
|
+
function unhandledNames(errors) {
|
|
28083
|
+
if (errors.length === 0)
|
|
28084
|
+
return "no block of Bun's stderr named a file";
|
|
28085
|
+
const named3 = errors.slice(0, UNHANDLED_NAMED).map(unhandledName);
|
|
28086
|
+
const left = errors.length - named3.length;
|
|
28087
|
+
const more = left > 0 ? [`and ${left} more`] : [];
|
|
28088
|
+
return [...named3, ...more].join("; ");
|
|
28089
|
+
}
|
|
28090
|
+
function unhandledFiles(errors) {
|
|
28091
|
+
return [...new Set(errors.flatMap((error) => error.file === null ? [] : [error.file]))];
|
|
28092
|
+
}
|
|
28093
|
+
function runnablePath(path3) {
|
|
28094
|
+
return path3.startsWith("/") || path3.startsWith("./") ? path3 : `./${path3}`;
|
|
28095
|
+
}
|
|
28096
|
+
function errorsText(newErrors2, errors) {
|
|
28097
|
+
const counted4 = `${newErrors2} more error(s) outside any test than the baseline`;
|
|
28098
|
+
if (errors.length === 0)
|
|
28099
|
+
return `${counted4}: a test file that throws while it loads, which the JUnit report names no file for.`;
|
|
28100
|
+
const files = unhandledFiles(errors);
|
|
28101
|
+
const run = files.length === 0 ? "" : ` Run bun test ${files.map(runnablePath).join(" ")} and make each load.`;
|
|
28102
|
+
return `${counted4}; Bun's stderr named them as ${unhandledNames(errors)}.${run}`;
|
|
28103
|
+
}
|
|
28104
|
+
function blockerText(label, result, verdict) {
|
|
28105
|
+
const files = failingFiles(verdict.fresh);
|
|
28106
|
+
const parts = [`The runner's ${label} found failures the suite baseline does not hold.`];
|
|
28107
|
+
if (files.length > 0) {
|
|
28108
|
+
const named3 = files.map(([file, count]) => `${file} (${count} ${count === 1 ? "test" : "tests"})`);
|
|
28109
|
+
parts.push(`New failing test files: ${named3.join(", ")}. Run bun test ${files.map(([file]) => runnablePath(file)).join(" ")} and make them pass.`);
|
|
26932
28110
|
}
|
|
26933
|
-
|
|
28111
|
+
if (verdict.newErrors > 0)
|
|
28112
|
+
parts.push(errorsText(verdict.newErrors, result.unhandled));
|
|
28113
|
+
if (verdict.unreported)
|
|
28114
|
+
parts.push(`bun test exited ${result.exitCode} and printed no summary line.`);
|
|
28115
|
+
return parts.join(" ");
|
|
26934
28116
|
}
|
|
26935
|
-
|
|
26936
|
-
|
|
26937
|
-
|
|
26938
|
-
|
|
26939
|
-
|
|
26940
|
-
|
|
26941
|
-
|
|
26942
|
-
|
|
26943
|
-
return file;
|
|
28117
|
+
var REPAIR_AGENT = "build-error-resolver";
|
|
28118
|
+
var REPAIR_TASK = /^Repair the red (?:task|stage|pre-wrap-up) step at commit \S+$/;
|
|
28119
|
+
var PRE_WRAP_UP_REPAIR = /^Repair the red pre-wrap-up step at commit \S+$/;
|
|
28120
|
+
var COMMIT_SHOWN = 12;
|
|
28121
|
+
var TICKED_PREFIX = "- [x] ";
|
|
28122
|
+
function repairTaskText(kind, commit) {
|
|
28123
|
+
const shown2 = commit === null ? "(unread)" : commit.slice(0, COMMIT_SHOWN);
|
|
28124
|
+
return `Repair the red ${kind} step at commit ${shown2}`;
|
|
26944
28125
|
}
|
|
26945
|
-
function
|
|
26946
|
-
|
|
28126
|
+
function isRepairTask(text) {
|
|
28127
|
+
return REPAIR_TASK.test(text.trim());
|
|
26947
28128
|
}
|
|
26948
|
-
function
|
|
26949
|
-
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
|
|
26954
|
-
|
|
26955
|
-
|
|
26956
|
-
|
|
26957
|
-
|
|
26958
|
-
|
|
28129
|
+
function lineOfTask(content, text) {
|
|
28130
|
+
const found = parsePlan(content).tasks.filter((task) => task.text === text.trim());
|
|
28131
|
+
return found.at(-1)?.lineNum ?? null;
|
|
28132
|
+
}
|
|
28133
|
+
function lineOfTickedPreWrapUpRepair(content) {
|
|
28134
|
+
const found = parsePlan(content).tasks.filter((task) => task.status === "done" && PRE_WRAP_UP_REPAIR.test(task.text.trim()));
|
|
28135
|
+
return found.at(-1)?.lineNum ?? null;
|
|
28136
|
+
}
|
|
28137
|
+
function existingRepairOf(content, site) {
|
|
28138
|
+
if (site.kind === "pre-wrap-up")
|
|
28139
|
+
return lineOfTickedPreWrapUpRepair(content);
|
|
28140
|
+
return site.task !== undefined && isRepairTask(site.task) ? lineOfTask(content, site.task) : null;
|
|
28141
|
+
}
|
|
28142
|
+
function blockRepairAgain(trackerPath, content, line, blocker) {
|
|
28143
|
+
const lines = content.split(`
|
|
26959
28144
|
`);
|
|
28145
|
+
const current = lines[line] ?? "";
|
|
28146
|
+
if (current.startsWith(TICKED_PREFIX)) {
|
|
28147
|
+
const reopened = [...lines.slice(0, line), `- [ ] ${current.slice(TICKED_PREFIX.length)}`, ...lines.slice(line + 1)];
|
|
28148
|
+
writeFileSync14(trackerPath, reopened.join(`
|
|
28149
|
+
`), "utf8");
|
|
26960
28150
|
}
|
|
28151
|
+
return writeTrackerBlocker(trackerPath, line, blocker) ? { line, inserted: false } : null;
|
|
26961
28152
|
}
|
|
26962
|
-
function
|
|
26963
|
-
const
|
|
26964
|
-
const
|
|
26965
|
-
|
|
26966
|
-
|
|
28153
|
+
function writeRepairTask(trackerPath, blocker, site) {
|
|
28154
|
+
const content = readFileSync39(trackerPath, "utf8");
|
|
28155
|
+
const repaired = existingRepairOf(content, site);
|
|
28156
|
+
if (repaired !== null)
|
|
28157
|
+
return blockRepairAgain(trackerPath, content, repaired, blocker);
|
|
28158
|
+
const firstOpen = parsePlan(content).tasks.find((task) => task.status !== "done")?.lineNum ?? null;
|
|
28159
|
+
const entry = { task: repairTaskText(site.kind, site.commit), declaration: `agent=${REPAIR_AGENT}`, blocker };
|
|
28160
|
+
return { line: insertTrackerTask(trackerPath, entry, firstOpen), inserted: true };
|
|
26967
28161
|
}
|
|
26968
|
-
|
|
26969
|
-
|
|
28162
|
+
|
|
28163
|
+
// src/start/retry-budget.ts
|
|
28164
|
+
function resolveRunRetries(flag, configured) {
|
|
28165
|
+
return flag ?? configured;
|
|
26970
28166
|
}
|
|
26971
|
-
function
|
|
26972
|
-
|
|
26973
|
-
|
|
28167
|
+
function openRetryBudget(retries) {
|
|
28168
|
+
return Object.freeze({
|
|
28169
|
+
of: retries === false ? 0 : retries,
|
|
28170
|
+
used: 0
|
|
28171
|
+
});
|
|
26974
28172
|
}
|
|
26975
|
-
|
|
26976
|
-
|
|
26977
|
-
|
|
26978
|
-
|
|
26979
|
-
|
|
26980
|
-
|
|
28173
|
+
function takeRetry(budget) {
|
|
28174
|
+
if (budget.used >= budget.of)
|
|
28175
|
+
return { granted: false, budget };
|
|
28176
|
+
const used = budget.used + 1;
|
|
28177
|
+
return {
|
|
28178
|
+
granted: true,
|
|
28179
|
+
attempt: used,
|
|
28180
|
+
of: budget.of,
|
|
28181
|
+
budget: Object.freeze({ of: budget.of, used })
|
|
28182
|
+
};
|
|
28183
|
+
}
|
|
28184
|
+
function retryTaskOf(taskInfo, trackerContent) {
|
|
28185
|
+
const ref8 = taskRefIn(taskInfo, trackerContent);
|
|
28186
|
+
return Object.freeze({ key: `${String(ref8.ordinal)}:${ref8.task}`, repair: isRepairTask(ref8.task) });
|
|
28187
|
+
}
|
|
28188
|
+
function settleOnDone(budget, stoppedOn, done) {
|
|
28189
|
+
if (done.repair || done.key === stoppedOn)
|
|
28190
|
+
return budget;
|
|
28191
|
+
return Object.freeze({ of: budget.of, used: 0 });
|
|
28192
|
+
}
|
|
28193
|
+
function retryLine2(grant, reason) {
|
|
28194
|
+
return `\uD83D\uDD01 Retrying (retry ${grant.attempt} of ${grant.of} in a row) after the stop: ${reason}.` + " The loop goes on without a new rafa loop start.";
|
|
28195
|
+
}
|
|
28196
|
+
function movedLine2(reason) {
|
|
28197
|
+
return `\u26A0\uFE0F No retry after the stop: ${reason}. The checkout has moved from the loop's last commit,` + " so the task keeps its own stop.";
|
|
28198
|
+
}
|
|
28199
|
+
function createRunRetries(options) {
|
|
28200
|
+
let budget = openRetryBudget(options.retries);
|
|
28201
|
+
let refusal4 = null;
|
|
28202
|
+
let stoppedOn = null;
|
|
28203
|
+
const refuse11 = (why) => {
|
|
28204
|
+
refusal4 = why;
|
|
28205
|
+
return false;
|
|
28206
|
+
};
|
|
28207
|
+
const retry = (reason, stopped2) => {
|
|
28208
|
+
stoppedOn = stopped2?.key ?? null;
|
|
28209
|
+
if (options.isInterrupted())
|
|
28210
|
+
return refuse11("interrupted");
|
|
28211
|
+
const grant = takeRetry(budget);
|
|
28212
|
+
if (!grant.granted)
|
|
28213
|
+
return refuse11("spent");
|
|
28214
|
+
if (!options.isCheckoutHeld()) {
|
|
28215
|
+
activeOutput().warn(movedLine2(reason));
|
|
28216
|
+
return refuse11("checkout moved");
|
|
28217
|
+
}
|
|
28218
|
+
budget = grant.budget;
|
|
28219
|
+
refusal4 = null;
|
|
28220
|
+
activeOutput().warn(retryLine2(grant, reason));
|
|
28221
|
+
emitLoopEvent({ kind: "retry", attempt: grant.attempt, of: grant.of, reason });
|
|
28222
|
+
return true;
|
|
28223
|
+
};
|
|
28224
|
+
return {
|
|
28225
|
+
retry,
|
|
28226
|
+
taskDone: (done) => {
|
|
28227
|
+
budget = settleOnDone(budget, stoppedOn, done);
|
|
28228
|
+
},
|
|
28229
|
+
left: () => budget.of - budget.used,
|
|
28230
|
+
lastRefusal: () => refusal4
|
|
28231
|
+
};
|
|
28232
|
+
}
|
|
28233
|
+
|
|
28234
|
+
// src/start/continue-run.ts
|
|
28235
|
+
var SUITE_STEP_RED = "suite step red";
|
|
28236
|
+
var INTERRUPTED2 = "interrupted";
|
|
28237
|
+
var NO_LINES = new Set;
|
|
28238
|
+
var NO_DECISIONS = Object.freeze({
|
|
28239
|
+
skipLines: () => NO_LINES,
|
|
28240
|
+
atStop: () => Promise.resolve(false),
|
|
28241
|
+
atFirstTask: () => Promise.resolve(false),
|
|
28242
|
+
taskDone: () => {
|
|
28243
|
+
return;
|
|
28244
|
+
},
|
|
28245
|
+
atRunEnd: () => {
|
|
28246
|
+
return;
|
|
28247
|
+
},
|
|
28248
|
+
atPlanEnd: () => [],
|
|
28249
|
+
gateForcedWrapUp: (gate) => gate
|
|
28250
|
+
});
|
|
28251
|
+
function whenOf(stop) {
|
|
28252
|
+
if (stop.kind !== "clean-exit")
|
|
28253
|
+
return "spent";
|
|
28254
|
+
if (stop.finished.attempt.outcome === "failed")
|
|
28255
|
+
return "never";
|
|
28256
|
+
return heldOnNothingLeftBehind(stop.finished) ? "spent" : "now";
|
|
28257
|
+
}
|
|
28258
|
+
function openingList(seed, trackerPath) {
|
|
28259
|
+
if (seed.length === 0)
|
|
28260
|
+
return seed;
|
|
28261
|
+
const kept = existsSync33(trackerPath) ? seedFrom(seed, readFileSync40(trackerPath, "utf8")) : [];
|
|
28262
|
+
const back = seed.length - kept.length;
|
|
28263
|
+
if (back > 0) {
|
|
28264
|
+
activeOutput().info(`\u21A9\uFE0F ${back} task(s) the last --continue run passed over no longer read [BLOCKED], so this run takes them again.`);
|
|
26981
28265
|
}
|
|
28266
|
+
return kept;
|
|
28267
|
+
}
|
|
28268
|
+
function boundKey(taskInfo, trackerContent) {
|
|
28269
|
+
const ref8 = taskRefIn(taskInfo, trackerContent);
|
|
28270
|
+
return `${String(ref8.ordinal)}:${ref8.task}`;
|
|
28271
|
+
}
|
|
28272
|
+
function refusedRetry(refusal4) {
|
|
28273
|
+
if (refusal4 === "checkout moved")
|
|
28274
|
+
return "retry was chosen, but the checkout has moved from the loop's last commit";
|
|
28275
|
+
if (refusal4 === "interrupted")
|
|
28276
|
+
return "retry was chosen, but the run was interrupted";
|
|
28277
|
+
return "retry was chosen with no retry left in a row";
|
|
28278
|
+
}
|
|
28279
|
+
function shownLine2(taskInfo) {
|
|
28280
|
+
return taskInfo.lineNum + 1;
|
|
28281
|
+
}
|
|
28282
|
+
function createRunDecisions(options) {
|
|
28283
|
+
if (!options.continueArgs.on)
|
|
28284
|
+
return NO_DECISIONS;
|
|
28285
|
+
const { trackerPath, retries, session } = options;
|
|
28286
|
+
const mode = options.mode ?? activeOutputMode;
|
|
28287
|
+
let directive = options.continueArgs.directive;
|
|
28288
|
+
let firstPass = true;
|
|
28289
|
+
const readTracker = () => readFileSync40(trackerPath, "utf8");
|
|
28290
|
+
let list = openingList(options.seed, trackerPath);
|
|
28291
|
+
const deferred = new Set;
|
|
28292
|
+
const passedOver = new Set;
|
|
28293
|
+
if (list.length > 0) {
|
|
28294
|
+
session.decisionsChanged(list);
|
|
28295
|
+
activeOutput().info(`\u23ED Passing over ${list.length} task(s) the plan's last --continue run passed over, while they stay [BLOCKED].`);
|
|
28296
|
+
}
|
|
28297
|
+
const skipLines = (trackerContent) => skippedLines(list, trackerContent);
|
|
28298
|
+
const subjectOf = (stop) => {
|
|
28299
|
+
if (stop.kind === "clean-exit")
|
|
28300
|
+
return { taskInfo: stop.taskInfo, retryTask: stop.retryTask, holds: stop.finished.holds, stopEvent: stop.stopEvent };
|
|
28301
|
+
if (stop.kind === "session-exit") {
|
|
28302
|
+
return { taskInfo: stop.taskInfo, retryTask: stop.retryTask, holds: [`session exited ${stop.exitCode}`], stopEvent: stop.stopEvent };
|
|
28303
|
+
}
|
|
28304
|
+
const content = readTracker();
|
|
28305
|
+
const blocked3 = findNextTask(content, { skipLines: skipLines(content) });
|
|
28306
|
+
if (blocked3?.status !== "blocked")
|
|
28307
|
+
return null;
|
|
28308
|
+
return {
|
|
28309
|
+
taskInfo: blocked3,
|
|
28310
|
+
retryTask: retryTaskOf(blocked3, content),
|
|
28311
|
+
holds: [blocked3.blocker ?? SUITE_STEP_RED],
|
|
28312
|
+
stopEvent: { kind: "halt", reason: SUITE_STEP_RED }
|
|
28313
|
+
};
|
|
28314
|
+
};
|
|
28315
|
+
const stopWith = (decision, subject) => {
|
|
28316
|
+
const line = shownLine2(subject.taskInfo);
|
|
28317
|
+
emitLoopEvent({ kind: "decision", strategy: "stop", line, reason: decision.reason });
|
|
28318
|
+
emitLoopEvent(subject.stopEvent);
|
|
28319
|
+
throw new LoopEnd(DECISION_STOP_EXIT, `\u26D4 The --continue decision on line ${line} is stop: ${decision.reason}`);
|
|
28320
|
+
};
|
|
28321
|
+
const promptFor = (subject, content) => {
|
|
28322
|
+
const criteria = resolveContinueCriteria({
|
|
28323
|
+
root: options.repoRoot,
|
|
28324
|
+
path: options.settings.loopContinueCriteria,
|
|
28325
|
+
mode: options.settings.loopContinueCriteriaMode
|
|
28326
|
+
});
|
|
28327
|
+
if (!criteria.ok)
|
|
28328
|
+
return { strategy: "stop", reason: `the criteria cannot be read, so the run stops: ${criteria.refusal}` };
|
|
28329
|
+
return buildDecisionPrompt({
|
|
28330
|
+
plan: options.planPath,
|
|
28331
|
+
task: subject.taskInfo,
|
|
28332
|
+
holds: subject.holds,
|
|
28333
|
+
retriesLeft: retries.left(),
|
|
28334
|
+
openTasks: listOpenTasks(content),
|
|
28335
|
+
criteria: criteria.criteria
|
|
28336
|
+
});
|
|
28337
|
+
};
|
|
28338
|
+
const needDecision = (subject, content, prompt) => {
|
|
28339
|
+
const line = shownLine2(subject.taskInfo);
|
|
28340
|
+
emitLoopEvent({
|
|
28341
|
+
kind: "decision-needed",
|
|
28342
|
+
task: subject.taskInfo.task,
|
|
28343
|
+
line,
|
|
28344
|
+
holds: subject.holds,
|
|
28345
|
+
openTasks: listOpenTasks(content).map((task) => ({ line: shownLine2(task), text: task.task })),
|
|
28346
|
+
retriesLeft: retries.left(),
|
|
28347
|
+
prompt
|
|
28348
|
+
});
|
|
28349
|
+
emitLoopEvent(subject.stopEvent);
|
|
28350
|
+
throw new LoopEnd(DECISION_NEEDED_EXIT, [
|
|
28351
|
+
`\uD83E\uDDED A decision is needed for the stop on line ${line}; the decision-needed event holds its prompt.`,
|
|
28352
|
+
" Run again with --continue --decide=retry|stop|jump|defer (--approach=<text> for retry, --after=<line> for defer)."
|
|
28353
|
+
].join(`
|
|
28354
|
+
`));
|
|
28355
|
+
};
|
|
28356
|
+
const sessionDecision = async (subject, prompt) => {
|
|
28357
|
+
activeOutput().info(`
|
|
28358
|
+
\uD83E\uDDED Deciding how the run goes on after the stop on line ${shownLine2(subject.taskInfo)}: one read-only session (--continue).`);
|
|
28359
|
+
const answer4 = await runDecisionSession({
|
|
28360
|
+
prompt,
|
|
28361
|
+
settingSources: options.settingSources,
|
|
28362
|
+
checkout: options.checkout,
|
|
28363
|
+
...options.spawn === undefined ? {} : { spawn: options.spawn }
|
|
28364
|
+
});
|
|
28365
|
+
if (options.isInterrupted())
|
|
28366
|
+
return null;
|
|
28367
|
+
if (answer4.exitCode !== 0) {
|
|
28368
|
+
return { strategy: "stop", reason: `the decision session exited ${answer4.exitCode}, so the run stops` };
|
|
28369
|
+
}
|
|
28370
|
+
return parseDecision(answer4.stdout);
|
|
28371
|
+
};
|
|
28372
|
+
const takeDirective = (subject) => {
|
|
28373
|
+
const taken = directive;
|
|
28374
|
+
if (taken === null)
|
|
28375
|
+
return null;
|
|
28376
|
+
directive = null;
|
|
28377
|
+
activeOutput().info(`
|
|
28378
|
+
\uD83E\uDDED Applying --decide=${taken.strategy}, named on the line, to line ${shownLine2(subject.taskInfo)}.`);
|
|
28379
|
+
return taken;
|
|
28380
|
+
};
|
|
28381
|
+
const decisionFor2 = async (subject, content) => {
|
|
28382
|
+
const directed = takeDirective(subject);
|
|
28383
|
+
if (directed !== null)
|
|
28384
|
+
return directed;
|
|
28385
|
+
const prompt = promptFor(subject, content);
|
|
28386
|
+
if (typeof prompt !== "string")
|
|
28387
|
+
return prompt;
|
|
28388
|
+
if (mode() === "json")
|
|
28389
|
+
needDecision(subject, content, prompt);
|
|
28390
|
+
return sessionDecision(subject, prompt);
|
|
28391
|
+
};
|
|
28392
|
+
const bounded = (decision, identity2) => {
|
|
28393
|
+
const passing = decision.strategy === "jump" || decision.strategy === "defer";
|
|
28394
|
+
if (passing && passedOver.has(identity2)) {
|
|
28395
|
+
return { strategy: "stop", reason: `the task was passed over once already in this run and stopped again, so the run stops: ${decision.reason}` };
|
|
28396
|
+
}
|
|
28397
|
+
if (decision.strategy === "defer" && deferred.has(identity2)) {
|
|
28398
|
+
return { strategy: "jump", reason: `the task was deferred once already in this run, so it is passed over as a jump: ${decision.reason}` };
|
|
28399
|
+
}
|
|
28400
|
+
return decision;
|
|
28401
|
+
};
|
|
28402
|
+
const passOver = (decision, subject, content, identity2) => {
|
|
28403
|
+
const line = shownLine2(subject.taskInfo);
|
|
28404
|
+
list = addDecision(list, subject.taskInfo, decision, content);
|
|
28405
|
+
session.decisionsChanged(list);
|
|
28406
|
+
if (decision.strategy === "defer")
|
|
28407
|
+
deferred.add(identity2);
|
|
28408
|
+
else
|
|
28409
|
+
passedOver.add(identity2);
|
|
28410
|
+
emitLoopEvent({ kind: "decision", strategy: decision.strategy, line, reason: decision.reason });
|
|
28411
|
+
const how = decision.strategy === "defer" ? `deferred until line ${String(decision.after)} is done` : "passed over for the rest of this run";
|
|
28412
|
+
activeOutput().warn(`\u23ED Line ${line} is ${how}: ${decision.reason}`);
|
|
28413
|
+
return true;
|
|
28414
|
+
};
|
|
28415
|
+
const interruptedDeciding = (subject) => {
|
|
28416
|
+
emitLoopEvent({ ...subject.stopEvent, reason: INTERRUPTED2 });
|
|
28417
|
+
throw new LoopEnd(0, `
|
|
28418
|
+
\u26A0\uFE0F Interrupted during the --continue decision on line ${shownLine2(subject.taskInfo)}: nothing was decided, and the task stays [BLOCKED]. Run again to resume.`);
|
|
28419
|
+
};
|
|
28420
|
+
const retryWith = (decision, subject) => {
|
|
28421
|
+
if (!retries.retry("the decision chose retry", subject.retryTask)) {
|
|
28422
|
+
return stopWith({ strategy: "stop", reason: `${refusedRetry(retries.lastRefusal())}, so the run stops: ${decision.reason}` }, subject);
|
|
28423
|
+
}
|
|
28424
|
+
const line = shownLine2(subject.taskInfo);
|
|
28425
|
+
writeTrackerBlocker(trackerPath, subject.taskInfo.lineNum, decision.approach ?? "");
|
|
28426
|
+
emitLoopEvent({ kind: "decision", strategy: "retry", line, reason: decision.reason });
|
|
28427
|
+
activeOutput().warn(`\uD83D\uDD01 Line ${line} is retried with a new approach: ${decision.reason}`);
|
|
28428
|
+
return true;
|
|
28429
|
+
};
|
|
28430
|
+
const apply = (decision, subject, content, identity2) => {
|
|
28431
|
+
switch (decision.strategy) {
|
|
28432
|
+
case "retry":
|
|
28433
|
+
return retryWith(decision, subject);
|
|
28434
|
+
case "stop":
|
|
28435
|
+
return stopWith(decision, subject);
|
|
28436
|
+
case "jump":
|
|
28437
|
+
case "defer":
|
|
28438
|
+
return passOver(decision, subject, content, identity2);
|
|
28439
|
+
}
|
|
28440
|
+
};
|
|
28441
|
+
const decide3 = async (subject) => {
|
|
28442
|
+
const content = readTracker();
|
|
28443
|
+
const decision = await decisionFor2(subject, content);
|
|
28444
|
+
if (decision === null)
|
|
28445
|
+
return interruptedDeciding(subject);
|
|
28446
|
+
const identity2 = boundKey(subject.taskInfo, content);
|
|
28447
|
+
return apply(bounded(decision, identity2), subject, content, identity2);
|
|
28448
|
+
};
|
|
28449
|
+
const atStop = async (stop) => {
|
|
28450
|
+
if (options.isInterrupted())
|
|
28451
|
+
return false;
|
|
28452
|
+
const when = whenOf(stop);
|
|
28453
|
+
if (when === "never")
|
|
28454
|
+
return false;
|
|
28455
|
+
const refusal4 = retries.lastRefusal();
|
|
28456
|
+
if (when === "spent" && refusal4 !== "spent")
|
|
28457
|
+
return false;
|
|
28458
|
+
const subject = subjectOf(stop);
|
|
28459
|
+
return subject === null ? false : decide3(subject);
|
|
28460
|
+
};
|
|
28461
|
+
const refuseMisdirected = (taskInfo, taken) => {
|
|
28462
|
+
const needed2 = options.previousNeeded ?? null;
|
|
28463
|
+
if (needed2 === null || taskIdentity(needed2.task) === taskIdentity(taskInfo.task))
|
|
28464
|
+
return;
|
|
28465
|
+
directive = null;
|
|
28466
|
+
throw new CommandExit(1, [
|
|
28467
|
+
`\u274C Refusing --decide=${taken.strategy}: the last run needed a decision on line ${needed2.line} "${taskIdentity(needed2.task)}",` + ` but this run opens on line ${shownLine2(taskInfo)} "${taskIdentity(taskInfo.task)}".`,
|
|
28468
|
+
" Check the tracker, then run again with --continue alone for a decision on the task this run opens on.",
|
|
28469
|
+
" Nothing was decided and nothing was dispatched."
|
|
28470
|
+
].join(`
|
|
28471
|
+
`));
|
|
28472
|
+
};
|
|
28473
|
+
const atFirstTask = async (taskInfo, trackerContent) => {
|
|
28474
|
+
if (!firstPass)
|
|
28475
|
+
return false;
|
|
28476
|
+
firstPass = false;
|
|
28477
|
+
if (directive === null || taskInfo?.status !== "blocked")
|
|
28478
|
+
return false;
|
|
28479
|
+
refuseMisdirected(taskInfo, directive);
|
|
28480
|
+
const stopEvent = {
|
|
28481
|
+
kind: "task-blocked",
|
|
28482
|
+
position: taskPosition(trackerContent, taskInfo.lineNum),
|
|
28483
|
+
reason: `--decide=${directive.strategy}`
|
|
28484
|
+
};
|
|
28485
|
+
return decide3({
|
|
28486
|
+
taskInfo,
|
|
28487
|
+
retryTask: retryTaskOf(taskInfo, trackerContent),
|
|
28488
|
+
holds: [taskInfo.blocker ?? "blocked by an earlier run"],
|
|
28489
|
+
stopEvent
|
|
28490
|
+
});
|
|
28491
|
+
};
|
|
28492
|
+
const taskDone = (taskInfo) => {
|
|
28493
|
+
const next = markDone(list, taskInfo, readTracker());
|
|
28494
|
+
if (next.length === list.length)
|
|
28495
|
+
return;
|
|
28496
|
+
list = next;
|
|
28497
|
+
session.decisionsChanged(list);
|
|
28498
|
+
};
|
|
28499
|
+
let announced = false;
|
|
28500
|
+
const atPlanEnd = (trackerContent) => {
|
|
28501
|
+
const left = remaining(list, trackerContent);
|
|
28502
|
+
if (left.length === 0)
|
|
28503
|
+
return [];
|
|
28504
|
+
const tasks = left.map((entry) => ({
|
|
28505
|
+
line: entry.task.lineNum + 1,
|
|
28506
|
+
text: entry.task.task,
|
|
28507
|
+
strategy: entry.strategy,
|
|
28508
|
+
reason: entry.reason
|
|
28509
|
+
}));
|
|
28510
|
+
const forcing = options.continueArgs.forceWrapUp;
|
|
28511
|
+
if (!announced) {
|
|
28512
|
+
activeOutput().warn(forcing ? `
|
|
28513
|
+
\u23ED Every task left is one this run passed over; --force-wrap-up wraps up anyway, as a draft pull request listing them:` : `
|
|
28514
|
+
\u23ED Every task left is one this run passed over; no wrap-up runs and no pull request is opened:`);
|
|
28515
|
+
for (const task of tasks)
|
|
28516
|
+
activeOutput().warn(` line ${task.line} (${task.strategy}): ${task.text}
|
|
28517
|
+
${task.reason}`);
|
|
28518
|
+
emitLoopEvent({ kind: "passed-over", tasks });
|
|
28519
|
+
announced = true;
|
|
28520
|
+
}
|
|
28521
|
+
if (forcing)
|
|
28522
|
+
return tasks;
|
|
28523
|
+
emitLoopEvent({ kind: "halt", reason: `passed over ${tasks.length} task(s)` });
|
|
28524
|
+
throw new LoopEnd(PASSED_OVER_EXIT, [
|
|
28525
|
+
`\u23ED The run ended with ${tasks.length} passed-over task(s) left [BLOCKED]. A run with --continue passes them over again.`,
|
|
28526
|
+
" To put a task back, mark its tracker line - [ ] and run again, or run again without --continue, which resumes the first [BLOCKED] line.",
|
|
28527
|
+
" Or run again with --continue --force-wrap-up to open a draft pull request listing them."
|
|
28528
|
+
].join(`
|
|
28529
|
+
`));
|
|
28530
|
+
};
|
|
28531
|
+
const gateForcedWrapUp = (gate, forced, outcome2) => {
|
|
28532
|
+
if (gate !== "stop" || forced.length === 0 || outcome2 === null || !outcome2.red || outcome2.interrupted)
|
|
28533
|
+
return gate;
|
|
28534
|
+
const max = options.settings.loopForceWrapUpMaxNewFailures;
|
|
28535
|
+
const tolerated = max === false ? 0 : max;
|
|
28536
|
+
const count = outcome2.step?.newFailures.length ?? 0;
|
|
28537
|
+
if (count > 0 && count <= tolerated) {
|
|
28538
|
+
activeOutput().warn(`\u26A0\uFE0F --force-wrap-up goes on past the red pre-wrap-up step: ${count} new failure(s), within loop.forceWrapUp.maxNewFailures ${tolerated}.`);
|
|
28539
|
+
return "go-on";
|
|
28540
|
+
}
|
|
28541
|
+
const why = count === 0 ? "the red pre-wrap-up step holds no failure it can count (errors outside any test, or no summary)" : `${count} new failure(s), over loop.forceWrapUp.maxNewFailures ${String(max)}`;
|
|
28542
|
+
const halt2 = count === 0 ? "forced wrap-up refused: no failure to count" : `forced wrap-up refused: ${why}`;
|
|
28543
|
+
emitLoopEvent({ kind: "halt", reason: halt2 });
|
|
28544
|
+
throw new LoopEnd(DECISION_STOP_EXIT, `\u26D4 --force-wrap-up refused the wrap-up: ${why}. Repair them, or raise loop.forceWrapUp.maxNewFailures.`);
|
|
28545
|
+
};
|
|
28546
|
+
const atRunEnd = () => {
|
|
28547
|
+
if (directive === null)
|
|
28548
|
+
return;
|
|
28549
|
+
activeOutput().warn(`\u26A0\uFE0F --decide=${directive.strategy} was not applied: the run met no decision point. It applies once, to the next run's first [BLOCKED] task or first stop.`);
|
|
28550
|
+
};
|
|
28551
|
+
return { skipLines, atStop, atFirstTask, taskDone, atRunEnd, atPlanEnd, gateForcedWrapUp };
|
|
26982
28552
|
}
|
|
26983
28553
|
|
|
26984
28554
|
// src/start/pause.ts
|
|
@@ -27030,22 +28600,22 @@ async function holdWhilePaused(options) {
|
|
|
27030
28600
|
}
|
|
27031
28601
|
|
|
27032
28602
|
// src/start/plan-path.ts
|
|
27033
|
-
import { existsSync as
|
|
27034
|
-
import { resolve as
|
|
28603
|
+
import { existsSync as existsSync34 } from "fs";
|
|
28604
|
+
import { resolve as resolve21 } from "path";
|
|
27035
28605
|
var DEFAULT_PLAN_FILE = "PLAN.md";
|
|
27036
28606
|
function resolvePlanPath(repoRoot, planDir, planArg) {
|
|
27037
28607
|
if (planArg)
|
|
27038
|
-
return
|
|
27039
|
-
const inPlanDir =
|
|
27040
|
-
return
|
|
28608
|
+
return resolve21(repoRoot, planArg);
|
|
28609
|
+
const inPlanDir = resolve21(repoRoot, planDir, DEFAULT_PLAN_FILE);
|
|
28610
|
+
return existsSync34(inPlanDir) ? inPlanDir : resolve21(repoRoot, DEFAULT_PLAN_FILE);
|
|
27041
28611
|
}
|
|
27042
28612
|
|
|
27043
28613
|
// src/start/risk-total.ts
|
|
27044
|
-
import { readFileSync as
|
|
28614
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
27045
28615
|
|
|
27046
28616
|
// src/plan/risk.ts
|
|
27047
|
-
import { readFileSync as
|
|
27048
|
-
import { isAbsolute as isAbsolute6, join as
|
|
28617
|
+
import { readFileSync as readFileSync41 } from "fs";
|
|
28618
|
+
import { isAbsolute as isAbsolute6, join as join34, relative as relative9 } from "path";
|
|
27049
28619
|
|
|
27050
28620
|
// src/plan/risk/accounts.ts
|
|
27051
28621
|
var PUSH_REMOTE = "origin";
|
|
@@ -27126,7 +28696,7 @@ function repoPhrase(view) {
|
|
|
27126
28696
|
async function viewRepo(gh, target) {
|
|
27127
28697
|
const result = await gh(["repo", "view", ...target, "--json", "nameWithOwner,visibility"]);
|
|
27128
28698
|
if (!result.ok) {
|
|
27129
|
-
return { ok: false, reason: `gh repo view: ${
|
|
28699
|
+
return { ok: false, reason: `gh repo view: ${firstLine7(result.stderr) ?? "gh exited non-zero"}` };
|
|
27130
28700
|
}
|
|
27131
28701
|
const parsed2 = parseJsonObject(result.stdout);
|
|
27132
28702
|
if (parsed2 === null) {
|
|
@@ -27150,7 +28720,7 @@ function stringField2(object, key) {
|
|
|
27150
28720
|
const value = object[key];
|
|
27151
28721
|
return typeof value === "string" && value !== "" ? value : null;
|
|
27152
28722
|
}
|
|
27153
|
-
function
|
|
28723
|
+
function firstLine7(text) {
|
|
27154
28724
|
return text.split(`
|
|
27155
28725
|
`).map((line) => line.trim()).find((line) => line !== "") ?? null;
|
|
27156
28726
|
}
|
|
@@ -27581,15 +29151,15 @@ function toolsFinding(task, plan, seams) {
|
|
|
27581
29151
|
return { level: "high", kind: "tools", text: `${EVERY_TOOL} \u2014 ${access.source}; ${model}; ${budget}`, ...where };
|
|
27582
29152
|
}
|
|
27583
29153
|
if (holdsShell(access.tools) && access.budget === null) {
|
|
27584
|
-
const
|
|
27585
|
-
return { level: "note", kind: "tools", text: `${SHELL_TOOL} with no budget= \u2014 ${access.source}: ${
|
|
29154
|
+
const listed2 = access.tools.join(", ");
|
|
29155
|
+
return { level: "note", kind: "tools", text: `${SHELL_TOOL} with no budget= \u2014 ${access.source}: ${listed2}; ${model}`, ...where };
|
|
27586
29156
|
}
|
|
27587
29157
|
return null;
|
|
27588
29158
|
}
|
|
27589
29159
|
function displayPath(path3, seams) {
|
|
27590
|
-
const absolute = isAbsolute6(path3) ? path3 :
|
|
29160
|
+
const absolute = isAbsolute6(path3) ? path3 : join34(seams.repoRoot, path3);
|
|
27591
29161
|
const within = (root) => {
|
|
27592
|
-
const step =
|
|
29162
|
+
const step = relative9(root, absolute);
|
|
27593
29163
|
return step === "" || step.startsWith("..") || isAbsolute6(step) ? null : step;
|
|
27594
29164
|
};
|
|
27595
29165
|
const inRepo = within(seams.repoRoot);
|
|
@@ -27604,7 +29174,7 @@ function located(finding2, file, task) {
|
|
|
27604
29174
|
}
|
|
27605
29175
|
function readText5(path3) {
|
|
27606
29176
|
try {
|
|
27607
|
-
return
|
|
29177
|
+
return readFileSync41(path3, "utf8");
|
|
27608
29178
|
} catch {
|
|
27609
29179
|
return null;
|
|
27610
29180
|
}
|
|
@@ -27612,7 +29182,7 @@ function readText5(path3) {
|
|
|
27612
29182
|
function findSkillFile(name2, seams) {
|
|
27613
29183
|
const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
|
|
27614
29184
|
for (const { dir } of tiers) {
|
|
27615
|
-
const path3 =
|
|
29185
|
+
const path3 = join34(dir, name2, "SKILL.md");
|
|
27616
29186
|
if (readText5(path3) !== null)
|
|
27617
29187
|
return path3;
|
|
27618
29188
|
}
|
|
@@ -27706,7 +29276,7 @@ function spawningRunners(root) {
|
|
|
27706
29276
|
}
|
|
27707
29277
|
async function readRiskTotalLine(input, seams = {}) {
|
|
27708
29278
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
27709
|
-
const report2 = await assessPlanRisk({ path: input.planPath, text:
|
|
29279
|
+
const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync42(input.planPath, "utf8") }, {
|
|
27710
29280
|
repoRoot: input.repoRoot,
|
|
27711
29281
|
home: input.home,
|
|
27712
29282
|
settingSources: input.config.settingSources,
|
|
@@ -28253,6 +29823,33 @@ function guardRunBranch(planStub2, branch, args) {
|
|
|
28253
29823
|
}
|
|
28254
29824
|
}
|
|
28255
29825
|
var NO_CI_WAIT_FLAG = "--no-ci-wait";
|
|
29826
|
+
var RETRY_FLAG = "--retry";
|
|
29827
|
+
var NO_RETRY_FLAG = "--no-retry";
|
|
29828
|
+
var RETRY_COUNT = /^[1-9][0-9]*$/;
|
|
29829
|
+
function refuseRetry(reason) {
|
|
29830
|
+
throw new CommandExit(1, [
|
|
29831
|
+
`\u274C Refusing ${reason}: ${RETRY_FLAG} takes a whole number from 1 to 3 as ${RETRY_FLAG}=<n>,`,
|
|
29832
|
+
" the retries this run makes after a retry-safe stop. Leave it out to take `loop.retries`,",
|
|
29833
|
+
" and set `loop.retries: false` in .rafa/config.yaml for none.",
|
|
29834
|
+
NOTHING_DISPATCHED
|
|
29835
|
+
].join(`
|
|
29836
|
+
`));
|
|
29837
|
+
}
|
|
29838
|
+
function readRetryFlag(args) {
|
|
29839
|
+
const end = args.indexOf("--");
|
|
29840
|
+
const words = end === -1 ? args : args.slice(0, end);
|
|
29841
|
+
if (words.includes(NO_RETRY_FLAG))
|
|
29842
|
+
refuseRetry(NO_RETRY_FLAG);
|
|
29843
|
+
if (words.includes(RETRY_FLAG))
|
|
29844
|
+
refuseRetry(`a bare ${RETRY_FLAG}`);
|
|
29845
|
+
const typed = argValue([...words].reverse(), RETRY_FLAG);
|
|
29846
|
+
if (typed === undefined)
|
|
29847
|
+
return;
|
|
29848
|
+
const count = RETRY_COUNT.test(typed) ? Number(typed) : Number.NaN;
|
|
29849
|
+
if (!isLoopRetryCount(count))
|
|
29850
|
+
refuseRetry(`${RETRY_FLAG}=${typed}`);
|
|
29851
|
+
return count;
|
|
29852
|
+
}
|
|
28256
29853
|
function readRunArgs(args) {
|
|
28257
29854
|
return {
|
|
28258
29855
|
startAt: argValue(args, "--start-at"),
|
|
@@ -28260,13 +29857,15 @@ function readRunArgs(args) {
|
|
|
28260
29857
|
ciWait: !args.includes(NO_CI_WAIT_FLAG),
|
|
28261
29858
|
ciTimeoutMin: Number(argValue(args, "--ci-timeout") ?? DEFAULT_CI_TIMEOUT_MIN),
|
|
28262
29859
|
ciAttempts: Number(argValue(args, "--ci-attempts") ?? DEFAULT_CI_ATTEMPTS),
|
|
28263
|
-
roadmap: args.includes(ROADMAP_FLAG2)
|
|
29860
|
+
roadmap: args.includes(ROADMAP_FLAG2),
|
|
29861
|
+
retry: readRetryFlag(args),
|
|
29862
|
+
continueRun: readContinueArgs(args)
|
|
28264
29863
|
};
|
|
28265
29864
|
}
|
|
28266
29865
|
|
|
28267
29866
|
// src/start/worktree.ts
|
|
28268
|
-
import { realpathSync as
|
|
28269
|
-
import { join as
|
|
29867
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
29868
|
+
import { join as join35, resolve as resolve23 } from "path";
|
|
28270
29869
|
|
|
28271
29870
|
// src/start/claim-catch-up.ts
|
|
28272
29871
|
var OWN_COMMIT_FORMAT = "--format=%H%x09%P%x09%s";
|
|
@@ -28299,18 +29898,18 @@ function isClaimOnly(git, commit, baseTip) {
|
|
|
28299
29898
|
function readClaimCatchUp(git, branch, base) {
|
|
28300
29899
|
const named3 = { branch, base };
|
|
28301
29900
|
const remoteBase = `${REMOTE}/${base}`;
|
|
28302
|
-
const
|
|
29901
|
+
const unreadable2 = (reason) => ({ kind: "unreadable", ...named3, reason });
|
|
28303
29902
|
const baseRef = remoteTrackingRef(base);
|
|
28304
29903
|
const branchRef = localRef(branch);
|
|
28305
29904
|
const baseTip = commitAt(git, baseRef);
|
|
28306
29905
|
if (baseTip === null)
|
|
28307
|
-
return
|
|
29906
|
+
return unreadable2(`${remoteBase} is not known here`);
|
|
28308
29907
|
if (commitAt(git, branchRef) === null)
|
|
28309
|
-
return
|
|
29908
|
+
return unreadable2(`${branch} is not known here`);
|
|
28310
29909
|
const counted5 = git(["rev-list", "--count", `${branchRef}..${baseRef}`]);
|
|
28311
29910
|
const behind = Number(counted5.stdout.trim());
|
|
28312
29911
|
if (!counted5.ok || counted5.stdout.trim() === "" || !Number.isInteger(behind)) {
|
|
28313
|
-
return
|
|
29912
|
+
return unreadable2(`could not count how far ${branch} is behind ${remoteBase}: ${gitSaid(counted5)}`);
|
|
28314
29913
|
}
|
|
28315
29914
|
if (behind === 0)
|
|
28316
29915
|
return { kind: "current", ...named3 };
|
|
@@ -28318,22 +29917,22 @@ function readClaimCatchUp(git, branch, base) {
|
|
|
28318
29917
|
const mergeBase = shared.stdout.trim();
|
|
28319
29918
|
if (!shared.ok || mergeBase === "") {
|
|
28320
29919
|
const said = gitSaid(shared);
|
|
28321
|
-
return
|
|
29920
|
+
return unreadable2(said === "" ? `${branch} shares no history with ${remoteBase}` : `could not read where ${branch} left ${remoteBase}: ${said}`);
|
|
28322
29921
|
}
|
|
28323
|
-
const
|
|
28324
|
-
if (!
|
|
28325
|
-
return
|
|
29922
|
+
const listed2 = git(["log", OWN_COMMIT_FORMAT, `${baseRef}..${branchRef}`]);
|
|
29923
|
+
if (!listed2.ok) {
|
|
29924
|
+
return unreadable2(`could not list ${branch}'s commits past ${remoteBase}: ${gitSaid(listed2)}`);
|
|
28326
29925
|
}
|
|
28327
|
-
const own = ownCommitsOf(
|
|
29926
|
+
const own = ownCommitsOf(listed2.stdout);
|
|
28328
29927
|
const workCommits = own.filter((commit) => !isClaimOnly(git, commit, baseTip)).length;
|
|
28329
29928
|
const standing = { ...named3, behind, ahead: own.length, mergeBase, baseTip };
|
|
28330
29929
|
return workCommits === 0 ? { kind: "catch-up", ...standing } : { kind: "behind", ...standing, workCommits };
|
|
28331
29930
|
}
|
|
28332
29931
|
|
|
28333
29932
|
// src/start/worktree-dir.ts
|
|
28334
|
-
import { resolve as
|
|
29933
|
+
import { resolve as resolve22 } from "path";
|
|
28335
29934
|
function worktreeDirAt(projectRoot3, worktreeDir) {
|
|
28336
|
-
return
|
|
29935
|
+
return resolve22(projectRoot3, worktreeDir);
|
|
28337
29936
|
}
|
|
28338
29937
|
|
|
28339
29938
|
// src/start/worktree.ts
|
|
@@ -28341,7 +29940,7 @@ var INDENT7 = " ";
|
|
|
28341
29940
|
var UNTOUCHED = `${INDENT7}The main checkout's branch and working tree were not touched.`;
|
|
28342
29941
|
var DEFAULT_WORKTREE_SEAMS = Object.freeze({});
|
|
28343
29942
|
function worktreePathFor(projectRoot3, worktreeDir, stub) {
|
|
28344
|
-
return
|
|
29943
|
+
return join35(worktreeDirAt(projectRoot3, worktreeDir), stub);
|
|
28345
29944
|
}
|
|
28346
29945
|
function addStep(route, plan, path3) {
|
|
28347
29946
|
const label = `add the worktree for ${plan.branch} at ${path3}`;
|
|
@@ -28395,9 +29994,9 @@ function heldRefusal(branch, holder, detail) {
|
|
|
28395
29994
|
}
|
|
28396
29995
|
function canonicalPath(path3) {
|
|
28397
29996
|
try {
|
|
28398
|
-
return
|
|
29997
|
+
return realpathSync5(path3);
|
|
28399
29998
|
} catch {
|
|
28400
|
-
return
|
|
29999
|
+
return resolve23(path3);
|
|
28401
30000
|
}
|
|
28402
30001
|
}
|
|
28403
30002
|
function readExisting(listing2, branch, path3) {
|
|
@@ -28433,8 +30032,8 @@ function refExists2(git, ref8) {
|
|
|
28433
30032
|
return git(["show-ref", "--verify", "--quiet", ref8]).ok;
|
|
28434
30033
|
}
|
|
28435
30034
|
function readListing(git) {
|
|
28436
|
-
const
|
|
28437
|
-
return
|
|
30035
|
+
const listed2 = git(["worktree", "list", "--porcelain"]);
|
|
30036
|
+
return listed2.ok ? parseWorktrees(listed2.stdout) : [];
|
|
28438
30037
|
}
|
|
28439
30038
|
function openingLine2(route, plan) {
|
|
28440
30039
|
return route === "create" ? `
|
|
@@ -28570,13 +30169,13 @@ async function settleRunCheckout(request, seams = {}) {
|
|
|
28570
30169
|
}
|
|
28571
30170
|
|
|
28572
30171
|
// src/start/suite-step.ts
|
|
28573
|
-
import { existsSync as
|
|
30172
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync14, readFileSync as readFileSync48, renameSync as renameSync7, writeFileSync as writeFileSync17 } from "fs";
|
|
28574
30173
|
import { constants as constants2 } from "os";
|
|
28575
|
-
import { basename as basename11, dirname as
|
|
30174
|
+
import { basename as basename11, dirname as dirname18, join as join40 } from "path";
|
|
28576
30175
|
|
|
28577
30176
|
// src/suite/baseline.ts
|
|
28578
|
-
import { existsSync as
|
|
28579
|
-
import { basename as basename9, dirname as
|
|
30177
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync12, readFileSync as readFileSync43, renameSync as renameSync6, writeFileSync as writeFileSync15 } from "fs";
|
|
30178
|
+
import { basename as basename9, dirname as dirname16, join as join36 } from "path";
|
|
28580
30179
|
var BASELINE_VERSION = 1;
|
|
28581
30180
|
var BASELINE_FILE_PREFIX = "SUITE_BASELINE";
|
|
28582
30181
|
var PLAN_FILE_NAME2 = /^PLAN(?:_TRACKER)?(-.+)?\.md$/;
|
|
@@ -28586,7 +30185,7 @@ function baselinePathFor(trackerPath) {
|
|
|
28586
30185
|
if (match === null) {
|
|
28587
30186
|
throw new RangeError(`baselinePathFor: ${trackerPath} is not a PLAN or PLAN_TRACKER file`);
|
|
28588
30187
|
}
|
|
28589
|
-
return
|
|
30188
|
+
return join36(dirname16(trackerPath), `${BASELINE_FILE_PREFIX}${match[1] ?? ""}.json`);
|
|
28590
30189
|
}
|
|
28591
30190
|
function copyFailure(failure3) {
|
|
28592
30191
|
return failure3.message === undefined ? { file: failure3.file, name: failure3.name } : { file: failure3.file, name: failure3.name, message: failure3.message };
|
|
@@ -28605,9 +30204,9 @@ function baselineOf(result, recordedAt, commit) {
|
|
|
28605
30204
|
};
|
|
28606
30205
|
}
|
|
28607
30206
|
function writeBaseline(path3, baseline) {
|
|
28608
|
-
mkdirSync12(
|
|
30207
|
+
mkdirSync12(dirname16(path3), { recursive: true });
|
|
28609
30208
|
const temporary = `${path3}.${process.pid}.tmp`;
|
|
28610
|
-
|
|
30209
|
+
writeFileSync15(temporary, `${JSON.stringify(baseline, null, 2)}
|
|
28611
30210
|
`, "utf8");
|
|
28612
30211
|
renameSync6(temporary, path3);
|
|
28613
30212
|
}
|
|
@@ -28649,11 +30248,11 @@ function asBaseline(parsed2) {
|
|
|
28649
30248
|
return { state: "read", baseline: parsed2 };
|
|
28650
30249
|
}
|
|
28651
30250
|
function readBaseline(path3) {
|
|
28652
|
-
if (!
|
|
30251
|
+
if (!existsSync35(path3))
|
|
28653
30252
|
return { state: "missing" };
|
|
28654
30253
|
let text;
|
|
28655
30254
|
try {
|
|
28656
|
-
text =
|
|
30255
|
+
text = readFileSync43(path3, "utf8");
|
|
28657
30256
|
} catch (error) {
|
|
28658
30257
|
const reason = error instanceof Error ? error.message : String(error);
|
|
28659
30258
|
return { state: "unreadable", reason };
|
|
@@ -28748,8 +30347,8 @@ async function readPlanOwns(options) {
|
|
|
28748
30347
|
}
|
|
28749
30348
|
|
|
28750
30349
|
// src/suite/run.ts
|
|
28751
|
-
import { existsSync as
|
|
28752
|
-
import { basename as basename10, dirname as
|
|
30350
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync13, readFileSync as readFileSync44, rmSync as rmSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
30351
|
+
import { basename as basename10, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute7, join as join37 } from "path";
|
|
28753
30352
|
|
|
28754
30353
|
// src/suite/unhandled.ts
|
|
28755
30354
|
var UNHANDLED_HEADING = "# Unhandled error between tests";
|
|
@@ -28790,14 +30389,14 @@ function rawBlocks(lines) {
|
|
|
28790
30389
|
return [{ header: headerAbove(lines, index), body: lines.slice(start, bodyEnd(lines, start)) }];
|
|
28791
30390
|
});
|
|
28792
30391
|
}
|
|
28793
|
-
function
|
|
30392
|
+
function errorLine2(body) {
|
|
28794
30393
|
const found = body.find((line) => line.trim() !== "" && !FRAME_LINE.test(line) && !CARET_LINE.test(line));
|
|
28795
30394
|
return found?.trim() ?? null;
|
|
28796
30395
|
}
|
|
28797
30396
|
function parseUnhandled(stderr) {
|
|
28798
30397
|
return rawBlocks(linesOf2(stderr)).map((raw) => ({
|
|
28799
30398
|
file: raw.header === null ? null : raw.header.slice(0, -1),
|
|
28800
|
-
firstLine:
|
|
30399
|
+
firstLine: errorLine2(raw.body)
|
|
28801
30400
|
}));
|
|
28802
30401
|
}
|
|
28803
30402
|
function summaryLines(lines) {
|
|
@@ -28859,7 +30458,7 @@ var OUTPUT_SUFFIX = ".output.txt";
|
|
|
28859
30458
|
function outputFileFor(junitFile) {
|
|
28860
30459
|
const name2 = basename10(junitFile);
|
|
28861
30460
|
const stem = name2.endsWith(JUNIT_SUFFIX) ? name2.slice(0, -JUNIT_SUFFIX.length) : name2.slice(0, name2.length - extname2(name2).length);
|
|
28862
|
-
return
|
|
30461
|
+
return join37(dirname17(junitFile), `${stem}${OUTPUT_SUFFIX}`);
|
|
28863
30462
|
}
|
|
28864
30463
|
function suiteEnv(base) {
|
|
28865
30464
|
return Object.fromEntries(Object.entries(base).filter(([key]) => key !== CLAUDE_CODE_ENV));
|
|
@@ -28963,11 +30562,11 @@ function parseJunitFailures(xml) {
|
|
|
28963
30562
|
return walk.failures;
|
|
28964
30563
|
}
|
|
28965
30564
|
function readJunit(path3) {
|
|
28966
|
-
if (!
|
|
30565
|
+
if (!existsSync36(path3))
|
|
28967
30566
|
return { junit: "missing", failures: [] };
|
|
28968
30567
|
let text;
|
|
28969
30568
|
try {
|
|
28970
|
-
text =
|
|
30569
|
+
text = readFileSync44(path3, "utf8");
|
|
28971
30570
|
} catch {
|
|
28972
30571
|
return { junit: "unreadable", failures: [] };
|
|
28973
30572
|
}
|
|
@@ -28991,13 +30590,13 @@ async function runSuite(options) {
|
|
|
28991
30590
|
}
|
|
28992
30591
|
const command = suiteCommand(options.junitFile, options.paths, options.changedSince);
|
|
28993
30592
|
const outputFile = outputFileFor(options.junitFile);
|
|
28994
|
-
mkdirSync13(
|
|
28995
|
-
|
|
28996
|
-
|
|
30593
|
+
mkdirSync13(dirname17(options.junitFile), { recursive: true });
|
|
30594
|
+
rmSync5(options.junitFile, { force: true });
|
|
30595
|
+
rmSync5(outputFile, { force: true });
|
|
28997
30596
|
const spawn = options.spawn ?? spawnSuite;
|
|
28998
30597
|
const env = suiteEnv(options.env ?? process.env);
|
|
28999
30598
|
const { exitCode, stderr } = await spawn(command, { cwd: options.cwd, env });
|
|
29000
|
-
|
|
30599
|
+
writeFileSync16(outputFile, unhandledText(stderr));
|
|
29001
30600
|
const { summary, errors } = readSummary2(stderr);
|
|
29002
30601
|
const { junit, failures } = readJunit(options.junitFile);
|
|
29003
30602
|
const unhandled = parseUnhandled(stderr);
|
|
@@ -29006,8 +30605,8 @@ async function runSuite(options) {
|
|
|
29006
30605
|
}
|
|
29007
30606
|
|
|
29008
30607
|
// src/suite/scope.ts
|
|
29009
|
-
import { existsSync as
|
|
29010
|
-
import { isAbsolute as isAbsolute8, join as
|
|
30608
|
+
import { existsSync as existsSync37, readdirSync as readdirSync14, readFileSync as readFileSync45 } from "fs";
|
|
30609
|
+
import { isAbsolute as isAbsolute8, join as join38, relative as relative10, sep as sep3 } from "path";
|
|
29011
30610
|
var BUNFIG_FILE = "bunfig.toml";
|
|
29012
30611
|
var TEST_FILE_NAME = /[._](?:test|spec)\.(?:tsx?|jsx?|[cm][jt]s)$/;
|
|
29013
30612
|
var DEPENDENCY_FOLDER = "node_modules";
|
|
@@ -29067,7 +30666,7 @@ function stageStepScope(input) {
|
|
|
29067
30666
|
return { scope: "paths", folders, integration, paths };
|
|
29068
30667
|
}
|
|
29069
30668
|
function preloadPath(root, entry) {
|
|
29070
|
-
const path3 = isAbsolute8(entry) ?
|
|
30669
|
+
const path3 = isAbsolute8(entry) ? relative10(root, entry).split(sep3).join("/") : entry;
|
|
29071
30670
|
const repoRelative2 = normaliseFolder(path3);
|
|
29072
30671
|
if (repoRelative2 === "" || isAbsolute8(repoRelative2))
|
|
29073
30672
|
return null;
|
|
@@ -29090,11 +30689,11 @@ function preloadFilesOf(text, root) {
|
|
|
29090
30689
|
return paths.filter((path3) => path3 !== null);
|
|
29091
30690
|
}
|
|
29092
30691
|
function readPreloadFiles(root) {
|
|
29093
|
-
const path3 =
|
|
29094
|
-
if (!
|
|
30692
|
+
const path3 = join38(root, BUNFIG_FILE);
|
|
30693
|
+
if (!existsSync37(path3))
|
|
29095
30694
|
return { state: "missing", files: [] };
|
|
29096
30695
|
try {
|
|
29097
|
-
return { state: "read", files: preloadFilesOf(
|
|
30696
|
+
return { state: "read", files: preloadFilesOf(readFileSync45(path3, "utf8"), root) };
|
|
29098
30697
|
} catch (error) {
|
|
29099
30698
|
const reason = error instanceof Error ? error.message : String(error);
|
|
29100
30699
|
return { state: "unreadable", files: [], reason };
|
|
@@ -29103,7 +30702,7 @@ function readPreloadFiles(root) {
|
|
|
29103
30702
|
function listTestFiles(root) {
|
|
29104
30703
|
const found = [];
|
|
29105
30704
|
const walk = (folder) => {
|
|
29106
|
-
for (const entry of readdirSync14(
|
|
30705
|
+
for (const entry of readdirSync14(join38(root, folder), { withFileTypes: true })) {
|
|
29107
30706
|
const path3 = folder === "" ? entry.name : `${folder}/${entry.name}`;
|
|
29108
30707
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== DEPENDENCY_FOLDER)
|
|
29109
30708
|
walk(path3);
|
|
@@ -29116,8 +30715,8 @@ function listTestFiles(root) {
|
|
|
29116
30715
|
}
|
|
29117
30716
|
|
|
29118
30717
|
// src/start/lint-step.ts
|
|
29119
|
-
import { existsSync as
|
|
29120
|
-
import { join as
|
|
30718
|
+
import { existsSync as existsSync38 } from "fs";
|
|
30719
|
+
import { join as join39, relative as relative11 } from "path";
|
|
29121
30720
|
var LINT_COMMAND = ["bunx", "eslint", "--no-warn-ignored"];
|
|
29122
30721
|
var LINT_REPORT_FLAGS = ["--format", "json"];
|
|
29123
30722
|
var ESLINT_CONFIG_FILES = [
|
|
@@ -29128,9 +30727,9 @@ var ESLINT_CONFIG_FILES = [
|
|
|
29128
30727
|
"eslint.config.mts",
|
|
29129
30728
|
"eslint.config.cts"
|
|
29130
30729
|
];
|
|
29131
|
-
var
|
|
30730
|
+
var ERRORS_LISTED2 = 10;
|
|
29132
30731
|
var CRASH_BANNER = /^(?:Oops!|ESLint: \d)/;
|
|
29133
|
-
var
|
|
30732
|
+
var NOTHING_RAN2 = { ran: false, red: false, interrupted: false, blocker: null };
|
|
29134
30733
|
async function runEslint(options) {
|
|
29135
30734
|
const proc = Bun.spawn([...options.argv], {
|
|
29136
30735
|
cwd: options.cwd,
|
|
@@ -29146,7 +30745,7 @@ async function runEslint(options) {
|
|
|
29146
30745
|
return { exitCode, stdout, stderr };
|
|
29147
30746
|
}
|
|
29148
30747
|
function hasEslintConfig(checkout) {
|
|
29149
|
-
return ESLINT_CONFIG_FILES.some((name2) =>
|
|
30748
|
+
return ESLINT_CONFIG_FILES.some((name2) => existsSync38(join39(checkout, name2)));
|
|
29150
30749
|
}
|
|
29151
30750
|
function readLintFiles(git, base) {
|
|
29152
30751
|
const result = git(["diff", "--name-only", "-z", "--no-renames", "--diff-filter=d", base, "HEAD"]);
|
|
@@ -29163,7 +30762,7 @@ function fileErrorsOf(entry, cwd) {
|
|
|
29163
30762
|
const errors = entry["errorCount"];
|
|
29164
30763
|
if (typeof entry["filePath"] !== "string" || typeof errors !== "number" || errors === 0)
|
|
29165
30764
|
return null;
|
|
29166
|
-
const file =
|
|
30765
|
+
const file = relative11(cwd, entry["filePath"]);
|
|
29167
30766
|
const messages = Array.isArray(entry["messages"]) ? entry["messages"] : [];
|
|
29168
30767
|
const lines = messages.filter((message) => message["severity"] === 2).map((message) => messageLine(file, message));
|
|
29169
30768
|
return { file, errors, lines };
|
|
@@ -29208,10 +30807,10 @@ function announce(label, count, run, files) {
|
|
|
29208
30807
|
const errors = files.reduce((sum, file) => sum + file.errors, 0);
|
|
29209
30808
|
activeOutput().info(`${ran}; ${errors} error(s) in ${files.length} file(s).`);
|
|
29210
30809
|
const lines = files.flatMap((file) => file.lines);
|
|
29211
|
-
for (const line of lines.slice(0,
|
|
30810
|
+
for (const line of lines.slice(0, ERRORS_LISTED2))
|
|
29212
30811
|
activeOutput().info(` ${line}`);
|
|
29213
|
-
if (lines.length >
|
|
29214
|
-
activeOutput().info(` ...and ${lines.length -
|
|
30812
|
+
if (lines.length > ERRORS_LISTED2)
|
|
30813
|
+
activeOutput().info(` ...and ${lines.length - ERRORS_LISTED2} more.`);
|
|
29215
30814
|
}
|
|
29216
30815
|
function filesToLint(input, label) {
|
|
29217
30816
|
if (!hasEslintConfig(input.checkout)) {
|
|
@@ -29230,13 +30829,13 @@ async function runLintStep(input) {
|
|
|
29230
30829
|
const label = lintLabel(input.task);
|
|
29231
30830
|
const files = filesToLint(input, label);
|
|
29232
30831
|
if (files === null)
|
|
29233
|
-
return
|
|
30832
|
+
return NOTHING_RAN2;
|
|
29234
30833
|
let run;
|
|
29235
30834
|
try {
|
|
29236
30835
|
run = await (input.runLint ?? runEslint)({ cwd: input.checkout, argv: [...LINT_COMMAND, ...LINT_REPORT_FLAGS, ...files] });
|
|
29237
30836
|
} catch (error) {
|
|
29238
30837
|
activeOutput().warn(`\u26A0\uFE0F The ${label} could not run (${messageOf(error)}); the run goes on without it.`);
|
|
29239
|
-
return
|
|
30838
|
+
return NOTHING_RAN2;
|
|
29240
30839
|
}
|
|
29241
30840
|
if (run.exitCode === input.stopCode || input.isInterrupted?.() === true) {
|
|
29242
30841
|
activeOutput().info(`\u23F9 The ${label} was interrupted by SIGINT: read as a stop, not as errors.`);
|
|
@@ -29249,102 +30848,8 @@ async function runLintStep(input) {
|
|
|
29249
30848
|
return { ran: true, red: true, interrupted: false, blocker: lintBlockerText(label, run, reported) };
|
|
29250
30849
|
}
|
|
29251
30850
|
|
|
29252
|
-
// src/start/suite-blocker.ts
|
|
29253
|
-
import { readFileSync as readFileSync41, writeFileSync as writeFileSync15 } from "fs";
|
|
29254
|
-
function failingFiles(failures) {
|
|
29255
|
-
const counts = new Map;
|
|
29256
|
-
for (const failure3 of failures)
|
|
29257
|
-
counts.set(failure3.file, (counts.get(failure3.file) ?? 0) + 1);
|
|
29258
|
-
return [...counts.entries()];
|
|
29259
|
-
}
|
|
29260
|
-
var UNHANDLED_NAMED = 10;
|
|
29261
|
-
function unhandledName(error) {
|
|
29262
|
-
const file = error.file ?? "a file Bun did not name";
|
|
29263
|
-
return error.firstLine === null ? `${file} threw an error Bun printed no line for` : `${file} threw "${error.firstLine}"`;
|
|
29264
|
-
}
|
|
29265
|
-
function unhandledNames(errors) {
|
|
29266
|
-
if (errors.length === 0)
|
|
29267
|
-
return "no block of Bun's stderr named a file";
|
|
29268
|
-
const named3 = errors.slice(0, UNHANDLED_NAMED).map(unhandledName);
|
|
29269
|
-
const left = errors.length - named3.length;
|
|
29270
|
-
const more = left > 0 ? [`and ${left} more`] : [];
|
|
29271
|
-
return [...named3, ...more].join("; ");
|
|
29272
|
-
}
|
|
29273
|
-
function unhandledFiles(errors) {
|
|
29274
|
-
return [...new Set(errors.flatMap((error) => error.file === null ? [] : [error.file]))];
|
|
29275
|
-
}
|
|
29276
|
-
function runnablePath(path3) {
|
|
29277
|
-
return path3.startsWith("/") || path3.startsWith("./") ? path3 : `./${path3}`;
|
|
29278
|
-
}
|
|
29279
|
-
function errorsText(newErrors, errors) {
|
|
29280
|
-
const counted5 = `${newErrors} more error(s) outside any test than the baseline`;
|
|
29281
|
-
if (errors.length === 0)
|
|
29282
|
-
return `${counted5}: a test file that throws while it loads, which the JUnit report names no file for.`;
|
|
29283
|
-
const files = unhandledFiles(errors);
|
|
29284
|
-
const run = files.length === 0 ? "" : ` Run bun test ${files.map(runnablePath).join(" ")} and make each load.`;
|
|
29285
|
-
return `${counted5}; Bun's stderr named them as ${unhandledNames(errors)}.${run}`;
|
|
29286
|
-
}
|
|
29287
|
-
function blockerText(label, result, verdict) {
|
|
29288
|
-
const files = failingFiles(verdict.fresh);
|
|
29289
|
-
const parts = [`The runner's ${label} found failures the suite baseline does not hold.`];
|
|
29290
|
-
if (files.length > 0) {
|
|
29291
|
-
const named3 = files.map(([file, count]) => `${file} (${count} ${count === 1 ? "test" : "tests"})`);
|
|
29292
|
-
parts.push(`New failing test files: ${named3.join(", ")}. Run bun test ${files.map(([file]) => runnablePath(file)).join(" ")} and make them pass.`);
|
|
29293
|
-
}
|
|
29294
|
-
if (verdict.newErrors > 0)
|
|
29295
|
-
parts.push(errorsText(verdict.newErrors, result.unhandled));
|
|
29296
|
-
if (verdict.unreported)
|
|
29297
|
-
parts.push(`bun test exited ${result.exitCode} and printed no summary line.`);
|
|
29298
|
-
return parts.join(" ");
|
|
29299
|
-
}
|
|
29300
|
-
var REPAIR_AGENT = "build-error-resolver";
|
|
29301
|
-
var REPAIR_TASK = /^Repair the red (?:task|stage|pre-wrap-up) step at commit \S+$/;
|
|
29302
|
-
var PRE_WRAP_UP_REPAIR = /^Repair the red pre-wrap-up step at commit \S+$/;
|
|
29303
|
-
var COMMIT_SHOWN = 12;
|
|
29304
|
-
var TICKED_PREFIX = "- [x] ";
|
|
29305
|
-
function repairTaskText(kind, commit) {
|
|
29306
|
-
const shown2 = commit === null ? "(unread)" : commit.slice(0, COMMIT_SHOWN);
|
|
29307
|
-
return `Repair the red ${kind} step at commit ${shown2}`;
|
|
29308
|
-
}
|
|
29309
|
-
function isRepairTask(text) {
|
|
29310
|
-
return REPAIR_TASK.test(text.trim());
|
|
29311
|
-
}
|
|
29312
|
-
function lineOfTask(content, text) {
|
|
29313
|
-
const found = parsePlan(content).tasks.filter((task) => task.text === text.trim());
|
|
29314
|
-
return found.at(-1)?.lineNum ?? null;
|
|
29315
|
-
}
|
|
29316
|
-
function lineOfTickedPreWrapUpRepair(content) {
|
|
29317
|
-
const found = parsePlan(content).tasks.filter((task) => task.status === "done" && PRE_WRAP_UP_REPAIR.test(task.text.trim()));
|
|
29318
|
-
return found.at(-1)?.lineNum ?? null;
|
|
29319
|
-
}
|
|
29320
|
-
function existingRepairOf(content, site) {
|
|
29321
|
-
if (site.kind === "pre-wrap-up")
|
|
29322
|
-
return lineOfTickedPreWrapUpRepair(content);
|
|
29323
|
-
return site.task !== undefined && isRepairTask(site.task) ? lineOfTask(content, site.task) : null;
|
|
29324
|
-
}
|
|
29325
|
-
function blockRepairAgain(trackerPath, content, line, blocker) {
|
|
29326
|
-
const lines = content.split(`
|
|
29327
|
-
`);
|
|
29328
|
-
const current = lines[line] ?? "";
|
|
29329
|
-
if (current.startsWith(TICKED_PREFIX)) {
|
|
29330
|
-
const reopened = [...lines.slice(0, line), `- [ ] ${current.slice(TICKED_PREFIX.length)}`, ...lines.slice(line + 1)];
|
|
29331
|
-
writeFileSync15(trackerPath, reopened.join(`
|
|
29332
|
-
`), "utf8");
|
|
29333
|
-
}
|
|
29334
|
-
return writeTrackerBlocker(trackerPath, line, blocker) ? { line, inserted: false } : null;
|
|
29335
|
-
}
|
|
29336
|
-
function writeRepairTask(trackerPath, blocker, site) {
|
|
29337
|
-
const content = readFileSync41(trackerPath, "utf8");
|
|
29338
|
-
const repaired = existingRepairOf(content, site);
|
|
29339
|
-
if (repaired !== null)
|
|
29340
|
-
return blockRepairAgain(trackerPath, content, repaired, blocker);
|
|
29341
|
-
const firstOpen = parsePlan(content).tasks.find((task) => task.status !== "done")?.lineNum ?? null;
|
|
29342
|
-
const entry = { task: repairTaskText(site.kind, site.commit), declaration: `agent=${REPAIR_AGENT}`, blocker };
|
|
29343
|
-
return { line: insertTrackerTask(trackerPath, entry, firstOpen), inserted: true };
|
|
29344
|
-
}
|
|
29345
|
-
|
|
29346
30851
|
// src/start/sweep-timing.ts
|
|
29347
|
-
import { existsSync as
|
|
30852
|
+
import { existsSync as existsSync39, readFileSync as readFileSync46 } from "fs";
|
|
29348
30853
|
var SLOW_SWEEP_SECONDS = 10;
|
|
29349
30854
|
var SUITE_TAG = /<(\/?)testsuite\b([^>]*?)(\/?)>/g;
|
|
29350
30855
|
function fileTimeOf(attributes) {
|
|
@@ -29384,10 +30889,10 @@ function slowSweepLine(slow) {
|
|
|
29384
30889
|
return `\uD83D\uDC22 ${slow.length} tests.alwaysRun file(s) took over ${SLOW_SWEEP_SECONDS}s in the task step: ${named3}.`;
|
|
29385
30890
|
}
|
|
29386
30891
|
function readFileTimes(path3) {
|
|
29387
|
-
if (!
|
|
30892
|
+
if (!existsSync39(path3))
|
|
29388
30893
|
return [];
|
|
29389
30894
|
try {
|
|
29390
|
-
return parseJunitFileTimes(
|
|
30895
|
+
return parseJunitFileTimes(readFileSync46(path3, "utf8")) ?? [];
|
|
29391
30896
|
} catch {
|
|
29392
30897
|
return [];
|
|
29393
30898
|
}
|
|
@@ -29462,280 +30967,6 @@ function foldResults(first, second, stopCode) {
|
|
|
29462
30967
|
return first.noTestFiles === true && second.noTestFiles === true ? { ...folded2, noTestFiles: true } : folded2;
|
|
29463
30968
|
}
|
|
29464
30969
|
|
|
29465
|
-
// src/start/type-step.ts
|
|
29466
|
-
import { existsSync as existsSync35, mkdtempSync, realpathSync as realpathSync5, rmSync as rmSync5, symlinkSync as symlinkSync2, writeFileSync as writeFileSync16 } from "fs";
|
|
29467
|
-
import { tmpdir } from "os";
|
|
29468
|
-
import { dirname as dirname14, join as join36, relative as relative11, resolve as resolve22, sep as sep3 } from "path";
|
|
29469
|
-
var TEST_FILE_SUFFIX = ".test.ts";
|
|
29470
|
-
var TSC_FLAGS = ["--noEmit", "--pretty", "false"];
|
|
29471
|
-
var ERRORS_LISTED2 = 10;
|
|
29472
|
-
var NODE_MODULES = "node_modules";
|
|
29473
|
-
var TSC_BIN = [".bin", "tsc"];
|
|
29474
|
-
var SCRATCH_NAME = "tsconfig.json";
|
|
29475
|
-
var FILE_ERROR = /^(.+)\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
|
29476
|
-
var GLOBAL_ERROR = /^error (TS\d+): (.*)$/;
|
|
29477
|
-
var NOTHING_RAN2 = { ran: false, red: false, interrupted: false, blocker: null };
|
|
29478
|
-
var GREEN = { ran: true, red: false, interrupted: false, blocker: null };
|
|
29479
|
-
var INTERRUPTED = { ran: true, red: false, interrupted: true, blocker: null };
|
|
29480
|
-
async function runTsc(options) {
|
|
29481
|
-
const proc = Bun.spawn([...options.argv], {
|
|
29482
|
-
cwd: options.cwd,
|
|
29483
|
-
stdin: "ignore",
|
|
29484
|
-
stdout: "pipe",
|
|
29485
|
-
stderr: "pipe"
|
|
29486
|
-
});
|
|
29487
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
29488
|
-
new Response(proc.stdout).text(),
|
|
29489
|
-
new Response(proc.stderr).text(),
|
|
29490
|
-
proc.exited
|
|
29491
|
-
]);
|
|
29492
|
-
return { exitCode, stdout, stderr };
|
|
29493
|
-
}
|
|
29494
|
-
function typeLabel(task) {
|
|
29495
|
-
return `type step after "${task}"`;
|
|
29496
|
-
}
|
|
29497
|
-
function pathsOf(status) {
|
|
29498
|
-
return /^[RC]/.test(status) ? 2 : 1;
|
|
29499
|
-
}
|
|
29500
|
-
function readTypeFiles(git, base) {
|
|
29501
|
-
const result = git(["diff", "--name-status", "-z", "--find-renames", "--diff-filter=d", base, "HEAD"]);
|
|
29502
|
-
if (!result.ok) {
|
|
29503
|
-
activeOutput().warn(`\u26A0\uFE0F git diff from ${base} did not answer (${gitSaid(result) || "nothing said"}); the type step runs nothing.`);
|
|
29504
|
-
return null;
|
|
29505
|
-
}
|
|
29506
|
-
const fields = result.stdout.split("\x00").filter((field4) => field4 !== "");
|
|
29507
|
-
const files = [];
|
|
29508
|
-
for (let index = 0;index < fields.length; ) {
|
|
29509
|
-
const status = fields[index] ?? "";
|
|
29510
|
-
const count = pathsOf(status);
|
|
29511
|
-
const paths = fields.slice(index + 1, index + 1 + count);
|
|
29512
|
-
index += 1 + count;
|
|
29513
|
-
const head = paths[paths.length - 1];
|
|
29514
|
-
if (head === undefined || !head.endsWith(TEST_FILE_SUFFIX))
|
|
29515
|
-
continue;
|
|
29516
|
-
files.push({ head, base: status.startsWith("A") ? null : paths[0] ?? null });
|
|
29517
|
-
}
|
|
29518
|
-
return files;
|
|
29519
|
-
}
|
|
29520
|
-
function realPath(path3) {
|
|
29521
|
-
try {
|
|
29522
|
-
return realpathSync5(path3);
|
|
29523
|
-
} catch {
|
|
29524
|
-
return resolve22(path3);
|
|
29525
|
-
}
|
|
29526
|
-
}
|
|
29527
|
-
function walkStop(git) {
|
|
29528
|
-
const result = git(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
29529
|
-
const common = result.stdout.trim();
|
|
29530
|
-
return result.ok && common !== "" ? dirname14(realPath(common)) : null;
|
|
29531
|
-
}
|
|
29532
|
-
function isUnder(dir, root) {
|
|
29533
|
-
return dir === root || dir.startsWith(root.endsWith(sep3) ? root : `${root}${sep3}`);
|
|
29534
|
-
}
|
|
29535
|
-
function findNodeModules(checkout, git) {
|
|
29536
|
-
const start = realPath(checkout);
|
|
29537
|
-
const stop = walkStop(git);
|
|
29538
|
-
const top = stop !== null && isUnder(start, stop) ? stop : start;
|
|
29539
|
-
for (let dir = start;; dir = dirname14(dir)) {
|
|
29540
|
-
const modules = join36(dir, NODE_MODULES);
|
|
29541
|
-
if (existsSync35(join36(modules, ...TSC_BIN)))
|
|
29542
|
-
return modules;
|
|
29543
|
-
if (dir === top || dirname14(dir) === dir)
|
|
29544
|
-
return null;
|
|
29545
|
-
}
|
|
29546
|
-
}
|
|
29547
|
-
function scratchTsconfig(tree, files, modules) {
|
|
29548
|
-
const root = resolve22(tree);
|
|
29549
|
-
return `${JSON.stringify({
|
|
29550
|
-
extends: join36(root, "tsconfig.json"),
|
|
29551
|
-
compilerOptions: { typeRoots: [join36(resolve22(modules), "@types")] },
|
|
29552
|
-
files: files.map((file) => join36(root, file)),
|
|
29553
|
-
include: []
|
|
29554
|
-
}, null, 2)}
|
|
29555
|
-
`;
|
|
29556
|
-
}
|
|
29557
|
-
function parseTscOutput(stdout, cwd) {
|
|
29558
|
-
const errors = [];
|
|
29559
|
-
for (const line of stdout.split(`
|
|
29560
|
-
`)) {
|
|
29561
|
-
const atFile = FILE_ERROR.exec(line);
|
|
29562
|
-
const global = atFile === null ? GLOBAL_ERROR.exec(line) : null;
|
|
29563
|
-
const last = errors[errors.length - 1];
|
|
29564
|
-
if (atFile !== null) {
|
|
29565
|
-
const [, file = "", row = "0", column = "0", code = "", message = ""] = atFile;
|
|
29566
|
-
errors.push({ file: relative11(cwd, resolve22(cwd, file)), line: Number(row), column: Number(column), code, message });
|
|
29567
|
-
} else if (global !== null) {
|
|
29568
|
-
errors.push({ file: null, line: 0, column: 0, code: global[1] ?? "", message: global[2] ?? "" });
|
|
29569
|
-
} else if (line.startsWith(" ") && last !== undefined) {
|
|
29570
|
-
errors[errors.length - 1] = { ...last, message: `${last.message}
|
|
29571
|
-
${line.trimEnd()}` };
|
|
29572
|
-
}
|
|
29573
|
-
}
|
|
29574
|
-
return errors;
|
|
29575
|
-
}
|
|
29576
|
-
function identity(error) {
|
|
29577
|
-
return `${error.code} ${error.message}`;
|
|
29578
|
-
}
|
|
29579
|
-
function newErrors(head, base) {
|
|
29580
|
-
const left = new Map;
|
|
29581
|
-
for (const error of base) {
|
|
29582
|
-
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
29583
|
-
left.set(key, (left.get(key) ?? 0) + 1);
|
|
29584
|
-
}
|
|
29585
|
-
return head.filter((error) => {
|
|
29586
|
-
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
29587
|
-
const count = left.get(key) ?? 0;
|
|
29588
|
-
if (count === 0)
|
|
29589
|
-
return true;
|
|
29590
|
-
left.set(key, count - 1);
|
|
29591
|
-
return false;
|
|
29592
|
-
});
|
|
29593
|
-
}
|
|
29594
|
-
function errorLine2(error) {
|
|
29595
|
-
const first = error.message.split(`
|
|
29596
|
-
`)[0] ?? "";
|
|
29597
|
-
return `${error.file ?? "(no file)"}:${error.line}:${error.column} ${error.code} ${first}`;
|
|
29598
|
-
}
|
|
29599
|
-
function listed(errors) {
|
|
29600
|
-
const shown2 = errors.slice(0, ERRORS_LISTED2).map(errorLine2);
|
|
29601
|
-
return errors.length > ERRORS_LISTED2 ? [...shown2, `...and ${errors.length - ERRORS_LISTED2} more`] : shown2;
|
|
29602
|
-
}
|
|
29603
|
-
function typeBlockerText(label, base, errors) {
|
|
29604
|
-
const files = [...new Set(errors.map((error) => error.file ?? ""))];
|
|
29605
|
-
const shown2 = listed(errors).join("; ").replace(/\.$/u, "");
|
|
29606
|
-
return `The runner's ${label} found type errors in the task's test files that ${base} did not hold. ` + `New errors: ${shown2}. ` + `Fix them, then check ${files.join(" ")} with tsc ${TSC_FLAGS.join(" ")} -p over a tsconfig outside the checkout ` + "that extends its tsconfig.json by absolute path and lists them under files.";
|
|
29607
|
-
}
|
|
29608
|
-
function brokenBlockerText(label, run, said) {
|
|
29609
|
-
return `The runner's ${label} could not type-check the task's test files: tsc exited ${run.exitCode}${said === undefined ? " and printed no error." : `: ${said}`}`;
|
|
29610
|
-
}
|
|
29611
|
-
function configError(errors, scratch, cwd) {
|
|
29612
|
-
const config = relative11(cwd, scratch);
|
|
29613
|
-
return errors.find((error) => error.file === null || error.file === config);
|
|
29614
|
-
}
|
|
29615
|
-
function firstLine7(run) {
|
|
29616
|
-
return `${run.stdout}
|
|
29617
|
-
${run.stderr}`.split(`
|
|
29618
|
-
`).map((line) => line.trim()).find((line) => line !== "");
|
|
29619
|
-
}
|
|
29620
|
-
async function checkTree(input, modules, tree, files) {
|
|
29621
|
-
const scratchDir = mkdtempSync(join36(tmpdir(), "rafa-type-step-"));
|
|
29622
|
-
const scratch = join36(scratchDir, SCRATCH_NAME);
|
|
29623
|
-
try {
|
|
29624
|
-
writeFileSync16(scratch, scratchTsconfig(tree, files, modules), "utf8");
|
|
29625
|
-
const tsc = join36(modules, ...TSC_BIN);
|
|
29626
|
-
let run;
|
|
29627
|
-
try {
|
|
29628
|
-
run = await (input.runTypes ?? runTsc)({ cwd: tree, argv: [tsc, "-p", scratch, ...TSC_FLAGS] });
|
|
29629
|
-
} catch (error) {
|
|
29630
|
-
return { kind: "threw", why: messageOf(error) };
|
|
29631
|
-
}
|
|
29632
|
-
if (run.exitCode === input.stopCode || input.isInterrupted?.() === true)
|
|
29633
|
-
return { kind: "interrupted" };
|
|
29634
|
-
const errors = parseTscOutput(run.stdout, tree);
|
|
29635
|
-
const config = configError(errors, scratch, tree);
|
|
29636
|
-
if (config !== undefined)
|
|
29637
|
-
return { kind: "broken", run, said: `error ${config.code}: ${config.message.split(`
|
|
29638
|
-
`)[0] ?? ""}` };
|
|
29639
|
-
if (run.exitCode !== 0 && errors.length === 0)
|
|
29640
|
-
return { kind: "broken", run, said: firstLine7(run) };
|
|
29641
|
-
const wanted = new Set(files);
|
|
29642
|
-
return { kind: "read", errors: errors.filter((error) => error.file !== null && wanted.has(error.file)) };
|
|
29643
|
-
} finally {
|
|
29644
|
-
rmSync5(scratchDir, { recursive: true, force: true });
|
|
29645
|
-
}
|
|
29646
|
-
}
|
|
29647
|
-
async function inBaseTree(input, label, modules, check) {
|
|
29648
|
-
const parent = mkdtempSync(join36(tmpdir(), "rafa-type-base-"));
|
|
29649
|
-
const tree = join36(parent, "tree");
|
|
29650
|
-
try {
|
|
29651
|
-
const added = input.git(["worktree", "add", "--detach", tree, input.base]);
|
|
29652
|
-
if (!added.ok) {
|
|
29653
|
-
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check out ${input.base} (${gitSaid(added) || "nothing said"}); it reads no error as new.`);
|
|
29654
|
-
return null;
|
|
29655
|
-
}
|
|
29656
|
-
if (existsSync35(modules) && !existsSync35(join36(tree, NODE_MODULES)))
|
|
29657
|
-
symlinkSync2(modules, join36(tree, NODE_MODULES));
|
|
29658
|
-
if (!existsSync35(join36(tree, "tsconfig.json")))
|
|
29659
|
-
return { kind: "read", errors: [] };
|
|
29660
|
-
return await check(tree);
|
|
29661
|
-
} finally {
|
|
29662
|
-
if (existsSync35(tree)) {
|
|
29663
|
-
const removed = input.git(["worktree", "remove", "--force", tree]);
|
|
29664
|
-
if (!removed.ok)
|
|
29665
|
-
activeOutput().warn(`\u26A0\uFE0F The ${label} could not remove its worktree ${tree} (${gitSaid(removed) || "nothing said"}).`);
|
|
29666
|
-
}
|
|
29667
|
-
rmSync5(parent, { recursive: true, force: true });
|
|
29668
|
-
}
|
|
29669
|
-
}
|
|
29670
|
-
async function baseErrors(input, label, modules, files) {
|
|
29671
|
-
const pairs = files.filter((file) => file.base !== null);
|
|
29672
|
-
if (pairs.length === 0)
|
|
29673
|
-
return { errors: [] };
|
|
29674
|
-
const run = await inBaseTree(input, label, modules, (tree) => checkTree(input, modules, tree, pairs.map((file) => file.base)));
|
|
29675
|
-
if (run === null)
|
|
29676
|
-
return { outcome: GREEN };
|
|
29677
|
-
if (run.kind === "interrupted")
|
|
29678
|
-
return { outcome: interrupted(label) };
|
|
29679
|
-
if (run.kind !== "read") {
|
|
29680
|
-
const why = run.kind === "threw" ? run.why : brokenBlockerText(label, run.run, run.said);
|
|
29681
|
-
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check the files at ${input.base} (${why}); it reads no error as new.`);
|
|
29682
|
-
return { outcome: GREEN };
|
|
29683
|
-
}
|
|
29684
|
-
const headOf = new Map(pairs.map((file) => [file.base, file.head]));
|
|
29685
|
-
return { errors: run.errors.map((error) => ({ ...error, file: headOf.get(error.file ?? "") ?? error.file })) };
|
|
29686
|
-
}
|
|
29687
|
-
function interrupted(label) {
|
|
29688
|
-
activeOutput().info(`\u23F9 The ${label} was interrupted by SIGINT: read as a stop, not as errors.`);
|
|
29689
|
-
return INTERRUPTED;
|
|
29690
|
-
}
|
|
29691
|
-
function filesToCheck(input, label) {
|
|
29692
|
-
if (!existsSync35(join36(input.checkout, "tsconfig.json"))) {
|
|
29693
|
-
activeOutput().info(`\uD83D\uDD0E ${label}: no tsconfig.json at the checkout root; nothing to type-check.`);
|
|
29694
|
-
return null;
|
|
29695
|
-
}
|
|
29696
|
-
const files = readTypeFiles(input.git, input.base);
|
|
29697
|
-
if (files === null)
|
|
29698
|
-
return null;
|
|
29699
|
-
if (files.length > 0)
|
|
29700
|
-
return files;
|
|
29701
|
-
activeOutput().info(`\uD83D\uDD0E ${label}: the task changed no ${TEST_FILE_SUFFIX} file; nothing to type-check.`);
|
|
29702
|
-
return null;
|
|
29703
|
-
}
|
|
29704
|
-
async function runTypeStep(input) {
|
|
29705
|
-
const label = typeLabel(input.task);
|
|
29706
|
-
const files = filesToCheck(input, label);
|
|
29707
|
-
if (files === null)
|
|
29708
|
-
return NOTHING_RAN2;
|
|
29709
|
-
const modules = findNodeModules(input.checkout, input.git) ?? join36(resolve22(input.checkout), NODE_MODULES);
|
|
29710
|
-
const head = await checkTree(input, modules, input.checkout, files.map((file) => file.head));
|
|
29711
|
-
if (head.kind === "threw") {
|
|
29712
|
-
activeOutput().warn(`\u26A0\uFE0F The ${label} could not run tsc (${head.why}); the run goes on without it.`);
|
|
29713
|
-
return NOTHING_RAN2;
|
|
29714
|
-
}
|
|
29715
|
-
if (head.kind === "interrupted")
|
|
29716
|
-
return interrupted(label);
|
|
29717
|
-
if (head.kind === "broken") {
|
|
29718
|
-
const blocker = brokenBlockerText(label, head.run, head.said);
|
|
29719
|
-
activeOutput().info(`\uD83D\uDD0E ${label}: ${blocker}`);
|
|
29720
|
-
return { ran: true, red: true, interrupted: false, blocker };
|
|
29721
|
-
}
|
|
29722
|
-
const ran = `\uD83D\uDD0E ${label}: tsc over ${files.length} test file(s)`;
|
|
29723
|
-
if (head.errors.length === 0) {
|
|
29724
|
-
activeOutput().info(`${ran}: no type error.`);
|
|
29725
|
-
return GREEN;
|
|
29726
|
-
}
|
|
29727
|
-
const base = await baseErrors(input, label, modules, files.filter((file) => head.errors.some((error) => error.file === file.head)));
|
|
29728
|
-
if ("outcome" in base)
|
|
29729
|
-
return base.outcome;
|
|
29730
|
-
const added = newErrors(head.errors, base.errors);
|
|
29731
|
-
activeOutput().info(`${ran}: ${head.errors.length} error(s), ${head.errors.length - added.length} already held at ${input.base}, ${added.length} not held.`);
|
|
29732
|
-
for (const line of listed(added))
|
|
29733
|
-
activeOutput().info(` ${line}`);
|
|
29734
|
-
if (added.length === 0)
|
|
29735
|
-
return GREEN;
|
|
29736
|
-
return { ran: true, red: true, interrupted: false, blocker: typeBlockerText(label, input.base, added) };
|
|
29737
|
-
}
|
|
29738
|
-
|
|
29739
30970
|
// src/start/task-step-checks.ts
|
|
29740
30971
|
function lintTask(context, seams, input) {
|
|
29741
30972
|
const isInterrupted = context.isInterrupted ?? (() => false);
|
|
@@ -29754,7 +30985,7 @@ async function taskDiffChecks(context, seams, input, result) {
|
|
|
29754
30985
|
return { lint, types: await typeCheckTask(context, seams, input) };
|
|
29755
30986
|
}
|
|
29756
30987
|
// src/start/suite-stage-step.ts
|
|
29757
|
-
import { readFileSync as
|
|
30988
|
+
import { readFileSync as readFileSync47 } from "fs";
|
|
29758
30989
|
function stageSince(trackerPath, baseline) {
|
|
29759
30990
|
const taken = readStageLedger(stageLedgerPathFor(trackerPath)).filter((entry) => entry.commit !== null);
|
|
29760
30991
|
return taken.at(-1)?.commit ?? baseline?.commit ?? null;
|
|
@@ -29810,7 +31041,7 @@ async function runStageStep(context, stage, baseline) {
|
|
|
29810
31041
|
}
|
|
29811
31042
|
async function runDueStageSteps(context, baseline) {
|
|
29812
31043
|
const ledger = readStageLedger(stageLedgerPathFor(context.trackerPath));
|
|
29813
|
-
const due = dueStages(
|
|
31044
|
+
const due = dueStages(readFileSync47(context.trackerPath, "utf8"), ledger);
|
|
29814
31045
|
const outcomes = [];
|
|
29815
31046
|
for (const stage of due) {
|
|
29816
31047
|
const outcome2 = await runStageStep(context, stage, baseline);
|
|
@@ -29842,15 +31073,15 @@ function seamsOf(context) {
|
|
|
29842
31073
|
};
|
|
29843
31074
|
}
|
|
29844
31075
|
function junitFileFor(repoRoot, sessionId, kind) {
|
|
29845
|
-
return
|
|
31076
|
+
return join40(runsDir(repoRoot), sessionId, "suite", `${kind}.junit.xml`);
|
|
29846
31077
|
}
|
|
29847
31078
|
function alwaysRunJunitFileFor(repoRoot, sessionId, kind = "task") {
|
|
29848
|
-
return
|
|
31079
|
+
return join40(runsDir(repoRoot), sessionId, "suite", `${kind}-always-run.junit.xml`);
|
|
29849
31080
|
}
|
|
29850
31081
|
function stageLedgerPathFor(trackerPath) {
|
|
29851
31082
|
const baselinePath = baselinePathFor(trackerPath);
|
|
29852
31083
|
const name2 = basename11(baselinePath).replace(BASELINE_FILE_PREFIX, STAGE_LEDGER_PREFIX);
|
|
29853
|
-
return
|
|
31084
|
+
return join40(dirname18(baselinePath), name2);
|
|
29854
31085
|
}
|
|
29855
31086
|
function isLedgerEntry(value) {
|
|
29856
31087
|
if (typeof value !== "object" || value === null)
|
|
@@ -29874,11 +31105,11 @@ function parseLedger(text) {
|
|
|
29874
31105
|
return stages;
|
|
29875
31106
|
}
|
|
29876
31107
|
function readStageLedger(path3) {
|
|
29877
|
-
if (!
|
|
31108
|
+
if (!existsSync40(path3))
|
|
29878
31109
|
return [];
|
|
29879
31110
|
let parsed2;
|
|
29880
31111
|
try {
|
|
29881
|
-
parsed2 = parseLedger(
|
|
31112
|
+
parsed2 = parseLedger(readFileSync48(path3, "utf8"));
|
|
29882
31113
|
} catch (error) {
|
|
29883
31114
|
parsed2 = messageOf(error);
|
|
29884
31115
|
}
|
|
@@ -29888,7 +31119,7 @@ function readStageLedger(path3) {
|
|
|
29888
31119
|
return [];
|
|
29889
31120
|
}
|
|
29890
31121
|
function writeStageLedger(path3, entries) {
|
|
29891
|
-
mkdirSync14(
|
|
31122
|
+
mkdirSync14(dirname18(path3), { recursive: true });
|
|
29892
31123
|
const temporary = `${path3}.${process.pid}.tmp`;
|
|
29893
31124
|
writeFileSync17(temporary, `${JSON.stringify({ version: STAGE_LEDGER_VERSION, stages: entries }, null, 2)}
|
|
29894
31125
|
`, "utf8");
|
|
@@ -30063,7 +31294,7 @@ async function ensureBaseline(context) {
|
|
|
30063
31294
|
activeOutput().warn(`\u26A0\uFE0F Could not write ${basename11(path3)} (${messageOf(error)}); the next run records the baseline again.`);
|
|
30064
31295
|
}
|
|
30065
31296
|
const outcome2 = settleStep(context, seams, settling);
|
|
30066
|
-
const covered = completeStages(
|
|
31297
|
+
const covered = completeStages(readFileSync48(context.trackerPath, "utf8"));
|
|
30067
31298
|
addToLedger(context.trackerPath, covered.map((stage) => ({ ...stage, commit, via: "baseline" })));
|
|
30068
31299
|
return { baseline, step: outcome2.step, interrupted: false };
|
|
30069
31300
|
}
|
|
@@ -30202,10 +31433,17 @@ function createRunSuiteSteps(options) {
|
|
|
30202
31433
|
...options.seams === undefined ? {} : { seams: options.seams },
|
|
30203
31434
|
...options.isInterrupted === undefined ? {} : { isInterrupted: options.isInterrupted }
|
|
30204
31435
|
};
|
|
31436
|
+
let signalled = false;
|
|
31437
|
+
const goesOnNoting = (outcome2) => {
|
|
31438
|
+
if (outcome2?.interrupted === true)
|
|
31439
|
+
signalled = true;
|
|
31440
|
+
return goesOn(outcome2);
|
|
31441
|
+
};
|
|
30205
31442
|
let held = null;
|
|
30206
31443
|
const baseline = () => {
|
|
30207
31444
|
held ??= guarded("suite baseline", () => calls.ensureBaseline(context)).then((outcome2) => {
|
|
30208
31445
|
if (outcome2?.interrupted === true) {
|
|
31446
|
+
signalled = true;
|
|
30209
31447
|
announceInterrupted();
|
|
30210
31448
|
return "interrupted";
|
|
30211
31449
|
}
|
|
@@ -30217,8 +31455,10 @@ function createRunSuiteSteps(options) {
|
|
|
30217
31455
|
return held;
|
|
30218
31456
|
};
|
|
30219
31457
|
let repairSent = false;
|
|
31458
|
+
let lastPreWrapUp = null;
|
|
30220
31459
|
const preWrapUp = async (known) => {
|
|
30221
31460
|
const outcome2 = await guarded("pre-wrap-up step", () => calls.runPreWrapUpStep(context, known));
|
|
31461
|
+
lastPreWrapUp = outcome2;
|
|
30222
31462
|
if (insertedRepair(outcome2) && !repairSent) {
|
|
30223
31463
|
repairSent = true;
|
|
30224
31464
|
announceRepair();
|
|
@@ -30228,7 +31468,7 @@ function createRunSuiteSteps(options) {
|
|
|
30228
31468
|
announceRedAgain();
|
|
30229
31469
|
return "stop";
|
|
30230
31470
|
}
|
|
30231
|
-
return answerOf(
|
|
31471
|
+
return answerOf(goesOnNoting(outcome2));
|
|
30232
31472
|
};
|
|
30233
31473
|
const beforeSession = async (taskInfo) => {
|
|
30234
31474
|
const known = await baseline();
|
|
@@ -30241,7 +31481,7 @@ function createRunSuiteSteps(options) {
|
|
|
30241
31481
|
if (taskInfo.status === "blocked")
|
|
30242
31482
|
return "go-on";
|
|
30243
31483
|
const stages = await guarded("stage steps", () => calls.runDueStageSteps(context, known));
|
|
30244
|
-
return answerOf((stages ?? []).every((outcome2) =>
|
|
31484
|
+
return answerOf((stages ?? []).every((outcome2) => goesOnNoting(outcome2)));
|
|
30245
31485
|
};
|
|
30246
31486
|
const afterTask = async (taskInfo, base) => {
|
|
30247
31487
|
const known = await baseline();
|
|
@@ -30251,9 +31491,9 @@ function createRunSuiteSteps(options) {
|
|
|
30251
31491
|
return true;
|
|
30252
31492
|
const { text, declaration } = parseTaskDeclaration(taskInfo.task);
|
|
30253
31493
|
const input = { baseline: known, base, declared: readTestScope(declaration), task: text };
|
|
30254
|
-
return
|
|
31494
|
+
return goesOnNoting(await guarded("task step", () => calls.runTaskStep(context, input)));
|
|
30255
31495
|
};
|
|
30256
|
-
return { beforeSession, afterTask };
|
|
31496
|
+
return { beforeSession, afterTask, stoppedOnSignal: () => signalled, lastPreWrapUp: () => lastPreWrapUp };
|
|
30257
31497
|
}
|
|
30258
31498
|
|
|
30259
31499
|
// src/start/triage.ts
|
|
@@ -30400,12 +31640,12 @@ function inheritedFailureOf(bug, failures) {
|
|
|
30400
31640
|
// src/triage/issue-text.ts
|
|
30401
31641
|
var TITLE_MAX_LENGTH = 120;
|
|
30402
31642
|
var TITLE_CUT = "...";
|
|
30403
|
-
var
|
|
31643
|
+
var MIN_FENCE_LENGTH2 = 3;
|
|
30404
31644
|
var ISSUE_OPENING = "An out-of-scope bug a rafa task session reported. The loop filed it and" + " dispatches no task for it: a plan that wants it fixed declares a task.";
|
|
30405
31645
|
var COMMENT_OPENING = "Reported again by a rafa task session.";
|
|
30406
|
-
function
|
|
31646
|
+
function fenced2(value) {
|
|
30407
31647
|
const runs = Array.from(value.matchAll(/`+/g), (run) => run[0].length);
|
|
30408
|
-
const fence = "`".repeat(Math.max(
|
|
31648
|
+
const fence = "`".repeat(Math.max(MIN_FENCE_LENGTH2, ...runs.map((length) => length + 1)));
|
|
30409
31649
|
const body = value.endsWith(`
|
|
30410
31650
|
`) ? value : `${value}
|
|
30411
31651
|
`;
|
|
@@ -30445,8 +31685,8 @@ function supersedesSection(closed, redact) {
|
|
|
30445
31685
|
|
|
30446
31686
|
${redact(sentence)}`;
|
|
30447
31687
|
}
|
|
30448
|
-
function section(heading, value,
|
|
30449
|
-
const shown2 = value === null ?
|
|
31688
|
+
function section(heading, value, absent3, redact) {
|
|
31689
|
+
const shown2 = value === null ? absent3 : fenced2(redact(value));
|
|
30450
31690
|
return `## ${heading}
|
|
30451
31691
|
|
|
30452
31692
|
${shown2}`;
|
|
@@ -30471,7 +31711,7 @@ function issueText(opening, values, redact) {
|
|
|
30471
31711
|
|
|
30472
31712
|
// src/triage/local-paths.ts
|
|
30473
31713
|
import { realpathSync as realpathSync6 } from "fs";
|
|
30474
|
-
import { resolve as
|
|
31714
|
+
import { resolve as resolve24 } from "path";
|
|
30475
31715
|
var HOME_MARKER = "[redacted: HOME]";
|
|
30476
31716
|
var SEGMENT = "[^/\\s'\"`()<>:,;]+";
|
|
30477
31717
|
var HOME_PATTERN = `(?:/${SEGMENT})*?/(?:Users|home)/${SEGMENT}`;
|
|
@@ -30482,7 +31722,7 @@ function literalPattern(text) {
|
|
|
30482
31722
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30483
31723
|
}
|
|
30484
31724
|
function forms(path3) {
|
|
30485
|
-
const given =
|
|
31725
|
+
const given = resolve24(path3);
|
|
30486
31726
|
try {
|
|
30487
31727
|
const real = realpathSync6(given);
|
|
30488
31728
|
return real === given ? [given] : [given, real];
|
|
@@ -30515,11 +31755,11 @@ function localPathRedactor(repoRoot, home) {
|
|
|
30515
31755
|
|
|
30516
31756
|
// src/triage/triage.ts
|
|
30517
31757
|
import { homedir as homedir8 } from "os";
|
|
30518
|
-
import { join as
|
|
31758
|
+
import { join as join41 } from "path";
|
|
30519
31759
|
|
|
30520
31760
|
// src/effort/store/tracker-refs.ts
|
|
30521
|
-
import { randomUUID as
|
|
30522
|
-
import { existsSync as
|
|
31761
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
31762
|
+
import { existsSync as existsSync41 } from "fs";
|
|
30523
31763
|
var SELECT_HELD2 = `
|
|
30524
31764
|
SELECT seq, tracker_ref FROM findings
|
|
30525
31765
|
WHERE session_id = ? AND artifact = ?
|
|
@@ -30657,7 +31897,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
30657
31897
|
const path3 = sqliteStorePath(repoRoot);
|
|
30658
31898
|
const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
|
|
30659
31899
|
const collectedAt = (seams.now ?? (() => new Date))().toISOString();
|
|
30660
|
-
const newId = seams.newId ??
|
|
31900
|
+
const newId = seams.newId ?? randomUUID10;
|
|
30661
31901
|
const insertValues = () => [
|
|
30662
31902
|
newId(),
|
|
30663
31903
|
sessionId,
|
|
@@ -30668,7 +31908,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
30668
31908
|
wanted.json,
|
|
30669
31909
|
collectedAt
|
|
30670
31910
|
];
|
|
30671
|
-
const [action, stored] = withSqliteStore(path3, "write", !
|
|
31911
|
+
const [action, stored] = withSqliteStore(path3, "write", !existsSync41(path3), (db) => {
|
|
30672
31912
|
const place = db.transaction(() => placeRef(db, path3, write, wanted, superseded, insertValues));
|
|
30673
31913
|
return place.immediate();
|
|
30674
31914
|
});
|
|
@@ -30677,7 +31917,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
30677
31917
|
function readTrackerRef(repoRoot, artifact) {
|
|
30678
31918
|
checkArtifact("read", artifact);
|
|
30679
31919
|
const path3 = sqliteStorePath(repoRoot);
|
|
30680
|
-
if (!
|
|
31920
|
+
if (!existsSync41(path3))
|
|
30681
31921
|
return null;
|
|
30682
31922
|
const newest = withSqliteStore(path3, "read", false, (db) => db.query(SELECT_NEWEST).get(artifact));
|
|
30683
31923
|
return newest === null ? null : heldRefOf(path3, newest.seq, newest.tracker_ref).ref;
|
|
@@ -30806,12 +32046,12 @@ function nearestOpenBugs(report2, openBugs) {
|
|
|
30806
32046
|
}
|
|
30807
32047
|
|
|
30808
32048
|
// src/triage/triage.ts
|
|
30809
|
-
var PRIVATE_TRIAGE_DIR =
|
|
32049
|
+
var PRIVATE_TRIAGE_DIR = join41(".rafa", "triage", "private");
|
|
30810
32050
|
var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
|
|
30811
32051
|
var TRIAGE_MODULE = "unassigned";
|
|
30812
32052
|
var BLOCKER_SEPARATOR2 = "; ";
|
|
30813
32053
|
function privateTriageDir(repoRoot) {
|
|
30814
|
-
return
|
|
32054
|
+
return join41(repoRoot, PRIVATE_TRIAGE_DIR);
|
|
30815
32055
|
}
|
|
30816
32056
|
function createPrivateTriageTracker(repoRoot, options = {}) {
|
|
30817
32057
|
return createLocalTracker({
|
|
@@ -31115,16 +32355,16 @@ function foundByText(bug) {
|
|
|
31115
32355
|
return bug.score === undefined ? found : `${found} (score ${scoreText(bug.score)})`;
|
|
31116
32356
|
}
|
|
31117
32357
|
function unresolvedTracker(kind, reason) {
|
|
31118
|
-
const
|
|
32358
|
+
const refuse11 = () => Promise.reject(new Error(reason));
|
|
31119
32359
|
return Object.freeze({
|
|
31120
32360
|
kind,
|
|
31121
32361
|
capabilities: () => ({ projects: false, customFields: false, issueTypes: false }),
|
|
31122
32362
|
preflight: () => Promise.resolve({ ok: false, reason }),
|
|
31123
|
-
find:
|
|
31124
|
-
get:
|
|
31125
|
-
create:
|
|
31126
|
-
comment:
|
|
31127
|
-
transition:
|
|
32363
|
+
find: refuse11,
|
|
32364
|
+
get: refuse11,
|
|
32365
|
+
create: refuse11,
|
|
32366
|
+
comment: refuse11,
|
|
32367
|
+
transition: refuse11
|
|
31128
32368
|
});
|
|
31129
32369
|
}
|
|
31130
32370
|
function filesPublicly(bug) {
|
|
@@ -31173,9 +32413,9 @@ function describeStartTriage(result, inherited = new Map) {
|
|
|
31173
32413
|
};
|
|
31174
32414
|
}
|
|
31175
32415
|
async function resolvePublicTracker(options, redact) {
|
|
31176
|
-
const
|
|
32416
|
+
const resolve25 = options.resolve ?? resolveTracker;
|
|
31177
32417
|
try {
|
|
31178
|
-
const { tracker, attempts } = await
|
|
32418
|
+
const { tracker, attempts } = await resolve25({
|
|
31179
32419
|
config: options.config,
|
|
31180
32420
|
context: { repoRoot: options.repoRoot }
|
|
31181
32421
|
});
|
|
@@ -31690,8 +32930,8 @@ function firstLine8(text) {
|
|
|
31690
32930
|
}
|
|
31691
32931
|
|
|
31692
32932
|
// src/cleanup/runs.ts
|
|
31693
|
-
import { existsSync as
|
|
31694
|
-
import { join as
|
|
32933
|
+
import { existsSync as existsSync42, readdirSync as readdirSync15, readFileSync as readFileSync49 } from "fs";
|
|
32934
|
+
import { join as join42 } from "path";
|
|
31695
32935
|
var LIVE_STATES = new Set(["running", "paused"]);
|
|
31696
32936
|
function readRunRecords(root, seams = {}) {
|
|
31697
32937
|
const dir = runsDir(root);
|
|
@@ -31707,7 +32947,7 @@ function readRunRecords(root, seams = {}) {
|
|
|
31707
32947
|
const notes = [];
|
|
31708
32948
|
const finished = [];
|
|
31709
32949
|
for (const name2 of names2.filter((entry) => entry.endsWith(RECORD_EXTENSION)).sort()) {
|
|
31710
|
-
const file =
|
|
32950
|
+
const file = join42(dir, name2);
|
|
31711
32951
|
const read = readRecord(file);
|
|
31712
32952
|
if (typeof read === "string") {
|
|
31713
32953
|
notes.push(read);
|
|
@@ -31726,7 +32966,7 @@ function readRunRecords(root, seams = {}) {
|
|
|
31726
32966
|
}
|
|
31727
32967
|
function readRecord(file) {
|
|
31728
32968
|
try {
|
|
31729
|
-
return parseSessionRecord(
|
|
32969
|
+
return parseSessionRecord(readFileSync49(file, "utf8"), file);
|
|
31730
32970
|
} catch (error) {
|
|
31731
32971
|
return `run records: skipped ${file}: ${messageOf(error)}`;
|
|
31732
32972
|
}
|
|
@@ -31739,11 +32979,11 @@ function byStart(a, b) {
|
|
|
31739
32979
|
}
|
|
31740
32980
|
function runRow(dir, entry) {
|
|
31741
32981
|
const { record } = entry;
|
|
31742
|
-
const events =
|
|
32982
|
+
const events = join42(dir, `${record.sessionId}${EVENTS_EXTENSION}`);
|
|
31743
32983
|
return {
|
|
31744
32984
|
sessionId: record.sessionId,
|
|
31745
32985
|
path: entry.file,
|
|
31746
|
-
eventsPath:
|
|
32986
|
+
eventsPath: existsSync42(events) ? events : null,
|
|
31747
32987
|
plan: record.planStub ?? record.plan,
|
|
31748
32988
|
startedAt: record.startedAt,
|
|
31749
32989
|
ticked: true
|
|
@@ -31752,19 +32992,19 @@ function runRow(dir, entry) {
|
|
|
31752
32992
|
|
|
31753
32993
|
// src/cleanup/worktrees.ts
|
|
31754
32994
|
import { realpathSync as realpathSync8, statSync as statSync15 } from "fs";
|
|
31755
|
-
import { join as
|
|
32995
|
+
import { join as join44, sep as sep4 } from "path";
|
|
31756
32996
|
|
|
31757
32997
|
// src/pr/worktree.ts
|
|
31758
|
-
import { existsSync as
|
|
31759
|
-
import { join as
|
|
31760
|
-
var WORKTREES_SUBDIR =
|
|
32998
|
+
import { existsSync as existsSync43, realpathSync as realpathSync7 } from "fs";
|
|
32999
|
+
import { join as join43 } from "path";
|
|
33000
|
+
var WORKTREES_SUBDIR = join43(".rafa", "worktrees");
|
|
31761
33001
|
var WORKTREE_PREFIX = "pr-";
|
|
31762
33002
|
var DEFAULT_REMOTE3 = "origin";
|
|
31763
33003
|
var REFUSAL_PREFIX2 = "rafa pr triage --resolve refuses";
|
|
31764
33004
|
var FORCING_FLAG = /^(?:-f|--force(?:-with-lease|-if-includes)?(?:=.*)?|--mirror)$/;
|
|
31765
33005
|
var FORCING_REFSPEC_MARK = "+";
|
|
31766
33006
|
function resolveWorktreePath(home, number) {
|
|
31767
|
-
return
|
|
33007
|
+
return join43(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${number}`);
|
|
31768
33008
|
}
|
|
31769
33009
|
function crossRepositoryRefusal(pr) {
|
|
31770
33010
|
if (!pr.isCrossRepository)
|
|
@@ -31831,7 +33071,7 @@ function pushResolved(git, branch, remote = DEFAULT_REMOTE3) {
|
|
|
31831
33071
|
return { id: step2.id, ok: false, command: worktreeCommandLine(step2), said: refusal4 };
|
|
31832
33072
|
}
|
|
31833
33073
|
function pathForms(path3) {
|
|
31834
|
-
if (!
|
|
33074
|
+
if (!existsSync43(path3))
|
|
31835
33075
|
return [path3];
|
|
31836
33076
|
return [path3, realpathSync7(path3)];
|
|
31837
33077
|
}
|
|
@@ -31844,7 +33084,7 @@ function worktreeAt(git, path3) {
|
|
|
31844
33084
|
}
|
|
31845
33085
|
|
|
31846
33086
|
// src/cleanup/worktrees.ts
|
|
31847
|
-
var CLAUDE_WORKTREES_SUBDIR =
|
|
33087
|
+
var CLAUDE_WORKTREES_SUBDIR = join44(".claude", "worktrees");
|
|
31848
33088
|
var WORKTREE_LIST = Object.freeze(["worktree", "list", "--porcelain"]);
|
|
31849
33089
|
var GIT_DIR = Object.freeze(["rev-parse", "--absolute-git-dir"]);
|
|
31850
33090
|
var WORKTREE_STATUS = Object.freeze([
|
|
@@ -31853,7 +33093,7 @@ var WORKTREE_STATUS = Object.freeze([
|
|
|
31853
33093
|
"--porcelain=v1",
|
|
31854
33094
|
"--untracked-files=all"
|
|
31855
33095
|
]);
|
|
31856
|
-
var ADMIN_FILES = Object.freeze(["HEAD",
|
|
33096
|
+
var ADMIN_FILES = Object.freeze(["HEAD", join44("logs", "HEAD"), "index"]);
|
|
31857
33097
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1000;
|
|
31858
33098
|
var UNTRACKED_MARK = "?? ";
|
|
31859
33099
|
var LOCKED_LINE = "locked";
|
|
@@ -31890,8 +33130,8 @@ function readWorktrees(seams, read) {
|
|
|
31890
33130
|
return { ok: true, worktrees: [] };
|
|
31891
33131
|
}
|
|
31892
33132
|
const roots = [
|
|
31893
|
-
...pathForms2(seams,
|
|
31894
|
-
...pathForms2(seams,
|
|
33133
|
+
...pathForms2(seams, join44(main, CLAUDE_WORKTREES_SUBDIR)),
|
|
33134
|
+
...pathForms2(seams, join44(read.home, WORKTREES_SUBDIR)),
|
|
31895
33135
|
...pathForms2(seams, worktreeDirAt(read.projectRoot, read.worktreeDir))
|
|
31896
33136
|
];
|
|
31897
33137
|
const locks = parseLocks(listed2.stdout);
|
|
@@ -31962,7 +33202,7 @@ function lastModified(seams, git, path3) {
|
|
|
31962
33202
|
if (!gitDir.ok || adminDir === "") {
|
|
31963
33203
|
return failure4("git rev-parse --absolute-git-dir", gitDir);
|
|
31964
33204
|
}
|
|
31965
|
-
const times = [path3, ...ADMIN_FILES.map((file) =>
|
|
33205
|
+
const times = [path3, ...ADMIN_FILES.map((file) => join44(adminDir, file))].map((file) => seams.modifiedAt(file)).filter((time) => time !== null).map((time) => time.getTime());
|
|
31966
33206
|
if (times.length === 0) {
|
|
31967
33207
|
return `no modification time could be read under ${path3} or ${adminDir}`;
|
|
31968
33208
|
}
|
|
@@ -32291,6 +33531,66 @@ function firstLine9(text) {
|
|
|
32291
33531
|
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
32292
33532
|
}
|
|
32293
33533
|
|
|
33534
|
+
// src/start/forced-draft.ts
|
|
33535
|
+
var PASSED_OVER_HEADING = "## Passed-over tasks";
|
|
33536
|
+
function forcedDraftSeamsIn(dir) {
|
|
33537
|
+
return { gh: createGhRunner({ cwd: dir }), pulls: ghPullRequestsIn(dir) };
|
|
33538
|
+
}
|
|
33539
|
+
function passedOverSection(tasks) {
|
|
33540
|
+
return [
|
|
33541
|
+
PASSED_OVER_HEADING,
|
|
33542
|
+
"",
|
|
33543
|
+
"This pull request is a draft: `rafa loop start --continue --force-wrap-up` wrapped up with these tasks still open.",
|
|
33544
|
+
"",
|
|
33545
|
+
...tasks.map((task) => `- line ${task.line} (${task.strategy}): ${task.text}. ${task.reason}`)
|
|
33546
|
+
].join(`
|
|
33547
|
+
`);
|
|
33548
|
+
}
|
|
33549
|
+
function bodyWithPassedOver(body, tasks) {
|
|
33550
|
+
if (body.includes(PASSED_OVER_HEADING))
|
|
33551
|
+
return body;
|
|
33552
|
+
const kept = body.trimEnd();
|
|
33553
|
+
return kept === "" ? passedOverSection(tasks) : `${kept}
|
|
33554
|
+
|
|
33555
|
+
${passedOverSection(tasks)}`;
|
|
33556
|
+
}
|
|
33557
|
+
async function convertToDraft(number, gh) {
|
|
33558
|
+
const command = `gh pr ready ${number} --undo`;
|
|
33559
|
+
const result = await gh(["pr", "ready", String(number), "--undo"]);
|
|
33560
|
+
if (result.ok)
|
|
33561
|
+
return true;
|
|
33562
|
+
const said = result.stderr.trim() || result.stdout.trim() || "nothing said";
|
|
33563
|
+
activeOutput().warn(`\u26A0\uFE0F The forced wrap-up could not mark pull request #${number} as a draft (${said}); run ${command} by hand.`);
|
|
33564
|
+
return false;
|
|
33565
|
+
}
|
|
33566
|
+
async function writeSection(number, tasks, pulls) {
|
|
33567
|
+
try {
|
|
33568
|
+
const pull = await pulls.get(number);
|
|
33569
|
+
if (pull === null)
|
|
33570
|
+
throw new Error(`no pull request #${number}`);
|
|
33571
|
+
const body = bodyWithPassedOver(pull.body, tasks);
|
|
33572
|
+
if (body !== pull.body)
|
|
33573
|
+
await pulls.editBody(number, body);
|
|
33574
|
+
} catch (error) {
|
|
33575
|
+
activeOutput().warn(`\u26A0\uFE0F The forced wrap-up could not write the passed-over tasks into pull request #${number} (${messageOf(error)}).`);
|
|
33576
|
+
}
|
|
33577
|
+
}
|
|
33578
|
+
async function markForcedDraft(number, tasks, seams) {
|
|
33579
|
+
const drafted = await convertToDraft(number, seams.gh);
|
|
33580
|
+
await writeSection(number, tasks, seams.pulls);
|
|
33581
|
+
if (drafted)
|
|
33582
|
+
activeOutput().info(`\uD83D\uDCDD Pull request #${number} is a draft, listing the ${tasks.length} passed-over task(s) in its body.`);
|
|
33583
|
+
return drafted;
|
|
33584
|
+
}
|
|
33585
|
+
function refuseUndraftedPullRequest(number) {
|
|
33586
|
+
emitLoopEvent({ kind: "halt", reason: `forced draft refused: pull request #${number} is ready for review` });
|
|
33587
|
+
throw new LoopEnd(DECISION_STOP_EXIT, [
|
|
33588
|
+
`\u26D4 The forced wrap-up's pull request #${number} could not be made a draft, so it is open ready for review with passed-over tasks left.`,
|
|
33589
|
+
` Run gh pr ready ${number} --undo by hand. The run is recorded stopped, not done, and the CI wait was skipped.`
|
|
33590
|
+
].join(`
|
|
33591
|
+
`));
|
|
33592
|
+
}
|
|
33593
|
+
|
|
32294
33594
|
// src/start/pr-retarget.ts
|
|
32295
33595
|
function retargetedLine(number, from, to) {
|
|
32296
33596
|
return `\u21AA Retargeted pull request #${String(number)} from ${from} to ${to} (pr.base).`;
|
|
@@ -32381,8 +33681,8 @@ async function carryReleaseIntoPullRequest(finish, target, seams = {}) {
|
|
|
32381
33681
|
}
|
|
32382
33682
|
|
|
32383
33683
|
// src/release/prepare.ts
|
|
32384
|
-
import { mkdirSync as mkdirSync15, readFileSync as
|
|
32385
|
-
import { dirname as
|
|
33684
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync50, writeFileSync as writeFileSync18 } from "fs";
|
|
33685
|
+
import { dirname as dirname19, join as join45, posix as posix5 } from "path";
|
|
32386
33686
|
var NO_FRAGMENT = "no release fragment";
|
|
32387
33687
|
var WHITESPACE3 = /\s+/g;
|
|
32388
33688
|
function levelFields(reading2) {
|
|
@@ -32440,7 +33740,7 @@ function readBase(input) {
|
|
|
32440
33740
|
}
|
|
32441
33741
|
function textOf3(path3) {
|
|
32442
33742
|
try {
|
|
32443
|
-
return
|
|
33743
|
+
return readFileSync50(path3, "utf8");
|
|
32444
33744
|
} catch {
|
|
32445
33745
|
return null;
|
|
32446
33746
|
}
|
|
@@ -32450,7 +33750,7 @@ function nameOf2(path3) {
|
|
|
32450
33750
|
}
|
|
32451
33751
|
function writeFragment(path3, resolved, text) {
|
|
32452
33752
|
try {
|
|
32453
|
-
mkdirSync15(
|
|
33753
|
+
mkdirSync15(dirname19(resolved), { recursive: true });
|
|
32454
33754
|
writeFileSync18(resolved, text);
|
|
32455
33755
|
return null;
|
|
32456
33756
|
} catch (error) {
|
|
@@ -32475,7 +33775,7 @@ function prepareRelease(input) {
|
|
|
32475
33775
|
const fragment = { plan: input.plan, title, level, notes: lines2.notes };
|
|
32476
33776
|
const name2 = allocateFragmentName(input.plan, read.base.waiting.map((waiting) => nameOf2(waiting.path)));
|
|
32477
33777
|
const path3 = posix5.join(input.settings.releaseFragments.replace(/\\/g, "/"), name2);
|
|
32478
|
-
const resolved =
|
|
33778
|
+
const resolved = join45(input.repoRoot, path3);
|
|
32479
33779
|
const before = textOf3(resolved);
|
|
32480
33780
|
const after = serializeFragment(fragment);
|
|
32481
33781
|
const problems = lines2.problem === null ? read.problems : [...read.problems, lines2.problem];
|
|
@@ -32495,12 +33795,12 @@ function prepareRelease(input) {
|
|
|
32495
33795
|
}
|
|
32496
33796
|
|
|
32497
33797
|
// src/release/verify.ts
|
|
32498
|
-
import { readFileSync as
|
|
33798
|
+
import { readFileSync as readFileSync51, writeFileSync as writeFileSync19 } from "fs";
|
|
32499
33799
|
import { posix as posix6 } from "path";
|
|
32500
33800
|
var STATUS_PREFIX_LENGTH = 3;
|
|
32501
33801
|
function textOf4(path3) {
|
|
32502
33802
|
try {
|
|
32503
|
-
return
|
|
33803
|
+
return readFileSync51(path3, "utf8");
|
|
32504
33804
|
} catch {
|
|
32505
33805
|
return null;
|
|
32506
33806
|
}
|
|
@@ -32526,7 +33826,7 @@ function restoreFragment(file) {
|
|
|
32526
33826
|
};
|
|
32527
33827
|
}
|
|
32528
33828
|
}
|
|
32529
|
-
function
|
|
33829
|
+
function refuse11(prepared, refusal4) {
|
|
32530
33830
|
const restore = restoreFragment(prepared.file);
|
|
32531
33831
|
const clause = restore.problem ?? `${restore.path} was restored to the text the loop wrote`;
|
|
32532
33832
|
return {
|
|
@@ -32594,20 +33894,20 @@ function checkFragment(prepared, text) {
|
|
|
32594
33894
|
function verifyRelease(prepared, context) {
|
|
32595
33895
|
const changed = changedBesideFragment(prepared, context);
|
|
32596
33896
|
if (!changed.ok)
|
|
32597
|
-
return
|
|
33897
|
+
return refuse11(prepared, changed.refusal);
|
|
32598
33898
|
if (changed.paths.length > 0)
|
|
32599
|
-
return
|
|
33899
|
+
return refuse11(prepared, otherFilesRefusal(prepared, changed.paths));
|
|
32600
33900
|
const path3 = prepared.file.path;
|
|
32601
33901
|
const text = textOf4(prepared.file.resolved);
|
|
32602
33902
|
if (text === null) {
|
|
32603
|
-
return
|
|
33903
|
+
return refuse11(prepared, {
|
|
32604
33904
|
reason: "fragment-unreadable",
|
|
32605
33905
|
because: `${path3} could not be read back after the wrap-up session`
|
|
32606
33906
|
});
|
|
32607
33907
|
}
|
|
32608
33908
|
const fragment = checkFragment(prepared, text);
|
|
32609
33909
|
if ("reason" in fragment)
|
|
32610
|
-
return
|
|
33910
|
+
return refuse11(prepared, fragment);
|
|
32611
33911
|
return { kind: "verified", path: path3, fragment, text };
|
|
32612
33912
|
}
|
|
32613
33913
|
|
|
@@ -32794,7 +34094,7 @@ async function finishRelease(input, seams = {}) {
|
|
|
32794
34094
|
}
|
|
32795
34095
|
|
|
32796
34096
|
// src/start/runner-pr.ts
|
|
32797
|
-
import { readFileSync as
|
|
34097
|
+
import { readFileSync as readFileSync52 } from "fs";
|
|
32798
34098
|
import path3 from "path";
|
|
32799
34099
|
var RELEASE_NOTES_HEADING = "## Release notes";
|
|
32800
34100
|
var UNFINISHED_WRAP_UP_LINE = "> \u26A0\uFE0F The wrap-up did not finish: no wrap-up session opened this pull request, so the loop opened it. Check that the base is merged in, the release notes were rewritten and the lessons were promoted before merging.";
|
|
@@ -32827,7 +34127,7 @@ function fragmentNotesIn(checkout, fragment) {
|
|
|
32827
34127
|
return [];
|
|
32828
34128
|
let text;
|
|
32829
34129
|
try {
|
|
32830
|
-
text =
|
|
34130
|
+
text = readFileSync52(path3.resolve(checkout, fragment), "utf8");
|
|
32831
34131
|
} catch {
|
|
32832
34132
|
return [];
|
|
32833
34133
|
}
|
|
@@ -32942,7 +34242,7 @@ function parsePromoted(output) {
|
|
|
32942
34242
|
if (!block.closed)
|
|
32943
34243
|
return { status: "unclosed", answers: [], unreadable: [] };
|
|
32944
34244
|
const answers = [];
|
|
32945
|
-
const
|
|
34245
|
+
const unreadable2 = [];
|
|
32946
34246
|
const firstLine10 = new Map;
|
|
32947
34247
|
block.body.split(`
|
|
32948
34248
|
`).forEach((text, index) => {
|
|
@@ -32951,20 +34251,20 @@ function parsePromoted(output) {
|
|
|
32951
34251
|
const line = block.span.first + 1 + index;
|
|
32952
34252
|
const reading2 = readLine2(text, line);
|
|
32953
34253
|
if ("reason" in reading2) {
|
|
32954
|
-
|
|
34254
|
+
unreadable2.push({ line, text, reason: reading2.reason });
|
|
32955
34255
|
return;
|
|
32956
34256
|
}
|
|
32957
34257
|
const { id: id2 } = reading2.answer;
|
|
32958
34258
|
const earlier = firstLine10.get(id2);
|
|
32959
34259
|
if (earlier !== undefined) {
|
|
32960
34260
|
const reason = `\`${id2}\` was already answered at line ${String(earlier)}`;
|
|
32961
|
-
|
|
34261
|
+
unreadable2.push({ line, text, reason });
|
|
32962
34262
|
return;
|
|
32963
34263
|
}
|
|
32964
34264
|
firstLine10.set(id2, line);
|
|
32965
34265
|
answers.push(reading2.answer);
|
|
32966
34266
|
});
|
|
32967
|
-
return { status: "read", answers, unreadable };
|
|
34267
|
+
return { status: "read", answers, unreadable: unreadable2 };
|
|
32968
34268
|
}
|
|
32969
34269
|
|
|
32970
34270
|
// src/start/promoted-check.ts
|
|
@@ -33212,7 +34512,7 @@ async function openPullRequestNumber(checkout, branch) {
|
|
|
33212
34512
|
return null;
|
|
33213
34513
|
}
|
|
33214
34514
|
}
|
|
33215
|
-
function
|
|
34515
|
+
function messageOf11(error) {
|
|
33216
34516
|
return error instanceof Error ? error.message : String(error);
|
|
33217
34517
|
}
|
|
33218
34518
|
function wrapUpAdapter(learning) {
|
|
@@ -33233,7 +34533,7 @@ async function lessonsToPromote(learning) {
|
|
|
33233
34533
|
minConfidence: learning.promoteMinConfidence
|
|
33234
34534
|
});
|
|
33235
34535
|
} catch (error) {
|
|
33236
|
-
activeOutput().warn(` The wrap-up lists no lesson to promote: the \`${learning.kind}\` learning adapter answered no blessed set: ${
|
|
34536
|
+
activeOutput().warn(` The wrap-up lists no lesson to promote: the \`${learning.kind}\` learning adapter answered no blessed set: ${messageOf11(error)}`);
|
|
33237
34537
|
return [];
|
|
33238
34538
|
}
|
|
33239
34539
|
}
|
|
@@ -33343,9 +34643,11 @@ async function runWrapUp(input) {
|
|
|
33343
34643
|
ciTimeoutMin,
|
|
33344
34644
|
ciAttempts
|
|
33345
34645
|
} = input;
|
|
34646
|
+
const passedOver = input.passedOver ?? [];
|
|
33346
34647
|
session.wrapUpStarted();
|
|
33347
|
-
activeOutput().info(`
|
|
33348
|
-
\u2705 All tasks completed!`
|
|
34648
|
+
activeOutput().info(passedOver.length === 0 ? `
|
|
34649
|
+
\u2705 All tasks completed!` : `
|
|
34650
|
+
\u26A0\uFE0F Wrapping up with ${passedOver.length} passed-over task(s) left open (--force-wrap-up): the pull request becomes a draft listing them.`);
|
|
33349
34651
|
activeOutput().info("\uD83E\uDDF9 Wrap-up session starting: promote the listed lessons, sync with main, then commit, push and open the PR.");
|
|
33350
34652
|
activeOutput().info(" This is one full Claude session with no intermediate output \u2014 expect several quiet minutes. Interrupting it skips the push and PR; if that happens, run again to retry just this stage.");
|
|
33351
34653
|
emitLoopEvent({ kind: "wrap-up", phase: "fragment" });
|
|
@@ -33371,15 +34673,23 @@ async function runWrapUp(input) {
|
|
|
33371
34673
|
}
|
|
33372
34674
|
emitLoopEvent({ kind: "wrap-up", phase: "release" });
|
|
33373
34675
|
const finish = await finishRelease({ repoRoot: checkout, branch: expected2.branch, settings, preparation: release }, { readProvider });
|
|
34676
|
+
if (passedOver.length > 0 && readProvider().provider === "none") {
|
|
34677
|
+
activeOutput().warn("\u26A0\uFE0F pr.provider is none: the forced wrap-up has no pull request to mark as a draft.");
|
|
34678
|
+
}
|
|
33374
34679
|
if (readProvider().provider !== "none") {
|
|
33375
34680
|
const delivery = await deliverPullRequest({ branch: expected2.branch, retries: settings.loopWrapUpRetries, previousMessage: finalMessage }, deliverySeamsIn({ ...input, base, fragment: finish.fragment }));
|
|
33376
|
-
|
|
34681
|
+
const drafted = delivery.kind !== "delivered" || passedOver.length === 0 || await markForcedDraft(delivery.pull.number, passedOver, forcedDraftSeamsIn(checkout));
|
|
34682
|
+
if (drafted) {
|
|
34683
|
+
await emitPullRequestEvent(expected2.branch, delivery.kind === "delivered" ? delivery.pull.number : null, lookup);
|
|
34684
|
+
}
|
|
33377
34685
|
if (delivery.kind === "interrupted")
|
|
33378
34686
|
return;
|
|
33379
34687
|
if (delivery.kind === "blocked")
|
|
33380
34688
|
throw new CommandExit(1, delivery.message);
|
|
33381
34689
|
await carryReleaseIntoPullRequest(finish, { repoRoot: checkout, branch: expected2.branch }, { readProvider });
|
|
33382
34690
|
await retargetPullRequest(delivery.pull, base, { pulls: ghPullRequestsIn(checkout), output: activeOutput() });
|
|
34691
|
+
if (!drafted)
|
|
34692
|
+
refuseUndraftedPullRequest(delivery.pull.number);
|
|
33383
34693
|
} else {
|
|
33384
34694
|
await emitPullRequestEvent(expected2.branch, null, lookup);
|
|
33385
34695
|
}
|
|
@@ -33532,8 +34842,8 @@ function msUntil(startAt, now = new Date) {
|
|
|
33532
34842
|
return target.getTime() - now.getTime();
|
|
33533
34843
|
}
|
|
33534
34844
|
function sleepFor(ms) {
|
|
33535
|
-
return new Promise((
|
|
33536
|
-
setTimeout(
|
|
34845
|
+
return new Promise((resolve25) => {
|
|
34846
|
+
setTimeout(resolve25, ms);
|
|
33537
34847
|
});
|
|
33538
34848
|
}
|
|
33539
34849
|
async function deferUntil(startAt, now = new Date, sleep = sleepFor) {
|
|
@@ -33543,7 +34853,7 @@ async function deferUntil(startAt, now = new Date, sleep = sleepFor) {
|
|
|
33543
34853
|
}
|
|
33544
34854
|
|
|
33545
34855
|
// src/start.ts
|
|
33546
|
-
var __dirname2 = path4.dirname(
|
|
34856
|
+
var __dirname2 = path4.dirname(fileURLToPath5(import.meta.url));
|
|
33547
34857
|
var interrupted2 = false;
|
|
33548
34858
|
async function start(args, repoRoot) {
|
|
33549
34859
|
refuseDetachedRun(args);
|
|
@@ -33563,7 +34873,10 @@ async function start(args, repoRoot) {
|
|
|
33563
34873
|
}
|
|
33564
34874
|
refuseWorktreeWhileTracking(args, runConfig.config);
|
|
33565
34875
|
const { inject: injectMode, settingSources } = runConfig.config;
|
|
33566
|
-
const { startAt, plan, ciWait, ciTimeoutMin, ciAttempts, roadmap } = readRunArgs(args);
|
|
34876
|
+
const { startAt, plan, ciWait, ciTimeoutMin, ciAttempts, roadmap, retry, continueRun } = readRunArgs(args);
|
|
34877
|
+
refuseUnusableCriteria(continueRun, repoRoot, runConfig.config);
|
|
34878
|
+
const runRetries = resolveRunRetries(retry, configuredRetries(runConfig.config, continueRun.on));
|
|
34879
|
+
refuseRetryWithoutBudget(continueRun, runRetries);
|
|
33567
34880
|
if (startAt)
|
|
33568
34881
|
await deferUntil(startAt);
|
|
33569
34882
|
const planPath = resolvePlanPath(repoRoot, runConfig.config.planDir, plan);
|
|
@@ -33593,6 +34906,9 @@ async function start(args, repoRoot) {
|
|
|
33593
34906
|
setActivePlanStub(planStub2);
|
|
33594
34907
|
const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch, roadmap, checkout });
|
|
33595
34908
|
bindEventsFile(repoRoot, session.id);
|
|
34909
|
+
let decisionsAtEnd = () => {
|
|
34910
|
+
return;
|
|
34911
|
+
};
|
|
33596
34912
|
try {
|
|
33597
34913
|
const planContent = fs.readFileSync(planPath, "utf8");
|
|
33598
34914
|
const promptContent = fs.readFileSync(promptPath, "utf8");
|
|
@@ -33650,6 +34966,27 @@ async function start(args, repoRoot) {
|
|
|
33650
34966
|
planContent,
|
|
33651
34967
|
isInterrupted: () => interrupted2
|
|
33652
34968
|
});
|
|
34969
|
+
const retries = createRunRetries({
|
|
34970
|
+
retries: runRetries,
|
|
34971
|
+
isInterrupted: () => interrupted2,
|
|
34972
|
+
isCheckoutHeld: () => guardCheckout(expected2).held
|
|
34973
|
+
});
|
|
34974
|
+
const runPlace = { planPath, planStub: planStub2, branch, checkout };
|
|
34975
|
+
const decisions = createRunDecisions({
|
|
34976
|
+
continueArgs: continueRun,
|
|
34977
|
+
repoRoot,
|
|
34978
|
+
checkout,
|
|
34979
|
+
trackerPath,
|
|
34980
|
+
planPath,
|
|
34981
|
+
settings: runConfig.config,
|
|
34982
|
+
settingSources,
|
|
34983
|
+
session,
|
|
34984
|
+
retries,
|
|
34985
|
+
isInterrupted: () => interrupted2,
|
|
34986
|
+
seed: continueRun.on ? readPreviousPassOver(repoRoot, runPlace) : [],
|
|
34987
|
+
previousNeeded: continueRun.directive === null ? null : readPreviousDecisionNeeded(repoRoot, runPlace)
|
|
34988
|
+
});
|
|
34989
|
+
decisionsAtEnd = decisions.atRunEnd;
|
|
33653
34990
|
if (!fs.existsSync(trackerPath)) {
|
|
33654
34991
|
activeOutput().info(`\uD83D\uDCCB Creating new plan tracker at ${path4.basename(trackerPath)}...`);
|
|
33655
34992
|
fs.copyFileSync(planPath, trackerPath);
|
|
@@ -33667,7 +35004,10 @@ async function start(args, repoRoot) {
|
|
|
33667
35004
|
if (interrupted2)
|
|
33668
35005
|
break;
|
|
33669
35006
|
const trackerContent = fs.readFileSync(trackerPath, "utf8");
|
|
33670
|
-
const taskInfo = findNextTask(trackerContent);
|
|
35007
|
+
const taskInfo = findNextTask(trackerContent, { skipLines: decisions.skipLines(trackerContent) });
|
|
35008
|
+
const passedOver = taskInfo ? [] : decisions.atPlanEnd(trackerContent);
|
|
35009
|
+
if (await decisions.atFirstTask(taskInfo, trackerContent))
|
|
35010
|
+
continue;
|
|
33671
35011
|
const moved = taskInfo ? haltIfCheckoutMoved({ expected: expected2, trackerPath, taskInfo }) : haltIfWrapUpMoved({ expected: expected2, before: "dispatch" });
|
|
33672
35012
|
if (moved) {
|
|
33673
35013
|
emitLoopEvent({ kind: "halt", reason: "checkout moved" });
|
|
@@ -33675,8 +35015,12 @@ async function start(args, repoRoot) {
|
|
|
33675
35015
|
}
|
|
33676
35016
|
if (!taskInfo)
|
|
33677
35017
|
emitLoopEvent({ kind: "wrap-up", phase: "tests" });
|
|
33678
|
-
const suiteGate = await suiteSteps.beforeSession(taskInfo);
|
|
35018
|
+
const suiteGate = decisions.gateForcedWrapUp(await suiteSteps.beforeSession(taskInfo), passedOver, suiteSteps.lastPreWrapUp());
|
|
33679
35019
|
if (suiteGate === "stop") {
|
|
35020
|
+
if (!suiteSteps.stoppedOnSignal() && retries.retry("suite step red", null))
|
|
35021
|
+
continue;
|
|
35022
|
+
if (!suiteSteps.stoppedOnSignal() && await decisions.atStop({ kind: "suite-red" }))
|
|
35023
|
+
continue;
|
|
33680
35024
|
emitLoopEvent({ kind: "halt", reason: "suite step red" });
|
|
33681
35025
|
return;
|
|
33682
35026
|
}
|
|
@@ -33701,12 +35045,14 @@ async function start(args, repoRoot) {
|
|
|
33701
35045
|
ciWait,
|
|
33702
35046
|
ciTimeoutMin,
|
|
33703
35047
|
ciAttempts,
|
|
33704
|
-
isInterrupted: () => interrupted2
|
|
35048
|
+
isInterrupted: () => interrupted2,
|
|
35049
|
+
passedOver
|
|
33705
35050
|
});
|
|
33706
35051
|
break;
|
|
33707
35052
|
}
|
|
33708
35053
|
const base = expected2.head;
|
|
33709
35054
|
const position = taskPosition(trackerContent, taskInfo.lineNum);
|
|
35055
|
+
const retryTask = retryTaskOf(taskInfo, trackerContent);
|
|
33710
35056
|
const startedAt = Date.now();
|
|
33711
35057
|
emitLoopEvent({ kind: "task-start", position, text: parseTaskDeclaration(taskInfo.task).text });
|
|
33712
35058
|
session.taskStarted(taskInfo);
|
|
@@ -33722,6 +35068,7 @@ async function start(args, repoRoot) {
|
|
|
33722
35068
|
knownMissing,
|
|
33723
35069
|
inherited: runStartFailures(trackerPath),
|
|
33724
35070
|
alwaysRun: readAlwaysRunFiles(createGitRunner(checkout), runConfig.config.testsAlwaysRun),
|
|
35071
|
+
typeCheck: readTypeCheckRecipe(checkout, createGitRunner(checkout)),
|
|
33725
35072
|
serving,
|
|
33726
35073
|
handout,
|
|
33727
35074
|
base,
|
|
@@ -33755,10 +35102,15 @@ async function start(args, repoRoot) {
|
|
|
33755
35102
|
}
|
|
33756
35103
|
if (exitCode !== 0) {
|
|
33757
35104
|
updateTrackerLine(trackerPath, taskInfo.lineNum, "blocked");
|
|
35105
|
+
await storeReport("failed");
|
|
35106
|
+
const failed3 = { kind: "task-blocked", position, reason: `session exited ${exitCode}` };
|
|
35107
|
+
if (retries.retry(`session exited ${exitCode}`, retryTask))
|
|
35108
|
+
continue;
|
|
35109
|
+
if (await decisions.atStop({ kind: "session-exit", taskInfo, retryTask, exitCode, stopEvent: failed3 }))
|
|
35110
|
+
continue;
|
|
33758
35111
|
activeOutput().error(`
|
|
33759
35112
|
\u274C Task failed (exit ${exitCode}). Marked as blocked. Run again to retry.`);
|
|
33760
|
-
emitLoopEvent(
|
|
33761
|
-
await storeReport("failed");
|
|
35113
|
+
emitLoopEvent(failed3);
|
|
33762
35114
|
return;
|
|
33763
35115
|
}
|
|
33764
35116
|
if (haltIfCheckoutMoved({ expected: expected2, trackerPath, taskInfo, before: "commit" })) {
|
|
@@ -33776,9 +35128,16 @@ async function start(args, repoRoot) {
|
|
|
33776
35128
|
expected2 = advanceExpectation(expected2, finished.attempt);
|
|
33777
35129
|
const stored = await storeReport(finished.outcome);
|
|
33778
35130
|
if (finished.outcome !== "done") {
|
|
33779
|
-
|
|
35131
|
+
const held = { kind: "task-blocked", position, reason: finished.holds[0] ?? "held by its report" };
|
|
35132
|
+
if (heldOnNothingLeftBehind(finished) && retries.retry("left neither a report nor a commit", retryTask))
|
|
35133
|
+
continue;
|
|
35134
|
+
if (await decisions.atStop({ kind: "clean-exit", taskInfo, retryTask, finished, stopEvent: held }))
|
|
35135
|
+
continue;
|
|
35136
|
+
emitLoopEvent(held);
|
|
33780
35137
|
return;
|
|
33781
35138
|
}
|
|
35139
|
+
decisions.taskDone(taskInfo);
|
|
35140
|
+
retries.taskDone(retryTask);
|
|
33782
35141
|
const tokens = await unlessText(async () => taskTokens(checkout, dispatch.sessionId));
|
|
33783
35142
|
emitLoopEvent({ kind: "task-done", position, durationMs: Date.now() - startedAt, tokens });
|
|
33784
35143
|
if (!stored) {
|
|
@@ -33787,14 +35146,20 @@ async function start(args, repoRoot) {
|
|
|
33787
35146
|
return;
|
|
33788
35147
|
}
|
|
33789
35148
|
if (!await suiteSteps.afterTask(taskInfo, base)) {
|
|
35149
|
+
if (!suiteSteps.stoppedOnSignal() && retries.retry("suite step red", retryTask))
|
|
35150
|
+
continue;
|
|
35151
|
+
if (!suiteSteps.stoppedOnSignal() && await decisions.atStop({ kind: "suite-red" }))
|
|
35152
|
+
continue;
|
|
33790
35153
|
emitLoopEvent({ kind: "halt", reason: "suite step red" });
|
|
33791
35154
|
return;
|
|
33792
35155
|
}
|
|
33793
35156
|
}
|
|
33794
35157
|
} catch (error) {
|
|
33795
|
-
|
|
35158
|
+
if (!(error instanceof LoopEnd))
|
|
35159
|
+
emitLoopEvent({ kind: "error", message: messageOf(error) });
|
|
33796
35160
|
throw error;
|
|
33797
35161
|
} finally {
|
|
35162
|
+
decisionsAtEnd();
|
|
33798
35163
|
unbindEventsFile();
|
|
33799
35164
|
session.end();
|
|
33800
35165
|
}
|
|
@@ -33804,7 +35169,7 @@ async function start(args, repoRoot) {
|
|
|
33804
35169
|
import fs4 from "fs";
|
|
33805
35170
|
import { homedir as homedir11 } from "os";
|
|
33806
35171
|
import path8 from "path";
|
|
33807
|
-
import { fileURLToPath as
|
|
35172
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
33808
35173
|
|
|
33809
35174
|
// src/claims/plan-claim.ts
|
|
33810
35175
|
import path5 from "path";
|
|
@@ -33944,7 +35309,7 @@ function otherBranchRefusal(attempt3, reading2, context) {
|
|
|
33944
35309
|
function claimOwnBranch(attempt3, reading2, context) {
|
|
33945
35310
|
if (reading2.state === "absent")
|
|
33946
35311
|
return { kind: "fresh" };
|
|
33947
|
-
const
|
|
35312
|
+
const unreadable2 = () => ({
|
|
33948
35313
|
kind: "answer",
|
|
33949
35314
|
answer: refused2(attempt3, attempt3.branch, null, `the claim on ${attempt3.branch} could not be read`)
|
|
33950
35315
|
});
|
|
@@ -33952,12 +35317,12 @@ function claimOwnBranch(attempt3, reading2, context) {
|
|
|
33952
35317
|
if (unnamed !== null)
|
|
33953
35318
|
return { kind: "answer", answer: unnamed };
|
|
33954
35319
|
if (reading2.state !== "found")
|
|
33955
|
-
return
|
|
35320
|
+
return unreadable2();
|
|
33956
35321
|
const weighed = weighBranchClaim(reading2, { labels: attempt3.labels ?? [], staleAfter: context.staleAfter, now: context.now });
|
|
33957
35322
|
if (weighed.state === "released" || weighed.state === "stale-claimed")
|
|
33958
35323
|
return { kind: "take", tip: reading2.tip };
|
|
33959
35324
|
if (weighed.state === "none" || weighed.state === "unreadable")
|
|
33960
|
-
return
|
|
35325
|
+
return unreadable2();
|
|
33961
35326
|
if (weighed.owner === attempt3.storeId)
|
|
33962
35327
|
return { kind: "held" };
|
|
33963
35328
|
const note = weighed.state === "stale-in-development" ? ", stale but in development, which is never taken over automatically" : "";
|
|
@@ -34414,11 +35779,11 @@ async function resolveCreateSpec(options, seams = DEFAULT_ROUTE_SEAMS) {
|
|
|
34414
35779
|
const source = readSpecSourceFlags(options.args);
|
|
34415
35780
|
if (source.request === null)
|
|
34416
35781
|
throw new CommandExit(SOURCE_REFUSAL_EXIT, usageRefusal(specsDir));
|
|
34417
|
-
const
|
|
35782
|
+
const resolve25 = seams.resolve ?? resolvePlanSpec;
|
|
34418
35783
|
const makeReadyOffer = seams.makeReadyOffer ?? createPlanReadyOffer;
|
|
34419
35784
|
const makeAlternativeOffer = seams.makeAlternativeOffer ?? createPlanBlockedOffer;
|
|
34420
35785
|
const makeRefreshOffer = seams.makeRefreshOffer ?? createPlanRefreshOffer;
|
|
34421
|
-
return await
|
|
35786
|
+
return await resolve25({
|
|
34422
35787
|
request: source.request,
|
|
34423
35788
|
refresh: source.refresh,
|
|
34424
35789
|
dryRun: source.dryRun,
|
|
@@ -34437,7 +35802,7 @@ async function resolveCreateSpec(options, seams = DEFAULT_ROUTE_SEAMS) {
|
|
|
34437
35802
|
}
|
|
34438
35803
|
|
|
34439
35804
|
// src/plan.ts
|
|
34440
|
-
var __dirname3 = path8.dirname(
|
|
35805
|
+
var __dirname3 = path8.dirname(fileURLToPath6(import.meta.url));
|
|
34441
35806
|
function argValue2(args, flag) {
|
|
34442
35807
|
const hit = args.find((a) => a.startsWith(`${flag}=`));
|
|
34443
35808
|
return hit?.slice(flag.length + 1);
|
|
@@ -34581,7 +35946,7 @@ var PLAN_PROMPT_SLOTS = [
|
|
|
34581
35946
|
"EPIC_CONTEXT",
|
|
34582
35947
|
"SPEC_CONTENT"
|
|
34583
35948
|
];
|
|
34584
|
-
var
|
|
35949
|
+
var SLOT_PATTERN3 = new RegExp(`\\{(${PLAN_PROMPT_SLOTS.join("|")})\\}`, "g");
|
|
34585
35950
|
function buildPlanPrompt(template, planFormat, specContent, stub, planDir, progressContent, routing = DEFAULT_ROUTING, skillIndex = "", epic = null) {
|
|
34586
35951
|
const values = {
|
|
34587
35952
|
PLAN_FILE: planFilePath(planDir, `PLAN-${stub}.md`),
|
|
@@ -34593,7 +35958,7 @@ function buildPlanPrompt(template, planFormat, specContent, stub, planDir, progr
|
|
|
34593
35958
|
EPIC_CONTEXT: formatEpicContextSection(epic),
|
|
34594
35959
|
SPEC_CONTENT: specContent
|
|
34595
35960
|
};
|
|
34596
|
-
return template.replace(
|
|
35961
|
+
return template.replace(SLOT_PATTERN3, (_slot, name2) => values[name2]);
|
|
34597
35962
|
}
|
|
34598
35963
|
function stubFromSpecPath(specPath2) {
|
|
34599
35964
|
return path8.basename(specPath2).replace(/\.md$/, "");
|
|
@@ -34666,7 +36031,7 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
34666
36031
|
const epic = await readPlanEpicContext(resolved.spec, createGhRunner({ cwd: repoRoot }));
|
|
34667
36032
|
const template = fs4.readFileSync(path8.join(__dirname3, "plan-prompt.md"), "utf8");
|
|
34668
36033
|
const planFormat = readPlanFormat(__dirname3);
|
|
34669
|
-
const skillIndex = readPlanSkillIndex(repoRoot, home, config,
|
|
36034
|
+
const skillIndex = readPlanSkillIndex(repoRoot, home, config, fileURLToPath6(import.meta.url));
|
|
34670
36035
|
const planner = registry.resolve("planner", "claude").create({
|
|
34671
36036
|
repoRoot,
|
|
34672
36037
|
planDir,
|
|
@@ -34701,4 +36066,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
34701
36066
|
}
|
|
34702
36067
|
}
|
|
34703
36068
|
|
|
34704
|
-
export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseTreePaths, untrackedLeftLine, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, commandRetrySeams, openProjectRunner, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, skippedFieldWarning, notRefreshedWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, openPhase, commandProgressFeed, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, CHANGELOG_TITLE, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, readRemoteHead,
|
|
36069
|
+
export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseTreePaths, untrackedLeftLine, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, commandRetrySeams, openProjectRunner, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, skippedFieldWarning, notRefreshedWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, openPhase, commandProgressFeed, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, CHANGELOG_TITLE, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, eventsFileOf, readEventsFrom, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, readRemoteHead, WORKTREES_SUBDIR, resolveWorktreePath, crossRepositoryRefusal, addResolveWorktree, removeResolveWorktree, pushResolved, worktreeAt, worktreeDirAt, WORKTREE_STATUS, countChanges, cleanupSteps, dryRunLines, runCleanupSteps, defaultCleanupSeams, readCleanup, cleanupCounts, createLinePrompter, openNextRelations, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, LABELS_ONLY_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter2 as lazyPrompter, ready_default, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, readBlockedIssues, renderBlockedIssues, blockerToken, addBlockerToBody, resolveCreateSpec, PLACEHOLDER_REASON, renderEpicBody, splitCriteria, criteriaToAsk, readVerifyPrompt, fenceFor, buildVerifyPrompt, parseVerifyPlan, TRIAGE_MODULE, namedSecrets, triageReport, unresolvedTracker, DISK_ROOTS_FILE_SYSTEM, gitToplevel, rootRefusal, rootCandidates, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, retargetedLine, MECHANICAL_CONFLICT_SENTENCE, start, createPlanRefsVerifier, readDoctorRefs, renderDoctorRefs, createPlanDirNames, hasPlanFor, HORIZONS, horizonOf, readListedEpics, stageOf, rankOf, readIssueFacts, readRefreshBoard, refreshProjectItems, refreshIssueItems, refreshFailedWarning, unblock_default, unblockAfterMerge, labelsLineBlockersOf, LABELS_READS, createLabelsRelations, EPIC_TYPE_LABEL, plan };
|