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.
- package/CHANGELOG.md +92 -0
- package/package.json +1 -1
- package/packages/ampcode/skills/branch-review/SKILL.md +20 -3
- package/packages/ampcode/skills/docs-builder/SKILL.md +168 -70
- package/packages/ampcode/skills/docs-builder/docs-builder.cjs +325 -70
- package/packages/ampcode/skills/release/SKILL.md +38 -8
- package/packages/ampcode/skills/remember/AGENT_RULES.md +14 -6
- package/packages/ampcode/skills/security/SKILL.md +6 -3
- package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package-lock.json +3 -3
- package/packages/claude/skills/branch-review/SKILL.md +20 -3
- package/packages/claude/skills/docs-builder/SKILL.md +168 -69
- package/packages/claude/skills/docs-builder/docs-builder.cjs +325 -70
- package/packages/claude/skills/release/SKILL.md +38 -8
- package/packages/claude/skills/remember/AGENT_RULES.md +14 -6
- package/packages/claude/skills/security/SKILL.md +6 -3
- package/packages/droid/commands/branch-review.md +20 -3
- package/packages/droid/commands/docs-builder/docs-builder.cjs +325 -70
- package/packages/droid/commands/docs-builder.md +168 -70
- package/packages/droid/commands/release.md +38 -8
- package/packages/droid/commands/remember/AGENT_RULES.md +14 -6
- package/packages/droid/commands/security.md +6 -3
- package/packages/opencode/command/branch-review.md +20 -3
- package/packages/opencode/command/docs-builder/docs-builder.cjs +325 -70
- package/packages/opencode/command/docs-builder.md +168 -70
- package/packages/opencode/command/release.md +38 -8
- package/packages/opencode/command/remember/AGENT_RULES.md +14 -6
- package/packages/opencode/command/security.md +6 -3
|
@@ -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
|
|
131
|
-
|
|
132
|
-
`docs/index.md` when the project has one to find what exists.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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 /
|
|
@@ -60,7 +60,7 @@ Not courtesies. These bind you as written, whether or not your tool enforces the
|
|
|
60
60
|
|
|
61
61
|
- **Always** identify affected files before making changes, and explain what will change and why
|
|
62
62
|
- **Ask first** — stop and get explicit sign-off — before modifying authentication systems, database schema or migrations, CI workflows, or `.claude/settings.json`
|
|
63
|
-
- **Never**
|
|
63
|
+
- **Never** put secrets in the tree — see [Security & Robustness Invariants §1](#security--robustness-invariants)
|
|
64
64
|
- **Never** commit to `main`. Commit to a new branch (name doesn't matter), then propose `/branch-review` followed by `/release`; merging and releasing are my call, made by name — "approve", "good", or "go" on a draft is not that call
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -113,6 +113,7 @@ Before adding any external dependency, all of these must be true:
|
|
|
113
113
|
- **Containerize only when necessary.** Start with a virtualenv or bare metal. Docker adds value for deployment parity and isolation — not for running a script
|
|
114
114
|
- **Responsive web UI is mandatory in dev projects.** Any web UI must be usable on mobile by default — fluid layouts, viewport meta tag, breakpoints for narrow screens, no horizontal scroll. Test in DevTools device emulation before declaring a UI task done. POCs are exempt (validate the idea first), but the moment a POC graduates to a real project this becomes a hard requirement
|
|
115
115
|
- **Surgical changes only.** Touch what the task requires; nothing else. Don't "improve" adjacent code, comments, or formatting. Match existing style even if you'd do it differently. Only clean up orphans your own change created. Dead code, nits, bugs you pass on the way: if it's inside or affects the code you're already changing, and the fix changes no behavior, fix it and say so. Otherwise report it — say what it costs to leave it. "It would be nicer" is not a cost. Every changed line traces to the request or to a fix you named
|
|
116
|
+
- **Meter the whole unit of work.** Usage and cost sum every call a unit of work makes; the last call's number is never the total, and one unpriced call makes the total unknown, not zero
|
|
116
117
|
|
|
117
118
|
### Red Flags — Stop and Flag These
|
|
118
119
|
- Over-engineering simple problems
|
|
@@ -144,7 +145,7 @@ particular toolchain spells it.
|
|
|
144
145
|
|
|
145
146
|
- **After the design stabilizes, not during exploration.** Do not test a prototype — you will write tests for code you delete tomorrow. First make it work (POC), then make it right (tests), then make it fast
|
|
146
147
|
- **Tests first when you already know the contract.** Pure functions, algorithms, parsers, validators, data transformations — write the test, watch it fail, then implement. When you are still discovering the interface, that same discipline produces churn and false confidence
|
|
147
|
-
- **Write tests for bugs.** Every fix ships a regression test
|
|
148
|
+
- **Write tests for bugs.** Every fix ships a regression test — the highest-value test there is
|
|
148
149
|
- **Write tests before refactoring.** Characterization tests lock in current behavior first, then change the code
|
|
149
150
|
- **Write tests when the code has users.** Called by other modules or exposed externally means it needs tests; a helper serving one caller does not need its own file
|
|
150
151
|
- **Do not test glue code.** Something that only wires A to B to C is covered at the integration level
|
|
@@ -152,7 +153,7 @@ particular toolchain spells it.
|
|
|
152
153
|
### What makes a good test
|
|
153
154
|
|
|
154
155
|
- **Tests real behavior.** Call the public interface, assert on observable output. Never reach into internals
|
|
155
|
-
- **Fails for the right reason.** It breaks when the feature breaks, not when the implementation moves
|
|
156
|
+
- **Fails for the right reason — prove it.** It breaks when the feature breaks, not when the implementation moves. Revert the fix (or switch the check off), run the test, watch it go red, then restore it: a test you have never seen fail is unverified, however right it reads
|
|
156
157
|
- **Reads like a spec.** Someone new to the code should learn what the feature does by reading it
|
|
157
158
|
- **Self-contained.** Sets up its own state, runs, cleans up. No ordering dependencies, and no reliance on project directories, user config, or ambient environment
|
|
158
159
|
- **Deterministic.** Flaky tests erode trust. A dependency on timing, network, or global state is a defect in the test
|
|
@@ -186,15 +187,22 @@ Throwaway POCs are exempt while you validate logic (per **POC first** above) —
|
|
|
186
187
|
1. **No secrets in the repo.** Keys, tokens, and credentials load from the environment / a secret store at runtime — never hardcoded, never logged. `.env` is gitignored; only a value-less `.env.example` is committed. Scan history before trusting a repo. One leaked key is a breached database or a runaway bill.
|
|
187
188
|
2. **Scope every data access to its owner.** Each record read or written is constrained to the requesting principal — via DB-level rules (RLS / row policies) and/or an application-layer ownership check. Never trust a client-supplied id without a gate. If the storage layer offers row-level policies, enabling them is not optional, and "on but too broad" still fails.
|
|
188
189
|
3. **Bound every reachable endpoint.** Rate-limit public routes AND authenticated mutation/write routes AND abuse-prone inbound paths (mail, webhooks). An unbounded route is a free DoS and bill amplifier — a script in a loop should not be able to take the service down.
|
|
189
|
-
4. **Handle the unhappy path.** Every IO / network / DB / third-party call has an explicit failure path. Nothing fails silently. Internal detail (stack traces, queries, secrets) never reaches the client. Async/background work carries its own catch.
|
|
190
|
+
4. **Handle the unhappy path.** Every IO / network / DB / third-party call has an explicit failure path. Nothing fails silently. Internal detail (stack traces, queries, secrets) never reaches the client. Async/background work carries its own catch. A warning nobody has to act on is not a check — either it halts / reds the run, or it is counted and surfaced where a human reads it. Record what you asked a dependency for and what you got back. Read and record the stop/finish reason of every external or model call — a cut-off answer (length/max_tokens) is never the same as no answer.
|
|
190
191
|
5. **Authorization is not authentication.** "Logged in" never implies "allowed". Every state-changing or privileged action checks ownership AND role/permission. If swapping an id in a request would expose or mutate someone else's data, it's a bug — return 403.
|
|
191
192
|
6. **Data access scales.** No queries inside loops, no per-render repeated round-trips, indexes on every filtered/joined column. Code that's fine at 10 users and collapses at 1,000 is a latent outage.
|
|
192
193
|
|
|
193
|
-
Also hold the line on:
|
|
194
|
+
Also hold the line on:
|
|
195
|
+
|
|
196
|
+
- **Validate input at every trust boundary.** Untrusted uploads, inbound mail, webhooks, and spoofable headers like `X-Forwarded-For` — trust the latter only behind a vetted proxy.
|
|
197
|
+
- **Model output is untrusted input too.** Validate it against a schema and key every decision on a typed field — never regex-parse the model's prose for a number, id, or verdict.
|
|
198
|
+
- **Guard lookups keyed by an external string.** Look the key up only among the map's own entries, never inherited ones — an inherited member can otherwise answer for a key the map never had, so "not found → throw" is bypassed.
|
|
199
|
+
- **Parameterize every query.** Never string-build SQL.
|
|
200
|
+
- **Use vetted libraries for security-critical code** — see [External Dependency Checklist](#external-dependency-checklist).
|
|
201
|
+
- **Bind least-privilege.** Loopback, not `0.0.0.0`, unless the port is deliberately public.
|
|
194
202
|
|
|
195
203
|
**Verify at two moments, not one.**
|
|
196
204
|
- **While building** — this list shapes the code as it's written.
|
|
197
|
-
- **Before deploy/merge** —
|
|
205
|
+
- **Before deploy/merge** — see [Operating Flow §2](#operating-flow). A Critical/High finding blocks the ship; lower-severity findings get logged and triaged, not silently shipped. Proactively remind the user to run `/branch-review` whenever a change touches auth, data access, endpoints, secrets, or untrusted input.
|
|
198
206
|
|
|
199
207
|
---
|
|
200
208
|
|
|
@@ -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
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
589
|
-
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.
|
|
590
|
-
"integrity": "sha512-
|
|
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
|
|
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
|
|
64
|
-
> too big into pages and index them. Use
|
|
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
|
|
93
|
-
status is near-conclusive for `archive` and that `suggested` is a prior, not
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
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/
|
|
163
|
-
(`git mv`);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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` / `
|
|
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`,
|
|
208
|
-
`
|
|
209
|
-
|
|
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
|
|
212
|
-
|
|
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 #
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
|
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
|
-
- `
|
|
353
|
-
- `
|
|
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 —
|
|
373
|
-
rename immediately (that is what preserves history), so when `apply-reorg` returns the
|
|
374
|
-
is holding N staged renames
|
|
375
|
-
|
|
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
|
|
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
|
|
417
|
-
|
|
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
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|