tldr-experts 0.3.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 +913 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/hooks/answer-capture.js +174 -0
- package/dist/hooks/budget-gate.js +173 -0
- package/dist/hooks/chunk-0bt6yb2q.js +88 -0
- package/dist/hooks/chunk-1zwcxd3f.js +66 -0
- package/dist/hooks/chunk-9gb21660.js +66 -0
- package/dist/hooks/chunk-a8p2rc94.js +20 -0
- package/dist/hooks/chunk-ae6bkfs5.js +0 -0
- package/dist/hooks/chunk-azctppjh.js +198 -0
- package/dist/hooks/chunk-g395gk7e.js +503 -0
- package/dist/hooks/chunk-j234zf0t.js +339 -0
- package/dist/hooks/chunk-kw4tffzf.js +139 -0
- package/dist/hooks/chunk-p274ckxv.js +7435 -0
- package/dist/hooks/chunk-sdjnnmzz.js +497 -0
- package/dist/hooks/chunk-t56k6146.js +14 -0
- package/dist/hooks/chunk-t8tdv11p.js +35 -0
- package/dist/hooks/chunk-x98qs959.js +302 -0
- package/dist/hooks/chunk-y0jdr3et.js +627 -0
- package/dist/hooks/claim-sources.js +72 -0
- package/dist/hooks/dod-gate.js +198 -0
- package/dist/hooks/no-reask.js +67 -0
- package/dist/hooks/session-start.js +1348 -0
- package/dist/hooks/statusline.js +116 -0
- package/dist/tldrx.js +34219 -0
- package/env.yml +79 -0
- package/package.json +60 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/README.md +113 -0
- package/plugin/agents/README.md +22 -0
- package/plugin/hooks/hooks.json +107 -0
- package/plugin/skills/tldrx/SKILL.md +170 -0
- package/stages/build/stage.md +70 -0
- package/stages/build/stage.yml +54 -0
- package/stages/how/stage.md +89 -0
- package/stages/how/stage.yml +53 -0
- package/stages/plan/stage.md +89 -0
- package/stages/plan/stage.yml +48 -0
- package/stages/watch/stage.md +85 -0
- package/stages/watch/stage.yml +61 -0
- package/stages/what/stage.md +89 -0
- package/stages/what/stage.yml +61 -0
- package/templates/budget.yml +17 -0
- package/templates/competencies.yml +14 -0
- package/templates/env.yml +19 -0
- package/templates/epic.md +38 -0
- package/templates/expert.md +51 -0
- package/templates/experts/architect.md +77 -0
- package/templates/experts/delivery.md +76 -0
- package/templates/experts/developer.md +78 -0
- package/templates/experts/operations.md +74 -0
- package/templates/experts/product.md +78 -0
- package/templates/facts.yml +18 -0
- package/templates/handoff.md +63 -0
- package/templates/process.yml +33 -0
- package/templates/questions.md +63 -0
- package/templates/run.yml +24 -0
- package/templates/story.md +55 -0
- package/templates/watcher.md +68 -0
- package/templates/waves.yml +17 -0
- package/templates/workspace.yml +30 -0
- package/workflows/bugfix.yml +26 -0
- package/workflows/docs.yml +23 -0
- package/workflows/feature.yml +22 -0
- package/workflows/hotfix.yml +24 -0
- package/workflows/integration.yml +24 -0
- package/workflows/migration.yml +24 -0
- package/workflows/performance.yml +23 -0
- package/workflows/prototype.yml +24 -0
- package/workflows/refactor.yml +24 -0
- package/workflows/retro.yml +20 -0
- package/workflows/security-patch.yml +23 -0
- package/workflows/spike.yml +23 -0
- package/workflows/upgrade.yml +23 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — 2026-08-30
|
|
4
|
+
|
|
5
|
+
Every measurement below was taken on a real workspace on 2026-08-29 unless another date is
|
|
6
|
+
given. A claim with no measurement behind it is marked `[assumption]` where it appears in the
|
|
7
|
+
code.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
**Commands**
|
|
12
|
+
|
|
13
|
+
- `tldrx status [--json]` — one report of everything in the workspace waiting on a human, in
|
|
14
|
+
the order the sources block each other: open init questions, every `split.yml` still
|
|
15
|
+
`status: proposed`, every open run with the exact command it needs, and every expert a stage
|
|
16
|
+
will load with zero evidence. `--json` gives `{root, pending, items[], advice[]}`; `items`
|
|
17
|
+
are the blockers, `advice` blocks nothing. Deterministic and read-only; exits `0` whatever
|
|
18
|
+
it finds, `3` only when there is no `.tldrx/` at all. Runs are dependency-aware: one whose
|
|
19
|
+
`triage.depends_on` sibling is not `done` shows `blocked by <slug>` and is offered no
|
|
20
|
+
command, and the first runnable one is marked `← next`. A run folder that does not validate
|
|
21
|
+
gets its own item rather than vanishing.
|
|
22
|
+
- `tldrx expert train <name> --area <a> [--mode light|full]` — training runs. It used to print
|
|
23
|
+
a prompt and exit `64`. A deterministic pre-pass picks the files (map domains, graphify
|
|
24
|
+
communities, a bounded keyword grep; capped at 40 files / 96 KB with everything over the cap
|
|
25
|
+
named as "not read"); one sub-agent writes `knowledge/<area>.md`; the framework re-reads it
|
|
26
|
+
off disk and validates it with the same parser the `claim-sources` hook uses; evidence is
|
|
27
|
+
**derived** from the citations, never asserted. `--mode full` adds a second sub-agent mining
|
|
28
|
+
`tldrx-work/**/{handoff,retro}.md` — Claude Code transcripts are deliberately out of scope,
|
|
29
|
+
they carry no citation anything can re-resolve. `--max-usd` (default `$2.00`), `--model`,
|
|
30
|
+
`--effort`, `--yolo`, `--prepare`/`--commit`, `--print-prompt`.
|
|
31
|
+
- `tldrx expert recompute [<name>] [--json]` — recomputes `areas[].level` from the evidence
|
|
32
|
+
already on disk. Only the headless/`--commit` path ever wrote a level, so a human who pasted
|
|
33
|
+
the `--print-prompt` prompt into their own session ended with `level: 0` while the formula
|
|
34
|
+
computed 5, and nothing could settle it. Idempotent; touches neither `status` nor
|
|
35
|
+
`last_trained`; spawns nothing.
|
|
36
|
+
- `tldrx install --claude` — writes the facilitator into a real `.claude/` without the plugin
|
|
37
|
+
and without `init`: the skill file (marker `<!-- tldrx-managed -->`, `disable-model-invocation`
|
|
38
|
+
intact) plus a merge of two `settings.json` keys — the six hooks as eight handlers on four
|
|
39
|
+
events, each `tldrx hook <name>`, and `statusLine`. `--project` (default; refuses outside a
|
|
40
|
+
git repo) `--user` `--skill-only` `--no-hooks` `--no-statusline` `--force-statusline`
|
|
41
|
+
`--uninstall` `--dry-run`.
|
|
42
|
+
- `tldrx hook <name>` and `tldrx statusline` — run one hook script (`dist/hooks/<name>.js`, or
|
|
43
|
+
`src/hooks/<name>.ts` in a source checkout), passing stdin, stdout, stderr and the exit code
|
|
44
|
+
through unchanged. That is what lets a *committed* `settings.json` name a hook without an
|
|
45
|
+
absolute path. The plugin keeps `${CLAUDE_PLUGIN_ROOT}`, because it has to work for someone
|
|
46
|
+
who cloned the repo and installed nothing.
|
|
47
|
+
- `tldrx interview [--run <id>] [--init] [--yes-to-defaults]` — the Interview step in a
|
|
48
|
+
terminal, recording through the same `src/core/answers/` path as `tldrx answer` and the
|
|
49
|
+
`answer-capture` hook. It never answers for you: end of input, `s` and `q` all leave the
|
|
50
|
+
question `status: open`, and a letter the question does not offer is reported and skipped.
|
|
51
|
+
Piped stdin is one answer per line.
|
|
52
|
+
- `tldrx run auto [<run>]` — the headless loop: `next` until a human gate or open question
|
|
53
|
+
(`4`), a failure (`5`), a budget refusal (`2`), `--until <stage>` reached or the run finished
|
|
54
|
+
(`0`). Holds no state, so killing it leaves a run `tldrx next` picks up unchanged.
|
|
55
|
+
`--max-usd` is a ceiling on the LOOP's total spend, checked *between* stages.
|
|
56
|
+
- `tldrx run unlock [<run>] [--force]` and `tldrx run cancel [<run>] --note <t> [--force]` —
|
|
57
|
+
the two ways out of a stuck run, with new `run.unlocked` / `run.cancelled` events and an
|
|
58
|
+
additive `cancelled: {by, at, note}` on `run.yml` (which is what lets a FAILED run be closed
|
|
59
|
+
without overwriting the failure on its stages). Nothing is deleted; `tldrx replay` still
|
|
60
|
+
reads the whole thing.
|
|
61
|
+
- `tldrx cost [<run>] [--all] [--json]` — what was actually spent, read off `agent.result`
|
|
62
|
+
events and nothing else. Per attempt, per stage, per run. All four token counters, both
|
|
63
|
+
prompt-cache halves included.
|
|
64
|
+
- `tldrx run estimate [<run>] [--json]` — the only command that guesses, and it says so. The
|
|
65
|
+
input half is measured (the same prompt assembly and ledger `next` uses); the output half is
|
|
66
|
+
the median output tokens of past attempts at that stage id, and with no history it prints no
|
|
67
|
+
estimate. Prices and context windows live in one dated `[assumption]` file,
|
|
68
|
+
`src/core/budget/modelPrices.ts`.
|
|
69
|
+
- `tldrx questions lint [--run <id>] [--fix] [--area <a>]` — names every `questions.md` block
|
|
70
|
+
the §2.7 parser cannot see and exits `2`. `--fix` converts the prose form
|
|
71
|
+
(`### Qn — …` / `**Answer:**`) into the grammar **without changing a word**; it does not
|
|
72
|
+
invent the `[src: …]` token §2.7 wants, it lists the blocks that still need one.
|
|
73
|
+
- `tldrx reject --stage <phase>/<stage> --note <t>` — revokes an approval already given,
|
|
74
|
+
whoever signed it. The cursor moves back, one new `gate.revoked` event carries `signed_by`,
|
|
75
|
+
and later stages that had run are marked `stale: true` (additive, cleared when the stage
|
|
76
|
+
re-runs). It is the one verb that may reopen a FINISHED run.
|
|
77
|
+
- `tldrx seed triage <path>` — free, offline, no LLM: `inventory.md` + `inventory.json` with
|
|
78
|
+
per-document tokens, headings, cross-links, `Status:`, open markers, and a **code-derived**
|
|
79
|
+
flag set when ≥ 8 distinct path-like tokens it cites resolve to real files. Threshold:
|
|
80
|
+
`--threshold-tokens`, else the new optional `seed_triage.threshold_tokens` in
|
|
81
|
+
`workspace.yml`, else 20,000. Both directions of that heuristic were measured on a real
|
|
82
|
+
design folder (31 files, ~66k tokens): a 152 KB legacy inventory document cites **294**
|
|
83
|
+
distinct path-like tokens and **0** resolve — that repo is a rewrite and those paths belong to
|
|
84
|
+
the system it replaced — so it is *not* flagged, where a rule that counted citations instead
|
|
85
|
+
of resolving them would have called it code-derived and been wrong; `ADR-D005-ORDERING.md`,
|
|
86
|
+
written against the current tree, cites 12 and **8 resolve**, so it *is*. An earlier reading
|
|
87
|
+
the same afternoon said 24 files / ~44k tokens: the folder gained seven ADRs between the two
|
|
88
|
+
runs, which is why the verdict is printed rather than remembered.
|
|
89
|
+
- `tldrx seed triage <path> --propose` — ONE sub-agent (effort `low`, `--max-usd 1.00`) that
|
|
90
|
+
proposes a split and **never creates a run**. The answer is validated against *this*
|
|
91
|
+
workspace before a byte is written; failure is whole (exit `5`, no `split.yml`, raw answer
|
|
92
|
+
kept).
|
|
93
|
+
- `tldrx seed answer <split.yml> <Qid> "<text>"` — records a decision beside the question. The
|
|
94
|
+
key is human-owned: the propose schema still refuses it, so a model can never write one.
|
|
95
|
+
- `tldrx seed apply <split.yml> [--dry-run]` — the human gate. Refuses anything not
|
|
96
|
+
`status: proposed`, revalidates the file you were invited to edit, and creates the runs in
|
|
97
|
+
topological order through the same `createRun` that `run new` calls.
|
|
98
|
+
- `tldrx tickets sync|status` — the optional ticket mirror (off unless `process.yml` names a
|
|
99
|
+
`ticket_tool.kind`). GitHub through the `gh` CLI so no token is handled here; Jira through
|
|
100
|
+
REST v3 with `JIRA_BASE_URL`/`JIRA_EMAIL`/`JIRA_API_TOKEN`, a missing one being exit `1`
|
|
101
|
+
naming all three *before* `03-plan/` is read. Both providers take their transport as an
|
|
102
|
+
argument, so **no test in this repo makes an outbound call or spawns `gh`**. New
|
|
103
|
+
`ticket.synced` event (`stage: null`, `cost_usd: 0` — no model ran, and a mirror is not a
|
|
104
|
+
stage).
|
|
105
|
+
- `tldrx <command> --help` now answers the question: a one-line description, the positional
|
|
106
|
+
arguments, every flag with its meaning and — where the set is closed — its values, one or two
|
|
107
|
+
real invocations, and the exit codes that command can return with what each means. One
|
|
108
|
+
registry, `src/cli/helpText.ts`, read by the renderer, the argv guard and a drift test.
|
|
109
|
+
Closed sets are imported from where they are enforced (`EFFORT_LEVELS`, `UI_MODES`, the
|
|
110
|
+
`workflows/*.yml` stems on disk), so a help screen cannot offer a value the validator
|
|
111
|
+
refuses. `tldrx --help` gains the same exit table as a legend.
|
|
112
|
+
|
|
113
|
+
**Gates, money and safety**
|
|
114
|
+
|
|
115
|
+
- **Gate policy is data.** `workflows/<scope>.yml` carries `gates:` (stage id → `human|auto`);
|
|
116
|
+
`tldrx run new … --gates <a,b|all|none>` overrides it and **names the HUMAN gates**; the
|
|
117
|
+
resolved map is frozen into `run.yml` as an additive `gates_policy:`. Absence means `human`
|
|
118
|
+
everywhere. Shipped defaults keep at least one human gate in every scope.
|
|
119
|
+
- **An `auto` gate closes only when it can show its work** — five conditions, all measured off
|
|
120
|
+
files that already exist, all evaluated even after one fails: checks pass, no open question
|
|
121
|
+
in the phase, spend inside the stage and phase ceilings, the stage did not end `failed`, and
|
|
122
|
+
the §2.8 claim-sources validator reports nothing (zero refused **and** zero unverified). The
|
|
123
|
+
§2.8 check runs whether or not the stage listed it under `checks:`. Approval goes through the
|
|
124
|
+
SAME path a person's does, lands `by: auto`, and writes a note carrying every value. No new
|
|
125
|
+
event type was invented: `gate.approved` gained `by`.
|
|
126
|
+
- `effort:` in `stage.yml` and `--effort <low|medium|high|xhigh|max>` on the command line — the
|
|
127
|
+
cost lever `--max-budget-usd` is not, because the budget flag can only end a turn already in
|
|
128
|
+
flight. Shipped stage defaults, all `[assumption]`: what `medium`, how `high`, plan `medium`,
|
|
129
|
+
build `high`, watch `low`. Recorded on `agent.spawned`/`agent.result` and every
|
|
130
|
+
`training.jsonl` line, so cost-per-effort becomes measurable rather than arguable.
|
|
131
|
+
- **A context ledger, and `prompt_max_bytes` as a refusal.** `--prepare` and `--dry-run` print
|
|
132
|
+
bytes per section and `pending.json` carries the same under `context:`. Over
|
|
133
|
+
`prompt_max_bytes` (default 160 KB, `--prompt-max-bytes` to override) the stage exits `2`
|
|
134
|
+
before anything spawns, naming the biggest sections and the key that shrinks each. The
|
|
135
|
+
model's context window is only ever a stderr warning at 80% — it and the bytes-per-token
|
|
136
|
+
ratio are both `[assumption]`, and refusing on two stacked assumptions would block work the
|
|
137
|
+
framework could have done.
|
|
138
|
+
- **`max_reads` — the brake `--max-budget-usd` is not.** Completed `Read`/`Glob`/`Grep` calls
|
|
139
|
+
are counted off the stream that is already arriving (no second model call, no extra tokens)
|
|
140
|
+
and the process tree is killed at the ceiling: 120 for what/how/plan, 200 build, 60 watch;
|
|
141
|
+
`--max-reads` to override. The attempt records `stopped_by: max_reads`, written only when a
|
|
142
|
+
cap bit, and the live view shows `reads 37/120`.
|
|
143
|
+
- `tldrx next --commit --cost-usd <n> [--tokens <n>]` — so a host session can declare what its
|
|
144
|
+
own sub-agent cost. Without it the task is `cost_usd: null, metered: false`.
|
|
145
|
+
- New `budget.raised` event with before/after for both ceilings, the actor and an optional
|
|
146
|
+
`--note`; `--take-from <phase>` moves money instead of adding it, refusing to cut a donor
|
|
147
|
+
below what it has already spent.
|
|
148
|
+
- `map.refreshed` is finally emitted — providers, document count, repo count — recorded against
|
|
149
|
+
the newest OPEN run, and the command says which. It had been in the §2.9 enum and the replay
|
|
150
|
+
renderer since v0 with nothing emitting it.
|
|
151
|
+
- `src/cli/signals.ts` — SIGINT/SIGTERM kill the sub-agent's whole process tree, record a
|
|
152
|
+
partial `agent.result` carrying `cost_usd: null` and `stopped_by: "signal"`, demote `running`
|
|
153
|
+
→ `ready`, release the lock, restore the cursor and exit `130`. A second signal exits at
|
|
154
|
+
once. A `--prepare` bundle with nothing spawned is left alone.
|
|
155
|
+
- **`.tldrx/.lock`, a workspace lock held across read-modify-write.** Two processes appending to
|
|
156
|
+
`facts.yml` each computed `max(id) + 1`, each got `F001`, and the second save erased the
|
|
157
|
+
first fact outright. `FactsStore.update` holds the lock across load → append → save, and
|
|
158
|
+
`run new` holds it across its whole creation.
|
|
159
|
+
- Two new waiting kinds, `running` and `prepared`, so a stage killed between `--prepare` and
|
|
160
|
+
`--commit` is no longer reported as `ready` and re-spawned. `tldrx next` refuses to re-spawn
|
|
161
|
+
over a bundle (exit `2`) naming all three ways out; `--discard-pending` is the explicit "bin
|
|
162
|
+
it and run it again". Phases with an executor are exempt — Build stays `running` across
|
|
163
|
+
cycles by design.
|
|
164
|
+
|
|
165
|
+
**Experts**
|
|
166
|
+
|
|
167
|
+
- **A stage prompt now carries what its experts LEARNED, not only who they are.** Each loaded
|
|
168
|
+
expert contributes its `expert.md` body, its **star chart** (one line per area, computed from
|
|
169
|
+
evidence) and its **knowledge files**, most-recently-trained first by the file's own
|
|
170
|
+
`trained_at`, never an mtime. The prompt states that those `[src: …]` tokens already resolved
|
|
171
|
+
when the knowledge was accepted and may be reused verbatim as evidence. Measured on a fixture:
|
|
172
|
+
**1,493 bytes → 4,758 bytes**, with both areas' findings inlined.
|
|
173
|
+
- **A third expert-loading rule.** `experts:` and `stack_experts:` could not reach an expert
|
|
174
|
+
`init` seeded from this workspace's own folders. Now a `kind: domain` expert whose
|
|
175
|
+
front-matter `repos:` or `## Domain` paths intersect the run's repos or cited paths is loaded
|
|
176
|
+
too — path matches ranked first, capped at 8, deduped, deterministic. A `repos:` match only
|
|
177
|
+
counts in a workspace declaring two or more repos, because in a single-repo workspace it
|
|
178
|
+
selects everybody. Rank is a score: a direct path match is 10, a path within 2 hops in
|
|
179
|
+
`graphify-out/<repo>/graph.json` is 1, scores add, and score 0 means body only. The graph walk
|
|
180
|
+
is undirected, bounded, and degrades to an empty set on a missing or unparseable graph
|
|
181
|
+
(measured: 298 neighbour paths in 14 ms over a 5,091,949-byte graph).
|
|
182
|
+
- **`tldrx init` seeds five ROLE experts** — `product`, `architect`, `delivery`, `developer`,
|
|
183
|
+
`operations`, the names the shipped stage files have always listed and of which `init` seeded
|
|
184
|
+
only the first. A role expert's subject is the workflow, so its body ships as an editable
|
|
185
|
+
`templates/experts/<role>.md`, copied in once and yours after that. `kind: role` keeps it out
|
|
186
|
+
of the domain-match rule. Seeding is additive: an existing workspace gains the four missing
|
|
187
|
+
folders and keeps every `expert.md` it had, byte-for-byte. `tldrx expert create <name> --role
|
|
188
|
+
<slug>` writes the same seed on demand, and says which template it used.
|
|
189
|
+
- `.tldrx/experts/<name>/training.jsonl` (spec §2.6.1) — the §2.9 envelope with `run` replaced
|
|
190
|
+
by `expert` and `stage` by `area`, because training outlives every run. A REFUSED run still
|
|
191
|
+
writes its `agent.result`: money spent is recorded whether or not the knowledge was kept.
|
|
192
|
+
- `kind: test` is a first-class evidence kind at weight 1.0, the same as `code` — a test read or
|
|
193
|
+
run is a direct observation of behaviour.
|
|
194
|
+
- `tldrx expert list` gains total **evidence** and per-area **levels** columns, an ASCII star
|
|
195
|
+
chart, and a `loaded by: what (named), how (stack)` line per expert derived from the same
|
|
196
|
+
selection rule `next` runs — so "trained and never loaded" stops being invisible. `--json`
|
|
197
|
+
gains `evidence_count`.
|
|
198
|
+
- `tldrx next --prepare` prints one line per loaded expert with its bytes, plus `truncated` and
|
|
199
|
+
`NOT LOADED` where they apply, and `pending.json` gains an `experts:` array with the same
|
|
200
|
+
numbers, the reason each loaded and its knowledge file paths. An expert with zero evidence
|
|
201
|
+
anywhere earns one **stderr** note naming its train command — never a block.
|
|
202
|
+
|
|
203
|
+
**Knowledge that has to be worth something**
|
|
204
|
+
|
|
205
|
+
- **A citation must sustain its claim, not only resolve.** Making every `src` resolvable is a
|
|
206
|
+
check on the citation and says nothing about the sentence. An **execution claim** now needs a
|
|
207
|
+
command src — `execution claim needs a '$ <cmd> → exit <n>' src, not a file line` — because a
|
|
208
|
+
real `knowledge/aparece-api.md` asserted `dotnet build` exit 0, "measured, exit code captured
|
|
209
|
+
unpiped", citing `.tldrx/workspace.yml:19`: the line that *declares* the command. It claimed
|
|
210
|
+
"78/78 passed, exit 0" citing a line of the test script. Every citation resolved; none was
|
|
211
|
+
evidence anything ran. The rule reads prose paragraphs as well as bullets, because that header
|
|
212
|
+
IS a paragraph and its tokens sit mid-line where a line-anchored parser never looks. Measured
|
|
213
|
+
after: 7 refusals on that file, 1 on `aparece-platform`, 0 on the third.
|
|
214
|
+
- **Three warnings that cost a citation its evidence row without rejecting the file:**
|
|
215
|
+
`paraphrase` (the bullet is ≥ 90% a verbatim substring of the ±3-line neighbourhood of the
|
|
216
|
+
line it cites), `outside domain` (the path is outside the expert's own `## Domain` — and the
|
|
217
|
+
expert whose domain does contain it is named), and `duplicate src` (already on record for this
|
|
218
|
+
expert). None of them is a lie; they are ways of being worth nothing, and the honest response
|
|
219
|
+
is a level that does not move. Measured on the real corpus: 57 outside-domain and 7 duplicate
|
|
220
|
+
warnings across 248 bullets.
|
|
221
|
+
- **`## Sources` earns nothing.** It was 41 of 107 bullets in one real knowledge file and 18 of
|
|
222
|
+
56 in another, every one re-citing a source cited above it. It is still validated like any
|
|
223
|
+
other section; it just derives no evidence, and `countFindings` stops counting it.
|
|
224
|
+
- **A bullet may carry its own confidence** — ending in `(measured)` / `(inferred)` /
|
|
225
|
+
`(assumed)`, or leading with `*measured* —`, the other spelling the real corpus uses — parsed
|
|
226
|
+
onto the evidence row as `confidence:`. Both spellings are stripped before the execution rule
|
|
227
|
+
matches: inside the annotation the word is a LABEL, and refusing a file for obeying §2.3's own
|
|
228
|
+
"say which of measured / inferred / assumed each claim is" would be the rule being wrong.
|
|
229
|
+
- **`tldrx expert list` warns on a shared citation** — `warning: shared citation <file:line> by
|
|
230
|
+
<a>,<b> — check for contradiction`, on stderr, when two experts cite one line with bullets
|
|
231
|
+
whose normalised texts differ. 16 files on the real workspace were cited by two trained
|
|
232
|
+
experts each and nothing compared what the two said. It resolves nothing on purpose: deciding
|
|
233
|
+
which expert is right is not something a deterministic tool can do.
|
|
234
|
+
- **The Build executor writes `retro.md` as the run goes.** Role experts train from
|
|
235
|
+
`tldrx-work/<run>/**/{handoff,retro}.md` and nothing else, and all five sat at level 0 because
|
|
236
|
+
`retro.md` existed only when a human typed `tldrx retro`. Build now appends `## Build
|
|
237
|
+
feedback` as each story settles — every reviewer `changes` verdict and finding, every DoD
|
|
238
|
+
command that failed on the first attempt with its exit code, every merge conflict, and (read
|
|
239
|
+
back off `events.jsonl`, since they happen between invocations) every gate rejected and every
|
|
240
|
+
approval revoked, with its note and what it staled. Deterministic, deduped verbatim, every
|
|
241
|
+
bullet carrying a `[src: …]` into the review log or the events line. `tldrx retro` carries the
|
|
242
|
+
section forward instead of overwriting it.
|
|
243
|
+
|
|
244
|
+
**Dashboard**
|
|
245
|
+
|
|
246
|
+
- Redesigned, and it now draws in the browser: the model rides inline in a
|
|
247
|
+
`<script type="application/json">` and every view is rendered client-side, so `--static` is
|
|
248
|
+
the same document with no server behind it. Five views behind a hash route — runs, run
|
|
249
|
+
detail, experts, watchers, how-to. `render.ts` stays TypeScript against `DashboardModel`, so
|
|
250
|
+
`tsc --strict` is still on the markup.
|
|
251
|
+
- Citations are first-class: `[src: …]` renders as a reference chip and `[assumption]` /
|
|
252
|
+
`[inference]` as a flag, marked in the DOM so the model's `handoffHtml` is styled and never
|
|
253
|
+
re-parsed.
|
|
254
|
+
- `runs[].waiting` is the CLI's own `{kind, message, questions}` from one shared derivation
|
|
255
|
+
(`src/core/run/waiting.ts`), and `dependsOn` / `blockedBy` / `runnable` / `order` / `chains`
|
|
256
|
+
come from the same `triage.depends_on` resolver `tldrx status` uses
|
|
257
|
+
(`src/core/run/dependencies.ts`). Two screens, one answer.
|
|
258
|
+
- ORDER is the default sort; the first runnable run wears the same `← next` marker the CLI
|
|
259
|
+
prints; chains draw as root-to-leaf PATHS, so every arrow is a real edge. An attention line
|
|
260
|
+
mirrors `tldrx status`.
|
|
261
|
+
- Stage rows carry `gatePolicy`, the execution path gains a `signed by` column, and the section
|
|
262
|
+
counts them the way the CLI does.
|
|
263
|
+
- The server checks `Host`. A request that does not name `127.0.0.1`, `localhost`, `::1` or the
|
|
264
|
+
host it was told to bind gets 403 before the method check — binding loopback is necessary,
|
|
265
|
+
not sufficient, when a page on a name the attacker owns can point a browser at 127.0.0.1.
|
|
266
|
+
Only the name is compared, never the port, so `ssh -L` and container port maps keep working.
|
|
267
|
+
|
|
268
|
+
**Build**
|
|
269
|
+
|
|
270
|
+
- Story branches are `story/<run-id>/<story-id>` and their worktrees
|
|
271
|
+
`.tldrx/worktrees/<repo>/<run-id>-<story-id>`, so two runs of one plan cannot collide.
|
|
272
|
+
- An `epic/<slug>` branch this run did not cut is refused (exit `2`, nothing spent) unless
|
|
273
|
+
`tldrx next --reuse-epic`. What the run cut or adopted is recorded as `build.epic_branch`.
|
|
274
|
+
|
|
275
|
+
### Changed
|
|
276
|
+
|
|
277
|
+
- **The level ladder weighs findings, not files.** `W = Σ (recency × weight)` with
|
|
278
|
+
`recency = max(0.25, 1 - ageDays/365)`, `weight = code 1.0 · run 1.0 · test 1.0 · answer 0.8 ·
|
|
279
|
+
doc 0.5`, **× 2** when the row is `cross: true` (a finding tying two or more distinct files
|
|
280
|
+
together) and **× 0.5** when `confidence: assumed`. Thresholds `[0.5, 1.5, 3, 6, 20]` (the
|
|
281
|
+
fifth was 12), then three caps in order: no `kind: run` row ⇒ `min(level, 3)`; level 5 needs
|
|
282
|
+
≥ 2 distinct kinds, else 4; `level ≤ count(distinct src)`. Three things changed here at once,
|
|
283
|
+
and **levels move: run `tldrx expert recompute` after upgrading.**
|
|
284
|
+
- *Reading alone now caps at 3.* Measured: an expert holding 15 `code` + 2 `test` rows, all
|
|
285
|
+
written the same afternoon by one reading session with no command ever executed, computed
|
|
286
|
+
**5/5**. Reading is evidence that code *says* something; only a run is evidence that it
|
|
287
|
+
*does* it. A `run` row is necessary, not sufficient: one alone is `W = 1.0`, level 1.
|
|
288
|
+
- *Recency decays continuously and the 180-day staleness cap is gone.* It was a cliff: an
|
|
289
|
+
expert trained on day 179 and the same expert on day 181 knew identical things and the
|
|
290
|
+
ladder reported 4 and 2. Knowledge fades; it does not expire on a Tuesday. One continuous
|
|
291
|
+
factor, floored at 0.25 so a year-old reading is worth a quarter of a fresh one rather than
|
|
292
|
+
nothing, replaces both the four-band table and the cap.
|
|
293
|
+
- *A cross-file finding counts double, an `assumed` one half.* `cross:` and `confidence:` are
|
|
294
|
+
additive `evidence[]` fields derived from the bullet, never asserted; a row written before
|
|
295
|
+
they existed carries neither and computes as it always did. A model can re-derive anything
|
|
296
|
+
one file says by reading it; what it cannot re-derive is the relationship between two.
|
|
297
|
+
- **The training prompt asks for value, not coverage.** It used to say "Citing the same file
|
|
298
|
+
twelve times is worth one row; reading twelve files is worth twelve" — an accurate description
|
|
299
|
+
of the old formula, and a Goodhart instruction. Both prompts (the spawned one and
|
|
300
|
+
`--print-prompt`) now carry the same criterion word for word: a finding is something a model
|
|
301
|
+
could not re-derive by reading that one file once — cross-file contradictions, dead paths,
|
|
302
|
+
defaults that differ from their docstrings, absences written as a negative claim, measured
|
|
303
|
+
commands. Restating a docstring is not a finding.
|
|
304
|
+
- **Light mode's file selection is bounded by `## Domain`.** Only files inside the expert's
|
|
305
|
+
declared folders are scored, read or inlined; every file inside them is a candidate even when
|
|
306
|
+
it greps for nothing. Bounding the input is cheaper than warning about the output.
|
|
307
|
+
- **One shared knowledge budget, declared inputs first.** `inputs_max_bytes` (default 96 KB) is
|
|
308
|
+
spent on every declared input in declaration order; the loaded experts then share
|
|
309
|
+
`knowledge_max_bytes` (default 48 KB) **in total**, split by rank, never one budget each. The
|
|
310
|
+
retired `expert_knowledge_bytes` is still read, as the same total — a per-expert cap scales
|
|
311
|
+
with a number nobody set, which is how 64 KB became 83,523 measured bytes. Truncation cuts at
|
|
312
|
+
an H2 boundary and appends `… N more findings in …`; a file whose first section already blows
|
|
313
|
+
the budget is named, not half-inlined. Measured on one real stage: `01-what/what` prepared a
|
|
314
|
+
**159,575-byte** prompt — 45% declared inputs, 52% expert bodies and knowledge, eight of its
|
|
315
|
+
nine experts loaded because they shared a repo with the run and not one had read a file the
|
|
316
|
+
run cited — and the 64 KB seed budget had dropped `ADR-D013-DELIVERY-ZONE-GEOMETRY.md`
|
|
317
|
+
(5,863 B) whole, the sixth of the six decisions the run existed to settle, while 70,923 B of
|
|
318
|
+
unrequested knowledge went in untouched. The same prompt is now **85,676 bytes**, loads two
|
|
319
|
+
experts, and contains ADR-D013 in full.
|
|
320
|
+
- **The prompt is ordered for the cache**: `stage.md`, expert blocks, `## Inputs`,
|
|
321
|
+
`## Previous attempt` — most stable to least. Measured, two real `claude` 2.1.251 calls, the
|
|
322
|
+
same 40,715-byte prompt, separate processes and sessions: call 1 wrote 37,059 cache tokens
|
|
323
|
+
and cost **$0.074982**; call 2 read all 37,059 back and cost **$0.004550** — 16.5x less.
|
|
324
|
+
`cache_creation_input_tokens` and `cache_read_input_tokens` are now parsed, published on the
|
|
325
|
+
`cost` event and written to `agent.result`.
|
|
326
|
+
- **Attempt 2 gets the refused draft.** The declared outputs that exist on disk are inlined
|
|
327
|
+
under `### Previous attempt — edit, do not restart`, capped at 32 KB shared across them.
|
|
328
|
+
Before this, a stage rejected over one missing section paid full price to rewrite four
|
|
329
|
+
documents from a blank page.
|
|
330
|
+
- **`claude` is spawned with `--output-format stream-json --verbose`** and the JSONL is parsed
|
|
331
|
+
as it arrives. Verified against one real measured call: `--verbose` is required (without it
|
|
332
|
+
`stream-json` in print mode refuses before spending anything) and `--json-schema` coexists
|
|
333
|
+
with it, the last `result` event carrying `structured_output` exactly as the single-blob
|
|
334
|
+
format did. `resolveResultDoc` reads EITHER format, so an older `claude` still works.
|
|
335
|
+
- **A progress view, on by default, on the four commands that make you wait** — `next`,
|
|
336
|
+
`run auto`, `expert train`, `seed triage --propose`. `--ui scene|compact|plain|off` or
|
|
337
|
+
`TLDRX_UI`, `auto` by default. Every summary is derived from bytes the sub-agent was already
|
|
338
|
+
sending: no second model call, no summary agent. **stdout is never written to** — asserted
|
|
339
|
+
end-to-end by comparing stdout with the view on and off — and the cursor is restored on a
|
|
340
|
+
normal exit, a thrown error and Ctrl-C. `--prepare`, `--commit` and `--dry-run` spawn nothing
|
|
341
|
+
and show nothing.
|
|
342
|
+
- **Ambiguity is refused rather than guessed.** With more than one open run and no explicit id,
|
|
343
|
+
every run-targeting command exits `2` and lists the candidates. Selectors: positional `<run>`
|
|
344
|
+
on `next` and `run status`, `--run <id>` on the rest. `tldrx run status` with several open
|
|
345
|
+
prints a table and exits `0`; `--json` returns `{ "runs": [...] }`, and the single-run shape
|
|
346
|
+
is unchanged when exactly one is open. Hooks never block on the ambiguity — a refusal
|
|
347
|
+
reaching a `PreToolUse` decision would stop work the human never asked to stop — and the
|
|
348
|
+
status line appends `(+N open)`.
|
|
349
|
+
- **An unknown flag is refused instead of ignored.** `tldrx status --nope` exited `0` having
|
|
350
|
+
run with its defaults after being told something it dropped. The guard lives in the
|
|
351
|
+
dispatcher, driven by the help registry, so it covers the commands that never used
|
|
352
|
+
`parseArgs` too, and it scans argv the way `parseArgs` does so a VALUE that looks like a flag
|
|
353
|
+
is not mistaken for one. `hook` and `statusline` forward their argv and are judged not at all.
|
|
354
|
+
- **`--json` is supported or it is an error.** It had three behaviours: supported,
|
|
355
|
+
accepted-and-ignored (`doctor`, `watch list`, `tickets status`) and refused (`map --check`).
|
|
356
|
+
`doctor --json` and `watch list --json` now print the data they already had structured;
|
|
357
|
+
everywhere else it is exit `1` with `--json is not supported by <cmd>`. `doctor`'s
|
|
358
|
+
`mcp: null` means NOT PROBED — "no servers" is a different claim.
|
|
359
|
+
- **An unknown COMMAND exits `1`, not `64`.** `64` means "on the roadmap, not built", which a
|
|
360
|
+
mistyped word has no business claiming. It is reserved and currently unreachable, and that is
|
|
361
|
+
asserted.
|
|
362
|
+
- **`tldrx status` stops counting advice as work.** A freshly initialised workspace printed
|
|
363
|
+
seven numbered items, five of them seeded experts at level 0 repeating the same sentence.
|
|
364
|
+
They collapse into one uncounted line under the blockers, with the trainable ones named and
|
|
365
|
+
one runnable example. `WorkspaceStatus.items` now means BLOCKERS and `pending` counts only
|
|
366
|
+
those; `--json` keeps `items` shape-identical and adds `advice` beside it.
|
|
367
|
+
- **`tldrx replay`'s usage stops requiring an id it does not require.** With no id it narrates
|
|
368
|
+
the newest run and refuses (exit `2`) only when several are open.
|
|
369
|
+
- **`tldrx tickets sync` previews by default; `--apply` writes.** It is the only verb that
|
|
370
|
+
reaches a third party, and a destructive default on the one networked command is backwards.
|
|
371
|
+
`--provider` no longer switches on a workspace set to `ticket_tool.kind: none` — it picks
|
|
372
|
+
between configured providers, and the config is not a flag's to override.
|
|
373
|
+
- **`ticket_tool.sync` means something**: `mirror-out` pushes and reads nothing back;
|
|
374
|
+
`two-way` also pulls each issue's status string, **verbatim**, into `external_status:` and
|
|
375
|
+
into nothing else. The name is generous — the second direction is one opaque string into one
|
|
376
|
+
front-matter key.
|
|
377
|
+
- **`version: 1` is the key; `schema_version:` is deprecated for one release.** The spec said
|
|
378
|
+
`version: 1` from the first draft and `tldrx init` has always written it, yet seven skeleton
|
|
379
|
+
validators demanded `schema_version` and seven templates printed `schema_version: 0` — so the
|
|
380
|
+
shipped validators rejected the tool's own output (5 of 5 real files failed on that key; 0 do
|
|
381
|
+
now). One `requireVersion` is the rule everywhere. A file still on the old key LOADS, prints
|
|
382
|
+
`<file>: schema_version is deprecated — say version: 1` on stderr once per process, and is
|
|
383
|
+
listed by `tldrx doctor`. `templates/*.yml` and `env.yml` now say `version: 1`.
|
|
384
|
+
- **The DoD gate no longer runs an un-allowlisted command.** `runDodCommand` handed the model's
|
|
385
|
+
own string to `/bin/sh -c` and the hook ships enabled by default with a 960 s timeout: a
|
|
386
|
+
story saying `dod: rm -rf ~` ran it the moment someone marked the story done. A command must
|
|
387
|
+
now be byte-equal to a `workspace.yml` command and is spawned argv-split with **no shell**; a
|
|
388
|
+
bare metacharacter refuses. An EMPTY `commands:` now refuses every dod entry instead of
|
|
389
|
+
permitting anything.
|
|
390
|
+
- **`budget-gate` covers every spender and fails CLOSED.** It matched `^(claude -p|tldrx next)`
|
|
391
|
+
only, so `run auto` — the one command that can spend a whole run in one invocation — plus
|
|
392
|
+
`expert train` and `seed triage --propose` walked straight past it. All five are matched and
|
|
393
|
+
priced, and an unreadable `run.yml`/`budget.yml` now denies and names the file: "cannot read
|
|
394
|
+
the budget" is not "the budget is fine". It still allows silently outside a workspace.
|
|
395
|
+
- **`--yolo` no longer reaches the reviewer.** `--dangerously-skip-permissions` was passed to
|
|
396
|
+
the read-only reviewer sub-agent, which was the one whose read-only-ness was the point. The
|
|
397
|
+
developer still gets it; that one is meant to write.
|
|
398
|
+
- **Unmetered is not zero.** An in-session `--commit` with no declared cost recorded `$0.00`,
|
|
399
|
+
so a ledger could read "$0.00 spent" after real money had gone. Such a task is now
|
|
400
|
+
`cost_usd: null` + `metered: false` (a null cost without the flag is a schema error). Sums
|
|
401
|
+
treat it as nothing, and every report says so: `budget show` and `run status` render
|
|
402
|
+
`unmetered (in-session)` and call `spent` a LOWER BOUND.
|
|
403
|
+
- **Build cannot charge 2.5x its phase, and Watch's floor cannot exceed its ceiling.** Build's
|
|
404
|
+
shares are divided by the worst case — stories × attempts × (1 + reviewer share) — instead of
|
|
405
|
+
by story count. Watch refuses BEFORE spawning when N features cannot each get the $0.25 spawn
|
|
406
|
+
floor inside the ceiling, naming the `budget raise` that fits.
|
|
407
|
+
- `run.yml` and `budget.yml` are written temp + `rename`, and `budget.yml` ceilings are re-read
|
|
408
|
+
from disk before every write unless this store deliberately changed them — a `budget raise`
|
|
409
|
+
that landed while a stage was in flight used to be silently reverted when that stage saved.
|
|
410
|
+
- `EventLog.read` skips an unparseable line instead of throwing. `events.jsonl` is appended line
|
|
411
|
+
by line, so a process killed mid-write leaves half an object on the last line, and `replay`
|
|
412
|
+
reported "events.jsonl could not be read" for a ledger whose first four hundred lines were
|
|
413
|
+
good. Skips are counted, not swallowed: `readAll()` returns `{events, lines, skipped}` and the
|
|
414
|
+
note is printed once per file. Line numbers now come from the reader, so a torn line no longer
|
|
415
|
+
shifts every `L<n>` after it.
|
|
416
|
+
- A job stopped halfway now looks stopped halfway: `seed apply` writes `status: applying` before
|
|
417
|
+
the loop and grows `created_runs` after each run; `seed triage --propose` writes its `.agent/`
|
|
418
|
+
bundle in headless mode too; `expert train` writes `knowledge/<area>.md.partial` and renames
|
|
419
|
+
it onto the real name only after the file validates (`.md.partial` never matches `*.md`, so
|
|
420
|
+
nothing half-written can be inlined).
|
|
421
|
+
- `tldrx init` and `.tldrx/init-questions.md` both point at `tldrx interview --init`. Measured:
|
|
422
|
+
the `answer-capture` hook returns early unless the path has a `tldrx-work` segment, so a
|
|
423
|
+
hand-typed answer in the init questions file records no fact, logs no event, and never reaches
|
|
424
|
+
the code that writes `process.yml`. The `[Answer]:` slot text stays — `captureAnswers` and
|
|
425
|
+
`tldrx answer` read and write it.
|
|
426
|
+
- `tldrx interview --init` applies the two process answers to `.tldrx/process.yml`
|
|
427
|
+
(`methodology`, `ticket_tool.kind`, and for GitHub the `owner/repo` from the git remote), and
|
|
428
|
+
ends with one line saying which happened. The two process questions are reordered so option
|
|
429
|
+
**A** is "None" for both, which makes `--yes-to-defaults` a real default there rather than a
|
|
430
|
+
guess. It remains a guess on the ownership and dead-code questions, so it stays a human's
|
|
431
|
+
flag.
|
|
432
|
+
- `tldrx tickets status` validates `process.yml` **before** the no-run check, so a broken
|
|
433
|
+
adapter config is reported as a config error rather than as "no run".
|
|
434
|
+
- The SessionStart hook appends up to three lines of the `tldrx status` report after the three
|
|
435
|
+
it always printed, so a session opening on work that is NOT a run — a proposed split,
|
|
436
|
+
unanswered setup questions, untrained experts — is no longer greeted with silence. Nothing
|
|
437
|
+
pending AND no run is still no output at all.
|
|
438
|
+
- `--seed` is repeatable: several are merged, deduped and re-sorted, with the 50-file cap
|
|
439
|
+
applied to the merged set rather than per argument. One occurrence is byte-for-byte what it
|
|
440
|
+
always was. `run new --seed` also prints one **stderr** note over the threshold or over 10
|
|
441
|
+
files, naming `tldrx seed triage`.
|
|
442
|
+
- `run.yml` gains an optional `triage: {split, depends_on}` block, written only by `seed apply`
|
|
443
|
+
and absent everywhere else.
|
|
444
|
+
- The `/tldrx` skill is "status → guide": step 1 is always `tldrx status --json`, then it walks
|
|
445
|
+
the items one at a time, asking when the decision is the human's and acting when the step is
|
|
446
|
+
mechanical, re-running `tldrx status` after each. 199 → 149 lines.
|
|
447
|
+
- Package `tldr-experts` now installs two commands, `tldrx` and `tldr-experts`. (Unscoped
|
|
448
|
+
`tldrx` as a package name is refused by npm's similarity rule; 0.0.1–0.2.0 were unpublished
|
|
449
|
+
on 2026-08-29 and their numbers can never be reused, so this is the first version back on the
|
|
450
|
+
registry.) README gains a release table with defined status tags, and npm/CI badges.
|
|
451
|
+
- `.claude/settings.json.bak-tldrx-*` is gitignored — `install --claude` backs the file up
|
|
452
|
+
before merging into it, and that backup was the one thing the framework writes that nothing
|
|
453
|
+
ignored.
|
|
454
|
+
|
|
455
|
+
- **Documented, from measurement: `--max-budget-usd` is a stop, not a cap.** `--max-usd 1.5`
|
|
456
|
+
over two repos: the sub-agent was killed with `error_max_budget_usd` ("Reached maximum budget
|
|
457
|
+
($1.5)") **after** `total_cost_usd: 5.15325`, on a single turn (597 s, 105,698 cache-creation
|
|
458
|
+
+ 60,548 output tokens, 1M-context model). The flag ends a run once a turn's cost is known; it
|
|
459
|
+
cannot end a turn already in flight. Budgets gate before and reconcile after, and overshoot is
|
|
460
|
+
bounded by one turn. The pipeline itself held: a non-zero `claude` exit is a failed run, so
|
|
461
|
+
nothing reached `competencies.yml`, the $5.15 was recorded in `training.jsonl`, and the
|
|
462
|
+
knowledge file the agent had already finished writing was **quarantined** — it would in fact
|
|
463
|
+
have validated (111 sourced items, 21 distinct files, level 5), which is exactly why leaving
|
|
464
|
+
it where an accepted one lives would have been the dangerous outcome.
|
|
465
|
+
- **Documented, from measurement: nested `claude -p` works; the ceiling was the constraint.** A
|
|
466
|
+
cold session pays ~10–26k cache-creation tokens before its first reply, so any
|
|
467
|
+
`--max-budget-usd` under about $0.25 fails as `error_max_budget_usd` before work starts. That
|
|
468
|
+
is why every spawn has a $0.25 floor and refuses below it. `--prepare`/`--commit` survives
|
|
469
|
+
because it is *cheaper* and because it works where spawning is disallowed — not because
|
|
470
|
+
spawning fails.
|
|
471
|
+
|
|
472
|
+
### Fixed
|
|
473
|
+
|
|
474
|
+
- **`claim-sources` verified six `src` kinds it used to wave through.** Measured probe: a
|
|
475
|
+
handoff citing `[src: F999]`, `[src: Q42]`, `[src: graph:i-made-this-up]` and
|
|
476
|
+
`[src: absent:ops/backup.yml]` to assert "we removed the auth check from /admin" validated
|
|
477
|
+
CLEAN, closed its own auto gate and advanced the cursor — `resolveSrc` returned `ok` by
|
|
478
|
+
default for six of eight kinds. Now `F<n>` must be a live (non-retired) row in `facts.yml`,
|
|
479
|
+
`Q<n>` a question this run actually asked, `graph:<node>` a node in the graph or a token named
|
|
480
|
+
in `.tldrx/map/`, and `absent:` may only source a NEGATIVE claim (`## Unknowns` is exempt —
|
|
481
|
+
that heading IS the negation). A third outcome, **`unverified`**, sits between ok and refused
|
|
482
|
+
for what cannot be checked offline; it never fails a stage, it stops an auto gate.
|
|
483
|
+
- **A `[src: …]` wrapped in backticks is a citation, not a missing one.** `TRAILING_TOKEN_RE`
|
|
484
|
+
was anchored to end-of-line, so `` `[src: x]` ``, `[src: x].` and `([src: x])` all read as
|
|
485
|
+
unsourced. A real user's first `tldrx next` was refused with "9 unsourced bullet(s)" when all
|
|
486
|
+
nine carried citations — $0.40 spent to be told the work had no evidence. Closing quotes,
|
|
487
|
+
brackets and terminal punctuation after the `]` are now ignored; words after it still are not.
|
|
488
|
+
A line that TRIED to cite gets `malformed citation on line N`, not `unsourced bullet` — the
|
|
489
|
+
two need different advice.
|
|
490
|
+
- **An auto gate can no longer be closed by silence.** Three ways it could be: condition 5
|
|
491
|
+
counted only refusals, so an unverifiable citation passed (it is now zero refused AND zero
|
|
492
|
+
unverified); a stage that declared `questions.md` as an output and wrote one the §2.7 parser
|
|
493
|
+
could not read had "0 open questions" recorded as satisfied — a real stage wrote
|
|
494
|
+
`### Q1 — …` / `**Answer:**`, copied faithfully from this repo's own `templates/questions.md`,
|
|
495
|
+
and the gate signed itself over four unanswered questions (unreadable or empty now falls to
|
|
496
|
+
the human gate naming the ids it could not see, and `next --commit` refuses the same file with
|
|
497
|
+
exit `5`); and `templates/questions.md` IS the grammar now, with one worked example, inlined
|
|
498
|
+
into every `stage.md` that may write questions.
|
|
499
|
+
- **An approval could not be revoked.** `approve()` moves the cursor in the same transaction
|
|
500
|
+
that signs the gate, and `reject` only ever looked at the cursor, so the probe above met
|
|
501
|
+
`REJECT REFUSED: nothing to reject`. A machine that can sign but cannot be overruled is not a
|
|
502
|
+
gate — hence `reject --stage` above.
|
|
503
|
+
- **`--print-prompt` told everyone they had no repos.** `expert train … --print-prompt` handed
|
|
504
|
+
`loadWorkspaceFile` the `.tldrx/` directory, but that function joins `.tldrx/workspace.yml`
|
|
505
|
+
onto its argument — so it looked for `<root>/.tldrx/.tldrx/workspace.yml`, threw, and a bare
|
|
506
|
+
`catch` turned the failure into an empty list. Every printed prompt on every real workspace
|
|
507
|
+
said "none declared … run `tldrx init` first". It names them now, `tldrx map` and
|
|
508
|
+
`tldrx expert` agree about what that function takes, and a genuine read failure prints a
|
|
509
|
+
warning on stderr instead of disappearing. The headless path never had the bug.
|
|
510
|
+
- **`kind: test` was dropped without a word.** The evidence kinds were `code`, `run`, `doc`,
|
|
511
|
+
`answer`; the train prompt said to write `{kind, src, at}` and never said which kinds exist; a
|
|
512
|
+
session wrote two `kind: test` rows and both vanished on read, so `expert list` printed 15
|
|
513
|
+
evidence over a file holding 17. `test` is a first-class kind now, the prompt lists all five
|
|
514
|
+
with a one-line meaning each (rendered from a total record, so adding a kind without
|
|
515
|
+
explaining it will not compile), and an unrecognised kind is never silent again — `expert
|
|
516
|
+
list` (stderr, so it survives `--json`), the dashboard model and training's merge all report
|
|
517
|
+
`N evidence row(s) ignored — unknown kind '<x>'`.
|
|
518
|
+
- **Light-mode training was structurally incapable of exceeding level 3.** `codeEvidence`
|
|
519
|
+
dropped every `cmd` ref on the floor, so a sub-agent that ran the suite and cited
|
|
520
|
+
`[src: $ npm test → exit 0]` earned nothing for it — and the new run cap then held it at 3
|
|
521
|
+
whatever it measured. One `run` row per distinct command **and exit code** now. The training
|
|
522
|
+
prompt also said "do not run anything" while `allowedTools` already granted a
|
|
523
|
+
`Bash(<command>)` per `workspace.yml` command; it now names those commands verbatim from the
|
|
524
|
+
same list that becomes the grant, forbids everything else, and says that citing
|
|
525
|
+
`[src: $ <cmd> → exit <n>]` is the only way the expert earns a `run` row.
|
|
526
|
+
- **An evidence `src` is validated against its `kind`, both directions.** Nothing checked
|
|
527
|
+
before, so `{kind: run, src: "the tests pass"}` counted as a run — one row's difference
|
|
528
|
+
between level 3 and level 4. The §2.8 grammar decides now, through the same `classifySrc` the
|
|
529
|
+
`claim-sources` hook uses. Reading warns and drops (a `competencies.yml` may have been
|
|
530
|
+
hand-edited); writing refuses outright, because everything reaching it was derived by the
|
|
531
|
+
framework from a file the framework already validated.
|
|
532
|
+
- **The stage files named experts that `init` never seeds.** Measured on a real workspace whose
|
|
533
|
+
`.tldrx/experts/` held `product`, `dotnet-stack` and seven domain experts: four of the five
|
|
534
|
+
role names resolved to nothing and every How, Plan, Build and Watch run printed
|
|
535
|
+
`expert <name> — NOT LOADED`. Fixed by seeding the five roles; a stage naming an expert that
|
|
536
|
+
genuinely does not exist still says so.
|
|
537
|
+
- **A run nobody has started is `ready`, not "waiting at a gate".** The dashboard model derived
|
|
538
|
+
`pendingGate` from the stage gate objects — the first whose `gate.status` is `pending`, which
|
|
539
|
+
on a fresh run is every stage, because `pending` is the value the field is born with.
|
|
540
|
+
Measured on eight freshly applied runs: the page drew **8 red "waiting on a human" cards**
|
|
541
|
+
while `tldrx run status --json` said `waiting: {kind: "ready"}` for all eight. **0 cards
|
|
542
|
+
after.** `pendingGate`/`pendingQuestion` survive one release as documented aliases.
|
|
543
|
+
- Only a gate, a question or a failure raises an alert on the page: `ready` reads
|
|
544
|
+
"ready — `tldrx next <id>`" in the WAITING ON column instead of the blank "nothing" that made
|
|
545
|
+
a startable run look finished, and a run waiting behind a sibling raises nothing at all.
|
|
546
|
+
- Re-rendering the dashboard keeps the reader's place: handoff panel ids are derived from run +
|
|
547
|
+
phase rather than render order, so a `reload` restores the open panel and the scroll offset.
|
|
548
|
+
Verified live — scroll, route, open panel and all 180 citation marks survived.
|
|
549
|
+
- A dashboard path row is marked "waiting" only when the run is actually stopped at that stage
|
|
550
|
+
(every downstream gate also reads `pending`, which had painted four rows of five as an alert),
|
|
551
|
+
and the competency radar's viewBox was widened and its axis labels clipped to fit — a
|
|
552
|
+
24-character area name used to overflow the chart.
|
|
553
|
+
- **An ADR's status is read from the document, not from the cached inventory**, and `statusOf`
|
|
554
|
+
recognises a bulleted status line. Measured: thirteen ADRs, every one of them writing
|
|
555
|
+
`- Status: proposed — owner decision pending`, and the inventory reported `adrStatus: null`
|
|
556
|
+
for all thirteen. The field whose whole job is "is this document still current" answered "no
|
|
557
|
+
idea" for the commonest form there is.
|
|
558
|
+
- **A recycled pid made a `.lock` permanent.** `kill(pid, 0)` said alive forever, `next` exited
|
|
559
|
+
`2` forever, and the fix was knowing to delete a gitignored file by hand — hence
|
|
560
|
+
`run unlock`. And `cancelled` was a status in the schema with nothing that could write it, so
|
|
561
|
+
a run you had given up on stayed open forever and made every id-less command ambiguous —
|
|
562
|
+
hence `run cancel`.
|
|
563
|
+
- **Ctrl-C did not reach the sub-agent.** There was no signal handler on the run path at all.
|
|
564
|
+
A sub-agent is spawned detached (a timeout needs a process group to kill), so the terminal's
|
|
565
|
+
Ctrl-C never reached it: it kept running with `ppid 1`, kept billing against its
|
|
566
|
+
`--max-budget-usd`, and because a stage's cost is only written after the spawn returns, not a
|
|
567
|
+
cent of it appeared in `events.jsonl`. `dashboard` and `watch` keep their own exit-0 shutdown.
|
|
568
|
+
- **Four runs of one plan all cut `story/S1`.** The second found the branch already there,
|
|
569
|
+
`git worktree add` checked it out as it stood, and one run's commits landed on another's
|
|
570
|
+
branch — and the fourth reused the third's LIVE worktree, so two sub-agents were editing the
|
|
571
|
+
same files at the same time. Neither name can collide now.
|
|
572
|
+
- **`bun dist/tldrx.js` printed `·` where `node dist/tldrx.js` printed `·`.** The bundle was
|
|
573
|
+
correct UTF-8 all along; the `// @bun` header `bun build` emits tells the Bun *runtime*
|
|
574
|
+
"already transpiled, load raw", and on bun 1.3.14 that path decodes the file
|
|
575
|
+
byte-per-character. `scripts/build.ts` strips the marker from every emitted file, and `cmp` on
|
|
576
|
+
the two runs is exit 0 — measured cost, ~4 ms per Bun start over ten `--version` runs. A test
|
|
577
|
+
runs the built bundle under both and compares bytes.
|
|
578
|
+
- `bun test` no longer prints `fatal: Needed a single revision` twice: `execFileSync` inherits
|
|
579
|
+
the child's stderr, and the two assertions that prove Build cut no branch leaked git's
|
|
580
|
+
complaint into every run. The assertions are unchanged and still rest on the exit code.
|
|
581
|
+
- **The `/tldrx` skill's "PRE-ALPHA — some commands are still stubs and exit 64" warning was
|
|
582
|
+
false** (`grep -rn "implemented: false" src/cli/commands/` comes back empty). It says alpha
|
|
583
|
+
now, points at `tldrx --help` as the authority, and writes down three things a real session
|
|
584
|
+
spent five minutes reverse-engineering out of `dist/tldrx.js`. The skill and the README also
|
|
585
|
+
say plainly that `.tldrx/` and `tldrx-work/` are **committed**, and that `init` gitignores
|
|
586
|
+
exactly five machine-local paths.
|
|
587
|
+
- README and ROADMAP stop claiming a release that has not happened: both `npm i -g` lines say
|
|
588
|
+
so and keep the commands, and ROADMAP's four "shipped in 0.3.0" become "on main, unreleased
|
|
589
|
+
(0.3.0 pending tag)". `dashboard` is off the README's "refuses on ambiguity" list — it draws
|
|
590
|
+
every run in the workspace, so it has no single run to be ambiguous about.
|
|
591
|
+
|
|
592
|
+
### Removed
|
|
593
|
+
|
|
594
|
+
- **`gate.requires:` — from all five shipped stage files and from `StageGate`.** Nineteen
|
|
595
|
+
acceptance sentences that no gate enforced and no agent ever read: `normaliseGate`
|
|
596
|
+
(`src/core/run/workflowPreset.ts:216-231`) takes `.type`, `validateStage`
|
|
597
|
+
(`src/core/schemas/stage.ts:72-76`) checks `gate.type`, and the prompt ships `stage.md`, never
|
|
598
|
+
`stage.yml`. Removed from the type only — the validator never inspected the key, so a stage
|
|
599
|
+
library that still declares it keeps validating. The enforcement that IS real is `checks:`.
|
|
600
|
+
- **The placeholders `domain` and `stack` from the shipped `experts:` lists.** Neither was ever
|
|
601
|
+
an expert NAME: `stack_experts: true` already loads `<lang>-stack` for the run's repos, and a
|
|
602
|
+
`kind: domain` expert is picked by the paths the run cites. A forked or older stage file that
|
|
603
|
+
still lists them keeps working and gets ONE note — `experts: domain/stack are selected by
|
|
604
|
+
rule, not by name` — instead of a NOT LOADED line on every stage of every run. That line is
|
|
605
|
+
the one that matters when a real name is misspelled, and an operator who sees it every time
|
|
606
|
+
stops reading it.
|
|
607
|
+
|
|
608
|
+
## 0.2.0 — 2026-08-29
|
|
609
|
+
|
|
610
|
+
### The Build phase executes
|
|
611
|
+
|
|
612
|
+
`tldrx next` on `04-build` used to do what every other phase does: assemble one
|
|
613
|
+
prompt, spawn one sub-agent, validate its files. That is the wrong shape for a
|
|
614
|
+
phase whose work is a dozen agents in a dozen worktrees, so `04-build` now selects
|
|
615
|
+
a **wave executor**.
|
|
616
|
+
|
|
617
|
+
- **`waves.yml` is the schedule.** Wave by wave, story by story: resolve the
|
|
618
|
+
story's repo from `workspace.yml`, ensure `epic/<slug>` exists off the repo's
|
|
619
|
+
`default_branch`, open a worktree at `.tldrx/worktrees/<repo>/<story-id>` on
|
|
620
|
+
`story/<id>`, and spawn ONE developer sub-agent with its cwd inside it.
|
|
621
|
+
- **Done means proven.** After the agent, the facilitator re-runs the story's
|
|
622
|
+
fenced ```dod block **in that worktree** — the same runner `dod-gate` uses — and
|
|
623
|
+
every command must exit 0. Then it commits anything the agent left uncommitted
|
|
624
|
+
as `feat(<story-id>): <title>`, merges `story/<id>` into the epic with
|
|
625
|
+
`--no-ff`, and hands the diff to a **read-only reviewer** (`Read`, `Grep`,
|
|
626
|
+
`Glob`, `Bash(git diff *)`). A story reaches `done` only on DoD green **and** an
|
|
627
|
+
approval, and its `evidence:` is written from what was measured: `$ <cmd> →
|
|
628
|
+
exit 0` per command, the commit sha, the review path.
|
|
629
|
+
- **A failure costs one story, not the wave.** A red DoD or a merge conflict
|
|
630
|
+
blocks that story — the merge is aborted so the epic branch stays usable, and
|
|
631
|
+
the conflicting paths are recorded as its evidence — and the wave carries on.
|
|
632
|
+
- **A reviewer's `changes` requeues the story once**, with the review rendered
|
|
633
|
+
under `## Previous attempt` in the next prompt. A second `changes` blocks it.
|
|
634
|
+
- **Nothing ships.** No `git push` is run and no allowance grants one; no epic is
|
|
635
|
+
merged into a default branch. The phase ends at a human gate that lists the epic
|
|
636
|
+
branches ready to merge, per repo.
|
|
637
|
+
- **Safety.** A repo with uncommitted changes is refused **before** anything is
|
|
638
|
+
cut, naming the files and the fix (exit `2`, the stage stays `ready`).
|
|
639
|
+
`--dry-run` is refused outright: branches and commits are not revertible by a
|
|
640
|
+
flag. Worktrees are removed when a story reaches `done` or `blocked`, unless
|
|
641
|
+
`--keep-worktrees`.
|
|
642
|
+
- **`04-build/handoff.md` is generated, not asked for.** The executor holds the
|
|
643
|
+
exit codes and the merge results, so it writes the four §2.8 sections itself:
|
|
644
|
+
Findings cite `04-build/log/<story-id>.md`, the Evidence ledger is the dod
|
|
645
|
+
commands as `[src: $ <cmd> → exit <n>]`. One review log per story, always —
|
|
646
|
+
including a story blocked before a reviewer ran, so every citation resolves.
|
|
647
|
+
- **`run status` grows a Build line:** `04-build W1 [S1 done, S2 review]
|
|
648
|
+
W2 [S3 todo]` with per-story cost, read from the story files and the ledger.
|
|
649
|
+
A one-stage phase holding a dozen sub-agents cannot say anything with a
|
|
650
|
+
stage-level progress bar.
|
|
651
|
+
- **In-session:** `--prepare` bundles the NEXT pending story into
|
|
652
|
+
`.agent/<stage>/<story-id>/` (one story per cycle) and `--commit` continues that
|
|
653
|
+
story's pipeline from the DoD step.
|
|
654
|
+
|
|
655
|
+
### The executor plug-in point
|
|
656
|
+
|
|
657
|
+
`src/core/facilitator/executors/` is a map from **phase id** to executor; a phase
|
|
658
|
+
with no entry keeps the single-agent path. Everything either side — the lock, the
|
|
659
|
+
cursor, the budget gate, `run.yml`'s tasks, the outputs re-read off disk, the
|
|
660
|
+
checks and the gate — stays in `runNext.ts`, because an executor that could move
|
|
661
|
+
the cursor would be a second facilitator. An executor may force a human gate
|
|
662
|
+
(Build does) and may refuse without failing the stage (a dirty repo).
|
|
663
|
+
|
|
664
|
+
### Money
|
|
665
|
+
|
|
666
|
+
A stage's budget is split by the sub-agents an executor actually runs:
|
|
667
|
+
`min(stage budget ÷ stories, per_agent_max_usd, --max-usd)` for a developer, a
|
|
668
|
+
quarter of that share for its reviewer. The budget gate guards *starting* a stage,
|
|
669
|
+
so a mid-pipeline `--prepare` cycle is not charged the whole estimate again.
|
|
670
|
+
### `tldrx dashboard` — a live, read-only local server
|
|
671
|
+
|
|
672
|
+
`dashboard` without `--static` used to exit `64`. It now serves.
|
|
673
|
+
|
|
674
|
+
- **Three GET routes on `127.0.0.1`**, default port `4477` (`--port <n>`,
|
|
675
|
+
`--port 0` for any free one, `--open` to launch a browser): `/` is the page,
|
|
676
|
+
`/model.json` is the model it was drawn from, `/events` is a Server-Sent Events
|
|
677
|
+
stream. Nothing else is answered, and anything that is not a `GET` gets `405` —
|
|
678
|
+
a dashboard that can change state is a second source of truth competing with
|
|
679
|
+
the files (concept §12).
|
|
680
|
+
- **A watcher over `.tldrx/**` and `tldrx-work/**`**, debounced 300 ms, pushes a
|
|
681
|
+
`reload` event; the page re-fetches the model and redraws. Recursive `fs.watch`
|
|
682
|
+
where the platform has it, an mtime sweep where it does not — an untested
|
|
683
|
+
fallback is a dashboard that quietly stops being live, so both paths are
|
|
684
|
+
covered by the same test.
|
|
685
|
+
- **`node:http` and `node:fs`, nothing else.** No framework, no runtime
|
|
686
|
+
dependency, and the built `dist/tldrx.js` serves under plain `node` — proven by
|
|
687
|
+
a test that runs it, not by inspection.
|
|
688
|
+
- Ctrl-C closes the listener and the watcher and exits `0`. A directory with no
|
|
689
|
+
`.tldrx/` gets a page that says which two commands fix that, and fills itself in
|
|
690
|
+
when one of them is run.
|
|
691
|
+
|
|
692
|
+
### Model and renderer are now separate things
|
|
693
|
+
|
|
694
|
+
The rendering layer is meant to be replaced by a designer, so it stopped being
|
|
695
|
+
entangled with the reading layer.
|
|
696
|
+
|
|
697
|
+
- **`src/core/dashboard/model.ts` produces one plain JSON `DashboardModel`** from
|
|
698
|
+
the files — runs, execution path, handoffs, open questions, experts with levels
|
|
699
|
+
recomputed from evidence, the FAQ as data, and the Plan's stories/epics/waves
|
|
700
|
+
when a run has written them. It survives a JSON round trip unchanged, and a test
|
|
701
|
+
pins the field NAMES rather than the markup. Documented field by field in
|
|
702
|
+
`docs/dashboard-model.md`.
|
|
703
|
+
- **`src/core/dashboard/render.ts` is the only markup in the product.** The static
|
|
704
|
+
export renders on the server; the live page redraws in the browser — with the
|
|
705
|
+
*same functions*, serialised into the page by `clientRenderer()`. A test
|
|
706
|
+
evaluates that serialised source in an empty scope and demands byte-identical
|
|
707
|
+
output, so a template function that closes over a module constant fails there
|
|
708
|
+
instead of as a blank page in someone's browser.
|
|
709
|
+
- `--static` is unchanged in what it shows, and gained the plan block.
|
|
710
|
+
### Watch: one watcher card per shipped feature
|
|
711
|
+
|
|
712
|
+
The phase that answers "how would anyone know this still works next month?" — and
|
|
713
|
+
refuses to answer it aspirationally.
|
|
714
|
+
|
|
715
|
+
- **`05-watch/watchers/<feature>.md`** (spec §2.16, `templates/watcher.md`): front
|
|
716
|
+
matter (`version`, `id`, `epic`, `title`, `stories`, `repos`, `status`) plus
|
|
717
|
+
`## Signal` · `## Where` · `## Healthy baseline` · `## Looks broken when` ·
|
|
718
|
+
`## Query` (fenced, copy-paste) · `## Sources`. Every list item in the first four
|
|
719
|
+
ends with a `[src: …]` token, checked by the **same parser `claim-sources` uses**.
|
|
720
|
+
- **`status` is computed, never claimed.** `verified` only when no `absent:` source
|
|
721
|
+
remains under `## Signal`; otherwise `draft`, and the card says what to
|
|
722
|
+
instrument. The executor re-reads the card off disk and rewrites the line, so a
|
|
723
|
+
sub-agent that stamps its own work `verified` is overruled.
|
|
724
|
+
- **The Watch executor** (`src/core/facilitator/executors/`, a map from phase id to
|
|
725
|
+
executor). A deterministic pre-pass groups **done** stories by epic — one feature
|
|
726
|
+
per epic, named after the epic's branch slug. Then one sub-agent per feature,
|
|
727
|
+
handed that epic's done stories, the **read-only diff of its branch against each
|
|
728
|
+
repo's `default_branch`** (through the runtime seam; nothing checks out or
|
|
729
|
+
fetches), the `observability`/`deploy` facts and the repos' `gotchas.md`, and
|
|
730
|
+
nothing else. The diff is what landed; `touches:` was written before the code
|
|
731
|
+
existed. `05-watch/handoff.md` is then written deterministically from the cards.
|
|
732
|
+
- **No done stories is a result, not an error.** The stage completes, spawns
|
|
733
|
+
nothing, spends nothing, and its handoff reads `- none [src: absent:03-plan/stories]`.
|
|
734
|
+
- **`--prepare`/`--commit` is per feature**: one
|
|
735
|
+
`.agent/<stage>/<feature>/{prompt.md,pending.json,result.json}` each.
|
|
736
|
+
- **`tldrx watch list [--run <id>]`** — feature, status and Signal line per card.
|
|
737
|
+
**`tldrx watch check <feature>`** — re-resolves one card's citations and
|
|
738
|
+
re-computes its status, and **exits 1 when either fails**, so CI can see it. It
|
|
739
|
+
catches both ways a card rots: the code moved under a citation, or somebody
|
|
740
|
+
hand-edited `draft` to `verified`.
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
## 0.1.0 — 2026-08-29
|
|
744
|
+
|
|
745
|
+
### Greenfield: a project with no code yet
|
|
746
|
+
|
|
747
|
+
Measured on a temp repo holding only `requirements.md` (2026-08-29): `init` seeded
|
|
748
|
+
zero experts, and `run new` had no way to be handed the document — so the What
|
|
749
|
+
stage would have ideated from nothing.
|
|
750
|
+
|
|
751
|
+
- **`tldrx init` names the case.** A single repo with zero code files is recorded
|
|
752
|
+
as `mode: greenfield` in `workspace.yml`, and `map/<repo>/architecture.md` says
|
|
753
|
+
so with an `absent:` source instead of describing an empty tree as an
|
|
754
|
+
architecture. "Code file" is one rule, by extension, shared with the map
|
|
755
|
+
(`src/core/detect/codeFiles.ts`).
|
|
756
|
+
- **`init` always seeds a `product` expert** — the What stage names one, so a
|
|
757
|
+
workspace without it handed that stage a prompt with no expert body at all.
|
|
758
|
+
- **`init --stack ts,dotnet,python,go,rust,…`** seeds a `<lang>-stack` expert per
|
|
759
|
+
declared language when there is no manifest to detect one from. Without it the
|
|
760
|
+
greenfield interview asks *"Which stack will this project use?"* (fixed list plus
|
|
761
|
+
free text) and *"Which single document is the source of requirements?"*.
|
|
762
|
+
|
|
763
|
+
### `tldrx run new --seed <file|dir>` — import any document
|
|
764
|
+
|
|
765
|
+
- Takes one `.md`/`.txt` file or a directory of them (recursive, sorted, ≤50 files,
|
|
766
|
+
≤2 MB each; anything larger is skipped **and named**). PDFs and Word files are
|
|
767
|
+
out of scope and say so. Distinct from `--from`, which stays AI-DLC-specific;
|
|
768
|
+
passing both is an error.
|
|
769
|
+
- **Copies nothing.** The originals stay where the team keeps them and every claim
|
|
770
|
+
cites them as `[src: <path>:<line>]`, workspace-relative.
|
|
771
|
+
- Writes `01-what/seed-index.md` (documents, sizes, skips, warnings) and
|
|
772
|
+
`01-what/handoff.md` whose Findings are every heading, bullet and paragraph of
|
|
773
|
+
the seed. Unknowns are deterministic: the What outputs
|
|
774
|
+
(`intent`/`scope`/`success-metrics`/`open-questions`) that no seed heading
|
|
775
|
+
matches.
|
|
776
|
+
- The seed documents are added to the What stage's **declared inputs** in
|
|
777
|
+
`run.yml`, so `tldrx next` inlines their content into the prompt. `stage.yml`
|
|
778
|
+
opts in with `seed: true` (§2.3 `inputs.seed` is accepted too); over the 64 KB
|
|
779
|
+
inline budget the index plus a labelled prefix is inlined and the prompt says
|
|
780
|
+
what was cut. Input count is capped at §2.3's 20.
|
|
781
|
+
|
|
782
|
+
**Plan/Build schemas (spec §2.13–§2.15).** `03-plan/stories/<id>.md`, `03-plan/epics/<id>.md` and
|
|
783
|
+
`03-plan/waves.yml` now have a shape, templates and validators — the last of spec §7's schema open items.
|
|
784
|
+
|
|
785
|
+
- Story front matter: `id` `epic` `title` `repo` `status` `depends_on` `touches` `acceptance` `test_plan` `evidence`,
|
|
786
|
+
plus the fenced `` ```dod `` block. **Every dod command must equal a `.tldrx/workspace.yml` command verbatim** — a story
|
|
787
|
+
is data, and data does not get to invent a shell command. **`status: done` requires `evidence`**: done means proven.
|
|
788
|
+
- Epic front matter: `id` `title` `repos` `stories` `branch: epic/<slug>` `status`. A story belongs to exactly one
|
|
789
|
+
epic, and the story ↔ epic reference must agree in both directions.
|
|
790
|
+
- `waves.yml`: `waves: [{id: W1, stories: [S1, S2]}, …]`, ids ascending because file order is execution order, and the
|
|
791
|
+
rule the shape cannot enforce alone — **every story's `depends_on` must be in an earlier wave**. A dependency inside
|
|
792
|
+
the *same* wave is an error, not a warning: those two stories would go to parallel agents that overwrite each other.
|
|
793
|
+
- New `plan` gate check reads all three together at the Plan gate (`tldrx approve`), which is the only place the
|
|
794
|
+
cross-file rules can be checked. `dod-gate` keeps its line scanner — a gate that only ran when the front matter
|
|
795
|
+
parsed would let a malformed story write `status: done` unchecked — but now shares one `` ```dod `` parser with the schema.
|
|
796
|
+
- Templates: `templates/story.md`, `templates/epic.md`, `templates/waves.yml`.
|
|
797
|
+
|
|
798
|
+
**Budget UX.** Measured in the pilot: a retry was refused twice because the phase ceiling had been sized for exactly
|
|
799
|
+
one attempt, and nothing put "what is left" next to "what the next stage costs".
|
|
800
|
+
|
|
801
|
+
- `tldrx budget show [<run>] [--json]` — a phase table of ceiling, spent, remaining, the next stage and its own
|
|
802
|
+
estimate, and whether `tldrx next` would be blocked there.
|
|
803
|
+
- `tldrx budget raise <phase> <usd> [--take-from <phase>]` — the one sanctioned edit to `budget.yml`, validated before
|
|
804
|
+
it writes: Σ phase ceilings ≤ run ceiling holds on the way out, a `--take-from` donor can never be cut below what it
|
|
805
|
+
has already spent, and the output says out loud whether the run ceiling grew or the money merely moved.
|
|
806
|
+
- Both `budget.blocked` messages (the hook and `tldrx next`) now name **the exact command**, with the shortfall
|
|
807
|
+
computed and rounded **up** to the cent, instead of naming the field to hand-edit.
|
|
808
|
+
- `run status` shows per-attempt cost for the cursor stage — `attempts: 2 · $1.39 + $1.21` — read from `agent.result`
|
|
809
|
+
events. A stage's total `cost_usd` cannot tell one $2.60 attempt from two $1.30 ones, and only the second says
|
|
810
|
+
whether a retry fits.
|
|
811
|
+
|
|
812
|
+
**Checked sections must contain items (spec §2.8).** Findings / Decisions / Unknowns / Evidence ledger must each hold
|
|
813
|
+
at least one list item; a section that is present but carries only prose is now a validation error. A genuinely empty
|
|
814
|
+
section is written `- none [src: absent:<what was looked at>]`. This closes the way an unsourced claim used to get
|
|
815
|
+
written anyway: a paragraph carries no bullet for the checker to look at, so "no unknowns that we can see" validated
|
|
816
|
+
clean. The parser, the `claim-sources` hook, the facilitator's post-stage check and `tldrx approve` share one parser
|
|
817
|
+
and all four now refuse it. The Evidence ledger in the shipped stage templates is a list rather than a table (a table
|
|
818
|
+
holds no list items), and both deterministic renderers — the `--from` distill and the `--seed` import — write
|
|
819
|
+
`- none [src: absent:…]` where they used to write an italic paragraph, so a `run new` cannot produce a handoff its own
|
|
820
|
+
validator would reject. **Outstanding:** `stages/what/stage.md` still ships the table and the old `## Rules` block; it
|
|
821
|
+
was owned by a concurrent branch when this landed.
|
|
822
|
+
|
|
823
|
+
All notable changes to tldr-experts. Dates are the day the work landed on `main`.
|
|
824
|
+
|
|
825
|
+
## 0.0.2 — 2026-08-29
|
|
826
|
+
|
|
827
|
+
Fixes found by the first real pilot (scavtopia, `--from` an AI-DLC intent, What stage):
|
|
828
|
+
|
|
829
|
+
- `claim-sources`: bare `path:line` sources now resolve against the workspace root, then the run directory, then a repo dir (a run-relative `01-what/intent.md:12` was reported as missing).
|
|
830
|
+
- Handoff parser: wrapped bullets (token on an indented continuation line) and ordered items (`1.` / `1)` at column 0) are validated like `- ` bullets; the facilitator check, `approve` and the hook share one parser.
|
|
831
|
+
- `next` retries a `failed` cursor stage instead of walking past it; `run status` renders failed stages; `reject --note` works from `failed` and the note reaches the next prompt under `## Previous attempt`.
|
|
832
|
+
- `run new --from` no longer duplicates facts on re-import.
|
|
833
|
+
- `--root` on every run-scoped command; `<cmd> --help` works without a workspace.
|
|
834
|
+
- Stage prompts carry the citation grammar and the no-re-ask rule.
|
|
835
|
+
- Hook bundles split into one shared chunk (`dist/` 2.4 MB → 0.9 MB); CI build step is honest; tag-driven trusted-publishing release workflow.
|
|
836
|
+
|
|
837
|
+
## 0.0.1 — 2026-08-29
|
|
838
|
+
|
|
839
|
+
First published version. Pre-alpha: the v0 loop runs end to end, and the parts
|
|
840
|
+
that do not exist yet exit `64` and say so rather than pretending.
|
|
841
|
+
|
|
842
|
+
### What is implemented
|
|
843
|
+
|
|
844
|
+
- **`tldrx init`** — detects repos, stack, package manager, default branch and
|
|
845
|
+
build/test commands from the filesystem and `git` alone (no LLM, no network);
|
|
846
|
+
writes `.tldrx/workspace.yml`, the code map under `.tldrx/map/**`, the init
|
|
847
|
+
handoff, an interview containing only the real gaps, seeded experts at level 0,
|
|
848
|
+
`conventions/`, `process.yml` and an empty `facts.yml`, plus a marked block in
|
|
849
|
+
`.gitignore` and `CLAUDE.md`. Re-running keeps everything a human authored.
|
|
850
|
+
- **`tldrx map --refresh | --check`** — rebuilds the map, or resolves every
|
|
851
|
+
`[src: …]` citation in it against the filesystem and exits `1` naming the ones
|
|
852
|
+
that no longer land. Providers: `graphify` when it is on PATH, otherwise a
|
|
853
|
+
static provider (file tree, manifests, 90-day git churn).
|
|
854
|
+
- **`tldrx doctor`** — runs every check in `env.yml` and prints the table. Exit
|
|
855
|
+
`0` only when every required tool meets its `min_version`. `--mcp` adds live
|
|
856
|
+
MCP health checks.
|
|
857
|
+
- **The run lifecycle** — `run new <slug>` seeds `tldrx-work/<yymmdd>-<slug>/`
|
|
858
|
+
from a scope preset and its stage files, with per-phase budget ceilings scaled
|
|
859
|
+
to `--budget`; `run status` renders the execution path, the money and what the
|
|
860
|
+
run is waiting on; `answer`, `approve` and `reject` are the human half.
|
|
861
|
+
`approve` re-runs the stage's declared checks against what is on disk before it
|
|
862
|
+
will advance anything.
|
|
863
|
+
- **`run new --from <dir>`** — the AI-DLC distill: reads a listed set of files
|
|
864
|
+
from an intent folder, turns each bullet into a sourced Finding and each
|
|
865
|
+
answered question into a fact, drops the unanswered, and turns a claim that
|
|
866
|
+
contradicts a non-retired fact into a question rather than overwriting it.
|
|
867
|
+
Deterministic; no model runs.
|
|
868
|
+
- **`tldrx next`, in two execution modes** — headless (`tldrx next` spawns
|
|
869
|
+
`claude -p` itself) and in-session (`--prepare` writes the prompt bundle, the
|
|
870
|
+
host Claude Code session dispatches its own sub-agent, `--commit` picks it up).
|
|
871
|
+
From "re-read the declared outputs off disk" onwards both are the same code:
|
|
872
|
+
same validation, same checks, same cost roll-up, same gate. `--dry-run` keeps
|
|
873
|
+
the handoff and reverts the rest.
|
|
874
|
+
- **Six hooks** — `claim-sources` (a handoff bullet without a resolvable `[src:]`
|
|
875
|
+
is denied), `no-re-ask` (a question `facts.yml` already answers is denied),
|
|
876
|
+
`answer-capture` (records the answer, the fact and the event), `dod-gate`
|
|
877
|
+
(re-runs a story's `dod` block; the one hook that fails closed), `budget-gate`
|
|
878
|
+
and `session-start`. Everything but `dod-gate` fails open.
|
|
879
|
+
- **`tldrx expert list | create | train --print-prompt`** — experts are files.
|
|
880
|
+
`list` recomputes every level from evidence before printing it and warns when
|
|
881
|
+
the stored number disagrees.
|
|
882
|
+
- **`tldrx replay`** — `events.jsonl` rendered as a stakeholder narrative.
|
|
883
|
+
- **`tldrx retro [--apply]`** — five deterministic heuristics over the event log,
|
|
884
|
+
each citing the line that proves it; `--apply` appends the proposals to
|
|
885
|
+
`practices.md`, idempotently.
|
|
886
|
+
- **`tldrx dashboard --static`** — one self-contained `index.html`, no external
|
|
887
|
+
request in any `src`/`href`.
|
|
888
|
+
- **Runtime: Node ≥ 20 or Bun.** Every capability that differs between the two
|
|
889
|
+
lives behind `src/core/runtime/`; a test asserts no `Bun.` call site survives
|
|
890
|
+
outside it. The published bundle inlines its one devDependency, so an install
|
|
891
|
+
resolves zero runtime dependencies.
|
|
892
|
+
|
|
893
|
+
### What is NOT implemented
|
|
894
|
+
|
|
895
|
+
- **Build and Watch phase execution.** The stages, their contracts and the story
|
|
896
|
+
and wave file shapes exist; running them does not. Story/epic/`waves.yml`
|
|
897
|
+
schemas are still an open decision.
|
|
898
|
+
- **Expert training.** `expert train` prints the prompt to paste and exits `64`
|
|
899
|
+
without `--print-prompt`. Running the training loop, and writing the resulting
|
|
900
|
+
`knowledge/*.md` and levels back, is v1.1.
|
|
901
|
+
- **The live dashboard.** `dashboard` without `--static` exits `64`. The watching
|
|
902
|
+
server is v1.
|
|
903
|
+
- **The ticket adapter.** No Jira or GitHub integration ships. Direction
|
|
904
|
+
(mirror-only vs two-way) and which one lands first are undecided.
|
|
905
|
+
- **Parallel execution.** v0 runs one stage at a time, one task at a time.
|
|
906
|
+
|
|
907
|
+
### Notes
|
|
908
|
+
|
|
909
|
+
- `tldrx <command> --help` prints usage and exits `0` without needing a
|
|
910
|
+
workspace. `--root <path>` works on every command that touches one.
|
|
911
|
+
- A `failed` stage is not progress and not the end: `run status` renders it as a
|
|
912
|
+
failure, `tldrx next` retries it, and `reject --note` sends it back to `ready`
|
|
913
|
+
with the note fed into the next prompt.
|