liteagents 3.6.0 → 3.8.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 (27) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/package.json +1 -1
  3. package/packages/ampcode/skills/branch-review/SKILL.md +20 -3
  4. package/packages/ampcode/skills/docs-builder/SKILL.md +168 -70
  5. package/packages/ampcode/skills/docs-builder/docs-builder.cjs +325 -70
  6. package/packages/ampcode/skills/release/SKILL.md +38 -8
  7. package/packages/ampcode/skills/remember/AGENT_RULES.md +14 -6
  8. package/packages/ampcode/skills/security/SKILL.md +6 -3
  9. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package-lock.json +3 -3
  10. package/packages/claude/skills/branch-review/SKILL.md +20 -3
  11. package/packages/claude/skills/docs-builder/SKILL.md +168 -69
  12. package/packages/claude/skills/docs-builder/docs-builder.cjs +325 -70
  13. package/packages/claude/skills/release/SKILL.md +38 -8
  14. package/packages/claude/skills/remember/AGENT_RULES.md +14 -6
  15. package/packages/claude/skills/security/SKILL.md +6 -3
  16. package/packages/droid/commands/branch-review.md +20 -3
  17. package/packages/droid/commands/docs-builder/docs-builder.cjs +325 -70
  18. package/packages/droid/commands/docs-builder.md +168 -70
  19. package/packages/droid/commands/release.md +38 -8
  20. package/packages/droid/commands/remember/AGENT_RULES.md +14 -6
  21. package/packages/droid/commands/security.md +6 -3
  22. package/packages/opencode/command/branch-review.md +20 -3
  23. package/packages/opencode/command/docs-builder/docs-builder.cjs +325 -70
  24. package/packages/opencode/command/docs-builder.md +168 -70
  25. package/packages/opencode/command/release.md +38 -8
  26. package/packages/opencode/command/remember/AGENT_RULES.md +14 -6
  27. package/packages/opencode/command/security.md +6 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,98 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [3.8.0] - 2026-09-13
