liteagents 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/package.json +1 -1
- package/packages/ampcode/commands/docs-builder/docs-builder.cjs +25 -16
- package/packages/ampcode/commands/docs-builder.md +43 -20
- package/packages/ampcode/commands/remember/AGENT_RULES.md +1 -1
- package/packages/ampcode/commands/remember/friction.cjs +407 -7
- package/packages/ampcode/commands/remember.md +210 -149
- package/packages/claude/commands/docs-builder/docs-builder.cjs +25 -16
- package/packages/claude/commands/docs-builder.md +43 -20
- package/packages/claude/commands/remember/friction.cjs +407 -7
- package/packages/claude/commands/remember.md +210 -149
- package/packages/droid/commands/docs-builder/docs-builder.cjs +25 -16
- package/packages/droid/commands/docs-builder.md +43 -20
- package/packages/droid/commands/remember/AGENT_RULES.md +1 -1
- package/packages/droid/commands/remember/friction.cjs +407 -7
- package/packages/droid/commands/remember.md +210 -149
- package/packages/opencode/command/docs-builder/docs-builder.cjs +25 -16
- package/packages/opencode/command/docs-builder.md +43 -20
- package/packages/opencode/command/remember/AGENT_RULES.md +1 -1
- package/packages/opencode/command/remember/friction.cjs +407 -7
- package/packages/opencode/command/remember.md +210 -149
|
@@ -9,7 +9,7 @@ Run friction analysis, then consolidate session stashes + friction antigens into
|
|
|
9
9
|
**Guardrails**
|
|
10
10
|
- Favor straightforward, minimal implementations first and add complexity only when requested or clearly required.
|
|
11
11
|
- Keep changes tightly scoped to the requested outcome.
|
|
12
|
-
- **Precision over recall for hot memory.** A false antigen loaded into `@MEMORY.md` steers every future session. When unsure,
|
|
12
|
+
- **Precision over recall for hot memory.** A false antigen loaded into `@MEMORY.md` steers every future session. When unsure, do not promote — leave it to recurrence (a ledger `observing` entry at 2 sessions, nothing at 1).
|
|
13
13
|
- **Mid-tier model, not hardcoded.** Steps 2/3/4a delegate to a mid-tier model — capable of
|
|
14
14
|
semantic judgment, cheaper/faster than your top reasoning tier (e.g. Claude's Sonnet vs
|
|
15
15
|
Opus). Use whatever your tool designates as that balanced default; never hardcode a
|
|
@@ -39,7 +39,9 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
39
39
|
(the same directory as `remember.md`, whether installed or run from the package). If it
|
|
40
40
|
exists nowhere, skip to step 1 (stash-only) and tell the user friction.cjs is missing.
|
|
41
41
|
- **Resolve the global sessions root** — probe this list top-to-bottom, use the first that
|
|
42
|
-
exists and contains `.jsonl` files
|
|
42
|
+
exists and contains `.jsonl` files directly, or one level down in per-project
|
|
43
|
+
subdirectories (friction.cjs scans exactly those two levels, not a deep recursive walk).
|
|
44
|
+
**Never prompt the user.**
|
|
43
45
|
```
|
|
44
46
|
# ── Add your own global sessions root at the TOP so it is checked first ──
|
|
45
47
|
~/.claude/projects/ # Claude Code
|
|
@@ -76,15 +78,17 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
76
78
|
already exists, leave it untouched — never overwrite, even if the bundled template
|
|
77
79
|
changes in a later version; it becomes user-owned the moment it lands in the project.
|
|
78
80
|
- Read all `.amp/stash/*.md` files in the current project
|
|
79
|
-
- Read friction output written in step 0: `.amp/remember/friction/antigen_clusters.json` (preferred) or `.amp/remember/friction/antigen_review.md` (fallback)
|
|
81
|
+
- Read friction output written in step 0: `.amp/remember/friction/antigen_clusters.json` (preferred) or `.amp/remember/friction/antigen_review.md` (fallback). On the fallback path, step 4c does NO counting — merge quotes into
|
|
82
|
+
matching entries only; never change `sessions`, `last_seen`, or `recurred_while_hot` (the
|
|
83
|
+
fallback carries no `session_ids`, so identity matching cannot run on it).
|
|
80
84
|
- Read existing `.amp/remember/MEMORY.md` if it exists — create dir if missing
|
|
81
85
|
- Read processed manifest at `.amp/remember/.processed` — skip already-processed stashes
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
- **No unprocessed stashes → skip steps 2-3 (extraction and the Facts rewrite)
|
|
87
|
+
entirely — facts are never rewritten with zero new input**, not even to clear existing
|
|
88
|
+
length-gate debt on `.amp/remember/MEMORY.md`. Steps 4-5 (friction → ledger count →
|
|
89
|
+
Antigens render) are stash-independent and still run whenever friction produced output
|
|
90
|
+
(see step 4's own guard). If there is also no friction
|
|
91
|
+
output, report "nothing to consolidate" and stop after step 1.
|
|
88
92
|
|
|
89
93
|
2. **Extract from unprocessed stashes** (up to 5 stashes per agent, as few agents as possible — see Guardrails)
|
|
90
94
|
- Each agent reads its batch of stashes together and calls the mid-tier model (see Guardrails) to extract:
|
|
@@ -123,10 +127,14 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
123
127
|
history — never exempted. Only a draft that passes the gate (or has its overruns
|
|
124
128
|
exempted under the 100-char backtick rule) is written to `.amp/remember/MEMORY.md`.
|
|
125
129
|
- **Episodes section**: append new episode entries, keep only the **10 most recent**.
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
**Dedup before appending**: if a new episode covers the same work as one already in the
|
|
131
|
+
section (same goal or same session's work under different wording — judge by content, not
|
|
132
|
+
title), merge the new detail into the existing entry instead of appending a second copy.
|
|
133
|
+
Re-processing a stash whose episode is already filed must not create a near-duplicate
|
|
134
|
+
pair. Every older episode is **folded, then deleted**: its lesson becomes a fact (handed
|
|
135
|
+
to the rewrite above); the narrative is removed. No archive — git has the history.
|
|
128
136
|
- **Antigens section**: only update from friction output (step 4)
|
|
129
|
-
- Write merged result to `.amp/remember/MEMORY.md` in the format under step
|
|
137
|
+
- Write merged result to `.amp/remember/MEMORY.md` in the format under step 5.
|
|
130
138
|
|
|
131
139
|
4. **Distill friction into antigens** (only if friction output exists)
|
|
132
140
|
|
|
@@ -141,48 +149,72 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
141
149
|
> logs.** Friction's lexical grouping and flags are hints, not the verdict.
|
|
142
150
|
|
|
143
151
|
- Read `.amp/remember/friction/antigen_clusters.json`.
|
|
144
|
-
- **4a. Classify
|
|
145
|
-
Call the mid-tier model
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
- **4a. Classify** (the LLM classifies only — no merging, no arithmetic; counting
|
|
153
|
+
and rendering are mechanical, see 4c and step 5). Call the mid-tier model once per
|
|
154
|
+
cluster batch with each cluster's `contexts`, `preceding`, `errors`,
|
|
155
|
+
`self_suspect`, `projects`, `sessions`, `top_keywords`, and the ledger's existing
|
|
156
|
+
entries (`id`, `class_hints`, `rule`, `evidence.quotes`). For EACH cluster, output
|
|
157
|
+
exactly one label — nothing else:
|
|
158
|
+
- `drop` — self-directed correction, agent's own prose captured as context, or a
|
|
159
|
+
real reaction too short/ambiguous to name a specific mistake (`self_suspect` and
|
|
160
|
+
an empty `preceding` — no agent action — are strong self-directed cues). Don't
|
|
161
|
+
force a match on one overlapping word.
|
|
162
|
+
- an existing ledger id (`ag-NNN`) — only if the cluster is narrowly the SAME
|
|
163
|
+
mistake class as that entry's `class_hints`+`rule`+`evidence.quotes`, not just
|
|
164
|
+
similar sentiment. State the entry's specific claim precisely in the prompt (a
|
|
165
|
+
generic one-liner rule is not enough to bound the match — see 4c Open item 2) and
|
|
166
|
+
give the classifier a negative example, not just the positive claim, e.g. for
|
|
167
|
+
ag-001 (validate, don't assert): "did you test it?" matches; "we're burning money,
|
|
168
|
+
why is it failing?" does NOT — cost/outcome complaints are not validation claims.
|
|
169
|
+
- `new:<theme>` — a real, agent-directed mistake matching no existing entry.
|
|
170
|
+
`<theme>` is NOT freeform LLM prose: derive it mechanically from the cluster's
|
|
171
|
+
own `top_keywords[0]` and `top_keywords[1]` (lowercase, hyphen-joined). This
|
|
172
|
+
alone raised measured 5-run exact-label agreement from 0.884 to ~0.97-0.99 by
|
|
173
|
+
removing wording variance as a source of disagreement — the remaining variance
|
|
174
|
+
is genuine classification disagreement (drop vs. new:, or which existing id),
|
|
175
|
+
not paraphrase noise. Also output a `rule`: one line stating the behavioral
|
|
176
|
+
rule this cluster's evidence supports, same do/don't imperative style as an
|
|
177
|
+
existing ledger entry's `rule` (e.g. "Never say work is validated... without an
|
|
178
|
+
actual run behind it"). This is the only LLM-authored field here — `<theme>`
|
|
179
|
+
naming stays mechanical. `friction.cjs count` requires it whenever the cluster's
|
|
180
|
+
own `sessions >= 2` (it will create a ledger entry); below that it's unused.
|
|
181
|
+
|
|
182
|
+
Output is `{cluster_index: label}` for `drop`/`ag-NNN`; for `new:<theme>`, output
|
|
183
|
+
`{cluster_index: {label: "new:<theme>", rule: "<one-line rule>"}}`.
|
|
184
|
+
- **4b. Route + tier by recurrence.** For each cluster and each same-label group
|
|
185
|
+
(the clusters 4a gave the same label) — its tier comes from the distinct-
|
|
186
|
+
conversation count `friction.cjs count` (4c) computes for it, the union of the
|
|
187
|
+
group's hashes deduped against the ledger:
|
|
188
|
+
- `suggested_artifact: antigen` (recurring + severe) or a same-label group → an
|
|
166
189
|
**antigen** (a "do/don't" behavioral rule), with its verbatim evidence quotes.
|
|
167
190
|
- `suggested_artifact: fact` (recurring + mild) → a **Fact**.
|
|
168
|
-
- `suggested_artifact: episode` that did **not**
|
|
169
|
-
|
|
191
|
+
- `suggested_artifact: episode` that did **not** land in a recurring group → **not
|
|
192
|
+
an Episode, and at 1 session not written anywhere.** The Episodes section is stash-fed
|
|
193
|
+
and capped at 10; friction's one-offs are cross-project and arrive by the dozen, so
|
|
194
|
+
filing them there would flush the stash episodes. Nothing is lost: friction re-scans
|
|
195
|
+
every session log on every run, so the cluster re-surfaces until it recurs — and at 2
|
|
196
|
+
sessions it gets its home, a ledger `observing` entry (4c), which step 5 renders under
|
|
197
|
+
Low Confidence. `suggested_artifact` is friction's structural proposal, not a filing
|
|
198
|
+
decision; the filing rule is this list.
|
|
170
199
|
- Confidence by distinct-session recurrence:
|
|
171
200
|
- **High** (5+ sessions) → loaded hot via `@MEMORY.md`
|
|
172
201
|
- **Medium** (3-4 sessions) → recorded under Antigens, *not* loaded hot
|
|
173
|
-
- **Low** (<3 sessions) →
|
|
174
|
-
- **Recurrence tiers bind everything, including
|
|
175
|
-
evidence, it never elevates it — a
|
|
176
|
-
distinct-session count (e.g. a 2-session
|
|
177
|
-
`observing
|
|
202
|
+
- **Low** (<3 sessions) → ledger `observing` only at 2 sessions; nothing at 1
|
|
203
|
+
- **Recurrence tiers bind everything, including same-label groups:** grouping consolidates
|
|
204
|
+
evidence, it never elevates it — a same-label group's tier comes from its combined
|
|
205
|
+
distinct-session count (e.g. a 2-session same-label group is still Low → ledger
|
|
206
|
+
`observing` only, not an antigen entry in MEMORY.md).
|
|
178
207
|
- **Never auto-promote.** Only High-confidence (5+ sessions) antigens load hot. A
|
|
179
|
-
single dramatic correction is
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
208
|
+
single dramatic correction is recorded nowhere yet, not an antigen.
|
|
209
|
+
- **4c. Count** (`.amp/remember/ledger.json`, the evidence trail linking each rule
|
|
210
|
+
to the mistake it targets and whether it is working — replaces the old evidence-merge
|
|
211
|
+
arithmetic; a script now does every count, not the LLM). **This is a literal command
|
|
212
|
+
you run, not a description you reason from.** The LLM's job ended at 4a — do not
|
|
213
|
+
hand-compute session counts, do not decide by inspection which entries changed, even
|
|
214
|
+
if you are confident you can do it correctly. Create `ledger.json` as `{"version": 1,
|
|
215
|
+
"entries": []}` if missing. It is JSON for exact matching — bookkeeping only, never
|
|
216
|
+
injected into context as guidance (MEMORY.md is what gets read; the ledger is what
|
|
217
|
+
gets checked).
|
|
186
218
|
|
|
187
219
|
Entry shape:
|
|
188
220
|
```json
|
|
@@ -196,96 +228,90 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
196
228
|
"history": [{ "date": "YYYY-MM-DD", "event": "<transition>" }] }
|
|
197
229
|
```
|
|
198
230
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
- **
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- If 2 attempts have already failed and the antigen persists → `status: "escalated"`:
|
|
284
|
-
remove the rule from MEMORY.md's hot section, record a Fact instead ("persistent
|
|
285
|
-
failure mode: <class> — no phrasing reduces it"), and flag it in the step-7 report.
|
|
286
|
-
**Flag, don't act** — the user decides: enforcement (a hook, where the tool has
|
|
287
|
-
them) or accepted limit.
|
|
288
|
-
- **Match, `escalated`/`rejected`** (rejected = user veto) → merge evidence only; never re-propose.
|
|
231
|
+
Immediately after 4a produces `labels.json`, run these exact commands as real shell
|
|
232
|
+
invocations, in order. First:
|
|
233
|
+
```bash
|
|
234
|
+
node <path-to-friction.cjs> migrate-attempts .amp/remember/ledger.json .amp/remember/ledger.json
|
|
235
|
+
```
|
|
236
|
+
This records any hand-drifted `rule` text as a new attempt so I7 (`rule` == the last
|
|
237
|
+
attempt's `rule`) holds before counting runs; it is a no-op on an already-consistent
|
|
238
|
+
ledger, so always run it regardless of whether drift is suspected. Then:
|
|
239
|
+
```bash
|
|
240
|
+
node <path-to-friction.cjs> count <labels.json> <ledger.json> <clusters.json> <today's-date> <ledger.json>.new .amp/remember/friction/count_report.json
|
|
241
|
+
```
|
|
242
|
+
Then **overwrite `ledger.json` with `<ledger.json>.new`'s contents** (e.g. `mv
|
|
243
|
+
ledger.json.new ledger.json`). Do not stop after `labels.json` — producing labels is
|
|
244
|
+
4a, not the deliverable of this step. `friction.cjs count <labels.json> <ledger.json>
|
|
245
|
+
[clusters.json] [runDate] [outLedgerPath] [reportPath]` is deterministic, no LLM
|
|
246
|
+
involved, so it can't drift between repos or runs; the count report is also written to
|
|
247
|
+
`.amp/remember/friction/count_report.json` for step 8 to read back. It implements,
|
|
248
|
+
mechanically, everything the old
|
|
249
|
+
prose reasoning here used to require by hand:
|
|
250
|
+
|
|
251
|
+
- **Session identity** — the trailing 8-char hash of a session id (stable across
|
|
252
|
+
project-label renames) — with the same fork/resume canonicalization friction.cjs
|
|
253
|
+
already applies before clusters are emitted (sessions sharing >=1 message `uuid`
|
|
254
|
+
collapse to one canonical id; do not re-derive fork identity yourself).
|
|
255
|
+
- **Migration (one-time, grandfathered): SEED, DO NOT COUNT** — an entry whose
|
|
256
|
+
`session_ids` is empty going in AND carries no prior "identity migration" history
|
|
257
|
+
line has that line's first match seed `session_ids` from this run's matches WITHOUT
|
|
258
|
+
incrementing `sessions`/`last_seen`/`recurred_while_hot`. The **migration-fill
|
|
259
|
+
sub-case** — `session_ids` still empty but an "identity migration" line already
|
|
260
|
+
exists — fills `session_ids` on the first post-migration match, still without
|
|
261
|
+
incrementing; counting resumes only once `session_ids` is non-empty.
|
|
262
|
+
- **Counting is per CLUSTER INDEX, never per hash and never once per label-group.**
|
|
263
|
+
For each cluster index in a matched group: if none of that cluster's own hashes are
|
|
264
|
+
already in the entry's stored set, it is one genuinely new conversation — `sessions`
|
|
265
|
+
+= 1 (never by hash count), `last_seen` refreshed, and — if the entry is `hot` and
|
|
266
|
+
the session's own date is on/after the current attempt's `adopted` date (the
|
|
267
|
+
**adopted-date gate**: a mistake that predates the rule's current phrasing isn't a
|
|
268
|
+
phrasing failure of it) — `recurred_while_hot` += 1. A gated-out (predates-adopted)
|
|
269
|
+
new conversation still counts as evidence (sessions, hash) but not toward
|
|
270
|
+
`recurred_while_hot`. A hash already present is a re-scan of an already-counted
|
|
271
|
+
conversation: only missing alias hashes are added, nothing else changes.
|
|
272
|
+
- Promotes `observing`→`hot` at `sessions >= 5` (a fresh ledger on a project with
|
|
273
|
+
mature global evidence can hit the hot case on its very first run: new entry born
|
|
274
|
+
`hot` directly if `sessions >= 5` on arrival).
|
|
275
|
+
- **No match, cluster `sessions` == 1** → writes nothing. The ledger tracks
|
|
276
|
+
recurrence; a single occurrence has none to track yet. Friction re-scans every
|
|
277
|
+
session log every run, so a later run matches it back to 2+ sessions and seeds it
|
|
278
|
+
then — this does not change matching against an EXISTING entry, which is recurrence
|
|
279
|
+
regardless of the matching cluster's own session count.
|
|
280
|
+
- For `new:<theme>` groups with no ledger match: distinct conversations = distinct
|
|
281
|
+
cluster indices in the group (within one classify batch, no two cluster indices
|
|
282
|
+
share a session hash). `sessions < 2` → writes nothing. `sessions >= 2` → new entry,
|
|
283
|
+
`status` follows the same >=5-hot / else-observing rule.
|
|
284
|
+
|
|
285
|
+
**Open item 1 — `new:` label collisions: resolved (Guard B).** `new:` clusters never
|
|
286
|
+
merge in-batch, regardless of whether two cluster indices share the same `new:` string
|
|
287
|
+
— each `new:` cluster with `sessions >= 2` creates its own ledger entry, and a genuine
|
|
288
|
+
recurrence of the same new mistake is matched on a later run by `class_hints`, like any
|
|
289
|
+
other entry. Measured against the alternative (merge same-labeled `new:` clusters when
|
|
290
|
+
their `top_keywords` overlap by >=1): a synthetic new entry was correctly re-matched by
|
|
291
|
+
a fresh classifier on 5/5 runs under Guard B, while the keyword-overlap guard wrongly
|
|
292
|
+
merged two real, distinct mistakes on real data (clusters 21/23 — unrelated mistakes
|
|
293
|
+
sharing the generic keyword "fucking validate"). `friction.cjs count` implements Guard
|
|
294
|
+
B: every `new:`-labeled cluster stands alone.
|
|
295
|
+
|
|
296
|
+
**Open item 2 — a generic one-line `rule` under-specifies the class for matching:**
|
|
297
|
+
the ledger's `class_hints`+`rule` alone can be too broad for the LLM classifier (4a)
|
|
298
|
+
to reliably tell two different mistakes apart, as the "fucking validate" false-merge
|
|
299
|
+
case above shows. Consider requiring a short negative example ("NOT X, even though it
|
|
300
|
+
sounds similar") on ledger entries whose `class_hints` are single generic words/phrases.
|
|
301
|
+
|
|
302
|
+
**After `friction.cjs count` returns**, resume the parts it does not do:
|
|
303
|
+
- **Escalation.** For any `hot` entry the count run left with `recurred_while_hot >=
|
|
304
|
+
2`: the phrasing failed. Mark the current attempt `outcome: "failed"`, draft
|
|
305
|
+
attempt n+1 — it must differ from **every** prior attempt's text in this entry
|
|
306
|
+
(failed attempts are the rejected-edit buffer: never re-propose one verbatim) —
|
|
307
|
+
replace `rule`, update MEMORY.md's Antigens section (step 5), reset
|
|
308
|
+
`recurred_while_hot` to 0. If 2 attempts have already failed and the antigen
|
|
309
|
+
persists → `status: "escalated"`: remove the rule from MEMORY.md's hot section,
|
|
310
|
+
record a Fact instead ("persistent failure mode: <class> — no phrasing reduces
|
|
311
|
+
it"), and flag it in the step-8 report. **Flag, don't act** — the user decides:
|
|
312
|
+
enforcement (a hook, where the tool has them) or accepted limit.
|
|
313
|
+
- **`escalated`/`rejected` entries** (rejected = user veto) never get a new attempt
|
|
314
|
+
proposed, count run or not.
|
|
289
315
|
|
|
290
316
|
**Decay (observing only).** Antigens are the fastest-decaying artifact and, until now,
|
|
291
317
|
had no exit. This is meaningful *because* of the identity fix above — without it,
|
|
@@ -343,19 +369,44 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
343
369
|
### YYYY-MM-DD - [title]
|
|
344
370
|
- [bullet narrative]
|
|
345
371
|
|
|
372
|
+
## Antigens
|
|
373
|
+
[rendered — see below, do not hand-write]
|
|
374
|
+
```
|
|
375
|
+
**The `## Antigens` section is rendered, not hand-written — run this too, as a literal
|
|
376
|
+
command, do not hand-write it even to match the format shown above.** After 4c has
|
|
377
|
+
overwritten `ledger.json`, run:
|
|
378
|
+
```bash
|
|
379
|
+
node <path-to-friction.cjs> render <ledger.json>
|
|
380
|
+
```
|
|
381
|
+
Take that command's stdout **verbatim** and replace MEMORY.md's entire `## Antigens`
|
|
382
|
+
section with it (from the `## Antigens` line up to, but not including, the next `## `
|
|
383
|
+
heading, or to end of file if Antigens is the last section — it usually is). This step's
|
|
384
|
+
output IS the correctness check for itself: after replacing, `node
|
|
385
|
+
<path-to-friction.cjs> check <ledger.json> <MEMORY.md>` must report `I6-new: EQUAL` — if
|
|
386
|
+
it doesn't, something was edited by hand instead of pasted from the script's stdout; redo
|
|
387
|
+
it from the script's stdout exactly, never patch MEMORY.md manually to make it match.
|
|
388
|
+
|
|
389
|
+
`friction.cjs render` prints the section byte-for-byte, no LLM paraphrase, no manual
|
|
390
|
+
template filling:
|
|
391
|
+
```
|
|
346
392
|
## Antigens
|
|
347
393
|
### High Confidence (loaded — applies every session)
|
|
348
|
-
- [behavioral rule] (evidence: [N] sessions — "[
|
|
394
|
+
- [behavioral rule] (evidence: [N] sessions, [P] projects — "[quote1]", "[quote2]") — ag-NNN
|
|
349
395
|
|
|
350
396
|
### Medium Confidence (observing — not loaded)
|
|
351
|
-
- [behavioral rule] (evidence: [N] sessions)
|
|
397
|
+
- [behavioral rule] (evidence: [N] sessions) — ag-NNN
|
|
352
398
|
|
|
353
399
|
### Low Confidence (needs more data)
|
|
354
|
-
- [pattern] (evidence: [N] sessions)
|
|
400
|
+
- [pattern] (evidence: [N] sessions) — ag-NNN
|
|
355
401
|
```
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
402
|
+
Tiers: High = `status == "hot" && sessions >= 5` (the only tier that prints quotes — the
|
|
403
|
+
first 2 of `evidence.quotes`, verbatim, capped at 2 — and `evidence.projects.length`).
|
|
404
|
+
Medium = `status == "observing" && 3 <= sessions <= 4`. Low = `status == "observing" &&
|
|
405
|
+
sessions == 2`. `expired`/`escalated`/`rejected`/`sessions < 2` never render, same as
|
|
406
|
+
before — an entry marked `expired` by the decay rule (4c) is skipped here even though it
|
|
407
|
+
stays in `ledger.json`. A legacy 1-session `observing` entry (see 4b) stays in the ledger
|
|
408
|
+
and grows or expires like any other, but is not rendered. An empty tier prints `- (none —
|
|
409
|
+
<why>)`, one line, never an empty section.
|
|
359
410
|
|
|
360
411
|
6. **Update processed manifest**
|
|
361
412
|
- Append paths of newly processed stashes to `.amp/remember/.processed`
|
|
@@ -367,7 +418,9 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
367
418
|
write that already happened in steps 3-6.
|
|
368
419
|
|
|
369
420
|
- **Locate `docs-builder.cjs`** — bundled next to this command at
|
|
370
|
-
`docs-builder/docs-builder.cjs` (same convention as `remember/friction.cjs`).
|
|
421
|
+
`docs-builder/docs-builder.cjs` (same convention as `remember/friction.cjs`). Call it by
|
|
422
|
+
its **absolute path** in the command below — the cwd here is the target repo, not this
|
|
423
|
+
package, so a cwd-relative path fails everywhere except the liteagents repo itself.
|
|
371
424
|
- **Not applicable, stay silent:** if the project has no `docs/` directory, skip without
|
|
372
425
|
saying anything. Most projects have no doc corpus and a nudge every run is noise.
|
|
373
426
|
- **Applicable but could not run — say so, loudly:** if `docs/` exists but the script is
|
|
@@ -382,12 +435,15 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
382
435
|
below.
|
|
383
436
|
- Otherwise run it and pass through its verdict:
|
|
384
437
|
```bash
|
|
385
|
-
node docs-builder
|
|
438
|
+
node <docs-builder.cjs> due
|
|
386
439
|
```
|
|
387
440
|
`due` compares `docs/` against the SHA stamped in `docs/.docs-builder/ledger.json`
|
|
388
441
|
using `git diff --numstat -M`, classifying each doc as new / moved / moved+changed /
|
|
389
442
|
changed (with the line delta and rough percentage) / deleted. It is **due at >=5
|
|
390
443
|
changed docs** — the same derived-not-counted shape as `/stash`'s nudge.
|
|
444
|
+
- If `due` prints "no ledger yet" (no `docs/.docs-builder/ledger.json` to compare against),
|
|
445
|
+
do NOT relay it — print the same `/docs-builder reorg` line as the no-`docs/.docs-builder/`
|
|
446
|
+
case above, for the same reason: `ledger` would stamp an unsorted pile as correct.
|
|
391
447
|
- If DUE, end with one line and nothing more:
|
|
392
448
|
```
|
|
393
449
|
docs: 7 changed since 991f72d3 — run /docs-builder reorg
|
|
@@ -396,7 +452,10 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
396
452
|
8. **Report to user** — print it AND write the same content to `.amp/remember/report.md`
|
|
397
453
|
(overwritten each run; the ledger keeps history — the report is just the latest snapshot)
|
|
398
454
|
- Number of stashes processed
|
|
399
|
-
- Facts count (before → after the rewrite
|
|
455
|
+
- Facts count (before → after the rewrite) plus how many existing lines were merged or
|
|
456
|
+
shortened. At steady state — lines already ≤160, no near-duplicates — a run that grows by
|
|
457
|
+
exactly its new facts and shortens nothing is correct; say so rather than forcing merges
|
|
458
|
+
to hit a number. The bound on facts is the write-bar at entrance, not a count.
|
|
400
459
|
- **Mechanical length check** — run, don't estimate. This confirms the step-3 gate rather
|
|
401
460
|
than being the first check to catch an overrun. It implements the SAME mechanical
|
|
402
461
|
exemption as the gate (a line whose longest backtick literal exceeds 100 chars is not
|
|
@@ -418,7 +477,9 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
418
477
|
— every remaining overrun already had its chance to be exempted (100-char backtick
|
|
419
478
|
literal) inside the step-3 gate, so anything printed here should not exist.
|
|
420
479
|
- Episodes count (new, kept hot, folded + deleted)
|
|
421
|
-
- Antigens count by confidence tier, with how many newly promoted to hot
|
|
480
|
+
- Antigens count by confidence tier, with how many newly promoted to hot — sourced
|
|
481
|
+
from `.amp/remember/friction/count_report.json` (4c's count report), not
|
|
482
|
+
recomputed by hand
|
|
422
483
|
- Ledger lines — one per non-observing entry: id, short rule, status, recurrences since
|
|
423
484
|
adoption. Highlight rephrased (RECURRED) and ESCALATED entries; escalations need a
|
|
424
485
|
user decision, e.g.:
|
|
@@ -435,7 +496,7 @@ Reads all raw material (`.amp/stash/*.md` + `.amp/remember/friction/antigen_clus
|
|
|
435
496
|
- Memory file: `.amp/remember/MEMORY.md` (single source of truth, referenced as `@.amp/remember/MEMORY.md`)
|
|
436
497
|
- Rules template: `.amp/remember/AGENT_RULES.md` (bootstrapped once from the bundled package template on first `/remember` run, never overwritten again — user-owned after that; referenced as `@.amp/remember/AGENT_RULES.md`)
|
|
437
498
|
- Antigen ledger: `.amp/remember/ledger.json` (per-rule evidence trail: class, status, attempts/rejected-buffer, recurrence-while-hot)
|
|
438
|
-
- Consolidation report: `.amp/remember/report.md` (latest step-
|
|
499
|
+
- Consolidation report: `.amp/remember/report.md` (latest step-8 report, overwritten each run)
|
|
439
500
|
- Processed manifest: `.amp/remember/.processed`
|
|
440
501
|
- Docs ledger (READ ONLY from here — owned by `/docs-builder`): `docs/.docs-builder/ledger.json`
|
|
441
502
|
- Friction output (transient, regenerated each run): `.amp/remember/friction/` — `antigen_clusters.json` (preferred input), `antigen_review.md` (fallback), plus raw analysis files
|
|
@@ -761,7 +761,7 @@ function indexFlat() {
|
|
|
761
761
|
|
|
762
762
|
if (!productFiles.length && !logsFiles.length && !archiveFiles.length && !pageFiles.length) {
|
|
763
763
|
console.log('nothing to index — run `discover` + `apply-reorg` first.');
|
|
764
|
-
return;
|
|
764
|
+
return false; // caller (applyReorg) uses this to skip the config pointer — no index.md was written
|
|
765
765
|
}
|
|
766
766
|
|
|
767
767
|
const outRel = process.env.OUT || 'docs/index.md';
|
|
@@ -1748,7 +1748,11 @@ function discover(root) {
|
|
|
1748
1748
|
// writes had persisted. Carry-forward (above) is exactly why a re-run can arrive here with
|
|
1749
1749
|
// buckets already set, so this has to report what is actually in the plan.
|
|
1750
1750
|
const filled = rows.filter(r => r.bucket).length;
|
|
1751
|
-
if (!
|
|
1751
|
+
if (!rows.length) {
|
|
1752
|
+
console.log(`plan written to docs/.docs-builder/reorg-plan.json — 0 rows. Nothing outside `
|
|
1753
|
+
+ 'product/, logs/ and archive/ is left to classify — the corpus is already sorted. '
|
|
1754
|
+
+ '`apply-reorg` will only rescan and rebuild the index.');
|
|
1755
|
+
} else if (!filled) {
|
|
1752
1756
|
console.log(`plan written to docs/.docs-builder/reorg-plan.json — every row's \`suggested\` `
|
|
1753
1757
|
+ 'is a PRIOR, not a verdict, and `bucket` is empty. Run the classification interview '
|
|
1754
1758
|
+ '(docs-builder.md): feed the model the plan, get bucket+reason per row, get the user\'s '
|
|
@@ -1880,7 +1884,7 @@ const DOCS_INDEX_END = '<!-- DOCS_INDEX:END -->';
|
|
|
1880
1884
|
function docsIndexBlock() {
|
|
1881
1885
|
return `${DOCS_INDEX_START}\n`
|
|
1882
1886
|
+ 'Docs map: `docs/index.md` — every doc in this project, with line counts.\n'
|
|
1883
|
-
+ '
|
|
1887
|
+
+ 'Search this corpus instead of reading it whole: `/docs-builder search <query words>`\n'
|
|
1884
1888
|
+ `${DOCS_INDEX_END}`;
|
|
1885
1889
|
}
|
|
1886
1890
|
function injectClaudeMdPointer() {
|
|
@@ -1969,7 +1973,6 @@ function applyReorg(planFile) {
|
|
|
1969
1973
|
// (re)builds outline.json for a corpus that already sat in docs/product/docs/archive/docs/logs
|
|
1970
1974
|
// from a previous run, e.g. after a manual git mv or a re-run with nothing left to do.
|
|
1971
1975
|
scanWholeCorpus();
|
|
1972
|
-
console.log(JSON.stringify(results, null, 1));
|
|
1973
1976
|
if (splitCandidates.length) {
|
|
1974
1977
|
// Ranked, logs last (spec §5): a prereg is a legitimate split target but rarely the best
|
|
1975
1978
|
// NEXT one. Array.prototype.sort is stable in Node, so this only reorders logs to the
|
|
@@ -1982,18 +1985,23 @@ function applyReorg(planFile) {
|
|
|
1982
1985
|
// v3: apply-reorg writes docs/index.md itself — a reorg-only corpus ends up indexed
|
|
1983
1986
|
// without a second command. Runs unconditionally — oversized docs are sorted like anything
|
|
1984
1987
|
// else now, so this was never conditional on them.
|
|
1985
|
-
indexFlat();
|
|
1986
|
-
// Crash-isolated, same spirit as the moveDoc() follow-up failures collected above: a
|
|
1987
|
-
// failure to write the config file is a WARN, never a thrown error — it must not make an
|
|
1988
|
-
// already-moved file look unmoved or fail the run.
|
|
1988
|
+
const indexed = indexFlat() !== false;
|
|
1989
1989
|
const configName = process.env.CONFIG || 'CLAUDE.md';
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1990
|
+
if (!indexed) {
|
|
1991
|
+
console.log(` skipped the ${configName} pointer — no docs/index.md was written`);
|
|
1992
|
+
} else {
|
|
1993
|
+
// Crash-isolated, same spirit as the moveDoc() follow-up failures collected above: a
|
|
1994
|
+
// failure to write the config file is a WARN, never a thrown error — it must not make an
|
|
1995
|
+
// already-moved file look unmoved or fail the run.
|
|
1996
|
+
try {
|
|
1997
|
+
injectClaudeMdPointer();
|
|
1998
|
+
results.claudeMdUpdated = true;
|
|
1999
|
+
console.log(` updated ${configName} with the docs/index.md pointer`);
|
|
2000
|
+
} catch (e) {
|
|
2001
|
+
console.error(` WARN could not update ${configName} with the docs/index.md pointer: ${e.message}`);
|
|
2002
|
+
}
|
|
1996
2003
|
}
|
|
2004
|
+
console.log(JSON.stringify(results, null, 1));
|
|
1997
2005
|
logOp('apply-reorg', `moved ${results.moved}, skipped ${results.skipped}, `
|
|
1998
2006
|
+ `${splitCandidates.length} oversized split candidate(s), `
|
|
1999
2007
|
+ `${results.linksRewritten} link(s) rewritten, ${results.syncFailed} sync failure(s), `
|
|
@@ -2327,7 +2335,7 @@ function cleanupApply(file, outlineF, labelsF) {
|
|
|
2327
2335
|
|
|
2328
2336
|
// Machine state has one home. Callers can override with OUT, but the default must never
|
|
2329
2337
|
// scatter JSON into whatever directory the user happened to be standing in.
|
|
2330
|
-
const ARTIFACTS = 'docs/.docs-builder';
|
|
2338
|
+
const ARTIFACTS = path.join(REPO, 'docs/.docs-builder');
|
|
2331
2339
|
function write(obj, fallback) {
|
|
2332
2340
|
const dest = process.env.OUT || path.join(ARTIFACTS, fallback);
|
|
2333
2341
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
@@ -2352,7 +2360,7 @@ switch (cmd) {
|
|
|
2352
2360
|
case 'cleanup-apply': cleanupApply(rest[0], rest[1], rest[2]); break;
|
|
2353
2361
|
default:
|
|
2354
2362
|
die('usage: docs-builder.cjs <scan|validate|plan|index-flat|search|archive|ledger|due|lint|'
|
|
2355
|
-
+ 'discover|apply-reorg|reorg|cleanup> [args]\n'
|
|
2363
|
+
+ 'discover|apply-reorg|reorg|cleanup|cleanup-apply> [args]\n'
|
|
2356
2364
|
+ ' scan <file.md...> -> outline.json\n'
|
|
2357
2365
|
+ ' validate <outline.json> <labels.json> -> PASS/FAIL (exit 1 on FAIL)\n'
|
|
2358
2366
|
+ ' plan <outline.json> <labels.json> -> task-<theme>.json per page\n'
|
|
@@ -2368,6 +2376,7 @@ switch (cmd) {
|
|
|
2368
2376
|
+ 'drift summary if a ledger stamp exists (the single front door)\n'
|
|
2369
2377
|
+ ' cleanup <file.md> -> ONE named file: cost estimate, then scan\n'
|
|
2370
2378
|
+ ' (the ONLY entry point to the split pipeline)\n'
|
|
2379
|
+
+ ' cleanup-apply <file.md> [outline] [labels] -> plan + pages + archive, after the cleanup interview\n'
|
|
2371
2380
|
+ 'env: REPO (default cwd), OUT (output path), INDEX (default docs/index.md), '
|
|
2372
2381
|
+ 'PAGES (default docs/wiki), TASKS (default docs/.docs-builder/tasks), '
|
|
2373
2382
|
+ 'N (search result count, default 10), OVERSIZED_LINES (default 500)');
|