tldr-experts 0.9.2 → 0.10.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,111 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.10.0 — 2026-09-07
5
+
6
+ ### Fixed
7
+
8
+ - **A refused Definition-of-Done command is no longer recorded as `exit 126`.** When the
9
+ sandbox declined to run a `dod` or base command — a bare shell metacharacter with no
10
+ shell open to interpret it, an unsplittable pipeline — `runStoryDod` wrote
11
+ `{exitCode: 126}` for it, a fabricated exit code that told the ledger, the reviewer
12
+ prompt and bundle, the dashboard and the ship gate that the command RAN and failed with
13
+ 126, rather than that it never ran at all. `DodResult` grows `status?: "ran" | "refused"`
14
+ (absent stays `ran`, so every pre-existing record loads unchanged) and
15
+ `refusedBecause?: string`; a refused row carries no `exitCode` anywhere it is read or
16
+ rendered. `dodFailureReason` is the one derivation of the sentence a reader prints for a
17
+ refused command versus a failed one. `parsePreflight` tolerates exactly this one new hole
18
+ — a refused row has no `exit_code` and is never read as green — and a fourth golden build
19
+ scenario (`refused`) pins the shape. `PreflightRowModel.exitCode` widens to
20
+ `number | null`; `DASHBOARD_MODEL_VERSION` does not bump (a widened type and an added
21
+ field, no existing field's meaning moved). (#165)
22
+ - **The reviewer no longer diffs an empty range.** The reviewer's diff base was always
23
+ `epicBranch`'s current tip, but by the time review runs the story's own commits are
24
+ already merged onto that branch — so the reviewer diffed a range that had shrunk to
25
+ nothing, or silently picked up work from a story that landed after. `epic_base` (the full
26
+ 40-hex sha, captured immediately before the merge) is now recorded additively on
27
+ `task.done` and in the review bundle, omitted rather than guessed when unknown, and
28
+ recovered on resume from the ledger or the bundle; `reviewDiffCommand(diffBase,
29
+ epicBranch, branch)` is the one place the fallback to `epicBranch` lives, so a record with
30
+ no base still falls back to the branch and says so rather than diffing nothing. Seven
31
+ golden prompt/event artifacts moved — a prompt normaliser learned to scrub verified shas
32
+ first, so the sha itself never becomes a golden byte. (#166)
33
+ - **A costless turn that declared only a provider token split is no longer counted silent.**
34
+ Both spend-basis feeders — the handoff's cost line and the dashboard's spend model —
35
+ summed a task's host `tokens` scalar only, so a turn that reported a real
36
+ `input_tokens`/`output_tokens` split but never set the host field read as zero, tripping
37
+ `costlessTokens` and landing on `basis: "absent"` for a turn that was fully measured. One
38
+ leaf, `turnTokens(task)` (host `tokens` when declared — present as a finite number,
39
+ including an explicit `0` — else the input+output split when both are positive, else
40
+ `null`), is now read by both feeders; `costlessTokens` widens to
41
+ "host-declared or provider-reported", and an explicit `tokens: 0` still reads as a
42
+ declaration, not an absence. The host-token sum that feeds the `absent` sentence's own
43
+ count keeps summing the raw scalar only, so a provider split can never inflate what
44
+ "declared host tokens" means. The dashboard's own `RunTask` projection — a hand-tolerant
45
+ duplicate of `RunFile.ts`'s type, not a re-export, on the identical seam — gained the same
46
+ two fields it was silently dropping; without that half, the dashboard side of this fix
47
+ would not have fired at all. `DASHBOARD_MODEL_VERSION` unchanged. (#159)
48
+ - **A reviewer turn's token split reaches its `run.yml` row.** 0.9.1 wired the provider's
49
+ input/output split into the Build executor's own task rows; the reviewer's two spawn call
50
+ sites never got the same two fields, so a reviewer turn kept `run.yml`'s per-task tokens
51
+ blank even when the provider reported them — the sibling of the gap #159 closes for spend
52
+ basis. `formatRetry` and `recordReview`'s task structs both carry
53
+ `input_tokens`/`output_tokens` now, read at the same call sites the build path already
54
+ used; a host-envelope review (no provider usage to read) is left alone. Three golden
55
+ run-tasks artifacts moved. (#173)
56
+ - **The dirty-tree refusal prints the commands, and the reason it gives is true.** The
57
+ message said stashing would "carry the mess forward" through `git worktree add`, and told
58
+ you to "commit or stash" with no command to run — but the #41 base pre-flight runs the
59
+ gate commands in the repo's OWN checkout, not a worktree, so an uncommitted product change
60
+ sits inside the very measurement that decides whether a red DoD is the story's fault or
61
+ the base's; that was the real reason, and neither the docstring nor the message said it.
62
+ The refusal now prints the two literal commands (`git -C <dir> stash push -u -m "tldrx
63
+ <runId> foreign work"` / `git -C <dir> stash pop`, the run id in the stash message so a
64
+ resumed run's mess stays traceable) and stashes nothing itself; the docstring states the
65
+ true mechanism. (#164)
66
+
67
+ ### Added
68
+
69
+ - **`command_probes:` — what `tldrx init` actually ran, not what it guessed.**
70
+ `workspace.yml`'s `commands:` block came from pattern-matching a repo's files — a
71
+ `package.json` script name, a Makefile target — and was never executed, so a stack pack,
72
+ the dashboard and the base pre-flight all treated a detected command as verified when
73
+ "verified" had never been checked. `tldrx init` now probes each detected
74
+ `build`/`test`/`lint`/`typecheck` command once (`run` is excluded by name — starting a
75
+ long-running process is not a probe) and records `command_probes:` rows per repo:
76
+ `{status, verified, exit_code, at, reason}`, one status vocabulary for the whole surface
77
+ so a genuine `exit 127` (found, ran, failed) and a command that never started — either
78
+ `unspawnable` (ENOENT/EACCES) or `not-probed` (a bare metacharacter needing a shell this
79
+ probe does not open) — are different, honestly labelled rows instead of the same
80
+ fabricated 127. `--no-probe` skips entirely and records the skip as its own reason
81
+ (`skipped: --no-probe`); the base pre-flight cites a probe only when its `status` is
82
+ `failed` — the one status that means the command ran and exited non-zero. `tldrx learn`'s
83
+ sandboxed walkthrough passes `--no-probe` to the `init` it runs; `tldrx map` never requests
84
+ a probe at all — it omits `detectWorkspace`'s `probe` option entirely, as before. Probing is
85
+ a real spawn, and `init`'s own `--help` "Deterministic and offline" claim is corrected to
86
+ say so.
87
+ Additive on
88
+ `WorkspaceRepoDocument`; every pre-#168 `workspace.yml` still validates. (#168)
89
+
90
+ ### Changed
91
+
92
+ - **`tldrx ship`'s PR body is written for a PR, not forwarded from the last handoff
93
+ verbatim.** The body was the raw `04-build/handoff.md` text — a build log addressed to
94
+ the next Build turn, headed by phase names and gate jargon nobody opening the PR asked
95
+ for. `renderShipBody` is now the one renderer: what shipped (the handoff's `done`
96
+ findings), what did not (its unknowns), the fix-list findings still open, and the full
97
+ handoff moved inside a collapsed `<details>` block rather than dropped. Dry-run and the
98
+ real create path share the one rendered body file, so what you preview is what posts. And
99
+ the state refusal has an allowed move at last: a story at `status: done` EXCUSES the state
100
+ paths its own `touches:` declared, so a story written to edit `.tldrx/workspace.yml` —
101
+ adding a repo, declaring a command — can finally be shipped, where before #102's refusal
102
+ had no way through at all. A story at `review` or `blocked` excuses nothing: a declaration
103
+ with no verdict behind it is a plan, not a fact. The excuse is matched per `(repo, path)`,
104
+ since `touches:` is relative to the story's own `repo:`, and the remedy names only the
105
+ paths still REFUSED — never the blanket `tldrx-work .tldrx`, which would revert the very
106
+ edit printed three lines above it as excused. It exits the money/gate family
107
+ (`EXIT_GATE_REFUSED`), not a module-local code. (#167)
108
+
4
109
  ## 0.9.2 — 2026-09-07
5
110
 
6
111
  ### Changed
package/README.md CHANGED
@@ -314,6 +314,7 @@ back on the registry is 0.3.0.
314
314
 
315
315
  | Version | Date | Status | Contains |
316
316
  |---|---|---|---|
317
+ | 0.10.0 | 2026-09-07 | `beta` | the mechanical blockers measured in real runs, fixed: a refused DoD command is recorded as refused (never a fabricated exit 126) in the event, the handoff, the review and retro logs, and the readers that used to recover it as green; the dirty-tree refusal prints the exact stash and pop commands and its true reason; the reviewer diffs the epic as it was before the merge (`epic_base` recorded on the story and the bundle) instead of an empty range; `tldrx init` probes each declared command once and records `command_probes` beside a `commands:` allowlist it never guesses; `tldrx ship` opens a PR whose body describes the change and its known defects, with the handoff folded underneath, and a state refusal that honours settled stories' declared touches; reviewer turns carry their token split and the spend basis reads it, so a whole provider no longer reads as absent |
317
318
  | 0.9.2 | 2026-09-07 | `beta` | the Build executor decomposed: 4,351 lines became an orchestrator plus eight modules under src/core/build/ (review ledger and phase cost, money caps, the DoD runner with its preflight cache, worktrees and epic-branch claims, the reviewer bundle and review round) — a pure refactor proven byte-for-byte by a golden guard over prompts, ordered events, run.yml rows and exit codes across three scenarios; no behaviour changed, every remaining hardening fix now lands in a file a reviewer can hold |
318
319
  | 0.9.1 | 2026-09-06 | `beta` | records and money that do not lie: a turn without a provider USD figure is unmetered, never a metered $0.00; a refusal no longer discards the turn's cost (banked first, deduped only against marked rows); an event over the 4096-byte cap spills its text beside the run and names it instead of losing every row of the invocation; preflight rows carry `command_hash` and their own `checked_at`, so a cached red is re-probed when the command changed, after 30 minutes, or under --prepare; the fix list writes back git's canonical 40-hex sha; `tldrx facts add` exists with owner/driver attribution rendered in prompts; run.yml rows carry the provider's token split only when reported |
319
320
  | 0.9.0 | 2026-09-06 | `beta` | opt-in stack packs: four language pack bodies and thirteen framework overlays detected from manifests, interrogative by default (Defaults yield to any repo signal, Checks are questions with `verify:` hints), behind one per-project switch (`tldrx expert packs enable`) that materialises into `.tldrx/experts/<lang>-stack/` without touching trained knowledge; the Build reviewer gets `## Stack checks (the repo's own conventions win)`; every stage names the project's `.claude/skills` and the developer may invoke them; workspace.yml records overlays with evidence and skills with a tracked flag, `version: 1` unchanged |
@@ -1,17 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  FactsStore
4
- } from "./chunk-f6484ykg.js";
4
+ } from "./chunk-ecxt4p6n.js";
5
5
  import {
6
6
  parseHookInput,
7
7
  readStdin
8
- } from "./chunk-x3zy7zrn.js";
8
+ } from "./chunk-3rwbyema.js";
9
9
  import {
10
10
  EventLog,
11
11
  PHASE_ID_RE
12
- } from "./chunk-fv66tdsm.js";
13
- import"./chunk-5w5eg00k.js";
14
- import"./chunk-8awygmqw.js";
12
+ } from "./chunk-dtpjhddq.js";
13
+ import"./chunk-gvs5fvve.js";
15
14
  import {
16
15
  MAX_FACT_CHARS,
17
16
  detectAnswered,
@@ -19,12 +18,11 @@ import {
19
18
  recordAnswer,
20
19
  replaceBlock,
21
20
  serializeQuestions
22
- } from "./chunk-vyvjbbmb.js";
23
- import"./chunk-39zh2e44.js";
21
+ } from "./chunk-14496ta8.js";
24
22
  import {
25
23
  PROJECT_WORK_DIR,
26
24
  factsPath
27
- } from "./chunk-y3cxqvnx.js";
25
+ } from "./chunk-f6d6cp4h.js";
28
26
 
29
27
  // src/hooks/answer-capture.ts
30
28
  import { existsSync as existsSync3 } from "fs";
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  budgetGateDeny
4
- } from "./chunk-cf4az0j5.js";
4
+ } from "./chunk-7ye9jde9.js";
5
5
  import {
6
6
  allow,
7
7
  deny,
8
8
  readPayload,
9
9
  runHook,
10
10
  toolInput
11
- } from "./chunk-2y3kwfrx.js";
12
- import"./chunk-x3zy7zrn.js";
11
+ } from "./chunk-dzm2bzqp.js";
12
+ import"./chunk-3rwbyema.js";
13
13
  import {
14
14
  asRunBudget,
15
15
  currentActor,
@@ -29,23 +29,20 @@ import {
29
29
  validateRunBudget,
30
30
  wouldExceed,
31
31
  wouldExceedHostTokens
32
- } from "./chunk-hg43zexg.js";
32
+ } from "./chunk-cmx01x0r.js";
33
33
  import {
34
34
  EventLog
35
- } from "./chunk-fv66tdsm.js";
36
- import"./chunk-rrkdfk7s.js";
37
- import"./chunk-5w5eg00k.js";
35
+ } from "./chunk-dtpjhddq.js";
38
36
  import {
39
37
  noteDeprecations
40
- } from "./chunk-vyvjbbmb.js";
41
- import"./chunk-39zh2e44.js";
38
+ } from "./chunk-14496ta8.js";
42
39
  import {
43
40
  PROJECT_WORK_DIR,
44
41
  findWorkspaceRoot,
45
42
  locateWork,
46
43
  parseYaml,
47
44
  stageYamlPath
48
- } from "./chunk-y3cxqvnx.js";
45
+ } from "./chunk-f6d6cp4h.js";
49
46
 
