Implement inline commit context, sync command, and trigger modes

- Consolidate commit-context.json into dirty-files with inline format:
  /path/to/file [hash: commit message]
- Switch memory-updater agent from haiku to sonnet (fixes #7)
- Add /auto-memory:sync command for manual file changes (fixes #5)
- Add configurable trigger modes: default vs gitmode (fixes #6)
- Update tests for new paths and model expectation
This commit is contained in:
severity1
2025-12-04 01:06:53 +13:00
parent fe38414718
commit e68fa5c36d
14 changed files with 285 additions and 64 deletions

View File

@@ -0,0 +1,3 @@
{
"triggerMode": "gitmode"
}

View File

@@ -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.
<!-- END AUTO-MANAGED -->
@@ -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)
<!-- END AUTO-MANAGED -->
@@ -77,8 +82,12 @@ claude-code-auto-memory/
- **CLAUDE.md Markers**: Use `<!-- AUTO-MANAGED: section-name -->` and `<!-- END AUTO-MANAGED -->` for auto-updated sections
- **Manual Sections**: Use `<!-- MANUAL -->` 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
<!-- END AUTO-MANAGED -->

View File

@@ -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)

View File

@@ -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 -- <file>` for each changed file
- `git diff HEAD~5 -- <file> | 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

View File

@@ -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.

40
commands/sync.md Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
Second test file for gitmode verification.
Auto-memory should capture commit hash and message.
Updated: Round 2 of gitmode testing.

View File

@@ -1,2 +0,0 @@
Testing inline commit context format.
This file should be tracked with commit hash and message inline.

View File

@@ -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")

View File

@@ -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:

2
uv.lock generated
View File

@@ -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]