tldr-experts 0.31.1 → 0.33.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 CHANGED
@@ -1,5 +1,278 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.0 — 2026-09-17
4
+
5
+ ### Added
6
+
7
+ - **A workspace can opt its base pre-flight probe into a story-shaped worktree, so an
8
+ environmental red that only appears there is caught before the first story (#371).** #363
9
+ shipped `tree:` provenance on every base-pre-flight row and declined, by measured decision, to
10
+ move the probe itself into a worktree by default — doing so on every run would pay for the
11
+ declared command a second time and reopen the `node_modules`-less outage the checkout-only
12
+ design exists to avoid. `probe_in_worktree: true`, workspace-wide and additive, is the opt-in
13
+ for a workspace that has actually seen the gap #363 named: a command green in the checkout and
14
+ red only in a story's own worktree shape (a `.git` FILE vs. a `.git` directory is the measured
15
+ case). On, every declared base command is ALSO measured, once per repo, in a fresh detached
16
+ worktree of the base sha — kept beside the checkout row, never instead of it, under the same
17
+ `tree:` field (`checkout` vs. `worktree`) rather than a third tree constant. Off (the default,
18
+ and every workspace before this key existed) leaves the base pre-flight measuring the checkout
19
+ only, exactly as before. Pre-merge review measurement: a session that calls the base pre-flight
20
+ more than once (a `prepare()` followed by the real call) paid for a `git worktree add`/`remove`
21
+ on EVERY call, even when every declared command already had a fresh `tree: worktree` reading and
22
+ nothing was spawned there — fixed by checking every command's cache hit before touching git at
23
+ all, so a worktree is opened only when at least one command still needs a fresh measurement.
24
+
25
+ ### Fixed
26
+
27
+ - **The plan sequencing rule (#365) could false-positive two unrelated stories that happened to
28
+ share a generic single-transition camelCase token (#370).** `fieldsIn`
29
+ (`src/core/plan/planShape.ts`) captured ANY bare camelCase/PascalCase token with one inner
30
+ capital as a field candidate, so a story enforcing an invariant near a word like `toString` or
31
+ `getId` collided with an unrelated story populating a same-named `toString` or `getId`
32
+ elsewhere, refusing an honest plan for sharing nothing but an incidental identifier. A bare
33
+ camelCase/PascalCase token with at least two internal capital transitions
34
+ (`DeliveryAddressText`) still always counts; a one-transition token (`toString`, `getId`) now
35
+ counts only when the SAME normalized field is ALSO spelled `snake_case` or backtick-quoted
36
+ somewhere else in the plan — dropping one-transition tokens outright, the first cut at this
37
+ fix, reopened #365 for a genuine two-word field spelled bare PascalCase on one side and
38
+ `snake_case` on the other (`order_total` enforced in one story, bare `OrderTotal` populated in
39
+ another still refuses; `toString` shared by two unrelated stories, with no `snake_case` or
40
+ backtick sibling anywhere in the plan, still does not). `snake_case` and backtick-quoted
41
+ identifiers are unaffected either way, and the originating #365 case still refuses.
42
+ - **`task.done`'s `as_is_note` had no size bound, unlike its two siblings on the same event
43
+ (#368).** `permission_refused` and `budget_death` were clamped by #359 after a ~110-line
44
+ refused heredoc alone pushed `task.done` past the §2.9 4096-byte payload cap and failed the
45
+ whole build STAGE, not just the story. `as_is_note` — the operator-typed `--note` on `tldrx
46
+ story reopen <id> --as-is` — was never given the same treatment, even though nothing stops a
47
+ long, scripted or pasted note from reaching the exact same shape. It is now passed through the
48
+ same `clampAgentText` call, the same `TASK_DONE_FIELD_MAX_BYTES` constant and the same
49
+ `[clamped: N bytes exceeds the M byte cap]` marker convention #359 already set, so an
50
+ oversized as-is note can no longer fail the build stage.
51
+ - **The Build-entry probe's throwaway worktree ran a repo's `install:` but not its
52
+ `tool_restore:`, so a command whose resolution depended on the restore having run could be
53
+ refused there even though the checkout door — which does run `tool_restore:` since #363 —
54
+ would have been fine (part of #371).** `entryProbe.ts`'s per-repo declaration now also reads
55
+ `tool_restore:` (`toolRestoreCommandFor`, the same slot `prepBaseTree` already runs at the
56
+ checkout door) and runs it in the fresh worktree before that worktree's `install:` and before
57
+ any Definition-of-Done command's binary is resolved there, refusing Build by name on a
58
+ nonzero exit — the same shape the checkout door already uses, never a second copy of the
59
+ restore logic.
60
+
61
+ ### Changed
62
+
63
+ - **`settleAskedNoDiff` and `settleRedDod` now share one requeue-settle helper instead of each
64
+ hand-building the same shape (#369).** Both settled a "requeue this attempt, attempts remain"
65
+ turn by constructing an identical eight-field n-a-review object and `keepWorktree: true` by
66
+ hand — copied verbatim in two places, the AGENTS.md §12 duplication the file's own
67
+ decomposition precedent exists to avoid. `settleRequeue` now holds that shape once, taking the
68
+ fields that actually vary (`dod`, `reason`, `askedNoDiff`) as data; both call sites are
69
+ unchanged in behavior — `test/build-golden.test.ts` stays byte-identical.
70
+ - **AGENTS.md §11 now names the negated-closing-verb trap and widens its own verification
71
+ grep to catch it.** Measured 2026-09-16: commit `d8f90f9`'s sentence `reported not fixed:
72
+ #359` auto-closed #359 anyway — GitHub's closing-keyword parser matches `fixed` (any
73
+ inflection) followed by an optional colon/punctuation and `#N`, and does not parse the
74
+ negation three tokens earlier; #359 needed a reopen before its real fix (`24f1324`) closed
75
+ it a second time, legitimately. §11's own documented verification command,
76
+ `grep -iE '(clos|fix|resolv)[a-z]* #'`, had the identical blind spot — it does not allow a
77
+ colon between the verb and the number, so it read `fixed: #359` as a non-match (measured
78
+ exit 1) and would have let this exact sentence through. The grep is widened to
79
+ `(clos|fix|resolv)[a-z]*[[:punct:]]* #` (measured exit 0 on the same sentence); the real
80
+ discipline stays "never write a closing-shaped phrase near a `#N` that should not close" —
81
+ the grep is a tripwire, not the fix (closes #372).
82
+ - **`ci.yml` now keeps `test/merge-wave.test.ts`'s tmp sandbox as a build artifact when the
83
+ job fails.** This is an INSTRUMENT change, not a fix for the still-undiagnosed #237 flake
84
+ (interrupted-merge case expects a SIGTERM exit of 143, sometimes sees 0 after ~92s): every
85
+ #237 data point so far recorded the same second wall on top of the first — "no merge.log is
86
+ available from the CI sandbox" — because the sandbox that file plants was discarded with
87
+ the runner. The `bun test` step's `TMPDIR` now points at `${{ runner.temp }}/mw` instead of
88
+ the OS default, and a new `if: failure()` step uploads a scoped subset of it on failure
89
+ (`actions/upload-artifact@v4`, `retention-days: 14`). Review caught a second wall in the
90
+ first cut (closes #372's own class of bug, an instrument that cannot observe the thing):
91
+ `afterEach` `rmSync`s every sandbox unconditionally, pass or fail, INSIDE the bun process,
92
+ before that upload step ever runs — so the artifact would have been empty on the exact run
93
+ it exists for. `test/merge-wave.test.ts` now mkdtemps its sandboxes under the recognisable
94
+ `mw-sandbox-` prefix and skips its own cleanup when `MERGE_WAVE_KEEP_SANDBOX` is set (the
95
+ decision lives in one small, unit-tested function, `keepSandbox`/`cleanupSandbox` in
96
+ `test/fixtures/mergeWaveSandbox.ts` — local runs stay hermetic and self-cleaning per §8
97
+ unless that var is set); ci.yml sets it on the `bun test` step and scopes the upload's
98
+ `path:` to exactly that prefix, so the next red leaves a `merge.log` and lock state an
99
+ agent can actually read (see #237, closes #373).
100
+
101
+ ## 0.32.0 — 2026-09-16
102
+
103
+ ### Fixed
104
+
105
+ - **A refused COMPOUND ad hoc Bash line — never the Definition of Done command itself — was
106
+ turning an ordinary red DoD into a human `blocked` (#360).** Measured on a live unattended run:
107
+ 4 of 4 refusals were the developer's own ad hoc verification line (a `dotnet format … > log
108
+ 2>&1; echo …`, a `;`-chained `find`, a piped `find … | xargs cat`, a `docker info … && echo …`
109
+ — none of them the DoD), and 2 of 5 stories blocked at attempt 1 of 2 even though each had
110
+ committed work and a DoD that went on to run and decide, each costing an operator `story
111
+ reopen` + `reject --and-continue` (22–27 minutes measured twice). The #271/#313 rule reasoned
112
+ that "the same allowance would refuse the same command again", which is true of a verbatim
113
+ re-ask but not of a compound line refused for its SHAPE: `dodRedRequeue`
114
+ (`src/core/build/reviewRound.ts`) now requeues a red DoD, like any other, when the refusal
115
+ `classifyRefusal` (`src/core/build/refusalKind.ts`) reads as `separator` (chained — `&&`, `;`,
116
+ a pipe, a redirect, a heredoc) or `undeclared` (a command no `commands:` slot grants) — an
117
+ ungranted git `verb` or `git -C` (`elsewhere`) refusal still blocks on the first attempt,
118
+ because THAT line, verbatim, would be refused again. The refusal stays on the record either
119
+ way; only the block is narrowed.
120
+ - **The developer prompt told every Bash call to run one command alone only for the Definition of
121
+ Done (#360).** `## Rules` (`src/core/build/prompts.ts`) carried the "no redirection, pipes or
122
+ chaining" sentence beside the DoD rule alone, so a developer choosing its OWN ad hoc
123
+ verification command — 4 of 4 refusals in the run above — never read it. The rule now covers
124
+ every Bash call: "one command per Bash call, run verbatim and alone … no redirection, pipes,
125
+ chaining or heredocs … a compound line is refused unread". This changes the golden developer
126
+ prompt's bytes on purpose — `test/build-golden.test.ts`'s fixtures are updated for exactly
127
+ those lines.
128
+ - **A story released from a stale dependency hold could sit `blocked` forever if the gh #298
129
+ rate-limit park landed on its own turn (#361).** `staleDependencyHold` deciding a `blocked`
130
+ row's dependency is now `done` only added the story to this pass's `pending` list and logged a
131
+ line — the story file itself stayed `blocked` until `driveStory` actually settled the attempt,
132
+ and `driveStory`'s very first act is the rate-limit park, which returns before any write when
133
+ the wall is already up from an earlier story's turn. Measured on a live unattended run
134
+ (0.31.1): a story sat `blocked` across a whole further invocation, with `gate.requested`
135
+ reporting the generic `settled by an earlier \`tldrx next\`` reason a never-touched row falls
136
+ back to, and an operator had to `story reopen` it by hand. The release is now written to the
137
+ story file (`blocked` → `todo`) the moment it is decided, so a park before the attempt leaves
138
+ the story exactly where an ordinary un-attempted `todo` row sits — picked up plainly by the
139
+ next invocation instead of re-deriving the same release from the log every time it gets that
140
+ far.
141
+ - **An oversized `permission_refused` failed the whole BUILD STAGE, not just the story, and bought
142
+ `run auto` a relaunch nobody owed (#359).** Measured on a live unattended run (0.31.1): a
143
+ developer's refused command was a ~110-line heredoc; `permission_refused` (#271, additive) is
144
+ copied verbatim and was never on the emit seam's droppable-field list
145
+ (`detail`/`recording_error`/`outputs`/the `touches_widened` path lists), so a 5474-byte refused
146
+ command alone put a real `task.done` over the §2.9 4096-byte cap, `EventLog.append` refused it,
147
+ the executor threw, and the stage failed — re-dispatching a sibling story's developer for a turn
148
+ its own attempt never spent. `permission_refused` and `budget_death` (#277, the same shape) are
149
+ now clamped in `executors/build.ts`'s `settle` — head, bounded to 1024 bytes (a quarter of the
150
+ cap, `dodOutput.ts`'s existing budget, reused rather than re-derived), plus a marker naming the
151
+ original byte count and pointing at `04-build/log/<story>.md`, which already carries the field
152
+ in full (written by `writeLog` before this event, so the pointer is true by construction). The
153
+ review log, the handoff and the blocked-story reason are unaffected — none of them carry the
154
+ event-payload cap.
155
+ - **A rate-limit `allowed_warning` parked a Build stage's whole dispatch at ANY utilization,
156
+ turning an unattended run into a human gate seven times over 53–60% of the seven-day window
157
+ (#367).** `noteRateLimit` (`src/core/facilitator/executors/build.ts`) now parks an
158
+ `allowed_warning` only at or above 90% utilization of the window that carries it (owner
159
+ decision, Slack, 2026-09-16); below the line the warning is recorded on the same
160
+ `agent.rate_limited` event (`parked_absent: "below the 90% park threshold…"`) and dispatch
161
+ continues. Any other status — a real wall, or one this repo has no reading for — still parks
162
+ unconditionally, as #298 always has. Second half: `run auto --wait-gates` (`runAuto.ts`) no
163
+ longer waits on a rate-limit-parked gate as if a person had to decide it — it reads the
164
+ park's own `resets_at` off the ledger (`src/core/run/rateLimitPark.ts`) and resumes the
165
+ stage itself once the provider's own clock has passed it (signed `run auto (rate-limit)`,
166
+ a separate actor from #231's checks-retry so the two bounds can never collide), backing off
167
+ a fixed interval when the frame stated no reset at all — but ONLY when that park explains
168
+ EVERY story the stage still has unfinished (every one a plain, never-attempted `todo`,
169
+ scoped to the CURRENT attempt's own `stage.started`): a gate also held by an independently
170
+ `blocked` story, or one whose only park is stale history from an earlier attempt, is never
171
+ auto-resumed (caught on review — `storiesCondition` collapses every unfinished story into
172
+ one `stories` id, so a park on one story is not evidence about another). `run status`'s
173
+ "held by: stories" line now says "parked by a rate-limit warning" and when it resumes,
174
+ under the same predicate, instead of reading as a decision waiting on a human.
175
+ - **A declared `tool_restore:` command never ran anywhere automatically, so the base pre-flight's
176
+ own first probed command could refuse over a missing local tool before an operator had any
177
+ evidence beyond that refusal (#363).** Measured on a live unattended run (0.31.1, .NET
178
+ workspace): the workspace declared `tool_restore: dotnet tool restore` and no `install:` at
179
+ all — only `install:` was ever auto-run, and only inside a story's own fresh worktree
180
+ (`worktreeDeps.ts`) or the Build-entry probe's throwaway worktree (`entryProbe.ts`, #254),
181
+ never in the checkout the base pre-flight itself reads — so `dotnet-ef` stayed missing there
182
+ until an operator ran `dotnet tool restore` by hand. `redBaseRefusal`
183
+ (`src/core/build/dodRunner.ts`) now runs a repo's declared `tool_restore:` once in the checkout,
184
+ before its first probed command, and refuses Build by name — nothing dispatched, nothing
185
+ charged — when that restore itself fails. `install:` is deliberately NOT added to this same
186
+ door: it already has two (a story's worktree, the entry probe's worktree), and running it a
187
+ third time, automatically, in the human's own checkout regressed an existing one during this
188
+ fix (`test/story-worktree-deps.test.ts` case (d) started reporting the wrong sentence) — see
189
+ `toolRestoreCommandFor` in `src/core/build/worktreeDeps.ts` for why that door stays closed.
190
+ - **The base pre-flight's own checkout and a story's worktree are different trees, and a green
191
+ reading from the first said nothing about it (#363).** Measured on the same live run: the base
192
+ pre-flight ran a command clean in the checkout; the identical command, on the identical base
193
+ sha, then failed inside a story's own worktree — a contract test asserting a build-time commit
194
+ id that SourceLink could not resolve there, an environment difference (a `.git` FILE, git's own
195
+ worktree convention, vs. the checkout's `.git` directory) the checkout reading cannot see by
196
+ construction. Moving the probe itself into a throwaway worktree was considered and rejected
197
+ with evidence: `dodRunner.ts`'s own "Where it runs" already documents why the checkout is
198
+ deliberate (a pristine worktree "would fail half the world's repos for want of `node_modules`
199
+ — turning this safety net into an outage"), and paying for every declared command a second time
200
+ on every run that needs a fresh measurement is a real, ongoing cost for a narrower class of gap
201
+ than that outage. Instead, absent-with-reason (AGENTS.md §7): every base-tree row in
202
+ `04-build/preflight.yml` now carries `tree: checkout`, so a reader — human or the loop itself —
203
+ cannot mistake "green on the base" for "green in the tree shape a story's own DoD actually runs
204
+ in".
205
+ - **The Build developer's own brief told it "these files are the ONLY ones you may read" —
206
+ literally, for every story whose declared `touches` were fully inlined — and a headless
207
+ developer that believed it asked a question instead of opening a file it needed and left no
208
+ diff (#364).** Measured live: story S5's `touches` had 2 files, both inlined; `## Inputs`'
209
+ preamble (`src/core/facilitator/prompt.ts`) and `## Investigate` step 1 (`src/core/build/
210
+ prompts.ts`, "They are the whole brief") both read as a hard READ ceiling rather than the
211
+ WRITE allowlist `touches` is meant to be, so the developer could not see how to reach the
212
+ port interface, command fields and sibling-test conventions its acceptance criteria needed —
213
+ $0.50, no diff, and the story sat `blocked` until an operator ran `story reopen` +
214
+ `reject --and-continue`. `preamble()` is now role-aware (`PreambleRole`, default `"reader"`
215
+ byte-identical to before): the Build developer's `## Inputs` and `## Investigate` now say
216
+ plainly that `touches` limits what it may CHANGE, never what it may READ — every other
217
+ caller (Watch, the `What` stage) is unchanged. Second half of the same defect: when the
218
+ run's `questions_policy` (§2.2) resolves the Build stage to anything but `human`, the
219
+ developer brief now carries an explicit rule — "This run is unattended: nobody answers
220
+ questions. Decide, state the assumption in your handoff, and proceed" — and a turn that
221
+ asks anyway (`questions_asked` non-empty) and leaves no diff is recorded `failure_kind:
222
+ "asked_no_diff"` (reusing #348's vocabulary) and requeued while attempts remain, the same
223
+ shape a red DoD gets, instead of a terminal `blocked` only a human could lift. Attended runs
224
+ are unchanged: the new checks are gated on `unattended` and never fire under the default
225
+ `human` policy. This changes the golden developer prompt's bytes on purpose —
226
+ `test/build-golden.test.ts`'s fixtures are updated for exactly the `## Inputs` preamble and
227
+ `## Investigate` steps 1–2.
228
+ - **A story rescued from a dead-developer block had the same gap #361 fixed for a stale
229
+ dependency release, one branch above it in the wave loop (#366).** Deciding
230
+ `blockedByFailedDeveloper(planned) !== null` — a `blocked` row whose developer never actually
231
+ ran — only added the story to this pass's `pending` list and logged a line; the story file
232
+ itself stayed `blocked` until `driveStory` settled an attempt, and `driveStory`'s very first
233
+ act is the same gh #298 rate-limit park #361 fixed for the dependency case, which returns
234
+ before any write when the wall is already up from an earlier story's turn in the same pass. A
235
+ story rescued and then parked before its own turn was left exactly where it started, `blocked`
236
+ on disk, forever. Both releases now go through one shared step, `releaseBlockedStory`
237
+ (`src/core/facilitator/executors/build.ts`), that writes `blocked` → `todo` the instant either
238
+ kind of release is decided, before the story is even offered a turn — one implementation, so a
239
+ park right after either can never undo it.
240
+
241
+ ### Changed
242
+
243
+ - **`docs/ROADMAP.md` states the project's north star and how we measure toward it (owner
244
+ decision, 2026-09-16).** One sentence, three metrics tracked on every run (human
245
+ interventions per run, predicted-vs-actual cost and time, defects the reviewer catches
246
+ before merge versus after), a measured "where we are" for 2026-09-16, and how the shipped
247
+ pieces and the still-unbuilt ones map to it.
248
+
249
+ ### Added
250
+
251
+ - **The `plan` gate refuses a story that enforces an invariant over existing data ahead of the
252
+ story that populates it (#365).** Measured on a live unattended run: S1 (wave 1) added a
253
+ database check constraint reading, in effect, "when fulfillment mode = Delivery, three columns
254
+ are NOT NULL" — and S4 (wave 2), the story that starts populating those columns, ran a wave
255
+ later. The `depends_on` graph was VALID (S4 legitimately depended on S1), so `validateWaveOrder`
256
+ saw nothing wrong; the defect was semantic, not structural — every existing Delivery fixture
257
+ violated S1's own constraint the moment S1 added it, so S1's DoD was structurally red before a
258
+ line of implementation. Two changes, `src/core/plan/planShape.ts`: (a) a new `PLAN_SHAPE_RULES`
259
+ entry tells the Plan agent a story adding an invariant over existing data lands in the same
260
+ story as, or in a wave after, the story that satisfies it — write it non-enforcing
261
+ (nullable/consistency-only) if it must land first; (b) `validatePlanShape` now scans each
262
+ story's `acceptance`/`test_plan` sentences for a short, exported enforcement-keyword set
263
+ (`ENFORCEMENT_KEYWORDS`: check constraint, not null, required, must be present, validation
264
+ rule, rejects, refuses) naming a field, and refuses the plan when a story no later in wave
265
+ order names the same field — matched case- and separator-insensitively, so `delivery_address_text`
266
+ and `DeliveryAddressText` collide — with a populate verb (`POPULATE_VERBS`: sets, populates,
267
+ writes, stores, fills, assigns) — naming both story ids, each story's own spelling, and both
268
+ sentences. This is the
269
+ cheaper substitute for re-enabling the `how` stage the owner already declined (owner decision):
270
+ a targeted static check over the plan's own text, not a whole extra paid agent turn. No
271
+ override field exists for a false positive on purpose — rewording the sentence past the
272
+ keyword set, or moving the story, is cheaper than a new plan-file escape hatch, and none of
273
+ the sibling shape rules (the wave cap's `wave_cap_reason` aside, which relaxes a different,
274
+ purely numeric rule) has one either.
275
+
3
276
  ## 0.31.1 — 2026-09-16
4
277
 
5
278
  ### Fixed
package/README.md CHANGED
@@ -335,6 +335,8 @@ back on the registry is 0.3.0.
335
335
 
336
336
  | Version | Date | Status | Contains |
337
337
  |---|---|---|---|
338
+ | 0.33.0 | 2026-09-17 | `beta` | Six follow-ups from the 0.32.0 cycle, each measured on the second unattended run (run #42) or on the review that shipped it. The plan sequencing rule counts a bare camelCase field only when it is unambiguous or the plan spells it elsewhere as snake_case or in backticks, so unrelated stories sharing a word like `toString` no longer refuse each other (#370); `task.done` clamps an oversized `as_is_note` the way it clamps `permission_refused` (#368); the asked-no-diff and red-DoD requeues settle through one helper that takes data (#369); a workspace can opt its base pre-flight probe into a story-shaped worktree with `probe_in_worktree: true`, and the Build-entry probe runs `tool_restore:` first (#371, the remainder of #363); AGENTS §11 names the negated closing verb that still auto-closes and widens the pre-wave grep (#372); and CI keeps merge-wave's sandboxes on failure so the next #237 red carries its `merge.log` (#373). Minor release: a new workspace field and a new plan refusal shape. |
339
+ | 0.32.0 | 2026-09-16 | `beta` | Seven framework defects measured on the second unattended run (run #42), where every one of nine operator stalls was the framework's, not the code's. A refused compound shell line is a rework note for the developer instead of a human stop (#360); a dependency hold is re-evaluated the moment the dependency lands (#361) and a rescued story is written back to `todo` the instant the release is decided (#366); an event payload over the size cap is clamped to a bounded head instead of failing the whole build stage (#359); a headless developer is told it is unattended, that `## Inputs` is where to start and not an allowlist, and a turn that asks instead of deciding is a named `asked_no_diff` failure (#364); the rate-limit park waits for 90% of the window, not the provider's first warning at half of it (#367); the plan refuses a story that enforces an invariant on a field before the story that populates it, matching the field across spellings (#365); and the preflight restores workspace tools before its first probe and names the tree it ran in (#363, in part). Minor release: a new plan refusal, a changed park threshold and a new failure kind. |
338
340
  | 0.31.1 | 2026-09-16 | `beta` | One fix, measured on the first unattended run under 0.31.0: a seed placed at the documented `.tldrx/seeds/` location was invisible to the seed-solution detector, because the filter that drops the `what` stage's own framework inputs dropped every `.tldrx/` path, so `how` ran at full price for exactly the seeds #346 was written to skip (#358). |
339
341
  | 0.31.0 | 2026-09-16 | `beta` | Ten efficiency adjustments, ranked from a measured audit of 48 runs, so an unattended run spends less and stalls less. `how` is skipped when the seed already declares its own solution, and runs on `sonnet` when it does run (#346); `risks.md` is no longer generated (#350); `watch` follows `build`'s gate policy, so an all-auto run signs itself (#349); a Watch card's `[src:]` punctuation slip is repaired locally instead of costing a turn (#345); a fix list with zero open findings settles the round, an unreadable one holds it by name (#218); `run status` warns NO-RETRY before a retry the budget refuses (#232); a failed headless turn carries a named `failure_kind`, so the loop stops buying the same retry for every death (#348); an orphaned turn is banked as unmetered instead of a confident zero (#337); a relaunch resumes its own epic branch instead of restarting it (#347); the base-tree DoD refusal names the workspace `install:` (#343) and `run auto` names `--wait-gates` (#342). |
340
342
  | 0.30.0 | 2026-09-15 | `beta` | Seventeen changes that keep an unattended run moving instead of stranding. A cached Build refusal says so and a relaunch re-measures it (#339); a rate-limit warning is read instead of dropped, so a Build parks before the wall (#298); the plan-over-stage advisory speaks only past 2× (#302); a stage death stops recommending a retry the budget refuses (#232); a DoD command is measured twice, a fix-list finding is checked against the epic tip, and an over-long sha is refused by name (#163); a formatting slip gets one bounded repair turn (#288); five Checks stop asking the reviewer for what it can't do (#195); a Codex review no longer dies before it runs (#148); `tldrx init` probes four commands at once (#180); `tldrx ship` refuses a merged diff nobody judged (#311). Docs: the zero-touch recipe pairs `run auto` with `--wait-gates`/`--wait-answers` (#342). |
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  conflictOf
4
- } from "./chunk-04kpbhvk.js";
4
+ } from "./chunk-px7swzs1.js";
5
5
  import {
6
6
  FactsStore,
7
7
  formatJaccard
8
- } from "./chunk-f9d2efhb.js";
8
+ } from "./chunk-bfvcebn6.js";
9
9
  import {
10
10
  parseHookInput,
11
11
  readStdin
12
- } from "./chunk-zyz0fbz5.js";
12
+ } from "./chunk-jfaarcb1.js";
13
13
  import"./chunk-ae6bkfs5.js";
14
14
  import {
15
15
  EventLog,
16
16
  PHASE_ID_RE
17
- } from "./chunk-akemhezh.js";
17
+ } from "./chunk-5wh26b5f.js";
18
18
  import {
19
19
  PHASE_IDS
20
- } from "./chunk-yyhwf3bp.js";
21
- import"./chunk-5kv2vtha.js";
20
+ } from "./chunk-vhy24kv6.js";
21
+ import"./chunk-7ypyp8xk.js";
22
22
  import {
23
23
  ADVISORY_KEY,
24
24
  MAX_FACT_CHARS,
@@ -28,7 +28,7 @@ import {
28
28
  renderQuestionBlock,
29
29
  replaceBlock,
30
30
  serializeQuestions
31
- } from "./chunk-x1f4dxwk.js";
31
+ } from "./chunk-sjykymar.js";
32
32
  import {
33
33
  ITERATION_ONLY_SLOT,
34
34
  PROJECT_FRAMEWORK_DIR,
@@ -38,7 +38,7 @@ import {
38
38
  isScopedTemplate,
39
39
  parseYaml,
40
40
  scopedSlotOf
41
- } from "./chunk-9bd6b3xf.js";
41
+ } from "./chunk-0a3b8w1c.js";
42
42
 
43
43
  // src/hooks/answer-capture.ts
44
44
  import { existsSync as existsSync5 } from "fs";
@@ -157,6 +157,7 @@ function loadWorkspace(root) {
157
157
  const scopedTemplates = new Set;
158
158
  const defaultBranches = new Map;
159
159
  let seedTriageThresholdTokens = null;
160
+ let probeInWorktree = false;
160
161
  const empty = () => ({
161
162
  root,
162
163
  repos,
@@ -168,7 +169,8 @@ function loadWorkspace(root) {
168
169
  scopedCommands,
169
170
  scopedTemplates,
170
171
  defaultBranches,
171
- seedTriageThresholdTokens
172
+ seedTriageThresholdTokens,
173
+ probeInWorktree
172
174
  });
173
175
  const path = join(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
174
176
  if (!existsSync(path))
@@ -186,6 +188,8 @@ function loadWorkspace(root) {
186
188
  seedTriageThresholdTokens = tokens;
187
189
  }
188
190
  }
191
+ if (doc?.probe_in_worktree === true)
192
+ probeInWorktree = true;
189
193
  const list = doc?.repos;
190
194
  if (!Array.isArray(list))
191
195
  return empty();
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  budgetGateDeny
4
- } from "./chunk-q1ge3nex.js";
4
+ } from "./chunk-vhn2vz83.js";
5
5
  import {
6
6
  allow,
7
7
  deny,
8
8
  readPayload,
9
9
  runHook,
10
10
  toolInput
11
- } from "./chunk-27v2rwa9.js";
12
- import"./chunk-zyz0fbz5.js";
11
+ } from "./chunk-d3jrf6yy.js";
12
+ import"./chunk-jfaarcb1.js";
13
13
  import {
14
14
  REBALANCE_SOURCE,
15
15
  applyRebalance,
@@ -27,10 +27,10 @@ import {
27
27
  validateRunBudget,
28
28
  wouldExceed,
29
29
  wouldExceedHostTokens
30
- } from "./chunk-me0kzc6k.js";
30
+ } from "./chunk-s4kz5nn2.js";
31
31
  import {
32
32
  EventLog
33
- } from "./chunk-akemhezh.js";
33
+ } from "./chunk-5wh26b5f.js";
34
34
  import {
35
35
  cursorStage,
36
36
  hostTokensIn,
@@ -39,20 +39,20 @@ import {
39
39
  newestActiveRun,
40
40
  renderRunEconomies,
41
41
  runSpend
42
- } from "./chunk-8ds2ret8.js";
42
+ } from "./chunk-57s4mdhy.js";
43
43
  import {
44
44
  buildStageDefaults
45
- } from "./chunk-yyhwf3bp.js";
45
+ } from "./chunk-vhy24kv6.js";
46
46
  import {
47
47
  noteDeprecations
48
- } from "./chunk-x1f4dxwk.js";
48
+ } from "./chunk-sjykymar.js";
49
49
  import {
50
50
  PROJECT_WORK_DIR,
51
51
  findWorkspaceRoot,
52
52
  locateWork,
53
53
  parseYaml,
54
54
  stageYamlPath
55
- } from "./chunk-9bd6b3xf.js";
55
+ } from "./chunk-0a3b8w1c.js";
56
56
 
57
57
  // src/hooks/budget-gate.ts
58
58
  import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
@@ -8818,6 +8818,7 @@ function loadWorkspace(root) {
8818
8818
  const scopedTemplates = new Set;
8819
8819
  const defaultBranches = new Map;
8820
8820
  let seedTriageThresholdTokens = null;
8821
+ let probeInWorktree = false;
8821
8822
  const empty = () => ({
8822
8823
  root,
8823
8824
  repos,
@@ -8829,7 +8830,8 @@ function loadWorkspace(root) {
8829
8830
  scopedCommands,
8830
8831
  scopedTemplates,
8831
8832
  defaultBranches,
8832
- seedTriageThresholdTokens
8833
+ seedTriageThresholdTokens,
8834
+ probeInWorktree
8833
8835
  });
8834
8836
  const path = join3(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
8835
8837
  if (!existsSync3(path))
@@ -8847,6 +8849,8 @@ function loadWorkspace(root) {
8847
8849
  seedTriageThresholdTokens = tokens;
8848
8850
  }
8849
8851
  }
8852
+ if (doc?.probe_in_worktree === true)
8853
+ probeInWorktree = true;
8850
8854
  const list = doc?.repos;
8851
8855
  if (!Array.isArray(list))
8852
8856
  return empty();