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.
Files changed (36) hide show
  1. package/CHANGELOG.md +1957 -0
  2. package/README.md +99 -12
  3. package/dist/hooks/answer-capture.js +21 -12
  4. package/dist/hooks/budget-gate.js +95 -11
  5. package/dist/hooks/{chunk-0bt6yb2q.js → chunk-39zh2e44.js} +10 -1
  6. package/dist/hooks/chunk-4cp363kv.js +1766 -0
  7. package/dist/hooks/{chunk-t8tdv11p.js → chunk-7y2dq0pj.js} +1 -1
  8. package/dist/hooks/chunk-9zsqxr6y.js +213 -0
  9. package/dist/hooks/{chunk-a8p2rc94.js → chunk-b8kxzna2.js} +1 -1
  10. package/dist/hooks/{chunk-j234zf0t.js → chunk-c6t5nx0r.js} +37 -166
  11. package/dist/hooks/{chunk-g395gk7e.js → chunk-m3mewgnw.js} +36 -17
  12. package/dist/hooks/chunk-phmdk72a.js +164 -0
  13. package/dist/hooks/chunk-rpcxsqh3.js +199 -0
  14. package/dist/hooks/{chunk-azctppjh.js → chunk-rz541e2b.js} +7 -1
  15. package/dist/hooks/{chunk-y0jdr3et.js → chunk-s1c5h7yx.js} +71 -29
  16. package/dist/hooks/{chunk-p274ckxv.js → chunk-sq44k6g2.js} +155 -12
  17. package/dist/hooks/{chunk-x98qs959.js → chunk-t1ywrfr4.js} +35 -42
  18. package/dist/hooks/{chunk-1zwcxd3f.js → chunk-tzzwddct.js} +1 -1
  19. package/dist/hooks/claim-sources.js +31 -23
  20. package/dist/hooks/dod-gate.js +12 -37
  21. package/dist/hooks/no-reask.js +9 -9
  22. package/dist/hooks/session-start.js +176 -422
  23. package/dist/hooks/statusline.js +14 -11
  24. package/dist/tldrx.js +22798 -12309
  25. package/package.json +3 -1
  26. package/plugin/.claude-plugin/plugin.json +1 -1
  27. package/plugin/skills/tldrx/SKILL.md +16 -3
  28. package/stages/build/stage.md +5 -0
  29. package/stages/build/stage.yml +2 -1
  30. package/stages/plan/stage.md +11 -0
  31. package/templates/budget.yml +5 -0
  32. package/templates/epic.md +5 -3
  33. package/dist/hooks/chunk-ae6bkfs5.js +0 -0
  34. package/dist/hooks/chunk-kw4tffzf.js +0 -139
  35. package/dist/hooks/chunk-sdjnnmzz.js +0 -497
  36. 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.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.3.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 five conditions hold and cannot judge whether a decision was RIGHT: quote
59
- the note — it carries all five measured values — and ask before revoking.
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 five of its conditions hold.
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`
@@ -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
 
@@ -22,7 +22,8 @@ model: sonnet
22
22
  effort: high
23
23
  budget_usd: 9
24
24
  timeout_s: 1800
25
- # Branches and commits are not revertible by a flag, so a dry run is refused
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
 
@@ -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
@@ -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
- | Story | Repo | Depends on | Status |
27
- |---|---|---|---|
28
- | S1 | example | — | todo |
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 };