diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 14d10e5f1c..06d48aaa01 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -198,7 +198,7 @@ "name": "claude-memory", "source": "./plugins/claude-memory", "category": "claude-code", - "tags": ["memory", "claude-md", "rules", "auto-memory", "health", "audit", "skill"] + "tags": ["memory", "claude-md", "rules", "auto-memory", "health", "audit", "stateless", "disable", "purge", "skill"] }, { "name": "work-items", diff --git a/README.md b/README.md index 8679f01000..9d31f7ce47 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ user opts in with `/plugin enable`; an existing install is never flipped by cata - [`desktop-notification`](plugins/desktop-notification) — Alert you when Claude Code needs input — an audible terminal bell, an OSC 9 terminal notification, and an OS-native toast (macOS/Linux) on permission and idle prompts. - [`playbooks`](plugins/playbooks) — Doctrine and knowledge playbooks as on-demand skills, plus a maintainer-facing update skill. boris — Boris Cherny's Claude Code workflow tips (howborisusesclaudecode.com); skill-authoring — Anthropic's internal skill-authoring playbook; fable-5 — Claude Fable 5's operating doctrine (self-authored, no upstream). The boris and skill-authoring packs vendor a verbatim upstream baseline; /playbooks:update drift-checks and syncs those baselines centrally (maintainers). - [`claude-config`](plugins/claude-config) — Three audit skills for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), and audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability). -- [`claude-memory`](plugins/claude-memory) — Audits the Claude Code instruction/memory layer — CLAUDE.md, CLAUDE.local.md, .claude/rules/, and auto-memory — against a checklist derived from official Claude Code documentation. A deterministic script-backed spine (MEMORY.md index integrity, orphan always-loaded rules) yields identical findings on identical repo state; judgment-tier checks apply fixed criteria with model reading. Actions: audit (default), fix (per-item approval), update (refresh criteria from current docs), report. +- [`claude-memory`](plugins/claude-memory) — Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes. - [`claude-ops`](plugins/claude-ops) — Claude Code operations toolkit. Seven skills: observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort and a repo-pull + marketplace-refresh launch step), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads. - [`skill-quality`](plugins/skill-quality) — Skill-authoring QA tooling: a static contract checker that runs seventeen deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout. diff --git a/plugins/claude-memory/.claude-plugin/plugin.json b/plugins/claude-memory/.claude-plugin/plugin.json index e8bf32162a..ce8bb5cc21 100644 --- a/plugins/claude-memory/.claude-plugin/plugin.json +++ b/plugins/claude-memory/.claude-plugin/plugin.json @@ -1,12 +1,12 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-memory", - "version": "0.2.3", - "description": "Audits the Claude Code instruction/memory layer — CLAUDE.md, CLAUDE.local.md, .claude/rules/, and auto-memory — against a checklist derived from official Claude Code documentation. A deterministic script-backed spine (MEMORY.md index integrity, orphan always-loaded rules) yields identical findings on identical repo state; judgment-tier checks apply fixed criteria with model reading. Actions: audit (default), fix (per-item approval), update (refresh criteria from current docs), report.", + "version": "0.3.0", + "description": "Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes.", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" }, "license": "MIT", - "keywords": ["memory", "claude-md", "rules", "auto-memory", "instructions", "audit", "maintenance", "skill"] + "keywords": ["memory", "claude-md", "rules", "auto-memory", "instructions", "audit", "stateless", "disable", "purge", "maintenance", "skill"] } diff --git a/plugins/claude-memory/CHANGELOG.md b/plugins/claude-memory/CHANGELOG.md index 3447e933f1..b57b8acd38 100644 --- a/plugins/claude-memory/CHANGELOG.md +++ b/plugins/claude-memory/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to the `claude-memory` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.3.0] + +### Added + +- **New `stateless` skill (`/claude-memory:stateless`)** for inspecting and disabling Claude + Code auto memory — the notes Claude writes for itself per repo under + `~/.claude/projects//memory/` (relocatable via `autoMemoryDirectory`). Actions: + `status` (default, read-only — effective on/off state and store contents across all settings + scopes), `disable` (sets `autoMemoryEnabled: false` and `CLAUDE_CODE_DISABLE_AUTO_MEMORY` in a + confirmed scope, and flags a dotfile-manager backfill for a tracked `settings.json`), and + `purge` (destructive — reads `autoMemoryDirectory` at every scope, shows a deletion manifest, + and deletes auto-memory `*.md` files only after explicit confirmation). Scope is auto-memory + only; the instruction layer stays with `audit`, and transcripts/history are out of scope + (auto-cleaned by `cleanupPeriodDays`). Claude Desktop / claude.ai account memory is a + server-side store the skill gives direction for rather than deleting locally. Per the + env-vars doc, `CLAUDE_CODE_DISABLE_AUTO_MEMORY` overrides `autoMemoryEnabled` (the env var is + authoritative when set); `disable` writes the env var (`1`) plus `autoMemoryEnabled: false`, + and `status` treats a set env var as authoritative. The bundled `scope-report.sh` reuses the + plugin's single-source memory-dir resolver rather than re-deriving the path. + +### Fixed + +- **`resolve-memory-dir.sh` now honors `CLAUDE_CONFIG_DIR`.** The shared resolver (used by both + the `audit` and `stateless` skills) resolved the config root as `$HOME/.claude`, so a machine + that relocates its Claude Code config via `CLAUDE_CONFIG_DIR` had its memory directory resolved + to the wrong path. It now uses `${CLAUDE_CONFIG_DIR:-$HOME/.claude}`, per the official + `.claude-directory` doc, so the relocated `projects//memory/` tree resolves correctly. + ## [0.2.3] ### Fixed diff --git a/plugins/claude-memory/README.md b/plugins/claude-memory/README.md index 8ba1ff1503..73bdda8f29 100644 --- a/plugins/claude-memory/README.md +++ b/plugins/claude-memory/README.md @@ -1,15 +1,19 @@ # claude-memory -A Claude Code plugin that keeps a repo's instruction/memory layer healthy. It ships one skill: +A Claude Code plugin that keeps a repo's Claude Code memory layer healthy and under your control. +It ships two skills: | Skill | Question it answers | |---|---| | `/claude-memory:audit` | Is the instruction/memory layer (`CLAUDE.md`, `CLAUDE.local.md`, `.claude/rules/`, auto-memory) healthy against official-doc criteria? | +| `/claude-memory:stateless` | Is Claude's auto memory on, where does it live, and how do I turn it off or wipe it? | -The configuration FILES, automation SET, and permission GRANTS are audited by the sibling skills in the -separate `claude-config` plugin (`audit`, `automation-gaps`, `permission-hygiene`). +The two skills split by axis: `audit` checks the health of the instruction/memory layer; `stateless` +controls the on/off state and contents of the Claude-written auto-memory store. The configuration +FILES, automation SET, and permission GRANTS are audited by the sibling skills in the separate +`claude-config` plugin (`audit`, `automation-gaps`, `permission-hygiene`). -## What the skill does +## What the skills do ### audit @@ -27,6 +31,26 @@ audit contributor-personal auto-memory, so they never land in the repo. /claude-memory:audit report # show the last audit without re-running ``` +### stateless + +Inspects and disables Claude Code **auto memory** — the notes Claude writes for itself per repo at +`~/.claude/projects//memory/` (relocatable via `autoMemoryDirectory`). Scope is auto-memory +only: the instruction layer (`CLAUDE.md`, `.claude/rules/`) belongs to `audit`, and transcripts / +history are out of scope (Claude Code auto-cleans those via `cleanupPeriodDays`). + +```shell +/claude-memory:stateless # status (default) — effective on/off state + where the store lives +/claude-memory:stateless disable # autoMemoryEnabled:false + CLAUDE_CODE_DISABLE_AUTO_MEMORY (scope-confirmed) +/claude-memory:stateless purge # DESTRUCTIVE — delete auto-memory files after a confirmation gate +``` + +`disable` sets both the env var (authoritative — it overrides `autoMemoryEnabled` per the env-vars +doc) and the setting (persistent fallback); `purge` reads `autoMemoryDirectory` at every settings +scope before it enumerates what to delete, shows a manifest, and deletes only after explicit +confirmation. Claude Desktop / claude.ai +account memory is a separate server-side store — the skill gives direction to the app's Settings → +Memory controls rather than deleting it locally. + ## Consumer conventions The skill reads the consuming repo's own `CLAUDE.md` / `.claude/rules/` for project-specific @@ -44,10 +68,13 @@ doc-derived checks. Nothing project-specific is baked into the plugin. ## Configuration -No `userConfig`. State: audit reports persist under the plugin's `${CLAUDE_PLUGIN_DATA}` directory — +No `userConfig`. State: `audit` reports persist under the plugin's `${CLAUDE_PLUGIN_DATA}` directory — they are contributor-local because they cover per-contributor auto-memory, so they never land in the -consuming repo. Network: the `update` action fetches official docs pages (read-only). Scripts require -`git` and standard shell utilities. +consuming repo. Side effects: `stateless disable` edits a `settings.json` you choose (setting +`autoMemoryEnabled` and an `env` var, then flagging a dotfile-manager backfill if the file is tracked); +`stateless purge` deletes auto-memory `*.md` files after a confirmation gate — both act only on the +scope you confirm. Network: the `audit update` action fetches official docs pages (read-only). Scripts +require `git` and standard shell utilities. ## License diff --git a/plugins/claude-memory/skills/audit/scripts/resolve-memory-dir.sh b/plugins/claude-memory/skills/audit/scripts/resolve-memory-dir.sh index d1480d66b9..d467aac9cc 100755 --- a/plugins/claude-memory/skills/audit/scripts/resolve-memory-dir.sh +++ b/plugins/claude-memory/skills/audit/scripts/resolve-memory-dir.sh @@ -11,6 +11,10 @@ # Single source of truth for memory-dir resolution within this plugin — sibling # scripts and the audit workflow call this rather than inlining the glob. # +# Config root honors CLAUDE_CONFIG_DIR: per the official .claude-directory doc, +# setting it relocates every `~/.claude` path (settings AND the projects/ memory +# tree) under that directory, so the memory dir moves with it. +# # Usage (CWD-independent within the target repo): # MEMORY_DIR=$(bash "${CLAUDE_PLUGIN_ROOT}/skills/audit/scripts/resolve-memory-dir.sh") # @@ -47,9 +51,12 @@ if [[ -z "$repo_root" ]]; then exit 1 fi +# Config root: CLAUDE_CONFIG_DIR relocates the whole `~/.claude` tree when set. +config_root="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" + # sed (not tr) for path-char replacement — tr mishandles backslashes on Git Bash. project_slug=$(printf '%s' "$repo_root" | sed 's/[:\\/.]/-/g') -session_data_dir="$HOME/.claude/projects/$project_slug" +session_data_dir="$config_root/projects/$project_slug" # Bare-clone-hub worktree: transcripts are keyed by the worktree cwd, but auto-memory # is shared at the HUB (keyed by git-common-dir). HUB_SLUG also maps '.' (e.g. a @@ -60,7 +67,7 @@ hub_raw=$(cygpath -w "$git_common" 2>/dev/null || printf '%s' "$git_common") hub_slug=$(printf '%s' "$hub_raw" | sed 's/[:\\/.]/-/g') memory_dir="" -for cand in "$session_data_dir/memory" "$HOME/.claude/projects/$hub_slug/memory"; do +for cand in "$session_data_dir/memory" "$config_root/projects/$hub_slug/memory"; do if [[ -f "$cand/MEMORY.md" ]]; then memory_dir="$cand" break diff --git a/plugins/claude-memory/skills/stateless/SKILL.md b/plugins/claude-memory/skills/stateless/SKILL.md new file mode 100644 index 0000000000..d704cf3c58 --- /dev/null +++ b/plugins/claude-memory/skills/stateless/SKILL.md @@ -0,0 +1,89 @@ +--- +name: stateless +description: "Inspect and turn off Claude Code's auto memory — the notes Claude writes itself per repo under ~/.claude/projects//memory/. Use when: 'make Claude stateless', 'stop Claude remembering', 'disable auto memory', 'turn off auto-memory', 'purge/clear/delete auto memory', 'wipe what Claude saved about this repo', 'does Claude have saved memories'. Actions: status (default — memory + settings across all scopes), disable (autoMemoryEnabled:false + CLAUDE_CODE_DISABLE_AUTO_MEMORY), purge (destructive delete, confirm-gated). Auto-memory only — not CLAUDE.md/rules (use /claude-memory:audit) and not transcripts/history." +argument-hint: "[status|disable|purge] — default: status" +user-invocable: true +disable-model-invocation: false +--- + +## Auto-memory snapshot + +```! +bash "${CLAUDE_PLUGIN_ROOT}/skills/stateless/scripts/scope-report.sh" || echo "(snapshot unavailable — run the scope-report script manually)" +``` + +# Stateless + +Inspect and disable Claude Code **auto memory** — the store Claude writes for itself, one +directory per repo (`~/.claude/projects//memory/`, relocatable via +`autoMemoryDirectory`). Governs auto-memory only. Not in scope: CLAUDE.md / CLAUDE.local.md / +`.claude/rules/` (use `/claude-memory:audit`), transcripts, history, or shell snapshots. + +Criteria and exact doc quotes live in [reference/official-guidance.md](reference/official-guidance.md); +re-fetch the two source pages if a fact is load-bearing before you act. + +## Scope + +| Entity | Location | This skill | +|--------|----------|-----------| +| Auto-memory store | `~/.claude/projects//memory/` (or `autoMemoryDirectory`) | Yes — status / disable / purge | +| `autoMemoryEnabled` setting | any settings scope | Yes — reads & writes | +| `CLAUDE_CODE_DISABLE_AUTO_MEMORY` | OS env or settings `env` block | Yes — reads & writes | +| CLAUDE.md / CLAUDE.local.md / `.claude/rules/` | repo + user | No — use `/claude-memory:audit` | +| Transcripts / history / sessions / snapshots | `~/.claude/...` | No — auto-cleaned by `cleanupPeriodDays` | +| Claude Desktop / claude.ai memory | server-side account | Direction only — [context/desktop.md](context/desktop.md) | + +## Argument parsing + +| Argument | Action | +|----------|--------| +| *(none)* or `status` | Report the auto-memory posture: effective enabled/disabled state, where the store lives, what it holds. Read-only. | +| `disable` | Turn auto memory off durably (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_AUTO_MEMORY`). Edits settings — confirm scope first. | +| `purge` | **Destructive.** Delete the auto-memory files. Reads `autoMemoryDirectory` at every scope first, shows a manifest, and deletes only after explicit confirmation. | + +## Precedence (documented) + +`CLAUDE_CODE_DISABLE_AUTO_MEMORY` **overrides** `autoMemoryEnabled`: per the env-vars doc, `=1` +disables and `=0` forces auto memory *on* even when `autoMemoryEnabled: false` would disable +it. When the env var is unset, `autoMemoryEnabled` (by settings precedence) governs. So a set +env var of `0` alongside `autoMemoryEnabled: false` means auto memory is effectively **on** — +`status` must report the env var as authoritative whenever it is set. `disable` sets the env +var to `1` (the authoritative lever) and `autoMemoryEnabled: false` together. See the +reference file's "Precedence: the env var overrides the setting (VERIFIED)". + +## Actions + +- **status** (default): load [context/status.md](context/status.md). +- **disable**: load [context/disable.md](context/disable.md). +- **purge**: load [context/purge.md](context/purge.md). + +For the Claude Desktop / claude.ai account store (server-side, not local files), load +[context/desktop.md](context/desktop.md) — relevant to `status` and `purge` whenever the user +wants to be stateless everywhere, not just in this repo. + +## Gotchas + +- **Precedence**: `CLAUDE_CODE_DISABLE_AUTO_MEMORY` overrides `autoMemoryEnabled` (`=0` forces + on even against `autoMemoryEnabled: false`). A set env var is authoritative in `status`. (See above.) +- **`autoMemoryDirectory` relocates the store** and is read from *any* scope. The snapshot + prints the slug-derived default only — `purge` and `status` must read the override at every + scope or they act on the wrong directory. +- **`CLAUDE_CONFIG_DIR` relocates the whole config root**: when set, the user `settings.json` + *and* the `projects//memory/` tree live under it, not `~/.claude`. All scope and + memory-dir resolution honors `${CLAUDE_CONFIG_DIR:-~/.claude}` (scripts + workflows); the + snapshot reports the resolved root, and `purge`'s relocation check treats it as expected. +- **Windows managed policy** can live in the registry (`HKLM`/`HKCU\SOFTWARE\Policies\ClaudeCode`), + not a file. `scope-report.sh` can't read it — report managed scope as unread, don't assume empty. +- **`disable` applies next session**, not immediately: the setting and `env` block are read at + startup. Tell the user to restart / start a new session. +- **Tracked `settings.json`**: a live edit to a dotfile-manager-tracked settings file must be + backfilled to the source; never run an `apply` that could revert the edit. +- **Desktop / claude.ai memory is server-side** — `purge` cannot delete it; give direction only. + +## Repo-agnostic contract + +Discover the consumer's state at runtime — never hardcode a machine's paths or current +posture. Settings scopes, the memory directory, and the env var are read fresh from the +snapshot above and the workflow scripts. The bundled `scope-report.sh` reuses the plugin's +single-source memory-dir resolver (`skills/audit/scripts/resolve-memory-dir.sh`) rather than +re-implementing slug derivation. diff --git a/plugins/claude-memory/skills/stateless/context/desktop.md b/plugins/claude-memory/skills/stateless/context/desktop.md new file mode 100644 index 0000000000..8171996fbf --- /dev/null +++ b/plugins/claude-memory/skills/stateless/context/desktop.md @@ -0,0 +1,29 @@ +# Claude Desktop / claude.ai account memory (direction only) + +This is a **separate, server-side store** from Claude Code auto memory. It belongs to your +claude.ai account (used by the Claude Desktop app and claude.ai chat), not local files under +`~/.claude/`. This skill cannot read or delete it — it can only tell you where to go. + +Because it is account-side, "going stateless" in Claude Code does nothing to it, and vice +versa. Handle both if you want to be stateless everywhere. + +## Guided steps (verify labels in the live app) + +The exact menu labels are not verified against a fetched doc in this session and the product +UI changes — treat these as directions to the right area, and confirm against what you see: + +1. Open **Settings** in Claude Desktop or on claude.ai, and find the **Memory** (or + personalization) section. +2. **Turn the memory toggle off** to stop new memories being saved. Turning it off does + **not** delete what is already saved. +3. **Clear existing saved memories** using the separate "clear"/"delete" control in that + section — this is a distinct action from the toggle. +4. Review the **privacy / model-training** setting while you are there: whether your chats can + be used to improve models is a separate control from memory. Adjust it to your preference. + +## Honesty notes + +- Do not claim the account memory was deleted — you cannot verify it from here. Confirm the + outcome is the user's to check in the app. +- If the user needs exact current steps, point them to Anthropic's official Help Center for + the Claude app "Memory" article rather than asserting labels from training data. diff --git a/plugins/claude-memory/skills/stateless/context/disable.md b/plugins/claude-memory/skills/stateless/context/disable.md new file mode 100644 index 0000000000..280c2190d8 --- /dev/null +++ b/plugins/claude-memory/skills/stateless/context/disable.md @@ -0,0 +1,85 @@ +# Disable Workflow + +Turn auto memory off durably. This edits a settings file, so confirm the target scope first +and never edit silently. + +## Step 1: Confirm the scope + +Ask which reach the user wants; recommend based on intent: + +- **Machine-wide (RECOMMENDED for "make Claude stateless")** → user settings at + `${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json`. Applies to every project on this machine. + Honor `CLAUDE_CONFIG_DIR`: when it is set, the user config root (and this file) live under it, + not `~/.claude` — the SKILL.md snapshot reports the resolved path. +- **This repo only** → project settings `/.claude/settings.json` (team-shared, committed) + or local `/.claude/settings.local.json` (personal, gitignored). Ask which; local for a + personal choice, project to disable it for everyone on the team. + +Do not proceed until the scope is chosen. + +## Step 2: Apply both levers + +In the chosen settings file, set both. The env var is the authoritative lever (it overrides +`autoMemoryEnabled` per the docs); the setting is the persistent fallback that still holds and +keeps the `/memory` toggle consistent if the env var is later unset (see SKILL.md "Precedence"): + +```json +{ + "autoMemoryEnabled": false, + "env": { + "CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1" + } +} +``` + +Merge into existing JSON — do not clobber other keys or an existing `env` block. Prefer a +deterministic merge over hand-editing. When `jq` is available, use it (it preserves every other +key and only adds/overwrites the two targets; it starts from `{}` when the file is absent): + +```bash +settings="" # user scope: ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json +mkdir -p "$(dirname "$settings")" # project/local .claude/ may not exist yet +tmp=$(mktemp) +{ cat "$settings" 2>/dev/null || echo '{}'; } | + jq '.autoMemoryEnabled = false + | .env = ((.env // {}) + {"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1"})' >"$tmp" && + mv "$tmp" "$settings" || { rm -f "$tmp"; echo "jq merge failed — fall back to a careful manual edit"; } +``` + +The `mkdir -p` matters for a repo/local scope whose `.claude/` directory does not exist yet — +without it the `mv` fails. `jq` reformats the file (2-space JSON) — acceptable for a +machine-managed settings file. If `jq` is unavailable, Read the file and edit it by hand +(creating the parent directory first): add/set exactly these two keys, leave every other key +and any existing `env` entries intact, and keep the trailing newline. + +An even stronger, session-independent lever is a real **OS environment variable** +`CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` (e.g. `setx` on Windows, a shell profile export on +Unix). Offer it when the user wants disablement that survives outside Claude Code's settings; +it is OS-specific and outside a settings file, so present the command, don't run it blind. + +## Step 3: Dotfile / config-management backfill + +A user-scope `settings.json` is often tracked by a dotfile manager. If it is, a live edit +must be backfilled to the source of truth — do not leave the tracked file drifted, and never +run an `apply` that could revert your edit. + +Detect and route generically (repo-agnostic — do not assume a specific manager): + +```bash +command -v chezmoi >/dev/null 2>&1 && chezmoi managed "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" 2>/dev/null \ + && echo "TRACKED by chezmoi — backfill to the dotfiles source" \ + || echo "not chezmoi-tracked (check any other dotfile manager)" +``` + +If tracked, tell the user to backfill through their dotfiles repo's own flow (for chezmoi: +its `add-dotfile` / drift-reconcile path), not `chezmoi apply` from this session. If no +manager is detected, note that a manually managed settings file needs no backfill. + +## Step 4: Confirm effect + +- The `autoMemoryEnabled` change and `env` block take effect on the next session (env is read + at startup; `/memory` also reflects the toggle). Tell the user a restart or new session + applies it. +- Optionally re-run `status` to show the new posture. +- Disabling stops future writes; it does **not** delete existing memory files. If the user + also wants the saved notes gone, point to `purge`. diff --git a/plugins/claude-memory/skills/stateless/context/purge.md b/plugins/claude-memory/skills/stateless/context/purge.md new file mode 100644 index 0000000000..4dbce0ee9f --- /dev/null +++ b/plugins/claude-memory/skills/stateless/context/purge.md @@ -0,0 +1,86 @@ +# Purge Workflow (destructive — confirm-gated) + +Delete the auto-memory files for the current repo. This is irreversible. Never delete before +the confirmation gate in Step 3. + +## Step 1: Resolve EVERY candidate directory + +The store may be relocated by `autoMemoryDirectory`, which is read from **any** settings scope +(user, project, local, policy, `--settings`). Miss that and you purge the wrong place. So: + +1. Read `autoMemoryDirectory` from every present settings scope (managed / local / project / + user — the snapshot in SKILL.md lists which files exist; Read each). Expand `~/` to `$HOME`. +2. Resolve the default via the snapshot / `scope-report.sh` (slug-derived + `${CLAUDE_CONFIG_DIR:-~/.claude}/projects//memory/` — the config root honors + `CLAUDE_CONFIG_DIR`, so a config root relocated by it is the *expected* tree, not a flag). +3. Build the candidate set = the highest-precedence `autoMemoryDirectory` override if any set, + plus the default. Include the default even when an override exists (older writes may remain + there). De-duplicate. + +## Step 2: Capture the exact manifest (and flag relocations) + +Enumerate the files ONCE into an explicit list, and delete exactly that captured list in Step 4 +— never re-glob at deletion time (a re-glob reopens a time-of-check/time-of-use gap and can +delete files created between the manifest and the delete). Capture regular files only (`-type f` +skips symlinks, so a symlinked `*.md` is never followed): + +```bash +config_root="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" # honors a relocated config root +manifest=$(mktemp) +for dir in ; do + [[ -d "$dir" ]] || continue + case "$dir" in + "$config_root/projects/"*) : ;; # expected default (or CLAUDE_CONFIG_DIR-relocated) tree + *) echo "UNEXPECTED RELOCATION: $dir is outside $config_root/projects/ (from autoMemoryDirectory)" ;; + esac + find "$dir" -maxdepth 1 -type f -name '*.md' 2>/dev/null +done | sort -u >"$manifest" +``` + +Present to the user: + +- Each directory and the **resolved absolute path** of every file in `$manifest` (with count). +- **Explicitly flag any `UNEXPECTED RELOCATION` line**: a candidate dir outside the config root's + `projects/` tree came from an `autoMemoryDirectory` override that a project/local settings file + can set — confirm the user intends to delete from that absolute path before proceeding, since + it could point at an unrelated directory. +- That this deletes auto-memory notes only — **not** CLAUDE.md, rules, transcripts, or history. +- If `$manifest` is empty, report that there is nothing to purge and stop (no-op). + +## Step 3: Confirmation gate + +Ask for explicit confirmation, quoting the concrete manifest (and any `UNEXPECTED RELOCATION` +paths), e.g.: + +> This will permanently delete N auto-memory file(s): `/MEMORY.md`, +> `/debugging.md`, … This cannot be undone. Type "yes" to proceed. + +Proceed only on an unambiguous yes. Anything else — abort and change nothing. Never infer +consent from the original request; the gate is a separate, explicit step. + +## Step 4: Delete the captured manifest + +After confirmation, delete exactly the paths captured in `$manifest` in Step 2 — do not +re-enumerate, do not `find ... -delete`, do not `rm -rf` any directory: + +```bash +while IFS= read -r file; do + [[ -n "$file" ]] && rm -- "$file" +done <"$manifest" +rm -f "$manifest" +``` + +`rm -- "$file"` on a symlink removes the link, not its target; combined with the `-type f` +capture in Step 2, nothing outside the enumerated regular files is touched. Remove a +now-empty memory directory only if the user explicitly asked to remove the folder itself; +otherwise leaving the empty directory is harmless. + +## Step 5: Report and offer follow-through + +- Confirm what was deleted (files, directories). +- Purge removes existing notes but does **not** stop new ones. If the user wants to stay + stateless, point to `disable` (or run it now if they ask) so Claude doesn't immediately + re-accumulate memory. +- If the user wants to be stateless everywhere, summarize the Claude Desktop / claude.ai + account store steps in [desktop.md](desktop.md) — that store is server-side and cannot be + deleted from here. diff --git a/plugins/claude-memory/skills/stateless/context/status.md b/plugins/claude-memory/skills/stateless/context/status.md new file mode 100644 index 0000000000..54996fe406 --- /dev/null +++ b/plugins/claude-memory/skills/stateless/context/status.md @@ -0,0 +1,54 @@ +# Status Workflow (read-only) + +Report the effective auto-memory posture for the current repo. Change nothing. + +## Step 1: Read the snapshot + +The SKILL.md snapshot already ran `scope-report.sh`, which lists each settings scope file +(managed / user / project / local), its existence, the live +`CLAUDE_CODE_DISABLE_AUTO_MEMORY` OS-env value, and the default memory directory with its +`MEMORY.md` line count and topic-file count. If the snapshot is missing, run: + +```bash +bash "${CLAUDE_PLUGIN_ROOT}/skills/stateless/scripts/scope-report.sh" +``` + +## Step 2: Read the setting values from each present scope + +The snapshot reports which settings files exist but not their key values. For each scope +listed `PRESENT`, Read the file and extract: + +- `autoMemoryEnabled` (boolean, if set) +- `autoMemoryDirectory` (string, if set) +- `env.CLAUDE_CODE_DISABLE_AUTO_MEMORY` (if set in the `env` block) + +Absent keys inherit the default: `autoMemoryEnabled` defaults to `true` (auto memory is on). +On Windows, managed policy may be in the registry rather than a file — note it as unread if +you cannot inspect it, don't assume it is empty. + +## Step 3: Resolve the effective state + +- **Enabled state.** `CLAUDE_CODE_DISABLE_AUTO_MEMORY` overrides `autoMemoryEnabled` (docs): + if the env var is set anywhere (OS env or any `env` block), it is authoritative — `=1` → + **off**, `=0` → **on** even against `autoMemoryEnabled: false`. If the env var is unset, + apply settings precedence (managed > local > project > user) to `autoMemoryEnabled` + (default `true`). When the env var and the setting disagree, report the effective state as + the env var dictates and call out the disagreement so the user can align them. +- **Store location.** If any scope sets `autoMemoryDirectory`, the effective directory is that + override (highest-precedence scope wins), not the slug-derived default the snapshot printed. + Expand `~/` and report the real path, plus whether `MEMORY.md` and topic files exist there. +- **Store contents.** Report the `MEMORY.md` line count and topic-file count at the effective + directory (re-list if the effective dir differs from the default). + +## Step 4: Report + +Present a short posture summary: + +1. **Effective auto-memory state**: on / off / conflicting (with the reason). +2. **Where it is configured**: which scope(s) set `autoMemoryEnabled` / the env var, and to what. +3. **Store**: effective directory path; present or empty; line/topic counts if present. +4. **Next actions**: if on and the user wants it off, point to `disable`; if files exist and + the user wants them gone, point to `purge`; for the Claude Desktop / claude.ai account + store, summarize [desktop.md](desktop.md). + +Do not edit files or delete anything in this action. diff --git a/plugins/claude-memory/skills/stateless/evals/evals.json b/plugins/claude-memory/skills/stateless/evals/evals.json new file mode 100644 index 0000000000..90aecede0d --- /dev/null +++ b/plugins/claude-memory/skills/stateless/evals/evals.json @@ -0,0 +1,77 @@ +{ + "skill_name": "stateless", + "evals": [ + { + "id": 1, + "name": "status-default-read-only", + "prompt": "Does Claude have any saved memories about this repo, and is auto memory on?", + "expected_output": "Runs the default status action: reports the effective auto-memory state (on/off/conflicting) by reading autoMemoryEnabled and CLAUDE_CODE_DISABLE_AUTO_MEMORY across settings scopes, reports where the store lives (default or autoMemoryDirectory override) and whether MEMORY.md / topic files exist. Changes nothing.", + "files": [], + "expectations": [ + "Output reports the effective auto-memory enabled/disabled state across settings scopes", + "Output reports the auto-memory directory location and whether memory files exist", + "Output does not edit any settings file or delete any memory files" + ] + }, + { + "id": 2, + "name": "scope-boundary-excludes-instruction-layer-and-transcripts", + "prompt": "Make this repo stateless — clear my CLAUDE.md, rules, and chat transcripts too while you're at it.", + "expected_output": "Governs auto-memory only. Routes CLAUDE.md / CLAUDE.local.md / .claude/rules/ to /claude-memory:audit, and explains transcripts/history are out of scope (auto-cleaned by cleanupPeriodDays), rather than editing the instruction layer or deleting transcripts here.", + "files": [], + "expectations": [ + "Output limits its actions to auto-memory (status/disable/purge), not CLAUDE.md/rules", + "Output routes the CLAUDE.md / rules request to /claude-memory:audit", + "Output does not delete transcripts/history and explains they are out of scope" + ] + }, + { + "id": 3, + "name": "purge-requires-confirmation-and-manifest", + "prompt": "Purge all of Claude's auto memory for this repo right now.", + "expected_output": "Resolves candidate memory directories (reading autoMemoryDirectory at every scope plus the default), shows a manifest of the exact files that would be deleted, and requires explicit confirmation before deleting. Does not delete auto-memory files silently on the initial request.", + "files": [], + "expectations": [ + "Output reads autoMemoryDirectory across scopes and resolves the real memory directory before deleting", + "Output presents a manifest of files to be deleted and asks for explicit confirmation", + "Output does not delete any files without confirmation" + ] + }, + { + "id": 4, + "name": "disable-writes-both-levers-and-flags-backfill", + "prompt": "Turn off auto memory everywhere on this machine so Claude stops remembering.", + "expected_output": "Targets user settings (~/.claude/settings.json) for machine-wide reach after confirming scope, sets both autoMemoryEnabled:false and CLAUDE_CODE_DISABLE_AUTO_MEMORY, and flags that a dotfile-manager-tracked settings.json edit must be backfilled to the source rather than left drifted or reverted by apply.", + "files": [], + "expectations": [ + "Output sets autoMemoryEnabled:false AND CLAUDE_CODE_DISABLE_AUTO_MEMORY (both levers)", + "Output targets the correct scope for machine-wide reach (user settings) after confirming scope", + "Output flags the dotfile/config-management backfill requirement for a tracked settings file" + ] + }, + { + "id": 5, + "name": "precedence-env-var-overrides-setting", + "prompt": "If I set CLAUDE_CODE_DISABLE_AUTO_MEMORY but a project sets autoMemoryEnabled true, which wins?", + "expected_output": "States, per the env-vars doc, that CLAUDE_CODE_DISABLE_AUTO_MEMORY overrides autoMemoryEnabled — the env var is authoritative when set (=1 disables, =0 forces on even against autoMemoryEnabled:false), and autoMemoryEnabled governs only when the env var is unset. Does not claim the setting wins over a set env var.", + "files": [], + "expectations": [ + "Output states the env var overrides / takes precedence over autoMemoryEnabled when set", + "Output does not claim autoMemoryEnabled wins over a set CLAUDE_CODE_DISABLE_AUTO_MEMORY", + "Output notes autoMemoryEnabled governs only when the env var is unset" + ] + }, + { + "id": 6, + "name": "desktop-account-memory-direction-only", + "prompt": "I also want to wipe everything Claude remembers about me in the Claude desktop app.", + "expected_output": "Explains that Claude Desktop / claude.ai memory is a separate server-side account store this skill cannot delete locally, and gives direction (Settings -> Memory: toggle off, separately clear saved memories, review the training/privacy setting) without claiming to have deleted it or asserting exact UI labels as verified.", + "files": [], + "expectations": [ + "Output distinguishes the server-side Claude Desktop/claude.ai memory from local auto-memory files", + "Output gives direction-only steps (toggle off, clear saved memories) rather than deleting it locally", + "Output does not claim to have deleted the account memory or assert unverified UI labels as fact" + ] + } + ] +} diff --git a/plugins/claude-memory/skills/stateless/reference/official-guidance.md b/plugins/claude-memory/skills/stateless/reference/official-guidance.md new file mode 100644 index 0000000000..0a437501cb --- /dev/null +++ b/plugins/claude-memory/skills/stateless/reference/official-guidance.md @@ -0,0 +1,135 @@ +# Official Claude Code Guidance on Auto Memory State + +Last researched: 2026-07-20 +Sources: [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory), +[code.claude.com/docs/en/settings](https://code.claude.com/docs/en/settings), +[code.claude.com/docs/en/env-vars](https://code.claude.com/docs/en/env-vars) + +Refresh this file from current official docs before relying on it (re-fetch both pages). + +--- + +## What auto memory is + +> "Auto memory lets Claude accumulate knowledge across sessions without you writing +> anything. Claude saves notes for itself as it works: build commands, debugging insights, +> architecture notes, code style preferences, and workflow habits." +> — code.claude.com/docs/en/memory + +Distinct from CLAUDE.md (which **you** write). This skill governs only the Claude-written +auto-memory store — not CLAUDE.md / CLAUDE.local.md / `.claude/rules/` (the sibling +`/claude-memory:audit` skill owns that instruction layer). + +## Enable / disable + +> "Auto memory is on by default. To toggle it, open `/memory` in a session and use the auto +> memory toggle, which saves `autoMemoryEnabled` to your user settings at +> `~/.claude/settings.json`. To turn it off for a single project, set `autoMemoryEnabled` in +> that project's settings" +> — code.claude.com/docs/en/memory + +> "To disable auto memory via environment variable, set `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`." +> — code.claude.com/docs/en/memory + +> "When `false`, Claude does not read from or write to the auto memory directory. You can +> also toggle this with `/memory` during a session. To disable via environment variable, set +> `CLAUDE_CODE_DISABLE_AUTO_MEMORY` in `env`" +> — code.claude.com/docs/en/settings (`autoMemoryEnabled` description) + +### Precedence: the env var overrides the setting (VERIFIED) + +> "`CLAUDE_CODE_DISABLE_AUTO_MEMORY` | Set to `1` to disable auto memory. Set to `0` to force +> auto memory on even when `--bare` mode or `autoMemoryEnabled: false` would otherwise disable +> it. When disabled, Claude does not create or load auto memory files" +> — code.claude.com/docs/en/env-vars + +So when the env var is set (to `0` or `1`), it **overrides** `autoMemoryEnabled` — `=1` +disables, `=0` forces on even against `autoMemoryEnabled: false`. When the env var is unset, +`autoMemoryEnabled` (resolved by settings precedence) governs. `status` reports the env var as +authoritative whenever it is set — a set env var of `0` alongside `autoMemoryEnabled: false` +means auto memory is effectively **on**. `disable` sets the env var to `1` (the strong, +authoritative lever) and `autoMemoryEnabled: false` together, so the state is unambiguous and +survives the env var later being unset. + +## Storage location + +> "Each project gets its own memory directory at `~/.claude/projects//memory/`. The +> `` path is derived from the git repository, so all worktrees and subdirectories +> within the same repo share one auto memory directory. Outside a git repo, the project root +> is used instead." +> — code.claude.com/docs/en/memory + +> "To store auto memory in a different location, set `autoMemoryDirectory` in your +> `settings.json`. It is read from any settings scope: user, project, local, policy, or +> `--settings`. ... The value must be an absolute path or start with `~/`. When set in a +> project's `.claude/settings.json` or `.claude/settings.local.json`, the value is honored +> only after you accept the workspace trust dialog for that folder" +> — code.claude.com/docs/en/memory + +**Load-bearing for `purge`:** because `autoMemoryDirectory` is read from *any* scope, the +real memory dir may not be the slug-derived default. Purge must read that key at every scope +before it enumerates what to delete, or it can miss (and fail to purge) a relocated store. + +### CLAUDE_CONFIG_DIR relocates the whole config root + +> "On Windows, `~/.claude` resolves to `%USERPROFILE%\.claude`. If you set `CLAUDE_CONFIG_DIR`, +> every `~/.claude` path on this page lives under that directory instead." +> — code.claude.com/docs/en/claude-directory (the page scopes settings AND memory under `~/.claude`) + +So the config root is `${CLAUDE_CONFIG_DIR:-~/.claude}`: when the env var is set, the user +`settings.json` and the `projects//memory/` tree both live under it. Every scope and +memory-dir resolution in this skill (the `scope-report.sh` snapshot, the shared +`resolve-memory-dir.sh`, and the disable/purge workflows) resolves the config root this way, so +a relocated root is honored rather than mistaken for an `autoMemoryDirectory` override. + +The directory holds a `MEMORY.md` index plus optional topic files (layout per +code.claude.com/docs/en/memory): + +```text +~/.claude/projects//memory/ +├── MEMORY.md # Concise index, loaded into every session +├── debugging.md # Detailed notes on debugging patterns +├── api-conventions.md # API design decisions +└── ... # Any other topic files Claude creates +``` + +> "Auto memory files are plain markdown you can edit or delete at any time." +> — code.claude.com/docs/en/memory + +There is no built-in purge command — deletion is manual removal of these files. + +## Settings scopes and precedence + +> "1. Managed (highest priority) — cannot be overridden 2. Command-line arguments 3. Local +> (`.claude/settings.local.json`) 4. Project (`.claude/settings.json`) 5. User +> (`~/.claude/settings.json`) — lowest priority" +> — code.claude.com/docs/en/settings + +Managed settings live outside the repo (macOS `/Library/Application Support/ClaudeCode/`, +Linux/WSL `/etc/claude-code/`, Windows registry `HKLM`/`HKCU\SOFTWARE\Policies\ClaudeCode`). + +> "Environment variables defined in the `settings.json` `env` object are applied to every +> session and passed to all subprocesses Claude Code spawns." +> — code.claude.com/docs/en/settings + +So `CLAUDE_CODE_DISABLE_AUTO_MEMORY` can be set as a real OS environment variable **or** +inside a settings file's `env` block; the docs bless the `env`-block form explicitly. + +## Out of scope for this skill (verified, deliberate) + +- **Transcripts / history / shell snapshots / sessions.** Session files are auto-cleaned at + startup by `cleanupPeriodDays` (default 30, minimum 1). Purging those is a different + concern and is deferred to a future skill. + > "cleanupPeriodDays ... Default: 30 days (minimum: 1) ... Age threshold for deleting + > session files and application data at startup" + > — code.claude.com/docs/en/settings + + `CLAUDE_CODE_SKIP_PROMPT_HISTORY` disables transcript writes entirely — the true + "no session persistence" lever, recorded here for that future skill, not acted on by this one. + +- **Claude Desktop / claude.ai account memory.** That is a server-side account store, not + local files — this skill cannot delete it and only gives direction (see + [../context/desktop.md](../context/desktop.md)). + +- **Subagent auto memory.** A subagent's `memory` field points at its own separate + directory; this skill governs the main conversation's auto-memory store. diff --git a/plugins/claude-memory/skills/stateless/scripts/scope-report.sh b/plugins/claude-memory/skills/stateless/scripts/scope-report.sh new file mode 100755 index 0000000000..c5b5405295 --- /dev/null +++ b/plugins/claude-memory/skills/stateless/scripts/scope-report.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# Snapshot the auto-memory posture for the CURRENT repo across every settings scope. +# +# Reports two deterministic things so the skill workflow doesn't hand-derive them: +# 1. Which settings.json files exist at each scope (managed / user / project / local), +# and whether CLAUDE_CODE_DISABLE_AUTO_MEMORY is set in the live OS environment. +# 2. The DEFAULT auto-memory directory for this repo (via the plugin's single-source +# resolver) plus its MEMORY.md line count and topic-file count. +# +# It intentionally does NOT parse the JSON key values (autoMemoryEnabled, +# autoMemoryDirectory, env.CLAUDE_CODE_DISABLE_AUTO_MEMORY): that avoids a hard `jq` +# dependency and cross-scope precedence guessing. The workflow reads the existing +# settings files (listed here) and extracts those keys with the model's own reading. +# +# autoMemoryDirectory can relocate the memory dir away from the default this script +# reports; the workflow folds any override in as an additional candidate. Windows +# managed policy can live in the registry rather than a file — flagged, not read here. +# +# Usage: bash "${CLAUDE_PLUGIN_ROOT}/skills/stateless/scripts/scope-report.sh" +# Output: a plain-text report on stdout. Never exits non-zero for a missing file or +# a non-git directory — absence is data the caller reports, not an error. + +set -uo pipefail + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + cat <<'EOF' +scope-report.sh — snapshot auto-memory posture across settings scopes for this repo. + +Usage: + scope-report.sh [--help] + +Prints, for the current working directory's repo: + - existence of each settings.json scope file (managed / user / project / local) + - the live CLAUDE_CODE_DISABLE_AUTO_MEMORY environment value (if any) + - the default auto-memory dir, its MEMORY.md line count, and topic-file count + +Reads no JSON key values and never fails on a missing file or non-git dir. +EOF + exit 0 +fi + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +resolver="$script_dir/../../audit/scripts/resolve-memory-dir.sh" + +# Config root: CLAUDE_CONFIG_DIR relocates the whole `~/.claude` tree (user settings +# AND the projects/ memory tree) when set — see the official .claude-directory doc. +config_root="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" + +exists() { [[ -f "$1" ]] && echo "PRESENT" || echo "absent"; } + +# Managed/policy settings location is OS-specific. Report the path for this OS so the +# workflow knows where to look; on Windows it may instead be in the registry. +case "$(uname -s 2>/dev/null || echo unknown)" in +Darwin) managed="/Library/Application Support/ClaudeCode/managed-settings.json" ;; +Linux) managed="/etc/claude-code/managed-settings.json" ;; +MINGW* | MSYS* | CYGWIN*) managed="C:/Program Files/ClaudeCode/managed-settings.json (or Windows registry: HKLM/HKCU\\SOFTWARE\\Policies\\ClaudeCode)" ;; +*) managed="(unknown OS — see settings doc for managed-settings.json location)" ;; +esac + +user_settings="$config_root/settings.json" + +# Project/local scopes: anchor to the repo root when inside one, else CWD. +repo_root=$(git rev-parse --show-toplevel 2>/dev/null | tr -d '\r') +base="${repo_root:-$(pwd)}" +project_settings="$base/.claude/settings.json" +local_settings="$base/.claude/settings.local.json" + +echo "=== Settings scopes (precedence: managed > local > project > user) ===" +managed_file="${managed%% (*}" +printf '%-10s %-8s %s\n' "managed" "$(exists "$managed_file")" "$managed" +printf '%-10s %-8s %s\n' "user" "$(exists "$user_settings")" "$user_settings" +printf '%-10s %-8s %s\n' "project" "$(exists "$project_settings")" "$project_settings" +printf '%-10s %-8s %s\n' "local" "$(exists "$local_settings")" "$local_settings" + +echo +echo "=== Live environment ===" +if [[ -n "${CLAUDE_CONFIG_DIR:-}" ]]; then + echo "CLAUDE_CONFIG_DIR=${CLAUDE_CONFIG_DIR} (config root relocated — user scope + memory tree live here)" +else + echo "CLAUDE_CONFIG_DIR: unset (config root is ~/.claude)" +fi +if [[ -n "${CLAUDE_CODE_DISABLE_AUTO_MEMORY:-}" ]]; then + echo "CLAUDE_CODE_DISABLE_AUTO_MEMORY=${CLAUDE_CODE_DISABLE_AUTO_MEMORY} (set in OS environment)" +else + echo "CLAUDE_CODE_DISABLE_AUTO_MEMORY: unset in OS environment" +fi + +echo +echo "=== Default auto-memory directory (this repo) ===" +if [[ -z "$repo_root" ]]; then + echo "Not inside a git repository — outside a repo the project root is used as the key." + echo "Run the skill from within the target repo, or set autoMemoryDirectory explicitly." + exit 0 +fi + +mem_dir=$(bash "$resolver" 2>/dev/null | tr -d '\r') +if [[ -z "$mem_dir" ]]; then + echo "Could not resolve the default memory dir (resolver unavailable)." + exit 0 +fi + +echo "$mem_dir" +if [[ -f "$mem_dir/MEMORY.md" ]]; then + lines=$(wc -l <"$mem_dir/MEMORY.md" | tr -d ' \r') + topics=$(find "$mem_dir" -maxdepth 1 -name '*.md' ! -name 'MEMORY.md' 2>/dev/null | wc -l | tr -d ' \r') + echo "MEMORY.md: PRESENT (${lines} lines); topic files: ${topics}" +else + echo "MEMORY.md: absent (no auto-memory written to the default location for this repo)" +fi diff --git a/plugins/claude-memory/skills/stateless/scripts/scope-report.test.sh b/plugins/claude-memory/skills/stateless/scripts/scope-report.test.sh new file mode 100755 index 0000000000..18089e2b09 --- /dev/null +++ b/plugins/claude-memory/skills/stateless/scripts/scope-report.test.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Regression tests for scope-report.sh (self-contained — ships with the plugin). +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT="$SCRIPT_DIR/scope-report.sh" + +TEST_TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TEST_TMPDIR"' EXIT + +FAILED=0 +CASE_NUM=0 + +pass() { + CASE_NUM=$((CASE_NUM + 1)) + printf 'PASS: %s\n' "$1" +} +fail() { + CASE_NUM=$((CASE_NUM + 1)) + FAILED=$((FAILED + 1)) + printf 'FAIL: %s\n detail: %s\n' "$1" "$2" >&2 +} +assert_exit() { + if [[ "$2" == "$3" ]]; then pass "$1"; else fail "$1" "expected exit $2, got $3"; fi +} +assert_contains() { + case "$2" in + *"$3"*) pass "$1" ;; + *) fail "$1" "expected to contain: $3" ;; + esac +} + +# Fixture git repos must never inherit an outer hook chain's exported git env. +make_repo() { + unset GIT_DIR GIT_INDEX_FILE GIT_WORK_TREE GIT_COMMON_DIR + mkdir -p "$1" + (cd "$1" && git init -q && git config user.email "test@example.com" && git config user.name "test" && git commit -q --allow-empty -m init) +} + +# --- Case 1: --help exits 0 with usage --- + +rc=0 +OUT=$(bash "$SCRIPT" --help) || rc=$? +assert_exit "--help exits 0" 0 "$rc" +assert_contains "--help prints usage" "$OUT" "Usage:" + +# --- Case 2: inside a git repo, isolated HOME with no memory written --- +# Isolate HOME so the report reads the fixture, never the real user settings/memory. + +REPO="$TEST_TMPDIR/repo" +make_repo "$REPO" +ISO_HOME="$TEST_TMPDIR/home" +mkdir -p "$ISO_HOME/.claude" +printf '{}\n' >"$ISO_HOME/.claude/settings.json" + +rc=0 +OUT=$(cd "$REPO" && env -u CLAUDE_CODE_DISABLE_AUTO_MEMORY HOME="$ISO_HOME" bash "$SCRIPT") || rc=$? +assert_exit "repo report exits 0" 0 "$rc" +assert_contains "reports settings-scope section" "$OUT" "Settings scopes" +assert_contains "reports user settings PRESENT" "$OUT" "PRESENT" +assert_contains "reports default memory dir section" "$OUT" "Default auto-memory directory" +assert_contains "reports MEMORY.md absent when none written" "$OUT" "MEMORY.md: absent" +assert_contains "env var reported unset when not exported" "$OUT" "unset in OS environment" + +# --- Case 3: env var set is reflected --- + +OUT=$(cd "$REPO" && CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 HOME="$ISO_HOME" bash "$SCRIPT") +assert_contains "env var reported set when exported" "$OUT" "set in OS environment" + +# --- Case 4: MEMORY.md at the resolved default is counted --- +# Derive the default dir from the resolver itself (no slug re-derivation in the test). + +RESOLVER="$SCRIPT_DIR/../../audit/scripts/resolve-memory-dir.sh" +MEM_DIR=$(cd "$REPO" && HOME="$ISO_HOME" bash "$RESOLVER" 2>/dev/null | tr -d '\r') +mkdir -p "$MEM_DIR" +printf '# MEMORY\n- one\n- two\n' >"$MEM_DIR/MEMORY.md" +printf '# topic\n' >"$MEM_DIR/debugging.md" + +OUT=$(cd "$REPO" && HOME="$ISO_HOME" bash "$SCRIPT") +assert_contains "MEMORY.md present is reported" "$OUT" "MEMORY.md: PRESENT" +assert_contains "topic file count reported" "$OUT" "topic files: 1" + +# --- Case 5: outside a git repo, exits 0 with a clear note --- + +NONREPO="$TEST_TMPDIR/plain" +mkdir -p "$NONREPO" +rc=0 +OUT=$(cd "$NONREPO" && env -u GIT_DIR HOME="$ISO_HOME" bash "$SCRIPT") || rc=$? +assert_exit "non-git dir exits 0" 0 "$rc" +assert_contains "non-git dir reports it is not a repo" "$OUT" "Not inside a git repository" + +# --- Case 6: CLAUDE_CONFIG_DIR relocates the config root (user scope + memory tree) --- + +CFG="$TEST_TMPDIR/cfg" +mkdir -p "$CFG" +printf '{}\n' >"$CFG/settings.json" +OUT=$(cd "$REPO" && env -u CLAUDE_CODE_DISABLE_AUTO_MEMORY HOME="$ISO_HOME" CLAUDE_CONFIG_DIR="$CFG" bash "$SCRIPT") +assert_contains "reports CLAUDE_CONFIG_DIR when set" "$OUT" "CLAUDE_CONFIG_DIR=$CFG" +assert_contains "user settings resolved under CLAUDE_CONFIG_DIR" "$OUT" "$CFG/settings.json" +assert_contains "memory dir resolved under CLAUDE_CONFIG_DIR" "$OUT" "$CFG/projects/" + +if [[ "$FAILED" -eq 0 ]]; then + printf '\nAll %d checks passed.\n' "$CASE_NUM" + exit 0 +fi +printf '\n%d/%d checks failed.\n' "$FAILED" "$CASE_NUM" >&2 +exit 1