tldr-experts 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +222 -0
- package/README.md +2 -0
- package/dist/hooks/answer-capture.js +2 -2
- package/dist/hooks/budget-gate.js +2 -2
- package/dist/hooks/{chunk-2jb272zj.js → chunk-bbrhs055.js} +3 -0
- package/dist/hooks/{chunk-tjx8g39f.js → chunk-d0jhr5wh.js} +1 -1
- package/dist/hooks/{chunk-4frmcwp3.js → chunk-pwzy19er.js} +108 -10
- package/dist/hooks/session-start.js +4 -4
- package/dist/hooks/statusline.js +3 -3
- package/dist/tldrx.js +556 -151
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/watch/stage.md +2 -1
- package/templates/watcher.md +6 -1
- /package/dist/hooks/{chunk-6z5rmj0b.js → chunk-41k19q8t.js} +0 -0
package/dist/tldrx.js
CHANGED
|
@@ -7362,24 +7362,34 @@ function capPayload(payload, save) {
|
|
|
7362
7362
|
const where = (text, field) => save === undefined ? "the full text was not preserved and is not recoverable from this event" : save(text, field);
|
|
7363
7363
|
const named = (size, text, field) => `${String(size)} bytes exceeds the ${String(MAX_PAYLOAD_BYTES)}-byte cap — ${where(text, field)}`;
|
|
7364
7364
|
let current = payload;
|
|
7365
|
-
|
|
7366
|
-
const
|
|
7367
|
-
|
|
7365
|
+
for (const field of DROPPABLE_PROSE) {
|
|
7366
|
+
const dropped = current[field];
|
|
7367
|
+
if (typeof dropped !== "string")
|
|
7368
|
+
continue;
|
|
7369
|
+
const { [field]: _gone, ...rest } = current;
|
|
7370
|
+
current = { ...rest, [`${field}_omitted`]: named(byteSize(current), dropped, field) };
|
|
7368
7371
|
if (byteSize(current) <= MAX_PAYLOAD_BYTES)
|
|
7369
7372
|
return current;
|
|
7370
7373
|
}
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
+
for (const field of DROPPABLE_LISTS) {
|
|
7375
|
+
if (byteSize(current) <= MAX_PAYLOAD_BYTES)
|
|
7376
|
+
return current;
|
|
7377
|
+
const list = current[field];
|
|
7378
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
7379
|
+
continue;
|
|
7380
|
+
const { [field]: dropped, ...rest } = current;
|
|
7381
|
+
const already = current[`${field}_omitted`];
|
|
7374
7382
|
current = {
|
|
7375
7383
|
...rest,
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
`),
|
|
7384
|
+
[`${field}_omitted`]: list.length + (typeof already === "number" ? already : 0),
|
|
7385
|
+
[`${field}_omitted_reason`]: named(byteSize(current), dropped.map((p) => String(p)).join(`
|
|
7386
|
+
`), field)
|
|
7379
7387
|
};
|
|
7380
7388
|
}
|
|
7381
7389
|
return current;
|
|
7382
7390
|
}
|
|
7391
|
+
var DROPPABLE_LISTS = ["outputs", "after", "before", "paths"];
|
|
7392
|
+
var DROPPABLE_PROSE = ["recording_error", "detail"];
|
|
7383
7393
|
function byteSize(payload) {
|
|
7384
7394
|
return Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
7385
7395
|
}
|
|
@@ -17140,6 +17150,7 @@ import { basename as basename4 } from "node:path";
|
|
|
17140
17150
|
|
|
17141
17151
|
// src/core/run/RunStore.ts
|
|
17142
17152
|
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
|
|
17153
|
+
import { isDeepStrictEqual } from "node:util";
|
|
17143
17154
|
import { basename as basename3, join as join14 } from "node:path";
|
|
17144
17155
|
|
|
17145
17156
|
// src/hooks/lib/actor.ts
|
|
@@ -17160,11 +17171,14 @@ class RunStore {
|
|
|
17160
17171
|
events;
|
|
17161
17172
|
current;
|
|
17162
17173
|
currentBudget;
|
|
17174
|
+
loaded;
|
|
17175
|
+
cancelledUnderFlag = false;
|
|
17163
17176
|
budgetMutated = false;
|
|
17164
17177
|
constructor(runDir, run, budget, events) {
|
|
17165
17178
|
this.runDir = runDir;
|
|
17166
17179
|
this.events = events;
|
|
17167
17180
|
this.current = run;
|
|
17181
|
+
this.loaded = run;
|
|
17168
17182
|
this.currentBudget = budget;
|
|
17169
17183
|
}
|
|
17170
17184
|
static open(runDir) {
|
|
@@ -17249,6 +17263,20 @@ class RunStore {
|
|
|
17249
17263
|
get runId() {
|
|
17250
17264
|
return this.current.run;
|
|
17251
17265
|
}
|
|
17266
|
+
get cancelledUnder() {
|
|
17267
|
+
return this.cancelledUnderFlag;
|
|
17268
|
+
}
|
|
17269
|
+
static cancelledOnDisk(runDir) {
|
|
17270
|
+
const path = join14(runDir, "run.yml");
|
|
17271
|
+
if (!existsSync12(path))
|
|
17272
|
+
return false;
|
|
17273
|
+
try {
|
|
17274
|
+
const doc = parseYaml(readFileSync12(path, "utf8"));
|
|
17275
|
+
return validateRunFile(doc).ok && asRunFile(doc).cancelled !== undefined;
|
|
17276
|
+
} catch {
|
|
17277
|
+
return false;
|
|
17278
|
+
}
|
|
17279
|
+
}
|
|
17252
17280
|
cursorEntry() {
|
|
17253
17281
|
return stageAt(this.current, this.current.cursor);
|
|
17254
17282
|
}
|
|
@@ -17270,13 +17298,14 @@ class RunStore {
|
|
|
17270
17298
|
this.events.append(event2);
|
|
17271
17299
|
}
|
|
17272
17300
|
save() {
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
const
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17301
|
+
return withWorkspaceLock(workspaceRootOfRunDir(this.runDir), () => {
|
|
17302
|
+
const base = this.runToWrite();
|
|
17303
|
+
const rolled = rollUp(base.run);
|
|
17304
|
+
const runValidation = validateRunFile(rolled);
|
|
17305
|
+
if (!runValidation.ok) {
|
|
17306
|
+
const first = runValidation.issues[0];
|
|
17307
|
+
throw new RunStoreError(`refusing to write an invalid run.yml: ${first?.path ?? ""} ${first?.message ?? ""}`);
|
|
17308
|
+
}
|
|
17280
17309
|
const budget = rollUpBudget(this.ceilingsToWrite(), rolled);
|
|
17281
17310
|
const budgetValidation = validateRunBudget(budget);
|
|
17282
17311
|
if (!budgetValidation.ok) {
|
|
@@ -17284,12 +17313,56 @@ class RunStore {
|
|
|
17284
17313
|
throw new RunStoreError(`refusing to write an invalid budget.yml: ${first?.path ?? ""} ${first?.message ?? ""}`);
|
|
17285
17314
|
}
|
|
17286
17315
|
writeAtomic(join14(this.runDir, "budget.yml"), emitBudgetYaml(budget));
|
|
17287
|
-
|
|
17316
|
+
const runPath = join14(this.runDir, "run.yml");
|
|
17317
|
+
writeAtomic(runPath, emitRunYaml(rolled));
|
|
17288
17318
|
this.current = rolled;
|
|
17319
|
+
this.loaded = rolled;
|
|
17289
17320
|
this.currentBudget = budget;
|
|
17290
17321
|
this.budgetMutated = false;
|
|
17322
|
+
if (base.fallback !== null) {
|
|
17323
|
+
process.stderr.write(`tldrx: ${runPath} ${base.fallback}, so it could not be merged with: this process's copy of ` + `the run was written whole, and the version that was on disk is beside it as ${backupPathFor(runPath)}. ` + "A change another command wrote there since this process read the run (a budget raise, a " + `cancel, a rejection) may be in that backup and not in run.yml.
|
|
17324
|
+
`);
|
|
17325
|
+
}
|
|
17326
|
+
if (base.cancelledUnder) {
|
|
17327
|
+
this.cancelledUnderFlag = true;
|
|
17328
|
+
const who = rolled.cancelled;
|
|
17329
|
+
process.stderr.write(`tldrx: run ${rolled.run} was cancelled` + (who === undefined ? "" : ` by ${who.by} at ${who.at} (${who.note})`) + " while this process held it: the cancel's statuses were kept, this process's task rows were " + `recorded beside them, and nothing more may be spawned for it.
|
|
17330
|
+
`);
|
|
17331
|
+
}
|
|
17332
|
+
return { cancelledUnder: base.cancelledUnder };
|
|
17291
17333
|
});
|
|
17292
17334
|
}
|
|
17335
|
+
runToWrite() {
|
|
17336
|
+
const path = join14(this.runDir, "run.yml");
|
|
17337
|
+
if (!existsSync12(path))
|
|
17338
|
+
return { run: this.current, fallback: null, cancelledUnder: false };
|
|
17339
|
+
let doc;
|
|
17340
|
+
try {
|
|
17341
|
+
doc = parseYaml(readFileSync12(path, "utf8"));
|
|
17342
|
+
} catch (error) {
|
|
17343
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
17344
|
+
return { run: this.current, fallback: `could not be read or parsed (${detail})`, cancelledUnder: false };
|
|
17345
|
+
}
|
|
17346
|
+
const validation = validateRunFile(doc);
|
|
17347
|
+
if (!validation.ok) {
|
|
17348
|
+
const first = validation.issues[0];
|
|
17349
|
+
return {
|
|
17350
|
+
run: this.current,
|
|
17351
|
+
fallback: `does not validate (${first?.path ?? ""} ${first?.message ?? "schema error"})`,
|
|
17352
|
+
cancelledUnder: false
|
|
17353
|
+
};
|
|
17354
|
+
}
|
|
17355
|
+
const onDisk = asRunFile(doc);
|
|
17356
|
+
if (onDisk.run !== this.current.run) {
|
|
17357
|
+
throw new RunStoreError(`refusing to write ${path}: it records run ${onDisk.run}, and this store holds run ` + `${this.current.run} — a save never overwrites another run's record`);
|
|
17358
|
+
}
|
|
17359
|
+
const cancelledUnder = onDisk.cancelled !== undefined && this.loaded.cancelled === undefined && this.current.cancelled === undefined;
|
|
17360
|
+
return {
|
|
17361
|
+
run: carryChanges(onDisk, this.current, this.loaded, { cancelledWins: this.cancelledUnderFlag || cancelledUnder }),
|
|
17362
|
+
fallback: null,
|
|
17363
|
+
cancelledUnder
|
|
17364
|
+
};
|
|
17365
|
+
}
|
|
17293
17366
|
ceilingsToWrite() {
|
|
17294
17367
|
if (this.budgetMutated)
|
|
17295
17368
|
return this.currentBudget;
|
|
@@ -17337,6 +17410,41 @@ function parseStateFile(path) {
|
|
|
17337
17410
|
`));
|
|
17338
17411
|
}
|
|
17339
17412
|
}
|
|
17413
|
+
function carryChanges(onDisk, mine, loaded, rules = { cancelledWins: false }) {
|
|
17414
|
+
const top = carryLevel(onDisk, mine, loaded, rules.cancelledWins ? ["phases", "cursor"] : ["phases"]);
|
|
17415
|
+
const phases = mine.phases.map((phase) => {
|
|
17416
|
+
const theirs = onDisk.phases.find((p) => p.id === phase.id);
|
|
17417
|
+
const was = loaded.phases.find((p) => p.id === phase.id);
|
|
17418
|
+
if (theirs === undefined || was === undefined)
|
|
17419
|
+
return phase;
|
|
17420
|
+
const stages = phase.stages.map((stage2) => {
|
|
17421
|
+
const theirStage = theirs.stages.find((s) => s.id === stage2.id);
|
|
17422
|
+
const wasStage = was.stages.find((s) => s.id === stage2.id);
|
|
17423
|
+
if (theirStage === undefined || wasStage === undefined)
|
|
17424
|
+
return stage2;
|
|
17425
|
+
const kept = rules.cancelledWins && theirStage.status === "cancelled" ? ["status", "started_at", "ended_at"] : [];
|
|
17426
|
+
return carryLevel(theirStage, stage2, wasStage, kept);
|
|
17427
|
+
});
|
|
17428
|
+
return { ...carryLevel(theirs, phase, was, ["stages"]), stages };
|
|
17429
|
+
});
|
|
17430
|
+
return { ...top, phases };
|
|
17431
|
+
}
|
|
17432
|
+
function carryLevel(onDisk, mine, loaded, skip) {
|
|
17433
|
+
const out = { ...onDisk };
|
|
17434
|
+
const theirs = mine;
|
|
17435
|
+
const was = loaded;
|
|
17436
|
+
for (const key of new Set([...Object.keys(theirs), ...Object.keys(was)])) {
|
|
17437
|
+
if (skip.includes(key))
|
|
17438
|
+
continue;
|
|
17439
|
+
if (isDeepStrictEqual(theirs[key], was[key]))
|
|
17440
|
+
continue;
|
|
17441
|
+
if (theirs[key] === undefined)
|
|
17442
|
+
delete out[key];
|
|
17443
|
+
else
|
|
17444
|
+
out[key] = theirs[key];
|
|
17445
|
+
}
|
|
17446
|
+
return out;
|
|
17447
|
+
}
|
|
17340
17448
|
function rollUp(run) {
|
|
17341
17449
|
const phases = run.phases.map((phase) => {
|
|
17342
17450
|
const stages = phase.stages.map((stage2) => ({
|
|
@@ -25206,6 +25314,8 @@ function renderStagePreamble(preamble) {
|
|
|
25206
25314
|
var QUESTIONS_FILE2 = "questions.md";
|
|
25207
25315
|
var INPUTS_HEADING = "Inputs";
|
|
25208
25316
|
var PREVIOUS_ATTEMPT_HEADING = "Previous attempt";
|
|
25317
|
+
var PREVIOUS_ATTEMPT_EDIT_HEADING = "Previous attempt — edit, do not restart";
|
|
25318
|
+
var MAX_PREVIOUS_ATTEMPT_BYTES = 32 * 1024;
|
|
25209
25319
|
function buildPrompt(parts) {
|
|
25210
25320
|
return renderParts(parts).map((part) => part.text).join("");
|
|
25211
25321
|
}
|
|
@@ -29938,8 +30048,9 @@ function wideningBullets(rows) {
|
|
|
29938
30048
|
for (const row2 of rows.slice(0, MAX_WIDENING_BULLETS)) {
|
|
29939
30049
|
const named = row2.paths.slice(0, NAMED_WIDENED_PATHS).map((path) => `\`${path}\``).join(", ");
|
|
29940
30050
|
const more = row2.paths.length > NAMED_WIDENED_PATHS ? `, +${String(row2.paths.length - NAMED_WIDENED_PATHS)} more` : "";
|
|
30051
|
+
const unlisted = (row2.pathsOmitted ?? 0) > 0 ? `${row2.paths.length === 0 ? "" : ", "}${String(row2.pathsOmitted)} path(s) not listed on the event` : "";
|
|
29941
30052
|
const opening = row2.basis === "measured" ? `${row2.story}'s declared surface was under the work it did (measured)` : `${row2.story}'s surface was widened by a person (declared)`;
|
|
29942
|
-
out.push(`- ${opening}: +${named}${more} ` + `(${String(row2.before)} → ${String(row2.after)} path(s))` + `${row2.note === "" ? "" : ` — ${row2.note}`} [src: events.jsonl:${String(row2.line)}]`);
|
|
30053
|
+
out.push(`- ${opening}: +${named}${more}${unlisted} ` + `(${String(row2.before)} → ${String(row2.after)} path(s))` + `${row2.note === "" ? "" : ` — ${row2.note}`} [src: events.jsonl:${String(row2.line)}]`);
|
|
29943
30054
|
}
|
|
29944
30055
|
if (rows.length > MAX_WIDENING_BULLETS) {
|
|
29945
30056
|
const next = rows[MAX_WIDENING_BULLETS]?.line ?? 1;
|
|
@@ -34508,94 +34619,6 @@ function quoted(values) {
|
|
|
34508
34619
|
// src/core/watch/watchPrompt.ts
|
|
34509
34620
|
import { existsSync as existsSync66, readFileSync as readFileSync60 } from "node:fs";
|
|
34510
34621
|
import { join as join80 } from "node:path";
|
|
34511
|
-
var WATCH_FACT_AREAS = ["observability", "deploy", "ownership"];
|
|
34512
|
-
function featureInputs(options) {
|
|
34513
|
-
const inputs = [];
|
|
34514
|
-
for (const story of options.feature.stories) {
|
|
34515
|
-
inputs.push({ path: story.path, content: story.text });
|
|
34516
|
-
}
|
|
34517
|
-
const epicPath = `${PLAN_PHASE2}/${EPICS_DIR}/${options.feature.epicId}.md`;
|
|
34518
|
-
const epicAbs = join80(options.runDir, epicPath);
|
|
34519
|
-
if (existsSync66(epicAbs))
|
|
34520
|
-
inputs.push({ path: epicPath, content: readFileSync60(epicAbs, "utf8") });
|
|
34521
|
-
inputs.push({ path: `(git) ${options.feature.epicId} branch vs default branch`, content: renderDiffs(options.diffs) });
|
|
34522
|
-
inputs.push({ path: `(facts) area: ${WATCH_FACT_AREAS.join(", ")}`, content: renderWatchFacts(options.facts, options.feature.repos) });
|
|
34523
|
-
for (const repo of options.feature.repos) {
|
|
34524
|
-
const rel = `${PROJECT_FRAMEWORK_DIR}/map/${repo}/gotchas.md`;
|
|
34525
|
-
const abs = join80(options.root, rel);
|
|
34526
|
-
if (existsSync66(abs))
|
|
34527
|
-
inputs.push({ path: rel, content: readFileSync60(abs, "utf8") });
|
|
34528
|
-
}
|
|
34529
|
-
return inputs.slice(0, MAX_STAGE_INPUTS);
|
|
34530
|
-
}
|
|
34531
|
-
function renderWatchFacts(facts, repos) {
|
|
34532
|
-
const areas = new Set(WATCH_FACT_AREAS);
|
|
34533
|
-
const relevant = facts.filter((fact) => isLive(fact) && areas.has(fact.area) && (fact.repos.length === 0 || fact.repos.some((r) => repos.includes(r))));
|
|
34534
|
-
if (relevant.length === 0) {
|
|
34535
|
-
return `_No live fact is tagged ${WATCH_FACT_AREAS.join(" or ")} for these repos. ` + "Cite `absent:.tldrx/memory/facts.yml` rather than inventing where a signal is read._";
|
|
34536
|
-
}
|
|
34537
|
-
return relevant.map((fact) => `- [${fact.id}] ${fact.fact} (${fact.area} · ${fact.confidence})`).join(`
|
|
34538
|
-
`);
|
|
34539
|
-
}
|
|
34540
|
-
function watcherRelPath(featureId2) {
|
|
34541
|
-
return `${WATCH_PHASE}/${WATCHERS_DIR}/${featureId2}.md`;
|
|
34542
|
-
}
|
|
34543
|
-
function featureBrief(feature) {
|
|
34544
|
-
const path = watcherRelPath(feature.id);
|
|
34545
|
-
const stories = feature.stories.map((s) => s.story.id);
|
|
34546
|
-
return [
|
|
34547
|
-
`Write exactly ONE file: \`${path}\`. Do not write, edit or delete anything else.`,
|
|
34548
|
-
"",
|
|
34549
|
-
"Its YAML front matter is fixed — copy it verbatim:",
|
|
34550
|
-
"",
|
|
34551
|
-
"```yaml",
|
|
34552
|
-
"---",
|
|
34553
|
-
"version: 1",
|
|
34554
|
-
`id: ${feature.id}`,
|
|
34555
|
-
`epic: ${feature.epicId}`,
|
|
34556
|
-
`title: ${JSON.stringify(feature.title)}`,
|
|
34557
|
-
`stories: [${stories.join(", ")}]`,
|
|
34558
|
-
`repos: [${feature.repos.join(", ")}]`,
|
|
34559
|
-
"status: draft",
|
|
34560
|
-
"---",
|
|
34561
|
-
"```",
|
|
34562
|
-
"",
|
|
34563
|
-
`Then these H2 sections, in this order: ${WATCHER_SECTIONS.map((s) => `\`## ${s}\``).join(" · ")}.`,
|
|
34564
|
-
"",
|
|
34565
|
-
"- Every list item under **Signal**, **Where**, **Healthy baseline** and **Looks broken when**",
|
|
34566
|
-
" ends with a `[src: …]` token: `<repo>:<path>:<line>` for a line in the built code, `F<n>` for",
|
|
34567
|
-
" a recorded fact, or `absent:<what you looked at>` when the code emits nothing.",
|
|
34568
|
-
"- **Signal** names the log line, metric or event that is IN the diff above, at the line it is on.",
|
|
34569
|
-
" If nothing is emitted, say so with an `absent:` source and say what to instrument. Do not",
|
|
34570
|
-
" describe a signal that would be nice to have as though it exists.",
|
|
34571
|
-
"- **Query** is one fenced block, copy-pasteable in whatever place **Where** names.",
|
|
34572
|
-
" If — and ONLY if — the code emits nothing at all, so there is no place to paste a query into,",
|
|
34573
|
-
" write ONE line instead of the block, sourced like every other claim on the card:",
|
|
34574
|
-
"",
|
|
34575
|
-
" Query: none — <why nothing is queryable> [src: absent:<what you looked at>]",
|
|
34576
|
-
"",
|
|
34577
|
-
" This is CHECKED, not trusted: it is refused unless **Signal** above cites `absent:` and the",
|
|
34578
|
-
" reason's own source is `absent:` too. A card that names a real signal has somewhere to point a",
|
|
34579
|
-
" query at, so `none` on one is a shortcut and will fail the stage. An unsourced `none` is refused",
|
|
34580
|
-
" exactly like an unsourced bullet, and describing a query in prose is still refused either way.",
|
|
34581
|
-
"- **Sources** is prose: each citation above, once, with what it establishes.",
|
|
34582
|
-
"",
|
|
34583
|
-
"Owner (optional, gh #70). A Signal item may name WHO to ask about it, as `(owner: <name>)`",
|
|
34584
|
-
"placed BEFORE its `[src: …]` token — the token is still the last thing on the line:",
|
|
34585
|
-
"",
|
|
34586
|
-
" - `checkout.completed` is written on every order (owner: alice) [src: api:src/Checkout.cs:88]",
|
|
34587
|
-
"",
|
|
34588
|
-
"Write it ONLY from an `ownership` fact inlined above, and write the SAME name that fact uses.",
|
|
34589
|
-
"Put it in the front matter as `owner: <name>` when one name covers every item on the card, or on",
|
|
34590
|
-
"the individual items when they differ. Do not invent a name, do not put a repo name there (the",
|
|
34591
|
-
"framework already derives that from your citation), and leave it off entirely when no fact says.",
|
|
34592
|
-
"An `(owner: )` with nothing in it is refused — it loses the name it was trying to write.",
|
|
34593
|
-
"",
|
|
34594
|
-
"Leave `status: draft`. The framework sets it: a card is stamped `verified` only when nothing",
|
|
34595
|
-
"under **Signal** cites `absent:`. Writing `verified` yourself changes nothing and will be overwritten."
|
|
34596
|
-
].join(`
|
|
34597
|
-
`);
|
|
34598
|
-
}
|
|
34599
34622
|
|
|
34600
34623
|
// src/core/watch/itemOwner.ts
|
|
34601
34624
|
var MAX_OWNER_CHARS = 64;
|
|
@@ -34632,7 +34655,8 @@ function itemOwner(text3) {
|
|
|
34632
34655
|
}
|
|
34633
34656
|
|
|
34634
34657
|
// src/core/watch/watcherFile.ts
|
|
34635
|
-
var
|
|
34658
|
+
var NON_FILE_SOURCE_CURE = "a table, queue, dashboard or console is not a line of code, so cite the file that defines it " + "(the migration, model or config, as `<repo>:<path>:<line>`) or the `F<n>` fact that names it";
|
|
34659
|
+
var NO_SRC_TOKEN_ISSUE = `no \`[src: …]\` token — every item on a card is sourced; ${NON_FILE_SOURCE_CURE}`;
|
|
34636
34660
|
var QUERY_NONE_NOT_EARNED_ISSUE = "`Query: none` is only for a card whose `## Signal` is itself `absent:` — this one names a real signal";
|
|
34637
34661
|
var QUERY_NOT_PASTEABLE_ISSUE = "`## Query` holds no fenced block — the query has to be copy-pasteable, not described";
|
|
34638
34662
|
var SRC_SECTION2 = "Evidence ledger";
|
|
@@ -34820,12 +34844,147 @@ function setWatcherStatus(text3, status) {
|
|
|
34820
34844
|
}
|
|
34821
34845
|
return text3;
|
|
34822
34846
|
}
|
|
34847
|
+
function describeWatcherIssue(issue) {
|
|
34848
|
+
return `L${String(issue.line)}${issue.path === "" ? "" : ` ${issue.path}`}: ${issue.message}`;
|
|
34849
|
+
}
|
|
34823
34850
|
function describeWatcherIssues(issues, max = 5) {
|
|
34824
|
-
const shown = issues.slice(0, max).map((issue) => `
|
|
34851
|
+
const shown = issues.slice(0, max).map((issue) => ` ${describeWatcherIssue(issue)}`);
|
|
34825
34852
|
const rest = issues.length - shown.length;
|
|
34826
34853
|
return rest > 0 ? [...shown, ` (+${String(rest)} more)`] : shown;
|
|
34827
34854
|
}
|
|
34828
34855
|
|
|
34856
|
+
// src/core/watch/watchPrompt.ts
|
|
34857
|
+
var WATCH_FACT_AREAS = ["observability", "deploy", "ownership"];
|
|
34858
|
+
function featureInputs(options) {
|
|
34859
|
+
const inputs = [];
|
|
34860
|
+
for (const story of options.feature.stories) {
|
|
34861
|
+
inputs.push({ path: story.path, content: story.text });
|
|
34862
|
+
}
|
|
34863
|
+
const epicPath = `${PLAN_PHASE2}/${EPICS_DIR}/${options.feature.epicId}.md`;
|
|
34864
|
+
const epicAbs = join80(options.runDir, epicPath);
|
|
34865
|
+
if (existsSync66(epicAbs))
|
|
34866
|
+
inputs.push({ path: epicPath, content: readFileSync60(epicAbs, "utf8") });
|
|
34867
|
+
inputs.push({ path: `(git) ${options.feature.epicId} branch vs default branch`, content: renderDiffs(options.diffs) });
|
|
34868
|
+
inputs.push({ path: `(facts) area: ${WATCH_FACT_AREAS.join(", ")}`, content: renderWatchFacts(options.facts, options.feature.repos) });
|
|
34869
|
+
for (const repo of options.feature.repos) {
|
|
34870
|
+
const rel = `${PROJECT_FRAMEWORK_DIR}/map/${repo}/gotchas.md`;
|
|
34871
|
+
const abs = join80(options.root, rel);
|
|
34872
|
+
if (existsSync66(abs))
|
|
34873
|
+
inputs.push({ path: rel, content: readFileSync60(abs, "utf8") });
|
|
34874
|
+
}
|
|
34875
|
+
return inputs.slice(0, MAX_STAGE_INPUTS);
|
|
34876
|
+
}
|
|
34877
|
+
function renderWatchFacts(facts, repos) {
|
|
34878
|
+
const areas = new Set(WATCH_FACT_AREAS);
|
|
34879
|
+
const relevant = facts.filter((fact) => isLive(fact) && areas.has(fact.area) && (fact.repos.length === 0 || fact.repos.some((r) => repos.includes(r))));
|
|
34880
|
+
if (relevant.length === 0) {
|
|
34881
|
+
return `_No live fact is tagged ${WATCH_FACT_AREAS.join(" or ")} for these repos. ` + "Cite `absent:.tldrx/memory/facts.yml` rather than inventing where a signal is read._";
|
|
34882
|
+
}
|
|
34883
|
+
return relevant.map((fact) => `- [${fact.id}] ${fact.fact} (${fact.area} · ${fact.confidence})`).join(`
|
|
34884
|
+
`);
|
|
34885
|
+
}
|
|
34886
|
+
function watcherRelPath(featureId2) {
|
|
34887
|
+
return `${WATCH_PHASE}/${WATCHERS_DIR}/${featureId2}.md`;
|
|
34888
|
+
}
|
|
34889
|
+
function featureBrief(feature) {
|
|
34890
|
+
const path = watcherRelPath(feature.id);
|
|
34891
|
+
const stories = feature.stories.map((s) => s.story.id);
|
|
34892
|
+
return [
|
|
34893
|
+
`Write exactly ONE file: \`${path}\`. Do not write, edit or delete anything else.`,
|
|
34894
|
+
"",
|
|
34895
|
+
"Its YAML front matter is fixed — copy it verbatim:",
|
|
34896
|
+
"",
|
|
34897
|
+
"```yaml",
|
|
34898
|
+
"---",
|
|
34899
|
+
"version: 1",
|
|
34900
|
+
`id: ${feature.id}`,
|
|
34901
|
+
`epic: ${feature.epicId}`,
|
|
34902
|
+
`title: ${JSON.stringify(feature.title)}`,
|
|
34903
|
+
`stories: [${stories.join(", ")}]`,
|
|
34904
|
+
`repos: [${feature.repos.join(", ")}]`,
|
|
34905
|
+
"status: draft",
|
|
34906
|
+
"---",
|
|
34907
|
+
"```",
|
|
34908
|
+
"",
|
|
34909
|
+
`Then these H2 sections, in this order: ${WATCHER_SECTIONS.map((s) => `\`## ${s}\``).join(" · ")}.`,
|
|
34910
|
+
"",
|
|
34911
|
+
"- Every list item under **Signal**, **Where**, **Healthy baseline** and **Looks broken when**",
|
|
34912
|
+
" ends with a `[src: …]` token: `<repo>:<path>:<line>` for a line in the built code, `F<n>` for",
|
|
34913
|
+
" a recorded fact, or `absent:<what you looked at>` when the code emits nothing.",
|
|
34914
|
+
`- **Where** names a PLACE, and a place is cited like a line: ${NON_FILE_SOURCE_CURE}.`,
|
|
34915
|
+
" An item with no token at all is refused, however obvious the place. One example, for a table",
|
|
34916
|
+
" that no fact and no dashboard names yet:",
|
|
34917
|
+
"",
|
|
34918
|
+
" - PostgreSQL `leaderboard_refreshes` table, read with `psql` [src: api:db/migrations/0007_leaderboard_refreshes.sql:1]",
|
|
34919
|
+
"",
|
|
34920
|
+
"- **Signal** names the log line, metric or event that is IN the diff above, at the line it is on.",
|
|
34921
|
+
" If nothing is emitted, say so with an `absent:` source and say what to instrument. Do not",
|
|
34922
|
+
" describe a signal that would be nice to have as though it exists.",
|
|
34923
|
+
"- **Query** is one fenced block, copy-pasteable in whatever place **Where** names.",
|
|
34924
|
+
" If — and ONLY if — the code emits nothing at all, so there is no place to paste a query into,",
|
|
34925
|
+
" write ONE line instead of the block, sourced like every other claim on the card:",
|
|
34926
|
+
"",
|
|
34927
|
+
" Query: none — <why nothing is queryable> [src: absent:<what you looked at>]",
|
|
34928
|
+
"",
|
|
34929
|
+
" This is CHECKED, not trusted: it is refused unless **Signal** above cites `absent:` and the",
|
|
34930
|
+
" reason's own source is `absent:` too. A card that names a real signal has somewhere to point a",
|
|
34931
|
+
" query at, so `none` on one is a shortcut and will fail the stage. An unsourced `none` is refused",
|
|
34932
|
+
" exactly like an unsourced bullet, and describing a query in prose is still refused either way.",
|
|
34933
|
+
"- **Sources** is prose: each citation above, once, with what it establishes.",
|
|
34934
|
+
"",
|
|
34935
|
+
"Owner (optional, gh #70). A Signal item may name WHO to ask about it, as `(owner: <name>)`",
|
|
34936
|
+
"placed BEFORE its `[src: …]` token — the token is still the last thing on the line:",
|
|
34937
|
+
"",
|
|
34938
|
+
" - `checkout.completed` is written on every order (owner: alice) [src: api:src/Checkout.cs:88]",
|
|
34939
|
+
"",
|
|
34940
|
+
"Write it ONLY from an `ownership` fact inlined above, and write the SAME name that fact uses.",
|
|
34941
|
+
"Put it in the front matter as `owner: <name>` when one name covers every item on the card, or on",
|
|
34942
|
+
"the individual items when they differ. Do not invent a name, do not put a repo name there (the",
|
|
34943
|
+
"framework already derives that from your citation), and leave it off entirely when no fact says.",
|
|
34944
|
+
"An `(owner: )` with nothing in it is refused — it loses the name it was trying to write.",
|
|
34945
|
+
"",
|
|
34946
|
+
"Leave `status: draft`. The framework sets it: a card is stamped `verified` only when nothing",
|
|
34947
|
+
"under **Signal** cites `absent:`. Writing `verified` yourself changes nothing and will be overwritten."
|
|
34948
|
+
].join(`
|
|
34949
|
+
`);
|
|
34950
|
+
}
|
|
34951
|
+
function previousCard(options) {
|
|
34952
|
+
const rel = watcherRelPath(options.feature.id);
|
|
34953
|
+
const abs = join80(options.runDir, rel);
|
|
34954
|
+
if (!existsSync66(abs))
|
|
34955
|
+
return "";
|
|
34956
|
+
const text3 = readFileSync60(abs, "utf8");
|
|
34957
|
+
if (text3.trim() === "")
|
|
34958
|
+
return "";
|
|
34959
|
+
const card = parseWatcherCard(text3, options.ctx, options.feature.id);
|
|
34960
|
+
const lines = text3.split(`
|
|
34961
|
+
`);
|
|
34962
|
+
const out = [];
|
|
34963
|
+
if (card.ok) {
|
|
34964
|
+
out.push(`The previous attempt at this stage wrote \`${rel}\` and it validates. The stage is being run`, "again for another reason; keep this card as it is unless the evidence above contradicts it.");
|
|
34965
|
+
} else {
|
|
34966
|
+
out.push(`The previous attempt at this stage wrote \`${rel}\` and it was REFUSED — ` + `${String(card.issues.length)} line(s) do not validate. Each one, with the line as written:`, "");
|
|
34967
|
+
for (const issue of card.issues) {
|
|
34968
|
+
out.push(`- ${describeWatcherIssue(issue)}`);
|
|
34969
|
+
const quoted2 = issue.line > 0 ? lines[issue.line - 1] : undefined;
|
|
34970
|
+
if (quoted2 !== undefined && quoted2.trim() !== "")
|
|
34971
|
+
out.push(` > ${quoted2}`);
|
|
34972
|
+
}
|
|
34973
|
+
out.push("", "Fix what is described above. Everything else in this prompt still applies.");
|
|
34974
|
+
}
|
|
34975
|
+
out.push("", `### ${PREVIOUS_ATTEMPT_EDIT_HEADING}`, "", "This card is on disk RIGHT NOW, exactly as the last attempt left it. It is the draft you are", "being paid to fix, not history: keep every item that already carries a `[src: …]` token, cure", "the lines marked above, and write the file back at the same path. Starting from a blank page", "throws away paid-for work — and, measured, moves a refusal to a different line instead of", "curing it.", "");
|
|
34976
|
+
const budget = options.maxBytes ?? MAX_PREVIOUS_ATTEMPT_BYTES;
|
|
34977
|
+
const size = Buffer.byteLength(text3, "utf8");
|
|
34978
|
+
if (size > budget) {
|
|
34979
|
+
out.push(`_Not inlined (past the ${budget.toLocaleString("en-US")}-byte previous-attempt budget): ` + `${rel} (${size.toLocaleString("en-US")} B). It is on disk; read it before you rewrite it._`);
|
|
34980
|
+
} else {
|
|
34981
|
+
const fence = fenceFor(text3);
|
|
34982
|
+
out.push(`#### \`${rel}\``, "", fence, text3.replace(/\n$/, ""), fence);
|
|
34983
|
+
}
|
|
34984
|
+
return out.join(`
|
|
34985
|
+
`);
|
|
34986
|
+
}
|
|
34987
|
+
|
|
34829
34988
|
// src/core/watch/renderWatchHandoff.ts
|
|
34830
34989
|
var NO_STORIES_SRC = `absent:${PLAN_PHASE2}/stories`;
|
|
34831
34990
|
function renderWatchHandoff(cards, ctx) {
|
|
@@ -35050,6 +35209,7 @@ async function watchExecutor(ctx) {
|
|
|
35050
35209
|
refused: true
|
|
35051
35210
|
};
|
|
35052
35211
|
}
|
|
35212
|
+
const srcCtx = toSrcContext(loadWorkspace(ctx.root), ctx.runDir, { epicRefs: true });
|
|
35053
35213
|
let prompts = [];
|
|
35054
35214
|
if (ctx.mode !== "commit") {
|
|
35055
35215
|
const build = recordedBuild(ctx.runDir);
|
|
@@ -35068,7 +35228,7 @@ async function watchExecutor(ctx) {
|
|
|
35068
35228
|
signature: incoherent.signature
|
|
35069
35229
|
};
|
|
35070
35230
|
}
|
|
35071
|
-
prompts = features.map((feature, i) => featurePrompt(ctx, feature, diffs[i] ?? []));
|
|
35231
|
+
prompts = features.map((feature, i) => featurePrompt(ctx, feature, diffs[i] ?? [], srcCtx));
|
|
35072
35232
|
}
|
|
35073
35233
|
if (ctx.mode === "prepare")
|
|
35074
35234
|
return prepare2(ctx, features, prompts);
|
|
@@ -35109,7 +35269,6 @@ async function watchExecutor(ctx) {
|
|
|
35109
35269
|
}
|
|
35110
35270
|
}
|
|
35111
35271
|
}
|
|
35112
|
-
const srcCtx = toSrcContext(loadWorkspace(ctx.root), ctx.runDir, { epicRefs: true });
|
|
35113
35272
|
const written = [];
|
|
35114
35273
|
for (const feature of features) {
|
|
35115
35274
|
const rel = watcherRelPath(feature.id);
|
|
@@ -35196,7 +35355,7 @@ function collectResults(ctx, features) {
|
|
|
35196
35355
|
}
|
|
35197
35356
|
return { tasks, error: null };
|
|
35198
35357
|
}
|
|
35199
|
-
function featurePrompt(ctx, feature, diffs) {
|
|
35358
|
+
function featurePrompt(ctx, feature, diffs, srcCtx) {
|
|
35200
35359
|
const facts = FactsStore.loadOrEmpty(factsPath(ctx.root)).facts;
|
|
35201
35360
|
const stageMd = applyCheckContracts(readFileSync62(stageMdPath(ctx.spec.planned.id, ctx.spec.planned.source), "utf8"), {
|
|
35202
35361
|
checks: ctx.spec.planned.checks.map((check) => check.id),
|
|
@@ -35227,7 +35386,8 @@ function featurePrompt(ctx, feature, diffs) {
|
|
|
35227
35386
|
budget_usd: agentShare(ctx, 1).toFixed(2)
|
|
35228
35387
|
},
|
|
35229
35388
|
experts: bundles.experts,
|
|
35230
|
-
inputs: featureInputs({ root: ctx.root, runDir: ctx.runDir, feature, diffs, facts })
|
|
35389
|
+
inputs: featureInputs({ root: ctx.root, runDir: ctx.runDir, feature, diffs, facts }),
|
|
35390
|
+
previousAttempt: previousCard({ runDir: ctx.runDir, feature, ctx: srcCtx })
|
|
35231
35391
|
});
|
|
35232
35392
|
return replaceSection(body, "Feature", featureBrief(feature));
|
|
35233
35393
|
}
|
|
@@ -35649,6 +35809,11 @@ var MEASURED_NOTE_TAIL = " — measured off the story's own diff, the range the
|
|
|
35649
35809
|
function basisOf(payload) {
|
|
35650
35810
|
return payload.basis === "measured" ? "measured" : "declared";
|
|
35651
35811
|
}
|
|
35812
|
+
function listCount(payload, field) {
|
|
35813
|
+
const list = payload[field];
|
|
35814
|
+
const omitted = payload[`${field}_omitted`];
|
|
35815
|
+
return (Array.isArray(list) ? list.length : 0) + (typeof omitted === "number" ? omitted : 0);
|
|
35816
|
+
}
|
|
35652
35817
|
function stringList3(value) {
|
|
35653
35818
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
35654
35819
|
}
|
|
@@ -35673,8 +35838,9 @@ function wideningRows(eventsText) {
|
|
|
35673
35838
|
story: typeof payload.story === "string" ? payload.story : "?",
|
|
35674
35839
|
basis: basisOf(payload),
|
|
35675
35840
|
paths: stringList3(payload.paths),
|
|
35676
|
-
|
|
35677
|
-
|
|
35841
|
+
...typeof payload.paths_omitted === "number" && payload.paths_omitted > 0 ? { pathsOmitted: payload.paths_omitted } : {},
|
|
35842
|
+
before: listCount(payload, "before"),
|
|
35843
|
+
after: listCount(payload, "after"),
|
|
35678
35844
|
note: typeof payload.note === "string" ? payload.note : "",
|
|
35679
35845
|
line: i + 1
|
|
35680
35846
|
});
|
|
@@ -38436,6 +38602,27 @@ function dependencyHoldOfLog(log) {
|
|
|
38436
38602
|
const body = (end === -1 ? rest : rest.slice(0, end)).map((line) => line.trim()).filter((line) => line !== "");
|
|
38437
38603
|
return body.length === 1 && body[0] !== undefined ? dependencyNamedByHold(body[0]) : null;
|
|
38438
38604
|
}
|
|
38605
|
+
function dependencyHoldCure(held2) {
|
|
38606
|
+
if (dependencyIsPending(held2.status)) {
|
|
38607
|
+
return `${held2.id} \`done\` is what releases it — it is offered again by the next \`tldrx next --prepare\``;
|
|
38608
|
+
}
|
|
38609
|
+
return held2.status === "blocked" ? `${held2.id} will not land in this run as it stands — \`tldrx story reopen ${held2.id} --note "…"\` gives it its turn` : `${held2.id} is \`todo\` after its wave ran, so its developer never delivered — \`tldrx next --prepare\` offers ${held2.id} first`;
|
|
38610
|
+
}
|
|
38611
|
+
function dependencyPrepareRefusal(storyId, held2) {
|
|
38612
|
+
if (dependencyIsPending(held2.status)) {
|
|
38613
|
+
return `nothing to prepare yet: ${storyId} waits on dependency ${held2.id}, which is \`${held2.status}\`, ` + `not \`done\` — ${storyId} is left \`todo\`, not \`blocked\`; ${dependencyHoldCure(held2)}`;
|
|
38614
|
+
}
|
|
38615
|
+
return `nothing to review: ${dependencyHoldReason(held2)}, so ${storyId} has no developer turn to review and ` + `none is owed — \`tldrx next --prepare\` records ${storyId} \`blocked\` with that reason; ${dependencyHoldCure(held2)}`;
|
|
38616
|
+
}
|
|
38617
|
+
function dependencyCommitRefusal(storyId, held2, work) {
|
|
38618
|
+
return `${storyId} is not settled: ${dependencyHoldReason(held2)}, and a story does not land over a dependency ` + `that has not. Nothing is lost — the bundle stays at ${work.bundleDir}, the work stays on \`${work.branch}\` ` + `in ${work.worktree}, and ${storyId} stays \`in_progress\`; ${dependencyHoldCure(held2)}, ` + `then \`tldrx next --commit\` settles ${storyId}`;
|
|
38619
|
+
}
|
|
38620
|
+
function dependencyNextLine(storyId, held2) {
|
|
38621
|
+
if (dependencyIsPending(held2.status)) {
|
|
38622
|
+
return `nothing is next yet — ${storyId} waits on dependency ${held2.id}, which is \`${held2.status}\`, ` + `not \`done\`; ${dependencyHoldCure(held2)}`;
|
|
38623
|
+
}
|
|
38624
|
+
return `nothing is next — ${dependencyHoldReason(held2)}, so \`tldrx next --prepare\` records ${storyId} ` + `\`blocked\` with that reason and closes the stage at its gate; ${dependencyHoldCure(held2)}`;
|
|
38625
|
+
}
|
|
38439
38626
|
|
|
38440
38627
|
// src/core/facilitator/reviewerModel.ts
|
|
38441
38628
|
function resolveReviewer(layers) {
|
|
@@ -38660,6 +38847,9 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38660
38847
|
const ceilings = new Map;
|
|
38661
38848
|
const measured = new Map;
|
|
38662
38849
|
const unmetered = new Map;
|
|
38850
|
+
const open = new Map;
|
|
38851
|
+
const openStage = new Map;
|
|
38852
|
+
const lost = new Map;
|
|
38663
38853
|
const storyKeys = new Set;
|
|
38664
38854
|
const storyPhases = new Set;
|
|
38665
38855
|
let excluded = 0;
|
|
@@ -38708,6 +38898,39 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38708
38898
|
else
|
|
38709
38899
|
measured.set(turn.key, round9((measured.get(turn.key) ?? 0) + turn.costUsd));
|
|
38710
38900
|
}
|
|
38901
|
+
for (const event4 of events) {
|
|
38902
|
+
if (event4.type === "agent.spawned" && inScope(event4)) {
|
|
38903
|
+
const story = str10(event4.payload.story);
|
|
38904
|
+
if (story === null)
|
|
38905
|
+
continue;
|
|
38906
|
+
open.set(story, (open.get(story) ?? 0) + 1);
|
|
38907
|
+
openStage.set(story, event4.stage);
|
|
38908
|
+
continue;
|
|
38909
|
+
}
|
|
38910
|
+
if (event4.type === "agent.result" && inScope(event4)) {
|
|
38911
|
+
const key = str10(event4.payload.key);
|
|
38912
|
+
if (key === null || !isStory(key, str10(event4.payload.phase)))
|
|
38913
|
+
continue;
|
|
38914
|
+
const opened = open.get(key) ?? 0;
|
|
38915
|
+
if (opened > 0)
|
|
38916
|
+
open.set(key, opened - 1);
|
|
38917
|
+
else if ((lost.get(key) ?? 0) > 0)
|
|
38918
|
+
lost.set(key, (lost.get(key) ?? 0) - 1);
|
|
38919
|
+
continue;
|
|
38920
|
+
}
|
|
38921
|
+
if (!closesAttempt(event4))
|
|
38922
|
+
continue;
|
|
38923
|
+
for (const [story, count] of open) {
|
|
38924
|
+
if (count === 0 || openStage.get(story) !== event4.stage)
|
|
38925
|
+
continue;
|
|
38926
|
+
lost.set(story, (lost.get(story) ?? 0) + count);
|
|
38927
|
+
open.set(story, 0);
|
|
38928
|
+
}
|
|
38929
|
+
}
|
|
38930
|
+
for (const [story, count] of lost) {
|
|
38931
|
+
if (count > 0)
|
|
38932
|
+
unmetered.set(story, (unmetered.get(story) ?? 0) + count);
|
|
38933
|
+
}
|
|
38711
38934
|
return {
|
|
38712
38935
|
rows: order.map((story) => ({
|
|
38713
38936
|
story,
|
|
@@ -38719,6 +38942,14 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38719
38942
|
events: events.length
|
|
38720
38943
|
};
|
|
38721
38944
|
}
|
|
38945
|
+
function closesAttempt(event4) {
|
|
38946
|
+
if (event4.type === "stage.started" || event4.type === "stage.done" || event4.type === "stage.failed" || event4.type === "stage.skipped")
|
|
38947
|
+
return true;
|
|
38948
|
+
if (event4.type !== "error")
|
|
38949
|
+
return false;
|
|
38950
|
+
const where = str10(event4.payload.where);
|
|
38951
|
+
return where === "executor" || where === "record-tasks";
|
|
38952
|
+
}
|
|
38722
38953
|
function buildStoryCost(runDir2) {
|
|
38723
38954
|
let title = "";
|
|
38724
38955
|
let runId = basename13(runDir2);
|
|
@@ -38836,7 +39067,7 @@ function storyVerdict(cost) {
|
|
|
38836
39067
|
return "no total: a story above is missing one side, and an absent figure is not summed as zero.";
|
|
38837
39068
|
}
|
|
38838
39069
|
if (floor) {
|
|
38839
|
-
return `no verdict: ${turns} unmetered, so at least one story's measurement is a
|
|
39070
|
+
return `no verdict: ${turns} unmetered, so at least one story's measurement is a ${LOWER_BOUND_MARK} ` + "— an unmetered turn could put it over a spawn ceiling this cannot see.";
|
|
38840
39071
|
}
|
|
38841
39072
|
return "every story measured inside the spawn ceilings it was given.";
|
|
38842
39073
|
}
|
|
@@ -38865,7 +39096,7 @@ function renderStoryCost(cost) {
|
|
|
38865
39096
|
lines.push(" measured not recorded — no metered `agent.result` carried a cost for that story. " + "A turn this process never metered is a cost it did not see, never a free one.");
|
|
38866
39097
|
}
|
|
38867
39098
|
if (cost.unmeteredStories.length > 0) {
|
|
38868
|
-
lines.push(` ${cost.unmeteredStories.join(", ")}: ${plural3(cost.unmeteredTurns, "turn")} UNMETERED, so ` +
|
|
39099
|
+
lines.push(` ${cost.unmeteredStories.join(", ")}: ${plural3(cost.unmeteredTurns, "turn")} UNMETERED, so ` + `that measurement is a ${LOWER_BOUND_MARK}, not a total.`);
|
|
38869
39100
|
}
|
|
38870
39101
|
if (cost.excludedKeyedResults > 0)
|
|
38871
39102
|
lines.push(` ${excludedLine(cost)}`);
|
|
@@ -38934,6 +39165,7 @@ function readBudget(runDir2) {
|
|
|
38934
39165
|
var ECONOMY_WIDTH = 12;
|
|
38935
39166
|
var DASH = "—";
|
|
38936
39167
|
var NOT_RECORDED = "not recorded";
|
|
39168
|
+
var LOWER_BOUND_MARK = "LOWER BOUND";
|
|
38937
39169
|
function tokenColumns(t, declared2 = 0) {
|
|
38938
39170
|
const measured = `${bigTokens(t.input)} in · ${bigTokens(t.output)} out · ` + `${bigTokens(t.cacheCreation)} cache write · ${bigTokens(t.cacheRead)} cache read`;
|
|
38939
39171
|
if (declared2 <= 0)
|
|
@@ -39117,7 +39349,7 @@ async function buildExecutor(ctx) {
|
|
|
39117
39349
|
try {
|
|
39118
39350
|
await session.restoreForeignWorkAside();
|
|
39119
39351
|
} catch {}
|
|
39120
|
-
throw error;
|
|
39352
|
+
throw withPartialTasks(error, session.tasks);
|
|
39121
39353
|
}
|
|
39122
39354
|
}
|
|
39123
39355
|
function refusedOnBase(session, error) {
|
|
@@ -39302,12 +39534,18 @@ class BuildSession {
|
|
|
39302
39534
|
return await this.finish();
|
|
39303
39535
|
}
|
|
39304
39536
|
async prepare() {
|
|
39305
|
-
|
|
39537
|
+
let planned = this.nextPending();
|
|
39306
39538
|
if (planned === null)
|
|
39307
39539
|
return await this.finish();
|
|
39308
39540
|
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign() ?? await this.refuseOnRedBase() ?? await this.refuseOnUnrunnableWorktree();
|
|
39309
39541
|
if (refusal !== null)
|
|
39310
39542
|
return refusal;
|
|
39543
|
+
const offered = this.offerAtFrontier();
|
|
39544
|
+
if (offered.kind === "wait")
|
|
39545
|
+
return refusedOnSequence(this.ctx, dependencyPrepareRefusal(offered.story, offered.held));
|
|
39546
|
+
if (offered.kind === "none")
|
|
39547
|
+
return await this.finish();
|
|
39548
|
+
planned = offered.planned;
|
|
39311
39549
|
const review = this.reviewWorkFor(planned);
|
|
39312
39550
|
if (review !== null)
|
|
39313
39551
|
return await this.prepareReview(planned, review);
|
|
@@ -39399,6 +39637,9 @@ class BuildSession {
|
|
|
39399
39637
|
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign();
|
|
39400
39638
|
if (refusal !== null)
|
|
39401
39639
|
return refusal;
|
|
39640
|
+
const held2 = this.blockingDependency(planned);
|
|
39641
|
+
if (held2 !== null)
|
|
39642
|
+
return refusedOnSequence(this.ctx, dependencyPrepareRefusal(planned.story.id, held2));
|
|
39402
39643
|
const work = this.reviewWorkFor(planned) ?? this.reviewWorkFromLedger(planned);
|
|
39403
39644
|
if (work === null) {
|
|
39404
39645
|
return refusedOnSequence(this.ctx, `${planned.story.id} has no merged commit to review — a story is reviewed after its developer turn, ` + "not instead of one. Run `tldrx next --prepare` for the developer half first.");
|
|
@@ -39493,7 +39734,7 @@ class BuildSession {
|
|
|
39493
39734
|
lines: [
|
|
39494
39735
|
...this.lines,
|
|
39495
39736
|
`${planned.story.id} → \`${outcome?.status ?? "?"}\` (host review, unmetered)`,
|
|
39496
|
-
|
|
39737
|
+
this.nextAtFrontier()
|
|
39497
39738
|
],
|
|
39498
39739
|
stderr: [...this.advisories],
|
|
39499
39740
|
error: null
|
|
@@ -39505,6 +39746,14 @@ class BuildSession {
|
|
|
39505
39746
|
if (planned === null) {
|
|
39506
39747
|
return refusedOnSequence(this.ctx, "no story is `in_progress` — run `tldrx next --prepare` first");
|
|
39507
39748
|
}
|
|
39749
|
+
const held2 = this.blockingDependency(planned);
|
|
39750
|
+
if (held2 !== null) {
|
|
39751
|
+
return refusedOnSequence(this.ctx, dependencyCommitRefusal(planned.story.id, held2, {
|
|
39752
|
+
bundleDir: relative16(this.ctx.root, agentDir(this.ctx.runDir, this.bundleKey(planned.story.id))),
|
|
39753
|
+
branch: storyBranchOf(this.ctx.runId, planned.story.id),
|
|
39754
|
+
worktree: relative16(this.ctx.root, this.storyWorktree(planned))
|
|
39755
|
+
}));
|
|
39756
|
+
}
|
|
39508
39757
|
const key = this.bundleKey(planned.story.id);
|
|
39509
39758
|
let result2;
|
|
39510
39759
|
try {
|
|
@@ -39553,11 +39802,14 @@ class BuildSession {
|
|
|
39553
39802
|
lines: [
|
|
39554
39803
|
...this.lines,
|
|
39555
39804
|
`${planned.story.id} → \`${outcome?.status ?? "?"}\``,
|
|
39556
|
-
|
|
39805
|
+
this.nextAtFrontier()
|
|
39557
39806
|
],
|
|
39558
39807
|
error: null
|
|
39559
39808
|
};
|
|
39560
39809
|
}
|
|
39810
|
+
cancelledUnder() {
|
|
39811
|
+
return RunStore.cancelledOnDisk(this.ctx.runDir);
|
|
39812
|
+
}
|
|
39561
39813
|
async driveStory(planned) {
|
|
39562
39814
|
const resume = this.resumableReview(planned);
|
|
39563
39815
|
if (resume !== null) {
|
|
@@ -39565,6 +39817,10 @@ class BuildSession {
|
|
|
39565
39817
|
return;
|
|
39566
39818
|
}
|
|
39567
39819
|
for (let i = 0;i < this.attempts; i++) {
|
|
39820
|
+
if (this.cancelledUnder()) {
|
|
39821
|
+
this.lines.push(` · ${planned.story.id}: ${i === 0 ? "not started" : "not requeued"} — the run was cancelled ` + "(tldrx run cancel) while this stage held it");
|
|
39822
|
+
return;
|
|
39823
|
+
}
|
|
39568
39824
|
await this.settleHalf(await this.buildHalf(planned));
|
|
39569
39825
|
const outcome = this.outcomes.get(planned.story.id);
|
|
39570
39826
|
if (outcome !== undefined && outcome.developerError !== null)
|
|
@@ -39614,6 +39870,10 @@ class BuildSession {
|
|
|
39614
39870
|
const planned = queue[cursor++];
|
|
39615
39871
|
if (planned === undefined)
|
|
39616
39872
|
return;
|
|
39873
|
+
if (this.cancelledUnder()) {
|
|
39874
|
+
this.lines.push(` · ${planned.story.id}: not started — the run was cancelled (tldrx run cancel) while this stage held it`);
|
|
39875
|
+
return;
|
|
39876
|
+
}
|
|
39617
39877
|
halves.set(planned.story.id, await this.buildHalf(planned));
|
|
39618
39878
|
}
|
|
39619
39879
|
};
|
|
@@ -39940,6 +40200,29 @@ class BuildSession {
|
|
|
39940
40200
|
});
|
|
39941
40201
|
return "settled";
|
|
39942
40202
|
}
|
|
40203
|
+
if (supplied === undefined && this.cancelledUnder()) {
|
|
40204
|
+
const review2 = reviewerUnfunded("the run was cancelled (tldrx run cancel) before the review — the diff is merged on the epic " + "branch and nobody has judged it");
|
|
40205
|
+
this.ctx.emit("check.failed", {
|
|
40206
|
+
phase: this.ctx.phaseId,
|
|
40207
|
+
check: "review",
|
|
40208
|
+
story: story.planned.story.id,
|
|
40209
|
+
verdict: review2.verdict,
|
|
40210
|
+
attempt: story.attempt,
|
|
40211
|
+
detail: review2.summary
|
|
40212
|
+
});
|
|
40213
|
+
await this.settle(story, "review", {
|
|
40214
|
+
dod,
|
|
40215
|
+
commit: commit2,
|
|
40216
|
+
merged: true,
|
|
40217
|
+
carried,
|
|
40218
|
+
epicBase,
|
|
40219
|
+
verdict: "n-a",
|
|
40220
|
+
review: review2,
|
|
40221
|
+
cost: round23(priorCost),
|
|
40222
|
+
reason: review2.summary
|
|
40223
|
+
});
|
|
40224
|
+
return "settled";
|
|
40225
|
+
}
|
|
39943
40226
|
const outcome = supplied === undefined ? await this.spawnReviewer(story, dod, epicBase) : { review: supplied, cost: 0 };
|
|
39944
40227
|
const review = outcome.review;
|
|
39945
40228
|
const cost = round23(priorCost + outcome.cost);
|
|
@@ -40187,6 +40470,41 @@ class BuildSession {
|
|
|
40187
40470
|
}
|
|
40188
40471
|
return decidingHold(holds);
|
|
40189
40472
|
}
|
|
40473
|
+
offerAtFrontier() {
|
|
40474
|
+
const walked = this.walkFrontier(true);
|
|
40475
|
+
if (walked.kind === "held" && !walked.recorded)
|
|
40476
|
+
return { kind: "wait", story: walked.story, held: walked.held };
|
|
40477
|
+
if (walked.kind === "held")
|
|
40478
|
+
return { kind: "none" };
|
|
40479
|
+
return walked;
|
|
40480
|
+
}
|
|
40481
|
+
nextAtFrontier() {
|
|
40482
|
+
const walked = this.walkFrontier(false);
|
|
40483
|
+
if (walked.kind === "offer")
|
|
40484
|
+
return `${walked.planned.story.id} is next — run \`tldrx next --prepare\``;
|
|
40485
|
+
if (walked.kind === "held")
|
|
40486
|
+
return dependencyNextLine(walked.story, walked.held);
|
|
40487
|
+
return "nothing is next — run `tldrx next --prepare` to close the stage at its gate";
|
|
40488
|
+
}
|
|
40489
|
+
walkFrontier(record3) {
|
|
40490
|
+
let recorded = false;
|
|
40491
|
+
let first2 = null;
|
|
40492
|
+
for (const planned of this.pendingStories()) {
|
|
40493
|
+
const held2 = this.blockingDependency(planned);
|
|
40494
|
+
if (held2 === null)
|
|
40495
|
+
return { kind: "offer", planned };
|
|
40496
|
+
first2 ??= { story: planned.story.id, held: held2 };
|
|
40497
|
+
if (dependencyIsPending(held2.status)) {
|
|
40498
|
+
if (record3)
|
|
40499
|
+
this.waitOnDependency(planned, held2);
|
|
40500
|
+
continue;
|
|
40501
|
+
}
|
|
40502
|
+
if (record3)
|
|
40503
|
+
this.blockOnDependency(planned, held2);
|
|
40504
|
+
recorded = true;
|
|
40505
|
+
}
|
|
40506
|
+
return first2 === null ? { kind: "none" } : { kind: "held", ...first2, recorded };
|
|
40507
|
+
}
|
|
40190
40508
|
waitOnDependency(planned, held2) {
|
|
40191
40509
|
this.waits.set(planned.story.id, held2);
|
|
40192
40510
|
this.lines.push(dependencyWaitLine(planned.story.id, held2));
|
|
@@ -40917,14 +41235,33 @@ class BuildSession {
|
|
|
40917
41235
|
const measured = measuredWidening(changed, declared2);
|
|
40918
41236
|
if (measured === null)
|
|
40919
41237
|
return;
|
|
40920
|
-
|
|
40921
|
-
|
|
40922
|
-
|
|
40923
|
-
|
|
40924
|
-
|
|
40925
|
-
|
|
40926
|
-
|
|
40927
|
-
|
|
41238
|
+
const id = story.planned.story.id;
|
|
41239
|
+
const actor = "framework";
|
|
41240
|
+
try {
|
|
41241
|
+
this.ctx.emit("story.touches_widened", {
|
|
41242
|
+
story: id,
|
|
41243
|
+
paths: [...measured.paths],
|
|
41244
|
+
note: measured.note,
|
|
41245
|
+
before: [...measured.before],
|
|
41246
|
+
after: [...measured.after],
|
|
41247
|
+
basis: "measured"
|
|
41248
|
+
}, 0, actor);
|
|
41249
|
+
return;
|
|
41250
|
+
} catch (error) {
|
|
41251
|
+
const why = firstLine7(error instanceof Error ? error.message : String(error));
|
|
41252
|
+
try {
|
|
41253
|
+
this.ctx.emit("story.touches_widened", {
|
|
41254
|
+
story: id,
|
|
41255
|
+
paths_omitted: measured.paths.length,
|
|
41256
|
+
before_omitted: measured.before.length,
|
|
41257
|
+
after_omitted: measured.after.length,
|
|
41258
|
+
note: `${measured.note} — the lists could not be recorded on this event: ${why}`,
|
|
41259
|
+
basis: "measured"
|
|
41260
|
+
}, 0, actor);
|
|
41261
|
+
} catch (again) {
|
|
41262
|
+
this.advisories.push(`${id}: the measured widening of its surface (${String(measured.paths.length)} path(s) outside ` + `\`touches:\`) could not be recorded in events.jsonl — ` + firstLine7(again instanceof Error ? again.message : String(again)));
|
|
41263
|
+
}
|
|
41264
|
+
}
|
|
40928
41265
|
}
|
|
40929
41266
|
orderedOutcomes() {
|
|
40930
41267
|
const rows = [];
|
|
@@ -41535,6 +41872,20 @@ function failed3(ctx, error, tasks) {
|
|
|
41535
41872
|
}
|
|
41536
41873
|
|
|
41537
41874
|
// src/core/facilitator/executors/index.ts
|
|
41875
|
+
var PARTIAL_TASKS = Symbol.for("tldrx.partialTasks");
|
|
41876
|
+
function withPartialTasks(error, tasks) {
|
|
41877
|
+
if (tasks.length === 0)
|
|
41878
|
+
return error;
|
|
41879
|
+
const carrier = typeof error === "object" && error !== null ? error : new Error(String(error));
|
|
41880
|
+
Object.defineProperty(carrier, PARTIAL_TASKS, { value: [...tasks], enumerable: false, configurable: true });
|
|
41881
|
+
return carrier;
|
|
41882
|
+
}
|
|
41883
|
+
function partialTasksOf(error) {
|
|
41884
|
+
if (typeof error !== "object" || error === null)
|
|
41885
|
+
return [];
|
|
41886
|
+
const tasks = error[PARTIAL_TASKS];
|
|
41887
|
+
return Array.isArray(tasks) ? tasks : [];
|
|
41888
|
+
}
|
|
41538
41889
|
var EXECUTORS = new Map([
|
|
41539
41890
|
[BUILD_PHASE, buildExecutor],
|
|
41540
41891
|
[WATCH_PHASE, watchExecutor]
|
|
@@ -42279,14 +42630,33 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42279
42630
|
outcome = await executor(executorCtx);
|
|
42280
42631
|
} catch (error) {
|
|
42281
42632
|
const why = error instanceof Error ? error.message : String(error);
|
|
42282
|
-
|
|
42633
|
+
const partial = partialTasksOf(error);
|
|
42634
|
+
const progress2 = { rows: 0, events: 0 };
|
|
42635
|
+
let recordingFailed = null;
|
|
42636
|
+
if (partial.length > 0) {
|
|
42637
|
+
try {
|
|
42638
|
+
recordExecutorTasks(store, options, phaseId, stageId, spec, partial, progress2);
|
|
42639
|
+
} catch (again) {
|
|
42640
|
+
recordingFailed = again instanceof Error ? again.message : String(again);
|
|
42641
|
+
}
|
|
42642
|
+
}
|
|
42643
|
+
const written = requireStage(store, phaseId, stageId).tasks.length - tasksBefore;
|
|
42644
|
+
const whole = partial.length > 0 && written === partial.length;
|
|
42645
|
+
store.save();
|
|
42646
|
+
const extra = appendOrSay(() => appendCapped("error", {
|
|
42283
42647
|
phase: phaseId,
|
|
42284
42648
|
where: "executor",
|
|
42285
42649
|
detail: why,
|
|
42286
|
-
tasks_recorded:
|
|
42287
|
-
|
|
42288
|
-
|
|
42289
|
-
|
|
42650
|
+
tasks_recorded: whole,
|
|
42651
|
+
rows_written: written,
|
|
42652
|
+
rows_expected: partial.length,
|
|
42653
|
+
events_written: progress2.events,
|
|
42654
|
+
...recordingFailed === null ? {} : { recording_error: recordingFailed }
|
|
42655
|
+
}, 0));
|
|
42656
|
+
const n = String(partial.length);
|
|
42657
|
+
const eventsShort = recordingFailed === null ? "" : `; ${String(progress2.events)} of ${n} agent.result events could not be appended: ${recordingFailed}`;
|
|
42658
|
+
const reason = partial.length === 0 ? `the executor threw before returning its task rows — this invocation's turn costs are not in run.yml: ${why}` : whole ? `the executor threw after ${n} turn(s) — their rows and costs are in run.yml${eventsShort}: ${why}` : `the executor threw after ${n} turn(s) and recording their rows threw too — ` + `${String(written)} of ${n} rows are in run.yml and the rest are not: ${why}; recording: ${recordingFailed ?? "unknown"}`;
|
|
42659
|
+
return failStage(store, options, phaseId, stageId, reason, [...notes, ...extra], tasksBefore + written);
|
|
42290
42660
|
}
|
|
42291
42661
|
if (isSequencingRefusal(outcome)) {
|
|
42292
42662
|
if (!started) {
|
|
@@ -42296,22 +42666,34 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42296
42666
|
return out(EXIT_USAGE3, [...notes, ...outcome.lines], [], executorSignature(outcome));
|
|
42297
42667
|
}
|
|
42298
42668
|
claimEpicBranches(store, outcome.epicBranches, outcome.branchModel);
|
|
42669
|
+
const progress = { rows: 0, events: 0 };
|
|
42299
42670
|
try {
|
|
42300
|
-
recordExecutorTasks(store, options, phaseId, stageId, spec, outcome);
|
|
42671
|
+
recordExecutorTasks(store, options, phaseId, stageId, spec, outcome.tasks, progress);
|
|
42301
42672
|
} catch (error) {
|
|
42302
42673
|
const why = error instanceof Error ? error.message : String(error);
|
|
42303
42674
|
const recorded = requireStage(store, phaseId, stageId).tasks.length;
|
|
42304
|
-
|
|
42675
|
+
const written = recorded - tasksBefore;
|
|
42676
|
+
const expected = outcome.tasks.length;
|
|
42677
|
+
store.save();
|
|
42678
|
+
const extra = appendOrSay(() => appendCappedEvent(store, options, phaseId, stageId, "error", {
|
|
42305
42679
|
phase: phaseId,
|
|
42306
42680
|
where: "record-tasks",
|
|
42307
42681
|
detail: why,
|
|
42308
|
-
rows_written:
|
|
42309
|
-
rows_expected:
|
|
42310
|
-
|
|
42311
|
-
|
|
42312
|
-
|
|
42682
|
+
rows_written: written,
|
|
42683
|
+
rows_expected: expected,
|
|
42684
|
+
events_written: progress.events
|
|
42685
|
+
}, 0));
|
|
42686
|
+
const rows = written === expected ? `all ${String(expected)} rows are in run.yml and ${String(progress.events)} of ${String(expected)} ` + "agent.result events were appended" : `${String(written)} of ${String(expected)} rows are in run.yml and the rest are not`;
|
|
42687
|
+
return failStage(store, options, phaseId, stageId, `recording this invocation's task rows threw — ${rows}: ${why}`, [...notes, ...extra], recorded);
|
|
42313
42688
|
}
|
|
42314
42689
|
store.save();
|
|
42690
|
+
if (store.cancelledUnder) {
|
|
42691
|
+
return out(EXIT_OK4, [
|
|
42692
|
+
...notes,
|
|
42693
|
+
...outcome.lines,
|
|
42694
|
+
`run ${store.runId} was cancelled while ${phaseId}/${stageId} ran — stopped here: ` + `${String(outcome.tasks.length)} task row(s) from this invocation are recorded, and nothing more is spawned`
|
|
42695
|
+
], outcome.stderr ?? []);
|
|
42696
|
+
}
|
|
42315
42697
|
if (outcome.refused === true) {
|
|
42316
42698
|
setStatus(store, phaseId, stageId, "ready");
|
|
42317
42699
|
store.save();
|
|
@@ -42332,11 +42714,22 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42332
42714
|
function isSequencingRefusal(outcome) {
|
|
42333
42715
|
return outcome.sequencing === true && outcome.refused === true && outcome.tasks.length === 0 && outcome.costUsd === 0 && (outcome.epicBranches?.length ?? 0) === 0;
|
|
42334
42716
|
}
|
|
42335
|
-
function
|
|
42717
|
+
function appendOrSay(append) {
|
|
42718
|
+
try {
|
|
42719
|
+
append();
|
|
42720
|
+
return [];
|
|
42721
|
+
} catch (error) {
|
|
42722
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
42723
|
+
return [`the error event for this failure could not be appended to events.jsonl — ${why}; run.yml is saved regardless`];
|
|
42724
|
+
}
|
|
42725
|
+
}
|
|
42726
|
+
function recordExecutorTasks(store, options, phaseId, stageId, spec, tasks, progress = { rows: 0, events: 0 }) {
|
|
42336
42727
|
const version = frameworkVersionSync();
|
|
42337
|
-
|
|
42728
|
+
const rows = [];
|
|
42729
|
+
for (const task2 of tasks) {
|
|
42338
42730
|
const id = nextTaskId(store, phaseId, stageId);
|
|
42339
42731
|
const metered = task2.metered !== false;
|
|
42732
|
+
rows.push({ id, task: task2, metered });
|
|
42340
42733
|
recordTask(store, phaseId, stageId, {
|
|
42341
42734
|
id,
|
|
42342
42735
|
status: task2.error === null ? "done" : "failed",
|
|
@@ -42355,6 +42748,9 @@ function recordExecutorTasks(store, options, phaseId, stageId, spec, outcome) {
|
|
|
42355
42748
|
...cacheSplit(task2.usage?.cache_creation_input_tokens, task2.usage?.cache_read_input_tokens),
|
|
42356
42749
|
...task2.durationMs === undefined ? {} : { duration_ms: task2.durationMs, duration_basis: "spawned" }
|
|
42357
42750
|
});
|
|
42751
|
+
progress.rows += 1;
|
|
42752
|
+
}
|
|
42753
|
+
for (const { id, task: task2, metered } of rows) {
|
|
42358
42754
|
appendCappedEvent(store, options, phaseId, stageId, "agent.result", {
|
|
42359
42755
|
phase: phaseId,
|
|
42360
42756
|
task: id,
|
|
@@ -42369,6 +42765,7 @@ function recordExecutorTasks(store, options, phaseId, stageId, spec, outcome) {
|
|
|
42369
42765
|
...task2.tokens === undefined ? {} : { tokens: task2.tokens },
|
|
42370
42766
|
...task2.usage === undefined ? {} : { usage: usagePayload(task2.usage) }
|
|
42371
42767
|
}, metered ? round26(task2.costUsd) : 0);
|
|
42768
|
+
progress.events += 1;
|
|
42372
42769
|
}
|
|
42373
42770
|
}
|
|
42374
42771
|
async function commitStage(store, options, phaseId, stageId, spec, notes) {
|
|
@@ -42790,7 +43187,6 @@ function assemblePrompt(store, options, spec, stage2, inputs, ctx, seed) {
|
|
|
42790
43187
|
dispatchNotes
|
|
42791
43188
|
};
|
|
42792
43189
|
}
|
|
42793
|
-
var MAX_PREVIOUS_ATTEMPT_BYTES = 32 * 1024;
|
|
42794
43190
|
function describePreviousAttempt(stage2, options) {
|
|
42795
43191
|
const lines = [];
|
|
42796
43192
|
const failure = [...stage2.tasks].reverse().find((task2) => task2.error !== null)?.error ?? null;
|
|
@@ -42833,7 +43229,7 @@ function priorOutputs(options) {
|
|
|
42833
43229
|
return [];
|
|
42834
43230
|
const out = [
|
|
42835
43231
|
"",
|
|
42836
|
-
|
|
43232
|
+
`### ${PREVIOUS_ATTEMPT_EDIT_HEADING}`,
|
|
42837
43233
|
"",
|
|
42838
43234
|
"These files are on disk RIGHT NOW, exactly as the last attempt left them. They are",
|
|
42839
43235
|
"not a suggestion and they are not history: they are the draft you are being paid to",
|
|
@@ -53250,6 +53646,8 @@ import { isAbsolute as isAbsolute13, join as join135 } from "node:path";
|
|
|
53250
53646
|
import { existsSync as existsSync111, readFileSync as readFileSync98, readdirSync as readdirSync34 } from "node:fs";
|
|
53251
53647
|
import { basename as basename17, join as join131 } from "node:path";
|
|
53252
53648
|
// src/core/replay/renderReplay.ts
|
|
53649
|
+
var STAGE_FAILED_MARKER = "FAILED: ";
|
|
53650
|
+
var NO_REASON_RECORDED = "no reason recorded";
|
|
53253
53651
|
function renderReplay(loaded) {
|
|
53254
53652
|
const run2 = loaded.run;
|
|
53255
53653
|
const out2 = [
|
|
@@ -53377,9 +53775,9 @@ function bullet2(item, trail) {
|
|
|
53377
53775
|
case "stage.done":
|
|
53378
53776
|
return `${prefix5}ended`;
|
|
53379
53777
|
case "stage.failed":
|
|
53380
|
-
return `${prefix5}
|
|
53778
|
+
return `${prefix5}${STAGE_FAILED_MARKER}${text3(payload2.reason) || NO_REASON_RECORDED}`;
|
|
53381
53779
|
case "stage.skipped":
|
|
53382
|
-
return `${prefix5}skipped: ${text3(payload2.reason) ||
|
|
53780
|
+
return `${prefix5}skipped: ${text3(payload2.reason) || NO_REASON_RECORDED}`;
|
|
53383
53781
|
case "question.asked":
|
|
53384
53782
|
return `${prefix5}${q || "a question"} asked by ${actor}: ${text3(payload2.question)}`.trimEnd();
|
|
53385
53783
|
case "question.answered":
|
|
@@ -53395,7 +53793,7 @@ function bullet2(item, trail) {
|
|
|
53395
53793
|
case "story.reopened":
|
|
53396
53794
|
return `${prefix5}story ${text3(payload2.story) || "?"} REOPENED by ${actor}` + ` — back to \`${text3(payload2.to_status) || "todo"}\` from \`${text3(payload2.from_status) || "?"}\`` + `${note(payload2.note)}`;
|
|
53397
53795
|
case "story.touches_widened":
|
|
53398
|
-
return `${prefix5}story ${text3(payload2.story) || "?"}'s \`touches:\` WIDENED by ${actor}` + ` (${basisOf(payload2)})` + ` — +${
|
|
53796
|
+
return `${prefix5}story ${text3(payload2.story) || "?"}'s \`touches:\` WIDENED by ${actor}` + ` (${basisOf(payload2)})` + ` — +${widenedPaths(payload2)}` + ` (${String(listCount(payload2, "before"))} → ${String(listCount(payload2, "after"))} path(s))` + `${note(payload2.note)}`;
|
|
53399
53797
|
case "story.base_fastforwarded":
|
|
53400
53798
|
return `${prefix5}story ${text3(payload2.story) || "?"}'s base fast-forwarded to` + ` \`${text3(payload2.base) || "?"}\` — ${text3(payload2.from) || "?"} → ${text3(payload2.to) || "?"}` + ` (${String(payload2.commits ?? "?")} commit(s))`;
|
|
53401
53799
|
case "story.base_updated":
|
|
@@ -53432,8 +53830,11 @@ function bullet2(item, trail) {
|
|
|
53432
53830
|
return `${prefix5}map refreshed`;
|
|
53433
53831
|
case "result.unreadable":
|
|
53434
53832
|
return `${prefix5}UNREADABLE ${text3(payload2.path) || "result.json"}` + ` — ${text3(payload2.error) || "no parse error recorded"}` + " (nothing failed; rewrite it and run the same command again)";
|
|
53435
|
-
case "error":
|
|
53436
|
-
|
|
53833
|
+
case "error": {
|
|
53834
|
+
const what = text3(payload2.detail) || text3(payload2.detail_omitted) || text3(payload2.message) || "no message recorded";
|
|
53835
|
+
const rows = typeof payload2.rows_expected === "number" ? ` (${String(payload2.rows_written ?? "?")} of ${String(payload2.rows_expected)} task rows recorded)` : "";
|
|
53836
|
+
return `${prefix5}error: ${what}${rows}`;
|
|
53837
|
+
}
|
|
53437
53838
|
default:
|
|
53438
53839
|
return null;
|
|
53439
53840
|
}
|
|
@@ -53491,8 +53892,12 @@ function pathList(value) {
|
|
|
53491
53892
|
const items = value.filter((item) => typeof item === "string");
|
|
53492
53893
|
return items.length === 0 ? "?" : items.join(", ");
|
|
53493
53894
|
}
|
|
53494
|
-
function
|
|
53495
|
-
|
|
53895
|
+
function widenedPaths(payload2) {
|
|
53896
|
+
const listed = pathList(payload2.paths);
|
|
53897
|
+
const omitted = typeof payload2.paths_omitted === "number" && payload2.paths_omitted > 0 ? `${String(payload2.paths_omitted)} path(s) not listed on the event` : "";
|
|
53898
|
+
if (omitted === "")
|
|
53899
|
+
return listed;
|
|
53900
|
+
return listed === "?" ? omitted : `${listed}, ${omitted}`;
|
|
53496
53901
|
}
|
|
53497
53902
|
function signature(actor, payload2) {
|
|
53498
53903
|
const executed = payload2.executed_by;
|