11
+
12
+ ### Added
13
+ - **`AGENT_RULES.md` gains five rules from fwdloop's m0a review**, in all four kits:
14
+ prove a test can fail (revert the fix, watch it go red, restore, rather than trust
15
+ a test that "reads right"); model output is untrusted input (schema-validate it and
16
+ key decisions on typed fields, never regex the model's prose); a warning nobody has
17
+ to act on is not a check (either it halts/reds the run, or it is counted and
18
+ surfaced where a human reads it — including recording the stop/finish reason of
19
+ every external or model call, since a cut-off answer is never the same as no
20
+ answer); meter the whole unit of work (cost sums every call; one unpriced call
21
+ makes the total unknown, not zero); and guard lookups keyed by an external string
22
+ against inherited map entries answering for a key the map never had.
23
+
24
+ ### Changed
25
+ - **`AGENT_RULES.md` redundancy pass** (all four kits): secrets, vetted libraries, and
26
+ the review chain are now each stated once with pointers elsewhere instead of
27
+ repeated inline — the "Not courtesies" secrets bullet points at Security &
28
+ Robustness Invariants §1, and "Before deploy/merge" points at Operating Flow §2
29
+ instead of restating the `/branch-review` → `/security` → `/ship` chain. The
30
+ security section's "Also hold the line on" paragraph is now one bullet per rule.
31
+ A duplicated regression-test rule was trimmed from "Write tests for bugs" since
32
+ "Fails for the right reason" now covers it.
33
+
34
+ ## [3.7.0] - 2026-09-11
35
+
36
+ ### Added
37
+ - **`docs-builder` sorts a repo that has no `docs/` yet.** With no folder named and no
38
+ `docs/` directory, `discover` / `reorg` scan the repo's loose `.md` files instead of
39
+ stopping. Protected entry-point files (`README.md`, `CLAUDE.md`, `CHANGELOG.md`,
40
+ `AGENTS.md`, ...) never move.
41
+ - **`reorg <dir>` / `discover <dir>`** re-check a single bucket that got messy.
42
+ - **`docs-builder` asks before committing.** After a run, the skill shows exactly which
43
+ files the run touched and asks; it refuses to commit on `main` / `master`.
44
+ - `discover` warns that `ROOT=` is ignored and says to pass the folder as an argument.
45
+ - **`docs-builder` gains a fourth bucket, `wiki/`**, for generic non-product-specific
46
+ knowledge (conventions, how-tos, standards, reference) — `product/`, `wiki/`, `logs/`,
47
+ `archive/` is now the full layout everywhere (SKILL.md, README, `index-flat`).
48
+ - **`docs-builder` classifies from headings, not just filenames.** A doc with no
49
+ filename signal now also gets a weaker, case-insensitive prior from its own H1 + first
50
+ 3 H2s.
51
+ - **`logs/` is the one bucket that nests**, one level, grouped by the file's own first
52
+ path segment under `docs/` (a special subfolder like `docs/fwd/` stays one group
53
+ however deep a file sits inside it, unless that segment is itself a bucket name); loose
54
+ files stay flat. `index-flat`'s `## Logs` section groups rows the same way.
55
+
56
+ ### Security
57
+ - `hono` bumped 4.13.0 → 4.13.7 (indirect dependency, Dependabot).
58
+
59
+ ### Fixed
60
+ - **`docs-builder`'s commit recipe mishandled non-ASCII renames, silently swept up the
61
+ operator's own uncommitted edits, and broke under `REPO=<subdir>`.** A moved file with a
62
+ non-ASCII name (e.g. `café.md`) lost its rename because `ls-tree` C-quoted the path,
63
+ dropping it from `commit-files.txt`; now uses `ls-tree -z`. A pre-run dirty snapshot now
64
+ names any listed file that already carried the user's own uncommitted edits
65
+ (`commit-dirty.txt`), and `SKILL.md` surfaces that in the commit question instead of
66
+ silently including them. `REPO=<subdir>` no longer writes lists to a doubled path or
67
+ exits 128 — lists are written under `ARTIFACTS` directly and the recipe uses `git -C`
68
+ when `REPO` isn't the cwd.
69
+ - **`docs-builder`'s dirty-file warning no longer flags the tool's own log/index.** `reorg`
70
+ appends to `docs/log.md` and rebuilds `docs/index.md` as part of its own run, so the
71
+ following `apply-reorg` warned that those tool-owned files carried "your own uncommitted
72
+ edits." Both are now excluded from the dirty-file check; every other listed file is still
73
+ checked.
74
+ - **`docs-builder`'s link rewriter now touches only `.md` files, and `reorg <dir>` /
75
+ `discover <dir>` re-check an already-classified bucket instead of leaving it a false
76
+ SKIP.** Previously the rewriter could open signed JSON job specs or byte-signed scripts
77
+ outside `docs/`, and the commit advisory could stage unrelated files alongside the run's
78
+ own changes; the advisory now names exactly the staged renames, unstaged rewrites, and
79
+ distinct outside-`docs/` locations touched by that run.
80
+ - **`/branch-review` and `/security` effort level no longer cuts which checks run** — only
81
+ how many findings are reported. Stage 1's fail-first check is one red run per changed
82
+ test file, never a sample; a check that can't run is written `NOT RUN: <reason>` on a
83
+ new `checks:` line, visible but non-blocking (`/release` still reads only `coverage:`).
84
+ Stage 2's secrets scan always covers all history, never narrowed to the review range.
85
+ - **`docs-builder`'s default scan scope no longer sweeps the whole repo.** With no
86
+ directory named, `discover` / `reorg` now scan only root-level `.md` files
87
+ (non-recursive) plus everything under `docs/` (recursive); `discover <dir>` /
88
+ `reorg <dir>` are unchanged. `PROTECTED_NAMES` is now matched case-insensitively at
89
+ every call site, so files like `readme.md` or `Claude.md` are protected too, not just
90
+ their exact-case forms. `docs/product`, `docs/wiki`, and `docs/logs` are now
91
+ re-checked on every bare run (only `docs/archive` stays frozen) — this also fixed an
92
+ ordering bug where a resident row could be bumped off its own bucket by an unrelated
93
+ same-basename row visited earlier.
94
+ - **`/release`'s docs sweep is now required and can't be skipped or sampled.** A prior
95
+ run had built a `CHANGELOG` from commit subjects alone (missing changes only named in
96
+ commit bodies) and called a stale README line "already stale before this branch"
97
+ without checking. Phase 2 is now three required passes — list every change from commit
98
+ bodies, map each to a CHANGELOG heading, and grep docs for every replaced string,
99
+ proving any "pre-existing" claim against the merge-base — with one evidence row per
100
+ doc; a "no change" with no evidence now fails the phase.
101
+
10
102
  ## [3.6.0] - 2026-09-05
11
103
 
12
104
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liteagents",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "description": "AI development toolkit with 10 specialized agents and 13 capabilities 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": {
@@ -140,6 +140,13 @@ governs **stage 1 only**:
140
140
  - **high / max** — broader coverage; uncertain findings are allowed, but each
141
141
  must be labelled uncertain.
142
142
 
143
+ **No shortcuts.** The level decides how many findings you report, never which
144
+ checks you run. Every check this file calls required runs at every level —
145
+ never cut or sample one "given the effort level", the branch size, or time. If
146
+ a check truly cannot run, write `NOT RUN: <reason>` for it on the `checks:`
147
+ line of the report and the review record. That is a visible gap, not a
148
+ blocker and not a pass.
149
+
143
150
  **Stage 2 (security) always runs full, at every level.** A shallow security
144
151
  pass is worse than none — it reads as coverage while missing the class of bug
145
152
  that costs the most.
@@ -189,7 +196,10 @@ carries the reproduction.
189
196
  location outside the repo; the tree must still be clean at exit. A test that
190
197
  passes against both the buggy and the fixed source is a tautology and proves
191
198
  nothing. Flag every one you find, and say so explicitly when the tests are
192
- the branch's only evidence for its claims.
199
+ the branch's only evidence for its claims. **Required, every test file the
200
+ diff adds or changes — one red run per file is enough; checking a sample of
201
+ the files is a skip.** Count them as `fail-first N/M files` on the
202
+ `checks:` line.
193
203
  - **Maintainability.** Complexity, naming, duplication — only when material.
194
204
 
195
205
  ## Stage 2 — Security (always full)
@@ -204,7 +214,10 @@ If `security.md` cannot be found, run what you can from the list above and
204
214
 
205
215
  This stage is repo- and history-scoped, not diff-scoped: a key committed forty
206
216
  commits ago, an unbounded route the diff never touched, or a missing row
207
- policy on a table the new code now reads are all in scope.
217
+ policy on a table the new code now reads are all in scope. **The review range
218
+ never narrows this stage** — even when you were handed `main..HEAD`, the
219
+ secrets scan covers every commit on every branch (`security.md` item 1 has
220
+ the command).
208
221
 
209
222
  ## Stage 3 — Verify (adversarial)
210
223
  Findings are claims, not facts. **Try to break each one, not to confirm it** —
@@ -292,7 +305,10 @@ uncertain).
292
305
 
