tldr-experts 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +1 -0
- package/dist/hooks/session-start.js +8 -0
- package/dist/tldrx.js +56 -7
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## 0.13.1 — 2026-09-08
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- **A stage prompt now opens by saying what to do (#196).** On a real workspace at 0.13.0 a
|
|
9
|
+
What sub-agent was handed the whole 66,452-byte prompt and answered: *"I don't see an actual
|
|
10
|
+
request in your message — only system context, tldrx state, and template/expert file dumps."*
|
|
11
|
+
It was right. `renderParts` prepended nothing, so the document opened on `stage.md` — which is
|
|
12
|
+
a fill-in HANDOFF TEMPLATE that describes a finished document without ever saying that writing
|
|
13
|
+
it is the job. Grepping that prompt found zero occurrences of "You are", "## Produce", "your
|
|
14
|
+
task" or "write the following files", while line ~89 of it forbade "sections beyond the ones
|
|
15
|
+
listed under **Produce**" — a rule pointing at a heading that was never assembled. The agent
|
|
16
|
+
asked a question instead, wrote none of its six declared outputs, and the stage failed with
|
|
17
|
+
$0.29 spent. The prompt now leads with a generated brief: who the reader is, which stage of
|
|
18
|
+
which run, that the template below is to be FILLED, the exact path of every declared output,
|
|
19
|
+
and that a question goes in the questions file rather than back to an operator who is not
|
|
20
|
+
there. Generated from the same `outputs:` list `pending.json` records, so it cannot name a
|
|
21
|
+
path the commit will not look for.
|
|
22
|
+
Not a regression — a 2026-08-30 run of the same stage on the same model passed. What changed
|
|
23
|
+
is the ratio: the spliced citation grammar grew the stage section 5,007 B → 13,180 B and this
|
|
24
|
+
run's seed was small, so work material fell from 93% to 76% of the prompt. The instruction was
|
|
25
|
+
always missing; it had never been this outnumbered.
|
|
26
|
+
- **The "N runs are open" nudge stops talking to sub-agents (#196).** It was the one
|
|
27
|
+
imperative-shaped sentence in that agent's window — `tldrx: 7 runs are open — pass a run id to
|
|
28
|
+
next/answer/approve/…` — and the agent answered it, which is the most reasonable thing it
|
|
29
|
+
could have done with what it was given. `spawnAgent` now marks every child it spawns
|
|
30
|
+
(`TLDRX_SUBAGENT=1`) and `session-start` emits nothing when it sees the marker: those lines
|
|
31
|
+
orient a human who opened a session and may be in the wrong run, and a sub-agent was handed
|
|
32
|
+
its run in its prompt. An absent marker is a human's session and behaves exactly as before.
|
|
33
|
+
|
|
4
34
|
## 0.13.0 — 2026-09-08
|
|
5
35
|
|
|
6
36
|
### Added
|
package/README.md
CHANGED
|
@@ -316,6 +316,7 @@ back on the registry is 0.3.0.
|
|
|
316
316
|
|
|
317
317
|
| Version | Date | Status | Contains |
|
|
318
318
|
|---|---|---|---|
|
|
319
|
+
| 0.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 |
|
|
319
320
|
| 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 |
|
|
320
321
|
| 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 |
|
|
321
322
|
| 0.11.1 | 2026-09-08 | `beta` | operating a run when nobody is watching: the drive mandate now names the host's own context as the costliest instrument and tells the driver what to READ back — a sub-agent's outcome from its `result.json` and the ledger, never its transcript, each sub-agent briefed to report its turn in ten lines, and a cited file re-opened to VERIFY at a gate rather than re-read for a claim already made — paid for inside the unchanged line budgets rather than appended; `.tldrx/workspace.yml` takes one optional `notify:` block whose command is handed a `version: 1` JSON object on stdin at each moment a person is needed (an open question with its options, its recommendation and the literal `tldrx answer` line; a gate with its approve line; a finished or failed run with what its exit code's family means; a budget warning with both numbers), split to argv and never shelled, with a notifier that will not split, is not there, exits non-zero or hangs recorded as `notify.failed` instead of failing the run; `tldrx run auto --wait-answers <duration>` polls the question files and resumes when the answer lands instead of exiting 4 the moment a stage parks, and `--notify-every <duration>` sends a timer-driven heartbeat that reminds rather than reassures while a run is parked; and a new EN+ES guide page walks the whole unattended loop — host mode versus the engine, the payload keys, a dependency-free Node adapter, a first-run checklist and the four ways it silently does nothing — naming no messaging service, by the same reasoning the mandate has always given |
|
|
@@ -981,11 +981,19 @@ function sessionStartLines(status, max) {
|
|
|
981
981
|
}
|
|
982
982
|
return lines;
|
|
983
983
|
}
|
|
984
|
+
// src/core/facilitator/subagent.ts
|
|
985
|
+
var SUBAGENT_ENV_VAR = "TLDRX_SUBAGENT";
|
|
986
|
+
function isSubagentEnv(env = process.env) {
|
|
987
|
+
return (env[SUBAGENT_ENV_VAR] ?? "") !== "";
|
|
988
|
+
}
|
|
989
|
+
|
|
984
990
|
// src/hooks/session-start.ts
|
|
985
991
|
var MAX_LINES = 3;
|
|
986
992
|
var MAX_PENDING_LINES = 3;
|
|
987
993
|
var MAX_OPEN_LISTED = 8;
|
|
988
994
|
await runHook("session-start", async () => {
|
|
995
|
+
if (isSubagentEnv())
|
|
996
|
+
return;
|
|
989
997
|
const payload = await readPayload();
|
|
990
998
|
const root = findWorkspaceRoot(payload.cwd ?? process.cwd());
|
|
991
999
|
if (root === null)
|
package/dist/tldrx.js
CHANGED
|
@@ -19707,6 +19707,12 @@ function millis(value) {
|
|
|
19707
19707
|
return Number.isFinite(at) ? at : null;
|
|
19708
19708
|
}
|
|
19709
19709
|
|
|
19710
|
+
// src/core/facilitator/subagent.ts
|
|
19711
|
+
var SUBAGENT_ENV_VAR = "TLDRX_SUBAGENT";
|
|
19712
|
+
function subagentEnv(base) {
|
|
19713
|
+
return { ...base ?? process.env, [SUBAGENT_ENV_VAR]: "1" };
|
|
19714
|
+
}
|
|
19715
|
+
|
|
19710
19716
|
// src/core/facilitator/envelope.ts
|
|
19711
19717
|
var ENVELOPE_SCHEMA = {
|
|
19712
19718
|
type: "object",
|
|
@@ -19859,7 +19865,7 @@ async function spawnAgent(request) {
|
|
|
19859
19865
|
stdin: request.prompt,
|
|
19860
19866
|
timeoutMs: request.timeoutMs,
|
|
19861
19867
|
...cap > 0 ? { signal: controller.signal } : {},
|
|
19862
|
-
env: request.env
|
|
19868
|
+
env: subagentEnv(request.env),
|
|
19863
19869
|
onStdoutLine: (line) => {
|
|
19864
19870
|
for (const event2 of stream.push(line)) {
|
|
19865
19871
|
if (!capped && event2.kind === "tool-done" && (event2.countsAsRead === true || isReadTool(event2.name))) {
|
|
@@ -24503,6 +24509,24 @@ function isNotInlined(input) {
|
|
|
24503
24509
|
return true;
|
|
24504
24510
|
return input.totalBytes !== undefined && (input.inlinedBytes ?? 0) === 0;
|
|
24505
24511
|
}
|
|
24512
|
+
var STAGE_PREAMBLE_MARKER = "You are the tldrx stage sub-agent";
|
|
24513
|
+
function renderStagePreamble(preamble) {
|
|
24514
|
+
const questions = preamble.outputs.find((path) => path.split("/").pop() === QUESTIONS_FILE2);
|
|
24515
|
+
const lines = [
|
|
24516
|
+
`${STAGE_PREAMBLE_MARKER} for stage \`${preamble.stage}\` of run \`${preamble.run}\`.`,
|
|
24517
|
+
"This prompt is the entire request: there is no other message to find, and no operator to reply to.",
|
|
24518
|
+
"",
|
|
24519
|
+
"Do this now:",
|
|
24520
|
+
"",
|
|
24521
|
+
"1. Fill in the template below — replace every `<…>` placeholder with real, sourced content, and keep the sections it declares.",
|
|
24522
|
+
"2. Write the result to the files this stage declares, all of them, at exactly these paths:",
|
|
24523
|
+
...preamble.outputs.map((path) => ` - \`${path}\``),
|
|
24524
|
+
questions === undefined ? "3. Do not reply with a question. Record anything you cannot settle as an explicit unknown inside the outputs above." : `3. Do not reply with a question. Record anything you cannot settle in \`${questions}\`, in the shape the template gives.`
|
|
24525
|
+
];
|
|
24526
|
+
return lines.join(`
|
|
24527
|
+
`);
|
|
24528
|
+
}
|
|
24529
|
+
var QUESTIONS_FILE2 = "questions.md";
|
|
24506
24530
|
var INPUTS_HEADING = "Inputs";
|
|
24507
24531
|
var PREVIOUS_ATTEMPT_HEADING = "Previous attempt";
|
|
24508
24532
|
function buildPrompt(parts) {
|
|
@@ -24510,10 +24534,19 @@ function buildPrompt(parts) {
|
|
|
24510
24534
|
}
|
|
24511
24535
|
function renderParts(parts) {
|
|
24512
24536
|
const substituted = cutSection(cutSection(cutSection(cutSection(substitute(parts.stageMd, parts.values), INPUTS_HEADING), DISPATCH_NOTES_HEADING), PROJECT_SKILLS_HEADING), PREVIOUS_ATTEMPT_HEADING);
|
|
24513
|
-
const out = [
|
|
24514
|
-
|
|
24515
|
-
|
|
24516
|
-
|
|
24537
|
+
const out = [];
|
|
24538
|
+
if (parts.preamble !== undefined) {
|
|
24539
|
+
out.push({
|
|
24540
|
+
kind: "preamble",
|
|
24541
|
+
name: "preamble",
|
|
24542
|
+
text: `${renderStagePreamble(parts.preamble)}
|
|
24543
|
+
|
|
24544
|
+
---
|
|
24545
|
+
`
|
|
24546
|
+
});
|
|
24547
|
+
}
|
|
24548
|
+
out.push({ kind: "stage", name: "stage.md", text: `${substituted.trimEnd()}
|
|
24549
|
+
` });
|
|
24517
24550
|
for (const expert of parts.experts) {
|
|
24518
24551
|
out.push({
|
|
24519
24552
|
kind: "expert-body",
|
|
@@ -30365,6 +30398,10 @@ function buildLedger(input) {
|
|
|
30365
30398
|
for (const part of input.parts) {
|
|
30366
30399
|
const bytes = byteLength(part.text);
|
|
30367
30400
|
switch (part.kind) {
|
|
30401
|
+
case "preamble":
|
|
30402
|
+
stage2 += bytes;
|
|
30403
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30404
|
+
break;
|
|
30368
30405
|
case "stage":
|
|
30369
30406
|
stage2 += bytes;
|
|
30370
30407
|
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
@@ -30486,6 +30523,8 @@ function label(row2) {
|
|
|
30486
30523
|
return `expert ${row2.name} body`;
|
|
30487
30524
|
case "expert-knowledge":
|
|
30488
30525
|
return `expert ${row2.name} knowledge`;
|
|
30526
|
+
case "preamble":
|
|
30527
|
+
return "stage preamble";
|
|
30489
30528
|
case "dispatch-notes":
|
|
30490
30529
|
return "dispatch notes";
|
|
30491
30530
|
case "project-skills":
|
|
@@ -32128,6 +32167,11 @@ function featurePrompt(ctx, feature, diffs) {
|
|
|
32128
32167
|
knowledgeBytes: ctx.spec.knowledgeMaxBytes
|
|
32129
32168
|
});
|
|
32130
32169
|
const body = buildPrompt({
|
|
32170
|
+
preamble: {
|
|
32171
|
+
stage: ctx.stageId,
|
|
32172
|
+
run: ctx.runId,
|
|
32173
|
+
outputs: [relative12(ctx.root, join78(ctx.runDir, watcherRelPath(feature.id)))]
|
|
32174
|
+
},
|
|
32131
32175
|
stageMd,
|
|
32132
32176
|
values: {
|
|
32133
32177
|
run: ctx.runId,
|
|
@@ -33656,7 +33700,7 @@ function strings4(value) {
|
|
|
33656
33700
|
var RUN_FILE = "run.yml";
|
|
33657
33701
|
var BUDGET_FILE = "budget.yml";
|
|
33658
33702
|
var EVENTS_FILE = "events.jsonl";
|
|
33659
|
-
var
|
|
33703
|
+
var QUESTIONS_FILE3 = "questions.md";
|
|
33660
33704
|
var HANDOFF_FILE2 = "handoff.md";
|
|
33661
33705
|
function workDir(root2) {
|
|
33662
33706
|
return join83(root2, PROJECT_WORK_DIR);
|
|
@@ -33751,7 +33795,7 @@ function loadPhaseArtefacts(loaded, phase) {
|
|
|
33751
33795
|
const path = join83(loaded.dir, phase, name);
|
|
33752
33796
|
return existsSync66(path) ? readFileSync60(path, "utf8") : null;
|
|
33753
33797
|
};
|
|
33754
|
-
return { phase, handoff: read2(HANDOFF_FILE2), questions: read2(
|
|
33798
|
+
return { phase, handoff: read2(HANDOFF_FILE2), questions: read2(QUESTIONS_FILE3) };
|
|
33755
33799
|
}
|
|
33756
33800
|
function loadGateEvidence(loaded, relPath) {
|
|
33757
33801
|
if (relPath === "" || relPath.includes("..") || relPath.startsWith("/"))
|
|
@@ -38300,6 +38344,11 @@ function assemblePrompt(store, options, spec, stage2, inputs, ctx, seed) {
|
|
|
38300
38344
|
const dispatchNotes = loadDispatchNotes(store.runDir, [stage2.id]);
|
|
38301
38345
|
const absentInputs = absentDeclaredInputs(store, spec, ctx);
|
|
38302
38346
|
const parts = renderParts({
|
|
38347
|
+
preamble: {
|
|
38348
|
+
stage: stage2.id,
|
|
38349
|
+
run: store.runId,
|
|
38350
|
+
outputs: expandAll(spec.planned.outputs, store.run.repos).map((declared2) => relative17(options.root, resolveDeclared(declared2, ctx)))
|
|
38351
|
+
},
|
|
38303
38352
|
stageMd,
|
|
38304
38353
|
absentInputs,
|
|
38305
38354
|
dispatchNotes: dispatchNotes.body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework - five stages, a gate on every one, and every claim cited or refused. Installs the `tldrx` (and `tldr-experts`) command. Beta.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alan Martinez",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$doc": "Shape verified from https://code.claude.com/docs/en/plugins.md (Quickstart > Create the plugin manifest). Fields used here: name, description, version, author.name. Only plugin.json goes inside .claude-plugin/; skills/, agents/ and hooks/ live at the plugin root.",
|
|
3
3
|
"name": "tldrx",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework. Five stages, a gate on every one, every claim cited or refused. Beta.",
|
|
5
|
-
"version": "0.13.
|
|
5
|
+
"version": "0.13.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|