50
47
  // src/hooks/budget-gate.ts
51
48
  import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSrcToken
3
- } from "./chunk-y3cxqvnx.js";
3
+ } from "./chunk-f6d6cp4h.js";
4
4
 
5
5
  // src/core/text/questions.ts
6
6
  var REQUIRED_METADATA_KEYS = ["id", "status", "area", "asked_by", "asked_at"];
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runtime
3
- } from "./chunk-y3cxqvnx.js";
3
+ } from "./chunk-f6d6cp4h.js";
4
4
 
5
5
  // src/core/hooks/passthrough.ts
6
6
  async function readStdin() {
@@ -3,7 +3,7 @@ import {
3
3
  EMPTY_SECTION_RULE,
4
4
  noneBullet,
5
5
  srcRule
6
- } from "./chunk-y3cxqvnx.js";
6
+ } from "./chunk-f6d6cp4h.js";
7
7
 
8
8
  // src/hooks/lib/messages.ts
9
9
  var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
@@ -7,7 +7,7 @@ import {
7
7
  nowRfc3339,
8
8
  openRunViews,
9
9
  validateRunBudget
10
- } from "./chunk-hg43zexg.js";
10
+ } from "./chunk-cmx01x0r.js";
11
11
  import {
12
12
  EventLog,
13
13
  asRunFile,
@@ -19,7 +19,7 @@ import {
19
19
  isTerminal,
20
20
  stageAt,
21
21
  validateRunFile
22
- } from "./chunk-fv66tdsm.js";
22
+ } from "./chunk-dtpjhddq.js";
23
23
  import {
24
24
  backupPathFor,
25
25
  isAlive,
@@ -28,17 +28,17 @@ import {
28
28
  workspaceRootOfRunDir,
29
29
  writeAtomic,
30
30
  yamlScalar
31
- } from "./chunk-8awygmqw.js";
31
+ } from "./chunk-gvs5fvve.js";
32
32
  import {
33
33
  noteDeprecations,
34
34
  openBlocks,
35
35
  parseQuestions
36
- } from "./chunk-vyvjbbmb.js";
36
+ } from "./chunk-14496ta8.js";
37
37
  import {
38
38
  listRunDirs,
39
39
  parseYaml,
40
40
  parseYamlRepairing
41
- } from "./chunk-y3cxqvnx.js";
41
+ } from "./chunk-f6d6cp4h.js";
42
42
 
