liteagents 3.6.0 → 3.7.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.
@@ -126,19 +126,49 @@ confirmed in Phase 0.5.
126
126
  summarize, escalate. Do not weigh it yourself.
127
127
  - **All clean** → continue.
128
128
 
129
- ## Phase 2 — Docs sweep
130
- Update what this feature actually changed, wherever those docs live in this
131
- project — match each file's existing format, touch nothing unrelated. Use
132
- `docs/index.md` when the project has one to find what exists.
133
-
134
- - **CHANGELOG.md** — new entry.
135
- - **README.md** — only if user-facing usage changed.
129
+ ## Phase 2 — Docs sweep (required — no skipping, no sampling)
130
+ Update what this branch changed, wherever those docs live in this project —
131
+ match each file's existing format, touch nothing unrelated. Use
132
+ `docs/index.md` when the project has one to find what exists. **All three
133
+ passes run on every release**; the size of the branch or the change never
134
+ cuts one.
135
+
136
+ 1. **List every change.** Read `git log --format='%h %s%n%b'
137
+ origin/main..HEAD` — the bodies, not just the subjects — and the diff.
138
+ Write one line per user-visible change: feature, command, flag, behaviour,
139
+ fix, dependency bump. A subject is a summary; the body is the list, and a
140
+ sweep built from subjects drops whatever only a body mentions.
141
+ 2. **CHANGELOG.md** — a new entry holding every line from pass 1, each under
142
+ the heading the file already uses for its kind (Added / Changed / Fixed /
143
+ Security). A new capability is **Added** even when it shipped in a `fix:`
144
+ commit. Then check it back: every pass-1 line maps to an entry, and the
145
+ semver level in Phase 3 agrees with the headings (an Added entry means at
146
+ least minor).
147
+ 3. **Grep for stale text.** For every string the diff removed or replaced
148
+ that a reader might have copied — a command line, flag, file name, recipe,
149
+ env var, printed message — search the docs for the old form:
150
+ `grep -rnF "<old string>" --include='*.md' .` (past CHANGELOG entries are
151
+ history; leave them). Every other hit is stale **because of this branch** —
152
+ update it. "It was already stale before this branch" is a claim: prove it
153
+ with `git show $(git merge-base origin/main HEAD):<source path>`. If the
154
+ base code already disagreed with the doc, report it as out of scope;
155
+ otherwise it is yours to fix.
156
+
157
+ Then judge each of these against the pass-1 list:
158
+ - **README.md** — if user-facing usage changed.
136
159
  - **PRD** — the feature's entry / status.
137
160
  - **Guide / context docs** — the project's standing context.
138
161
  - **Findings / learnings** — where the project keeps them.
139
162
  - **Any other frequently-updated doc** this change makes stale.
140
163
 
141
- If a doc needs no change, **say so** rather than editing it for its own sake.
164
+ Report **one row per doc**: file · changed / no change · the evidence (the
165
+ grep or diff you ran). A "no change" with no evidence is a skip, and a skip
166
+ fails this phase — it is not a pass. If a doc truly needs no change, say so
167
+ with the evidence rather than editing it for its own sake.
168
+
169
+ **The sweep is the worker's job, start to finish.** The orchestrator checks
170
+ the report; it does not redo or patch the sweep. A gap it finds goes back to
171
+ the worker, and it counts as a failed sweep, not a small follow-up.
142
172
 
143
173
  ## Phase 3 — Cut (local only)
144
174
  1. **Version bump** — pick the semver level from the change (patch / minor /
@@ -23,9 +23,12 @@ These show up in nearly every quickly-built app regardless of stack:
23
23
 
24
24
  1. **Secrets in the repo.** Tokens / API keys / `.env` files committed to
25
25
  tracked files or anywhere in git history. Verify `.env` is gitignored and
26
- only a value-less `.env.example` is tracked; scan history (`git log -p`,
27
- `git grep`) for leaked keys. Secrets must load from env / a secret store at
28
- runtime never hardcoded, never logged.
26
+ only a value-less `.env.example` is tracked. Scan ALL history, every
27
+ branch a range or target never narrows this with:
28
+ `git log --all -p | grep -nE '(sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{30,}|xox[abprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{35}|-----BEGIN [A-Z ]*PRIVATE KEY)'`
29
+ plus any key shape specific to this project's providers. Report each hit
30
+ as real or a known test fixture. Secrets must load from env / a secret
31
+ store at runtime — never hardcoded, never logged.
29
32
  2. **Data-access authorization (tenant isolation).** Every record read or
30
33
  written must be scoped to the requesting principal — via DB-level rules
31
34
  (RLS / row policies) and/or application-layer ownership checks. Flag any
@@ -585,9 +585,9 @@
585
585
  }
