liteagents 2.17.1 → 2.18.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
@@ -15,6 +15,111 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Enhanced testing capabilities
16
16
  - Performance optimizations
17
17
 
18
+ ## [2.18.0] - 2026-08-26
19
+
20
+ ### Changed
21
+ - **`/remember`'s antigen step redesigned to classify-then-count.** A 15-repo audit found
22
+ MEMORY.md's Antigens section hand-drifted from the ledger in 14/15 repos and rule-text
23
+ disagreement in 22 entries, root-caused to the model writing the same rule text in three
24
+ places and doing hash/dedup arithmetic in prose. Narrowed the LLM to one classification
25
+ judgment per cluster (`drop` | existing `ag-NNN` | `new:<theme>`); moved everything else —
26
+ hash union, dedup, promotion, rendering, and checking — into deterministic code
27
+ (`friction.cjs count`/`render`/`check`/`migrate-attempts`). Added invariants I6-new
28
+ (render(ledger) byte-equal to MEMORY.md's Antigens section) and I7 (`rule` ==
29
+ `attempts[last].rule`), Guard B (`new:` clusters never merge with each other in the same
30
+ classify batch), and an adopted-date gate so pre-fix evidence can't count toward
31
+ `recurred_while_hot`. `remember.md` rewritten as literal commands to run, not prose to
32
+ interpret. Validated live on 3 real repos; 947 tests passing, mirrored to all
33
+ four packages with 0 non-path diffs.
34
+
35
+ ### Fixed
36
+ - **`friction.cjs check` exited 0 when I6-new was NOT EQUAL** — only I7 could fail it, so an
37
+ automated caller saw a pass while MEMORY.md was hand-drifted from the ledger. Now exits 1 on
38
+ either invariant failing. Validated on real backups (zkagent NOT EQUAL → 1, bareloop 8 I7
39
+ mismatches → 1, liteagents EQUAL → 0).
40
+ - **`observing`→`hot` promotion in `friction.cjs count` wrote no history line and left
41
+ `attempts[last].adopted` at the candidate date**, so on the next run a conversation from
42
+ before the rule went hot counted toward `recurred_while_hot` — the adopted-date gate was
43
+ comparing against the wrong date. Promotion now appends `promoted to hot (N sessions)` and
44
+ re-stamps `adopted` to the run date. Reproduced and fixed on liteagents' real ag-003
45
+ (unfixed: rwh=1; fixed: rwh=0, gated).
46
+ - **`/remember` could append near-duplicate episodes when re-processing already-filed
47
+ stashes.** The Episodes section only ever appended; nothing checked whether a new episode
48
+ covered the same work as one already in the section. Step 4b's episode rule now dedups
49
+ before appending — merge into the existing entry (judged by content, not title) instead of
50
+ adding a second copy. Validated on the exact data that surfaced it: with the old rule, 4/5
51
+ duplicate pairs survived across 5 isolated runs; with dedup, 0/5, and the 10-most-recent cap
52
+ still held. All four packages.
53
+ - **A newly-created antigen entry's `rule` text was a literal placeholder string,
54
+ not real content.** `friction.cjs count` wrote
55
+ a hardcoded placeholder string into `rule` and
56
+ `attempts[0].rule` for any brand-new `ag-NNN` entry, and `render` printed it verbatim into
57
+ MEMORY.md — so the placeholder could land in a user's actual memory file. The 4a classifier
58
+ now emits the one-line rule text alongside a `new:<theme>` label in the same judgment (no
59
+ new LLM pass): `{cluster_index: {label: "new:<theme>", rule: "..."}}`. `count` accepts both
60
+ this shape and the old bare-string shape (bare stays valid for `drop`/`ag-NNN`, and for
61
+ `new:` clusters with `sessions < 2`, which never create an entry). A `new:` cluster with
62
+ `sessions >= 2` and no rule is reported as malformed and creates nothing — never falls back
63
+ to placeholder text. All four packages; 947 tests passing.
64
+ - **friction's severity axis was degenerate — every cluster it ever emitted was severe.**
65
+ Clusters are seeded only on an observed reaction (`user_correction`, `user_curse`,
66
+ `interrupt_cascade`), and the severe test accepted all three of those same signals, so the
67
+ thing required for a cluster to exist also made it severe. Measured 69/69 severe on the real
68
+ corpus (3170 sessions, 77 projects) and 66/66 on the privcloud fixture. The documented
69
+ recurrence × severity 2×2 was therefore a 1×2 on recurrence alone: `fact` (recurring + mild)
70
+ and `drop` (one-off + mild) had never once fired, and every one-off "no, do X instead" was
71
+ labelled an `episode` — 68 of them in a single `/remember` run. Severe now means intensity:
72
+ a curse, an interrupt cascade, or a tool error corroborating the reaction; a plain
73
+ correction is mild. Re-run on the same corpus: 69 → 31 clusters, all 38 dropped were
74
+ correction-only with no curse/interrupt/error. Regression fixture (i) plus a new assertion
75
+ on (h) (13 plain corrections → `fact`, not `antigen`) observed failing on pre-fix code;
76
+ five dedup fixtures gained a curse word so they stay observable. All four packages.
77
+ - **Review of both specs against their scripts found 24 more defects; each was reproduced,
78
+ approved, and then fixed one at a time by a delegated agent with a failing-first test.**
79
+ docs-builder script: `apply-reorg` wrote a config pointer to a `docs/index.md` that
80
+ `index-flat` had just declined to write (now skipped with a message); its results JSON
81
+ reported `claudeMdUpdated: false` because it printed before the flag was set; `discover` on
82
+ an already-sorted corpus told the operator to run a classification interview on a 0-row
83
+ plan; the usage string omitted `cleanup-apply`; and JSON state (`docs/.docs-builder/*`)
84
+ resolved against the cwd while `index.md`/ledger/config resolved against `REPO`, splitting
85
+ the state when run from anywhere but the root — `ARTIFACTS` now resolves under `REPO` at
86
+ the one chokepoint, explicit `OUT=`/path args unchanged. friction: when analyze found no
87
+ sessions it fell through to extract on the PREVIOUS run's `friction_analysis.json`, exit 0,
88
+ clobbering `antigen_clusters.json` to empty — the no-input case now returns a distinct code
89
+ (2; 1 was already the verdict) and stops. docs-builder spec: commands now `cd` to the target
90
+ root and call the script by absolute path (`$DB`), every `REPO=<repo>` prefix dropped (they
91
+ also never matched `allowed-tools: Bash(node:*)`); neither picker flow ever stamped the
92
+ ledger, so `due` said NOT due forever — both end with `ledger` after the commit; Modes table
93
+ pointed at a nonexistent "step 4"; empty follow-up list had no instruction; two stale
94
+ `docs/README.md` refs and "fifteen subcommands" (fourteen). remember spec: step 7 called
95
+ `docs-builder.cjs` cwd-relative (MODULE_NOT_FOUND on every repo but this one) and relayed
96
+ `due`'s "run ledger" advice that step 7 itself forbids; the `antigen_review.md` fallback has
97
+ no `session_ids`, so 4c would have counted every re-scan as recurrence (no counting on that
98
+ path now); the migration clause re-fired every run on an entry that matched nothing; the
99
+ early-stop condition depended on 4c's own output; step 5 rendered legacy 1-session
100
+ `observing` entries; three wrong step cross-references; "recursively" was two levels.
101
+ Also: both test suites leaked every `mkdtemp` dir (~1,000 per docs-builder run) until
102
+ `/tmp` ran out of inodes mid-session — both now remove them on exit (`KEEP_TMP=1` keeps).
103
+ Not changed, flagged as a design call: a friction `fact` (3+ sessions, mild) writes straight
104
+ into hot Facts with no ledger stage while an antigen needs 5 — reachable for the first time
105
+ since the severity fix.
106
+ - **`/docs-builder` could not find its own script outside this repo.** `docs-builder.md` wrote
107
+ every command as `node docs-builder/docs-builder.cjs …` without saying that path is
108
+ relative to the command's own directory (`~/.claude/commands/`), so on an external repo the
109
+ model searched the target tree, found nothing, and refused to run. Added the same "locate
110
+ the script first" step `remember.md` already has for `friction.cjs`. All four packages.
111
+
112
+ - **`/remember` step 4b routed a one-off severe friction cluster to "an Episode" that has no
113
+ home.** The Episodes section is stash-fed and capped at 10, and 4c already refuses a ledger
114
+ entry for a 1-session cluster — so the instruction contradicted the rest of the command and,
115
+ post-severity-fix, would have asked for ~30 cross-project one-offs to flush the stash
116
+ episodes. 4b now says what 4c already implied: a one-off is written nowhere; friction
117
+ re-surfaces it every run until it recurs, and at 2 sessions it gets a ledger `observing`
118
+ entry. Step 8's "facts should not grow by the number of new facts" expectation was reworded
119
+ too — measured on bareloop at steady state (273 facts, mean 131 chars, 0 near-duplicates)
120
+ the compressor correctly shortens nothing, and the old wording invited forced merges.
121
+ All four packages.
122
+
18
123
  ## [2.17.1] - 2026-08-25
