tldr-experts 0.16.1 → 0.17.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 +89 -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-gv3sj8gv.js → chunk-g8kkq85r.js} +4 -3
- package/dist/hooks/{chunk-rz0qr006.js → chunk-jp6jscsd.js} +3 -0
- package/dist/hooks/{chunk-z0hnthw4.js → chunk-tj66vg1n.js} +1 -1
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +43 -10
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **A `gate.requested` payload says what is HOLDING the gate, and hands over the rejection that
|
|
8
|
+
carries on (#243).** The owner's phone renders a gate as two buttons, Yes and No, and the No
|
|
9
|
+
branch rejects nothing — it logs *"gate stays open"*. That is not the adapter being lazy: it is
|
|
10
|
+
the adapter working with what it was handed. Of the three things #239 taught the framework to
|
|
11
|
+
distinguish, exactly one travelled as data — `detail.stories` says there is unbuilt work —
|
|
12
|
+
while "there are open questions" travelled in nothing but the PREFIX of `command`
|
|
13
|
+
(`tldrx answer …`). Routing on that is a second, untested, out-of-repo copy of the mapping,
|
|
14
|
+
and it goes quietly wrong the day a command is reworded. So the branch `clearingCommand`
|
|
15
|
+
already takes is now NAMED: `detail.holding` is `questions`, `stories` or `none`, and
|
|
16
|
+
`clearingCommand` switches on it, so the field and the command are two renderings of one
|
|
17
|
+
derivation and cannot disagree (§7). And the payload now spells the other half of what a third
|
|
18
|
+
button needs: `detail.continue_command` is `tldrx reject --run <id> --and-continue --note "…"`
|
|
19
|
+
— #242's verb, with the substitutable `…` `answer_command` established, rather than
|
|
20
|
+
`reject_command`'s `<why>`, which is prose for a human and would have been sent literally as
|
|
21
|
+
the next turn's prompt. Its note is DERIVED, never canned: `continue_note` is the blocked
|
|
22
|
+
story and the handoff's own reason for it, the same two facts already on the payload as
|
|
23
|
+
`blocked_story` and `blocked_reason`. Both keys are absent together wherever the gate cannot
|
|
24
|
+
name what has to change — held by open questions (the gate is downstream of them, and a
|
|
25
|
+
one-tap refusal there is the mirror of the mistake #239 was filed over), held by nothing
|
|
26
|
+
mechanical (the reason to refuse a judgement is in a person's head), and held by stories none
|
|
27
|
+
of which is blocked or whose blocked one recorded no reason. `--note` is mechanically required
|
|
28
|
+
and becomes the next turn's prompt, so a canned *"rejected from Slack"* would satisfy the flag
|
|
29
|
+
and empty the rule it exists for: a button that hands the re-run an empty instruction is worse
|
|
30
|
+
than no button. Every key is additive — a consumer that reads none of them gets exactly the
|
|
31
|
+
payload it got before. Which buttons an adapter draws, and how, stays the adapter's.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`run status` could print more money left than it had ceiling, and the fix was to delete a
|
|
36
|
+
copy rather than synchronise one (#236).** Measured live on a hosted run after four raises:
|
|
37
|
+
`budget $0.00 spent of $190.00 ceiling ($200.00 left)` — a sentence that cannot be true. The
|
|
38
|
+
two figures came from two files. The remainder has always been `budget.yml`'s; the ceiling was
|
|
39
|
+
`run.yml`'s `budget.ceiling_usd`, and the issue's own diagnosis — that `budget raise` never
|
|
40
|
+
wrote that key — was WRONG: it wrote it, on the line after it wrote `budget.yml`, and had since
|
|
41
|
+
the command's first commit. What defeated it was concurrency. `RunStore.save()` re-reads
|
|
42
|
+
budget.yml's ceilings from disk before every write, precisely because a store that loaded the
|
|
43
|
+
file an hour ago must not clobber a raise (`ceilingsToWrite`); run.yml's mirror had no such
|
|
44
|
+
re-read, so `rollUp` carried the pre-raise value straight back out. A long-lived `run auto`
|
|
45
|
+
saving after an operator's raise reverted the mirror, and the ceiling on screen fell below the
|
|
46
|
+
remainder beside it. Nothing decided against the stale figure — every refusal, gate and brake
|
|
47
|
+
already reads budget.yml (measured: all 47 `ceiling_usd` references read; the mirror's four
|
|
48
|
+
readers are all display), so this was a lie on a screen and not money spent wrongly, which is
|
|
49
|
+
the part the issue left open. The fix follows §7 rather than the obvious repair: two copies
|
|
50
|
+
that must both be fresh is the shape the house rule forbids, and writing both harder is exactly
|
|
51
|
+
what concurrency beat. So the live readers — `run status`, the open-runs table, `tldrx
|
|
52
|
+
statusline`'s validated path and the dashboard's headline — now read budget.yml, the same field
|
|
53
|
+
`budget show` reads, and `budget raise` no longer writes the mirror at all. run.yml's
|
|
54
|
+
`budget.ceiling_usd` and `per_agent_max_usd` are documented for what they always were: the
|
|
55
|
+
figures the run was CREATED with. That is not a `version: 1` meaning change — the keys are
|
|
56
|
+
required and still written, they held the creation ceiling before this change too, and what
|
|
57
|
+
moved is only that nothing reads them as the current one. The corollary is the part worth
|
|
58
|
+
writing down, because the first cut of this change got it wrong and a pre-merge review caught
|
|
59
|
+
it: run.yml's budget block is **half live**. `spent_usd` is re-derived by `rollUp` on every
|
|
60
|
+
save; `ceiling_usd` beside it is frozen. Printing the two as one sentence therefore reproduces
|
|
61
|
+
the same defect somewhere else, which is precisely what `tldrx replay` did — measured through
|
|
62
|
+
the CLI on a run raised $10 → $30 with $12 spent: `Status: **pending** · $12.00 spent of $10.00
|
|
63
|
+
ceiling`, no corruption and no concurrency needed, just a raise. "A replay narrates the
|
|
64
|
+
document" does not rescue it when the document itself is mixed. So the ceiling is resolved once
|
|
65
|
+
in `replay/loadRun.ts`, the single place both files are in hand, and `tldrx replay` and the
|
|
66
|
+
dashboard share that derivation. Swept for the same class: the only other frozen keys in
|
|
67
|
+
run.yml are `budget.per_agent_max_usd`, which no reader anywhere displays, and `created_with`,
|
|
68
|
+
which is frozen on purpose and paired with `last_written_by` to show exactly that difference.
|
|
69
|
+
One reader stays on the mirror by design: the statusline's tolerant fallback, which runs only
|
|
70
|
+
when run.yml fails validation, where a second tolerant parser for budget.yml would be a worse
|
|
71
|
+
trade than a figure on an already-degraded screen.
|
|
72
|
+
|
|
73
|
+
- **A task row now says which ROLE took the turn, instead of filing every Build turn under the
|
|
74
|
+
developer (#234).** `run.yml`'s `tasks[].expert` is the STAGE's expert — one value copied onto
|
|
75
|
+
every row from `stage.experts[0]` — and a Build stage declares `experts: [developer]` while
|
|
76
|
+
running two roles under it. So the reviewer's turn, with its own money, its own token split
|
|
77
|
+
and its own measured span, was recorded as the developer's, and the golden fixtures had that
|
|
78
|
+
frozen: three of the four Build scenarios shipped a `fake-reviewer-*` session labelled
|
|
79
|
+
`developer`. The role was never unknown — the executor spawns under it and emits it on
|
|
80
|
+
`agent.spawned` — it was thrown away one function later, because `ExecutorTask` had nowhere to
|
|
81
|
+
put it. Nothing downstream computed a wrong NUMBER from this (no reader reads the field; the
|
|
82
|
+
per-role cost report it looked like it fed does not exist), which is exactly why it was worth
|
|
83
|
+
fixing now rather than after something started reading it: what was broken is the audit
|
|
84
|
+
record, and §7's rule is that those never lie in the dangerous direction. The fix is a new
|
|
85
|
+
additive `tasks[].role`, NOT a new meaning for `expert` — a `version: 1` field never changes
|
|
86
|
+
what it says — carried from the four places the Build executor records a turn. A turn whose
|
|
87
|
+
role nothing recorded carries no key at all: absent is "not recorded", never a `developer` of
|
|
88
|
+
convenience, which is the same guess the bug was made of. Rows written before the key exist
|
|
89
|
+
unchanged and still validate. `docs/spec.md` documents the new field and, while it was open,
|
|
90
|
+
the `tasks[].expert` that has been written on every row since 0.1 and was never documented.
|
|
91
|
+
|
|
3
92
|
## 0.16.1 — 2026-09-12
|
|
4
93
|
|
|
5
94
|
### Added
|
package/README.md
CHANGED
|
@@ -316,6 +316,7 @@ back on the registry is 0.3.0.
|
|
|
316
316
|
|
|
317
317
|
| Version | Date | Status | Contains |
|
|
318
318
|
|---|---|---|---|
|
|
319
|
+
| 0.17.0 | 2026-09-12 | `beta` | three things the framework knew and recorded wrongly, each found by reading its own records rather than by using it: a gate notification now carries the condition HOLDING the gate as data instead of leaving it to be guessed from the text of the command it suggests — `holding` says questions, stories or nothing-mechanical, and when a story is blocked with a recorded reason the payload also hands over a ready-made rejection that lets the loop carry on, with the note DERIVED from the blocked story rather than canned, because a rejection's note is fed to the next turn's prompt and a generic one would hand that turn an empty instruction, so when no reason can be derived the offer is absent rather than invented; the emitted command keeps a literal placeholder and never interpolates the reason, which keeps the quoting hazard out of the record and puts it where a substituting client can see it; `run status` stopped printing more money left than it had ceiling — the run ceiling now has ONE live copy, read from `budget.yml` by every live screen, and `run.yml`'s mirror is documented as the creation value and no longer written by a raise, since the break was never that a raise failed to write the mirror but that an ordinary concurrent save carried a stale copy over it, and the fix was to delete the half-sync rather than to build a better one — pre-merge review caught the first version pairing a LIVE spend with that now-frozen ceiling inside `tldrx replay`, which would have reproduced the same impossible line in a different command on every run whose budget had been raised, measured on the real CLI as `$12.00 spent of $10.00 ceiling` with no concurrency required; and a task row now records the ROLE its turn actually ran under, where every Build turn had been filed as the developer including the reviewer's — the role was known at spawn and written to the event stream, and was dropped on the way to the ledger, so the audit record named the wrong actor for work it had itself measured; the new key is additive and written on EVERY row including the developer's, because a role present only on reviewers would make its absence mean developer-or-not-recorded and send a reader back to inferring the role from an absence, which is the guess the change exists to remove |
|
|
319
320
|
| 0.16.1 | 2026-09-12 | `beta` | five things the framework knew and did not say, or said wrong — four of them found by using it rather than by reading it: a red base pre-flight now KEEPS its output, so a refusal that blocks every story in a Build names the failing test and cites the file, where it used to record only the last line of stdout — measured 2026-09-10, a stage refused with `tail: "Test run completed with non-success exit code: 2"` while the cause, `DockerUnavailableException`, sat on line 12 of 163,702 lines the run had already captured and thrown away, so diagnosing a refusal the framework had itself measured meant re-running the workspace's test command by hand; it now routes through the same seam #211 built for a story's DoD, which had been naming its failing test correctly all along on the same command, the same day, in the same repo — the path with the SMALLER blast radius was the legible one; a gate notification now offers the command that CLEARS it rather than always `tldrx approve` — questions open give `tldrx answer <id>`, unfinished stories give `tldrx run status`, and `approve` is offered only when nothing mechanical is outstanding, after an owner approved a Build gate by mistake twice in one evening over unbuilt stories, each time from a phone, each time needing a revoke, while a ten-minute heartbeat repeated `Run: tldrx approve` seven times under a sentence that correctly named the five open questions holding it; `tldrx reject --and-continue` lets a rejection mean "redo it this way and carry on" instead of ending the run — the loop resumed after an approve and stopped after a reject, so the button meaning "there is still work to do" was the one that stopped the work and only a terminal could revive it; five real rejections that night all meant continue, five cost a manual relaunch, and a bare `tldrx reject` still writes a byte-identical `run.yml` and stops exactly as before; the expert-recompute fixture anchors its evidence dates to a `now` it can move, so `bun test` stops going red by the calendar — pristine `main` was red at the exact sha of the published 0.16.0 with no commit in between, and a clock moved one year forward reddened FIVE cases, not the one that had already fired; and `test/merge-wave.test.ts`'s concurrency failure, documented as a known flake since #115 and carrying a written licence to re-run it, was never one: `merge-guard.sh` rewrote `.git/hooks/reference-transaction` IN PLACE while a sibling wave's `git merge` was exec'ing it — ETXTBSY on Linux at 31% under contention, benign on macOS, which is why it was green locally and red in CI, and why a same-sha re-run failed 2 for 2 rather than passing; the hook is now written to a temp file and RENAMED into place, the refusal that used to borrow `2`/`merge conflict` for a hook abort now says what it was and exits 11, and AGENTS.md §4 withdraws the re-run licence for those two cases while naming the interrupted-merge case (#237) as still open and undiagnosed — because "all real" for a whole file costs the same as "all flake", in the other direction |
|
|
320
321
|
| 0.16.0 | 2026-09-11 | `beta` | an unattended run can now clear the one kind of failure it was stopping on, and a gate that refuses says why it refused: measured 2026-09-10 on a real unattended `run auto`, the loop drove itself through what → how → plan and signed all three `auto` gates by itself, and still needed a person four times — three of those were content or money decisions a loop must not make, and the fourth was a plan that failed its own check by five characters over a cap, where a person relaunched the same command and the next attempt fixed the two files and passed, so the loop stopped on the one failure it could have cleared; `tldrx run auto --retry-failed <n>` now runs a failed stage again at most `n` times in a row, bounding exit `5` and nothing else — a usage error (`1`), a money refusal (`2`) and an awaiting-human park (`4`) are each attempted ONCE however large `n` is, because a phase ceiling means a human decides about money and a retry would turn that sentence into a delay — only CONSECUTIVE failures count since what is bounded is "this run is stuck" and not "this run has ever failed", a retry SPENDS as a fresh metered stage under the same phase ceiling and the same `--max-usd`, `0` is the default and a default invocation's lines are byte-identical to what they were, and when the bound is spent the loop stops on the failure's own exit `5` and says the count LAST, so the sentence that reaches a phone is what the loop tried and not a bare number; and an `auto` gate that REFUSES now writes down the verdict its note was always designed to carry — a gate sat pending ~40 minutes while `run status` and `--verbose` named no condition at all, and the reason surfaced only when a person guessed at the `tldrx approve` the status line suggested, which is the one route nobody unattended is going to take — recording all seven conditions WITH THEIR VALUES on the still-`pending` gate, since a note that dropped the passing ones would answer "was it the money" with the same silence, and naming the holding ids on the gate row and on the `waiting` line; it writes only over a `pending` gate, so a gate a person has since signed keeps THEIR words, and only when the verdict would change, so a four-hour `--wait-gates` poll writes once per distinct verdict rather than thousands of times — and that test and that write are a compare-and-set under the workspace lock, because pre-merge review reproduced, with two real processes, a check-then-act over an earlier snapshot erasing a concurrent `approve` outright, and the poll runs every two seconds precisely while a person is deciding |
|
|
321
322
|
| 0.15.0 | 2026-09-10 | `beta` | defaults for the models actually running today, and records that name what happened: measured 2026-09-07/09 across three real workspaces, the first engine-driven run of each was ended by a calibration rather than by the work — a `how` turn and two Build developer turns killed at a 900 s per-turn clock while Opus turns on real repositories run 15-50 minutes, a 202 KB prompt refused by a ceiling whose own message called it "29% of a 200k window", and a 169 KB `facts.yml` sliced to 96 KB on its way into a design turn that then died. So a turn gets two hours (`timeout_s` 900 → 7200), a prompt 400 KB and inputs 256 KB, a phase ceiling holds every attempt its stages may take so the first retry of a stage that spent anything is no longer refused by arithmetic — `warn_at_pct` still measured against one attempt's share, so the warning still arrives before the money — and the four numbers that were calibrations rather than invariants (`attempts`, `fixlist_rounds`, `reviewer_share`, `gate_signer_share`) became optional `stage.yml` keys, refused by name out of range instead of clamped, absent meaning today's constant byte for byte, with `tldrx run auto --prompt-max-bytes` and `--max-reads` for the unattended run that would otherwise need a file edit to get past one refusal; a story's Definition of Done now runs with its dependencies installed — the `install:` slot has sat unread in `templates/workspace.yml` since the beginning and now runs in every fresh story worktree through the same allowlist-and-argv runner, recorded with its own exit code and duration, blocking the story rather than paying a turn to discover it — an exit 127 is reported as a named absent binary and not as a red test, a declared command may be run WITH ARGUMENTS (the exact `Bash(npm run test)` grant matched nothing the developer actually typed, so its own 127 was first seen by the gate, after the turn was paid for), and every DoD check says which tree it ran in; the Build gate now names story outcomes on every policy and not only `auto` — two runs approved from a phone printed `run is done` over zero stories delivered — `run.yml` records an additive `outcome:` written once by all three commands that close a run and rendered by six surfaces, and `tldrx ship` refuses with exit 1 instead of opening a PR over nothing; a red DoD keeps its real failure — the last 200 lines on disk (gitignored, since a tail can carry a secret), up to five failure-looking lines as the detail rather than the last `DeprecationWarning` on stderr, the failing line cited at the line it starts on, and the next attempt told it was the check and not a reviewer; a watcher card may honestly say `Query: none — <reason> [src: …]`, earned only over a card whose own `## Signal` cites `absent:` and refused like any unsourced item otherwise, after a stage spent real money writing the honest answer and was refused for it; a truncated input is told to the OWNER at spawn and not only to the sub-agent, a turn killed on timeout keeps the usage it had already streamed and never a price; and the maintain skill says which sha a review record must cite — the code head — a rule that cost a wave and was written down nowhere an agent reads |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-tj66vg1n.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-jp6jscsd.js";
|
|
17
17
|
import {
|
|
18
18
|
PHASE_IDS
|
|
19
19
|
} from "./chunk-d0rp8c68.js";
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
isTerminal,
|
|
22
22
|
stageAt,
|
|
23
23
|
validateRunFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-jp6jscsd.js";
|
|
25
25
|
import {
|
|
26
26
|
cursorStage,
|
|
27
27
|
isAttendedByHostView,
|
|
@@ -109,8 +109,9 @@ function task(t, indent) {
|
|
|
109
109
|
const tokens = t.tokens === undefined ? "" : `, tokens: ${String(t.tokens)}`;
|
|
110
110
|
const inTokens = t.input_tokens === undefined ? "" : `, input_tokens: ${String(t.input_tokens)}`;
|
|
111
111
|
const outTokens = t.output_tokens === undefined ? "" : `, output_tokens: ${String(t.output_tokens)}`;
|
|
112
|
+
const role = t.role === undefined ? "" : `, role: ${yamlScalar(t.role)}`;
|
|
112
113
|
return [
|
|
113
|
-
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens}${inTokens}${outTokens},`,
|
|
114
|
+
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}${role}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens}${inTokens}${outTokens},`,
|
|
114
115
|
`${inner}error: ${yamlScalar(t.error)}, session_id: ${yamlScalar(t.session_id)},`,
|
|
115
116
|
`${inner}started_at: ${yamlScalar(t.started_at)}, ended_at: ${yamlScalar(t.ended_at)},`,
|
|
116
117
|
...t.stopped_by === undefined || t.stopped_by === null ? [] : [`${inner}stopped_by: ${yamlScalar(t.stopped_by)},`],
|
|
@@ -606,7 +607,7 @@ function fromStore(root) {
|
|
|
606
607
|
expert: entry?.stage.expert ?? null,
|
|
607
608
|
done: stages.filter((stage2) => isTerminal(stage2.status)).length,
|
|
608
609
|
total: stages.length,
|
|
609
|
-
ceilingUsd:
|
|
610
|
+
ceilingUsd: store.budget.ceiling_usd,
|
|
610
611
|
spentUsd: run.budget.spent_usd,
|
|
611
612
|
openCount: open.length,
|
|
612
613
|
machineGates: stages.filter((s) => s.gate.status === "approved" && closedByMachine(s.gate)).length,
|
|
@@ -700,6 +700,9 @@ function validateRunFile(input) {
|
|
|
700
700
|
requireEnum(task.status, STAGE_STATUSES, `${tp}.status`, issues);
|
|
701
701
|
if (task.cost_usd !== null)
|
|
702
702
|
requireNumber(task.cost_usd, `${tp}.cost_usd`, issues);
|
|
703
|
+
if (task.role !== undefined && typeof task.role !== "string") {
|
|
704
|
+
issues.push({ path: `${tp}.role`, message: "expected a string" });
|
|
705
|
+
}
|
|
703
706
|
if (task.metered !== undefined && typeof task.metered !== "boolean") {
|
|
704
707
|
issues.push({ path: `${tp}.metered`, message: "expected true or false" });
|
|
705
708
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
questionsCard
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-tj66vg1n.js";
|
|
5
5
|
import"./chunk-54vzevgt.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-g8kkq85r.js";
|
|
24
24
|
import {
|
|
25
25
|
expertsDir,
|
|
26
26
|
loadExperts,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "./chunk-3kmx3dmz.js";
|
|
31
31
|
import {
|
|
32
32
|
isFinished
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-jp6jscsd.js";
|
|
34
34
|
import"./chunk-d0rp8c68.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-g8kkq85r.js";
|
|
6
6
|
import"./chunk-3kmx3dmz.js";
|
|
7
|
-
import"./chunk-
|
|
7
|
+
import"./chunk-jp6jscsd.js";
|
|
8
8
|
import"./chunk-d0rp8c68.js";
|
|
9
9
|
import"./chunk-4mjxyfp9.js";
|
|
10
10
|
import"./chunk-k4nqzdw5.js";
|
package/dist/tldrx.js
CHANGED
|
@@ -11912,8 +11912,9 @@ function task(t, indent) {
|
|
|
11912
11912
|
const tokens = t.tokens === undefined ? "" : `, tokens: ${String(t.tokens)}`;
|
|
11913
11913
|
const inTokens = t.input_tokens === undefined ? "" : `, input_tokens: ${String(t.input_tokens)}`;
|
|
11914
11914
|
const outTokens = t.output_tokens === undefined ? "" : `, output_tokens: ${String(t.output_tokens)}`;
|
|
11915
|
+
const role = t.role === undefined ? "" : `, role: ${yamlScalar(t.role)}`;
|
|
11915
11916
|
return [
|
|
11916
|
-
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens}${inTokens}${outTokens},`,
|
|
11917
|
+
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}${role}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens}${inTokens}${outTokens},`,
|
|
11917
11918
|
`${inner}error: ${yamlScalar(t.error)}, session_id: ${yamlScalar(t.session_id)},`,
|
|
11918
11919
|
`${inner}started_at: ${yamlScalar(t.started_at)}, ended_at: ${yamlScalar(t.ended_at)},`,
|
|
11919
11920
|
...t.stopped_by === undefined || t.stopped_by === null ? [] : [`${inner}stopped_by: ${yamlScalar(t.stopped_by)},`],
|
|
@@ -13292,6 +13293,9 @@ function validateRunFile(input) {
|
|
|
13292
13293
|
requireEnum(task2.status, STAGE_STATUSES, `${tp}.status`, issues);
|
|
13293
13294
|
if (task2.cost_usd !== null)
|
|
13294
13295
|
requireNumber(task2.cost_usd, `${tp}.cost_usd`, issues);
|
|
13296
|
+
if (task2.role !== undefined && typeof task2.role !== "string") {
|
|
13297
|
+
issues.push({ path: `${tp}.role`, message: "expected a string" });
|
|
13298
|
+
}
|
|
13295
13299
|
if (task2.metered !== undefined && typeof task2.metered !== "boolean") {
|
|
13296
13300
|
issues.push({ path: `${tp}.metered`, message: "expected true or false" });
|
|
13297
13301
|
}
|
|
@@ -28980,6 +28984,14 @@ function gateStoriesPayload(view) {
|
|
|
28980
28984
|
...blocked === null ? {} : { blocked_story: blocked.id, blocked_reason: blocked.reason }
|
|
28981
28985
|
};
|
|
28982
28986
|
}
|
|
28987
|
+
function continueNote(view) {
|
|
28988
|
+
const blocked = view.firstBlocked;
|
|
28989
|
+
if (blocked === null)
|
|
28990
|
+
return null;
|
|
28991
|
+
if (blocked.reason === REASON_NOT_RECORDED)
|
|
28992
|
+
return null;
|
|
28993
|
+
return `${blocked.id} is blocked: ${blocked.reason}. Redo ${blocked.id}.`;
|
|
28994
|
+
}
|
|
28983
28995
|
var REASON_IN_SUMMARY = 80;
|
|
28984
28996
|
var NAMED_IN_SUMMARY = 3;
|
|
28985
28997
|
function deliveredPhrase(view) {
|
|
@@ -29959,7 +29971,7 @@ function buildStatus(run, budget, runDir) {
|
|
|
29959
29971
|
phases,
|
|
29960
29972
|
budget: {
|
|
29961
29973
|
spent_usd: run.budget.spent_usd,
|
|
29962
|
-
ceiling_usd:
|
|
29974
|
+
ceiling_usd: budget.ceiling_usd,
|
|
29963
29975
|
remaining_usd: remaining(budget)
|
|
29964
29976
|
},
|
|
29965
29977
|
attempts: stageAttempts(runDir, run.cursor.phase, run.cursor.stage),
|
|
@@ -30166,7 +30178,7 @@ function openRunRow(store) {
|
|
|
30166
30178
|
cursor: `${run.cursor.phase}/${run.cursor.stage}`,
|
|
30167
30179
|
waiting: whatIsWaiting(run, store.runDir).kind,
|
|
30168
30180
|
spentUsd: run.budget.spent_usd,
|
|
30169
|
-
ceilingUsd:
|
|
30181
|
+
ceilingUsd: store.budget.ceiling_usd
|
|
30170
30182
|
};
|
|
30171
30183
|
}
|
|
30172
30184
|
function openRunRows(stores) {
|
|
@@ -30301,6 +30313,9 @@ function approveCommand(runId) {
|
|
|
30301
30313
|
function rejectCommand(runId) {
|
|
30302
30314
|
return `tldrx reject --run ${runId} --note "<why>"`;
|
|
30303
30315
|
}
|
|
30316
|
+
function continueCommand(runId) {
|
|
30317
|
+
return `tldrx reject --run ${runId} --and-continue --note "…"`;
|
|
30318
|
+
}
|
|
30304
30319
|
function toQuestion(block2, runId, recommendations) {
|
|
30305
30320
|
const recommended = recommendations.get(block2.id);
|
|
30306
30321
|
return {
|
|
@@ -30675,17 +30690,28 @@ function gatePhrase(policy) {
|
|
|
30675
30690
|
}
|
|
30676
30691
|
}
|
|
30677
30692
|
function clearingCommand(runId, openQuestions2, unfinishedStories) {
|
|
30678
|
-
|
|
30679
|
-
|
|
30680
|
-
|
|
30693
|
+
switch (gateHolding(openQuestions2, unfinishedStories)) {
|
|
30694
|
+
case "questions":
|
|
30695
|
+
return answerCommand(openQuestions2[0] ?? "", runId);
|
|
30696
|
+
case "stories":
|
|
30697
|
+
return `tldrx run status ${runId}`;
|
|
30698
|
+
default:
|
|
30699
|
+
return approveCommand(runId);
|
|
30700
|
+
}
|
|
30701
|
+
}
|
|
30702
|
+
function gateHolding(openQuestions2, unfinishedStories) {
|
|
30703
|
+
if (openQuestions2[0] !== undefined)
|
|
30704
|
+
return "questions";
|
|
30681
30705
|
if (unfinishedStories > 0)
|
|
30682
|
-
return
|
|
30683
|
-
return
|
|
30706
|
+
return "stories";
|
|
30707
|
+
return "none";
|
|
30684
30708
|
}
|
|
30685
30709
|
function gateNotification(ctx, costUsd, policy = null, held2 = [], stories = null, openQuestions2 = []) {
|
|
30686
30710
|
const approve = approveCommand(ctx.runId);
|
|
30687
30711
|
const why = held2.length === 0 ? "" : policy === "auto" ? ` It is held by: ${held2.join("; ")}.` : ` The engine's signer held it: ${held2.join("; ")}.`;
|
|
30688
30712
|
const delivered = stories === null ? "" : ` It has ${deliveredPhrase(stories)}.`;
|
|
30713
|
+
const holding = gateHolding(openQuestions2, stories?.unfinished.length ?? 0);
|
|
30714
|
+
const note = holding === "stories" && stories !== null ? continueNote(stories) : null;
|
|
30689
30715
|
return {
|
|
30690
30716
|
...base(ctx, "gate.requested"),
|
|
30691
30717
|
summary: `${ctx.runId} finished ${ctx.stage ?? "a stage"} for $${costUsd.toFixed(2)} and is waiting ` + `at ${gateArticle(policy)} ${gatePhrase(policy)}.${delivered}${why} Nothing runs after it until the gate is ` + "approved or rejected.",
|
|
@@ -30696,6 +30722,8 @@ function gateNotification(ctx, costUsd, policy = null, held2 = [], stories = nul
|
|
|
30696
30722
|
reject_command: rejectCommand(ctx.runId),
|
|
30697
30723
|
...policy === null ? {} : { gate_policy: policy },
|
|
30698
30724
|
...stories === null ? {} : gateStoriesPayload(stories),
|
|
30725
|
+
holding,
|
|
30726
|
+
...note === null ? {} : { continue_command: continueCommand(ctx.runId), continue_note: note },
|
|
30699
30727
|
...held2.length === 0 ? {} : policy === "auto" ? { held_by: held2 } : { signer_held: held2 }
|
|
30700
30728
|
}
|
|
30701
30729
|
};
|
|
@@ -35099,6 +35127,7 @@ function loadRunResult(root2, id) {
|
|
|
35099
35127
|
budget = null;
|
|
35100
35128
|
}
|
|
35101
35129
|
}
|
|
35130
|
+
const withLiveCeiling = budget?.ceiling_usd === null || budget?.ceiling_usd === undefined ? run : { ...run, ceiling_usd: budget.ceiling_usd };
|
|
35102
35131
|
const { events, error, skipped, mtime } = readEvents2(dir);
|
|
35103
35132
|
return {
|
|
35104
35133
|
kind: "ok",
|
|
@@ -35106,7 +35135,7 @@ function loadRunResult(root2, id) {
|
|
|
35106
35135
|
root: root2,
|
|
35107
35136
|
dir,
|
|
35108
35137
|
id,
|
|
35109
|
-
run,
|
|
35138
|
+
run: withLiveCeiling,
|
|
35110
35139
|
budget,
|
|
35111
35140
|
events,
|
|
35112
35141
|
eventsError: error,
|
|
@@ -37181,6 +37210,7 @@ class BuildSession {
|
|
|
37181
37210
|
costUsd: cost ?? 0,
|
|
37182
37211
|
sessionId: result2.session_id,
|
|
37183
37212
|
error: null,
|
|
37213
|
+
role: "developer",
|
|
37184
37214
|
outputs: result2.outputs,
|
|
37185
37215
|
...cost === null ? { metered: false } : {},
|
|
37186
37216
|
...this.ctx.tokens === null ? {} : { tokens: this.ctx.tokens }
|
|
@@ -37739,6 +37769,7 @@ class BuildSession {
|
|
|
37739
37769
|
costUsd: round25(agent.costUsd),
|
|
37740
37770
|
sessionId: agent.sessionId,
|
|
37741
37771
|
error: agent.error,
|
|
37772
|
+
role: "developer",
|
|
37742
37773
|
outputs: agent.envelope?.outputs ?? [],
|
|
37743
37774
|
metered: agent.metered,
|
|
37744
37775
|
inputTokens: agent.usage.input_tokens,
|
|
@@ -37866,6 +37897,7 @@ class BuildSession {
|
|
|
37866
37897
|
costUsd: task2.costUsd,
|
|
37867
37898
|
sessionId: task2.sessionId,
|
|
37868
37899
|
error: null,
|
|
37900
|
+
role: "reviewer",
|
|
37869
37901
|
outputs: [],
|
|
37870
37902
|
...task2.metered ? {} : { metered: false },
|
|
37871
37903
|
...task2.tokens === undefined ? {} : { tokens: task2.tokens },
|
|
@@ -37939,6 +37971,7 @@ class BuildSession {
|
|
|
37939
37971
|
costUsd: task2.costUsd,
|
|
37940
37972
|
sessionId: task2.sessionId,
|
|
37941
37973
|
error: task2.error ?? null,
|
|
37974
|
+
role: "reviewer",
|
|
37942
37975
|
outputs: [],
|
|
37943
37976
|
...task2.metered ? {} : { metered: false },
|
|
37944
37977
|
...task2.tokens === undefined ? {} : { tokens: task2.tokens },
|
|
@@ -39491,6 +39524,7 @@ function recordExecutorTasks(store, options, phaseId, stageId, spec, outcome) {
|
|
|
39491
39524
|
id,
|
|
39492
39525
|
status: task2.error === null ? "done" : "failed",
|
|
39493
39526
|
expert: spec.planned.experts[0] ?? null,
|
|
39527
|
+
...task2.role === undefined ? {} : { role: task2.role },
|
|
39494
39528
|
model: task2.model,
|
|
39495
39529
|
cost_usd: metered ? round26(task2.costUsd) : null,
|
|
39496
39530
|
...metered ? {} : { metered: false },
|
|
@@ -45007,7 +45041,6 @@ function budgetRaise(argv) {
|
|
|
45007
45041
|
return EXIT_GATE_REFUSED;
|
|
45008
45042
|
}
|
|
45009
45043
|
store.mutateBudget(() => outcome.budget);
|
|
45010
|
-
store.mutate((run) => ({ ...run, budget: { ...run.budget, ceiling_usd: outcome.runCeilingAfter } }));
|
|
45011
45044
|
store.append({
|
|
45012
45045
|
ts: nowRfc3339(),
|
|
45013
45046
|
run: store.runId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.17.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|