tldr-experts 0.23.0 → 0.25.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 +273 -0
- package/README.md +2 -0
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-1p5027fn.js → chunk-bbrhs055.js} +13 -4
- package/dist/hooks/{chunk-ah1pdax3.js → chunk-rgn23z77.js} +117 -9
- package/dist/hooks/session-start.js +2 -2
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +1416 -915
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/watch/stage.md +2 -1
- package/templates/watcher.md +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,278 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.25.0 — 2026-09-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A Watch card refused for an unsourced `## Where` item comes back to its writer, marked, instead
|
|
8
|
+
of being rewritten blind (#301).** MEASURED on two unattended field runs the same night: the
|
|
9
|
+
`05-watch` writer named a PostgreSQL table under `## Where` with no `[src: …]` at all — twice per
|
|
10
|
+
run, on both workspaces, once on two cards in one run — while the migration defining that table
|
|
11
|
+
was cited two lines above under `## Signal`. Three things let it through. The rule said "every
|
|
12
|
+
item is sourced" and listed five source kinds, and nowhere said which kind a table, queue or
|
|
13
|
+
dashboard takes (`stage.md`, the feature brief and the template each showed a file or a fact,
|
|
14
|
+
never a place). The refusal, `no [src: …] token — every item on a card is sourced`, restated the
|
|
15
|
+
rule and named no cure. And the retry re-ran `tldrx next` from scratch: `featurePrompt` took no
|
|
16
|
+
previous-card parameter, the refusal was written to the operator's terminal and never into the
|
|
17
|
+
next prompt, and attempt 2 on run A moved the failure from L21+L22 to L19 — an independent
|
|
18
|
+
re-generation, not a repair. A person hand-edited the card and recorded the stage twice. Now:
|
|
19
|
+
ONE sentence, `NON_FILE_SOURCE_CURE`, says what a place cites (the migration, model or config
|
|
20
|
+
that defines it, or the fact that names it), and the writer's brief teaches it with one worked
|
|
21
|
+
example while the validator's refusal repeats it verbatim — one derivation, so a retry armed with
|
|
22
|
+
the refusal is armed with the cure. And the retry's prompt carries `## Previous attempt` (the
|
|
23
|
+
same heading, H3 and 32 KB cap as the one-agent path, now constants in `prompt.ts`): the card on
|
|
24
|
+
disk re-read by the parser that refused it, each refused line as `L<n> <section>: <message>`
|
|
25
|
+
with the line quoted as written, and the card inlined under "edit, do not restart". Re-validated
|
|
26
|
+
at prompt time rather than replayed from `run.yml`, so a card a person has since fixed shows no
|
|
27
|
+
stale marks; a first attempt has no card and no section, so its prompt is unchanged. The model
|
|
28
|
+
pin stays `haiku`: nothing measured says the model cannot follow the rule — it was never told
|
|
29
|
+
the rule for this case, and the retry was never shown its own card. Pinned by the fake-agent
|
|
30
|
+
harness: the first attempt's refusal names line, rule and cure; the headless retry's and the
|
|
31
|
+
`--prepare` bundle's prompts carry the marked card; a first attempt carries no section.
|
|
32
|
+
|
|
33
|
+
- **The freeze between a release and a merge wave runs both ways now (#304).** #299 made one
|
|
34
|
+
direction mechanical — the wave waits on `.RELEASE-IN-PROGRESS` — and left the other as prose:
|
|
35
|
+
MEASURED by reading `scripts/release.sh` at `a341b4f`, it sourced `merge-lock.sh` only to name
|
|
36
|
+
its own marker, and nothing between its first line and `git commit` read `merge-wave.lock`. A
|
|
37
|
+
release started mid-wave edited CHANGELOG, README and package.json in a tree another process
|
|
38
|
+
was gating, and the only thing that stopped the commit was the ref hook — which aborts the
|
|
39
|
+
COMMIT and leaves the three edits dirty in the shared checkout, with nothing to roll them back.
|
|
40
|
+
`release.sh` now WAITS on a running wave's lock before it writes its marker and before its
|
|
41
|
+
first edit — same `MW_LOCK_*` knobs, same dead-owner rule (a dead wave's lock is broken open
|
|
42
|
+
and said so on stderr) — and gives up with **exit 14**, having edited nothing. The precedence
|
|
43
|
+
is fixed rather than symmetric (peer review of this change): a wave holding the lock finishes,
|
|
44
|
+
never preempted; once the marker is up the release is ahead, a wave that took the lock in the
|
|
45
|
+
gap hands it back as it already did, and the release waits for the lock to clear WITHOUT
|
|
46
|
+
handing back its marker — two sides yielding on the same poll cadence would ping-pong for the
|
|
47
|
+
whole budget and end in 13 and 14; pinned with the gap interleaving built deterministically (a
|
|
48
|
+
stand-in `mv` plants the lock the instant the marker lands). Which makes the wave's dead-owner
|
|
49
|
+
check the only thing that ever clears a marker a SIGKILL or a cut session orphaned (the traps
|
|
50
|
+
cover INT/TERM), so it now says so on stderr, naming the marker and the pid, and that sentence
|
|
51
|
+
is pinned. And a kept marker means two things — queued and untouched, or editing and tagging —
|
|
52
|
+
so it carries `phase: waiting|releasing` (rewritten atomically), which `--status` and the wave's
|
|
53
|
+
refusal print — and while a wave holds the lock, `--status` names the queued release on a
|
|
54
|
+
second line rather than answering "a wave, nothing else": a record never says more than the
|
|
55
|
+
truth (§7). The code: 14 rather than 1 because there is nothing to undo, and
|
|
56
|
+
not the wave's own 6 because the two scripts' codes are read in the same logs, so the next
|
|
57
|
+
number after the wave's 13 keeps a bare "exit 14" unambiguous. Pinned in both directions with
|
|
58
|
+
the real scripts: a real wave against a real running release exits 13, a real release against a
|
|
59
|
+
live wave lock exits 14 with the tree untouched, and a queued release edits nothing until the
|
|
60
|
+
lock is gone. The wave's exit-12 heading gate now also requires the unreleased version to be
|
|
61
|
+
above `package.json`'s — the CHANGELOG used to be checked only against its own top dated
|
|
62
|
+
heading, so a dated section never written, or a heading edited by hand, passed on two lies from
|
|
63
|
+
one file; `package.json` is what shipped (§9's drift guard already pins README's top row to it).
|
|
64
|
+
Same exit 12: one condition, measured twice, and the refusal names both figures. Two Minors the
|
|
65
|
+
#299 review named are pinned rather than fixed, because reading was right: `--status` on an
|
|
66
|
+
old-format lock (owner and token only) with a live pid prints `?` for what it never recorded,
|
|
67
|
+
and `ver_gt` orders `1.0.0 > 0.99.0` and `0.10.0 > 0.9.0` numerically — the function is
|
|
68
|
+
lifted from the script's own text for the test, so the pin is on the real implementation.
|
|
69
|
+
|
|
70
|
+
- **`tldrx next --prepare` and `--commit` ask the same dependency frontier the headless loop asks (#300).**
|
|
71
|
+
MEASURED at `31ed3d7` on the mixed shape #280 opened: a headless pass met S2 behind S1 at `review` and
|
|
72
|
+
— correctly — left S2 `todo`; the host's `--commit --review` blocked S1; the next `--prepare` said
|
|
73
|
+
`prepared S2 · … ($3.20 ceiling, attempt 1 of 1)` and `--commit` then merged S2 into an epic branch
|
|
74
|
+
S1 had put nothing on. `runAll` has asked `blockingDependency` per story since #260/#263, but the
|
|
75
|
+
three in-session doors took `nextPending()` / `inProgress()` as offered, and `pendingStories` skips
|
|
76
|
+
only `done` and a terminal `blocked` — so the `blocked` row #280 stopped writing was, incidentally,
|
|
77
|
+
the only thing keeping `--prepare` off that dependent. A pure in-session run had the hole all along
|
|
78
|
+
(S1 blocked by a verdict, S2 `todo`); the mixed run is what made it fire without a person's hand.
|
|
79
|
+
Now `--prepare` walks the pending stories through the ONE frontier and does per story what the loop
|
|
80
|
+
does — a terminal hold records the dependent `blocked` with the recorded sentence, so the reason
|
|
81
|
+
reaches the gate and `staleDependencyHold` releases it when the dependency lands; a pending hold
|
|
82
|
+
leaves the row `todo` (never `blocked`, which would re-create #280 on the other door) and, with
|
|
83
|
+
nothing else to offer, refuses with exit 1 saying there is nothing to prepare YET and what releases
|
|
84
|
+
it. `--prepare --review` refuses over a hold and records nothing. `--commit` on a story whose
|
|
85
|
+
dependency is no longer `done` refuses with exit 1 in both cases and writes nothing — the bundle,
|
|
86
|
+
the branch and the worktree stay, the story stays `in_progress`, and the sentence names all three
|
|
87
|
+
— because that story has a developer's attempt on its branch and the loop's row would say it had
|
|
88
|
+
none. Exit 1 is the sequencing family (`refusedOnSequence`): the cycle is fine and the fix is the
|
|
89
|
+
other command; a `2` would send the stage back to `ready` and throw away the bundle a held
|
|
90
|
+
`--commit` is sitting on. And the closing hint of a `--commit` with more to do asks the same frontier,
|
|
91
|
+
recording nothing: it used to read raw `nextPending()` and say `S2 is next — run tldrx next --prepare`
|
|
92
|
+
the instant S1 was blocked, naming the one bundle the next `--prepare` refuses to write; it now names
|
|
93
|
+
what `--prepare` would actually offer, or says nothing is next and why. One correction to the issue's
|
|
94
|
+
proposed sequence, measured: S1 at `review`
|
|
95
|
+
followed by `--prepare` does NOT hand S2 out — S1 is pending too and earlier in wave order, so that
|
|
96
|
+
`--prepare` offers S1's review; the dependent is offered the moment S1 leaves `review` for a status
|
|
97
|
+
`pendingStories` skips. Minor by behaviour: a `--prepare` and a `--commit` that used to hand out and
|
|
98
|
+
settle now record `blocked` or refuse.
|
|
99
|
+
- **A `budget raise --stage` typed during a live `run auto` loop no longer evaporates on the loop's
|
|
100
|
+
next save (#305).** MEASURED in the field on 0.21.0: the command printed `04-build/build budget_usd
|
|
101
|
+
$12.60 → $62.60`, exited 0, and two hours later `run.yml` read `budget_usd: 12.60` again with the
|
|
102
|
+
gate note holding the stage on `budget=$35.78 of $12.60` — the developers had been capped on the
|
|
103
|
+
figure the operator had raised. Not a `budget` bug: a LOST UPDATE on `run.yml`. `RunStore.save()`
|
|
104
|
+
re-read `budget.yml`'s ceilings from disk before writing (the 2026-08-29 fix) and wrote `run.yml`
|
|
105
|
+
WHOLE from the copy the store loaded — and `run auto` holds one store for the whole of a stage, the
|
|
106
|
+
Build fan-out included. Pinned RED through the real writers on today's `main`: the raise reverted
|
|
107
|
+
(`Expected: 54 / Received: 4`), a `reject --and-continue` beside a held store reverted (`rejected` →
|
|
108
|
+
`pending`), a `run cancel` beside one lost its `cancelled:` block. `save()` now re-reads `run.yml`
|
|
109
|
+
under the workspace lock and carries over it ONLY the fields this store changed since it last read
|
|
110
|
+
or wrote the file; every derived figure is rolled up from the merged document. That is ownership by
|
|
111
|
+
change, not the blind reload-and-merge the issue warned against: every value on disk was written by
|
|
112
|
+
the store that changed it, and a store never writes back a value it merely loaded — so the loop,
|
|
113
|
+
which never touches a stage's `budget_usd`, cannot revert a raise, and the raise, which never touches
|
|
114
|
+
task rows, cannot revert the ledger. Who changes what is now written down in spec §2.2 — the loop
|
|
115
|
+
owns the execution record, a person's commands own `budget_usd`, `cancelled`, gate decisions and the
|
|
116
|
+
policies — and the one field two writers can both change, a stage's `status` under `run cancel
|
|
117
|
+
--force` while a loop still runs it, has a DECLARED winner (peer review): `cancelled` is terminal
|
|
118
|
+
and wins. A cancel that landed under a held store keeps its statuses and the cursor on the file —
|
|
119
|
+
the held store's task rows still land, those turns happened and cost money — the save answers
|
|
120
|
+
`cancelledUnder: true` and says so on stderr, and the PROCESS obeys the file, not only the file:
|
|
121
|
+
the Build executor asks `RunStore.cancelledOnDisk` before every spawn (each story attempt, each
|
|
122
|
+
parallel lane, the reviewer) and `tldrx next` stops after the save that merged the cancel, with
|
|
123
|
+
the same exit 0 its "is cancelled — nothing to advance" path uses, rather than parking a
|
|
124
|
+
cancelled stage on a gate. Pinned through the real dispatch with the fake agent: a forced cancel
|
|
125
|
+
fired from S1's own DoD command leaves exactly one `agent.spawned` on the log — S1's developer —
|
|
126
|
+
S1 parked at `review` with an `n-a` verdict naming the cancel, S2 never started, `run.yml`
|
|
127
|
+
`cancelled` with the stage `cancelled`. MEASURED before the rule: the same collision spawned five
|
|
128
|
+
times over an already-cancelled run (S1's reviewer, S2's developer and reviewer, S1's developer
|
|
129
|
+
again), and the file ended `cancelled` with the stage `awaiting_gate` — #305's shape one level
|
|
130
|
+
down. A verdict read across two owners (the loop's spend against an
|
|
131
|
+
operator's ceiling) already records the figures it read beside itself — `budget.blocked` carries
|
|
132
|
+
`remaining_usd` and `ceiling_usd`, the auto-gate note carries `budget=$x of $y` — and that is now
|
|
133
|
+
the stated rule, because the file can legitimately hold a pair no single writer saw together. For
|
|
134
|
+
the record: `reject` had no live exposure by construction (it refuses anything but
|
|
135
|
+
`awaiting_gate`/`failed`, by which point the loop's store for that stage is gone) and `run cancel`
|
|
136
|
+
already refused under a live `.lock` unless forced; both are pinned at store level anyway, because
|
|
137
|
+
the next writer will not be one of these three.
|
|
138
|
+
Two edges from the pre-merge review, because a fallback that guesses silently is the same lie in a
|
|
139
|
+
smaller font: a `run.yml` on disk that exists but cannot be merged with (does not parse, does not
|
|
140
|
+
validate) has the in-memory copy written whole — the only honest move — but that is exactly the
|
|
141
|
+
case an external write may just have been destroyed, so `save()` says so on stderr, naming the
|
|
142
|
+
reason and the `.bak` beside the file where the replaced version is (the convention the repair path
|
|
143
|
+
already uses; no new event kind, none exists for repairs and §7 does not invent one); an absent file
|
|
144
|
+
is merged with nothing and stays silent, there was nothing to lose. And a `run.yml` that records
|
|
145
|
+
ANOTHER run is not damage but a violated premise: the save REFUSES, naming both ids and the path,
|
|
146
|
+
and writes nothing — unreachable by path construction today, pinned so it stays unreachable by
|
|
147
|
+
accident.
|
|
148
|
+
|
|
149
|
+
## 0.24.0 — 2026-09-14
|
|
150
|
+
|
|
151
|
+
### Added
|
|
152
|
+
|
|
153
|
+
- **The two-session protocol is mechanical where it was chat (#299).** MEASURED over one
|
|
154
|
+
unattended day (2026-09-13/14, two sessions, ~16 issues): two branches each staged ONE
|
|
155
|
+
unreleased CHANGELOG heading — `0.22.1` and `0.23.0` — for the same next version, and every
|
|
156
|
+
per-branch check passed both because the defect only exists on the merged tree; "is a release
|
|
157
|
+
in flight?" was asked by message four times because `scripts/release.sh` wrote nothing anyone
|
|
158
|
+
could wait on; and a wave's liveness was read off `ps` and a marker's mtime, once wrongly.
|
|
159
|
+
Three things change, none of them in `src/`. `scripts/merge-wave.sh` now refuses a MERGED tree
|
|
160
|
+
carrying more than one `## <v> — unreleased` heading, or one whose version is not above the
|
|
161
|
+
top dated heading, with **exit 12** — the merge commit is rewound, nothing is pushed, both
|
|
162
|
+
headings are named; zero unreleased headings (the state right after a release) still merges.
|
|
163
|
+
`scripts/release.sh` writes `.RELEASE-IN-PROGRESS` at the repo root (pid, host, version,
|
|
164
|
+
started) for its whole span and removes it on every exit path including a red gate and a
|
|
165
|
+
signal; the wave WAITS on that marker exactly as it waits on its own lock — same poll, same
|
|
166
|
+
stale-by-dead-pid rule, same `MW_LOCK_*` budget — and gives up with **exit 13**. And
|
|
167
|
+
`scripts/merge-wave.sh --status` prints one line, exit 0 either way: the live wave's holder,
|
|
168
|
+
branch, phase (`merge|gates|push`, now recorded in the lock) and start time, a live release's
|
|
169
|
+
holder and version, or `idle`. The `maintain` skill gains a §9 for a session that is not the
|
|
170
|
+
driver — announce the file set first (naming the `build.ts` rule), agree the one unreleased
|
|
171
|
+
heading before writing it, announce lock/sha/OK line, re-review after a rebase — citing
|
|
172
|
+
`AGENTS.md` by section; `test/maintain-skill.test.ts` pins the section and its citations.
|
|
173
|
+
Minor by behaviour: a wave that used to pass now refuses.
|
|
174
|
+
|
|
175
|
+
### Fixed
|
|
176
|
+
|
|
177
|
+
- **A per-story cap now says what it was derived from, and names a lever that moves it (#281).**
|
|
178
|
+
MEASURED on a live unattended run (0.18.2, eight stories): a developer died twice on a **$5.97**
|
|
179
|
+
cap while `03-plan/budget.yml` priced its story at **$14.00** and the 0.18.2 entry below promised
|
|
180
|
+
`max(price × 3, $4.00)` = $42. The `blocked_reason` told the operator to raise the plan price; the
|
|
181
|
+
operator raised every price ×4 and the cap did not move by a cent. The arithmetic is not the
|
|
182
|
+
defect: `priceScale` fits the plan's SUM into the stage's `budget_usd` — $16.20 over a $114.00 plan
|
|
183
|
+
is a scale of 0.1421 — and a uniform raise keeps the ratio and the sum, so the scale absorbs it
|
|
184
|
+
exactly. What the framework SAID was: two comment copies in `caps.ts` and the 0.18.2 bullet wrote
|
|
185
|
+
`price` where the code reads `price × scale`, and that section is dated and immutable, so this
|
|
186
|
+
bullet corrects #277's wording — the ceiling is `max(price × scale × story_cap_multiplier,
|
|
187
|
+
story_cap_floor_usd)`, scale being 1 only while the prices sum inside the stage. Three things
|
|
188
|
+
change, none of them a number. The cap death reason now shows the FORMULA with its inputs, not
|
|
189
|
+
the conclusion — `cap $5.97 = plan price $14.00 × stage scale 0.1421 (stage budget_usd $16.20 over
|
|
190
|
+
$114.00 of plan prices) × story_cap_multiplier 3 = $5.97, above the floor $4.00` — and on a scaled
|
|
191
|
+
plan it says the price cannot move the cap and names the stage's own `budget_usd` with the
|
|
192
|
+
`tldrx budget raise … --stage` command (#244) that lifts the scale to 1; an unscaled plan keeps
|
|
193
|
+
the price as a lever (the old sentence was right exactly there), and an unpriced story names its
|
|
194
|
+
uniform share instead of a plan price it never had. A plan priced past its Build stage still
|
|
195
|
+
passes the `plan` gate — the scale is a deliberate tolerance — but the gate's detail now carries
|
|
196
|
+
the factor (`7.0× what the stage holds`), the scale, the largest story as a worked example and the
|
|
197
|
+
same command; the Build executor prints the same advisory on stderr at entry, before any spawn,
|
|
198
|
+
so a run already in flight is told too. `shortBy` moved from `budget/budgetView.ts` to
|
|
199
|
+
`build/caps.ts` (re-exported where it was): the plan-price shortfall needs the same round-up, and a
|
|
200
|
+
second copy of a rounding rule is what §7 forbids.
|
|
201
|
+
- **A story whose dependency is at `review` now WAITS instead of being written `blocked`, and a
|
|
202
|
+
`blocked` row whose reason names a dependency that has since turned `done` is offered again
|
|
203
|
+
(#280).** Measured on a live unattended run (0.18.2, four stories in four waves, S3 and S4
|
|
204
|
+
`depends_on: [S2]`): S2 came out of its fix round at `review` — verdict recorded, branch merged
|
|
205
|
+
into the epic — and the loop parked both dependents `blocked` with `dependency S2 is \`review\`,
|
|
206
|
+
not \`done\``. Two polls later S2 was `done`; the dependents were still `blocked`, because
|
|
207
|
+
`blocked` is a terminal row the loop never revisits, and a person had to `story reopen` both.
|
|
208
|
+
#260's frontier drew one line — `done` runs, anything else blocks — which is right for a
|
|
209
|
+
dependency that will not land in this loop (`blocked`, or `todo` after its developer died, #263)
|
|
210
|
+
and wrong for `review`/`in_progress`: that is a story mid-pipeline, re-offered by the very next
|
|
211
|
+
invocation, and a terminal row over it confused "not yet" with "never". Now such a dependency is
|
|
212
|
+
a wait — the dependent's row is left untouched at `todo`, no log and no outcome row are written,
|
|
213
|
+
`## Unknowns` names the wait with the story it waits on, and the next invocation asks again. And
|
|
214
|
+
the shape every earlier run left on disk is released the same way a dead developer's block is:
|
|
215
|
+
a `blocked` row whose `## Why it is not done` is a dependency hold, over dependencies that are
|
|
216
|
+
all `done` now, is offered again — nothing attempted it, so nothing about it was judged; a row a
|
|
217
|
+
reviewer blocked keeps its verdict. One leaf, `build/dependencyHold.ts`, writes the sentence and
|
|
218
|
+
reads it back. #263's own pin — a dependency parked `todo` still blocks — is untouched. Visible
|
|
219
|
+
in-session too: after a story settles, a dependent that used to sit `blocked` is now the next
|
|
220
|
+
`--prepare`, so the stage stays `running` and names it instead of reaching the gate.
|
|
221
|
+
- **`--until-done` compares the REFUSAL across attempts, not the last line printed (#297).** The
|
|
222
|
+
guard exists so a loop does not spend its whole relaunch budget hammering a wall, and for whole
|
|
223
|
+
exit families it could not see a wall at all: every stage death ends with the same literal advice
|
|
224
|
+
— `cost is recorded, not refunded — retry with …` — and so did every context refusal, every
|
|
225
|
+
budget refusal, every host-tokens refusal, each of them a string with nothing interpolated in it.
|
|
226
|
+
Reading `lines[lines.length - 1]` there compared a constant to itself, so the SECOND stage death
|
|
227
|
+
of any run was declared a verbatim repeat whether the two deaths were the same refusal, different
|
|
228
|
+
refusals, or measurable progress — and every remaining relaunch was thrown away. Both halves of
|
|
229
|
+
the guard were wrong at once: it spent nothing on the stuck case it was built for, and spent the
|
|
230
|
+
budget on the case that was moving. The fix is not a better index — an index is right only for
|
|
231
|
+
the report shapes that exist the day it is written, and breaks the moment a caller appends
|
|
232
|
+
another note. A report now NAMES its own refusal (`NextOutcome.signature`), the loop compares
|
|
233
|
+
that, and it falls back to the last line only where nothing named one (a throw's message, a
|
|
234
|
+
missing input — reports whose last line already IS their reason; a repeat there is still real
|
|
235
|
+
evidence, and the bound is a backstop that costs money, not a reading). `run.relaunched` carries
|
|
236
|
+
the comparand it will compare against next, and its `reason` — the sentence the stop line and the
|
|
237
|
+
ledger both quote — now names the refusal rather than the advice under it. Executor refusals
|
|
238
|
+
(`refused: true`) are covered at their ONE pass-through rather than producer by producer: eight
|
|
239
|
+
producers across the Build and Watch executors leave through a single `out()` call, which now
|
|
240
|
+
passes the executor's own signature, falling back to `ExecutorOutcome.error` — and four of those
|
|
241
|
+
sentences had to be corrected before the fallback was worth anything. The question is not what a
|
|
242
|
+
sentence interpolates but whether it DISTINGUISHES the states a relaunch can move between, and a
|
|
243
|
+
run comes back to the same repo: naming the repo alone made two different dirty trees one
|
|
244
|
+
refusal, two different stash failures one refusal, and four structurally different foreign-epic
|
|
245
|
+
faults one refusal. So the dirty-tree refusal now names the overlapping paths and why each is
|
|
246
|
+
claimed; the foreign-epic refusal carries one sentence per fault (unreadable claims, an open
|
|
247
|
+
claimant, nobody's leftover, a leftover that could not be moved) instead of one per branch; the
|
|
248
|
+
could-not-be-set-aside refusal carries git's own reason, which its printed lines already had; and
|
|
249
|
+
the red-base refusal names EVERY red command rather than the first in iteration order — measured
|
|
250
|
+
on two base trees that differ only in their second command, where the printed refusal changed and
|
|
251
|
+
the comparand did not move a byte. Watch's branch-incoherence refusal, whose `error` is null and
|
|
252
|
+
whose last line is a literal about `tldrx doctor`, names its faults itself. A ninth producer
|
|
253
|
+
added tomorrow inherits the door instead of being born blind.
|
|
254
|
+
|
|
255
|
+
- **The Build handoff no longer fails its own `claim-sources` check over a command that spans
|
|
256
|
+
lines (#283).** MEASURED twice in one hour on a live unattended run (0.18.2 → 0.18.3): the
|
|
257
|
+
executor wrote `04-build/handoff.md` itself and then refused it — `trailing-position` on a
|
|
258
|
+
blocked story's Findings and Unknowns bullets, then `unsourced` on a `<id>'s developer had …
|
|
259
|
+
refused` bullet — stage exit 5, a `--until-done` relaunch burned each time. The issue's own
|
|
260
|
+
reading, the DoD citation joined mid-line by `; and `, was probed on the same base and passes:
|
|
261
|
+
the reader takes the LAST `[src: …]` on a line. What fails is a NEWLINE. The refused command
|
|
262
|
+
reaches the renderer as the developer typed it (`agentEvents.ts` `toolTarget` returns the Bash
|
|
263
|
+
`command` input verbatim — a wrapped `mv a \` + `b`, a heredoc), `renderBuildHandoff` quoted it
|
|
264
|
+
inside one bullet, and `parseHandoff` ends a bullet at the first column-0 line, so the first
|
|
265
|
+
physical line carried its citation mid-sentence or none at all and the rest was prose nothing
|
|
266
|
+
read. Every element of the document is now made ONE line at the join — the break shown as ` ⏎ `
|
|
267
|
+
rather than erased, so `mv a \ ⏎ b` still says the line was wrapped and nothing is dropped —
|
|
268
|
+
which holds for every field the renderer embeds, not only the two the run hit; the review log
|
|
269
|
+
keeps the command verbatim — and a document that had to draw the mark says so once, under its
|
|
270
|
+
header, where the developer that copies the line reads it. Text the framework composes has to
|
|
271
|
+
satisfy the grammar the framework checks; the rule is one function, `asOneLine`, and the file's
|
|
272
|
+
other writer (`epicRelease.ts`, the `## Epic branch released` section carrying a `run cancel
|
|
273
|
+
--note` verbatim) goes through it too. A document with no newline in any quoted text is
|
|
274
|
+
byte-identical, so the build golden is unchanged.
|
|
275
|
+
|
|
3
276
|
## 0.23.0 — 2026-09-14
|
|
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.25.0 | 2026-09-14 | `beta` | The other half of the two-session protocol and four holes measured on live runs. `scripts/release.sh` now waits on a running wave's lock before touching the tree and never hands back the marker it wrote — release has precedence, the wave yields, exit 14 when it gives up; the heading gate also refuses an unreleased version at or below `package.json`; the marker carries its phase and `--status` reports a release queued behind a wave (#304). In-session `tldrx next --prepare` and `--commit` ask the same dependency frontier the headless loop asks: a terminal hold records the dependent `blocked`, a pending hold refuses without writing, a held `--commit` keeps the work it sits on and says where it is (#300). A Watch card refused for an unsourced `## Where` item comes back to its writer marked, the refusal names the cure, and the brief shows one non-file example, so a retry edits the card instead of regenerating it blind (#301). And `run.yml` no longer loses an external write to a live loop: a save carries only the fields the store changed over a fresh read, ownership is declared, `cancelled` is terminal and wins in the file and in the process, a file that cannot be merged is kept beside as `.bak` and said so, and a file recording another run is refused (#305). Minor release: a new exit code on a public script, a stricter merge gate, and `--prepare`/`--commit` refuse where they used to proceed. |
|
|
339
|
+
| 0.24.0 | 2026-09-14 | `beta` | Five fixes from one unattended night, all measured on live runs. The two-session protocol is mechanical where it was chat: the merge wave refuses a merged tree with two unreleased CHANGELOG headings or one at or below the last release (exit 12), `release.sh` holds a `.RELEASE-IN-PROGRESS` marker the wave waits on (exit 13 when it gives up), `merge-wave.sh --status` reads holder, branch and phase in one line, and the `maintain` skill gains a worker-mode section (#299). A per-story cap says what it was derived from — plan price × stage scale — and names the lever that moves it, and a plan whose prices exceed the stage budget is told so instead of being scaled in silence (#281). A story whose dependency is still at `review` waits in `todo` instead of being written `blocked`, and a dependency block left by an earlier version is released once the dependency is `done` (#280). `--until-done` compares the refusal across attempts, not the advice line printed under it, so a failure that made progress keeps its relaunches (#297). And the Build handoff no longer fails its own `claim-sources` check over a refused command that spans lines: one physical line per element, the break shown as `⏎` and explained once in the document (#283). Minor release: the wave and `--until-done` behave differently in situations that exist today. |
|
|
338
340
|
| 0.23.0 | 2026-09-14 | `beta` | `tldrx story reopen --as-is` now settles a story whose work is already merged into the epic but whose review never completed (`n-a` or `error` on its last recorded merge): a new, named review-only case that merges nothing and only routes the story to its reviewer, read from an additive ledger field; the existing refusal ("no commit the epic has not got") is untouched and now names the standing verdict when one exists (`changes`, `approve`, `fixlist` still refuse). A fix list whose findings are all deferred settles the story `done` instead of spawning a developer with nothing to fix, measured on a live run that paid for four such developers (#295). And a turn that died on a provider limit no longer records `success` borrowed from the provider's own subtype: the failure record says what the host saw (#296, first half — DETECTING the provider limit as its own non-execution kind is #298 and is NOT in this release). Minor release: `story reopen --as-is` behaves differently in a situation that exists today. |
|
|
339
341
|
| 0.22.0 | 2026-09-14 | `beta` | A developer may now READ its own tree — `git status`, `log`, `diff` and `show` join the one constant the grant, the developer prompt and the refusal classifier all read; measured on a live unattended run where a developer was refused `git -C <worktree> log` twice and the story died, ~$5 for a command that changes nothing. `-C <path>` (and `--git-dir`, `--work-tree`) stays ungranted as a decision with its own refusal kind, `elsewhere`, because it points git at trees the story does not own. And every refusal cure now says WHY it is a cure: three consecutive developers on one story re-appended `; echo "EXIT:$?"` to a DoD command because the cure said what to drop and never that the facilitator re-runs the Definition of Done itself and records each exit code (#287, #294). Minor release: the developer's git allowance grew and a new refusal kind was added. |
|
|
340
342
|
| 0.21.0 | 2026-09-13 | `beta` | `tldrx budget raise <phase> <usd> --stage <id>` now moves the stage's own `budget_usd` — the figure that actually sets a developer's and a reviewer's spawn ceiling — instead of the phase figure, which caps no spawn at all: measured on a live unattended run where raising the plan's per-story price and the phase ceiling moved a developer's cap by nothing, and only raising the stage figure moved it; a raise naming no `--stage` now says outright that it moved no spawn ceiling. And a reviewer a nearly-exhausted stage cannot fund is refused before it is spawned — the run that surfaced this handed a reviewer **$0.43**, which died before reading a line of diff and recorded `verdict: error`, parking the story with its dependents blocked; the refusal now costs $0 and records `verdict: n-a`, not an error the reviewer never formed (#244, #289). Minor release: a new flag, `--stage`, on `budget raise`. |
|
|
@@ -706,6 +706,9 @@ function stackExpertNames(root, repos) {
|
|
|
706
706
|
return names;
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
// src/core/facilitator/prompt.ts
|
|
710
|
+
var MAX_PREVIOUS_ATTEMPT_BYTES = 32 * 1024;
|
|
711
|
+
|
|
709
712
|
// src/core/text/srcGrammarContract.ts
|
|
710
713
|
var KINDS = {
|
|
711
714
|
file: { shape: "`[repo:]path:line[-line]`", example: "api:src/Selector.ts:241" },
|
|
@@ -1232,12 +1235,18 @@ function wouldExceedHostTokens(budget, phaseId, spentTokens) {
|
|
|
1232
1235
|
};
|
|
1233
1236
|
}
|
|
1234
1237
|
|
|
1238
|
+
// src/core/build/caps.ts
|
|
1239
|
+
var MAX_ATTEMPTS2 = STAGE_TUNING_DEFAULTS.attempts;
|
|
1240
|
+
var REVIEWER_SHARE2 = STAGE_TUNING_DEFAULTS.reviewerShare;
|
|
1241
|
+
var STORY_CAP_MULTIPLIER2 = STAGE_TUNING_DEFAULTS.storyCapMultiplier;
|
|
1242
|
+
var STORY_CAP_FLOOR_USD2 = STAGE_TUNING_DEFAULTS.storyCapFloorUsd;
|
|
1243
|
+
function shortBy(estimate, remaining) {
|
|
1244
|
+
return Math.max(0.01, Math.ceil((estimate - remaining) * 100) / 100);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1235
1247
|
// src/core/budget/budgetView.ts
|
|
1236
1248
|
function raiseCommand(runId, phaseId, amountUsd) {
|
|
1237
1249
|
return `tldrx budget raise ${phaseId} ${amountUsd.toFixed(2)} --run ${runId}`;
|
|
1238
1250
|
}
|
|
1239
|
-
function shortBy(estimate, remaining) {
|
|
1240
|
-
return Math.max(0.01, Math.ceil((estimate - remaining) * 100) / 100);
|
|
1241
|
-
}
|
|
1242
1251
|
|
|
1243
|
-
export { currentActor, nowRfc3339, spentBasis, tallyOf, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ON_GRANT_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, expertsDir, loadExperts, readExpertDomain, pathsIntersect, stackExpertNames, BUILD_PHASE2 as BUILD_PHASE, remainingWork, wouldExceed, wouldExceedHostTokens,
|
|
1252
|
+
export { currentActor, nowRfc3339, spentBasis, tallyOf, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ON_GRANT_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, expertsDir, loadExperts, readExpertDomain, pathsIntersect, stackExpertNames, BUILD_PHASE2 as BUILD_PHASE, remainingWork, wouldExceed, wouldExceedHostTokens, shortBy, raiseCommand };
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
spentBasis,
|
|
9
9
|
tallyOf,
|
|
10
10
|
validateRunBudget
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-bbrhs055.js";
|
|
12
12
|
import {
|
|
13
13
|
EventLog,
|
|
14
14
|
OUTCOME_NOT_RECORDED,
|
|
@@ -56,6 +56,7 @@ import { join as join4 } from "node:path";
|
|
|
56
56
|
|
|
57
57
|
// src/core/run/RunStore.ts
|
|
58
58
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
59
|
+
import { isDeepStrictEqual } from "node:util";
|
|
59
60
|
import { basename, join as join2 } from "node:path";
|
|
60
61
|
|
|
61
62
|
// src/core/frameworkVersion.ts
|
|
@@ -297,11 +298,14 @@ class RunStore {
|
|
|
297
298
|
events;
|
|
298
299
|
current;
|
|
299
300
|
currentBudget;
|
|
301
|
+
loaded;
|
|
302
|
+
cancelledUnderFlag = false;
|
|
300
303
|
budgetMutated = false;
|
|
301
304
|
constructor(runDir, run, budget, events) {
|
|
302
305
|
this.runDir = runDir;
|
|
303
306
|
this.events = events;
|
|
304
307
|
this.current = run;
|
|
308
|
+
this.loaded = run;
|
|
305
309
|
this.currentBudget = budget;
|
|
306
310
|
}
|
|
307
311
|
static open(runDir) {
|
|
@@ -386,6 +390,20 @@ class RunStore {
|
|
|
386
390
|
get runId() {
|
|
387
391
|
return this.current.run;
|
|
388
392
|
}
|
|
393
|
+
get cancelledUnder() {
|
|
394
|
+
return this.cancelledUnderFlag;
|
|
395
|
+
}
|
|
396
|
+
static cancelledOnDisk(runDir) {
|
|
397
|
+
const path = join2(runDir, "run.yml");
|
|
398
|
+
if (!existsSync(path))
|
|
399
|
+
return false;
|
|
400
|
+
try {
|
|
401
|
+
const doc = parseYaml(readFileSync2(path, "utf8"));
|
|
402
|
+
return validateRunFile(doc).ok && asRunFile(doc).cancelled !== undefined;
|
|
403
|
+
} catch {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
389
407
|
cursorEntry() {
|
|
390
408
|
return stageAt(this.current, this.current.cursor);
|
|
391
409
|
}
|
|
@@ -407,13 +425,14 @@ class RunStore {
|
|
|
407
425
|
this.events.append(event);
|
|
408
426
|
}
|
|
409
427
|
save() {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
428
|
+
return withWorkspaceLock(workspaceRootOfRunDir(this.runDir), () => {
|
|
429
|
+
const base = this.runToWrite();
|
|
430
|
+
const rolled = rollUp(base.run);
|
|
431
|
+
const runValidation = validateRunFile(rolled);
|
|
432
|
+
if (!runValidation.ok) {
|
|
433
|
+
const first = runValidation.issues[0];
|
|
434
|
+
throw new RunStoreError(`refusing to write an invalid run.yml: ${first?.path ?? ""} ${first?.message ?? ""}`);
|
|
435
|
+
}
|
|
417
436
|
const budget = rollUpBudget(this.ceilingsToWrite(), rolled);
|
|
418
437
|
const budgetValidation = validateRunBudget(budget);
|
|
419
438
|
if (!budgetValidation.ok) {
|
|
@@ -421,12 +440,56 @@ class RunStore {
|
|
|
421
440
|
throw new RunStoreError(`refusing to write an invalid budget.yml: ${first?.path ?? ""} ${first?.message ?? ""}`);
|
|
422
441
|
}
|
|
423
442
|
writeAtomic(join2(this.runDir, "budget.yml"), emitBudgetYaml(budget));
|
|
424
|
-
|
|
443
|
+
const runPath = join2(this.runDir, "run.yml");
|
|
444
|
+
writeAtomic(runPath, emitRunYaml(rolled));
|
|
425
445
|
this.current = rolled;
|
|
446
|
+
this.loaded = rolled;
|
|
426
447
|
this.currentBudget = budget;
|
|
427
448
|
this.budgetMutated = false;
|
|
449
|
+
if (base.fallback !== null) {
|
|
450
|
+
process.stderr.write(`tldrx: ${runPath} ${base.fallback}, so it could not be merged with: this process's copy of ` + `the run was written whole, and the version that was on disk is beside it as ${backupPathFor(runPath)}. ` + "A change another command wrote there since this process read the run (a budget raise, a " + `cancel, a rejection) may be in that backup and not in run.yml.
|
|
451
|
+
`);
|
|
452
|
+
}
|
|
453
|
+
if (base.cancelledUnder) {
|
|
454
|
+
this.cancelledUnderFlag = true;
|
|
455
|
+
const who = rolled.cancelled;
|
|
456
|
+
process.stderr.write(`tldrx: run ${rolled.run} was cancelled` + (who === undefined ? "" : ` by ${who.by} at ${who.at} (${who.note})`) + " while this process held it: the cancel's statuses were kept, this process's task rows were " + `recorded beside them, and nothing more may be spawned for it.
|
|
457
|
+
`);
|
|
458
|
+
}
|
|
459
|
+
return { cancelledUnder: base.cancelledUnder };
|
|
428
460
|
});
|
|
429
461
|
}
|
|
462
|
+
runToWrite() {
|
|
463
|
+
const path = join2(this.runDir, "run.yml");
|
|
464
|
+
if (!existsSync(path))
|
|
465
|
+
return { run: this.current, fallback: null, cancelledUnder: false };
|
|
466
|
+
let doc;
|
|
467
|
+
try {
|
|
468
|
+
doc = parseYaml(readFileSync2(path, "utf8"));
|
|
469
|
+
} catch (error) {
|
|
470
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
471
|
+
return { run: this.current, fallback: `could not be read or parsed (${detail})`, cancelledUnder: false };
|
|
472
|
+
}
|
|
473
|
+
const validation = validateRunFile(doc);
|
|
474
|
+
if (!validation.ok) {
|
|
475
|
+
const first = validation.issues[0];
|
|
476
|
+
return {
|
|
477
|
+
run: this.current,
|
|
478
|
+
fallback: `does not validate (${first?.path ?? ""} ${first?.message ?? "schema error"})`,
|
|
479
|
+
cancelledUnder: false
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const onDisk = asRunFile(doc);
|
|
483
|
+
if (onDisk.run !== this.current.run) {
|
|
484
|
+
throw new RunStoreError(`refusing to write ${path}: it records run ${onDisk.run}, and this store holds run ` + `${this.current.run} — a save never overwrites another run's record`);
|
|
485
|
+
}
|
|
486
|
+
const cancelledUnder = onDisk.cancelled !== undefined && this.loaded.cancelled === undefined && this.current.cancelled === undefined;
|
|
487
|
+
return {
|
|
488
|
+
run: carryChanges(onDisk, this.current, this.loaded, { cancelledWins: this.cancelledUnderFlag || cancelledUnder }),
|
|
489
|
+
fallback: null,
|
|
490
|
+
cancelledUnder
|
|
491
|
+
};
|
|
492
|
+
}
|
|
430
493
|
ceilingsToWrite() {
|
|
431
494
|
if (this.budgetMutated)
|
|
432
495
|
return this.currentBudget;
|
|
@@ -474,6 +537,41 @@ function parseStateFile(path) {
|
|
|
474
537
|
`));
|
|
475
538
|
}
|
|
476
539
|
}
|
|
540
|
+
function carryChanges(onDisk, mine, loaded, rules = { cancelledWins: false }) {
|
|
541
|
+
const top = carryLevel(onDisk, mine, loaded, rules.cancelledWins ? ["phases", "cursor"] : ["phases"]);
|
|
542
|
+
const phases = mine.phases.map((phase) => {
|
|
543
|
+
const theirs = onDisk.phases.find((p) => p.id === phase.id);
|
|
544
|
+
const was = loaded.phases.find((p) => p.id === phase.id);
|
|
545
|
+
if (theirs === undefined || was === undefined)
|
|
546
|
+
return phase;
|
|
547
|
+
const stages = phase.stages.map((stage2) => {
|
|
548
|
+
const theirStage = theirs.stages.find((s) => s.id === stage2.id);
|
|
549
|
+
const wasStage = was.stages.find((s) => s.id === stage2.id);
|
|
550
|
+
if (theirStage === undefined || wasStage === undefined)
|
|
551
|
+
return stage2;
|
|
552
|
+
const kept = rules.cancelledWins && theirStage.status === "cancelled" ? ["status", "started_at", "ended_at"] : [];
|
|
553
|
+
return carryLevel(theirStage, stage2, wasStage, kept);
|
|
554
|
+
});
|
|
555
|
+
return { ...carryLevel(theirs, phase, was, ["stages"]), stages };
|
|
556
|
+
});
|
|
557
|
+
return { ...top, phases };
|
|
558
|
+
}
|
|
559
|
+
function carryLevel(onDisk, mine, loaded, skip) {
|
|
560
|
+
const out = { ...onDisk };
|
|
561
|
+
const theirs = mine;
|
|
562
|
+
const was = loaded;
|
|
563
|
+
for (const key of new Set([...Object.keys(theirs), ...Object.keys(was)])) {
|
|
564
|
+
if (skip.includes(key))
|
|
565
|
+
continue;
|
|
566
|
+
if (isDeepStrictEqual(theirs[key], was[key]))
|
|
567
|
+
continue;
|
|
568
|
+
if (theirs[key] === undefined)
|
|
569
|
+
delete out[key];
|
|
570
|
+
else
|
|
571
|
+
out[key] = theirs[key];
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
477
575
|
function rollUp(run) {
|
|
478
576
|
const phases = run.phases.map((phase) => {
|
|
479
577
|
const stages = phase.stages.map((stage2) => ({
|
|
@@ -515,10 +613,20 @@ function round(n) {
|
|
|
515
613
|
return Math.round(n * 100) / 100;
|
|
516
614
|
}
|
|
517
615
|
|
|
616
|
+
// src/core/facilitator/seedInputs.ts
|
|
617
|
+
var DEFAULT_INPUTS_MAX_BYTES = 256 * 1024;
|
|
618
|
+
var MAX_SEED_INLINE_BYTES = 64 * 1024;
|
|
619
|
+
var MIN_SLICE_BYTES = 2 * 1024;
|
|
620
|
+
|
|
621
|
+
// src/core/facilitator/contextLedger.ts
|
|
622
|
+
var DEFAULT_PROMPT_MAX_BYTES = 400 * 1024;
|
|
623
|
+
|
|
518
624
|
// src/core/run/runOutcome.ts
|
|
519
625
|
import { join as join3 } from "node:path";
|
|
520
626
|
|
|
521
627
|
// src/core/build/handoff.ts
|
|
628
|
+
var LINE_BREAK_MARK = "⏎";
|
|
629
|
+
var LINE_BREAK_NOTE = `\`${LINE_BREAK_MARK}\` marks a line break inside a quoted command; the review log beside this file ` + "keeps the command verbatim.";
|
|
522
630
|
var FINDING_STATUS_RE = new RegExp(`—\\s+(${PLAN_STATUSES.join("|")})\\s+—`);
|
|
523
631
|
var FINDING_REASON_RE = new RegExp(`—\\s+(?:${PLAN_STATUSES.join("|")})\\s+—\\s+[^:]*:\\s*(\\S.*)$`);
|
|
524
632
|
|
|
@@ -20,14 +20,14 @@ import {
|
|
|
20
20
|
runSnapshot,
|
|
21
21
|
statusWithOutcome,
|
|
22
22
|
whatIsWaiting
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-rgn23z77.js";
|
|
24
24
|
import {
|
|
25
25
|
expertsDir,
|
|
26
26
|
loadExperts,
|
|
27
27
|
pathsIntersect,
|
|
28
28
|
readExpertDomain,
|
|
29
29
|
stackExpertNames
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-bbrhs055.js";
|
|
31
31
|
import {
|
|
32
32
|
isFinished
|
|
33
33
|
} from "./chunk-6z5rmj0b.js";
|
package/dist/hooks/statusline.js
CHANGED