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:
3
.claude/auto-memory/config.json
Normal file
3
.claude/auto-memory/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"triggerMode": "gitmode"
|
||||
}
|
||||
25
CLAUDE.md
25
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.
|
||||
|
||||
<!-- 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 -->
|
||||
|
||||
55
README.md
55
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
40
commands/sync.md
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -1,4 +0,0 @@
|
||||
Second test file for gitmode verification.
|
||||
Auto-memory should capture commit hash and message.
|
||||
|
||||
Updated: Round 2 of gitmode testing.
|
||||
@@ -1,2 +0,0 @@
|
||||
Testing inline commit context format.
|
||||
This file should be tracked with commit hash and message inline.
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user