586
586
  },
587
587
  "node_modules/hono": {
588
- "version": "4.13.0",
589
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz",
590
- "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==",
588
+ "version": "4.13.7",
589
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz",
590
+ "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==",
591
591
  "license": "MIT",
592
592
  "engines": {
593
593
  "node": ">=16.9.0"
@@ -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 `.claude/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
  ```
@@ -51,8 +51,13 @@ Every command below is `node $DB …`; everything the script writes (`docs/.docs
51
51
  JSON state, `docs/index.md`, the ledger, the log, the config pointer) lands under the target
52
52
  repo. `REPO=` is optional and only needed when not running from the repo root.
53
53
 
54
- **With an argument** (`reorg`, `cleanup <file>`, or `search <query words...>`) — run that mode
55
- directly, no question asked.
54
+ **With an argument** (`reorg [dir]`, `cleanup <file>`, or `search <query words...>`) — run that
55
+ mode directly, no question asked. `reorg <dir>` re-checks every doc already inside `<dir>` —
56
+ useful to scope a check to one directory outside the default scan. A **bare** `reorg`/`discover`
57
+ (no argument) already re-checks `docs/product/`, `docs/wiki/` and `docs/logs/` on every run —
58
+ only `docs/archive/` stays frozen and out of scope. Scan scope with no argument is otherwise
59
+ narrow by design: root-level `.md` files (top level only, not recursive) plus everything under
60
+ `docs/` (recursive) — nothing else in the repo is ever listed or moved.
56
61
 
57
62
  **Bare `/docs-builder`, no argument — ALWAYS ask, never auto-detect.** Run `due` first and
58
63
  put its one-line verdict in the question text so the choice is informed. Then use
@@ -60,9 +65,9 @@ put its one-line verdict in the question text so the choice is informed. Then us
60
65
 
61
66
  > **Question: What should docs-builder do?**
62
67
  >
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.
68
+ > - **First run** — sort root-level `.md` files and everything under `docs/` into
69
+ > product/wiki/logs/archive, then split anything too big into pages and index them. Use
70
+ > when docs are a pile of loose files, or docs-builder has never run here.
66
71
  > - **Docs drift** — docs moved on since the last run: report what changed, rebuild the
67
72
  > index, re-run lint. Nothing is restructured and nothing is split.
68
73
 
@@ -89,8 +94,9 @@ read-only — no model cost, no interview, nothing moves.
89
94
  bucket). `bucket` itself starts **empty** on every row.
90
95
  2. **The classification interview.** Feed the model the WHOLE plan table (file, h1, snip,
91
96
  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.
97
+ `product`/`wiki`/`logs`/`archive` — with a one-line reason: honour that a SHOUTED
98
+ self-declared status is near-conclusive for `archive` and that `suggested` is a prior, not
99
+ an authority.
94
100
  The model writes its answers straight into `reorg-plan.json`. Then show the user the full
95
101
  resulting table via `AskUserQuestion` (approve all / correct specific rows / abort) — a
96
102
  correction changes the plan file before anything moves.
@@ -102,12 +108,10 @@ read-only — no model cost, no interview, nothing moves.
102
108
  to split** (any, all, none). Only then run `cleanup <file>` (Mode 1) on each chosen file —
103
109
  `cleanup` itself prints the estimated split cost for that one file, then a mechanical
104
110
  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.
111
+ Add `docs/.docs-builder/` to `.gitignore` if it is not already ignored: it is machine state,
112
+ regenerated every run, and the ledger stamp is per-clone by design — it must never ride into
113
+ history on a later `git add -A`. Then follow "Finishing a run" (below `apply-reorg`, Mode 0
114
+ step 3) to commit and stamp the ledger.
111
115
 
112
116
  The two stops are deliberate and different. Step 2 guards *correctness* — the interview and
113
117
  the user's approval, before a single file moves. Step 3's follow-up guards *cost* — splitting
@@ -118,7 +122,7 @@ when they pick "First run". Never split N files in one shot on an unseen list.
118
122
  first, if a ledger stamp exists, then it runs `discover`. If any row's `bucket` is still
119
123
  empty (true on a genuine first run, or when new files appeared since the last classification),
120
124
  `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
125
+ unclassified plan. Otherwise follow "Finishing a run" once it's done. Once
122
126
  the plan is fully classified (an already-sorted corpus's re-run
123
127
  carries its prior classifications forward automatically — see "Discover is idempotent"
124
128
  below), `reorg` continues straight through `apply-reorg` → `lint`, no further stop, so
@@ -131,7 +135,7 @@ already sorted: nothing new to classify, so the interview gate never fires.
131
135
 
132
136
  | Mode | Menu option | Does | Destructive |
133
137
  |---|---|---|---|
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) |
138
+ | `/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
139
  | `/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
140
  | `/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
141
  | `/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 +159,31 @@ docs/
155
159
  README.md entry point, referenced from CLAUDE.md
156
160
  index.md GENERATED by index-flat/apply-reorg/cleanup-apply. never hand-edited.
157
161
  READER-FACING. The WHOLE-CORPUS map — the only file with a completeness
158
- guarantee. ## Product, ## Logs, ## Archive.
162
+ guarantee. ## Product, ## Logs (grouped by subdir), ## Archive.
159
163
  log.md append-only: ## [DATE] operation | description — written by
160
164
  `archive`, `apply-reorg`, `validate`, and `reorg`; NOT written by
161
165
  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:
166
+ product/ docs ABOUT THE PRODUCT ITSELF specs, PRDs, designs, guides for this
167
+ product. FLAT, no subdirs. `apply-reorg` MOVES files here (`git mv`);
168
+ content is never rewritten. Re-checked on EVERY reorg (files sitting here
169
+ are re-classified along with everything else — agents litter buckets
170
+ over time).
171
+ wiki/ GENERIC knowledge for the whole repo, not product-specific — conventions,
172
+ how-tos, standards, reference. FLAT, no subdirs. Also where Mode 1
173
+ (`cleanup`)'s split page writers put synthesised pages. Re-checked every
174
+ reorg, same as product/.
175
+ logs/ ONE-TIME, specific, timely knowledge tied to one section/scenario/case —
176
+ POCs, experiments, investigations, incident/session write-ups, reports. The
177
+ ONLY bucket that may nest, ONE level: `docs/logs/<group>/*.md` — the group
178
+ is the FIRST path segment under `docs/` (a special subfolder is one
179
+ self-explanatory group, e.g. every POC under `docs/fwd/` stays together as
180
+ `fwd`, however deep a file actually sits inside it), UNLESS that segment is
181
+ itself a bucket name (`product`/`wiki`/`archive` → flat; `logs` → the group
182
+ is the SECOND segment instead). A file with no first segment — loose at the
183
+ repo root, or directly under `docs/` — stays flat. Re-checked every reorg,
184
+ same as product/wiki/.
185
+ archive/ what got cleaned up: self-declared dead. FROZEN — never re-checked, never
186
+ walked by a bare `discover`/`reorg` at all. Originals are BYTE-FROZEN:
168
187
  nothing under here is ever a rewrite target, so a doc lands byte-identical
169
188
  to what it carried in (a clean R100 rename) and stays that way. Links
170
189
  elsewhere POINTING AT it are still repaired. History via `git mv`.
@@ -202,14 +221,19 @@ moved the problem (it then clobbered `outline.json` across concurrent splits ins
202
221
  **the themed index was removed outright, 2026-08-24**. One index, rebuilt on every reorg and
203
222
  after every split, is the whole design.
204
223
 
205
- **Never moved — enforced in code, not just documented** (`PROTECTED_NAMES` / `walkMd`):
224
+ **Never moved — enforced in code, not just documented** (`PROTECTED_NAMES` / `isProtectedName` /
225
+ `walkMd`):
206
226
 
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.
227
+ - **Files, at any depth, matched CASE-INSENSITIVELY:** `README.md`, `index.md`, `log.md`,
228
+ `CHANGELOG.md`, `LICENSE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`,
229
+ `CLAUDE.md`, `AGENTS.md`, `AGENT.md` so `readme.md`, `Claude.md`, `changelog.md`,
230
+ `agents.md`, etc. are protected too, not just their exact-case forms. Bare `LICENSE`/`NOTICE`
231
+ have no `.md` extension, so the walker never sees them.
210
232
  - **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.
233
+ `.amp/`, `.docs-builder/`) plus `node_modules/`. `archive/` is skipped unconditionally by a
234
+ bare `discover`/`reorg` (frozen, never re-checked); `product/`, `wiki/` and `logs/` are
235
+ entered and re-checked on that same bare run — only an explicit `discover <dir>` naming one
236
+ of the reserved names directly bypasses this at all four.
213
237
 
214
238
  ---
215
239
 
@@ -229,11 +253,31 @@ purpose, not silently dropped.
229
253
  ### 1. Discover (script) — enriches and PROPOSES, never classifies, never moves
230
254
 
231
255
  ```bash
232
- node $DB discover # defaults to docs/
256
+ node $DB discover # root-level .md files (top level only) + everything under docs/
257
+ node $DB discover <dir> # scopes to exactly <dir> instead — e.g. a directory outside docs/
233
258
  ```
234
259
 
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:
260
+ **Scan scope with no argument is deliberately narrow:** (a) `.md` files sitting directly at
261
+ the repo root (top level, not recursive) and (b) everything under `docs/` (recursive, entering
262
+ `product/`, `wiki/` and `logs/` — only `docs/archive/` stays frozen and skipped). Every other
263
+ `.md` file anywhere else in the repo is out of scope entirely — never listed, never moved,
264
+ whether or not `docs/` exists. `PROTECTED_NAMES` still applies at the repo root (case-
265
+ insensitively — see Layout above), so `README.md`, `CLAUDE.md`, `CHANGELOG.md`, `AGENTS.md`,
266
+ `AGENT.md`, `LICENSE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `index.md`
267
+ and `log.md` are never planned or moved. `ROOT=` is NOT read by this script — setting it
268
+ prints a `WARN`; pass the folder as the argument.
269
+
270
+ **Because `product/`, `wiki/` and `logs/` are re-checked every bare run,** a file already
271
+ sitting in one of them is a REAL row too, not folded away — its `suggested` bucket defaults to
272
+ wherever it already sits (a stronger signal, like a filename token or a SHOUTED status word,
273
+ can still override that). It still needs `bucket` filled by the interview like any other row
274
+ (even if the answer is "yes, stays put") — that settles after one classify+apply pass and
275
+ carries forward from then on, same as everything else. An explicit `discover <dir>` still
276
+ scopes to exactly that one directory, unaffected — useful for anything outside the default
277
+ root+docs/ coverage.
278
+
279
+ Recursively finds every in-scope `*.md` (skipping `.docs-builder/` and the protected files, at
280
+ any depth in scope), and for each one writes a row with:
237
281
 
238
282
  - `h1` and a short `snip` (first ~200 chars of body, fence-masked) — reused straight from the
239
283
  same `headings()`/`snippet()`/`fenceMask()` parsers `scan` uses, no second extraction path.
@@ -242,14 +286,22 @@ Recursively finds every `*.md` under the root (skipping `wiki/`, `logs/`, `archi
242
286
  *sorted* — it is no longer a bucket.
243
287
  - `suggested` + `reason` — a mechanical PRIOR, never a verdict:
244
288
 
289
+ Checked in this order — the first rule that matches wins:
290
+
245
291
  | suggested | rule |
246
292
  |---|---|
247
293
  | `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
294
  | `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 |
295
+ | *(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`) |
296
+ | `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". |
297
+ | `product` | has an H1, no archive/logs/wiki signal — the default when nothing else applies |
250
298
  | `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
299
  | `product` | no H1 at all, and not an include stub — no strong signal either way; the interview decides, same as any other row |
252
300
 
301
+ `suggested` never proposes `wiki` from a residency check alone reaching further than its own
302
+ match — the heuristic is only ever a prior; the interview decides the real `bucket`, including
303
+ routing a doc INTO `wiki` from anywhere.
304
+
253
305
  - `bucket` — **empty on any row discover has not classified before** (see carry-forward
254
306
  below; a re-run keeps a bucket the interview already set). This is the field the
255
307
  classification interview (step 2) fills, and the ONLY field `apply-reorg` reads to decide
@@ -308,7 +360,7 @@ was the silent move, not the judgement.
308
360
 
309
361
  1. Read `docs/.docs-builder/reorg-plan.json`. Feed the model the WHOLE table — `file`, `h1`,
310
362
  `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
363
+ every row (`product`/`wiki`/`logs`/`archive`) with a one-line reason. `suggested` is a PRIOR the
312
364
  model is shown, never an authority over it — but a SHOUTED self-declared status
313
365
  (`**Status: CLOSED**`) is near-conclusive for `archive` regardless of what the mechanical
314
366
  prior says.
@@ -348,9 +400,15 @@ message, not a crash — so nothing can move on an unreviewed plan. A plan from
348
400
  version (`bucket: 'oversized'` or `'review'`, both gone from the schema) is refused too, with
349
401
  a pointer to re-run `discover`.
350
402
 
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>`
403
+ - `product` → verified `git mv` to `docs/product/<basename>` (always flat)
404
+ - `wiki` → verified `git mv` to `docs/wiki/<basename>` (always flat)
405
+ - `logs` → verified `git mv` to `docs/logs/<basename>` (loose) **or**
406
+ `docs/logs/<group>/<basename>` — the ONE bucket that may nest, ONE level, where the group is
407
+ the file's FIRST path segment under `docs/` (unless that segment is itself a bucket name —
408
+ see the Layout section above). A deeper path inside the same special subfolder joins the
409
+ SAME group (no ratchet on a re-check either).
410
+ - `archive` → verified `git mv` to `docs/archive/<basename>` (always flat, and frozen — never
411
+ re-checked again once there)
354
412
  - **Oversized files move too — size decides splittable, not sorted.** No bucket is exempt.
355
413
  After the move, every oversized row is printed as a follow-up list at its NEW path, one
356
414
  `cleanup <path> (N lines)` line per file — run `cleanup` (Mode 1, below) on each, by hand,
@@ -369,20 +427,10 @@ a pointer to re-run `discover`.
369
427
  static — it never varies with row count, so a re-run rewrites identical bytes. Idempotent:
370
428
  an existing block is replaced in place, never duplicated; other content is left alone.
371
429
  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.
430
+ - **The moves land STAGED in your git index — nothing is committed for you.** `git mv` stages
431
+ each rename immediately (that is what preserves history), so when `apply-reorg` returns the
432
+ repo is holding N staged renames plus unstaged link rewrites. `apply-reorg` prints a closing
433
+ commit advisory; see "Finishing a run" below for the actual commit flow.
386
434
  - A basename collision (two files, same name, different original folders) is
387
435
  disambiguated (`-2`, `-3`, …); a collision with a **file that already exists at the
388
436
  destination** is skipped, logged, and does not stop the rest of the run.
@@ -393,7 +441,9 @@ a pointer to re-run `discover`.
393
441
  touches — is never removed. Only directories THIS run emptied are candidates; a dir that
394
442
  happened to already be empty before this run started is not this tool's to remove.
395
443
  - **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`
444
+ `docs/logs/`, and `docs/archive/` all (`docs/wiki/` is excluded, same as any other
445
+ `PAGES` dir — it holds synthesised pages, not source docs to reorg) — straight into
446
+ `outline.json`, the database `search`
397
447
  reads. Not a hint, not opt-in: it runs every time, even when nothing moved this run (e.g.
398
448
  re-running on a corpus already sorted from a previous pass). Measured bug this closes: on a
399
449
  real 37-doc corpus, `outline.json` used to hold records for only the 12 files a split had
@@ -402,6 +452,47 @@ a pointer to re-run `discover`.
402
452
  content, so a pre-move scan would just be redone), and reuses the same `scan` used
403
453
  everywhere else in this pipeline — no second scanner, no second outline format.
404
454
 
455
+ ### Finishing a run — the commit flow, run by hand every time
456
+
457
+ `apply-reorg`/`archive`/`cleanup-apply` never auto-commit, by design. Each run's staged renames
458
+ and unstaged link rewrites (in `.md` files only, but anywhere — e.g. the root `README.md`)
459
+ belong in ONE commit, including the
460
+ `CLAUDE.md` docs-pointer block `apply-reorg` writes — it is in `commit-files.txt` along with
461
+ everything else. Run these steps literally, in order:
462
+
463
+ 1. `git branch --show-current`. If it prints `main` or `master`, do **NOT** commit — tell the
464
+ user the files are ready and to switch to a branch first.
465
+ 2. `cat docs/.docs-builder/commit-files.txt` to see exactly what this run touched, and
466
+ `cat docs/.docs-builder/commit-dirty.txt` to see which of those files already carried the
467
+ operator's own uncommitted edits BEFORE this run — pathspec can't split hunks, so committing
468
+ the file commits that edit too. Then ask with `AskUserQuestion`, header `Commit`: if
469
+ `commit-dirty.txt` lists anything, **"Commit these N files now? Note: these files also
470
+ carry your own uncommitted edits, which will be committed too: …"** (name them); otherwise
471
+ **"Commit these N files now?"** — **Commit** (run the printed recipe) / **Leave uncommitted**
472
+ (say what is pending; nothing this run did gets undone).
473
+ 3. On **Commit**, run the printed recipe line EXACTLY as printed:
474
+ ```
475
+ 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
476
+ ```
477
+ (when `REPO` isn't the shell's cwd, both commands are printed as `git -C '<REPO>' …` instead
478
+ — run that form, unmodified). Do not hand-edit it, do not stage by hand instead, and do not
479
+ scope it to `docs/` alone — a `.md` outside `docs/` (e.g. `README.md`) can carry a repaired
480
+ link. If it errors or names a path that doesn't exist, that is a BUG: stop and report it to
481
+ the user; do not silently hand-repair and move on.
482
+ OBSERVED, real (privcloud first field run, pre-dating the pathspec-file recipe): a
483
+ hand-rolled recipe once omitted `docs/log.md` and the operator quietly added it by hand —
484
+ the bug only surfaced later, when asked for near-misses. A silent repair is a lost bug
485
+ report.
486
+ 4. After a successful commit, run `node $DB ledger` to stamp the consolidation — nothing in
487
+ `discover`/`apply-reorg`/`archive`/`cleanup-apply` stamps it for you, and without the stamp
488
+ `due` stays NOT due, the picker's verdict stays uninformed, and `/remember`'s docs nudge
489
+ never fires.
490
+
491
+ Add `docs/.docs-builder/` to `.gitignore` before the first commit if it is not already
492
+ ignored — it is machine state, regenerated every run, and the ledger stamp is per-clone by
493
+ design. Never use `git add -A` / `git add -u` / `git commit -a` for this: either would ALSO
494
+ absorb any unrelated in-flight work in the tree — OBSERVED TWICE, in two different repos.
495
+
405
496
  **After each move it repairs the paths that move just broke** — the whole point of doing this
406
497
  in a script. Both movers (`apply-reorg` and `archive`) go through ONE function, `moveDoc`, so
407
498
  the follow-up list cannot be added to one and missed by the other; that exact miss shipped
@@ -413,8 +504,12 @@ reported as a file that needs re-moving. Two follow-ups:
413
504
  (`records[].file`, and the `<file> :: ` prefix inside every key). This is the same
414
505
  function `archive` calls; `apply-reorg` used to bypass it, which silently invalidated
415
506
  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.
507
+ 2. **Inbound links** — every git-tracked `.md` file, anywhere in the repo, that points at the
508
+ old path (repo-rooted, e.g. `docs/GUIDE.md`) is rewritten to the new one. Only `.md` files
509
+ are ever opened, read, or edited — this is a DOCS tool, not a repo-wide text rewriter.
510
+ FIELD BUG, real (bareloop, 2026-09-10): the previous version also scanned
511
+ `.js`/`.cjs`/`.mjs`/`.json`/`.yml`, and rewrote 6 signed JSON job specs (breaking their
512
+ hashes), a byte-signed `.mjs` close script, and a code comment that tripped a commit gate.
418
513
  In `.md` files specifically, a RELATIVE link is also caught: inside actual markdown link
419
514
  syntax only (`[text](../concepts/x.md)` or a reference-style `[label]: ./tools.md`), never
420
515
  bare prose, the target is resolved against the SCANNING file's own directory, and — if it
@@ -449,13 +544,14 @@ predicate, `isRewriteExempt`, at one call site. `docs/.docs-builder/` is exclude
449
544
 
450
545
  **A known, deliberate trade-off: this is a literal exact-path match over raw file bytes, not
451
546
  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.
547
+ in every git-tracked `.md` file (except the two exemptions above) — inside a code fence, inside
548
+ a sentence describing history ("this used to live at docs/OLD.md"), anywhere. A prose mention of
549
+ where a file *used to be* WILL be rewritten to say where it is now, changing what the sentence
550
+ says. This is intentional, not an oversight: a dead link is worse than a reworded sentence, the
551
+ match is exact rather than inferred (unlike the dangling-reference *lint*, which infers and was
552
+ cut outright at 1/27 precision), and every rewrite is printed per file so it is visible, never
553
+ silent. Only `.md` files are ever in scope, so this trade-off never touches non-doc files —
554
+ see "Inbound links" above.
459
555
 
460
556
  ---
461
557
 
@@ -746,14 +842,16 @@ node $DB index-flat
746
842
  Writes **one** `docs/index.md` covering the whole corpus, in three sections: `## Product`
747
843
  (one row per file under `docs/product/`, plus any pages under `PAGES` — default `docs/wiki/`
748
844
  — 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.
845
+ under `docs/logs/`, **grouped by its one-level subdir** a loose `docs/logs/x.md` row is
846
+ listed ungrouped first, then each subdir gets its own `### <subdir>/` header with its files
847
+ under it, subdirs sorted alphabetically), and `## Archive` (one row per file under
848
+ `docs/archive/`). Each row (product or logs, grouped or not) is an H1 title, a line count, and
849
+ a link, plus one indented line per H2 heading (in document order) so an agent can find and
850
+ slice-read a section without opening the doc each H2 line carries its own `(Lstart–end)`
851
+ line range, reusing the SAME `headings()`+`fenceMask()` boundaries `scan` already writes to
852
+ `outline.json` (no second parser). Omitted when the doc has no H2s. `## Archive` rows are
853
+ H1-only, never H2 lines — an archived doc is frozen history, not a live section to route into.
854
+ No theme grouping, no `labels.json`, no model call.
757
855
  Default destination `docs/index.md` — **the only writer of that default path** in this whole
758
856
  pipeline (nothing else writes an index at all).
759
857
  `search` reads `outline.json`, never `index.md`. Prints the row counts and records a `log.md`
@@ -774,7 +872,8 @@ v3 folds the old `reconcile` and `due` commands into one: "first run" (nothing s
774
872
  state, and two separate commands only made users guess which one to run.
775
873
 
776
874
  ```bash
777
- node $DB reorg
875
+ node $DB reorg # root-level .md files (top level only) + everything under docs/
876
+ node $DB reorg <dir> # re-checks every doc already inside <dir> — passes <dir> to discover
778
877
  ```
779
878
 
780
879
  If a ledger stamp exists (see "Knowing when reorg is due" below), its `due`-style drift