tldr-experts 0.13.1 → 0.14.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 CHANGED
@@ -1,6 +1,83 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.14.0 — 2026-09-09
5
+
6
+ ### Added
7
+
8
+ - **`run auto` now closes an `agent` gate itself, over a note it wrote and had validated (#198).**
9
+ `gates_policy: agent` said who MAY sign a gate; nothing in the engine produced the evidence note
10
+ it is signed over. The only writer was the `tldrx gate template` skeleton a host session fills
11
+ in at a keyboard — so moving a run's gates to `agent` changed *who is permitted to sign* without
12
+ changing *whether the loop could keep going*, and an unattended run over an all-`agent` run still
13
+ stopped at every gate with exit 4. Measured on 0.13.1 against a real workspace: the owner ran
14
+ `tldrx run gates set what:agent / plan:agent / watch:agent`, whose own reply promises "an agent
15
+ may now close it", and the loop stopped at the next gate anyway.
16
+ It now spawns one bounded **gate signer** when a stage's checks have passed and its policy is
17
+ `agent`: the stage's own model and effort, a quarter of the stage's per-agent ceiling, a tool
18
+ allowance that reads anything and writes exactly one file (`.agent/<stage>/evidence.md`), and a
19
+ prompt carrying the stage's declared outputs, the seven `auto` conditions as measured, and the
20
+ §2.8 skeleton rendered by the same function `gate template` writes.
21
+ The important half is what did NOT change: the note goes through the unchanged
22
+ `approve --as-agent` path, so `verdict: sign` with every condition holding closes the gate under
23
+ the note's own `by:`, and a `refuse`, a note that does not validate, a signer that wrote nothing
24
+ and a signer that died are one outcome — pending for a person, with the reason named on stdout
25
+ and now in the `gate.requested` notification's summary. There is no path from the signer to an
26
+ approval the validator would not also have taken, and no flag turns it on: an `agent` policy is
27
+ already the owner's recorded decision, and a second opt-in would mean the policy never did what
28
+ it said. `human` gates are untouched and `--gate-agent` stays rendering-only.
29
+ The turn is recorded like any other (`agent.spawned` / `agent.result`, `role: gate-signer`, a
30
+ `run.yml` task row, a row in `tldrx cost`), and it runs BEFORE the stage moves to
31
+ `awaiting_gate` — measured while building this, the other order made the `--wait-gates`
32
+ heartbeat announce "a signature is waiting on you" while the engine's own signer was still
33
+ mid-turn, and a person signed the gate the engine was in the middle of signing, three runs out
34
+ of three.
35
+
36
+ ### Changed
37
+
38
+ - **The pre-merge reviewer runs targeted tests, not the whole suite.** Measured over one fix
39
+ wave: the 4,200-test suite ran THREE times in series at ~10 min each — the implementer
40
+ (`AGENTS.md` §3 requires it), the reviewer, and `merge-wave.sh` (by design). Only the
41
+ reviewer's was avoidable, and it was avoidable because nothing had told it not to: the brief
42
+ said merely that it *may* run tests, so a fresh reviewer sensibly ran everything. It now says
43
+ the negative out loud — run only the test files that cover the diff, named from
44
+ `git diff --stat`, plus `bun run typecheck`, and **never** the full `bun test` — with the
45
+ reason attached, that the wave re-runs every gate on the MERGED tree, so the reviewer's run
46
+ verifies nothing the wave will not, on a tree that is not the one being merged. The rule is
47
+ in `.claude/skills/maintain/references/sub-agent-briefs.md`, where the reviewer reads it, and
48
+ stated once in the skill's §3 for the workflow reader.
49
+ - **`AGENTS.md` §2 now says a slash in a branch name is a directory.** The review-record gate
50
+ (#192) builds `.review/<branch>.md` from the branch name VERBATIM, so `fix/x` wants
51
+ `.review/fix/x.md` — a nested directory, not `.review/fix-x.md`. Learned from the gate's
52
+ first refusal in the wild, which cost five minutes to read. The canonical fact lives in §2;
53
+ the maintain skill cites it rather than restating it.
54
+ - **`ci` cancels a run a newer push has already superseded.** The workflow had no
55
+ `concurrency:` group, so every push started a run and none of them stopped. Measured over the
56
+ last week: 25 of 122 push-triggered runs began less than 10 minutes apart on a 5.2-minute
57
+ average, which is roughly an hour to two hours of runner time per week spent finishing
58
+ answers nobody would read. `ci.yml` now groups on `github.ref` with `cancel-in-progress`.
59
+ It weakens no gate: `ci` is not the release gate — `scripts/release-check.sh` runs the full
60
+ gates before anything is pushed and `publish.yml` re-runs them after the tag
61
+ (`docs/RELEASING.md`), and neither is touched here.
62
+ - **The docs deploy now fires on `src/cli/helpText.ts`.** `docs-site/scripts/gen-cli.ts`
63
+ generates the site's `reference/cli-flags.md` and `cli.md` from that one registry, but the
64
+ workflow's `paths:` filter never listed it — so a help-registry change landed on `main`,
65
+ deployed nothing, and the published CLI reference kept showing the previous surface. It went
66
+ stale exactly that way after this week's CLI changes. The filter is otherwise unchanged, and
67
+ a test now pins the entry rather than trusting the next reader to notice.
68
+ - **`publish.yml` depends on `ci`'s answer instead of recomputing it.** The release job ran
69
+ typecheck, tests and build as its own steps — a THIRD run of the same gates on the same sha,
70
+ after the local `release-check.sh --pre-push` and after `ci`. Measured: ~6.5 min a publish,
71
+ ~14 publishes in a week, ~87 min/week of runner time to re-derive something the repo already
72
+ knew. Publish now refuses unless the `ci` workflow has a `success` run for `github.sha`,
73
+ polled for up to 15 minutes through the REST API with `head_sha` (never `gh run list
74
+ --commit`, which returns `[]` for minutes while the runs exist — `AGENTS.md` §4). Nothing is
75
+ weakened: the same gates still run, once, and the checks only publish can do — tag equals
76
+ `package.json`, already-on-the-registry, `release-check.sh --ci`, OIDC trusted publishing —
77
+ are untouched. A `cancelled` ci run fails by name with the remedy, because the new
78
+ `cancel-in-progress` can supersede a release commit's run; merges stay frozen during a
79
+ release, as they already were.
80
+
4
81
  ## 0.13.1 — 2026-09-08
5
82
 
6
83
  ### Fixed
@@ -35,6 +112,44 @@
35
112
 
36
113
  ### Added
37
114
 
115
+ - **`tldrx run auto --wait-gates <duration>` — the loop waits for a signature the way it
116
+ already waits for an answer (#197).** Measured on a real workspace the day the notify hook
117
+ first drove a run: the question loop closed itself — the hook delivered Q1, the owner
118
+ answered from his chat, and `run auto` printed `waited 132s … resuming` — and then the very
119
+ next thing the stage did was reach its human gate, where the same owner approved from the
120
+ same chat and the loop had already exited 4. Three human gates a feature run is three manual
121
+ re-launches the hook was supposed to make unnecessary. The cause was scoped, not accidental:
122
+ `--wait-answers` gates its whole wait on an open-question card, which is `null` when the park
123
+ is a gate, so the wait was never reached. `--wait-gates` is a sibling flag rather than a wider
124
+ `--wait-answers`, because the two parks are closed by different verbs and calling a signature
125
+ an "answer" would be the flag name lying about what a person did. Approved → the loop carries
126
+ on; rejected → it stops and prints the note, which is now its LAST line so the `run.failed`
127
+ payload carries it to the phone of whoever has to act on it; lapsed → exit 4 with the same
128
+ lines it always had, after one `gate.timeout` (new kind, `question.timeout`'s twin, carrying
129
+ the approve and reject lines, the gate's policy, `waited_ms`, and `cost_usd` only when this
130
+ loop is the one that measured it). Nothing is spent while it polls — it reads files — and
131
+ whether a gate is pending is read through `waitingFor`, the one derivation `tldrx run status`
132
+ and the dashboard already share, never a second copy. **It waits FOR a signature and never
133
+ produces one**: there is no engine-side signing in this loop, so a stage on
134
+ `gates_policy: agent` stops it exactly as a `human` one does and is waited on identically —
135
+ an owner who switched three gates to `agent` expecting the loop to carry on was measuring
136
+ who MAY sign, not that anything had. Both wait flags may be given together, and without the
137
+ flag a gate exits 4 on the spot exactly as before.
138
+ - **The heartbeat stops telling a waiting owner that nothing is waiting on him at a GATE.**
139
+ `--notify-every`'s `status` payload learned in 0.11.1 not to say "Nothing is waiting on you"
140
+ over a run parked on a question — the fix that exists because a heartbeat is believed — but
141
+ its parked-ness came from the blocking-question predicate alone, so a run parked on a
142
+ signature got `waiting_on: []` and the exact sentence the fix was written to prevent. A
143
+ pending gate is now named in `waiting_on_gate` (`<phase>/<stage>`) beside `gate_policy`, the
144
+ summary says the run is waiting for a person to sign that stage, and `command` is the literal
145
+ `tldrx approve` line — the same spelling `gate.requested` and every decision card use, now
146
+ one exported helper instead of four literals. `waiting_on_gate` is a SIBLING key rather than
147
+ a member of `waiting_on`: an adapter maps every id in `waiting_on` to `tldrx answer <id>`,
148
+ and a stage id there would make it build a command nobody can type. Both keys are absent when
149
+ no gate is pending, so a heartbeat over a moving run is byte-identical to the one it sent
150
+ before. `gate.requested` now names the policy too, so an owner reading it on a phone knows
151
+ whether he is signing a `human` gate or overriding an `agent` one.
152
+
38
153
  - **`tldrx expert rescore [<name>] [--area <a>]` — score the knowledge you already paid for.**
39
154
  Its sibling `recompute` is arithmetic over the evidence rows already in `competencies.yml`;
40
155
  `rescore` RE-READS `knowledge/*.md` and derives their evidence again under today's rules. It
package/README.md CHANGED
@@ -316,6 +316,7 @@ back on the registry is 0.3.0.
316
316
 
317
317
  | Version | Date | Status | Contains |
318
318
  |---|---|---|---|
319
+ | 0.14.0 | 2026-09-09 | `beta` | an `agent` gate the engine can actually close, and three computations of the same answer cut to one: `gates_policy: agent` named who MAY sign a gate but nothing in the engine produced the evidence note it is signed over — measured on 0.13.1, an owner ran `tldrx run gates set what:agent`, was told "an agent may now close it", and the loop stopped at the next gate anyway with exit 4 — so `run auto` now spawns one bounded **gate signer** when a stage's checks pass under an `agent` policy: the stage's own model and effort, a quarter of its per-agent ceiling, a tool allowance that reads anything and writes exactly one file, and a prompt carrying the stage's declared outputs, the seven `auto` conditions as measured and the §2.8 skeleton `gate template` itself renders — the note going through the UNCHANGED `approve --as-agent` path, so a refusal, a note that does not validate, a signer that wrote nothing and a signer that died are one outcome, pending for a person with the reason named on stdout and now in the `gate.requested` summary, and there is no flag to turn it on because an `agent` policy is already the owner's recorded decision; the turn is recorded like any other (`role: gate-signer`, a `run.yml` task row, a row in `tldrx cost`) and taken BEFORE the stage moves to `awaiting_gate`, because the other order had a person sign the gate the engine was mid-signing, three runs out of three; and the suite stops being run three times per change — the pre-merge reviewer now runs only the test files that cover its diff plus `typecheck`, never the full `bun test`, since the wave re-runs every gate on the MERGED tree anyway, `publish.yml` refuses unless `ci` has a `success` run for the same sha instead of recomputing typecheck/tests/build (~87 min/week of runner time, with a `cancelled` ci run failing by name and the remedy), `ci` cancels a run a newer push has already superseded (25 of 122 push runs began under 10 minutes apart), the docs deploy finally fires on `src/cli/helpText.ts` so a help-registry change stops deploying nothing while the published CLI reference goes stale, and `AGENTS.md` §2 now says out loud that a slash in a branch name is a directory, which is what the review-record gate builds |
319
320
  | 0.13.1 | 2026-09-08 | `beta` | a stage prompt that opens by saying what to do: every stage prompt now leads with a generated brief — who the reader is, which stage of which run, that the template below is to be FILLED, the exact path of every declared output, and that a question goes in the questions file rather than back to an operator who is not there — because on a real workspace at 0.13.0 a What sub-agent read its 66,452-byte prompt, found no request in it, wrote none of its six declared outputs and asked what to do, $0.29 spent; the brief is generated from the same `outputs:` list `pending.json` records, so it cannot name a path the commit will not look for, and the failure was never a regression — the spliced citation grammar grew the stage section 5,007 B → 13,180 B and the missing instruction was finally outnumbered; and the `N runs are open` nudge, the one imperative-shaped sentence in that agent's window and the one it duly answered, stops reaching sub-agents at all — `spawnAgent` marks every child it spawns and `session-start` emits nothing when it sees the marker, an absent marker still being a human's session and behaving exactly as before |
320
321
  | 0.13.0 | 2026-09-08 | `beta` | evidence a role expert can actually earn, and a review that leaves a record: `--mode full`'s runs pass mines `tldrx-work/**` while the domain gate judged every citation it produced against folders of code — measured at four role experts, **$9.47 and one evidence row**, and unfixable from the workspace because the single spelling the matcher would reach is the one `domainPaths()` drops — so the gate now treats the run record as in-domain for the file mined FROM it, scoped to the pass and never to the expert's `kind:` (a light file citing a handoff is still out of domain and still says so), two shipped role templates stop declaring `.tldrx/map/**` and `.tldrx/map/{repo}/gotchas.md` paths that matched nothing at all, and a pass that validated, spent money and earned zero rows now prints `the level did not move — $X.XX bought 0 evidence row(s)` with its reasons carried into `check.passed` instead of a silent ledger; `tldrx expert rescore` recovers what was already bought for $0 by re-reading `knowledge/*.md` under today's rules, dating rows by the knowledge file's own `trained_at` and never by the clock, with `rescored_at` additive beside `at` — its ABSENCE keeping the meaning every existing row had — and one `evidence.rescored` line per file it actually moved, so a free re-derivation can never be read as a paid turn; `scripts/merge-wave.sh` refuses a branch carrying no `.review/<branch>.md` with **exit 10**, its own code because `2` in that script is already "merge conflict", a stale record refusing rather than warning and staleness measured as "the code moved" rather than "the sha differs", since committing the record moves the head past exactly the sha it names; and the mutation check moves from the reviewer, whose allowance is `Read`/`Grep`/`Glob`/`Bash(git diff *)` and holds no pen, to the developer's contract that can run it, leaving the reviewer the read it can actually perform |
321
322
  | 0.12.0 | 2026-09-08 | `beta` | records that can be attributed and spans that were actually measured: the reviewer can be pinned to its own model and effort per role (`reviewer:`) and per story stakes (`reviewer_by_stakes:`, keyed on a story's new optional `stakes:` enum), resolved field by field under `--model`/`--effort` and shipping NO opus default — because there is no evidence yet that a stronger reviewer finds more, only the record that lets the evidence accumulate: every verdict now names the model that produced it, a host review reading `basis: host-declared` off its own flags and a host that declared nothing reading `not recorded` rather than the bundle's suggestion; `run.yml` gains `created_with` and `last_written_by` beside the file format's own `version: 1`, and `agent.spawned`/`agent.result` carry `tldrx_version`, so a run that outlived an upgrade carries both ends of the range that drove it; a task row carries `duration_ms` that never travels without `duration_basis` — `spawned` is the wall clock around the sub-agent's process, `prepare-to-commit` is a ceiling that includes the host's own time — and `tldrx cost` shows a duration per attempt and a per-stage sum that names a mixed basis instead of adding two different quantities, with a pre-existing row reading `not recorded` and never `0s`; no surface prints a bare `$0.00` over work nobody metered — one implementation writes `≥ $12.40 (7 tasks unmetered)` or `not measured: 9 in-session tasks, 0 metered` across `run status`, `budget show`, the dashboard, `replay`, `run auto`, the Build handoff and every notification, and `budget.yml` gains `unmetered_tasks` and `spent_basis`; the surface a story actually changed is measured off its own diff over the range the reviewer was shown and appended as one `story.touches_widened` with `basis: "measured"` beside what was declared, advisory and never rewriting the operator's `touches:`; and a `maintain` skill encodes the maintenance circuit twelve hand-run waves actually measured — reproduce an issue on current `origin/main` before touching anything, a fresh reviewer before `merge-wave.sh` and not after, at most 3 issues and 2 implementers a cycle — pinned by a test that every command and `§N` it cites resolves |
@@ -347,7 +348,8 @@ path documented; `stable` = 1.0, semver from here on. The badge above shows the
347
348
  **One command: `scripts/release.sh X.Y.Z --tag beta`.** The tag is not optional in practice: omit
348
349
  `--tag` and the script writes `alpha`, which is no longer this project's status. It is the only
349
350
  sanctioned path — a Claude Code hook denies hand-made `git tag` / `npm publish`, and `publish.yml`
350
- runs `release-check.sh --ci` (the file checks only) plus its own typecheck, tests and build.
351
+ runs `release-check.sh --ci` (the file checks only) and refuses to publish unless the `ci`
352
+ workflow is already green for that exact sha.
351
353
  Checklist and judgement calls: `docs/RELEASING.md`.
352
354
 
353
355
  MIT, © 2026 Alan Martinez — a placeholder made while scaffolding; change it freely before anything ships.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  conflictOf
4
- } from "./chunk-fyr7f9t6.js";
4
+ } from "./chunk-3ns54mnq.js";
5
5
  import {
6
6
  FactsStore,
7
7
  formatJaccard
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  questionsCard
4
- } from "./chunk-fyr7f9t6.js";
4
+ } from "./chunk-3ns54mnq.js";
5
5
  import"./chunk-nwzevx0j.js";
6
6
  import {
7
7
  allow,