tldr-experts 0.11.0 → 0.12.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 +204 -0
- package/README.md +2 -0
- package/dist/hooks/answer-capture.js +7 -7
- package/dist/hooks/budget-gate.js +7 -7
- package/dist/hooks/{chunk-wzzywf6c.js → chunk-1knfmj6j.js} +1 -1
- package/dist/hooks/{chunk-r1efatxk.js → chunk-6fywd8xd.js} +1 -1
- package/dist/hooks/{chunk-ecwes7kd.js → chunk-cg8v4mz4.js} +1 -1
- package/dist/hooks/{chunk-9v45j71e.js → chunk-f1w9sy07.js} +21 -1
- package/dist/hooks/{chunk-bk7rkdzw.js → chunk-fyr7f9t6.js} +3 -3
- package/dist/hooks/{chunk-d20x2aew.js → chunk-jd48713j.js} +62 -32
- package/dist/hooks/{chunk-6c84kjd7.js → chunk-mvz7jwt8.js} +1 -1
- package/dist/hooks/{chunk-kqvbzyc0.js → chunk-nwzevx0j.js} +3 -3
- package/dist/hooks/{chunk-stw0dxna.js → chunk-qefqmgjn.js} +1 -1
- package/dist/hooks/{chunk-6ws778wq.js → chunk-v2xzr4qd.js} +1 -1
- package/dist/hooks/{chunk-tg7ppsxr.js → chunk-yvgkw0gm.js} +1 -1
- package/dist/hooks/{chunk-1c43349s.js → chunk-zrjgpgwy.js} +28 -2
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +5 -5
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +10 -10
- package/dist/hooks/statusline.js +6 -6
- package/dist/tldrx.js +2082 -1020
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/build/stage.yml +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,210 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## 0.12.0 — 2026-09-08
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- **The reviewer can run on a different model than the developer, and every verdict now says
|
|
9
|
+
which model produced it.** Measured this week across three real workspaces and 168 Build
|
|
10
|
+
stories: `model:` and `effort:` in `stage.yml` are per STAGE, one accessor served both
|
|
11
|
+
spawns, and all 168 stories ran the same model at the same effort for the developer AND the
|
|
12
|
+
reviewer that judged its diff. Zero reviewers ran on anything stronger — so *does a stronger
|
|
13
|
+
reviewer find more* was not a question the data could answer, while hosts were already
|
|
14
|
+
answering it by hand, upgrading the reviewer on stories whose own text said they were
|
|
15
|
+
security-bearing. Three additive keys close that: `reviewer: {model?, effort?}` in
|
|
16
|
+
`stage.yml` is the reviewer role's own pin; `reviewer_by_stakes:` is the same thing keyed on
|
|
17
|
+
a story's new optional `stakes:` field (`security | money | data | correctness | routine`, a
|
|
18
|
+
closed enum the Plan agent emits and nothing infers from prose); and both resolve FIELD BY
|
|
19
|
+
FIELD under `--model`/`--effort`, which stay the operator's word. Absent everywhere ⇒ the
|
|
20
|
+
reviewer runs on the stage's own two lines, byte for byte. **No opus default ships**:
|
|
21
|
+
`stages/build/stage.yml` declares `reviewer_by_stakes: {}` and a commented example, because
|
|
22
|
+
there is no evidence yet that a stronger reviewer finds more and shipping the answer as a
|
|
23
|
+
default would spend money asserting the thing these keys exist to measure. What ships is the
|
|
24
|
+
record that makes the evidence accumulate — a spawned reviewer's arguments were always on
|
|
25
|
+
its `agent.spawned`, a host review's are now read off `--model`/`--effort` on `tldrx next
|
|
26
|
+
--commit --review` and written as `basis: host-declared`, `04-build/log/<story>.md` carries
|
|
27
|
+
a `- Reviewer:` line, and `tldrx replay` names the model on every review round. A host that
|
|
28
|
+
declared nothing reads `not recorded` rather than the bundle's suggested model: that
|
|
29
|
+
suggestion is one tldrx made, and quoting it back would be a record lying in the dangerous
|
|
30
|
+
direction. (#178)
|
|
31
|
+
- **`touches` was declared and never measured, so the boundary audit was answering against a
|
|
32
|
+
forecast.** Measured on three real workspaces: one story changed 21 files and 18 of them fell
|
|
33
|
+
outside its declared `touches`, and the other two stories of the same run under-declared as
|
|
34
|
+
well — none of it a mistake anybody could point at, because the 18 were the Application-layer
|
|
35
|
+
files the work genuinely required and no compiler runs at Plan time to predict them. The
|
|
36
|
+
consequence is the part worth fixing: the check that exists to say "this epic changed something
|
|
37
|
+
no story claimed" was comparing a diff against a guess, which is the shape of check that gets
|
|
38
|
+
switched off. Build now takes the second reading, at the moment a story's work is final and the
|
|
39
|
+
answer is free: it diffs the story over exactly the range the reviewer was shown (the
|
|
40
|
+
`epic_base` recorded on `task.done` since 0.10.0 — one definition of "the story's diff", not a
|
|
41
|
+
second `git diff` derivation) and, when paths landed outside the declared list, appends ONE
|
|
42
|
+
`story.touches_widened` with `basis: "measured"` and `actor: framework`, naming the paths and
|
|
43
|
+
the count. It is the SAME event `tldrx story widen` writes, deliberately: "the surface grew" is
|
|
44
|
+
one fact, and a sibling type would make every reader ask two questions to learn it. `basis` is
|
|
45
|
+
additive and **absent means `declared`**, so every row written before it existed keeps its
|
|
46
|
+
meaning and reads unchanged. It is advisory and cannot refuse — nothing is written when the
|
|
47
|
+
work stayed inside the forecast, an unreadable diff is simply no measurement, and the story's
|
|
48
|
+
own `touches:` is never rewritten, because declaring is the operator's verb and a framework
|
|
49
|
+
that back-dated a declaration would make the plan claim it declared a path it did not. Both
|
|
50
|
+
bases render labelled in the Build handoff, in the `tldrx ship` PR body that embeds it, and in
|
|
51
|
+
`tldrx replay`, so the paths outside BOTH readings are the ones a human is pointed at.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
- **A run now records which tldrx wrote it (#183).** `run.yml`'s `version: 1` is the FILE
|
|
55
|
+
FORMAT's number; nothing anywhere carried the framework's, so across 23 unattended runs on
|
|
56
|
+
three real workspaces no record could be attributed to the release that produced it — in a
|
|
57
|
+
week that shipped ten of them and moved behaviour in three (the DoD refusal record grew
|
|
58
|
+
`status`, the reviewer's diff base moved, a mandate check was reworded). `run.yml` gains
|
|
59
|
+
two additive keys beside the format's: `created_with`, stamped once at `run new` and never
|
|
60
|
+
rewritten, and `last_written_by`, rewritten by every save — so a run that outlived an
|
|
61
|
+
upgrade carries both ends of the range that drove it. `agent.spawned` and `agent.result`
|
|
62
|
+
carry `tldrx_version` for the same reason at the turn level: a spawn that never produced a
|
|
63
|
+
result is still a turn a release is answerable for. All of it reads from the one source
|
|
64
|
+
`tldrx --version` reads, so a record and the CLI cannot disagree. `run status` prints both
|
|
65
|
+
stamps; a run.yml written before the keys reads `not recorded`, which is a different fact
|
|
66
|
+
from a guess at what happened to be installed that day.
|
|
67
|
+
|
|
68
|
+
- **A task row now carries a duration it actually measured, and says which span it is
|
|
69
|
+
(#184).** `started_at` is the INVOCATION's clock and `ended_at` is the instant the row was
|
|
70
|
+
written, so every task of one parallel Build shares a start and subtracting the two yields
|
|
71
|
+
close to the whole invocation for each of them — three unattended runs measured 34.5 h,
|
|
72
|
+
43.2 h and 36.8 h of span that no record could attribute to a phase or a sub-agent. Those
|
|
73
|
+
two fields keep their meaning; `duration_ms` is the new one, and it never travels without
|
|
74
|
+
`duration_basis`. `spawned` is the wall clock around the sub-agent's own process, measured
|
|
75
|
+
in `spawnAgent.ts` and written on the task row and on `agent.result`. `prepare-to-commit`
|
|
76
|
+
is the gap between a `--prepare` bundle's `prepared_at` and the `--commit` that recorded
|
|
77
|
+
the turn: the only span the framework can see for an in-session turn, and one that INCLUDES
|
|
78
|
+
the host's own time — a ceiling on the sub-agent's span the way `spent_usd` is a floor on
|
|
79
|
+
the money, and the spec says so rather than letting the number be read as the sub-agent's.
|
|
80
|
+
`tldrx cost` gains a duration column per attempt and a per-stage sum that names a mixed
|
|
81
|
+
basis instead of adding two different quantities, and counts the attempts it could not
|
|
82
|
+
time. A row from before this reads `not recorded`, never `0s`: "it took no time" and
|
|
83
|
+
"nobody timed it" are different facts and only one is a measurement.
|
|
84
|
+
|
|
85
|
+
- **A `maintain` skill turns a session into this repo's maintainer, and the circuit it encodes
|
|
86
|
+
is the one that was measured, not the one that sounds right.** Twelve maintenance waves ran
|
|
87
|
+
over two days by hand, and three of their lessons were costing time every cycle. First,
|
|
88
|
+
issues go stale in days: one asked for a fix to sequential Build execution while `--parallel`
|
|
89
|
+
had already shipped, so the skill reproduces an issue's measurement on current `origin/main`
|
|
90
|
+
BEFORE anything is touched, and a measured refutation closes it. Second, a fresh reviewer who
|
|
91
|
+
did not write the code found a real Important defect in four of those waves, each one
|
|
92
|
+
checkable from `git log`: `2a6413f` fixed a defect review found in `cbd5c4b`, and the fixes
|
|
93
|
+
that pre-merge review forced landed inside `28a987e` (the B0 mandate reword), `674049a` (the
|
|
94
|
+
heartbeat fix) and `103ff96` (the `runAuto` $0.00 fix). The one wave that reviewed AFTER
|
|
95
|
+
merging left its defect on `main` for two hours. So review is a mandatory step BEFORE
|
|
96
|
+
`scripts/merge-wave.sh`, run by a separate sub-agent on a mid-tier model, which is where the
|
|
97
|
+
defects were actually found. Third, concurrency: at most 3 issues a
|
|
98
|
+
cycle with non-overlapping file sets and at most 2 implementers at once, because the flaky
|
|
99
|
+
tests this repo already knows about redden under machine load and a red gate you caused
|
|
100
|
+
yourself costs a full re-run to disprove. The skill is a workflow and cites `AGENTS.md` and
|
|
101
|
+
`docs/RELEASING.md` by section rather than restating them; `test/maintain-skill.test.ts`
|
|
102
|
+
holds that premise mechanically — every `tldrx` command and `scripts/*.sh` it names must
|
|
103
|
+
exist in the real surface, every `§N` it cites must resolve to a real heading, and no private
|
|
104
|
+
workspace name or chat-product name may appear anywhere in the skill tree (#191). The review
|
|
105
|
+
step itself is still prose, which is the one thing the skill cannot fix about itself — #192
|
|
106
|
+
proposes making `scripts/merge-wave.sh` refuse a branch that carries no review record.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **No surface prints a bare `$0.00` over work nobody metered.** Measured across 23 real runs:
|
|
111
|
+
45 % of 845 task rows are in-session turns recorded `cost_usd: null, metered: false`, which
|
|
112
|
+
every sum treats as contributing nothing — the only honest arithmetic there is. Two of those
|
|
113
|
+
runs then RENDERED `spent_usd: 0.00` against $3,000 and $200 ceilings after 30 and 9
|
|
114
|
+
stories. The counting and the caveat already existed (#103, #139); what did not was a rule
|
|
115
|
+
for the FIGURE, so each screen printed `$${n.toFixed(2)}` in its own words and three printed
|
|
116
|
+
it with no caveat at all. One implementation now writes it everywhere: `≥ $12.40 (7 tasks
|
|
117
|
+
unmetered)` when some of the work was metered, and `not measured: 9 in-session tasks, 0
|
|
118
|
+
metered` when none of it was and there is no floor worth printing. `run status`, `budget
|
|
119
|
+
show`, the dashboard's row and its hero, `replay`, `run auto`'s per-loop lines, the Build
|
|
120
|
+
handoff's `Cost:` header (and so the ship PR body, which embeds it), the `budget.warned`
|
|
121
|
+
note and every notification payload all read from it. `budget.yml` gains `unmetered_tasks`
|
|
122
|
+
and `spent_basis: lower-bound | complete` so an archived file alone still tells the truth —
|
|
123
|
+
emitted only when there IS something unmetered, so a fully metered run and every budget.yml
|
|
124
|
+
written before them stay byte-identical. `spent_usd` is unchanged and still what the ceiling
|
|
125
|
+
arithmetic enforces; a run that really did meter everything keeps its plain figure, because
|
|
126
|
+
a caveat on every screen is a caveat nobody reads.
|
|
127
|
+
|
|
128
|
+
## 0.11.1 — 2026-09-08
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- **The mandate now tells the driver what to READ, because the host's own context was the
|
|
133
|
+
bill.** Measured over one week of host sessions on three real workspaces: ~$8.5k at list,
|
|
134
|
+
68 % of it Opus — and the work was not what cost it. Per-story time was a 0.6-1.35 h
|
|
135
|
+
median; the spend was 600-800-turn host sessions re-reading their whole context every
|
|
136
|
+
turn, one workspace alone at 2.5B cache-read tokens. `--tldr` already trims what the host
|
|
137
|
+
WRITES and what a sub-agent DELIVERS, and nothing anywhere told the host what to read
|
|
138
|
+
back. One bullet in the evidence discipline now does, in all four modes: a sub-agent's
|
|
139
|
+
outcome comes from its `result.json` and the ledger, never its transcript; each is briefed
|
|
140
|
+
to report its TURN in ten lines (a handoff is not a turn report — that one is a gate input
|
|
141
|
+
with its own contract, and trimming it costs the gate); and a cited file is re-opened to
|
|
142
|
+
VERIFY it at a gate, never re-read for a claim already made. It is paid for rather than
|
|
143
|
+
appended — the line budgets did not move (`MANDATE_MAX_LINES` 140, `MANDATE_TLDR_MAX_LINES`
|
|
144
|
+
165, unattended still exactly at both) because the negative-case and contradiction bullets
|
|
145
|
+
each lost a line of wrapping to the same meaning and the unattended gate's interrupt
|
|
146
|
+
paragraph lost the clause that restated "## Do not stop".
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- **A run can now tell a person it needs them, without the framework naming a chat tool.**
|
|
151
|
+
`tldrx run auto` announced an open question or a gate by exiting `4` and printing a
|
|
152
|
+
decision card to stdout, and stdout is in a terminal nobody is watching — which is why
|
|
153
|
+
every run in the week of 2026-09-07 was driven in host mode instead, trading a metered
|
|
154
|
+
budget, an enforced model and parallel stories for a notification. `.tldrx/workspace.yml`
|
|
155
|
+
now takes one optional `notify:` block — a `command`, an optional `events:` list, an
|
|
156
|
+
optional `timeout_s` — and the loop hands that command one `version: 1` JSON object on
|
|
157
|
+
**stdin** at each moment a person is needed: the open question with its options, its
|
|
158
|
+
recommendation and the literal `tldrx answer <Qid> "…" --run <id>` line; a gate with the
|
|
159
|
+
literal approve line; a finished or failed run with its exit code and what that code's
|
|
160
|
+
family means; a budget warning with both numbers. The command is held to §2.1's rule
|
|
161
|
+
exactly — split to argv and run directly, never through a shell, a bare metacharacter
|
|
162
|
+
refused rather than shelled — and the payload never touches the command line, so a
|
|
163
|
+
question's own title cannot become shell syntax. Which service reaches the owner stays the
|
|
164
|
+
owner's decision: a built-in integration would be this framework deciding whose product it
|
|
165
|
+
depends on, which is the same reasoning the drive mandate has always given. `tldrx init`
|
|
166
|
+
writes the block commented out with a line saying what it is for, the way `test_fast` is
|
|
167
|
+
written — who gets woken up is not a thing to detect. Schema and payload: spec §2.18.
|
|
168
|
+
(#180)
|
|
169
|
+
- **A failing notifier is written down, never a refusal.** A command that will not split, a
|
|
170
|
+
binary that is not there, a non-zero exit, a hang — each becomes a `notify.failed` event
|
|
171
|
+
carrying the reason, and the run keeps the exit code it already had; a delivered one is
|
|
172
|
+
`notify.sent` with the kind, the exit code and the duration. Both are `cost_usd: 0` and
|
|
173
|
+
both join the closed `EVENT_TYPES` enum. "The owner was not told, and here is why" is a
|
|
174
|
+
fact about the run; "the chat tool was down, so the run failed" would make a side channel
|
|
175
|
+
load-bearing. (#180)
|
|
176
|
+
- **`tldrx run auto --notify-every <duration>`** sends the declared hook a periodic `status`
|
|
177
|
+
payload carrying what `tldrx run status` prints. It fires from a timer rather than between
|
|
178
|
+
iterations on purpose: the period when somebody most wants to know a run is alive is the
|
|
179
|
+
twenty minutes it is inside one stage. Off by default. Over a run that is PARKED it
|
|
180
|
+
reminds rather than reassures — the summary names the open questions, `command` is the
|
|
181
|
+
literal `tldrx answer` line and `detail.waiting_on` carries the ids. A heartbeat that went
|
|
182
|
+
on saying "nothing is waiting on you" while the run sat on somebody's answer was
|
|
183
|
+
reproduced in review with both flags on, and it is worse than silence, because a heartbeat
|
|
184
|
+
is believed. Parked-ness comes from the same predicate `--wait-answers` polls and `next`
|
|
185
|
+
parks on, never a second opinion. (#180)
|
|
186
|
+
- **`tldrx run auto --wait-answers <duration>`** polls the run's question files instead of
|
|
187
|
+
exiting `4` the moment a stage parks, and resumes if the question is answered — by a
|
|
188
|
+
person, or by whatever the notify hook reached — then exits `4` with the same lines it
|
|
189
|
+
always did, after one `question.timeout` notification, when the wait lapses. Nothing is
|
|
190
|
+
spent while it polls, and the loop never answers its own question: the answer is an
|
|
191
|
+
ordinary `tldrx answer`. Both flags take `30s`, `10m`, `2h` or a bare number of seconds
|
|
192
|
+
through one parser, and refuse anything else by name at exit `1`. Without either flag,
|
|
193
|
+
behaviour is unchanged for every existing user — a question or a gate still exits `4`, the
|
|
194
|
+
hook has simply already fired with the answer command in it. (#180)
|
|
195
|
+
- **The docs site now explains how to OPERATE a run unattended, not just what the flags
|
|
196
|
+
are.** The notify hook shipped with its schema in the spec and a paragraph in the driving
|
|
197
|
+
guide, and neither answers the question somebody actually has at 11pm: what do I write, and
|
|
198
|
+
what do I check when nothing arrives. A new guide page (EN + ES, in both sidebars) walks the
|
|
199
|
+
whole loop — host mode versus the engine with the honest trade-off, the `notify:` block and
|
|
200
|
+
its argv rule, the nine top-level payload keys with `question.raised` and `status` shown in
|
|
201
|
+
full and a per-kind table of the other six, a dependency-free ~30-line Node adapter you can
|
|
202
|
+
paste, `--notify-every` / `--wait-answers` with what exit `4` means when a wait lapses, a
|
|
203
|
+
first-run checklist that dry-runs the adapter on a hand-made payload before any run depends
|
|
204
|
+
on it, and troubleshooting for the four ways it silently does nothing. It names no
|
|
205
|
+
messaging service and it is not going to: the adapter is the reader's, and the page says
|
|
206
|
+
why in the same words the drive mandate does. (#180)
|
|
207
|
+
|
|
4
208
|
## 0.11.0 — 2026-09-08
|
|
5
209
|
|
|
6
210
|
### Fixed
|
package/README.md
CHANGED
|
@@ -316,6 +316,8 @@ back on the registry is 0.3.0.
|
|
|
316
316
|
|
|
317
317
|
| Version | Date | Status | Contains |
|
|
318
318
|
|---|---|---|---|
|
|
319
|
+
| 0.12.0 | 2026-09-08 | `beta` | records that can be attributed and spans that were actually measured: the reviewer can be pinned to its own model and effort per role (`reviewer:`) and per story stakes (`reviewer_by_stakes:`, keyed on a story's new optional `stakes:` enum), resolved field by field under `--model`/`--effort` and shipping NO opus default — because there is no evidence yet that a stronger reviewer finds more, only the record that lets the evidence accumulate: every verdict now names the model that produced it, a host review reading `basis: host-declared` off its own flags and a host that declared nothing reading `not recorded` rather than the bundle's suggestion; `run.yml` gains `created_with` and `last_written_by` beside the file format's own `version: 1`, and `agent.spawned`/`agent.result` carry `tldrx_version`, so a run that outlived an upgrade carries both ends of the range that drove it; a task row carries `duration_ms` that never travels without `duration_basis` — `spawned` is the wall clock around the sub-agent's process, `prepare-to-commit` is a ceiling that includes the host's own time — and `tldrx cost` shows a duration per attempt and a per-stage sum that names a mixed basis instead of adding two different quantities, with a pre-existing row reading `not recorded` and never `0s`; no surface prints a bare `$0.00` over work nobody metered — one implementation writes `≥ $12.40 (7 tasks unmetered)` or `not measured: 9 in-session tasks, 0 metered` across `run status`, `budget show`, the dashboard, `replay`, `run auto`, the Build handoff and every notification, and `budget.yml` gains `unmetered_tasks` and `spent_basis`; the surface a story actually changed is measured off its own diff over the range the reviewer was shown and appended as one `story.touches_widened` with `basis: "measured"` beside what was declared, advisory and never rewriting the operator's `touches:`; and a `maintain` skill encodes the maintenance circuit twelve hand-run waves actually measured — reproduce an issue on current `origin/main` before touching anything, a fresh reviewer before `merge-wave.sh` and not after, at most 3 issues and 2 implementers a cycle — pinned by a test that every command and `§N` it cites resolves |
|
|
320
|
+
| 0.11.1 | 2026-09-08 | `beta` | operating a run when nobody is watching: the drive mandate now names the host's own context as the costliest instrument and tells the driver what to READ back — a sub-agent's outcome from its `result.json` and the ledger, never its transcript, each sub-agent briefed to report its turn in ten lines, and a cited file re-opened to VERIFY at a gate rather than re-read for a claim already made — paid for inside the unchanged line budgets rather than appended; `.tldrx/workspace.yml` takes one optional `notify:` block whose command is handed a `version: 1` JSON object on stdin at each moment a person is needed (an open question with its options, its recommendation and the literal `tldrx answer` line; a gate with its approve line; a finished or failed run with what its exit code's family means; a budget warning with both numbers), split to argv and never shelled, with a notifier that will not split, is not there, exits non-zero or hangs recorded as `notify.failed` instead of failing the run; `tldrx run auto --wait-answers <duration>` polls the question files and resumes when the answer lands instead of exiting 4 the moment a stage parks, and `--notify-every <duration>` sends a timer-driven heartbeat that reminds rather than reassures while a run is parked; and a new EN+ES guide page walks the whole unattended loop — host mode versus the engine, the payload keys, a dependency-free Node adapter, a first-run checklist and the four ways it silently does nothing — naming no messaging service, by the same reasoning the mandate has always given |
|
|
319
321
|
| 0.11.0 | 2026-09-08 | `beta` | governance that leaves a record: every answer says who decided it and what repo it binds, an advisory contradiction check raises a question that never stops a run and states its own limit, the close and the Build handoff count decided vs not-stated; `tldrx story widen` is the sanctioned way to grow a story's surface (a done story needs `reopen --for-fix` first) and a defect no story owns is named in the handoff, the PR body and the boundary card instead of absorbed; a budget grant is a recorded number in `budget.yml` that survives every writer, `budget raise` warns or refuses against it, presets are labelled as the assumptions they are, `triage.budget_basis` says where a figure came from and `tldrx cost --stories` measures each story against the ceiling its spawn was given, honest about lower bounds |
|
|
320
322
|
| 0.10.0 | 2026-09-07 | `beta` | the mechanical blockers measured in real runs, fixed: a refused DoD command is recorded as refused (never a fabricated exit 126) in the event, the handoff, the review and retro logs, and the readers that used to recover it as green; the dirty-tree refusal prints the exact stash and pop commands and its true reason; the reviewer diffs the epic as it was before the merge (`epic_base` recorded on the story and the bundle) instead of an empty range; `tldrx init` probes each declared command once and records `command_probes` beside a `commands:` allowlist it never guesses; `tldrx ship` opens a PR whose body describes the change and its known defects, with the handoff folded underneath, and a state refusal that honours settled stories' declared touches; reviewer turns carry their token split and the spend basis reads it, so a whole provider no longer reads as absent |
|
|
321
323
|
| 0.9.2 | 2026-09-07 | `beta` | the Build executor decomposed: 4,351 lines became an orchestrator plus eight modules under src/core/build/ (review ledger and phase cost, money caps, the DoD runner with its preflight cache, worktrees and epic-branch claims, the reviewer bundle and review round) — a pure refactor proven byte-for-byte by a golden guard over prompts, ordered events, run.yml rows and exit codes across three scenarios; no behaviour changed, every remaining hardening fix now lands in a file a reviewer can hold |
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-fyr7f9t6.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-nwzevx0j.js";
|
|
9
9
|
import {
|
|
10
10
|
parseHookInput,
|
|
11
11
|
readStdin
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-mvz7jwt8.js";
|
|
13
13
|
import {
|
|
14
14
|
EventLog,
|
|
15
15
|
PHASE_ID_RE
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import"./chunk-
|
|
16
|
+
} from "./chunk-f1w9sy07.js";
|
|
17
|
+
import"./chunk-yvgkw0gm.js";
|
|
18
18
|
import {
|
|
19
19
|
ADVISORY_KEY,
|
|
20
20
|
MAX_FACT_CHARS,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
renderQuestionBlock,
|
|
25
25
|
replaceBlock,
|
|
26
26
|
serializeQuestions
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-1knfmj6j.js";
|
|
28
28
|
import {
|
|
29
29
|
ITERATION_ONLY_SLOT,
|
|
30
30
|
PROJECT_FRAMEWORK_DIR,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
commandProbeIssues,
|
|
33
33
|
factsPath,
|
|
34
34
|
parseYaml
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6fywd8xd.js";
|
|
36
36
|
|
|
37
37
|
// src/hooks/answer-capture.ts
|
|
38
38
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-qefqmgjn.js";
|
|
5
5
|
import {
|
|
6
6
|
allow,
|
|
7
7
|
deny,
|
|
8
8
|
readPayload,
|
|
9
9
|
runHook,
|
|
10
10
|
toolInput
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import"./chunk-
|
|
11
|
+
} from "./chunk-v2xzr4qd.js";
|
|
12
|
+
import"./chunk-mvz7jwt8.js";
|
|
13
13
|
import {
|
|
14
14
|
asRunBudget,
|
|
15
15
|
currentActor,
|
|
@@ -29,20 +29,20 @@ import {
|
|
|
29
29
|
validateRunBudget,
|
|
30
30
|
wouldExceed,
|
|
31
31
|
wouldExceedHostTokens
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-zrjgpgwy.js";
|
|
33
33
|
import {
|
|
34
34
|
EventLog
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-f1w9sy07.js";
|
|
36
36
|
import {
|
|
37
37
|
noteDeprecations
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-1knfmj6j.js";
|
|
39
39
|
import {
|
|
40
40
|
PROJECT_WORK_DIR,
|
|
41
41
|
findWorkspaceRoot,
|
|
42
42
|
locateWork,
|
|
43
43
|
parseYaml,
|
|
44
44
|
stageYamlPath
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-6fywd8xd.js";
|
|
46
46
|
|
|
47
47
|
// src/hooks/budget-gate.ts
|
|
48
48
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -9122,4 +9122,4 @@ function noneBullet(lookedAt) {
|
|
|
9122
9122
|
return `- none [src: absent:${lookedAt}]`;
|
|
9123
9123
|
}
|
|
9124
9124
|
|
|
9125
|
-
export { runtime, parseYaml, parseYamlRepairing, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, ITERATION_ONLY_SLOT, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
9125
|
+
export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, ITERATION_ONLY_SLOT, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
requireString,
|
|
11
11
|
requireVersion,
|
|
12
12
|
result
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6fywd8xd.js";
|
|
14
14
|
|
|
15
15
|
// src/core/events/EventLog.ts
|
|
16
16
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -58,6 +58,8 @@ var EVENT_TYPES = [
|
|
|
58
58
|
"fact.superseded",
|
|
59
59
|
"fact.conflict_raised",
|
|
60
60
|
"doc.superseded",
|
|
61
|
+
"notify.sent",
|
|
62
|
+
"notify.failed",
|
|
61
63
|
"map.refreshed",
|
|
62
64
|
"ticket.synced",
|
|
63
65
|
"error"
|
|
@@ -446,6 +448,9 @@ function describe(value) {
|
|
|
446
448
|
return typeof value === "string" ? `\`${value}\`` : String(value);
|
|
447
449
|
}
|
|
448
450
|
|
|
451
|
+
// src/core/run/duration.ts
|
|
452
|
+
var DURATION_BASES = ["spawned", "prepare-to-commit"];
|
|
453
|
+
|
|
449
454
|
// src/core/run/RunFile.ts
|
|
450
455
|
var STAGE_STATUSES = [
|
|
451
456
|
"pending",
|
|
@@ -594,6 +599,10 @@ function validateRunFile(input) {
|
|
|
594
599
|
}
|
|
595
600
|
if (doc.attended_by !== undefined)
|
|
596
601
|
requireEnum(doc.attended_by, ATTENDED_BY, "attended_by", issues);
|
|
602
|
+
for (const key of ["created_with", "last_written_by"]) {
|
|
603
|
+
if (doc[key] !== undefined)
|
|
604
|
+
requireString(doc[key], key, issues);
|
|
605
|
+
}
|
|
597
606
|
if (doc.keep_worktrees !== undefined && typeof doc.keep_worktrees !== "boolean") {
|
|
598
607
|
issues.push({ path: "keep_worktrees", message: `expected a boolean, got ${typeof doc.keep_worktrees}` });
|
|
599
608
|
}
|
|
@@ -715,6 +724,17 @@ function validateRunFile(input) {
|
|
|
715
724
|
issues.push({ path: `${tp}.${key}`, message: "expected a number >= 0" });
|
|
716
725
|
}
|
|
717
726
|
}
|
|
727
|
+
if (task.duration_ms !== undefined) {
|
|
728
|
+
if (typeof task.duration_ms !== "number" || !Number.isFinite(task.duration_ms) || task.duration_ms < 0) {
|
|
729
|
+
issues.push({ path: `${tp}.duration_ms`, message: "expected a number >= 0" });
|
|
730
|
+
}
|
|
731
|
+
requireEnum(task.duration_basis, DURATION_BASES, `${tp}.duration_basis`, issues);
|
|
732
|
+
} else if (task.duration_basis !== undefined) {
|
|
733
|
+
issues.push({
|
|
734
|
+
path: `${tp}.duration_basis`,
|
|
735
|
+
message: "a duration_basis with no duration_ms names the basis of nothing"
|
|
736
|
+
});
|
|
737
|
+
}
|
|
718
738
|
if (typeof task.cost_usd === "number")
|
|
719
739
|
spentFromTasks += task.cost_usd;
|
|
720
740
|
checkOrder(task.started_at, task.ended_at, tp, issues);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findDuplicate
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-nwzevx0j.js";
|
|
4
4
|
import {
|
|
5
5
|
evidencePath,
|
|
6
6
|
gateEvidencePath,
|
|
7
7
|
parseEvidence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-f1w9sy07.js";
|
|
9
9
|
import {
|
|
10
10
|
openBlocks,
|
|
11
11
|
parseQuestions
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-1knfmj6j.js";
|
|
13
13
|
|
|
14
14
|
// src/core/distill/distill.ts
|
|
15
15
|
var CONFLICT_THRESHOLD = 0.6;
|