tldr-experts 0.32.0 → 0.34.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 +286 -0
- package/README.md +2 -0
- package/dist/hooks/answer-capture.js +13 -9
- package/dist/hooks/budget-gate.js +18 -14
- package/dist/hooks/{chunk-9bd6b3xf.js → chunk-0a3b8w1c.js} +5 -1
- package/dist/hooks/{chunk-akemhezh.js → chunk-4d22xm3c.js} +23 -3
- package/dist/hooks/{chunk-8ds2ret8.js → chunk-57s4mdhy.js} +1 -1
- package/dist/hooks/{chunk-whvae1vw.js → chunk-5q89ffsn.js} +61 -7
- package/dist/hooks/{chunk-72et1xzx.js → chunk-6jd4dnyg.js} +1 -1
- package/dist/hooks/{chunk-5kv2vtha.js → chunk-7ypyp8xk.js} +1 -1
- package/dist/hooks/{chunk-04kpbhvk.js → chunk-9kzevq39.js} +3 -3
- package/dist/hooks/{chunk-f9d2efhb.js → chunk-bfvcebn6.js} +3 -3
- package/dist/hooks/{chunk-27v2rwa9.js → chunk-d3jrf6yy.js} +1 -1
- package/dist/hooks/{chunk-5at77jx8.js → chunk-dw8gggv9.js} +6 -15
- package/dist/hooks/{chunk-zyz0fbz5.js → chunk-jfaarcb1.js} +1 -1
- package/dist/hooks/{chunk-yyhwf3bp.js → chunk-kcrphpwc.js} +4 -3
- package/dist/hooks/{chunk-x1f4dxwk.js → chunk-sjykymar.js} +1 -1
- package/dist/hooks/{chunk-q1ge3nex.js → chunk-vhn2vz83.js} +1 -1
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +7 -7
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +12 -12
- package/dist/hooks/statusline.js +8 -8
- package/dist/tldrx.js +1796 -1380
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,291 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.34.0 — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`budget show`, `run estimate` and the `budget-gate` hook now price a Build stage's
|
|
8
|
+
`reviewer_share`, `story_cap_multiplier` and `story_cap_floor_usd` the same way `tldrx next`'s
|
|
9
|
+
brake does, instead of the shipped defaults (see #333).** #214 fixed exactly one of the four
|
|
10
|
+
`stage.yml` calibrations `remainingWork` takes — `attempts` — leaving these three still
|
|
11
|
+
defaulted by the three readers while the brake resolved all four through `spec.tuning`.
|
|
12
|
+
Measured against the Build fixture (three stories priced 2.00/2.50/4.50, two done, stage
|
|
13
|
+
ceiling $60): a stage that writes `story_cap_multiplier: 1` ("take the plan's price
|
|
14
|
+
literally") was quoted $24.25 by `budget show`/`run estimate`/the hook against the $10.75 the
|
|
15
|
+
brake would actually price and dispatch — 2.3x — with the phase ceiling able to sit between
|
|
16
|
+
the two, so `budget show` said BLOCKED and the hook denied a `tldrx next` the brake itself
|
|
17
|
+
allows. `buildStageDefaults` (`core/run/workflowPreset.ts`) now resolves and returns the
|
|
18
|
+
stage's full `StageTuning`, not just `attempts` and `timeout_s`, by re-reading the same
|
|
19
|
+
`stage.yml` `facilitator/stageSpec.ts`'s `overlay` already reads — tolerant exactly as before,
|
|
20
|
+
so an unreadable stage file still falls back to the shipped defaults. `run estimate` already
|
|
21
|
+
held the full tuning via `loadStageSpec`; it was just not forwarding three of the four fields
|
|
22
|
+
to `remainingWork`.
|
|
23
|
+
- **`round2` and the spec §3 exit-code table are each defined in exactly one place, with a shape
|
|
24
|
+
test refusing a second copy (see #174).** `round2` (`Math.round(n * 100) / 100`) was
|
|
25
|
+
independently restated in nine files; the spec §3 exit codes (`cli/exitCodes.ts`) were
|
|
26
|
+
independently restated, under a mix of matching and diverging names (`EXIT_REFUSED` for
|
|
27
|
+
`EXIT_GATE_REFUSED`, `EXIT_AWAITING` for `EXIT_AWAITING_HUMAN`), in fourteen more — wider than
|
|
28
|
+
the issue's own grep found, which only named `runNext.ts`/`runAuto.ts`; the broader set was
|
|
29
|
+
measured here (`grep -rlE '^const EXIT_[A-Z_]+\s*=\s*[0-9]+;' src/`) and folded into the same
|
|
30
|
+
fix rather than filed separately, since it is the identical defect the issue is about. Every
|
|
31
|
+
local copy now imports the canonical one (`core/build/caps.ts`'s exported `round2`,
|
|
32
|
+
`cli/exitCodes.ts`'s table, aliased where a file's own name differed) instead of restating it.
|
|
33
|
+
Mechanism this was hiding: a golden-guard mutation of `cli/exitCodes.ts` left
|
|
34
|
+
`test/build-golden.test.ts` byte-identical, because the Build path never read that file's
|
|
35
|
+
constants at all — it read `runNext.ts`'s and `runAuto.ts`'s own private copies. The new
|
|
36
|
+
`test/round2-exitcodes-one-derivation.test.ts` asserts, on the SHAPE (a local, unexported
|
|
37
|
+
`function round2(`/`const EXIT_*` declaration) rather than on the measured names, that no file
|
|
38
|
+
under `src/` besides the two canonical ones defines either — on the precedent of
|
|
39
|
+
`test/phase-ids-one-derivation.test.ts` (#187): deleting today's copies does not stop a third
|
|
40
|
+
one from coming back under a name this file never mentions.
|
|
41
|
+
- **`merge-wave.sh --status` now says why it could not look instead of answering `idle` (see
|
|
42
|
+
#332).** AGENTS.md §2 calls this command the authoritative instrument for "is a wave alive" —
|
|
43
|
+
the reference-transaction guard and the merge lock exist precisely so nobody has to `ps` plus
|
|
44
|
+
a marker's mtime. Measured on the first autonomous run against this repo (2026-09-17): run
|
|
45
|
+
from outside a git repository, or with its `merge-lock.sh` sibling missing, it answered `idle`
|
|
46
|
+
and exit 0 — a false all-clear that could start a wave on top of one already live. It now
|
|
47
|
+
prints `unknown: <reason>` and exits **15**, its own code (`14` stays release.sh's — see
|
|
48
|
+
AGENTS.md §2 and docs/RELEASING.md), so a caller polling stdout can no longer read "could not
|
|
49
|
+
check" as "nothing is happening".
|
|
50
|
+
- **`merge-wave.sh` now diffs a released, dated CHANGELOG section's content, not just its
|
|
51
|
+
heading, so a rebase-then-merge across a release can no longer land a bullet inside it
|
|
52
|
+
silently.** The exit-12 gate only ever counted unreleased headings; a branch written before
|
|
53
|
+
a release and rebased onto main after that release merged CLEANLY — no conflict, no heading
|
|
54
|
+
touched — and its bullet landed inside the now-dated, supposedly immutable section, caught
|
|
55
|
+
only by a human reading the diff or much later by `release-check.sh`'s own #200 check. The
|
|
56
|
+
wave now compares every dated section against what main had immediately before the merge and
|
|
57
|
+
refuses with its own exit **16**, naming the section and the first difference (see #336).
|
|
58
|
+
- **A spawning test run whose `$TMPDIR` sits inside a git work tree can no longer commit its
|
|
59
|
+
scratch paths onto the enclosing branch (#335).** Fixtures resolve their sandbox through
|
|
60
|
+
`os.tmpdir()`/`$TMPDIR` and run real git inside it; when that path is inside a worktree,
|
|
61
|
+
git's own upward repo search walks out of the fixture and finds the WORKTREE's repo, so
|
|
62
|
+
`git add`/`git commit` there land on whatever branch is checked out — #335 measured 960
|
|
63
|
+
tracked scratch paths and dozens of foreign commits on a real branch this way, and the same
|
|
64
|
+
underlying mechanism reproduced here on `490f637` from a fresh throwaway worktree as the same
|
|
65
|
+
two test failures the issue reported (`test/gitignore-shadow.test.ts`,
|
|
66
|
+
`test/install.test.ts`). The `bun test` preload `test/preload/tmpdirGuard.ts` now refuses,
|
|
67
|
+
named, before any test runs, when the resolved `$TMPDIR` is inside a git work tree — opt out
|
|
68
|
+
only with `TLDRX_ALLOW_TMPDIR_IN_WORKTREE=1`, once the isolation is verified some other way.
|
|
69
|
+
- **A turn killed mid-flight by SIGINT/SIGTERM now banks its cost as absent, not a measured
|
|
70
|
+
`$0.00` (see #355).** `interrupt.ts`'s `recordPartialResult` wrote `cost_usd: 0` with no
|
|
71
|
+
`metered` key on both the task row and the `agent.result` payload — legal by `RunFile.ts`'s
|
|
72
|
+
validator, which only ever checked the converse (a `null` needs `metered: false`), so it
|
|
73
|
+
passed silently while `tallyOf` summed the row into the metered bucket as a real, measured
|
|
74
|
+
zero and `spendBasis` called the run's total complete. It now writes `cost_usd: null,
|
|
75
|
+
metered: false` on the row and the payload, the same idiom every other unknown-cost turn in
|
|
76
|
+
this codebase already uses, so a Ctrl-C mid-turn reads as a stated lower bound.
|
|
77
|
+
- **A killed parent's abandoned Build-story turn now leaves a row in `run.yml`, not nothing (see
|
|
78
|
+
#337).** The headless half of this shipped earlier as the orphan case #337 was filed for; the
|
|
79
|
+
fix deliberately skipped every Build-executor phase, reasoning the per-story ceiling ledger
|
|
80
|
+
already covered it — true for `tldrx cost`'s per-story view, but that ledger has no `run.yml`
|
|
81
|
+
task row to show for it, so `tallyOf`/`spendBasis` (and so `budget.spent_usd`) never saw the
|
|
82
|
+
abandoned attempt at all, not even as unmetered. `demoteStaleRunning` now reads which of a
|
|
83
|
+
stuck Build stage's `agent.spawned` turns no `agent.result` ever answered
|
|
84
|
+
(`build/orphanedTurns.ts`) and records one `cost_usd: null, metered: false` row per abandoned
|
|
85
|
+
story, same as the headless case.
|
|
86
|
+
- **The boundary gate now diffs the branch Build actually cut under `branch_model: integration`,
|
|
87
|
+
instead of an epic file's unused per-epic `branch:` label (see #225).** `epicTargets` read
|
|
88
|
+
`03-plan/epics/*.md`'s own `branch:` as the ref to diff — right under `per-epic`, where Build
|
|
89
|
+
cuts exactly that name, and silently wrong under `integration`, where every epic's stories
|
|
90
|
+
merge into ONE recorded branch and the epic's `branch:` is never cut at all. A run with eight
|
|
91
|
+
such epics measured `boundary=n/a (nothing could be diffed: … does not resolve …)` and
|
|
92
|
+
auto-signed a +31,465/−237-line PR across 117 files with the condition having diffed nothing.
|
|
93
|
+
The branch now comes from `run.yml`'s `build.epic_branch`, through the same `recordedEpicBranch`
|
|
94
|
+
derivation `watch` and `ship` already use (#90) — the epic file supplies repos and stories,
|
|
95
|
+
never the ref. A run that recorded an epic branch and still cannot diff it now HOLDS the gate
|
|
96
|
+
instead of reading `n/a`; `n/a` stays reserved for a run that named no branch to diff at all.
|
|
97
|
+
- **The boundary gate reads a run's stories through the same validated, all-phases walk `ship`
|
|
98
|
+
and carried-findings ownership already share, instead of its own `03-plan/`-only, schema-less
|
|
99
|
+
reader (see #189).** `boundary.ts`'s `storyTouches` was a third `stories/*.md` reader: it
|
|
100
|
+
looked only under `03-plan/` (missing a story filed under `04-build/`, the phase a carried fix
|
|
101
|
+
list's own story is written under), parsed front matter with no schema (taking a story a real
|
|
102
|
+
reader would report as unreadable), and dropped an unparseable file silently. It now calls the
|
|
103
|
+
shared `scanStories`/`phaseDirsOf` leaf (extracted from `build/carriedRows.ts` into
|
|
104
|
+
`build/storyScan.ts` so `boundary.ts` — which `carriedRows.ts` already imports from — does not
|
|
105
|
+
import it back), closing a divergence `carriedRows.ts` had documented as "somebody else's
|
|
106
|
+
issue" since #171.
|
|
107
|
+
- **An agent gate can no longer sign over evidence dated before the stage it is closing even
|
|
108
|
+
started (see #144).** A field evaluation of a delegated `watch` gate found its evidence note
|
|
109
|
+
timestamped 7h before its stage began, asserting facts already false by the time it signed,
|
|
110
|
+
with `refuted: 0` — the resolution half of that bug was #140; this is the remaining half
|
|
111
|
+
AGENTS.md §7 names: "a gate's evidence must not predate what it signs". `validateEvidence`
|
|
112
|
+
refuses a note whose `at:` is before the CURRENT attempt's own start — the new, additive
|
|
113
|
+
`RunStage.attempt_started_at`, which `markRunning` overwrites on every run, never the frozen
|
|
114
|
+
`started_at` it sits beside (§7: existing fields only grow, never change meaning; `started_at`
|
|
115
|
+
is set once, at the stage's first run, and stays that forever) — on both the engine's own
|
|
116
|
+
signer and `approve --as-agent`, one derivation, both doors. Two more holes pre-merge review
|
|
117
|
+
found in the same check: `at` was only confirmed non-empty, so a raw string compare read a
|
|
118
|
+
date-only `at` as earlier than a same-day, full-precision stage start; `at` is now confirmed a
|
|
119
|
+
real RFC3339 instant first, and the two timestamps are compared as instants, not strings. And
|
|
120
|
+
the full test gate caught the new check's own collateral: `tldrx learn`'s chapter 6 planted its
|
|
121
|
+
stand-in evidence note with a fixed past date, which the real wall clock had by now walked
|
|
122
|
+
past — its `at:` is stamped off `Date.now()` (with a 5-minute forward buffer, since the note is
|
|
123
|
+
written before the step that starts the stage it signs) rather than a date fixed at authoring
|
|
124
|
+
time.
|
|
125
|
+
- **`tldrx reject` now archives a stage's scratch evidence note instead of leaving it for the
|
|
126
|
+
next attempt to sign over (see #199).** A reject sent a stage back to `ready` but never
|
|
127
|
+
touched `.agent/<stage>/evidence.md`, so a note that had already said `verdict: sign` over one
|
|
128
|
+
attempt's outputs was still the first (and only) thing either signer found after the stage
|
|
129
|
+
re-ran with different outputs — and could sign again over an attempt it never read. The note
|
|
130
|
+
is renamed to `evidence.rejected-<at>.md` in the same scratch directory (moved, not deleted:
|
|
131
|
+
it is still the record of a real check somebody made), so the re-run meets the ordinary "no
|
|
132
|
+
evidence note at …" state instead. `reject` also advances the stage's `attempt_started_at`
|
|
133
|
+
anchor to its own moment (see #144's entry above) — closing a gap pre-merge review found:
|
|
134
|
+
without it, `approve --evidence <path>` pointed straight at the just-archived note still
|
|
135
|
+
signed, since nothing yet had moved the anchor past it. Archiving is collision-safe too
|
|
136
|
+
(`-2`, `-3`, … suffixes): two rejections landing in the same clock second used to let the
|
|
137
|
+
second silently overwrite the first's archive.
|
|
138
|
+
- **`reject --stage` (i.e. a revoke) no longer strands a later stage `running` with no verb that
|
|
139
|
+
owns it, and `run unlock`'s stranded-run note no longer promises a demotion `next` does not
|
|
140
|
+
make (see #228).** A revoke marked every later stage `stale: true` but left `status: running`
|
|
141
|
+
untouched; `next` only demotes a `running` stage behind a DEAD-PID lock, and a revoke leaves no
|
|
142
|
+
lock behind at all, so the cursor could walk back into that stranded stage mid-flight once the
|
|
143
|
+
revoked stage was re-approved. A later `running` stage with nothing holding it (no `--prepare`
|
|
144
|
+
bundle waiting) now goes back to `ready` alongside going stale, named on the outcome and on the
|
|
145
|
+
additive `gate.revoked` event; one holding a bundle is a sub-agent turn already paid for, so
|
|
146
|
+
the revoke refuses instead and names it. `run unlock` had the same false promise for a
|
|
147
|
+
lockless `running` stage ("`tldrx next` demotes it") — it now performs that demotion itself
|
|
148
|
+
when nothing is holding the run, rather than describing a move nothing makes. A revoke also
|
|
149
|
+
advances `attempt_started_at` (see #144, #199) on the stage it revokes and on every stage it
|
|
150
|
+
demotes, for the same reason `reject` does.
|
|
151
|
+
- **A mutating generator command in a story's dod block can no longer be run by the base-tree
|
|
152
|
+
pre-flight as a routine measurement (see #362).** Measured live, tldrx 0.31.1: a `.NET`
|
|
153
|
+
story's dod block declared `dotnet ef migrations add …` beside ordinary build/test commands,
|
|
154
|
+
and the base-tree probe ran it against the UNTOUCHED base tree — a DELTA-gate measurement is
|
|
155
|
+
not supposed to mutate the tree it is measuring, and it would have written a migration triple
|
|
156
|
+
into pristine `main` as a side effect of asking a question. `isGeneratorCommand`
|
|
157
|
+
(`schemas/commandAllowlist.ts`) detects a generator BY NAME — the command text, not a
|
|
158
|
+
workspace-declared slot key, since a team names its migration-add slot whatever it likes — and
|
|
159
|
+
two doors read that one derivation: `validateStoryDod` now refuses a generator command at Plan
|
|
160
|
+
time, so a plan is never written this way, and `dodRunner.ts`'s `measureBaseCommand` refuses
|
|
161
|
+
to run one against either base tree it measures, as defense in depth for a story file that
|
|
162
|
+
reached Build some other way. Neither door touches a story's own fresh worktree, where a
|
|
163
|
+
generator legitimately runs once, by the developer.
|
|
164
|
+
- **`tldrx next` now self-closes a parked `auto` gate the moment every condition it was held on
|
|
165
|
+
clears, `--wait-gates` or not (see #342).** Before this, the only door that ever re-measured an
|
|
166
|
+
already-`awaiting_gate` stage was `run auto --wait-gates`'s own poll (`waitForGate` →
|
|
167
|
+
`selfCloseAutoGate`); without that flag, `run auto` answering its own blocking questions (or a
|
|
168
|
+
person answering them by hand) left the gate `awaiting_gate` forever, and the next `next` call
|
|
169
|
+
reported it as terminal-for-this-call with a generic `gate pending: tldrx approve` — never
|
|
170
|
+
noticing the questions it was parked on had since been answered. `next` now re-runs the same
|
|
171
|
+
seven conditions (`reevaluateAutoGate`) on every `awaiting_gate` stage whose policy is `auto`
|
|
172
|
+
before giving up, and signs it through the same `approve` door a freshly-closed gate uses —
|
|
173
|
+
`human` and `agent` gates are untouched, and a verdict that still fails falls through to the
|
|
174
|
+
ordinary human-gate report exactly as before.
|
|
175
|
+
- **The default Build budget gate now parks at a story boundary instead of stopping the stage
|
|
176
|
+
incomplete with no boundary the operator chose (see #354).** `runNext.ts`'s `budgetRefusal`
|
|
177
|
+
meters the phase ceiling exactly once, at stage entry — the Build executor itself never
|
|
178
|
+
re-checked the envelope between two dispatches of one headless run, so a phase that could
|
|
179
|
+
afford its entry estimate but ran short partway through kept dispatching until the money was
|
|
180
|
+
gone, and the only way the gate fired again was an all-or-nothing refusal on a RETRY of a
|
|
181
|
+
failed stage. Modeled directly on #298's rate-limit park: before starting a new story's
|
|
182
|
+
developer, the executor now checks whether the stage's remainder still covers that story's
|
|
183
|
+
floor and, if not, lets whatever is already running finish and settle, then stops — recording
|
|
184
|
+
one `budget.parked` event (mirroring `agent.rate_limited`'s shape) and naming the shortfall as
|
|
185
|
+
the reason in both the operator line and the handoff's `## Unknowns`. `run auto --wait-gates`
|
|
186
|
+
resumes the parked stage itself once an operator's `tldrx budget raise --stage` (or
|
|
187
|
+
`--rebalance-finished`) actually raises the STAGE's own `budget_usd` — checked fresh on every
|
|
188
|
+
poll against the phase's real remainder, never a clock — and `run status` names the park
|
|
189
|
+
instead of reading as a decision waiting on a person.
|
|
190
|
+
|
|
191
|
+
## 0.33.0 — 2026-09-17
|
|
192
|
+
|
|
193
|
+
### Added
|
|
194
|
+
|
|
195
|
+
- **A workspace can opt its base pre-flight probe into a story-shaped worktree, so an
|
|
196
|
+
environmental red that only appears there is caught before the first story (#371).** #363
|
|
197
|
+
shipped `tree:` provenance on every base-pre-flight row and declined, by measured decision, to
|
|
198
|
+
move the probe itself into a worktree by default — doing so on every run would pay for the
|
|
199
|
+
declared command a second time and reopen the `node_modules`-less outage the checkout-only
|
|
200
|
+
design exists to avoid. `probe_in_worktree: true`, workspace-wide and additive, is the opt-in
|
|
201
|
+
for a workspace that has actually seen the gap #363 named: a command green in the checkout and
|
|
202
|
+
red only in a story's own worktree shape (a `.git` FILE vs. a `.git` directory is the measured
|
|
203
|
+
case). On, every declared base command is ALSO measured, once per repo, in a fresh detached
|
|
204
|
+
worktree of the base sha — kept beside the checkout row, never instead of it, under the same
|
|
205
|
+
`tree:` field (`checkout` vs. `worktree`) rather than a third tree constant. Off (the default,
|
|
206
|
+
and every workspace before this key existed) leaves the base pre-flight measuring the checkout
|
|
207
|
+
only, exactly as before. Pre-merge review measurement: a session that calls the base pre-flight
|
|
208
|
+
more than once (a `prepare()` followed by the real call) paid for a `git worktree add`/`remove`
|
|
209
|
+
on EVERY call, even when every declared command already had a fresh `tree: worktree` reading and
|
|
210
|
+
nothing was spawned there — fixed by checking every command's cache hit before touching git at
|
|
211
|
+
all, so a worktree is opened only when at least one command still needs a fresh measurement.
|
|
212
|
+
|
|
213
|
+
### Fixed
|
|
214
|
+
|
|
215
|
+
- **The plan sequencing rule (#365) could false-positive two unrelated stories that happened to
|
|
216
|
+
share a generic single-transition camelCase token (#370).** `fieldsIn`
|
|
217
|
+
(`src/core/plan/planShape.ts`) captured ANY bare camelCase/PascalCase token with one inner
|
|
218
|
+
capital as a field candidate, so a story enforcing an invariant near a word like `toString` or
|
|
219
|
+
`getId` collided with an unrelated story populating a same-named `toString` or `getId`
|
|
220
|
+
elsewhere, refusing an honest plan for sharing nothing but an incidental identifier. A bare
|
|
221
|
+
camelCase/PascalCase token with at least two internal capital transitions
|
|
222
|
+
(`DeliveryAddressText`) still always counts; a one-transition token (`toString`, `getId`) now
|
|
223
|
+
counts only when the SAME normalized field is ALSO spelled `snake_case` or backtick-quoted
|
|
224
|
+
somewhere else in the plan — dropping one-transition tokens outright, the first cut at this
|
|
225
|
+
fix, reopened #365 for a genuine two-word field spelled bare PascalCase on one side and
|
|
226
|
+
`snake_case` on the other (`order_total` enforced in one story, bare `OrderTotal` populated in
|
|
227
|
+
another still refuses; `toString` shared by two unrelated stories, with no `snake_case` or
|
|
228
|
+
backtick sibling anywhere in the plan, still does not). `snake_case` and backtick-quoted
|
|
229
|
+
identifiers are unaffected either way, and the originating #365 case still refuses.
|
|
230
|
+
- **`task.done`'s `as_is_note` had no size bound, unlike its two siblings on the same event
|
|
231
|
+
(#368).** `permission_refused` and `budget_death` were clamped by #359 after a ~110-line
|
|
232
|
+
refused heredoc alone pushed `task.done` past the §2.9 4096-byte payload cap and failed the
|
|
233
|
+
whole build STAGE, not just the story. `as_is_note` — the operator-typed `--note` on `tldrx
|
|
234
|
+
story reopen <id> --as-is` — was never given the same treatment, even though nothing stops a
|
|
235
|
+
long, scripted or pasted note from reaching the exact same shape. It is now passed through the
|
|
236
|
+
same `clampAgentText` call, the same `TASK_DONE_FIELD_MAX_BYTES` constant and the same
|
|
237
|
+
`[clamped: N bytes exceeds the M byte cap]` marker convention #359 already set, so an
|
|
238
|
+
oversized as-is note can no longer fail the build stage.
|
|
239
|
+
- **The Build-entry probe's throwaway worktree ran a repo's `install:` but not its
|
|
240
|
+
`tool_restore:`, so a command whose resolution depended on the restore having run could be
|
|
241
|
+
refused there even though the checkout door — which does run `tool_restore:` since #363 —
|
|
242
|
+
would have been fine (part of #371).** `entryProbe.ts`'s per-repo declaration now also reads
|
|
243
|
+
`tool_restore:` (`toolRestoreCommandFor`, the same slot `prepBaseTree` already runs at the
|
|
244
|
+
checkout door) and runs it in the fresh worktree before that worktree's `install:` and before
|
|
245
|
+
any Definition-of-Done command's binary is resolved there, refusing Build by name on a
|
|
246
|
+
nonzero exit — the same shape the checkout door already uses, never a second copy of the
|
|
247
|
+
restore logic.
|
|
248
|
+
|
|
249
|
+
### Changed
|
|
250
|
+
|
|
251
|
+
- **`settleAskedNoDiff` and `settleRedDod` now share one requeue-settle helper instead of each
|
|
252
|
+
hand-building the same shape (#369).** Both settled a "requeue this attempt, attempts remain"
|
|
253
|
+
turn by constructing an identical eight-field n-a-review object and `keepWorktree: true` by
|
|
254
|
+
hand — copied verbatim in two places, the AGENTS.md §12 duplication the file's own
|
|
255
|
+
decomposition precedent exists to avoid. `settleRequeue` now holds that shape once, taking the
|
|
256
|
+
fields that actually vary (`dod`, `reason`, `askedNoDiff`) as data; both call sites are
|
|
257
|
+
unchanged in behavior — `test/build-golden.test.ts` stays byte-identical.
|
|
258
|
+
- **AGENTS.md §11 now names the negated-closing-verb trap and widens its own verification
|
|
259
|
+
grep to catch it.** Measured 2026-09-16: commit `d8f90f9`'s sentence `reported not fixed:
|
|
260
|
+
#359` auto-closed #359 anyway — GitHub's closing-keyword parser matches `fixed` (any
|
|
261
|
+
inflection) followed by an optional colon/punctuation and `#N`, and does not parse the
|
|
262
|
+
negation three tokens earlier; #359 needed a reopen before its real fix (`24f1324`) closed
|
|
263
|
+
it a second time, legitimately. §11's own documented verification command,
|
|
264
|
+
`grep -iE '(clos|fix|resolv)[a-z]* #'`, had the identical blind spot — it does not allow a
|
|
265
|
+
colon between the verb and the number, so it read `fixed: #359` as a non-match (measured
|
|
266
|
+
exit 1) and would have let this exact sentence through. The grep is widened to
|
|
267
|
+
`(clos|fix|resolv)[a-z]*[[:punct:]]* #` (measured exit 0 on the same sentence); the real
|
|
268
|
+
discipline stays "never write a closing-shaped phrase near a `#N` that should not close" —
|
|
269
|
+
the grep is a tripwire, not the fix (closes #372).
|
|
270
|
+
- **`ci.yml` now keeps `test/merge-wave.test.ts`'s tmp sandbox as a build artifact when the
|
|
271
|
+
job fails.** This is an INSTRUMENT change, not a fix for the still-undiagnosed #237 flake
|
|
272
|
+
(interrupted-merge case expects a SIGTERM exit of 143, sometimes sees 0 after ~92s): every
|
|
273
|
+
#237 data point so far recorded the same second wall on top of the first — "no merge.log is
|
|
274
|
+
available from the CI sandbox" — because the sandbox that file plants was discarded with
|
|
275
|
+
the runner. The `bun test` step's `TMPDIR` now points at `${{ runner.temp }}/mw` instead of
|
|
276
|
+
the OS default, and a new `if: failure()` step uploads a scoped subset of it on failure
|
|
277
|
+
(`actions/upload-artifact@v4`, `retention-days: 14`). Review caught a second wall in the
|
|
278
|
+
first cut (closes #372's own class of bug, an instrument that cannot observe the thing):
|
|
279
|
+
`afterEach` `rmSync`s every sandbox unconditionally, pass or fail, INSIDE the bun process,
|
|
280
|
+
before that upload step ever runs — so the artifact would have been empty on the exact run
|
|
281
|
+
it exists for. `test/merge-wave.test.ts` now mkdtemps its sandboxes under the recognisable
|
|
282
|
+
`mw-sandbox-` prefix and skips its own cleanup when `MERGE_WAVE_KEEP_SANDBOX` is set (the
|
|
283
|
+
decision lives in one small, unit-tested function, `keepSandbox`/`cleanupSandbox` in
|
|
284
|
+
`test/fixtures/mergeWaveSandbox.ts` — local runs stay hermetic and self-cleaning per §8
|
|
285
|
+
unless that var is set); ci.yml sets it on the `bun test` step and scopes the upload's
|
|
286
|
+
`path:` to exactly that prefix, so the next red leaves a `merge.log` and lock state an
|
|
287
|
+
agent can actually read (see #237, closes #373).
|
|
288
|
+
|
|
3
289
|
## 0.32.0 — 2026-09-16
|
|
4
290
|
|
|
5
291
|
### 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.34.0 | 2026-09-17 | `beta` | Four waves of the audited backlog, every branch reviewed before main: run auto no longer stops itself (parked auto gates self-close, Build parks at a story boundary on budget), gates cannot sign stale or backdated evidence, interrupted turns bank their cost as absent instead of a measured zero, the boundary condition diffs the branch the run recorded, mutating generators are refused from dod blocks, merge-wave refuses edits to released CHANGELOG sections and answers `unknown` instead of `idle`, and the test harness refuses a TMPDIR inside a work tree — 14 issues, the first of them shipped end-to-end by a tldrx run against this repo |
|
|
339
|
+
| 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. |
|
|
338
340
|
| 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. |
|
|
339
341
|
| 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). |
|
|
340
342
|
| 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). |
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-9kzevq39.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-bfvcebn6.js";
|
|
9
9
|
import {
|
|
10
10
|
parseHookInput,
|
|
11
11
|
readStdin
|
|
12
|
-
} from "./chunk-
|
|
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-
|
|
17
|
+
} from "./chunk-4d22xm3c.js";
|
|
18
18
|
import {
|
|
19
19
|
PHASE_IDS
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import"./chunk-
|
|
20
|
+
} from "./chunk-kcrphpwc.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-
|
|
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-
|
|
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,24 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
12
|
-
import"./chunk-
|
|
11
|
+
} from "./chunk-d3jrf6yy.js";
|
|
12
|
+
import"./chunk-jfaarcb1.js";
|
|
13
13
|
import {
|
|
14
14
|
REBALANCE_SOURCE,
|
|
15
15
|
applyRebalance,
|
|
16
16
|
asRunBudget,
|
|
17
|
-
currentActor,
|
|
18
17
|
describeRebalance,
|
|
19
18
|
economyFor,
|
|
20
19
|
isHostTokens,
|
|
21
|
-
nowRfc3339,
|
|
22
20
|
planRebalance,
|
|
23
21
|
raiseCommand,
|
|
24
22
|
raiseGrantVerdict,
|
|
@@ -27,10 +25,12 @@ import {
|
|
|
27
25
|
validateRunBudget,
|
|
28
26
|
wouldExceed,
|
|
29
27
|
wouldExceedHostTokens
|
|
30
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-dw8gggv9.js";
|
|
31
29
|
import {
|
|
32
|
-
EventLog
|
|
33
|
-
|
|
30
|
+
EventLog,
|
|
31
|
+
currentActor,
|
|
32
|
+
nowRfc3339
|
|
33
|
+
} from "./chunk-4d22xm3c.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-
|
|
42
|
+
} from "./chunk-57s4mdhy.js";
|
|
43
43
|
import {
|
|
44
44
|
buildStageDefaults
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-kcrphpwc.js";
|
|
46
46
|
import {
|
|
47
47
|
noteDeprecations
|
|
48
|
-
} from "./chunk-
|
|
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-
|
|
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";
|
|
@@ -125,7 +125,8 @@ await runHook("budget-gate", async () => {
|
|
|
125
125
|
const attended = isAttendedByHostView(view);
|
|
126
126
|
const stage = cursorStage(view);
|
|
127
127
|
const declared = stage?.budget_usd ?? stageBudgetFromLibrary(root, view.cursor.stage);
|
|
128
|
-
const
|
|
128
|
+
const tuning = buildStageDefaults(root, view.scope, view.cursor.stage);
|
|
129
|
+
const attempts = tuning.attempts;
|
|
129
130
|
const work = declared === null ? null : remainingWork({
|
|
130
131
|
runDir: view.dir,
|
|
131
132
|
phaseId: view.cursor.phase,
|
|
@@ -135,7 +136,10 @@ await runHook("budget-gate", async () => {
|
|
|
135
136
|
maxUsd: null,
|
|
136
137
|
economy: economyFor(budget, view.cursor.phase),
|
|
137
138
|
attended,
|
|
138
|
-
attempts
|
|
139
|
+
attempts,
|
|
140
|
+
reviewerShare: tuning.reviewerShare,
|
|
141
|
+
storyCapMultiplier: tuning.storyCapMultiplier,
|
|
142
|
+
storyCapFloorUsd: tuning.storyCapFloorUsd
|
|
139
143
|
});
|
|
140
144
|
const estimate = estimateFor(command, work === null ? null : work.usd);
|
|
141
145
|
if (estimate <= 0)
|
|
@@ -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();
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
GATE_POLICIES,
|
|
3
3
|
validateGatesPolicy,
|
|
4
4
|
validateStagePolicy
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-kcrphpwc.js";
|
|
6
6
|
import {
|
|
7
7
|
asDocument,
|
|
8
8
|
isRecord,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
requireString,
|
|
16
16
|
requireVersion,
|
|
17
17
|
result
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-0a3b8w1c.js";
|
|
19
19
|
|
|
20
20
|
// src/core/events/EventLog.ts
|
|
21
21
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -69,6 +69,7 @@ var EVENT_TYPES = [
|
|
|
69
69
|
"budget.blocked",
|
|
70
70
|
"budget.raised",
|
|
71
71
|
"budget.granted",
|
|
72
|
+
"budget.parked",
|
|
72
73
|
"fact.added",
|
|
73
74
|
"fact.retired",
|
|
74
75
|
"fact.superseded",
|
|
@@ -231,6 +232,19 @@ function skippedNote(skipped) {
|
|
|
231
232
|
}
|
|
232
233
|
var WARNED = new Set;
|
|
233
234
|
|
|
235
|
+
// src/hooks/lib/actor.ts
|
|
236
|
+
function currentActor() {
|
|
237
|
+
const user = process.env.USER ?? process.env.USERNAME ?? "";
|
|
238
|
+
return user.trim() === "" ? "unknown" : user.trim();
|
|
239
|
+
}
|
|
240
|
+
function nowRfc3339() {
|
|
241
|
+
return `${new Date().toISOString().slice(0, 19)}Z`;
|
|
242
|
+
}
|
|
243
|
+
var RFC3339_RE = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
244
|
+
function isRfc3339(value) {
|
|
245
|
+
return RFC3339_RE.test(value) && !Number.isNaN(Date.parse(value));
|
|
246
|
+
}
|
|
247
|
+
|
|
234
248
|
// src/core/run/questionsPolicy.ts
|
|
235
249
|
var QUESTION_POLICIES = ["human", "recommended"];
|
|
236
250
|
function validateQuestionsPolicy(value, stageIds, issues) {
|
|
@@ -300,6 +314,9 @@ function parseEvidence(text) {
|
|
|
300
314
|
const role = requireEnum2(doc.role, EVIDENCE_ROLES, "role", issues);
|
|
301
315
|
const by = requireText(doc.by, "by", issues);
|
|
302
316
|
const at = requireText(doc.at, "at", issues);
|
|
317
|
+
if (at !== "" && !isRfc3339(at)) {
|
|
318
|
+
issues.push(front(0, `\`at\` must be a valid RFC3339 timestamp, got \`${at}\``));
|
|
319
|
+
}
|
|
303
320
|
const verdict = requireEnum2(doc.verdict, EVIDENCE_VERDICTS, "verdict", issues);
|
|
304
321
|
const read = requireStrings(doc.read, "read", issues);
|
|
305
322
|
const citations = requireCitations(doc.citations, issues);
|
|
@@ -728,6 +745,9 @@ function validateRunFile(input) {
|
|
|
728
745
|
if (stage.stale !== undefined && typeof stage.stale !== "boolean") {
|
|
729
746
|
issues.push({ path: `${path}.stale`, message: "expected true or false" });
|
|
730
747
|
}
|
|
748
|
+
if (stage.attempt_started_at !== undefined && stage.attempt_started_at !== null && typeof stage.attempt_started_at !== "string") {
|
|
749
|
+
issues.push({ path: `${path}.attempt_started_at`, message: "expected a string or null" });
|
|
750
|
+
}
|
|
731
751
|
checkOrder(stage.started_at, stage.ended_at, path, issues);
|
|
732
752
|
if (cursor !== null && cursor.phase === phaseId && cursor.stage === stageId)
|
|
733
753
|
cursorResolves = true;
|
|
@@ -918,4 +938,4 @@ function gateEvidencePath(runDir, phaseId, stageId) {
|
|
|
918
938
|
return join2(runDir, ...gateEvidenceRelPath(phaseId, stageId).split("/"));
|
|
919
939
|
}
|
|
920
940
|
|
|
921
|
-
export { EventLog, parseEvidence, PHASE_ID_RE, OUTCOME_NOT_RECORDED, isTerminal, isFinished, isAttendedByHost, flatten, stageAt, deriveRunStatus, derivePhaseStatus, validateRunFile, asRunFile, evidencePath, gateEvidencePath };
|
|
941
|
+
export { EventLog, currentActor, nowRfc3339, parseEvidence, PHASE_ID_RE, OUTCOME_NOT_RECORDED, isTerminal, isFinished, isAttendedByHost, flatten, stageAt, deriveRunStatus, derivePhaseStatus, validateRunFile, asRunFile, evidencePath, gateEvidencePath };
|