293
306
  Then a coverage line: stage 1 at level `<level>`, stage 2 full, stage 3 —
294
307
  each `ran ✓/✗` with its evidence. A stage you did not actually run is a **✗**, never an
295
- assumed pass.
308
+ assumed pass. Then a `checks:` line for the two checks most often cut short:
309
+ `fail-first N/M files` and `secrets-history all-branches` (or `NOT RUN:
310
+ <reason>` for either). An N below M, or a NOT RUN, is reported as-is — it
311
+ does not block.
296
312
 
297
313
  **Write the review record** to `.amp/remember/last-review.md`, overwriting
298
314
  it. `/release` reads this file; a SHA that lives only in a chat message is
@@ -311,6 +327,7 @@ level: <low | medium | high | max>
311
327
  verdict: <ready | blocked>
312
328
  date: <YYYY-MM-DD>
313
329
  coverage: stage1 <ran|NOT RUN>, stage2 <ran|NOT RUN>, stage3 <ran|NOT RUN>
330
+ checks: fail-first <N/M files|NOT RUN: reason>, secrets-history <all-branches|NOT RUN: reason>
314
331
  blockers:
315
332
  - <file:line> · <one-sentence claim, no scenario, no suggested fix>
316
333
  ```
@@ -5,7 +5,6 @@ argument-hint: [reorg | cleanup <file.md> | search <query words...> — empty as
5
5
  allowed-tools: Read, Write, Edit, Grep, Glob, Task, AskUserQuestion, Bash(node:*), Bash(git:*), Bash(rg:*)
6
6
  disable-model-invocation: true
7
7
  ---
8
-
9
8
  # docs-builder
10
9
 
11
10
  Keep project docs **current, complete and findable**, and split a file when it outgrows
@@ -51,8 +50,13 @@ Every command below is `node $DB …`; everything the script writes (`docs/.docs
51
50
  JSON state, `docs/index.md`, the ledger, the log, the config pointer) lands under the target
52
51
  repo. `REPO=` is optional and only needed when not running from the repo root.
53
52
 
54
- **With an argument** (`reorg`, `cleanup <file>`, or `search <query words...>`) — run that mode
55
- directly, no question asked.
53
+ **With an argument** (`reorg [dir]`, `cleanup <file>`, or `search <query words...>`) — run that
54
+ mode directly, no question asked. `reorg <dir>` re-checks every doc already inside `<dir>` —
55
+ useful to scope a check to one directory outside the default scan. A **bare** `reorg`/`discover`
56
+ (no argument) already re-checks `docs/product/`, `docs/wiki/` and `docs/logs/` on every run —
57
+ only `docs/archive/` stays frozen and out of scope. Scan scope with no argument is otherwise
58
+ narrow by design: root-level `.md` files (top level only, not recursive) plus everything under
59
+ `docs/` (recursive) — nothing else in the repo is ever listed or moved.
56
60
 
57
61
  **Bare `/docs-builder`, no argument — ALWAYS ask, never auto-detect.** Run `due` first and
58
62
  put its one-line verdict in the question text so the choice is informed. Then use
@@ -60,9 +64,9 @@ put its one-line verdict in the question text so the choice is informed. Then us
60
64
 
61
65
  > **Question: What should docs-builder do?**
62
66
  >
63
- > - **First run** — sort every `.md` in `docs/` into product / archive, then split anything
64
- > too big into pages and index them. Use when docs are a pile of loose files, or
65
- > docs-builder has never run here.
67
+ > - **First run** — sort root-level `.md` files and everything under `docs/` into
68
+ > product/wiki/logs/archive, then split anything too big into pages and index them. Use
69
+ > when docs are a pile of loose files, or docs-builder has never run here.
66
70
  > - **Docs drift** — docs moved on since the last run: report what changed, rebuild the
67
71
  > index, re-run lint. Nothing is restructured and nothing is split.
68
72
 
@@ -89,8 +93,9 @@ read-only — no model cost, no interview, nothing moves.
89
93
  bucket). `bucket` itself starts **empty** on every row.
90
94
  2. **The classification interview.** Feed the model the WHOLE plan table (file, h1, snip,
91
95
  lines, suggested+reason) in one call and have it fill `bucket` for every row —
92
- `product`/`logs`/`archive` — with a one-line reason: honour that a SHOUTED self-declared
93
- status is near-conclusive for `archive` and that `suggested` is a prior, not an authority.
96
+ `product`/`wiki`/`logs`/`archive` — with a one-line reason: honour that a SHOUTED
97
+ self-declared status is near-conclusive for `archive` and that `suggested` is a prior, not
98
+ an authority.
94
99
  The model writes its answers straight into `reorg-plan.json`. Then show the user the full
95
100
  resulting table via `AskUserQuestion` (approve all / correct specific rows / abort) — a
96
101
  correction changes the plan file before anything moves.
@@ -102,12 +107,10 @@ read-only — no model cost, no interview, nothing moves.
102
107
  to split** (any, all, none). Only then run `cleanup <file>` (Mode 1) on each chosen file —
103
108
  `cleanup` itself prints the estimated split cost for that one file, then a mechanical
104
109
  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.
110
+ Add `docs/.docs-builder/` to `.gitignore` if it is not already ignored: it is machine state,
111
+ regenerated every run, and the ledger stamp is per-clone by design — it must never ride into
112
+ history on a later `git add -A`. Then follow "Finishing a run" (below `apply-reorg`, Mode 0
113
+ step 3) to commit and stamp the ledger.
111
114
 
