From ff7ae532b4bce13f201bb74b6e7ecbdcdbdea213 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Tue, 5 May 2026 12:15:54 +0200 Subject: [PATCH 1/4] Sync capture rules across skills and harden audit consent - Lock Capture Rules / strip-the-anchors / Applies-to text verbatim across both skills via SYNC fences; CI workflow + maintainer SYNC-BLOCKS.md fail on drift - Memory-audit: regroup criteria into Groups A/B/C, add per-batch HARD STOP consent gate, cwd callout, audit-scope subsection - Continuous-learning: drop unused Task* tools, clarify retrospective runs autonomously, tag anti-example rows by rule, add two new forcing-function anti-examples --- .github/workflows/sync-blocks.yml | 44 ++++++++ SYNC-BLOCKS.md | 82 ++++++++++++++ skills/continuous-learning/SKILL.md | 94 ++++++++++------ skills/memory-audit/SKILL.md | 159 ++++++++++++++++++---------- techpack.yaml | 6 ++ 5 files changed, 298 insertions(+), 87 deletions(-) create mode 100644 .github/workflows/sync-blocks.yml create mode 100644 SYNC-BLOCKS.md diff --git a/.github/workflows/sync-blocks.yml b/.github/workflows/sync-blocks.yml new file mode 100644 index 0000000..c99132b --- /dev/null +++ b/.github/workflows/sync-blocks.yml @@ -0,0 +1,44 @@ +name: sync-blocks + +on: + pull_request: + paths: + - "skills/continuous-learning/SKILL.md" + - "skills/memory-audit/SKILL.md" + - "SYNC-BLOCKS.md" + - ".github/workflows/sync-blocks.yml" + push: + branches: [main] + paths: + - "skills/continuous-learning/SKILL.md" + - "skills/memory-audit/SKILL.md" + - "SYNC-BLOCKS.md" + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Verify SYNC blocks are byte-identical across both skills + run: | + set -euo pipefail + A=skills/continuous-learning/SKILL.md + B=skills/memory-audit/SKILL.md + fail=0 + for tag in capture-rules strip-the-anchors applies-to; do + echo "=== $tag ===" + if diff -u \ + <(awk "//,//" "$A") \ + <(awk "//,//" "$B"); then + echo "OK: identical" + else + echo "DRIFT in $tag" + fail=1 + fi + done + if [ "$fail" -ne 0 ]; then + echo + echo "One or more SYNC blocks have drifted. Update SYNC-BLOCKS.md and copy the canonical text into both skills." + exit 1 + fi diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md new file mode 100644 index 0000000..d795fbd --- /dev/null +++ b/SYNC-BLOCKS.md @@ -0,0 +1,82 @@ +# Sync Blocks (maintainer-only) + +This file is **not loaded by any skill**. It is the canonical source for content that must remain verbatim-identical across `skills/continuous-learning/SKILL.md` and `skills/memory-audit/SKILL.md`. + +When you edit one of the blocks below, update this file first, then copy the new text into every listed location. Both skills mark synced sections with HTML-comment fences (`` … ``) so a one-liner can verify byte-equality: + +```sh +diff \ + <(awk '//,//' skills/continuous-learning/SKILL.md) \ + <(awk '//,//' skills/memory-audit/SKILL.md) +``` + +Repeat for `strip-the-anchors` and `applies-to`. + +The locked blocks are written in neutral voice — they describe what qualifies as a memory, not what to do with one. Each skill prepends/appends its own one-line framing **outside** the SYNC fences (capture says "do not save"; audit says "recommend DROP"). Do not move action verbs inside the locked block. + +--- + +## Block 1: Capture Rules + +Locations: +- `skills/continuous-learning/SKILL.md` — `## Capture Rules` +- `skills/memory-audit/SKILL.md` — `## Capture Rules` + +```markdown + +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. +- **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. + - **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. + +``` + +--- + +## Block 2: Strip-the-anchors test + +Locations: +- `skills/continuous-learning/SKILL.md` — inside Step 4 (pre-save Check 1) +- `skills/memory-audit/SKILL.md` — inside criterion 1 + +```markdown + +**Strip-the-anchors test.** Mentally delete every project-specific reference (paths, symbols, endpoints, business logic, ticket prefixes, instance IDs, custom-field IDs, internal CLI flags) from the memory's content. What is left is the *substance*. If the substance is a useful standalone document — generic tool, language, or framework knowledge that would help any reader anywhere — the project tie was decoration and the memory does not qualify as project knowledge. **Internal or proprietary tools are not exempt:** how a private CLI, MCP server, GUI, or company-internal tool *works in general* belongs in the tool's own docs or in `CLAUDE.local.md`. Project endpoints sprinkled inside a tool how-to do not make it project knowledge. + +``` + +--- + +## Block 3: `Applies to:` semantics + +Locations: +- `skills/continuous-learning/SKILL.md` — inside Step 4 (`### Applies to` subsection) +- `skills/memory-audit/SKILL.md` — inside the `Applies to:` callout near the top + +```markdown + +**The `Applies to:` field.** The first line of every memory 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 `library:` parameter used for `search_docs`, 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. + +``` + +--- + +## Drift verification (CI-ready) + +```sh +#!/usr/bin/env bash +set -euo pipefail +A=skills/continuous-learning/SKILL.md +B=skills/memory-audit/SKILL.md +for tag in capture-rules strip-the-anchors applies-to; do + diff -u \ + <(awk "//,//" "$A") \ + <(awk "//,//" "$B") \ + || { echo "DRIFT in $tag"; exit 1; } +done +echo "all sync blocks identical" +``` diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index bd14561..7d77223 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -6,14 +6,14 @@ 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__docs-mcp-server__search_docs, mcp__docs-mcp-server__list_libraries, TaskCreate, TaskUpdate, TaskList +allowed-tools: Write, Read, Glob, Edit, Bash, WebSearch, mcp__docs-mcp-server__search_docs, mcp__docs-mcp-server__list_libraries --- # 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. -> **Note:** `` refers to the current working directory (project root) throughout this document. When calling `search_docs`, the `library:` parameter is the root directory name (e.g., for `/Users/me/dev/my-app`, use `library: "my-app"`) — this is set automatically by the indexing hook and stays folder-based. The `Applies to:` field inside memory content is **different**: it identifies the repo, not the local checkout, so it survives clones into different folder names. See [Step 4](#step-4-structure-and-save). +> **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). ## Memory Categories @@ -59,12 +59,16 @@ Deliberate choices about how the project should work. ## Capture Rules -Every memory must satisfy all three rules, regardless of whether the KB is used by one engineer or shared with a team. +Apply these rules at save time. A draft that fails any rule is not saved (or is rewritten until it qualifies). The same rules apply whether the KB is used by one engineer or shared with a team. -- **Tied to at least one project.** The memory must be about the architecture, conventions, bugs, workflows, or tool interactions of at least one real project, named in `Applies to:`. Multi-project captures are fine: if the same convention genuinely holds across several repos, list them comma-separated. What's excluded is free-floating language, framework, or CLI knowledge with no project anchor — even when it felt like a discovery in the moment; that belongs in the tool's own docs, not here. **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: summarizing one into a memory *is* project knowledge, provided the memory links back to the source in `References:` so it doesn't silently drift. The test is *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → skip. -- **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. Omit the actor; do not invent a role for them. Applies even in a single-user KB — identifiers age badly and add no signal. -- **Project pattern, not personal preference.** Capture what the *project* does, not what the engineer driving the session likes. A pattern qualifies when any of these hold: it's enforced by lint/formatter config, documented in a style guide or ADR, agreed by the team (written *or* verbal — Slack, meeting, session-level consensus all count), **or** already used consistently in the codebase. The codebase itself is the strongest evidence — if the pattern is demonstrably present in existing code, it's a pattern. If none of those hold and the only support is *"I prefer,"* *"I like,"* *"my style,"* it's a preference — do not save. When in doubt, the project's existing patterns win over the engineer's taste. - - **Bad patterns present in the code** are handled by category, not by blocking the capture. If one engineer flags a pattern as bad without team ratification, save a `learning_` warning (e.g. `learning_dont_use_X_because_Y`) — **only** when the warning has actionable shape: 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 an actionable shape don't belong in the KB — they belong in the issue tracker. + +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. +- **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. + - **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. + ## Extraction Workflow @@ -82,13 +86,13 @@ After completing any task, evaluate in two stages. If NO to all → skip. Otherwise continue to Stage B. -**Stage B — Apply the Capture Rules above. Each is a hard gate; all three must pass.** +**Stage B — Apply the three Capture Rules above as hard gates. All three must pass.** Enforcement maps: -1. **Project-tied** (Rule 1). Run the **strip-the-anchors test**: mentally delete every project-specific reference (paths, symbols, endpoints, business logic) from the draft. If what's left is still worth saving on its own, the project tie was decoration and the substance is tool/tooling knowledge — skip, or rewrite so the project-specific part *is* the substance. **Internal or proprietary tools are not exempt:** how a private CLI, MCP server, GUI, or company-internal tool *works in general* belongs in the tool's own docs or in `CLAUDE.local.md`, never in project memory. Test phrasing: *"would another engineer cloning this repo on a fresh machine without my dev tools still find this useful?"* -2. **Anonymous** (Rule 2). Also enforced mechanically by the pre-save identifier scan in Step 4. -3. **Pattern, not preference** (Rule 3). Strongest evidence: consistent use in the codebase. Also valid: lint/formatter config, style guide, team agreement (written or verbal). +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 recorded team agreement. -All three must pass. If any fail, either rewrite the memory to satisfy them (e.g. anonymize an actor) or skip. Do not save partial-fit memories. +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 @@ -127,7 +131,15 @@ Research should **enrich** project-specific knowledge, not replace it. The goal Read [references/templates.md](references/templates.md) for template structures. For learnings, use the Learning Memory Template. For decisions, use the ADR-Inspired Template for complex trade-offs or the Simplified Template for straightforward, evidence-backed decisions. -**Fill in `Applies to`** at the top of every memory. Default to the **git repo name** — the last path segment of `git remote get-url origin`, with `.git` stripped (e.g. `git@github.com:mcs-cli/memory.git` → `memory`; `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 (`~/dev/memory` vs `~/work/mcs-memory`) while the repo name is stable; this is also why `Applies to:` may differ from the `library:` parameter used for `search_docs`. If the session made it clear the memory applies to multiple projects, list them comma-separated (e.g. `**Applies to:** web-dashboard, ios-app, api-backend`); keep the content generic enough to stay true in every listed project, and if a memory is only partially relevant to one, save two separate memories instead of one mixed memory. This field is informational — it helps semantic search and makes the memory portable if it's later consolidated into a cross-project knowledge base. +#### Applies to + +Fill in `Applies to:` at the top of every memory. + + +**The `Applies to:` field.** The first line of every memory 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 `library:` parameter used for `search_docs`, 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 @@ -135,14 +147,24 @@ Run both checks as visible output before any `Write` to `/.claude/memor **Check 1: Strip-the-anchors (routing).** -Print, in two short lines: + +**Strip-the-anchors test.** Mentally delete every project-specific reference (paths, symbols, endpoints, business logic, ticket prefixes, instance IDs, custom-field IDs, internal CLI flags) from the memory's content. What is left is the *substance*. If the substance is a useful standalone document — generic tool, language, or framework knowledge that would help any reader anywhere — the project tie was decoration and the memory does not qualify as project knowledge. **Internal or proprietary tools are not exempt:** how a private CLI, MCP server, GUI, or company-internal tool *works in general* belongs in the tool's own docs or in `CLAUDE.local.md`. Project endpoints sprinkled inside a tool how-to do not make it project knowledge. + -- **Anchors stripped:** comma-separated list of every project-specific reference removed from the draft (paths, symbols, endpoints, ticket prefixes, instance IDs, custom-field IDs, internal CLI flags). If none → save is fine. -- **Substance without anchors:** one sentence describing what's left after stripping (e.g. *"how Jira's customfield API works"*, *"how to write a mock rule in tool X"*, *"the project's coordinator pattern"*). +Print, in two short lines, before the save: -If the substance line describes general/tool/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` yourself; the user decides. +- **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"*). -This shape forces the test to happen (you can't list anchors without finding them, can't describe the substance without evaluating it) without reprinting the full draft. +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. + +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. **Check 2: Personal-identifier scan.** @@ -181,16 +203,18 @@ Anti-examples, generalized — do not create memories like these: | Category | Concrete anti-example | Why it fails | |----------|-----------------------|--------------| -| Public tool / CLI reference | "`git rebase -i` opens an editor with a todo list" | Git's own docs cover this verbatim | -| Internal / proprietary tool reference | "How to write a mock rule in `` to return 500 for endpoint X, plus where the rules JSON lives on disk" | Tool mechanics — applies to any project using that tool, not specific to this codebase. Sprinkling project endpoints into the example doesn't make it project knowledge. Belongs in the tool's own docs or `CLAUDE.local.md`. | -| Documented language / framework behavior | "`$status` is read-only in zsh" | First hit in `man zshparam` — no project anchor | -| Public API reference | "GitHub API rate limit is 5000/hr authenticated" | Public API docs, no project-specific twist | -| Personal identifier | Problem section says *"`@alice` hit a cache bug"* | Rule 2 violation — names an engineer | -| Personal preference without project evidence | "Prefer early returns" with no lint rule, consistent codebase usage, or team agreement | Rule 3 violation — taste, not pattern | -| Historical record of a one-time shipped change | "We renamed folder `Install/` to `Sync/` after the command rename" | Once shipped, `git log` answers this. Future sessions read current code, not the migration story. The memory adds nothing actionable. | -| Generic engineering wisdom with a token project example | "Extract methods over condensing for lint compliance" with one PR cited | Strip the example — what's left is universal advice that fits any project. Belongs in a coding-style doc, not a per-project KB. | -| One-line rule that belongs in CLAUDE.md | A single-sentence convention with no Context / Options / Consequences | If it fits in one bullet under "Conventions" in CLAUDE.md, put it there. A standalone memory file is overhead for content that can't grow. | -| Naming/prefix decision once enforced | "We kept the `External` prefix on adapter types" | Once the type system, lint, or formatter enforces it, the decision lives in the code. Future sessions reads the code. | +| Public tool / CLI reference | "`git rebase -i` opens an editor with a todo list" | **[Rule 1]** Public docs cover this verbatim — no project anchor. | +| Internal / proprietary tool reference | "How to write a mock rule in `` to return 500 for endpoint X, plus where the rules JSON lives on disk" | **[Rule 1]** Tool mechanics — applies to any project using the tool. Sprinkling project endpoints into the example does not make it project knowledge. Belongs in the tool's own docs or `CLAUDE.local.md`. | +| Documented language / framework behavior | "`$status` is read-only in zsh" | **[Rule 1]** First hit in the language reference — no project anchor. | +| Public API reference | "Public Git hosting API rate limit is N/hr authenticated" | **[Rule 1]** Public API docs cover this — no project-specific twist. | +| Personal identifier | Problem section narrates a specific engineer hitting a cache bug | **[Rule 2]** Names an engineer. | +| Personal preference without project evidence | "Prefer early returns" with no lint rule, consistent codebase usage, or team agreement | **[Rule 3]** Taste, not pattern. | +| Historical record of a one-time shipped change | "We renamed folder `Install/` to `Sync/` after the command rename" | **[Forcing-function]** Once shipped, `git log` answers this. Future sessions read the current code, not the migration story. The memory drives no future behavior. | +| Generic engineering wisdom with a token project example | "Extract methods over condensing for lint compliance" with one PR cited | **[Rule 1]** Strip the example — what is left is universal advice that fits any project. Belongs in a coding-style doc, not a per-project KB. | +| One-line rule that belongs in CLAUDE.md | A single-sentence convention with no Context / Options / Consequences | **[Scope]** If it fits in one bullet under "Conventions" in CLAUDE.md, put it there. A standalone memory file is overhead for content that cannot grow. | +| Naming/prefix decision once enforced | "We kept the `External` prefix on adapter types" | **[Forcing-function]** Once the type system, lint, or formatter enforces it, the decision lives in the code. Future sessions read the code, not the memory. | +| One-time bug fix self-evident in current code | "Bug X used `dropFirst()`; we changed to a guarded check" | **[Forcing-function]** The fix is a small diff; the code reads correctly today. Save only if the bug class is recurring and the memory teaches the *avoidance pattern*, not the one fix. | +| 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. @@ -199,7 +223,7 @@ When the underlying knowledge *is* salvageable, rewrite before saving — or ski | Bad | Good | |-----|------| | Memory describes how a CLI flag works | *skip — that's tool documentation, not project knowledge* | -| Problem section says *"`@alice` hit a cache bug in auth"* | *"Auth flow hits a cache bug under condition X"* — drop the actor, keep the symptom | +| 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 | @@ -221,13 +245,15 @@ Before saving, check memory content against these rules: ## Retrospective Mode +Retrospective mode runs the same autonomous flow as incidental capture, applied retroactively to the session. Save silently, report results — do not ask the user to pick. + When the user asks to "run a retrospective", "extract learnings from this session", or similar: -1. Review conversation history for extractable knowledge -2. Search existing memories following Step 2 of the Extraction Workflow -3. List candidates with brief justifications — prioritize by the evaluation criteria in Step 1 (non-obvious investigation, architectural choices, established project conventions). Filter the list through the Capture Rules before presenting it — drop anything that's generic tool/language reference with no project tie, names an engineer, or is a personal preference without project evidence. -4. Extract top 1-3 highest-value memories -5. Report what was created and why +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). +4. Save the top 1–3 highest-value candidates that pass, following Step 4's pre-`Write` checks. +5. Report what was created and why in a brief summary. --- diff --git a/skills/memory-audit/SKILL.md b/skills/memory-audit/SKILL.md index ed7f94e..141f6bd 100644 --- a/skills/memory-audit/SKILL.md +++ b/skills/memory-audit/SKILL.md @@ -10,82 +10,120 @@ allowed-tools: Read, Glob, Grep, Edit, Bash, Write, mcp__docs-mcp-server__search # Memory Audit Skill -Audit the knowledge base in `/.claude/memories/` to keep it lean, relevant, and high-quality. `` refers to the current working directory. When calling `search_docs`, the `library:` parameter is the root directory name (folder-based, set automatically by the indexing hook). When matching memories' `Applies to:` against "the current project," compare instead against the **git repo name** (last path segment of `git remote get-url origin`, `.git` stripped) — that is the stable identifier that travels with the file across clones. +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. 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. -> **Rule alignment.** The audit applies three Capture Rules: tied to at least one project, anonymous, project-pattern-not-personal-preference. Existing memories that violate any of them are candidates for UPDATE or DROP — see criteria 1, 8, and 9 below. +> **This skill is user-initiated only.** Never run it automatically or as part of another workflow. -> **`Applies to` field is informational.** Memories carry an `Applies to:` line declaring which project(s) they target. Use it as context, but audit only the memories in the current checkout's `.claude/memories/` — fact-check against this project only. When deciding whether a memory targets *this* project, compare its `Applies to:` against the **git repo name**, not the directory basename — a folder rename does not change the project. Never DROP a memory solely because its `Applies to:` lists other projects. If the KB is centralized across projects via a separate mechanism (e.g. a shared-memories techpack), that mechanism owns its own audit — this skill does not reach across repos. +--- -> **This skill is user-initiated only.** Never run it automatically or as part of another workflow. +## Capture Rules + +Apply these rules at audit time. A memory that fails any rule is a candidate for UPDATE (when a rewrite restores qualification) or DROP (when nothing of value remains after the rewrite). + + +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. +- **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. + - **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. + + +The audit enforces these rules through the criteria below — see Group A. + +## The `Applies to:` field + + +**The `Applies to:` field.** The first line of every memory 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 `library:` parameter used for `search_docs`, 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. + + +**Audit-time usage.** Audit only the memories in the current checkout's `.claude/memories/` and fact-check against this project only. When deciding whether a memory targets *this* project, compare its `Applies to:` against the **git repo name**, not the directory basename — a folder rename does not change the project. Never DROP a memory solely because its `Applies to:` lists other projects. If the KB is centralized across projects via a separate mechanism, that mechanism owns its own audit — this skill does not reach across repos. --- ## Audit Criteria -Evaluate each memory against these dimensions: +Evaluate each memory against the criteria below, grouped into three: + +- **Group A** mirrors the Capture Rules (the same gates a memory had to pass at save time). A memory that violates any of these now is a candidate for UPDATE or DROP — even if it slipped through capture. +- **Group B** is the audit's own gate: the forcing-function test. Capture cannot test it because only audit sees how a memory has aged. This is where past audits drifted into KEEP-by-default; apply it with teeth. +- **Group C** are audit-only mechanics — tests that depend on the current state of the codebase, the KB as a whole, or time elapsed since capture. + +### Group A — Capture Rules, restated + +#### 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. +- Apply the strip-the-anchors test: -### 1. Relevance -- Does this memory apply to the **current state** of the project? -- Has the underlying code, API, or framework changed since it was written? -- **Tied to at least one project vs. generic.** KEEP memories meaningfully tied to at least one project listed in `Applies to:` (single- or multi-project both count) — its codepaths, architecture, build/deploy setup, test strategy, tooling choices, team workflow, or recurring implementation patterns. DROP generic best practices, generic style advice, or broadly applicable how-to guidance that could fit almost any project with no real anchor to the repos in `Applies to:`. -- **Public docs vs. project knowledge vs. internal docs.** DROP memories that duplicate **public** documentation anyone could look up (language reference, public CLI/API docs, framework README). DROP memories whose content has no tie to any project in `Applies to:` — even if the memory describes a "non-obvious gotcha," if the root cause is generic and nothing in the content anchors it to a real codebase, config, or workflow, it belongs in the tool's docs, not here. **KEEP** summaries of **internal** docs (Confluence, ADRs, RFCs, wiki) — those sources aren't publicly lookupable, and a local summary makes them discoverable from a session. If an internal-doc summary lacks a `References:` link back to the source, flag for UPDATE (add the link) rather than DROP. Test: *"Name the project(s) this applies to and why."* If the answer is "any project, it's just how the tool works" → DROP. -- **Strip-the-anchors test.** Mentally delete every project-specific reference (paths, symbols, endpoints, business logic) from the memory's content. If what's left is still a useful standalone document, the project tie was decoration and the substance is tool/tooling knowledge — DROP, or UPDATE only if a rewrite around the actual project anchor produces something genuinely project-specific. **Internal or proprietary tools are not exempt:** memories describing how a private CLI, MCP server, GUI, or company-internal tool *works in general* belong in the tool's own docs or in `CLAUDE.md`, not here. Project endpoints sprinkled inside a tool how-to do not make it project knowledge. + +**Strip-the-anchors test.** Mentally delete every project-specific reference (paths, symbols, endpoints, business logic, ticket prefixes, instance IDs, custom-field IDs, internal CLI flags) from the memory's content. What is left is the *substance*. If the substance is a useful standalone document — generic tool, language, or framework knowledge that would help any reader anywhere — the project tie was decoration and the memory does not qualify as project knowledge. **Internal or proprietary tools are not exempt:** how a private CLI, MCP server, GUI, or company-internal tool *works in general* belongs in the tool's own docs or in `CLAUDE.local.md`. Project endpoints sprinkled inside a tool how-to do not make it project knowledge. + + +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"? +- **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. -### 2. Actionability — the forcing-function test -- **Primary test:** *"Would a future Claude session act differently in this codebase because this memory exists?"* If the answer is "no, the code itself or `git log` already conveys it" → DROP. +#### A.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. + +### Group B — The audit's own gate + +#### B.1 Actionability — the forcing-function test +- **Primary test:** *"Would a future session act differently in this codebase because this memory exists?"* If the answer is "no, the code itself or `git log` already conveys it" → DROP. - Can a future session **act on** this memory to avoid a mistake or follow a convention? Or is it purely descriptive/documentary with no clear "do this, not that" takeaway? -- **Fact-check ≠ actionability.** A claim being *true* and *project-specific* is not enough. Many memories pass criterion 1 (real anchors) and criterion 6 (claims still verifiable) but still fail this one — historical records, shipped naming decisions, one-time bug fixes whose fix is self-evident in the code. Apply both passes; don't conflate them. +- **Fact-check ≠ actionability.** A claim being *true* and *project-specific* is not enough. Many memories pass A.1 (real anchors) and C.4 (claims still verifiable) but still fail this one — historical records, shipped naming decisions, one-time bug fixes whose fix is self-evident in the code. Apply both passes; do not conflate them. - **Bias check.** If you find yourself defending KEEP with "it's project-specific and still accurate" without identifying the *behavior change* it drives, that's the leniency trap. KEEP requires a positive answer to the forcing-function test, not just absence of a reason to drop. -### 3. Naming Convention -- Learnings must follow `learning__.md` +### Group C — Audit-only mechanics + +#### C.1 Naming Convention +- Learnings must follow `learning__.md`. - Decisions must follow `decision__.md`. - Files that don't follow either pattern are likely older or ad-hoc — flag for rename or reclassification. -### 4. Duplication +#### 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. - Search the existing knowledge base or memory files for semantically similar content — two memories may use different names but cover the same ground. -### 5. Quality +#### C.3 Quality - Does the memory follow the standard templates? (Problem/Trigger/Solution/Verification/Example for learnings; Decision/Context/Options/Choice/Consequences for ADR decisions; Decision/Rationale/Examples for simplified decisions) - Is the content specific enough to be useful but general enough to be reusable? - Are code examples still accurate? -### 6. Fact-Checking -- **Verify key claims against the codebase.** If a memory says "we use pattern X in module Y", search the code to confirm that pattern still exists. +#### C.4 Fact-Checking +- **Verify key claims against the codebase.** If a memory says "we use pattern X in module Y," search the code to confirm that pattern still exists. - Use `Grep` to check for symbol names, type names, or patterns referenced in the memory. - Use `Glob` to verify that referenced files or modules still exist. - If a memory describes a convention (e.g., "all repositories conform to protocol X"), spot-check a few cases to confirm it holds. -- Don't audit every single line — focus on the **central claim** of the memory. If the core assertion is wrong, recommend DROP or UPDATE. +- Do not audit every single line — focus on the **central claim** of the memory. If the core assertion is wrong, recommend DROP or UPDATE. -### 7. Staleness Signals +#### C.5 Staleness Signals - **Line number references** — e.g., `lines 266-296` or `FileName.swift:142`. These break after any edit. Recommend UPDATE to replace with symbol names. -- **Deep file paths** — full nested paths like `Sources/Features/Auth/Managers/Session/SessionManager.swift` are fragile. Recommend UPDATE to use module-level references unless the path is stable and well-known. +- **Deep file paths** — full nested paths are fragile. Recommend UPDATE to use module-level references unless the path is stable and well-known. - **Transient details** — feature flag names being removed, in-progress PR numbers, temporary workarounds with known expiry. -- References to features/files that may have been removed or heavily refactored. +- 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. -### 8. Personal Identifiers -- 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"? -- **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. - -### 9. Preference vs Pattern -- 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's a preference. -- **Verdict:** DROP when no evidence exists anywhere — it's an individual preference, not a project decision. 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. - --- ## DROP Categories — recurring patterns that should not need user pushback -Each of these has tripped past audits. Recognize the shape and call DROP without hedging. None of these are "in doubt" cases — they fail criterion 2's forcing-function test outright. +The categories below are the recurring concrete shapes of B.1 (forcing-function) failure. When a memory matches one, the analysis is already done — call DROP without hedging. None of these are "in doubt" cases. ### 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. @@ -122,6 +160,12 @@ Each of these has tripped past audits. Recognize the shape and call DROP without ## Audit Workflow +> **Per-batch consent is mandatory.** Each batch is its own approval cycle: produce the verdict table, **stop**, wait for the user, apply their decisions, summarize, then — only after that — move on to the next batch. Never chain batches without an explicit go-ahead between them. See Step 3 for the hard-stop rules. + +> **Stay at project root.** Do not `cd` into `.claude/memories/` (or any subdirectory) at any point during the audit. Codebase fact-checks (Grep/Glob/Bash) need cwd at the project root — running them from inside `.claude/memories/` resolves patterns against memory files instead of project source, silently passing fact-checks that should fail. Reference memory files by full path (e.g. `.claude/memories/.md`). + +> **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 List all memory files in the project: @@ -144,11 +188,11 @@ Read memories in batches (10-15 at a time) and produce a verdict table for each ``` | # | File | Verdict | Rationale | |---|------|---------|-----------| -| 1 | learning_background_task_watchdog.md | KEEP | Project-specific debugging discovery, still relevant | -| 2 | learning_cli_tool_flags.md | DROP | Generic third-party CLI reference, not tied to this project (criterion 1) | -| 3 | learning_auth_cache_bug.md | UPDATE | Problem section names an engineer by handle — strip the identifier, keep the symptom (criterion 8) | -| 4 | decision_codestyle_tabs.md | DROP | Stated as personal preference with no lint rule, formatter config, or team agreement behind it (criterion 9) | -| 5 | decision_codestyle_naming.md | UPDATE | Convention still valid but example uses old API | +| 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) | ``` **Verdict definitions:** @@ -159,19 +203,28 @@ Read memories in batches (10-15 at a time) and produce a verdict table for each For UPDATE verdicts, briefly describe what needs to change. -### Step 3: User Review +### 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.** + +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"). +- A request to see more (full content of a memory, fuller rationale) — answer it, then return to the stop. + +Things that are **not** approval: +- Silence, a thumbs-up emoji alone, or a one-word "ok" without context — if ambiguous, ask. +- The model's own internal certainty that the batch looks fine. +- 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. -Present each batch and wait for the user's approval before proceeding. The user may: -- Agree with all verdicts -- Override specific verdicts (e.g., "keep #2, I still reference it") -- Ask for more detail on why a verdict was given -- Ask to see the full content of a memory before deciding +If the user goes quiet after a batch, do not move on. End the turn. The user resumes when ready. -Respect every override — the user knows their workflow better than any heuristic. +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. ### Step 4: Execute Approved Changes -After the user confirms a batch: +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 )` - **UPDATE (rename)**: Rename with `Bash(mv )` @@ -203,10 +256,10 @@ Knowledge base reduced from 42 → 34 files. ## Guidelines -- **Never delete without explicit user approval.** Always present the verdict and wait. +- **Never delete or edit without explicit per-batch approval.** Print the verdict table, then stop. Do not run any tool until the user replies for *this* batch — silence is not consent, and approval of an earlier batch does not carry forward. - **Explain the "why" clearly.** The user should understand the reasoning behind every DROP and UPDATE recommendation, not just see the label. -- **Apply criteria with teeth, not deference.** Past audits drifted into KEEP-by-default because each memory had *some* tie to the project. The forcing-function test (criterion 2) 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. -- **Watch for the "but it's true and project-specific" trap.** That sentence is criteria 1 and 6 passing — it says nothing about criterion 2. Two-pass thinking: first verify, then ask "does this change behavior?" +- **Apply criteria with teeth, not deference.** Past audits drifted into KEEP-by-default because each memory had *some* tie to the project. The forcing-function test (B.1) is the correction: KEEP requires identifying behavior the memory drives, not just absence of error. When the DROP categories above match, call DROP — don't soften it to UPDATE or stash in KEEP "to be safe." +- **In genuine doubt, prefer DROP with rationale over silent KEEP.** The user can always override. A KEEP that should have been DROP rarely gets revisited; a proposed DROP gets debated and resolved in seconds. **DROP is not a failure** — moving content to `CLAUDE.local.md`, to a planning doc, or simply deleting it because the code now documents itself is the audit doing its job. +- **Watch for the "but it's true and project-specific" trap.** That sentence is A.1 and C.4 passing — it says nothing about B.1. Two-pass thinking: first verify, then ask "does this change behavior?" - **Batch size matters.** 10-15 per batch keeps the review manageable. - **End-of-audit check for broken cross-links.** After DROPs land, grep `Related:` / `References:` lines for any pointer to a deleted filename and clean those up — broken refs accumulate silently otherwise. diff --git a/techpack.yaml b/techpack.yaml index 3e7aca0..719a4d6 100644 --- a/techpack.yaml +++ b/techpack.yaml @@ -161,6 +161,12 @@ components: gitignore: - ".claude/.memories-last-indexed" +# --------------------------------------------------------------------------- +# Ignore — maintainer-only files not shipped with the pack +# --------------------------------------------------------------------------- +ignore: + - SYNC-BLOCKS.md + # --------------------------------------------------------------------------- # Templates — CLAUDE.local.md sections # --------------------------------------------------------------------------- From 8ea84ee0469d91610f6ce5597d07406d38bf1097 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Tue, 5 May 2026 12:17:46 +0200 Subject: [PATCH 2/4] Bump actions/checkout to v6 - Latest stable major (v6.0.2 at time of bump) --- .github/workflows/sync-blocks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-blocks.yml b/.github/workflows/sync-blocks.yml index c99132b..0fa89ea 100644 --- a/.github/workflows/sync-blocks.yml +++ b/.github/workflows/sync-blocks.yml @@ -18,7 +18,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Verify SYNC blocks are byte-identical across both skills run: | From 95bd0000ab85c75047caead7ed2dca3d4dea34d6 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Tue, 5 May 2026 12:22:32 +0200 Subject: [PATCH 3/4] Align drift-check script and tighten CI hygiene - Local drift script in SYNC-BLOCKS.md now reports every drifted block (was early-exit on first), matching the CI workflow output - Workflow declares contents:read permission and a per-ref concurrency group --- .github/workflows/sync-blocks.yml | 7 +++++++ SYNC-BLOCKS.md | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sync-blocks.yml b/.github/workflows/sync-blocks.yml index 0fa89ea..25aeb22 100644 --- a/.github/workflows/sync-blocks.yml +++ b/.github/workflows/sync-blocks.yml @@ -14,6 +14,13 @@ on: - "skills/memory-audit/SKILL.md" - "SYNC-BLOCKS.md" +permissions: + contents: read + +concurrency: + group: sync-blocks-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index d795fbd..0916597 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -69,14 +69,22 @@ When a memory genuinely applies to multiple projects, list them comma-separated ```sh #!/usr/bin/env bash -set -euo pipefail +set -uo pipefail A=skills/continuous-learning/SKILL.md B=skills/memory-audit/SKILL.md +fail=0 for tag in capture-rules strip-the-anchors applies-to; do - diff -u \ - <(awk "//,//" "$A") \ - <(awk "//,//" "$B") \ - || { echo "DRIFT in $tag"; exit 1; } + echo "=== $tag ===" + if diff -u \ + <(awk "//,//" "$A") \ + <(awk "//,//" "$B"); then + echo "OK: identical" + else + echo "DRIFT in $tag" + fail=1 + fi done -echo "all sync blocks identical" +exit "$fail" ``` + +The script reports every drifted block in one pass — same behaviour as the CI workflow, so a maintainer's local run and a PR check produce the same output. From 5860dc9cc800723e65faf965c04287c6de316285 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Tue, 5 May 2026 12:28:45 +0200 Subject: [PATCH 4/4] Address Copilot review: cover SYNC-BLOCKS.md and missing fences - Drift check now diffs all three files (skills + SYNC-BLOCKS.md) and asserts each block is present, so a maintainer who edits the skills without updating the canonical reference, or who deletes/misspells a fence, fails CI - Stage B Rule 3 summary now matches the synced rule by allowing verbal team agreements - SYNC-BLOCKS.md drops the lead diff example (whose literal tag name shadowed the real canonical block during awk extraction) and replaces it with prose guidance to never use real tag names outside the canonical fences --- .github/workflows/sync-blocks.yml | 37 +++++++++++++++++-------- SYNC-BLOCKS.md | 43 +++++++++++++++++------------ skills/continuous-learning/SKILL.md | 2 +- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/.github/workflows/sync-blocks.yml b/.github/workflows/sync-blocks.yml index 25aeb22..ea9befe 100644 --- a/.github/workflows/sync-blocks.yml +++ b/.github/workflows/sync-blocks.yml @@ -27,25 +27,40 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Verify SYNC blocks are byte-identical across both skills + - name: Verify SYNC blocks exist and agree across both skills + SYNC-BLOCKS.md run: | - set -euo pipefail + set -uo pipefail A=skills/continuous-learning/SKILL.md B=skills/memory-audit/SKILL.md - fail=0 + C=SYNC-BLOCKS.md + overall=0 for tag in capture-rules strip-the-anchors applies-to; do echo "=== $tag ===" - if diff -u \ - <(awk "//,//" "$A") \ - <(awk "//,//" "$B"); then - echo "OK: identical" + tag_fail=0 + for f in "$A" "$B" "$C"; do + if [ -z "$(awk "//,//" "$f")" ]; then + echo "MISSING: block '$tag' not found in $f" + tag_fail=1 + fi + done + if [ "$tag_fail" -eq 0 ]; then + for ref in "$B" "$C"; do + if ! diff -u --label "$A" --label "$ref" \ + <(awk "//,//" "$A") \ + <(awk "//,//" "$ref"); then + echo "DRIFT between $A and $ref for tag '$tag'" + tag_fail=1 + fi + done + fi + if [ "$tag_fail" -eq 0 ]; then + echo "OK: identical across all three files" else - echo "DRIFT in $tag" - fail=1 + overall=1 fi done - if [ "$fail" -ne 0 ]; then + if [ "$overall" -ne 0 ]; then echo - echo "One or more SYNC blocks have drifted. Update SYNC-BLOCKS.md and copy the canonical text into both skills." + echo "One or more SYNC blocks drifted or went missing. Update SYNC-BLOCKS.md and copy the canonical text into both skills." exit 1 fi diff --git a/SYNC-BLOCKS.md b/SYNC-BLOCKS.md index 0916597..278d5cd 100644 --- a/SYNC-BLOCKS.md +++ b/SYNC-BLOCKS.md @@ -2,15 +2,9 @@ This file is **not loaded by any skill**. It is the canonical source for content that must remain verbatim-identical across `skills/continuous-learning/SKILL.md` and `skills/memory-audit/SKILL.md`. -When you edit one of the blocks below, update this file first, then copy the new text into every listed location. Both skills mark synced sections with HTML-comment fences (`` … ``) so a one-liner can verify byte-equality: +When you edit one of the blocks below, update this file first, then copy the new text into every listed location. The full drift-check script (with non-empty assertion and three-way comparison) is at the bottom of this file; CI runs the same logic. -```sh -diff \ - <(awk '//,//' skills/continuous-learning/SKILL.md) \ - <(awk '//,//' skills/memory-audit/SKILL.md) -``` - -Repeat for `strip-the-anchors` and `applies-to`. +> **Heads up for editors.** Anywhere outside the canonical fenced blocks below, refer to fences using the placeholder form (the text `SYNC` followed by a colon and `` in angle brackets, inside an HTML comment) — never with a real tag name like the three this file owns. The drift check's `awk` range grabs the first matching opener, so a literal real tag in prose would shadow the real block and make the canonical text invisible to the verifier. The locked blocks are written in neutral voice — they describe what qualifies as a memory, not what to do with one. Each skill prepends/appends its own one-line framing **outside** the SYNC fences (capture says "do not save"; audit says "recommend DROP"). Do not move action verbs inside the locked block. @@ -72,19 +66,34 @@ When a memory genuinely applies to multiple projects, list them comma-separated set -uo pipefail A=skills/continuous-learning/SKILL.md B=skills/memory-audit/SKILL.md -fail=0 +C=SYNC-BLOCKS.md +overall=0 for tag in capture-rules strip-the-anchors applies-to; do echo "=== $tag ===" - if diff -u \ - <(awk "//,//" "$A") \ - <(awk "//,//" "$B"); then - echo "OK: identical" + tag_fail=0 + for f in "$A" "$B" "$C"; do + if [ -z "$(awk "//,//" "$f")" ]; then + echo "MISSING: block '$tag' not found in $f" + tag_fail=1 + fi + done + if [ "$tag_fail" -eq 0 ]; then + for ref in "$B" "$C"; do + if ! diff -u --label "$A" --label "$ref" \ + <(awk "//,//" "$A") \ + <(awk "//,//" "$ref"); then + echo "DRIFT between $A and $ref for tag '$tag'" + tag_fail=1 + fi + done + fi + if [ "$tag_fail" -eq 0 ]; then + echo "OK: identical across all three files" else - echo "DRIFT in $tag" - fail=1 + overall=1 fi done -exit "$fail" +exit "$overall" ``` -The script reports every drifted block in one pass — same behaviour as the CI workflow, so a maintainer's local run and a PR check produce the same output. +The script enforces three things: every block exists in every source file (catches deleted or misspelled fences), the two skill files agree, and `SYNC-BLOCKS.md` itself agrees with them (catches "edited skills, forgot to update the maintainer reference"). CI runs the same script. diff --git a/skills/continuous-learning/SKILL.md b/skills/continuous-learning/SKILL.md index 7d77223..1dd6bc1 100644 --- a/skills/continuous-learning/SKILL.md +++ b/skills/continuous-learning/SKILL.md @@ -90,7 +90,7 @@ If NO to all → skip. Otherwise continue to Stage B. 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 recorded team agreement. +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.