Two-phase cleanup - normalize long filenames, then consolidate semantic duplicates using AI
Two-phase knowledge base cleanup: normalize verbose filenames, then consolidate semantic duplicates.
/mem-consolidate
/mem-consolidate project:my-project
/mem-consolidate project:my-project dryRun:true
/mem-consolidate project:my-project normalizeOnly:true # Skip duplicate merging
Phase 1: Normalize Long Filenames
āāā Find files with names > 40 chars
āāā AI suggests short generic titles (2-4 words)
āāā Rename files, add original as alias
āāā Handle collisions (merge or suffix)
Phase 2: Consolidate Semantic Duplicates
āāā AI groups notes by topic similarity
āāā Merge groups into single files
āāā Archive source files
āāā Update aliases for future matching
Before any file operations, you MUST read the config:
Read user config: ~/.cc-obsidian-mem/config.json
C:\Users\{username}\.cc-obsidian-mem\config.json~/.cc-obsidian-mem/config.jsonExtract settings from config:
{
"vault": {
"path": "/path/to/obsidian/vault",
"memFolder": "_claude-mem"
},
"ai": {
"enabled": true,
"model": "sonnet",
"timeout": 30000
}
}
Construct project paths:
{vault.path}/{vault.memFolder}{vault.path}/{vault.memFolder}/projects{vault.path}/{vault.memFolder}/projects/{project-name}Note AI settings for semantic matching:
ai.enabled: Whether to use AI (default: true)ai.model: "sonnet", "haiku", or "opus" (default: sonnet)Important: Never hardcode or assume vault paths. Always read from config first.
Consolidation is always scoped to a single project. Notes from different projects are never merged together.
.git root)For each category (decisions, patterns, errors, research, knowledge):
List all files in {project}/{category}/ folder
decisions/decisions.md).archive/ subfolderBuild notes list with title, category, and filename length:
[
{ "title": "Stop hook non-blocking", "category": "decisions", "path": "...", "filenameLength": 24 },
{ "title": "QA: How to prevent stop hook from blocking", "category": "research", "path": "...", "filenameLength": 53 },
...
]
Before finding duplicates, normalize verbose filenames to short generic titles.
Find all files with filename length > 40 characters (excluding .md extension):
Long filenames found:
1. [decisions] use-writeifchanged-pattern-to-avoid-unnecessary-fi.md (50 chars)
2. [research] qa-how-to-prevent-stop-hook-from-blocking-claude-s.md (53 chars)
3. [patterns] atomic-file-operations-with-collision-handling.md (47 chars)
Send long filenames to AI for generic title suggestions:
Suggest short generic titles (2-4 words) for these verbose filenames.
FILES:
0. [decisions] "use-writeifchanged-pattern-to-avoid-unnecessary-fi"
1. [research] "qa-how-to-prevent-stop-hook-from-blocking-claude-s"
2. [patterns] "atomic-file-operations-with-collision-handling"
Respond with JSON:
{"titles": ["WriteIfChanged pattern", "Stop hook blocking", "Atomic file operations"]}
Rules:
- Keep titles short: 2-4 words maximum
- Capture the core concept, not implementation details
- Remove prefixes like "qa-", "how-to-", "pattern-for-"
- Use title case
For each long filename:
write-if-changed-pattern.md)Example transformation:
Before: use-writeifchanged-pattern-to-avoid-unnecessary-fi.md
After: write-if-changed-pattern.md
aliases: ["use writeifchanged pattern to avoid unnecessary fi"]
If the generic filename already exists:
write-if-changed-pattern-2.mdUse AI semantic matching to identify groups of notes within the current project that discuss the same topic.
Send this prompt to Claude CLI using the model from config:
Group these notes by semantic similarity (same topic, different wording).
NOTES (0-indexed):
0. [decisions] "Stop hook non-blocking"
1. [research] "QA: How to prevent stop hook from blocking"
2. [research] "QA: Stop hook blocking Claude session"
3. [patterns] "Two-phase locking"
...
Respond with JSON using 0-based indices:
{"groups": [[0,1,2], [3]], "genericTitles": ["Stop hook blocking", "Two-phase locking"]}
Rules:
- Only group notes that are semantically about the SAME topic
- Notes in different categories CAN be grouped if same topic
- Each note appears in at most one group
- Single-note groups can be omitted
- Suggest a short generic title (2-4 words) for each group
echo '<prompt>' | claude -p - --model {config.ai.model} --no-session-persistence --output-format text
AI returns groups like:
{
"groups": [[0,1,2], [5,8,12]],
"genericTitles": ["Stop hook blocking", "Windows path normalization"]
}
For each group with 2+ notes:
Show the group for confirmation (unless auto-mode):
Found duplicate group: "Stop hook blocking"
1. [decisions] stop-hook-non-blocking.md
2. [research] qa-how-to-prevent-stop-hook-from-blocking-claude-s.md
3. [research] qa-stop-hook-blocking-claude-session.md
Merge into: decisions/stop-hook-blocking.md ?
[Yes / Skip / Custom target]
Choose target file:
Merge content:
created date (oldest first)## Entry: YYYY-MM-DD HH:MMentry_countWrite merged file to target path
Archive source files (except target) to .archive/
When merging multiple files into one:
Sort files by created date (oldest first)
created field (ISO8601 format)created missingPrepare merged content:
created date## Entry: YYYY-MM-DD HH:MM
{content from this file}
entry_count from all ## Entry headersWrite merged file:
Archive source files (except the target file):
.archive/ subfolderWhen user requests automatic merging (no prompts):
Trust AI groupings: Auto-merge all groups identified by AI
Category priority for target: When group spans categories:
Still prompt for:
After processing all files:
# Consolidation Summary
**Scanned**: {N} files across {M} categories
**AI Model**: {config.ai.model}
## Phase 1: Filename Normalization
| Before | After | Category |
|--------|-------|----------|
| use-writeifchanged-pattern-to-avoid-unnecessary-fi.md | write-if-changed-pattern.md | decisions |
| qa-how-to-prevent-stop-hook-from-blocking-claude-s.md | stop-hook-blocking.md | research |
**Normalized**: {X} files renamed to shorter titles
## Phase 2: Duplicate Consolidation
| Group | Files | Target | Aliases Added |
|-------|-------|--------|---------------|
| Stop hook blocking | 3 | decisions/stop-hook-blocking.md | 2 |
| Windows path normalization | 4 | research/windows-path-normalization.md | 3 |
**Merged**: {Y} duplicate groups
## Skipped
- `{filename}`: {reason}
## Final Stats
- Files before: {N}
- Normalized: {X} long filenames ā short titles
- Merged: {Y} duplicate groups
- Files after: {M}
- Total reduction: {N-M} files ({percentage}%)
---
Archived files are in `.archive/` folders.
Aliases added will improve future Jaccard matching.
Filename-based matching (Jaccard word similarity) misses semantic duplicates:
AI understands that these are the same topic even with different wording.
When AI identifies duplicates and they're merged:
If project has many notes (50+), batch the AI call:
Problem: Category index files like decisions/decisions.md have the same slug as their folder name.
Solution: Explicitly exclude files matching ${category}.md pattern.
Problem: sessions/ folder contains auto-generated session notes.
Solution: Exclude sessions category from consolidation.
Problem: Some files may not have created field in frontmatter.
Solution: Fall back to filesystem mtime and warn user.
Problem: Some generic files may already have multiple entries from previous consolidations.
Solution: Append new entries, merge aliases, update entry_count.
Problem: File already exists in .archive/ from previous consolidation.
Solution: Append timestamp to filename: auth-bug_20260115-103000.md
.archive/ for easy recoveryAfter consolidation, suggest:
.archive/ folders/mem-audit to verify vault structure is clean