From eaf1344dc977782285921af3d1d87b991cddbd5e Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Sun, 27 Sep 2026 02:02:36 +0200 Subject: [PATCH 1/5] Stop memory capture from recording unmerged-branch claims as fact - Capture prints a default-branch check; branch-only claims are saved conditionally with a Pending line, and the audit resolves them - Edits rewrite contradicted statements in place, and the identifier and routing checks run on added text - Align capture staleness rules with the audit and scope both skills' allowed-tools to what they run --- CLAUDE.md | 2 +- SYNC-BLOCKS.md | 2 +- hooks/memory-loop-activator.sh | 4 +- skills/continuous-learning/SKILL.md | 57 +++++++++++++------ .../references/templates.md | 2 + skills/memory-audit/SKILL.md | 24 ++++++-- templates/continuous-learning.md | 2 +- tests/fixtures/hook-messages.json | 2 +- 8 files changed, 67 insertions(+), 28 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5cadc84..6e5c310 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,4 +81,4 @@ To measure any of this, `qmd bench -c memories` is usable as ship **Check what contradiction a change creates.** Both skills carry emphatic guidance that can overrule a softer new instruction. The audit's "in genuine doubt, prefer DROP" is the clearest example: a new rule saying "report this rather than dropping it" loses unless that guideline is carved out explicitly. After editing, read the new text alongside the sections that push the opposite way. -Six DROP categories are duplicated between the two skills with nothing keeping them in step. Change one side, check the other. +Six DROP categories are duplicated between the two skills with nothing keeping them in step, and so are capture's Staleness Prevention and the audit's C.5 Staleness Signals, and the `Pending:` convention (capture's Check 3 and Step 2, the audit's Step 2, DROP category A, and C.5), and rewrite-in-place editing (capture's Step 2, the audit's Step 4). Change one side, check the other. diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index 34dbca2..12ea99b 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -51,7 +51,7 @@ Locations: ```markdown -**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the working directory's basename only when the repo has no remote configured. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. +**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the project directory's basename only when the repo has no remote configured or the project is not a git repo. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. 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. diff --git a/hooks/memory-loop-activator.sh b/hooks/memory-loop-activator.sh index 0263d8f..36ebed1 100644 --- a/hooks/memory-loop-activator.sh +++ b/hooks/memory-loop-activator.sh @@ -13,6 +13,6 @@ If this session produced reusable knowledge worth preserving, invoke Skill(continuous-learning) — the skill evaluates and routes the knowledge to the correct destination. Do not ask permission. -NEVER write to .claude/memories/ directly. Always go through the skill -so the routing and quality gates run. +NEVER write to .claude/memories/ directly. Only the continuous-learning +and memory-audit skills write there, so the routing and quality gates run. EOF diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index e3ee056..59cb13f 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -6,14 +6,27 @@ description: > .claude/memories/ for codebase knowledge, CLAUDE.local.md for environment/tool/instance config, or skip for public documentation. Also use when the user asks to "run a retrospective", "extract learnings", or "save what we learned" from the current session. -allowed-tools: Write, Read, Glob, Edit, Bash, WebSearch, mcp__memory-loop__query, mcp__memory-loop__get +allowed-tools: + - Read + - Glob + - WebSearch + - Write(.claude/memories/**) + - Edit(.claude/memories/**) + - Bash(git grep *) + - Bash(git show *) + - Bash(git rev-parse *) + - Bash(git remote get-url *) + - mcp__memory-loop__query + - mcp__memory-loop__get + - mcp__memory-loop__multi_get +disallowed-tools: AskUserQuestion --- # Continuous Learning Skill -Evaluate reusable knowledge from work sessions and route it: codebase knowledge → `/.claude/memories/`, environment/tool/instance config → suggest a `CLAUDE.local.md` section, public documentation → skip. The skill is the **only** path to `memories/` — never `Write` there directly. Suggesting `CLAUDE.local.md` is a successful outcome, not a failure. +Evaluate reusable knowledge from work sessions and route it: codebase knowledge → `/.claude/memories/`, environment/tool/instance config → suggest a `CLAUDE.local.md` section, public documentation → skip. Suggesting `CLAUDE.local.md` is a successful outcome, not a failure. -> **Note:** `` refers to the current working directory (project root) throughout this document. The `Applies to:` field inside memory content has its own semantics — see the **Applies to** subsection in [Step 4](#step-4-route-and-save). +> **Note:** `` is the project root throughout this document: `git rev-parse --show-toplevel`, else `CLAUDE_PROJECT_DIR`, else the current working directory — the same order the memory hooks use. Run every command from there and never `cd` into `.claude/memories/` — the memories may live in a separate git repo, so `git` commands would silently answer for the wrong one. The `Applies to:` field inside memory content has its own semantics — see the **Applies to** subsection in [Step 4](#step-4-route-and-save). ## Memory Categories @@ -72,7 +85,7 @@ Every memory must satisfy all three rules. ## Extraction Workflow -> **Autonomous by default.** This skill saves memories automatically when the quality gates are met. Never ask the user for permission to save — evaluate, decide, and save silently. Only mention saved memories in a brief one-line note after the main task response. +> **Autonomous by default.** This skill saves memories automatically when the quality gates are met. Never ask the user for permission to save — evaluate, decide, and save silently. *Silently* means without asking; the Step 2 and Step 4 check lines are still printed. Otherwise, only mention saved memories in a brief one-line note after the main task response. ### Step 1: Evaluate the Current Task @@ -81,7 +94,7 @@ After completing any task, evaluate in two stages. **Stage A — Is it worth saving?** - 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? +- Is there an established project convention the code follows but does not make obvious? - **Forcing-function (hard gate):** without this memory, would a future session act differently in the project? If the current code or a mechanical check already drives the behavior → skip. The "Do Not Save" table cites this as `[Forcing-function]`. If the forcing-function gate fails, or no other prompt answers yes → skip. Otherwise continue to Stage B. @@ -117,7 +130,7 @@ is a guess — and the cost of guessing wrong is a duplicate memory or a lost re Decide what to do, in this order of preference: 1. **Knowledge is already captured.** Skip. -2. **The new knowledge extends or refines an existing memory.** Prefer this: `Edit` the existing memory. The KB stays lean and a stronger single memory beats two partial ones. +2. **The new knowledge extends or refines an existing memory.** Prefer this: `Edit` the existing memory. The KB stays lean and a stronger single memory beats two partial ones. When editing, run Step 4 Check 3 first. Where the new knowledge contradicts or supersedes a statement, rewrite that statement where it stands — a `Related:` note is not a correction — unless Check 3 prints `pending`: then keep the current statement and add the conditional one beside it. If the memory being edited carries a `Pending:` line whose change has since merged, fold it in with the same edit: remove the superseded statement, the conditional phrasing, and the `Pending:` line. Re-read the whole file afterwards to catch a broken splice. 3. **The content is too different to merge but still related.** Save a new memory and add a `Related:` cross-link to the neighbor. If the relationship is bidirectional, also `Edit` the neighbor to add a reciprocal `Related:` entry. 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. @@ -132,7 +145,7 @@ KB search: "" -> hits, -> best practices ") +WebSearch(query: " ") ``` Research should **enrich** project-specific knowledge, not replace it. The goal is to add context or verify a finding — not to save generic knowledge that any LLM already knows. If the research result is general programming advice without a project-specific angle, skip saving it. @@ -148,14 +161,14 @@ Read [references/templates.md](references/templates.md) for template structures. Fill in `Applies to:` directly under the title heading of every memory. -**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the working directory's basename only when the repo has no remote configured. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. +**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the project directory's basename only when the repo has no remote configured or the project is not a git repo. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. 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. #### 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 these checks as visible output before any `Write` to `/.claude/memories/`. Before an `Edit`, run them on the added text; if Check 1 fails, route that text, not the memory. Hidden reasoning is easy to skip; printed output is reviewable. **Check 1: Strip-the-anchors (routing).** @@ -180,9 +193,19 @@ This shape forces the test to happen — you cannot list anchors without finding **Check 2: Personal-identifier scan.** -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. +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. Print one line: -**Save (only after both checks pass):** +- **Identifiers:** `none` | `rewrote ` | `skipped` + +**Check 3: Default-branch state.** + +Under version control, verify the memory's central claim against the default branch, not only the working tree. Resolve the ref without fetching — `git rev-parse --verify origin/HEAD`, else the local default branch (e.g. `main`, `master`, `develop`) — then `git grep `, or `git show :` when the claim is not greppable. Print one line: + +- **Default branch:** `holds` | `pending ()` | `no VCS` + +`pending` requires a change you know is in flight, usually this session's own branch; a claim missing from the default branch with no such change is wrong, not pending — don't save it. On `pending`, phrase the claim conditionally (*"once X lands…"*) and add `**Pending:** ` on the line after `Applies to:` — a ticket, a PR, or a short description of the change, never a branch name. Never write *"already migrated"* or *"not merged yet"*; both go stale on merge. + +**Save (only after Checks 1 and 2 pass and Check 3 is printed):** ``` Write(file_path: "/.claude/memories/__.md", content: "") ``` @@ -201,10 +224,10 @@ Edit(file_path: "/.claude/memories/.md", old_string: "_`) - [ ] Content uses the appropriate template from references/templates.md -- [ ] Solution is verified to work (not theoretical) +- [ ] Solution is verified to work (not theoretical), on the branch it describes - [ ] Content is specific enough to be actionable - [ ] Content is general enough to be reusable -- [ ] No sensitive information (credentials, internal URLs) +- [ ] No sensitive information (credentials, tokens, private endpoints) — an internal doc's link in `References:` is expected, not sensitive - [ ] References included if external sources were consulted ### Do Not Save @@ -235,7 +258,7 @@ When the underlying knowledge *is* salvageable, rewrite before saving — or ski | Memory describes how a CLI flag works | *skip — that's tool documentation, not project knowledge* | | Problem section names a specific engineer hitting a cache bug in auth | *"Auth flow hits a cache bug under condition X"* — drop the actor, keep the symptom | | *"I prefer early returns"* and the codebase mixes both styles freely | *skip — preference, not pattern* | -| *"I prefer early returns"* and existing code consistently uses them (or a lint rule enforces it, or the team agreed) | Save as `decision_codestyle_early_returns` citing the codebase usage, rule, or agreement — now it's a pattern with evidence | +| *"I prefer early returns"*, existing code consistently uses them (or the team agreed), and no lint rule, formatter, or compiler check enforces them | Save as `decision_codestyle_early_returns` citing the codebase usage or agreement — Rule 3 makes it a pattern; the forcing-function passes only because nothing enforces it and new code could break it | --- @@ -245,8 +268,8 @@ 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, interface and type names, and architectural concepts are durable. -- **Omit transient details** — feature flags being removed, in-progress PR numbers, temporary workarounds. +- **Use semantic anchors** — method signatures, interface and type names, and architectural concepts are durable. For personal or environment tooling, name the action, not the tool (*"clean, then rebuild"*, not a specific MCP tool or local CLI) — which tool does it is environment config. Commands the project's own scripts or CI define stay verbatim. +- **Omit transient details** — feature flags being removed, in-progress PR numbers (a `Pending:` line excepted), temporary workarounds without a removal condition, and the story of how the knowledge was found (PR sequences, attempts, reverts): state what it taught as a rule. **Good:** `SessionManager.refreshToken` in the `Auth` module **Bad:** `src/features/auth/session/SessionManager.:142` @@ -261,7 +284,7 @@ When the user asks to "run a retrospective", "extract learnings from this sessio 1. Review conversation history for extractable knowledge. 2. Search existing memories following Step 2 of the Extraction Workflow. -3. Filter candidates through the Capture Rules. Drop anything that fails Rule 1 (no project tie), Rule 2 (names an engineer), or Rule 3 (preference without project evidence). +3. Filter candidates through Stage A's forcing-function gate and the Capture Rules. Drop anything that fails the forcing-function (the code already drives the behavior), Rule 1 (no project tie), Rule 2 (names an engineer), or Rule 3 (preference without project evidence). 4. Save the top 1–3 highest-value candidates that pass, following Step 4's pre-`Write` checks. The cap is deliberate: a long session can yield many qualifying memories, and three is the most worth adding at once — the gates decide what is eligible, the cap decides how many land per session. Note any you set aside. 5. Report what was created and why in a brief summary. diff --git a/skills/continuous-learning/references/templates.md b/skills/continuous-learning/references/templates.md index c2be528..b9125c5 100644 --- a/skills/continuous-learning/references/templates.md +++ b/skills/continuous-learning/references/templates.md @@ -2,6 +2,8 @@ Template structures for the continuous-learning skill. Load this when creating or updating memories. Capture Rules, `Applies to:` derivation, and Staleness rules live in [SKILL.md](../SKILL.md) — this file is templates only. +When a memory's claim holds only on an unmerged branch, add `**Pending:** ` on the line after `Applies to:` (SKILL.md, Step 4 Check 3). + ## Learning Memory Template ```markdown diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 24eeda4..6073e6b 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -5,12 +5,23 @@ description: > Use this skill when the user says "audit memories", "review memories", "clean up memories", "memory audit", "check my memories", or wants to prune, deduplicate, or assess the quality of their stored learnings and decisions. This is a manual-only skill — never trigger automatically. -allowed-tools: Read, Glob, Grep, Edit, Bash, Write, mcp__memory-loop__query, mcp__memory-loop__get, AskUserQuestion +allowed-tools: + - Read + - Glob + - Grep + - AskUserQuestion + - Bash(git grep *) + - Bash(git show *) + - Bash(git rev-parse *) + - Bash(git remote get-url *) + - mcp__memory-loop__query + - mcp__memory-loop__get + - mcp__memory-loop__multi_get --- # Memory Audit Skill -Audit the knowledge base in `/.claude/memories/` to keep it lean, relevant, and high-quality. `` refers to the current working directory. **DROP is not a failure** — deleting a memory that does not qualify (or that belongs in `CLAUDE.local.md`, in a planning doc, or in the tool's own docs) is the audit doing its job. +Audit the knowledge base in `/.claude/memories/` to keep it lean, relevant, and high-quality. `` is the project root: `git rev-parse --show-toplevel`, else `CLAUDE_PROJECT_DIR`, else the current working directory — the same order the memory hooks use. **DROP is not a failure** — deleting a memory that does not qualify (or that belongs in `CLAUDE.local.md`, in a planning doc, or in the tool's own docs) is the audit doing its job. Over time, memory files accumulate — some become stale, some duplicate each other, some capture generic knowledge that doesn't belong in a project-specific KB. This skill walks through every memory with the user, recommending **KEEP**, **DROP**, or **UPDATE** with clear rationale, and only acts on user-approved changes. @@ -36,7 +47,7 @@ The audit enforces these rules through the criteria below — see Group A. ## The `Applies to:` field -**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the working directory's basename only when the repo has no remote configured. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. +**The `Applies to:` field.** Place `**Applies to:**` on the line immediately after the `# Title` heading of every memory; it declares which project(s) the memory targets. Use the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:org/repo.git` → `repo`; `https://github.com/owner/my-app.git` → `my-app`). Fall back to the project directory's basename only when the repo has no remote configured or the project is not a git repo. Use the repo name — not the directory basename — because folder names vary across clones while the repo name is stable. This is also why `Applies to:` may differ from the set of memories the search index actually covers, which is folder-based and set automatically by the indexing hook. 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. @@ -115,7 +126,8 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act #### 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. -- **Transient details** — feature flag names being removed, in-progress PR numbers, temporary workarounds with known expiry. +- **Transient details** — feature flag names being removed, in-progress PR numbers (a `Pending:` line excepted), temporary workarounds whose removal condition has been met, or that state none. +- **Narrative and tool names** — the story of how the knowledge was found (PR sequences, attempts, reverts), or a personal or environment tool (an MCP tool name, a local CLI) where naming the action would do — commands the project's own scripts or CI define stay. Recommend UPDATE to state the rule it taught and name the action. - 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. - Old dates without timeless content — treat as a signal for closer scrutiny, not an automatic DROP. @@ -129,6 +141,7 @@ The categories below are the recurring concrete shapes of B.1 (forcing-function) ### 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. - The "historical context" argument is rarely worth a file. If the superseder cross-links back, that's enough provenance. DROP the older one. +- A `Pending:` line is not deferral — it marks work in flight, judged by whether it merged (Step 2). ### 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. This assumes version control holds the history — without it nothing else records the change, so judge on behavior alone. @@ -183,6 +196,7 @@ If the directory is missing or empty, report the situation (specify whether it d - Grep for every distinct symbol/type referenced in the batch — confirm presence, note renames or deletions. - 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. +- Check memories carrying a `**Pending:**` line against the default branch first (resolve the ref as capture's Check 3 does). Merged → UPDATE to the merged state: remove the statement it superseded, the conditional phrasing, and the `Pending:` line. Still in flight → leave it. Abandoned → UPDATE to remove the conditional part, or DROP if nothing else remains. Read memories in batches (10-15 at a time) and produce a verdict table for each batch: @@ -229,7 +243,7 @@ Run only after Step 3 has produced an explicit approval (or per-item decisions) - **DROP**: Delete the file with `Bash(rm )` - **UPDATE (rename)**: Rename with `Bash(mv )` -- **UPDATE (content)**: Use `Edit` or `Write` to update the file +- **UPDATE (content)**: Use `Edit` or `Write` to update the file. Rewrite a contradicted statement where it stands rather than appending a correction, then re-read the whole file to catch a broken splice. - **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.** 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. diff --git a/templates/continuous-learning.md b/templates/continuous-learning.md index 8c9cfd4..fdafd32 100644 --- a/templates/continuous-learning.md +++ b/templates/continuous-learning.md @@ -17,7 +17,7 @@ Before writing code, planning, or exploring — **always search the knowledge ba Results carry a score of `1/rank`, not a confidence — a poor match still scores 1.00 at the top. If nothing fits, re-query with different terms; raising `limit` only appends a tail and never reorders the results above it. -2. **Retrieve before relying on a result** — a result carries a snippet, which is a lead, not evidence. Fetch what you intend to use with `mcp__memory-loop__multi_get` (or `get` for one document) and read it. Never quote, summarise, or act on a memory you have only seen as a snippet. +2. **Retrieve before relying on a result** — a result carries a snippet, which is a lead, not evidence. Fetch what you intend to use with `mcp__memory-loop__multi_get` (or `get` for one document) and read it. Never quote, summarise, or act on a memory you have only seen as a snippet. A memory describes the code when it was captured, possibly on another branch: where the code you read disagrees, the code wins — say so rather than following the memory, and correct it through continuous-learning. ``` mcp__memory-loop__get(file: "memories/foo.md") # or file: "#docid" from a query hit diff --git a/tests/fixtures/hook-messages.json b/tests/fixtures/hook-messages.json index d0f79cf..d39b55d 100644 --- a/tests/fixtures/hook-messages.json +++ b/tests/fixtures/hook-messages.json @@ -18,5 +18,5 @@ "additionalContext": "This project keeps a knowledge base of past learnings, decisions, and\ndebugging discoveries in .claude/memories/, searchable with\nmcp__memory-loop__query.\n\n- If your prompt contains a \"KB context:\" block, treat it as established\n ground truth. Verify it against the code, but do NOT search the KB and\n do NOT re-derive it.\n- Otherwise, if you are about to read or grep more than a couple of files,\n issue ONE mcp__memory-loop__query for the topic of your task first — a\n lex line of terms you expect verbatim plus a vec line phrased as a\n question, and an intent saying what you want and what to avoid, with\n rerank:false and limit:6. One search is far cheaper than a blind\n file sweep. Unlike the main thread, do not try keyword variations: if\n nothing relevant comes back, move on to the code.\n- Snippets are leads, not evidence — they are capped at 300 characters and\n often show only a title. mcp__memory-loop__get a document before relying\n on what it says.\n- Report back anything the KB got wrong or left out." } }, - "activator": "MANDATORY MEMORY PROTOCOL\n\nIf this starts a new sub-task or phase (tests, refactor, deploy, etc.)\n→ search the KB via mcp__memory-loop__query for relevant patterns first.\n\nIf you hit an unexpected error or are about to debug/diagnose\n→ search the KB FIRST, before reasoning from scratch.\n\nIf this session produced reusable knowledge worth preserving, invoke\nSkill(continuous-learning) — the skill evaluates and routes the knowledge\nto the correct destination. Do not ask permission.\n\nNEVER write to .claude/memories/ directly. Always go through the skill\nso the routing and quality gates run.\n" + "activator": "MANDATORY MEMORY PROTOCOL\n\nIf this starts a new sub-task or phase (tests, refactor, deploy, etc.)\n→ search the KB via mcp__memory-loop__query for relevant patterns first.\n\nIf you hit an unexpected error or are about to debug/diagnose\n→ search the KB FIRST, before reasoning from scratch.\n\nIf this session produced reusable knowledge worth preserving, invoke\nSkill(continuous-learning) — the skill evaluates and routes the knowledge\nto the correct destination. Do not ask permission.\n\nNEVER write to .claude/memories/ directly. Only the continuous-learning\nand memory-audit skills write there, so the routing and quality gates run.\n" } From 866f7ac5ce9a82bb35d6c136f2201a6a9ab24efe Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Sun, 27 Sep 2026 02:31:34 +0200 Subject: [PATCH 2/5] Group the memory edit steps and trim restated rules from both skills - Capture's edit procedure is one ordered list beside the Edit call instead of a nested paragraph in Step 2 - Check 1's stop rule now names its Edit exception where the model reads it - Drop paraphrases of the synced rules and duplicated examples from both skills --- CLAUDE.md | 2 +- skills/continuous-learning/SKILL.md | 49 ++++++++++------------------- skills/memory-audit/SKILL.md | 12 ++----- 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6e5c310..ef4a946 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,4 +81,4 @@ To measure any of this, `qmd bench -c memories` is usable as ship **Check what contradiction a change creates.** Both skills carry emphatic guidance that can overrule a softer new instruction. The audit's "in genuine doubt, prefer DROP" is the clearest example: a new rule saying "report this rather than dropping it" loses unless that guideline is carved out explicitly. After editing, read the new text alongside the sections that push the opposite way. -Six DROP categories are duplicated between the two skills with nothing keeping them in step, and so are capture's Staleness Prevention and the audit's C.5 Staleness Signals, and the `Pending:` convention (capture's Check 3 and Step 2, the audit's Step 2, DROP category A, and C.5), and rewrite-in-place editing (capture's Step 2, the audit's Step 4). Change one side, check the other. +Six DROP categories are duplicated between the two skills with nothing keeping them in step, and so are capture's Staleness Prevention and the audit's C.5 Staleness Signals, and the `Pending:` convention (capture's Check 3 and **Update existing** list, the audit's Step 2, DROP category A, and C.5), and rewrite-in-place editing (capture's **Update existing**, the audit's Step 4). Change one side, check the other. diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index 59cb13f..d8f1e9e 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -99,13 +99,7 @@ After completing any task, evaluate in two stages. If the forcing-function gate fails, or no other prompt answers yes → skip. Otherwise continue to Stage B. -**Stage B — Apply the three Capture Rules above as hard gates. All three must pass.** Enforcement maps: - -1. **Rule 1 (project-tied)** — apply Step 4 Check 1 (strip-the-anchors). -2. **Rule 2 (anonymous)** — apply Step 4 Check 2 (identifier scan). -3. **Rule 3 (pattern, not preference)** — verify by codebase usage, lint/formatter config, style guide, or team agreement (written or verbal — see the synced rule for the full list of valid evidence). - -If any rule fails, rewrite the memory to satisfy it (e.g. anonymize an actor, replace tool-only substance with the actual project anchor) or skip. Do not save partial-fit memories. +**Stage B — The three Capture Rules above are hard gates; all three must pass.** Step 4's Checks 1 and 2 enforce Rules 1 and 2; verify Rule 3 against the evidence the rule lists. If any rule fails, rewrite the memory to satisfy it (e.g. anonymize an actor, replace tool-only substance with the actual project anchor) or skip. Do not save partial-fit memories. ### Step 2: Search Existing Knowledge @@ -130,7 +124,7 @@ is a guess — and the cost of guessing wrong is a duplicate memory or a lost re Decide what to do, in this order of preference: 1. **Knowledge is already captured.** Skip. -2. **The new knowledge extends or refines an existing memory.** Prefer this: `Edit` the existing memory. The KB stays lean and a stronger single memory beats two partial ones. When editing, run Step 4 Check 3 first. Where the new knowledge contradicts or supersedes a statement, rewrite that statement where it stands — a `Related:` note is not a correction — unless Check 3 prints `pending`: then keep the current statement and add the conditional one beside it. If the memory being edited carries a `Pending:` line whose change has since merged, fold it in with the same edit: remove the superseded statement, the conditional phrasing, and the `Pending:` line. Re-read the whole file afterwards to catch a broken splice. +2. **The new knowledge extends or refines an existing memory.** Prefer this: `Edit` the existing memory, following **Update existing** in Step 4. The KB stays lean and a stronger single memory beats two partial ones. 3. **The content is too different to merge but still related.** Save a new memory and add a `Related:` cross-link to the neighbor. If the relationship is bidirectional, also `Edit` the neighbor to add a reciprocal `Related:` entry. 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. @@ -143,14 +137,7 @@ KB search: "" -> hits, -> ") -``` - -Research should **enrich** project-specific knowledge, not replace it. The goal is to add context or verify a finding — not to save generic knowledge that any LLM already knows. If the research result is general programming advice without a project-specific angle, skip saving it. - -**Skip research for:** project-specific conventions, time-sensitive captures. +To verify a finding about a library or tool, check available documentation sources first, then `WebSearch(query: " ")`. Research enriches project knowledge; general advice with no project angle is not saved. Skip research for project-specific conventions and time-sensitive captures. ### Step 4: Route and Save @@ -168,7 +155,7 @@ When a memory genuinely applies to multiple projects, list them comma-separated #### Mandatory pre-`Write` checks -Run these checks as visible output before any `Write` to `/.claude/memories/`. Before an `Edit`, run them on the added text; if Check 1 fails, route that text, not the memory. Hidden reasoning is easy to skip; printed output is reviewable. +Run these checks as visible output before any `Write` to `/.claude/memories/`, and on the added text before any `Edit`. Hidden reasoning is easy to skip; printed output is reviewable. **Check 1: Strip-the-anchors (routing).** @@ -181,13 +168,7 @@ Print, in two short lines, before the save: - **Anchors stripped:** comma-separated list of every project-specific reference identified above. If none → the draft has no project tie; reject the save. - **Substance without anchors:** one sentence describing what is left after stripping (e.g. *"the project's coordinator pattern between view-models and routing"*, *"how a third-party HTTP-debugging proxy's mock-rule syntax works"*). -If the substance line describes general, tool, language, or environment knowledge, reject the `Write` to `memories/`. Emit the content as a draft `CLAUDE.local.md` section (heading `## `) and a one-line note: "this is environment/tool config — consider adding the section above to `CLAUDE.local.md`." Stop. Do not edit `CLAUDE.local.md`; the user decides. - -*Worked example.* Draft says "how to write a mock rule for an HTTP-debugging proxy returning 500 for `/checkout`." -- Anchors stripped: `/checkout`. -- Substance without anchors: "how the proxy's mock-rule syntax works." - -Substance is tool knowledge → reject the `memories/` save; emit as a `CLAUDE.local.md` draft section under the proxy's name. +If the substance line describes general, tool, language, or environment knowledge, reject the `Write` to `memories/`. Emit the content as a draft `CLAUDE.local.md` section (heading `## `) and a one-line note: "this is environment/tool config — consider adding the section above to `CLAUDE.local.md`." Stop (on an Edit, route only the added text — see Update existing). Do not edit `CLAUDE.local.md`; the user decides. This shape forces the test to happen — you cannot list anchors without finding them, cannot describe the substance without evaluating it — without reprinting the full draft. @@ -203,7 +184,9 @@ Under version control, verify the memory's central claim against the default bra - **Default branch:** `holds` | `pending ()` | `no VCS` -`pending` requires a change you know is in flight, usually this session's own branch; a claim missing from the default branch with no such change is wrong, not pending — don't save it. On `pending`, phrase the claim conditionally (*"once X lands…"*) and add `**Pending:** ` on the line after `Applies to:` — a ticket, a PR, or a short description of the change, never a branch name. Never write *"already migrated"* or *"not merged yet"*; both go stale on merge. +**`pending` only when** you know the change is in flight, usually this session's own branch. A claim missing from the default branch with no such change is wrong, not pending — don't save it. + +**How to write it:** phrase the claim conditionally (*"once X lands…"*) and add `**Pending:** ` on the line after `Applies to:` — a ticket, a PR, or a short description of the change, never a branch name. Never write *"already migrated"* or *"not merged yet"*; both go stale on merge. **Save (only after Checks 1 and 2 pass and Check 3 is printed):** ``` @@ -211,6 +194,12 @@ Write(file_path: "/.claude/memories/__.md", ``` **Update existing:** + +1. Run the checks on the added text. If Check 1 fails, route that text, not the memory. +2. Rewrite a contradicted or superseded statement where it stands — a `Related:` note is not a correction. If Check 3 printed `pending`, keep the statement and add the conditional one beside it. +3. If the memory carries a `Pending:` line whose change has since merged, fold it in with the same edit: remove the superseded statement, the conditional phrasing, and the `Pending:` line. +4. Re-read the whole file afterwards to catch a broken splice. + ``` Edit(file_path: "/.claude/memories/.md", old_string: "
", new_string: "") ``` @@ -219,7 +208,7 @@ Edit(file_path: "/.claude/memories/.md", old_string: " Capture Rules are gated in **Stage B** above; do not re-evaluate them here. This checklist covers formatting, quality, and security only — different concerns. +> Capture Rules are gated in **Stage B**; this checklist covers formatting, quality, and security only. Before saving any memory, verify: - [ ] Name follows the correct pattern (`learning_` or `decision__`) @@ -249,15 +238,11 @@ Anti-examples, generalized — do not create memories like these: | 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. - -When the underlying knowledge *is* salvageable, rewrite before saving — or skip entirely: +When the underlying knowledge *is* salvageable, rewrite before saving: | Bad | Good | |-----|------| -| Memory describes how a CLI flag works | *skip — that's tool documentation, not project knowledge* | | Problem section names a specific engineer hitting a cache bug in auth | *"Auth flow hits a cache bug under condition X"* — drop the actor, keep the symptom | -| *"I prefer early returns"* and the codebase mixes both styles freely | *skip — preference, not pattern* | | *"I prefer early returns"*, existing code consistently uses them (or the team agreed), and no lint rule, formatter, or compiler check enforces them | Save as `decision_codestyle_early_returns` citing the codebase usage or agreement — Rule 3 makes it a pattern; the forcing-function passes only because nothing enforces it and new code could break it | --- @@ -284,7 +269,7 @@ When the user asks to "run a retrospective", "extract learnings from this sessio 1. Review conversation history for extractable knowledge. 2. Search existing memories following Step 2 of the Extraction Workflow. -3. Filter candidates through Stage A's forcing-function gate and the Capture Rules. Drop anything that fails the forcing-function (the code already drives the behavior), Rule 1 (no project tie), Rule 2 (names an engineer), or Rule 3 (preference without project evidence). +3. Filter candidates through Step 1 — Stage A's forcing-function gate and Stage B's Capture Rules. 4. Save the top 1–3 highest-value candidates that pass, following Step 4's pre-`Write` checks. The cap is deliberate: a long session can yield many qualifying memories, and three is the most worth adding at once — the gates decide what is eligible, the cap decides how many land per session. Note any you set aside. 5. Report what was created and why in a brief summary. diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 6073e6b..c3d28fa 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -67,9 +67,8 @@ Evaluate each memory against the criteria below, grouped into three: ### Group A — Capture Rules, restated #### A.1 Tied to at least one project (Capture Rule 1) -- Does the memory's content stay tied to a real project named in `Applies to:` — its codepaths, architecture, build/deploy setup, test strategy, tooling choices, team workflow, or recurring implementation patterns? -- DROP memories that duplicate **public** documentation anyone could look up (language reference, public CLI/API docs, framework README) and memories whose content has no real tie to any project in `Applies to:`. -- KEEP summaries of **internal** docs (Confluence, ADRs, RFCs, wiki). If an internal-doc summary lacks a `References:` link back to the source, flag for UPDATE (add the link) rather than DROP. +- DROP memories that duplicate **public** documentation, or that have no real tie to any project in `Applies to:`. +- KEEP summaries of **internal** docs. One that lacks a `References:` link to its source is UPDATE (add the link), not DROP. - Apply the strip-the-anchors test: @@ -79,13 +78,10 @@ Evaluate each memory against the criteria below, grouped into three: If the test fails, recommend DROP — or UPDATE only if a rewrite around the actual project anchor produces something genuinely project-specific. #### A.2 Anonymous (Capture Rule 2) -- Does the memory name specific engineers, GitHub/Slack handles, or emails anywhere (problem, example, footnote)? -- Does it narrate "who investigated whom" or "who fixed what"? +- Look for names, handles, emails, and "who fixed what" narration anywhere in the memory. - **Verdict:** UPDATE to strip the identifier entirely (describe the artifact, not the actor) when the underlying knowledge is still useful; DROP when the identifier *is* the content and removing it leaves nothing. #### A.3 Project pattern, not personal preference (Capture Rule 3) -- Is the memory a `decision_` backed by real evidence the pattern is the project's? Valid evidence: consistent use in the codebase, lint/formatter config, style guide / docs, or a team agreement (written *or* verbal — not every agreement is in a doc). -- Red-flag phrases inside the memory: *"I prefer,"* *"I like,"* *"my style."* - 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. @@ -276,6 +272,4 @@ Knowledge base reduced from 42 → 34 files. - **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. This is about doubt over a memory's *value*. Doubt over a *fact you could not check* is different — an unverified claim is a reason to ask, not to delete. -- **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. From 6cbf67361605a102dbc899239eba001ac6caee4d Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Mon, 28 Sep 2026 12:58:55 +0200 Subject: [PATCH 3/5] Prompt the activator to correct stale memories, not only capture new ones - Trigger continuous-learning when a memory read proves wrong or outdated - Keep the reminder at the same length since it runs on every prompt --- hooks/memory-loop-activator.sh | 6 +++--- tests/fixtures/hook-messages.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hooks/memory-loop-activator.sh b/hooks/memory-loop-activator.sh index 36ebed1..a079746 100644 --- a/hooks/memory-loop-activator.sh +++ b/hooks/memory-loop-activator.sh @@ -9,9 +9,9 @@ If this starts a new sub-task or phase (tests, refactor, deploy, etc.) If you hit an unexpected error or are about to debug/diagnose → search the KB FIRST, before reasoning from scratch. -If this session produced reusable knowledge worth preserving, invoke -Skill(continuous-learning) — the skill evaluates and routes the knowledge -to the correct destination. Do not ask permission. +If this session produced reusable knowledge, or showed a memory is wrong +or outdated, invoke Skill(continuous-learning) — it routes the change to +the correct destination. Do not ask permission. NEVER write to .claude/memories/ directly. Only the continuous-learning and memory-audit skills write there, so the routing and quality gates run. diff --git a/tests/fixtures/hook-messages.json b/tests/fixtures/hook-messages.json index d39b55d..cdd1b74 100644 --- a/tests/fixtures/hook-messages.json +++ b/tests/fixtures/hook-messages.json @@ -18,5 +18,5 @@ "additionalContext": "This project keeps a knowledge base of past learnings, decisions, and\ndebugging discoveries in .claude/memories/, searchable with\nmcp__memory-loop__query.\n\n- If your prompt contains a \"KB context:\" block, treat it as established\n ground truth. Verify it against the code, but do NOT search the KB and\n do NOT re-derive it.\n- Otherwise, if you are about to read or grep more than a couple of files,\n issue ONE mcp__memory-loop__query for the topic of your task first — a\n lex line of terms you expect verbatim plus a vec line phrased as a\n question, and an intent saying what you want and what to avoid, with\n rerank:false and limit:6. One search is far cheaper than a blind\n file sweep. Unlike the main thread, do not try keyword variations: if\n nothing relevant comes back, move on to the code.\n- Snippets are leads, not evidence — they are capped at 300 characters and\n often show only a title. mcp__memory-loop__get a document before relying\n on what it says.\n- Report back anything the KB got wrong or left out." } }, - "activator": "MANDATORY MEMORY PROTOCOL\n\nIf this starts a new sub-task or phase (tests, refactor, deploy, etc.)\n→ search the KB via mcp__memory-loop__query for relevant patterns first.\n\nIf you hit an unexpected error or are about to debug/diagnose\n→ search the KB FIRST, before reasoning from scratch.\n\nIf this session produced reusable knowledge worth preserving, invoke\nSkill(continuous-learning) — the skill evaluates and routes the knowledge\nto the correct destination. Do not ask permission.\n\nNEVER write to .claude/memories/ directly. Only the continuous-learning\nand memory-audit skills write there, so the routing and quality gates run.\n" + "activator": "MANDATORY MEMORY PROTOCOL\n\nIf this starts a new sub-task or phase (tests, refactor, deploy, etc.)\n→ search the KB via mcp__memory-loop__query for relevant patterns first.\n\nIf you hit an unexpected error or are about to debug/diagnose\n→ search the KB FIRST, before reasoning from scratch.\n\nIf this session produced reusable knowledge, or showed a memory is wrong\nor outdated, invoke Skill(continuous-learning) — it routes the change to\nthe correct destination. Do not ask permission.\n\nNEVER write to .claude/memories/ directly. Only the continuous-learning\nand memory-audit skills write there, so the routing and quality gates run.\n" } From 2d4e8a0c723ddea65cb311d952d53944a6ba1e95 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Mon, 28 Sep 2026 13:45:20 +0200 Subject: [PATCH 4/5] Harden the memory audit against lenient and unverified verdicts - Require a Drives column and a calibration stop that challenges proposed KEEPs - Search unmerged remote branches before calling a missing symbol stale - Keep verdicts and prose rewrites behind per-batch approval, never delegated --- skills/memory-audit/SKILL.md | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index c3d28fa..aa00630 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -117,7 +117,7 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - Use `Glob` to verify that referenced files or modules still exist. - 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. -- **An empty grep is not proof the symbol never existed.** It may live on an unmerged branch, or the grep may have matched only the memory's own text. Where you cannot confirm a symbol in current source, that is the UPDATE-uncertain verdict, not DROP: say in the rationale what you could not confirm, and let the user decide. +- **An empty grep is not proof the symbol never existed.** It may live on an unmerged branch, the grep may have matched only the memory's own text, or the search may not have run at all — confirm its exit status rather than discarding stderr. When a claim does not match current source, list the remote branches with `git rev-parse --symbolic --remotes`, then `git grep` the symbol across them in a separate call, without fetching. A hit outside the default branch is unmerged work, not staleness: name the branch in the rationale and ask whether it is still in flight (UPDATE to add `Pending:`) or abandoned. Where you still cannot confirm a symbol, that is the UPDATE-uncertain verdict, not DROP: say in the rationale what you could not confirm, and let the user decide. #### 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. @@ -174,6 +174,8 @@ The categories below are the recurring concrete shapes of B.1 (forcing-function) > **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`). +> **Verdicts are not delegated.** Sub-agents may gather facts for a batch — each memory's claims marked found, missing, or contradicted, with evidence — but never KEEP/UPDATE/DROP, a category, or a rationale; say so in their prompt. Read every memory yourself and reach each verdict here: a verdict handed over in writing anchors whoever reviews it. + > **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. ### Step 1: Inventory @@ -191,33 +193,36 @@ If the directory is missing or empty, report the situation (specify whether it d **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. - Spot-check any line numbers and historical line counts; flag stale ones for UPDATE. +- Read each memory's `Related:` entries, and search the KB on its topic, before judging duplication or contradiction (C.2) — an overlap found while editing arrives after the verdict it should have changed. - Watch for `Applies to:` typos (e.g. `mcs-2` when the project is `mcs`) — quick one-line fixes. - Check memories carrying a `**Pending:**` line against the default branch first (resolve the ref as capture's Check 3 does). Merged → UPDATE to the merged state: remove the statement it superseded, the conditional phrasing, and the `Pending:` line. Still in flight → leave it. Abandoned → UPDATE to remove the conditional part, or DROP if nothing else remains. Read memories in batches (10-15 at a time) and produce a verdict table for each batch: ``` -| # | File | Verdict | Rationale | -|---|------|---------|-----------| -| 1 | learning_background_task_watchdog.md | KEEP | Project-specific debugging discovery; future sessions act on the avoidance pattern (B.1) | -| 2 | learning_cli_tool_flags.md | DROP | Generic third-party CLI reference, no project anchor (A.1) | -| 3 | learning_auth_cache_bug.md | UPDATE | Problem section names an engineer — strip the identifier, keep the symptom (A.2) | -| 4 | decision_codestyle_tabs.md | DROP | Personal preference with no lint rule, formatter config, or team agreement (A.3) | -| 5 | decision_codestyle_naming.md | UPDATE | Convention still valid but example uses old API (C.4) | +| # | File | Verdict | Drives | Rationale | +|---|------|---------|--------|-----------| +| 1 | learning_background_task_watchdog.md | KEEP | Moves long work off the watchdog-timed thread instead of raising the timeout | Project-specific debugging discovery (B.1) | +| 2 | learning_cli_tool_flags.md | DROP | — | Generic third-party CLI reference, no project anchor (A.1) | +| 3 | learning_auth_cache_bug.md | UPDATE | Clears the auth cache on account switch, which the code does not signal | Problem section names an engineer — strip the identifier, keep the symptom (A.2) | +| 4 | decision_codestyle_tabs.md | DROP | — | Personal preference with no lint rule, formatter config, or team agreement (A.3) | +| 5 | decision_codestyle_naming.md | UPDATE | Names new types by the convention the formatter cannot enforce | Convention still valid but example uses old API (C.4) | ``` **Verdict definitions:** -- **KEEP** — Memory is relevant, actionable, well-structured, and not duplicated. No changes needed. +- **KEEP** — Memory is relevant, actionable, well-structured, and not duplicated. No changes needed. **Drives** names what a future session would do differently because it exists; "claims verified" is a fact-check, not an answer (B.1). - **DROP** — Memory is stale, duplicated, generic, or no longer applicable. Recommend deletion. - **UPDATE** — Memory has value but needs fixes: rename to follow conventions, merge with another memory, refresh stale references, or improve structure. -For UPDATE verdicts, briefly describe what needs to change. +For UPDATE verdicts, briefly describe what needs to change, and fill **Drives** for the memory *after* the fix. Apply the strip-the-anchors test to what the fix leaves: an UPDATE that trims narrative or stale numbers off a generic core is a DROP. ### Step 3: User Review — HARD STOP **This is a blocking gate. After printing the verdict table, stop. Do not call any tool. Do not start the next batch. Do not run any `Edit`, `Write`, `Bash(rm)`, or `Bash(mv)`. Wait for the user's reply.** +If a hook propagates memory changes (e.g. an autopush on turn end), say so with the table: approving the batch publishes it. + The user must explicitly respond before you do anything else. Acceptable signals to proceed: - Explicit approval ("approved," "go ahead," "looks good," "yes," "proceed"). - Per-item overrides ("keep #2, drop the rest," "change #3 to UPDATE"). @@ -229,6 +234,8 @@ Things that are **not** approval: - A previous batch's approval — approval is per-batch, never carried forward. - A general "do an audit" instruction at the start of the session — that authorizes the audit, not any specific verdict. +**Calibration stop.** When the audit runs past two batches, after executing the second one and before assessing the third, list every KEEP you proposed so far — not memories the user kept by overriding a DROP — with the strongest case for dropping it, and wait for the user. Leniency is invisible from inside a batch; this is where it gets challenged. + If the user goes quiet after a batch, do not move on. End the turn. The user resumes when ready. Respect every override without arguing — the user knows their workflow better than any heuristic. If you disagree with an override, note it once for the record and apply the override anyway. @@ -237,14 +244,14 @@ 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. -- **DROP**: Delete the file with `Bash(rm )` +- **DROP**: Re-read the file and confirm the rationale still holds — for a "covered by X" DROP, that X actually carries the content — then delete it with `Bash(rm )`. If it no longer holds, tell the user before deleting — the approval rested on that rationale. - **UPDATE (rename)**: Rename with `Bash(mv )` - **UPDATE (content)**: Use `Edit` or `Write` to update the file. Rewrite a contradicted statement where it stands rather than appending a correction, then re-read the whole file to catch a broken splice. - **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.** 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. -Report what was done after each batch. +Report what was done after each batch. When a later batch contradicts a memory an earlier batch already handled, reopen that memory with the user rather than letting the earlier verdict stand. ### Step 5: Summary @@ -272,4 +279,4 @@ Knowledge base reduced from 42 → 34 files. - **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. This is about doubt over a memory's *value*. Doubt over a *fact you could not check* is different — an unverified claim is a reason to ask, not to delete. -- **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. +- **End-of-audit check for broken cross-links.** After DROPs and renames land, grep every memory's full text — not only `Related:` / `References:` lines — for the old filenames. Remove the pointers — broken refs accumulate silently otherwise. Where prose leaned on the dropped memory ("see the related memory"), propose the rewrite as an UPDATE and wait for approval like any other. From e539af4be483be8e11078f243849350e3b145f5d Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Mon, 28 Sep 2026 13:54:12 +0200 Subject: [PATCH 5/5] Keep the audit's unmerged-branch search tractable on large repos - Narrow remote refs by the memory's ticket or feature word, excluding HEAD - Search a batch's unconfirmed symbols in one git grep call - Treat git grep exit 128 as an error, not a negative --- skills/memory-audit/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index aa00630..51797b6 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -117,7 +117,7 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act - Use `Glob` to verify that referenced files or modules still exist. - 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. -- **An empty grep is not proof the symbol never existed.** It may live on an unmerged branch, the grep may have matched only the memory's own text, or the search may not have run at all — confirm its exit status rather than discarding stderr. When a claim does not match current source, list the remote branches with `git rev-parse --symbolic --remotes`, then `git grep` the symbol across them in a separate call, without fetching. A hit outside the default branch is unmerged work, not staleness: name the branch in the rationale and ask whether it is still in flight (UPDATE to add `Pending:`) or abandoned. Where you still cannot confirm a symbol, that is the UPDATE-uncertain verdict, not DROP: say in the rationale what you could not confirm, and let the user decide. +- **An empty grep is not proof the symbol never existed.** It may live on an unmerged branch, the grep may have matched only the memory's own text, or the search may not have run at all — confirm its exit status rather than discarding stderr (for `git grep`, 1 is no match, 128 is an error). When a claim does not match current source, list remote branches with `git rev-parse --symbolic --exclude='*/HEAD' --remotes='**'`, the anchor being a ticket or feature word from the memory, then `git grep` them in a separate call, without fetching — one call per batch, with `-e` per unconfirmed symbol. On a large repo each branch searched costs time, so widen to every branch only when the memory has no anchor, and tell the user the cost first. A hit outside the default branch is unmerged work, not staleness: name the branch in the rationale and ask whether it is still in flight (UPDATE to add `Pending:`) or abandoned. Where you still cannot confirm a symbol, that is the UPDATE-uncertain verdict, not DROP: say in the rationale what you could not confirm, and let the user decide. #### 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.