19
124
 
20
125
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liteagents",
3
- "version": "2.17.1",
3
+ "version": "2.18.0",
4
4
  "description": "AI development toolkit with 11 specialized agents and 18 commands including live-canvas UI design with click-to-annotate feedback. Simple one-question installer for Claude, Opencode, Ampcode, and Droid.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -761,7 +761,7 @@ function indexFlat() {
761
761
 
762
762
  if (!productFiles.length && !logsFiles.length && !archiveFiles.length && !pageFiles.length) {
763
763
  console.log('nothing to index — run `discover` + `apply-reorg` first.');
764
- return;
764
+ return false; // caller (applyReorg) uses this to skip the config pointer — no index.md was written
765
765
  }
766
766
 
767
767
  const outRel = process.env.OUT || 'docs/index.md';
@@ -1748,7 +1748,11 @@ function discover(root) {
1748
1748
  // writes had persisted. Carry-forward (above) is exactly why a re-run can arrive here with
1749
1749
  // buckets already set, so this has to report what is actually in the plan.
1750
1750
  const filled = rows.filter(r => r.bucket).length;
1751
- if (!filled) {
1751
+ if (!rows.length) {
1752
+ console.log(`plan written to docs/.docs-builder/reorg-plan.json — 0 rows. Nothing outside `
1753
+ + 'product/, logs/ and archive/ is left to classify — the corpus is already sorted. '
1754
+ + '`apply-reorg` will only rescan and rebuild the index.');
1755
+ } else if (!filled) {
1752
1756
  console.log(`plan written to docs/.docs-builder/reorg-plan.json — every row's \`suggested\` `
1753
1757
  + 'is a PRIOR, not a verdict, and `bucket` is empty. Run the classification interview '
1754
1758
  + '(docs-builder.md): feed the model the plan, get bucket+reason per row, get the user\'s '
@@ -1880,7 +1884,7 @@ const DOCS_INDEX_END = '<!-- DOCS_INDEX:END -->';
1880
1884
  function docsIndexBlock() {
1881
1885
  return `${DOCS_INDEX_START}\n`
1882
1886
  + 'Docs map: `docs/index.md` — every doc in this project, with line counts.\n'
1883
- + 'Too many rows to read whole? Search instead: `/docs-builder search <query words>`\n'
1887
+ + 'Search this corpus instead of reading it whole: `/docs-builder search <query words>`\n'
1884
1888
  + `${DOCS_INDEX_END}`;
1885
1889
  }
1886
1890
  function injectClaudeMdPointer() {
@@ -1969,7 +1973,6 @@ function applyReorg(planFile) {
1969
1973
  // (re)builds outline.json for a corpus that already sat in docs/product/docs/archive/docs/logs
1970
1974
  // from a previous run, e.g. after a manual git mv or a re-run with nothing left to do.
1971
1975
  scanWholeCorpus();
1972
- console.log(JSON.stringify(results, null, 1));
1973
1976
  if (splitCandidates.length) {
1974
1977
  // Ranked, logs last (spec §5): a prereg is a legitimate split target but rarely the best
1975
1978
  // NEXT one. Array.prototype.sort is stable in Node, so this only reorders logs to the
@@ -1982,18 +1985,23 @@ function applyReorg(planFile) {
1982
1985
  // v3: apply-reorg writes docs/index.md itself — a reorg-only corpus ends up indexed
1983
1986
  // without a second command. Runs unconditionally — oversized docs are sorted like anything
1984
1987
  // else now, so this was never conditional on them.
1985
- indexFlat();
1986
- // Crash-isolated, same spirit as the moveDoc() follow-up failures collected above: a
1987
- // failure to write the config file is a WARN, never a thrown error — it must not make an
1988
- // already-moved file look unmoved or fail the run.
1988
+ const indexed = indexFlat() !== false;
1989
1989
  const configName = process.env.CONFIG || 'CLAUDE.md';
1990
- try {
1991
- injectClaudeMdPointer();
1992
- results.claudeMdUpdated = true;
1993
- console.log(` updated ${configName} with the docs/index.md pointer`);
1994
- } catch (e) {
1995
- console.error(` WARN could not update ${configName} with the docs/index.md pointer: ${e.message}`);
1990
+ if (!indexed) {
1991
+ console.log(` skipped the ${configName} pointer — no docs/index.md was written`);
1992
+ } else {
1993
+ // Crash-isolated, same spirit as the moveDoc() follow-up failures collected above: a
1994
+ // failure to write the config file is a WARN, never a thrown error — it must not make an
1995
+ // already-moved file look unmoved or fail the run.
1996
+ try {
1997
+ injectClaudeMdPointer();
1998
+ results.claudeMdUpdated = true;
1999
+ console.log(` updated ${configName} with the docs/index.md pointer`);
2000
+ } catch (e) {
2001
+ console.error(` WARN could not update ${configName} with the docs/index.md pointer: ${e.message}`);
2002
+ }
1996
2003
  }
2004
+ console.log(JSON.stringify(results, null, 1));
1997
2005
  logOp('apply-reorg', `moved ${results.moved}, skipped ${results.skipped}, `
1998
2006
  + `${splitCandidates.length} oversized split candidate(s), `
1999
2007
  + `${results.linksRewritten} link(s) rewritten, ${results.syncFailed} sync failure(s), `
@@ -2327,7 +2335,7 @@ function cleanupApply(file, outlineF, labelsF) {
2327
2335
 
2328
2336
  // Machine state has one home. Callers can override with OUT, but the default must never
2329
2337
  // scatter JSON into whatever directory the user happened to be standing in.
2330
- const ARTIFACTS = 'docs/.docs-builder';
2338
+ const ARTIFACTS = path.join(REPO, 'docs/.docs-builder');
2331
2339
  function write(obj, fallback) {
2332
2340
  const dest = process.env.OUT || path.join(ARTIFACTS, fallback);
2333
2341
  fs.mkdirSync(path.dirname(dest), { recursive: true });
@@ -2352,7 +2360,7 @@ switch (cmd) {
2352
2360
  case 'cleanup-apply': cleanupApply(rest[0], rest[1], rest[2]); break;
2353
2361
  default:
2354
2362
  die('usage: docs-builder.cjs <scan|validate|plan|index-flat|search|archive|ledger|due|lint|'
2355
- + 'discover|apply-reorg|reorg|cleanup> [args]\n'
2363
+ + 'discover|apply-reorg|reorg|cleanup|cleanup-apply> [args]\n'
2356
2364
  + ' scan <file.md...> -> outline.json\n'
2357
2365
  + ' validate <outline.json> <labels.json> -> PASS/FAIL (exit 1 on FAIL)\n'
2358
2366
  + ' plan <outline.json> <labels.json> -> task-<theme>.json per page\n'
@@ -2368,6 +2376,7 @@ switch (cmd) {
2368
2376
  + 'drift summary if a ledger stamp exists (the single front door)\n'
2369
2377
  + ' cleanup <file.md> -> ONE named file: cost estimate, then scan\n'
2370
2378
  + ' (the ONLY entry point to the split pipeline)\n'
2379
+ + ' cleanup-apply <file.md> [outline] [labels] -> plan + pages + archive, after the cleanup interview\n'
2371
2380
  + 'env: REPO (default cwd), OUT (output path), INDEX (default docs/index.md), '
2372
2381
  + 'PAGES (default docs/wiki), TASKS (default docs/.docs-builder/tasks), '
2373
2382
  + 'N (search result count, default 10), OVERSIZED_LINES (default 500)');
@@ -9,7 +9,7 @@ allowed-tools: Read, Write, Edit, Grep, Glob, Task, AskUserQuestion, Bash(node:*
9
9
  # docs-builder
10
10
 
11
11
  Keep project docs **current, complete and findable**, and split a file when it outgrows
12
- its row in `docs/README.md`.
12
+ its row in `docs/index.md`.
13
13
 
14
14
  > **The honest label: this does NOT make docs cheaper to read.**
15
15
  > Measured four ways; best case is a tie with doing nothing. Cost tracks *findings*, not
@@ -36,6 +36,21 @@ carry over, the absolute prices do not.
36
36
 
37
37
  ## Invocation
38
38
 
39
+ **Locate the script first.** `docs-builder.cjs` is bundled next to this command at
40
+ `docs-builder/docs-builder.cjs` — the same directory as this file, whether installed or run
41
+ from the package. Never search the target repo for it, never reconstruct it from this spec,
42
+ and if it truly exists nowhere say so and stop. Set `DB` to its ABSOLUTE path, then `cd` to
43
+ the target repo's root:
44
+
45
+ ```
46
+ DB=<absolute path to docs-builder.cjs>
47
+ cd <target repo root>
48
+ ```
49
+
50
+ Every command below is `node $DB …`; everything the script writes (`docs/.docs-builder/*`
51
+ JSON state, `docs/index.md`, the ledger, the log, the config pointer) lands under the target
52
+ repo. `REPO=` is optional and only needed when not running from the repo root.
53
+
39
54
  **With an argument** (`reorg`, `cleanup <file>`, or `search <query words...>`) — run that mode
40
55
  directly, no question asked.
41
56
 
@@ -82,10 +97,17 @@ read-only — no model cost, no interview, nothing moves.
82
97
  3. `apply-reorg` moves every row, **oversized included** — size only decides whether a doc is
83
98
  *splittable*, not whether it gets sorted. It refuses outright if any row's `bucket` is
84
99
  still empty. Afterward it prints the oversized docs it just moved as a follow-up list,
85
- `cleanup <NEW path> (N lines)`, `logs/` entries last. Show that list, then **ask which to
86
- split** (any, all, none). Only then run `cleanup <file>` (Mode 1) on each chosen file
100
+ `cleanup <NEW path> (N lines)`, `logs/` entries last. If the list is empty (nothing
101
+ oversized), say so and skip the split question; otherwise show the list, then **ask which
102
+ to split** (any, all, none). Only then run `cleanup <file>` (Mode 1) on each chosen file —
87
103
  `cleanup` itself prints the estimated split cost for that one file, then a mechanical
88
104
  shape report, then stops for its own interview (Mode 1, step 1b) before anything else runs.
105
+ Before that first commit, add `docs/.docs-builder/` to `.gitignore` if it is not already
106
+ ignored: it is machine state, regenerated every run, and the ledger stamp is per-clone by
107
+ design — it must never ride into history on a later `git add -A`.
108
+ Once the moves are committed, run `node $DB ledger` — nothing in steps 1-3 stamps the
109
+ ledger, and without the stamp `due` stays NOT due, the picker's verdict stays uninformed,
110
+ and `/remember`'s docs nudge never fires.
89
111
 
90
112
  The two stops are deliberate and different. Step 2 guards *correctness* — the interview and
91
113
  the user's approval, before a single file moves. Step 3's follow-up guards *cost* — splitting
@@ -96,7 +118,8 @@ when they pick "First run". Never split N files in one shot on an unseen list.
96
118
  first, if a ledger stamp exists, then it runs `discover`. If any row's `bucket` is still
97
119
  empty (true on a genuine first run, or when new files appeared since the last classification),
98
120
  `reorg` **stops right there** and prints what to do next — it never silently proceeds past an
99
- unclassified plan. Once the plan is fully classified (an already-sorted corpus's re-run
121
+ unclassified plan. Commit what it changed, then run `node $DB ledger` to move the stamp. Once
122
+ the plan is fully classified (an already-sorted corpus's re-run
100
123
  carries its prior classifications forward automatically — see "Discover is idempotent"
101
124
  below), `reorg` continues straight through `apply-reorg` → `lint`, no further stop, so
102
125
  `index.md` and `lint.json` stay current. This is the common, cheap case for a corpus that is
@@ -109,7 +132,7 @@ already sorted: nothing new to classify, so the interview gate never fires.
109
132
  | Mode | Menu option | Does | Destructive |
110
133
  |---|---|---|---|
111
134
  | `/docs-builder reorg` (discover, classification interview, confirm, then apply-reorg) | *First run*, steps 1-3 | classify a WHOLE corpus into product/logs/archive | no (moves are `git mv`, plan classified and reviewed first) |
112
- | `/docs-builder cleanup <file>` | *First run*, step 4 | measure ONE named oversized doc (cost, scan, heading shape) → **stops for the interview** | no (measure-only; original preserved) |
135
+ | `/docs-builder cleanup <file>` | *First run*, step 3's split question | measure ONE named oversized doc (cost, scan, heading shape) → **stops for the interview** | no (measure-only; original preserved) |
113
136
  | `/docs-builder reorg` (bare `docs-builder.cjs reorg`) | *Docs drift* | due's drift summary (if a ledger stamp exists) + discover → (stops here if anything is still unclassified) → apply-reorg → lint, whole corpus | no |
114
137
  | `/docs-builder search <query words...>` | *(none — explicit-argument mode only, never offered in the bare picker)* | BM25-rank sections of `docs/.docs-builder/outline.json` against the query, read-only | no |
115
138
 
@@ -206,7 +229,7 @@ purpose, not silently dropped.
206
229
  ### 1. Discover (script) — enriches and PROPOSES, never classifies, never moves
207
230
 
208
231
  ```bash
209
- REPO=<repo> node docs-builder/docs-builder.cjs discover # defaults to docs/
232
+ node $DB discover # defaults to docs/
210
233
  ```
211
234
 
212
235
  Recursively finds every `*.md` under the root (skipping `wiki/`, `logs/`, `archive/`,
@@ -317,7 +340,7 @@ files with no gate at all.
317
340
  ### 3. Apply (script) — an ALREADY-CLASSIFIED plan, verified moves, survives a bad file
318
341
 
319
342
  ```bash
320
- CONFIG=AGENT.md node docs-builder/docs-builder.cjs apply-reorg # defaults to the plan above
343
+ CONFIG=AGENT.md node $DB apply-reorg # defaults to the plan above
321
344
  ```
322
345
 
323
346
  **Refuses outright if any row's `bucket` is still empty** — the interview-has-not-happened
@@ -455,7 +478,7 @@ real page count isn't known until the model groups sections in step 2), runs ste
455
478
  below for you, then measures the document's heading shape and **stops**:
456
479
 
457
480
  ```bash
458
- REPO=<repo> node docs-builder/docs-builder.cjs cleanup docs/BIG.md
481
+ node $DB cleanup docs/BIG.md
459
482
  ```
460
483
 
461
484
  **Nothing past this command runs until a human has answered the interview (step 1b) below.**
@@ -468,8 +491,8 @@ choosing to continue, and confirming, the themes.
468
491
  ### 1. Scan (script) — run automatically by `cleanup`, shown here for what it produces
469
492
 
470
493
  ```bash
471
- REPO=<repo> OUT=docs/.docs-builder/outline.json \
472
- node docs-builder/docs-builder.cjs scan docs/BIG.md
494
+ OUT=docs/.docs-builder/outline.json \
495
+ node $DB scan docs/BIG.md
473
496
  ```
474
497
 
475
498
  One record per H2, each carrying the doc's H1 identity, a 2-line snippet, every H3 **with
@@ -560,7 +583,7 @@ it `false`) on every other theme.
560
583
  ### 3. Validate (script) — **hard gate, exits 1 on failure**
561
584
 
562
585
  ```bash
563
- REPO=<repo> node docs-builder/docs-builder.cjs validate \
586
+ node $DB validate \
564
587
  docs/.docs-builder/{outline,labels}.json
565
588
  ```
566
589
 
@@ -586,7 +609,7 @@ index, which is gone — on the one whole-corpus index it would silently skip mo
586
609
  ### 4. Plan + apply (script) — `cleanup-apply`, the door back in after the interview
587
610
 
588
611
  ```bash
589
- REPO=<repo> node docs-builder/docs-builder.cjs cleanup-apply docs/BIG.md \
612
+ node $DB cleanup-apply docs/BIG.md \
590
613
  docs/.docs-builder/outline.json docs/.docs-builder/labels.json
591
614
  ```
592
615
 
@@ -616,7 +639,7 @@ the checkpoint; there is no separate state file to go stale. It is also still ru
616
639
  own:
617
640
 
618
641
  ```bash
619
- REPO=<repo> OUT=docs/.docs-builder/tasks node docs-builder/docs-builder.cjs plan \
642
+ OUT=docs/.docs-builder/tasks node $DB plan \
620
643
  docs/.docs-builder/{outline,labels}.json
621
644
  ```
622
645
 
@@ -653,7 +676,7 @@ Each agent reads **only its own line ranges**. The value is context isolation.
653
676
  ### 6. Archive the original (script) — run for you by `cleanup-apply` once all pages exist
654
677
 
655
678
  ```bash
656
- REPO=<repo> node docs-builder/docs-builder.cjs archive docs/BIG.md
679
+ node $DB archive docs/BIG.md
657
680
  ```
658
681
 
659
682
  A **verified move**, not a copy: hash → `git mv` (so history follows) → hash again →
@@ -697,7 +720,7 @@ which defaults the outline path and takes only the query. The underlying script
697
720
  works directly, and is what the slash command runs:
698
721
 
699
722
  ```bash
700
- REPO=<repo> node docs-builder/docs-builder.cjs search docs/.docs-builder/outline.json <query words...>
723
+ node $DB search docs/.docs-builder/outline.json <query words...>
701
724
  ```
702
725
 
703
726
  BM25 over each section's real text (no deps, no separate index to build — it reads
@@ -717,7 +740,7 @@ reorg, not only whichever ones a split happened to touch.
717
740
  some archived docs).
718
741
 
719
742
  ```bash
720
- REPO=<repo> node docs-builder/docs-builder.cjs index-flat
743
+ node $DB index-flat
721
744
  ```
722
745
 
723
746
  Writes **one** `docs/index.md` covering the whole corpus, in three sections: `## Product`
@@ -745,7 +768,7 @@ v3 folds the old `reconcile` and `due` commands into one: "first run" (nothing s
745
768
  state, and two separate commands only made users guess which one to run.
746
769
 
747
770
  ```bash
748
- REPO=<repo> node docs-builder/docs-builder.cjs reorg
771
+ node $DB reorg
749
772
  ```
750
773
 
751
774
  If a ledger stamp exists (see "Knowing when reorg is due" below), its `due`-style drift
@@ -773,7 +796,7 @@ still runnable by hand once a `labels.json` exists.
773
796
  `lint` is also runnable standalone, on any file list, not only as part of `reorg`:
774
797
 
775
798
  ```bash
776
- REPO=<repo> node docs-builder/docs-builder.cjs lint <file.md...>
799
+ node $DB lint <file.md...>
777
800
  ```
778
801
 
779
802
  -> `lint.json`. Every check below is declared-only (see the governing rule further down) —
@@ -812,8 +835,8 @@ git is the diff engine. The ledger stores only the one thing git cannot know —
812
835
  last consolidated** — so the two can never drift apart.
813
836
 
814
837
  ```bash
815
- node docs-builder/docs-builder.cjs ledger # stamp the current state (run after a reorg)
816
- node docs-builder/docs-builder.cjs due # what changed since, and by how much
838
+ node $DB ledger # stamp the current state (run after COMMITTING a reorg)
839
+ node $DB due # what changed since, and by how much
817
840
  ```
818
841
 
819
842
  `due` classifies every doc against the stamped SHA using `git diff --numstat -M`: