tldr-experts 0.18.0 → 0.18.1
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 +136 -0
- package/README.md +1 -0
- package/dist/hooks/answer-capture.js +28 -11
- package/dist/hooks/budget-gate.js +9 -9
- package/dist/hooks/{chunk-7hhjx1wk.js → chunk-1tynpxvm.js} +3 -3
- package/dist/hooks/{chunk-qdvwnts3.js → chunk-1v05a468.js} +1 -1
- package/dist/hooks/{chunk-vmhfw2r1.js → chunk-2caxb41h.js} +1 -1
- package/dist/hooks/{chunk-msswet35.js → chunk-2ym87z6d.js} +1 -1
- package/dist/hooks/{chunk-p0ns6fex.js → chunk-8ak5kwd5.js} +1 -1
- package/dist/hooks/{chunk-bm9aqtg3.js → chunk-bgxj40rr.js} +2 -2
- package/dist/hooks/{chunk-77y6q9mt.js → chunk-c1h38et6.js} +29 -2
- package/dist/hooks/{chunk-rw9zk47w.js → chunk-ct7kazfy.js} +7 -4
- package/dist/hooks/{chunk-4mjxyfp9.js → chunk-hwsng0xq.js} +1 -1
- package/dist/hooks/{chunk-26xkmfdr.js → chunk-kwaqdq9w.js} +3 -3
- package/dist/hooks/{chunk-ds9sew2e.js → chunk-pday823f.js} +1 -1
- package/dist/hooks/{chunk-rr7n3j59.js → chunk-rmpcrt4c.js} +22 -7
- package/dist/hooks/{chunk-ab6cv5v5.js → chunk-vf992pfw.js} +1 -1
- package/dist/hooks/{chunk-00h4xah7.js → chunk-x64paqbg.js} +22 -2
- package/dist/hooks/{chunk-xxcy0tsa.js → chunk-zg7q0q4y.js} +1 -1
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +8 -8
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +13 -13
- package/dist/hooks/statusline.js +9 -9
- package/dist/tldrx.js +1263 -669
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,141 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.1 — 2026-09-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Per-story scoped checks, and the full suite once on the epic head (#257).** Measured on a
|
|
8
|
+
field run at 0.16.1: 99 `check.*` events on one 8-story epic — every story's Definition of Done
|
|
9
|
+
ran the whole declared list, the full suite among it, on every attempt and every fix round, and
|
|
10
|
+
the epic head that actually ships was never run at all. The DoD is a delta gate ("this story did
|
|
11
|
+
not break the tree"), proven until now by running everything because nothing narrower existed.
|
|
12
|
+
`workspace.yml` grows `commands.<slot>_scoped: "<cmd> {{paths}}"`, a template beside a declared
|
|
13
|
+
slot: a story's dod line whose command has one runs the template over the story's own paths —
|
|
14
|
+
declared `touches:` ∪ its committed diff ∪ its dirty entries, existing paths only, one derivation
|
|
15
|
+
in `build/scopedPaths.ts` — substituted at the argv level (a path with a space is ONE argument,
|
|
16
|
+
no shell), and the row says `scope: "paths"` with the paths. The moment the epic flips to `done`,
|
|
17
|
+
the deduped full commands of its stories run once in the epic worktree, after `install:` if
|
|
18
|
+
declared, as `scope: "full"` rows on the epic's `lane` attributed to the last story merged; a red
|
|
19
|
+
there blocks that story, so the `stories` gate condition refuses. The template is never a
|
|
20
|
+
command — not in the allowlist, not citable, not handed to the developer, refused at Plan time
|
|
21
|
+
by a sentence naming the suffix; the story's evidence keeps citing the DECLARED command and the
|
|
22
|
+
row's prose names what ran. A repo that declares no template runs, records and cites exactly
|
|
23
|
+
what it did before: the golden is byte-identical, and a `scope` key emitted unconditionally
|
|
24
|
+
reddens it. Pinned by a two-story fake-agent build — exactly 2 `scope: paths` rows with the
|
|
25
|
+
right paths and 1 `scope: full` row with `lane`, a red epic head blocking S2 with the gate
|
|
26
|
+
counting 1 blocked, and a control with no template emitting no `scope` key at all; skipping the
|
|
27
|
+
epic-head run or substituting nothing each reddens exactly one of those.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **A cancelled run's epic branch no longer blocks the next run for the same feature: `run cancel`
|
|
32
|
+
releases it, and Build moves a finished run's leftover aside instead of refusing it as someone
|
|
33
|
+
else's (#272).** Measured 2026-09-13 on a headless proof run at a509dca: run A had cut
|
|
34
|
+
`epic/main-ci-green` and was cancelled; the ordinary retry after a cancelled attempt — same
|
|
35
|
+
feature, id `…-2` — reached Build and was refused, "already exists … did not cut it — refusing to
|
|
36
|
+
stack this run's commits onto someone else's epic", relaunched once under `--until-done`, got the
|
|
37
|
+
same last line and stopped as #252 designed. $3.70 of what/how/plan died on a leftover with ZERO
|
|
38
|
+
commits beyond `main` — the only work of the cancelled run sat on its story branch (#129), which
|
|
39
|
+
nothing touches. Two halves, one rule. `tldrx run cancel` now releases every branch its
|
|
40
|
+
`build.epic_branch` claims, after `closeRun` has taken the run's epic worktrees back: no commit
|
|
41
|
+
beyond the repo's default branch → deleted (it was the base under another name); commits → renamed
|
|
42
|
+
to `epic/<slug>@<run-id>` (one derivation, `build/epicRelease.ts`) so they survive under a name
|
|
43
|
+
that says whose they were; checked out somewhere → left alone and the checkout named; a base git
|
|
44
|
+
cannot count against → left alone, because a miscounted zero there deletes commits. And Build, at
|
|
45
|
+
the refusal, reads WHO owns the branch from the claims under `tldrx-work/`: an owner whose
|
|
46
|
+
`run.yml` is explicitly `cancelled` or `done` — never "no process", since a run killed mid-Build or
|
|
47
|
+
parked at a gate has no process and is still the owner — left a leftover, and Build moves it
|
|
48
|
+
aside by the same rule and cuts its own; an open owner, an unreadable owner, and a branch NO run
|
|
49
|
+
claims keep the refusal verbatim with one line naming which — the unclaimed case is what #262's
|
|
50
|
+
killed-mid-cut run leaves, and its relaunch must find its epic where it left it. Every move is
|
|
51
|
+
written where a person reads it, not only in the branch list: the owner's run.yml
|
|
52
|
+
(`build.epic_released`, additive), its `04-build/handoff.md` when it wrote one, an `epic.released`
|
|
53
|
+
event on its ledger and — from Build — on the new run's ledger and in its handoff's Decisions.
|
|
54
|
+
Pinned against a real repo: delete / rename-with-commits-surviving / worktree-kept for the cancel
|
|
55
|
+
half, and owner-cancelled → renamed + fresh epic + both records, owner-open → verbatim exit 2,
|
|
56
|
+
no-claim → verbatim exit 2 for the Build half; the golden build is byte-identical.
|
|
57
|
+
- **A permission refusal on a story whose tree holds work no longer blocks it before the
|
|
58
|
+
facilitator's own Definition of Done can measure it, and the developer is told why a plumbed
|
|
59
|
+
DoD line is refused (#271).** Measured 2026-09-12 on a headless `run auto --until-done` at
|
|
60
|
+
0.18.0: the developer finished, committed, and then ran the workspace's DoD command wrapped in
|
|
61
|
+
`> log 2>&1; echo EXIT:$? >> log; tail …` — a line the agent's permission layer splits at every
|
|
62
|
+
separator and refuses because the fragments do not each match a grant (#215). #261's block did
|
|
63
|
+
exactly what it says: `blocked: permission — <that line>` on the gate, one attempt spent,
|
|
64
|
+
`--until-done` correctly parked. And it was the wrong verdict, because the refusal was the
|
|
65
|
+
developer's OWN attempt at verification, the work was already in the tree, and the facilitator
|
|
66
|
+
re-runs the DoD one step later regardless — $8.12 to reach a committed story nothing measured.
|
|
67
|
+
The refusal now stops a story only when the tree holds NO work. "Work" is `workSince`, one
|
|
68
|
+
derivation against the tree the developer was handed: the branch tip's tree differs from it
|
|
69
|
+
(`git diff --quiet`), OR the working copy is dirty (`git status --porcelain` semantics —
|
|
70
|
+
untracked files count, git-ignored files do not), both outside `tldrx-work/` and `.tldrx/`,
|
|
71
|
+
which a `root_is_repo` worktree carries and the framework writes during the turn. Uncommitted
|
|
72
|
+
work counts on purpose: the normal path already takes a dirty tree into the DoD and commits it
|
|
73
|
+
afterwards (`runDod` runs before `commitIfDirty`), and the incident's likeliest shape is a
|
|
74
|
+
developer refused while VERIFYING that never reaches its commit. An empty commit, an untouched
|
|
75
|
+
tree, a turn that wrote only framework state and a turn that wrote only an ignored file all
|
|
76
|
+
still block with #261's exact wording after one attempt. With work the refusal is RECORDED and
|
|
77
|
+
the Definition of Done decides: green, and the story goes on to review with the refused command
|
|
78
|
+
named on its review log (`- Developer: … was refused for approval …`), on `task.done` as an
|
|
79
|
+
additive `permission_refused`, and in the handoff's `## Unknowns`; red, and the story blocks
|
|
80
|
+
with BOTH reasons on one row, the DoD's first. Not blocking never means not recording, and a
|
|
81
|
+
refusal is its own story's and its own attempt's only. The spawned developer prompt also gains
|
|
82
|
+
one rule beside "Done means proven": run each DoD command verbatim and alone, with the
|
|
83
|
+
mechanism stated — `>`, `>>`, `2>&1`, `<`, `|`, `;`, `&&`, `||`, `&`, `$()` split a line into
|
|
84
|
+
subcommands and each must match its own grant, so a compound line is refused even when the
|
|
85
|
+
script is allowed — and that the facilitator re-runs the DoD anyway. That sentence moves the
|
|
86
|
+
frozen developer prompt in `test/build-golden.test.ts` by exactly those lines in six prompt
|
|
87
|
+
files; events, `run.yml` rows and exit codes are byte-identical. Pinned by nine tests: refusal
|
|
88
|
+
after a commit + green DoD → `check.*` ran, `done`, recorded on all three surfaces; uncommitted
|
|
89
|
+
work + green DoD → measured, committed by the facilitator, landed; + red DoD → `blocked` with
|
|
90
|
+
both reasons; empty commit, state-dir-only and ignored-file-only → `blocked: permission — …`
|
|
91
|
+
with no DoD spent, one spawn; two stories with the refusal on one → the other's record clean;
|
|
92
|
+
refused on attempt 1 and clean on attempt 2 → attempt 2's record clean; and the prompt sentence
|
|
93
|
+
once, in `## Rules`. Mutations, each measured: dropping the comparison (every refusal
|
|
94
|
+
continues) reddens #261's own test and the empty-commit, state-only and ignored-only cases; a
|
|
95
|
+
`headSha !== since` proxy reddens the uncommitted and empty-commit cases; dropping the dirty
|
|
96
|
+
half reddens the uncommitted case; dropping the state-dir exclusions reddens state-only;
|
|
97
|
+
counting ignored files (`git status --ignored`) reddens state-only and ignored-only; removing
|
|
98
|
+
the per-attempt reset reddens the attempt-2 case.
|
|
99
|
+
- **The headless refusal under a `host-tokens` ceiling now writes its `budget.blocked` — it was
|
|
100
|
+
the one money-family exit 2 the ledger could not see (#266).** Measured 2026-09-12 on
|
|
101
|
+
`runNext.ts`: `budgetRefusal` and `hostTokensNote` both append a `budget.blocked` before
|
|
102
|
+
returning exit 2; `economyRefusal` — the §E.2 gate that refuses a headless spawn on a phase
|
|
103
|
+
priced in host tokens — returned the same exit with only its CLI lines and no
|
|
104
|
+
`store.append` at all. The `--until-done` supervisor (#252) keys its money guard on a
|
|
105
|
+
`budget.blocked` among the attempt's fresh events, so a refusal that wrote nothing read as
|
|
106
|
+
"a refusal with no money behind it" and was relaunched into the same refusal — harmless to
|
|
107
|
+
the wallet, since the gate fires before any spawn, but a relaunch that could never do
|
|
108
|
+
anything, and a run whose own audit trail never states why it stopped (the same
|
|
109
|
+
blind-instrument shape as #248). The gate now appends one row of the `hostTokensNote` shape
|
|
110
|
+
(`phase`, `economy: host-tokens`, `ceiling_tokens`, a `reason` naming the headless
|
|
111
|
+
invocation) before the exit; the refusal text is byte-for-byte what it was. Pinned by a
|
|
112
|
+
headless run on a host-tokens phase → exit 2 AND exactly one `budget.blocked` on the ledger,
|
|
113
|
+
with `stage.started` still at zero (the row records a refusal, not a stage); removing the
|
|
114
|
+
append reddens exactly that test.
|
|
115
|
+
- **A Plan that priced its stories in a shape nothing reads is refused at the Plan gate, and the
|
|
116
|
+
Plan agent is now told the shape (#264).** Measured on a field run: `03-plan/budget.yml` carried
|
|
117
|
+
`ceiling_usd: 108`, a `stories:` list with an `estimate_usd` and a `why` per story, and
|
|
118
|
+
`total_estimate_usd: 111` — priced, sourced, and unreadable. The only reader, `loadPlanPrices`,
|
|
119
|
+
accepts `validateBudget`'s shape (`run`, `ceiling_usd`, `spent_usd`, `per_phase_usd`) and prices
|
|
120
|
+
only the `per_phase_usd` keys that are story ids, so the file failed validation before the map was
|
|
121
|
+
ever looked for and every story got the uniform cap: $5.40 for the story the plan had priced at
|
|
122
|
+
$28, about a quarter of what the reader's own arithmetic would have handed it. Nothing could have
|
|
123
|
+
gone otherwise — the Plan prompt named `budget.yml` as a filename and never its shape,
|
|
124
|
+
`validatePlan` had zero references to the file, and the one string that named the problem
|
|
125
|
+
(`priceIssue`) was computed at Build time and read by nothing. Two halves. The `## Output
|
|
126
|
+
schemas` section the Plan agent reads carries a fourth artefact, GENERATED like the other three:
|
|
127
|
+
its rule table from `BUDGET_REQUIRED_KEYS`, the list `validateBudget` itself enforces, and its
|
|
128
|
+
example run through that validator by the test that pins the contract — and `tldrx plan schema
|
|
129
|
+
--budget` prints it for a person. And the `plan` check runs `validateBudget` over
|
|
130
|
+
`03-plan/budget.yml` at the gate when the file exists, naming the file and every missing key
|
|
131
|
+
(`budget.yml spent_usd: missing required key \`spent_usd\``), so the shape is refused where it
|
|
132
|
+
costs nothing instead of pricing nothing one stage later. The file stays optional; only its shape
|
|
133
|
+
is not. The Build loader is untouched on purpose: it refuses to load on any `validatePlan` issue,
|
|
134
|
+
and the spec's rule for an invalid budget at Build time — an advisory and the uniform split, never
|
|
135
|
+
a refused build — is what a file edited after its gate still gets, which is why the budget check
|
|
136
|
+
is the gate's own function and not a fourth pass inside `validatePlan` (a guard test pins that
|
|
137
|
+
`validatePlan` does not learn it).
|
|
138
|
+
|
|
3
139
|
## 0.18.0 — 2026-09-13
|
|
4
140
|
|
|
5
141
|
### Added
|
package/README.md
CHANGED
|
@@ -316,6 +316,7 @@ back on the registry is 0.3.0.
|
|
|
316
316
|
|
|
317
317
|
| Version | Date | Status | Contains |
|
|
318
318
|
|---|---|---|---|
|
|
319
|
+
| 0.18.1 | 2026-09-13 | `beta` | five things a run could not survive on its own, every one measured on the unattended proof runs that followed 0.18.0 rather than reasoned about: a priced plan was never priced — the planner wrote `stories[].estimate_usd` and Build read only `per_phase_usd` + `spent_usd`, so every story got the same $5.40 cap while the plan said $28, and the contract now names the shape and the plan check refuses a budget.yml that does not carry it; a headless refusal under a host-tokens ceiling wrote nothing to the ledger, so `--until-done` relaunched over a refusal it could not see, and now writes its `budget.blocked` first; a developer that had committed its work and was then refused a compound verification command was blocked BEFORE the facilitator ran the Definition of Done, so the DoD now decides when there is work and the refusal is recorded either way, with the prompt saying to run each DoD command verbatim and alone because shell separators split a line into subcommands that each need their own grant; a workspace can declare `<slot>_scoped` DoD commands that run per story on the paths it touched with the full suite once per epic head, after the full suite was measured running three times per story attempt; and a cancelled run no longer leaves an epic branch that refuses the ordinary retry — `run cancel` releases it and Build renames aside an epic whose owner run is closed, while an open or unknown owner still refuses verbatim and an uncounted branch is never deleted |
|
|
319
320
|
| 0.18.0 | 2026-09-13 | `beta` | the release that lets an unattended run survive its own accidents and end somewhere, every item measured on live runs at 0.16.1 rather than reasoned about: a turn that wrote a lot of files could not be RECORDED — an `agent.result` whose `outputs` field held 3924 bytes of run-relative paths blew the 4096-byte event cap through a RAW append that never reached the capped seam, the throw landed one line after the try/catch built for exactly this and came out as exit 1, which `--retry-failed` does not retry since its branch keys on exit 5, so a loop told to survive two failures survived zero, and the epic branch had been cut on disk one line before the throw with the save after it, leaving the relaunch to read its own epic as a stranger's; `outputs` is now named by COUNT with the full list written beside the event rather than truncated, all four raw appends route through the capped seam at module scope, a throw while recording fails the STAGE with exit 5 saying how many of how many rows landed, the claim is saved the moment it is earned, and `capPayload` still knows its two fields BY NAME — a third growable field has to be taught to it deliberately or it is refused whole; Build entry now proves the Definition of Done can run IN A WORKTREE before a developer is paid, after 18 h from `run auto` to the first story that could run on one workspace, five relaunches, because the repo's `install:` named a file that existed in the human's checkout and was never committed and `git worktree` carries TRACKED FILES ONLY, so every story failed identically after the story was opened and immediately before the paid turn while the base pre-flight was green and right to be, measuring in the checkout where the file is — one throwaway worktree at the base sha, the install run inside it, each DoD command's first token resolved there, NEVER the suite, refusal exit 2 naming the exact path or binary, cached under an additive `worktree:` key narrowed by base sha, declaration hash and AGE (six hours for a green, because the row is a claim about an ENVIRONMENT that no sha contains; the base red's 30 minutes for a red), and costing zero where it could learn nothing; where the two readings could not be told apart it deliberately refuses LESS — a path named by a `dod` command after the first may be an artefact an earlier one builds, so that is an advisory and not a verdict; one blocked story no longer stops every later wave, after a loop told to build 8 stories built 5, never started 3, and reported the stage `done`, twice on the same run, because the parallel path asked only "did any story block" and `depends_on` appeared ZERO times in the Build executor — a story now runs when every one of its `depends_on` is `done`, one held back is `blocked` WITH the reason `dependency S7 blocked` (transitive for free) which is what the gate's `blocked_reason` and the continue note read, the stage line counts every story `waves.yml` scheduled rather than the rows the executor happened to hold, and a scheduled story with no outcome at all is named in `## Unknowns` instead of dropped, while `--parallel 1` is untouched on purpose — a story whose dependency blocked is still attempted there, and changing that would be a second change in the opposite direction; the spawned developer can now delete and rename paths in its own tree, after a story that was simply "delete an unused file" proved undoable — nothing in the allowance could unlink a path or take it out of the index, so every `git rm` came back needing approval nobody was there to give, the refused turn exited 0 with an envelope, the DoD ran green on an untouched tree and the reviewer faulted a diff that was never written, twice — the file-lifecycle git verbs are granted as index operations on the story's OWN tree (never a bare `rm`, `git push` still asserted absent), and a tool call refused for approval now BLOCKS the story at once with the command and why, reaching the story file, the handoff and the gate, so the next missing verb costs one attempt instead of two and nothing; that detector reads a structural field first and the English refusal sentence only as a fenced fallback, because pre-merge review measured the unfenced version reading a plain file-read of a document CONTAINING the phrase as a refusal — this very CHANGELOG is one of them — and a refusal carrying neither signal is a miss taken on purpose, with the sentence half depending on prose the host can change without notice; a fix-list finding now declares what it IS, after three stories with a green definition of done, a merged commit and an APPROVING reviewer settled `blocked`, each on a `fix-now` finding whose whole content was a stale docstring or a citation that did not resolve — the reviewer was not wrong and `severity` was never the vehicle (a free string NO consumer read, one render site under `src`), so a required `kind` splits behaviour from text: `correctness` and `security` hold the story exactly as before, a `docs`/`style` finding submitted `fix-now` is routed to `defer-with-log` with a `Normalised-from:` line, and because this makes a gate WEAKER on purpose the unblocking is priced — declaring a finding `docs` costs the SAME `[src: …]` citation that refuting one already costs, a `kind` that is absent or outside the enum is refused for free and indexed as a fault so it costs the story no attempt, a fix list already on disk with no `Kind:` line reads as not stated and not stated blocks, and the four reviewer-prompt goldens MOVED because the schema alone would have left a reviewer nobody told about the field; `run.yml` can finally explain its own `cost_usd` — a row read `input_tokens: 84, output_tokens: 37150, cost_usd: 1.98` for a 124 KB prompt while the provider reported 4,911,750 cache reads for that same turn, and across 101 transcripts in one workspace cache reads were 97.3% of the input side against a column that saw 0.002% of it — the counters were parsed on every turn and thrown away one function later, and the half the issue did not know about is that the executor path's `agent.result` emitted NO `usage` at all, so every Build and Watch turn lost all four counters and the whole Build was priced off zeroes (measured: zero `usage` lines across the four frozen goldens with ten `agent.result` among them); they are recorded additively and gated one at a time rather than both-or-nothing, `turnTokens` names them while deliberately reading neither so the exclusion is a decision on the record, nothing displayed changed — the dashboard and `tldrx cost` are byte-identical — and a row from before this reads absent, since nothing reconstructs a counter nobody wrote down; a gate about to sign itself is no longer sent as a Yes/No the owner cannot answer, after 3 of 3 questioned stages on two live workspaces sent the prompt 600 ms after the last answer and self-signed on the very next poll, so the one tap it invited ran `approve` on an already-approved gate and, because the in-wait approval fell between two notify windows, the bridge kept the prompt open and re-mentioned its owner every escalate tick for two hours — the release now asks the gate's CONDITIONS through the same re-evaluation the poll uses, extracted so neither can form a second opinion, sends nothing when there is no decision to take and says which stage and why on stdout instead of going quiet, and words a genuinely held gate from that one reading so a payload can no longer name four open questions beside `holding: "none"`; and three things a run could not do before: it can end in a pull request toward main on purpose, frozen at `run new --ship` with `push`, `pr` or `merge` into an additive `run.yml` block and not a sixth phase, pushed through the ONE push wrapper in the codebase with a PR that reports NO check left open and recorded as `merge: absent — no checks to wait on` because auto-merge over nothing to wait on is a merge now rather than a merge when green — pre-merge review caught the first version erasing a recorded merge failure with an empty string and exit 0, and the emitter dropped the whole block until it was taught the key; `questions_policy: recommended` answers a blocking question that carries its own `Recommended:` line and escalates the ones that do not, after 10 owner questions across two headless runs ($89.82 over 28 h and $26.38 over 5.6 h, the agents busy 4.9 h and 56 min of it) parked the loop on 9 that already held the pick in a parsed field, writing `decided_by: agent-default` as a THIRD value beside owner and driver because `owner` there would be an audit record lying in the dangerous direction, and never inventing a pick — no line, a letter naming no option, or a block marked irreversible or money escalates exactly as before; and `run auto --until-done [<n>]` relaunches the loop in-process after an exit it can do nothing else with, at most `n` times (default 5), after five hand relaunches and 18 h before a story ran — never over a person's exit 4, never over a budget block since nothing in-process moves a ceiling, never twice over the same last line, with the run id and the spend baseline pinned across relaunches so `--max-usd` still describes the one command a person typed; the epic-claim-at-cut half of that issue lives in the Build executor and is NOT fixed here |
|
|
320
321
|
| 0.17.0 | 2026-09-12 | `beta` | three things the framework knew and recorded wrongly, each found by reading its own records rather than by using it: a gate notification now carries the condition HOLDING the gate as data instead of leaving it to be guessed from the text of the command it suggests — `holding` says questions, stories or nothing-mechanical, and when a story is blocked with a recorded reason the payload also hands over a ready-made rejection that lets the loop carry on, with the note DERIVED from the blocked story rather than canned, because a rejection's note is fed to the next turn's prompt and a generic one would hand that turn an empty instruction, so when no reason can be derived the offer is absent rather than invented; the emitted command keeps a literal placeholder and never interpolates the reason, which keeps the quoting hazard out of the record and puts it where a substituting client can see it; `run status` stopped printing more money left than it had ceiling — the run ceiling now has ONE live copy, read from `budget.yml` by every live screen, and `run.yml`'s mirror is documented as the creation value and no longer written by a raise, since the break was never that a raise failed to write the mirror but that an ordinary concurrent save carried a stale copy over it, and the fix was to delete the half-sync rather than to build a better one — pre-merge review caught the first version pairing a LIVE spend with that now-frozen ceiling inside `tldrx replay`, which would have reproduced the same impossible line in a different command on every run whose budget had been raised, measured on the real CLI as `$12.00 spent of $10.00 ceiling` with no concurrency required; and a task row now records the ROLE its turn actually ran under, where every Build turn had been filed as the developer including the reviewer's — the role was known at spawn and written to the event stream, and was dropped on the way to the ledger, so the audit record named the wrong actor for work it had itself measured; the new key is additive and written on EVERY row including the developer's, because a role present only on reviewers would make its absence mean developer-or-not-recorded and send a reader back to inferring the role from an absence, which is the guess the change exists to remove |
|
|
321
322
|
| 0.16.1 | 2026-09-12 | `beta` | five things the framework knew and did not say, or said wrong — four of them found by using it rather than by reading it: a red base pre-flight now KEEPS its output, so a refusal that blocks every story in a Build names the failing test and cites the file, where it used to record only the last line of stdout — measured 2026-09-10, a stage refused with `tail: "Test run completed with non-success exit code: 2"` while the cause, `DockerUnavailableException`, sat on line 12 of 163,702 lines the run had already captured and thrown away, so diagnosing a refusal the framework had itself measured meant re-running the workspace's test command by hand; it now routes through the same seam #211 built for a story's DoD, which had been naming its failing test correctly all along on the same command, the same day, in the same repo — the path with the SMALLER blast radius was the legible one; a gate notification now offers the command that CLEARS it rather than always `tldrx approve` — questions open give `tldrx answer <id>`, unfinished stories give `tldrx run status`, and `approve` is offered only when nothing mechanical is outstanding, after an owner approved a Build gate by mistake twice in one evening over unbuilt stories, each time from a phone, each time needing a revoke, while a ten-minute heartbeat repeated `Run: tldrx approve` seven times under a sentence that correctly named the five open questions holding it; `tldrx reject --and-continue` lets a rejection mean "redo it this way and carry on" instead of ending the run — the loop resumed after an approve and stopped after a reject, so the button meaning "there is still work to do" was the one that stopped the work and only a terminal could revive it; five real rejections that night all meant continue, five cost a manual relaunch, and a bare `tldrx reject` still writes a byte-identical `run.yml` and stops exactly as before; the expert-recompute fixture anchors its evidence dates to a `now` it can move, so `bun test` stops going red by the calendar — pristine `main` was red at the exact sha of the published 0.16.0 with no commit in between, and a clock moved one year forward reddened FIVE cases, not the one that had already fired; and `test/merge-wave.test.ts`'s concurrency failure, documented as a known flake since #115 and carrying a written licence to re-run it, was never one: `merge-guard.sh` rewrote `.git/hooks/reference-transaction` IN PLACE while a sibling wave's `git merge` was exec'ing it — ETXTBSY on Linux at 31% under contention, benign on macOS, which is why it was green locally and red in CI, and why a same-sha re-run failed 2 for 2 rather than passing; the hook is now written to a temp file and RENAMED into place, the refusal that used to borrow `2`/`merge conflict` for a hook abort now says what it was and exits 11, and AGENTS.md §4 withdraws the re-run licence for those two cases while naming the interrupted-merge case (#237) as still open and undiagnosed — because "all real" for a whole file costs the same as "all flake", in the other direction |
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-1tynpxvm.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-kwaqdq9w.js";
|
|
9
9
|
import {
|
|
10
10
|
parseHookInput,
|
|
11
11
|
readStdin
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-pday823f.js";
|
|
13
13
|
import {
|
|
14
14
|
EventLog,
|
|
15
15
|
PHASE_ID_RE
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-x64paqbg.js";
|
|
17
17
|
import {
|
|
18
18
|
PHASE_IDS
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import"./chunk-
|
|
21
|
-
import"./chunk-
|
|
19
|
+
} from "./chunk-bgxj40rr.js";
|
|
20
|
+
import"./chunk-zg7q0q4y.js";
|
|
21
|
+
import"./chunk-vf992pfw.js";
|
|
22
22
|
import {
|
|
23
23
|
ADVISORY_KEY,
|
|
24
24
|
MAX_FACT_CHARS,
|
|
@@ -28,15 +28,17 @@ import {
|
|
|
28
28
|
renderQuestionBlock,
|
|
29
29
|
replaceBlock,
|
|
30
30
|
serializeQuestions
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-8ak5kwd5.js";
|
|
32
32
|
import {
|
|
33
33
|
ITERATION_ONLY_SLOT,
|
|
34
34
|
PROJECT_FRAMEWORK_DIR,
|
|
35
35
|
PROJECT_WORK_DIR,
|
|
36
36
|
commandProbeIssues,
|
|
37
37
|
factsPath,
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
isScopedTemplate,
|
|
39
|
+
parseYaml,
|
|
40
|
+
scopedSlotOf
|
|
41
|
+
} from "./chunk-c1h38et6.js";
|
|
40
42
|
|
|
41
43
|
// src/hooks/answer-capture.ts
|
|
42
44
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -151,6 +153,8 @@ function loadWorkspace(root) {
|
|
|
151
153
|
const commandRoles = new Map;
|
|
152
154
|
const commandProbes = new Map;
|
|
153
155
|
const iterationCommands = new Set;
|
|
156
|
+
const scopedCommands = new Map;
|
|
157
|
+
const scopedTemplates = new Set;
|
|
154
158
|
const defaultBranches = new Map;
|
|
155
159
|
let seedTriageThresholdTokens = null;
|
|
156
160
|
const empty = () => ({
|
|
@@ -161,6 +165,8 @@ function loadWorkspace(root) {
|
|
|
161
165
|
commandRoles,
|
|
162
166
|
commandProbes,
|
|
163
167
|
iterationCommands,
|
|
168
|
+
scopedCommands,
|
|
169
|
+
scopedTemplates,
|
|
164
170
|
defaultBranches,
|
|
165
171
|
seedTriageThresholdTokens
|
|
166
172
|
});
|
|
@@ -192,13 +198,24 @@ function loadWorkspace(root) {
|
|
|
192
198
|
defaultBranches.set(entry.name, typeof entry.default_branch === "string" && entry.default_branch !== "" ? entry.default_branch : FALLBACK_DEFAULT_BRANCH);
|
|
193
199
|
const own = [];
|
|
194
200
|
const roles = declared.get(entry.name) ?? new Map;
|
|
195
|
-
|
|
201
|
+
const scoped = new Map;
|
|
202
|
+
for (const [role, value] of roles) {
|
|
203
|
+
const base = scopedSlotOf(role);
|
|
204
|
+
if (base !== null) {
|
|
205
|
+
const full = roles.get(base);
|
|
206
|
+
if (full !== undefined && scopedSlotOf(base) === null && isScopedTemplate(value)) {
|
|
207
|
+
scoped.set(full, value);
|
|
208
|
+
scopedTemplates.add(value);
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
196
212
|
commands.add(value);
|
|
197
213
|
if (!own.includes(value))
|
|
198
214
|
own.push(value);
|
|
199
215
|
}
|
|
200
216
|
repoCommands.set(entry.name, own);
|
|
201
217
|
commandRoles.set(entry.name, roles);
|
|
218
|
+
scopedCommands.set(entry.name, scoped);
|
|
202
219
|
const fast = roles.get(ITERATION_ONLY_SLOT);
|
|
203
220
|
if (fast !== undefined)
|
|
204
221
|
iterationCommands.add(fast);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2caxb41h.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-2ym87z6d.js";
|
|
12
|
+
import"./chunk-pday823f.js";
|
|
13
13
|
import {
|
|
14
14
|
asRunBudget,
|
|
15
15
|
currentActor,
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
validateRunBudget,
|
|
23
23
|
wouldExceed,
|
|
24
24
|
wouldExceedHostTokens
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-ct7kazfy.js";
|
|
26
26
|
import {
|
|
27
27
|
EventLog
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-x64paqbg.js";
|
|
29
29
|
import {
|
|
30
30
|
cursorStage,
|
|
31
31
|
hostTokensIn,
|
|
@@ -34,18 +34,18 @@ import {
|
|
|
34
34
|
newestActiveRun,
|
|
35
35
|
renderRunEconomies,
|
|
36
36
|
runSpend
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import"./chunk-
|
|
37
|
+
} from "./chunk-hwsng0xq.js";
|
|
38
|
+
import"./chunk-zg7q0q4y.js";
|
|
39
39
|
import {
|
|
40
40
|
noteDeprecations
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-8ak5kwd5.js";
|
|
42
42
|
import {
|
|
43
43
|
PROJECT_WORK_DIR,
|
|
44
44
|
findWorkspaceRoot,
|
|
45
45
|
locateWork,
|
|
46
46
|
parseYaml,
|
|
47
47
|
stageYamlPath
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-c1h38et6.js";
|
|
49
49
|
|
|
50
50
|
// src/hooks/budget-gate.ts
|
|
51
51
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findDuplicate
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-kwaqdq9w.js";
|
|
4
4
|
import {
|
|
5
5
|
evidencePath,
|
|
6
6
|
gateEvidencePath,
|
|
7
7
|
parseEvidence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-x64paqbg.js";
|
|
9
9
|
import {
|
|
10
10
|
openBlocks,
|
|
11
11
|
parseQuestions
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-8ak5kwd5.js";
|
|
13
13
|
|
|
14
14
|
// src/core/distill/distill.ts
|
|
15
15
|
var CONFLICT_THRESHOLD = 0.6;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
EMPTY_SECTION_RULE,
|
|
4
4
|
noneBullet,
|
|
5
5
|
srcRule
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-c1h38et6.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/lib/messages.ts
|
|
9
9
|
var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
STAGE_TUNING_DEFAULTS,
|
|
4
4
|
parseWorkflowGates,
|
|
5
5
|
readStageTuning
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-zg7q0q4y.js";
|
|
7
7
|
import {
|
|
8
8
|
PROJECT_FRAMEWORK_DIR,
|
|
9
9
|
STAGES_DIR,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isRecord,
|
|
13
13
|
loadWorkspace,
|
|
14
14
|
parseYaml
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-c1h38et6.js";
|
|
16
16
|
|
|
17
17
|
// src/core/run/workflowPreset.ts
|
|
18
18
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -7654,6 +7654,18 @@ function allowlistIssue(command, allowed, subject) {
|
|
|
7654
7654
|
return allowed.has(command) ? null : notDeclaredMessage(command, subject);
|
|
7655
7655
|
}
|
|
7656
7656
|
var ITERATION_ONLY_SLOT = "test_fast";
|
|
7657
|
+
var SCOPED_SUFFIX = "_scoped";
|
|
7658
|
+
var PATHS_PLACEHOLDER = "{{paths}}";
|
|
7659
|
+
function scopedSlotOf(slot) {
|
|
7660
|
+
if (!slot.endsWith(SCOPED_SUFFIX))
|
|
7661
|
+
return null;
|
|
7662
|
+
const base = slot.slice(0, -SCOPED_SUFFIX.length);
|
|
7663
|
+
return base === "" ? null : base;
|
|
7664
|
+
}
|
|
7665
|
+
function isScopedTemplate(value) {
|
|
7666
|
+
const words = value.split(/\s+/).filter((w) => w !== "");
|
|
7667
|
+
return words.filter((w) => w === PATHS_PLACEHOLDER).length === 1 && !words.some((w) => w !== PATHS_PLACEHOLDER && w.includes(PATHS_PLACEHOLDER));
|
|
7668
|
+
}
|
|
7657
7669
|
// src/core/schemas/story.ts
|
|
7658
7670
|
var DOD_FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
7659
7671
|
var DOD_FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
@@ -8779,6 +8791,8 @@ function loadWorkspace(root) {
|
|
|
8779
8791
|
const commandRoles = new Map;
|
|
8780
8792
|
const commandProbes = new Map;
|
|
8781
8793
|
const iterationCommands = new Set;
|
|
8794
|
+
const scopedCommands = new Map;
|
|
8795
|
+
const scopedTemplates = new Set;
|
|
8782
8796
|
const defaultBranches = new Map;
|
|
8783
8797
|
let seedTriageThresholdTokens = null;
|
|
8784
8798
|
const empty = () => ({
|
|
@@ -8789,6 +8803,8 @@ function loadWorkspace(root) {
|
|
|
8789
8803
|
commandRoles,
|
|
8790
8804
|
commandProbes,
|
|
8791
8805
|
iterationCommands,
|
|
8806
|
+
scopedCommands,
|
|
8807
|
+
scopedTemplates,
|
|
8792
8808
|
defaultBranches,
|
|
8793
8809
|
seedTriageThresholdTokens
|
|
8794
8810
|
});
|
|
@@ -8820,13 +8836,24 @@ function loadWorkspace(root) {
|
|
|
8820
8836
|
defaultBranches.set(entry.name, typeof entry.default_branch === "string" && entry.default_branch !== "" ? entry.default_branch : FALLBACK_DEFAULT_BRANCH);
|
|
8821
8837
|
const own = [];
|
|
8822
8838
|
const roles = declared.get(entry.name) ?? new Map;
|
|
8823
|
-
|
|
8839
|
+
const scoped = new Map;
|
|
8840
|
+
for (const [role, value] of roles) {
|
|
8841
|
+
const base = scopedSlotOf(role);
|
|
8842
|
+
if (base !== null) {
|
|
8843
|
+
const full = roles.get(base);
|
|
8844
|
+
if (full !== undefined && scopedSlotOf(base) === null && isScopedTemplate(value)) {
|
|
8845
|
+
scoped.set(full, value);
|
|
8846
|
+
scopedTemplates.add(value);
|
|
8847
|
+
}
|
|
8848
|
+
continue;
|
|
8849
|
+
}
|
|
8824
8850
|
commands.add(value);
|
|
8825
8851
|
if (!own.includes(value))
|
|
8826
8852
|
own.push(value);
|
|
8827
8853
|
}
|
|
8828
8854
|
repoCommands.set(entry.name, own);
|
|
8829
8855
|
commandRoles.set(entry.name, roles);
|
|
8856
|
+
scopedCommands.set(entry.name, scoped);
|
|
8830
8857
|
const fast = roles.get(ITERATION_ONLY_SLOT);
|
|
8831
8858
|
if (fast !== undefined)
|
|
8832
8859
|
iterationCommands.add(fast);
|
|
@@ -9153,4 +9180,4 @@ function noneBullet(lookedAt) {
|
|
|
9153
9180
|
return `- none [src: absent:${lookedAt}]`;
|
|
9154
9181
|
}
|
|
9155
9182
|
|
|
9156
|
-
export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, WORKFLOWS_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, PLAN_STATUSES, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, allowlistIssue, ITERATION_ONLY_SLOT, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
9183
|
+
export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, WORKFLOWS_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, PLAN_STATUSES, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, allowlistIssue, ITERATION_ONLY_SLOT, scopedSlotOf, isScopedTemplate, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
STAGE_TUNING_DEFAULTS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-zg7q0q4y.js";
|
|
4
4
|
import {
|
|
5
5
|
MAX_PLAN_STORIES,
|
|
6
6
|
MAX_STORIES_PER_WAVE,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
requireVersion,
|
|
26
26
|
requireVersion1,
|
|
27
27
|
result
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-c1h38et6.js";
|
|
29
29
|
|
|
30
30
|
// src/hooks/lib/actor.ts
|
|
31
31
|
function currentActor() {
|
|
@@ -764,6 +764,7 @@ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:f
|
|
|
764
764
|
import { join as join4 } from "node:path";
|
|
765
765
|
|
|
766
766
|
// src/core/schemas/budget.ts
|
|
767
|
+
var BUDGET_REQUIRED_KEYS = ["run", "ceiling_usd", "spent_usd", "per_phase_usd"];
|
|
767
768
|
function validateBudget(input) {
|
|
768
769
|
const issues = [];
|
|
769
770
|
const deprecations = [];
|
|
@@ -771,7 +772,7 @@ function validateBudget(input) {
|
|
|
771
772
|
if (!doc)
|
|
772
773
|
return result(issues);
|
|
773
774
|
requireVersion(doc, issues, deprecations);
|
|
774
|
-
requireKeys(doc,
|
|
775
|
+
requireKeys(doc, BUDGET_REQUIRED_KEYS, "", issues);
|
|
775
776
|
requireString(doc.run, "run", issues);
|
|
776
777
|
requireNumber(doc.ceiling_usd, "ceiling_usd", issues);
|
|
777
778
|
requireNumber(doc.spent_usd, "spent_usd", issues);
|
|
@@ -786,10 +787,12 @@ function validateBudget(input) {
|
|
|
786
787
|
return result(issues, deprecations);
|
|
787
788
|
}
|
|
788
789
|
|
|
790
|
+
// src/core/plan/validatePlan.ts
|
|
791
|
+
var PLAN_BUDGET_FILE = "budget.yml";
|
|
792
|
+
|
|
789
793
|
// src/core/build/plan.ts
|
|
790
794
|
var BUILD_PHASE = "04-build";
|
|
791
795
|
var PLAN_PHASE = "03-plan";
|
|
792
|
-
var PLAN_BUDGET_FILE = "budget.yml";
|
|
793
796
|
function loadPlanPrices(planDir, stories) {
|
|
794
797
|
const empty = new Map;
|
|
795
798
|
const path = join4(planDir, PLAN_BUDGET_FILE);
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
withWorkspaceLock,
|
|
4
4
|
workspaceRootOfFactsPath,
|
|
5
5
|
writeAtomic
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-vf992pfw.js";
|
|
7
7
|
import {
|
|
8
8
|
FACT_CONFIDENCES,
|
|
9
9
|
FACT_DECIDERS,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isLive,
|
|
16
16
|
isRetired,
|
|
17
17
|
noteDeprecations
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-8ak5kwd5.js";
|
|
19
19
|
import {
|
|
20
20
|
SRC_PATTERNS,
|
|
21
21
|
asDocument,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
requireString,
|
|
29
29
|
requireVersion,
|
|
30
30
|
result
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-c1h38et6.js";
|
|
32
32
|
|
|
33
33
|
// src/core/facts/FactsStore.ts
|
|
34
34
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
spentBasis,
|
|
9
9
|
tallyOf,
|
|
10
10
|
validateRunBudget
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ct7kazfy.js";
|
|
12
12
|
import {
|
|
13
13
|
EventLog,
|
|
14
14
|
OUTCOME_NOT_RECORDED,
|
|
@@ -21,12 +21,12 @@ import {
|
|
|
21
21
|
isTerminal,
|
|
22
22
|
stageAt,
|
|
23
23
|
validateRunFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-x64paqbg.js";
|
|
25
25
|
import {
|
|
26
26
|
cursorStage,
|
|
27
27
|
isAttendedByHostView,
|
|
28
28
|
openRunViews
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-hwsng0xq.js";
|
|
30
30
|
import {
|
|
31
31
|
backupPathFor,
|
|
32
32
|
isAlive,
|
|
@@ -35,20 +35,20 @@ import {
|
|
|
35
35
|
workspaceRootOfRunDir,
|
|
36
36
|
writeAtomic,
|
|
37
37
|
yamlScalar
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-vf992pfw.js";
|
|
39
39
|
import {
|
|
40
40
|
isAdvisory,
|
|
41
41
|
noteDeprecations,
|
|
42
42
|
openBlocks,
|
|
43
43
|
parseQuestions
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-8ak5kwd5.js";
|
|
45
45
|
import {
|
|
46
46
|
FRAMEWORK_ROOT,
|
|
47
47
|
PLAN_STATUSES,
|
|
48
48
|
listRunDirs,
|
|
49
49
|
parseYaml,
|
|
50
50
|
parseYamlRepairing
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-c1h38et6.js";
|
|
52
52
|
|
|
53
53
|
// src/core/statusline/runSnapshot.ts
|
|
54
54
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
@@ -78,6 +78,20 @@ function frameworkVersionSync() {
|
|
|
78
78
|
function inlineList(values) {
|
|
79
79
|
return `[${values.map((v) => yamlScalar(v)).join(", ")}]`;
|
|
80
80
|
}
|
|
81
|
+
function epicRelease(row) {
|
|
82
|
+
const parts = [
|
|
83
|
+
`branch: ${yamlScalar(row.branch)}`,
|
|
84
|
+
`repo: ${yamlScalar(row.repo)}`,
|
|
85
|
+
`outcome: ${yamlScalar(row.outcome)}`,
|
|
86
|
+
...row.renamed_to === undefined ? [] : [`renamed_to: ${yamlScalar(row.renamed_to)}`],
|
|
87
|
+
`commits: ${String(row.commits)}`,
|
|
88
|
+
`base: ${yamlScalar(row.base)}`,
|
|
89
|
+
`via: ${yamlScalar(row.via)}`,
|
|
90
|
+
`at: ${yamlScalar(row.at)}`,
|
|
91
|
+
`reason: ${yamlScalar(row.reason)}`
|
|
92
|
+
];
|
|
93
|
+
return `{${parts.join(", ")}}`;
|
|
94
|
+
}
|
|
81
95
|
function money(n) {
|
|
82
96
|
return n.toFixed(2);
|
|
83
97
|
}
|
|
@@ -174,7 +188,8 @@ function emitRunYaml(run) {
|
|
|
174
188
|
}
|
|
175
189
|
if (run.build !== undefined && run.build.epic_branch.length > 0) {
|
|
176
190
|
const model = run.build.branch_model === undefined ? "" : `, branch_model: ${yamlScalar(run.build.branch_model)}`;
|
|
177
|
-
|
|
191
|
+
const released = run.build.epic_released === undefined || run.build.epic_released.length === 0 ? "" : `, epic_released: [${run.build.epic_released.map(epicRelease).join(", ")}]`;
|
|
192
|
+
lines.push(`build: {epic_branch: ${inlineList(run.build.epic_branch)}${model}${released}}`);
|
|
178
193
|
}
|
|
179
194
|
if (run.cancelled !== undefined) {
|
|
180
195
|
lines.push(`cancelled: {by: ${yamlScalar(run.cancelled.by)}, at: ${yamlScalar(run.cancelled.at)}, ` + `note: ${yamlScalar(run.cancelled.note)}}`);
|