tldr-experts 0.3.0 → 0.4.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 +1957 -0
- package/README.md +99 -12
- package/dist/hooks/answer-capture.js +21 -12
- package/dist/hooks/budget-gate.js +95 -11
- package/dist/hooks/{chunk-0bt6yb2q.js → chunk-39zh2e44.js} +10 -1
- package/dist/hooks/chunk-4cp363kv.js +1766 -0
- package/dist/hooks/{chunk-t8tdv11p.js → chunk-7y2dq0pj.js} +1 -1
- package/dist/hooks/chunk-9zsqxr6y.js +213 -0
- package/dist/hooks/{chunk-a8p2rc94.js → chunk-b8kxzna2.js} +1 -1
- package/dist/hooks/{chunk-j234zf0t.js → chunk-c6t5nx0r.js} +37 -166
- package/dist/hooks/{chunk-g395gk7e.js → chunk-m3mewgnw.js} +36 -17
- package/dist/hooks/chunk-phmdk72a.js +164 -0
- package/dist/hooks/chunk-rpcxsqh3.js +199 -0
- package/dist/hooks/{chunk-azctppjh.js → chunk-rz541e2b.js} +7 -1
- package/dist/hooks/{chunk-y0jdr3et.js → chunk-s1c5h7yx.js} +71 -29
- package/dist/hooks/{chunk-p274ckxv.js → chunk-sq44k6g2.js} +155 -12
- package/dist/hooks/{chunk-x98qs959.js → chunk-t1ywrfr4.js} +35 -42
- package/dist/hooks/{chunk-1zwcxd3f.js → chunk-tzzwddct.js} +1 -1
- package/dist/hooks/claim-sources.js +31 -23
- package/dist/hooks/dod-gate.js +12 -37
- package/dist/hooks/no-reask.js +9 -9
- package/dist/hooks/session-start.js +176 -422
- package/dist/hooks/statusline.js +14 -11
- package/dist/tldrx.js +22798 -12309
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/tldrx/SKILL.md +16 -3
- package/stages/build/stage.md +5 -0
- package/stages/build/stage.yml +2 -1
- package/stages/plan/stage.md +11 -0
- package/templates/budget.yml +5 -0
- package/templates/epic.md +5 -3
- package/dist/hooks/chunk-ae6bkfs5.js +0 -0
- package/dist/hooks/chunk-kw4tffzf.js +0 -139
- package/dist/hooks/chunk-sdjnnmzz.js +0 -497
- package/dist/hooks/chunk-t56k6146.js +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "tldr-experts: a lightweight, file-based AI development workflow. Installs the `tldrx` (and `tldr-experts`) command. Alpha.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alan Martinez",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"test": "bun test",
|
|
50
50
|
"build": "bun scripts/build.ts",
|
|
51
|
+
"docs:dev": "cd docs-site && bun install && bun run dev",
|
|
52
|
+
"docs:build": "cd docs-site && bun install --frozen-lockfile && bun run build",
|
|
51
53
|
"typecheck": "tsc --noEmit",
|
|
52
54
|
"doctor": "bun bin/tldrx.ts doctor",
|
|
53
55
|
"prepublishOnly": "bun run build"
|
|
@@ -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: a file-based AI development workflow. One loop, five phases, everything on disk. Alpha.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|
|
@@ -21,6 +21,11 @@ A facilitator, not an author. You read files, hand ONE sub-agent ONE task, write
|
|
|
21
21
|
the result back, and stop at the gate. You hold no state between steps — the files
|
|
22
22
|
are the state. You do not decide anything that is the human's to decide.
|
|
23
23
|
|
|
24
|
+
The framework's `stages/`, `workflows/` and `templates/` live in the installed package —
|
|
25
|
+
`$(npm root -g)/tldr-experts/` globally, `node_modules/tldr-experts/` locally, and
|
|
26
|
+
`tldrx doctor` prints the exact path on its `framework` line. This project's overrides
|
|
27
|
+
live in `.tldrx/stages/` and `.tldrx/workflows/`, and win. Never `find /` for them.
|
|
28
|
+
|
|
24
29
|
## Step 1, always
|
|
25
30
|
|
|
26
31
|
```
|
|
@@ -55,8 +60,11 @@ Never answer on their behalf, never pick a default, never "assume yes":
|
|
|
55
60
|
edit `split.yml` if they want different runs.
|
|
56
61
|
- a gate, an answer, or any ADR/decision document — quote it and ask.
|
|
57
62
|
- a gate `tldrx status` reports as signed `by: auto` that they disagree with. It signs
|
|
58
|
-
only when its
|
|
59
|
-
the note — it carries all
|
|
63
|
+
only when its seven conditions hold and cannot judge whether a decision was RIGHT: quote
|
|
64
|
+
the note — it carries all seven measured values — and ask before revoking.
|
|
65
|
+
- a Build story left `blocked` that they want built anyway. Two reviewers judged the diff
|
|
66
|
+
and were not necessarily wrong; overruling them costs another two turns and is theirs to
|
|
67
|
+
decide. Quote `04-build/log/<id>.md` — it says what each verdict was and why — and ask.
|
|
60
68
|
|
|
61
69
|
**You act** once they have said what they want — these are mechanical:
|
|
62
70
|
|
|
@@ -65,6 +73,11 @@ Never answer on their behalf, never pick a default, never "assume yes":
|
|
|
65
73
|
after they said yes · `tldrx reject --run <id> --note "<their reason>"`, and
|
|
66
74
|
`tldrx reject --run <id> --stage <phase>/<stage> --note "…"` to take back an
|
|
67
75
|
approval already given (including one the harness signed `by: auto`).
|
|
76
|
+
- `tldrx story reopen <id> --run <id> --note "<their reason>"` once they have said a
|
|
77
|
+
blocked story must be built anyway: it puts that one story back to `todo` and restarts
|
|
78
|
+
its attempt counter at 1 of 2, keeping every earlier attempt on the record. It does NOT
|
|
79
|
+
send the stage back — if the Build stage is at its gate, `tldrx reject` does that first,
|
|
80
|
+
and the reopen's own output names which one applies.
|
|
68
81
|
- `tldrx questions lint --run <id>` when a stage wrote a `questions.md`: it exits 2
|
|
69
82
|
if any block is invisible to the parser, and `--fix` converts it without changing
|
|
70
83
|
a word. An unreadable file reads as "no questions" to everything downstream.
|
|
@@ -152,7 +165,7 @@ retries are where the money is) and `tldrx budget show`. Quote them, never the a
|
|
|
152
165
|
from disk. A sub-agent reporting "ok" is not evidence.
|
|
153
166
|
6. **Say which you are in: measured, inferred, or assumed.** Never blur them.
|
|
154
167
|
7. **Stop at gates.** You never close one — not even an `auto` gate, which the
|
|
155
|
-
framework closes itself and only when all
|
|
168
|
+
framework closes itself and only when all seven of its conditions hold.
|
|
156
169
|
8. **A checked section holds at least one item.** Nothing to report is still an
|
|
157
170
|
item: `- none [src: absent:<what you looked at>]`. Prose is refused.
|
|
158
171
|
9. **`.tldrx/` and `tldrx-work/` are committed** — the files ARE the state. `init`
|
package/stages/build/stage.md
CHANGED
|
@@ -56,6 +56,11 @@ Per story, in `waves.yml` order:
|
|
|
56
56
|
evidence and is never recorded as any.
|
|
57
57
|
- A reviewer's `changes` requeues the story ONCE, with the review under
|
|
58
58
|
`## Previous attempt`. A second `changes` blocks it.
|
|
59
|
+
- An acceptance criterion that embeds a literal command or pattern is validated BEFORE
|
|
60
|
+
the edit — run against the current tree first, and a criterion that reports zero while
|
|
61
|
+
the goal says the work exists is broken, so the real inventory becomes the completion
|
|
62
|
+
test and the discrepancy goes in the handoff (the criterion text is not the story's to
|
|
63
|
+
edit). The developer prompt carries this rule verbatim.
|
|
59
64
|
- A DoD failure or a merge conflict blocks that story only — the wave carries on.
|
|
60
65
|
- Nothing pushes. No epic is merged into a default branch.
|
|
61
66
|
|
package/stages/build/stage.yml
CHANGED
|
@@ -22,7 +22,8 @@ model: sonnet
|
|
|
22
22
|
effort: high
|
|
23
23
|
budget_usd: 9
|
|
24
24
|
timeout_s: 1800
|
|
25
|
-
#
|
|
25
|
+
# A stage that cuts branches and fans out per-story sub-agents has no ONE dispatch to
|
|
26
|
+
# describe, so a dry run is refused
|
|
26
27
|
# rather than half-honoured (spec §5, Build executor).
|
|
27
28
|
dry_run_allowed: false
|
|
28
29
|
|
package/stages/plan/stage.md
CHANGED
|
@@ -52,6 +52,17 @@
|
|
|
52
52
|
- `waves.yml`
|
|
53
53
|
- `budget.yml`
|
|
54
54
|
|
|
55
|
+
The first three are **schema'd files a machine parses**, not free markdown. Their exact
|
|
56
|
+
front matter, id shapes, enums and size caps are under **Output schemas** below — read
|
|
57
|
+
that section before writing any of them.
|
|
58
|
+
|
|
59
|
+
## Output schemas
|
|
60
|
+
|
|
61
|
+
_The framework renders this section from the validators that gate this stage, so it is the
|
|
62
|
+
contract itself rather than a copy of it. If you are reading this sentence instead of a
|
|
63
|
+
schema, the bundle was not assembled by `tldrx next`: read `src/core/schemas/story.ts`,
|
|
64
|
+
`epic.ts` and `waves.ts` before writing anything._
|
|
65
|
+
|
|
55
66
|
## Gate
|
|
56
67
|
|
|
57
68
|
Blocked on: **<human approval | checks green>** — `stage.yml`'s `gate.type`. What is
|
package/templates/budget.yml
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
# ceiling_usd is a refusal, not a warning: a stage whose estimate exceeds what
|
|
5
5
|
# remains does not start. spent_usd is rolled up from events.jsonl — never typed
|
|
6
6
|
# by hand, never estimated by an agent.
|
|
7
|
+
#
|
|
8
|
+
# `economy:` is optional and says what these numbers are DENOMINATED IN:
|
|
9
|
+
# metered-usd (the default) dollars a spawn may spend
|
|
10
|
+
# host-tokens a host-session token allowance — not money, never a spawn cap
|
|
11
|
+
# Leave it out and everything behaves as it always has. See spec §2.11.
|
|
7
12
|
version: 1
|
|
8
13
|
run: "260828-example-slug"
|
|
9
14
|
ceiling_usd: 25
|
package/templates/epic.md
CHANGED
|
@@ -23,9 +23,11 @@ What this epic is for, sourced. Two or three sentences.
|
|
|
23
23
|
|
|
24
24
|
## Stories
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
The `stories:` list in the front matter above is the membership, and it is the whole
|
|
27
|
+
of it. Each story has its own file at `03-plan/stories/<id>.md`, and its status, its
|
|
28
|
+
repo and its `depends_on` live in THAT file's front matter, where the Build phase
|
|
29
|
+
writes them. None of it is copied here: a copy with no writer is wrong from the
|
|
30
|
+
moment the first story lands (gh #50).
|
|
29
31
|
|
|
30
32
|
## Integration test
|
|
31
33
|
|
|
File without changes
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
hasSrcMarker,
|
|
3
|
-
parseSrcToken,
|
|
4
|
-
resolveSrc
|
|
5
|
-
} from "./chunk-g395gk7e.js";
|
|
6
|
-
|
|
7
|
-
// src/core/text/handoff.ts
|
|
8
|
-
var HANDOFF_SECTIONS = ["Findings", "Decisions", "Unknowns", "Evidence ledger"];
|
|
9
|
-
var H2_RE = /^##\s+(.+?)\s*$/;
|
|
10
|
-
var BULLET_RE = /^(?: {0,3}-|\d{1,9}[.)])\s+(\S.*)$/;
|
|
11
|
-
var CONTINUATION_RE = /^\s+\S/;
|
|
12
|
-
function parseHandoff(text) {
|
|
13
|
-
const lines = text.split(`
|
|
14
|
-
`);
|
|
15
|
-
const sections = [];
|
|
16
|
-
const headings = [];
|
|
17
|
-
let current = null;
|
|
18
|
-
let pending = null;
|
|
19
|
-
const flushBullet = () => {
|
|
20
|
-
if (pending === null)
|
|
21
|
-
return;
|
|
22
|
-
if (current !== null) {
|
|
23
|
-
const joined = pending.parts.join(" ");
|
|
24
|
-
current.bullets.push({ line: pending.line, text: joined, token: parseSrcToken(joined) });
|
|
25
|
-
}
|
|
26
|
-
pending = null;
|
|
27
|
-
};
|
|
28
|
-
const flush = () => {
|
|
29
|
-
flushBullet();
|
|
30
|
-
if (current !== null)
|
|
31
|
-
sections.push(current);
|
|
32
|
-
current = null;
|
|
33
|
-
};
|
|
34
|
-
for (let i = 0;i < lines.length; i++) {
|
|
35
|
-
const line = lines[i] ?? "";
|
|
36
|
-
const heading = H2_RE.exec(line);
|
|
37
|
-
if (heading !== null && heading[1] !== undefined) {
|
|
38
|
-
flush();
|
|
39
|
-
headings.push(heading[1]);
|
|
40
|
-
current = { name: heading[1], headingLine: i + 1, bullets: [] };
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (line.startsWith("# ")) {
|
|
44
|
-
flush();
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (current === null)
|
|
48
|
-
continue;
|
|
49
|
-
const bullet = BULLET_RE.exec(line);
|
|
50
|
-
if (bullet !== null && bullet[1] !== undefined) {
|
|
51
|
-
flushBullet();
|
|
52
|
-
pending = { line: i + 1, parts: [bullet[1]] };
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (pending !== null && CONTINUATION_RE.test(line)) {
|
|
56
|
-
pending.parts.push(line.trim());
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
flushBullet();
|
|
60
|
-
}
|
|
61
|
-
flush();
|
|
62
|
-
return { sections, headings };
|
|
63
|
-
}
|
|
64
|
-
function isHandoff(text) {
|
|
65
|
-
return missingSections(parseHandoff(text)).length === 0;
|
|
66
|
-
}
|
|
67
|
-
function missingSections(handoff) {
|
|
68
|
-
const present = handoff.headings;
|
|
69
|
-
const missing = [];
|
|
70
|
-
let cursor = 0;
|
|
71
|
-
for (const required of HANDOFF_SECTIONS) {
|
|
72
|
-
const at = present.indexOf(required, cursor);
|
|
73
|
-
if (at === -1)
|
|
74
|
-
missing.push(required);
|
|
75
|
-
else
|
|
76
|
-
cursor = at + 1;
|
|
77
|
-
}
|
|
78
|
-
return missing;
|
|
79
|
-
}
|
|
80
|
-
var MAX_BULLETS = 200;
|
|
81
|
-
function validateHandoff(text, ctx) {
|
|
82
|
-
const handoff = parseHandoff(text);
|
|
83
|
-
const missing = missingSections(handoff);
|
|
84
|
-
const emptySections = [];
|
|
85
|
-
const unsourced = [];
|
|
86
|
-
const malformed = [];
|
|
87
|
-
const unresolved = [];
|
|
88
|
-
const unverified = [];
|
|
89
|
-
let bulletCount = 0;
|
|
90
|
-
const required = new Set(HANDOFF_SECTIONS);
|
|
91
|
-
for (const section of handoff.sections) {
|
|
92
|
-
if (!required.has(section.name))
|
|
93
|
-
continue;
|
|
94
|
-
if (section.bullets.length === 0) {
|
|
95
|
-
emptySections.push({ name: section.name, line: section.headingLine });
|
|
96
|
-
}
|
|
97
|
-
for (const bullet of section.bullets) {
|
|
98
|
-
bulletCount++;
|
|
99
|
-
if (bullet.token === null) {
|
|
100
|
-
if (hasSrcMarker(bullet.text)) {
|
|
101
|
-
malformed.push({
|
|
102
|
-
line: bullet.line,
|
|
103
|
-
message: "malformed citation — the `[src: …]` token must be the last thing on the line " + "(closing quotes, brackets and a final `.` are allowed after it, words are not)"
|
|
104
|
-
});
|
|
105
|
-
} else {
|
|
106
|
-
unsourced.push(bullet.line);
|
|
107
|
-
}
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
for (const error of bullet.token.errors) {
|
|
111
|
-
unresolved.push({ line: bullet.line, message: `[src: ${error.raw}] — ${error.message}` });
|
|
112
|
-
}
|
|
113
|
-
const claim = bullet.text.replace(bullet.token.raw, " ").trim();
|
|
114
|
-
for (const ref of bullet.token.refs) {
|
|
115
|
-
const resolution = resolveSrc(ref, ctx, section.name, claim);
|
|
116
|
-
const issue = { line: bullet.line, message: `[src: ${ref.raw}] — ${resolution.message ?? "unresolvable"}` };
|
|
117
|
-
if (resolution.outcome === "refused")
|
|
118
|
-
unresolved.push(issue);
|
|
119
|
-
else if (resolution.outcome === "unverified")
|
|
120
|
-
unverified.push(issue);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (bulletCount > MAX_BULLETS) {
|
|
125
|
-
unresolved.push({ line: 0, message: `${bulletCount} bullets exceeds the ${MAX_BULLETS} cap` });
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
ok: missing.length === 0 && emptySections.length === 0 && unsourced.length === 0 && malformed.length === 0 && unresolved.length === 0,
|
|
129
|
-
missingSections: missing,
|
|
130
|
-
emptySections,
|
|
131
|
-
unsourced,
|
|
132
|
-
malformed,
|
|
133
|
-
unresolved,
|
|
134
|
-
unverified,
|
|
135
|
-
bulletCount
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export { isHandoff, validateHandoff };
|