diff --git a/.claude/auto-memory/config.json b/.claude/auto-memory/config.json new file mode 100644 index 0000000..7664916 --- /dev/null +++ b/.claude/auto-memory/config.json @@ -0,0 +1,3 @@ +{ + "triggerMode": "gitmode" +} diff --git a/CLAUDE.md b/CLAUDE.md index 83ba407..52fdc81 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **auto-memory** - automatically maintains CLAUDE.md files as codebases evolve. Tagline: "Your CLAUDE.md, always in sync. Minimal tokens. Zero config. Just works." -Watches what Claude Code edits, deletes, and moves - then quietly updates project memory in the background. Uses PostToolUse hooks to track Edit/Write/Bash operations (including rm, mv, git rm, git mv, unlink), stores changes in .dirty-files, then triggers isolated memory-updater agent to process and update memory sections with detected patterns, conventions, and architecture insights. Processing runs in separate context window, consuming no main session tokens. +Watches what Claude Code edits, deletes, and moves - then quietly updates project memory in the background. Uses PostToolUse hooks to track Edit/Write/Bash operations (including rm, mv, git rm, git mv, unlink), stores changes in .claude/auto-memory/dirty-files, then triggers isolated memory-updater agent to process and update memory sections with detected patterns, conventions, and architecture insights. Processing runs in separate context window, consuming no main session tokens. @@ -31,23 +31,28 @@ Watches what Claude Code edits, deletes, and moves - then quietly updates projec ``` claude-code-auto-memory/ ├── scripts/ # Python hook scripts -│ ├── post-tool-use.py # Tracks edited files to .claude/.dirty-files +│ ├── post-tool-use.py # Tracks edited files; detects git commits for context enrichment │ └── stop.py # Blocks stop if dirty files exist, triggers memory-updater ├── skills/ # Skill definitions (SKILL.md files) │ ├── codebase-analyzer/ # Analyzes codebase, generates CLAUDE.md templates │ └── memory-processor/ # Processes file changes, updates CLAUDE.md sections ├── commands/ # Slash commands (markdown files) -│ ├── init.md # /auto-memory:init -│ ├── calibrate.md # /auto-memory:calibrate -│ └── status.md # /auto-memory:status +│ ├── init.md # /auto-memory:init - Initialize auto-memory plugin +│ ├── calibrate.md # /auto-memory:calibrate - Full codebase recalibration +│ ├── sync.md # /auto-memory:sync - Sync manual file changes detected by git +│ └── status.md # /auto-memory:status - Show memory status ├── agents/ # Agent definitions -│ └── memory-updater.md # Orchestrates CLAUDE.md updates +│ └── memory-updater.md # Orchestrates CLAUDE.md updates with 6-phase workflow ├── hooks/ # Hook configuration │ └── hooks.json # PostToolUse and Stop hook definitions └── tests/ # pytest test suite ``` -**Data Flow**: Edit/Write/Bash (rm, mv, git rm, git mv, unlink) -> post-tool-use.py -> .dirty-files -> stop.py -> memory-updater agent -> memory-processor skill -> CLAUDE.md updates +**Data Flow**: Edit/Write/Bash (rm, mv, git rm, git mv, unlink) -> post-tool-use.py -> .claude/auto-memory/dirty-files -> stop.py -> memory-updater agent -> memory-processor skill -> CLAUDE.md updates + +**State Files** (in `.claude/auto-memory/`): +- `dirty-files` - Pending file list with optional inline commit context +- `config.json` - Trigger mode configuration (default or gitmode) @@ -77,8 +82,12 @@ claude-code-auto-memory/ - **CLAUDE.md Markers**: Use `` and `` for auto-updated sections - **Manual Sections**: Use `` markers for user-editable content - **Skill Templates**: Use `{{PLACEHOLDER}}` syntax for variable substitution -- **File Tracking**: Dirty files stored in `.claude/.dirty-files`, one path per line +- **File Tracking**: Dirty files stored in `.claude/auto-memory/dirty-files`, one path per line with optional inline commit context: `/path/to/file [hash: message]` +- **Trigger Modes**: Config-driven behavior - `default` mode tracks all Edit/Write/Bash operations; `gitmode` only triggers on git commits; default mode used if config missing +- **Git Commit Enrichment**: When git commit detected, enriches each file path with inline commit context for semantic context during updates - **Stop Hook UX**: Blocks at turn end if dirty files exist; instructs Claude to spawn memory-updater agent using Task tool with formatted file list; suggests reading root CLAUDE.md after agent completes to refresh context +- **Memory-Updater Agent**: Orchestrates CLAUDE.md updates through 6-phase workflow - load dirty files (parsing inline commit context), gather file context with imports, extract git insights, discover CLAUDE.md targets, invoke memory-processor skill, cleanup dirty-files; processes max 7 files per run with truncated git diffs; designed for minimal token consumption in isolated context +- **Memory Processor Updates**: Skill analyzes changed files and updates relevant AUTO-MANAGED sections with detected patterns, architecture changes, and new commands; follows content rules (specific, concise, structured); preserves manual sections and maintains < 500 line target - **Test Coverage**: Use subprocess to invoke hooks, verify zero output behavior, test file filtering logic diff --git a/README.md b/README.md index 914f380..8e6b7c4 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,19 @@ Force a full recalibration of all CLAUDE.md files. /auto-memory:calibrate ``` +### `/auto-memory:sync` + +Sync CLAUDE.md with manual file changes detected by git. Use this when you've edited files outside Claude Code (in your IDE, terminal, etc.) and want to update memory without a full recalibration. + +``` +/auto-memory:sync +``` + +Detects: +- Modified tracked files (`git diff`) +- Staged files (`git diff --cached`) +- New untracked files (`git ls-files --others`) + ### `/auto-memory:status` Show current sync status and pending changes. @@ -178,6 +191,45 @@ Content never touched by plugin - `git-insights` - Decisions from commit history - `best-practices` - From official Claude Code docs +## Configuration + +Optional configuration is stored in `.claude/auto-memory/config.json`. + +### Trigger Modes + +Control when auto-memory triggers CLAUDE.md updates: + +```json +{ + "triggerMode": "default" +} +``` + +| Mode | Behavior | +|------|----------| +| `default` | Track file edits in real-time (Edit/Write/Bash operations). Best for most workflows. | +| `gitmode` | Only trigger on `git commit`. Best for developers who commit frequently. | + +**Note**: If no config file exists, `default` mode is used. + +### Git Commit Enrichment + +When a `git commit` is detected (in both modes), auto-memory captures the commit context: +- Commit hash and message are saved to `.claude/auto-memory/commit-context.json` +- The memory-updater agent uses this to provide semantic context: "Changes from commit [hash]: [message]" + +This helps CLAUDE.md updates reflect the *intent* behind changes, not just which files changed. + +### Data Files + +All auto-memory state is stored in `.claude/auto-memory/`: + +| File | Purpose | +|------|---------| +| `dirty-files` | List of files pending CLAUDE.md update | +| `config.json` | Trigger mode configuration | +| `commit-context.json` | Last commit hash + message (temporary) | + ## Development ### Setup @@ -218,6 +270,7 @@ claude-code-auto-memory/ ├── commands/ │ ├── init.md # /auto-memory:init │ ├── calibrate.md # /auto-memory:calibrate +│ ├── sync.md # /auto-memory:sync │ └── status.md # /auto-memory:status └── tests/ ``` @@ -231,7 +284,7 @@ claude-code-auto-memory/ | Processing | **Isolated agent** | Inline or external service | | Updates | **Marker-based** | Full file regeneration | | Monorepo | **Subtree CLAUDE.md** | Root only | -| Config required | **None** | Config files needed | +| Config required | **Optional** (zero-config default) | Config files needed | See also: [memory-store-plugin](https://github.com/julep-ai/memory-store-plugin), [claude-mem](https://github.com/thedotmack/claude-mem), [claude-code-branch-memory-manager](https://github.com/Davidcreador/claude-code-branch-memory-manager) diff --git a/agents/memory-updater.md b/agents/memory-updater.md index b11444f..8ce9b03 100644 --- a/agents/memory-updater.md +++ b/agents/memory-updater.md @@ -1,7 +1,7 @@ --- name: memory-updater description: Orchestrates CLAUDE.md updates for changed files -model: haiku +model: sonnet permissionMode: bypassPermissions tools: Read, Write, Edit, Bash, Glob, Skill --- @@ -11,10 +11,13 @@ You are the memory-updater agent. Your job is to gather context about file chang ## Workflow ### Phase 1: Load Dirty Files -1. Read `.claude/.dirty-files` using Read tool -2. Parse file paths (one per line), deduplicate -3. If empty or missing: return "No changes to process" -4. Categorize files: source, config, test, docs +1. Read `.claude/auto-memory/dirty-files` using Read tool +2. Parse each line - two formats: + - Plain path: `/path/to/file` + - With commit context: `/path/to/file [hash: commit message]` +3. Extract file paths and any inline commit context, deduplicate paths +4. If empty or missing: return "No changes to process" +5. Categorize files: source, config, test, docs ### Phase 2: Gather File Context For each changed file (max 7 files total): @@ -31,7 +34,10 @@ For each changed file (max 7 files total): 2. If git available: - `git log -5 --oneline -- ` for each changed file - `git diff HEAD~5 -- | head -100` for context -3. If not git: skip this phase, note in summary +3. If inline commit context was found in Phase 1: + - Include in summary: "Changes from commit [hash]: [message]" + - This provides semantic context for why files changed +4. If not git: skip this phase, note in summary ### Phase 4: Discover CLAUDE.md Files 1. Find all CLAUDE.md files: `fd -t f -g 'CLAUDE.md' .` (or `find . -name 'CLAUDE.md'`) @@ -48,15 +54,16 @@ For each changed file (max 7 files total): - CLAUDE.md files to update ### Phase 6: Cleanup -1. Clear `.claude/.dirty-files` using Write tool (write empty string to file) +1. Clear `.claude/auto-memory/dirty-files` using Write tool (write empty string) 2. Return summary: - "Updated [sections] in [CLAUDE.md files]" - "Based on changes to [file list]" + - If commit context was present: "From commit [hash]: [message]" - Note any errors or skipped items ## Tool Usage -- **Read**: File contents (respect line limits) -- **Write**: Clear .dirty-files (write empty string) +- **Read**: File contents, dirty-files (respect line limits) +- **Write**: Clear dirty-files (write empty string) - **Edit**: Update CLAUDE.md sections - **Bash**: Git commands only (read-only) - **Glob**: Find CLAUDE.md files diff --git a/commands/init.md b/commands/init.md index 62010a0..ba74caf 100644 --- a/commands/init.md +++ b/commands/init.md @@ -4,10 +4,33 @@ description: Initialize CLAUDE.md memory structure for project with interactive Initialize auto-managed CLAUDE.md memory files for this project. +## Workflow + +### Step 1: Configure Trigger Mode + +Ask the user how auto-memory should trigger updates using AskUserQuestion: + +**Question**: "How should auto-memory trigger CLAUDE.md updates?" + +**Options**: +- **default** (recommended): Track file edits in real-time. Updates trigger after Edit/Write operations and file modifications (rm, mv, etc.). Best for most workflows. +- **gitmode**: Only trigger on git commits. Updates happen when you commit changes. Best for developers who commit frequently and prefer "if you didn't commit, it didn't happen" workflow. + +Save the selection to `.claude/auto-memory/config.json`: +```json +{ + "triggerMode": "default" +} +``` + +### Step 2: Analyze Codebase + Invoke the `codebase-analyzer` skill to: 1. Analyze the codebase structure 2. Detect frameworks and build commands 3. Identify subtree candidates for monorepos 4. Detect code patterns and conventions +### Step 3: Generate CLAUDE.md + Guide the user through the setup process and confirm before writing any files. diff --git a/commands/sync.md b/commands/sync.md new file mode 100644 index 0000000..3ab04fe --- /dev/null +++ b/commands/sync.md @@ -0,0 +1,40 @@ +--- +description: Sync CLAUDE.md with manual file changes detected by git +--- + +Detect files changed outside Claude Code and update CLAUDE.md incrementally. + +Use this when you've edited files manually (outside Claude Code) and want to update CLAUDE.md without a full recalibration. + +## Workflow + +1. **Check if git repo**: Run `git rev-parse --is-inside-work-tree` + - If not a git repo: Report error and suggest `/auto-memory:calibrate` instead + +2. **Detect changed files using git**: + ```bash + git diff --name-only HEAD # Modified tracked files + git diff --cached --name-only # Staged files + git ls-files --others --exclude-standard # New untracked files + ``` + +3. **Filter files** (exclude from processing): + - Files in `.claude/` directory + - `CLAUDE.md` files + - Files outside project directory + +4. **If no changes detected**: Report "Already in sync - no manual changes found" + +5. **If changes found**: + - Convert paths to absolute paths + - Write to `.claude/.dirty-files` (one path per line) + - Use the Task tool to spawn the `memory-updater` agent with prompt: + "Update CLAUDE.md for manually changed files: [file list]" + +6. **Report summary**: List files that were processed + +## Notes + +- Requires a git repository for change detection +- For non-git projects or full recalibration, use `/auto-memory:calibrate` instead +- The memory-updater agent handles the actual CLAUDE.md updates diff --git a/scripts/post-tool-use.py b/scripts/post-tool-use.py index 518f531..bf9c99f 100644 --- a/scripts/post-tool-use.py +++ b/scripts/post-tool-use.py @@ -2,19 +2,75 @@ """PostToolUse hook - tracks edited files for CLAUDE.md updates. Fires after Edit, Write, or Bash tool execution. Appends changed file -paths to .claude/.dirty-files for batch processing at turn end. +paths to .claude/auto-memory/dirty-files for batch processing at turn end. Produces no output to maintain zero token cost. + +Supports configurable trigger modes: +- default: Track Edit/Write/Bash operations (current behavior) +- gitmode: Only track git commits + +When a git commit is detected, enriches each file path with inline commit +context: /path/to/file [hash: commit message] """ from __future__ import annotations import json import os -import re import shlex +import subprocess import sys from pathlib import Path +def load_config(project_dir: str) -> dict: + """Load plugin configuration from .claude/auto-memory/config.json.""" + config_file = Path(project_dir) / ".claude" / "auto-memory" / "config.json" + if config_file.exists(): + try: + with open(config_file) as f: + return json.load(f) + except (json.JSONDecodeError, OSError): + pass + return {"triggerMode": "default"} + + +def handle_git_commit(project_dir: str) -> tuple[list[str], dict | None]: + """Extract context from a git commit. + + Returns: (files, commit_context) where commit_context is {"hash": ..., "message": ...} + """ + # Get commit info (hash and message) + result = subprocess.run( + ["git", "log", "-1", "--format=%h %s"], + capture_output=True, + text=True, + cwd=project_dir, + ) + if result.returncode != 0: + return [], None + + parts = result.stdout.strip().split(" ", 1) + commit_hash = parts[0] + commit_message = parts[1] if len(parts) > 1 else "" + + # Get list of committed files + result = subprocess.run( + ["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"], + capture_output=True, + text=True, + cwd=project_dir, + ) + if result.returncode != 0: + return [], {"hash": commit_hash, "message": commit_message} + + files = [f.strip() for f in result.stdout.strip().split("\n") if f.strip()] + + # Resolve to absolute paths + files = [str((Path(project_dir) / f).resolve()) for f in files] + + return files, {"hash": commit_hash, "message": commit_message} + + def should_track(file_path: str, project_dir: str) -> bool: """Check if file should be tracked for CLAUDE.md updates.""" path = Path(file_path) @@ -134,6 +190,9 @@ def extract_files_from_bash(command: str, project_dir: str) -> list[str]: def main(): project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()) + if not project_dir: + return + # Read tool input from stdin (JSON format) try: stdin_data = sys.stdin.read() @@ -144,17 +203,37 @@ def main(): tool_name = tool_input.get("tool_name", "") tool_input_data = tool_input.get("tool_input", {}) + # Load configuration + config = load_config(project_dir) + trigger_mode = config.get("triggerMode", "default") + + # Check if this is a git commit (anywhere in the command, handles chained commands) + is_git_commit = False + command = "" + if tool_name == "Bash": + command = tool_input_data.get("command", "").strip() + is_git_commit = "git commit" in command + + # In gitmode, only process git commits + if trigger_mode == "gitmode" and not is_git_commit: + return + files_to_track = [] + commit_context = None + + # Handle git commit specially - extract commit context + if is_git_commit: + files, commit_context = handle_git_commit(project_dir) + files_to_track.extend(files) # Handle Edit/Write tools - extract file_path directly - if tool_name in ("Edit", "Write"): + elif tool_name in ("Edit", "Write"): file_path = tool_input_data.get("file_path", "") if file_path: files_to_track.append(file_path) # Handle Bash tool - parse command for file operations elif tool_name == "Bash": - command = tool_input_data.get("command", "") files_to_track = extract_files_from_bash(command, project_dir) # Legacy support: if no tool_name, try file_path directly @@ -163,7 +242,7 @@ def main(): if file_path: files_to_track.append(file_path) - if not project_dir or not files_to_track: + if not files_to_track: return # Filter to only trackable files @@ -172,12 +251,19 @@ def main(): if not trackable: return - dirty_file = Path(project_dir) / ".claude" / ".dirty-files" - dirty_file.parent.mkdir(parents=True, exist_ok=True) + # Ensure auto-memory directory exists + auto_memory_dir = Path(project_dir) / ".claude" / "auto-memory" + auto_memory_dir.mkdir(parents=True, exist_ok=True) + # Write dirty files (with optional commit context inline) + dirty_file = auto_memory_dir / "dirty-files" with open(dirty_file, "a") as f: for file_path in trackable: - f.write(file_path + "\n") + if commit_context: + # Format: /path/to/file [hash: message] + f.write(f"{file_path} [{commit_context['hash']}: {commit_context['message']}]\n") + else: + f.write(file_path + "\n") # NO output - zero token cost diff --git a/scripts/stop.py b/scripts/stop.py index d0309d5..15bb5a5 100644 --- a/scripts/stop.py +++ b/scripts/stop.py @@ -26,15 +26,25 @@ def main(): if input_data.get("stop_hook_active", False): return - dirty_file = Path(project_dir) / ".claude" / ".dirty-files" + dirty_file = Path(project_dir) / ".claude" / "auto-memory" / "dirty-files" # Pass through if no dirty files if not dirty_file.exists() or dirty_file.stat().st_size == 0: return # Get unique file list (max 20 files in message) + # Lines may have inline commit context: /path/to/file [hash: message] with open(dirty_file) as f: - files = sorted(set(line.strip() for line in f if line.strip()))[:20] + files = set() + for line in f: + line = line.strip() + if not line: + continue + # Strip inline commit context if present + if " [" in line: + line = line.split(" [")[0] + files.add(line) + files = sorted(files)[:20] if not files: return diff --git a/test-file-1.txt b/test-file-1.txt deleted file mode 100644 index 9ac9c78..0000000 --- a/test-file-1.txt +++ /dev/null @@ -1,4 +0,0 @@ -This is a test file to verify gitmode detection. -Created to test auto-memory git commit enrichment. - -Updated: Round 3 - testing chained command detection fix. diff --git a/test-file-2.txt b/test-file-2.txt deleted file mode 100644 index ee559ed..0000000 --- a/test-file-2.txt +++ /dev/null @@ -1,4 +0,0 @@ -Second test file for gitmode verification. -Auto-memory should capture commit hash and message. - -Updated: Round 2 of gitmode testing. diff --git a/test-inline-context.txt b/test-inline-context.txt deleted file mode 100644 index 8fd90e9..0000000 --- a/test-inline-context.txt +++ /dev/null @@ -1,2 +0,0 @@ -Testing inline commit context format. -This file should be tracked with commit hash and message inline. diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 30d927f..65ab593 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -43,12 +43,12 @@ class TestPostToolUseHook: text=True, ) assert result.returncode == 0 - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() def test_appends_paths(self, tmp_path): """Hook appends file paths to dirty file.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) existing_file = str(tmp_path / "existing" / "file.py") dirty_file.write_text(existing_file + "\n") @@ -102,7 +102,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert not dirty_file.exists() def test_excludes_claude_md(self, tmp_path): @@ -116,7 +116,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert not dirty_file.exists() def test_excludes_files_outside_project(self, tmp_path): @@ -130,7 +130,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert not dirty_file.exists() # Bash command tracking tests @@ -145,7 +145,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "file.py" in content @@ -160,7 +160,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "old_module" in content @@ -175,7 +175,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "file1.py" in content @@ -192,7 +192,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "obsolete.py" in content @@ -207,7 +207,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "old_name.py" in content @@ -224,7 +224,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "temp.txt" in content @@ -253,7 +253,7 @@ class TestPostToolUseHook: text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert not dirty_file.exists() def test_bash_no_output(self, tmp_path): @@ -281,7 +281,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" assert dirty_file.exists() content = dirty_file.read_text() assert "file.py" in content @@ -299,7 +299,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" content = dirty_file.read_text() assert "old.py" in content assert "ls" not in content @@ -315,7 +315,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" content = dirty_file.read_text() assert "build" in content assert "tee" not in content @@ -331,7 +331,7 @@ class TestPostToolUseHook: capture_output=True, text=True, ) - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" content = dirty_file.read_text() assert "deleted.py" in content assert "/dev/null" not in content @@ -355,7 +355,7 @@ class TestStopHook: def test_passes_when_active(self, tmp_path): """Hook passes through when stop_hook_active is true.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) dirty_file.write_text("/path/to/file.py\n") @@ -372,7 +372,7 @@ class TestStopHook: def test_blocks_with_files(self, tmp_path): """Hook blocks and outputs JSON when dirty files exist.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) dirty_file.write_text("/path/to/file.py\n") @@ -391,7 +391,7 @@ class TestStopHook: def test_json_format(self, tmp_path): """Hook output is valid JSON with required fields.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) dirty_file.write_text("/path/to/file.py\n") @@ -409,7 +409,7 @@ class TestStopHook: def test_deduplicates_files(self, tmp_path): """Hook deduplicates file paths in output.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) dirty_file.write_text("/file.py\n/file.py\n/file.py\n") @@ -427,7 +427,7 @@ class TestStopHook: def test_limits_file_count(self, tmp_path): """Hook limits file list to 20 files max.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) files = [f"/file{i}.py" for i in range(30)] dirty_file.write_text("\n".join(files) + "\n") @@ -450,7 +450,7 @@ class TestStopHook: def test_handles_invalid_json_input(self, tmp_path): """Hook handles invalid JSON input gracefully.""" - dirty_file = tmp_path / ".claude" / ".dirty-files" + dirty_file = tmp_path / ".claude" / "auto-memory" / "dirty-files" dirty_file.parent.mkdir(parents=True) dirty_file.write_text("/path/to/file.py\n") diff --git a/tests/test_integration.py b/tests/test_integration.py index ca089a3..62f23f6 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -108,10 +108,10 @@ class TestAgentConfiguration: frontmatter = parse_markdown_frontmatter(agent_path) assert "description" in frontmatter - def test_agent_uses_haiku(self, agent_path): - """Agent uses haiku model for efficiency.""" + def test_agent_uses_sonnet(self, agent_path): + """Agent uses sonnet model (haiku doesn't support extended thinking).""" frontmatter = parse_markdown_frontmatter(agent_path) - assert frontmatter.get("model") == "haiku" + assert frontmatter.get("model") == "sonnet" class TestCommandsConfiguration: diff --git a/uv.lock b/uv.lock index 78e8758..fe38621 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "claude-code-auto-memory" -version = "0.4.0" +version = "0.5.0" source = { editable = "." } [package.optional-dependencies]