tldr-experts 0.8.0 → 0.9.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 +213 -0
- package/README.md +4 -2
- package/dist/hooks/answer-capture.js +4 -4
- package/dist/hooks/budget-gate.js +3 -3
- package/dist/hooks/{chunk-pf9d8ag8.js → chunk-4w03q2kj.js} +9 -5
- package/dist/hooks/{chunk-6405vkzk.js → chunk-8awygmqw.js} +2 -1
- package/dist/hooks/{chunk-ecfqpeyj.js → chunk-f6484ykg.js} +9 -2
- package/dist/hooks/{chunk-b9rjx4qc.js → chunk-fv66tdsm.js} +14 -0
- package/dist/hooks/{chunk-md1c7z7q.js → chunk-hg43zexg.js} +34 -17
- package/dist/hooks/no-reask.js +3 -3
- package/dist/hooks/session-start.js +16 -8
- package/dist/hooks/statusline.js +5 -5
- package/dist/tldrx.js +2974 -1578
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/templates/experts/stack/dotnet.md +95 -0
- package/templates/experts/stack/javascript.md +86 -0
- package/templates/experts/stack/overlays/aspnet-controllers.md +47 -0
- package/templates/experts/stack/overlays/aspnet-minimal-apis.md +51 -0
- package/templates/experts/stack/overlays/efcore-npgsql.md +57 -0
- package/templates/experts/stack/overlays/expo-router.md +50 -0
- package/templates/experts/stack/overlays/fastapi.md +55 -0
- package/templates/experts/stack/overlays/mediatr-cqrs.md +47 -0
- package/templates/experts/stack/overlays/next-app-router.md +61 -0
- package/templates/experts/stack/overlays/node-express.md +55 -0
- package/templates/experts/stack/overlays/postgres-testcontainers.md +55 -0
- package/templates/experts/stack/overlays/prisma.md +52 -0
- package/templates/experts/stack/overlays/react.md +62 -0
- package/templates/experts/stack/overlays/sqlalchemy-alembic.md +51 -0
- package/templates/experts/stack/overlays/vite-react-spa.md +49 -0
- package/templates/experts/stack/python.md +90 -0
- package/templates/experts/stack/typescript.md +89 -0
- /package/dist/hooks/{chunk-37fwvg3x.js → chunk-vyvjbbmb.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,218 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## 0.9.1 — 2026-09-06
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- **The docs site carries the whole CLI, generated from the registry.** The site's
|
|
9
|
+
`reference/cli.md` was a curated map that named about half the flags and pointed at
|
|
10
|
+
`docs/guide/08-cli-reference.md` for the rest — a file the site never builds, because
|
|
11
|
+
`docs/` is not in its `srcDir`. So the "exhaustive version" was not on the website at all,
|
|
12
|
+
and the owner went looking for `--yolo`, the flag that drops per-tool permission prompts,
|
|
13
|
+
and found it only in the changelog. `docs-site/scripts/gen-cli.ts` now writes
|
|
14
|
+
`/reference/cli-flags` and `/es/reference/cli-flags` at build time out of
|
|
15
|
+
`src/cli/helpText.ts` — all 33 commands, every flag with its meaning, every allowed value
|
|
16
|
+
spelled out from where it is enforced, every exit code, the environment variables, and the
|
|
17
|
+
rules that hold everywhere. Generated, gitignored and never hand-edited, for the same
|
|
18
|
+
reason the changelog page is: a second copy of 180 flags drifts on its first edit. The
|
|
19
|
+
Spanish page is framed in Spanish and keeps the meanings in English, like the release notes
|
|
20
|
+
and the sample CLI strings.
|
|
21
|
+
- **`test/docs-cli-coverage.test.ts` — the docs are now a gate.** Nothing asserted that a
|
|
22
|
+
declared flag was EXPLAINED anywhere a reader who is not typing `--help` would look. It now
|
|
23
|
+
asserts both surfaces: the repo guide names every flag inside its own command's section,
|
|
24
|
+
and the generated page carries every command, flag, allowed value and exit code, with
|
|
25
|
+
`--yolo` explained on each of the four commands that take it rather than left bare in a
|
|
26
|
+
usage line. It also holds the env-var table against a grep of `src/` in both directions.
|
|
27
|
+
- **`tldrx facts add` — the command the drive mandate has been naming since 0.8.0 (#161).** The
|
|
28
|
+
mandate tells the driver "a fact that must outlive the turn is `tldrx facts add`, which every
|
|
29
|
+
later prompt DOES read", and no such command was dispatched. What drivers did instead was edit
|
|
30
|
+
`.tldrx/memory/facts.yml` by hand, which walks past `FactsStore.append`'s 2000-character cap,
|
|
31
|
+
past its `…` marker and its `truncated: true` flag, and past `save()`'s validation — and a
|
|
32
|
+
fact cut mid-word with no marker is a record that does not know it is incomplete. It writes
|
|
33
|
+
through the store, under the workspace lock, with `--area` and `--decided-by owner|driver`
|
|
34
|
+
both required — a driver's default is never cited as the owner's decision, so the command
|
|
35
|
+
never lets the caller skip saying which of the two it was. The underlying `source.decided_by`
|
|
36
|
+
field stays additive (rows written before it existed, or by another writer, still validate)
|
|
37
|
+
and renders in every `{{facts}}` block as `· decided by owner` / `· decided by driver`,
|
|
38
|
+
appended only when present. Without `--run` it uses the one open run and refuses to guess
|
|
39
|
+
between several, recording the absence with its reason.
|
|
40
|
+
- **The provider's token split on the `run.yml` task row (#159).** `input_tokens` and `output_tokens`
|
|
41
|
+
were parsed on every provider turn and reached the event log only, so `run.yml` — the file
|
|
42
|
+
every cost report and every resumed run reads — carried a dollar figure with no token figure
|
|
43
|
+
beside it, which is a number nobody can check against a price table. Both fields are additive,
|
|
44
|
+
written only when a turn reported them and only together — a half-reported split is exactly
|
|
45
|
+
as unverifiable as an absent one, so it is dropped rather than have the parse's own zero
|
|
46
|
+
default stand in for a real count — and distinct from `tokens`, which keeps its meaning as a
|
|
47
|
+
host declaration. `version: 1` is unchanged and every older row loads.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **Two commands the docs told you to run that do not exist.** `tldrx facts add` was
|
|
52
|
+
instructed three times, in `07-claude-code.md`, `10-unattended-mode.md` and
|
|
53
|
+
`08-cli-reference.md`, as the way to record something that must outlive a turn — measured:
|
|
54
|
+
`tldrx facts` is `unknown command 'facts'`, exit 1. A row in `.tldrx/memory/facts.yml` is
|
|
55
|
+
written by answering an open question, so all three now say `tldrx answer <Qid> "…"`. And
|
|
56
|
+
the environment-variable section told you to check that `TLDRX_CLAUDE_BIN` took with
|
|
57
|
+
`tldrx run --dry-run`; `--dry-run` is `next`'s flag, not `run`'s. The guard that found them
|
|
58
|
+
is now part of the suite: every `tldrx …` line inside a code span or a fenced block, on
|
|
59
|
+
every page of the site and the guide plus the README, is parsed and held against the
|
|
60
|
+
registry — an unknown command or a flag its command does not declare fails the build.
|
|
61
|
+
- **Two flags the guide never documented**, found by that test on its first run:
|
|
62
|
+
`tldrx dashboard --serve` (the default mode, and half of the two-modes refusal that
|
|
63
|
+
`--static` is the other half of) and `tldrx run status --verbose` (the two instants behind
|
|
64
|
+
a gate row). Both were real, both were reachable, and neither appeared in the page that
|
|
65
|
+
claims to be exhaustive. The shared spawn flags — `--model`, `--effort`, `--max-usd`,
|
|
66
|
+
`--ui`, `--prepare`/`--commit`, `--yolo` — now point from `run auto`, `seed triage` and
|
|
67
|
+
`expert train` at the one place they are explained, instead of appearing as bare tokens in
|
|
68
|
+
a usage line.
|
|
69
|
+
- **A turn with no provider USD figure is unmetered, not a metered `$0.00` (#159).** `interpret`
|
|
70
|
+
wrote `cost_usd: 0, metered: true` for a Claude result document that carried no
|
|
71
|
+
`total_cost_usd` — contradicting the `metered` field's own contract three lines above it, and
|
|
72
|
+
letting a stage, and then a whole run, read `$0.00` after real turns had run. `metered` is now
|
|
73
|
+
derived from the presence of the figure, so those turns reach `cost_usd: null` +
|
|
74
|
+
`metered: false` and every report that already knows how to say "LOWER BOUND, not a total"
|
|
75
|
+
says it. No new `spendBasis` word: `absent` already meant this, and the fix is to make more
|
|
76
|
+
turns reach it honestly. Codex keeps its pin — its synthesized result document carries a
|
|
77
|
+
`total_cost_usd: 0` that is a placeholder, and reading it as a measurement would be the same
|
|
78
|
+
lie under a different provider.
|
|
79
|
+
- **A refusal no longer discards a cost (#159).** `tldrx next --commit` returned
|
|
80
|
+
`EXIT_AGENT_FAILED` on a `questions.md` the §2.7 parser cannot read BEFORE it recorded the
|
|
81
|
+
task — so a turn that had already been paid for left no row anywhere, and the run's own ledger
|
|
82
|
+
was short by exactly the amount nobody could see. The row lands first and the refusal exits
|
|
83
|
+
after it. Because that refusal deliberately leaves the stage `running` so the operator can fix
|
|
84
|
+
the file and re-run, the banked row is marked `banked_before_refusal`, and the re-run is
|
|
85
|
+
matched to it by session id, declared cost and outputs — against MARKED rows only, so an
|
|
86
|
+
ordinary attempt a gate sent back is never mistaken for a re-read. Two things it deliberately
|
|
87
|
+
does NOT do. A result with no `session_id` is never fingerprinted at all: a null id identifies
|
|
88
|
+
nothing, so the second turn gets its own row carrying `dedupe: "none — no session id"` and the
|
|
89
|
+
reason is in `run.yml`, not only on the console — recording it twice is a smaller lie than
|
|
90
|
+
dropping a turn that ran. And the marker is single-use: the re-run that matches it stamps the
|
|
91
|
+
row `matched by the re-run committed at <at> — the marker is spent`, because a marker left
|
|
92
|
+
armed matched every later turn of the same shape for the life of the stage, and a real retry
|
|
93
|
+
after a gate reject — same session, same outputs, the same `null` cost — was silently dropped
|
|
94
|
+
as already recorded.
|
|
95
|
+
- **An oversized reviewer verdict no longer takes the whole invocation's ledger with it
|
|
96
|
+
(#160).** The 4096-byte payload cap was enforced by `EventLog.append` throwing, nothing
|
|
97
|
+
wrapped the executor call, and the reviewer's verdict prose is the field that overflows — so
|
|
98
|
+
one wordy review escaped past `recordExecutorTasks` and `store.save()`, leaving the epic merge
|
|
99
|
+
on disk and every task's cost gone from `run.yml`. The cap is honoured, never raised: at the
|
|
100
|
+
emit seam the oversized `detail` becomes `detail_omitted`, carrying its own byte count and
|
|
101
|
+
pointing at a sidecar the seam wrote FIRST — `<phase>/log/overflow/<stamp>-<n>-<type>-detail.txt`,
|
|
102
|
+
one file per omission — while the verdict itself survives. It is not the review log: that file
|
|
103
|
+
does not exist yet when the event is built, it later holds only the final verdict's prose, and
|
|
104
|
+
a story that does not settle never gets one, so pointing at it was a promise about a file that
|
|
105
|
+
might never arrive. Around all of it, the executor call is now wrapped: ANY throw out of an
|
|
106
|
+
executor exits 5 with the stage failed by name and the loss said plainly — which rows this
|
|
107
|
+
invocation could not recover — where before the throw simply escaped, leaving the stage
|
|
108
|
+
`running` in a file nobody saved. It attributes that failure to nothing it did not do, either:
|
|
109
|
+
the row it marks `failed` is one THIS invocation recorded, so a retry whose executor throws
|
|
110
|
+
leaves the previous attempt's `done` turn exactly as it was rather than repainting it with an
|
|
111
|
+
error it never produced.
|
|
112
|
+
- **A cached red base is no longer trusted forever (#162).** `04-build/preflight.yml` was
|
|
113
|
+
invalidated only by a base-sha comparison that no-ops when either sha is empty, and
|
|
114
|
+
`checked_at` was written and never read — so a red measured once came back from every later
|
|
115
|
+
`--prepare` in 0 seconds, over a base a live probe would show green, and the
|
|
116
|
+
`.tldrx/workspace.yml` fix that the refusal itself asks the operator to make was the one thing
|
|
117
|
+
the cache could not see. A red is now re-probed when the command hash differs, when the row is
|
|
118
|
+
older than 30 minutes, or always under `--prepare`. The hash covers the command together with
|
|
119
|
+
the workspace's whole declared command list, because the command string was already the join
|
|
120
|
+
key and hashing it alone would have changed nothing. A cached green keeps the rule it had, and
|
|
121
|
+
both new row fields are additive — an absent one invalidates nothing.
|
|
122
|
+
- **`tldrx facts add --run <id>` no longer invents an absence.** `RunStore.resolve` answers
|
|
123
|
+
`{kind: "none"}` both to "no run is open" and to "there is no run by that id", and the command
|
|
124
|
+
took the one branch for both: a typo'd `--run` wrote the fact with `source.run: null` under the
|
|
125
|
+
stdout line "no run recorded: no open run to attribute it to" — a sentence that is false
|
|
126
|
+
whenever a run IS open, over provenance the operator had asked for by name and silently did not
|
|
127
|
+
get. An id nothing in `tldrx-work/` answers to is now refused before the store is opened: exit
|
|
128
|
+
3, nothing written, no event, and the id named back. The other two branches are unchanged —
|
|
129
|
+
one open run is used, several are still never guessed between.
|
|
130
|
+
- **A fix list records the canonical 40-hex sha (#163).** `Resolved: yes <sha>` accepted 7-40
|
|
131
|
+
hex, so a sha that had lost a character read as a deliberate abbreviation: git resolved it,
|
|
132
|
+
the claim verified, and the audit record kept a form no later reader can tell from a prefix of
|
|
133
|
+
a different commit. The grammar is unchanged — demanding 40 would refuse the abbreviation a
|
|
134
|
+
person legitimately types — and the full object id is written back after the verification,
|
|
135
|
+
which is strictly stronger and refuses nobody.
|
|
136
|
+
|
|
137
|
+
### Changed
|
|
138
|
+
|
|
139
|
+
- **A command's subcommands are declared once.** They were written in the command module AND
|
|
140
|
+
needed by the docs generator, which would have made three copies of the same list. They now
|
|
141
|
+
live in `helpText.ts` beside the flags that are scoped by them, `subcommandsOf(name)` reads
|
|
142
|
+
them, and `Command.subcommands` is gone — one derivation, one implementation, per the house
|
|
143
|
+
invariant that the rest of the repo is already held to.
|
|
144
|
+
|
|
145
|
+
## 0.9.0 — 2026-09-06
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
|
|
149
|
+
- **`AGENTS.md` is the canonical agent context, and `CLAUDE.md` imports it.** Four days of
|
|
150
|
+
live-maintenance tribal knowledge written down where every agent — Claude Code sessions in
|
|
151
|
+
fresh windows, Codex, anything reading `AGENTS.md` — starts with it: the golden evidence
|
|
152
|
+
rules (including "never quote a CLI flag from memory"), worktree+merge-wave discipline, the
|
|
153
|
+
five gates, the `gh run list --commit` lie and the non-empty-checks assertion, CHANGELOG
|
|
154
|
+
union and release ritual, the pinned house invariants, hermetic test rules, public-surface
|
|
155
|
+
guard rules, provider facts, and the known live traps. One file; the other imports it,
|
|
156
|
+
because two rule files drifting apart is the failure mode this repo exists to kill.
|
|
157
|
+
- **Stack packs — `tldrx expert packs <enable|disable|status>`.** A `<lang>-stack` expert was a
|
|
158
|
+
name-only stub: the language name was the only stack-specific token in its body, so the
|
|
159
|
+
developer built from the model's habits and the reviewer had no stack checklist to hold a
|
|
160
|
+
story against (the repo's own audit scored the experts' knowledge 6/10). Off by default,
|
|
161
|
+
behind one per-project switch: `enable` materialises four language packs (TypeScript,
|
|
162
|
+
JavaScript, Python, .NET) and thirteen manifest-detected framework overlays (`react`,
|
|
163
|
+
`next-app-router`, `vite-react-spa`, `expo-router`, `node-express`, `prisma`,
|
|
164
|
+
`aspnet-minimal-apis`, `aspnet-controllers`, `mediatr-cqrs`, `efcore-npgsql`, `fastapi`,
|
|
165
|
+
`sqlalchemy-alembic`, `postgres-testcontainers`) into `.tldrx/experts/<lang>-stack/`, kept apart
|
|
166
|
+
from `knowledge/`, which no packs command ever touches. A pack is interrogative by owner
|
|
167
|
+
decision, never prescriptive over a repo's own conventions: **Defaults** apply only where the
|
|
168
|
+
project is silent and each one names the signal that overrides it, and **Checks** are questions
|
|
169
|
+
the Build reviewer now asks under every diff, each carrying a `verify:` hint and yielding to
|
|
170
|
+
the project's own convention when it has one. `enable` replaces a body only when it is an
|
|
171
|
+
untouched stub — an edited body is `kept:` and the command says so — and a body one shipment
|
|
172
|
+
behind is `upgraded:` rather than silently frozen at the version it was first materialised
|
|
173
|
+
with, told apart by two additive front-matter keys the materialised body carries: `pack:
|
|
174
|
+
<lang>@<hash>` names the shipment and `pack_body:` names the body's own bytes, so "somebody
|
|
175
|
+
edited this" (bytes differ) and "this is one shipment behind" (bytes match, shipment differs)
|
|
176
|
+
are never the same answer. `disable` removes `overlays/` and nothing else. Overlays are
|
|
177
|
+
detected from manifests (`package.json`'s two dependency groups kept apart, `*.csproj` /
|
|
178
|
+
`Directory.Packages.props`, `pyproject.toml` and `requirements*.txt`) and never inferred from
|
|
179
|
+
the language, because two workspaces of the same language can run opposite architectures — one
|
|
180
|
+
prescriptive ".NET pack" would be wrong for whichever one it disagreed with. No new prompt
|
|
181
|
+
mechanism: every renderer already prints an expert's body, so `body + overlays` composes once
|
|
182
|
+
in `loadExpertBundles` and reaches every stage prompt and the Build developer unchanged; the
|
|
183
|
+
reviewer, which carried no expert content at all, is handed the active packs' Checks explicitly
|
|
184
|
+
under a new `## Stack checks (the repo's own conventions win)` section, rendered only when BOTH
|
|
185
|
+
the workspace switch and the story's own stage-level `stack_experts` are on — so a developer
|
|
186
|
+
turn that was never shown a stack expert is never graded against Checks it was never briefed
|
|
187
|
+
on. `workspace.yml` gains `stack_packs` and `repos[].overlays` (each with its evidence string)
|
|
188
|
+
— additive, `version: 1` unchanged, and read back on every re-init so a regenerate never
|
|
189
|
+
silently turns the switch off. For a workspace that never threw the switch, this is today's
|
|
190
|
+
bytes, byte for byte. Once `enable` has run, `disable` stops the overlays and the reviewer's
|
|
191
|
+
Checks, but a pack body already materialised into `expert.md` stays there until you delete
|
|
192
|
+
it — `disable` removes only `overlays/`, by design (fix round 2, ruled) — and `enable` re-seeds
|
|
193
|
+
a body it finds missing or still an untouched stub.
|
|
194
|
+
- **Project skills are named to the developer, independent of the packs switch.** A repo's own
|
|
195
|
+
`.claude/skills/*/SKILL.md` files are detected into `repos[].skills` and rendered under
|
|
196
|
+
`## Project skills` in every stage prompt and the Build developer's — name, description, and
|
|
197
|
+
the path to read — because skills are for doing and packs are for checking: the harness runs a
|
|
198
|
+
skill, the framework only says it exists. The Build developer's allowed tools gain `Skill` only
|
|
199
|
+
when that story's repo has one (whether an agent CLI's print mode actually denies an unlisted
|
|
200
|
+
`Skill` call is unverified — the framework's job is the list, not the enforcement). A skill git
|
|
201
|
+
does not track is flagged in the prompt and warned at Build start, by name and by repo, because
|
|
202
|
+
a story worktree carries tracked files only and the path the prompt names would not exist
|
|
203
|
+
there.
|
|
204
|
+
- **The context ledger separates `expert.md` bytes from overlay bytes.** `pending.json` gains
|
|
205
|
+
`overlays`/`overlay_bytes` on a stack expert's row — additive and absent (not `0` or `[]`) when
|
|
206
|
+
nothing was inlined, so a bundle the switch never touched stays byte-identical to one written
|
|
207
|
+
before packs existed — and a new `project_skills_bytes` beside the other context groups, a
|
|
208
|
+
required count like its siblings, `0` when the workspace detected no project skills rather than
|
|
209
|
+
absent. `expert_md_bytes` keeps the meaning it has always had, `expert.md`'s own bytes and never
|
|
210
|
+
the composed total, and the operator line prints the two counts separately for the same reason.
|
|
211
|
+
- Two out-of-scope findings from building this: stack-expert naming has two independent
|
|
212
|
+
derivations that agree today only because a mismatch fails silently (#152), and a project's own
|
|
213
|
+
`AGENTS.md`/`CLAUDE.md`/`CONTRIBUTING.md` are detected by presence only — their content never
|
|
214
|
+
reaches a stage prompt (#153).
|
|
215
|
+
|
|
3
216
|
## 0.8.0 — 2026-09-04
|
|
4
217
|
|
|
5
218
|
### Added
|
package/README.md
CHANGED
|
@@ -251,7 +251,7 @@ Most run-targeting commands take the id either way, a positional `<run>` or `--r
|
|
|
251
251
|
`cost`, `note`, `gate template`, `questions`, `budget show`, `ship`, `tickets`, and `run attend` ·
|
|
252
252
|
`status` · `estimate` · `auto` · `unlock` · `cancel`. `replay` and `retro` take the positional only
|
|
253
253
|
— `--run` there is an unknown flag. `approve`, `reject`, `answer`, `interview`, `plan`,
|
|
254
|
-
`story reopen`, `watch` and `run gates set` take `--run <id>` only.
|
|
254
|
+
`story reopen`, `facts add`, `watch` and `run gates set` take `--run <id>` only.
|
|
255
255
|
|
|
256
256
|
`tldrx retro --all` goes the other way: it reads **every** run in the workspace and prints one
|
|
257
257
|
table of what keeps catching you — finding class × count × how many runs × one example with its
|
|
@@ -288,7 +288,7 @@ draws before installing anything.
|
|
|
288
288
|
The reference guide, in `docs/guide/`: [1 Quick start](docs/guide/01-quick-start.md) ·
|
|
289
289
|
[2 The loop](docs/guide/02-the-loop.md) (the four steps, what a stage file controls, the two execution modes) ·
|
|
290
290
|
[3 Runs and gates](docs/guide/03-runs-and-gates.md) (`run new`→`retro`, gate policy, `run auto`, unlock/cancel, dashboard, tickets) ·
|
|
291
|
-
[4 Experts](docs/guide/04-experts.md) (loading rules, role experts, training, levels) ·
|
|
291
|
+
[4 Experts](docs/guide/04-experts.md) (loading rules, role experts, stack packs, training, levels) ·
|
|
292
292
|
[5 Seeds and triage](docs/guide/05-seeds-and-triage.md) (`--seed`, `--from`, splitting a big seed) ·
|
|
293
293
|
[6 Budgets and cost](docs/guide/06-budgets-and-cost.md) · [7 Claude Code](docs/guide/07-claude-code.md) (plugin, hooks, `/tldrx`) ·
|
|
294
294
|
[8 CLI reference](docs/guide/08-cli-reference.md) (every command, flag and exit code) ·
|
|
@@ -314,6 +314,8 @@ back on the registry is 0.3.0.
|
|
|
314
314
|
|
|
315
315
|
| Version | Date | Status | Contains |
|
|
316
316
|
|---|---|---|---|
|
|
317
|
+
| 0.9.1 | 2026-09-06 | `beta` | records and money that do not lie: a turn without a provider USD figure is unmetered, never a metered $0.00; a refusal no longer discards the turn's cost (banked first, deduped only against marked rows); an event over the 4096-byte cap spills its text beside the run and names it instead of losing every row of the invocation; preflight rows carry `command_hash` and their own `checked_at`, so a cached red is re-probed when the command changed, after 30 minutes, or under --prepare; the fix list writes back git's canonical 40-hex sha; `tldrx facts add` exists with owner/driver attribution rendered in prompts; run.yml rows carry the provider's token split only when reported |
|
|
318
|
+
| 0.9.0 | 2026-09-06 | `beta` | opt-in stack packs: four language pack bodies and thirteen framework overlays detected from manifests, interrogative by default (Defaults yield to any repo signal, Checks are questions with `verify:` hints), behind one per-project switch (`tldrx expert packs enable`) that materialises into `.tldrx/experts/<lang>-stack/` without touching trained knowledge; the Build reviewer gets `## Stack checks (the repo's own conventions win)`; every stage names the project's `.claude/skills` and the developer may invoke them; workspace.yml records overlays with evidence and skills with a tracked flag, `version: 1` unchanged |
|
|
317
319
|
| 0.8.0 | 2026-09-04 | `beta` | the unattended mandate learns to keep going: a `## Do not stop` section that defines the one thing allowed to end a run early (a strict blocker, named against the work it does not block), product questions parked as **guided** ones with lettered options and a pre-declared fallback, and a budget stop that asks instead of halting — written against 26 `budget.raised` and 26 `question.answered` events on a real ten-run workspace where the owner had to re-authorise "unattended" mid-run. Plus `tldrx drive --tldr`: a reporting contract for runs nobody will audit — the `run status` block plus three bullets of delta, no operator notes, minimal gate evidence, handoffs trimmed of prose but never of the citations `claim-sources` gates on. The ask channel stays the console and the framework names no chat vendor |
|
|
318
320
|
| 0.7.0 | 2026-09-03 | `beta` | Codex as a second honest automated runner: recorded JSONL contract, structured envelopes, role-based sandboxes, token/session provenance, and explicitly unmetered USD accounting; Claude remains the default and the pilot harness. Also absorbs the citation-honesty work previously staged as 0.6.2: a `file` src resolves against the branches the run RECORDED and NAMES the unmerged ref instead of passing in silence or breaking with the temp dir (#140), watcher cards name it the same way (#143), and every run close reports the questions nobody answered (#141) |
|
|
319
321
|
| 0.6.1 | 2026-09-03 | `beta` | dogfooding fixes from the first fully-unattended runs: blocked stories never lose uncommitted work (rescue commits + `story.work_rescued`), a fix is only Resolved with a reachable sha, re-entered stages reconstruct their handoff from the ledger instead of degrading it, the Cost header reports the phase and names its lower bound (shared `spendBasis`), feature presets resolve their per-repo maps, Plan sees the command allowlist its gate enforces, the landing sells the unattended flow |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
FactsStore
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-f6484ykg.js";
|
|
5
5
|
import {
|
|
6
6
|
parseHookInput,
|
|
7
7
|
readStdin
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
EventLog,
|
|
11
11
|
PHASE_ID_RE
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-fv66tdsm.js";
|
|
13
13
|
import"./chunk-5w5eg00k.js";
|
|
14
|
-
import"./chunk-
|
|
14
|
+
import"./chunk-8awygmqw.js";
|
|
15
15
|
import {
|
|
16
16
|
MAX_FACT_CHARS,
|
|
17
17
|
detectAnswered,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
recordAnswer,
|
|
20
20
|
replaceBlock,
|
|
21
21
|
serializeQuestions
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-vyvjbbmb.js";
|
|
23
23
|
import"./chunk-39zh2e44.js";
|
|
24
24
|
import {
|
|
25
25
|
PROJECT_WORK_DIR,
|
|
@@ -29,15 +29,15 @@ import {
|
|
|
29
29
|
validateRunBudget,
|
|
30
30
|
wouldExceed,
|
|
31
31
|
wouldExceedHostTokens
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-hg43zexg.js";
|
|
33
33
|
import {
|
|
34
34
|
EventLog
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-fv66tdsm.js";
|
|
36
36
|
import"./chunk-rrkdfk7s.js";
|
|
37
37
|
import"./chunk-5w5eg00k.js";
|
|
38
38
|
import {
|
|
39
39
|
noteDeprecations
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-vyvjbbmb.js";
|
|
41
41
|
import"./chunk-39zh2e44.js";
|
|
42
42
|
import {
|
|
43
43
|
PROJECT_WORK_DIR,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
nowRfc3339,
|
|
8
8
|
openRunViews,
|
|
9
9
|
validateRunBudget
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-hg43zexg.js";
|
|
11
11
|
import {
|
|
12
12
|
EventLog,
|
|
13
13
|
asRunFile,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isTerminal,
|
|
20
20
|
stageAt,
|
|
21
21
|
validateRunFile
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-fv66tdsm.js";
|
|
23
23
|
import {
|
|
24
24
|
backupPathFor,
|
|
25
25
|
isAlive,
|
|
@@ -28,12 +28,12 @@ import {
|
|
|
28
28
|
workspaceRootOfRunDir,
|
|
29
29
|
writeAtomic,
|
|
30
30
|
yamlScalar
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-8awygmqw.js";
|
|
32
32
|
import {
|
|
33
33
|
noteDeprecations,
|
|
34
34
|
openBlocks,
|
|
35
35
|
parseQuestions
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-vyvjbbmb.js";
|
|
37
37
|
import {
|
|
38
38
|
listRunDirs,
|
|
39
39
|
parseYaml,
|
|
@@ -80,11 +80,15 @@ function task(t, indent) {
|
|
|
80
80
|
const cost = t.cost_usd === null ? "null" : money(t.cost_usd);
|
|
81
81
|
const metered = t.metered === false ? ", metered: false" : "";
|
|
82
82
|
const tokens = t.tokens === undefined ? "" : `, tokens: ${String(t.tokens)}`;
|
|
83
|
+
const inTokens = t.input_tokens === undefined ? "" : `, input_tokens: ${String(t.input_tokens)}`;
|
|
84
|
+
const outTokens = t.output_tokens === undefined ? "" : `, output_tokens: ${String(t.output_tokens)}`;
|
|
83
85
|
return [
|
|
84
|
-
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens},`,
|
|
86
|
+
`${indent} - {id: ${yamlScalar(t.id)}, status: ${yamlScalar(t.status)}, expert: ${yamlScalar(t.expert)}, ` + `model: ${yamlScalar(t.model)}, cost_usd: ${cost}${metered}${tokens}${inTokens}${outTokens},`,
|
|
85
87
|
`${inner}error: ${yamlScalar(t.error)}, session_id: ${yamlScalar(t.session_id)},`,
|
|
86
88
|
`${inner}started_at: ${yamlScalar(t.started_at)}, ended_at: ${yamlScalar(t.ended_at)},`,
|
|
87
89
|
...t.stopped_by === undefined || t.stopped_by === null ? [] : [`${inner}stopped_by: ${yamlScalar(t.stopped_by)},`],
|
|
90
|
+
...t.banked_before_refusal === undefined ? [] : [`${inner}banked_before_refusal: true,`],
|
|
91
|
+
...t.dedupe === undefined ? [] : [`${inner}dedupe: ${yamlScalar(t.dedupe)},`],
|
|
88
92
|
`${inner}outputs: ${inlineList(t.outputs)}}`
|
|
89
93
|
].join(`
|
|
90
94
|
`);
|
|
@@ -143,6 +143,7 @@ function inlineList(values) {
|
|
|
143
143
|
}
|
|
144
144
|
function emitFact(fact, indent = " ") {
|
|
145
145
|
const inner = `${indent} `;
|
|
146
|
+
const decidedBy = fact.source.decided_by === undefined ? "" : `, decided_by: ${yamlScalar(fact.source.decided_by)}`;
|
|
146
147
|
const lines = [
|
|
147
148
|
`${indent}- id: ${yamlScalar(fact.id)}`,
|
|
148
149
|
`${inner}fact: ${yamlScalar(fact.fact)}`,
|
|
@@ -150,7 +151,7 @@ function emitFact(fact, indent = " ") {
|
|
|
150
151
|
`${inner}repos: ${inlineList(fact.repos)}`,
|
|
151
152
|
`${inner}kind: ${yamlScalar(fact.kind)}`,
|
|
152
153
|
`${inner}confidence: ${yamlScalar(fact.confidence)}`,
|
|
153
|
-
`${inner}source: {who: ${yamlScalar(fact.source.who)}, when: ${yamlScalar(fact.source.when)}, ` + `run: ${yamlScalar(fact.source.run)}, q: ${yamlScalar(fact.source.q)}}`,
|
|
154
|
+
`${inner}source: {who: ${yamlScalar(fact.source.who)}, when: ${yamlScalar(fact.source.when)}, ` + `run: ${yamlScalar(fact.source.run)}, q: ${yamlScalar(fact.source.q)}${decidedBy}}`,
|
|
154
155
|
`${inner}supersedes: ${yamlScalar(fact.supersedes)}`,
|
|
155
156
|
`${inner}superseded_by: ${yamlScalar(fact.superseded_by)}`
|
|
156
157
|
];
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
withWorkspaceLock,
|
|
4
4
|
workspaceRootOfFactsPath,
|
|
5
5
|
writeAtomic
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-8awygmqw.js";
|
|
7
7
|
import {
|
|
8
8
|
FACT_CONFIDENCES,
|
|
9
9
|
FACT_KINDS,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isLive,
|
|
15
15
|
isRetired,
|
|
16
16
|
noteDeprecations
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-vyvjbbmb.js";
|
|
18
18
|
import {
|
|
19
19
|
asDocument,
|
|
20
20
|
isRecord,
|
|
@@ -86,6 +86,13 @@ function validateFactsFile(input) {
|
|
|
86
86
|
if (typeof q === "string" && !Q_RE.test(q)) {
|
|
87
87
|
issues.push({ path: `${path}.source.q`, message: `expected ${readableSource(Q_RE)} or null` });
|
|
88
88
|
}
|
|
89
|
+
const decidedBy = row.source.decided_by;
|
|
90
|
+
if (decidedBy !== undefined && decidedBy !== "owner" && decidedBy !== "driver") {
|
|
91
|
+
issues.push({
|
|
92
|
+
path: `${path}.source.decided_by`,
|
|
93
|
+
message: "expected owner, driver or absent"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
89
96
|
} else if (row.source !== undefined) {
|
|
90
97
|
issues.push({ path: `${path}.source`, message: "expected a mapping" });
|
|
91
98
|
}
|
|
@@ -698,6 +698,20 @@ function validateRunFile(input) {
|
|
|
698
698
|
if (task.cost_usd === null && task.metered !== false) {
|
|
699
699
|
issues.push({ path: `${tp}.metered`, message: "a null cost_usd must be marked `metered: false`" });
|
|
700
700
|
}
|
|
701
|
+
if (task.banked_before_refusal !== undefined && task.banked_before_refusal !== true) {
|
|
702
|
+
issues.push({ path: `${tp}.banked_before_refusal`, message: "expected `true` or absent" });
|
|
703
|
+
}
|
|
704
|
+
if (task.dedupe !== undefined && typeof task.dedupe !== "string") {
|
|
705
|
+
issues.push({ path: `${tp}.dedupe`, message: "expected a string" });
|
|
706
|
+
}
|
|
707
|
+
for (const key of ["input_tokens", "output_tokens"]) {
|
|
708
|
+
const value = task[key];
|
|
709
|
+
if (value === undefined)
|
|
710
|
+
continue;
|
|
711
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
712
|
+
issues.push({ path: `${tp}.${key}`, message: "expected a number >= 0" });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
701
715
|
if (typeof task.cost_usd === "number")
|
|
702
716
|
spentFromTasks += task.cost_usd;
|
|
703
717
|
checkOrder(task.started_at, task.ended_at, tp, issues);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EVIDENCE_FILE,
|
|
3
3
|
gateEvidenceRelPath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-fv66tdsm.js";
|
|
5
5
|
import {
|
|
6
6
|
MAX_PLAN_STORIES,
|
|
7
7
|
MAX_STORIES_PER_WAVE,
|
|
@@ -752,33 +752,50 @@ function pathsIntersect(a, b) {
|
|
|
752
752
|
// src/core/experts/expertKnowledge.ts
|
|
753
753
|
var DEFAULT_KNOWLEDGE_MAX_BYTES = 48 * 1024;
|
|
754
754
|
|
|
755
|
-
// src/core/
|
|
756
|
-
var DISPATCH_NOTES_MAX_BYTES = 8 * 1024;
|
|
757
|
-
|
|
758
|
-
// src/core/experts/stackExperts.ts
|
|
755
|
+
// src/core/experts/workspaceRepos.ts
|
|
759
756
|
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
760
757
|
import { join as join5 } from "node:path";
|
|
761
|
-
function
|
|
758
|
+
function readWorkspaceDocument(root) {
|
|
762
759
|
const path = join5(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
|
|
763
760
|
if (!existsSync4(path))
|
|
764
|
-
return
|
|
765
|
-
let doc;
|
|
761
|
+
return null;
|
|
766
762
|
try {
|
|
767
|
-
|
|
763
|
+
return parseYaml(readFileSync4(path, "utf8"));
|
|
768
764
|
} catch {
|
|
769
|
-
return
|
|
765
|
+
return null;
|
|
770
766
|
}
|
|
771
|
-
|
|
767
|
+
}
|
|
768
|
+
function workspaceRepoRows(doc) {
|
|
769
|
+
if (typeof doc !== "object" || doc === null)
|
|
770
|
+
return [];
|
|
771
|
+
const list = doc.repos;
|
|
772
772
|
if (!Array.isArray(list))
|
|
773
773
|
return [];
|
|
774
|
-
const
|
|
774
|
+
const rows = [];
|
|
775
775
|
for (const row of list) {
|
|
776
|
-
if (typeof row
|
|
776
|
+
if (typeof row !== "object" || row === null)
|
|
777
777
|
continue;
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
778
|
+
const record = row;
|
|
779
|
+
if (typeof record.name !== "string")
|
|
780
|
+
continue;
|
|
781
|
+
rows.push(record);
|
|
782
|
+
}
|
|
783
|
+
return rows;
|
|
784
|
+
}
|
|
785
|
+
function strings(value) {
|
|
786
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/core/facilitator/dispatchNotes.ts
|
|
790
|
+
var DISPATCH_NOTES_MAX_BYTES = 8 * 1024;
|
|
791
|
+
|
|
792
|
+
// src/core/experts/stackExperts.ts
|
|
793
|
+
function stackExpertNames(root, repos) {
|
|
794
|
+
const names = [];
|
|
795
|
+
for (const row of workspaceRepoRows(readWorkspaceDocument(root))) {
|
|
796
|
+
if (!repos.includes(row.name))
|
|
797
|
+
continue;
|
|
798
|
+
for (const language of strings(row.stack).filter((item) => item !== "")) {
|
|
782
799
|
const expert = `${language}-stack`;
|
|
783
800
|
if (!names.includes(expert))
|
|
784
801
|
names.push(expert);
|
package/dist/hooks/no-reask.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-cf4az0j5.js";
|
|
8
8
|
import {
|
|
9
9
|
FactsStore
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-f6484ykg.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
runHook
|
|
18
18
|
} from "./chunk-2y3kwfrx.js";
|
|
19
19
|
import"./chunk-x3zy7zrn.js";
|
|
20
|
-
import"./chunk-
|
|
20
|
+
import"./chunk-8awygmqw.js";
|
|
21
21
|
import {
|
|
22
22
|
openBlocks,
|
|
23
23
|
parseQuestions
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-vyvjbbmb.js";
|
|
25
25
|
import"./chunk-39zh2e44.js";
|
|
26
26
|
import {
|
|
27
27
|
factsPath,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import"./chunk-
|
|
2
|
+
import"./chunk-f6484ykg.js";
|
|
3
3
|
import {
|
|
4
4
|
allow,
|
|
5
5
|
readPayload,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
openQuestions,
|
|
16
16
|
runSnapshot,
|
|
17
17
|
whatIsWaiting
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4w03q2kj.js";
|
|
19
19
|
import {
|
|
20
20
|
evidencePath,
|
|
21
21
|
expertsDir,
|
|
@@ -25,17 +25,17 @@ import {
|
|
|
25
25
|
pathsIntersect,
|
|
26
26
|
readExpertDomain,
|
|
27
27
|
stackExpertNames
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-hg43zexg.js";
|
|
29
29
|
import {
|
|
30
30
|
parseEvidence
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-fv66tdsm.js";
|
|
32
32
|
import"./chunk-rrkdfk7s.js";
|
|
33
33
|
import"./chunk-5w5eg00k.js";
|
|
34
|
-
import"./chunk-
|
|
34
|
+
import"./chunk-8awygmqw.js";
|
|
35
35
|
import {
|
|
36
36
|
openBlocks,
|
|
37
37
|
parseQuestions
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-vyvjbbmb.js";
|
|
39
39
|
import {
|
|
40
40
|
isRecord
|
|
41
41
|
} from "./chunk-39zh2e44.js";
|
|
@@ -925,11 +925,19 @@ function evidenceCount(expert) {
|
|
|
925
925
|
// src/core/experts/roleExperts.ts
|
|
926
926
|
import { join as join5 } from "node:path";
|
|
927
927
|
var ROLE_TEMPLATES_DIR = join5(TEMPLATES_DIR, "experts");
|
|
928
|
+
// src/core/experts/packSections.ts
|
|
929
|
+
var PACK_MAX_BYTES = 24 * 1024;
|
|
928
930
|
// src/core/experts/domainRank.ts
|
|
929
931
|
var MAX_GRAPH_BYTES = 64 * 1024 * 1024;
|
|
932
|
+
// src/core/experts/packTemplates.ts
|
|
933
|
+
import { join as join6 } from "node:path";
|
|
934
|
+
var PACK_TEMPLATES_DIR = join6(TEMPLATES_DIR, "experts", "stack");
|
|
935
|
+
var OVERLAY_TEMPLATES_DIR = join6(PACK_TEMPLATES_DIR, "overlays");
|
|
936
|
+
var PACK_BODY_MAX_BYTES = 8 * 1024;
|
|
937
|
+
var OVERLAY_MAX_BYTES = 6 * 1024;
|
|
930
938
|
// src/core/training/mineRuns.ts
|
|
931
939
|
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
932
|
-
import { join as
|
|
940
|
+
import { join as join7, relative as relative2 } from "node:path";
|
|
933
941
|
var MAX_RUN_BYTES = 64 * 1024;
|
|
934
942
|
var MINED_NAMES = ["handoff.md", "retro.md"];
|
|
935
943
|
var MAX_DEPTH = 4;
|
|
@@ -953,7 +961,7 @@ function findMined(dir, depth) {
|
|
|
953
961
|
for (const entry of [...entries].sort()) {
|
|
954
962
|
if (entry.startsWith("."))
|
|
955
963
|
continue;
|
|
956
|
-
const abs =
|
|
964
|
+
const abs = join7(dir, entry);
|
|
957
965
|
let isDir = false;
|
|
958
966
|
try {
|
|
959
967
|
isDir = statSync4(abs).isDirectory();
|
package/dist/hooks/statusline.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bar,
|
|
4
4
|
runSnapshot
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import"./chunk-
|
|
7
|
-
import"./chunk-
|
|
5
|
+
} from "./chunk-4w03q2kj.js";
|
|
6
|
+
import"./chunk-hg43zexg.js";
|
|
7
|
+
import"./chunk-fv66tdsm.js";
|
|
8
8
|
import"./chunk-rrkdfk7s.js";
|
|
9
9
|
import"./chunk-5w5eg00k.js";
|
|
10
|
-
import"./chunk-
|
|
11
|
-
import"./chunk-
|
|
10
|
+
import"./chunk-8awygmqw.js";
|
|
11
|
+
import"./chunk-vyvjbbmb.js";
|
|
12
12
|
import"./chunk-39zh2e44.js";
|
|
13
13
|
import {
|
|
14
14
|
findWorkspaceRoot,
|