From 60b18c67ff0f97c9d09f4b29ec4956865c1e7742 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Thu, 8 Oct 2026 18:13:00 +0200 Subject: [PATCH] Tighten memory capture rules and run capture once per turn - Session-written code no longer counts as evidence; claims must change work in the reading project - Fold same-trigger knowledge into existing memories or skip; new memories print a Drives line - Pending names a PR or ticket; updates refresh the file report-only; no-VCS fallback in both skills --- SYNC-BLOCKS.md | 4 +-- hooks/memory-loop-activator.sh | 9 +++--- skills/continuous-learning/SKILL.md | 32 +++++++++++++------ .../references/templates.md | 2 +- skills/memory-audit/SKILL.md | 14 +++++--- tests/fixtures/hook-messages.json | 2 +- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index 12ea99b..425da61 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -20,9 +20,9 @@ Locations: Every memory must satisfy all three rules. -- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. +- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. Every claim must also change how a session works in a project whose sessions read this KB. Listing another project in `Applies to:` does not make its internals relevant: knowledge of another project's internals is cut down to the conclusion that changes work in the reading project. A claim about another project counts as checked only if the session read that project's code, or an internal doc cited in `References:`. - **Anonymous.** No personal names, GitHub/Slack handles, or emails anywhere in the memory — not in the problem description, not in examples, not in narration of "who did what." Describe the artifact (the bug, the pattern, the decision), not who touched it. Identifiers age badly and add no signal even in a single-user KB. -- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, session-level consensus all count), **or** already used consistently in the codebase. Codebase usage is the strongest evidence. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. +- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, or the user stating the agreement in the session), **or** already used consistently in the codebase. Codebase usage is the strongest evidence that a pattern exists, but code written in the same session is no evidence at all: the pattern needs a precedent from before the session, a lint rule or ADR, or a stated agreement. Usage alone does not make the pattern worth a memory, because the code already conveys it — unless new code could break it unnoticed. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. - **Bad patterns present in the code** are handled by category, not by exclusion. If one engineer flags a pattern as bad without team ratification, the appropriate shape is a `learning_` warning (e.g. `learning_dont_use_X_because_Y`) — **only** when it carries trigger (*"when you use X in case Y…"*), symptom (*"…it leaks / races / drops data"*), and avoidance (*"use Z instead"*). If the team has agreed the pattern is bad and should be avoided or replaced, the team agreement itself makes it a `decision_` (e.g. `decision_architecture_deprecate_X`). Pure *"this should be refactored someday"* observations without that shape belong in the issue tracker. ``` diff --git a/hooks/memory-loop-activator.sh b/hooks/memory-loop-activator.sh index 888d862..d07545c 100644 --- a/hooks/memory-loop-activator.sh +++ b/hooks/memory-loop-activator.sh @@ -2,9 +2,8 @@ cat << 'EOF' Memory: search the KB before a new phase, and on an unexpected error -before diagnosing it from scratch. Run Skill(continuous-learning) when -a cause is found or a decision made, and before your final reply for a -task (fix, refactor, investigation, commit, PR), if it produced reusable -knowledge. Run it as soon as a memory proves wrong, outdated or -incomplete. Never write .claude/memories/ directly. +before diagnosing it from scratch. Before your final reply, run +Skill(continuous-learning) once if this turn found a non-obvious cause, +settled a project decision the user made or agreed to, or showed a +memory is wrong or outdated. Never write .claude/memories/ directly. EOF diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index d8f1e9e..2e1a45f 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -9,6 +9,7 @@ description: > allowed-tools: - Read - Glob + - Grep - WebSearch - Write(.claude/memories/**) - Edit(.claude/memories/**) @@ -28,6 +29,8 @@ Evaluate reusable knowledge from work sessions and route it: codebase knowledge > **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). +> **Without version control.** Every git step applies only when the relevant root — the project for code claims, the memories folder for history — is under version control. Otherwise check the working tree with Grep/Glob, skip `Pending:` handling, and say which you did. Without VCS the working tree is the only state, so a claim missing there is wrong, not pending. + ## Memory Categories ### Learnings (`learning__`) @@ -77,9 +80,9 @@ Apply these rules at save time. A draft that fails any rule is not saved (or is Every memory must satisfy all three rules. -- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. +- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. Every claim must also change how a session works in a project whose sessions read this KB. Listing another project in `Applies to:` does not make its internals relevant: knowledge of another project's internals is cut down to the conclusion that changes work in the reading project. A claim about another project counts as checked only if the session read that project's code, or an internal doc cited in `References:`. - **Anonymous.** No personal names, GitHub/Slack handles, or emails anywhere in the memory — not in the problem description, not in examples, not in narration of "who did what." Describe the artifact (the bug, the pattern, the decision), not who touched it. Identifiers age badly and add no signal even in a single-user KB. -- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, session-level consensus all count), **or** already used consistently in the codebase. Codebase usage is the strongest evidence. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. +- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, or the user stating the agreement in the session), **or** already used consistently in the codebase. Codebase usage is the strongest evidence that a pattern exists, but code written in the same session is no evidence at all: the pattern needs a precedent from before the session, a lint rule or ADR, or a stated agreement. Usage alone does not make the pattern worth a memory, because the code already conveys it — unless new code could break it unnoticed. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. - **Bad patterns present in the code** are handled by category, not by exclusion. If one engineer flags a pattern as bad without team ratification, the appropriate shape is a `learning_` warning (e.g. `learning_dont_use_X_because_Y`) — **only** when it carries trigger (*"when you use X in case Y…"*), symptom (*"…it leaks / races / drops data"*), and avoidance (*"use Z instead"*). If the team has agreed the pattern is bad and should be avoided or replaced, the team agreement itself makes it a `decision_` (e.g. `decision_architecture_deprecate_X`). Pure *"this should be refactored someday"* observations without that shape belong in the issue tracker. @@ -95,7 +98,7 @@ 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 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]`. +- **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. It fails outright when the error or compiler message already names the cause, and for a "use X for Y" memory when X is already the dominant way the code does Y. 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. @@ -124,8 +127,10 @@ 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, 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. +2. **It shares a trigger with an existing memory** — one you read and judged on-topic, not merely a search hit. Fold it in, following **Update existing** in Step 4, or skip; when unsure, skip. If this session already saved or edited that memory, edit it again only to fix a contradiction. +3. **It has a different trigger.** Save a new memory only if it passes Stage A on its own: would a future session holding only this text act differently? Otherwise skip. Link the neighbor with `Related:` in the new memory only; edit the neighbor only when it is wrong without the new memory. + +When one run yields several candidates, compare them with each other as well as with the KB: candidates that share a trigger are one memory. Each memory that survives runs every check on its own, with its own search and printed lines. 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. @@ -157,6 +162,10 @@ When a memory genuinely applies to multiple projects, list them comma-separated 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. +For a new memory, first print what it changes — Stage A's answer, made visible: + +- **Drives:** what a future session does differently because this memory exists. *"Claims verified"* or a restatement of the content is not an answer. If two memories in one run print the same behavior, merge them. + **Check 1: Strip-the-anchors (routing).** @@ -184,11 +193,11 @@ Under version control, verify the memory's central claim against the default bra - **Default branch:** `holds` | `pending ()` | `no VCS` -**`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. +**`pending` only when** the change is in flight and has a PR or ticket to name. A claim missing from the default branch with neither is wrong or not ready, 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. +**How to write it:** phrase the claim conditionally (*"once X lands…"*) and add `**Pending:** ` on the line after `Applies to:` — never a branch name or a description of the change. Record what stays true after the merge, not the surface of the unmerged API. 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):** +**Save (only after Checks 1 and 2 pass and the Drives line and Check 3 are printed):** ``` Write(file_path: "/.claude/memories/__.md", content: "") ``` @@ -196,8 +205,9 @@ 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. +2. Fold the new text into the section it belongs to, and rewrite a contradicted or superseded statement where it stands. Never append a section for a refinement or a correction — a `Related:` note is not a correction either. If Check 3 printed `pending`, keep the statement and add the conditional one beside it. +3. Refresh the rest of the file in the same edit. If its `Pending:` change has merged, fold it in: remove the superseded statement, the conditional phrasing, and the `Pending:` line. Check its backticked symbols with one `git grep` against the default branch, and its `Related:` targets for existence. Report a missing symbol or broken link rather than fixing or deleting it — removing content is the audit's job, after approval. Print one line: + - **Refresh:** `pending merged -> folded` | `pending open` | `no pending` | `pending n/a (no VCS)`; `symbols /` (missing ones named); `links ok` | `links broken: ` 4. Re-read the whole file afterwards to catch a broken splice. ``` @@ -236,6 +246,8 @@ Anti-examples, generalized — do not create memories like these: | 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 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. | +| Description of code written this session | "What the new `ReportPublisher` chain emits and in which order", saved from the session that wrote it | **[Forcing-function]** Merged, the code explains itself; unmerged, it is not evidence (Rule 3). A trap found while writing it qualifies only with trigger, symptom, and avoidance. | +| Fault in one engineer's environment | "Backend rejects signed requests because this machine's clock drifts with NTP blocked" | **[Rule 1]** Not project behavior, even with project anchors — it is that engineer's `CLAUDE.local.md` note. | | 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. | When the underlying knowledge *is* salvageable, rewrite before saving: diff --git a/skills/continuous-learning/references/templates.md b/skills/continuous-learning/references/templates.md index b9125c5..926d4b6 100644 --- a/skills/continuous-learning/references/templates.md +++ b/skills/continuous-learning/references/templates.md @@ -2,7 +2,7 @@ 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). +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 diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index 51797b6..2a8d887 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -27,6 +27,8 @@ Over time, memory files accumulate — some become stale, some duplicate each ot > **This skill is user-initiated only.** Never run it automatically or as part of another workflow. +> **Without version control.** Every git step applies only when the relevant root — the project for code claims, the memories folder for history — is under version control. Otherwise check the working tree with Grep/Glob, skip `Pending:` handling, and say which you did. Without VCS the working tree is the only state, so a claim missing there is wrong, not pending. + --- ## Capture Rules @@ -36,9 +38,9 @@ Apply these rules at audit time. A memory that fails any rule is a candidate for Every memory must satisfy all three rules. -- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. +- **Tied to at least one project.** The content must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project named in `Applies to:`. Multi-project entries are fine when the same convention genuinely holds across several repos, listed comma-separated. Out of scope: free-floating language, framework, or CLI knowledge with no project anchor — that belongs in the tool's own docs. Public documentation anyone could look up (language reference, framework README, public CLI docs, public API reference) is also out. Internal project docs (Confluence pages, ADRs, RFCs, team wiki) are different: a memory summarizing one *is* project knowledge, provided it links back to the source in `References:`. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → the memory does not qualify. Every claim must also change how a session works in a project whose sessions read this KB. Listing another project in `Applies to:` does not make its internals relevant: knowledge of another project's internals is cut down to the conclusion that changes work in the reading project. A claim about another project counts as checked only if the session read that project's code, or an internal doc cited in `References:`. - **Anonymous.** No personal names, GitHub/Slack handles, or emails anywhere in the memory — not in the problem description, not in examples, not in narration of "who did what." Describe the artifact (the bug, the pattern, the decision), not who touched it. Identifiers age badly and add no signal even in a single-user KB. -- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, session-level consensus all count), **or** already used consistently in the codebase. Codebase usage is the strongest evidence. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. +- **Project pattern, not personal preference.** Memories must capture what the *project* does, not what an individual engineer likes. A pattern qualifies when any of these hold: it is enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — chat, meeting, or the user stating the agreement in the session), **or** already used consistently in the codebase. Codebase usage is the strongest evidence that a pattern exists, but code written in the same session is no evidence at all: the pattern needs a precedent from before the session, a lint rule or ADR, or a stated agreement. Usage alone does not make the pattern worth a memory, because the code already conveys it — unless new code could break it unnoticed. If the only support is *"I prefer,"* *"I like,"* *"my style,"* it is a preference and does not qualify. - **Bad patterns present in the code** are handled by category, not by exclusion. If one engineer flags a pattern as bad without team ratification, the appropriate shape is a `learning_` warning (e.g. `learning_dont_use_X_because_Y`) — **only** when it carries trigger (*"when you use X in case Y…"*), symptom (*"…it leaks / races / drops data"*), and avoidance (*"use Z instead"*). If the team has agreed the pattern is bad and should be avoided or replaced, the team agreement itself makes it a `decision_` (e.g. `decision_architecture_deprecate_X`). Pure *"this should be refactored someday"* observations without that shape belong in the issue tracker. @@ -103,7 +105,7 @@ If the test fails, recommend DROP — or UPDATE only if a rewrite around the act #### C.2 Duplication - Does this memory overlap significantly with another memory? - Could two or more memories be merged into one stronger entry? -- If overlap is partial and the memories are genuinely distinct (different root causes, different scopes, or different categories like a `learning_` warning next to a `decision_` that resolved it), recommend UPDATE to cross-link them via `Related:` instead of merging. +- If overlap is partial and the memories are genuinely distinct (different root causes, different scopes, or different categories like a `learning_` warning next to a `decision_` that resolved it), recommend UPDATE to cross-link them via `Related:` instead of merging — one way, from the memory that builds on the other. - 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 @@ -117,7 +119,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 (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. +- **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:` naming its PR or ticket) 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. @@ -221,7 +223,7 @@ For UPDATE verdicts, briefly describe what needs to change, and fill **Drives** **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. +If a hook propagates memory changes (e.g. an autopush on turn end), say so with the table: approving the batch publishes it. If the memories folder is not under version control, say so too: DROPs and rewrites are permanent. The user must explicitly respond before you do anything else. Acceptable signals to proceed: - Explicit approval ("approved," "go ahead," "looks good," "yes," "proceed"). @@ -244,6 +246,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. +The per-prompt memory reminder does not apply while auditing: a memory found wrong goes into the batch, not to continuous-learning, and the approved edits below are this skill's own writes. + - **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. diff --git a/tests/fixtures/hook-messages.json b/tests/fixtures/hook-messages.json index 11402f5..2b57c5a 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": "Memory: search the KB before a new phase, and on an unexpected error\nbefore diagnosing it from scratch. Run Skill(continuous-learning) when\na cause is found or a decision made, and before your final reply for a\ntask (fix, refactor, investigation, commit, PR), if it produced reusable\nknowledge. Run it as soon as a memory proves wrong, outdated or\nincomplete. Never write .claude/memories/ directly.\n" + "activator": "Memory: search the KB before a new phase, and on an unexpected error\nbefore diagnosing it from scratch. Before your final reply, run\nSkill(continuous-learning) once if this turn found a non-obvious cause,\nsettled a project decision the user made or agreed to, or showed a\nmemory is wrong or outdated. Never write .claude/memories/ directly.\n" }