43
43
  // src/core/statusline/runSnapshot.ts
44
44
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
@@ -1,37 +1,33 @@
1
1
  import {
2
2
  EVIDENCE_FILE,
3
3
  gateEvidenceRelPath
4
- } from "./chunk-fv66tdsm.js";
4
+ } from "./chunk-dtpjhddq.js";
5
5
  import {
6
6
  MAX_PLAN_STORIES,
7
7
  MAX_STORIES_PER_WAVE,
8
8
  MAX_WAVES,
9
+ PROJECT_FRAMEWORK_DIR,
10
+ SRC_PATTERNS,
9
11
  STORY_ID_RE,
10
12
  WAVE_ID_RE,
11
- requirePattern,
12
- requireStringList,
13
- requireVersion1
14
- } from "./chunk-5w5eg00k.js";
15
- import {
16
13
  asDocument,
14
+ classifySrc,
17
15
  isRecord,
16
+ listRunDirs,
17
+ parseYaml,
18
+ readableSource,
18
19
  requireArray,
19
20
  requireEnum,
20
21
  requireKeys,
21
22
  requireNumber,
23
+ requirePattern,
22
24
  requireRecord,
23
25
  requireString,
26
+ requireStringList,
24
27
  requireVersion,
28
+ requireVersion1,
25
29
  result
26
- } from "./chunk-39zh2e44.js";
27
- import {
28
- PROJECT_FRAMEWORK_DIR,
29
- SRC_PATTERNS,
30
- classifySrc,
31
- listRunDirs,
32
- parseYaml,
33
- readableSource
34
- } from "./chunk-y3cxqvnx.js";
30
+ } from "./chunk-f6d6cp4h.js";
35
31
 
