From 12a2c1fd74b79f5f6f56dc78e4eea95c38f19107 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Thu, 27 Aug 2026 23:50:08 +0200 Subject: [PATCH 1/8] Catch falsified memories in the audit; extract execution mechanics - Add git triage for zero-hit symbols (shipped-then-removed vs branch-only vs never existed) and a DROP category for memories the code refutes - Require checking whether source comments or auto-loaded instructions already carry a memory before KEEP - Move edit-ordering and delegation rules to references/execution.md; make all examples language-agnostic --- skills/memory-audit/SKILL.md | 43 +++++++++++++++++++-- skills/memory-audit/references/execution.md | 38 ++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 skills/memory-audit/references/execution.md diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index c7aa983..7d966ef 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -85,6 +85,7 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - Can a future session **act on** this memory to avoid a mistake or follow a convention? Or is it purely descriptive/documentary with no clear "do this, not that" takeaway? - **Fact-check ≠ actionability.** A claim being *true* and *project-specific* is not enough. Many memories pass A.1 (real anchors) and C.4 (claims still verifiable) but still fail this one — historical records, shipped naming decisions, one-time bug fixes whose fix is self-evident in the code. Apply both passes; do not conflate them. - **Bias check.** If you find yourself defending KEEP with "it's project-specific and still accurate" without identifying the *behavior change* it drives, that's the leniency trap. KEEP requires a positive answer to the forcing-function test, not just absence of a reason to drop. +- **Something else may already be carrying it.** The code, a comment the fix left behind, or the auto-loaded instructions each dissolve a KEEP on their own. Check all three before answering yes — mechanics in C.4. ### Group C — Audit-only mechanics @@ -108,22 +109,46 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - **Verify key claims against the codebase.** If a memory says "we use pattern X in module Y," search the code to confirm that pattern still exists. - Use `Grep` to check for symbol names, type names, or patterns referenced in the memory. - Use `Glob` to verify that referenced files or modules still exist. -- If a memory describes a convention (e.g., "all repositories conform to protocol X"), spot-check a few cases to confirm it holds. +- If a memory describes a convention (e.g., "all data-access modules implement interface X"), spot-check a few cases to confirm it holds. - Do not audit every single line — focus on the **central claim** of the memory. If the core assertion is wrong, recommend DROP or UPDATE. +**A symbol with zero hits is not one finding — it is three, with three different verdicts.** Never stop at "the grep came back empty." Resolve which case you are in first: + +```bash +git log --all --oneline -S '' # does it exist on ANY ref? +git merge-base --is-ancestor # ...and is that ref merged? +git branch -a --contains # ...if not, which branch holds it? +``` + +| Finding | Verdict | +|---|---| +| Commits exist and `--is-ancestor` succeeds → it shipped, then was removed | **DROP** (cat D) — the code is gone; the memory is a historical record | +| Commits exist but `--is-ancestor` fails → the code is on an unmerged branch | **UPDATE**, not DROP. Add a status header naming the branch and stating the default branch's *current* values, so the memory is useful either way and self-corrects when the branch merges | +| No commits on any ref → the API never existed here | **DROP** — see DROP category I | + +`--all` only sees refs present in this clone, so **run `git fetch --all` first**: an unfetched or never-fetched branch otherwise reads as "never existed," which is the one verdict here that destroys a still-valid memory. + +**Check what already carries the memory's content (B.1).** Two carriers are easy to miss: +- **A comment the fix left in the source.** A landed fix often left a doc comment or inline note saying the same thing — read the cited file, don't just grep for the symbol. +- **The auto-loaded instructions** — `CLAUDE.md` / `AGENTS.md`, a lint config, an installed skill. A rule written into those no longer changes behavior on its own. Grep them before deciding, and check the lint rule's `severity:` — a `warning` does not block a PR, so "enforced mechanically" may be false, which flips the verdict from DROP back to UPDATE. + +Neither carrier can hold the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. When the mechanism is redundant but the wrong turn isn't, trim to that warning instead of dropping the file. + #### C.5 Staleness Signals -- **Line number references** — e.g., `lines 266-296` or `FileName.swift:142`. These break after any edit. Recommend UPDATE to replace with symbol names. +- **Line number references** — e.g., `lines 266-296` or `:142`. These break after any edit. Recommend UPDATE to replace with symbol names. - **Deep file paths** — full nested paths are fragile. Recommend UPDATE to use module-level references unless the path is stable and well-known. - **Transient details** — feature flag names being removed, in-progress PR numbers, temporary workarounds with known expiry. - References to features or files that may have been removed or heavily refactored. - **Broken `Related:` links** — an entry in the memory's `Related:` section that points at a memory filename no longer present (DROP'd or renamed during a previous audit). Recommend UPDATE to fix the link to its new name or remove the entry. +- **A stated open item that has since shipped** — a section headed "Open", "Still open", "Known TODO", "remaining item for the wiring phase", or an "Open Design Decisions" list. Check each entry against the code. A resolved item still listed as open is worse than an absent memory: it reads as pending work and invites someone to redo it. Recommend UPDATE to delete the entry (and note the resolution if it is non-obvious). +- **A transcribed per-instance value** — a constant, enum case, or per-call-site setting copied into the memory. These rot fastest of all, and one that has already been corrected once will usually be wrong again. Recommend UPDATE to keep only the *rule* that makes the value matter, pointing at the source to read the value itself. - Old dates without timeless content — treat as a signal for closer scrutiny, not an automatic DROP. --- ## DROP Categories — recurring patterns that should not need user pushback -The categories below are the recurring concrete shapes of B.1 (forcing-function) failure. When a memory matches one, the analysis is already done — call DROP without hedging. None of these are "in doubt" cases. +Categories A–H are the recurring concrete shapes of B.1 (forcing-function) failure. Category I is different in kind — a C.4 falsification, where the memory *would* change behavior, wrongly. When a memory matches one, the analysis is already done — call DROP without hedging. None of these are "in doubt" cases. ### A. Self-marked superseded / deferred / abandoned - The memory itself says **SUPERSEDED**, **deferred indefinitely**, **closed without implementation**, **path abandoned**, or points at another memory as the current decision. @@ -138,7 +163,7 @@ The categories below are the recurring concrete shapes of B.1 (forcing-function) - Keep only when the rule has *no* enforcer (no lint, no formatter, no compiler check) and the codebase actually depends on humans following it. ### D. One-time bug fixes whose fix is self-evident in the code now -- "Bug X used `dropFirst()`; we changed to `where index != firstIndex`." The fix is a 2-line diff, the code reads correctly today. A future regressor would not consult the memory; the existing code is the documentation. +- "Bug X skipped the first element instead of the matching one; we changed the filter to compare identity." The fix is a two-line diff and the code reads correctly today. A future regressor would not consult the memory; the existing code is the documentation. - Keep only when the bug class is *recurring* (same pattern in multiple places, or a footgun future code might re-introduce) and the memory teaches the *avoidance pattern*, not the one fix. ### E. Generic engineering wisdom dressed up with one project example @@ -156,6 +181,14 @@ The categories below are the recurring concrete shapes of B.1 (forcing-function) ### H. Tiny / narrow learnings whose scope is fully covered by a sibling memory - A 30-line learning that captures one facet of a 200-line learning next to it. Cross-reference and DROP the smaller one, or merge. +### I. Confidently wrong about what ships — **flag this one loudly** +Not a staleness problem: the memory is false. A session that trusts it will delete working code or hunt an API that isn't there. Two shapes: + +- **Contradicts the code.** A prohibition or settled fact the current code refutes — *"X was tried and removed, do not re-add it"* while X is live with an active consumer; *"this must stay a synchronous call"* where the shipped code is async; a warning about a gating hole since closed. +- **Prescribes an API that exists on no ref.** A helper, hook, or parameter in the present tense — *"hiding has to be a caller-implemented hook `setFooHidden()`"* — that appears in no file and in no commit (C.4, after a fetch). The intended design was recorded as though it had shipped, and there is no branch to point at, so a reader cannot discover the memory is fiction. Worst when it says "reuse this instead of building your own" — that actively blocks the correct action. + +Do not file either as a routine drop: call it out in the verdict table with what the code actually does now. These memories often pair a real finding with a wrong one, so salvage any *verified observation* into a sibling before deleting — and where the memory has inbound links, replace them with the corrected fact rather than merely unlinking (see `references/execution.md`). + --- ## Audit Workflow @@ -234,6 +267,8 @@ Run only after Step 3 has produced an explicit approval (or per-item decisions) Report what was done after each batch. +Read [references/execution.md](references/execution.md) before the first batch's edits land. It covers link-repair ordering, what to verify in the parts you KEEP, and the rules for delegating batches to subagents — each one a way a real audit has damaged the KB it was cleaning. + ### Step 5: Summary After all batches are processed, present a final summary: diff --git a/skills/memory-audit/references/execution.md b/skills/memory-audit/references/execution.md new file mode 100644 index 0000000..039bca2 --- /dev/null +++ b/skills/memory-audit/references/execution.md @@ -0,0 +1,38 @@ +# Execution Mechanics + +Ordering and technique rules for Step 4, once a batch has been approved. Each one corresponds to a way +a real audit has corrupted the KB it was cleaning. Add new lessons here rather than to the criteria +sections of `SKILL.md` — the criteria are the skill's spine and stay short enough to read as criteria. + +## Applying the edits + +1. **Repair inbound links *before* deleting, and rename *before* repointing.** Find referrers with + `grep -rl '' ` first. A rename invalidates any link you just wrote at + the old name, so when a batch contains both a rename and a repoint, do the rename first. The KB + should be consistent after every step, not only at the end. + +2. **Replace a dropped memory's inbound pointer with the corrected fact inline** — don't just delete + the bullet. The referrer then carries what the code actually does, which is strictly more useful + than the dead pointer was, and it preserves the verified half of the memory being removed. + +3. **Verify the symbols in the parts you KEEP, not just the parts you cut.** When trimming a memory it + is natural to fact-check the claims being deleted and trust the ones being carried forward — which + is how an audit propagates a reference to a symbol that never existed. + +4. **Close with a KB-wide link scan, and confirm a clean result a second way.** Walk every memory — + not just the touched files — and assert each `learning_*` / `decision_*` reference resolves to an + existing file. A scan can report a false clean (a pipeline that swallows its input, a wrong cwd), + so when "0 problems" comes back suspiciously easily, re-run it by a different method before + trusting it. + +## If you delegate the legwork + +Fanning batches out to subagents for reading and fact-checking works well, with one caveat. Agents are +reliable on "does this file/symbol exist" and on code they have actually opened, and **unreliable on +negative claims at scale** — "X appears nowhere", "only two call sites remain", "that snippet is +fabricated". Such claims were wrong repeatedly in practice, and they skewed consistently toward +deleting more, which compounds a skill that already leans DROP-aggressive. + +So: **the orchestrator re-verifies every negative claim that drives a DROP**, with a command whose +output it saw. Require agents to state exactly what they ran, and to say PARTIAL rather than guess. +Verdicts remain the orchestrator's; agents supply evidence. From 793ae2deb01bf0c40212edc7c9b2cecbfe65ce95 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Thu, 27 Aug 2026 23:58:30 +0200 Subject: [PATCH 2/8] Make the audit's symbol triage git- and storage-agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Exclude the memories dir and print changed filenames: -S counts prose, so a memory naming a fictional API was confirming it as real code history - Gate the triage on the project being a git repo, and never DROP on an unverifiable symbol - Forbid committing memory changes — a KB may live in the project repo, a separate repo, or only on disk --- skills/memory-audit/SKILL.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 7d966ef..9bf277e 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -41,7 +41,7 @@ The audit enforces these rules through the criteria below — see Group A. When a memory genuinely applies to multiple projects, list them comma-separated (e.g. `**Applies to:** web-dashboard, ios-app, api-backend`); the content must stay true in every listed project. When a memory is only partially relevant to one listed project, split it into separate memories instead of mixing. -**Audit-time usage.** Audit only the memories in the current checkout's `.claude/memories/` and fact-check against this project only. When deciding whether a memory targets *this* project, compare its `Applies to:` against the **git repo name**, not the directory basename — a folder rename does not change the project. Never DROP a memory solely because its `Applies to:` lists other projects. If the KB is centralized across projects via a separate mechanism, that mechanism owns its own audit — this skill does not reach across repos. +**Audit-time usage.** Audit only the memories in this project's `.claude/memories/` and fact-check against this project only. When deciding whether a memory targets *this* project, compare its `Applies to:` against the **git repo name**, not the directory basename — a folder rename does not change the project. Never DROP a memory solely because its `Applies to:` lists other projects. If the KB is centralized across projects via a separate mechanism, that mechanism owns its own audit — this skill does not reach across repos. --- @@ -112,21 +112,24 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - If a memory describes a convention (e.g., "all data-access modules implement interface X"), spot-check a few cases to confirm it holds. - Do not audit every single line — focus on the **central claim** of the memory. If the core assertion is wrong, recommend DROP or UPDATE. -**A symbol with zero hits is not one finding — it is three, with three different verdicts.** Never stop at "the grep came back empty." Resolve which case you are in first: +**A symbol with zero hits is not one finding — it is three, with three different verdicts.** Never stop at "the grep came back empty." Resolve which case you are in first — but only where the project is a git repo, which is not a given: **check `git rev-parse --git-dir` succeeds before running any of this.** ```bash -git log --all --oneline -S '' # does it exist on ANY ref? -git merge-base --is-ancestor # ...and is that ref merged? +git log --all --oneline --name-only -S '' -- . ':(exclude).claude/memories' +git merge-base --is-ancestor # ...is that ref merged? git branch -a --contains # ...if not, which branch holds it? ``` +The pathspec and `--name-only` are not optional. `-S` counts a string's occurrences in **any** tracked file, prose included — and memories are committed to the project repo unless someone gitignored them or moved them to a separate one. Without the exclusion, a memory naming a fictional API confirms that API as real code history, and the audit reports a *fabricated* rationale ("it shipped, then was removed") into the approval gate. Read the changed filenames before trusting a match: a hit that touches only docs is prose, not code. + | Finding | Verdict | |---|---| -| Commits exist and `--is-ancestor` succeeds → it shipped, then was removed | **DROP** (cat D) — the code is gone; the memory is a historical record | -| Commits exist but `--is-ancestor` fails → the code is on an unmerged branch | **UPDATE**, not DROP. Add a status header naming the branch and stating the default branch's *current* values, so the memory is useful either way and self-corrects when the branch merges | -| No commits on any ref → the API never existed here | **DROP** — see DROP category I | +| Commits touch **source** and `--is-ancestor` succeeds → it shipped, then was removed | **DROP** (cat D) — the code is gone; the memory is a historical record | +| Commits touch source but `--is-ancestor` fails → the code is on an unmerged branch | **UPDATE**, not DROP. Add a status header naming the branch and stating the default branch's *current* values, so the memory is useful either way and self-corrects when the branch merges | +| No commit touches source → the API never existed here | **DROP** — see DROP category I | +| Not a git repo, or the clone can't be trusted (shallow, no remote, fetch failed) | **Never DROP on this basis.** Report the symbol as unverifiable and let the user decide — Step 4's UPDATE-uncertain path | -`--all` only sees refs present in this clone, so **run `git fetch --all` first**: an unfetched or never-fetched branch otherwise reads as "never existed," which is the one verdict here that destroys a still-valid memory. +Where the repo has a remote and the network is reachable, fetch first (`git fetch --all`) — `--all` sees only refs already present, so an unfetched branch reads as "never existed." Skip the fetch when there is no remote, and never read a fetch failure as confirmation. **Check what already carries the memory's content (B.1).** Two carriers are easy to miss: - **A comment the fix left in the source.** A landed fix often left a doc comment or inline note saying the same thing — read the cited file, don't just grep for the symbol. @@ -185,7 +188,7 @@ Categories A–H are the recurring concrete shapes of B.1 (forcing-function) fai Not a staleness problem: the memory is false. A session that trusts it will delete working code or hunt an API that isn't there. Two shapes: - **Contradicts the code.** A prohibition or settled fact the current code refutes — *"X was tried and removed, do not re-add it"* while X is live with an active consumer; *"this must stay a synchronous call"* where the shipped code is async; a warning about a gating hole since closed. -- **Prescribes an API that exists on no ref.** A helper, hook, or parameter in the present tense — *"hiding has to be a caller-implemented hook `setFooHidden()`"* — that appears in no file and in no commit (C.4, after a fetch). The intended design was recorded as though it had shipped, and there is no branch to point at, so a reader cannot discover the memory is fiction. Worst when it says "reuse this instead of building your own" — that actively blocks the correct action. +- **Prescribes an API that exists on no ref.** A helper, hook, or parameter in the present tense — *"hiding has to be a caller-implemented hook `setFooHidden()`"* — that appears in no file and in no commit touching source (C.4 — a mention in prose is not evidence it shipped). The intended design was recorded as though it had shipped, and there is no branch to point at, so a reader cannot discover the memory is fiction. Worst when it says "reuse this instead of building your own" — that actively blocks the correct action. Do not file either as a routine drop: call it out in the verdict table with what the code actually does now. These memories often pair a real finding with a wrong one, so salvage any *verified observation* into a sibling before deleting — and where the memory has inbound links, replace them with the corrected fact rather than merely unlinking (see `references/execution.md`). @@ -265,6 +268,8 @@ Run only after Step 3 has produced an explicit approval (or per-item decisions) - **UPDATE (merge)**: Create the merged file, then delete the originals - **UPDATE (uncertain)**: If the correct replacement isn't obvious (e.g., a referenced symbol was removed and the new equivalent is unclear), ask the user what the updated content should be rather than guessing. +**Stop at the filesystem.** Those file operations are the whole job — never `git add`, commit, or push memory changes. A KB may be tracked in the project's own repo, kept in a separate repo with its own propagation rules, or gitignored and purely local; the audit cannot tell which, and in some setups committing bypasses an approval step. Leave the working tree to the user. + Report what was done after each batch. Read [references/execution.md](references/execution.md) before the first batch's edits land. It covers link-repair ordering, what to verify in the parts you KEEP, and the rules for delegating batches to subagents — each one a way a real audit has damaged the KB it was cleaning. From 651ec3fd83ffcb3917d56b3de6df7cc4e126e0c5 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:02:22 +0200 Subject: [PATCH 3/8] Close two storage-mode gaps in the audit - Forbid pointing git at the memories dir with -C as well as cd: when the KB is a separate repo, the symbol triage would answer about that repo's history instead of the project's - Carve out DROP category B for projects with no version control, where a historical-record memory may be the only trace of the change --- skills/memory-audit/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 9bf277e..1b9a38c 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -160,6 +160,7 @@ Categories A–H are the recurring concrete shapes of B.1 (forcing-function) fai ### B. Pure historical records of shipped one-time changes - Folder renames, file renames, identifier migrations, org migrations *that are done*. Once shipped, `git log` answers "why is this named X?" The memory adds nothing actionable. - Exception: when the historical change still imposes an ongoing constraint future code must honor — then the memory is about the constraint, not the change. +- **This category assumes version control holds the history.** In a project with no git, nothing else records the change and the memory may be its only trace — fall back to judging it on B.1 alone. ### C. Shipped naming or style decisions - "We named the prefix X" / "we kept type Y suffixed" / "we use this enum case style." Once enforced by the type system, lint, or formatter, the decision is in the code. Future sessions read the code, not the memory. @@ -198,7 +199,7 @@ Do not file either as a routine drop: call it out in the verdict table with what > **Per-batch consent is mandatory.** Each batch is its own approval cycle: produce the verdict table, **stop**, wait for the user, apply their decisions, summarize, then — only after that — move on to the next batch. Never chain batches without an explicit go-ahead between them. See Step 3 for the hard-stop rules. -> **Stay at project root.** Do not `cd` into `.claude/memories/` (or any subdirectory) at any point during the audit. Codebase fact-checks (Grep/Glob/Bash) need cwd at the project root — running them from inside `.claude/memories/` resolves patterns against memory files instead of project source, silently passing fact-checks that should fail. Reference memory files by full path (e.g. `.claude/memories/.md`). +> **Stay at project root.** Do not `cd` into `.claude/memories/` (or any subdirectory), and do not point git at it with `-C`, at any stage of the audit. Two separate things break. Codebase fact-checks (Grep/Glob/Bash) need cwd at the project root — run from inside `.claude/memories/` they resolve patterns against memory files instead of project source, silently passing fact-checks that should fail. And where the KB is a checkout of a *separate* repo, that directory is a different git root: the C.4 triage would answer about the memories repo's history instead of the project's, reporting whatever that unrelated history happens to contain with full confidence. Reference memory files by full path (e.g. `.claude/memories/.md`). > **Scope.** Default scope is every memory in `/.claude/memories/`. The user may scope narrower: by category (`learning_*` only), by age (older than N months), or by `Applies to:` (only memories tagging this repo). Honor the requested scope; report the count covered vs. total. From bc2cc314e1f1fedbaafe1c026df7406a0ad251e8 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:07:29 +0200 Subject: [PATCH 4/8] Address review findings on the audit triage and link mechanics - Query the default branch before widening to --all: a bare --all could match both the shipped-then-removed and branch-only rows, leaving the verdict to depend on which commit was tested - Split the inbound-pointer rule by drop reason, since only a falsified memory has a corrected fact worth inlining - Rename via copy-repoint-delete so no step leaves a dangling link; rank instruction carriers by whether they actually fire on the work --- skills/memory-audit/SKILL.md | 21 ++++++++++++------- skills/memory-audit/references/execution.md | 23 ++++++++++++++------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 1b9a38c..604a6ad 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -114,26 +114,33 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act **A symbol with zero hits is not one finding — it is three, with three different verdicts.** Never stop at "the grep came back empty." Resolve which case you are in first — but only where the project is a git repo, which is not a given: **check `git rev-parse --git-dir` succeeds before running any of this.** +Query the default branch first, and widen only if it comes back empty: + ```bash +# 1. Did it ever exist on the default branch? +git log --oneline --name-only -S '' -- . ':(exclude).claude/memories' + +# 2. Only if step 1 is empty — does any other ref carry it? git log --all --oneline --name-only -S '' -- . ':(exclude).claude/memories' -git merge-base --is-ancestor # ...is that ref merged? -git branch -a --contains # ...if not, which branch holds it? +git branch -a --contains # which branch holds it ``` -The pathspec and `--name-only` are not optional. `-S` counts a string's occurrences in **any** tracked file, prose included — and memories are committed to the project repo unless someone gitignored them or moved them to a separate one. Without the exclusion, a memory naming a fictional API confirms that API as real code history, and the audit reports a *fabricated* rationale ("it shipped, then was removed") into the approval gate. Read the changed filenames before trusting a match: a hit that touches only docs is prose, not code. +**The order is what makes the verdicts exclusive.** A bare `--all` can return several pickaxe commits at once — a symbol added and removed on the default branch long ago *and* reintroduced on a live feature branch — and then the first two rows below both match, with the verdict decided by whichever commit you happened to test. Asking the default branch on its own removes that choice. + +The pathspec and `--name-only` are not optional either. `-S` counts a string's occurrences in **any** tracked file, prose included — and memories are committed to the project repo unless someone gitignored them or moved them to a separate one. Without the exclusion, a memory naming a fictional API confirms that API as real code history, and the audit reports a *fabricated* rationale ("it shipped, then was removed") into the approval gate. Read the changed filenames before trusting a match: a hit that touches only docs is prose, not code. | Finding | Verdict | |---|---| -| Commits touch **source** and `--is-ancestor` succeeds → it shipped, then was removed | **DROP** (cat D) — the code is gone; the memory is a historical record | -| Commits touch source but `--is-ancestor` fails → the code is on an unmerged branch | **UPDATE**, not DROP. Add a status header naming the branch and stating the default branch's *current* values, so the memory is useful either way and self-corrects when the branch merges | -| No commit touches source → the API never existed here | **DROP** — see DROP category I | +| Step 1 returns commits touching **source** → it shipped, then was removed | **DROP** (cat D) — the code is gone; the memory is a historical record | +| Step 1 empty, step 2 returns commits touching source → the code is on an unmerged branch | **UPDATE**, not DROP. Add a status header naming the branch and stating the default branch's *current* values, so the memory is useful either way and self-corrects when the branch merges | +| Both steps empty — no commit touches source → the API never existed here | **DROP** — see DROP category I | | Not a git repo, or the clone can't be trusted (shallow, no remote, fetch failed) | **Never DROP on this basis.** Report the symbol as unverifiable and let the user decide — Step 4's UPDATE-uncertain path | Where the repo has a remote and the network is reachable, fetch first (`git fetch --all`) — `--all` sees only refs already present, so an unfetched branch reads as "never existed." Skip the fetch when there is no remote, and never read a fetch failure as confirmation. **Check what already carries the memory's content (B.1).** Two carriers are easy to miss: - **A comment the fix left in the source.** A landed fix often left a doc comment or inline note saying the same thing — read the cited file, don't just grep for the symbol. -- **The auto-loaded instructions** — `CLAUDE.md` / `AGENTS.md`, a lint config, an installed skill. A rule written into those no longer changes behavior on its own. Grep them before deciding, and check the lint rule's `severity:` — a `warning` does not block a PR, so "enforced mechanically" may be false, which flips the verdict from DROP back to UPDATE. +- **Another instruction source — but weigh how reliably each one fires.** `CLAUDE.md` / `AGENTS.md` load every session, so a rule written there does displace the memory. A **lint rule** displaces it only if the rule actually gates: read the `severity:` *and* how CI treats it, since a `warning` blocks under warnings-as-errors or a zero-warning threshold and is advisory otherwise. An **installed skill** loads only when its description matches the task at hand, so it displaces nothing for work that never triggers it. Grep all three, then ask which would actually fire on the work this memory covers — presence is not redundancy. Neither carrier can hold the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. When the mechanism is redundant but the wrong turn isn't, trim to that warning instead of dropping the file. diff --git a/skills/memory-audit/references/execution.md b/skills/memory-audit/references/execution.md index 039bca2..8beb6d3 100644 --- a/skills/memory-audit/references/execution.md +++ b/skills/memory-audit/references/execution.md @@ -6,14 +6,21 @@ sections of `SKILL.md` — the criteria are the skill's spine and stay short eno ## Applying the edits -1. **Repair inbound links *before* deleting, and rename *before* repointing.** Find referrers with - `grep -rl '' ` first. A rename invalidates any link you just wrote at - the old name, so when a batch contains both a rename and a repoint, do the rename first. The KB - should be consistent after every step, not only at the end. - -2. **Replace a dropped memory's inbound pointer with the corrected fact inline** — don't just delete - the bullet. The referrer then carries what the code actually does, which is strictly more useful - than the dead pointer was, and it preserves the verified half of the memory being removed. +1. **Never leave a broken link behind, not even between two steps.** Find the referrers first with + `grep -rl '' `. To rename: copy to the new name, repoint every referrer, + *then* delete the old file. In that order the KB is consistent after each step, so an interrupted + audit leaves a duplicate at worst, never a dangling pointer. `mv` cannot give you that — it breaks + inbound links the moment it runs, while repointing first writes links to a name that does not exist + yet. To delete: repair the referrers before removing the file. + +2. **What replaces an inbound pointer depends on why the memory was dropped.** Only the first case + calls for inlining anything: + - **Falsified (category I)** — write the corrected fact into the referrer. It then carries what the + code actually does, which is more useful than the dead pointer was, and it preserves the verified + half of the memory being removed. + - **Superseded or duplicated (A, H)** — repoint at the surviving memory. Do not copy content across. + - **Dropped as valueless (B–G)** — delete the pointer. There is no fact to salvage, and inlining + content from a memory just judged not worth keeping quietly undoes that decision. 3. **Verify the symbols in the parts you KEEP, not just the parts you cut.** When trimming a memory it is natural to fact-check the claims being deleted and trust the ones being carried forward — which From 54f4742863a2b314181656f1da4707635a96bea1 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:09:13 +0200 Subject: [PATCH 5/8] Forbid the audit from editing its own skill files - Drop the instruction telling the skill to append new lessons to its reference file: installed skill files are hash-verified, so a self-edit reads as content drift and is lost on the next sync - State that the audit's only writes are the memory files approved in Step 4, and route session knowledge through continuous-learning --- skills/memory-audit/references/execution.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skills/memory-audit/references/execution.md b/skills/memory-audit/references/execution.md index 8beb6d3..8c6f4f5 100644 --- a/skills/memory-audit/references/execution.md +++ b/skills/memory-audit/references/execution.md @@ -1,8 +1,11 @@ # Execution Mechanics Ordering and technique rules for Step 4, once a batch has been approved. Each one corresponds to a way -a real audit has corrupted the KB it was cleaning. Add new lessons here rather than to the criteria -sections of `SKILL.md` — the criteria are the skill's spine and stay short enough to read as criteria. +a real audit has corrupted the KB it was cleaning. + +**This file is read-only at audit time.** Never edit it, or any other file in this skill, while running +an audit — the audit's only writes are to the memory files the user approved in Step 4. Knowledge worth +keeping from an audit goes through the `continuous-learning` skill, which routes and gates it. ## Applying the edits From 33023159b750839485bea2ef9efefdd7030f6d64 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:24:32 +0200 Subject: [PATCH 6/8] Sync the forcing-function test across both skills and fix drift - Add a fourth SYNC block for the forcing-function test, resolving the two skills' disagreement on when a lint rule or version control actually displaces a memory - Gate capture on symbol existence so an API that never shipped cannot be recorded in the present tense - Neutralize the remaining stack-specific examples in the capture skill and drop three restated sections --- .github/workflows/sync-blocks.yml | 2 +- SYNC-BLOCKS.md | 27 ++++++++++++++- skills/continuous-learning/SKILL.md | 49 +++++++++++++-------------- skills/memory-audit/SKILL.md | 51 ++++++++++++++++------------- 4 files changed, 80 insertions(+), 49 deletions(-) diff --git a/.github/workflows/sync-blocks.yml b/.github/workflows/sync-blocks.yml index ea9befe..10de4fc 100644 --- a/.github/workflows/sync-blocks.yml +++ b/.github/workflows/sync-blocks.yml @@ -34,7 +34,7 @@ jobs: B=skills/memory-audit/SKILL.md C=SYNC-BLOCKS.md overall=0 - for tag in capture-rules strip-the-anchors applies-to; do + for tag in capture-rules strip-the-anchors applies-to forcing-function; do echo "=== $tag ===" tag_fail=0 for f in "$A" "$B" "$C"; do diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index f014594..f818057 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -59,6 +59,31 @@ When a memory genuinely applies to multiple projects, list them comma-separated --- +## Block 4: Forcing-function test + +Locations: +- `skills/continuous-learning/SKILL.md` — inside Step 1, Stage A +- `skills/memory-audit/SKILL.md` — criterion B.1 + +Capture frames the failure as "skip", audit as "DROP"; both verbs stay outside the fence. + +```markdown + +**The forcing-function test.** *"Would a future session act differently in this project because this memory exists?"* The memory qualifies only if nothing else already drives that behavior. + +Four things can already be driving it, and they are not equally reliable: + +- **The code itself** — the current source reads correctly, and a future session consults the code, not the memory. +- **A comment the fix left behind** — a doc comment or inline note at the cited symbol saying the same thing. Read the file; don't just grep for the symbol. +- **An auto-loaded instruction file** — `CLAUDE.md` / `AGENTS.md` load every session, so a rule written there does displace the memory. +- **A mechanical enforcer, but only where it actually gates.** Read a lint rule's `severity:` *and* how CI treats it: a `warning` blocks under warnings-as-errors or a zero-warning threshold, and is advisory otherwise. An installed skill loads only when its description matches the task, so it displaces nothing for work that never triggers it. Version control records the history only where the project is under version control at all. + +Presence is not redundancy — ask which of them would actually fire on the work this memory covers. And none of them can carry the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. Where the mechanism is redundant but the wrong turn is not, keep the warning and cut the rest. + +``` + +--- + ## Drift verification (CI-ready) ```sh @@ -68,7 +93,7 @@ A=skills/continuous-learning/SKILL.md B=skills/memory-audit/SKILL.md C=SYNC-BLOCKS.md overall=0 -for tag in capture-rules strip-the-anchors applies-to; do +for tag in capture-rules strip-the-anchors applies-to forcing-function; do echo "=== $tag ===" tag_fail=0 for f in "$A" "$B" "$C"; do diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index 3b37e30..5dfd982 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -82,7 +82,20 @@ After completing any task, evaluate in two stages. - Did this require non-obvious investigation or debugging? - Was a choice made about architecture, patterns, or approach? - Is there an established project convention worth documenting? -- **Forcing-function (hard gate):** without this memory, would a future session act differently in the project? If the code, `git log`, lint, or the formatter already drives the behavior → skip. The "Do Not Save" table cites this as `[Forcing-function]`. +- **Forcing-function (hard gate)** — apply the test below. If something else already drives the behavior → skip. The "Do Not Save" table cites this as `[Forcing-function]`. + + +**The forcing-function test.** *"Would a future session act differently in this project because this memory exists?"* The memory qualifies only if nothing else already drives that behavior. + +Four things can already be driving it, and they are not equally reliable: + +- **The code itself** — the current source reads correctly, and a future session consults the code, not the memory. +- **A comment the fix left behind** — a doc comment or inline note at the cited symbol saying the same thing. Read the file; don't just grep for the symbol. +- **An auto-loaded instruction file** — `CLAUDE.md` / `AGENTS.md` load every session, so a rule written there does displace the memory. +- **A mechanical enforcer, but only where it actually gates.** Read a lint rule's `severity:` *and* how CI treats it: a `warning` blocks under warnings-as-errors or a zero-warning threshold, and is advisory otherwise. An installed skill loads only when its description matches the task, so it displaces nothing for work that never triggers it. Version control records the history only where the project is under version control at all. + +Presence is not redundancy — ask which of them would actually fire on the work this memory covers. And none of them can carry the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. Where the mechanism is redundant but the wrong turn is not, keep the warning and cut the rest. + If the forcing-function gate fails, or no other prompt answers yes → skip. Otherwise continue to Stage B. @@ -143,7 +156,7 @@ When a memory genuinely applies to multiple projects, list them comma-separated #### Mandatory pre-`Write` checks -Run both checks as visible output before any `Write` to `/.claude/memories/`. Hidden reasoning is easy to skip; printed output is reviewable. +Run all three checks as visible output before any `Write` to `/.claude/memories/`. Hidden reasoning is easy to skip; printed output is reviewable. **Check 1: Strip-the-anchors (routing).** @@ -170,7 +183,11 @@ This shape forces the test to happen — you cannot list anchors without finding Scan the drafted content for personal identifiers. Look for `@` characters (handles, emails), `/-` and `/-description` branch-name shapes, `@` email shapes, and any first-name-looking tokens in examples, commit references, or narration. Any hit → rewrite to describe the artifact (the bug, pattern, decision) without the actor, or skip the save. Mechanical grep, not a vibe check. -**Save (only after both checks pass):** +**Check 3: Symbol existence.** + +Grep every symbol, path, API, or config key the draft names in the present tense. A draft asserting that "hiding is handled by `setFooHidden()`" must be able to point at `setFooHidden()` in the code. Where a name does not resolve, either cut the claim or mark it explicitly as proposed and not yet implemented — never record an intended design in the present tense. This is the cheapest place to stop a memory that prescribes an API which never shipped; once saved, only a later audit will catch it. + +**Save (only after all three checks pass):** ``` Write(file_path: "/.claude/memories/__.md", content: "") ``` @@ -193,9 +210,7 @@ Before saving any memory, verify: - [ ] Content is specific enough to be actionable - [ ] Content is general enough to be reusable - [ ] No sensitive information (credentials, internal URLs) -- [ ] Does not duplicate existing memories - [ ] References included if external sources were consulted -- [ ] No brittle references that rot quickly (see Staleness Prevention below) ### Do Not Save @@ -209,11 +224,11 @@ Anti-examples, generalized — do not create memories like these: | Public API reference | "Public Git hosting API rate limit is N/hr authenticated" | **[Rule 1]** Public API docs cover this — no project-specific twist. | | Personal identifier | Problem section narrates a specific engineer hitting a cache bug | **[Rule 2]** Names an engineer. | | Personal preference without project evidence | "Prefer early returns" with no lint rule, consistent codebase usage, or team agreement | **[Rule 3]** Taste, not pattern. | -| Historical record of a one-time shipped change | "We renamed folder `Install/` to `Sync/` after the command rename" | **[Forcing-function]** Once shipped, `git log` answers this. Future sessions read the current code, not the migration story. The memory drives no future behavior. | +| Historical record of a one-time shipped change | "We renamed folder `Install/` to `Sync/` after the command rename" | **[Forcing-function]** Once shipped, `git log` answers this. Future sessions read the current code, not the migration story. The memory drives no future behavior — unless the project has no version control, where nothing else records the change. | | Generic engineering wisdom with a token project example | "Extract methods over condensing for lint compliance" with one PR cited | **[Rule 1]** Strip the example — what is left is universal advice that fits any project. Belongs in a coding-style doc, not a per-project KB. | | One-line rule that belongs in CLAUDE.md | A single-sentence convention with no Context / Options / Consequences | **[Scope]** If it fits in one bullet under "Conventions" in CLAUDE.md, put it there. A standalone memory file is overhead for content that cannot grow. | | Naming/prefix decision once enforced | "We kept the `External` prefix on adapter types" | **[Forcing-function]** Once the type system, lint, or formatter enforces it, the decision lives in the code. Future sessions read the code, not the memory. | -| One-time bug fix self-evident in current code | "Bug X used `dropFirst()`; we changed to a guarded check" | **[Forcing-function]** The fix is a small diff; the code reads correctly today. Save only if the bug class is recurring and the memory teaches the *avoidance pattern*, not the one fix. | +| One-time bug fix self-evident in current code | "Bug X skipped the first element instead of the matching one; we changed the filter to compare identity" | **[Forcing-function]** The fix is a small diff; the code reads correctly today. Save only if the bug class is recurring and the memory teaches the *avoidance pattern*, not the one fix. | | Research artifact for deferred or dormant work | "Cross-platform audit / options-considered for feature X (deferred indefinitely)" | **[Forcing-function]** Useful when the work resumes — but it belongs in a planning doc or `docs/`, not the memory KB. The KB is for things that change how a session works on the active codebase today. | **Internal docs are fair game.** A memory summarizing a Confluence page, ADR, RFC, or team-wiki entry is project knowledge — those sources aren't "documentation anyone can look up." Always include the source URL in `References:` so the memory points at the canonical version and readers can check for drift. @@ -235,11 +250,11 @@ Before saving, check memory content against these rules: - **No line numbers.** Reference symbols (types, functions, methods) instead — they survive refactors. - **Prefer module-level paths** over deep file paths. Use full paths only for stable, well-known files. -- **Use semantic anchors** — method signatures, protocol names, and architectural concepts are durable. +- **Use semantic anchors** — method signatures, interface and type names, and architectural concepts are durable. - **Omit transient details** — feature flags being removed, in-progress PR numbers, temporary workarounds. -**Good:** `SessionManager.refreshToken(forceExpiry:)` in the `Auth` module -**Bad:** `SessionManager.swift:142` at `Sources/Features/Auth/Session/SessionManager.swift` +**Good:** `SessionManager.refreshToken` in the `Auth` module +**Bad:** `src/features/auth/session/SessionManager.:142` --- @@ -255,17 +270,3 @@ When the user asks to "run a retrospective", "extract learnings from this sessio 4. Save the top 1–3 highest-value candidates that pass, following Step 4's pre-`Write` checks. 5. Report what was created and why in a brief summary. ---- - -## Tool Reference - -| Tool | Purpose | -|------|---------| -| `mcp__docs-mcp-server__search_docs` | **Primary:** Semantic search across docs and memories | -| `mcp__docs-mcp-server__list_libraries` | List indexed libraries | -| `Glob` | **Fallback:** List all memory files (`.claude/memories/*.md`) | -| `Read` | Read a specific memory file | -| `Write` | Create new memory file | -| `Edit` | Update existing memory file | -| `Bash` | Resolve git repo name for `Applies to:` (`git remote get-url origin`) | -| `WebSearch` | Built-in web search for general topics | diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 604a6ad..9bffcda 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -50,7 +50,7 @@ When a memory genuinely applies to multiple projects, list them comma-separated Evaluate each memory against the criteria below, grouped into three: - **Group A** mirrors the Capture Rules (the same gates a memory had to pass at save time). A memory that violates any of these now is a candidate for UPDATE or DROP — even if it slipped through capture. -- **Group B** is the audit's own gate: the forcing-function test. Capture cannot test it because only audit sees how a memory has aged. This is where past audits drifted into KEEP-by-default; apply it with teeth. +- **Group B** re-applies the forcing-function test capture already ran — but against a codebase that has since moved. A memory can pass at save time and fail here once the code, a comment, or an instruction file caught up. This is where past audits drifted into KEEP-by-default; apply it with teeth. - **Group C** are audit-only mechanics — tests that depend on the current state of the codebase, the KB as a whole, or time elapsed since capture. ### Group A — Capture Rules, restated @@ -78,14 +78,30 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - Spot-check the repo — **codebase usage is the strongest single signal**. If the declared pattern is demonstrably present in existing code, the memory is a pattern even without a written rule. If the codebase is inconsistent and there's no config/doc/agreement, it is a preference. - **Verdict:** DROP when no evidence exists anywhere. UPDATE when the pattern is real (visible in code, or the user confirms a team agreement) but the memory is phrased as personal taste; rewrite to point at the actual evidence. -### Group B — The audit's own gate +### Group B — The forcing-function gate, re-applied #### B.1 Actionability — the forcing-function test -- **Primary test:** *"Would a future session act differently in this codebase because this memory exists?"* If the answer is "no, the code itself or `git log` already conveys it" → DROP. + +A memory that fails the test below → DROP. + + +**The forcing-function test.** *"Would a future session act differently in this project because this memory exists?"* The memory qualifies only if nothing else already drives that behavior. + +Four things can already be driving it, and they are not equally reliable: + +- **The code itself** — the current source reads correctly, and a future session consults the code, not the memory. +- **A comment the fix left behind** — a doc comment or inline note at the cited symbol saying the same thing. Read the file; don't just grep for the symbol. +- **An auto-loaded instruction file** — `CLAUDE.md` / `AGENTS.md` load every session, so a rule written there does displace the memory. +- **A mechanical enforcer, but only where it actually gates.** Read a lint rule's `severity:` *and* how CI treats it: a `warning` blocks under warnings-as-errors or a zero-warning threshold, and is advisory otherwise. An installed skill loads only when its description matches the task, so it displaces nothing for work that never triggers it. Version control records the history only where the project is under version control at all. + +Presence is not redundancy — ask which of them would actually fire on the work this memory covers. And none of them can carry the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. Where the mechanism is redundant but the wrong turn is not, keep the warning and cut the rest. + + +Further audit-specific cautions: + - Can a future session **act on** this memory to avoid a mistake or follow a convention? Or is it purely descriptive/documentary with no clear "do this, not that" takeaway? - **Fact-check ≠ actionability.** A claim being *true* and *project-specific* is not enough. Many memories pass A.1 (real anchors) and C.4 (claims still verifiable) but still fail this one — historical records, shipped naming decisions, one-time bug fixes whose fix is self-evident in the code. Apply both passes; do not conflate them. - **Bias check.** If you find yourself defending KEEP with "it's project-specific and still accurate" without identifying the *behavior change* it drives, that's the leniency trap. KEEP requires a positive answer to the forcing-function test, not just absence of a reason to drop. -- **Something else may already be carrying it.** The code, a comment the fix left behind, or the auto-loaded instructions each dissolve a KEEP on their own. Check all three before answering yes — mechanics in C.4. ### Group C — Audit-only mechanics @@ -101,7 +117,7 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - Search the existing knowledge base or memory files for semantically similar content — two memories may use different names but cover the same ground. #### C.3 Quality -- Does the memory follow the standard templates? (Problem/Trigger/Solution/Verification/Example for learnings; Decision/Context/Options/Choice/Consequences for ADR decisions; Decision/Rationale/Examples for simplified decisions) +- Is the memory structurally complete for its kind — a learning carrying the problem, the trigger, the resolution and evidence it worked; a decision carrying the choice, the reasoning and what it costs? Judge completeness, not exact heading names: the capture skill owns the templates, and their headings change without this file knowing. - Is the content specific enough to be useful but general enough to be reusable? - Are code examples still accurate? @@ -125,9 +141,9 @@ git log --all --oneline --name-only -S '' -- . ':(exclude).claude/memori git branch -a --contains # which branch holds it ``` -**The order is what makes the verdicts exclusive.** A bare `--all` can return several pickaxe commits at once — a symbol added and removed on the default branch long ago *and* reintroduced on a live feature branch — and then the first two rows below both match, with the verdict decided by whichever commit you happened to test. Asking the default branch on its own removes that choice. +**The order is what makes the verdicts exclusive.** A bare `--all` can return several pickaxe commits at once — removed on the default branch long ago, reintroduced on a live feature branch — and then the first two rows below both match, the verdict decided by whichever commit you happened to test. -The pathspec and `--name-only` are not optional either. `-S` counts a string's occurrences in **any** tracked file, prose included — and memories are committed to the project repo unless someone gitignored them or moved them to a separate one. Without the exclusion, a memory naming a fictional API confirms that API as real code history, and the audit reports a *fabricated* rationale ("it shipped, then was removed") into the approval gate. Read the changed filenames before trusting a match: a hit that touches only docs is prose, not code. +The pathspec and `--name-only` are not optional either. `-S` counts a string in **any** tracked file, prose included, and memories are committed to the project repo unless gitignored or kept separately — so without the exclusion a memory naming a fictional API confirms it as real history, feeding a *fabricated* rationale into the approval gate. A hit touching only docs is prose, not code. | Finding | Verdict | |---|---| @@ -138,12 +154,6 @@ The pathspec and `--name-only` are not optional either. `-S` counts a string's o Where the repo has a remote and the network is reachable, fetch first (`git fetch --all`) — `--all` sees only refs already present, so an unfetched branch reads as "never existed." Skip the fetch when there is no remote, and never read a fetch failure as confirmation. -**Check what already carries the memory's content (B.1).** Two carriers are easy to miss: -- **A comment the fix left in the source.** A landed fix often left a doc comment or inline note saying the same thing — read the cited file, don't just grep for the symbol. -- **Another instruction source — but weigh how reliably each one fires.** `CLAUDE.md` / `AGENTS.md` load every session, so a rule written there does displace the memory. A **lint rule** displaces it only if the rule actually gates: read the `severity:` *and* how CI treats it, since a `warning` blocks under warnings-as-errors or a zero-warning threshold and is advisory otherwise. An **installed skill** loads only when its description matches the task at hand, so it displaces nothing for work that never triggers it. Grep all three, then ask which would actually fire on the work this memory covers — presence is not redundancy. - -Neither carrier can hold the **wrong turn**: the approach tried and rejected, the fix that looks obvious and silently no-ops, why the wrong pattern keeps reappearing. When the mechanism is redundant but the wrong turn isn't, trim to that warning instead of dropping the file. - #### C.5 Staleness Signals - **Line number references** — e.g., `lines 266-296` or `:142`. These break after any edit. Recommend UPDATE to replace with symbol names. - **Deep file paths** — full nested paths are fragile. Recommend UPDATE to use module-level references unless the path is stable and well-known. @@ -158,7 +168,7 @@ Neither carrier can hold the **wrong turn**: the approach tried and rejected, th ## DROP Categories — recurring patterns that should not need user pushback -Categories A–H are the recurring concrete shapes of B.1 (forcing-function) failure. Category I is different in kind — a C.4 falsification, where the memory *would* change behavior, wrongly. When a memory matches one, the analysis is already done — call DROP without hedging. None of these are "in doubt" cases. +Categories A–H are the recurring concrete shapes of B.1 (forcing-function) failure. Category I is different in kind — a C.4 falsification, where the memory *would* change behavior, wrongly. When a memory matches one, the analysis is already done — call DROP without hedging. Two carve-outs: category I still needs an explicit callout rather than a routine drop, and category B does not apply at all in a project without version control. ### A. Self-marked superseded / deferred / abandoned - The memory itself says **SUPERSEDED**, **deferred indefinitely**, **closed without implementation**, **path abandoned**, or points at another memory as the current decision. @@ -223,7 +233,7 @@ If the directory is missing or empty, report the situation (specify whether it d ### Step 2: Batch Assessment **Fact-check first, verdict second.** Before producing the verdict table for a batch, run a single grep pass against the codebase for the central claims (symbol names, file paths, type names) referenced across the batch. Verdicts that rest on unverified claims are guesses dressed up as analysis. Specifically: -- Grep for every distinct symbol/type referenced in the batch — confirm presence, note renames or deletions. +- Grep for every distinct symbol/type referenced in the batch — confirm presence, note renames or deletions. Where a symbol returns zero hits, resolve it through the C.4 triage before assigning any verdict; an empty grep is not a finding on its own. - Spot-check any line numbers and historical line counts; flag stale ones for UPDATE. - Watch for `Applies to:` typos (e.g. `mcs-2` when the project is `mcs`) — quick one-line fixes. @@ -270,6 +280,8 @@ Respect every override without arguing — the user knows their workflow better Run only after Step 3 has produced an explicit approval (or per-item decisions) for *this* batch. Apply the user's decisions, not the originally proposed verdicts when they differ. +Read [references/execution.md](references/execution.md) before the first batch's edits land. It covers link-repair ordering, what to verify in the parts you KEEP, and the rules for delegating batches to subagents — each one a way a real audit has damaged the KB it was cleaning. + - **DROP**: Delete the file with `Bash(rm )` - **UPDATE (rename)**: Rename with `Bash(mv )` - **UPDATE (content)**: Use `Edit` or `Write` to update the file @@ -280,8 +292,6 @@ Run only after Step 3 has produced an explicit approval (or per-item decisions) Report what was done after each batch. -Read [references/execution.md](references/execution.md) before the first batch's edits land. It covers link-repair ordering, what to verify in the parts you KEEP, and the rules for delegating batches to subagents — each one a way a real audit has damaged the KB it was cleaning. - ### Step 5: Summary After all batches are processed, present a final summary: @@ -304,10 +314,5 @@ Knowledge base reduced from 42 → 34 files. ## Guidelines -- **Never delete or edit without explicit per-batch approval.** Print the verdict table, then stop. Do not run any tool until the user replies for *this* batch — silence is not consent, and approval of an earlier batch does not carry forward. - **Explain the "why" clearly.** The user should understand the reasoning behind every DROP and UPDATE recommendation, not just see the label. -- **Apply criteria with teeth, not deference.** Past audits drifted into KEEP-by-default because each memory had *some* tie to the project. The forcing-function test (B.1) is the correction: KEEP requires identifying behavior the memory drives, not just absence of error. When the DROP categories above match, call DROP — don't soften it to UPDATE or stash in KEEP "to be safe." -- **In genuine doubt, prefer DROP with rationale over silent KEEP.** The user can always override. A KEEP that should have been DROP rarely gets revisited; a proposed DROP gets debated and resolved in seconds. **DROP is not a failure** — moving content to `CLAUDE.local.md`, to a planning doc, or simply deleting it because the code now documents itself is the audit doing its job. -- **Watch for the "but it's true and project-specific" trap.** That sentence is A.1 and C.4 passing — it says nothing about B.1. Two-pass thinking: first verify, then ask "does this change behavior?" -- **Batch size matters.** 10-15 per batch keeps the review manageable. -- **End-of-audit check for broken cross-links.** After DROPs land, grep `Related:` / `References:` lines for any pointer to a deleted filename and clean those up — broken refs accumulate silently otherwise. +- **In genuine doubt, prefer DROP with rationale over silent KEEP.** The asymmetry is the point: a KEEP that should have been DROP rarely gets revisited, while a proposed DROP gets debated and resolved in seconds. The user can always override. From 7e3084fa531a8ec38efec46c35775f92b8223f0c Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:29:29 +0200 Subject: [PATCH 7/8] Require the capture skill to log its KB search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Step 2 mandated a duplicate search but printed nothing, so a skipped search was indistinguishable from one that ran — unlike the Step 4 checks, which must show their work - One logged line now records the query, the hits, and which branch was taken --- skills/continuous-learning/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index 5dfd982..d52f3e4 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -129,6 +129,14 @@ Decide what to do, in this order of preference: Use `Related:` for memories that share root causes, build on each other, contradict each other, or supersede older decisions. Don't cross-link every vaguely overlapping memory. +**Log the outcome in one line before moving on.** An unlogged search is indistinguishable from a skipped one — the same reason the Step 4 checks print their work. Duplicates that slip past here are what an audit later has to clean up. + +``` +KB search: "" -> hits, -> +``` + +e.g. `KB search: "retry backoff" -> 2 hits, both on request timeouts -> branch 3, new memory, Related: learning_networking_timeout_tuning` + ### Step 3: Research (When Appropriate) **For general topics** — search available documentation sources first (the user may have MCP servers providing official docs for frameworks or libraries), then fall back to web search: From ba51d705c78d177939a79c73cf7d32b384f2e13a Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 28 Aug 2026 00:32:40 +0200 Subject: [PATCH 8/8] Document the paired sections that cannot be locked - Six DROP categories, plus naming and staleness, exist on both sides unsynced; two had already drifted before this PR - A locked block is the wrong tool: audit needs exception clauses capture has no use for, and the optional audit skill can never be referenced by the required capture skill --- SYNC-BLOCKS.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index f818057..e1c2720 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -84,6 +84,34 @@ Presence is not redundancy — ask which of them would actually fire on the work --- +## Paired sections (not CI-enforced) + +These sections say the same thing from two sides — capture decides whether to *write* a memory, audit decides whether to *keep* one. They are deliberately **not** locked blocks, for two reasons: + +- The two skills need different shapes. Capture wants a one-line anti-example; audit wants the category plus its "keep only when…" exception. Forcing them byte-identical would strip the exceptions. +- Capture cannot point at audit instead. `techpack.yaml` marks `continuous-learning` as `isRequired: true` and `memory-audit` as optional, so capture must stand alone on machines where the audit skill is not installed. + +So this is a review checklist, not a guarantee. **Change one side, check the other.** + +| Concept | continuous-learning | memory-audit | +|---|---|---| +| Naming convention | `## Memory Categories` headings | `C.1 Naming Convention` | +| Historical record of a shipped change | "Do Not Save" → *Historical record of a one-time shipped change* | DROP category **B** | +| Naming / style decision once enforced | "Do Not Save" → *Naming/prefix decision once enforced* | DROP category **C** | +| Self-evident one-time bug fix | "Do Not Save" → *One-time bug fix self-evident in current code* | DROP category **D** | +| Generic wisdom with a token example | "Do Not Save" → *Generic engineering wisdom with a token project example* | DROP category **E** | +| Research artifact for dormant work | "Do Not Save" → *Research artifact for deferred or dormant work* | DROP category **F** | +| One-liner belonging in CLAUDE.md | "Do Not Save" → *One-line rule that belongs in CLAUDE.md* | DROP category **G** | +| A memory asserting an API that never shipped | Step 4 → *Check 3: Symbol existence* | DROP category **I** | +| Staleness | `## Staleness Prevention` | `C.5 Staleness Signals` | + +**Intentionally unpaired — do not "fix" these:** + +- Audit categories **A** (self-marked superseded) and **H** (scope covered by a sibling) have no capture counterpart. A memory cannot be superseded at the moment it is written, and capture handles sibling overlap at Step 2 instead. +- Audit criterion **C.3** deliberately stopped enumerating template headings. Capture owns `references/templates.md`, and the audit judges structural completeness, so heading names can change on one side without breaking the other. + +--- + ## Drift verification (CI-ready) ```sh