112
115
  The two stops are deliberate and different. Step 2 guards *correctness* — the interview and
113
116
  the user's approval, before a single file moves. Step 3's follow-up guards *cost* — splitting
@@ -118,7 +121,7 @@ when they pick "First run". Never split N files in one shot on an unseen list.
118
121
  first, if a ledger stamp exists, then it runs `discover`. If any row's `bucket` is still
119
122
  empty (true on a genuine first run, or when new files appeared since the last classification),
120
123
  `reorg` **stops right there** and prints what to do next — it never silently proceeds past an
121
- unclassified plan. Commit what it changed, then run `node $DB ledger` to move the stamp. Once
124
+ unclassified plan. Otherwise follow "Finishing a run" once it's done. Once
122
125
  the plan is fully classified (an already-sorted corpus's re-run
123
126
  carries its prior classifications forward automatically — see "Discover is idempotent"
124
127
  below), `reorg` continues straight through `apply-reorg` → `lint`, no further stop, so
@@ -131,7 +134,7 @@ already sorted: nothing new to classify, so the interview gate never fires.
131
134
 
132
135
  | Mode | Menu option | Does | Destructive |
133
136
  |---|---|---|---|
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) |
137
+ | `/docs-builder reorg` (discover, classification interview, confirm, then apply-reorg) | *First run*, steps 1-3 | classify a WHOLE corpus into product/wiki/logs/archive | no (moves are `git mv`, plan classified and reviewed first) |
135
138
  | `/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) |
136
139
  | `/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 |
