tldr-experts 0.14.2 → 0.15.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 +399 -0
- package/README.md +2 -0
- package/dist/hooks/answer-capture.js +11 -10
- package/dist/hooks/budget-gate.js +17 -14
- package/dist/hooks/{chunk-fefnr71h.js → chunk-3kmx3dmz.js} +84 -211
- package/dist/hooks/chunk-4mjxyfp9.js +148 -0
- package/dist/hooks/{chunk-mzwqb08p.js → chunk-54vzevgt.js} +3 -3
- package/dist/hooks/{chunk-wwwmkey4.js → chunk-77y6q9mt.js} +18 -3
- package/dist/hooks/chunk-d0rp8c68.js +278 -0
- package/dist/hooks/{chunk-tpz0zb0t.js → chunk-ds9sew2e.js} +1 -1
- package/dist/hooks/{chunk-bqmstq21.js → chunk-fm141jak.js} +1 -1
- package/dist/hooks/chunk-k4nqzdw5.js +93 -0
- package/dist/hooks/{chunk-gvz0rzb9.js → chunk-msswet35.js} +1 -1
- package/dist/hooks/{chunk-db7wf3hy.js → chunk-nqwg5ddj.js} +1 -1
- package/dist/hooks/{chunk-r95h9ynf.js → chunk-qdvwnts3.js} +1 -1
- package/dist/hooks/{chunk-dnat9z30.js → chunk-rz0qr006.js} +27 -31
- package/dist/hooks/{chunk-dxk7eaqn.js → chunk-vmhfw2r1.js} +1 -1
- package/dist/hooks/{chunk-w8qm1jbn.js → chunk-z0hnthw4.js} +3 -3
- package/dist/hooks/{chunk-97zs34wb.js → chunk-zccma7kt.js} +65 -19
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +18 -8
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +21 -13
- package/dist/hooks/statusline.js +9 -7
- package/dist/tldrx.js +2858 -1599
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/build/stage.yml +5 -1
- package/stages/watch/stage.yml +4 -1
- package/templates/budget.yml +13 -6
- package/templates/watcher.md +11 -0
- package/templates/workspace.yml +6 -0
- package/workflows/bugfix.yml +5 -1
- package/workflows/docs.yml +5 -1
- package/workflows/feature.yml +5 -1
- package/workflows/hotfix.yml +5 -1
- package/workflows/integration.yml +5 -1
- package/workflows/migration.yml +5 -1
- package/workflows/performance.yml +5 -1
- package/workflows/prototype.yml +5 -1
- package/workflows/refactor.yml +5 -1
- package/workflows/retro.yml +5 -1
- package/workflows/security-patch.yml +5 -1
- package/workflows/spike.yml +5 -1
- package/workflows/upgrade.yml +5 -1
- package/dist/hooks/chunk-dzkg8c68.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,404 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 — 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Four Build calibrations became optional `stage.yml` keys: `attempts` (default 2),
|
|
8
|
+
`fixlist_rounds` (1), `reviewer_share` (0.25), `gate_signer_share` (0.25).** Each was a code
|
|
9
|
+
constant, and each is a calibration rather than an invariant — "two attempts" is the
|
|
10
|
+
framework's opinion about how many developer turns a story deserves before a human should
|
|
11
|
+
look at it, and a hardening wave over a legacy repo holds a different one. Until now the only
|
|
12
|
+
way to hold it was a fork. `parallel:` made exactly this move already and the argument is the
|
|
13
|
+
same. Absent ⇒ today's constant, byte for byte, which is what the golden proves; present and
|
|
14
|
+
out of range (`attempts` 1..5, `fixlist_rounds` 0..3, the two shares 0..1) is REFUSED by name
|
|
15
|
+
at `validateStage` rather than clamped, because a clamp lets an operator write a number, pay
|
|
16
|
+
for something else, and never be told which. One derivation
|
|
17
|
+
(`src/core/schemas/stageTuning.ts`): `caps.ts`, `budget/remainingWork.ts`, `gateSigner.ts` and
|
|
18
|
+
`build/fixlist.ts` keep the constants they always exported — the prose that justifies a number
|
|
19
|
+
lives with the code it governs — but each is now defined as its field there, so the default
|
|
20
|
+
cannot move in one file and not the other.
|
|
21
|
+
- **`tldrx run auto --prompt-max-bytes <n>` and `--max-reads <n>` (#208).** Passed to every
|
|
22
|
+
`next` the loop makes, with the same precedence they have there: the flag beats the stage
|
|
23
|
+
file. Until now the only way to raise either for an *unattended* run was to edit
|
|
24
|
+
`.tldrx/stages/<id>/stage.yml` — a file change, in the workspace, to get past one refusal.
|
|
25
|
+
|
|
26
|
+
- **`install:` is a slot the framework runs, in every fresh story worktree, before the
|
|
27
|
+
developer (#209).** It has been in `templates/workspace.yml` since the beginning and no code
|
|
28
|
+
read it: `git grep` over `src/` found zero readers outside the unrelated Claude-Code
|
|
29
|
+
installer. Now, when a repo declares one, Build runs it in the story's worktree the moment
|
|
30
|
+
that worktree is created — through the same allowlist-and-argv runner every other declared
|
|
31
|
+
command goes through, so an installer is not a privileged string — and records it as its own
|
|
32
|
+
`check: "install"` with an exit code, a `duration_ms` and `tree: "worktree"`. The duration is
|
|
33
|
+
the point as much as the exit code: this work used to be folded into the story's paid turn or
|
|
34
|
+
absent from the record entirely. An install that FAILS blocks the story with what the
|
|
35
|
+
installer printed and dispatches no developer at all — a tree whose dependencies did not
|
|
36
|
+
install cannot prove anything, and paying a turn to discover that is the cost this whole
|
|
37
|
+
change exists to stop.
|
|
38
|
+
- **A watcher card can now say "there is nothing to query", and be believed (#212).** Measured
|
|
39
|
+
on 0.14.2, on a real workspace: the Watch stage read a code path that sends a verification
|
|
40
|
+
code and returns — no log line, no metric, no span anywhere in it. It said so correctly under
|
|
41
|
+
`## Signal`, `## Where` and `## Looks broken when`, each with an `absent:` source, and noted
|
|
42
|
+
that the only signal that exists is a customer reporting a missing code. Then it reached
|
|
43
|
+
`## Query`, wrote the same truth in prose, and the validator refused the whole card for it —
|
|
44
|
+
"`## Query` holds no fenced block — the query has to be copy-pasteable, not described". The
|
|
45
|
+
stage failed and the cost was spent on the honest answer. `Query` was the one checked section
|
|
46
|
+
with no absent form — every other section had `absent:` and it had nothing. It now accepts one line as well as a fenced block:
|
|
47
|
+
`Query: none — <reason> [src: …]`, where the reason ends with a §2.8 token read by the *same*
|
|
48
|
+
parser `claim-sources` denies a handoff bullet with, so an unsourced or unresolvable `none` is
|
|
49
|
+
refused exactly like any unsourced item. A line rather than a fence tagged `none` because the
|
|
50
|
+
`[src: …]` grammar is line-terminal: inside a fence the reason could only be sourced by a
|
|
51
|
+
second reader of that grammar, and a card has exactly one.
|
|
52
|
+
A `none` is EARNED, not asserted: it is refused unless the card's own `## Signal` cites `absent:`
|
|
53
|
+
and the reason's source is `absent:` too — found by a pre-merge reviewer's probe, where a card
|
|
54
|
+
naming a live, resolving emitting line took `Query: none` and validated, because the "only when
|
|
55
|
+
nothing is instrumented" rule lived in the stage prompt and nowhere a card had to pass.
|
|
56
|
+
`watch check`, `watch arm`'s post-merge screen and every other view print the form as
|
|
57
|
+
`unobservable — <reason>` with its source, through one renderer; the Watch handoff lists the
|
|
58
|
+
card under an `**unobservable**` line rather than quietly omitting it. Prose under `## Query` is still refused, in the same words as before
|
|
59
|
+
— the new form is a shape a reader can recognise, not permission to describe a query.
|
|
60
|
+
- **`run.yml` grows an additive `outcome:`, so a run can no longer read `done` over nothing
|
|
61
|
+
delivered (#210).** Run STATUS is a roll-up of the execution path and nothing else — every
|
|
62
|
+
stage of a run whose stories all blocked is terminal, so `deriveRunStatus` calls it `done`,
|
|
63
|
+
correctly, and until now that was the only word anyone got. `outcome:` is written ONCE when the
|
|
64
|
+
run closes, by all three commands that close one (`tldrx next` closing the last stage,
|
|
65
|
+
`tldrx approve` signing the last gate, and `tldrx run cancel`), and carries
|
|
66
|
+
`kind: delivered | partial | nothing-delivered | n/a` with the counts and the first blocked
|
|
67
|
+
story. It is rendered by `tldrx run status` (`status done — nothing delivered: 0 of 3 stories;
|
|
68
|
+
S1 — …`), by `tldrx status`, by the `run.finished` notification, by the dashboard model
|
|
69
|
+
(additive; `DASHBOARD_MODEL_VERSION` stays 3) and by the `ship` PR body — one renderer, six
|
|
70
|
+
surfaces, so none of them can disagree about what a run delivered. The two absences are named
|
|
71
|
+
rather than invented (§7): a `run.yml` written before the field reads `not recorded` with the
|
|
72
|
+
reason in it, and a run with no Build phase — a docs-scope run — reads `n/a` with `why`, never
|
|
73
|
+
a confident `0 of 0`.
|
|
74
|
+
- **A truncated input is now told to the OWNER, not only to the sub-agent (#207).** The inputs
|
|
75
|
+
budget has always written a "truncated inputs: …" line into the prompt itself, so the agent
|
|
76
|
+
knew; the person paying did not. Measured on a real workspace: a 168,873 B `facts.yml` reached
|
|
77
|
+
an `effort: high` design turn as 86,571 B under the 98,304-byte `inputs_max_bytes` of the day
|
|
78
|
+
(0.14.1; the default is 256 KB since #208), that
|
|
79
|
+
turn ran to its `timeout_s`, was killed, and booked nothing — and the only record that half the
|
|
80
|
+
fact ledger had been dropped lived in `.agent/how/prompt.md`, a file nobody opens until after
|
|
81
|
+
the failure it explains. The facilitator now appends one `input.truncated` event per cut input
|
|
82
|
+
AT SPAWN, carrying `stage`, `path`, `bytes`, `inlined_bytes` and `cap` — all four numbers, so a
|
|
83
|
+
reader never has to reconstruct one. `tldrx run status --verbose` lists them under the stage
|
|
84
|
+
the run is parked on, and the `stage.done` / `run.failed` / `status` notification summaries end
|
|
85
|
+
with one sentence naming them (*"1 input truncated: facts.yml 169 KB → 87 KB (cap 96 KB)."*).
|
|
86
|
+
**No tenth notify kind**: a truncation is a caveat on a moment already being announced, and a
|
|
87
|
+
new kind would have to be handled by every adapter already written against the nine. Absent
|
|
88
|
+
when nothing was cut, so an ordinary run's log and summaries are byte-identical to before.
|
|
89
|
+
- **A turn killed on `timeout_s` keeps the usage the provider had already streamed (#207).**
|
|
90
|
+
Before this, fifteen minutes of real `effort: high` compute booked an all-zero `usage` block —
|
|
91
|
+
honestly labelled `metered: false`, and still throwing away token counts that had already
|
|
92
|
+
crossed the wire. `agent.result` now carries an additive `usage_basis`, present only on a
|
|
93
|
+
killed turn: `partial-before-kill` means the counters beside it are the last frame the provider
|
|
94
|
+
streamed — a floor on the turn, never its total — and `absent` means not one frame arrived,
|
|
95
|
+
with `unmetered_reason` saying so in words rather than leaving a confident zero to be read as a
|
|
96
|
+
measurement. `tldrx cost` prints the basis under the attempt. **No dollar figure is invented in
|
|
97
|
+
either case**: measured against `claude` 2.1.251, the stream reports tokens per assistant
|
|
98
|
+
message and dollars only on the final `result` line, which a killed process never reaches, so a
|
|
99
|
+
USD number here could only be one this framework computed itself. `metered` stays `false` and
|
|
100
|
+
nothing sums these tokens into a spend.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **A stage's `timeout_s` now bounds a turn a real model can finish: 900 s → 7200 s
|
|
105
|
+
(#207).** It has always been a PER-TURN clock — a Build stage that dispatches six stories
|
|
106
|
+
gives each spawn its own — and 900 s was set before anyone had watched a week of unattended
|
|
107
|
+
runs. Measured 2026-09-07/09 across three real workspaces: Opus turns on real repositories
|
|
108
|
+
run 15-50 minutes, so a quarter of an hour sat under the MEDIAN of the work this framework
|
|
109
|
+
dispatches. It was not a safety net; it was the most common way a turn ended, and it killed
|
|
110
|
+
a `how` turn and two Build developer turns in a single day — each one a paid turn thrown
|
|
111
|
+
away with its output. Two hours is above the longest turn observed, so the next turn to hit
|
|
112
|
+
it is genuinely stuck rather than merely large. The shipped stage files that set the key
|
|
113
|
+
moved with it (`build` 1800, `watch` 900), and `PRECONDITION_TIMEOUT_S` is untouched at 60:
|
|
114
|
+
a precondition is a liveness question, and letting a hung `docker info` borrow a Build
|
|
115
|
+
stage's clock is exactly the waste that key exists to prevent.
|
|
116
|
+
- **The context ceilings stop refusing prompts the model would have read: `prompt_max_bytes`
|
|
117
|
+
163840 → 409600, `inputs_max_bytes` 98304 → 262144.** Both were sized just above the largest
|
|
118
|
+
thing this repo had SEEN in 2026-08, which is a reasonable way to set a ceiling once and a
|
|
119
|
+
bad way to keep one. Measured on the same three workspaces: a `how` stage built a 202 KB
|
|
120
|
+
prompt and was refused by a ceiling whose own message called that prompt "29% of a 200k
|
|
121
|
+
window"; and a 169 KB `facts.yml` was sliced to 96 KB — named, as a truncation always is,
|
|
122
|
+
but naming a loss the operator cannot afford is not the same as not taking it. Neither is
|
|
123
|
+
unbounded: 400 KB is still well under the smallest window in `budget/modelPrices.ts`, so the
|
|
124
|
+
first prompt past it is still one worth stopping. `knowledge_max_bytes` is deliberately
|
|
125
|
+
unchanged at 49152 — trained knowledge is the section nobody asked for, and the one the
|
|
126
|
+
refusal tells you to cut first.
|
|
127
|
+
- **The context line no longer quotes a window it cannot source.** `contextTokensFor` fell
|
|
128
|
+
back to a DOCUMENTED 200 000-token default for a model the price table has never heard of,
|
|
129
|
+
and the ledger printed that as if it were the model's own window. On the 1M-window models
|
|
130
|
+
these runs use, it understated the window by 5x — inside a refusal, which is how a ceiling
|
|
131
|
+
ends up arguing against itself. A window with no evidence behind it is now simply absent:
|
|
132
|
+
the line prints the token estimate and stops, with no percentage and no figure, the same
|
|
133
|
+
rule `priceFor` has always followed for prices. `knownContextTokensFor` is the null-returning
|
|
134
|
+
half; `contextTokensFor` is that plus the documented fallback, so there is still one table
|
|
135
|
+
and one derivation.
|
|
136
|
+
- **A phase ceiling now holds every attempt its stages may take, so a retry is not refused by
|
|
137
|
+
arithmetic (#170).** `run new` sized a phase for exactly ONE attempt while the framework
|
|
138
|
+
granted two, so the first retry of a stage that had spent anything at all was refused for
|
|
139
|
+
money the run had been given. Measured: a `what` stage that died with $0.29 spent left
|
|
140
|
+
$18.91 of its $19.20 phase, and `next` refused with "phase 01-what has $18.91 left and the
|
|
141
|
+
stage estimate is $19.20" — the estimate is the whole stage, and part of it had already been
|
|
142
|
+
charged. The operator's only move was to raise money for work paid for once. `planBudget`
|
|
143
|
+
now splits the run ceiling by `attempts × budget_usd`, so the STAGE figure stays what one
|
|
144
|
+
attempt costs — the figure the brake is checked against — and the PHASE holds all of them,
|
|
145
|
+
the way `worstCaseShares` has always sized Build stories. The shipped `default_budget_usd`
|
|
146
|
+
figures and `templates/budget.yml` doubled at the same commit, so the per-stage dollars an
|
|
147
|
+
existing workspace sees are unchanged. `on_exceed` and the grant/ceiling reconciliation are
|
|
148
|
+
untouched.
|
|
149
|
+
|
|
150
|
+
- **`warn_at_pct` still fires at the same real dollars.** A phase now holds every
|
|
151
|
+
attempt its stages may take, so measuring 80% against the raw ceiling would need
|
|
152
|
+
roughly twice the real spend on a run that never retries — the warning would
|
|
153
|
+
arrive after the money it was warning about had gone, which is a warning that has
|
|
154
|
+
stopped being one. `wouldExceed` measures it against **one attempt's share**
|
|
155
|
+
(`ceiling ÷ attempts`) and carries that figure as `warnBasis`. The refusal is
|
|
156
|
+
untouched: `exceeds`, `remaining` and `on_exceed` still answer for the whole
|
|
157
|
+
phase, because the phase may spend all of it. Owner decision 2026-09-09.
|
|
158
|
+
- **Everything that prints "attempt N of M" reads the stage that dispatched the
|
|
159
|
+
story.** The dashboard printed it off the global constant for every story of
|
|
160
|
+
every run (`RunModel.maxAttempts` is additive, so `DASHBOARD_MODEL_VERSION` does
|
|
161
|
+
not move — §7's rule is that additions do not bump it), and `story reopen
|
|
162
|
+
--for-fix` said "attempt 1 of 2" whatever the stage declared. One resolver,
|
|
163
|
+
`buildStageDefaults`, and it is tolerant: an unreadable workflow gives the
|
|
164
|
+
shipped pair rather than throwing on a page render.
|
|
165
|
+
- **The maintain skill says which sha a review record's `against:` must name (#207).** Both
|
|
166
|
+
SKILL.md §3 and the reviewer brief now state it once: the **code head** — the branch's last
|
|
167
|
+
commit that is not itself a `.review/` commit — never the record commit's own sha, and never an
|
|
168
|
+
amended one. Measured 2026-09-09: amending a record commit replaces its sha, and
|
|
169
|
+
`scripts/merge-wave.sh` then refuses the branch with exit 10, correctly. The rule cost a wave
|
|
170
|
+
and was written down nowhere an agent reads before it happens; `test/maintain-skill.test.ts`
|
|
171
|
+
now pins the literal phrase in both documents.
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
|
|
175
|
+
- **The DoD-gate hook re-runs a story's commands on the STAGE's clock, not on a
|
|
176
|
+
private 900 s constant.** `src/hooks/dod-gate.ts` carried
|
|
177
|
+
`const DEFAULT_TIMEOUT_S = 900` with a comment citing spec §2.3, and never opened
|
|
178
|
+
a stage file — so a workspace that had deliberately given its Build stage a
|
|
179
|
+
different `timeout_s` got 900 s in the gate and something else in the turn, and
|
|
180
|
+
this hook re-runs the very commands that turn ran. Measured RED: under a stage
|
|
181
|
+
declaring `timeout_s: 2`, a `sleep 30` in a `dod` block ran to completion and the
|
|
182
|
+
gate ALLOWED — "no deny decision (exit 0)". It now resolves story `timeout_s:` →
|
|
183
|
+
the Build stage's → the shipped default, which is the order §2.3 and §7's
|
|
184
|
+
DoD-gate row have always described.
|
|
185
|
+
|
|
186
|
+
- **A Build gate now says what the stage DELIVERED, on every gate policy — the counting existed
|
|
187
|
+
and ran for `auto` gates alone (#210).** Measured on tldrx 0.14.2 across two real workspaces,
|
|
188
|
+
2026-09-09: two engine-driven runs reached the end, both printed `run <id> is done`, both had
|
|
189
|
+
their Build gate approved by the owner from his phone, and both delivered ZERO stories. The
|
|
190
|
+
whole of what he had to decide on was
|
|
191
|
+
`{"phase":"04-build","cost_usd":1.78,"outputs":["04-build/handoff.md"],"checks":["claim-sources:passed"]}`
|
|
192
|
+
— a dollar figure, one output and one green check. The information existed on disk at that
|
|
193
|
+
instant: the same run's `04-build/handoff.md` `## Findings` read
|
|
194
|
+
`S1 · … — blocked — … npm run test exited 127 …`. `storiesCondition` in `autoGate.ts` computed
|
|
195
|
+
exactly the missing view — `N of M done`, with the unfinished ones named — but `evaluateAutoGate`
|
|
196
|
+
is called only when `policy === "auto"`, and both of these gates were `human`, so nothing counted
|
|
197
|
+
and nothing carried it. The derivation is lifted into one leaf (`core/run/runOutcome.ts`, §7 one
|
|
198
|
+
implementation per derivation) and `gate.requested` for a Build stage now carries
|
|
199
|
+
`stories: {total, done, in_progress, review, blocked, todo}` plus `blocked_story` and
|
|
200
|
+
`blocked_reason` — the FIRST blocked story's own words, read out of the handoff's `## Findings`
|
|
201
|
+
through the parser that lives beside the renderer that wrote it. The same sentence goes in the
|
|
202
|
+
notification's **summary** and not only its detail (the argument #203 already won for `held`: the
|
|
203
|
+
summary is the half that reaches a lock screen), on the terminal line under `<phase>/<stage> done`,
|
|
204
|
+
and at the head of the decision card. All four keys are absent on a non-Build stage and on a run
|
|
205
|
+
with no plan — a `stories` block of zeroes there would say a plan was read and found empty.
|
|
206
|
+
- **`tldrx ship` refuses a run that delivered no story instead of opening a PR over nothing
|
|
207
|
+
(#210).** It already knew: `shipBody.ts` reads the handoff's `## Findings`, keeps only the
|
|
208
|
+
bullets that say `done`, and with none of them writes ``- (nothing settled `done` in this run)``
|
|
209
|
+
— into a pull request it created anyway. That is a review request for a diff that does not
|
|
210
|
+
exist. It now refuses with exit `1` (the "nothing behind it" family, not `2`: no gate said no,
|
|
211
|
+
there is simply nothing to open a PR from), naming the counts and the first blocked story's
|
|
212
|
+
reason, and `--dry-run` is refused in the same words. A run that delivered at least one story
|
|
213
|
+
ships exactly as it did, and its PR body gains an **Outcome** header line so a reviewer reads
|
|
214
|
+
the ratio before the list. The refusal sits AFTER the no-handoff check, because the handoff is
|
|
215
|
+
the document the reason is read out of.
|
|
216
|
+
- **A story's Definition of Done exiting `127` is reported as an absent binary, not as a red
|
|
217
|
+
test (#209).** Measured on two real workspaces at 0.14.2: the Build-entry pre-flight recorded
|
|
218
|
+
`npm run test` → exit 0, `Ran all test suites.` in the human's checkout, and minutes later the
|
|
219
|
+
same command in the story's fresh worktree recorded `exit_code: 127`,
|
|
220
|
+
`detail: "sh: jest: command not found"`. The story blocked, verdict `n-a`, no reviewer, no
|
|
221
|
+
commit — with a full developer turn already paid for. Nothing about the story was wrong: 127
|
|
222
|
+
is `command not found`, the base tree has `node_modules` and a `git worktree` is a fresh
|
|
223
|
+
checkout of tracked files that has none. `dodRunner.ts` already documented that trap for the
|
|
224
|
+
base tree in as many words ("a pristine worktree would fail half the world's repos for want of
|
|
225
|
+
`node_modules`") while the story's tree ran under it untouched. A 127 now carries the reason
|
|
226
|
+
with it: the test command's binary is absent in the worktree, named where the shell named it,
|
|
227
|
+
with the `install:` slot given as the fix. Nothing is guessed in the process — not `npm ci`
|
|
228
|
+
from a lockfile, and not a symlink of the base tree's `node_modules`, which the message offers
|
|
229
|
+
as an option and explicitly does not take, because hoisting and cache layout are per-tree facts
|
|
230
|
+
a shared tree can be silently wrong about. Absent-with-reason, applied to an exit code.
|
|
231
|
+
- **A developer may run its declared commands WITH ARGUMENTS (#209).** From the same run's
|
|
232
|
+
transcript: the developer never ran its own Definition of Done either. Every attempt —
|
|
233
|
+
`npx jest …`, `npm run test -- app/dev/__tests__/…`, three times bare — came back
|
|
234
|
+
`"This command requires approval to run"`, so the 127 above was first seen by the gate, after
|
|
235
|
+
the turn had been paid for, by an agent that could not have seen it sooner. The cause was the
|
|
236
|
+
allowance: one `Bash(<declared command>)` per command, and Claude Code's permission grammar
|
|
237
|
+
makes that rule EXACT — its own docs say `Bash(npm run build)` "Doesn't match
|
|
238
|
+
`npm run build --watch`". Every declared command now emits both that exact grant and the
|
|
239
|
+
trailing-argument one (`Bash(<command> *)`, which the docs also spell `Bash(<command>:*)`),
|
|
240
|
+
from one derivation both tool lists share. The reviewer's allowance is untouched and still
|
|
241
|
+
read-only: `Read`, `Grep`, `Glob`, `Bash(git diff *)` — already the trailing-wildcard grammar.
|
|
242
|
+
- **A story's DoD check says which tree it ran in (#209).** `tree: "worktree"` on every
|
|
243
|
+
`check: "dod"` payload, and on the new install check. The pre-flight and the story ran the same
|
|
244
|
+
command and wrote the same shape of record, and nothing in either said they were different
|
|
245
|
+
trees — so a green pre-flight beside a story's 127 read as a contradiction instead of as the
|
|
246
|
+
environment gap it was. A `127` whose tail names the binary also carries `absent_binary`.
|
|
247
|
+
|
|
248
|
+
- **A red Definition-of-Done command's records name the FAILURE, not the last warning on
|
|
249
|
+
stderr (#211).** `check.failed`'s `detail`, `04-build/log/<story>.md` under both
|
|
250
|
+
`## Definition of done` and `## Why it is not done`, the handoff Finding and the notify
|
|
251
|
+
summary all carried ONE sentence, and that sentence was
|
|
252
|
+
`lastMeaningfulLine(stdout + "\n" + stderr)` — the last non-empty line of the two streams
|
|
253
|
+
concatenated, which is the last line of stderr whenever stderr wrote anything at all.
|
|
254
|
+
Measured on a real workspace: a story's gate script exited 1 and the only thing any record
|
|
255
|
+
kept was `sys:1: DeprecationWarning: builtin type swigvarlink has no __module__ attribute`.
|
|
256
|
+
Which test failed was in no record, the worktree had been deleted, and the owner could not
|
|
257
|
+
tell what broke without re-running the suite. A host had raised the same thing in prose a
|
|
258
|
+
week earlier.
|
|
259
|
+
Now: the last 200 lines (or 16 KB, whichever is smaller) of the command's combined output go
|
|
260
|
+
to `04-build/log/dod-output/<story>-<n>.txt`, one file per red check, written before the
|
|
261
|
+
worktree is removed. `check.failed` gains `output_path` and `output_bytes`, and its `detail`
|
|
262
|
+
becomes up to 5 failure-looking lines — `FAIL`, `Error`, `assert`, `Traceback`, `not ok` and
|
|
263
|
+
their siblings, the last 5 lines when none match, never the first line of stderr — bounded at
|
|
264
|
+
1024 bytes so this event, of all events, can never be the one whose `detail` §2.9's
|
|
265
|
+
4096-byte cap strips (#160). The story log quotes the excerpt in a fenced block and cites the
|
|
266
|
+
file `[src: …:1]`; the blocked-story reason and its handoff Finding carry the same citation;
|
|
267
|
+
and a story a person reopens now hands its next developer the log under `## Previous
|
|
268
|
+
attempt`, so the agent reads the real failure instead of rediscovering it in a worktree that
|
|
269
|
+
no longer exists. One heuristic in one helper (`failureExcerpt` in
|
|
270
|
+
`src/core/build/dodOutput.ts`), one path derivation (`dodOutputRel`), and a green command
|
|
271
|
+
still writes no file and emits the byte-identical event it always did. `not found` is in the
|
|
272
|
+
heuristic for a measured reason: a shell's `sh: <bin>: command not found` is the only line an
|
|
273
|
+
exit 127 has, and #209 reads the absent binary's name off `tail` — without it `absent_binary`
|
|
274
|
+
went from `dodbin` to `""` on the rebase. The citation resolves
|
|
275
|
+
through the repo's existing `lineOf()` to the line the excerpt starts on, never a constant
|
|
276
|
+
`:1`, and the heuristic reads bun's own `(fail)` summary line as well as the capitalised
|
|
277
|
+
spellings.
|
|
278
|
+
Two consequences worth stating in their own right. **The kept output may carry secrets** — it
|
|
279
|
+
is raw stdout and stderr, so an `env` dump, a token in a connection string or a stack trace
|
|
280
|
+
with a credential all land in it — and `tldrx-work/` is state this framework tells owners to
|
|
281
|
+
COMMIT. So `tldrx init`'s managed `.gitignore` block now excludes
|
|
282
|
+
`tldrx-work/**/04-build/log/dod-output/`, after the `!tldrx-work/**` re-include so it wins:
|
|
283
|
+
the tails stay on the machine that measured them, and the bounded excerpt inside
|
|
284
|
+
`04-build/log/<story>.md` is what goes into history. Delete that one line to opt in, and read
|
|
285
|
+
a tail before sharing it. And **the `## Previous attempt` header now says which kind of
|
|
286
|
+
attempt it was**: it was unconditionally "A reviewer read your last attempt … and asked for
|
|
287
|
+
changes", which over a DoD-only block would have sat above a log reading
|
|
288
|
+
`Verdict: n-a · Reviewer: not recorded` — a prompt asserting a review that never happened.
|
|
289
|
+
One renderer, two headers, chosen by data the executor already has.
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
## 0.14.3 — 2026-09-10
|
|
293
|
+
|
|
294
|
+
### Changed
|
|
295
|
+
|
|
296
|
+
- **A Build no longer stops because somebody else's work is uncommitted in the checkout
|
|
297
|
+
(#164).** The dirty-tree guard used to count every `git status --porcelain` entry and refuse,
|
|
298
|
+
and its only exits were "commit it" or "stash it" — neither of which an agent may take with
|
|
299
|
+
another person's files. Measured twice on two consecutive days on a Next.js workspace: 14
|
|
300
|
+
uncommitted paths, 13 untracked, none of them the run's, and nine stories waited three hours
|
|
301
|
+
for a human to grant one `git stash` at 4 a.m. Measured again across three real workspaces on
|
|
302
|
+
0.14.2: every first engine-driven run reaching Build stopped at `04-build`, over seed docs, a
|
|
303
|
+
data export and one untracked note. The dirt is now CLASSIFIED, and only one verdict still
|
|
304
|
+
refuses. `own` — anything under this workspace's own `tldrx-work/` or `.tldrx/` — is not dirt,
|
|
305
|
+
as before. `overlapping` — a dirty path inside a pending story's `touches:`, a submodule, or a
|
|
306
|
+
directory of the operator's that merely looks like tldrx state — refuses exactly as it did,
|
|
307
|
+
and names which path and why. Everything else is `foreign`: the engine sets it aside with a
|
|
308
|
+
pathspec-limited `git stash push` before it cuts the epic branch, records
|
|
309
|
+
`worktree.foreign_work_aside`, and pops it back when the stage ends, recording
|
|
310
|
+
`worktree.foreign_work_restored` — on every exit path, success or failure. A repo in the
|
|
311
|
+
middle of a merge, rebase, cherry-pick or bisect refuses outright and is never stashed into:
|
|
312
|
+
that state has no clean undo. Nothing is ever deleted and nothing is ever force-popped; a pop
|
|
313
|
+
git refuses is `restored: false` with the paths and the literal command, said as the stage's
|
|
314
|
+
LAST line, carried into the handoff's `## Unknowns` (and so into a PR body) and into the
|
|
315
|
+
`stage.done` / `run.finished` / `run.failed` notification's own summary — no new notify kind.
|
|
316
|
+
The run's exit code does not move for it, because that code answers for the run's work and not
|
|
317
|
+
for the operator's tree.
|
|
318
|
+
|
|
319
|
+
**The stash is the LAST step before anything is cut**, and every exit from the Build executor
|
|
320
|
+
— a refusal, a failure, a green stage — gives it back before it returns. An earlier draft took
|
|
321
|
+
the stash at the first door, so the foreign-epic refusal and the base pre-flight could both
|
|
322
|
+
refuse with the operator's files already moved, through a return path that neither restored
|
|
323
|
+
them nor said they had gone: reproduced in review as exit 2, a stash on the list, the file
|
|
324
|
+
missing, nothing printed, and a re-run that refused forever.
|
|
325
|
+
- **The restore reinstates the INDEX, not just the files (#164).** `git stash pop --index`, so a
|
|
326
|
+
path staged at one version and modified further in the worktree comes back both staged and
|
|
327
|
+
modified — the shape one of the three measured workspaces actually had (a script and a
|
|
328
|
+
`package.json` line staged in a sub-repo). Measured: a plain pop left `git show :f.txt` reading
|
|
329
|
+
the commit instead of what was staged. When `--index` itself refuses, the fallback is a plain
|
|
330
|
+
pop and the record says so — `index_restored: false` on the event and a sentence saying those
|
|
331
|
+
paths are back unstaged. `--keep-index` is deliberately not used on the push side: measured, it
|
|
332
|
+
leaves the staged content in the working tree, which is the dirt this path exists to remove.
|
|
333
|
+
|
|
334
|
+
### Fixed
|
|
335
|
+
|
|
336
|
+
- **The dashboard's live tests wait on the machine's clock, not a literal — the flake that
|
|
337
|
+
blocked four merges in two days (#193).** `test/dashboard-live.test.ts` and
|
|
338
|
+
`test/dashboard-server.test.ts` both call `setDefaultTimeout(spawnTestTimeout())`, so the
|
|
339
|
+
per-test BUDGET already scaled with load; every deadline that actually decided a result was
|
|
340
|
+
a number typed inside the assertion (`sse.next("reload", 5_000)`, `Date.now() + 2_000`), and
|
|
341
|
+
the load factor could never reach it. Each of those waits is an `fs.watch` notification
|
|
342
|
+
racing a stopwatch, so on a busy box a different SSE test reddened every time — measured
|
|
343
|
+
2026-09-09 under `load averages: 65–107` on 14 cores, five consecutive runs of the two files
|
|
344
|
+
went red 3 times, at 5084.27 / 5108.01 / 5256.49 ms against the 5000 and 2002.30 / 2002.55 /
|
|
345
|
+
2005.47 ms against the 2000. Not one failed on CONTENT: they timed out. The deadlines now go
|
|
346
|
+
through one helper, `eventWaitMs()` in `test/fixtures/machineLoad.ts`, which is
|
|
347
|
+
`spawnTestTimeout` with a 15 s base rather than a second scaler — the same derivation, so a
|
|
348
|
+
box given twice the budget to start a process is given twice the patience for a notification
|
|
349
|
+
from it — and the per-test budgets scale with it. The assertions are otherwise unchanged: the
|
|
350
|
+
fix is the instrument, not the behaviour, and `test/machine-load.test.ts` now refuses a
|
|
351
|
+
hard-coded millisecond deadline in either file so it cannot come back at somebody's merge.
|
|
352
|
+
The server's own timings are not what the 5 s bound was papering over — `DEBOUNCE_MS` is 300
|
|
353
|
+
(20 in these tests) and the mtime fallback sweeps every 500 ms, both an order of magnitude
|
|
354
|
+
under it — but the measurement did surface a second, distinct cause, filed as #213: with the
|
|
355
|
+
deadlines scaled, the failures that remain are all directory create/remove events that never
|
|
356
|
+
arrive AT ALL (114 s, 82 s, no frame), tracking `fseventsd` at ~100% CPU rather than load
|
|
357
|
+
average, and `watchWorkspace` runs its fingerprint sweep only in poll mode, so a dropped
|
|
358
|
+
FSEvents notification leaves a live dashboard silently stale for the life of the process.
|
|
359
|
+
This entry fixes the instrument; the bullet below is the product half.
|
|
360
|
+
|
|
361
|
+
- **A live dashboard can no longer go silently stale because the OS dropped a notification
|
|
362
|
+
(#213).** `fs.watch` is the fast path, never a guarantee — FSEvents drops and coalesces
|
|
363
|
+
under queue pressure — and `watchWorkspace` armed its mtime sweep only in `poll` mode, so
|
|
364
|
+
a dropped event meant nothing fired, nothing re-armed, and nothing ever swept: the page
|
|
365
|
+
stopped updating for the life of the process and said nothing about it. Measured on a
|
|
366
|
+
14-core macOS box with `fseventsd` at 98-115% CPU while `mdbulkimport` indexed: directory
|
|
367
|
+
create and remove events that never arrived at all — 82,556 ms and 113,942 ms with no
|
|
368
|
+
frame — while appends inside an already-watched directory kept arriving throughout. Load
|
|
369
|
+
average was not the predictor; fseventsd saturation was. The sweep now runs in BOTH modes:
|
|
370
|
+
it is the notifier in `poll` at 500 ms and a backstop in `watch` at `SWEEP_MS` (2 s,
|
|
371
|
+
overridable like `debounceMs`), so a dropped notification is bounded rather than fatal, and
|
|
372
|
+
the watcher is re-armed by the sweep's own `fire()`. Same `fingerprint`, one derivation, two
|
|
373
|
+
cadences. It is idempotent with the fast path — a change the watcher reported re-baselines
|
|
374
|
+
the sweep inside the same debounce, so one change is still one reload frame. RED first:
|
|
375
|
+
`test/dashboard-live.test.ts`'s new "a change the watcher missed still reaches the page, by
|
|
376
|
+
sweep" hangs a real change off a server whose `fs.watch` handles have been closed
|
|
377
|
+
(`simulateWatcherLoss()` — you cannot ask the OS to drop an event on demand) and went red
|
|
378
|
+
before this change with no frame at all.
|
|
379
|
+
- **The refusal's printed remedy no longer makes the run disappear (#164).** It printed
|
|
380
|
+
`git stash push -u -m "tldrx <run> foreign work"` with no pathspec. Measured on 0.14.2: an
|
|
381
|
+
owner ran it exactly as printed and it swept the run's OWN untracked records under
|
|
382
|
+
`tldrx-work/<run>/` into the stash, after which `tldrx next` answered
|
|
383
|
+
`no run '<id>' in tldrx-work/` and `tldrx status` said `nothing pending`. Nothing was lost —
|
|
384
|
+
`git stash pop` brings it all back — but the framework's own advice had made its own run
|
|
385
|
+
vanish, and an owner who does not know git internals reads that as the work being gone. The
|
|
386
|
+
remedy is now limited to exactly the paths the refusal listed, each passed after a literal
|
|
387
|
+
`--`, and the relaunch verb is chosen by MODE: `tldrx run auto <run>` when the engine is
|
|
388
|
+
driving, `tldrx next` when a person is. The owner was in `run auto` and the message told him
|
|
389
|
+
to run `next`, which is the cursor verb. The printed line is also the SAME string the engine
|
|
390
|
+
runs, `:(literal)` pathspecs and all, shell-quoted so it survives being retyped: it used to
|
|
391
|
+
join the raw paths while the engine passed `:(literal)`, and the docstring claiming they were
|
|
392
|
+
one thing was simply false — measured, the printed line for a file called `[x].txt` moved the
|
|
393
|
+
neighbouring `x.txt`.
|
|
394
|
+
- **A filename with a space, a leading dash or a bracket is the file that moves (#164).** Git
|
|
395
|
+
pathspecs are globs by default, so `git stash push -u -- 'a[b].txt'` takes the neighbouring
|
|
396
|
+
`ab.txt` with it — measured in a scratch repo, the tree came back empty where one file should
|
|
397
|
+
have remained dirty. Every path the framework hands to git for a write is now
|
|
398
|
+
`:(literal)<path>`, and `git status` is read with `-z` so the path is the bytes on disk rather
|
|
399
|
+
than git's quoted-and-escaped rendering of them. This is the first thing tldrx does that
|
|
400
|
+
WRITES to the operator's uncommitted work, and it is the one operation with no undo.
|
|
401
|
+
|
|
3
402
|
|
|
4
403
|
## 0.14.2 — 2026-09-09
|
|
5
404
|
|
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.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 |
|
|
320
|
+
| 0.14.3 | 2026-09-10 | `beta` | foreign uncommitted work no longer stops a Build, and the dashboard flake that blocked four merges in two days has a root cause: the dirty-tree guard used to count every `git status --porcelain` entry and refuse, offering only "commit it" or "stash it" — neither of which an agent may take with another person's files — and measured across three real workspaces on 0.14.2, every first engine-driven run reaching Build stopped at `04-build`, over seed docs, a data export and one untracked note; the dirt is now classified, `own` and `overlapping` refusing or passing exactly as before while everything `foreign` is set aside with a pathspec-limited `git stash push` as the LAST step before the epic branch is cut, recorded as `worktree.foreign_work_aside` and given back with `--index` on every exit path, success or failure, nothing ever deleted and nothing force-popped, a repo mid-merge, rebase, cherry-pick or bisect refused outright because that state has no clean undo, and a pop git refuses said as the stage's last line and carried into the handoff and the notification; the refusal's printed remedy is now the SAME string the engine runs, limited to the paths it listed and relaunching by mode, after an owner ran the pathspec-less line exactly as printed and it swept the run's own records under `tldrx-work/<run>/` into the stash until `tldrx next` answered `no run`; every path handed to git for a write is `:(literal)` and `git status` is read with `-z`, since a glob pathspec moved the neighbouring `x.txt` for a file called `[x].txt`; and the dashboard's live tests stop racing a typed millisecond — five consecutive runs of the two files went red 3 times, at 5084.27 / 5108.01 / 5256.49 ms against a hard-coded 5000 under load averages 65–107 on 14 cores — every deadline now deriving from one `eventWaitMs()` helper that scales like every other budget, with `test/machine-load.test.ts` refusing a hard-coded deadline in either file so it cannot come back at somebody's merge, while that measurement surfaced the product half: `watchWorkspace` armed its mtime sweep only in `poll` mode, so a dropped FSEvents notification left a live dashboard silently stale for the life of the process — measured with `fseventsd` at 98–115% CPU, directory events that never arrived AT ALL at 82,556 ms and 113,942 ms — and the sweep now runs in watch mode too, at 2 s, so a dropped notification is bounded rather than fatal |
|
|
319
321
|
| 0.14.2 | 2026-09-09 | `beta` | every citation check starts from a fresh view of `questions.md` and `facts.yml`: `srcToken.ts` memoised both indexes at module scope and nothing outside `test/` ever dropped them, so in `tldrx run auto` — one Node process for a whole run — the FIRST citation resolved anywhere froze the view every later stage was then judged against; measured on three real unattended workspaces at 0.14.1, a `how` stage refused with `no such question Q2 … declared: Q1` over questions it had itself written minutes earlier, and another refused over `145 live fact(s)` when `facts.yml` held 148, the three extra written by the owner's answers two seconds before the stage started — roughly $11 of paid turns thrown away for ids that were real the whole time; the indexes are now refreshed inside `toSrcContext`, the one place a citation context is built and a place every caller reaches exactly once per check, gate or hook, so a document's forty citations still read `facts.yml` once while nothing survives the check that read it |
|
|
320
322
|
| 0.14.1 | 2026-09-09 | `beta` | an auto gate that says what holds it and closes itself, and three derivations cut to one: measured on a real workspace at 0.14.0, the first `run auto --wait-answers 4h --wait-gates 4h` with a notify hook finished its What stage for $1.98 with every declared check green and told the owner *"waiting at a auto gate that did not close by itself — a person signs it"* — no reason, because `evaluateAutoGate` had computed exactly that sentence into a stdout line nobody was watching while `gate.requested` was appended a hundred lines BEFORE the verdict existed; the verdict is now taken one statement earlier and `why`/`held_by` ride the event to the phone (present only for an `auto` policy, since `held_by: []` on a `human` gate would read as "checked, and nothing held it"), the questions that hold a gate are notified BEFORE the gate downstream of them so the owner stops being told to sign a thing before being told what it is, and an `auto` gate whose only blocker was an open question stops permanently degrading into a `human` one — each poll re-runs the seven conditions off disk and signs through the same `approve` door `next` uses, never for `human` and never for `agent`, a person's `approve` or `reject` still landing first; a question can carry its own `Recommended: <letter> — <why> [src: …]`, parsed tolerantly so an older note reads as not recorded rather than refusing; `tldrx facts add --repo <name>` is now checked against `workspace.yml` through the ONE leaf `answer --repo` already refused on, because the unvalidated flag wrote `repos: [ghost]` at exit 0 and every prompt or filter keyed on a real repo name was then silently blind to that fact; the five phase ids are written out in one file instead of three — `PHASE_IDS`, `QUESTION_PHASES`, and an inline literal walked by `questions lint` that `QUESTION_PHASES`' own doc comment claimed could not exist — pinned from both ends, because an identical second copy and a copy that has already drifted redden different guards and neither alone is enough; a training test stops running on bun's fixed 5000 ms after a spawn probe, validated first against a file the heuristic already claimed (87 spawns recorded, so the instrument can see the thing), found it spawning three real children unclaimed by any marker, with the 14 further unclaimed files filed rather than fixed; and `release-check.sh` refuses a rewritten released section — every dated heading whose tag is present must equal `git show vX.Y.Z:CHANGELOG.md` byte for byte, three sections having drifted with nothing checking, and an amendment recorded in `CHANGELOG.amendments` must still contain the tag's section as an ordered subsequence and may only add lines that exist verbatim in its source sha, after a reviewer proved the first version of that check happily passed an invented bullet |
|
|
321
323
|
| 0.14.0 | 2026-09-09 | `beta` | an `agent` gate the engine can actually close, and three computations of the same answer cut to one: `gates_policy: agent` named who MAY sign a gate but nothing in the engine produced the evidence note it is signed over — measured on 0.13.1, an owner ran `tldrx run gates set what:agent`, was told "an agent may now close it", and the loop stopped at the next gate anyway with exit 4 — so `run auto` now spawns one bounded **gate signer** when a stage's checks pass under an `agent` policy: the stage's own model and effort, a quarter of its per-agent ceiling, a tool allowance that reads anything and writes exactly one file, and a prompt carrying the stage's declared outputs, the seven `auto` conditions as measured and the §2.8 skeleton `gate template` itself renders — the note going through the UNCHANGED `approve --as-agent` path, so a refusal, a note that does not validate, a signer that wrote nothing and a signer that died are one outcome, pending for a person with the reason named on stdout and now in the `gate.requested` summary, and there is no flag to turn it on because an `agent` policy is already the owner's recorded decision; the turn is recorded like any other (`role: gate-signer`, a `run.yml` task row, a row in `tldrx cost`) and taken BEFORE the stage moves to `awaiting_gate`, because the other order had a person sign the gate the engine was mid-signing, three runs out of three; and the suite stops being run three times per change — the pre-merge reviewer now runs only the test files that cover its diff plus `typecheck`, never the full `bun test`, since the wave re-runs every gate on the MERGED tree anyway, `publish.yml` refuses unless `ci` has a `success` run for the same sha instead of recomputing typecheck/tests/build (~87 min/week of runner time, with a `cancelled` ci run failing by name and the remedy), `ci` cancels a run a newer push has already superseded (25 of 122 push runs began under 10 minutes apart), the docs deploy finally fires on `src/cli/helpText.ts` so a help-registry change stops deploying nothing while the published CLI reference goes stale, and `AGENTS.md` §2 now says out loud that a slash in a branch name is a directory, which is what the review-record gate builds |
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-z0hnthw4.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-54vzevgt.js";
|
|
9
9
|
import {
|
|
10
10
|
parseHookInput,
|
|
11
11
|
readStdin
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
PHASE_IDS
|
|
15
|
-
} from "./chunk-dzkg8c68.js";
|
|
12
|
+
} from "./chunk-ds9sew2e.js";
|
|
16
13
|
import {
|
|
17
14
|
EventLog,
|
|
18
15
|
PHASE_ID_RE
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import
|
|
16
|
+
} from "./chunk-rz0qr006.js";
|
|
17
|
+
import {
|
|
18
|
+
PHASE_IDS
|
|
19
|
+
} from "./chunk-d0rp8c68.js";
|
|
20
|
+
import"./chunk-k4nqzdw5.js";
|
|
21
|
+
import"./chunk-nqwg5ddj.js";
|
|
21
22
|
import {
|
|
22
23
|
ADVISORY_KEY,
|
|
23
24
|
MAX_FACT_CHARS,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
renderQuestionBlock,
|
|
28
29
|
replaceBlock,
|
|
29
30
|
serializeQuestions
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-fm141jak.js";
|
|
31
32
|
import {
|
|
32
33
|
ITERATION_ONLY_SLOT,
|
|
33
34
|
PROJECT_FRAMEWORK_DIR,
|
|
@@ -35,7 +36,7 @@ import {
|
|
|
35
36
|
commandProbeIssues,
|
|
36
37
|
factsPath,
|
|
37
38
|
parseYaml
|
|
38
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-77y6q9mt.js";
|
|
39
40
|
|
|
40
41
|
// src/hooks/answer-capture.ts
|
|
41
42
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -1,48 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-vmhfw2r1.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-msswet35.js";
|
|
12
|
+
import"./chunk-ds9sew2e.js";
|
|
13
13
|
import {
|
|
14
14
|
asRunBudget,
|
|
15
15
|
currentActor,
|
|
16
|
-
cursorStage,
|
|
17
16
|
economyFor,
|
|
18
|
-
hostTokensIn,
|
|
19
|
-
isAttendedByHostView,
|
|
20
17
|
isHostTokens,
|
|
21
|
-
loadRunView,
|
|
22
|
-
newestActiveRun,
|
|
23
18
|
nowRfc3339,
|
|
24
19
|
raiseCommand,
|
|
25
20
|
remainingWork,
|
|
26
|
-
renderRunEconomies,
|
|
27
|
-
runSpend,
|
|
28
21
|
shortBy,
|
|
29
22
|
validateRunBudget,
|
|
30
23
|
wouldExceed,
|
|
31
24
|
wouldExceedHostTokens
|
|
32
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-3kmx3dmz.js";
|
|
33
26
|
import {
|
|
34
27
|
EventLog
|
|
35
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-rz0qr006.js";
|
|
29
|
+
import {
|
|
30
|
+
cursorStage,
|
|
31
|
+
hostTokensIn,
|
|
32
|
+
isAttendedByHostView,
|
|
33
|
+
loadRunView,
|
|
34
|
+
newestActiveRun,
|
|
35
|
+
renderRunEconomies,
|
|
36
|
+
runSpend
|
|
37
|
+
} from "./chunk-4mjxyfp9.js";
|
|
38
|
+
import"./chunk-k4nqzdw5.js";
|
|
36
39
|
import {
|
|
37
40
|
noteDeprecations
|
|
38
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-fm141jak.js";
|
|
39
42
|
import {
|
|
40
43
|
PROJECT_WORK_DIR,
|
|
41
44
|
findWorkspaceRoot,
|
|
42
45
|
locateWork,
|
|
43
46
|
parseYaml,
|
|
44
47
|
stageYamlPath
|
|
45
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-77y6q9mt.js";
|
|
46
49
|
|
|
47
50
|
// src/hooks/budget-gate.ts
|
|
48
51
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|