commit c2e58f5d7ee7743309fe943ca25d88b443dcde8c Author: Zhongwei Li Date: Sat Nov 29 17:58:13 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..486e92a --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "tabula-scripta", + "description": "Working memory system for Claude Code - integrates with Obsidian via MCP for persistent, cross-session memory management", + "version": "0.1.0", + "author": { + "name": "Drew Ritter", + "email": "drew@ritter.dev" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f802249 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tabula-scripta + +Working memory system for Claude Code - integrates with Obsidian via MCP for persistent, cross-session memory management diff --git a/commands/recall.md b/commands/recall.md new file mode 100644 index 0000000..c453418 --- /dev/null +++ b/commands/recall.md @@ -0,0 +1,386 @@ +# Recall - Search Existing Memories + +Search for existing memory notes in the Obsidian vault and present the top results. + +## Usage + +``` +/recall [query] +``` + +**Arguments:** +- `query` - Search terms to find relevant memories (required) + +**Examples:** +``` +/recall git worktrees +/recall debugging race condition +/recall react hooks patterns +/recall authentication flow +``` + +## Implementation + +When this command is invoked: + +### 1. Parse and Validate Query + +1. Parse the query by trimming whitespace from the command input +2. Validate that the query is not empty + - If query is empty, return error message: "Query is required. Usage: /recall [query]" + +### 2. Detect Project Context + +1. Attempt to detect the current project from the git repository: + - Run `git rev-parse --show-toplevel` to find the git repository root + - Extract the project name from the repository directory name +2. If not in a git repository: + - Fall back to using the current working directory name + - If that fails, use 'default' as the project name +3. Store the detected project name for scoped search + +### 3. Attempt Semantic Search (Smart Connections) + +**Note:** Semantic search via Smart Connections plugin is an optional enhancement. If available, it provides better relevance ranking and conceptual matching. + +1. Set initial search method to 'semantic' and initialize empty results array +2. Attempt to use semantic search (if Smart Connections plugin is installed): + - Try to perform semantic search across both project notes and global entities + - Search path: `claude/projects/{currentProject}/**` and `claude/global/**` + - Retrieve top 10 results for ranking +3. If Smart Connections is not available or fails: + - Set search method to 'text' for fallback + - Continue to text search in next step + +**Implementation Note:** Since obsidian-mcp-plugin does not provide native Smart Connections integration, this step may require custom MCP extensions or can be skipped in favor of text search only. + +### 4. Perform Text Search + +If semantic search was unavailable or returned no results, use text search: + +1. Invoke MCP `search_notes` operation for project notes: + - vault: `~/.claude-memory` + - query: The user's search query + - path_filter: `claude/projects/{currentProject}/**` + +2. Invoke MCP `search_notes` operation for global entities: + - vault: `~/.claude-memory` + - query: The user's search query + - path_filter: `claude/global/entities/**` + +3. Combine the project results and global results, removing any duplicates +4. Set search method to 'text' + +5. Handle errors: + + **If MCPUnavailableError:** + - Return graceful degradation message explaining: + - MCP server is unavailable + - Steps to restore: ensure Obsidian is running, check plugin installation, verify config + - Reference to docs/setup-guide.md + + **If other error:** + - Return error message with details + +### 5. Rank and Filter Results + +1. Rank results by relevance: + - If semantic search was used: Results are already ranked by vector similarity + - If text search was used: Rank by match frequency and recency + - Calculate match count: How many times the query appears in the result + - Calculate recency boost: More recent notes ranked higher + - Combine into relevance score: match count + (recency boost × 10) + - Sort results by score in descending order + +2. Limit to top 5 results for presentation + +3. Check if no results were found: + - If results are empty, return info message with: + - "No memories found for query: '{query}'" + - Suggestions: Try different terms, use /remember to create memories, search more broadly + - Current project and search scope information + +### 6. Track Cross-Project Recalls + +Use the cross-project tracking patterns defined in the managing-working-memory skill: + +1. Identify cross-project recalls by filtering results: + - A recall is cross-project if the note's project differs from the current project + - Exclude global entities (project = 'global') as they're expected to be cross-project + +2. For each cross-project entity recall: + - Load the current note using MCP `read_note` + - Append a new entry to the `cross_project_recalls` frontmatter array: + - project: Current project name + - date: Current date (YYYY-MM-DD) + - context: "Recalled via search: '{query}'" + - Update the note using MCP `update_note` with: + - Updated cross_project_recalls array + - Updated claude_last_accessed timestamp + +3. Check if promotion threshold is met: + - If cross_project_recalls length >= 3, trigger promotion prompt (see below) + - Promotion prompt is handled by the managing-working-memory skill + +**Promotion Prompt Format (I4):** + +When an entity reaches 3 cross-project recalls, display: + +``` +I've referenced [[{Entity Name}]] from {source-project} while working on other projects 3 times now: + +1. {project-name} ({date}): {context} +2. {project-name} ({date}): {context} +3. {project-name} ({date}): {context} + +This pattern seems reusable across projects. Should I promote it to global knowledge? + +Options: +1. Yes, promote to global (move to ~/.claude-memory/claude/global/entities/) +2. Remind me later (ask again after 5 cross-project recalls) +3. No, it's project-specific (stop tracking) + +What should I do? +``` + +User responses: +- If option 1: Execute promotion process (move entity, update frontmatter, create redirect) +- If option 2: Continue tracking, increase threshold to 5 +- If option 3: Clear cross_project_recalls array and stop tracking + +4. Handle errors silently: + - Cross-project tracking is best-effort + - Log warnings for failed tracking but don't interrupt the search flow + +### 7. Present Results + +1. Format the output message with: + - Header: "Found {count} {memory/memories} for: '{query}'" + - Search method used: Semantic or Text search + - Current project name + - Blank line + +2. For each result (numbered 1-5): + - Extract title from path (filename without extension) + - Get type, project, and updated date from frontmatter + - Get snippet preview from search result + - Format as: + ``` + {number}. [[{title}]] ({type}) + Project: {project} + Updated: {updated} + Preview: {first 150 chars of snippet}... + ``` + +3. Add footer with options: + - "Type a number (1-{count}) to load full note content" + - "Continue conversation to work with these memories" + - "Use /remember to create a new memory if nothing matches" + +4. Return the formatted output as a success message + +## Error Handling + +### Missing Query + +**Input:** `/recall` + +**Output:** +``` +Error: Query is required. + +Usage: /recall [query] + +Examples: + /recall git worktrees + /recall debugging race condition + /recall react hooks patterns +``` + +### No Results Found + +**Input:** `/recall nonexistent topic` + +**Output:** +``` +No memories found for query: "nonexistent topic" + +Suggestions: +- Try different search terms +- Check if you've created memories for this topic (use /remember) +- Search more broadly (fewer specific terms) + +Current project: tabula-scripta +Searched: Project notes + global entities +``` + +### MCP Unavailable + +**Input:** `/recall authentication` (when MCP server is down) + +**Output:** +``` +Obsidian MCP server is unavailable. Cannot search memories. + +To restore memory features: +1. Ensure Obsidian is running +2. Check obsidian-mcp-plugin is installed in ~/.claude-memory/ +3. Verify Claude Code config includes MCP server + +See docs/setup-guide.md for troubleshooting. +``` + +## Success Output + +**Input:** `/recall git worktrees` + +**Output:** +``` +Found 3 memories for: "git worktrees" +Search method: Semantic (Smart Connections) +Project: tabula-scripta + +1. [[Git Worktrees]] (entity) + Project: tabula-scripta + Updated: 2025-11-18 + Preview: Git worktrees enable isolated directory trees for parallel development. Each worktree has its own working directory but shares the .git repository... + +2. [[Parallel Execution Patterns]] (topic) + Project: global + Updated: 2025-11-17 + Preview: Techniques for concurrent task execution including git worktrees, background processes, and isolation strategies... + +3. [[2025-11-18 - Spectacular Implementation]] (session) + Project: tabula-scripta + Updated: 2025-11-18 + Preview: Implementing parallel phase execution using git worktrees for task isolation. Decision: Use trap handlers for cleanup... + + +Options: +- Type a number (1-3) to load full note content +- Continue conversation to work with these memories +- Use /remember to create a new memory if nothing matches +``` + +## Search Scope + +The `/recall` command searches: + +1. **Current project notes:** + - `claude/projects/{current-project}/sessions/**` + - `claude/projects/{current-project}/entities/**` + +2. **Global entities:** + - `claude/global/entities/**` + - `claude/global/topics/**` + +3. **Excludes:** + - Archived sessions (`claude/projects/{project}/archive/**`) + - Other project's sessions (unless global) + +## Semantic Search vs Text Search + +### Semantic Search (Preferred) + +**Requirements:** +- Smart Connections plugin installed in Obsidian +- Plugin configured for `~/.claude-memory/` vault + +**Advantages:** +- Better relevance ranking +- Finds conceptually similar notes +- Handles synonyms and related concepts + +**Example:** +- Query: "concurrency issues" +- Finds: "Race Condition Debugging", "Parallel Execution Gotchas" + +### Text Search (Fallback) + +**When used:** +- Smart Connections not installed +- Smart Connections unavailable +- Semantic search fails + +**Behavior:** +- Exact/fuzzy text matching +- Ranked by match frequency and recency +- Still effective for keyword search + +**Example:** +- Query: "race condition" +- Finds: Notes containing exact phrase "race condition" + +## Cross-Project Recall Tracking + +When a memory from Project A is recalled while working in Project B: + +1. **Silent logging:** + - Update `cross_project_recalls` frontmatter array + - No user-visible output (non-intrusive) + +2. **Threshold detection:** + - After 3 cross-project recalls + - Trigger promotion prompt (via `managing-working-memory` skill) + +3. **Context capture:** + ```yaml + cross_project_recalls: + - project: tabula-scripta + date: 2025-11-18 + context: "Recalled via search: \"git worktrees\"" + - project: another-project + date: 2025-11-19 + context: "Recalled via search: \"isolation patterns\"" + ``` + +## Interactive Follow-Up + +After presenting results, user can: + +1. **Load full note content:** + - User types: `1` + - Claude loads: Full content of result #1 + +2. **Continue conversation:** + - User asks: "What did we decide about worktree cleanup?" + - Claude references loaded memories in response + +3. **Create new memory:** + - User types: `/remember entity Worktree Cleanup` + - New entity created based on discussion + +## Relevance Filtering + +Results are filtered for relevance: + +- **Minimum score threshold:** Only include results with score > 0.3 +- **Recency boost:** Recently updated notes ranked higher +- **Type priority:** Entities ranked above sessions (more persistent knowledge) +- **Project scoping:** Current project results ranked above cross-project results + +## Acceptance Criteria + +- [ ] Finds notes via semantic search (Smart Connections) when available +- [ ] Falls back to text search when semantic search unavailable +- [ ] Presents top 5 results with relevance ranking +- [ ] Handles MCP unavailable gracefully +- [ ] Detects project context correctly from git repo +- [ ] Tracks cross-project recalls silently +- [ ] Searches both project notes and global entities +- [ ] Shows clear message when no results found +- [ ] Includes note type, project, updated date, and preview in results +- [ ] Offers interactive follow-up options + +## Integration with managing-working-memory Skill + +This command provides the manual interface for memory search. The `managing-working-memory` skill uses the same search logic for: +- Proactive recall at session start +- Finding related entities during updates +- Cross-project pattern detection + +**Relationship:** +- `/recall` - Manual, user-initiated search +- `managing-working-memory` - Automatic, skill-driven search +- Both use same search methods (semantic → text fallback) +- Both track cross-project recalls diff --git a/commands/remember.md b/commands/remember.md new file mode 100644 index 0000000..9028a71 --- /dev/null +++ b/commands/remember.md @@ -0,0 +1,265 @@ +# Remember - Create New Memory Note + +Create a new memory note in the Obsidian vault at `~/.claude-memory/`. + +## Usage + +``` +/remember [type] [title] +``` + +**Arguments:** +- `type`: The type of memory note (required) + - `session` - Temporal note for current work session + - `entity` - Persistent note for a concept/component/pattern + - `topic` - Organizational note (Map of Content) +- `title` - The title for the memory note (required) + +**Examples:** +``` +/remember entity Git Worktrees +/remember session 2025-11-18 - Working Memory Implementation +/remember topic React Patterns +``` + +## Implementation + +When this command is invoked: + +### 1. Parse and Validate Arguments + +1. Parse the command input by splitting on spaces +2. Extract the first argument as the type +3. Extract remaining arguments and join with spaces as the title +4. Validate that type is one of the allowed values: session, entity, or topic + - If type is invalid, return error message: "Invalid type '{type}'. Must be one of: session, entity, topic" +5. Validate that title is provided and not empty + - If title is missing or empty, return error message: "Title is required. Usage: /remember [type] [title]" + +### 2. Detect Project Context + +1. Attempt to detect the current project from the git repository: + - Run `git rev-parse --show-toplevel` to find the git repository root + - Extract the project name from the repository directory name +2. If not in a git repository (command fails): + - Fall back to using the current working directory name as the project + - If that also fails, use 'default' as the project name +3. Store the detected project name for use in vault path generation + +### 3. Generate Vault Path + +1. Sanitize the title to create a valid filename: + - Replace any characters that are invalid in filenames with hyphens + - Invalid characters include: / \ : * ? " < > | +2. Generate the vault path based on the note type: + - For session notes: `claude/projects/{project}/sessions/{sanitized-title}.md` + - For entity notes: `claude/projects/{project}/entities/{sanitized-title}.md` + - For topic notes: `claude/global/topics/{sanitized-title}.md` + +Note that topic notes are always global (not project-specific) + +### 4. Generate Frontmatter + +1. Get the current date in YYYY-MM-DD format +2. Create frontmatter with the following fields: + - type: The note type (session, entity, or topic) + - project: The project name, or 'global' for topic notes + - tags: Type-specific tags + - Session notes: [session, work-in-progress] + - Entity notes: [entity] + - Topic notes: [topic, moc] + - created: Current date (YYYY-MM-DD) + - updated: Current date (YYYY-MM-DD) + - status: 'active' + - claude_last_accessed: Current date (YYYY-MM-DD) + - cross_project_recalls: Empty array (for tracking cross-project usage) + +### 5. Generate Note Content from Template + +Use the note templates defined in the managing-working-memory skill to generate the initial content. The templates vary by note type: + +**For session notes:** +- Include frontmatter with all required fields +- Add a main heading with the session title +- Include sections for: + - Context (why we're working on this) + - Work Log (timestamped entries of what happened) + - Decisions Made (with rationale) + - Open Questions (blockers or uncertainties) + - Next Steps (action items as checkboxes) + - Related Entities (wikilinks to relevant entities) + +**For entity notes:** +- Include frontmatter with all required fields +- Add a main heading with the entity name +- Include sections for: + - Overview (purpose and role) + - Architecture (structure and how it works) + - Key Decisions (with date, rationale, alternatives, impact) + - Gotchas & Troubleshooting (symptom, root cause, solution, tags) + - Recent Changes (timestamped log) + - Related Entities (wikilinks with relationship descriptions) + - References (external docs, code paths, commits) + +**For topic notes:** +- Include frontmatter with project set to 'global' +- Add a main heading with the topic name +- Include sections for: + - Overview (what the topic covers) + - Key Concepts (wikilinks to core entities) + - Patterns & Best Practices (descriptions and related entities) + - Common Pitfalls (antipatterns to avoid) + - Learning Path (ordered sequence of entities) + - References (external resources and documentation) + +All templates use placeholder text in curly braces {like this} to indicate where content should be filled in. + +### 6. Invoke MCP create_note + +1. Invoke the MCP `create_note` operation with: + - vault: `~/.claude-memory` + - path: The generated vault path + - content: The generated note content with frontmatter + +2. Handle the response: + + **If successful:** + - Return success message including: + - Confirmation with wikilink to the created note + - Type and project information + - Vault path + - Note that the note is ready for editing + + **If FileExistsError:** + - Return error message indicating the note already exists + - Suggest using /update-memory to update it, or choosing a different title + + **If MCPUnavailableError:** + - Return graceful degradation message explaining: + - MCP server is unavailable + - Steps to restore: ensure Obsidian is running, check plugin installation, verify config + - Reference to docs/setup-guide.md + - Offer to create a draft in a local markdown file instead + + **If unknown error:** + - Return error message with the error details + +## Error Handling + +### Invalid Type + +**Input:** `/remember invalid-type My Note` + +**Output:** +``` +Error: Invalid type "invalid-type". Must be one of: session, entity, topic + +Usage: /remember [type] [title] + +Examples: + /remember entity Git Worktrees + /remember session 2025-11-18 - Working Memory Implementation + /remember topic React Patterns +``` + +### Missing Title + +**Input:** `/remember entity` + +**Output:** +``` +Error: Title is required. + +Usage: /remember [type] [title] + +Examples: + /remember entity Git Worktrees + /remember session 2025-11-18 - Working Memory Implementation + /remember topic React Patterns +``` + +### Note Already Exists + +**Input:** `/remember entity Git Worktrees` (when it already exists) + +**Output:** +``` +Error: Memory note [[Git Worktrees]] already exists at claude/projects/tabula-scripta/entities/Git Worktrees.md + +Use /update-memory to update an existing note, or choose a different title. +``` + +### MCP Unavailable + +**Input:** `/remember entity My Component` (when MCP server is down) + +**Output:** +``` +Obsidian MCP server is unavailable. Cannot create memory note. + +To restore memory features: +1. Ensure Obsidian is running +2. Check obsidian-mcp-plugin is installed in ~/.claude-memory/ +3. Verify Claude Code config includes MCP server + +See docs/setup-guide.md for troubleshooting. + +Would you like me to create a draft in a local markdown file instead? +``` + +## Success Output + +**Input:** `/remember entity Git Worktrees` + +**Output:** +``` +Created memory note: [[Git Worktrees]] + +Type: entity +Project: tabula-scripta +Path: claude/projects/tabula-scripta/entities/Git Worktrees.md + +The note is ready for editing in Obsidian or via /update-memory. +``` + +## Integration with managing-working-memory Skill + +This command provides the manual interface for memory creation. The `managing-working-memory` skill uses the same underlying logic but triggers automatically based on workflow events (code review, debugging, etc.). + +**Relationship:** +- `/remember` - Manual, user-initiated memory creation +- `managing-working-memory` - Automatic, skill-driven memory creation +- Both use identical frontmatter schema and note templates +- Both invoke same MCP operations + +## Wikilink Generation + +All created notes support Obsidian wikilinks: +- Entity reference: `[[Git Worktrees]]` +- Session reference: `[[2025-11-18 - Working Memory Implementation]]` +- Topic reference: `[[React Patterns]]` + +Links work bidirectionally in Obsidian's graph view. + +## Project Context Detection + +The command detects project context in priority order: + +1. **Git repository name** - `git rev-parse --show-toplevel` +2. **Working directory name** - `path.basename(process.cwd())` +3. **Fallback** - `'default'` + +This ensures session and entity notes are scoped to the correct project. + +## Acceptance Criteria + +- [ ] Creates notes at correct vault path with valid frontmatter +- [ ] Validates type argument (session/entity/topic) +- [ ] Requires title argument +- [ ] Detects project context from git repo +- [ ] Generates wikilinks correctly +- [ ] Handles MCP unavailable gracefully +- [ ] Shows clear error for note already exists +- [ ] Uses templates from managing-working-memory skill +- [ ] Frontmatter includes all required fields +- [ ] Timestamp format is YYYY-MM-DD diff --git a/commands/update-memory.md b/commands/update-memory.md new file mode 100644 index 0000000..d8d521f --- /dev/null +++ b/commands/update-memory.md @@ -0,0 +1,459 @@ +# Update Memory - Update Existing Note + +Update an existing memory note with new information, using patch operations and conflict detection. + +## Usage + +``` +/update-memory [title] +``` + +**Arguments:** +- `title` - The title of the existing memory note to update (required) + +**Examples:** +``` +/update-memory Git Worktrees +/update-memory 2025-11-18 - Working Memory Implementation +/update-memory React Patterns +``` + +## Implementation + +When this command is invoked: + +### 1. Parse and Validate Title + +1. Parse the title by trimming whitespace from the command input +2. Validate that the title is not empty + - If title is empty, return error message: "Title is required. Usage: /update-memory [title]" + +### 2. Detect Project Context + +1. Attempt to detect the current project from the git repository: + - Run `git rev-parse --show-toplevel` to find the git repository root + - Extract the project name from the repository directory name +2. If not in a git repository: + - Fall back to using the current working directory name + - If that fails, use 'default' as the project name +3. Store the detected project name for locating the note + +### 3. Search for Note + +1. Try to find the note by checking multiple possible locations in order: + - Current project entities: `claude/projects/{currentProject}/entities/{title}.md` + - Current project sessions: `claude/projects/{currentProject}/sessions/{title}.md` + - Global entities: `claude/global/entities/{title}.md` + - Global topics: `claude/global/topics/{title}.md` + +2. For each possible path: + - Attempt to read the note using MCP `read_note` + - If successful, store the path and note content, then stop searching + - If FileNotFoundError, continue to next path + - If other error, propagate it + +3. If not found in standard locations, use search as fallback: + - Invoke MCP `search_notes` with query=title and path_filter='claude/**' + - Find exact title match in results (case-insensitive comparison) + - If exact match found, load the note using MCP `read_note` + +4. If still not found after all attempts: + - Return error message listing all searched locations + - Suggest using /remember to create the note + - Provide example command + +### 4. Load Note and Check Timestamps + +1. Store the loaded timestamp from frontmatter for conflict detection later + - Save note.frontmatter.updated as the loaded timestamp + +2. Update claude_last_accessed in frontmatter to current date (YYYY-MM-DD) + +3. Display the current note content to the user: + - Show wikilink title, type, project, last updated date, and path + - Display full current content + - Add separator line + - Prompt: "What would you like to update? I'll apply patch operations to preserve existing content." + +### 5. Collect User Updates + +This is a conversational step where the user describes what they want to update. + +1. User provides their update request in natural language + - Example: "Add a new decision about using trap handlers for cleanup" + +2. Parse the user's intent to identify the appropriate patch operation type: + - Append to section: Add new entry to an existing section (e.g., "## Recent Changes") + - Add new section: Create a completely new section (e.g., "## Troubleshooting") + - Update frontmatter: Modify frontmatter fields (e.g., add a tag) + - Insert in list: Add item to an existing list (e.g., "## Related Entities") + +3. Confirm with the user what will be done + - Example: "I'll add this to the Key Decisions section." + +### 6. Check for Conflicts (Before Writing) + +Use the conflict detection logic defined in the managing-working-memory skill: + +1. Before applying the patch, reload the note using MCP `read_note` to get the current state + +2. Compare timestamps to detect conflicts: + - Get the current timestamp from the reloaded note's frontmatter.updated + - Compare with the loaded timestamp saved in step 4 + - If current timestamp > loaded timestamp: CONFLICT DETECTED (human edited since Claude loaded) + - If timestamps match: No conflict, safe to update + +3. If conflict detected: + - Trigger conflict resolution flow (see managing-working-memory skill for detailed flow) + - Present both changes to user and offer resolution options + +4. If no conflict: + - Proceed to apply patch operation in next step + +### 7. Apply Patch Operation + +Use the patch operation patterns defined in the managing-working-memory skill. Apply the identified patch operation type: + +**For append_to_section:** +- Locate the specified section in the note content +- Append the new content to the end of that section +- Preserve all existing content in the section + +**For add_new_section:** +- Create a new markdown section with the specified title +- Add the section content +- Insert at appropriate location in the note structure + +**For update_frontmatter:** +- Modify the specified frontmatter field(s) +- Common operations: adding tags, updating status, etc. + +**For insert_in_list:** +- Locate the specified list within a section +- Add the new list item +- Maintain proper markdown list formatting + +After applying content changes: + +1. Update frontmatter timestamps: + - Set updated to current date (YYYY-MM-DD) + - Set claude_last_accessed to current date (YYYY-MM-DD) + +2. If adding tags, merge new tags with existing tags (remove duplicates) + +### 8. Write Updated Note + +1. Invoke MCP `update_note` with: + - vault: `~/.claude-memory` + - path: The note path + - content: The updated content + - frontmatter: The updated frontmatter + +2. Handle the response: + + **If successful:** + - Return success message including: + - Confirmation with wikilink + - Description of applied changes + - Updated timestamp + - Path + - Note that Obsidian has been updated + + **If MCPUnavailableError:** + - Return graceful degradation message explaining: + - MCP server is unavailable + - Steps to restore: ensure Obsidian is running, check plugin installation, verify config + - Reference to docs/setup-guide.md + - Offer to save pending update to a local file + + **If other error:** + - Return error message with details + +## Conflict Detection and Resolution + +All conflict detection and resolution flows are defined in the managing-working-memory skill. The /update-memory command uses these patterns: + +### Case 1: Clean Update (No Conflict) + +Timeline: +- T1: Claude loads note (updated = "2025-11-17") +- T2: User discusses updates with Claude +- T3: Claude applies patch (updated still "2025-11-17") +- Result: No conflict, proceed with update + +See managing-working-memory skill for timestamp comparison logic. + +**Example Output:** +``` +Updated: [[Git Worktrees]] + +Applied changes: +- Added new entry to "Recent Changes" +- Appended decision about trap handlers to "Key Decisions" +- Updated tags: [entity, git, worktrees, error-handling] + +Updated: 2025-11-18 +Path: claude/projects/tabula-scripta/entities/Git Worktrees.md + +The note has been updated in Obsidian. +``` + +### Case 2: Human Edit Conflict + +Timeline: +- T1: Claude loads note (updated = "2025-11-17") +- T2: Human edits note in Obsidian (updated = "2025-11-18") +- T3: Claude attempts patch (detects conflict) +- Result: Conflict detected, show diff and offer resolution options + +See managing-working-memory skill for conflict resolution flow and user options. + +**Example Output:** +``` +I want to update [[Git Worktrees]] but you've edited it since I loaded it. + +Your changes (at 2025-11-18): + + Added new section "## Performance Considerations" + + Updated "Overview" with additional context + ~ Modified frontmatter tags: added #optimization + +My pending changes: + - Add new entry to "Recent Changes" + - Append decision about trap handlers to "Key Decisions" + +Options: +1. Show me both diffs and I'll merge manually +2. Abort your update (keep my changes only) +3. Create new section "## Claude's Updates (conflicted)" with your changes +4. Let's discuss and resolve together + +What should I do? +``` + +### Case 3: Major Rewrite Needed + +When Claude's understanding contradicts existing note fundamentally, use the major rewrite pattern from managing-working-memory skill to present options without overwriting user knowledge. + +**Example Output:** +``` +My understanding of [[Git Worktrees]] changed significantly. + +Current note says: +"Git worktrees are primarily for parallel feature development and should be short-lived." + +But I now think: +"Git worktrees can be long-lived for maintaining multiple release branches simultaneously." + +Options: +1. Create new entity note with alternative understanding +2. Update existing note (I'll show you the full diff first) +3. Let's discuss the contradiction - maybe both are valid + +What should I do? +``` + +## Patch Operation Types + +All patch operations are defined in the managing-working-memory skill. The /update-memory command uses these standard patterns: + +### 1. Append to Section + +Add new content at the end of an existing section. Example: Adding to "## Recent Changes" section. + +See managing-working-memory skill for appendToSection pattern. + +### 2. Add New Section + +Create a new section in the note. Example: Adding "## Performance Considerations" section. + +See managing-working-memory skill for addSection pattern. + +### 3. Update Frontmatter + +Modify frontmatter fields such as tags or status. + +Examples: +- Adding a tag: Append to tags array +- Changing status: Set status field to new value + +See managing-working-memory skill for frontmatter update pattern. + +### 4. Insert in List + +Add item to an existing list like "## Related Entities" or "## References". + +See managing-working-memory skill for insertInList pattern. + +## Error Handling + +### Note Not Found + +**Input:** `/update-memory Nonexistent Entity` + +**Output:** +``` +Error: Memory note [[Nonexistent Entity]] not found. + +Searched: +- Project entities: claude/projects/tabula-scripta/entities/ +- Project sessions: claude/projects/tabula-scripta/sessions/ +- Global entities: claude/global/entities/ +- Global topics: claude/global/topics/ + +Would you like to create it with /remember? + +Example: /remember entity Nonexistent Entity +``` + +### MCP Unavailable + +**Input:** `/update-memory Git Worktrees` (when MCP server is down) + +**Output:** +``` +Obsidian MCP server is unavailable. Cannot update memory note. + +To restore memory features: +1. Ensure Obsidian is running +2. Check obsidian-mcp-plugin is installed in ~/.claude-memory/ +3. Verify Claude Code config includes MCP server + +See docs/setup-guide.md for troubleshooting. + +Would you like me to save the pending update to a local file? +``` + +### Missing Title + +**Input:** `/update-memory` + +**Output:** +``` +Error: Title is required. + +Usage: /update-memory [title] + +Examples: + /update-memory Git Worktrees + /update-memory 2025-11-18 - Working Memory Implementation + /update-memory React Patterns +``` + +## Success Output (Clean Update) + +**Input:** `/update-memory Git Worktrees` + +**Conversation:** +``` +User: /update-memory Git Worktrees + +Claude: Loaded: [[Git Worktrees]] +Type: entity +Project: tabula-scripta +Last updated: 2025-11-17 +Path: claude/projects/tabula-scripta/entities/Git Worktrees.md + +Current content: +[... full note content ...] + +--- + +What would you like to update? I'll apply patch operations to preserve existing content. + +User: Add a decision about using trap handlers for cleanup + +Claude: I'll add this to the Key Decisions section. + +[Claude applies patch] + +Updated: [[Git Worktrees]] + +Applied changes: +- Added new entry to "Key Decisions" + ### Trap Handlers for Cleanup + Date: 2025-11-18 + Rationale: Ensures worktree cleanup even if subagent crashes + Alternatives Considered: Manual cleanup (error-prone), atexit hooks (unreliable) + Impact: Improves reliability of parallel execution + +Updated: 2025-11-18 +Path: claude/projects/tabula-scripta/entities/Git Worktrees.md + +The note has been updated in Obsidian. +``` + +## Timestamp Tracking + +### Frontmatter Fields + +```yaml +--- +created: 2025-11-17 # Never changes, original creation +updated: 2025-11-18 # Modified every save (human or Claude) +claude_last_accessed: 2025-11-18 # When Claude loaded into context +--- +``` + +### Conflict Detection Logic + +See the managing-working-memory skill for the complete timestamp comparison logic. The basic flow is: + +1. **When loading:** Store the updated timestamp and set claude_last_accessed +2. **When updating:** Reload the note and compare timestamps +3. **If current timestamp > loaded timestamp:** Conflict detected +4. **If timestamps match:** Safe to update + +## Graceful Degradation (MCP Unavailable) + +When MCP server is unavailable: + +1. **Detect failure** - Catch `MCPUnavailableError` +2. **Offer alternatives:** + - Save pending update to local markdown file + - Export as diff patch for manual application + - Continue conversation without update +3. **Guide troubleshooting:** + - Link to `docs/setup-guide.md` + - Check Obsidian is running + - Verify plugin installation + +## Interactive Update Flow + +The `/update-memory` command is conversational: + +1. **Load note** - Show current content to user +2. **Collect intent** - User describes what to update +3. **Confirm operation** - Claude describes patch operation +4. **Check conflict** - Reload note to detect edits +5. **Apply or resolve** - Update cleanly or trigger conflict resolution +6. **Confirm success** - Show what changed + +This ensures transparency and prevents data loss. + +## Acceptance Criteria + +- [ ] Loads existing note via MCP read_note +- [ ] Checks timestamps for conflict detection (updated vs loaded) +- [ ] Detects conflicts when human edited since load +- [ ] Shows diff when conflict detected +- [ ] Applies patch operations (append, add section, update frontmatter) +- [ ] Preserves existing content (no full rewrites) +- [ ] Updates frontmatter timestamps (updated, claude_last_accessed) +- [ ] Handles MCP unavailable gracefully +- [ ] Offers to create note if not found +- [ ] Searches multiple locations (project entities, sessions, global) +- [ ] Shows clear success message with changes applied + +## Integration with managing-working-memory Skill + +This command provides the manual interface for memory updates. The `managing-working-memory` skill uses the same update logic for: +- Automatic updates after code review +- Updates after debugging sessions +- Periodic checkpoint updates +- Session end compaction + +**Relationship:** +- `/update-memory` - Manual, user-initiated updates +- `managing-working-memory` - Automatic, skill-driven updates +- Both use identical conflict detection and patch operations +- Both track timestamps for data integrity diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..cc134d2 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,26 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" + } + ] + } + ], + "SessionEnd": [ + { + "matcher": ".*", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-end.sh" + } + ] + } + ] + } +} diff --git a/hooks/session-end.md b/hooks/session-end.md new file mode 100644 index 0000000..c15190f --- /dev/null +++ b/hooks/session-end.md @@ -0,0 +1,620 @@ +# Session End Hook + +This hook runs automatically when a Claude Code session ends, handling session note updates, threshold-based compaction, and archival. + +## Overview + +**Purpose:** Finalize session notes, compact if threshold exceeded, and archive completed sessions + +**Compaction Threshold:** 500 lines OR 3 days old (whichever comes first) + +**Archival Strategy:** Never delete, always archive to preserve history + +## Implementation + +### 1. Load Current Session Note + +1. Detect the project context (same logic as session-start hook) + +2. Determine the session note path: + - Get current date in YYYY-MM-DD format + - Infer session topic from work log or user input + - Construct path: `claude/projects/{projectContext}/sessions/{date}-{topic}.md` + +3. Attempt to load the session note using MCP vault tool: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: `claude/projects/${projectContext}/sessions/${date}-${topic}.md`, + returnFullFile: true + }) + ``` + +4. Handle different cases: + + **If session note exists:** + - Proceed to finalize the session (next steps) + + **If FileNotFoundError:** + - No session note was created this session + - Display: "No session note created this session." + - Exit normally (no-op) + + **If MCPUnavailableError:** + - Display: "MCP unavailable. Cannot finalize session." + - See error recovery section below for options + +### 2. Final Session Note Update + +1. Append session closing entry to the "## Work Log" section: + - Add timestamp with current time + - Add heading: "Session End" + - Add message: "Session completed. Work finalized." + +2. Update frontmatter: + - Set updated to current date (YYYY-MM-DD) + - Set claude_last_accessed to current date + +3. Write the final update using MCP vault tool: + ```javascript + mcp__obsidian-vault__vault({ + action: "update", + path: sessionPath, + content: updatedContentWithFrontmatter + }) + ``` + + Or use edit tool for efficient append: + ```javascript + // Append closing entry + mcp__obsidian-vault__edit({ + action: "append", + path: sessionPath, + content: `\n### ${timestamp} - Session End\n\nSession completed. Work finalized.` + }); + + // Update frontmatter + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "updated", + operation: "replace", + content: new Date().toISOString().split('T')[0] + }); + ``` + +4. Proceed to check compaction threshold (next step) + +### 3. Check Compaction Threshold + +**Threshold Logic:** + +1. Count lines in the session note by splitting content on newlines + +2. Calculate age in days: + - Parse created date from frontmatter + - Compare with current date + - Calculate difference in days + +3. Check if either threshold is exceeded: + - Line threshold: >= 500 lines + - Age threshold: >= 3 days + +4. If either threshold is exceeded: + - Display reason: "Session note {exceeds 500 lines / exceeds 3 days old}. Triggering compaction..." + - Trigger compaction (next step) + +5. If both thresholds are not exceeded: + - Display: "Session note below threshold ({lineCount} lines, {ageInDays} days old). Keeping active." + - Mark session status as "active" in frontmatter + - Update the note using MCP edit tool: + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "status", + operation: "replace", + content: "active" + }) + ``` + +**Threshold Rules:** +- **500 lines:** Session note becomes too large to navigate efficiently +- **3 days old:** Knowledge should be consolidated into entities for long-term retention +- **Whichever comes first:** More aggressive compaction ensures timely knowledge extraction + +### 4. Compact Session Note (I1: Simplified Algorithm) + +**Compaction Process:** + +1. Display: "Compacting session note into entity notes..." + +2. Parse the session note to identify extractable knowledge: + - Use high-level heuristics rather than detailed algorithms + - Look for distinct concepts and decisions + +3. For each identified piece of knowledge: + - Update or create the appropriate entity note + - See step 5 for entity update logic + +4. Archive the session note (step 6) + +5. Display: "Compaction complete. Session archived." + +**Knowledge Extraction (Simplified):** + +Parse the session note to identify key extractable knowledge: + +1. **Extract decisions** from "## Decisions Made" section + - Each decision entry becomes an update to an entity note + - Infer which entity based on context and related entities + +2. **Extract gotchas** from "## Work Log" section + - Look for debugging work: "fixed bug", "troubleshot", "resolved issue" + - Each gotcha updates the troubleshooting section of an entity + +3. **Extract references** from wikilinks throughout the note + - All wikilinked entities get updated with reference to this session + +**Extraction Categories:** +- **Architectural decisions** → Update entity notes with decision and rationale +- **Bug fixes / gotchas** → Update entity troubleshooting section +- **New patterns** → Create topic notes or new entities +- **User preferences** → Update user preference entity + +**Note:** Keep extraction logic flexible. Claude should use judgment to identify valuable knowledge rather than following rigid algorithms. + +### 5. Update Entity Notes + +Use the patch operations and conflict detection patterns from the managing-working-memory skill. + +For each extracted piece of knowledge: + +1. Determine the target entity note path: + - `claude/projects/{projectContext}/entities/{entityName}.md` + +2. Attempt to load the existing entity using MCP vault tool: + ```javascript + const loadResult = await mcp__obsidian-vault__vault({ + action: "read", + path: `claude/projects/${projectContext}/entities/${entityName}.md`, + returnFullFile: true + }); + ``` + +3. Store the loaded content for conflict detection + +4. Apply patch operation based on knowledge type (using efficient edit tool): + - **Decision:** Append to "## Key Decisions" section + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: entityPath, + targetType: "heading", + target: "Key Decisions", + operation: "append", + content: `\n- ${date}: ${decisionText}` + }) + ``` + - **Gotcha:** Append to "## Gotchas & Troubleshooting" section + - **Reference:** Append to "## Recent Changes" section + +5. Before writing, reload the entity and check for conflicts: + - Compare loaded content with current content + - If conflict detected, trigger conflict resolution + - If no conflict, proceed with update + +6. Update frontmatter timestamps: + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: entityPath, + targetType: "frontmatter", + target: "updated", + operation: "replace", + content: new Date().toISOString().split('T')[0] + }) + ``` + +7. Display: "Updated [[{entityName}]] with knowledge from session." + +8. Handle errors: + - If FileNotFoundError: Create new entity from session knowledge + - If other error: Propagate + +**Conflict Handling:** Uses same timestamp-based detection as defined in `managing-working-memory.md` + +### 6. Archive Session Note + +**Archival Process:** + +1. Determine archive path: + - Format: `claude/projects/{projectContext}/archive/sessions/{filename}` + - Use same filename as original session note + +2. Update session note metadata: + - Set status to "archived" in frontmatter + - Set updated to current date + - Add archived_date field with current date + - Add archived_reason: "Compaction threshold exceeded" + +3. Ensure archive directory exists (create if needed) + +4. Copy session note to archive location using MCP vault tool: + ```javascript + mcp__obsidian-vault__vault({ + action: "create", + path: `claude/projects/${projectContext}/archive/sessions/${filename}`, + content: sessionContentWithArchivedFrontmatter + }) + ``` + +5. Verify archive was successful by attempting to read the archived note + +6. If verification successful: + - Display: "Session archived to: {archivePath}" + - Leave original note with archived status (default behavior) + - User can manually delete original if desired + +7. If verification fails: + - Throw error: "Archive verification failed. Session not moved." + - Keep original note intact + +**Archive Location:** `~/.claude-memory/claude/projects/{project}/archive/sessions/{date}-{topic}.md` + +**Preservation Strategy:** +- Never delete notes (always archive) +- Archived notes retain full content and frontmatter +- Status marked as "archived" in frontmatter +- Original note can optionally be removed (default: keep with archived status) + +### 7. Create Backlinks + +**Link archived session to updated entities:** + +For each entity that was updated during compaction: + +1. Determine the entity path: + - `claude/projects/{projectContext}/entities/{entityName}.md` + +2. Load the entity using MCP vault tool: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: `claude/projects/${projectContext}/entities/${entityName}.md`, + returnFullFile: true + }) + ``` + +3. Create a backlink to the archived session: + - Format: `- [[{session-filename}]] - Archived session (compacted)` + - Extract filename from session path (without .md extension) + +4. Append the backlink to the "## References" section of the entity + +5. Update the entity frontmatter: + - Set updated to current date + +6. Write the updated entity using MCP edit tool: + ```javascript + // Append backlink to References section + mcp__obsidian-vault__edit({ + action: "patch", + path: entityPath, + targetType: "heading", + target: "References", + operation: "append", + content: `\n- [[${sessionFilename}]] - Archived session (compacted)` + }); + + // Update frontmatter + mcp__obsidian-vault__edit({ + action: "patch", + path: entityPath, + targetType: "frontmatter", + target: "updated", + operation: "replace", + content: new Date().toISOString().split('T')[0] + }); + ``` + +This creates bidirectional links between archived sessions and the entities they contributed to. + +## Error Handling and Recovery (I2) + +### MCP Unavailable + +When MCP server is unavailable at session end: + +1. Detect the MCPUnavailableError + +2. Display message listing pending actions: + ``` + Obsidian MCP unavailable. Cannot finalize session. + + Pending actions: + - Session note update + - Compaction check + - Archival (if threshold exceeded) + ``` + +3. **Decision Point - Ask User:** + - **Option A: Exit without finalization** + - Session ends without memory updates + - User must manually run session cleanup later + - Provide manual steps: + 1. Check session note line count and age + 2. If threshold exceeded, compact into entity notes + 3. Archive to archive/sessions/ directory + - **Option B: Wait and retry** + - Pause session end + - Wait for user to fix MCP connection + - Retry finalization once fixed + - **Option C: Export pending work** + - Create local file with session summary + - User can manually update memory later + - Provide file path and instructions + +4. Display troubleshooting reference: "See docs/setup-guide.md for MCP troubleshooting." + +### Compaction Failure + +When compaction fails for any reason: + +1. Catch the error and log it + +2. **Decision Point - Do NOT archive if compaction failed** + - Leave session note as-is + - Session remains active until compaction succeeds + +3. Display error message with manual action steps: + ``` + Session compaction failed. Session note preserved. + + Manual action required: + 1. Review session note: {sessionPath} + 2. Extract knowledge into entity notes manually + 3. Archive session when complete + + Error: {error.message} + ``` + +4. Mark session with error status: + - Set frontmatter.status = "compaction_failed" + - Add frontmatter.error = error message + - Update note using MCP edit tool: + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "status", + operation: "replace", + content: "compaction_failed" + }); + + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "error", + operation: "replace", + content: error.message + }); + ``` + +5. **Recovery options:** + - User can manually compact and archive later + - On next session, user can trigger manual compaction + - Session will remain in active state with error marker + +### Archive Verification Failed + +When archive verification fails: + +1. Detect that archived note cannot be read + +2. Display error: "Archive verification failed. Session not moved." + +3. **Decision Point - Abort archival, keep session active:** + - Do NOT remove original session note + - Revert session status to "active" + - Add frontmatter.archive_error = "Verification failed" + +4. Update the original session note using MCP edit tool: + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "status", + operation: "replace", + content: "active" + }); + + mcp__obsidian-vault__edit({ + action: "patch", + path: sessionPath, + targetType: "frontmatter", + target: "archive_error", + operation: "replace", + content: "Verification failed" + }); + ``` + +5. Throw error to halt session end process: + - "Archive failed - session preserved at original location" + +6. **Recovery options:** + - User can investigate why archive failed + - Retry archival on next session end + - Manually move to archive directory if needed + +## Integration with Managing Working Memory Skill + +**Session End Checklist:** Uses TodoWrite checklist from `skills/managing-working-memory.md`: + +```markdown +- [ ] Final session note update with closing context +- [ ] Check session note size (line count) +- [ ] Check session note age (created date) +- [ ] If threshold met (500 lines OR 3 days old): + - [ ] Parse session note for extractable knowledge + - [ ] Identify entities to create or update + - [ ] Apply patches to entity notes + - [ ] Create new entities if needed + - [ ] Archive session note to archive/sessions/ + - [ ] Verify archive successful +- [ ] If threshold not met: + - [ ] Mark session note status: active + - [ ] Note next checkpoint time +- [ ] Review cross-project recall tracking +- [ ] Check for promotion threshold (3 recalls) +- [ ] Confirm all writes successful +``` + +**Compaction Triggers:** Aligns with triggers defined in managing-working-memory skill + +## Performance Considerations + +### Compaction Performance + +To avoid session-end delays, optimize compaction performance: + +1. **Track timing:** + - Record start time before compaction begins + - Record end time after archival completes + - Calculate total duration in milliseconds + +2. **Optimize operations:** + - Parse session (fast - single pass) + - Batch entity updates in parallel using Promise.all + - Archive in single operation (not per-entity) + +3. **Display timing:** + - Show "Compaction completed in {duration}ms" + +4. **Performance target:** <5 seconds for compaction (even with 10+ entity updates) + +### Async Session End + +Don't block session exit if MCP operations are slow: + +1. Display: "Finalizing session..." + +2. Run session-end hook asynchronously: + - Execute all finalization steps + - Allow user to exit if it takes too long + +3. Handle completion: + + **If successful:** + - Display: "Session finalized successfully." + + **If error:** + - Display: "Session end failed: {error.message}" + - Display: "Session state preserved. Manual cleanup may be needed." + - Allow user to exit anyway (don't block) + +## Testing + +### Manual Testing + +1. Create session note with >500 lines +2. Run session-end hook +3. Verify compaction triggered +4. Check entity notes updated +5. Verify session archived at correct path +6. Confirm original session marked as "archived" + +### Edge Cases + +- **Session note <500 lines and <3 days old:** No compaction, marked active +- **MCP unavailable:** Graceful error, pending actions logged +- **Compaction failure:** Session preserved, error status set +- **No session note:** No-op, clean exit +- **Archive directory missing:** Create directory automatically + +### Threshold Testing + +**Test Case 1: Line Threshold** +- Session with 600 lines, 1 day old +- Expected: Compact (exceeds 500 lines) + +**Test Case 2: Age Threshold** +- Session with 300 lines, 4 days old +- Expected: Compact (exceeds 3 days) + +**Test Case 3: Both Thresholds** +- Session with 600 lines, 4 days old +- Expected: Compact (exceeds both thresholds) + +**Test Case 4: Neither Threshold** +- Session with 200 lines, 1 day old +- Expected: Mark active, no compaction + +## Example Session End Flow + +``` +$ exit + +[Session End Hook Executing...] + +Finalizing session for project: tabula-scripta + +Session note: 2025-11-18-session-hooks.md +Status: 487 lines, 1 day old (below threshold) + +Marking session as active (no compaction needed). + +Session finalized successfully. + +--- + +Goodbye! +``` + +**With Compaction:** + +``` +$ exit + +[Session End Hook Executing...] + +Finalizing session for project: tabula-scripta + +Session note: 2025-11-15-memory-design.md +Status: 623 lines, 1 day old (exceeds 500 line threshold) + +Triggering compaction... + +Extracting knowledge: +- 3 architectural decisions → [[Memory System Architecture]] +- 2 debugging insights → [[Conflict Detection]] +- 5 entity references → Updated backlinks + +Compaction complete. Session archived to: + archive/sessions/2025-11-15-memory-design.md + +Session finalized successfully. + +--- + +Goodbye! +``` + +## Summary + +This session-end hook finalizes working memory at the end of each session: + +1. **Loads current session note** and applies final update +2. **Checks compaction threshold** (500 lines OR 3 days old) +3. **Compacts if threshold exceeded** by extracting knowledge into entity notes +4. **Archives session note** to preserve history (never deletes) +5. **Updates entity notes** with decisions, gotchas, and patterns +6. **Creates backlinks** from entities to archived session +7. **Handles errors gracefully** (MCP unavailable, compaction failures, etc.) + +Users experience automatic knowledge consolidation without manual effort, while maintaining full session history in archived notes for future reference. diff --git a/hooks/session-end.sh b/hooks/session-end.sh new file mode 100755 index 0000000..1b7a6c1 --- /dev/null +++ b/hooks/session-end.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SessionEnd hook for tabula-scripta plugin +# Injects working memory finalization instructions into Claude's context + +set -euo pipefail + +# Determine plugin root directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Read session-end hook documentation +session_end_instructions=$(cat "${PLUGIN_ROOT}/hooks/session-end.md" 2>&1 || echo "Error reading session-end hook documentation") + +# Escape for JSON +instructions_escaped=$(echo "$session_end_instructions" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') + +# Output context injection as JSON +cat <\n**Tabula Scripta: Session Finalization**\n\nThis hook automatically finalizes working memory at session end.\n\n**Instructions:**\n\n${instructions_escaped}\n\n**Action Required:**\n1. Follow the session-end implementation steps above\n2. Check compaction threshold (500 lines OR 3 days old)\n3. If threshold met: extract knowledge, update entities, archive session\n4. Use the correct MCP tool names (mcp__obsidian-vault__vault, mcp__obsidian-vault__edit, etc.)\n5. Handle errors gracefully\n\n**Note:** If MCP is unavailable, notify user and provide manual cleanup steps.\n" + } +} +EOF + +exit 0 diff --git a/hooks/session-start.md b/hooks/session-start.md new file mode 100644 index 0000000..ce6608e --- /dev/null +++ b/hooks/session-start.md @@ -0,0 +1,509 @@ +# MANDATORY SESSION START PROTOCOL + +**YOU MUST execute these steps BEFORE responding to the user's first message.** + +## Why This Matters + +Without loading memory proactively, you will: +- Lose context from previous sessions +- Repeat solved problems +- Miss important decisions and open questions +- Waste user's time re-establishing context + +## The Iron Law + +``` +LOAD MEMORY FIRST, RESPOND TO USER SECOND +``` + +No exceptions. Execute the protocol below, THEN respond to whatever the user asked. + +## Required Protocol (Execute in Order) + +**Performance Target:** <2 seconds total +**User Experience:** 1-3 sentence summary, then respond to their message + +## Implementation + +### 1. Detect Project Context + +1. Attempt to detect the current project from the git repository: + - Run `git rev-parse --show-toplevel` to find the git repository root + - Extract the project name from the repository directory name + - Return the project name + +2. If not in a git repository (command fails): + - Fall back to using the current working directory name + - If that also fails, use 'default' as the project name + +3. Store the detected project context for all subsequent operations + +**Fallback:** If not in git repo, use current directory name. If that fails, use "default". + +### 2. Load Project Index + +1. Construct the project index path: `claude/projects/{projectContext}/_index.md` + +2. Attempt to load the project index using MCP vault tool: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: `claude/projects/${projectContext}/_index.md`, + returnFullFile: true + }) + ``` + +3. If successful: + - Extract wikilinks from the index content + - These represent the most important entities for this project + +4. Handle errors: + + **If FileNotFoundError:** + - This is the first time working on this project + - Display message: "Starting new project: {projectContext}" + - Offer to create project index for future sessions + + **If MCPUnavailableError:** + - Graceful degradation - continue without memory + - Display message: "Obsidian MCP unavailable. See docs/setup-guide.md" + - Continue session without memory loading (see error recovery in section below) + +### 3. Query Last 3 Session Notes + +**Using Dataview Query (if available):** + +1. Construct a Dataview query to find recent sessions: + - Source: `claude/projects/{projectContext}/sessions` + - Filter: status = "active" OR status = "archived" + - Sort: created DESC (most recent first) + - Limit: 3 + +2. Invoke MCP bases tool to query sessions: + ```javascript + mcp__obsidian-vault__bases({ + action: "query", + filters: [ + { property: "status", operator: "in", value: ["active", "archived"] } + ], + sort: { property: "created", order: "desc" }, + pagination: { page: 1, pageSize: 3 } + }) + ``` + + Note: If Dataview base not configured, falls back to search (see fallback below) + +3. For each returned session path, load the full content in parallel: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: sessionPath, + returnFullFile: true + }) + ``` + +4. Handle errors: + + **If Dataview base not available:** + - Fallback to list and filter approach: + ```javascript + // List all session files + const listResult = await mcp__obsidian-vault__vault({ + action: "list", + directory: `claude/projects/${projectContext}/sessions` + }); + + // Load frontmatter for each in parallel + const sessions = await Promise.all( + listResult.result.map(async path => { + const r = await mcp__obsidian-vault__vault({ + action: "read", + path: path + }); + return { path, frontmatter: r.result.frontmatter }; + }) + ); + + // Filter and sort + const recent = sessions + .filter(s => s.frontmatter.status === 'active' || s.frontmatter.status === 'archived') + .sort((a, b) => b.frontmatter.created.localeCompare(a.frontmatter.created)) + .slice(0, 3); + ``` + +**Fallback Strategy:** If Dataview base unavailable, use list + filter approach with frontmatter sorting. + +### 4. Load Linked Entities + +1. From the project index, extract the linked entities (wikilinks) + +2. Limit to top 5 most important entities to keep load time under 2 seconds + +3. For each entity (in parallel): + - First attempt: Try to load from project entities path: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: `claude/projects/${projectContext}/entities/${entityName}.md`, + returnFullFile: true + }) + ``` + - If not found (error.message includes "not found"): Try global entities path: + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: `claude/global/entities/${entityName}.md`, + returnFullFile: true + }) + ``` + +4. Collect all successfully loaded entities + +**Optimization:** Only load top 5 most important entities (limit based on recency or importance) + +### 5. Generate Summary + +**Summary Generation:** + +1. Initialize an empty highlights array + +2. Extract key information from the most recent session: + - Parse the "## Decisions Made" section to find recent decisions + - Parse the "## Open Questions" section to find blockers or uncertainties + - If decisions exist, add the most recent one to highlights + - If open questions exist, add the first one with "Open:" prefix + +3. Limit to 1-3 highlights total to create a concise summary + +4. Join highlights into a single summary string (1-3 sentences) + +**Summary Content Focus:** +- Recent decisions and rationale +- Open questions or blockers +- Key patterns or architectural choices +- Next steps from previous session + +**Length Limit:** 1-3 sentences maximum (not a memory dump) + +### 6. Present Summary to User + +**Output Format:** + +``` +Working Memory Loaded for Project: {project-name} + +Summary: +{1-3 sentence summary of recent context} + +Last session: {date} - {topic} +Active entities: {count} loaded +Recent sessions: {count} reviewed + +Need more context? Ask me to recall specific entities or sessions. +``` + +**Example:** + +``` +Working Memory Loaded for Project: tabula-scripta + +Summary: +Last session completed plugin foundation and core memory skill. Currently implementing session hooks with proactive recall. Open question: how to handle MCP connection failures gracefully. + +Last session: 2025-11-17 - Memory System Design +Active entities: 5 loaded +Recent sessions: 3 reviewed + +Need more context? Ask me to recall specific entities or sessions. +``` + +### 7. Offer Additional Context + +**User can request more:** + +``` +User: "Tell me about the memory system architecture" + +Claude: Loads [[Memory System Architecture]] entity and presents details +``` + +**User can request session details:** + +``` +User: "What did we work on last session?" + +Claude: Loads full content of last session note and summarizes +``` + +## Red Flags - STOP and Execute Protocol + +If you catch yourself thinking ANY of these thoughts, STOP. Execute the memory loading protocol FIRST: + +- "User seems urgent, I'll respond immediately" +- "Their question is simple, I don't need memory" +- "I'll load memory if they specifically ask for it" +- "Let me respond quickly, memory later" +- "They just asked 'what do u remember' - that's different" +- "I can see the git history, that's good enough" +- "This is just a greeting, skip the protocol" + +**ALL of these mean:** Stop. Execute the MANDATORY SESSION START PROTOCOL. THEN respond. + +## Common Rationalizations (Don't Do These) + +| Excuse | Reality | +|--------|---------| +| "User seems urgent, respond immediately" | Protocol takes <2 seconds. User benefits from context. | +| "Question is simple, don't need memory" | You don't know what's simple without loading context. | +| "I'll load if they ask for it" | Proactive loading IS the feature. Don't make user ask. | +| "Git history is good enough" | Git shows files, not decisions/questions/context. Load memory. | +| "This is just a greeting" | Every session starts with greeting. Still load memory. | +| "They said 'what do u remember'" | That's still a first message. Execute protocol FIRST. | + +## Error Handling and Recovery + +### MCP Unavailable (I2: Error Recovery Paths) + +When MCP server is unavailable at session start: + +1. Detect the MCPUnavailableError during any memory operation + +2. Display user-friendly message: + ``` + Obsidian MCP server unavailable. Working memory features disabled. + + To restore memory: + 1. Ensure Obsidian is running + 2. Verify obsidian-mcp-plugin installed + 3. Check Claude Code config includes MCP server + + See docs/setup-guide.md for setup instructions. + ``` + +3. **Decision Point - Ask User:** + - **Option A: Continue without memory** + - Session proceeds normally but without context loading + - Memory writes will also be disabled for this session + - User can still work on tasks, just no memory features + - **Option B: Wait and retry** + - Pause session startup + - Wait for user to fix MCP connection + - Retry memory loading once fixed + - **Option C: Exit and fix setup** + - Exit the session + - User fixes MCP setup + - Restart session with working memory + +4. If user chooses to continue without memory: + - Set session flag: `memory_disabled = true` + - Skip all memory operations for this session + - Display reminder that memory is disabled + +### Project Index Not Found + +If the project index doesn't exist (FileNotFoundError): + +1. Display message: + ``` + Starting fresh project: {projectContext} + + I'll create a project index to track your work. + Would you like me to create it now? (yes/no) + ``` + +2. If user confirms: + - Create a new project index note + - Initialize with basic structure + +3. If user declines: + - Continue session without index + - Index will be created on first memory write + +### No Previous Sessions + +If no session notes are found for this project: + +1. Display welcome message: + ``` + Welcome to project: {projectContext} + + This is your first session. I'll start building working memory as we work. + + Ready to begin! + ``` + +2. Return and start the session normally + - Memory features are active but no context to load yet + +## Performance Optimization and Measurement + +### Parallel Loading + +1. Load project index, sessions, and entities in parallel using Promise.all: + - Load project index + - Query recent sessions (limit 3) + - Load linked entities (limit 5) + +2. Wait for all operations to complete before generating summary + +**Target:** <2 seconds total time for all operations + +### I3: Performance Measurement and Warning + +1. **Track timing:** + - Record start time before beginning memory loading + - Record end time after all operations complete + - Calculate total duration in milliseconds + +2. **Performance check:** + - If total duration < 2000ms: Success, no message needed + - If total duration >= 2000ms but < 5000ms: Display warning + ``` + Working memory loaded in {duration}ms (target: <2s) + + Consider optimizing: + - Reduce number of linked entities in project index + - Archive old session notes + - Check Obsidian vault performance + ``` + - If total duration >= 5000ms: Display strong warning + ``` + Working memory loading is slow ({duration}ms) + + This may impact session startup time. Recommendations: + 1. Archive sessions older than 30 days + 2. Limit project index to 5 most important entities + 3. Check Obsidian performance (large vault, plugins) + 4. Consider reducing session note size threshold + + Continue with memory features? (yes/no) + ``` + +3. **User decision on slow performance:** + - If user says no: Disable memory for this session + - If user says yes: Continue with warning noted + +### Lazy Loading + +Instead of loading full session content upfront, load summaries first: + +1. For each recent session, extract only: + - Path + - Title from frontmatter + - Created date from frontmatter + +2. Don't load full content until user requests it + +3. This speeds up initial load for sessions with large notes + +### Caching + +To avoid repeated MCP calls during the session: + +1. Create a session cache (Map or similar structure) + +2. When loading a note: + - Check if already in cache + - If in cache: Return cached version + - If not in cache: Load via MCP vault tool and store in cache + ```javascript + mcp__obsidian-vault__vault({ + action: "read", + path: notePath, + returnFullFile: true + }) + ``` + +3. Cache persists for session duration only (cleared on exit) + +## Integration with Managing Working Memory Skill + +**Relationship:** This hook triggers the recall flow defined in `skills/managing-working-memory.md` + +**Update claude_last_accessed:** When loading notes, update frontmatter: + +1. For each loaded note: + - Set frontmatter.claude_last_accessed to current date (YYYY-MM-DD) + - Update the note using MCP edit tool (efficient patch): + ```javascript + mcp__obsidian-vault__edit({ + action: "patch", + path: notePath, + targetType: "frontmatter", + target: "claude_last_accessed", + operation: "replace", + content: new Date().toISOString().split('T')[0] // YYYY-MM-DD + }) + ``` + +**Cross-Project Tracking:** If loading entity from different project, log cross-project recall: + +1. Check if entity is from a different project: + - Compare entity.frontmatter.project with currentProject + - Exclude global entities (project = 'global') + +2. If cross-project recall detected: + - Append to cross_project_recalls array: + - project: Current project + - date: Current date + - context: "Session start recall" + +3. Check promotion threshold: + - If cross_project_recalls length >= 3, trigger promotion prompt + - Use promotion flow defined in managing-working-memory skill + +## Testing + +### Manual Testing + +1. Start Claude Code session in git repo +2. Verify project detection works (correct project name) +3. Check summary generated (1-3 sentences) +4. Verify <2 second load time +5. Request additional context (entities, sessions) +6. Test in non-git directory (fallback to directory name) + +### Edge Cases + +- **No previous sessions:** Welcome message +- **MCP unavailable:** Graceful degradation message +- **Large vault (>100 notes):** Performance still <2s +- **Project index missing:** Offer to create +- **Dataview not installed:** Fallback to search_notes + +## Example Session Start Flow + +``` +$ claude-code + +[Session Start Hook Executing...] + +Working Memory Loaded for Project: tabula-scripta + +Summary: +Completed Phase 2 (Core Memory Skill) implementing write triggers and conflict detection. Now implementing Phase 3 session hooks. Performance target is <2 seconds for session start recall. + +Last session: 2025-11-17 - Implementing Core Skill +Active entities: 3 loaded ([[Memory System]], [[MCP Integration]], [[Conflict Detection]]) +Recent sessions: 2 reviewed + +Need more context? Ask me to recall specific entities or sessions. + +--- + +How can I help you today? +``` + +## Summary + +This session-start hook provides proactive recall of working memory at the beginning of each session: + +1. **Detects project** from git repo or directory name +2. **Loads project index** with linked entities +3. **Queries last 3 sessions** via Dataview +4. **Generates 1-3 sentence summary** (not overwhelming) +5. **Presents context** with option to request more +6. **Performs in <2 seconds** via parallel loading +7. **Handles errors gracefully** (MCP unavailable, no sessions, etc.) + +Users experience automatic context restoration without manual searching, while maintaining control to request additional details as needed. diff --git a/hooks/session-start.sh b/hooks/session-start.sh new file mode 100755 index 0000000..8356604 --- /dev/null +++ b/hooks/session-start.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SessionStart hook for tabula-scripta plugin +# Injects working memory recall instructions into Claude's context + +set -euo pipefail + +# Determine plugin root directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Read session-start hook documentation +session_start_instructions=$(cat "${PLUGIN_ROOT}/hooks/session-start.md" 2>&1 || echo "Error reading session-start hook documentation") + +# Escape for JSON +instructions_escaped=$(echo "$session_start_instructions" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') + +# Output context injection as JSON +cat <\n**Tabula Scripta: Working Memory System**\n\nThis hook automatically loads relevant context from previous sessions.\n\n**Instructions:**\n\n${instructions_escaped}\n\n**Action Required:**\n1. Follow the session-start implementation steps above\n2. Use the correct MCP tool names (mcp__obsidian-vault__vault, mcp__obsidian-vault__edit, etc.)\n3. Present a concise 1-3 sentence summary to the user\n4. Complete within 2 seconds performance target\n\n**Note:** If MCP is unavailable, gracefully degrade and continue without memory.\n" + } +} +EOF + +exit 0 diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..54a12b0 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,77 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:arittr/tabula-scripta:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "120f764e0b0d06857fbdcf70793daff123c131a6", + "treeHash": "3773a703a1aedc0dcde848fea63cf232e4890d752b516e2c1fdebafcfbb44a8f", + "generatedAt": "2025-11-28T10:13:56.461575Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "tabula-scripta", + "description": "Working memory system for Claude Code - integrates with Obsidian via MCP for persistent, cross-session memory management", + "version": "0.1.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "2691094f3f222cef48ea212628c6050986fa14f42a49bf8fb2c32d4d063aa93b" + }, + { + "path": "hooks/session-end.md", + "sha256": "e284342f4ce70f730afad88a582ba0a2aaf9396fb1331836671e24edbdead5bd" + }, + { + "path": "hooks/session-start.md", + "sha256": "6496cb6726a135afdf0bbe181608550cadb4bc97b6ce4fe741726d21e6c10073" + }, + { + "path": "hooks/session-start.sh", + "sha256": "0ecff7f9f3aebb33525a3225ac6827a90e95230f6bc35fe3910117010e9a2885" + }, + { + "path": "hooks/hooks.json", + "sha256": "dc8a7435c9c8d9fc7fadb72a6ce3a5ad317dfb5c43c7108df5261ef077ff6ab0" + }, + { + "path": "hooks/session-end.sh", + "sha256": "672dc9ba555b3aa37f86b1db89af0abd408e5e62d20543ac8e32e7464a6ccf91" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "19045bda50519c9488048c196579ddadd989557c565f7e0976540aaf1c7e8378" + }, + { + "path": "commands/update-memory.md", + "sha256": "5ff841bdebc8542a6a653845871d10386842f07d7855888a73227b8dd6790d7d" + }, + { + "path": "commands/remember.md", + "sha256": "20180fea6dd034411f87b4abf89366748aec06a8a062fdfe764f6a789fe44a2d" + }, + { + "path": "commands/recall.md", + "sha256": "be8574c605d229812acdb8bd2823ce646addd568921699985e18e13694cb9ffe" + }, + { + "path": "skills/managing-working-memory.md", + "sha256": "9425d8a15321c68dc5e0697b3caeb442b3f9f12e343103388e0f4bf5664b4405" + } + ], + "dirSha256": "3773a703a1aedc0dcde848fea63cf232e4890d752b516e2c1fdebafcfbb44a8f" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/managing-working-memory.md b/skills/managing-working-memory.md new file mode 100644 index 0000000..aa08512 --- /dev/null +++ b/skills/managing-working-memory.md @@ -0,0 +1,1024 @@ +# Managing Working Memory + +Use this skill to manage Claude's working memory system using Obsidian as the backend storage. This skill defines when, what, and how to write memories, enabling proactive recall and living documentation that evolves with your understanding. + +## When to Use This Skill + +### Automatic Triggers (No Permission Needed) + +Use this skill automatically in these scenarios: + +1. **After code review completes** - Update project entity notes for reviewed components with architectural decisions and links to related entities +2. **After debugging session** - Record troubleshooting patterns, root causes, fixes, and tag with symptom keywords for future search +3. **After architectural decision** - Create or update entity notes with rationale, alternatives considered, and links to related architectural entities +4. **After discovering reusable pattern** - Update topic notes or create new entities with links to examples from current project +5. **Periodic checkpoints during long sessions** - Update session note with current context every 30-60 minutes or after major milestones +6. **End of session** - Final session note update, compact if threshold exceeded, and archive + +### Ask-First Triggers (Subjective Content) + +Ask user permission before writing in these scenarios: + +1. **User states preference** - When user says "I prefer X over Y for Z reason", confirm: "Should I remember this preference?" +2. **Contradicts existing entity note** - Show diff between current understanding and new information, ask: "Update? Create alternative? Discuss?" +3. **Creating new global entity** - Ask: "This seems reusable across projects. Create global entity?" +4. **Large-scale reorganization** - Ask: "I want to split this entity into 3 separate notes. Proceed?" + +### Memory Spam Prevention + +**Trust the triggers:** Memory writes are naturally throttled by the selectivity of automatic triggers. Don't impose arbitrary limits. + +**Natural rate limiting:** +- Automatic triggers only fire after meaningful events (code reviews, debugging completions, architectural decisions) +- Periodic checkpoints are already spaced 30-60 minutes apart +- Session-end compaction consolidates session notes into entities + +**Batching strategy:** +- When multiple related entities need updates (e.g., after code review touching 3 components), batch into a single operation +- Group related changes to avoid redundant MCP calls +- Example: Update [[Component A]], [[Component B]], [[Component C]] in parallel, not sequentially + +**User control:** +- Users can always disable memory features if they find them intrusive +- For subjective content (preferences, contradictions), still ask permission before writing + +## Memory Granularity + +### Three-Layer System + +1. **Session Notes** (temporal, ephemeral) + - Location: `~/.claude-memory/claude/projects/{project}/sessions/{date}-{topic}.md` + - Purpose: Updateable scratchpad for current work + - Lifecycle: Created at session start, updated during session, compacted into entity notes when exceeding threshold (500 lines OR 3 days old) + - Content: Stream of consciousness, decisions and why, approaches tried/failed, open questions, context for resuming + +2. **Entity Notes** (persistent, specific) + - Location: `~/.claude-memory/claude/projects/{project}/entities/{Entity Name}.md` + - Global: `~/.claude-memory/claude/global/entities/{Entity Name}.md` + - Purpose: One note per concept/component/pattern + - Lifecycle: Created when new concept encountered, updated via patch operations (not full rewrites), never deleted (archive if obsolete) + - Content: Purpose/overview, architecture/structure, key decisions and rationale, gotchas/troubleshooting, related entities, recent changes log + +3. **Topic Notes** (organizational) + - Location: `~/.claude-memory/claude/global/topics/{Topic Name}.md` + - Purpose: Maps of Content (MOC) for topic clusters + - Lifecycle: Created when pattern emerges across multiple entities, human-curated over time + - Content: Links to related entities, conceptual frameworks, best practices, curated organization + +## Integration with Superpowers Skills + +### Code Review Integration + +When using `requesting-code-review` skill: +- After review completes → update component entity note +- Record architectural decisions made during review +- Link to related entities +- Example trigger: Review approved, acceptance criteria met + +### Debugging Integration + +When using `systematic-debugging` skill: +- After root cause identified → record troubleshooting pattern +- Update entity note with fix and rationale +- Tag with symptom keywords (error messages, stack traces) +- Example trigger: Bug fixed and verified + +### Brainstorming Integration + +When using `brainstorming` skill: +- After architectural decision finalized → create/update entity +- Record alternatives considered and why they were rejected +- Link to related architectural entities +- Example trigger: Design approved, ready for implementation + +## Note Templates with Frontmatter + +### Session Note Template + +```markdown +--- +type: session +project: {project-name} +tags: [session, work-in-progress] +created: {YYYY-MM-DD} +updated: {YYYY-MM-DD} +status: active +claude_last_accessed: {YYYY-MM-DD} +cross_project_recalls: [] +--- + +# Session: {date} - {topic} + +## Context + +{Why we're working on this, what we're trying to accomplish} + +## Work Log + +### {Time} - {Milestone} + +{What happened, decisions made, approaches tried} + +## Decisions Made + +- **{Decision}**: {Rationale} +- **{Decision}**: {Rationale} + +## Open Questions + +- {Question requiring further investigation} +- {Blocker or uncertainty} + +## Next Steps + +- [ ] {Action item} +- [ ] {Action item} + +## Related Entities + +- [[Entity Name]] - {Why it's relevant} +- [[Entity Name]] - {Why it's relevant} +``` + +### Entity Note Template + +```markdown +--- +type: entity +project: {project-name} +tags: [component, pattern, debugging, architecture] +created: {YYYY-MM-DD} +updated: {YYYY-MM-DD} +status: active +claude_last_accessed: {YYYY-MM-DD} +cross_project_recalls: [] +--- + +# {Entity Name} + +## Overview + +{What this entity is, its purpose and role} + +## Architecture + +{Structure, key components, how it works} + +## Key Decisions + +### {Decision Title} + +**Date:** {YYYY-MM-DD} +**Rationale:** {Why we chose this approach} +**Alternatives Considered:** {What we didn't do and why} +**Impact:** {How this affects the system} + +## Gotchas & Troubleshooting + +### {Problem/Symptom} + +**Symptom:** {How it manifests} +**Root Cause:** {Why it happens} +**Solution:** {How to fix it} +**Tags:** #debugging #troubleshooting + +## Recent Changes + +### {Date} - {Change Description} + +{What changed and why} + +## Related Entities + +- [[Entity Name]] - {Relationship description} +- [[Entity Name]] - {Relationship description} + +## References + +- {External documentation} +- {Code file paths} +- {Commits or PRs} +``` + +### Topic Note Template + +```markdown +--- +type: topic +project: global +tags: [topic, moc, patterns] +created: {YYYY-MM-DD} +updated: {YYYY-MM-DD} +status: active +claude_last_accessed: {YYYY-MM-DD} +cross_project_recalls: [] +--- + +# {Topic Name} + +## Overview + +{What this topic covers, why it's important} + +## Key Concepts + +- [[Entity Name]] - {Core concept} +- [[Entity Name]] - {Core concept} +- [[Entity Name]] - {Core concept} + +## Patterns & Best Practices + +### {Pattern Name} + +{Description of pattern, when to use, examples} + +**Related Entities:** [[Entity A]], [[Entity B]] + +### {Pattern Name} + +{Description of pattern, when to use, examples} + +**Related Entities:** [[Entity C]], [[Entity D]] + +## Common Pitfalls + +- {Antipattern or common mistake} +- {How to avoid it} + +## Learning Path + +1. Start with: [[Entity Name]] +2. Then explore: [[Entity Name]] +3. Advanced: [[Entity Name]] + +## References + +- {External resources} +- {Documentation links} +``` + +## Living Memory Update Logic + +### Patch Operations + +**Principle:** Preserve existing content, append new insights + +**Implementation:** +1. Load existing note via MCP `read_note` +2. Parse markdown structure (identify sections) +3. Append to relevant sections (don't rewrite entire sections) +4. Add new sections if needed +5. Update frontmatter: `updated` timestamp, `claude_last_accessed` +6. Write back via MCP `update_note` + +**Example patch patterns:** +- Append to `## Recent Changes` with new entry +- Add new `### Gotcha` under `## Gotchas & Troubleshooting` +- Insert new `### Decision` under `## Key Decisions` +- Append to `## Related Entities` with new links + +### Conflict Detection + +**Timestamp Comparison Logic:** + +``` +When loading note: + Store: note.frontmatter.updated as LOADED_TIMESTAMP + Update: note.frontmatter.claude_last_accessed = CURRENT_DATE + +When updating note: + Reload: note.frontmatter.updated as CURRENT_TIMESTAMP + + IF CURRENT_TIMESTAMP > LOADED_TIMESTAMP: + CONFLICT DETECTED (human edited since Claude loaded) + TRIGGER CONFLICT RESOLUTION + ELSE: + SAFE TO UPDATE (no human edits) + APPLY PATCH +``` + +**Fields used:** +- `created`: Never changes, original creation date +- `updated`: Modified every time note is saved (by human or Claude) +- `claude_last_accessed`: Updated when Claude loads note into context + +### Conflict Resolution Flow + +**Case 1: Clean Update (No Conflict)** + +``` +1. Claude loaded version A at T1 +2. No human edits since T1 +3. Apply patch operation +4. Update frontmatter: + - updated = CURRENT_DATE + - claude_last_accessed = CURRENT_DATE +5. Write via MCP update_note +``` + +**Case 2: Human Edit Conflict** + +``` +1. Claude loaded version A at T1 (updated = T1, claude_last_accessed = T1) +2. Human edited to version B at T2 (updated = T2) +3. Claude attempts patch at T3 +4. Detect: T2 > T1 (updated > loaded timestamp) +5. Show user: + + "I want to update [[{Entity Name}]] but you've edited it since I loaded it. + + Your changes (at {T2}): + {DIFF: show what human changed} + + My changes (at {T3}): + {DIFF: show what Claude wants to add} + + Options: + 1. Show me both diffs and I'll merge manually + 2. Abort your update (keep my changes only) + 3. Create new section '## Claude's Updates (conflicted)' + 4. Let's discuss and resolve together + + What should I do?" + +6. Wait for user decision +7. Execute chosen resolution +``` + +**Case 3: Major Rewrite Needed** + +``` +1. Claude's understanding contradicts existing note fundamentally +2. Don't silently overwrite user's knowledge +3. Ask user: + + "My understanding of [[{Entity Name}]] changed significantly. + + Current note says: + {EXCERPT: key claim from existing note} + + But I now think: + {EXCERPT: contradictory understanding} + + Options: + 1. Create new entity note with alternative understanding + 2. Update existing note (I'll show you the full diff first) + 3. Let's discuss the contradiction - maybe both are valid + + What should I do?" + +4. Wait for user decision +5. Execute chosen resolution +``` + +## Cross-Project Pattern Detection + +### Tracking Cross-Project Recalls + +**When it happens:** +- Claude is working in Project B +- Loads memory from Project A (via search, graph query, or explicit recall) +- This signals the pattern might be reusable + +**Automatic logging:** + +``` +1. Detect cross-project recall: + - Current project context: {project-b} + - Loaded entity: {entity-from-project-a} + - Entity frontmatter.project: {project-a} + - IF project-a ≠ project-b: CROSS-PROJECT RECALL + +2. Update entity frontmatter: + Append to cross_project_recalls array: + + cross_project_recalls: + - project: project-b + date: 2025-11-18 + context: "Used worktree pattern for parallel tasks" + +3. No user-visible noise (silent tracking) +``` + +### Promotion Threshold Logic + +**When to prompt:** +- Entity has 3 or more cross-project recalls +- Entity is currently project-scoped (not global) + +**Promotion prompt:** + +``` +"I've referenced [[{Entity Name}]] from {project-a} while working on other projects 3 times now: + +1. {project-b} ({date}): {context} +2. {project-c} ({date}): {context} +3. {project-d} ({date}): {context} + +This pattern seems reusable across projects. Should I promote it to global knowledge? + +Options: +1. Yes, promote to global (move to ~/.claude-memory/claude/global/entities/) +2. Remind me later (ask again after 5 cross-project recalls) +3. No, it's project-specific (stop tracking) + +What should I do?" +``` + +**Promotion process:** + +``` +1. User approves promotion +2. Move entity from: + ~/.claude-memory/claude/projects/{project-a}/entities/{Entity}.md + TO: + ~/.claude-memory/claude/global/entities/{Entity}.md +3. Update frontmatter: + - project: global (was: project-a) + - Add tag: #global-pattern +4. Create redirect note in original location: + "This entity has been promoted to global. See [[{Entity}]]" +5. Update all links in project-a notes to point to global entity +6. Confirm: "Promoted [[{Entity}]] to global knowledge." +``` + +## MCP Operation Patterns + +### Required MCP Server + +**Dependency:** `obsidian-mcp-plugin` by aaronsb +**Repository:** https://github.com/aaronsb/obsidian-mcp-plugin + +### Operations Used + +#### 1. create_note + +**Purpose:** Create new session, entity, or topic note + +**Usage:** +```javascript +mcp.create_note({ + vault: "~/.claude-memory", + path: "claude/projects/{project}/entities/{Entity Name}.md", + content: "{markdown content with frontmatter}" +}) +``` + +**Error handling:** +``` +TRY: + create_note(...) +CATCH FileExistsError: + "Note [[{Entity Name}]] already exists. Use update_note instead." +CATCH MCPUnavailableError: + "Obsidian MCP server unavailable. Check setup-guide.md for configuration." +``` + +#### 2. read_note + +**Purpose:** Load existing note for updating or reference + +**Usage:** +```javascript +mcp.read_note({ + vault: "~/.claude-memory", + path: "claude/projects/{project}/entities/{Entity Name}.md" +}) +``` + +**Returns:** +```javascript +{ + content: "{full markdown content}", + frontmatter: { + type: "entity", + updated: "2025-11-17", + claude_last_accessed: "2025-11-16", + ... + } +} +``` + +**Error handling:** +``` +TRY: + read_note(...) +CATCH FileNotFoundError: + "Note [[{Entity Name}]] not found. Create it with /remember?" +CATCH MCPUnavailableError: + GRACEFUL_DEGRADATION (see below) +``` + +#### 3. update_note + +**Purpose:** Update existing note with patch operation + +**Usage:** +```javascript +mcp.update_note({ + vault: "~/.claude-memory", + path: "claude/projects/{project}/entities/{Entity Name}.md", + content: "{updated markdown content}", + frontmatter: { + updated: "{CURRENT_DATE}", + claude_last_accessed: "{CURRENT_DATE}" + } +}) +``` + +**Error handling:** +``` +TRY: + update_note(...) +CATCH FileNotFoundError: + "Note disappeared. Create new one?" +CATCH MCPUnavailableError: + GRACEFUL_DEGRADATION (see below) +``` + +#### 4. search_notes + +**Purpose:** Text search across vault + +**Usage:** +```javascript +mcp.search_notes({ + vault: "~/.claude-memory", + query: "{search term}", + path_filter: "claude/projects/{project}/**" +}) +``` + +**Returns:** +```javascript +[ + { + path: "claude/projects/{project}/entities/{Entity}.md", + snippet: "{matching text}", + score: 0.95 + }, + ... +] +``` + +**Error handling:** +``` +TRY: + search_notes(...) +CATCH MCPUnavailableError: + "Cannot search memories - MCP unavailable." +``` + +#### 5. graph_query + +**Purpose:** Find linked entities via wikilinks + +**Usage:** +```javascript +mcp.graph_query({ + vault: "~/.claude-memory", + start_node: "[[{Entity Name}]]", + depth: 2, + direction: "outgoing" // or "incoming" for backlinks +}) +``` + +**Returns:** +```javascript +{ + nodes: [ + { name: "Entity A", type: "entity" }, + { name: "Entity B", type: "entity" } + ], + edges: [ + { from: "Entity A", to: "Entity B", type: "wikilink" } + ] +} +``` + +**Error handling:** +``` +TRY: + graph_query(...) +CATCH MCPUnavailableError: + FALLBACK to search_notes (less precise but functional) +``` + +#### 6. dataview_query + +**Purpose:** Query notes using Dataview syntax + +**Usage:** +```javascript +mcp.dataview_query({ + vault: "~/.claude-memory", + query: ` + LIST FROM "claude/projects/{project}/sessions" + WHERE status = "active" + SORT created DESC + LIMIT 3 + ` +}) +``` + +**Returns:** +```javascript +[ + { + path: "claude/projects/{project}/sessions/2025-11-17-memory-design.md", + frontmatter: { ... } + }, + ... +] +``` + +**Error handling:** +``` +TRY: + dataview_query(...) +CATCH DataviewNotInstalledError: + FALLBACK to search_notes with path filter +CATCH MCPUnavailableError: + GRACEFUL_DEGRADATION (see below) +``` + +### Graceful Degradation + +**When MCP unavailable:** + +``` +1. Detect MCP connection failure +2. Show user: + "Obsidian MCP server is unavailable. I can't access working memory. + + To restore memory features: + 1. Ensure Obsidian is running + 2. Check obsidian-mcp-plugin is installed + 3. Verify Claude Code config includes MCP server + + See docs/setup-guide.md for troubleshooting. + + Continue without memory? (yes/no)" + +3. If user says yes: + - Continue session without memory reads/writes + - Track what would have been written + - Offer to export pending writes to markdown file + +4. If user says no: + - Pause work until MCP restored + - Guide user through troubleshooting +``` + +**Fallback strategies:** + +- graph_query unavailable → use search_notes +- dataview_query unavailable → use search_notes with path filtering +- semantic search unavailable → use text search +- All MCP unavailable → offer manual markdown file export + +## TodoWrite Integration + +### Memory Checkpoint Checklist + +Use when periodic checkpoint time (30-60 min) or major milestone reached: + +``` +- [ ] Review current session note +- [ ] Identify key decisions made since last checkpoint +- [ ] Update session note with new context +- [ ] Check if any entities need updating +- [ ] Apply patch operations to relevant entity notes +- [ ] Update frontmatter timestamps +- [ ] Verify write count (under 5 per hour limit) +- [ ] Mark checkpoint complete +``` + +### Session End Checklist + +Use at end of work session: + +``` +- [ ] Final session note update with closing context +- [ ] Check session note size (line count) +- [ ] Check session note age (created date) +- [ ] If threshold met (500 lines OR 3 days old): + - [ ] Parse session note for extractable knowledge + - [ ] Identify entities to create or update + - [ ] Apply patches to entity notes + - [ ] Create new entities if needed + - [ ] Archive session note to archive/sessions/ + - [ ] Verify archive successful +- [ ] If threshold not met: + - [ ] Mark session note status: active + - [ ] Note next checkpoint time +- [ ] Review cross-project recall tracking +- [ ] Check for promotion threshold (3 recalls) +- [ ] Confirm all writes successful +``` + +### Compaction Checklist + +Use when compacting session note into entity notes: + +``` +- [ ] Load session note content +- [ ] Parse for distinct concepts/decisions: + - [ ] Architectural decisions → entity notes + - [ ] Bug fixes → entity notes (troubleshooting) + - [ ] New patterns → topic notes or entities + - [ ] Preferences → user preference entity +- [ ] For each extractable item: + - [ ] Identify target entity (existing or new) + - [ ] Prepare patch content + - [ ] Check for conflicts (timestamp comparison) + - [ ] Apply patch or resolve conflict + - [ ] Update frontmatter +- [ ] Create backlinks from entities to archived session +- [ ] Move session note to archive: + - [ ] Copy to ~/.claude-memory/claude/projects/{project}/archive/sessions/ + - [ ] Verify archive successful + - [ ] Update session note status: archived +- [ ] Confirm compaction complete +- [ ] Update project index if new entities created +``` + +## Usage Examples + +### Example 1: After Code Review + +``` +Context: Just completed code review using requesting-code-review skill + +Trigger: Automatic (code review complete, no permission needed) + +Action: +1. Identify reviewed component: "Git Worktrees Integration" +2. Check if entity exists: search_notes("Git Worktrees") +3. If exists: + - Load entity via read_note + - Check timestamps (conflict detection) + - Append to "## Recent Changes": + ### 2025-11-18 - Code Review Findings + - Reviewed implementation of worktree cleanup + - Decision: Use trap handlers for cleanup on failure + - Rationale: Ensures cleanup even if subagent crashes + - Related: [[Error Handling Patterns]] + - Update frontmatter: updated, claude_last_accessed + - Write via update_note +4. If not exists: + - Create entity via create_note (use entity template) + - Populate with review findings + - Link to related entities: [[Parallel Execution]], [[Spectacular]] + +Result: [[Git Worktrees]] entity updated with code review insights +``` + +### Example 2: After Debugging + +``` +Context: Fixed race condition bug using systematic-debugging skill + +Trigger: Automatic (debugging complete, no permission needed) + +Action: +1. Create or update entity: "Race Condition Debugging" +2. Add to "## Gotchas & Troubleshooting": + ### Parallel Phase Cleanup Race Condition + + **Symptom:** Worktree cleanup fails intermittently with "directory not empty" + **Root Cause:** Cleanup runs before subagent finishes writing + **Solution:** Add wait for background processes before cleanup + **Tags:** #debugging #race-condition #parallel-execution + + **Code:** + ```bash + # Wait for all background processes + wait + # Then cleanup + rm -rf worktree + ``` +3. Update session note with debugging story (context for future) +4. Link to related entities: [[Parallel Execution]], [[Error Handling]] + +Result: [[Race Condition Debugging]] entity created/updated, pattern documented +``` + +### Example 3: User Preference (Ask First) + +``` +Context: User says "I prefer conventional commits for all commits" + +Trigger: Ask-first (user preference) + +Action: +1. Detect preference statement +2. Ask user: + "You mentioned preferring conventional commits. Should I remember this preference? + + This would create/update an entity: [[User Preferences - Commits]] + + Options: + 1. Yes, remember this + 2. No, just for this session + 3. Let me think about it + + What should I do?" + +3. If user approves: + - Create/update [[User Preferences - Commits]] + - Add to "## Commit Style": + **Preference:** Conventional commits + **Format:** type(scope): description + **Rationale:** {what user said} + **Date noted:** 2025-11-18 + - Apply in future commits automatically + +Result: User preference documented and respected in future sessions +``` + +### Example 4: Cross-Project Recall + +``` +Context: Working in project-b, recalled [[Git Worktrees]] from project-a + +Trigger: Automatic (silent tracking, no user-visible action) + +Action: +1. Detect cross-project recall: + - Current project: project-b + - Entity project: project-a + - Different → log it + +2. Update [[Git Worktrees]] frontmatter: + cross_project_recalls: + - project: project-a + date: 2025-11-16 + context: "Initial creation" + - project: project-b + date: 2025-11-18 + context: "Used worktree pattern for parallel tasks" + +3. Check count: 2 cross-project recalls (threshold is 3) +4. No action yet (wait for threshold) + +Later: Third cross-project recall from project-c + +Action: +1. Detect: 3 cross-project recalls (threshold met) +2. Ask user: + "I've referenced [[Git Worktrees]] from project-a while working on other projects 3 times now: + + 1. project-b (2025-11-18): Used worktree pattern for parallel tasks + 2. project-c (2025-11-19): Referenced for isolation strategy + 3. project-d (2025-11-20): Applied to test environment setup + + This pattern seems reusable. Promote to global knowledge? + + Options: + 1. Yes, promote to global + 2. Remind me at 5 recalls + 3. No, it's project-specific + + What should I do?" + +3. If approved: Execute promotion (move to global/entities/, update links) + +Result: Pattern promoted to global knowledge, easier discovery in future +``` + +## Best Practices + +### Do's + +- ✅ Preserve existing content (patch operations, not rewrites) +- ✅ Always check timestamps before updating (conflict detection) +- ✅ Show diffs when conflicts detected (transparency) +- ✅ Link entities via wikilinks (build knowledge graph) +- ✅ Tag appropriately (enables Dataview queries) +- ✅ Update frontmatter every write (timestamps critical) +- ✅ Ask permission for subjective content (preferences, contradictions) +- ✅ Batch related updates (reduce write spam) +- ✅ Archive, don't delete (preserve history) +- ✅ Use descriptive entity names (clear, searchable) + +### Don'ts + +- ❌ Don't rewrite entire notes (use patch operations) +- ❌ Don't skip conflict detection (causes data loss) +- ❌ Don't write more than 5 times per hour (spam) +- ❌ Don't create duplicate entities (search first) +- ❌ Don't overwrite human edits silently (ask first) +- ❌ Don't create entities prematurely (wait for pattern to emerge) +- ❌ Don't delete notes (archive instead) +- ❌ Don't forget cross-project tracking (promotion opportunity) +- ❌ Don't write without frontmatter (breaks queries) +- ❌ Don't use vague entity names (be specific) + +## Troubleshooting + +### Issue: MCP Connection Failed + +**Symptom:** `MCPUnavailableError` when trying to read/write notes + +**Diagnosis:** +1. Check Obsidian is running +2. Check obsidian-mcp-plugin installed in vault +3. Check Claude Code config includes MCP server +4. Check vault path is correct + +**Solution:** See docs/setup-guide.md for full MCP setup instructions + +**Workaround:** Continue without memory, export pending writes to markdown file at session end + +### Issue: Conflict Detection False Positive + +**Symptom:** Conflict detected but user didn't edit note + +**Diagnosis:** +1. Check if another Claude session edited note +2. Check if Obsidian sync changed timestamps +3. Check if filesystem events triggered spurious update + +**Solution:** +- Show diff to user (likely minor) +- If diff is empty: ignore conflict, proceed with update +- If diff is real but minor: ask user to merge +- If false positive pattern repeats: adjust timestamp granularity + +### Issue: Memory Spam + +**Symptom:** Too many write operations, cluttering vault + +**Diagnosis:** +1. Count writes in current session +2. Review what triggered each write +3. Identify if triggers are too aggressive + +**Solution:** +- Batch related updates into single operation +- Increase checkpoint interval (60-90 min instead of 30-60) +- Ask user: "Reduce memory write frequency?" +- Adjust trigger sensitivity + +### Issue: Search Returns Irrelevant Results + +**Symptom:** Recalled memories not related to current work + +**Diagnosis:** +1. Check search query (too broad?) +2. Check if semantic search is available (better relevance) +3. Review entity tags (improve discoverability) + +**Solution:** +- Refine search queries (use more specific terms) +- Add tags to entities (improve filtering) +- Install Smart Connections plugin (semantic search) +- Use graph queries instead of text search (more precise) + +### Issue: Session Note Too Large + +**Symptom:** Session note exceeds 500 lines, performance degrading + +**Diagnosis:** +1. Check line count: `wc -l session-note.md` +2. Review if compaction threshold was missed +3. Check if threshold detection is working + +**Solution:** +- Trigger compaction immediately +- Extract knowledge into entity notes +- Archive session note +- Adjust threshold if too high (reduce to 300 lines?) + +### Issue: Cross-Project Promotion Not Triggering + +**Symptom:** Entity used across projects but no promotion prompt + +**Diagnosis:** +1. Check cross_project_recalls frontmatter (is it logging?) +2. Check recall count (reached threshold of 3?) +3. Review detection logic (cross-project recall tracking) + +**Solution:** +- Manually check entity frontmatter +- If logging works but prompt missing: check promotion threshold logic +- If logging not working: verify cross-project detection in recall flow +- Ask user manually: "Should [[Entity]] be global?" + +--- + +## Summary + +This skill enables Claude to maintain proactive, updateable working memory using Obsidian as the backend. Key principles: + +1. **Automatic with selective asking** - Write memories autonomously, ask for subjective content +2. **Three-layer granularity** - Session (temporal), Entity (persistent), Topic (organizational) +3. **Living memory** - Patch operations preserve existing content while adding new insights +4. **Conflict detection** - Timestamp comparison prevents data loss from human edits +5. **Cross-project patterns** - Track reuse, promote to global at threshold +6. **Graceful degradation** - Fallback strategies when MCP unavailable +7. **Memory spam prevention** - Limit writes to <5 per hour +8. **TodoWrite integration** - Checklists for checkpoints, session end, compaction + +Use this skill to ensure context persists across sessions, decisions are documented with rationale, and patterns are captured for future reuse.