137
140
  | `/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 |
@@ -155,16 +158,31 @@ docs/
155
158
  README.md entry point, referenced from CLAUDE.md
156
159
  index.md GENERATED by index-flat/apply-reorg/cleanup-apply. never hand-edited.
157
160
  READER-FACING. The WHOLE-CORPUS map — the only file with a completeness
158
- guarantee. ## Product, ## Logs, ## Archive.
161
+ guarantee. ## Product, ## Logs (grouped by subdir), ## Archive.
159
162
  log.md append-only: ## [DATE] operation | description — written by
160
163
  `archive`, `apply-reorg`, `validate`, and `reorg`; NOT written by
161
164
  read-only commands (`due`, `search`, `discover`).
162
- product/ specs, designs, plansthe default. `apply-reorg` MOVES files here
163
- (`git mv`); content is never rewritten.
164
- logs/ pre-registrations, results, learnings, reports historical, still
165
- relevant. Same MOVE discipline as product/archive.
166
- wiki/ synthesised pages, written by Mode 1 (`cleanup`)'s page writers.
167
- archive/ what got cleaned up: self-declared dead. Originals are BYTE-FROZEN:
165
+ product/ docs ABOUT THE PRODUCT ITSELF specs, PRDs, designs, guides for this
166
+ product. FLAT, no subdirs. `apply-reorg` MOVES files here (`git mv`);
167
+ content is never rewritten. Re-checked on EVERY reorg (files sitting here
168
+ are re-classified along with everything else — agents litter buckets
169
+ over time).
170
+ wiki/ GENERIC knowledge for the whole repo, not product-specific — conventions,
171
+ how-tos, standards, reference. FLAT, no subdirs. Also where Mode 1
172
+ (`cleanup`)'s split page writers put synthesised pages. Re-checked every
173
+ reorg, same as product/.
174
+ logs/ ONE-TIME, specific, timely knowledge tied to one section/scenario/case —
175
+ POCs, experiments, investigations, incident/session write-ups, reports. The
176
+ ONLY bucket that may nest, ONE level: `docs/logs/<group>/*.md` — the group
177
+ is the FIRST path segment under `docs/` (a special subfolder is one
178
+ self-explanatory group, e.g. every POC under `docs/fwd/` stays together as
179
+ `fwd`, however deep a file actually sits inside it), UNLESS that segment is
180
+ itself a bucket name (`product`/`wiki`/`archive` → flat; `logs` → the group
181
+ is the SECOND segment instead). A file with no first segment — loose at the
182
+ repo root, or directly under `docs/` — stays flat. Re-checked every reorg,
183
+ same as product/wiki/.
184
+ archive/ what got cleaned up: self-declared dead. FROZEN — never re-checked, never
185
+ walked by a bare `discover`/`reorg` at all. Originals are BYTE-FROZEN:
168
186
  nothing under here is ever a rewrite target, so a doc lands byte-identical
169
187
  to what it carried in (a clean R100 rename) and stays that way. Links
170
188
  elsewhere POINTING AT it are still repaired. History via `git mv`.
@@ -202,14 +220,19 @@ moved the problem (it then clobbered `outline.json` across concurrent splits ins
202
220
  **the themed index was removed outright, 2026-08-24**. One index, rebuilt on every reorg and
203
221
  after every split, is the whole design.
204
222
 
205
- **Never moved — enforced in code, not just documented** (`PROTECTED_NAMES` / `walkMd`):
223
+ **Never moved — enforced in code, not just documented** (`PROTECTED_NAMES` / `isProtectedName` /
224
+ `walkMd`):
206
225
 
207
- - **Files, at any depth:** `README.md`, `index.md`, `log.md`, `CHANGELOG.md`, `LICENSE.md`,
208
- `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `CLAUDE.md`, `AGENTS.md`, `AGENT.md`.
209
- Bare `LICENSE`/`NOTICE` have no `.md` extension, so the walker never sees them.
226
+ - **Files, at any depth, matched CASE-INSENSITIVELY:** `README.md`, `index.md`, `log.md`,
227
+ `CHANGELOG.md`, `LICENSE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`,
228
+ `CLAUDE.md`, `AGENTS.md`, `AGENT.md` so `readme.md`, `Claude.md`, `changelog.md`,
229
+ `agents.md`, etc. are protected too, not just their exact-case forms. Bare `LICENSE`/`NOTICE`
230
+ have no `.md` extension, so the walker never sees them.
210
231
  - **Directories:** every dot-dir (`.git/`, `.github/`, `.claude/`, `.factory/`, `.opencode/`,
211
- `.amp/`, `.docs-builder/`) plus `node_modules/`, and the dirs reorg itself owns
212
- (`product/`, `logs/`, `archive/`, `wiki/`) so a second run is idempotent.
232
+ `.amp/`, `.docs-builder/`) plus `node_modules/`. `archive/` is skipped unconditionally by a
233
+ bare `discover`/`reorg` (frozen, never re-checked); `product/`, `wiki/` and `logs/` are
234
+ entered and re-checked on that same bare run — only an explicit `discover <dir>` naming one
235
+ of the reserved names directly bypasses this at all four.
213
236
 
214
237
  ---
215
238
 
@@ -229,11 +252,31 @@ purpose, not silently dropped.
229
252
  ### 1. Discover (script) — enriches and PROPOSES, never classifies, never moves
230
253
 
231
254
  ```bash
232
- node $DB discover # defaults to docs/
255
+ node $DB discover # root-level .md files (top level only) + everything under docs/
256
+ node $DB discover <dir> # scopes to exactly <dir> instead — e.g. a directory outside docs/
233
257
  ```
234
258
 
235
- Recursively finds every `*.md` under the root (skipping `wiki/`, `logs/`, `archive/`,
236
- `product/`, `.docs-builder/`, and the protected files), and for each one writes a row with:
259
+ **Scan scope with no argument is deliberately narrow:** (a) `.md` files sitting directly at
260
+ the repo root (top level, not recursive) and (b) everything under `docs/` (recursive, entering
261
+ `product/`, `wiki/` and `logs/` — only `docs/archive/` stays frozen and skipped). Every other
262
+ `.md` file anywhere else in the repo is out of scope entirely — never listed, never moved,
263
+ whether or not `docs/` exists. `PROTECTED_NAMES` still applies at the repo root (case-
264
+ insensitively — see Layout above), so `README.md`, `CLAUDE.md`, `CHANGELOG.md`, `AGENTS.md`,
265
+ `AGENT.md`, `LICENSE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `index.md`
266
+ and `log.md` are never planned or moved. `ROOT=` is NOT read by this script — setting it
267
+ prints a `WARN`; pass the folder as the argument.
268
+
269
+ **Because `product/`, `wiki/` and `logs/` are re-checked every bare run,** a file already
270
+ sitting in one of them is a REAL row too, not folded away — its `suggested` bucket defaults to
271
+ wherever it already sits (a stronger signal, like a filename token or a SHOUTED status word,
272
+ can still override that). It still needs `bucket` filled by the interview like any other row
273
+ (even if the answer is "yes, stays put") — that settles after one classify+apply pass and
274
+ carries forward from then on, same as everything else. An explicit `discover <dir>` still
275
+ scopes to exactly that one directory, unaffected — useful for anything outside the default
276
+ root+docs/ coverage.
277
+
278
+ Recursively finds every in-scope `*.md` (skipping `.docs-builder/` and the protected files, at
279
+ any depth in scope), and for each one writes a row with:
237
280
 
238
281
  - `h1` and a short `snip` (first ~200 chars of body, fence-masked) — reused straight from the
239
282
  same `headings()`/`snippet()`/`fenceMask()` parsers `scan` uses, no second extraction path.
@@ -242,14 +285,22 @@ Recursively finds every `*.md` under the root (skipping `wiki/`, `logs/`, `archi
242
285
  *sorted* — it is no longer a bucket.
243
286
  - `suggested` + `reason` — a mechanical PRIOR, never a verdict:
244
287
 
288
+ Checked in this order — the first rule that matches wins:
289
+
245
290
  | suggested | rule |
246
291
  |---|---|
247
292
  | `archive` | path already under `archive/old/reports/phases`, **or** the doc's own opening declares a SHOUTED status word (`CLOSED`, `DEPRECATED`, `SUPERSEDED`, `WITHDRAWN`, `RETRACTED`, `REFUTED`, `ARCHIVAL`, `ARCHIVED`), **or** the filename matches an archive-shaped prefix (`REPORT`, `STATUS`, `SUMMARY`, `FIX_`, `PHASE_`, `SPRINT_`, `DRAFT`, `WIP`, `OLD`, `TEMP` followed by `-` or `_`) |
248
293
  | `logs` | filename carries an experiment-record token — `PREREG`, `LEARNINGS`, `REPORT`, `RESULTS`, `POSTMORTEM`, `RETRO` (case-sensitive, word-boundary, checked ONLY after the archive rules above, so a `REPORT-old.md` still reads as archive, not logs) |
249
- | `product` | has an H1, no archive/logs signal the default when nothing else applies |
294
+ | *(residency)* | already resident under `docs/product/`, `docs/wiki/` or `docs/logs/` — its own current bucket is its prior (a stronger signal above can still override, e.g. a product-resident doc now shouting `DEPRECATED`) |
295
+ | `archive`/`logs`/`wiki` | a WEAKER, case-insensitive secondary prior read from the doc's own **H1 + first 3 H2s** — e.g. "Postmortem"/"Retrospective"/"Investigation" → `logs`; "deprecated"/"retired"/"superseded" → `archive`; "Conventions"/"How-to"/"Style Guide"/"Glossary" → `wiki`. Deliberately narrow: bare "guide"/"reference" were tried and DROPPED after false-positiving on an ordinary doc plainly titled "Guide". |
296
+ | `product` | has an H1, no archive/logs/wiki signal — the default when nothing else applies |
250
297
  | `product` | no H1, but an **include stub** — its whole non-blank content (≤3 lines) is nothing but include directives (mkdocs `--8<--`, `{% include %}`, `{{ .. }}`, `<!-- include -->`) and/or markdown links | a live pointer, not an unknown doc — real-world miss: uv's `docs/reference/contributing.md` |
251
298
  | `product` | no H1 at all, and not an include stub — no strong signal either way; the interview decides, same as any other row |
252
299
 
300
+ `suggested` never proposes `wiki` from a residency check alone reaching further than its own
301
+ match — the heuristic is only ever a prior; the interview decides the real `bucket`, including
302
+ routing a doc INTO `wiki` from anywhere.
303
+
253
304
  - `bucket` — **empty on any row discover has not classified before** (see carry-forward
254
305
  below; a re-run keeps a bucket the interview already set). This is the field the
255
306
  classification interview (step 2) fills, and the ONLY field `apply-reorg` reads to decide
@@ -308,7 +359,7 @@ was the silent move, not the judgement.
308
359
 
309
360
  1. Read `docs/.docs-builder/reorg-plan.json`. Feed the model the WHOLE table — `file`, `h1`,
310
361
  `snip`, `lines`, `suggested`+`reason` — **in one call**, and have it fill `bucket` for
311
- every row (`product`/`logs`/`archive`) with a one-line reason. `suggested` is a PRIOR the
362
+ every row (`product`/`wiki`/`logs`/`archive`) with a one-line reason. `suggested` is a PRIOR the
312
363
  model is shown, never an authority over it — but a SHOUTED self-declared status
313
364
  (`**Status: CLOSED**`) is near-conclusive for `archive` regardless of what the mechanical
314
365
  prior says.
@@ -348,9 +399,15 @@ message, not a crash — so nothing can move on an unreviewed plan. A plan from
348
399
  version (`bucket: 'oversized'` or `'review'`, both gone from the schema) is refused too, with
349
400
  a pointer to re-run `discover`.
350
401
 
351
- - `product` → verified `git mv` to `docs/product/<basename>`
352
- - `logs` → verified `git mv` to `docs/logs/<basename>`
353
- - `archive` → verified `git mv` to `docs/archive/<basename>`
402
+ - `product` → verified `git mv` to `docs/product/<basename>` (always flat)
403
+ - `wiki` → verified `git mv` to `docs/wiki/<basename>` (always flat)
404
+ - `logs` → verified `git mv` to `docs/logs/<basename>` (loose) **or**
405
+ `docs/logs/<group>/<basename>` — the ONE bucket that may nest, ONE level, where the group is
406
+ the file's FIRST path segment under `docs/` (unless that segment is itself a bucket name —
407
+ see the Layout section above). A deeper path inside the same special subfolder joins the
408
+ SAME group (no ratchet on a re-check either).
409
+ - `archive` → verified `git mv` to `docs/archive/<basename>` (always flat, and frozen — never
410
+ re-checked again once there)
354
411
  - **Oversized files move too — size decides splittable, not sorted.** No bucket is exempt.
355
412
  After the move, every oversized row is printed as a follow-up list at its NEW path, one
356
413
  `cleanup <path> (N lines)` line per file — run `cleanup` (Mode 1, below) on each, by hand,
@@ -369,20 +426,10 @@ a pointer to re-run `discover`.
369
426
  static — it never varies with row count, so a re-run rewrites identical bytes. Idempotent:
370
427
  an existing block is replaced in place, never duplicated; other content is left alone.
371
428
  The target is `CONFIG=` (default `CLAUDE.md`); this package uses `CONFIG=CLAUDE.md`.
372
- - **The moves land STAGED in your git index — commit them promptly.** `git mv` stages each
373
- rename immediately (that is what preserves history), so when `apply-reorg` returns the repo
374
- is holding N staged renames. Any other session's `git add -A` or `git commit -a` will absorb
375
- them into an unrelated commit OBSERVED TWICE, in two different repos. `apply-reorg` prints
376
- a closing advisory naming the counts and a copy-pasteable recipe. **Run that recipe
377
- VERBATIM. Do NOT hand-edit it, and do NOT stage by hand instead.** If it looks incomplete
378
- or names a path that errors, that is a BUG in the recipe — stop and report it to the user;
379
- do not silently repair it and move on. OBSERVED, real (privcloud first field run): the
380
- recipe omitted `docs/log.md`, the operator quietly added it by hand, and the bug only
381
- surfaced because they were later asked for near-misses — a silent repair is a lost bug
382
- report. Do NOT scope the commit to `docs` alone either: the renames are staged, but the
383
- inbound-link rewrites are UNSTAGED and reach outside `docs/` (`src/`, `scripts/`, `tests/`,
384
- `README.md`). Both belong in ONE commit, or you ship moved files whose links were never
385
- repaired. The tool never auto-commits, by design.
429
+ - **The moves land STAGED in your git index — nothing is committed for you.** `git mv` stages
430
+ each rename immediately (that is what preserves history), so when `apply-reorg` returns the
431
+ repo is holding N staged renames plus unstaged link rewrites. `apply-reorg` prints a closing
432
+ commit advisory; see "Finishing a run" below for the actual commit flow.
386
433
  - A basename collision (two files, same name, different original folders) is
387
434
  disambiguated (`-2`, `-3`, …); a collision with a **file that already exists at the
388
435
  destination** is skipped, logged, and does not stop the rest of the run.
@@ -393,7 +440,9 @@ a pointer to re-run `discover`.
393
440
  touches — is never removed. Only directories THIS run emptied are candidates; a dir that
394
441
  happened to already be empty before this run started is not this tool's to remove.
395
442
  - **After every move, `apply-reorg` re-scans the whole corpus** — `docs/product/`,
396
- `docs/logs/`, and `docs/archive/` all straight into `outline.json`, the database `search`
443
+ `docs/logs/`, and `docs/archive/` all (`docs/wiki/` is excluded, same as any other
444
+ `PAGES` dir — it holds synthesised pages, not source docs to reorg) — straight into
445
+ `outline.json`, the database `search`
397
446
  reads. Not a hint, not opt-in: it runs every time, even when nothing moved this run (e.g.
398
447
  re-running on a corpus already sorted from a previous pass). Measured bug this closes: on a
399
448
  real 37-doc corpus, `outline.json` used to hold records for only the 12 files a split had
@@ -402,6 +451,47 @@ a pointer to re-run `discover`.
402
451
  content, so a pre-move scan would just be redone), and reuses the same `scan` used
403
452
  everywhere else in this pipeline — no second scanner, no second outline format.
404
453
 
454
+ ### Finishing a run — the commit flow, run by hand every time
455
+
456
+ `apply-reorg`/`archive`/`cleanup-apply` never auto-commit, by design. Each run's staged renames
457
+ and unstaged link rewrites (in `.md` files only, but anywhere — e.g. the root `README.md`)
458
+ belong in ONE commit, including the
459
+ `CLAUDE.md` docs-pointer block `apply-reorg` writes — it is in `commit-files.txt` along with
460
+ everything else. Run these steps literally, in order:
461
+
462
+ 1. `git branch --show-current`. If it prints `main` or `master`, do **NOT** commit — tell the
463
+ user the files are ready and to switch to a branch first.
464
+ 2. `cat docs/.docs-builder/commit-files.txt` to see exactly what this run touched, and
465
+ `cat docs/.docs-builder/commit-dirty.txt` to see which of those files already carried the
466
+ operator's own uncommitted edits BEFORE this run — pathspec can't split hunks, so committing
467
+ the file commits that edit too. Then ask with `AskUserQuestion`, header `Commit`: if
468
+ `commit-dirty.txt` lists anything, **"Commit these N files now? Note: these files also
469
+ carry your own uncommitted edits, which will be committed too: …"** (name them); otherwise
470
+ **"Commit these N files now?"** — **Commit** (run the printed recipe) / **Leave uncommitted**
471
+ (say what is pending; nothing this run did gets undone).
472
+ 3. On **Commit**, run the printed recipe line EXACTLY as printed:
473
+ ```
474
+ git add --pathspec-from-file=docs/.docs-builder/commit-add.txt && git commit -m "docs: reorg" --pathspec-from-file=docs/.docs-builder/commit-files.txt
475
+ ```
476
+ (when `REPO` isn't the shell's cwd, both commands are printed as `git -C '<REPO>' …` instead
477
+ — run that form, unmodified). Do not hand-edit it, do not stage by hand instead, and do not
478
+ scope it to `docs/` alone — a `.md` outside `docs/` (e.g. `README.md`) can carry a repaired
479
+ link. If it errors or names a path that doesn't exist, that is a BUG: stop and report it to
480
+ the user; do not silently hand-repair and move on.
481
+ OBSERVED, real (privcloud first field run, pre-dating the pathspec-file recipe): a
482
+ hand-rolled recipe once omitted `docs/log.md` and the operator quietly added it by hand —
483
+ the bug only surfaced later, when asked for near-misses. A silent repair is a lost bug
484
+ report.
485
+ 4. After a successful commit, run `node $DB ledger` to stamp the consolidation — nothing in
486
+ `discover`/`apply-reorg`/`archive`/`cleanup-apply` stamps it for you, and without the stamp
487
+ `due` stays NOT due, the picker's verdict stays uninformed, and `/remember`'s docs nudge
488
+ never fires.
489
+
490
+ Add `docs/.docs-builder/` to `.gitignore` before the first commit if it is not already
491
+ ignored — it is machine state, regenerated every run, and the ledger stamp is per-clone by
492
+ design. Never use `git add -A` / `git add -u` / `git commit -a` for this: either would ALSO
493
+ absorb any unrelated in-flight work in the tree — OBSERVED TWICE, in two different repos.
494
+
405
495
  **After each move it repairs the paths that move just broke** — the whole point of doing this
406
496
  in a script. Both movers (`apply-reorg` and `archive`) go through ONE function, `moveDoc`, so
407
497
  the follow-up list cannot be added to one and missed by the other; that exact miss shipped
@@ -413,8 +503,12 @@ reported as a file that needs re-moving. Two follow-ups:
413
503
  (`records[].file`, and the `<file> :: ` prefix inside every key). This is the same
414
504
  function `archive` calls; `apply-reorg` used to bypass it, which silently invalidated
415
505
  every key of every file it moved. Both now reach it through `moveDoc`.
416
- 2. **Inbound links** — every git-tracked `.md`/`.js`/`.cjs`/`.mjs`/`.json`/`.yml` file that
417
- points at the old path (repo-rooted, e.g. `docs/GUIDE.md`) is rewritten to the new one.
506
+ 2. **Inbound links** — every git-tracked `.md` file, anywhere in the repo, that points at the
507
+ old path (repo-rooted, e.g. `docs/GUIDE.md`) is rewritten to the new one. Only `.md` files
508
+ are ever opened, read, or edited — this is a DOCS tool, not a repo-wide text rewriter.
509
+ FIELD BUG, real (bareloop, 2026-09-10): the previous version also scanned
510
+ `.js`/`.cjs`/`.mjs`/`.json`/`.yml`, and rewrote 6 signed JSON job specs (breaking their
511
+ hashes), a byte-signed `.mjs` close script, and a code comment that tripped a commit gate.
418
512
  In `.md` files specifically, a RELATIVE link is also caught: inside actual markdown link
419
513
  syntax only (`[text](../concepts/x.md)` or a reference-style `[label]: ./tools.md`), never
420
514
  bare prose, the target is resolved against the SCANNING file's own directory, and — if it
@@ -449,13 +543,14 @@ predicate, `isRewriteExempt`, at one call site. `docs/.docs-builder/` is exclude
449
543
 
450
544
  **A known, deliberate trade-off: this is a literal exact-path match over raw file bytes, not
451
545
  fence-aware or context-aware.** It rewrites every exact, word-bounded occurrence of the old path
452
- in every git-tracked `.md`/`.js`/`.cjs`/`.mjs`/`.json`/`.yml` file (except the two exemptions
453
- above) — inside a code fence, inside a sentence describing history ("this used to live at
454
- docs/OLD.md"), anywhere. A prose mention of where a file *used to be* WILL be rewritten to say
455
- where it is now, changing what the sentence says. This is intentional, not an oversight: a dead
456
- link is worse than a reworded sentence, the match is exact rather than inferred (unlike the
457
- dangling-reference *lint*, which infers and was cut outright at 1/27 precision), and every
458
- rewrite is printed per file so it is visible, never silent.
546
+ in every git-tracked `.md` file (except the two exemptions above) — inside a code fence, inside
547
+ a sentence describing history ("this used to live at docs/OLD.md"), anywhere. A prose mention of
548
+ where a file *used to be* WILL be rewritten to say where it is now, changing what the sentence
549
+ says. This is intentional, not an oversight: a dead link is worse than a reworded sentence, the
550
+ match is exact rather than inferred (unlike the dangling-reference *lint*, which infers and was
551
+ cut outright at 1/27 precision), and every rewrite is printed per file so it is visible, never
552
+ silent. Only `.md` files are ever in scope, so this trade-off never touches non-doc files —
553
+ see "Inbound links" above.
459
554
 
460
555
  ---
461
556
 
@@ -746,14 +841,16 @@ node $DB index-flat
746
841
  Writes **one** `docs/index.md` covering the whole corpus, in three sections: `## Product`
747
842
  (one row per file under `docs/product/`, plus any pages under `PAGES` — default `docs/wiki/`
748
843
  — if they exist, plus any doc still sitting in place elsewhere), `## Logs` (one row per file
749
- under `docs/logs/`), and `## Archive` (one row per file under `docs/archive/`). Each row is
750
- an H1 title, a line count, and a link, plus one indented line per H2 heading (in document
751
- order) so an agent can find and slice-read a section without opening the doc — each H2
752
- line carries its own `(Lstart–end)` line range, reusing the SAME `headings()`+`fenceMask()`
753
- boundaries `scan` already writes to `outline.json` (no second parser). Omitted when the doc
754
- has no H2s. `## Archive` rows are H1-only, never H2 lines an archived doc is frozen
755
- history, not a live section to route into. No theme grouping, no `labels.json`, no model
756
- call.
844
+ under `docs/logs/`, **grouped by its one-level subdir** a loose `docs/logs/x.md` row is
845
+ listed ungrouped first, then each subdir gets its own `### <subdir>/` header with its files
846
+ under it, subdirs sorted alphabetically), and `## Archive` (one row per file under
847
+ `docs/archive/`). Each row (product or logs, grouped or not) is an H1 title, a line count, and
848
+ a link, plus one indented line per H2 heading (in document order) so an agent can find and
849
+ slice-read a section without opening the doc each H2 line carries its own `(Lstart–end)`
850
+ line range, reusing the SAME `headings()`+`fenceMask()` boundaries `scan` already writes to
851
+ `outline.json` (no second parser). Omitted when the doc has no H2s. `## Archive` rows are
852
+ H1-only, never H2 lines — an archived doc is frozen history, not a live section to route into.
853
+ No theme grouping, no `labels.json`, no model call.
757
854
  Default destination `docs/index.md` — **the only writer of that default path** in this whole
758
855
  pipeline (nothing else writes an index at all).
759
856
  `search` reads `outline.json`, never `index.md`. Prints the row counts and records a `log.md`
@@ -774,7 +871,8 @@ v3 folds the old `reconcile` and `due` commands into one: "first run" (nothing s
774
871
  state, and two separate commands only made users guess which one to run.
775
872
 
776
873
  ```bash
777
- node $DB reorg
874
+ node $DB reorg # root-level .md files (top level only) + everything under docs/
875
+ node $DB reorg <dir> # re-checks every doc already inside <dir> — passes <dir> to discover
778
876
  ```
779
877
 
780
878
  If a ledger stamp exists (see "Knowing when reorg is due" below), its `due`-style drift