Initial commit
This commit is contained in:
18
.claude-plugin/plugin.json
Normal file
18
.claude-plugin/plugin.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# tabula-scripta
|
||||||
|
|
||||||
|
Working memory system for Claude Code - integrates with Obsidian via MCP for persistent, cross-session memory management
|
||||||
386
commands/recall.md
Normal file
386
commands/recall.md
Normal file
@@ -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
|
||||||
265
commands/remember.md
Normal file
265
commands/remember.md
Normal file
@@ -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
|
||||||
459
commands/update-memory.md
Normal file
459
commands/update-memory.md
Normal file
@@ -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
|
||||||
26
hooks/hooks.json
Normal file
26
hooks/hooks.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
620
hooks/session-end.md
Normal file
620
hooks/session-end.md
Normal file
@@ -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.
|
||||||
27
hooks/session-end.sh
Executable file
27
hooks/session-end.sh
Executable file
@@ -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 <<EOF
|
||||||
|
{
|
||||||
|
"hookSpecificOutput": {
|
||||||
|
"hookEventName": "SessionEnd",
|
||||||
|
"additionalContext": "<IMPORTANT>\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</IMPORTANT>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
509
hooks/session-start.md
Normal file
509
hooks/session-start.md
Normal file
@@ -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.
|
||||||
27
hooks/session-start.sh
Executable file
27
hooks/session-start.sh
Executable file
@@ -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 <<EOF
|
||||||
|
{
|
||||||
|
"hookSpecificOutput": {
|
||||||
|
"hookEventName": "SessionStart",
|
||||||
|
"additionalContext": "<IMPORTANT>\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</IMPORTANT>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
77
plugin.lock.json
Normal file
77
plugin.lock.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
1024
skills/managing-working-memory.md
Normal file
1024
skills/managing-working-memory.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user