tldr-experts 0.25.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 +76 -0
- package/README.md +1 -0
- package/dist/hooks/answer-capture.js +2 -2
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-tjx8g39f.js → chunk-d0jhr5wh.js} +1 -1
- package/dist/hooks/{chunk-rgn23z77.js → chunk-pwzy19er.js} +1 -1
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +187 -44
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- /package/dist/hooks/{chunk-6z5rmj0b.js → chunk-41k19q8t.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0 — 2026-09-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`tldrx replay` says what a failed stage died of, instead of `FAILED: no reason recorded` for
|
|
8
|
+
every one (#309).** MEASURED by inspection at `03d53e8`: the ONLY writer of `stage.failed` in the
|
|
9
|
+
tree — and, per `git log -S`, in its whole history — carries the failure as `payload.reason`
|
|
10
|
+
(`runNext.ts`, the fail path; the same field `stage.skipped` uses, and the same field `run auto`'s
|
|
11
|
+
own summary reads). The narrative renderer read `payload.error`, a field nothing ever set, so
|
|
12
|
+
`text()` was always empty and the fallback always fired — a stakeholder reading a replay saw
|
|
13
|
+
that a stage failed and never why, while the reason sat one field over in the same line of
|
|
14
|
+
`events.jsonl`. Same class as the `message`/`detail` drift #249's round-1 review found:
|
|
15
|
+
payloads are untyped and nothing checks a reader's field name against its writer's. The
|
|
16
|
+
renderer now reads `reason`, like its sibling one line down; the `FAILED: ` marker and the
|
|
17
|
+
fallback sentence are exported constants, and `test/replay.test.ts` feeds a hand-built
|
|
18
|
+
`stage.failed` event and pins the rendered line to the reason it carried (RED on the old read:
|
|
19
|
+
the line was never found; the fallback case is a guard). No record changes shape — nothing
|
|
20
|
+
wrote `error`, so there is no old record to tolerate.
|
|
21
|
+
|
|
22
|
+
- **A wide story's measured widening no longer kills the invocation that earned it, and an executor that
|
|
23
|
+
throws after paid turns leaves their rows and their money in `run.yml` (#249).** MEASURED on a live 0.24.0
|
|
24
|
+
field run: a story with 16 declared touches changed 32 files, and the `story.touches_widened` the framework
|
|
25
|
+
measures at settle carried `paths`, `before` and `after` — three unbounded path lists, 5556 bytes against
|
|
26
|
+
the 4096-byte cap. `capPayload` knew `detail` and `outputs` by name and nothing else, so `EventLog.append`
|
|
27
|
+
threw; `measureSurface`'s "advisory, never throws" contract guarded the git read and not the emit, so the
|
|
28
|
+
throw went out through `settle` — after the story's `done` was on disk and before its `task.done` was in
|
|
29
|
+
the ledger — into `runNext`'s catch, which failed the stage with `tasks_recorded: false`: the developer's
|
|
30
|
+
and the reviewer's paid turns never reached `run.yml`, and every ceiling that derives from recorded spend
|
|
31
|
+
was short by exactly that, so the next economy refusal that should have fired would not have. Three fixes,
|
|
32
|
+
one per seam. The cap seam knows the three lists BY NAME in one declared table (`DROPPABLE_LISTS`):
|
|
33
|
+
`after` first (the largest, and derivable from the other two), then `before`, then `paths` last — each
|
|
34
|
+
dropped WHOLE and only while the payload is still over, each replaced by `<field>_omitted: N` and the same
|
|
35
|
+
reason-and-pointer sentence `outputs_omitted` carries, never a shortened list, which would read downstream
|
|
36
|
+
as the whole one; a `paths_omitted` a writer already put there (`worktree.foreign_work_aside`) is added
|
|
37
|
+
to, not overwritten. Every reader — `wideningRows`, the Build handoff, `tldrx replay` — renders
|
|
38
|
+
`(16 → 29 path(s))` from ONE derivation, `listCount`, so a row the cap could not carry reads the same
|
|
39
|
+
everywhere. `measureSurface` never throws: a refused emit records a bounded absence on the same event (the
|
|
40
|
+
counts, and a `note` naming the failure), and a log that refuses even that says so on stderr while the
|
|
41
|
+
story still settles. And the structural half, the expensive one: the Build executor carries the rows it
|
|
42
|
+
had already earned ON the error it throws (`withPartialTasks` — data on the same object, never a wrapper,
|
|
43
|
+
so what threw is still what the caller sees), and `runNext`'s catch records them through the same
|
|
44
|
+
`recordExecutorTasks` the return path uses BEFORE it fails the stage: the `error` event says
|
|
45
|
+
`tasks_recorded: true` with `rows_written`/`rows_expected` measured off the store after the write, the
|
|
46
|
+
rows keep their own `done` rather than being repainted with a throw they did not produce, and
|
|
47
|
+
`budget.spent_usd` is what was paid. Pinned by a real fake-agent build whose settle throws after both turns:
|
|
48
|
+
MEASURED on that case, the ledger before the fix recorded 0 rows / $0.00 for two paid turns, and after it
|
|
49
|
+
records 2 rows / $0.50. Which is the sentence an operator needs after this release: the cost half does not
|
|
50
|
+
just stop losing money, it stops LYING in favour of spend. With paid turns now recorded, ceilings bite
|
|
51
|
+
earlier — a run that used to reach the end because the ledger counted short may now be refused mid-way,
|
|
52
|
+
correctly but differently, so a new economy refusal after upgrading is this change working, not a
|
|
53
|
+
regression to hunt elsewhere. Pre-merge review found the double fault behind that: when recording the
|
|
54
|
+
carried rows itself threw (an `agent.result` the cap cannot rescue), rows and events were interleaved, so
|
|
55
|
+
a throw at event k stranded rows k+1… in memory and nothing the spend surfaces read said so — a plausible
|
|
56
|
+
short total, worse than a loud zero — and the `error` event was emitted BEFORE the save with a free-text
|
|
57
|
+
`recording_error` the cap did not trim, so a long one could throw the seam and lose the rows with no
|
|
58
|
+
trace. Now every row goes into the store before any event is appended (the in-memory row write cannot
|
|
59
|
+
throw; the append is the only line that can), the store is saved before the `error` event is emitted and
|
|
60
|
+
that emit is wrapped, `recording_error` sits in the cap's prose table beside `detail`, and no new
|
|
61
|
+
vocabulary reaches `budget show`, the dashboard or status: `run.yml` is whole, so they read a
|
|
62
|
+
measurement. Pre-merge review round 2 found the events side was NOT labelled — `tldrx cost --stories`
|
|
63
|
+
reads only `agent.result` events, so a story the fault left un-evented came back with no measured cost
|
|
64
|
+
and zero unmetered turns, a silent null at odds with the `budget.spent_usd` run.yml records. Now the
|
|
65
|
+
story ledger walks the log in order: a spawn opens a slot for its story, a result closes one (or, arriving
|
|
66
|
+
late from an orphaned agent after its slot was already counted lost, retires that lost slot — a result is
|
|
67
|
+
proof the turn was metered whenever it lands, never both the dollars and a lost turn), and an
|
|
68
|
+
invocation-terminal event on that stage (`stage.done`/`failed`/`skipped`, the executor or record-tasks
|
|
69
|
+
`error`, or a later `stage.started` that superseded it) turns every slot still open into a LOST turn,
|
|
70
|
+
which enters the SAME unmetered/lower-bound door: the story reads as a LOWER BOUND with the turn count,
|
|
71
|
+
not a confident zero (the dollars are in run.yml; a task row carries no story key, so they are named
|
|
72
|
+
absent per story, never invented). A spawn with nothing terminal after it is a turn IN FLIGHT — every
|
|
73
|
+
healthy mid-run read of `tldrx cost` and of the handoff — and is counted as nothing, review round 3's
|
|
74
|
+
finding: `spawned − accounted` alone had flagged every live build as a lower bound. Events carry no
|
|
75
|
+
attempt id, so the log's own order is the attempt scope. `tldrx replay`
|
|
76
|
+
renders the error line from `detail` (it read `message`, a field the event never carried) and reports
|
|
77
|
+
`rows_written` of `rows_expected` — reports, never sums.
|
|
78
|
+
|
|
3
79
|
## 0.25.0 — 2026-09-14
|
|
4
80
|
|
|
5
81
|
### Fixed
|
package/README.md
CHANGED
|
@@ -335,6 +335,7 @@ back on the registry is 0.3.0.
|
|
|
335
335
|
|
|
336
336
|
| Version | Date | Status | Contains |
|
|
337
337
|
|---|---|---|---|
|
|
338
|
+
| 0.26.0 | 2026-09-14 | `beta` | Two field defects from the same class — a reader of an event payload trusting a field its writer never set — plus the ceiling that killed the turn that earned it. A wide story's measured `touches_widened` no longer throws after the turn has settled: the cap knows the three lists by name and drops them whole by count, every reader derives before/after through one function, and an executor that throws after paid turns leaves their rows and money in `run.yml` and saves before it emits. The cost surfaces then agree with each other: a turn whose result was lost to that fault reads as UNMETERED, LOWER BOUND on `tldrx cost` and in the Build handoff through the door other unmeasured turns already use, a turn still in flight reads as nothing, and a late result retires the lost mark instead of counting twice. Replay reads the reason a failed stage recorded instead of printing "no reason recorded" for every one. |
|
|
338
339
|
| 0.25.0 | 2026-09-14 | `beta` | The other half of the two-session protocol and four holes measured on live runs. `scripts/release.sh` now waits on a running wave's lock before touching the tree and never hands back the marker it wrote — release has precedence, the wave yields, exit 14 when it gives up; the heading gate also refuses an unreleased version at or below `package.json`; the marker carries its phase and `--status` reports a release queued behind a wave (#304). In-session `tldrx next --prepare` and `--commit` ask the same dependency frontier the headless loop asks: a terminal hold records the dependent `blocked`, a pending hold refuses without writing, a held `--commit` keeps the work it sits on and says where it is (#300). A Watch card refused for an unsourced `## Where` item comes back to its writer marked, the refusal names the cure, and the brief shows one non-file example, so a retry edits the card instead of regenerating it blind (#301). And `run.yml` no longer loses an external write to a live loop: a save carries only the fields the store changed over a fresh read, ownership is declared, `cancelled` is terminal and wins in the file and in the process, a file that cannot be merged is kept beside as `.bak` and said so, and a file recording another run is refused (#305). Minor release: a new exit code on a public script, a stricter merge gate, and `--prepare`/`--commit` refuse where they used to proceed. |
|
|
339
340
|
| 0.24.0 | 2026-09-14 | `beta` | Five fixes from one unattended night, all measured on live runs. The two-session protocol is mechanical where it was chat: the merge wave refuses a merged tree with two unreleased CHANGELOG headings or one at or below the last release (exit 12), `release.sh` holds a `.RELEASE-IN-PROGRESS` marker the wave waits on (exit 13 when it gives up), `merge-wave.sh --status` reads holder, branch and phase in one line, and the `maintain` skill gains a worker-mode section (#299). A per-story cap says what it was derived from — plan price × stage scale — and names the lever that moves it, and a plan whose prices exceed the stage budget is told so instead of being scaled in silence (#281). A story whose dependency is still at `review` waits in `todo` instead of being written `blocked`, and a dependency block left by an earlier version is released once the dependency is `done` (#280). `--until-done` compares the refusal across attempts, not the advice line printed under it, so a failure that made progress keeps its relaunches (#297). And the Build handoff no longer fails its own `claim-sources` check over a refused command that spans lines: one physical line per element, the break shown as `⏎` and explained once in the document (#283). Minor release: the wave and `--until-done` behave differently in situations that exist today. |
|
|
340
341
|
| 0.23.0 | 2026-09-14 | `beta` | `tldrx story reopen --as-is` now settles a story whose work is already merged into the epic but whose review never completed (`n-a` or `error` on its last recorded merge): a new, named review-only case that merges nothing and only routes the story to its reviewer, read from an additive ledger field; the existing refusal ("no commit the epic has not got") is untouched and now names the standing verdict when one exists (`changes`, `approve`, `fixlist` still refuse). A fix list whose findings are all deferred settles the story `done` instead of spawning a developer with nothing to fix, measured on a live run that paid for four such developers (#295). And a turn that died on a provider limit no longer records `success` borrowed from the provider's own subtype: the failure record says what the host saw (#296, first half — DETECTING the provider limit as its own non-execution kind is #298 and is NOT in this release). Minor release: `story reopen --as-is` behaves differently in a situation that exists today. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-d0jhr5wh.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
EventLog,
|
|
15
15
|
PHASE_ID_RE
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-41k19q8t.js";
|
|
17
17
|
import {
|
|
18
18
|
PHASE_IDS
|
|
19
19
|
} from "./chunk-m1s8a6s0.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
questionsCard
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-d0jhr5wh.js";
|
|
5
5
|
import"./chunk-jpp137mb.js";
|
|
6
6
|
import {
|
|
7
7
|
allow,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
runSnapshot,
|
|
21
21
|
statusWithOutcome,
|
|
22
22
|
whatIsWaiting
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-pwzy19er.js";
|
|
24
24
|
import {
|
|
25
25
|
expertsDir,
|
|
26
26
|
loadExperts,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "./chunk-bbrhs055.js";
|
|
31
31
|
import {
|
|
32
32
|
isFinished
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-41k19q8t.js";
|
|
34
34
|
import"./chunk-m1s8a6s0.js";
|
|
35
35
|
import {
|
|
36
36
|
openRunViews
|
package/dist/hooks/statusline.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bar,
|
|
4
4
|
runSnapshot
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-pwzy19er.js";
|
|
6
6
|
import"./chunk-bbrhs055.js";
|
|
7
|
-
import"./chunk-
|
|
7
|
+
import"./chunk-41k19q8t.js";
|
|
8
8
|
import"./chunk-m1s8a6s0.js";
|
|
9
9
|
import"./chunk-yre2scxn.js";
|
|
10
10
|
import"./chunk-bznt6jb2.js";
|
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
|
}
|
|
@@ -30038,8 +30048,9 @@ function wideningBullets(rows) {
|
|
|
30038
30048
|
for (const row2 of rows.slice(0, MAX_WIDENING_BULLETS)) {
|
|
30039
30049
|
const named = row2.paths.slice(0, NAMED_WIDENED_PATHS).map((path) => `\`${path}\``).join(", ");
|
|
30040
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` : "";
|
|
30041
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)`;
|
|
30042
|
-
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)}]`);
|
|
30043
30054
|
}
|
|
30044
30055
|
if (rows.length > MAX_WIDENING_BULLETS) {
|
|
30045
30056
|
const next = rows[MAX_WIDENING_BULLETS]?.line ?? 1;
|
|
@@ -35798,6 +35809,11 @@ var MEASURED_NOTE_TAIL = " — measured off the story's own diff, the range the
|
|
|
35798
35809
|
function basisOf(payload) {
|
|
35799
35810
|
return payload.basis === "measured" ? "measured" : "declared";
|
|
35800
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
|
+
}
|
|
35801
35817
|
function stringList3(value) {
|
|
35802
35818
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
35803
35819
|
}
|
|
@@ -35822,8 +35838,9 @@ function wideningRows(eventsText) {
|
|
|
35822
35838
|
story: typeof payload.story === "string" ? payload.story : "?",
|
|
35823
35839
|
basis: basisOf(payload),
|
|
35824
35840
|
paths: stringList3(payload.paths),
|
|
35825
|
-
|
|
35826
|
-
|
|
35841
|
+
...typeof payload.paths_omitted === "number" && payload.paths_omitted > 0 ? { pathsOmitted: payload.paths_omitted } : {},
|
|
35842
|
+
before: listCount(payload, "before"),
|
|
35843
|
+
after: listCount(payload, "after"),
|
|
35827
35844
|
note: typeof payload.note === "string" ? payload.note : "",
|
|
35828
35845
|
line: i + 1
|
|
35829
35846
|
});
|
|
@@ -38830,6 +38847,9 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38830
38847
|
const ceilings = new Map;
|
|
38831
38848
|
const measured = new Map;
|
|
38832
38849
|
const unmetered = new Map;
|
|
38850
|
+
const open = new Map;
|
|
38851
|
+
const openStage = new Map;
|
|
38852
|
+
const lost = new Map;
|
|
38833
38853
|
const storyKeys = new Set;
|
|
38834
38854
|
const storyPhases = new Set;
|
|
38835
38855
|
let excluded = 0;
|
|
@@ -38878,6 +38898,39 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38878
38898
|
else
|
|
38879
38899
|
measured.set(turn.key, round9((measured.get(turn.key) ?? 0) + turn.costUsd));
|
|
38880
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
|
+
}
|
|
38881
38934
|
return {
|
|
38882
38935
|
rows: order.map((story) => ({
|
|
38883
38936
|
story,
|
|
@@ -38889,6 +38942,14 @@ function storyLedger(runDir2, scope = null, extraTurns = []) {
|
|
|
38889
38942
|
events: events.length
|
|
38890
38943
|
};
|
|
38891
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
|
+
}
|
|
38892
38953
|
function buildStoryCost(runDir2) {
|
|
38893
38954
|
let title = "";
|
|
38894
38955
|
let runId = basename13(runDir2);
|
|
@@ -39006,7 +39067,7 @@ function storyVerdict(cost) {
|
|
|
39006
39067
|
return "no total: a story above is missing one side, and an absent figure is not summed as zero.";
|
|
39007
39068
|
}
|
|
39008
39069
|
if (floor) {
|
|
39009
|
-
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.";
|
|
39010
39071
|
}
|
|
39011
39072
|
return "every story measured inside the spawn ceilings it was given.";
|
|
39012
39073
|
}
|
|
@@ -39035,7 +39096,7 @@ function renderStoryCost(cost) {
|
|
|
39035
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.");
|
|
39036
39097
|
}
|
|
39037
39098
|
if (cost.unmeteredStories.length > 0) {
|
|
39038
|
-
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.`);
|
|
39039
39100
|
}
|
|
39040
39101
|
if (cost.excludedKeyedResults > 0)
|
|
39041
39102
|
lines.push(` ${excludedLine(cost)}`);
|
|
@@ -39104,6 +39165,7 @@ function readBudget(runDir2) {
|
|
|
39104
39165
|
var ECONOMY_WIDTH = 12;
|
|
39105
39166
|
var DASH = "—";
|
|
39106
39167
|
var NOT_RECORDED = "not recorded";
|
|
39168
|
+
var LOWER_BOUND_MARK = "LOWER BOUND";
|
|
39107
39169
|
function tokenColumns(t, declared2 = 0) {
|
|
39108
39170
|
const measured = `${bigTokens(t.input)} in · ${bigTokens(t.output)} out · ` + `${bigTokens(t.cacheCreation)} cache write · ${bigTokens(t.cacheRead)} cache read`;
|
|
39109
39171
|
if (declared2 <= 0)
|
|
@@ -39287,7 +39349,7 @@ async function buildExecutor(ctx) {
|
|
|
39287
39349
|
try {
|
|
39288
39350
|
await session.restoreForeignWorkAside();
|
|
39289
39351
|
} catch {}
|
|
39290
|
-
throw error;
|
|
39352
|
+
throw withPartialTasks(error, session.tasks);
|
|
39291
39353
|
}
|
|
39292
39354
|
}
|
|
39293
39355
|
function refusedOnBase(session, error) {
|
|
@@ -41173,14 +41235,33 @@ class BuildSession {
|
|
|
41173
41235
|
const measured = measuredWidening(changed, declared2);
|
|
41174
41236
|
if (measured === null)
|
|
41175
41237
|
return;
|
|
41176
|
-
|
|
41177
|
-
|
|
41178
|
-
|
|
41179
|
-
|
|
41180
|
-
|
|
41181
|
-
|
|
41182
|
-
|
|
41183
|
-
|
|
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
|
+
}
|
|
41184
41265
|
}
|
|
41185
41266
|
orderedOutcomes() {
|
|
41186
41267
|
const rows = [];
|
|
@@ -41791,6 +41872,20 @@ function failed3(ctx, error, tasks) {
|
|
|
41791
41872
|
}
|
|
41792
41873
|
|
|
41793
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
|
+
}
|
|
41794
41889
|
var EXECUTORS = new Map([
|
|
41795
41890
|
[BUILD_PHASE, buildExecutor],
|
|
41796
41891
|
[WATCH_PHASE, watchExecutor]
|
|
@@ -42535,14 +42630,33 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42535
42630
|
outcome = await executor(executorCtx);
|
|
42536
42631
|
} catch (error) {
|
|
42537
42632
|
const why = error instanceof Error ? error.message : String(error);
|
|
42538
|
-
|
|
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", {
|
|
42539
42647
|
phase: phaseId,
|
|
42540
42648
|
where: "executor",
|
|
42541
42649
|
detail: why,
|
|
42542
|
-
tasks_recorded:
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
|
|
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);
|
|
42546
42660
|
}
|
|
42547
42661
|
if (isSequencingRefusal(outcome)) {
|
|
42548
42662
|
if (!started) {
|
|
@@ -42552,20 +42666,25 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42552
42666
|
return out(EXIT_USAGE3, [...notes, ...outcome.lines], [], executorSignature(outcome));
|
|
42553
42667
|
}
|
|
42554
42668
|
claimEpicBranches(store, outcome.epicBranches, outcome.branchModel);
|
|
42669
|
+
const progress = { rows: 0, events: 0 };
|
|
42555
42670
|
try {
|
|
42556
|
-
recordExecutorTasks(store, options, phaseId, stageId, spec, outcome);
|
|
42671
|
+
recordExecutorTasks(store, options, phaseId, stageId, spec, outcome.tasks, progress);
|
|
42557
42672
|
} catch (error) {
|
|
42558
42673
|
const why = error instanceof Error ? error.message : String(error);
|
|
42559
42674
|
const recorded = requireStage(store, phaseId, stageId).tasks.length;
|
|
42560
|
-
|
|
42675
|
+
const written = recorded - tasksBefore;
|
|
42676
|
+
const expected = outcome.tasks.length;
|
|
42677
|
+
store.save();
|
|
42678
|
+
const extra = appendOrSay(() => appendCappedEvent(store, options, phaseId, stageId, "error", {
|
|
42561
42679
|
phase: phaseId,
|
|
42562
42680
|
where: "record-tasks",
|
|
42563
42681
|
detail: why,
|
|
42564
|
-
rows_written:
|
|
42565
|
-
rows_expected:
|
|
42566
|
-
|
|
42567
|
-
|
|
42568
|
-
|
|
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);
|
|
42569
42688
|
}
|
|
42570
42689
|
store.save();
|
|
42571
42690
|
if (store.cancelledUnder) {
|
|
@@ -42595,11 +42714,22 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42595
42714
|
function isSequencingRefusal(outcome) {
|
|
42596
42715
|
return outcome.sequencing === true && outcome.refused === true && outcome.tasks.length === 0 && outcome.costUsd === 0 && (outcome.epicBranches?.length ?? 0) === 0;
|
|
42597
42716
|
}
|
|
42598
|
-
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 }) {
|
|
42599
42727
|
const version = frameworkVersionSync();
|
|
42600
|
-
|
|
42728
|
+
const rows = [];
|
|
42729
|
+
for (const task2 of tasks) {
|
|
42601
42730
|
const id = nextTaskId(store, phaseId, stageId);
|
|
42602
42731
|
const metered = task2.metered !== false;
|
|
42732
|
+
rows.push({ id, task: task2, metered });
|
|
42603
42733
|
recordTask(store, phaseId, stageId, {
|
|
42604
42734
|
id,
|
|
42605
42735
|
status: task2.error === null ? "done" : "failed",
|
|
@@ -42618,6 +42748,9 @@ function recordExecutorTasks(store, options, phaseId, stageId, spec, outcome) {
|
|
|
42618
42748
|
...cacheSplit(task2.usage?.cache_creation_input_tokens, task2.usage?.cache_read_input_tokens),
|
|
42619
42749
|
...task2.durationMs === undefined ? {} : { duration_ms: task2.durationMs, duration_basis: "spawned" }
|
|
42620
42750
|
});
|
|
42751
|
+
progress.rows += 1;
|
|
42752
|
+
}
|
|
42753
|
+
for (const { id, task: task2, metered } of rows) {
|
|
42621
42754
|
appendCappedEvent(store, options, phaseId, stageId, "agent.result", {
|
|
42622
42755
|
phase: phaseId,
|
|
42623
42756
|
task: id,
|
|
@@ -42632,6 +42765,7 @@ function recordExecutorTasks(store, options, phaseId, stageId, spec, outcome) {
|
|
|
42632
42765
|
...task2.tokens === undefined ? {} : { tokens: task2.tokens },
|
|
42633
42766
|
...task2.usage === undefined ? {} : { usage: usagePayload(task2.usage) }
|
|
42634
42767
|
}, metered ? round26(task2.costUsd) : 0);
|
|
42768
|
+
progress.events += 1;
|
|
42635
42769
|
}
|
|
42636
42770
|
}
|
|
42637
42771
|
async function commitStage(store, options, phaseId, stageId, spec, notes) {
|
|
@@ -53512,6 +53646,8 @@ import { isAbsolute as isAbsolute13, join as join135 } from "node:path";
|
|
|
53512
53646
|
import { existsSync as existsSync111, readFileSync as readFileSync98, readdirSync as readdirSync34 } from "node:fs";
|
|
53513
53647
|
import { basename as basename17, join as join131 } from "node:path";
|
|
53514
53648
|
// src/core/replay/renderReplay.ts
|
|
53649
|
+
var STAGE_FAILED_MARKER = "FAILED: ";
|
|
53650
|
+
var NO_REASON_RECORDED = "no reason recorded";
|
|
53515
53651
|
function renderReplay(loaded) {
|
|
53516
53652
|
const run2 = loaded.run;
|
|
53517
53653
|
const out2 = [
|
|
@@ -53639,9 +53775,9 @@ function bullet2(item, trail) {
|
|
|
53639
53775
|
case "stage.done":
|
|
53640
53776
|
return `${prefix5}ended`;
|
|
53641
53777
|
case "stage.failed":
|
|
53642
|
-
return `${prefix5}
|
|
53778
|
+
return `${prefix5}${STAGE_FAILED_MARKER}${text3(payload2.reason) || NO_REASON_RECORDED}`;
|
|
53643
53779
|
case "stage.skipped":
|
|
53644
|
-
return `${prefix5}skipped: ${text3(payload2.reason) ||
|
|
53780
|
+
return `${prefix5}skipped: ${text3(payload2.reason) || NO_REASON_RECORDED}`;
|
|
53645
53781
|
case "question.asked":
|
|
53646
53782
|
return `${prefix5}${q || "a question"} asked by ${actor}: ${text3(payload2.question)}`.trimEnd();
|
|
53647
53783
|
case "question.answered":
|
|
@@ -53657,7 +53793,7 @@ function bullet2(item, trail) {
|
|
|
53657
53793
|
case "story.reopened":
|
|
53658
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)}`;
|
|
53659
53795
|
case "story.touches_widened":
|
|
53660
|
-
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)}`;
|
|
53661
53797
|
case "story.base_fastforwarded":
|
|
53662
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))`;
|
|
53663
53799
|
case "story.base_updated":
|
|
@@ -53694,8 +53830,11 @@ function bullet2(item, trail) {
|
|
|
53694
53830
|
return `${prefix5}map refreshed`;
|
|
53695
53831
|
case "result.unreadable":
|
|
53696
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)";
|
|
53697
|
-
case "error":
|
|
53698
|
-
|
|
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
|
+
}
|
|
53699
53838
|
default:
|
|
53700
53839
|
return null;
|
|
53701
53840
|
}
|
|
@@ -53753,8 +53892,12 @@ function pathList(value) {
|
|
|
53753
53892
|
const items = value.filter((item) => typeof item === "string");
|
|
53754
53893
|
return items.length === 0 ? "?" : items.join(", ");
|
|
53755
53894
|
}
|
|
53756
|
-
function
|
|
53757
|
-
|
|
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}`;
|
|
53758
53901
|
}
|
|
53759
53902
|
function signature(actor, payload2) {
|
|
53760
53903
|
const executed = payload2.executed_by;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework - five stages, a gate on every one, and every claim cited or refused. Installs the `tldrx` (and `tldr-experts`) command. Beta.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alan Martinez",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$doc": "Shape verified from https://code.claude.com/docs/en/plugins.md (Quickstart > Create the plugin manifest). Fields used here: name, description, version, author.name. Only plugin.json goes inside .claude-plugin/; skills/, agents/ and hooks/ live at the plugin root.",
|
|
3
3
|
"name": "tldrx",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework. Five stages, a gate on every one, every claim cited or refused. Beta.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.26.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|
|
File without changes
|