From memento
Analyzes local modifications in Memory Bank files: computes MD5 hashes, detects new/ modified files, classifies changes like new sections or added lines, and provides merge strategies.
npx claudepluginhub mderk/memento --plugin mementoThis skill uses the workspace's default tool permissions.
Detect and analyze local modifications in Memory Bank files by:
Detects tech stack changes, plugin updates, and local modifications to selectively update Memory Bank files with 3-way merge preserving user edits.
Generates internal Ops update/migration guides from git diff analysis between refs. Auto-detects tags, analyzes commits for features, fixes, breaking changes; supports multi-language.
Updates README.md, CLAUDE.md, AGENTS.md, and CONTRIBUTING.md by analyzing codebase structure and fixing discrepancies. Use for initializing projects or syncing docs in git repos.
Share bugs, ideas, or general feedback.
Detect and analyze local modifications in Memory Bank files by:
Claude automatically invokes this skill when:
/create-environment needs to compute hashes after generating files/update-environment needs to detect and analyze local modificationsFrom target project, run:
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py <command> [args]
Commands: compute, compute-all, compute-source, detect, detect-source-changes, analyze, analyze-all, merge, commit-generation, recompute-source-hashes, update-plan, pre-update, copy-static
Compute hashes for files (used after generation).
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py compute .memory_bank/guides/testing.md
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py compute-all
Output:
{
"status": "success",
"files": [
{"path": ".memory_bank/guides/testing.md", "hash": "a1b2c3d4", "lines": 295}
]
}
Compare current hashes with stored hashes in generation-plan.md.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py detect
Output:
{
"status": "success",
"modified": [".memory_bank/guides/testing.md"],
"unchanged": [".memory_bank/guides/backend.md"],
"missing": [],
"new": [".memory_bank/guides/local-notes.md"],
"summary": {"total": 25, "modified": 1, "unchanged": 23, "missing": 0, "new": 1}
}
Analyze WHAT changed in modified files.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py analyze .memory_bank/guides/testing.md
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py analyze-all
Output:
{
"status": "success",
"path": ".memory_bank/guides/testing.md",
"hash": {
"stored": "a1b2c3d4",
"current": "x9y8z7w6"
},
"changes": [
{
"type": "new_section",
"header": "### Project-Specific Tests",
"level": 3,
"after_section": "## Unit Tests",
"lines": 15,
"content_preview": "Tests for domain-specific calculations..."
},
{
"type": "added_lines",
"in_section": "## Running Tests",
"lines_added": 3,
"content": [
"npm run test:integration",
"npm run test:e2e"
]
},
{
"type": "modified_content",
"in_section": "## API Patterns",
"lines_changed": 2,
"diff": "- Use Next.js patterns for all endpoints.\n+ Use Express patterns for API endpoints.",
"conflict": true
}
],
"merge_strategy": {
"auto_mergeable": [
{"type": "new_section", "header": "### Project-Specific Tests"},
{"type": "added_lines", "in_section": "## Running Tests"}
],
"requires_review": [
{"type": "modified_content", "in_section": "## API Patterns", "reason": "Content conflict"}
]
}
}
Compute hashes for source prompt/static files in plugin (used during generation).
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py compute-source prompts/memory_bank/README.md.prompt --plugin-root ${CLAUDE_PLUGIN_ROOT}
Output:
{
"status": "success",
"files": [
{
"path": "/path/to/plugin/prompts/memory_bank/README.md.prompt",
"relative_path": "prompts/memory_bank/README.md.prompt",
"hash": "def456gh",
"lines": 150
}
]
}
Detect which plugin prompts/statics have changed since last generation.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py detect-source-changes --plugin-root ${CLAUDE_PLUGIN_ROOT}
Output:
{
"status": "success",
"changed": [
{
"generated": ".memory_bank/guides/testing.md",
"source": "/path/to/plugin/prompts/memory_bank/guides/testing.md.prompt",
"stored_hash": "abc123",
"current_hash": "xyz789"
}
],
"unchanged": [
{
"generated": ".memory_bank/README.md",
"source": "/path/to/plugin/prompts/memory_bank/README.md.prompt"
}
],
"missing_source": [],
"no_source_hash": [".memory_bank/old-file.md"],
"summary": {
"total": 25,
"changed": 1,
"unchanged": 23,
"missing_source": 0,
"no_source_hash": 1
}
}
Merge a locally-modified file with a new plugin version using the Generation Base as reference.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py merge .memory_bank/guides/testing.md \
--base-commit abc1234 \
--new-file /tmp/new-testing.md \
[--write]
The script recovers the clean base via git show <base-commit>:<file>, reads the local file (with user additions), and merges with the new version.
With --write: if merge succeeds (no conflicts), the script writes merged content directly to the target file. When conflicts exist, the script does NOT write — it returns conflicts JSON for LLM resolution. This saves the LLM from reading merge JSON + writing file separately.
Output (no conflicts):
{
"status": "merged",
"merged_content": "# full merged file content...",
"conflicts": [],
"stats": {"from_new": 2, "from_local": 1, "unchanged": 5, "user_added": 1, "conflicts": 0}
}
Output (with conflicts):
{
"status": "conflicts",
"merged_content": "# merged with defaults for conflicts...",
"conflicts": [
{"section": "## API Patterns", "type": "both_modified", "base": "...", "local": "...", "new": "..."}
],
"stats": {"from_new": 1, "from_local": 0, "unchanged": 4, "user_added": 1, "conflicts": 1}
}
Merge rules (per section):
| Base | Local | New | Action |
|---|---|---|---|
| A | A | A' | Take new (plugin updated) |
| A | A' | A | Keep local (user modified) |
| A | A' | A'' | Conflict (both modified) |
| A | A | — | Skip (plugin removed, user didn't touch) |
| A | A' | — | Conflict (plugin removed, user modified) |
| — | — | B | Take new (plugin added) |
| — | B | — | Keep local (user added) |
| — | B | B' | Conflict (both added same header) |
Conflict types in output: both_modified, user_deleted, both_added, plugin_removed_user_modified.
When conflicts occur, the script defaults to keeping the local version and reports the conflict. Claude should show conflicts to the user for resolution.
Creates git commits for generated files, handling the two-commit system (base + merge).
Without merge (first generation or no local changes):
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py commit-generation --plugin-version 1.3.0
Creates two commits: one for generated files, one to update metadata with commit hashes. Sets both Generation Base and Generation Commit to the same hash.
With merge (local changes were merged into new versions):
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py commit-generation --plugin-version 1.3.0 \
--clean-dir /tmp/memento-clean/
The --clean-dir contains clean plugin output (before merge). The script:
--clean-dirmerge)generation-plan.md Metadata with both hashesOutput:
{
"status": "success",
"generation_base": "abc1234",
"generation_commit": "def5678",
"merge_applied": true,
"files_merged": [".memory_bank/guides/testing.md", ".memory_bank/workflows/bug-fixing.md"]
}
Why two commits? Generation Base stores clean plugin output so future 3-way merges can distinguish user additions from plugin content. Without this, previously-merged user additions would be silently dropped on the next update.
Pre-compute hashes for all source files (prompts + statics) into source-hashes.json. Run after modifying files in prompts/ or static/.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py recompute-source-hashes --plugin-root ${CLAUDE_PLUGIN_ROOT}
Output:
{
"status": "success",
"files": 60,
"written": "source-hashes.json"
}
The generated source-hashes.json maps relative paths to 8-character MD5 hashes:
{
"prompts/CLAUDE.md.prompt": "20d52ec2",
"prompts/memory_bank/README.md.prompt": "77ce72c3",
"static/memory_bank/workflows/development-workflow.md": "e3c77def"
}
Excludes manifest.yaml and __pycache__ directories. Other commands (compute-source, detect-source-changes, update-plan) read from this JSON instead of computing hashes on the fly.
Batch-update generation-plan.md after generating files. Computes file hashes, looks up source hashes from source-hashes.json, and updates the markdown table in one call.
--remove to delete rows from the plan for files that no longer exist# Update existing + auto-add new rows
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py update-plan .memory_bank/guides/testing.md .memory_bank/guides/new-guide.md --plugin-root ${CLAUDE_PLUGIN_ROOT}
# Update + remove obsolete rows
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py update-plan .memory_bank/README.md --plugin-root ${CLAUDE_PLUGIN_ROOT} --remove .memory_bank/guides/obsolete.md
Output:
{
"status": "success",
"updated": [
{"file": ".memory_bank/guides/testing.md", "lines": 295, "hash": "abc12345", "source_hash": "def67890"}
],
"added": [
{"file": ".memory_bank/guides/new-guide.md", "lines": 42, "hash": "xyz98765", "source_hash": "uvw54321"}
],
"removed": [
{"file": ".memory_bank/guides/obsolete.md"}
]
}
For each file: marks [x] in Status column, sets Lines, Hash, and Source Hash. If source hash is not found in JSON, falls back to computing from file.
Comprehensive pre-update detection that combines all Step 0 operations into one call.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py pre-update \
--plugin-root ${CLAUDE_PLUGIN_ROOT} \
[--new-analysis /tmp/new-project-analysis.json]
Internally runs:
detect — local file modificationsdetect-source-changes — plugin source changesprompts/**/*.prompt — reads frontmatter, compares with generation-plan.md → finds new/removed promptsstatic/manifest.yaml + evaluates conditionals → classifies static files using decision matrixproject-analysis.json for tech-stack diffOutput:
{
"status": "success",
"local_changes": { "modified": [...], "unchanged": [...] },
"source_changes": { "changed": [...], "unchanged": [...] },
"new_prompts": [{ "file": "prompts/...", "target": ".memory_bank/...", "conditional": "...", "applies": true }],
"removed_prompts": [{ "target": "...", "expected_source": "..." }],
"static_files": {
"new": [{ "source": "...", "target": "..." }],
"safe_overwrite": [...],
"local_only": [...],
"merge_needed": [...],
"up_to_date": [...],
"skipped_conditional": [...]
},
"obsolete_files": [{ "target": "...", "expected_source": "..." }],
"tech_stack_diff": { "high": [...], "medium": [...], "low": [...] },
"summary": {
"local_modified": 2, "source_changed": 3, "new_prompts": 1,
"removed_prompts": 0, "static_new": 1, "static_safe_overwrite": 5,
"static_merge_needed": 2, "static_local_only": 1, "static_up_to_date": 20,
"obsolete": 0
}
}
Copy all applicable static files in one call, with integrated 3-way merge for conflict-free cases.
python ${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py copy-static \
--plugin-root ${CLAUDE_PLUGIN_ROOT} \
[--clean-dir /tmp/memento-clean] \
[--filter new,safe_overwrite,merge_needed] \
[--base-commit abc1234]
--clean-dir: Save clean plugin versions (for commit-generation's two-commit system)--filter: Comma-separated categories to process (default: new,safe_overwrite,merge_needed)--base-commit: Enable 3-way merge for merge_needed files (recovers base via git show)Output:
{
"status": "success",
"copied": [{ "source": "...", "target": "...", "action": "new|safe_overwrite" }],
"auto_merged": [{ "target": "...", "stats": { "from_new": 2, "from_local": 1, "user_added": 1 } }],
"has_conflicts": [{ "target": "...", "conflicts": [{ "section": "...", "type": "both_modified" }] }],
"skipped": [{ "source": "...", "target": "...", "reason": "condition_false|local_only|up_to_date" }],
"summary": { "copied": 28, "auto_merged": 3, "conflicts": 1, "skipped": 5 }
}
LLM only needs to act on has_conflicts entries — present each conflict to user for resolution. Everything else is handled by the script.
| Type | Description | Auto-Merge? |
|---|---|---|
new_section | New ## or ### header with content | ✅ Yes |
added_lines | Lines added at end of existing section | ✅ Yes |
modified_content | Existing lines changed | ❌ No (conflict) |
deleted_lines | Lines removed from section | ⚠️ Review |
reordered_sections | Sections moved | ⚠️ Review |
import hashlib
def compute_hash(file_path: str, length: int = 8) -> str:
with open(file_path, 'rb') as f:
md5 = hashlib.md5(f.read()).hexdigest()
return md5[:length]
generation-plan.md to get stored hashesstored_hash != current_hash → modifieddifflib.unified_diff()## Headers and their contentnew_sectionadded_linesmodified_contentdef determine_merge_strategy(changes):
auto_merge = []
manual_review = []
for change in changes:
if change['type'] in ['new_section', 'added_lines']:
auto_merge.append(change)
else:
manual_review.append(change)
return {'auto_mergeable': auto_merge, 'requires_review': manual_review}
The generation-plan.md table includes both file hash and source hash:
| Status | File | Location | Lines | Hash | Source Hash |
|--------|------|----------|-------|------|-------------|
| [x] | README.md | .memory_bank/ | 127 | abc123 | def456 |
| [x] | testing.md | .memory_bank/guides/ | 295 | ghi789 | jkl012 |
Phase 2, Step 2 (static files):
analyze-local-changes copy-static --plugin-root ... --clean-dir /tmp/memento-clean
analyze-local-changes update-plan <all copied targets> --plugin-root ...
Phase 2, Step 6 (prompt-generated files):
For each file: generate → write to /tmp/memento-clean/<path>
If merge mode: analyze-local-changes merge <target> --base-commit <base> --new-file /tmp/... --write
After all batches: analyze-local-changes update-plan <all file paths> --plugin-root ...
Phase 3: After all files:
analyze-local-changes commit-generation --plugin-version X.Y.Z [--clean-dir /tmp/memento-clean/]
Step 0: Detect all changes in one call:
analyze-local-changes pre-update --plugin-root ... [--new-analysis /tmp/new-analysis.json]
Step 4A (static files):
analyze-local-changes copy-static --plugin-root ... --clean-dir /tmp/memento-clean \
--filter new,safe_overwrite,merge_needed --base-commit <generation_base>
analyze-local-changes update-plan <all copied/merged targets> --plugin-root ...
Step 4B (prompt-generated merges):
analyze-local-changes merge <target> --base-commit <base> --new-file /tmp/... --write
Step 5: After all files:
analyze-local-changes commit-generation --plugin-version X.Y.Z [--clean-dir /tmp/memento-clean/]
User added project-specific testing section to testing.md:
Input: testing.md with new "### Integration Tests" section
Analysis:
{
"changes": [
{
"type": "new_section",
"header": "### Integration Tests",
"after_section": "## Unit Tests",
"lines": 15
}
],
"merge_strategy": {
"auto_mergeable": [{"type": "new_section", ...}],
"requires_review": []
}
}
Result: Can auto-merge by inserting section after "## Unit Tests"
User modified existing API patterns section:
Input: backend.md with changed "## API Patterns" content
Analysis:
{
"changes": [
{
"type": "modified_content",
"in_section": "## API Patterns",
"diff": "- Use Next.js patterns\n+ Use FastAPI patterns",
"conflict": true
}
],
"merge_strategy": {
"auto_mergeable": [],
"requires_review": [{"type": "modified_content", ...}]
}
}
Result: Requires user decision - keep local or use plugin version
${CLAUDE_PLUGIN_ROOT}/skills/analyze-local-changes/scripts/analyze.py
All built-in (no pip install required):
hashlib - MD5 computationdifflib - Diff computationsubprocess - Git operations (merge, commit-generation)json - JSON outputre - Markdown parsingpathlib - Path handlingargparse - CLI arguments0: Success1: File not found2: generation-plan.md not found3: Invalid mode/argumentsmerge and commit-generation are the only commands that modify files/gitmerge and commit-generation)commit-generation preserves clean plugin base for future 3-way merges