36
32
  // src/hooks/lib/runFile.ts
37
33
  import { existsSync, readFileSync } from "node:fs";
@@ -1,9 +1,8 @@
1
- import {
2
- parseFrontMatter
3
- } from "./chunk-5w5eg00k.js";
4
1
  import {
5
2
  asDocument,
6
3
  isRecord,
4
+ parseFrontMatter,
5
+ parseHandoff,
7
6
  requireArray,
8
7
  requireEnum,
9
8
  requireKeys,
@@ -11,10 +10,7 @@ import {
11
10
  requireString,
12
11
  requireVersion,
13
12
  result
14
- } from "./chunk-39zh2e44.js";
15
- import {
16
- parseHandoff
17
- } from "./chunk-y3cxqvnx.js";
13
+ } from "./chunk-f6d6cp4h.js";
18
14
 
19
15
  // src/core/events/EventLog.ts
20
16
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseHookInput,
3
3
  readStdin
4
- } from "./chunk-x3zy7zrn.js";
4
+ } from "./chunk-3rwbyema.js";
5
5
 
6
6
  // src/hooks/lib/decide.ts
7
7
  function deny(reason) {
@@ -3,7 +3,7 @@ import {
3
3
  withWorkspaceLock,
4
4
  workspaceRootOfFactsPath,
5
5
  writeAtomic
6
- } from "./chunk-8awygmqw.js";
6
+ } from "./chunk-gvs5fvve.js";
7
7
  import {
8
8
  FACT_CONFIDENCES,
9
9
  FACT_KINDS,
@@ -14,22 +14,20 @@ import {
14
14
  isLive,
15
15
  isRetired,
16
16
  noteDeprecations
17
- } from "./chunk-vyvjbbmb.js";
17
+ } from "./chunk-14496ta8.js";
18
18
  import {
19
+ SRC_PATTERNS,
19
20
  asDocument,
20
21
  isRecord,
22
+ parseYaml,
23
+ readableSource,
21
24
  requireArray,
22
25
  requireEnum,
23
26
  requireKeys,
24
27
  requireString,
25
28
  requireVersion,
26
29
  result
27
- } from "./chunk-39zh2e44.js";
28
- import {
29
- SRC_PATTERNS,
30
- parseYaml,
31
- readableSource
32
- } from "./chunk-y3cxqvnx.js";
30
+ } from "./chunk-f6d6cp4h.js";
33
31
 
34
32
  // src/core/facts/FactsStore.ts
35
33
  import { existsSync, readFileSync } from "node:fs";