Compare commits

..

37 Commits

Author SHA1 Message Date
Alex Newman 5f7aa0710e Bump version to 5.5.1
This patch release includes:
- Summary hook enhancements to capture last user message from transcripts
- Activity indicator improvements for better user feedback
- Worker service enhancements for queue depth tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 17:41:22 -05:00
Alex Newman 39fedfc5fc feat: Enhance summary hook to include last user message from transcript (#95)
* feat: Enhance summary hook to include last user message from transcript

- Added functionality to extract the last user message from a JSONL transcript file in the summary hook.
- Updated the summary hook to send the last user message along with the summary request.
- Modified the SDKSession interface to include an optional last_user_message field.
- Updated the summary prompt to incorporate the last user message in the output format.
- Refactored worker service to handle the last user message in the summarize queue.
- Enhanced session manager to track and broadcast processing status based on active sessions and queue depth.
- Improved error handling and logging for better traceability during transcript reading and processing.

* feat(worker): enhance processing status broadcasting and session management

- Added immediate broadcasting of processing status when a prompt is received.
- Implemented logging for generator completion in multiple locations.
- Updated `broadcastProcessingStatus` to include queue depth and active session count in logs.
- Modified session iterator to stop yielding messages after a summary is yielded, with appropriate logging.
2025-11-11 17:38:22 -05:00
Alex Newman ecb8b39f6d Add auto-generated CHANGELOG from GitHub releases
New Features:
- Created scripts/generate-changelog.js to auto-generate CHANGELOG.md
- Fetches all GitHub releases and formats into Keep a Changelog format
- Added npm run changelog:generate command

Version-Bump Skill Updates:
- Added Step 10: Generate CHANGELOG to workflow
- Updated verification checklist to include CHANGELOG generation
- Updated skill description and critical rules
- Single source of truth: GitHub releases

Technical Details:
- Script fetches releases via gh CLI
- Parses release bodies and formats to markdown
- Removes duplicate headers and Claude Code footers
- Sorts releases by date (newest first)
- Generates clean, consistent changelog

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 16:26:10 -05:00
Alex Newman fe0902b48f Remove empty Unreleased section from CHANGELOG 2025-11-11 16:22:12 -05:00
Alex Newman 4ab9739e4c Update CHANGELOG.md for v5.5.0 release 2025-11-11 16:20:32 -05:00
Alex Newman 6ddef1093a Release v5.5.0: Enhanced mem-search skill
Breaking Changes: None (minor version)

Improvements:
- Merged PR #91: Replace generic "search" skill with enhanced "mem-search" skill
- Improved skill effectiveness from 67% to 100% (Anthropic standards)
- Enhanced scope differentiation to prevent confusion with native conversation memory
- Increased concrete triggers from 44% to 85%
- Added 5+ unique identifiers and explicit exclusion patterns
- Comprehensive documentation reorganization (17 total files)

Technical Changes:
- New mem-search skill with system-specific naming
- Explicit temporal keywords ("previous sessions", "weeks/months ago")
- Technical anchors referencing FTS5 full-text index and typed observations
- Documentation moved from /context/ to /docs/context/
- Detailed technical architecture documentation added
- 12 operation guides + 2 principle directories

Credits:
- Skill design and enhancement by @basher83

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 16:18:42 -05:00
basher83 97d565e3cd Replace search skill with mem-search (#91)
* feat: add mem-search skill with progressive disclosure architecture

Add comprehensive mem-search skill for accessing claude-mem's persistent
cross-session memory database. Implements progressive disclosure workflow
and token-efficient search patterns.

Features:
- 12 search operations (observations, sessions, prompts, by-type, by-concept, by-file, timelines, etc.)
- Progressive disclosure principles to minimize token usage
- Anti-patterns documentation to guide LLM behavior
- HTTP API integration for all search functionality
- Common workflows with composition examples

Structure:
- SKILL.md: Entry point with temporal trigger patterns
- principles/: Progressive disclosure + anti-patterns
- operations/: 12 search operation files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add CHANGELOG entry for mem-search skill

Document mem-search skill addition in Unreleased section with:
- 100% effectiveness compliance metrics
- Comparison to previous search skill implementation
- Progressive disclosure architecture details
- Reference to audit report documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add mem-search skill audit report

Add comprehensive audit report validating mem-search skill against
Anthropic's official skill-creator documentation.

Report includes:
- Effectiveness metrics comparison (search vs mem-search)
- Critical issues analysis for production readiness
- Compliance validation across 6 key dimensions
- Reference implementation guidance

Result: mem-search achieves 100% compliance vs search's 67%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add comprehensive search architecture analysis document

- Document current state of dual search architectures (HTTP API and MCP)
- Analyze HTTP endpoints and MCP search server architectures
- Identify DRY violations across search implementations
- Evaluate the use of curl as the optimal approach for search
- Provide architectural recommendations for immediate and long-term improvements
- Outline action plan for cleanup, feature parity, DRY refactoring

* refactor: Remove deprecated search skill documentation and operations

* refactor: Reorganize documentation into public and context directories

Changes:
- Created docs/public/ for Mintlify documentation (.mdx files)
- Created docs/context/ for internal planning and implementation docs
- Moved all .mdx files and assets to docs/public/
- Moved all internal .md files to docs/context/
- Added CLAUDE.md to both directories explaining their purpose
- Updated docs.json paths to work with new structure

Benefits:
- Clear separation between user-facing and internal documentation
- Easier to maintain Mintlify docs in dedicated directory
- Internal context files organized separately

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Enhance session management and continuity in hooks

- Updated new-hook.ts to clarify session_id threading and idempotent session creation.
- Modified prompts.ts to require claudeSessionId for continuation prompts, ensuring session context is maintained.
- Improved SessionStore.ts documentation on createSDKSession to emphasize idempotent behavior and session connection.
- Refined SDKAgent.ts to detail continuation prompt logic and its reliance on session.claudeSessionId for unified session handling.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>
2025-11-11 16:15:07 -05:00
Alex Newman eafdd6a7be Bump version to 5.4.5 2025-11-11 13:51:19 -05:00
Alex Newman 3529f9274b feat: Enhanced logging and SDK prompt improvements (#94)
* Initial plan

* Initial analysis: Found root cause of double entries bug

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Fix double entries by assigning generatorPromise in handleSessionInit

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* feat(logging): Enhance HTTP request logging and session management

- Added middleware for logging HTTP requests and responses, excluding static assets and health checks.
- Introduced a method to summarize request bodies for specific endpoints.
- Improved logging for user prompt synchronization with Chroma, including duration tracking.
- Enhanced session initialization logging to include additional session details.
- Updated observation and summary logging to provide more context and error handling during Chroma synchronization.
- Refactored tool name formatting for logging in the SessionManager.
- Expanded logger component types to include 'HTTP', 'SESSION', and 'CHROMA'.

* Refactor SDK prompts and logging for improved clarity and functionality

- Updated buildInitPrompt to clarify the observer's role and what to record.
- Enhanced buildSummaryPrompt with clearer instructions for summarizing ongoing sessions.
- Improved buildContinuationPrompt to emphasize the focus on deliverables and capabilities.
- Refactored WorkerService to utilize a centralized tool formatting function for logging.
- Added truncation for logged responses and observations to improve readability.
- Updated SessionManager to log the queuing of summarize actions with session details.
- Enhanced App and Sidebar components to support refreshing stats on sidebar open.
- Refactored useStats hook to allow manual refreshing of stats while maintaining automatic loading on mount.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-11 13:49:00 -05:00
Alex Newman 30ebe92a53 Release v5.4.4: Fix duplicate entries in viewer
Bugfix:
- Fixed duplicate observations and summaries appearing in viewer
- Root cause: handleSessionInit spawned SDK agent but didn't save promise to session.generatorPromise
- Second agent would spawn when handleObservations ran, causing duplicates
- Fix: Assign generatorPromise in handleSessionInit (matches handleSummarize pattern)

Technical changes:
- Modified src/services/worker-service.ts:265
- Now tracks promise to prevent duplicate agent spawning
- Guard condition in handleObservations (line 301) now works correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 17:30:58 -05:00
Copilot 1bb203cbb5 Fix duplicate entries in viewer caused by untracked SDK agent promise (#86)
* Initial plan

* Initial analysis: Found root cause of double entries bug

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Fix double entries by assigning generatorPromise in handleSessionInit

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>
2025-11-10 17:30:02 -05:00
Alex Newman 9f8499fe54 Bump version to 5.4.3
Release v5.4.3: Fix PM2 race condition in worker restart

Changes:
- Removed PM2 restart logic from ensureWorkerRunning()
- PM2 watch mode now exclusively handles worker restarts
- Eliminated race condition causing TypeError
- Reduced unnecessary worker restarts (39+ → minimal)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 16:53:23 -05:00
Alex Newman 40d105d7de Merge pull request #87 from thedotmack/copilot/fix-pm2-race-condition
Remove PM2 restart logic from hooks to fix race condition with watch mode
2025-11-10 16:52:25 -05:00
Alex Newman cda12d95c9 Merge main into copilot/fix-pm2-race-condition
Resolved conflicts in built files by rebuilding from source
2025-11-10 16:51:26 -05:00
Alex Newman bbd6113f69 Release v5.4.2: CWD spatial awareness fix
Bugfix release:
- Fixed SDK agent spatial awareness by propagating CWD context from tool execution
- Prevents false "file not found" reports when working across multiple repositories
- Added comprehensive test suite for CWD propagation (8 passing tests)
- Security analysis: Zero vulnerabilities, CodeQL approved

Technical changes:
- Hook extracts CWD from PostToolUseInput and forwards to worker service
- Worker types define optional CWD fields in PendingMessage and ObservationData
- SessionManager passes CWD to SDKAgent's addObservation
- SDK agent includes CWD in tool observation objects sent to Claude API
- Prompts render tool_cwd XML elements with spatial awareness guidance
- All changes present in compiled outputs (save-hook.js, worker-service.cjs)

Documentation:
- Technical documentation: context/CWD_CONTEXT_FIX.md
- PR summary: PR_SUMMARY.md
- Security review: SECURITY_SUMMARY.md
- CHANGELOG entry added

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 15:33:14 -05:00
Alex Newman 51d1315562 Merge pull request #90 from thedotmack/copilot/fix-sdk-agent-cwd-issue
Fix: Pass CWD context to SDK agent for spatial awareness
2025-11-10 15:32:00 -05:00
Alex Newman 85763d575d Move CWD documentation to context directory
Per project conventions, docs/ is reserved for user-facing Mintlify .mdx files.
Technical documentation should live in context/ directory.

- Moved docs/CWD_CONTEXT_FIX.md → context/CWD_CONTEXT_FIX.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 15:31:05 -05:00
Alex Newman c76ddc2f83 Add TypeScript Agent SDK reference documentation
- Introduced comprehensive API reference for the TypeScript Agent SDK.
- Documented key functions including `query()`, `tool()`, and `createSdkMcpServer()`.
- Detailed types such as `Options`, `Query`, `AgentDefinition`, and various tool input/output types.
- Included examples and descriptions for configuration options and permission handling.
- Added sections for hook types, tool input/output types, and permission types.
2025-11-10 15:04:44 -05:00
copilot-swe-agent[bot] 348cc7f7ac Add security summary documentation
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:31:57 +00:00
copilot-swe-agent[bot] ed19e92f75 Add PR summary and final verification
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:30:13 +00:00
copilot-swe-agent[bot] 2a96214456 Add documentation for CWD context fix
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:28:39 +00:00
copilot-swe-agent[bot] eaa2268bf9 Add CWD propagation tests and verify build output
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:25:08 +00:00
copilot-swe-agent[bot] 730c420a13 Add CWD propagation through hook, worker, and SDK agent
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:23:06 +00:00
copilot-swe-agent[bot] d0bdc6ae9b Initial exploration - understanding the codebase
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:20:41 +00:00
copilot-swe-agent[bot] d46fbb7166 Initial plan 2025-11-10 19:17:13 +00:00
copilot-swe-agent[bot] a60ef6eacb Remove PM2 restart logic from ensureWorkerRunning to fix race condition
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:05:17 +00:00
copilot-swe-agent[bot] 13941a1e72 Initial analysis: Identify PM2 race condition in ensureWorkerRunning
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-10 19:03:08 +00:00
copilot-swe-agent[bot] bd809c860e Initial plan 2025-11-10 19:00:05 +00:00
Alex Newman b4b90faa1e Release v5.4.1: MCP search server toggle functionality
New Features:
- REST API endpoints for MCP server status and toggle control
- UI toggle in viewer sidebar for enabling/disabling MCP search server
- File-based persistence mechanism (.mcp.json ↔ .mcp.json.disabled)
- Independent state management for MCP toggle

Technical changes:
- Added GET /api/mcp/status endpoint (returns mcpEnabled boolean)
- Added POST /api/mcp/toggle endpoint (toggles MCP server state)
- Updated Settings interface with mcpEnabled property
- Enhanced Sidebar component with MCP toggle UI and state management
- Updated worker service with MCP control logic
- Bumped version to 5.4.1 in all metadata files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 13:48:52 -05:00
Alex Newman 9e235b5b57 feat: Add MCP search server toggle with dedicated API architecture (#85)
* feat: add MCP search server toggle functionality

- Introduced `CLAUDE_MEM_MCP_ENABLED` setting to manage the MCP search server state.
- Updated `WorkerService` to handle MCP enabling/disabling based on the new setting.
- Enhanced `Sidebar` component to include a checkbox for toggling MCP search server.
- Modified `useSettings` hook to incorporate the new MCP setting.
- Updated default settings to include `CLAUDE_MEM_MCP_ENABLED` with a default value of true.
- Adjusted TypeScript types to include the new MCP setting.

* feat: Implement MCP toggle functionality in WorkerService and Sidebar

- Added API endpoints for MCP status retrieval and toggling in WorkerService.
- Updated Sidebar component to manage MCP toggle state and display status messages.
- Removed MCP_ENABLED from settings state management and default settings.
- Adjusted settings interface and related hooks to reflect the removal of MCP_ENABLED.
2025-11-10 13:47:25 -05:00
Alex Newman 5fdf25d60f docs: Add JIT context filtering post-mortem
Comprehensive analysis of the 3.5-hour JIT context filtering experiment.
Documents architectural learnings, implementation attempts, and why the
complex persistent-session approach was reverted in favor of a simpler
future implementation.

Key learnings:
- Hooks cannot run Agent SDK queries (no claudePath access)
- All AI processing must happen in worker service
- Dynamic timeline search is the right approach
- Infrastructure (SQLite FTS5 + Chroma) validated and ready
- Simple per-request approach should be tried before optimization

The vision is sound, execution was overcomplicated. This was productive
R&D that validated constraints and documented patterns for future work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 23:31:03 -05:00
Alex Newman a367436a78 Release v5.4.0: Skill-Based Search Migration & Progressive Disclosure
Version Bump:
- package.json: 5.3.0 → 5.4.0
- .claude-plugin/marketplace.json: 5.3.0 → 5.4.0
- plugin/.claude-plugin/plugin.json: 5.3.0 → 5.4.0
- CLAUDE.md: 5.3.0 → 5.4.0

Breaking Changes:
- MCP search tools removed (replaced with skill-based search)
- No user action required - migration is transparent

Major Features:
- Skill-based search architecture (~2,250 token savings per session)
- Progressive disclosure pattern (skill frontmatter ~250 tokens)
- 10 HTTP API endpoints for search operations
- Natural language queries replace MCP tool syntax

Technical Changes:
- Removed MCP search server registration
- Added search skill with comprehensive documentation
- Updated all documentation to reflect new architecture
- HTTP API endpoints in worker service

Migration Notes:
- Users: No action required, searches work identically
- Developers: MCP server source kept for reference

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 19:15:04 -05:00
Alex Newman 5d64df2ba5 docs: Update all documentation to reflect v5.4.0 skill-based search
Documentation Updates:
- README.md: Updated version badge, What's New, and search section
- docs/usage/search-tools.mdx: Rewrote for skill-based natural language approach
- docs/architecture/mcp-search.mdx → search-architecture.mdx: Complete rewrite
- docs/architecture/overview.mdx: Updated components and search pipeline
- docs/usage/getting-started.mdx: Added skill-based search section
- docs/configuration.mdx: Updated search configuration for v5.4.0
- docs/introduction.mdx: Updated key features
- docs/docs.json: Updated navigation to search-architecture

Key Changes:
- Emphasized ~2,250 token savings per session start
- Converted all examples to natural language queries
- Documented 10 HTTP API endpoints
- Explained progressive disclosure pattern
- Added migration notes (transparent, no user action required)
- Removed outdated MCP references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 19:14:05 -05:00
Alex Newman 22a04ac461 Merge pull request #78 from thedotmack/feature/skill-search
v5.4.0: Skill-Based Search Migration & Progressive Disclosure Pattern
2025-11-09 18:58:22 -05:00
Alex Newman 170b66f623 feat: Add documentation for project-level skills and their distinction from plugin skills 2025-11-09 18:55:09 -05:00
Alex Newman ca4f046777 feat: Add search skill with progressive disclosure and refactor existing skills
Enhancements:
- Added search skill with 10 HTTP API endpoints for memory queries
- Refactored version-bump and troubleshoot skills using progressive disclosure pattern
- Added operations/ subdirectories for detailed skill documentation
- Updated CLAUDE.md with skill-based search architecture
- Enhanced worker service with search API endpoints
- Updated CHANGELOG.md with v5.4.0 migration details

Technical changes:
- New plugin/skills/search/ directory with SKILL.md
- New .claude/skills/version-bump/operations/ (workflow.md, scenarios.md)
- New plugin/skills/troubleshoot/operations/ (common-issues.md, worker.md)
- Modified src/services/worker-service.ts (added search endpoints)
- Modified plugin/scripts/worker-service.cjs (rebuilt with search API)
- Reduced main skill files by 89% using progressive disclosure
- Token savings: ~2,250 tokens per session start

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:41:53 -05:00
Alex Newman 0475a57fb1 feat: Migrate to Skill-Based Search and enhance context handling
- Implemented a new skill-based search approach, replacing MCP search tools to reduce session start context from ~2,500 tokens to ~250 tokens.
- Added 10 new HTTP API endpoints to the worker service for various search functionalities.
- Created a new search skill with detailed instructions and examples for users.
- Removed MCP search server references and updated documentation to reflect the new skill-based approach.
- Enhanced context handling in the context hook to conditionally display the most recent summary based on observations.
- Introduced comprehensive documentation for Agent Skills, including creation, management, and best practices.
2025-11-09 17:34:00 -05:00
120 changed files with 15100 additions and 2314 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "5.3.0",
"version": "5.5.1",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+29
View File
@@ -0,0 +1,29 @@
# Project-Level Skills
This directory contains skills **for developing and maintaining the claude-mem project itself**, not skills that are released as part of the plugin.
## Distinction
**Project Skills** (`.claude/skills/`):
- Used by developers working on claude-mem
- Not included in the plugin distribution
- Project-specific workflows (version bumps, release management, etc.)
- Not synced to `~/.claude/plugins/marketplaces/thedotmack/`
**Plugin Skills** (`plugin/skills/`):
- Released as part of the claude-mem plugin
- Available to all users who install the plugin
- General-purpose memory search functionality
- Synced to user installations via `npm run sync-marketplace`
## Skills in This Directory
### version-bump
Manages semantic versioning for the claude-mem project itself. Handles updating all four version files (package.json, marketplace.json, plugin.json, CLAUDE.md), creating git tags, and GitHub releases.
**Usage**: Only for claude-mem maintainers releasing new versions.
## Adding New Skills
**For claude-mem development** → Add to `.claude/skills/`
**For end users** → Add to `plugin/skills/` (gets distributed with plugin)
+56 -170
View File
@@ -1,212 +1,98 @@
---
name: version-bump
description: Manage semantic version updates for claude-mem project. Handles patch, minor, and major version increments following semantic versioning. Updates package.json, marketplace.json, plugin.json, and CLAUDE.md version number (NOT version history). Creates git tags.
description: Manage semantic version updates for claude-mem project. Handles patch, minor, and major version increments following semantic versioning. Updates package.json, marketplace.json, plugin.json, and CLAUDE.md version number (NOT version history). Creates git tags and GitHub releases. Auto-generates CHANGELOG.md from releases.
---
# Version Bump Skill
IMPORTANT: This skill manages semantic versioning across the claude-mem project. YOU MUST update all FOUR version-tracked files consistently and create a git tag.
Manage semantic versioning across the claude-mem project with consistent updates to all version-tracked files.
## Quick Reference
**Files requiring updates:**
**Files requiring updates (ALL FOUR):**
1. `package.json` (line 3)
2. `.claude-plugin/marketplace.json` (line 13)
3. `plugin/.claude-plugin/plugin.json` (line 3)
4. `CLAUDE.md` (line 9 ONLY - version number, NOT version history)
**Semantic versioning:**
- PATCH (x.y.Z): Bugfixes only
- MINOR (x.Y.0): New features, backward compatible
- MAJOR (X.0.0): Breaking changes
- **PATCH** (x.y.Z): Bugfixes only
- **MINOR** (x.Y.0): New features, backward compatible
- **MAJOR** (X.0.0): Breaking changes
## Workflow
## Quick Decision Guide
When invoked, follow this process:
**What changed?**
- "Fixed a bug" → PATCH (5.3.0 → 5.3.1)
- "Added new feature" → MINOR (5.3.0 → 5.4.0)
- "Breaking change" → MAJOR (5.3.0 → 6.0.0)
### 1. Analyze Changes
First, understand what changed:
```bash
git log --oneline -5
git diff HEAD~1
```
**If unclear, ASK THE USER explicitly.**
### 2. Determine Version Type
Ask yourself:
- Breaking changes? → MAJOR
- New features? → MINOR
- Bugfixes only? → PATCH
## Standard Workflow
If unclear, ASK THE USER explicitly.
See [operations/workflow.md](operations/workflow.md) for detailed step-by-step process.
### 3. Calculate New Version
From current version in `package.json`:
```bash
grep '"version"' package.json
```
Apply semantic versioning rules:
- Patch: increment Z (4.2.8 → 4.2.9)
- Minor: increment Y, reset Z (4.2.8 → 4.3.0)
- Major: increment X, reset Y and Z (4.2.8 → 5.0.0)
### 4. Preview Changes
BEFORE making changes, show the user:
```
Current version: 4.2.8
New version: 4.2.9 (PATCH)
Reason: Fixed database query bug
Files to update:
- package.json: "version": "4.2.9"
- marketplace.json: "version": "4.2.9"
- plugin.json: "version": "4.2.9"
- CLAUDE.md line 9: "**Current Version**: 4.2.9" (version number ONLY)
- Git tag: v4.2.9
Proceed? (yes/no)
```
### 5. Update Files
**Update package.json:**
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
**Update .claude-plugin/marketplace.json:**
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
**Update plugin/.claude-plugin/plugin.json:**
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
**Update CLAUDE.md:**
ONLY update line 9 with the version number:
```markdown
**Current Version**: 4.2.9
```
**CRITICAL**: DO NOT add version history entries to CLAUDE.md. Version history is managed separately outside this skill.
### 6. Verify Consistency
```bash
# Check all versions match
grep -n '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
# Should show same version in all three files
```
### 7. Test
```bash
# Verify the plugin loads correctly
npm run build
```
### 8. Commit and Tag
```bash
# Stage all version files
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
# Commit with descriptive message
git commit -m "Release vX.Y.Z: [Brief description]"
# Create annotated git tag
git tag vX.Y.Z -m "Release vX.Y.Z: [Brief description]"
# Push commit and tags
git push && git push --tags
```
### 9. Create GitHub Release
```bash
# Create GitHub release from the tag
gh release create vX.Y.Z --title "vX.Y.Z" --notes "[Brief release notes]"
# Or generate notes automatically from commits
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
```
**IMPORTANT**: Always create the GitHub release immediately after pushing the tag. This makes the release discoverable to users and triggers any automated workflows.
**Quick version:**
1. Determine version type (PATCH/MINOR/MAJOR)
2. Calculate new version from current
3. Preview changes to user
4. Update ALL FOUR files
5. Verify consistency
6. Build and test
7. Commit and create git tag
8. Push and create GitHub release
9. Generate CHANGELOG.md from releases and commit
## Common Scenarios
**Scenario 1: Bug fix after testing**
```
User: "Fixed the memory leak in the search function"
You: Determine → PATCH
Calculate → 4.2.8 → 4.2.9
Update all four files (version numbers only)
Build and commit
Create git tag v4.2.9
Push commit and tags
Create GitHub release v4.2.9
```
See [operations/scenarios.md](operations/scenarios.md) for examples:
- Bug fix releases
- New feature releases
- Breaking change releases
**Scenario 2: New MCP tool added**
```
User: "Added web search MCP integration"
You: Determine → MINOR (new feature)
Calculate → 4.2.8 → 4.3.0
Update all four files (version numbers only)
Build and commit
Create git tag v4.3.0
Push commit and tags
Create GitHub release v4.3.0
```
## Critical Rules
**Scenario 3: Database schema redesign**
```
User: "Rewrote storage layer, old data needs migration"
You: Determine → MAJOR (breaking change)
Calculate → 4.2.8 → 5.0.0
Update all four files (version numbers only)
Build and commit
Create git tag v5.0.0
Push commit and tags
Create GitHub release v5.0.0
```
## Error Prevention
**ALWAYS verify:**
- [ ] All FOUR files have matching version numbers (package.json, marketplace.json, plugin.json, CLAUDE.md)
- [ ] Git tag created with format vX.Y.Z
- [ ] GitHub release created from the tag
- [ ] CLAUDE.md: ONLY updated line 9 (version number), did NOT touch version history
- [ ] Commit and tags pushed to remote
**ALWAYS:**
- Update ALL FOUR files with matching version numbers
- Create git tag with format `vX.Y.Z`
- Create GitHub release from the tag
- Generate CHANGELOG.md from releases after creating release
- Ask user if version type is unclear
**NEVER:**
- Update only one, two, or three files - ALL FOUR must be updated
- Update only one, two, or three files
- Skip the verification step
- Forget to create git tag
- Forget to create GitHub release
- Forget to ask user if version type is unclear
- Forget to create git tag or GitHub release
- Add version history entries to CLAUDE.md (that's managed separately)
## Verification Checklist
Before considering the task complete:
- [ ] All FOUR files have matching version numbers
- [ ] `npm run build` succeeds
- [ ] Git commit created with all version files
- [ ] Git tag created (format: vX.Y.Z)
- [ ] Commit and tags pushed to remote
- [ ] GitHub release created from the tag
- [ ] CHANGELOG.md generated and committed
- [ ] CLAUDE.md: ONLY line 9 updated (version number), NOT version history
## Reference Commands
```bash
# View current version
cat package.json | grep version
grep '"version"' package.json
# Verify consistency across all version files
grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
# View git tags
git tag -l -n1
# Check what will be committed
git status
git diff package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md
```
For more commands, see [operations/reference.md](operations/reference.md).
@@ -0,0 +1,275 @@
# Version Bump Reference
Quick reference for version bump commands and file locations.
## File Locations
### Version-Tracked Files (ALL FOUR)
1. **package.json**
- Path: `package.json`
- Line: 3
- Format: `"version": "X.Y.Z",`
2. **marketplace.json**
- Path: `.claude-plugin/marketplace.json`
- Line: 13
- Format: `"version": "X.Y.Z",`
3. **plugin.json**
- Path: `plugin/.claude-plugin/plugin.json`
- Line: 3
- Format: `"version": "X.Y.Z",`
4. **CLAUDE.md**
- Path: `CLAUDE.md`
- Line: 9
- Format: `**Current Version**: X.Y.Z`
## Essential Commands
### View Current Version
```bash
# From package.json
grep '"version"' package.json
# Extract just the version number
grep '"version"' package.json | head -1 | sed 's/.*"version": "\(.*\)".*/\1/'
# From all version files
grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
grep "Current Version" CLAUDE.md
```
### Verify Version Consistency
```bash
# Check all JSON files match
grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
# Should output identical version in all three:
# package.json:3: "version": "5.3.0",
# .claude-plugin/marketplace.json:13: "version": "5.3.0",
# plugin/.claude-plugin/plugin.json:3: "version": "5.3.0",
# Check CLAUDE.md
grep "Current Version" CLAUDE.md
# Should output: **Current Version**: 5.3.0
```
### Git Commands
```bash
# View recent commits
git log --oneline -5
# View changes since last tag
LAST_TAG=$(git describe --tags --abbrev=0)
git log $LAST_TAG..HEAD --oneline
git diff $LAST_TAG..HEAD
# List all tags
git tag -l
# View tag details
git show vX.Y.Z
# List tags with messages
git tag -l -n1
```
### Build and Test
```bash
# Build plugin
npm run build
# Sync to marketplace
npm run sync-marketplace
# Run tests (if available)
npm test
```
### Commit and Tag
```bash
# Stage version files
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
# Commit
git commit -m "Release vX.Y.Z: [Description]"
# Create tag
git tag vX.Y.Z -m "Release vX.Y.Z: [Description]"
# Push
git push && git push --tags
```
### GitHub Release
```bash
# Create release
gh release create vX.Y.Z --title "vX.Y.Z" --notes "[Release notes]"
# Create with auto-generated notes
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
# View release
gh release view vX.Y.Z
# List all releases
gh release list
# Delete release (if needed)
gh release delete vX.Y.Z
```
## Semantic Versioning Rules
### Version Format: MAJOR.MINOR.PATCH
**MAJOR (X.0.0):**
- Breaking changes
- Incompatible API changes
- Schema changes requiring migration
- Removes features
**MINOR (x.Y.0):**
- New features (backward compatible)
- New functionality
- Deprecations (but not removals)
- Resets PATCH to 0
**PATCH (x.y.Z):**
- Bug fixes
- Performance improvements
- Documentation fixes
- No new features
### Incrementing Rules
```
PATCH: 5.3.2 → 5.3.3
MINOR: 5.3.2 → 5.4.0 (resets patch)
MAJOR: 5.3.2 → 6.0.0 (resets minor and patch)
```
## Common Patterns
### Bug Fix Release
```bash
# Example: 5.3.0 → 5.3.1
# 1. Update all four files to 5.3.1
# 2. Build and test
npm run build
# 3. Commit and tag
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
git commit -m "Release v5.3.1: Fixed observer crash"
git tag v5.3.1 -m "Release v5.3.1: Fixed observer crash"
git push && git push --tags
# 4. Create release
gh release create v5.3.1 --title "v5.3.1" --notes "Fixed observer crash on empty content"
```
### Feature Release
```bash
# Example: 5.3.0 → 5.4.0
# 1. Update all four files to 5.4.0
# 2. Build and test
npm run build
# 3. Commit and tag
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
git commit -m "Release v5.4.0: Added dark mode support"
git tag v5.4.0 -m "Release v5.4.0: Added dark mode support"
git push && git push --tags
# 4. Create release
gh release create v5.4.0 --title "v5.4.0" --generate-notes
```
### Breaking Change Release
```bash
# Example: 5.3.0 → 6.0.0
# 1. Update all four files to 6.0.0
# 2. Build and test
npm run build
# 3. Commit and tag
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
git commit -m "Release v6.0.0: Storage layer redesign"
git tag v6.0.0 -m "Release v6.0.0: Storage layer redesign"
git push && git push --tags
# 4. Create release with warning
gh release create v6.0.0 --title "v6.0.0" --notes "⚠️ Breaking change: Storage layer redesigned. Migration required."
```
## Rollback Commands
### Delete Tag
```bash
# Delete local tag
git tag -d vX.Y.Z
# Delete remote tag
git push origin :refs/tags/vX.Y.Z
# Or: git push --delete origin vX.Y.Z
```
### Delete Release
```bash
# Delete GitHub release
gh release delete vX.Y.Z
# Confirm deletion
gh release delete vX.Y.Z --yes
```
### Revert Commit
```bash
# Revert last commit (creates new commit)
git revert HEAD
# Reset to previous commit (destructive)
git reset --hard HEAD~1
git push --force # Dangerous! Only if not shared
```
## Error Prevention
### Pre-commit Checks
```bash
# Check all versions match before committing
V1=$(grep '"version"' package.json | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
V2=$(grep '"version"' .claude-plugin/marketplace.json | sed 's/.*"\([^"]*\)".*/\1/')
V3=$(grep '"version"' plugin/.claude-plugin/plugin.json | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
if [ "$V1" = "$V2" ] && [ "$V2" = "$V3" ]; then
echo "✓ All versions match: $V1"
else
echo "✗ Version mismatch!"
echo " package.json: $V1"
echo " marketplace.json: $V2"
echo " plugin.json: $V3"
fi
```
### Pre-push Checks
```bash
# Check tag exists
git tag -l | grep vX.Y.Z || echo "Warning: Tag not created"
# Check build succeeds
npm run build || echo "Error: Build failed"
# Check no uncommitted changes
git status --porcelain | grep -q . && echo "Warning: Uncommitted changes"
```
@@ -0,0 +1,218 @@
# Common Version Bump Scenarios
Real-world examples of version bumps with decision rationale.
## Scenario 1: Bug Fix After Testing
**User request:**
> "Fixed the memory leak in the search function"
**Analysis:**
- What changed: Bug fix
- Breaking changes: No
- New features: No
- **Decision: PATCH**
**Workflow:**
```
Current: 4.2.8
New: 4.2.9 (PATCH)
Steps:
1. Update all four files to 4.2.9
2. npm run build
3. git commit -m "Release v4.2.9: Fixed memory leak in search"
4. git tag v4.2.9 -m "Release v4.2.9: Fixed memory leak in search"
5. git push && git push --tags
6. gh release create v4.2.9 --title "v4.2.9" --notes "Fixed memory leak in search function"
```
## Scenario 2: New Feature Added
**User request:**
> "Added web search MCP integration"
**Analysis:**
- What changed: New feature (MCP integration)
- Breaking changes: No
- Backward compatible: Yes
- **Decision: MINOR**
**Workflow:**
```
Current: 4.2.8
New: 4.3.0 (MINOR - reset patch to 0)
Steps:
1. Update all four files to 4.3.0
2. npm run build
3. git commit -m "Release v4.3.0: Added web search MCP integration"
4. git tag v4.3.0 -m "Release v4.3.0: Added web search MCP integration"
5. git push && git push --tags
6. gh release create v4.3.0 --title "v4.3.0" --generate-notes
```
## Scenario 3: Database Schema Redesign
**User request:**
> "Rewrote storage layer, old data needs migration"
**Analysis:**
- What changed: Storage layer rewrite
- Breaking changes: Yes (requires migration)
- Backward compatible: No
- **Decision: MAJOR**
**Workflow:**
```
Current: 4.2.8
New: 5.0.0 (MAJOR - reset minor and patch to 0)
Steps:
1. Update all four files to 5.0.0
2. npm run build
3. git commit -m "Release v5.0.0: Storage layer redesign with migration required"
4. git tag v5.0.0 -m "Release v5.0.0: Storage layer redesign"
5. git push && git push --tags
6. gh release create v5.0.0 --title "v5.0.0" --notes "⚠️ Breaking change: Storage layer redesigned. Migration required."
```
## Scenario 4: Multiple Small Bug Fixes
**User request:**
> "Fixed three bugs: observer crash, viewer pagination, and date formatting"
**Analysis:**
- What changed: Multiple bug fixes
- Breaking changes: No
- New features: No
- **Decision: PATCH** (one patch covers all fixes)
**Workflow:**
```
Current: 4.2.8
New: 4.2.9 (PATCH)
Steps:
1. Update all four files to 4.2.9
2. npm run build
3. git commit -m "Release v4.2.9: Multiple bug fixes
- Fixed observer crash on empty content
- Fixed viewer pagination edge case
- Fixed date formatting in timeline"
4. git tag v4.2.9 -m "Release v4.2.9: Multiple bug fixes"
5. git push && git push --tags
6. gh release create v4.2.9 --title "v4.2.9" --generate-notes
```
## Scenario 5: Feature + Bug Fix
**User request:**
> "Added dark mode support and fixed the viewer crash bug"
**Analysis:**
- What changed: New feature + bug fix
- Breaking changes: No
- **Decision: MINOR** (feature trumps bug fix)
**Workflow:**
```
Current: 5.1.0
New: 5.2.0 (MINOR)
Steps:
1. Update all four files to 5.2.0
2. npm run build
3. git commit -m "Release v5.2.0: Dark mode support + bug fixes
Features:
- Added dark mode toggle to viewer UI
Bug fixes:
- Fixed viewer crash on empty database"
4. git tag v5.2.0 -m "Release v5.2.0: Dark mode support"
5. git push && git push --tags
6. gh release create v5.2.0 --title "v5.2.0" --generate-notes
```
## Scenario 6: Documentation Only
**User request:**
> "Updated README with new installation instructions"
**Analysis:**
- What changed: Documentation only
- Breaking changes: No
- Code changes: No
- **Decision: PATCH** (or skip version bump if no code changes)
**Workflow:**
```
Option 1: PATCH (if you want to tag doc improvements)
Current: 4.2.8
New: 4.2.9
Option 2: No version bump (documentation-only changes don't require versioning)
Just commit without bumping version
```
**Recommendation:** Skip version bump for documentation-only changes unless it's a significant documentation overhaul.
## Scenario 7: Configuration Change
**User request:**
> "Changed default observation count from 50 to 30"
**Analysis:**
- What changed: Default configuration
- Breaking changes: Yes (behavior changes)
- Users might notice different context size
- **Decision: MINOR or MAJOR** (ask user)
**Workflow:**
```
Ask user:
"This changes default behavior (context size). Users will see different results.
Is this:
- MINOR (acceptable behavior change): 4.2.8 → 4.3.0
- MAJOR (breaking change): 4.2.8 → 5.0.0
Which should I use?"
```
## Scenario 8: Dependency Update
**User request:**
> "Updated Claude SDK from 1.2.0 to 1.3.0"
**Analysis:**
- What changed: Dependency version
- Breaking changes: Depends on SDK changes
- **Decision: Ask user or check SDK changelog**
**Workflow:**
```
1. Check SDK changelog for breaking changes
2. If SDK has breaking changes → MAJOR
3. If SDK adds features → MINOR
4. If SDK only fixes bugs → PATCH
Typical: PATCH (unless SDK breaks compatibility)
```
## Decision Tree
```
Is there a breaking change?
├─ Yes → MAJOR (X.0.0)
└─ No
├─ Is there a new feature?
│ ├─ Yes → MINOR (x.Y.0)
│ └─ No
│ ├─ Is there a bug fix?
│ │ ├─ Yes → PATCH (x.y.Z)
│ │ └─ No → Don't bump version (docs only, etc.)
│ └─ Configuration change? → Ask user (MINOR or MAJOR)
└─ Multiple changes? → Use highest level (MAJOR > MINOR > PATCH)
```
@@ -0,0 +1,251 @@
# Detailed Version Bump Workflow
Step-by-step process for bumping versions in the claude-mem project.
## Step 1: Analyze Changes
First, understand what changed:
```bash
# View recent commits
git log --oneline -5
# See what changed in last commit
git diff HEAD~1
# Or see all changes since last tag
LAST_TAG=$(git describe --tags --abbrev=0)
git log $LAST_TAG..HEAD --oneline
git diff $LAST_TAG..HEAD
```
## Step 2: Determine Version Type
Ask yourself:
- **Breaking changes?** → MAJOR
- **New features?** → MINOR
- **Bugfixes only?** → PATCH
**If unclear, ASK THE USER explicitly.**
### Decision Matrix
| Change Type | Version Bump | Example |
|------------|--------------|---------|
| Bug fix | PATCH | 4.2.8 → 4.2.9 |
| New feature (backward compatible) | MINOR | 4.2.8 → 4.3.0 |
| Breaking change | MAJOR | 4.2.8 → 5.0.0 |
| Multiple features | MINOR | 4.2.8 → 4.3.0 |
| Feature + breaking change | MAJOR | 4.2.8 → 5.0.0 |
## Step 3: Calculate New Version
From current version in `package.json`:
```bash
grep '"version"' package.json
```
Apply semantic versioning rules:
- **Patch:** increment Z (4.2.8 → 4.2.9)
- **Minor:** increment Y, reset Z (4.2.8 → 4.3.0)
- **Major:** increment X, reset Y and Z (4.2.8 → 5.0.0)
## Step 4: Preview Changes
**BEFORE making changes, show the user:**
```
Current version: 4.2.8
New version: 4.2.9 (PATCH)
Reason: Fixed database query bug
Files to update:
- package.json: "version": "4.2.9"
- marketplace.json: "version": "4.2.9"
- plugin.json: "version": "4.2.9"
- CLAUDE.md line 9: "**Current Version**: 4.2.9" (version number ONLY)
- Git tag: v4.2.9
Proceed? (yes/no)
```
Wait for user confirmation before proceeding.
## Step 5: Update Files
### Update package.json
File: `package.json`
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
Update line 3 with new version.
### Update marketplace.json
File: `.claude-plugin/marketplace.json`
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
Update line 13 with new version.
### Update plugin.json
File: `plugin/.claude-plugin/plugin.json`
```json
{
"name": "claude-mem",
"version": "4.2.9",
...
}
```
Update line 3 with new version.
### Update CLAUDE.md
File: `CLAUDE.md`
**ONLY update line 9 with the version number:**
```markdown
**Current Version**: 4.2.9
```
**CRITICAL:** DO NOT add version history entries to CLAUDE.md. Version history is managed separately outside this skill.
## Step 6: Verify Consistency
```bash
# Check all versions match
grep -n '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
# Should show same version in all three files:
# package.json:3: "version": "4.2.9",
# .claude-plugin/marketplace.json:13: "version": "4.2.9",
# plugin/.claude-plugin/plugin.json:3: "version": "4.2.9",
```
All three must match exactly.
## Step 7: Test
```bash
# Verify the plugin loads correctly
npm run build
```
Build must succeed before proceeding.
## Step 8: Commit and Tag
```bash
# Stage all version files
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
# Commit with descriptive message
git commit -m "Release vX.Y.Z: [Brief description]
[Optional detailed description]
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>"
# Create annotated git tag
git tag vX.Y.Z -m "Release vX.Y.Z: [Brief description]"
# Push commit and tags
git push && git push --tags
```
Replace `X.Y.Z` with actual version (e.g., `4.2.9`).
## Step 9: Create GitHub Release
```bash
# Create GitHub release from the tag
gh release create vX.Y.Z --title "vX.Y.Z" --notes "[Brief release notes]"
# Or generate notes automatically from commits
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
```
**IMPORTANT:** Always create the GitHub release immediately after pushing the tag. This makes the release discoverable to users and triggers any automated workflows.
## Step 10: Generate CHANGELOG
After creating the GitHub release, regenerate CHANGELOG.md from all releases:
```bash
# Generate CHANGELOG.md from all GitHub releases
npm run changelog:generate
# Review the generated changelog
git diff CHANGELOG.md
# Commit and push the updated changelog
git add CHANGELOG.md
git commit -m "Update CHANGELOG.md for vX.Y.Z release"
git push
```
**Why this step:**
- CHANGELOG.md is auto-generated from GitHub releases
- Keeps the changelog in sync with release notes
- No manual editing required
- Single source of truth: GitHub releases
## Verification
After completing all steps, verify:
```bash
# Check git tag created
git tag -l | grep vX.Y.Z
# Check remote has tag
git ls-remote --tags origin | grep vX.Y.Z
# Check GitHub release exists
gh release view vX.Y.Z
# Verify versions match
grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
```
All checks should pass.
## Rollback (If Needed)
If you made a mistake:
```bash
# Delete local tag
git tag -d vX.Y.Z
# Delete remote tag (if already pushed)
git push origin :refs/tags/vX.Y.Z
# Delete GitHub release (if created)
gh release delete vX.Y.Z
# Revert commits if needed
git revert HEAD
```
Then restart the workflow with correct version.
+1356 -343
View File
File diff suppressed because it is too large Load Diff
+40 -13
View File
@@ -6,7 +6,7 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
**Your Role**: You are working on the plugin itself. When users interact with Claude Code with this plugin installed, your observations get captured and become their persistent memory.
**Current Version**: 5.3.0
**Current Version**: 5.5.1
## Critical Architecture Knowledge
@@ -50,10 +50,11 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
- FTS5 virtual tables for full-text search
- `SessionStore` = CRUD, `SessionSearch` = FTS5 queries
**MCP Search Server** (`src/servers/search-server.ts`)
- Exposes 9 search tools to Claude Code
- Configured in `plugin/.mcp.json`
- Built to `plugin/search-server.mjs` (ESM format)
**Search Skill** (`plugin/skills/search/SKILL.md`)
- Provides access to all search functionality via HTTP API + skill
- Auto-invoked when users ask about past work, decisions, or history
- Uses HTTP endpoints instead of MCP tools (~2,250 token savings per session)
- 10 search operations: observations, sessions, prompts, by-type, by-file, by-concept, timelines, etc.
**Chroma Vector Database** (`src/services/sync/ChromaSync.ts`)
- Hybrid semantic + keyword search architecture
@@ -86,12 +87,11 @@ npm run worker:restart
```
Must restart PM2 worker for changes to take effect.
### When You Modify MCP Server
### When You Modify Search Skill
```bash
npm run build
npm run sync-marketplace
# Restart Claude Code for MCP changes
```
Skill changes take effect immediately on next Claude Code session. No build or restart needed (skills are markdown).
### When You Modify Viewer UI
```bash
@@ -104,7 +104,7 @@ Changes to React components, styles, or viewer logic require rebuilding and rest
### Build Pipeline
1. `npm run build` → Compiles TypeScript, outputs to `plugin/`
2. `npm run sync-marketplace` → Syncs to `~/.claude/plugins/marketplaces/thedotmack/`
3. Changes are live for next session (hooks/MCP) or after restart (worker)
3. Changes are live for next session (hooks/skills) or after restart (worker)
## Coding Standards: DRY, YAGNI, and Anti-Patterns
@@ -263,7 +263,7 @@ pm2 delete claude-mem-worker # Force clean start
2. `npm run build && npm run sync-marketplace`
3. Start new Claude Code session (hooks) or restart worker (worker changes)
4. Check `~/.claude-mem/claude-mem.db` for database state
5. Use MCP search tools to verify behavior
5. Use search skill to verify behavior (auto-invoked when asking about past work)
### Version Bumps
Use the version-bump skill:
@@ -291,7 +291,7 @@ Choose patch/minor/major. Updates package.json, marketplace.json, plugin.json, a
```
Deploy a general-purpose Task agent to:
1. Read src/hooks/context-hook.ts in full
2. Read src/servers/search-server.ts in full
2. Read src/services/worker-service.ts in full
3. Answer: How do these files work together? What's the current implementation state?
4. Find any bugs or inconsistencies between them
```
@@ -304,6 +304,33 @@ Use this when:
## Recent Changes
### v5.4.0 - Skill-Based Search Migration
**Breaking Change**: MCP search tools replaced with skill-based approach
- **Token Savings**: ~2,250 tokens per session start
- **Progressive Disclosure**: Skill frontmatter (~250 tokens) instead of 9 MCP tool definitions (~2,500 tokens)
- **New HTTP API**: 10 search endpoints in worker service (localhost:37777/api/search/*)
- **Search Skill**: Auto-invoked when users ask about past work, decisions, or history
- **No User Action Required**: Migration is transparent, searches work automatically
- **Deprecated**: MCP search server (source kept for reference: src/servers/search-server.ts)
**Available Search Operations:**
1. Search observations (full-text)
2. Search session summaries (full-text)
3. Search user prompts (full-text)
4. Search by observation type (bugfix, feature, refactor, discovery, decision)
5. Search by concept tag
6. Search by file path
7. Get recent context for a project
8. Get timeline around specific point in time
9. Get timeline by query (search + timeline in one call)
10. Get API help documentation
**How It Works:**
- User asks: "What bug did we fix last session?"
- Claude sees skill description matches → invokes search skill
- Skill loads full instructions → uses curl to call HTTP API → formats results
- User sees formatted answer with past work context
### v5.1.2 - Theme Toggle
**Theme Support**: Light/dark mode for viewer UI
- User-selectable theme with persistent settings
@@ -373,9 +400,9 @@ Use this when:
- Hybrid semantic + keyword search combining ChromaDB with SQLite FTS5
- ChromaSync service for automatic vector embedding synchronization (738 lines)
- 90-day recency filtering for contextually relevant results
- New MCP tools: `get_context_timeline` and `get_timeline_by_query`
- Timeline and context search capabilities (now provided via skill-based HTTP API)
- Performance: Semantic search <200ms with 8,000+ vector documents
- Enhanced all 9 MCP search tools with hybrid search capabilities
- Full-text search across observations, sessions, and prompts
## Configuration Users Can Set
+126
View File
@@ -0,0 +1,126 @@
# PR Summary: Fix SDK Agent Missing Working Directory Context (CWD)
## Problem
The SDK agent lacked spatial awareness because working directory (CWD) information was captured at the hook level but deliberately not passed to the worker service. This caused:
- SDK agent searching wrong repositories
- False "file not found" reports even when files existed
- Inability to match user-requested paths to tool execution paths
- Inaccurate observations due to spatial confusion
## Solution
Added CWD propagation through the entire data pipeline from hook to SDK agent, enabling spatial awareness.
## Technical Changes
### Data Flow
```
PostToolUseInput.cwd → save-hook → Worker API → SessionManager → SDK Agent → Prompt XML
```
### Files Modified (8 source + 2 build artifacts + 2 docs)
1. `src/services/worker-types.ts` - Added `cwd?: string` to interfaces
2. `src/hooks/save-hook.ts` - Extract and pass CWD to worker
3. `src/services/worker-service.ts` - Accept CWD in observations endpoint
4. `src/services/worker/SessionManager.ts` - Include CWD in message queue
5. `src/services/worker/SDKAgent.ts` - Pass CWD to prompt builder
6. `src/sdk/prompts.ts` - Include `<tool_cwd>` in XML + spatial awareness docs
7. `tests/cwd-propagation.test.ts` - 8 comprehensive tests (NEW)
8. `docs/CWD_CONTEXT_FIX.md` - Technical documentation (NEW)
9. `CHANGELOG.md` - User-facing changelog entry
### Example Output
Before (no spatial awareness):
```xml
<tool_used>
<tool_name>ReadTool</tool_name>
<tool_time>2025-11-10T19:18:03.065Z</tool_time>
<tool_input>{"path":"src/index.ts"}</tool_input>
<tool_output>{"content":"..."}</tool_output>
</tool_used>
```
After (with spatial awareness):
```xml
<tool_used>
<tool_name>ReadTool</tool_name>
<tool_time>2025-11-10T19:18:03.065Z</tool_time>
<tool_cwd>/home/user/awesome-project</tool_cwd>
<tool_input>{"path":"src/index.ts"}</tool_input>
<tool_output>{"content":"..."}</tool_output>
</tool_used>
```
### Init Prompt Enhancement
Added "SPATIAL AWARENESS" section explaining:
- Tool executions include working directory (tool_cwd)
- Which repository/project is being worked on
- Where files are located relative to project root
- How to match requested paths to actual execution paths
## Testing
### Unit Tests
✅ 8 tests in `tests/cwd-propagation.test.ts` - all passing
- Interface definitions include cwd
- Hook extracts cwd from input
- Worker API accepts cwd
- SessionManager queues cwd
- SDK Agent passes cwd to prompts
- Prompt builder includes tool_cwd element
- End-to-end flow validation
### Build Verification
✅ All builds successful
- `plugin/scripts/save-hook.js` includes `cwd:s||""`
- `plugin/scripts/worker-service.cjs` includes `<tool_cwd>` element
- `plugin/scripts/worker-service.cjs` includes "SPATIAL AWARENESS" section
### Security Scan
✅ CodeQL: 0 vulnerabilities
## Benefits
1. **Spatial Awareness**: SDK agent knows which directory/repository it's observing
2. **Accurate Path Matching**: Can verify if requested paths match executed paths
3. **Better Observations**: Won't search wrong repositories or report false negatives
4. **Universal Model Support**: Works with Haiku, Sonnet, and Opus (no premium workaround needed)
## Backward Compatibility
-`cwd` is optional (`cwd?: string`) - no breaking changes
- ✅ Missing `cwd` handled gracefully (defaults to empty string)
- ✅ Existing observations without `cwd` continue to work
- ✅ No database migration required (CWD is transient, not persisted)
## Evidence from Issue
**Test Case**: User requested "Review and understand ai_docs/continuous-improvement/rules.md"
**Before Fix**:
1. File exists at `/Users/.../dev/personal/lunar-claude/ai_docs/...`
2. Read tool successfully read the file ✅
3. SDK agent received tool executions but **no CWD**
4. SDK agent searched **claude-mem repository** instead of lunar-claude ❌
5. Summary reported: "File does not exist" ❌
**After Fix**:
1. File exists at `/Users/.../dev/personal/lunar-claude/ai_docs/...`
2. Read tool successfully read the file ✅
3. SDK agent receives tool executions **with CWD**
4. SDK agent searches **correct repository (lunar-claude)**
5. Summary accurate: "Reviewed rules.md in lunar-claude project" ✅
## Validation Checklist
- [x] TypeScript compiles without errors
- [x] All tests pass (8/8)
- [x] Build artifacts include CWD propagation
- [x] No security vulnerabilities
- [x] Documentation complete
- [x] Backward compatible
- [x] Example prompts verified
- [x] CHANGELOG updated
## Ready for Merge
This PR is ready for review and merge. All validation steps passed successfully.
+44 -34
View File
@@ -17,7 +17,7 @@
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-5.1.2-green.svg" alt="Version">
<img src="https://img.shields.io/badge/version-5.4.0-green.svg" alt="Version">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg" alt="Node">
@@ -69,7 +69,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
- 🧠 **Persistent Memory** - Context survives across sessions
- 📊 **Progressive Disclosure** - Layered memory retrieval with token cost visibility
- 🔍 **9 Search Tools** - Query your project history via MCP
- 🔍 **Skill-Based Search** - Query your project history with natural language (~2,250 token savings)
- 🖥️ **Web Viewer UI** - Real-time memory stream at http://localhost:37777
- 🤖 **Automatic Operation** - No manual intervention required
- 🔗 **Citations** - Reference past decisions with `claude-mem://` URIs
@@ -91,7 +91,7 @@ npx mintlify dev
- **[Installation Guide](docs/installation.mdx)** - Quick start & advanced installation
- **[Usage Guide](docs/usage/getting-started.mdx)** - How Claude-Mem works automatically
- **[MCP Search Tools](docs/usage/search-tools.mdx)** - Query your project history
- **[Search Tools](docs/usage/search-tools.mdx)** - Query your project history with natural language
### Best Practices
@@ -144,73 +144,83 @@ npx mintlify dev
**Core Components:**
1. **7 Lifecycle Hook Scripts** - smart-install, context-hook, user-message-hook, new-hook, save-hook, summary-hook, cleanup-hook
2. **Worker Service** - HTTP API on port 37777 with web viewer UI, managed by PM2
2. **Worker Service** - HTTP API on port 37777 with web viewer UI and 10 search endpoints, managed by PM2
3. **SQLite Database** - Stores sessions, observations, summaries with FTS5 full-text search
4. **9 MCP Search Tools** - Query historical context with citations and timeline analysis
4. **Search Skill** - Natural language queries with progressive disclosure (~2,250 token savings vs MCP)
5. **Chroma Vector Database** - Hybrid semantic + keyword search for intelligent context retrieval
See [Architecture Overview](docs/architecture/overview.mdx) for details.
---
## MCP Search Tools
## Skill-Based Search
Claude-Mem provides 9 specialized search tools:
Claude-Mem provides intelligent search through a skill that auto-invokes when you ask about past work:
1. **search_observations** - Full-text search across observations
2. **search_sessions** - Full-text search across session summaries
3. **search_user_prompts** - Search raw user requests
4. **find_by_concept** - Find by concept tags (discovery, problem-solution, pattern, etc.)
5. **find_by_file** - Find observations referencing specific files
6. **find_by_type** - Find by type (decision, bugfix, feature, refactor, discovery, change)
7. **get_recent_context** - Get recent session context for a project
8. **get_context_timeline** - Get unified timeline of context around a specific point in time
9. **get_timeline_by_query** - Search for observations and get timeline context around best match
**How It Works:**
- Just ask naturally: *"What did we do last session?"* or *"Did we fix this bug before?"*
- Claude automatically invokes the search skill to find relevant context
- ~2,250 token savings per session start vs MCP approach
**Example Queries:**
**Available Search Operations:**
1. **Search Observations** - Full-text search across observations
2. **Search Sessions** - Full-text search across session summaries
3. **Search Prompts** - Search raw user requests
4. **By Concept** - Find by concept tags (discovery, problem-solution, pattern, etc.)
5. **By File** - Find observations referencing specific files
6. **By Type** - Find by type (decision, bugfix, feature, refactor, discovery, change)
7. **Recent Context** - Get recent session context for a project
8. **Timeline** - Get unified timeline of context around a specific point in time
9. **Timeline by Query** - Search for observations and get timeline context around best match
10. **API Help** - Get search API documentation
**Example Natural Language Queries:**
```
search_observations with query="authentication" and type="decision"
find_by_file with filePath="worker-service.ts"
search_user_prompts with query="add dark mode"
get_recent_context with limit=5
get_context_timeline with anchor="S890" depth_before=10 depth_after=10
get_timeline_by_query with query="viewer UI implementation" mode="auto"
"What bugs did we fix last session?"
"How did we implement authentication?"
"What changes were made to worker-service.ts?"
"Show me recent work on this project"
"What was happening when we added the viewer UI?"
```
See [MCP Search Tools Guide](docs/usage/search-tools.mdx) for detailed examples.
See [Search Tools Guide](docs/usage/search-tools.mdx) for detailed examples.
---
## What's New in v5.1.2
## What's New in v5.4.0
**🔍 Skill-Based Search Architecture (v5.4.0):**
- **Token Savings**: ~2,250 tokens per session start
- **Progressive Disclosure**: Skill frontmatter (~250 tokens) vs MCP tool definitions (~2,500 tokens)
- **Natural Language**: Just ask about past work - Claude auto-invokes the search skill
- **10 HTTP API Endpoints**: Fast, efficient search operations
- **No User Action Required**: Migration is transparent
**🎨 Theme Toggle (v5.1.2):**
- Light/dark mode support in viewer UI
- System preference detection
- Persistent theme settings across sessions
- Smooth transitions between themes
**🖥️ Web-Based Viewer UI (v5.1.0):**
- Real-time memory stream visualization at http://localhost:37777
- Server-Sent Events (SSE) for instant updates
- Infinite scroll pagination with automatic deduplication
- Project filtering to focus on specific codebases
- Settings persistence (sidebar state, selected project)
- Auto-reconnection with exponential backoff
- Infinite scroll pagination with project filtering
**⚡ Smart Install Caching (v5.0.3):**
- Eliminated redundant npm installs on every session (2-5s → 10ms)
- Caches version in `.install-version` file
- Only runs npm install when needed (first time, version change, missing deps)
- Eliminated redundant npm installs (2-5s → 10ms)
- Caches version state, only installs when needed
**🔍 Hybrid Search Architecture (v5.0.0):**
- Chroma vector database for semantic search
- Combined with FTS5 keyword search
- Intelligent context retrieval with 90-day recency filtering
- 90-day recency filtering
See [CHANGELOG.md](CHANGELOG.md) for complete version history.
+71
View File
@@ -0,0 +1,71 @@
# Security Summary - CWD Context Fix
## Security Scan Results
### CodeQL Analysis
- **Status**: ✅ PASSED
- **Vulnerabilities Found**: 0
- **Language**: JavaScript
- **Scan Date**: 2025-11-10
## Security Considerations
### 1. Input Validation
The `cwd` field is treated as untrusted user input:
- ✅ Optional field (`cwd?: string`) - missing values default to empty string
- ✅ No direct file system operations using CWD
- ✅ CWD is only used for context in prompts (read-only)
- ✅ No shell command injection risk (not passed to exec/spawn)
### 2. Data Flow Security
```
Hook Input → Worker API → SessionManager → SDK Agent → Prompt Text
```
- ✅ CWD passed through JSON serialization (escaped)
- ✅ No SQL injection risk (not stored in database)
- ✅ No XSS risk (used in backend prompts, not web UI)
- ✅ No path traversal risk (not used for file operations)
### 3. Prompt Injection Considerations
The CWD is included in XML prompts sent to the SDK agent:
```xml
<tool_cwd>/home/user/project</tool_cwd>
```
**Risk Assessment**: LOW
- CWD comes from Claude Code runtime (trusted source)
- Claude Code validates and sanitizes session context
- SDK agent operates in isolated subprocess
- No user-controlled prompt injection vector
### 4. Backward Compatibility
- ✅ Optional field - no breaking changes
- ✅ Graceful degradation when CWD missing
- ✅ No changes to existing security boundaries
- ✅ No new external dependencies
## Security Best Practices Applied
1. **Defense in Depth**: CWD is display-only context, not used for authorization
2. **Least Privilege**: No elevated permissions required
3. **Input Validation**: Type-safe interfaces with optional fields
4. **Safe Defaults**: Missing CWD defaults to empty string (safe)
5. **Immutability**: CWD is read-only once extracted from hook input
## Potential Future Considerations
While the current implementation is secure, future enhancements should consider:
1. **Path Sanitization**: If CWD is ever used for file operations, implement strict path validation
2. **Length Limits**: Consider max length for CWD field to prevent buffer issues
3. **Allowlist**: If needed, implement allowlist of permitted directories
4. **Audit Logging**: Log CWD values for security monitoring (if required)
## Conclusion
**No security vulnerabilities identified**
**Implementation follows security best practices**
**Ready for production deployment**
The CWD context fix introduces no new security risks and maintains the existing security posture of the claude-mem plugin.
-73
View File
@@ -1,73 +0,0 @@
# Claude-Mem Documentation Folder
## What This Folder Is
This `docs/` folder is a **Mintlify documentation site** - the official user-facing documentation for claude-mem. It's a structured documentation platform with a specific file format and organization.
## File Structure Requirements
### Mintlify Documentation Files (.mdx)
All official documentation files must be:
- Written in `.mdx` format (Markdown with JSX support)
- Listed in `docs.json` navigation structure
- Follow Mintlify's schema and conventions
The documentation is organized into these sections:
- **Get Started**: Introduction, installation, usage guides
- **Best Practices**: Context engineering, progressive disclosure
- **Configuration & Development**: Settings, dev workflow, troubleshooting
- **Architecture**: System design, components, technical details
### Configuration File
`docs.json` defines:
- Site metadata (name, description, theme)
- Navigation structure
- Branding (logos, colors)
- Footer links and social media
## What Does NOT Belong Here
**Planning documents, design docs, and reference materials should go in `/context/` instead:**
Files that should be in `/context/` (not `/docs/`):
- Planning documents (`*-plan.md`, `*-outline.md`)
- Implementation analysis (`*-audit.md`, `*-code-reference.md`)
- Error tracking (`typescript-errors.md`)
- Design documents not part of official docs
- PR review responses
- Reference materials (like `agent-sdk-ref.md`)
**Example**: The deleted `VIEWER.md` was moved because it was implementation documentation, not user-facing docs.
## Current Files That Should Be Moved
These `.md` files currently in `docs/` should probably be moved to `context/`:
- `typescript-errors.md` - Error tracking
- `worker-service-architecture.md` - Implementation details (not user-facing architecture)
- `processing-indicator-audit.md` - Implementation audit
- `processing-indicator-code-reference.md` - Code reference
- `worker-service-rewrite-outline.md` - Planning document
- `worker-service-overhead.md` - Analysis document
- `CHROMA.md` - Implementation reference (if not user-facing)
- `chroma-search-completion-plan.md` - Planning document
## How to Add Official Documentation
1. Create a new `.mdx` file in the appropriate subdirectory
2. Add the file path to `docs.json` navigation
3. Use Mintlify's frontmatter and components
4. Follow the existing documentation style
## Development Workflow
**For contributors working on claude-mem:**
- Read `/CLAUDE.md` in the project root for development instructions
- Place planning/design docs in `/context/`
- Only add user-facing documentation to `/docs/`
- Test documentation locally with Mintlify CLI if available
## Summary
**Simple Rule**:
- `/docs/` = Official user documentation (Mintlify .mdx files)
- `/context/` = Development context, plans, references, internal docs
-447
View File
@@ -1,447 +0,0 @@
---
title: "MCP Search Server"
description: "9 search tools with examples and usage patterns"
---
# MCP Search Server
Claude-Mem includes a Model Context Protocol (MCP) server that exposes 9 specialized search tools for querying stored observations and sessions.
## Overview
- **Location**: `src/servers/search-server.ts`
- **Built Output**: `plugin/scripts/search-server.mjs`
- **Configuration**: `plugin/.mcp.json`
- **Transport**: stdio
- **Tools**: 9 specialized search functions
- **Citations**: All results use `claude-mem://` URI scheme
## Configuration
The MCP server is automatically registered via `plugin/.mcp.json`:
```json
{
"mcpServers": {
"claude-mem-search": {
"type": "stdio",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.mjs"
}
}
}
```
This registers the `claude-mem-search` server with Claude Code, making the 9 search tools available in all sessions. The server is automatically started when Claude Code launches and communicates via stdio transport.
## Search Tools
### 1. search_observations
Full-text search across observation titles, narratives, facts, and concepts.
**Parameters**:
- `query` (required): Search query for FTS5 full-text search
- `type`: Filter by observation type(s) (decision, bugfix, feature, refactor, discovery, change)
- `concepts`: Filter by concept tags
- `files`: Filter by file paths (partial match)
- `project`: Filter by project name
- `dateRange`: Filter by date range (`{start, end}`)
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" for titles/dates only, "full" for complete details)
**Example**:
```
search_observations with query="build system" and type="decision"
```
### 2. search_sessions
Full-text search across session summaries, requests, and learnings.
**Parameters**:
- `query` (required): Search query for FTS5 full-text search
- `project`: Filter by project name
- `dateRange`: Filter by date range
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" or "full")
**Example**:
```
search_sessions with query="hooks implementation"
```
### 3. search_user_prompts
Search raw user prompts with full-text search. Use this to find what the user actually said/requested across all sessions.
**Parameters**:
- `query` (required): Search query for FTS5 full-text search
- `project`: Filter by project name
- `dateRange`: Filter by date range
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" for truncated prompts/dates, "full" for complete prompt text)
**Example**:
```
search_user_prompts with query="authentication feature"
```
**Benefits**:
- Full context reconstruction from user intent → Claude actions → outcomes
- Pattern detection for repeated requests
- Improved debugging by tracing from original user words to final implementation
### 4. find_by_concept
Find observations tagged with specific concepts.
**Parameters**:
- `concept` (required): Concept tag to search for
- `project`: Filter by project name
- `dateRange`: Filter by date range
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" or "full")
**Example**:
```
find_by_concept with concept="architecture"
```
### 5. find_by_file
Find observations and sessions that reference specific file paths.
**Parameters**:
- `filePath` (required): File path to search for (supports partial matching)
- `project`: Filter by project name
- `dateRange`: Filter by date range
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" or "full")
**Example**:
```
find_by_file with filePath="worker-service.ts"
```
### 6. find_by_type
Find observations by type (decision, bugfix, feature, refactor, discovery, change).
**Parameters**:
- `type` (required): Observation type(s) to filter by (single type or array)
- `project`: Filter by project name
- `dateRange`: Filter by date range
- `orderBy`: Sort order (relevance, date_desc, date_asc)
- `limit`: Maximum results (default: 20, max: 100)
- `offset`: Number of results to skip
- `format`: Output format ("index" or "full")
**Example**:
```
find_by_type with type=["decision", "feature"]
```
### 7. get_recent_context
Get recent session context including summaries and observations for a project.
**Parameters**:
- `project`: Project name (defaults to current working directory basename)
- `limit`: Number of recent sessions to retrieve (default: 3, max: 10)
**Example**:
```
get_recent_context with limit=5
```
### 8. get_context_timeline
Get a unified timeline of context (observations, sessions, and prompts) around a specific point in time. All record types are interleaved chronologically.
**Parameters**:
- `anchor` (required): Anchor point - observation ID, session ID (e.g., "S123"), or ISO timestamp
- `depth_before` (default: 10): Number of records to retrieve before anchor (max: 50)
- `depth_after` (default: 10): Number of records to retrieve after anchor (max: 50)
- `project`: Filter by project name
**Return Format**:
Returns `depth_before` records + anchor + `depth_after` records, all interleaved chronologically. Total records: `depth_before + 1 + depth_after`.
**Use Case**: Understanding "what was happening when X occurred"
**Example**:
```
# Timeline around observation #123
get_context_timeline with anchor=123 and depth_before=5 and depth_after=5
# Timeline around a session
get_context_timeline with anchor="S456" and depth_before=10 and depth_after=10
# Timeline around a timestamp
get_context_timeline with anchor="2025-11-06T10:30:00Z" and depth_before=15 and depth_after=5
```
**Response Structure**:
```json
{
"timeline": [
{
"type": "observation",
"id": 120,
"title": "Context before",
"created_at": "2025-11-06T10:25:00Z"
},
{
"type": "user-prompt",
"id": 45,
"prompt": "User request",
"created_at": "2025-11-06T10:28:00Z"
},
{
"type": "observation",
"id": 123,
"title": "Anchor observation",
"created_at": "2025-11-06T10:30:00Z",
"isAnchor": true
},
{
"type": "session",
"id": "S456",
"request": "Session summary",
"created_at": "2025-11-06T10:32:00Z"
}
],
"anchor": {
"type": "observation",
"id": 123
}
}
```
### 9. get_timeline_by_query
Search for observations using natural language and get timeline context around the best match. Combines search + timeline into a single operation.
**Parameters**:
- `query` (required): Natural language search query to find relevant observations
- `mode` (default: "auto"): Operation mode
- `"auto"`: Automatically use top search result as timeline anchor
- `"interactive"`: Return top N search results for manual anchor selection
- `depth_before` (default: 10): Number of timeline records before anchor (max: 50)
- `depth_after` (default: 10): Number of timeline records after anchor (max: 50)
- `limit` (default: 5): For interactive mode - number of top search results to display (max: 20)
- `project`: Filter by project name
**Use Case**: Faster context discovery - "show me what happened around when we fixed the authentication bug"
**Example - Auto Mode**:
```
# Automatically find and show timeline for "authentication bug"
get_timeline_by_query with query="authentication bug" and mode="auto" and depth_before=10 and depth_after=10
```
**Example - Interactive Mode**:
```
# Show top 5 matches, let user choose anchor
get_timeline_by_query with query="authentication bug" and mode="interactive" and limit=5
```
**Auto Mode Response**:
```json
{
"search_result": {
"id": 123,
"title": "Fix authentication bug",
"relevance": 0.95
},
"timeline": [
/* timeline records before and after observation 123 */
]
}
```
**Interactive Mode Response**:
```json
{
"top_results": [
{
"id": 123,
"title": "Fix authentication bug",
"relevance": 0.95,
"created_at": "2025-11-06T10:30:00Z"
},
{
"id": 98,
"title": "Authentication refactor",
"relevance": 0.82,
"created_at": "2025-11-05T14:20:00Z"
}
],
"message": "Select an observation ID to view its timeline context"
}
```
## Output Formats
All search tools support two output formats:
### Index Format (Default)
Returns titles, dates, and source URIs only. Uses ~10x fewer tokens than full format.
**Always use index format first** to get an overview and identify relevant results.
**Example Output**:
```
1. [decision] Implement graceful session cleanup
Date: 2025-10-21 14:23:45
Source: claude-mem://observation/123
2. [feature] Add FTS5 full-text search
Date: 2025-10-21 13:15:22
Source: claude-mem://observation/124
```
### Full Format
Returns complete observation/summary details including narrative, facts, concepts, files, etc.
**Only use after reviewing index results** to dive deep into specific items of interest.
## Search Strategy
**Recommended Workflow**:
1. **Initial search**: Use default (index) format to see titles, dates, and sources
2. **Review results**: Identify which items are most relevant to your needs
3. **Deep dive**: Only then use `format: "full"` on specific items of interest
4. **Narrow down**: Use filters (type, dateRange, concepts, files) to refine results
**Token Efficiency**:
- Index format: ~50-100 tokens per result
- Full format: ~500-1000 tokens per result
- Start with 3-5 results to avoid MCP token limits
## Citations
All search results use the `claude-mem://` URI scheme for citations:
- `claude-mem://observation/{id}` - References specific observations
- `claude-mem://session/{id}` - References specific sessions
- `claude-mem://user-prompt/{id}` - References specific user prompts
These citations allow Claude to reference specific historical context in responses.
## FTS5 Query Syntax
The `query` parameter supports SQLite FTS5 full-text search syntax:
- **Simple**: `"error handling"`
- **AND**: `"error" AND "handling"`
- **OR**: `"bug" OR "fix"`
- **NOT**: `"bug" NOT "feature"`
- **Phrase**: `"'exact phrase'"`
- **Column**: `title:"authentication"`
## Security
As of v4.2.3, all FTS5 queries are properly escaped to prevent SQL injection attacks:
- Double quotes are escaped: `query.replace(/"/g, '""')`
- Comprehensive test suite with 332 injection attack tests
- Affects: `search_observations`, `search_sessions`, `search_user_prompts`
## Example Queries
```
# Find all decisions about build system
search_observations with query="build system" and type="decision"
# Show everything related to worker-service.ts
find_by_file with filePath="worker-service.ts"
# Search what we learned about hooks
search_sessions with query="hooks"
# Show observations tagged with 'architecture'
find_by_concept with concept="architecture"
# Find what user asked about authentication
search_user_prompts with query="authentication"
# Get recent context for debugging
get_recent_context with limit=5
# Timeline around a specific observation
get_context_timeline with anchor=123 and depth_before=10 and depth_after=10
# Quick timeline search for authentication work
get_timeline_by_query with query="authentication bug" and mode="auto"
```
## Implementation
The MCP search server is implemented using:
- `@modelcontextprotocol/sdk` (v1.20.1)
- `SessionSearch` service for FTS5 queries
- `SessionStore` for database access
- `zod-to-json-schema` for parameter validation
**Source Code**: `src/servers/search-server.ts`
## Troubleshooting
### Tool Not Available
If search tools are not available in Claude Code sessions:
1. Check MCP configuration:
```bash
cat plugin/.mcp.json
```
2. Verify search server is built:
```bash
ls -l plugin/scripts/search-server.mjs
```
3. Rebuild if needed:
```bash
npm run build
```
### Search Returns No Results
1. Check database has data:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
2. Verify FTS5 tables exist:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%_fts';"
```
3. Test query syntax:
```bash
# Simple query should work
search_observations with query="test"
```
### Token Limit Errors
If you hit MCP token limits:
1. Use `format: "index"` instead of `format: "full"`
2. Reduce `limit` parameter (try 3-5 instead of 20)
3. Use more specific filters to narrow results
4. Use `offset` to paginate through results
+104
View File
@@ -0,0 +1,104 @@
# Claude-Mem Context Documentation
## What This Folder Is
This `docs/context/` folder contains **internal documentation** - planning documents, design references, audits, and work-in-progress materials that support development but are NOT user-facing.
## Folder Structure
```
docs/
├── public/ ← User-facing Mintlify docs (DO NOT put internal docs there)
│ └── *.mdx - Official documentation
└── context/ ← You are here (Internal documentation)
├── *.md - Planning docs, audits, references
├── *-plan.md - Implementation plans
├── *-audit.md - Code audits and reviews
├── agent-sdk-*.md - SDK reference materials
└── subdirs/ - Organized by topic
```
## What Belongs Here
**Internal Documentation** (`.md` format):
- Planning documents (`*-plan.md`, `*-outline.md`)
- Implementation analysis (`*-audit.md`, `*-code-reference.md`)
- Error tracking (`typescript-errors.md`)
- Design documents not ready for public docs
- PR review responses
- Reference materials (like `agent-sdk-ref.md`)
- Work-in-progress documentation
- Technical investigations and postmortems
- Architecture analysis documents
**Examples from this folder:**
- `mem-search-technical-architecture.md` - Deep technical reference
- `search-architecture-analysis.md` - Implementation analysis
- `agent-sdk-ref.md` - SDK reference for developers
- `typescript-errors.md` - Error tracking during development
- `worker-service-architecture.md` - Internal architecture notes
- `processing-indicator-audit.md` - Code audit document
## What Does NOT Belong Here
**User-Facing Documentation** goes in `/docs/public/`:
- User guides and tutorials
- Official architecture documentation
- Installation instructions
- Configuration guides
- Best practices for users
- Troubleshooting guides
**Rule of Thumb:**
- If a user would read it → `/docs/public/` (as `.mdx`)
- If only developers/contributors need it → `/docs/context/` (as `.md`)
## File Organization
### By Type
- `*-plan.md` - Implementation plans for features
- `*-audit.md` - Code audits and reviews
- `*-postmortem.md` - Analysis of issues or incidents
- `*-reference.md` - Technical reference materials
- `*-analysis.md` - Architecture or design analysis
### By Topic
- Create subdirectories for related documents
- Example: `claude-code/` for Claude Code specific docs
- Example: `architecture/` for internal architecture notes
## Development Workflow
### When to Create Context Docs
1. **Planning Phase** - Before implementing a feature
- Create `feature-name-plan.md`
- Outline implementation steps
- Document decisions and tradeoffs
2. **During Development** - Track issues and decisions
- Create `feature-name-audit.md` for code reviews
- Update `typescript-errors.md` for build issues
- Document gotchas in topic-specific files
3. **After Implementation** - Preserve knowledge
- Create `feature-name-postmortem.md` if issues occurred
- Update architecture analysis documents
- Archive plan docs (don't delete - useful for history)
### Graduating to Public Docs
When internal docs are polished enough for users:
1. Convert `.md` to `.mdx` format
2. Add Mintlify frontmatter
3. Move to appropriate `/docs/public/` subdirectory
4. Add to `docs.json` navigation
5. Keep original in `/docs/context/` for reference
## Summary
**Simple Rule**:
- `/docs/context/` = Internal docs, plans, references, audits ← YOU ARE HERE
- `/docs/public/` = Official user documentation (Mintlify .mdx files)
**Purpose**: This folder preserves development context, design decisions, and technical knowledge that helps contributors understand WHY things work the way they do, even if users don't need those details.
+164
View File
@@ -0,0 +1,164 @@
# CWD Context Fix - Technical Documentation
## Overview
This fix adds working directory (CWD) context propagation through the entire claude-mem pipeline, enabling the SDK agent to have spatial awareness of which directory/repository it's observing.
## Problem Statement
Previously, the SDK agent would:
- Search wrong repositories when analyzing file operations
- Report "file not found" for files that actually exist
- Lack context about which project was being worked on
- Generate inaccurate observations due to spatial confusion
## Solution
The CWD information now flows through the entire system:
```
Hook Input (cwd) → Worker API (cwd) → SessionManager (cwd) → SDK Agent (tool_cwd)
```
## Data Flow
### 1. Hook Layer (`save-hook.ts`)
```typescript
export interface PostToolUseInput {
session_id: string;
cwd: string; // ← Captured from Claude Code
tool_name: string;
tool_input: any;
tool_response: any;
}
```
The hook extracts `cwd` and includes it in the worker API request:
```typescript
body: JSON.stringify({
tool_name,
tool_input,
tool_response,
prompt_number,
cwd: cwd || '' // ← Passed to worker
})
```
### 2. Worker Service (`worker-service.ts`)
```typescript
const { tool_name, tool_input, tool_response, prompt_number, cwd } = req.body;
this.sessionManager.queueObservation(sessionDbId, {
tool_name,
tool_input,
tool_response,
prompt_number,
cwd // ← Forwarded to queue
});
```
### 3. Session Manager (`SessionManager.ts`)
```typescript
session.pendingMessages.push({
type: 'observation',
tool_name: data.tool_name,
tool_input: data.tool_input,
tool_response: data.tool_response,
prompt_number: data.prompt_number,
cwd: data.cwd // ← Included in message queue
});
```
### 4. SDK Agent (`SDKAgent.ts`)
```typescript
content: buildObservationPrompt({
id: 0,
tool_name: message.tool_name!,
tool_input: JSON.stringify(message.tool_input),
tool_output: JSON.stringify(message.tool_response),
created_at_epoch: Date.now(),
cwd: message.cwd // ← Passed to prompt builder
})
```
### 5. Prompt Generation (`prompts.ts`)
```typescript
return `<tool_used>
<tool_name>${obs.tool_name}</tool_name>
<tool_time>${new Date(obs.created_at_epoch).toISOString()}</tool_time>${obs.cwd ? `
<tool_cwd>${obs.cwd}</tool_cwd>` : ''} // ← Included in XML
<tool_input>${JSON.stringify(toolInput, null, 2)}</tool_input>
<tool_output>${JSON.stringify(toolOutput, null, 2)}</tool_output>
</tool_used>`;
```
## SDK Agent Prompt Changes
The init prompt now includes a "SPATIAL AWARENESS" section:
```
SPATIAL AWARENESS: Tool executions include the working directory (tool_cwd) to help you understand:
- Which repository/project is being worked on
- Where files are located relative to the project root
- How to match requested paths to actual execution paths
```
## Example Usage
When a user executes a read operation in `/home/user/my-project`:
```xml
<tool_used>
<tool_name>ReadTool</tool_name>
<tool_time>2025-11-10T19:18:03.065Z</tool_time>
<tool_cwd>/home/user/my-project</tool_cwd>
<tool_input>
{
"path": "src/index.ts"
}
</tool_input>
<tool_output>
{
"content": "export default..."
}
</tool_output>
</tool_used>
```
The SDK agent now knows:
1. The operation happened in `/home/user/my-project`
2. The file `src/index.ts` is relative to that directory
3. Which repository context to search when generating observations
## Testing
8 comprehensive tests validate the CWD propagation:
```bash
npx tsx --test tests/cwd-propagation.test.ts
```
All tests verify:
- Type interfaces include `cwd` fields
- Hook extracts and passes `cwd`
- Worker accepts and forwards `cwd`
- SDK agent includes `cwd` in prompts
- End-to-end flow is correct
## Benefits
1. **Spatial Awareness**: SDK agent knows which directory/repository it's observing
2. **Accurate Path Matching**: Can verify if requested paths match executed paths
3. **Better Summaries**: Won't search wrong repositories or report false negatives
4. **Works with All Models**: Even Haiku benefits from correct context (no need for Opus workaround)
## Backward Compatibility
- `cwd` is optional in all interfaces (`cwd?: string`)
- Missing `cwd` values are handled gracefully (defaults to empty string)
- Existing observations without `cwd` continue to work
- No database migration required (CWD is transient, not persisted)
## Related Issues
Fixes issue #73 (CWD context missing from SDK agent)
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
# Agent Skills in the SDK
> Extend Claude with specialized capabilities using Agent Skills in the Claude Agent SDK
## Overview
Agent Skills extend Claude with specialized capabilities that Claude autonomously invokes when relevant. Skills are packaged as `SKILL.md` files containing instructions, descriptions, and optional supporting resources.
For comprehensive information about Skills, including benefits, architecture, and authoring guidelines, see the [Agent Skills overview](/en/docs/agents-and-tools/agent-skills/overview).
## How Skills Work with the SDK
When using the Claude Agent SDK, Skills are:
1. **Defined as filesystem artifacts**: Created as `SKILL.md` files in specific directories (`.claude/skills/`)
2. **Loaded from filesystem**: Skills are loaded from configured filesystem locations. You must specify `settingSources` (TypeScript) or `setting_sources` (Python) to load Skills from the filesystem
3. **Automatically discovered**: Once filesystem settings are loaded, Skill metadata is discovered at startup from user and project directories; full content loaded when triggered
4. **Model-invoked**: Claude autonomously chooses when to use them based on context
5. **Enabled via allowed\_tools**: Add `"Skill"` to your `allowed_tools` to enable Skills
Unlike subagents (which can be defined programmatically), Skills must be created as filesystem artifacts. The SDK does not provide a programmatic API for registering Skills.
<Note>
**Default behavior**: By default, the SDK does not load any filesystem settings. To use Skills, you must explicitly configure `settingSources: ['user', 'project']` (TypeScript) or `setting_sources=["user", "project"]` (Python) in your options.
</Note>
## Using Skills with the SDK
To use Skills with the SDK, you need to:
1. Include `"Skill"` in your `allowed_tools` configuration
2. Configure `settingSources`/`setting_sources` to load Skills from the filesystem
Once configured, Claude automatically discovers Skills from the specified directories and invokes them when relevant to the user's request.
<CodeGroup>
```python Python theme={null}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(
cwd="/path/to/project", # Project with .claude/skills/
setting_sources=["user", "project"], # Load Skills from filesystem
allowed_tools=["Skill", "Read", "Write", "Bash"] # Enable Skill tool
)
async for message in query(
prompt="Help me process this PDF document",
options=options
):
print(message)
asyncio.run(main())
```
```typescript TypeScript theme={null}
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Help me process this PDF document",
options: {
cwd: "/path/to/project", // Project with .claude/skills/
settingSources: ["user", "project"], // Load Skills from filesystem
allowedTools: ["Skill", "Read", "Write", "Bash"] // Enable Skill tool
}
})) {
console.log(message);
}
```
</CodeGroup>
## Skill Locations
Skills are loaded from filesystem directories based on your `settingSources`/`setting_sources` configuration:
* **Project Skills** (`.claude/skills/`): Shared with your team via git - loaded when `setting_sources` includes `"project"`
* **User Skills** (`~/.claude/skills/`): Personal Skills across all projects - loaded when `setting_sources` includes `"user"`
* **Plugin Skills**: Bundled with installed Claude Code plugins
## Creating Skills
Skills are defined as directories containing a `SKILL.md` file with YAML frontmatter and Markdown content. The `description` field determines when Claude invokes your Skill.
**Example directory structure**:
```bash theme={null}
.claude/skills/processing-pdfs/
└── SKILL.md
```
For complete guidance on creating Skills, including SKILL.md structure, multi-file Skills, and examples, see:
* [Agent Skills in Claude Code](https://code.claude.com/docs/skills): Complete guide with examples
* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines and naming conventions
## Tool Restrictions
<Note>
The `allowed-tools` frontmatter field in SKILL.md is only supported when using Claude Code CLI directly. **It does not apply when using Skills through the SDK**.
When using the SDK, control tool access through the main `allowedTools` option in your query configuration.
</Note>
To restrict tools for Skills in SDK applications, use the `allowedTools` option:
<Note>
Import statements from the first example are assumed in the following code snippets.
</Note>
<CodeGroup>
```python Python theme={null}
options = ClaudeAgentOptions(
setting_sources=["user", "project"], # Load Skills from filesystem
allowed_tools=["Skill", "Read", "Grep", "Glob"] # Restricted toolset
)
async for message in query(
prompt="Analyze the codebase structure",
options=options
):
print(message)
```
```typescript TypeScript theme={null}
// Skills can only use Read, Grep, and Glob tools
for await (const message of query({
prompt: "Analyze the codebase structure",
options: {
settingSources: ["user", "project"], // Load Skills from filesystem
allowedTools: ["Skill", "Read", "Grep", "Glob"] // Restricted toolset
}
})) {
console.log(message);
}
```
</CodeGroup>
## Discovering Available Skills
To see which Skills are available in your SDK application, simply ask Claude:
<CodeGroup>
```python Python theme={null}
options = ClaudeAgentOptions(
setting_sources=["user", "project"], # Load Skills from filesystem
allowed_tools=["Skill"]
)
async for message in query(
prompt="What Skills are available?",
options=options
):
print(message)
```
```typescript TypeScript theme={null}
for await (const message of query({
prompt: "What Skills are available?",
options: {
settingSources: ["user", "project"], // Load Skills from filesystem
allowedTools: ["Skill"]
}
})) {
console.log(message);
}
```
</CodeGroup>
Claude will list the available Skills based on your current working directory and installed plugins.
## Testing Skills
Test Skills by asking questions that match their descriptions:
<CodeGroup>
```python Python theme={null}
options = ClaudeAgentOptions(
cwd="/path/to/project",
setting_sources=["user", "project"], # Load Skills from filesystem
allowed_tools=["Skill", "Read", "Bash"]
)
async for message in query(
prompt="Extract text from invoice.pdf",
options=options
):
print(message)
```
```typescript TypeScript theme={null}
for await (const message of query({
prompt: "Extract text from invoice.pdf",
options: {
cwd: "/path/to/project",
settingSources: ["user", "project"], // Load Skills from filesystem
allowedTools: ["Skill", "Read", "Bash"]
}
})) {
console.log(message);
}
```
</CodeGroup>
Claude automatically invokes the relevant Skill if the description matches your request.
## Troubleshooting
### Skills Not Found
**Check settingSources configuration**: Skills are only loaded when you explicitly configure `settingSources`/`setting_sources`. This is the most common issue:
<CodeGroup>
```python Python theme={null}
# Wrong - Skills won't be loaded
options = ClaudeAgentOptions(
allowed_tools=["Skill"]
)
# Correct - Skills will be loaded
options = ClaudeAgentOptions(
setting_sources=["user", "project"], # Required to load Skills
allowed_tools=["Skill"]
)
```
```typescript TypeScript theme={null}
// Wrong - Skills won't be loaded
const options = {
allowedTools: ["Skill"]
};
// Correct - Skills will be loaded
const options = {
settingSources: ["user", "project"], // Required to load Skills
allowedTools: ["Skill"]
};
```
</CodeGroup>
For more details on `settingSources`/`setting_sources`, see the [TypeScript SDK reference](/en/docs/agent-sdk/typescript#settingsource) or [Python SDK reference](/en/docs/agent-sdk/python#settingsource).
**Check working directory**: The SDK loads Skills relative to the `cwd` option. Ensure it points to a directory containing `.claude/skills/`:
<CodeGroup>
```python Python theme={null}
# Ensure your cwd points to the directory containing .claude/skills/
options = ClaudeAgentOptions(
cwd="/path/to/project", # Must contain .claude/skills/
setting_sources=["user", "project"], # Required to load Skills
allowed_tools=["Skill"]
)
```
```typescript TypeScript theme={null}
// Ensure your cwd points to the directory containing .claude/skills/
const options = {
cwd: "/path/to/project", // Must contain .claude/skills/
settingSources: ["user", "project"], // Required to load Skills
allowedTools: ["Skill"]
};
```
</CodeGroup>
See the "Using Skills with the SDK" section above for the complete pattern.
**Verify filesystem location**:
```bash theme={null}
# Check project Skills
ls .claude/skills/*/SKILL.md
# Check personal Skills
ls ~/.claude/skills/*/SKILL.md
```
### Skill Not Being Used
**Check the Skill tool is enabled**: Confirm `"Skill"` is in your `allowedTools`.
**Check the description**: Ensure it's specific and includes relevant keywords. See [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices#writing-effective-descriptions) for guidance on writing effective descriptions.
### Additional Troubleshooting
For general Skills troubleshooting (YAML syntax, debugging, etc.), see the [Claude Code Skills troubleshooting section](https://code.claude.com/docs/skills#troubleshooting).
## Related Documentation
### Skills Guides
* [Agent Skills in Claude Code](https://code.claude.com/docs/skills): Complete Skills guide with creation, examples, and troubleshooting
* [Agent Skills Overview](/en/docs/agents-and-tools/agent-skills/overview): Conceptual overview, benefits, and architecture
* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines for effective Skills
* [Agent Skills Cookbook](https://github.com/anthropics/claude-cookbooks/tree/main/skills): Example Skills and templates
### SDK Resources
* [Subagents in the SDK](/en/docs/agent-sdk/subagents): Similar filesystem-based agents with programmatic options
* [Slash Commands in the SDK](/en/docs/agent-sdk/slash-commands): User-invoked commands
* [SDK Overview](/en/docs/agent-sdk/overview): General SDK concepts
* [TypeScript SDK Reference](/en/docs/agent-sdk/typescript): Complete API documentation
* [Python SDK Reference](/en/docs/agent-sdk/python): Complete API documentation
+607
View File
@@ -0,0 +1,607 @@
# Agent Skills
> Create, manage, and share Skills to extend Claude's capabilities in Claude Code.
This guide shows you how to create, use, and manage Agent Skills in Claude Code. Skills are modular capabilities that extend Claude's functionality through organized folders containing instructions, scripts, and resources.
## Prerequisites
* Claude Code version 1.0 or later
* Basic familiarity with [Claude Code](/en/quickstart)
## What are Agent Skills?
Agent Skills package expertise into discoverable capabilities. Each Skill consists of a `SKILL.md` file with instructions that Claude reads when relevant, plus optional supporting files like scripts and templates.
**How Skills are invoked**: Skills are **model-invoked**—Claude autonomously decides when to use them based on your request and the Skill's description. This is different from slash commands, which are **user-invoked** (you explicitly type `/command` to trigger them).
**Benefits**:
* Extend Claude's capabilities for your specific workflows
* Share expertise across your team via git
* Reduce repetitive prompting
* Compose multiple Skills for complex tasks
Learn more in the [Agent Skills overview](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview).
<Note>
For a deep dive into the architecture and real-world applications of Agent Skills, read our engineering blog: [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills).
</Note>
## Create a Skill
Skills are stored as directories containing a `SKILL.md` file.
### Personal Skills
Personal Skills are available across all your projects. Store them in `~/.claude/skills/`:
```bash theme={null}
mkdir -p ~/.claude/skills/my-skill-name
```
**Use personal Skills for**:
* Your individual workflows and preferences
* Experimental Skills you're developing
* Personal productivity tools
### Project Skills
Project Skills are shared with your team. Store them in `.claude/skills/` within your project:
```bash theme={null}
mkdir -p .claude/skills/my-skill-name
```
**Use project Skills for**:
* Team workflows and conventions
* Project-specific expertise
* Shared utilities and scripts
Project Skills are checked into git and automatically available to team members.
### Plugin Skills
Skills can also come from [Claude Code plugins](/en/plugins). Plugins may bundle Skills that are automatically available when the plugin is installed. These Skills work the same way as personal and project Skills.
## Write SKILL.md
Create a `SKILL.md` file with YAML frontmatter and Markdown content:
```yaml theme={null}
---
name: your-skill-name
description: Brief description of what this Skill does and when to use it
---
# Your Skill Name
## Instructions
Provide clear, step-by-step guidance for Claude.
## Examples
Show concrete examples of using this Skill.
```
**Field requirements**:
* `name`: Must use lowercase letters, numbers, and hyphens only (max 64 characters)
* `description`: Brief description of what the Skill does and when to use it (max 1024 characters)
The `description` field is critical for Claude to discover when to use your Skill. It should include both what the Skill does and when Claude should use it.
See the [best practices guide](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices) for complete authoring guidance including validation rules.
## Add supporting files
Create additional files alongside SKILL.md:
```
my-skill/
├── SKILL.md (required)
├── reference.md (optional documentation)
├── examples.md (optional examples)
├── scripts/
│ └── helper.py (optional utility)
└── templates/
└── template.txt (optional template)
```
Reference these files from SKILL.md:
````markdown theme={null}
For advanced usage, see [reference.md](reference.md).
Run the helper script:
```bash
python scripts/helper.py input.txt
```
````
Claude reads these files only when needed, using progressive disclosure to manage context efficiently.
## Restrict tool access with allowed-tools
Use the `allowed-tools` frontmatter field to limit which tools Claude can use when a Skill is active:
```yaml theme={null}
---
name: safe-file-reader
description: Read files without making changes. Use when you need read-only file access.
allowed-tools: Read, Grep, Glob
---
# Safe File Reader
This Skill provides read-only file access.
## Instructions
1. Use Read to view file contents
2. Use Grep to search within files
3. Use Glob to find files by pattern
```
When this Skill is active, Claude can only use the specified tools (Read, Grep, Glob) without needing to ask for permission. This is useful for:
* Read-only Skills that shouldn't modify files
* Skills with limited scope (e.g., only data analysis, no file writing)
* Security-sensitive workflows where you want to restrict capabilities
If `allowed-tools` is not specified, Claude will ask for permission to use tools as normal, following the standard permission model.
<Note>
`allowed-tools` is only supported for Skills in Claude Code.
</Note>
## View available Skills
Skills are automatically discovered by Claude from three sources:
* Personal Skills: `~/.claude/skills/`
* Project Skills: `.claude/skills/`
* Plugin Skills: bundled with installed plugins
**To view all available Skills**, ask Claude directly:
```
What Skills are available?
```
or
```
List all available Skills
```
This will show all Skills from all sources, including plugin Skills.
**To inspect a specific Skill**, you can also check the filesystem:
```bash theme={null}
# List personal Skills
ls ~/.claude/skills/
# List project Skills (if in a project directory)
ls .claude/skills/
# View a specific Skill's content
cat ~/.claude/skills/my-skill/SKILL.md
```
## Test a Skill
After creating a Skill, test it by asking questions that match your description.
**Example**: If your description mentions "PDF files":
```
Can you help me extract text from this PDF?
```
Claude autonomously decides to use your Skill if it matches the request—you don't need to explicitly invoke it. The Skill activates automatically based on the context of your question.
## Debug a Skill
If Claude doesn't use your Skill, check these common issues:
### Make description specific
**Too vague**:
```yaml theme={null}
description: Helps with documents
```
**Specific**:
```yaml theme={null}
description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
```
Include both what the Skill does and when to use it in the description.
### Verify file path
**Personal Skills**: `~/.claude/skills/skill-name/SKILL.md`
**Project Skills**: `.claude/skills/skill-name/SKILL.md`
Check the file exists:
```bash theme={null}
# Personal
ls ~/.claude/skills/my-skill/SKILL.md
# Project
ls .claude/skills/my-skill/SKILL.md
```
### Check YAML syntax
Invalid YAML prevents the Skill from loading. Verify the frontmatter:
```bash theme={null}
cat SKILL.md | head -n 10
```
Ensure:
* Opening `---` on line 1
* Closing `---` before Markdown content
* Valid YAML syntax (no tabs, correct indentation)
### View errors
Run Claude Code with debug mode to see Skill loading errors:
```bash theme={null}
claude --debug
```
## Share Skills with your team
**Recommended approach**: Distribute Skills through [plugins](/en/plugins).
To share Skills via plugin:
1. Create a plugin with Skills in the `skills/` directory
2. Add the plugin to a marketplace
3. Team members install the plugin
For complete instructions, see [Add Skills to your plugin](/en/plugins#add-skills-to-your-plugin).
You can also share Skills directly through project repositories:
### Step 1: Add Skill to your project
Create a project Skill:
```bash theme={null}
mkdir -p .claude/skills/team-skill
# Create SKILL.md
```
### Step 2: Commit to git
```bash theme={null}
git add .claude/skills/
git commit -m "Add team Skill for PDF processing"
git push
```
### Step 3: Team members get Skills automatically
When team members pull the latest changes, Skills are immediately available:
```bash theme={null}
git pull
claude # Skills are now available
```
## Update a Skill
Edit SKILL.md directly:
```bash theme={null}
# Personal Skill
code ~/.claude/skills/my-skill/SKILL.md
# Project Skill
code .claude/skills/my-skill/SKILL.md
```
Changes take effect the next time you start Claude Code. If Claude Code is already running, restart it to load the updates.
## Remove a Skill
Delete the Skill directory:
```bash theme={null}
# Personal
rm -rf ~/.claude/skills/my-skill
# Project
rm -rf .claude/skills/my-skill
git commit -m "Remove unused Skill"
```
## Best practices
### Keep Skills focused
One Skill should address one capability:
**Focused**:
* "PDF form filling"
* "Excel data analysis"
* "Git commit messages"
**Too broad**:
* "Document processing" (split into separate Skills)
* "Data tools" (split by data type or operation)
### Write clear descriptions
Help Claude discover when to use Skills by including specific triggers in your description:
**Clear**:
```yaml theme={null}
description: Analyze Excel spreadsheets, create pivot tables, and generate charts. Use when working with Excel files, spreadsheets, or analyzing tabular data in .xlsx format.
```
**Vague**:
```yaml theme={null}
description: For files
```
### Test with your team
Have teammates use Skills and provide feedback:
* Does the Skill activate when expected?
* Are the instructions clear?
* Are there missing examples or edge cases?
### Document Skill versions
You can document Skill versions in your SKILL.md content to track changes over time. Add a version history section:
```markdown theme={null}
# My Skill
## Version History
- v2.0.0 (2025-10-01): Breaking changes to API
- v1.1.0 (2025-09-15): Added new features
- v1.0.0 (2025-09-01): Initial release
```
This helps team members understand what changed between versions.
## Troubleshooting
### Claude doesn't use my Skill
**Symptom**: You ask a relevant question but Claude doesn't use your Skill.
**Check**: Is the description specific enough?
Vague descriptions make discovery difficult. Include both what the Skill does and when to use it, with key terms users would mention.
**Too generic**:
```yaml theme={null}
description: Helps with data
```
**Specific**:
```yaml theme={null}
description: Analyze Excel spreadsheets, generate pivot tables, create charts. Use when working with Excel files, spreadsheets, or .xlsx files.
```
**Check**: Is the YAML valid?
Run validation to check for syntax errors:
```bash theme={null}
# View frontmatter
cat .claude/skills/my-skill/SKILL.md | head -n 15
# Check for common issues
# - Missing opening or closing ---
# - Tabs instead of spaces
# - Unquoted strings with special characters
```
**Check**: Is the Skill in the correct location?
```bash theme={null}
# Personal Skills
ls ~/.claude/skills/*/SKILL.md
# Project Skills
ls .claude/skills/*/SKILL.md
```
### Skill has errors
**Symptom**: The Skill loads but doesn't work correctly.
**Check**: Are dependencies available?
Claude will automatically install required dependencies (or ask for permission to install them) when it needs them.
**Check**: Do scripts have execute permissions?
```bash theme={null}
chmod +x .claude/skills/my-skill/scripts/*.py
```
**Check**: Are file paths correct?
Use forward slashes (Unix style) in all paths:
**Correct**: `scripts/helper.py`
**Wrong**: `scripts\helper.py` (Windows style)
### Multiple Skills conflict
**Symptom**: Claude uses the wrong Skill or seems confused between similar Skills.
**Be specific in descriptions**: Help Claude choose the right Skill by using distinct trigger terms in your descriptions.
Instead of:
```yaml theme={null}
# Skill 1
description: For data analysis
# Skill 2
description: For analyzing data
```
Use:
```yaml theme={null}
# Skill 1
description: Analyze sales data in Excel files and CRM exports. Use for sales reports, pipeline analysis, and revenue tracking.
# Skill 2
description: Analyze log files and system metrics data. Use for performance monitoring, debugging, and system diagnostics.
```
## Examples
### Simple Skill (single file)
```
commit-helper/
└── SKILL.md
```
```yaml theme={null}
---
name: generating-commit-messages
description: Generates clear commit messages from git diffs. Use when writing commit messages or reviewing staged changes.
---
# Generating Commit Messages
## Instructions
1. Run `git diff --staged` to see changes
2. I'll suggest a commit message with:
- Summary under 50 characters
- Detailed description
- Affected components
## Best practices
- Use present tense
- Explain what and why, not how
```
### Skill with tool permissions
```
code-reviewer/
└── SKILL.md
```
```yaml theme={null}
---
name: code-reviewer
description: Review code for best practices and potential issues. Use when reviewing code, checking PRs, or analyzing code quality.
allowed-tools: Read, Grep, Glob
---
# Code Reviewer
## Review checklist
1. Code organization and structure
2. Error handling
3. Performance considerations
4. Security concerns
5. Test coverage
## Instructions
1. Read the target files using Read tool
2. Search for patterns using Grep
3. Find related files using Glob
4. Provide detailed feedback on code quality
```
### Multi-file Skill
```
pdf-processing/
├── SKILL.md
├── FORMS.md
├── REFERENCE.md
└── scripts/
├── fill_form.py
└── validate.py
```
**SKILL.md**:
````yaml theme={null}
---
name: pdf-processing
description: Extract text, fill forms, merge PDFs. Use when working with PDF files, forms, or document extraction. Requires pypdf and pdfplumber packages.
---
# PDF Processing
## Quick start
Extract text:
```python
import pdfplumber
with pdfplumber.open("doc.pdf") as pdf:
text = pdf.pages[0].extract_text()
```
For form filling, see [FORMS.md](FORMS.md).
For detailed API reference, see [REFERENCE.md](REFERENCE.md).
## Requirements
Packages must be installed in your environment:
```bash
pip install pypdf pdfplumber
```
````
<Note>
List required packages in the description. Packages must be installed in your environment before Claude can use them.
</Note>
Claude loads additional files only when needed.
## Next steps
<CardGroup cols={2}>
<Card title="Authoring best practices" icon="lightbulb" href="https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices">
Write Skills that Claude can use effectively
</Card>
<Card title="Agent Skills overview" icon="book" href="https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview">
Learn how Skills work across Claude products
</Card>
<Card title="Use Skills in the Agent SDK" icon="cube" href="https://docs.claude.com/en/docs/agent-sdk/skills">
Use Skills programmatically with TypeScript and Python
</Card>
<Card title="Get started with Agent Skills" icon="rocket" href="https://docs.claude.com/en/docs/agents-and-tools/agent-skills/quickstart">
Create your first Skill
</Card>
</CardGroup>
+616
View File
@@ -0,0 +1,616 @@
# JIT Context Filtering: Post-Mortem
**Date:** November 9, 2025
**Duration:** 3.5 hours (7:45 PM - 11:11 PM)
**Branches:** `feature/jit-context`, `failed/jit-context`
**Status:** Failed, reverted to main
**Commits:**
- `3ac0790` - feat: Implement JIT context hook for user prompt submission
- `adf7bf4` - Refactor JIT context handling in SDKAgent and WorkerService
## Executive Summary
Attempted to implement JIT (Just-In-Time) context filtering—a feature that would dynamically generate relevant context timelines on every user prompt, potentially replacing the static session-start context entirely. After multiple architectural iterations spanning 3.5 hours and adding ~2,850 lines of code, the implementation was abandoned and reverted. The revert was not due to lack of vision (the feature aligns with long-term architectural goals), but due to implementation complexity and the need for a simpler initial approach. Significant architectural knowledge was gained about hook limitations, worker patterns, and proper separation of concerns.
## What We Tried to Build
### Goal
When a user submits a prompt, dynamically generate a relevant context timeline instead of the static session-start context. Use the fast search infrastructure (SQLite FTS5 + ChromaDB) to fetch precisely relevant context on-demand.
### The Vision
**Current approach:** SessionStart hook loads 50 recent observations blindly, displays them all.
**Proposed approach:** UserPromptSubmit hook analyzes the prompt, queries the timeline search API, and loads only the relevant context window dynamically.
**Why this makes sense:**
- We already have fast search: SQLite FTS5 + Chroma semantic search
- Dynamic context timeline search is implemented and tested
- Search results come back in <200ms
- Could **replace** session-start context entirely with smarter, prompt-specific context
### User Experience
```
User types: "How did we fix the authentication bug?"
Behind the scenes:
1. Analyze prompt: "authentication bug fix"
2. Query timeline search for relevant period
3. Load 5-10 observations from that specific timeline
4. Inject as context
5. Claude answers with precisely relevant historical context
vs. Current:
Load 50 most recent observations regardless of relevance
```
### Why Checkbox Settings Became Less Important
Originally asked for checkboxes to customize session-start context display. But if JIT context could replace session-start context with intelligent, prompt-specific timelines, the display customization became a non-issue.
## Architectural Attempts
### Attempt 1: Hook-Based Filtering (7:45 PM - 9:30 PM)
**Approach:** Call Agent SDK `query()` directly in `new-hook.ts` during UserPromptSubmit event.
**Implementation:**
- Created `jit-context-hook.ts` (~432 lines)
- Added `generateJitContext()` function in hook
- Called SDK `query()` with observation list and user prompt
- Expected hook to block for ~1-2s while Haiku filters
**Failure:**
```
Error: Claude Code executable not found at
/Users/alexnewman/.claude/plugins/marketplaces/thedotmack/plugin/scripts/cli.js
```
**Root Cause:** Hooks run in sandboxed environment without access to `claudePath` (path to Claude Code executable). The Agent SDK requires this path, which is only available in the worker service.
**Architectural Violation:** This broke the established pattern where hooks handle orchestration and workers handle AI processing. The `save-hook` sets the precedent: hooks capture data, send to worker, worker runs SDK queries asynchronously.
### Attempt 2: Worker-Based with Simple Queries (9:30 PM - 10:30 PM)
**Approach:** Move JIT filtering to worker service, keep it simple with per-request SDK queries.
**Implementation:**
- Documented architecture fix plan in `docs/jit-context-architecture-fix.md`
- Moved `generateJitContext()` to worker (considered creating `src/services/worker/JitContext.ts`)
- Modified `/sessions/:id/init` endpoint to accept `jitEnabled` flag
- Worker would run one-shot SDK query per prompt
**Architecture:**
```
UserPromptSubmit → new-hook → POST /sessions/:id/init { jitEnabled: true }
Worker spawns Claude Haiku
Filters 50 obs → 3-5 IDs
Returns { context: [...] }
Hook injects context → Claude
```
**Issues Identified:**
- Each filter request spawns a new Claude subprocess (~200-500ms overhead)
- Observation list re-sent on every prompt (~5-10KB per request)
- No token caching between requests
- Performance worse than just loading all observations directly
**Decision:** Pivoted to persistent sessions to solve performance issues.
### Attempt 3: Persistent JIT Sessions (10:30 PM - 11:11 PM)
**Approach:** Create a long-lived Agent SDK session that persists throughout user session, similar to main memory session pattern.
**Implementation (291 new lines in SDKAgent.ts):**
1. **Session Lifecycle:**
- Added `jitSessionId`, `jitAbortController`, `jitGeneratorPromise` to `ActiveSession` interface
- `startJitSession()`: Creates persistent SDK session at session init
- `cleanupJitSession()`: Terminates JIT session at session end
2. **Request Queue Architecture:**
- `jitFilterQueues` Map: Per-session request queues
- `JITFilterRequest` interface: `{ userPrompt, resolve, reject }`
- EventEmitter coordination: Wake generator when new requests arrive
3. **Message Generator Pattern:**
- `createJitMessageGenerator()`: Async generator that yields filter requests
- Initial prompt: Load 50 observations, wait for "READY" response
- Loop: Wait for EventEmitter signal → yield user prompt → parse response → resolve promise
- Pattern: Persistent session stays alive between requests
4. **Filter Query Flow:**
```typescript
runFilterQuery(sessionDbId, userPrompt) {
// Queue request
queue.requests.push({ userPrompt, resolve, reject });
queue.emitter.emit('request');
// Wait for response (30s timeout)
return Promise.race([
new Promise((resolve, reject) => { /* queued */ }),
timeout(30000)
]);
}
```
5. **Response Processing:**
- `processJitFilterResponse()`: Accumulate streaming text
- Parse IDs: "1,5,23,41" or "NONE"
- Resolve queued promise with ID array
**Added Files:**
- `src/services/worker/SDKAgent.ts`: +291 lines
- `src/services/worker-types.ts`: +3 fields (jit state tracking)
- `src/services/worker/SessionManager.ts`: +26 lines (JIT cleanup)
- `src/services/worker-service.ts`: +102 lines (JIT initialization)
- `src/shared/settings.ts`: +65 lines (JIT config)
- `src/hooks/jit-context-hook.ts`: +208 lines (orchestration)
- `docs/jit-context-architecture-fix.md`: +265 lines
- `context/session-pattern-parity.md`: +298 lines
**Total Changes:** 18 files, +2,852 lines, -133 lines
**Final Status at Revert:** Implementation was complete and likely functional, but...
## Why It Failed
### 1. Architectural Complexity Explosion
**Problem:** The persistent session pattern added enormous complexity for marginal benefit.
**Evidence:**
- Parallel session management: Regular + JIT sessions running concurrently
- Complex coordination: EventEmitter + promise queues + generator pattern
- Lifecycle coupling: Session init, request handling, cleanup all intertwined
- State explosion: 3 new fields per session (`jitSessionId`, `jitAbortController`, `jitGeneratorPromise`)
**Code Smell:** When the "optimization" requires 300 lines of coordination code, it's probably not an optimization.
### 2. Premature Optimization
**YAGNI Violation:** Built elaborate token caching and persistent session architecture before proving the feature provided value.
**Reality Check:**
- **Current approach:** Load 50 observations = ~25KB context, works fine
- **JIT overhead:** Haiku query = 1-2s latency + coordination complexity
- **User benefit:** Unclear—users haven't complained about context relevance
- **Token savings:** Marginal—Claude caches long contexts efficiently anyway
**Quote from CLAUDE.md:**
> "Write the dumb, obvious thing first. Add complexity only when you actually hit the problem."
We didn't hit a problem. We invented one.
### 3. Implementation Complexity, Not Vision
**The Vision is Sound:**
- Dynamic context is better than static context
- Timeline search API exists and is fast
- Infrastructure (SQLite + Chroma) can support this
- Replacing session-start context with prompt-specific context makes sense
**The Problem:**
We jumped to the complex persistent-session approach without trying the simple per-request approach first.
**What We Should Have Done:**
```typescript
// Simple version (not tried):
app.post('/sessions/:id/init', async (req, res) => {
const { userPrompt } = req.body;
// Query timeline search API (already exists, fast)
const timeline = await timelineSearch(project, userPrompt, depth=10);
// Return observations
return res.json({ context: timeline });
});
```
**This would have:**
- Validated the feature's value quickly
- Used existing infrastructure
- Avoided all the persistence complexity
- Taken 30 minutes instead of 3.5 hours
### 4. Pattern Divergence
**Inconsistency:** JIT sessions work fundamentally differently from memory sessions.
**Memory Session Pattern:**
```typescript
// One-shot: Init → Process observations → Complete
startSession() → yield prompts → parse responses → complete
```
**JIT Session Pattern:**
```typescript
// Persistent: Init → Wait indefinitely → Process on-demand → Complete
startJitSession() → yield initial load → LOOP:
- Wait for EventEmitter signal
- Yield filter request
- Parse response
- Resolve promise
- GOTO LOOP
```
**Maintenance Burden:** Two completely different session patterns means:
- Doubled testing complexity
- Increased cognitive load for contributors
- Higher risk of subtle bugs in lifecycle management
**Session Pattern Parity Document:** The 298-line `session-pattern-parity.md` was created to document the differences—a sign that maybe they shouldn't be different.
### 5. Blocking I/O in Critical Path
**Performance Impact:** Every user prompt now blocks for 1-2s waiting for Haiku filtering.
**Current Flow:**
```
User types prompt → 10ms → Claude responds
```
**JIT Flow:**
```
User types prompt → 10ms init → 1-2s Haiku filter → Claude responds
```
**User Experience:** We added 1-2 seconds of latency to every interaction for questionable benefit.
**Alternative:** If context filtering is valuable, do it asynchronously and apply to next prompt.
### 6. Missing the Forest for the Trees
**Real Issue:** We focused on technical implementation without asking strategic questions:
- **Is context relevance actually a problem?** No evidence.
- **Do users want this?** No feedback requested.
- **Is 50 observations too many?** Not proven.
- **Does filtering improve responses?** Not tested.
**Anti-Pattern:** Solution in search of a problem.
## What We Should Have Done
### Option 1: Don't Build It
**Justification:** No validated user need. Current system works fine.
**Next Step:** Wait for user feedback indicating context relevance is an issue.
### Option 2: Simple MVP
If we really wanted to explore this:
1. **Week 1:** Add basic filtering in worker with one-shot queries
- Accept slight performance hit (~500ms overhead)
- Measure filter accuracy and user impact
- Gather feedback
2. **Week 2:** If proven valuable, optimize
- Add token caching only if needed
- Consider persistent sessions only if performance is bottleneck
3. **Week 3:** If still valuable, scale
- Polish error handling
- Add configuration options
- Document patterns
**Philosophy:** Incremental validation, not big-bang architecture.
### Option 3: Different Approach Entirely
**Alternative:** Pre-computed relevance scores
Instead of on-demand filtering:
- Score observations at creation time (save-hook)
- Store relevance embeddings in Chroma
- At session start, query Chroma with user's first prompt
- Load top 10-20 most relevant observations
- No runtime latency, better accuracy, simpler architecture
**Benefit:** Leverages existing Chroma infrastructure, avoids runtime overhead.
## Technical Lessons Learned
### 1. EventEmitter Coordination Anti-Pattern
**Code:**
```typescript
queue.emitter.on('request', () => {
// Wake up generator to process request
});
```
**Issue:** Complex async coordination using event-driven wakeup signals is hard to reason about.
**Better:** Use async queues or channels (e.g., `async-queue` package) that handle coordination internally.
### 2. Generator Pattern Complexity
**Pattern:**
```typescript
async *createJitMessageGenerator() {
yield initialPrompt;
while (!aborted) {
await waitForEvent(); // Blocks here
yield nextRequest;
}
}
```
**Tradeoff:** Generators are great for iteration, but terrible for event-driven request/response patterns.
**Better:** Use explicit session object with `sendMessage()/waitForResponse()` methods.
### 3. Dual Session Management
**Complexity:** Managing two concurrent SDK sessions per user session is inherently complex.
**Alternatives Considered:**
- Single session handling both observations and filtering (rejected: tight coupling)
- Separate service for filtering (rejected: too much infrastructure)
- Pre-computed filtering (not considered: should have been)
**Lesson:** When parallel state management feels hard, question whether you need parallel state.
### 4. Promise Queue Pattern
**Implementation:**
```typescript
interface QueuedRequest {
resolve: (result: T) => void;
reject: (error: Error) => void;
}
queue.push({ resolve, reject });
// Later...
queue[0].resolve(result);
```
**Good:** Clean async API for callers
**Bad:** Easy to leak promises if error handling isn't perfect
**Improvement:** Use libraries like `p-queue` that handle edge cases
## Process Lessons Learned
### 1. No Incremental Validation
**Mistake:** Went from "idea" to "complete architecture" without validation points.
**Better Process:**
1. Write one-pager explaining user value
2. Build simplest possible version (2 hours max)
3. Test with real usage
4. Measure impact
5. Decide: kill, iterate, or scale
**Checkpoint Questions:**
- After 1 hour: "Does this solve a real problem?"
- After 2 hours: "Is this getting too complex?"
- After 3 hours: "Should I just ship the simple version?"
### 2. Architecture Astronomy
**Definition:** Designing elaborate systems without building/testing them.
**Evidence:**
- 265-line architecture doc written before any code
- 298-line session pattern parity analysis
- Multiple complete rewrites of the same feature
**Better:** Code first, document later. Spike solutions, learn from implementation.
### 3. Sunk Cost Fallacy
**Timeline:**
- **Hour 1:** "This seems complex but achievable"
- **Hour 2:** "We're halfway done, can't stop now"
- **Hour 3:** "Just need to fix this one coordination issue"
- **Hour 4:** "It's working, but... this feels wrong"
**Correct Decision:** Revert. Took courage to throw away 4 hours of work.
**Learning:** Time invested is not a reason to continue. Quality of outcome matters more.
### 4. Missing User Feedback Loop
**No User Input:**
- Didn't ask: "Is context relevance a problem for you?"
- Didn't test: "Does filtered context improve your responses?"
- Didn't measure: "Are you hitting context limits?"
**Engineering Theater:** Building impressive-sounding features without user validation.
## What We Actually Learned (The Real Value)
Despite reverting, this was productive R&D:
### 1. Deep Understanding of Hook Architecture
**Critical Discovery:** Hooks run in sandboxed environment without `claudePath`.
- Hooks cannot call Agent SDK `query()` directly
- All AI processing must happen in worker service
- This architectural constraint is now documented
**Learned Pattern:**
```
Hook (orchestration) → Worker (AI processing)
✓ save-hook: Captures data → Worker processes with SDK
✓ new-hook: Creates session → Worker returns confirmation
✗ jit-hook: Tried SDK in hook → Failed, no claudePath
```
**Value:** Future features will avoid this mistake. We now know the boundary.
### 2. Worker Architecture Patterns
**Blocking vs. Non-Blocking:**
- SessionStart: Can be non-blocking (context loads async)
- UserPromptSubmit: Must be blocking (session must exist before processing)
- JIT Context: Must be blocking (context needed before prompt processed)
**Established Pattern:**
```typescript
// Worker endpoint for features requiring AI
app.post('/sessions/:id/operation', async (req, res) => {
const { operationData } = req.body;
const result = await sdkAgent.performOperation(operationData);
return res.json({ result });
});
```
### 3. Persistent Session Management
**Architecture Knowledge Gained:**
- How to maintain long-lived SDK sessions
- EventEmitter coordination patterns for request/response
- Promise queue management for async operations
- Proper cleanup with AbortControllers
**Pattern Documented:**
- Dual session management (regular + JIT)
- Generator-based message loops
- Request queuing with timeouts
**Value:** When we build the simpler version, we'll know these patterns.
### 4. Configuration Infrastructure
`src/shared/settings.ts` (65 lines) provides reusable configuration patterns:
```typescript
export function getConfigValue(key: string, defaultValue: string): string {
// Priority: settings.json → env var → default
}
```
**Kept After Revert:** This module is useful for other features.
### 5. Key Architectural Decisions Made
**Decisions that will guide future implementation:**
1. JIT context filtering must happen in worker (proven via failed hook attempt)
2. Context must be blocking on UserPromptSubmit (session needs context before processing)
3. Dynamic timeline search is the right approach (fast, precise, leverages existing infrastructure)
4. Simple per-request queries should be tried before persistent sessions
### 6. Documentation Quality
- `jit-context-architecture-fix.md`: Documents why hooks can't run SDK queries
- `session-pattern-parity.md`: Reference for implementing dual sessions
- Hooks reference: Comprehensive hook documentation added
**Value:** These docs help future contributors understand the system constraints.
### 7. Infrastructure Validation
**Confirmed that our search stack is ready:**
- SQLite FTS5: Fast full-text search (<50ms)
- ChromaDB: Semantic search (<200ms with 8,000+ vectors)
- Timeline search API: Already implemented and tested
- Worker service: Can handle synchronous AI operations
**The infrastructure exists. We just need a simpler integration.**
## Recommendations
### Immediate Actions
1. **Archive the work:**
- Keep `failed/jit-context` branch for reference
- Extract reusable components (settings.ts)
- Save architecture docs for future features
2. **Document the anti-patterns:**
- Add this post-mortem to CLAUDE.md references
- Update coding standards with lessons learned
3. **Reset focus:**
- Return to validated user needs
- Prioritize features with clear value propositions
### Future Feature Development
**Gating Questions (Answer before coding):**
1. **User Value:** What specific user problem does this solve?
2. **Evidence:** Have users requested this or reported the underlying issue?
3. **Measurement:** How will we know if it's successful?
4. **Simplicity:** What's the dumbest version that could work?
5. **Time Limit:** If we can't prove value in 2 hours, should we build it?
**Process:**
```
VALIDATE → BUILD SIMPLE → TEST → MEASURE → DECIDE
↑ ↓
└──────────── ITERATE OR KILL ────────────┘
```
### If Context Filtering Returns
Should we revisit this idea in the future:
**Prerequisites:**
- User feedback requesting better context relevance
- Metrics showing current context is too broad
- Evidence that filtering improves response quality
**Simple Approach:**
```typescript
// In worker-service.ts /sessions/:id/init
if (jitEnabled) {
const observations = await db.getRecentObservations(project, 50);
const filtered = await simpleFilter(observations, userPrompt); // One-shot query
return { context: filtered };
}
```
**Acceptance Criteria:**
- <100 lines of code
- <500ms latency impact
- No new session types
- Degrades gracefully on errors
**If that works:** Then consider optimization.
## Conclusion
JIT context filtering failed not because the vision was wrong, but because we jumped to the complex implementation without validating the simple one first. The feature aligns with long-term goals (dynamic, prompt-specific context using our fast search infrastructure), but the persistent-session architecture was premature optimization.
**The right call:** Revert the complex implementation. Build the simple version when ready.
**Key Takeaway:** The vision is sound. The execution was overcomplicated. We now have:
- Deep knowledge of hook/worker architecture constraints
- Documented patterns for persistent SDK sessions
- Validated fast search infrastructure
- Clear understanding of what to build next time (simple timeline search API integration)
**This was R&D, not failure.** We learned what doesn't work (SDK in hooks), what does work (worker-based AI processing), and how to approach it next time (simple API calls before persistent sessions).
**Next Implementation:**
When we revisit this (and we should), start with:
1. Worker endpoint that accepts prompt
2. Queries existing timeline search API
3. Returns context
4. Hook injects context
5. Validate it improves responses
6. Then optimize if needed
**Final Thought:** Sometimes you have to build the wrong thing to understand the right thing. That's R&D.
---
**Branch Status:**
- `feature/jit-context`: Abandoned
- `failed/jit-context`: Archived for reference
- `main`: Stable at v5.4.0
**Files to Keep:**
- `src/shared/settings.ts`: Reusable config utilities
**Files Discarded:**
- Everything else (+2,850 lines)
**Emotional State:** Relieved. Dodged a maintenance nightmare.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,857 @@
# Search Architecture Analysis
**Date:** 2025-11-11 **Scope:** HTTP API endpoints, MCP search server, DRY violations, architectural recommendations
---
## Current State: Dual Search Architectures
### Architecture Overview
```
┌─────────────────────────────────────────────────────────────┐
│ Claude Code Session │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ mem-search Skill (ACTIVE) │ │
│ │ - Uses HTTP API via curl commands │ │
│ │ - 10 search operations │ │
│ │ - Progressive disclosure workflow │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ │ HTTP GET │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ MCP Search Server (DEPRECATED but BUILT) │ │
│ │ - .mcp.json configured │ │
│ │ - search-server.mjs exists (74KB) │ │
│ │ - 9 MCP tools defined │ │
│ │ - Not used by skill │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌───────────┴───────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ Worker Service │ │ MCP Server │
│ (worker-service.ts) │ │ (search-server.ts) │
│ │ │ │
│ 10 HTTP Endpoints: │ │ 9 MCP Tools: │
│ ├─ /api/search/ │ │ ├─ search_observations │
│ │ observations │ │ ├─ search_sessions │
│ ├─ /api/search/ │ │ ├─ search_user_prompts │
│ │ sessions │ │ ├─ find_by_concept │
│ ├─ /api/search/ │ │ ├─ find_by_file │
│ │ prompts │ │ ├─ find_by_type │
│ ├─ /api/search/ │ │ ├─ get_recent_context │
│ │ by-concept │ │ ├─ get_context_timeline │
│ ├─ /api/search/ │ │ └─ get_timeline_by_query│
│ │ by-file │ │ │
│ ├─ /api/search/ │ │ Built: ✅ │
│ │ by-type │ │ Used: ❌ │
│ ├─ /api/context/recent │ │ Configured: ✅ │
│ ├─ /api/context/ │ │ Status: DEPRECATED │
│ │ timeline │ │ │
│ ├─ /api/timeline/ │ │ │
│ │ by-query │ │ │
│ └─ /api/search/help │ │ │
│ │ │ │
│ Built: ✅ │ │ │
│ Used: ✅ │ │ │
│ Status: ACTIVE │ │ │
└──────────────────────────┘ └──────────────────────────┘
│ │
└─────────┬─────────────────┘
┌────────────────────────────────┐
│ SessionSearch (Shared Layer) │
│ - FTS5 queries │
│ - SQLite operations │
│ - Common data access │
└────────────────────────────────┘
┌────────────────────────────────┐
│ SQLite Database │
│ ~/.claude-mem/claude-mem.db │
└────────────────────────────────┘
```
---
## HTTP Endpoints Architecture
### Location
`src/services/worker-service.ts` (lines 108-118, 748-1174)
### Endpoints (10 total)
| Endpoint | Method | Purpose | Used By |
| -------------------------- | ------ | ----------------------------------- | ---------------- |
| `/api/search/observations` | GET | Full-text search observations | mem-search skill |
| `/api/search/sessions` | GET | Full-text search session summaries | mem-search skill |
| `/api/search/prompts` | GET | Full-text search user prompts | mem-search skill |
| `/api/search/by-concept` | GET | Find observations by concept tag | mem-search skill |
| `/api/search/by-file` | GET | Find work related to specific files | mem-search skill |
| `/api/search/by-type` | GET | Find observations by type | mem-search skill |
| `/api/context/recent` | GET | Get recent session context | mem-search skill |
| `/api/context/timeline` | GET | Get timeline around point in time | mem-search skill |
| `/api/timeline/by-query` | GET | Search + timeline in one call | mem-search skill |
| `/api/search/help` | GET | API documentation | mem-search skill |
### Implementation Pattern
**Example: Search Observations**
```typescript
// src/services/worker-service.ts:748-781
private handleSearchObservations(req: Request, res: Response): void {
try {
// 1. Parse query parameters
const query = req.query.query as string;
const format = (req.query.format as string) || 'full';
const limit = parseInt(req.query.limit as string, 10) || 20;
const project = req.query.project as string | undefined;
// 2. Validate required parameters
if (!query) {
res.status(400).json({ error: 'Missing required parameter: query' });
return;
}
// 3. Call SessionSearch (shared data layer)
const sessionSearch = this.dbManager.getSessionSearch();
const results = sessionSearch.searchObservations(query, { limit, project });
// 4. Format response based on format parameter
res.json({
query,
count: results.length,
format,
results: format === 'index' ? results.map(r => ({
id: r.id,
type: r.type,
title: r.title,
subtitle: r.subtitle,
created_at_epoch: r.created_at_epoch,
project: r.project,
score: r.score
})) : results
});
} catch (error) {
logger.failure('WORKER', 'Search observations failed', {}, error as Error);
res.status(500).json({ error: (error as Error).message });
}
}
```
### Characteristics
**Pros:**
- ✅ Simple HTTP GET requests (curl-friendly)
- ✅ Standard REST API pattern
- ✅ Easy to test and debug
- ✅ No MCP protocol overhead
- ✅ Works with any HTTP client
**Cons:**
- ⚠️ Parameter parsing duplicated across 10 endpoints
- ⚠️ Format conversion logic duplicated
- ⚠️ Error handling pattern repeated
---
## MCP Search Server Architecture
### Location
`src/servers/search-server.ts` (1,781 lines)
### Status
- **Built:** ✅ Yes (`plugin/scripts/search-server.mjs`, 74KB)
- **Configured:** ✅ Yes (`.mcp.json` line 3-6)
- **Used:** ❌ No (deprecated in v5.4.0)
- **Maintained:** ⚠️ Source kept for reference
### Tools (9 total)
| Tool Name | Purpose | Line |
| ----------------------- | -------------------------------------- | -------- |
| `search_observations` | Search observations with FTS5 + Chroma | 348-422 |
| `search_sessions` | Search session summaries | 438-490 |
| `search_user_prompts` | Search user prompts | 506-558 |
| `find_by_concept` | Find by concept tag | 574-626 |
| `find_by_file` | Find by file path | 642-694 |
| `find_by_type` | Find by observation type | 710-762 |
| `get_recent_context` | Get recent sessions | 778-830 |
| `get_context_timeline` | Get timeline context | 846-950 |
| `get_timeline_by_query` | Search + timeline | 966-1064 |
### Implementation Pattern
**Example: Search Observations (MCP)**
```typescript
// src/servers/search-server.ts:348-422
{
name: 'search_observations',
description: 'Search observations using full-text search across titles, narratives, facts, and concepts...',
inputSchema: z.object({
query: z.string().describe('Search query for FTS5 full-text search'),
format: z.enum(['index', 'full']).default('index').describe('...'),
...filterSchema.shape
}),
handler: async (args: any) => {
try {
const { query, format = 'index', ...options } = args;
let results: ObservationSearchResult[] = [];
// Hybrid search: Try Chroma semantic search first, fall back to FTS5
if (chromaClient) {
try {
// Step 1: Chroma semantic search (top 100)
const chromaResults = await queryChroma(query, 100);
if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days)
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
const recentIds = chromaResults.ids.filter((_id, idx) => {
const meta = chromaResults.metadatas[idx];
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
// Step 3: Hydrate from SQLite
if (recentIds.length > 0) {
const limit = options.limit || 20;
results = store.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit });
}
}
} catch (chromaError: any) {
console.error('[search-server] Chroma query failed, falling back to FTS5:', chromaError.message);
}
}
// Fall back to FTS5 if Chroma unavailable or returned no results
if (results.length === 0) {
results = search.searchObservations(query, options);
}
// Format results
if (format === 'index') {
return {
content: [{
type: 'text',
text: results.map((r, i) => formatObservationIndex(r, i)).join('\n\n') + formatSearchTips()
}]
};
} else {
return {
content: results.map(r => ({
type: 'resource',
resource: {
uri: `claude-mem://observation/${r.id}`,
mimeType: 'text/markdown',
text: formatObservationResult(r)
}
}))
};
}
} catch (error: any) {
return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
}
}
}
```
### Characteristics
**Pros:**
- ✅ MCP protocol support
- ✅ Hybrid search (Chroma + FTS5)
- ✅ Rich formatting (markdown, resources)
- ✅ Comprehensive error handling
**Cons:**
- ❌ Not used by skill (deprecated)
- ❌ ~2,500 token overhead for tool definitions
- ❌ More complex than HTTP
- ❌ Still being built despite deprecation
---
## DRY Violation Analysis
### Areas of Duplication
#### 1. **Parameter Parsing** (10 HTTP endpoints + 9 MCP tools)
**HTTP Endpoints:**
```typescript
// Repeated in each endpoint handler
const query = req.query.query as string;
const format = (req.query.format as string) || "full";
const limit = parseInt(req.query.limit as string, 10) || 20;
const project = req.query.project as string | undefined;
if (!query) {
res.status(400).json({ error: "Missing required parameter: query" });
return;
}
```
**MCP Tools:**
```typescript
// Repeated in each tool handler
const { query, format = "index", ...options } = args;
if (!query) {
throw new Error("Missing required parameter: query");
}
```
**Violation:** Parameter parsing logic duplicated 19 times (10 + 9)
#### 2. **Format Conversion** (Index vs Full)
**HTTP Endpoints:**
```typescript
results: format === "index"
? results.map((r) => ({
id: r.id,
type: r.type,
title: r.title,
subtitle: r.subtitle,
created_at_epoch: r.created_at_epoch,
project: r.project,
score: r.score,
}))
: results;
```
**MCP Tools:**
```typescript
if (format === "index") {
return {
content: [
{
type: "text",
text: results.map((r, i) => formatObservationIndex(r, i)).join("\n\n"),
},
],
};
} else {
return {
content: results.map((r) => ({
type: "resource",
resource: {
uri: `claude-mem://observation/${r.id}`,
mimeType: "text/markdown",
text: formatObservationResult(r),
},
})),
};
}
```
**Violation:** Format conversion logic duplicated with different output formats
#### 3. **Search Logic Duplication**
**HTTP Endpoints:**
```typescript
const sessionSearch = this.dbManager.getSessionSearch();
const results = sessionSearch.searchObservations(query, { limit, project });
```
**MCP Tools:**
```typescript
// Hybrid search with Chroma fallback
if (chromaClient) {
const chromaResults = await queryChroma(query, 100);
// ... complex hybrid logic ...
}
if (results.length === 0) {
results = search.searchObservations(query, options);
}
```
**Violation:** MCP has hybrid Chroma+FTS5 search, HTTP only has FTS5
#### 4. **Error Handling**
**HTTP Endpoints:**
```typescript
try {
// ... handler logic ...
} catch (error) {
logger.failure("WORKER", "Search observations failed", {}, error as Error);
res.status(500).json({ error: (error as Error).message });
}
```
**MCP Tools:**
```typescript
try {
// ... handler logic ...
} catch (error: any) {
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
}
```
**Violation:** Different error handling patterns
### DRY Compliance at Data Layer ✅
**Good news:** Both architectures use the **same data layer**:
```
HTTP Endpoints → SessionSearch → SQLite
MCP Tools → SessionSearch → SQLite
```
The `SessionSearch` class is the **single source of truth** for data access. No duplication there.
---
## Is curl the Best Approach?
### Current Approach: curl Commands
**Example from skill:**
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"
```
### Alternative Approaches
#### 1. **MCP Tools** (Deprecated)
**Pros:**
- Native Claude Code protocol
- Rich type definitions
- Better error handling
- Resource formatting
**Cons:**
- ❌ ~2,500 token overhead per session
- ❌ More complex to implement
- ❌ Requires MCP server process
- ❌ Less accessible for external tools
**Verdict:** MCP was deprecated for good reasons (token overhead). curl is better.
#### 2. **Direct Database Access** (Not feasible)
**Pros:**
- No HTTP overhead
- No worker process needed
**Cons:**
- ❌ Skills can't access files directly
- ❌ No way to execute TypeScript/SQLite from skill
- ❌ Would require building native bindings
**Verdict:** Not possible with current skill architecture.
#### 3. **HTTP API via curl** (Current) ✅
**Pros:**
- ✅ Simple, standard protocol
- ✅ Works with skill architecture
- ✅ Easy to test (curl in terminal)
- ✅ Language-agnostic
- ✅ No MCP token overhead
- ✅ RESTful design
**Cons:**
- ⚠️ Requires worker service running
- ⚠️ HTTP parsing overhead (minimal)
**Verdict:** **Best approach given constraints.**
### Why curl is Optimal
1. **Skill Constraints:** Skills can only execute shell commands. curl is the standard HTTP client.
2. **Token Efficiency:** No tool definitions loaded into context (~2,250 token savings).
3. **Progressive Disclosure:** Skill loads gradually, HTTP requests are made only when needed.
4. **Debuggability:** Easy to test endpoints manually with curl.
5. **Cross-platform:** curl available on all platforms.
---
### Question: "Is it routing into the search-service MCP file or is it a DRY violation?"
**Answer:** Both architectures exist, creating a DRY violation:
1. **HTTP Endpoints** (worker-service.ts) ← **Used by skill**
2. **MCP Server** (search-server.ts) ← **Deprecated but still built**
### Current State
```
mem-search skill → HTTP API (worker-service.ts) → SessionSearch → SQLite
MCP search server (deprecated) → SessionSearch ──────────────────────┘
```
Both use the same data layer (SessionSearch), but:
- ❌ Parameter parsing duplicated
- ❌ Format conversion duplicated
- ❌ MCP has hybrid Chroma search, HTTP doesn't
- ❌ MCP still being built despite deprecation
**You said:** "We are intentionally exposing API search endpoints
```
┌─────────────────────────────────────────────────────────────┐
│ - Web UI │
│ - Mobile app │
│ - VS Code extension │
│ - CLI tools │
└─────────────────────────────────────────────────────────────┘
│ HTTP API
┌─────────────────────────────────────────────────────────────┐
│ Worker Service HTTP API │
│ localhost:37777/api/search/* │
│ │
│ - Standard REST endpoints │
│ - JSON responses │
│ - Query parameter API │
│ - format=index/full support │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SessionSearch + ChromaSync │
│ (Shared data layer) │
└─────────────────────────────────────────────────────────────┘
```
- Standard REST API
- Easy to consume from any language/platform
- Already supports format=index/full for token efficiency
- Well-documented in skill operation guides
- Clean JSON responses
---
## Architectural Recommendations
### Immediate Actions
#### 1. **Remove MCP Search Server** (Reduce Maintenance Burden)
**Problem:**
- MCP server is deprecated but still being built
- Adds 1,781 lines of maintenance burden
- Creates confusion about which search to use
- DRY violation with HTTP endpoints
**Recommendation:**
```bash
# Remove from build pipeline
# scripts/build-hooks.js - already commented out, make permanent
# Delete configuration
rm plugin/.mcp.json
# Archive source (don't delete, keep for reference)
git mv src/servers/search-server.ts archive/search-server.ts.archived
# Remove built file
rm plugin/scripts/search-server.mjs
```
**Impact:**
- ✅ Reduces build time
- ✅ Eliminates confusion
- ✅ Reduces maintenance burden
- ✅ Removes DRY violation
- ⚠️ Loses hybrid Chroma search in MCP (but HTTP doesn't have it anyway)
#### 2. **Add Hybrid Search to HTTP Endpoints** (Feature Parity)
**Problem:** MCP server has Chroma hybrid search, HTTP endpoints don't
**Recommendation:**
```typescript
// src/services/worker-service.ts
private async handleSearchObservations(req: Request, res: Response): Promise<void> {
try {
const { query, format, limit, project } = this.parseSearchParams(req);
// Try hybrid search first if Chroma available
let results = await this.hybridSearch(query, { limit, project });
// Fallback to FTS5 if Chroma unavailable
if (results.length === 0) {
const sessionSearch = this.dbManager.getSessionSearch();
results = sessionSearch.searchObservations(query, { limit, project });
}
res.json(this.formatSearchResponse(query, results, format));
} catch (error) {
this.handleSearchError(res, 'Search observations failed', error);
}
}
// Extract shared methods
private parseSearchParams(req: Request): SearchParams { /* ... */ }
private async hybridSearch(query: string, options: SearchOptions): Promise<any[]> { /* ... */ }
private formatSearchResponse(query: string, results: any[], format: string): any { /* ... */ }
private handleSearchError(res: Response, message: string, error: any): void { /* ... */ }
```
**Impact:**
- ✅ Adds Chroma semantic search to HTTP API
- ✅ Makes HTTP API feature-complete
#### 3. **Extract Shared Search Logic** (DRY Refactoring)
**Problem:** 10 HTTP endpoints have duplicated parameter parsing and formatting
**Recommendation:**
```typescript
// src/services/search/SearchController.ts (new file)
export class SearchController {
constructor(private sessionSearch: SessionSearch, private chromaSync: ChromaSync) {}
async searchObservations(params: SearchParams): Promise<SearchResponse> {
// Shared logic for observations search
const results = await this.hybridSearch(params);
return this.formatResponse(results, params.format);
}
async searchSessions(params: SearchParams): Promise<SearchResponse> {
// Shared logic for sessions search
}
// ... other search methods
private async hybridSearch(params: SearchParams): Promise<any[]> {
// Shared hybrid search logic
}
private formatResponse(results: any[], format: "index" | "full"): SearchResponse {
// Shared formatting logic
}
private parseParams(req: Request): SearchParams {
// Shared parameter parsing
}
}
```
**Usage in worker-service.ts:**
```typescript
private searchController: SearchController;
private handleSearchObservations(req: Request, res: Response): void {
try {
const params = this.searchController.parseParams(req);
const response = await this.searchController.searchObservations(params);
res.json(response);
} catch (error) {
this.handleSearchError(res, error);
}
}
```
**Impact:**
- ✅ Eliminates 90% of duplication across 10 endpoints
- ✅ Single source of truth for search logic
- ✅ Easier to test (test controller, not HTTP layer)
- ✅ Easier to maintain
- ✅ Easier to add new search endpoints
### Long-term Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Clients │
│ ┌──────────────┬──────────────┬──────────────────────┐ │
│ │ Skill │ Frontend │ (CLI, IDE plugins) │ │
│ └──────────────┴──────────────┴──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ HTTP API (REST)
┌─────────────────────────────────────────────────────────────┐
│ WorkerService (Express.js) │
│ │
│ Route Layer (thin) │
│ ├─ GET /api/search/observations │
│ ├─ GET /api/search/sessions │
│ └─ ... (delegates to controller) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SearchController (business logic) │
│ │
│ ├─ searchObservations() │
│ ├─ searchSessions() │
│ ├─ hybridSearch() - Chroma + FTS5 │
│ ├─ formatResponse() - index/full conversion │
│ └─ parseParams() - parameter validation │
└─────────────────────────────────────────────────────────────┘
┌───────────┴───────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ SessionSearch (FTS5) │ │ ChromaSync (Vectors) │
│ - searchObservations() │ │ - queryByEmbedding() │
│ - searchSessions() │ │ - 90-day recency filter │
│ - searchPrompts() │ │ - Hydrate from SQLite │
└──────────────────────────┘ └──────────────────────────┘
│ │
└─────────┬─────────────────┘
┌────────────────────────────────┐
│ SQLite Database │
│ ~/.claude-mem/claude-mem.db │
└────────────────────────────────┘
```
---
## Summary
### Current Architecture Issues
1.**Dual search implementations** (HTTP + deprecated MCP)
2.**DRY violations** across 19 search handlers
3.**MCP server still built** despite deprecation
4.**HTTP missing hybrid Chroma search** (MCP has it)
5.**No shared controller layer** for search logic
### Is curl the Best Approach?
**Yes.**
Given the constraints:
- Skills can only execute shell commands
- Token efficiency vs MCP (~2,250 token savings)
- Standard REST pattern, easy to consume
curl + HTTP API is the optimal architecture.
### Is it Routing into search-service or DRY Violation?
**DRY violation.**
Both architectures exist and duplicate logic:
- HTTP endpoints (worker-service.ts) ← ACTIVE
- MCP server (search-server.ts) ← DEPRECATED but BUILT
They share the data layer (SessionSearch) but duplicate:
- Parameter parsing
- Format conversion
- Error handling
- Search orchestration (MCP has Chroma, HTTP doesn't)
### Recommendations Priority
**High Priority:**
1. ✅ Remove MCP search server entirely (archive source)
2. ✅ Add hybrid Chroma search to HTTP endpoints
3. ✅ Extract SearchController for shared logic
**Medium Priority:**
5. Add API versioning (/api/v1/search/\*)
6. Add rate limiting for external access
**Low Priority:** 7. OpenAPI/Swagger documentation
9. WebSocket support for real-time search
### Action Plan
**Phase 1: Cleanup (1 day)**
- Remove .mcp.json
- Archive search-server.ts
- Update CLAUDE.md to reflect removal
- Update build scripts to skip MCP server
**Phase 2: Feature Parity (2 days)**
- Port hybrid Chroma search from MCP to HTTP
- Test all 10 endpoints with hybrid search
- Update skill documentation
**Phase 3: DRY Refactoring (3 days)**
- Create SearchController class
- Extract shared logic (parsing, formatting, errors)
- Refactor 10 HTTP handlers to use controller
- Add comprehensive tests
- Document API for external consumption
- Add authentication/authorization (if needed)
- Add rate limiting
- Create OpenAPI spec
---
## Files Referenced
**Active:**
- `src/services/worker-service.ts` - HTTP endpoints (1,338 lines)
- `src/services/sqlite/SessionSearch.ts` - FTS5 search
- `src/services/sync/ChromaSync.ts` - Vector search
- `plugin/skills/mem-search/SKILL.md` - Skill using HTTP API
**Deprecated:**
- `src/servers/search-server.ts` - MCP tools (1,781 lines)
- `plugin/.mcp.json` - MCP configuration
- `plugin/scripts/search-server.mjs` - Built MCP server (74KB)
**Configuration:**
- `CLAUDE.md` line 314 - Deprecation notice
- `CHANGELOG.md` line 32-52 - v5.4.0 migration
- `scripts/build-hooks.js` - Build pipeline (MCP commented out)
+160
View File
@@ -0,0 +1,160 @@
# Skill Audit Report
**Date:** 2025-11-10
**Validation:** Anthropic's official skill-creator documentation
**Skills Audited:** mem-search, search
## Executive Summary
The mem-search skill achieves 100% compliance across all dimensions. The search skill meets technical requirements but fails effectiveness metrics critical for auto-invocation.
**mem-search:** Production-ready. No changes required.
**search:** Requires three critical fixes before Claude reliably discovers and invokes this skill.
## mem-search Skill Results
**Status:** ✅ PASS
**Compliance:** 100% technical, 100% effectiveness
**Files:** 17 (202-line SKILL.md + 13 operations + 2 principles)
### Strengths
The skill demonstrates exemplary effectiveness engineering:
1. **Trigger Design (85% concrete)**
- Five unique identifiers: claude-mem, PM2-managed database, cross-session memory, session summaries, observations
- Nine scope differentiation keywords
- Explicit boundary: "NOT in the current conversation context"
- Minimal overlap with Claude's native capabilities
2. **Capability Visibility (100%)**
- All nine operations include inline "Use when" examples
- Decision guide reduces complexity from nine operations to five common cases
- No navigation friction
3. **Structure**
- 202 lines (60% under limit)
- Perfect progressive disclosure with token cost documentation
- Clean file organization: operations/ and principles/ directories
- No content duplication
### Issues
**One false positive:** Line 152 contains backslashes in regex notation `(bugfix\|feature\|decision)`. This documents parameter syntax, not Windows paths. No action required.
## search Skill Results
**Status:** ⚠️ NEEDS IMPROVEMENT
**Compliance:** 100% technical, 67% effectiveness
**Files:** 13 (96-line SKILL.md + 12 operations)
### Critical Effectiveness Issues
Three failures prevent reliable auto-invocation:
#### Issue 1: Insufficient Scope Differentiation
**Problem:** Description contains only two differentiation keywords (threshold: ≥3). Claude cannot distinguish this skill from native conversation memory.
**Current description:**
```text
Search claude-mem persistent memory for past sessions, observations, bugs
fixed, features implemented, decisions made, code changes, and previous work.
Use when answering questions about history, finding past decisions, or
researching previous implementations.
```
**Domain overlap analysis:**
- Claude answers natively: "What bugs did we fix?" (current conversation)
- Claude needs skill: "What bugs did we fix last week?" (external database)
**Fix required:**
```text
Search claude-mem's external database of past sessions, observations, and
work from previous conversations. Accesses persistent memory stored outside
current session context - NOT information from today's conversation. Use when
users ask about: (1) previous sessions ("what did we do last week?"),
(2) historical work ("bugs we fixed months ago"), (3) cross-session patterns
("how have we approached this before?"), (4) work already stored in claude-mem
("what's in the database about X?"). Searches FTS5 full-text index across
typed observations (bugfix/feature/refactor/decision/discovery). For current
session memory, use native conversation context instead.
```
This adds eight differentiation keywords: "external database", "past sessions", "previous conversations", "outside current session", "NOT information from today's", "last week", "months ago", "already stored in claude-mem".
#### Issue 2: Weak Trigger Specificity
**Problem:** Only 44% concrete triggers (threshold: >50%). Only one unique identifier (threshold: ≥2).
**Abstract triggers (low specificity):**
- "history" (could mean git history, browser history)
- "past work" (could mean files, commits, documents)
- "decisions" (could mean any decision tracking)
- "previous work" (could mean current session earlier)
- "implementations" (could mean code in current conversation)
**Concrete triggers (high specificity):**
- "claude-mem" (unique system name)
- "persistent memory" (system-specific)
- "sessions" (cross-session concept)
- "observations" (system-specific)
**Concrete ratio:** 4/9 = 44% (fails 50% threshold)
**Fix required:** Add system-specific terminology: "HTTP API", "port 37777", "FTS5 full-text index", "typed observations". See combined description in Issue 1 fix.
#### Issue 3: Wasted Content in Body
**Problem:** Lines 10-22 contain "When to Use This Skill" section in SKILL.md body. This loads AFTER triggering, wastes ~200 tokens, provides no value.
**Reference:** [Anthropic's skill-creator documentation](https://github.com/anthropics/anthropic-quickstarts/tree/main/skill-creator) states: "The body is only loaded after triggering, so 'When to Use This Skill' sections in the body are not helpful to Claude."
**Fix required:** Delete lines 10-22 entirely. Move triggering examples to description field (already included in Issue 1 fix).
### Strengths
The skill demonstrates strong structure:
- Excellent progressive disclosure (96-line navigation hub)
- Strong decision guide (reduces 10 operations to common cases)
- 100% capability visibility (all operations show purpose inline)
- No forbidden files or content duplication
- Clean operations/ directory structure
### Warning
**Minor:** Description uses imperative "Use when" instead of third person. Change to "Useful for" or "Invoked when" for consistency with skill-creator best practices.
## Comparison
| Metric | mem-search | search | Impact |
|--------|-----------|---------|--------|
| Concrete triggers | 85% | 44% | search harder to discover |
| Unique identifiers | 5+ | 1 | search less distinct |
| Scope differentiation | 9 keywords | 2 keywords | **search conflicts with native memory** |
| Body optimization | Clean | Wasted section | search wastes tokens |
| Overall effectiveness | 100% | 67% | search needs fixes |
## Critical Recommendations
The search skill requires three changes before production use:
1. **Rewrite description** to add scope differentiation and concrete triggers (see Issue 1 fix)
2. **Delete lines 10-22** from SKILL.md body
3. **Convert to third person** - change "Use when" to "Useful for"
**Why this matters:** Without scope differentiation, Claude assumes "What bugs did we fix?" refers to current conversation, not the external claude-mem database. This causes systematic under-invocation.
## Reference Implementation
The mem-search skill serves as a reference implementation for:
- Trigger design with explicit scope boundaries
- Progressive disclosure with token efficiency documentation
- Inline capability visibility eliminating navigation friction
- Decision guides reducing cognitive load
Study mem-search when creating skills that overlap with Claude's native capabilities.
+88
View File
@@ -0,0 +1,88 @@
# Claude-Mem Public Documentation
## What This Folder Is
This `docs/public/` folder contains the **Mintlify documentation site** - the official user-facing documentation for claude-mem. It's a structured documentation platform with a specific file format and organization.
## Folder Structure
```
docs/
├── public/ ← You are here (Mintlify MDX files)
│ ├── *.mdx - User-facing documentation pages
│ ├── docs.json - Mintlify configuration and navigation
│ ├── architecture/ - Technical architecture docs
│ ├── usage/ - User guides and workflows
│ └── *.webp, *.gif - Assets (logos, screenshots)
└── context/ ← Internal documentation (DO NOT put here)
└── *.md - Planning docs, audits, references
```
## File Requirements
### Mintlify Documentation Files (.mdx)
All official documentation files must be:
- Written in `.mdx` format (Markdown with JSX support)
- Listed in `docs.json` navigation structure
- Follow Mintlify's schema and conventions
The documentation is organized into these sections:
- **Get Started**: Introduction, installation, usage guides
- **Best Practices**: Context engineering, progressive disclosure
- **Configuration & Development**: Settings, dev workflow, troubleshooting
- **Architecture**: System design, components, technical details
### Configuration File
`docs.json` defines:
- Site metadata (name, description, theme)
- Navigation structure
- Branding (logos, colors)
- Footer links and social media
## What Does NOT Belong Here
**Planning documents, design docs, and reference materials go in `/docs/context/` instead:**
Files that belong in `/docs/context/` (NOT here):
- Planning documents (`*-plan.md`, `*-outline.md`)
- Implementation analysis (`*-audit.md`, `*-code-reference.md`)
- Error tracking (`typescript-errors.md`)
- Internal design documents
- PR review responses
- Reference materials (like `agent-sdk-ref.md`)
- Work-in-progress documentation
## How to Add Official Documentation
1. Create a new `.mdx` file in the appropriate subdirectory
2. Add the file path to `docs.json` navigation
3. Use Mintlify's frontmatter and components
4. Follow the existing documentation style
5. Test locally: `npx mintlify dev`
## Development Workflow
**For contributors working on claude-mem:**
- Read `/CLAUDE.md` in the project root for development instructions
- Place planning/design docs in `/docs/context/`
- Only add user-facing documentation to `/docs/public/`
- Test documentation locally with Mintlify CLI before committing
## Testing Documentation
```bash
# Validate docs structure
npx mintlify validate
# Check for broken links
npx mintlify broken-links
# Run local dev server
npx mintlify dev
```
## Summary
**Simple Rule**:
- `/docs/public/` = Official user documentation (Mintlify .mdx files) ← YOU ARE HERE
- `/docs/context/` = Internal docs, plans, references, audits
@@ -10,9 +10,9 @@ description: "System components and data flow in Claude-Mem"
Claude-Mem operates as a Claude Code plugin with five core components:
1. **Plugin Hooks** - Capture lifecycle events (7 hook files)
2. **Worker Service** - Process observations via Claude Agent SDK
3. **Database Layer** - Store sessions and observations (SQLite + FTS5)
4. **MCP Search Server** - Query historical context (9 search tools)
2. **Worker Service** - Process observations via Claude Agent SDK + HTTP API (10 search endpoints)
3. **Database Layer** - Store sessions and observations (SQLite + FTS5 + ChromaDB)
4. **Search Skill** - Skill-based search with progressive disclosure (v5.4.0+)
5. **Viewer UI** - Web-based real-time memory stream visualization
## Technology Stack
@@ -44,16 +44,19 @@ Hook (stdin) → Database → Worker Service → SDK Processor → Database →
4. **Output**: Processed summaries written back to database
5. **Retrieval**: Next session's context hook reads summaries from database
### Search Pipeline
### Search Pipeline (v5.4.0+)
```
Claude Request → MCP Server → SessionSearch Service → FTS5 Database → Search Results → Claude
User Query → Skill Invoked → HTTP API → SessionSearch Service → FTS5 Database → Search Results → Claude
```
1. **Query**: Claude uses MCP search tools (e.g., `search_observations`)
2. **Search**: MCP server calls SessionSearch service with query parameters
3. **FTS5**: Full-text search executes against FTS5 virtual tables
4. **Format**: Results formatted as `search_result` blocks with citations
5. **Return**: Claude receives citable search results for analysis
1. **User Query**: User asks naturally: "What bugs did we fix?"
2. **Skill Invoked**: Claude recognizes intent and invokes search skill
3. **HTTP API**: Skill uses curl to call HTTP endpoint (e.g., `/api/search/observations`)
4. **SessionSearch**: Worker service queries FTS5 virtual tables
5. **Format**: Results formatted and returned to skill
6. **Return**: Claude presents formatted results to user
**Token Savings**: ~2,250 tokens per session vs MCP approach through progressive disclosure
## Session Lifecycle
@@ -110,9 +113,6 @@ claude-mem/
│ │ ├── summary-hook.ts # Stop
│ │ └── cleanup-hook.ts # SessionEnd
│ │
│ ├── servers/ # MCP servers
│ │ └── search-server.ts # MCP search tools server (9 tools)
│ │
│ ├── sdk/ # Claude Agent SDK integration
│ │ ├── prompts.ts # XML prompt builders
│ │ ├── parser.ts # XML response parser
@@ -146,7 +146,6 @@ claude-mem/
├── plugin/ # Plugin distribution
│ ├── .claude-plugin/
│ │ └── plugin.json
│ ├── .mcp.json # MCP server configuration
│ ├── hooks/
│ │ └── hooks.json
│ ├── scripts/ # Built executables
@@ -157,8 +156,14 @@ claude-mem/
│ │ ├── save-hook.js
│ │ ├── summary-hook.js
│ │ ├── cleanup-hook.js
│ │ ── worker-service.cjs # Background worker
│ │ └── search-server.mjs # MCP search server
│ │ ── worker-service.cjs # Background worker + HTTP API
│ │
│ ├── skills/ # Agent skills (v5.4.0+)
│ │ ├── search/ # Search skill with progressive disclosure
│ │ │ ├── SKILL.md # Skill frontmatter (~250 tokens)
│ │ │ └── operations/ # Detailed operation docs
│ │ ├── troubleshoot/ # Troubleshooting skill
│ │ └── version-bump/ # Version management skill
│ │
│ └── ui/ # Built viewer UI
│ └── viewer.html # Self-contained bundle
@@ -183,7 +188,8 @@ See [Plugin Hooks](/architecture/hooks) for detailed hook documentation.
### 2. Worker Service
Express.js HTTP server on port 37777 (configurable) with:
- 8 HTTP/SSE endpoints for viewer UI
- 10 search HTTP API endpoints (v5.4.0+)
- 8 viewer UI HTTP/SSE endpoints
- Async observation processing via Claude Agent SDK
- Real-time updates via Server-Sent Events
- Auto-managed by PM2 process manager
@@ -199,13 +205,19 @@ SQLite3 with better-sqlite3 driver featuring:
See [Database Architecture](/architecture/database) for schema and FTS5 search.
### 4. MCP Search Server (9 Tools)
Provides 9 specialized search tools:
- search_observations, search_sessions, search_user_prompts
- find_by_concept, find_by_file, find_by_type
- get_recent_context, get_context_timeline, get_timeline_by_query
### 4. Search Skill (v5.4.0+)
Skill-based search with progressive disclosure providing 10 search operations:
- Search observations, sessions, prompts (full-text FTS5)
- Filter by type, concept, file
- Get recent context, timeline, timeline by query
- API help documentation
See [MCP Search Server](/architecture/mcp-search) for search tools and examples.
**Token Savings**: ~2,250 tokens per session vs MCP approach
- Skill frontmatter: ~250 tokens (loaded at session start)
- Full instructions: ~2,500 tokens (loaded on-demand when invoked)
- HTTP API endpoints instead of MCP tools
See [Search Architecture](/architecture/search-architecture) for technical details and examples.
### 5. Viewer UI
React + TypeScript web interface at http://localhost:37777 featuring:
@@ -0,0 +1,440 @@
---
title: "Search Architecture"
description: "Skill-based search with HTTP API and progressive disclosure"
---
# Search Architecture
Claude-Mem uses a skill-based search architecture that provides intelligent memory retrieval through natural language queries. This replaced the MCP-based approach in v5.4.0, saving ~2,250 tokens per session start.
## Overview
**Architecture**: Skill-Based Search + HTTP API + Progressive Disclosure
**Key Components**:
1. **Search Skill** (`plugin/skills/search/SKILL.md`) - Auto-invoked when users ask about past work
2. **HTTP API Endpoints** (10 routes) - Fast, efficient search operations on port 37777
3. **Worker Service** - Express.js server with FTS5 full-text search
4. **SQLite Database** - Persistent storage with FTS5 virtual tables
5. **Chroma Vector DB** - Semantic search with hybrid retrieval
## How It Works
### 1. User Query (Natural Language)
```
User: "What bugs did we fix last session?"
```
### 2. Skill Invocation
Claude recognizes the intent and invokes the search skill:
- Skill frontmatter (~250 tokens) loaded at session start
- Full skill instructions loaded on-demand when skill is invoked
- Progressive disclosure pattern minimizes context overhead
### 3. HTTP API Call
The skill uses `curl` to call the HTTP API:
```bash
curl "http://localhost:37777/api/search/observations?query=bugs&type=bugfix&limit=5"
```
### 4. FTS5 Search
Worker service queries SQLite FTS5 virtual tables:
```sql
SELECT * FROM observations_fts
WHERE observations_fts MATCH ?
AND type = 'bugfix'
ORDER BY rank
LIMIT 5
```
### 5. Results Formatted
Skill formats results and returns to Claude:
```
## Recent Bugfixes
1. [bugfix] Fixed authentication token expiry
Date: 2025-11-08 14:23:45
Files: src/auth/jwt.ts
2. [bugfix] Resolved database connection leak
Date: 2025-11-08 13:15:22
Files: src/services/database.ts
```
### 6. User Sees Answer
Claude presents the formatted results naturally in conversation.
## Architecture Change (v5.4.0)
### Before: MCP-Based Search
**Approach**: 9 MCP tools registered at session start
**Token Cost**: ~2,500 tokens in tool definitions per session
- Each tool's schema, parameters, descriptions loaded
- All 9 tools available whether needed or not
- No progressive disclosure
**Example MCP Tool**:
```json
{
"name": "search_observations",
"description": "Full-text search across observations...",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "..." },
"type": { "type": "array", "items": { "enum": [...] } },
"format": { "enum": ["index", "full"] },
// ... many more parameters
}
}
}
```
### After: Skill-Based Search
**Approach**: 1 search skill with progressive disclosure
**Token Cost**: ~250 tokens in skill frontmatter per session
- Only skill description loaded at session start
- Full instructions loaded on-demand when skill is invoked
- HTTP API endpoints instead of MCP protocol
**Example Skill Frontmatter**:
```markdown
# Claude-Mem Search Skill
Access claude-mem's persistent memory through a comprehensive HTTP API.
Search for past work, understand context, and learn from previous decisions.
## When to Use This Skill
Invoke this skill when users ask about:
- Past work: "What did we do last session?"
- Bug fixes: "Did we fix this before?"
- Features: "How did we implement authentication?"
...
```
**Token Savings**: ~2,250 tokens per session start (90% reduction)
## HTTP API Endpoints
The worker service exposes 10 search endpoints:
### Full-Text Search
```
GET /api/search/observations
GET /api/search/sessions
GET /api/search/prompts
```
**Parameters**:
- `query` - FTS5 search query (required)
- `type` - Filter by type (bugfix, feature, refactor, etc.)
- `project` - Filter by project name
- `limit` - Maximum results (default: 20)
- `offset` - Pagination offset
- `format` - Response format (index or full)
**Example**:
```bash
curl "http://localhost:37777/api/search/observations?query=authentication&type=decision&limit=5"
```
### Filtered Search
```
GET /api/search/by-type
GET /api/search/by-concept
GET /api/search/by-file
```
**Parameters**:
- `type` / `concept` / `filePath` - Filter criteria (required)
- `project` - Filter by project
- `limit` - Maximum results
- `format` - Response format
**Example**:
```bash
curl "http://localhost:37777/api/search/by-file?filePath=worker-service.ts&limit=10"
```
### Context Retrieval
```
GET /api/context/recent
GET /api/context/timeline
GET /api/timeline/by-query
```
**Parameters**:
- `project` - Filter by project
- `limit` - Number of sessions/records
- `anchor` - Timeline anchor point (ID or timestamp)
- `depth_before` - Records before anchor
- `depth_after` - Records after anchor
**Example**:
```bash
curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=5"
```
### Documentation
```
GET /api/search/help
```
Returns API documentation in JSON format.
## Progressive Disclosure Pattern
The search skill uses progressive disclosure to minimize token usage:
### Layer 1: Skill Frontmatter (Session Start)
**What's Loaded**: Skill description and when to use it (~250 tokens)
**Purpose**: Claude can recognize when to invoke the skill
**Example**:
```markdown
# Claude-Mem Search Skill
Access claude-mem's persistent memory through a comprehensive HTTP API.
## When to Use This Skill
Invoke this skill when users ask about:
- Past work: "What did we do last session?"
- Bug fixes: "Did we fix this before?"
...
```
### Layer 2: Full Skill Instructions (On-Demand)
**What's Loaded**: Complete operation documentation (~2,500 tokens)
**Purpose**: Detailed instructions for each search operation
**When Loaded**: Only when Claude invokes the skill
**Example Structure**:
```
/skills/search/
├── SKILL.md (main frontmatter)
├── operations/
│ ├── observations.md (detailed instructions)
│ ├── sessions.md
│ ├── prompts.md
│ ├── by-type.md
│ ├── by-concept.md
│ ├── by-file.md
│ ├── recent-context.md
│ ├── timeline.md
│ ├── timeline-by-query.md
│ ├── help.md
│ ├── formatting.md
│ └── common-workflows.md
```
### Layer 3: API Response
**What's Returned**: Search results in requested format
**Format Options**:
- `index` - Titles, dates, IDs only (~50-100 tokens per result)
- `full` - Complete details (~500-1000 tokens per result)
**Progressive Usage**: Start with `index`, drill down with `full` as needed
## Implementation Details
### Search Skill Structure
```
plugin/skills/search/
├── SKILL.md # Main frontmatter (~250 tokens)
├── operations/
│ ├── observations.md # Search observations
│ ├── sessions.md # Search sessions
│ ├── prompts.md # Search prompts
│ ├── by-type.md # Filter by type
│ ├── by-concept.md # Filter by concept
│ ├── by-file.md # Filter by file
│ ├── recent-context.md # Get recent context
│ ├── timeline.md # Timeline around point
│ ├── timeline-by-query.md # Search + timeline
│ ├── help.md # API documentation
│ ├── formatting.md # Result formatting guide
│ └── common-workflows.md # Usage patterns
```
### Worker Service Integration
**File**: `src/services/worker-service.ts`
**Search Routes**:
```typescript
// Full-text search
app.get('/api/search/observations', handleSearchObservations);
app.get('/api/search/sessions', handleSearchSessions);
app.get('/api/search/prompts', handleSearchPrompts);
// Filtered search
app.get('/api/search/by-type', handleSearchByType);
app.get('/api/search/by-concept', handleSearchByConcept);
app.get('/api/search/by-file', handleSearchByFile);
// Context retrieval
app.get('/api/context/recent', handleRecentContext);
app.get('/api/context/timeline', handleTimeline);
app.get('/api/timeline/by-query', handleTimelineByQuery);
// Documentation
app.get('/api/search/help', handleHelp);
```
**Database Access**:
- Uses `SessionSearch` service for FTS5 queries
- Uses `SessionStore` for structured queries
- Hybrid search with ChromaDB for semantic similarity
### Security
**FTS5 Injection Prevention** (v4.2.3):
```typescript
function escapeFTS5Query(query: string): string {
return query.replace(/"/g, '""');
}
```
All user-provided search queries are properly escaped to prevent SQL injection.
**Comprehensive Testing**: 332 injection attack tests covering:
- Special characters
- SQL keywords
- Quote escaping
- Boolean operators
## Benefits
### 1. Token Efficiency
**Before (MCP)**:
- Session start: ~2,500 tokens for tool definitions
- Every session pays this cost
- No progressive disclosure
**After (Skill)**:
- Session start: ~250 tokens for skill frontmatter
- Full instructions: ~2,500 tokens (only when invoked)
- Net savings: ~2,250 tokens per session (~90% reduction)
### 2. Natural Language Interface
**Before**: Users needed to learn MCP tool syntax
```
search_observations with query="authentication" and type="decision"
```
**After**: Users ask naturally
```
"What decisions did we make about authentication?"
```
Claude translates to appropriate API call.
### 3. Flexibility
**HTTP API Benefits**:
- Can be called from skills, MCP tools, or other clients
- Easy to test with curl
- Standard REST conventions
- JSON responses
**Progressive Disclosure**:
- Loads only what's needed
- Can add more operations without increasing base cost
- Documentation co-located with operations
### 4. Performance
**Fast Queries**: FTS5 full-text search <10ms for typical queries
**Caching**: HTTP layer allows response caching
**Pagination**: Efficient result pagination with offset/limit
## Migration Notes
### For Users
**No Action Required**: The migration from MCP to skill-based search is transparent.
**Same Questions Work**: Natural language queries work exactly the same way.
**Invisible Change**: Users won't notice any difference except better performance.
### For Developers
**Deprecated**: MCP search server (`src/servers/search-server.ts`)
- Source file kept for reference
- No longer built or registered
- MCP configuration removed from `plugin/.mcp.json`
**New Implementation**: Skill-based search
- Skill files: `plugin/skills/search/`
- HTTP endpoints: `src/services/worker-service.ts` (lines 200-400)
- Build script: `npm run build` includes skill files
- Sync script: `npm run sync-marketplace` copies to plugin directory
## Troubleshooting
### Worker Service Not Running
If searches fail, check worker service:
```bash
pm2 list # Check status
npm run worker:restart # Restart worker
npm run worker:logs # View logs
```
### HTTP Endpoints Not Responding
Test endpoints directly:
```bash
# Health check
curl http://localhost:37777/health
# Search test
curl "http://localhost:37777/api/search/observations?query=test&limit=1"
```
### Skill Not Invoking
If Claude doesn't invoke the skill:
1. Check skill files exist: `ls ~/.claude/plugins/marketplaces/thedotmack/plugin/skills/search/`
2. Restart Claude Code session
3. Try explicit skill invocation: `/skill search`
## Next Steps
- [Search Tools Usage](/usage/search-tools) - User guide with examples
- [Worker Service Architecture](/architecture/worker-service) - HTTP API details
- [Database Schema](/architecture/database) - FTS5 tables and indexes

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

@@ -137,22 +137,19 @@ Hooks are configured in `plugin/hooks/hooks.json`:
}
```
### MCP Server Configuration
### Search Configuration (v5.4.0+)
The MCP search server is configured in `plugin/.mcp.json`:
**Migration Note**: As of v5.4.0, Claude-Mem uses skill-based search instead of MCP tools.
```json
{
"mcpServers": {
"claude-mem-search": {
"type": "stdio",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.mjs"
}
}
}
```
**Previous (v5.3.x and earlier)**: MCP search server with 9 tools (~2,500 tokens per session)
**Current (v5.4.0+)**: Search skill with HTTP API (~250 tokens per session)
This registers the `claude-mem-search` server with Claude Code, making the 9 search tools available in all sessions.
**No configuration required** - the search skill is automatically available in Claude Code sessions.
Search operations are now provided via:
- **Skill**: `plugin/skills/search/SKILL.md` (auto-invoked when users ask about past work)
- **HTTP API**: 10 endpoints on worker service port 37777
- **Progressive Disclosure**: Full instructions loaded on-demand only when needed
## PM2 Configuration
+1 -1
View File
@@ -66,7 +66,7 @@
"architecture/hooks",
"architecture/worker-service",
"architecture/database",
"architecture/mcp-search"
"architecture/search-architecture"
]
}
]
@@ -23,7 +23,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
## Key Features
- 🧠 **Persistent Memory** - Context survives across sessions
- 🔍 **9 Search Tools** - Query your project history via MCP
- 🔍 **Skill-Based Search** - Query your project history with natural language (~2,250 token savings)
- 🌐 **Web Viewer UI** - Real-time memory stream visualization at http://localhost:37777
- 🎨 **Theme Toggle** - Light, dark, and system preference themes
- 🤖 **Automatic Operation** - No manual intervention required
@@ -54,10 +54,21 @@ When Claude finishes responding (triggering the Stop hook), a summary is automat
When you start a new Claude Code session, the SessionStart hook:
1. Queries the database for recent sessions in your project
2. Retrieves the last 10 session summaries
3. Formats them with three-tier verbosity (most recent = most detail)
4. Injects them into Claude's initial context
1. Queries the database for recent observations in your project (default: 50)
2. Retrieves recent session summaries for context
3. Displays observations in a chronological timeline with session markers
4. Shows full summary details (Investigated, Learned, Completed, Next Steps) **only if the summary was generated after the last observation**
5. Injects formatted context into Claude's initial context
**Summary Display Logic:**
The most recent summary's full details appear at the end of the context display **only when** the summary was generated after the most recent observation. This ensures you see summary details when they represent the latest state of your project, but not when new observations have been captured since the last summary.
For example:
- ✅ **Shows summary**: Last observation at 2:00 PM, summary generated at 2:05 PM → Summary details appear
- ❌ **Hides summary**: Summary generated at 2:00 PM, new observation at 2:05 PM → Summary details hidden (outdated)
This prevents showing stale summaries when new work has been captured but not yet summarized.
This means Claude "remembers" what happened in previous sessions!
@@ -138,26 +149,31 @@ FROM observations
WHERE session_id = 'YOUR_SESSION_ID';
```
## Understanding Verbosity Levels
## Understanding Progressive Disclosure
Context injection uses three-tier verbosity for efficient token usage:
Context injection uses progressive disclosure for efficient token usage:
### Tier 1 (Most Recent Session)
- Full summary with all details
- Request, investigated, learned, completed, next_steps, notes
- ~500-1000 tokens
### Layer 1: Index Display (Session Start)
- Shows observation titles with token cost estimates
- Displays session markers in chronological timeline
- Groups observations by file for visual clarity
- Shows full summary details **only if** generated after last observation
- Token cost: ~50-200 tokens for index view
### Tier 2 (Sessions 2-5)
- Medium detail
- Request, learned, completed
- ~200-400 tokens
### Layer 2: On-Demand Details (Skill-Based Search)
- Ask naturally: "What bugs did we fix?" or "How did we implement X?"
- Claude auto-invokes search skill to fetch full details
- Search by concept, file, type, or keyword
- Timeline context around specific observations
- Token cost: ~100-500 tokens per observation fetched
- Skill uses HTTP API (v5.4.0+) for efficient retrieval
### Tier 3 (Sessions 6-10)
- Brief summary
- Request and completed only
- ~100-200 tokens
### Layer 3: Perfect Recall (Code Access)
- Read source files directly when needed
- Access original transcripts and raw data
- Full context available on-demand
This ensures you get maximum detail for recent work while still having context from older sessions.
This ensures efficient token usage while maintaining access to complete history when needed.
## Multi-Prompt Sessions & `/clear` Behavior
@@ -177,8 +193,23 @@ When you use `/clear`, the session doesn't end - it continues with a new prompt
The `/clear` command clears the conversation context visible to Claude AND re-injects fresh context from recent sessions, while the underlying session continues tracking observations.
## Searching Your History (v5.4.0+)
Claude-Mem now uses skill-based search for querying your project history. Simply ask naturally:
```
"What bugs did we fix last session?"
"How did we implement authentication?"
"What changes were made to worker-service.ts?"
"Show me recent work on this project"
```
Claude automatically recognizes your intent and invokes the search skill, which uses HTTP API endpoints to query your memory efficiently.
**Token Savings**: ~2,250 tokens per session start vs previous MCP approach
## Next Steps
- [MCP Search Tools](/usage/search-tools) - Learn how to search your project history
- [Skill-Based Search](/usage/search-tools) - Learn how to search your project history
- [Architecture Overview](/architecture/overview) - Understand how it works
- [Troubleshooting](/troubleshooting) - Common issues and solutions
+402
View File
@@ -0,0 +1,402 @@
---
title: "Skill-Based Search"
description: "Query your project history with natural language"
---
# Skill-Based Search Usage
Once claude-mem is installed as a plugin, you can search your project history using natural language. Claude automatically invokes the search skill when you ask about past work.
## How It Works
**v5.4.0 Migration**: Claude-Mem now uses a skill-based search architecture instead of MCP tools, saving ~2,250 tokens per session start through progressive disclosure.
**Simple Usage:**
- Just ask naturally: *"What did we do last session?"*
- Claude recognizes the intent and invokes the search skill
- The skill uses HTTP API endpoints to query your memory
- Results are formatted and presented to you
**Benefits:**
- **Token Efficient**: ~250 tokens (skill frontmatter) vs ~2,500 tokens (MCP tool definitions)
- **Natural Language**: No need to learn specific tool syntax
- **Progressive Disclosure**: Only loads detailed instructions when needed
- **Auto-Invoked**: Claude knows when to search based on your questions
## Quick Reference
| Operation | Purpose |
|-------------------------|----------------------------------------------|
| Search Observations | Full-text search across observations |
| Search Sessions | Full-text search across session summaries |
| Search Prompts | Full-text search across raw user prompts |
| By Concept | Find observations tagged with concepts |
| By File | Find observations referencing files |
| By Type | Find observations by type |
| Recent Context | Get recent session context |
| Timeline | Get unified timeline around a specific point |
| Timeline by Query | Search and get timeline context in one step |
| API Help | Get search API documentation |
## Example Queries
### Natural Language Queries
**Search Observations:**
```
"What bugs did we fix related to authentication?"
"Show me all decisions about the build system"
"Find refactoring work on the database"
```
**Search Sessions:**
```
"What did we learn about hooks?"
"What was accomplished in the API implementation?"
"Show me recent work on this project"
```
**Search Prompts:**
```
"When did I ask about authentication features?"
"Find all my requests about dark mode"
```
**Note**: Claude automatically translates your natural language queries into the appropriate search operations.
### Search by File
```
"Show me everything related to worker-service.ts"
"What changes were made to migrations.ts?"
"Find all work on the database file"
```
### Search by Concept
```
"Show observations tagged with architecture"
"Find all security-related observations"
"What patterns have we used?"
```
### Search by Type
```
"Find all feature implementations"
"Show me all decisions and discoveries"
"What bugs have we fixed?"
```
### Recent Context
```
"Show me what we've been working on"
"Get context from the last 5 sessions"
"What happened recently on this project?"
```
### Timeline Queries
**Get timeline around a specific point:**
```
"What was happening when we implemented authentication?"
"Show me the context around that bug fix"
"What led to the decision to refactor the database?"
```
**Timeline by query:**
```
"Find when we added the viewer UI and show what happened around that time"
"Search for authentication work and show the timeline"
```
**Benefits:**
- See the complete narrative arc around key events
- All record types (observations, sessions, prompts) in chronological view
- Understand what was happening before and after important changes
## Search Strategy
The search skill uses a progressive disclosure pattern to efficiently retrieve information:
### 1. Ask Naturally
Start with a natural language question:
```
"What bugs did we fix related to authentication?"
```
### 2. Claude Invokes Search Skill
Claude recognizes your intent and loads the search skill (~250 tokens for skill frontmatter).
### 3. Skill Uses HTTP API
The skill calls the appropriate HTTP endpoint (e.g., `/api/search/observations`) with the query.
### 4. Results Formatted
Results are formatted and presented to you, usually starting with an index/summary format.
### 5. Deep Dive if Needed
If you need more details, ask follow-up questions:
```
"Tell me more about observation #123"
"Show me the full details of that decision"
```
**Benefits of This Approach:**
- **Token Efficient**: Only loads what you need, when you need it
- **Natural**: No syntax to learn
- **Progressive**: Start with overview, drill down as needed
- **Automatic**: Claude handles the search invocation
## Advanced Filtering
You can refine searches using natural language filters:
### Date Ranges
```
"What bugs did we fix in October?"
"Show me work from last week"
"Find decisions made between October 1-31"
```
### Multiple Types
```
"Show me all decisions and features"
"Find bugfixes and refactorings"
```
### Concepts
```
"Find database work related to architecture and performance"
"Show security observations"
```
### File-Specific
```
"Show refactoring work that touched worker-service.ts"
"Find changes to auth files"
```
### Project Filtering
```
"Show authentication work on my-app project"
"What have we done on this codebase?"
```
**Note**: Claude translates your natural language into the appropriate API filters automatically.
## Under the Hood: HTTP API
The search skill uses HTTP endpoints on the worker service (port 37777):
- `GET /api/search/observations` - Full-text search observations
- `GET /api/search/sessions` - Full-text search session summaries
- `GET /api/search/prompts` - Full-text search user prompts
- `GET /api/search/by-concept` - Find observations by concept tag
- `GET /api/search/by-file` - Find work related to specific files
- `GET /api/search/by-type` - Find observations by type
- `GET /api/context/recent` - Get recent session context
- `GET /api/context/timeline` - Get timeline around specific point
- `GET /api/timeline/by-query` - Search + timeline in one call
- `GET /api/search/help` - API documentation
These endpoints use FTS5 full-text search with support for:
- Boolean operators (AND, OR, NOT)
- Phrase searches
- Column-specific searches
- Date range filtering
- Project filtering
## Result Metadata
All results include rich metadata:
```
## JWT authentication decision
**Type**: decision
**Date**: 2025-10-21 14:23:45
**Concepts**: authentication, security, architecture
**Files Read**: src/auth/middleware.ts, src/utils/jwt.ts
**Files Modified**: src/auth/jwt-strategy.ts
**Narrative**:
Decided to implement JWT-based authentication instead of session-based
authentication for better scalability and stateless design...
**Facts**:
• JWT tokens expire after 1 hour
• Refresh tokens stored in httpOnly cookies
• Token signing uses RS256 algorithm
• Public keys rotated every 30 days
```
## Citations
All search results include citations using the `claude-mem://` URI scheme:
- `claude-mem://observation/123` - Specific observation
- `claude-mem://session/abc-456` - Specific session
- `claude-mem://user-prompt/789` - Specific user prompt
These citations enable referencing specific historical context in your work.
## Token Management
### Token Efficiency Tips
1. **Start with index format**: ~50-100 tokens per result
2. **Use small limits**: Start with 3-5 results
3. **Apply filters**: Narrow results before searching
4. **Paginate**: Use offset to browse results in batches
### Token Estimates
| Format | Tokens per Result |
|--------|-------------------|
| Index | 50-100 |
| Full | 500-1000 |
**Example**:
- 20 results in index format: ~1,000-2,000 tokens
- 20 results in full format: ~10,000-20,000 tokens
## Common Use Cases
### 1. Debugging Issues
Find what went wrong:
```
search_observations with query="error database connection" and type="bugfix"
```
### 2. Understanding Decisions
Review architectural choices:
```
find_by_type with type="decision" and format="index"
```
Then deep dive on specific decisions:
```
search_observations with query="[DECISION TITLE]" and format="full"
```
### 3. Code Archaeology
Find when a file was modified:
```
find_by_file with filePath="worker-service.ts"
```
### 4. Feature History
Track feature development:
```
search_sessions with query="authentication feature"
search_user_prompts with query="add authentication"
```
### 5. Learning from Past Work
Review refactoring patterns:
```
find_by_type with type="refactor" and limit=10
```
### 6. Context Recovery
Restore context after time away:
```
get_recent_context with limit=5
search_sessions with query="[YOUR PROJECT NAME]" and orderBy="date_desc"
```
## Best Practices
1. **Index first, full later**: Always start with index format
2. **Small limits**: Start with 3-5 results to avoid token limits
3. **Use filters**: Narrow results before searching
4. **Specific queries**: More specific = better results
5. **Review citations**: Use citations to reference past decisions
6. **Date filtering**: Use date ranges for time-based searches
7. **Type filtering**: Use types to categorize searches
8. **Concept tags**: Use concepts for thematic searches
## Troubleshooting
### No Results Found
1. Check database has data:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
2. Try broader natural language query:
```
"Show me anything about authentication" # Broader
vs
"Find exact JWT authentication implementation" # Too specific
```
3. Ask without filters first:
```
"What do we have about auth?"
# Then narrow down
"Show me auth-related decisions"
```
### Worker Service Not Running
If search isn't working, check the worker service:
```bash
pm2 list # Check worker status
npm run worker:restart # Restart if needed
npm run worker:logs # View logs
```
Or use the troubleshooting skill:
```
/skill troubleshoot
```
### Performance Issues
If searches seem slow:
1. Be more specific in your queries
2. Ask for recent work (naturally filters by date)
3. Specify the project you're interested in
4. Ask for fewer results initially
## Technical Details
**Architecture Change (v5.4.0)**:
- **Before**: 9 MCP tools (~2,500 tokens in tool definitions per session start)
- **After**: 1 search skill (~250 tokens in frontmatter, full instructions loaded on-demand)
- **Savings**: ~2,250 tokens per session start
- **Migration**: Transparent - users don't need to change how they ask questions
**How the Skill Works:**
1. User asks a question about past work
2. Claude recognizes the intent matches the search skill description
3. Skill loads full instructions from `plugin/skills/search/SKILL.md`
4. Skill uses `curl` to call HTTP API endpoints
5. Results formatted and returned to Claude
6. Claude presents results to user
## Next Steps
- [Architecture Overview](/architecture/overview) - System components
- [Database Schema](/architecture/database) - Understanding the data
- [Getting Started](/usage/getting-started) - Automatic operation
-492
View File
@@ -1,492 +0,0 @@
---
title: "MCP Search Tools"
description: "Query your project history with 9 specialized search tools"
---
# MCP Search Tools Usage
Once claude-mem is installed as a plugin, 9 search tools become available in your Claude Code sessions for querying project history.
## Quick Reference
| Tool | Purpose |
|-------------------------|----------------------------------------------|
| search_observations | Full-text search across observations |
| search_sessions | Full-text search across session summaries |
| search_user_prompts | Full-text search across raw user prompts |
| find_by_concept | Find observations tagged with concepts |
| find_by_file | Find observations referencing files |
| find_by_type | Find observations by type |
| get_recent_context | Get recent session context |
| get_context_timeline | Get unified timeline around a specific point |
| get_timeline_by_query | Search and get timeline context in one step |
## Example Queries
### search_observations
Find all decisions about the build system:
```
Use search_observations to find all decisions about the build system
```
Find bugs related to authentication:
```
search_observations with query="authentication" and type="bugfix"
```
Search for refactoring work:
```
search_observations with query="refactor database" and type="refactor"
```
### search_sessions
Find what we learned about hooks:
```
Use search_sessions to find what we learned about hooks
```
Search for completed work on the API:
```
search_sessions with query="API implementation"
```
### search_user_prompts
Find when user asked about authentication:
```
search_user_prompts with query="authentication feature"
```
Trace user requests for a specific feature:
```
search_user_prompts with query="dark mode"
```
**Benefits**:
- See exactly what the user asked for (vs what was implemented)
- Detect patterns in repeated requests
- Debug miscommunications between user intent and implementation
### find_by_file
Show everything related to worker-service.ts:
```
Use find_by_file to show me everything related to worker-service.ts
```
Find all work on the database migration file:
```
find_by_file with filePath="migrations.ts"
```
### find_by_concept
Show observations tagged with 'architecture':
```
Use find_by_concept to show observations tagged with 'architecture'
```
Find all 'security' related observations:
```
find_by_concept with concept="security"
```
### find_by_type
Find all feature implementations:
```
find_by_type with type="feature"
```
Find all decisions and discoveries:
```
find_by_type with type=["decision", "discovery"]
```
### get_recent_context
Get the last 5 sessions for context:
```
get_recent_context with limit=5
```
Get recent context for debugging:
```
Use get_recent_context to show me what we've been working on
```
### get_context_timeline
Get a unified timeline of context around a specific point in time. This tool interleaves observations, sessions, and user prompts chronologically to show what was happening before and after a specific moment.
**Anchor by observation ID:**
```
get_context_timeline with anchor=12345 and depth_before=10 and depth_after=10
```
**Anchor by session ID:**
```
get_context_timeline with anchor="S123" and depth_before=5 and depth_after=5
```
**Anchor by ISO timestamp:**
```
get_context_timeline with anchor="2025-10-21T14:30:00Z" and depth_before=15 and depth_after=15
```
**Use cases:**
- Understand what was happening when a specific observation occurred
- See the full context around a bug fix or decision
- Trace the events leading up to and following a specific change
- View chronological sequence of related work
**Benefits:**
- All record types (observations, sessions, prompts) in one chronological view
- Configurable depth before/after anchor point
- Flexible anchoring by ID or timestamp
- See the complete narrative arc around key events
### get_timeline_by_query
Search for observations using natural language and get timeline context around the best match. This combines search + timeline into a single operation for faster context discovery.
**Auto mode (default):**
```
get_timeline_by_query with query="authentication implementation"
```
Automatically uses the top search result as timeline anchor and returns surrounding context.
**Interactive mode:**
```
get_timeline_by_query with query="authentication" and mode="interactive" and limit=5
```
Shows top 5 search results for you to manually choose which to use as timeline anchor.
**Customize timeline depth:**
```
get_timeline_by_query with query="bug fix" and depth_before=20 and depth_after=10
```
**Use cases:**
- Quick context discovery: "What was happening when we implemented X?"
- Investigate issues: Find a bug fix and see what led to it
- Decision archaeology: Search for a decision and understand the context
- Feature timeline: See the complete story of a feature implementation
**Benefits:**
- Single-step operation (no need to search, then timeline separately)
- Auto mode provides instant context
- Interactive mode gives you control over anchor selection
- Natural language search makes it easy to find relevant moments
## Search Strategy
### 1. Start with Index Format
**Always use index format first** to get an overview:
```
search_observations with query="authentication" and format="index"
```
**Why?**
- Index format uses ~10x fewer tokens than full format
- See titles, dates, and sources to identify relevant results
- Avoid hitting MCP token limits
### 2. Review Results
Look at the index results to identify items of interest:
```
1. [decision] Implement JWT authentication
Date: 2025-10-21 14:23:45
Source: claude-mem://observation/123
2. [feature] Add user authentication endpoints
Date: 2025-10-21 13:15:22
Source: claude-mem://observation/124
3. [bugfix] Fix authentication token expiry
Date: 2025-10-20 16:45:30
Source: claude-mem://observation/125
```
### 3. Deep Dive with Full Format
Only use full format for specific items:
```
search_observations with query="JWT authentication" and format="full" and limit=3
```
### 4. Use Filters to Narrow Results
Combine filters for precise searches:
```
search_observations with query="authentication" and type="decision" and dateRange={start: "2025-10-20", end: "2025-10-21"}
```
## Advanced Filtering
### Date Ranges
Search within specific time periods:
```json
{
"dateRange": {
"start": "2025-10-01",
"end": "2025-10-31"
}
}
```
Or use epoch timestamps:
```json
{
"dateRange": {
"start": 1729449600,
"end": 1732128000
}
}
```
### Multiple Types
Search across multiple observation types:
```
find_by_type with type=["decision", "feature", "refactor"]
```
### Multiple Concepts
Search observations with specific concepts:
```
search_observations with query="database" and concepts=["architecture", "performance"]
```
### File Filtering
Search observations that touched specific files:
```
search_observations with query="refactor" and files="worker-service.ts"
```
### Project Filtering
Search within specific projects:
```
search_observations with query="authentication" and project="my-app"
```
## FTS5 Query Syntax
The `query` parameter supports SQLite FTS5 full-text search syntax:
### Simple Queries
```
"authentication" # Single word
"error handling" # Multiple words (OR)
```
### Boolean Operators
```
"error" AND "handling" # Both terms required
"bug" OR "fix" # Either term
"bug" NOT "feature" # First term, not second
```
### Phrase Searches
```
"'exact phrase'" # Exact phrase match
```
### Column Searches
```
title:"authentication" # Search specific column
narrative:"bug fix" # Search narrative field
```
## Result Metadata
All results include rich metadata:
```
## JWT authentication decision
**Type**: decision
**Date**: 2025-10-21 14:23:45
**Concepts**: authentication, security, architecture
**Files Read**: src/auth/middleware.ts, src/utils/jwt.ts
**Files Modified**: src/auth/jwt-strategy.ts
**Narrative**:
Decided to implement JWT-based authentication instead of session-based
authentication for better scalability and stateless design...
**Facts**:
• JWT tokens expire after 1 hour
• Refresh tokens stored in httpOnly cookies
• Token signing uses RS256 algorithm
• Public keys rotated every 30 days
```
## Citations
All search results include citations using the `claude-mem://` URI scheme:
- `claude-mem://observation/123` - Specific observation
- `claude-mem://session/abc-456` - Specific session
- `claude-mem://user-prompt/789` - Specific user prompt
These citations enable referencing specific historical context in your work.
## Token Management
### Token Efficiency Tips
1. **Start with index format**: ~50-100 tokens per result
2. **Use small limits**: Start with 3-5 results
3. **Apply filters**: Narrow results before searching
4. **Paginate**: Use offset to browse results in batches
### Token Estimates
| Format | Tokens per Result |
|--------|-------------------|
| Index | 50-100 |
| Full | 500-1000 |
**Example**:
- 20 results in index format: ~1,000-2,000 tokens
- 20 results in full format: ~10,000-20,000 tokens
## Common Use Cases
### 1. Debugging Issues
Find what went wrong:
```
search_observations with query="error database connection" and type="bugfix"
```
### 2. Understanding Decisions
Review architectural choices:
```
find_by_type with type="decision" and format="index"
```
Then deep dive on specific decisions:
```
search_observations with query="[DECISION TITLE]" and format="full"
```
### 3. Code Archaeology
Find when a file was modified:
```
find_by_file with filePath="worker-service.ts"
```
### 4. Feature History
Track feature development:
```
search_sessions with query="authentication feature"
search_user_prompts with query="add authentication"
```
### 5. Learning from Past Work
Review refactoring patterns:
```
find_by_type with type="refactor" and limit=10
```
### 6. Context Recovery
Restore context after time away:
```
get_recent_context with limit=5
search_sessions with query="[YOUR PROJECT NAME]" and orderBy="date_desc"
```
## Best Practices
1. **Index first, full later**: Always start with index format
2. **Small limits**: Start with 3-5 results to avoid token limits
3. **Use filters**: Narrow results before searching
4. **Specific queries**: More specific = better results
5. **Review citations**: Use citations to reference past decisions
6. **Date filtering**: Use date ranges for time-based searches
7. **Type filtering**: Use types to categorize searches
8. **Concept tags**: Use concepts for thematic searches
## Troubleshooting
### No Results Found
1. Check database has data:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
2. Try broader query:
```
search_observations with query="authentication" # Good
vs
search_observations with query="'exact JWT authentication implementation'" # Too specific
```
3. Remove filters:
```
# Start broad
search_observations with query="auth"
# Then add filters
search_observations with query="auth" and type="decision"
```
### Token Limit Errors
1. Use index format:
```
search_observations with query="..." and format="index"
```
2. Reduce limit:
```
search_observations with query="..." and limit=3
```
3. Use pagination:
```
# First page
search_observations with query="..." and limit=5 and offset=0
# Second page
search_observations with query="..." and limit=5 and offset=5
```
### Search Too Slow
1. Use more specific queries
2. Add date range filters
3. Add type/concept filters
4. Reduce result limit
## Next Steps
- [MCP Search Architecture](/architecture/mcp-search) - Technical details
- [Database Schema](/architecture/database) - Understanding the data
- [Getting Started](/usage/getting-started) - Automatic operation
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-mem",
"version": "5.2.2",
"version": "5.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-mem",
"version": "5.2.2",
"version": "5.4.1",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.27",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "5.3.0",
"version": "5.5.1",
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
@@ -40,6 +40,7 @@
"worker:stop": "pm2 stop claude-mem-worker",
"worker:restart": "pm2 restart claude-mem-worker",
"worker:logs": "pm2 logs claude-mem-worker",
"changelog:generate": "node scripts/generate-changelog.js",
"usage:analyze": "node scripts/analyze-usage.js",
"usage:today": "node scripts/analyze-usage.js $(date +%Y-%m-%d)"
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "5.3.0",
"version": "5.5.1",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+15 -15
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import{stdin as I}from"process";import w from"better-sqlite3";import{join as E,dirname as k,basename as W}from"path";import{homedir as O}from"os";import{existsSync as K,mkdirSync as x}from"fs";import{fileURLToPath as U}from"url";function M(){return typeof __dirname<"u"?__dirname:k(U(import.meta.url))}var q=M(),l=process.env.CLAUDE_MEM_DATA_DIR||E(O(),".claude-mem"),R=process.env.CLAUDE_CONFIG_DIR||E(O(),".claude"),J=E(l,"archives"),Q=E(l,"logs"),z=E(l,"trash"),Z=E(l,"backups"),ee=E(l,"settings.json"),f=E(l,"claude-mem.db"),se=E(l,"vector-db"),te=E(R,"settings.json"),re=E(R,"commands"),ne=E(R,"CLAUDE.md");function L(c){x(c,{recursive:!0})}var h=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(h||{}),N=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=h[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=h[e].padEnd(5),d=s.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let m="";n!=null&&(this.level===0&&typeof n=="object"?m=`
`+JSON.stringify(n,null,2):m=" "+this.formatData(n));let T="";if(r){let{sessionId:u,sdkSessionId:b,correlationId:p,...a}=r;Object.keys(a).length>0&&(T=` {${Object.entries(a).map(([y,D])=>`${y}=${D}`).join(", ")}}`)}let S=`[${o}] [${i}] [${d}] ${_}${t}${T}${m}`;e===3?console.error(S):console.log(S)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},A=new N;var g=class{db;constructor(){L(l),this.db=new w(f),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import{stdin as I}from"process";import w from"better-sqlite3";import{join as E,dirname as k,basename as W}from"path";import{homedir as O}from"os";import{existsSync as K,mkdirSync as x}from"fs";import{fileURLToPath as U}from"url";function M(){return typeof __dirname<"u"?__dirname:k(U(import.meta.url))}var q=M(),l=process.env.CLAUDE_MEM_DATA_DIR||E(O(),".claude-mem"),R=process.env.CLAUDE_CONFIG_DIR||E(O(),".claude"),J=E(l,"archives"),Q=E(l,"logs"),z=E(l,"trash"),Z=E(l,"backups"),ee=E(l,"settings.json"),f=E(l,"claude-mem.db"),se=E(l,"vector-db"),te=E(R,"settings.json"),re=E(R,"commands"),ne=E(R,"CLAUDE.md");function L(p){x(p,{recursive:!0})}var h=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(h||{}),N=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=h[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=h[e].padEnd(5),d=s.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let u="";n!=null&&(this.level===0&&typeof n=="object"?u=`
`+JSON.stringify(n,null,2):u=" "+this.formatData(n));let T="";if(r){let{sessionId:m,sdkSessionId:S,correlationId:c,...a}=r;Object.keys(a).length>0&&(T=` {${Object.entries(a).map(([y,D])=>`${y}=${D}`).join(", ")}}`)}let b=`[${o}] [${i}] [${d}] ${_}${t}${T}${u}`;e===3?console.error(b):console.log(b)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},A=new N;var g=class{db;constructor(){L(l),this.db=new w(f),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -318,23 +318,23 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let u=this.db.prepare(`
INSERT INTO observations
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
files_read, files_modified, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),o);return{id:Number(m.lastInsertRowid),createdAtEpoch:o}}storeSummary(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),o);return{id:Number(u.lastInsertRowid),createdAtEpoch:o}}storeSummary(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let u=this.db.prepare(`
INSERT INTO session_summaries
(sdk_session_id, project, request, investigated, learned, completed,
next_steps, notes, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),o);return{id:Number(m.lastInsertRowid),createdAtEpoch:o}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),o);return{id:Number(u.lastInsertRowid),createdAtEpoch:o}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
UPDATE sdk_sessions
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
@@ -357,31 +357,31 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE up.id IN (${i})
ORDER BY up.created_at_epoch ${n}
${o}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,n){let o=n?"AND project = ?":"",i=n?[n]:[],d,_;if(e!==null){let u=`
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,n){let o=n?"AND project = ?":"",i=n?[n]:[],d,_;if(e!==null){let m=`
SELECT id, created_at_epoch
FROM observations
WHERE id <= ? ${o}
ORDER BY id DESC
LIMIT ?
`,b=`
`,S=`
SELECT id, created_at_epoch
FROM observations
WHERE id >= ? ${o}
ORDER BY id ASC
LIMIT ?
`;try{let p=this.db.prepare(u).all(e,...i,t+1),a=this.db.prepare(b).all(e,...i,r+1);if(p.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=p.length>0?p[p.length-1].created_at_epoch:s,_=a.length>0?a[a.length-1].created_at_epoch:s}catch(p){return console.error("[SessionStore] Error getting boundary observations:",p.message),{observations:[],sessions:[],prompts:[]}}}else{let u=`
`;try{let c=this.db.prepare(m).all(e,...i,t+1),a=this.db.prepare(S).all(e,...i,r+1);if(c.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=c.length>0?c[c.length-1].created_at_epoch:s,_=a.length>0?a[a.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary observations:",c.message),{observations:[],sessions:[],prompts:[]}}}else{let m=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${o}
ORDER BY created_at_epoch DESC
LIMIT ?
`,b=`
`,S=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch >= ? ${o}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let p=this.db.prepare(u).all(s,...i,t),a=this.db.prepare(b).all(s,...i,r+1);if(p.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=p.length>0?p[p.length-1].created_at_epoch:s,_=a.length>0?a[a.length-1].created_at_epoch:s}catch(p){return console.error("[SessionStore] Error getting boundary timestamps:",p.message),{observations:[],sessions:[],prompts:[]}}}let m=`
`;try{let c=this.db.prepare(m).all(s,...i,t),a=this.db.prepare(S).all(s,...i,r+1);if(c.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=c.length>0?c[c.length-1].created_at_epoch:s,_=a.length>0?a[a.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary timestamps:",c.message),{observations:[],sessions:[],prompts:[]}}}let u=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
@@ -391,11 +391,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM session_summaries
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
ORDER BY created_at_epoch ASC
`,S=`
`,b=`
SELECT up.*, s.project, s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let u=this.db.prepare(m).all(d,_,...i),b=this.db.prepare(T).all(d,_,...i),p=this.db.prepare(S).all(d,_,...i);return{observations:u,sessions:b.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:p.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(u){return console.error("[SessionStore] Error querying timeline records:",u.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};import X from"path";import{homedir as F}from"os";import{existsSync as B,readFileSync as j}from"fs";function C(){try{let c=X.join(F(),".claude-mem","settings.json");if(B(c)){let e=JSON.parse(j(c,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function v(c){console.error("[claude-mem cleanup] Hook fired",{input:c?{session_id:c.session_id,cwd:c.cwd,reason:c.reason}:null}),c||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(`
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=c;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s});let t=new g,r=t.findActiveSDKSession(e);r||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),t.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:r.id,sdk_session_id:r.sdk_session_id,project:r.project,worker_port:r.worker_port}),t.markSessionCompleted(r.id),console.error("[claude-mem cleanup] Session marked as completed in database"),t.close();try{let n=r.worker_port||C();await fetch(`http://127.0.0.1:${n}/sessions/${r.id}/complete`,{method:"POST",signal:AbortSignal.timeout(1e3)}),console.error("[claude-mem cleanup] Worker notified to stop processing indicator")}catch(n){console.error("[claude-mem cleanup] Failed to notify worker (non-critical):",n)}console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(I.isTTY)v(void 0);else{let c="";I.on("data",e=>c+=e),I.on("end",async()=>{let e=c?JSON.parse(c):void 0;await v(e)})}
`;try{let m=this.db.prepare(u).all(d,_,...i),S=this.db.prepare(T).all(d,_,...i),c=this.db.prepare(b).all(d,_,...i);return{observations:m,sessions:S.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:c.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(m){return console.error("[SessionStore] Error querying timeline records:",m.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};import X from"path";import{homedir as F}from"os";import{existsSync as B,readFileSync as j}from"fs";function C(){try{let p=X.join(F(),".claude-mem","settings.json");if(B(p)){let e=JSON.parse(j(p,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function v(p){console.error("[claude-mem cleanup] Hook fired",{input:p?{session_id:p.session_id,cwd:p.cwd,reason:p.reason}:null}),p||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(`
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=p;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s});let t=new g,r=t.findActiveSDKSession(e);r||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),t.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:r.id,sdk_session_id:r.sdk_session_id,project:r.project,worker_port:r.worker_port}),t.markSessionCompleted(r.id),console.error("[claude-mem cleanup] Session marked as completed in database"),t.close();try{let n=r.worker_port||C();await fetch(`http://127.0.0.1:${n}/sessions/${r.id}/complete`,{method:"POST",signal:AbortSignal.timeout(1e3)}),console.error("[claude-mem cleanup] Worker notified to stop processing indicator")}catch(n){console.error("[claude-mem cleanup] Failed to notify worker (non-critical):",n)}console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(I.isTTY)v(void 0);else{let p="";I.on("data",e=>p+=e),I.on("end",async()=>{let e=p?JSON.parse(p):void 0;await v(e)})}
+11 -11
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import x from"path";import{homedir as se}from"os";import{existsSync as te,readFileSync as re}from"fs";import{stdin as F}from"process";import ee from"better-sqlite3";import{join as b,dirname as J,basename as Te}from"path";import{homedir as j}from"os";import{existsSync as Se,mkdirSync as Q}from"fs";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:J(z(import.meta.url))}var fe=Z(),N=process.env.CLAUDE_MEM_DATA_DIR||b(j(),".claude-mem"),$=process.env.CLAUDE_CONFIG_DIR||b(j(),".claude"),Ne=b(N,"archives"),Oe=b(N,"logs"),Ie=b(N,"trash"),Le=b(N,"backups"),ye=b(N,"settings.json"),P=b(N,"claude-mem.db"),Ae=b(N,"vector-db"),ve=b($,"settings.json"),Ce=b($,"commands"),De=b($,"CLAUDE.md");function H(c){Q(c,{recursive:!0})}var M=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(M||{}),w=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=M[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),p=M[e].padEnd(5),_=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let n="";i!=null&&(this.level===0&&typeof i=="object"?n=`
`+JSON.stringify(i,null,2):n=" "+this.formatData(i));let O="";if(r){let{sessionId:S,sdkSessionId:R,correlationId:l,...a}=r;Object.keys(a).length>0&&(O=` {${Object.entries(a).map(([T,m])=>`${T}=${m}`).join(", ")}}`)}let L=`[${d}] [${p}] [${_}] ${E}${t}${O}${n}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},G=new w;var C=class{db;constructor(){H(N),this.db=new ee(P),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import x from"path";import{homedir as re}from"os";import{existsSync as ne,readFileSync as ie}from"fs";import{stdin as X}from"process";import te from"better-sqlite3";import{join as b,dirname as z,basename as he}from"path";import{homedir as H}from"os";import{existsSync as fe,mkdirSync as Z}from"fs";import{fileURLToPath as ee}from"url";function se(){return typeof __dirname<"u"?__dirname:z(ee(import.meta.url))}var Oe=se(),N=process.env.CLAUDE_MEM_DATA_DIR||b(H(),".claude-mem"),M=process.env.CLAUDE_CONFIG_DIR||b(H(),".claude"),Ie=b(N,"archives"),Le=b(N,"logs"),ye=b(N,"trash"),Ae=b(N,"backups"),ve=b(N,"settings.json"),G=b(N,"claude-mem.db"),Ce=b(N,"vector-db"),De=b(M,"settings.json"),xe=b(M,"commands"),ke=b(M,"CLAUDE.md");function W(c){Z(c,{recursive:!0})}var w=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(w||{}),F=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=w[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),p=w[e].padEnd(5),_=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let n="";i!=null&&(this.level===0&&typeof i=="object"?n=`
`+JSON.stringify(i,null,2):n=" "+this.formatData(i));let O="";if(r){let{sessionId:S,sdkSessionId:R,correlationId:m,...a}=r;Object.keys(a).length>0&&(O=` {${Object.entries(a).map(([B,u])=>`${B}=${u}`).join(", ")}}`)}let L=`[${d}] [${p}] [${_}] ${E}${t}${O}${n}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},Y=new F;var C=class{db;constructor(){W(N),this.db=new te(G),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -299,7 +299,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
`).run(s,e).changes===0?(G.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
`).run(s,e).changes===0?(Y.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
UPDATE sdk_sessions
SET worker_port = ?
WHERE id = ?
@@ -369,7 +369,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE id >= ? ${d}
ORDER BY id ASC
LIMIT ?
`;try{let l=this.db.prepare(S).all(e,...p,t+1),a=this.db.prepare(R).all(e,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary observations:",l.message),{observations:[],sessions:[],prompts:[]}}}else{let S=`
`;try{let m=this.db.prepare(S).all(e,...p,t+1),a=this.db.prepare(R).all(e,...p,r+1);if(m.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=m.length>0?m[m.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(m){return console.error("[SessionStore] Error getting boundary observations:",m.message),{observations:[],sessions:[],prompts:[]}}}else{let S=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${d}
@@ -381,7 +381,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE created_at_epoch >= ? ${d}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let l=this.db.prepare(S).all(s,...p,t),a=this.db.prepare(R).all(s,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary timestamps:",l.message),{observations:[],sessions:[],prompts:[]}}}let n=`
`;try{let m=this.db.prepare(S).all(s,...p,t),a=this.db.prepare(R).all(s,...p,r+1);if(m.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=m.length>0?m[m.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(m){return console.error("[SessionStore] Error getting boundary timestamps:",m.message),{observations:[],sessions:[],prompts:[]}}}let n=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${d}
@@ -397,7 +397,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${d.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let S=this.db.prepare(n).all(_,E,...p),R=this.db.prepare(O).all(_,E,...p),l=this.db.prepare(L).all(_,E,...p);return{observations:S,sessions:R.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:l.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function ne(){try{let c=x.join(se(),".claude","settings.json");if(te(c)){let e=JSON.parse(re(c,"utf-8"));if(e.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let s=parseInt(e.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(!isNaN(s)&&s>0)return s}}}catch{}return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10)}var ie=ne(),W=10,oe=4,ae=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function de(c){if(!c)return[];try{let e=JSON.parse(c);return Array.isArray(e)?e:[]}catch{return[]}}function ce(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function pe(c){return new Date(c).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function _e(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function ue(c){return c?Math.ceil(c.length/oe):0}function le(c,e){return x.isAbsolute(c)?x.relative(e,c):c}function D(c,e,s,t){return e?t?[`${s}${c}:${o.reset} ${e}`,""]:[`**${c}**: ${e}`,""]:[]}async function Y(c,e=!1){let s=c?.cwd??process.cwd(),t=s?x.basename(s):"unknown-project",r=new C,i=r.db.prepare(`
`;try{let S=this.db.prepare(n).all(_,E,...p),R=this.db.prepare(O).all(_,E,...p),m=this.db.prepare(L).all(_,E,...p);return{observations:S,sessions:R.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:m.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function oe(){try{let c=x.join(re(),".claude","settings.json");if(ne(c)){let e=JSON.parse(ie(c,"utf-8"));if(e.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let s=parseInt(e.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(!isNaN(s)&&s>0)return s}}}catch{}return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10)}var ae=oe(),V=10,de=4,ce=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function pe(c){if(!c)return[];try{let e=JSON.parse(c);return Array.isArray(e)?e:[]}catch{return[]}}function _e(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function ue(c){return new Date(c).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function me(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function le(c){return c?Math.ceil(c.length/de):0}function Ee(c,e){return x.isAbsolute(c)?x.relative(e,c):c}function D(c,e,s,t){return e?t?[`${s}${c}:${o.reset} ${e}`,""]:[`**${c}**: ${e}`,""]:[]}async function q(c,e=!1){let s=c?.cwd??process.cwd(),t=s?x.basename(s):"unknown-project",r=new C,i=r.db.prepare(`
SELECT
id, sdk_session_id, type, title, subtitle, narrative,
facts, concepts, files_read, files_modified,
@@ -406,18 +406,18 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`).all(t,ie),d=r.db.prepare(`
`).all(t,ae),d=r.db.prepare(`
SELECT id, sdk_session_id, request, investigated, learned, completed, next_steps, created_at, created_at_epoch
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`).all(t,W+ae);if(i.length===0&&d.length===0)return r.close(),e?`
`).all(t,V+ce);if(i.length===0&&d.length===0)return r.close(),e?`
${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}
${o.gray}${"\u2500".repeat(60)}${o.reset}
${o.dim}No previous sessions found for this project yet.${o.reset}
`:`# [${t}] recent context
No previous sessions found for this project yet.`;let p=i,_=d.slice(0,W),E=p,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${t}] recent context`),n.push("")),E.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let O=d[0]?.id,L=_.map((u,T)=>{let m=T===0?null:d[T+1];return{...u,displayEpoch:m?m.created_at_epoch:u.created_at_epoch,displayTime:m?m.created_at:u.created_at,shouldShowLink:u.id!==O}}),S=[...E.map(u=>({type:"observation",data:u})),...L.map(u=>({type:"summary",data:u}))];S.sort((u,T)=>{let m=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,I=T.type==="observation"?T.data.created_at_epoch:T.data.displayEpoch;return m-I});let R=new Map;for(let u of S){let T=u.type==="observation"?u.data.created_at:u.data.displayTime,m=_e(T);R.has(m)||R.set(m,[]),R.get(m).push(u)}let l=Array.from(R.entries()).sort((u,T)=>{let m=new Date(u[0]).getTime(),I=new Date(T[0]).getTime();return m-I});for(let[u,T]of l){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let m=null,I="",y=!1;for(let k of T)if(k.type==="summary"){y&&(n.push(""),y=!1,m=null,I="");let g=k.data,A=`${g.request||"Session started"} (${ce(g.displayTime)})`,f=g.shouldShowLink?`claude-mem://session-summary/${g.id}`:"";if(e){let h=f?`${o.dim}[${f}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${g.id}${o.reset} ${A} ${h}`)}else{let h=f?` [\u2192](${f})`:"";n.push(`**\u{1F3AF} #S${g.id}** ${A}${h}`)}n.push("")}else{let g=k.data,A=de(g.files_modified),f=A.length>0?le(A[0],s):"General";f!==m&&(y&&n.push(""),e?n.push(`${o.dim}${f}${o.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),m=f,y=!0,I="");let h="\u2022";switch(g.type){case"bugfix":h="\u{1F534}";break;case"feature":h="\u{1F7E3}";break;case"refactor":h="\u{1F504}";break;case"change":h="\u2705";break;case"discovery":h="\u{1F535}";break;case"decision":h="\u{1F9E0}";break;default:h="\u2022"}let v=pe(g.created_at),X=g.title||"Untitled",U=ue(g.narrative),B=v!==I,V=B?v:"";if(I=v,e){let q=B?`${o.dim}${v}${o.reset}`:" ".repeat(v.length),K=U>0?`${o.dim}(~${U}t)${o.reset}`:"";n.push(` ${o.dim}#${g.id}${o.reset} ${q} ${h} ${X} ${K}`)}else n.push(`| #${g.id} | ${V||"\u2033"} | ${h} | ${X} | ~${U} |`)}y&&n.push("")}let a=d[0];a&&(a.investigated||a.learned||a.completed||a.next_steps)&&(n.push(...D("Investigated",a.investigated,o.blue,e)),n.push(...D("Learned",a.learned,o.yellow,e)),n.push(...D("Completed",a.completed,o.green,e)),n.push(...D("Next Steps",a.next_steps,o.magenta,e))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return r.close(),n.join(`
`).trimEnd()}var me=process.argv.includes("--colors");if(F.isTTY||me)Y(void 0,!0).then(c=>{console.log(c),process.exit(0)});else{let c="";F.on("data",e=>c+=e),F.on("end",async()=>{let e=c.trim()?JSON.parse(c):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await Y(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})}
No previous sessions found for this project yet.`;let p=i,_=d.slice(0,V),E=p,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${t}] recent context`),n.push("")),E.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let O=d[0]?.id,L=_.map((u,h)=>{let l=h===0?null:d[h+1];return{...u,displayEpoch:l?l.created_at_epoch:u.created_at_epoch,displayTime:l?l.created_at:u.created_at,shouldShowLink:u.id!==O}}),S=[...E.map(u=>({type:"observation",data:u})),...L.map(u=>({type:"summary",data:u}))];S.sort((u,h)=>{let l=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,I=h.type==="observation"?h.data.created_at_epoch:h.data.displayEpoch;return l-I});let R=new Map;for(let u of S){let h=u.type==="observation"?u.data.created_at:u.data.displayTime,l=me(h);R.has(l)||R.set(l,[]),R.get(l).push(u)}let m=Array.from(R.entries()).sort((u,h)=>{let l=new Date(u[0]).getTime(),I=new Date(h[0]).getTime();return l-I});for(let[u,h]of m){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let l=null,I="",y=!1;for(let U of h)if(U.type==="summary"){y&&(n.push(""),y=!1,l=null,I="");let T=U.data,A=`${T.request||"Session started"} (${_e(T.displayTime)})`,f=T.shouldShowLink?`claude-mem://session-summary/${T.id}`:"";if(e){let g=f?`${o.dim}[${f}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${T.id}${o.reset} ${A} ${g}`)}else{let g=f?` [\u2192](${f})`:"";n.push(`**\u{1F3AF} #S${T.id}** ${A}${g}`)}n.push("")}else{let T=U.data,A=pe(T.files_modified),f=A.length>0?Ee(A[0],s):"General";f!==l&&(y&&n.push(""),e?n.push(`${o.dim}${f}${o.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),l=f,y=!0,I="");let g="\u2022";switch(T.type){case"bugfix":g="\u{1F534}";break;case"feature":g="\u{1F7E3}";break;case"refactor":g="\u{1F504}";break;case"change":g="\u2705";break;case"discovery":g="\u{1F535}";break;case"decision":g="\u{1F9E0}";break;default:g="\u2022"}let v=ue(T.created_at),j=T.title||"Untitled",$=le(T.narrative),P=v!==I,K=P?v:"";if(I=v,e){let J=P?`${o.dim}${v}${o.reset}`:" ".repeat(v.length),Q=$>0?`${o.dim}(~${$}t)${o.reset}`:"";n.push(` ${o.dim}#${T.id}${o.reset} ${J} ${g} ${j} ${Q}`)}else n.push(`| #${T.id} | ${K||"\u2033"} | ${g} | ${j} | ~${$} |`)}y&&n.push("")}let a=d[0],k=p[0];a&&(a.investigated||a.learned||a.completed||a.next_steps)&&(!k||a.created_at_epoch>k.created_at_epoch)&&(n.push(...D("Investigated",a.investigated,o.blue,e)),n.push(...D("Learned",a.learned,o.yellow,e)),n.push(...D("Completed",a.completed,o.green,e)),n.push(...D("Next Steps",a.next_steps,o.magenta,e))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return r.close(),n.join(`
`).trimEnd()}var Te=process.argv.includes("--colors");if(X.isTTY||Te)q(void 0,!0).then(c=>{console.log(c),process.exit(0)});else{let c="";X.on("data",e=>c+=e),X.on("end",async()=>{let e=c.trim()?JSON.parse(c):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await q(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})}
+10 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import z from"path";import{stdin as U}from"process";import j from"better-sqlite3";import{join as u,dirname as X,basename as te}from"path";import{homedir as L}from"os";import{existsSync as ie,mkdirSync as F}from"fs";import{fileURLToPath as H}from"url";function P(){return typeof __dirname<"u"?__dirname:X(H(import.meta.url))}var B=P(),m=process.env.CLAUDE_MEM_DATA_DIR||u(L(),".claude-mem"),R=process.env.CLAUDE_CONFIG_DIR||u(L(),".claude"),pe=u(m,"archives"),de=u(m,"logs"),ce=u(m,"trash"),_e=u(m,"backups"),ue=u(m,"settings.json"),A=u(m,"claude-mem.db"),Ee=u(m,"vector-db"),me=u(R,"settings.json"),le=u(R,"commands"),Te=u(R,"CLAUDE.md");function C(a){F(a,{recursive:!0})}function v(){return u(B,"..","..")}var h=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(h||{}),N=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=h[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=h[e].padEnd(5),d=s.padEnd(6),c="";r?.correlationId?c=`[${r.correlationId}] `:r?.sessionId&&(c=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let T="";if(r){let{sessionId:l,sdkSessionId:b,correlationId:_,...p}=r;Object.keys(p).length>0&&(T=` {${Object.entries(p).map(([M,w])=>`${M}=${w}`).join(", ")}}`)}let S=`[${o}] [${i}] [${d}] ${c}${t}${T}${E}`;e===3?console.error(S):console.log(S)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},y=new N;var g=class{db;constructor(){C(m),this.db=new j(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import Y from"path";import{stdin as D}from"process";import F from"better-sqlite3";import{join as u,dirname as U,basename as J}from"path";import{homedir as f}from"os";import{existsSync as ee,mkdirSync as M}from"fs";import{fileURLToPath as w}from"url";function X(){return typeof __dirname<"u"?__dirname:U(w(import.meta.url))}var te=X(),m=process.env.CLAUDE_MEM_DATA_DIR||u(f(),".claude-mem"),h=process.env.CLAUDE_CONFIG_DIR||u(f(),".claude"),re=u(m,"archives"),ne=u(m,"logs"),oe=u(m,"trash"),ie=u(m,"backups"),ae=u(m,"settings.json"),L=u(m,"claude-mem.db"),pe=u(m,"vector-db"),de=u(h,"settings.json"),ce=u(h,"commands"),_e=u(h,"CLAUDE.md");function A(p){M(p,{recursive:!0})}var N=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(N||{}),O=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=N[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=N[e].padEnd(5),d=s.padEnd(6),c="";r?.correlationId?c=`[${r.correlationId}] `:r?.sessionId&&(c=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let T="";if(r){let{sessionId:l,sdkSessionId:b,correlationId:_,...a}=r;Object.keys(a).length>0&&(T=` {${Object.entries(a).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let S=`[${o}] [${i}] [${d}] ${c}${t}${T}${E}`;e===3?console.error(S):console.log(S)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},C=new O;var g=class{db;constructor(){A(m),this.db=new F(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -299,7 +299,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
`).run(s,e).changes===0?(y.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
`).run(s,e).changes===0?(C.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
UPDATE sdk_sessions
SET worker_port = ?
WHERE id = ?
@@ -369,7 +369,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE id >= ? ${o}
ORDER BY id ASC
LIMIT ?
`;try{let _=this.db.prepare(l).all(e,...i,t+1),p=this.db.prepare(b).all(e,...i,r+1);if(_.length===0&&p.length===0)return{observations:[],sessions:[],prompts:[]};d=_.length>0?_[_.length-1].created_at_epoch:s,c=p.length>0?p[p.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary observations:",_.message),{observations:[],sessions:[],prompts:[]}}}else{let l=`
`;try{let _=this.db.prepare(l).all(e,...i,t+1),a=this.db.prepare(b).all(e,...i,r+1);if(_.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=_.length>0?_[_.length-1].created_at_epoch:s,c=a.length>0?a[a.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary observations:",_.message),{observations:[],sessions:[],prompts:[]}}}else{let l=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${o}
@@ -381,7 +381,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE created_at_epoch >= ? ${o}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let _=this.db.prepare(l).all(s,...i,t),p=this.db.prepare(b).all(s,...i,r+1);if(_.length===0&&p.length===0)return{observations:[],sessions:[],prompts:[]};d=_.length>0?_[_.length-1].created_at_epoch:s,c=p.length>0?p[p.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary timestamps:",_.message),{observations:[],sessions:[],prompts:[]}}}let E=`
`;try{let _=this.db.prepare(l).all(s,...i,t),a=this.db.prepare(b).all(s,...i,r+1);if(_.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};d=_.length>0?_[_.length-1].created_at_epoch:s,c=a.length>0?a[a.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary timestamps:",_.message),{observations:[],sessions:[],prompts:[]}}}let E=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
@@ -397,4 +397,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let l=this.db.prepare(E).all(d,c,...i),b=this.db.prepare(T).all(d,c,...i),_=this.db.prepare(S).all(d,c,...i);return{observations:l,sessions:b.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:_.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import O from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function f(){try{let a=O.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=f();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=v(),e=O.join(a,"node_modules",".bin","pm2"),s=O.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function Z(a){if(!a)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=a,r=z.basename(s);await x();let n=new g,o=n.createSDKSession(e,r,t),i=n.incrementPromptCounter(o);n.saveUserPrompt(e,i,t),console.error(`[new-hook] Session ${o}, prompt #${i}`),n.close();let d=f();try{let c=await fetch(`http://127.0.0.1:${d}/sessions/${o}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let E=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${E}`)}}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(D("UserPromptSubmit",!0))}var I="";U.on("data",a=>I+=a);U.on("end",async()=>{let a=I?JSON.parse(I):void 0;await Z(a)});
`;try{let l=this.db.prepare(E).all(d,c,...i),b=this.db.prepare(T).all(d,c,...i),_=this.db.prepare(S).all(d,c,...i);return{observations:l,sessions:b.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:_.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function P(p,e,s){return p==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:p==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:p==="UserPromptSubmit"||p==="PostToolUse"?{continue:!0,suppressOutput:!0}:p==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function v(p,e,s={}){let t=P(p,e,s);return JSON.stringify(t)}import H from"path";import{homedir as B}from"os";import{existsSync as j,readFileSync as $}from"fs";var W=100;function R(){try{let p=H.join(B(),".claude-mem","settings.json");if(j(p)){let e=JSON.parse($(p,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function G(){try{let p=R();return(await fetch(`http://127.0.0.1:${p}/health`,{signal:AbortSignal.timeout(W)})).ok}catch{return!1}}async function y(){if(await G())return;let p=R();throw new Error(`Worker service is not responding on port ${p}.
If you just updated the plugin, PM2's watch mode should restart automatically.
If the problem persists, run: pm2 restart claude-mem-worker`)}async function K(p){if(!p)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=p,r=Y.basename(s);await y();let n=new g,o=n.createSDKSession(e,r,t),i=n.incrementPromptCounter(o);n.saveUserPrompt(e,i,t),console.error(`[new-hook] Session ${o}, prompt #${i}`),n.close();let d=R();try{let c=await fetch(`http://127.0.0.1:${d}/sessions/${o}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t,promptNumber:i}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let E=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${E}`)}}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(v("UserPromptSubmit",!0))}var I="";D.on("data",p=>I+=p);D.on("end",async()=>{let p=I?JSON.parse(I):void 0;await K(p)});
+40 -37
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import{stdin as U}from"process";import j from"better-sqlite3";import{join as E,dirname as F,basename as te}from"path";import{homedir as C}from"os";import{existsSync as ie,mkdirSync as X}from"fs";import{fileURLToPath as H}from"url";function P(){return typeof __dirname<"u"?__dirname:F(H(import.meta.url))}var B=P(),l=process.env.CLAUDE_MEM_DATA_DIR||E(C(),".claude-mem"),h=process.env.CLAUDE_CONFIG_DIR||E(C(),".claude"),de=E(l,"archives"),pe=E(l,"logs"),ce=E(l,"trash"),_e=E(l,"backups"),ue=E(l,"settings.json"),v=E(l,"claude-mem.db"),Ee=E(l,"vector-db"),me=E(h,"settings.json"),le=E(h,"commands"),Te=E(h,"CLAUDE.md");function y(a){X(a,{recursive:!0})}function D(){return E(B,"..","..")}var N=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(N||{}),O=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=N[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e<this.level)return;let n=new Date().toISOString().replace("T"," ").substring(0,23),i=N[e].padEnd(5),p=s.padEnd(6),u="";r?.correlationId?u=`[${r.correlationId}] `:r?.sessionId&&(u=`[session-${r.sessionId}] `);let c="";o!=null&&(this.level===0&&typeof o=="object"?c=`
`+JSON.stringify(o,null,2):c=" "+this.formatData(o));let m="";if(r){let{sessionId:T,sdkSessionId:S,correlationId:_,...d}=r;Object.keys(d).length>0&&(m=` {${Object.entries(d).map(([M,w])=>`${M}=${w}`).join(", ")}}`)}let g=`[${n}] [${i}] [${p}] ${u}${t}${m}${c}`;e===3?console.error(g):console.log(g)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},b=new O;var R=class{db;constructor(){y(l),this.db=new j(v),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import{stdin as D}from"process";import X from"better-sqlite3";import{join as m,dirname as U,basename as J}from"path";import{homedir as A}from"os";import{existsSync as ee,mkdirSync as M}from"fs";import{fileURLToPath as w}from"url";function F(){return typeof __dirname<"u"?__dirname:U(w(import.meta.url))}var te=F(),l=process.env.CLAUDE_MEM_DATA_DIR||m(A(),".claude-mem"),N=process.env.CLAUDE_CONFIG_DIR||m(A(),".claude"),re=m(l,"archives"),ne=m(l,"logs"),oe=m(l,"trash"),ie=m(l,"backups"),ae=m(l,"settings.json"),C=m(l,"claude-mem.db"),de=m(l,"vector-db"),pe=m(N,"settings.json"),ce=m(N,"commands"),_e=m(N,"CLAUDE.md");function v(d){M(d,{recursive:!0})}var O=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(O||{}),I=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=O[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=O[e].padEnd(5),p=s.padEnd(6),u="";r?.correlationId?u=`[${r.correlationId}] `:r?.sessionId&&(u=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let c="";if(r){let{sessionId:T,sdkSessionId:g,correlationId:_,...a}=r;Object.keys(a).length>0&&(c=` {${Object.entries(a).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let b=`[${o}] [${i}] [${p}] ${u}${t}${c}${E}`;e===3?console.error(b):console.log(b)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},S=new I;var R=class{db;constructor(){v(l),this.db=new X(C),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -244,12 +244,12 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT *
FROM observations
WHERE id = ?
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,o=t==="date_asc"?"ASC":"DESC",n=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT *
FROM observations
WHERE id IN (${i})
ORDER BY created_at_epoch ${o}
${n}
ORDER BY created_at_epoch ${n}
${o}
`).all(...e)}getSummaryForSession(e){return this.db.prepare(`
SELECT
request, investigated, learned, completed, next_steps,
@@ -262,7 +262,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT files_read, files_modified
FROM observations
WHERE sdk_session_id = ?
`).all(e),r=new Set,o=new Set;for(let n of t){if(n.files_read)try{let i=JSON.parse(n.files_read);Array.isArray(i)&&i.forEach(p=>r.add(p))}catch{}if(n.files_modified)try{let i=JSON.parse(n.files_modified);Array.isArray(i)&&i.forEach(p=>o.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(o)}}getSessionById(e){return this.db.prepare(`
`).all(e),r=new Set,n=new Set;for(let o of t){if(o.files_read)try{let i=JSON.parse(o.files_read);Array.isArray(i)&&i.forEach(p=>r.add(p))}catch{}if(o.files_modified)try{let i=JSON.parse(o.files_modified);Array.isArray(i)&&i.forEach(p=>n.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
SELECT id, claude_session_id, sdk_session_id, project, user_prompt
FROM sdk_sessions
WHERE id = ?
@@ -289,17 +289,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime(),i=this.db.prepare(`
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),i=this.db.prepare(`
INSERT OR IGNORE INTO sdk_sessions
(claude_session_id, sdk_session_id, project, user_prompt, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, ?, 'active')
`).run(e,e,s,t,r.toISOString(),o);return i.lastInsertRowid===0||i.changes===0?this.db.prepare(`
`).run(e,e,s,t,r.toISOString(),n);return i.lastInsertRowid===0||i.changes===0?this.db.prepare(`
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
`).get(e).id:i.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
`).run(s,e).changes===0?(b.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
`).run(s,e).changes===0?(S.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
UPDATE sdk_sessions
SET worker_port = ?
WHERE id = ?
@@ -308,33 +308,33 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM sdk_sessions
WHERE id = ?
LIMIT 1
`).get(e)?.worker_port||null}saveUserPrompt(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(`
`).get(e)?.worker_port||null}saveUserPrompt(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
INSERT INTO user_prompts
(claude_session_id, prompt_number, prompt_text, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?)
`).run(e,s,t,r.toISOString(),o).lastInsertRowid}storeObservation(e,s,t,r){let o=new Date,n=o.getTime();this.db.prepare(`
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}storeObservation(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,o.toISOString(),n),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let c=this.db.prepare(`
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let E=this.db.prepare(`
INSERT INTO observations
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
files_read, files_modified, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),n);return{id:Number(c.lastInsertRowid),createdAtEpoch:n}}storeSummary(e,s,t,r){let o=new Date,n=o.getTime();this.db.prepare(`
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),o);return{id:Number(E.lastInsertRowid),createdAtEpoch:o}}storeSummary(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,o.toISOString(),n),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let c=this.db.prepare(`
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let E=this.db.prepare(`
INSERT INTO session_summaries
(sdk_session_id, project, request, investigated, learned, completed,
next_steps, notes, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),n);return{id:Number(c.lastInsertRowid),createdAtEpoch:n}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),o);return{id:Number(E.lastInsertRowid),createdAtEpoch:o}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
UPDATE sdk_sessions
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
@@ -342,12 +342,12 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,o=t==="date_asc"?"ASC":"DESC",n=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT * FROM session_summaries
WHERE id IN (${i})
ORDER BY created_at_epoch ${o}
${n}
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,o=t==="date_asc"?"ASC":"DESC",n=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
ORDER BY created_at_epoch ${n}
${o}
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT
up.*,
s.project,
@@ -355,46 +355,49 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.id IN (${i})
ORDER BY up.created_at_epoch ${o}
${n}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,o){let n=o?"AND project = ?":"",i=o?[o]:[],p,u;if(e!==null){let T=`
ORDER BY up.created_at_epoch ${n}
${o}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,n){let o=n?"AND project = ?":"",i=n?[n]:[],p,u;if(e!==null){let T=`
SELECT id, created_at_epoch
FROM observations
WHERE id <= ? ${n}
WHERE id <= ? ${o}
ORDER BY id DESC
LIMIT ?
`,S=`
`,g=`
SELECT id, created_at_epoch
FROM observations
WHERE id >= ? ${n}
WHERE id >= ? ${o}
ORDER BY id ASC
LIMIT ?
`;try{let _=this.db.prepare(T).all(e,...i,t+1),d=this.db.prepare(S).all(e,...i,r+1);if(_.length===0&&d.length===0)return{observations:[],sessions:[],prompts:[]};p=_.length>0?_[_.length-1].created_at_epoch:s,u=d.length>0?d[d.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary observations:",_.message),{observations:[],sessions:[],prompts:[]}}}else{let T=`
`;try{let _=this.db.prepare(T).all(e,...i,t+1),a=this.db.prepare(g).all(e,...i,r+1);if(_.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};p=_.length>0?_[_.length-1].created_at_epoch:s,u=a.length>0?a[a.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary observations:",_.message),{observations:[],sessions:[],prompts:[]}}}else{let T=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${n}
WHERE created_at_epoch <= ? ${o}
ORDER BY created_at_epoch DESC
LIMIT ?
`,S=`
`,g=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch >= ? ${n}
WHERE created_at_epoch >= ? ${o}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let _=this.db.prepare(T).all(s,...i,t),d=this.db.prepare(S).all(s,...i,r+1);if(_.length===0&&d.length===0)return{observations:[],sessions:[],prompts:[]};p=_.length>0?_[_.length-1].created_at_epoch:s,u=d.length>0?d[d.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary timestamps:",_.message),{observations:[],sessions:[],prompts:[]}}}let c=`
`;try{let _=this.db.prepare(T).all(s,...i,t),a=this.db.prepare(g).all(s,...i,r+1);if(_.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};p=_.length>0?_[_.length-1].created_at_epoch:s,u=a.length>0?a[a.length-1].created_at_epoch:s}catch(_){return console.error("[SessionStore] Error getting boundary timestamps:",_.message),{observations:[],sessions:[],prompts:[]}}}let E=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${n}
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
ORDER BY created_at_epoch ASC
`,m=`
`,c=`
SELECT *
FROM session_summaries
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${n}
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
ORDER BY created_at_epoch ASC
`,g=`
`,b=`
SELECT up.*, s.project, s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${n.replace("project","s.project")}
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let T=this.db.prepare(c).all(p,u,...i),S=this.db.prepare(m).all(p,u,...i),_=this.db.prepare(g).all(p,u,...i);return{observations:T,sessions:S.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:_.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import I from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function L(){try{let a=I.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=L();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=D(),e=I.join(a,"node_modules",".bin","pm2"),s=I.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}var z=new Set(["ListMcpResourcesTool"]);async function Z(a){if(!a)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_response:r}=a;if(z.has(s)){console.log(f("PostToolUse",!0));return}await x();let o=new R,n=o.createSDKSession(e,"",""),i=o.getPromptCounter(n);o.close();let p=b.formatTool(s,t),u=L();b.dataIn("HOOK",`PostToolUse: ${p}`,{sessionId:n,workerPort:u});try{let c=await fetch(`http://127.0.0.1:${u}/sessions/${n}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_response:r!==void 0?JSON.stringify(r):"{}",prompt_number:i}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw b.failure("HOOK","Failed to send observation",{sessionId:n,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}b.debug("HOOK","Observation sent successfully",{sessionId:n,toolName:s})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var A="";U.on("data",a=>A+=a);U.on("end",async()=>{let a=A?JSON.parse(A):void 0;await Z(a)});
`;try{let T=this.db.prepare(E).all(p,u,...i),g=this.db.prepare(c).all(p,u,...i),_=this.db.prepare(b).all(p,u,...i);return{observations:T,sessions:g.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:_.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function H(d,e,s){return d==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:d==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:d==="UserPromptSubmit"||d==="PostToolUse"?{continue:!0,suppressOutput:!0}:d==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(d,e,s={}){let t=H(d,e,s);return JSON.stringify(t)}import P from"path";import{homedir as B}from"os";import{existsSync as j,readFileSync as $}from"fs";var W=100;function h(){try{let d=P.join(B(),".claude-mem","settings.json");if(j(d)){let e=JSON.parse($(d,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function G(){try{let d=h();return(await fetch(`http://127.0.0.1:${d}/health`,{signal:AbortSignal.timeout(W)})).ok}catch{return!1}}async function y(){if(await G())return;let d=h();throw new Error(`Worker service is not responding on port ${d}.
If you just updated the plugin, PM2's watch mode should restart automatically.
If the problem persists, run: pm2 restart claude-mem-worker`)}var Y=new Set(["ListMcpResourcesTool"]);async function K(d){if(!d)throw new Error("saveHook requires input");let{session_id:e,cwd:s,tool_name:t,tool_input:r,tool_response:n}=d;if(Y.has(t)){console.log(f("PostToolUse",!0));return}await y();let o=new R,i=o.createSDKSession(e,"",""),p=o.getPromptCounter(i);o.close();let u=S.formatTool(t,r),E=h();S.dataIn("HOOK",`PostToolUse: ${u}`,{sessionId:i,workerPort:E});try{let c=await fetch(`http://127.0.0.1:${E}/sessions/${i}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:t,tool_input:r!==void 0?JSON.stringify(r):"{}",tool_response:n!==void 0?JSON.stringify(n):"{}",prompt_number:p,cwd:s||""}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let b=await c.text();throw S.failure("HOOK","Failed to send observation",{sessionId:i,status:c.status},b),new Error(`Failed to send observation to worker: ${c.status} ${b}`)}S.debug("HOOK","Observation sent successfully",{sessionId:i,toolName:t})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var L="";D.on("data",d=>L+=d);D.on("end",async()=>{let d=L?JSON.parse(L):void 0;await K(d)});
+43 -38
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import{stdin as U}from"process";import j from"better-sqlite3";import{join as _,dirname as F,basename as se}from"path";import{homedir as A}from"os";import{existsSync as oe,mkdirSync as X}from"fs";import{fileURLToPath as H}from"url";function P(){return typeof __dirname<"u"?__dirname:F(H(import.meta.url))}var B=P(),E=process.env.CLAUDE_MEM_DATA_DIR||_(A(),".claude-mem"),h=process.env.CLAUDE_CONFIG_DIR||_(A(),".claude"),ae=_(E,"archives"),de=_(E,"logs"),pe=_(E,"trash"),ce=_(E,"backups"),_e=_(E,"settings.json"),C=_(E,"claude-mem.db"),ue=_(E,"vector-db"),me=_(h,"settings.json"),Ee=_(h,"commands"),le=_(h,"CLAUDE.md");function y(a){X(a,{recursive:!0})}function v(){return _(B,"..","..")}var N=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(N||{}),O=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=N[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let o=new Date().toISOString().replace("T"," ").substring(0,23),i=N[e].padEnd(5),p=s.padEnd(6),u="";r?.correlationId?u=`[${r.correlationId}] `:r?.sessionId&&(u=`[session-${r.sessionId}] `);let m="";n!=null&&(this.level===0&&typeof n=="object"?m=`
`+JSON.stringify(n,null,2):m=" "+this.formatData(n));let T="";if(r){let{sessionId:l,sdkSessionId:b,correlationId:c,...d}=r;Object.keys(d).length>0&&(T=` {${Object.entries(d).map(([M,w])=>`${M}=${w}`).join(", ")}}`)}let g=`[${o}] [${i}] [${p}] ${u}${t}${T}${m}`;e===3?console.error(g):console.log(g)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},S=new O;var R=class{db;constructor(){y(E),this.db=new j(C),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import{stdin as D}from"process";import{readFileSync as Y,existsSync as K}from"fs";import X from"better-sqlite3";import{join as u,dirname as U,basename as z}from"path";import{homedir as L}from"os";import{existsSync as te,mkdirSync as M}from"fs";import{fileURLToPath as w}from"url";function F(){return typeof __dirname<"u"?__dirname:U(w(import.meta.url))}var ne=F(),E=process.env.CLAUDE_MEM_DATA_DIR||u(L(),".claude-mem"),N=process.env.CLAUDE_CONFIG_DIR||u(L(),".claude"),oe=u(E,"archives"),ie=u(E,"logs"),ae=u(E,"trash"),pe=u(E,"backups"),de=u(E,"settings.json"),A=u(E,"claude-mem.db"),ce=u(E,"vector-db"),_e=u(N,"settings.json"),ue=u(N,"commands"),me=u(N,"CLAUDE.md");function C(a){M(a,{recursive:!0})}var O=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(O||{}),f=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=O[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),o=O[e].padEnd(5),d=s.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let m="";n!=null&&(this.level===0&&typeof n=="object"?m=`
`+JSON.stringify(n,null,2):m=" "+this.formatData(n));let S="";if(r){let{sessionId:l,sdkSessionId:g,correlationId:c,...p}=r;Object.keys(p).length>0&&(S=` {${Object.entries(p).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let b=`[${i}] [${o}] [${d}] ${_}${t}${S}${m}`;e===3?console.error(b):console.log(b)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},T=new f;var R=class{db;constructor(){C(E),this.db=new X(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(d=>d.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
CREATE TABLE session_summaries_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL,
@@ -244,12 +244,12 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT *
FROM observations
WHERE id = ?
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",i=r?`LIMIT ${r}`:"",o=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT *
FROM observations
WHERE id IN (${i})
WHERE id IN (${o})
ORDER BY created_at_epoch ${n}
${o}
${i}
`).all(...e)}getSummaryForSession(e){return this.db.prepare(`
SELECT
request, investigated, learned, completed, next_steps,
@@ -262,7 +262,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT files_read, files_modified
FROM observations
WHERE sdk_session_id = ?
`).all(e),r=new Set,n=new Set;for(let o of t){if(o.files_read)try{let i=JSON.parse(o.files_read);Array.isArray(i)&&i.forEach(p=>r.add(p))}catch{}if(o.files_modified)try{let i=JSON.parse(o.files_modified);Array.isArray(i)&&i.forEach(p=>n.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let o=JSON.parse(i.files_read);Array.isArray(o)&&o.forEach(d=>r.add(d))}catch{}if(i.files_modified)try{let o=JSON.parse(i.files_modified);Array.isArray(o)&&o.forEach(d=>n.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
SELECT id, claude_session_id, sdk_session_id, project, user_prompt
FROM sdk_sessions
WHERE id = ?
@@ -289,17 +289,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),i=this.db.prepare(`
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),o=this.db.prepare(`
INSERT OR IGNORE INTO sdk_sessions
(claude_session_id, sdk_session_id, project, user_prompt, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, ?, 'active')
`).run(e,e,s,t,r.toISOString(),n);return i.lastInsertRowid===0||i.changes===0?this.db.prepare(`
`).run(e,e,s,t,r.toISOString(),n);return o.lastInsertRowid===0||o.changes===0?this.db.prepare(`
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
`).get(e).id:i.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
`).get(e).id:o.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
`).run(s,e).changes===0?(S.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
`).run(s,e).changes===0?(T.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
UPDATE sdk_sessions
SET worker_port = ?
WHERE id = ?
@@ -312,29 +312,29 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
INSERT INTO user_prompts
(claude_session_id, prompt_number, prompt_text, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?)
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}storeObservation(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}storeObservation(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
`).run(e,e,s,n.toISOString(),i),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
INSERT INTO observations
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
files_read, files_modified, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),o);return{id:Number(m.lastInsertRowid),createdAtEpoch:o}}storeSummary(e,s,t,r){let n=new Date,o=n.getTime();this.db.prepare(`
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),i);return{id:Number(m.lastInsertRowid),createdAtEpoch:i}}storeSummary(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,n.toISOString(),o),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
`).run(e,e,s,n.toISOString(),i),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let m=this.db.prepare(`
INSERT INTO session_summaries
(sdk_session_id, project, request, investigated, learned, completed,
next_steps, notes, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),o);return{id:Number(m.lastInsertRowid),createdAtEpoch:o}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),i);return{id:Number(m.lastInsertRowid),createdAtEpoch:i}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
UPDATE sdk_sessions
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
@@ -342,59 +342,64 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",i=r?`LIMIT ${r}`:"",o=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT * FROM session_summaries
WHERE id IN (${i})
WHERE id IN (${o})
ORDER BY created_at_epoch ${n}
${o}
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",o=r?`LIMIT ${r}`:"",i=e.map(()=>"?").join(",");return this.db.prepare(`
${i}
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,n=t==="date_asc"?"ASC":"DESC",i=r?`LIMIT ${r}`:"",o=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT
up.*,
s.project,
s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.id IN (${i})
WHERE up.id IN (${o})
ORDER BY up.created_at_epoch ${n}
${o}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,n){let o=n?"AND project = ?":"",i=n?[n]:[],p,u;if(e!==null){let l=`
${i}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,n){let i=n?"AND project = ?":"",o=n?[n]:[],d,_;if(e!==null){let l=`
SELECT id, created_at_epoch
FROM observations
WHERE id <= ? ${o}
WHERE id <= ? ${i}
ORDER BY id DESC
LIMIT ?
`,b=`
`,g=`
SELECT id, created_at_epoch
FROM observations
WHERE id >= ? ${o}
WHERE id >= ? ${i}
ORDER BY id ASC
LIMIT ?
`;try{let c=this.db.prepare(l).all(e,...i,t+1),d=this.db.prepare(b).all(e,...i,r+1);if(c.length===0&&d.length===0)return{observations:[],sessions:[],prompts:[]};p=c.length>0?c[c.length-1].created_at_epoch:s,u=d.length>0?d[d.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary observations:",c.message),{observations:[],sessions:[],prompts:[]}}}else{let l=`
`;try{let c=this.db.prepare(l).all(e,...o,t+1),p=this.db.prepare(g).all(e,...o,r+1);if(c.length===0&&p.length===0)return{observations:[],sessions:[],prompts:[]};d=c.length>0?c[c.length-1].created_at_epoch:s,_=p.length>0?p[p.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary observations:",c.message),{observations:[],sessions:[],prompts:[]}}}else{let l=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${o}
WHERE created_at_epoch <= ? ${i}
ORDER BY created_at_epoch DESC
LIMIT ?
`,b=`
`,g=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch >= ? ${o}
WHERE created_at_epoch >= ? ${i}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let c=this.db.prepare(l).all(s,...i,t),d=this.db.prepare(b).all(s,...i,r+1);if(c.length===0&&d.length===0)return{observations:[],sessions:[],prompts:[]};p=c.length>0?c[c.length-1].created_at_epoch:s,u=d.length>0?d[d.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary timestamps:",c.message),{observations:[],sessions:[],prompts:[]}}}let m=`
`;try{let c=this.db.prepare(l).all(s,...o,t),p=this.db.prepare(g).all(s,...o,r+1);if(c.length===0&&p.length===0)return{observations:[],sessions:[],prompts:[]};d=c.length>0?c[c.length-1].created_at_epoch:s,_=p.length>0?p[p.length-1].created_at_epoch:s}catch(c){return console.error("[SessionStore] Error getting boundary timestamps:",c.message),{observations:[],sessions:[],prompts:[]}}}let m=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${i}
ORDER BY created_at_epoch ASC
`,T=`
`,S=`
SELECT *
FROM session_summaries
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${o}
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${i}
ORDER BY created_at_epoch ASC
`,g=`
`,b=`
SELECT up.*, s.project, s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${i.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let l=this.db.prepare(m).all(p,u,...i),b=this.db.prepare(T).all(p,u,...i),c=this.db.prepare(g).all(p,u,...i);return{observations:l,sessions:b.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:c.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import f from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var q=100,V=100,J=1e4;function I(){try{let a=f.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=I();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(q)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,V))}return!1}async function x(){if(await k())return;let a=v(),e=f.join(a,"node_modules",".bin","pm2"),s=f.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function z(a){if(!a)throw new Error("summaryHook requires input");let{session_id:e}=a;await x();let s=new R,t=s.createSDKSession(e,"",""),r=s.getPromptCounter(t);s.close();let n=I();S.dataIn("HOOK","Stop: Requesting summary",{sessionId:t,workerPort:n,promptNumber:r});try{let o=await fetch(`http://127.0.0.1:${n}/sessions/${t}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:r}),signal:AbortSignal.timeout(2e3)});if(!o.ok){let i=await o.text();throw S.failure("HOOK","Failed to generate summary",{sessionId:t,status:o.status},i),new Error(`Failed to request summary from worker: ${o.status} ${i}`)}S.debug("HOOK","Summary request sent successfully",{sessionId:t})}catch(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):o}finally{await fetch(`http://127.0.0.1:${n}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})})}console.log(D("Stop",!0))}var L="";U.on("data",a=>L+=a);U.on("end",async()=>{let a=L?JSON.parse(L):void 0;await z(a)});
`;try{let l=this.db.prepare(m).all(d,_,...o),g=this.db.prepare(S).all(d,_,...o),c=this.db.prepare(b).all(d,_,...o);return{observations:l,sessions:g.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:c.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function H(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function y(a,e,s={}){let t=H(a,e,s);return JSON.stringify(t)}import B from"path";import{homedir as P}from"os";import{existsSync as j,readFileSync as $}from"fs";var W=100;function h(){try{let a=B.join(P(),".claude-mem","settings.json");if(j(a)){let e=JSON.parse($(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function G(){try{let a=h();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(W)})).ok}catch{return!1}}async function v(){if(await G())return;let a=h();throw new Error(`Worker service is not responding on port ${a}.
If you just updated the plugin, PM2's watch mode should restart automatically.
If the problem persists, run: pm2 restart claude-mem-worker`)}function q(a){if(!a||!K(a))return"";try{let e=Y(a,"utf-8").trim();if(!e)return"";let s=e.split(`
`);for(let t=s.length-1;t>=0;t--)try{let r=JSON.parse(s[t]);if(r.role==="user"&&r.content){if(typeof r.content=="string")return r.content;if(Array.isArray(r.content))return r.content.filter(i=>i.type==="text").map(i=>i.text).join(`
`)}}catch{continue}}catch(e){T.error("HOOK","Failed to read transcript",{transcriptPath:a},e)}return""}async function V(a){if(!a)throw new Error("summaryHook requires input");let{session_id:e}=a;await v();let s=new R,t=s.createSDKSession(e,"",""),r=s.getPromptCounter(t);s.close();let n=h(),i=q(a.transcript_path||"");T.dataIn("HOOK","Stop: Requesting summary",{sessionId:t,workerPort:n,promptNumber:r,hasLastUserMessage:!!i});try{let o=await fetch(`http://127.0.0.1:${n}/sessions/${t}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:r,last_user_message:i}),signal:AbortSignal.timeout(2e3)});if(!o.ok){let d=await o.text();throw T.failure("HOOK","Failed to generate summary",{sessionId:t,status:o.status},d),new Error(`Failed to request summary from worker: ${o.status} ${d}`)}T.debug("HOOK","Summary request sent successfully",{sessionId:t})}catch(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):o}finally{await fetch(`http://127.0.0.1:${n}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})})}console.log(y("Stop",!0))}var I="";D.on("data",a=>I+=a);D.on("end",async()=>{let a=I?JSON.parse(I):void 0;await V(a)});
+5 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
import{execSync as _}from"child_process";import{join as i}from"path";import{homedir as p}from"os";import{existsSync as D}from"fs";import l from"path";import{homedir as f}from"os";import{existsSync as g,readFileSync as h}from"fs";import{join as e,dirname as m,basename as A}from"path";import{homedir as c}from"os";import{fileURLToPath as u}from"url";function d(){return typeof __dirname<"u"?__dirname:m(u(import.meta.url))}var w=d(),t=process.env.CLAUDE_MEM_DATA_DIR||e(c(),".claude-mem"),s=process.env.CLAUDE_CONFIG_DIR||e(c(),".claude"),P=e(t,"archives"),R=e(t,"logs"),S=e(t,"trash"),I=e(t,"backups"),b=e(t,"settings.json"),v=e(t,"claude-mem.db"),H=e(t,"vector-db"),L=e(s,"settings.json"),M=e(s,"commands"),U=e(s,"CLAUDE.md");function a(){try{let o=l.join(f(),".claude-mem","settings.json");if(g(o)){let n=JSON.parse(h(o,"utf-8")),r=parseInt(n.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(r))return r}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}var x=i(p(),".claude","plugins","marketplaces","thedotmack"),k=i(x,"node_modules");D(k)||(console.error(`
import{execSync as p}from"child_process";import{join as r}from"path";import{homedir as s}from"os";import{existsSync as u}from"fs";import i from"path";import{homedir as a}from"os";import{existsSync as c,readFileSync as l}from"fs";function n(){try{let e=i.join(a(),".claude-mem","settings.json");if(c(e)){let t=JSON.parse(l(e,"utf-8")),o=parseInt(t.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(o))return o}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}var m=r(s(),".claude","plugins","marketplaces","thedotmack"),d=r(m,"node_modules");u(d)||(console.error(`
---
\u{1F389} Note: This appears under Plugin Hook Error, but it's not an error. That's the only option for
user messages in Claude Code UI until a better method is provided.
@@ -17,12 +17,12 @@ Dependencies have been installed in the background. This only happens once.
Thank you for installing Claude-Mem!
This message was not added to your startup context, so you can continue working as normal.
`),process.exit(3));try{let o=i(p(),".claude","plugins","marketplaces","thedotmack","plugin","scripts","context-hook.js"),n=_(`node "${o}" --colors`,{encoding:"utf8"}),r=a();console.error(`
`),process.exit(3));try{let e=r(s(),".claude","plugins","marketplaces","thedotmack","plugin","scripts","context-hook.js"),t=p(`node "${e}" --colors`,{encoding:"utf8"}),o=n();console.error(`
\u{1F4DD} Claude-Mem Context Loaded
\u2139\uFE0F Note: This appears as stderr but is informational only
`+n+`
`+t+`
\u{1F4FA} Watch live in browser http://localhost:${r}/ (New! v5.1)
`)}catch(o){console.error(`\u274C Failed to load context display: ${o}`)}process.exit(3);
\u{1F4FA} Watch live in browser http://localhost:${o}/ (New! v5.1)
`)}catch(e){console.error(`\u274C Failed to load context display: ${e}`)}process.exit(3);
File diff suppressed because one or more lines are too long
+196
View File
@@ -0,0 +1,196 @@
---
name: mem-search
description: Search claude-mem's persistent cross-session memory database to find work from previous conversations days, weeks, or months ago. Access past session summaries, bug fixes, feature implementations, and decisions that are NOT in the current conversation context. Use when user asks "did we already solve this?", "how did we do X last time?", "what happened in last week's session?", or needs information from previous sessions stored in the PM2-managed database. Searches observations, session summaries, and user prompts across entire project history.
---
# Memory Search
Access claude-mem's persistent cross-session memory through HTTP API. Find past work, understand context across sessions, and learn from previous decisions.
## When to Use This Skill
**Use when users ask about work from PREVIOUS sessions** (not current conversation):
### Temporal Triggers (Key Indicators)
- "Did we **already** fix this bug?" or "Have we seen this error **before**?"
- "How did we solve X **last time**?" or "What approach did we take **previously**?"
- "What did we do in **yesterday's/last week's/last month's** session?"
- "**When** did we last work on this?" or "What's the **history** of this file?"
### Cross-Session Queries
- "Show me all authentication-related changes **across all sessions**"
- "What features did we add **last month**?" (not "today" or "this session")
- "Why did we choose this approach **before**?" (decisions from past sessions)
- "What files did we modify **when we added X**?" (historical context)
**Do NOT use** for current session work, future planning, or questions Claude can answer from current conversation context.
## Common Trigger Phrases
This skill activates when detecting phrases about **cross-session history**:
- "Did we already solve this?" / "Have we done this before?"
- "How did we implement X last time?"
- "What did we work on yesterday/last week/last month?"
- "Show me the history of [file/feature/decision]"
- "When did we fix/add/change X?"
- "What was happening around [date/time]?"
- "Catch me up on recent sessions" / "What have we been doing?"
- "What changes to [filename] across all sessions?"
**Unique identifiers:** claude-mem, persistent memory, cross-session database, session history, PM2-managed database
## Available Operations
### Full-Text Search
- **observations** - Search all observations by keyword (bugs, features, decisions, discoveries, changes)
- Use when: "How did we implement X?" or "What bugs did we fix?"
- Example: Search for "authentication JWT" to find auth-related work
- **sessions** - Search session summaries to find what was accomplished when
- Use when: "What did we accomplish last time?" or "What was the goal of that session?"
- Example: Find sessions where "added login feature"
- **prompts** - Find what users have asked about in previous sessions
- Use when: "Did I ask about this before?" or "What did I request last week?"
- Example: Search for "database migration" in past user prompts
### Filtered Search
- **by-type** - Filter observations by type (bugfix, feature, refactor, decision, discovery, change)
- Use when: "Show me all bug fixes" or "List features we added"
- Example: Get all observations with type=bugfix from last month
- **by-concept** - Find observations tagged with specific concepts (problem-solution, how-it-works, gotcha)
- Use when: "What patterns did we discover?" or "Show me gotchas"
- Example: Find all observations tagged with concept "gotcha"
- **by-file** - Find all work related to a specific file path across all sessions
- Use when: "What changes to auth.ts?" or "History of this file"
- Example: Get all work related to "src/auth/login.ts"
### Timeline & Context
- **recent-context** - Get last N sessions with summaries and observations
- Use when: "What's been happening?" or "Catch me up on recent work"
- Example: Get last 3 sessions with limit=3
- **timeline** - Get chronological context around a specific point in time (before/after window)
- Use when: "What was happening around [date]?" or "Show me context from that time"
- Example: Timeline around session 123 with depth 5 before and after
- **timeline-by-query** - Search first, then get timeline context around best match
- Use when: "When did we implement auth?" combined with "show me context around that time"
- Example: Search for "OAuth implementation" then get surrounding timeline
For detailed instructions on any operation, read the corresponding file in [operations/](operations/).
## Quick Decision Guide
**What is the user asking about?**
1. **Recent work** (last 3-5 sessions) → Use **recent-context** with limit=3-5
2. **Specific topic/keyword** (bugs, features, decisions) → Use **observations** search
3. **Specific file history** (changes to a file) → Use **by-file** search
4. **Timeline/chronology** (what happened when) → Use **timeline** or **timeline-by-query**
5. **Type-specific** (all bug fixes, all features) → Use **by-type** filter
**Most common:** Use **observations** search for general "how did we..." questions.
## Progressive Disclosure Workflow (Token Efficiency)
**Core Principle**: Find high-signal items in index format FIRST (~50-100 tokens each), then request full details ONLY for relevant items (~500-1000 tokens each).
**4-Step Workflow:**
1. **Start with Index Format**
- Always use `format=index` initially
- Set `limit=3-5` (not 10-20)
- Review titles and dates to assess relevance
- Token cost: ~50-100 per result
2. **Identify Relevant Items**
- Scan index results
- Discard irrelevant items from list
- Keep only 1-3 most relevant
3. **Request Full Details Selectively**
- Use `format=full` ONLY for specific relevant items
- Token cost: ~500-1000 per result
- **10x cost difference** - be selective
4. **Refine with Filters**
- Use type, dateRange, concepts, files filters
- Paginate with offset if needed
- Narrow scope before expanding limits
**DO:**
- ✅ Start with `format=index` and `limit=3-5`
- ✅ Use filters (type, dateRange, concepts, files) to narrow results
- ✅ Request `format=full` ONLY for specific relevant items
- ✅ Use offset for pagination instead of large limits
**DON'T:**
- ❌ Jump straight to `format=full`
- ❌ Request `limit=20` without assessing index results first
- ❌ Load full details for all results upfront
- ❌ Skip index format to "save a step" (costs 10x more tokens)
See [principles/progressive-disclosure.md](principles/progressive-disclosure.md) for complete workflow with examples and token calculations.
## Quick Reference Table
| Need | Operation | Key Parameters |
|------|-----------|----------------|
| Recent context | recent-context | limit=3-5 |
| Search observations | observations | query, format=index, limit=5 |
| Search sessions | sessions | query, format=index, limit=5 |
| Find by type | by-type | type=(bugfix\|feature\|decision), format=index |
| Find by file | by-file | filePath, format=index |
| Timeline around event | timeline | anchor=(sessionDbId), depth_before=5, depth_after=5 |
| Search + timeline | timeline-by-query | query, mode=auto |
## Common Workflows
For step-by-step guides on typical user requests, see [operations/common-workflows.md](operations/common-workflows.md):
- Understanding past work from previous sessions
- Finding specific bug fixes from history
- Understanding file history across sessions
- Timeline investigation workflows
- Search composition patterns
## Response Formatting
For guidelines on presenting search results to users, see [operations/formatting.md](operations/formatting.md):
- Index format responses (compact lists with titles/dates)
- Full format responses (complete observation details)
- Timeline responses (chronologically grouped)
- Error handling and user-friendly messages
## Technical Notes
- **Port:** Default 37777 (configurable via `CLAUDE_MEM_WORKER_PORT`)
- **Response format:** Always JSON
- **Search engine:** FTS5 full-text search + structured filters
- **All operations:** HTTP GET with query parameters
- **Worker:** PM2-managed background process
## Error Handling
If HTTP request fails:
1. Inform user the claude-mem search service isn't available
2. Suggest checking if worker is running: `pm2 list` or `pm2 status claude-mem-worker`
3. Offer to help troubleshoot using the troubleshoot skill
## Resources
**Principles:**
- [principles/progressive-disclosure.md](principles/progressive-disclosure.md) - Complete 4-step workflow with token calculations
- [principles/anti-patterns.md](principles/anti-patterns.md) - 5 anti-patterns to avoid with LLM behavior insights
**Operations:**
- [operations/](operations/) - Detailed instructions for each operation (9 operations + help)
- [operations/common-workflows.md](operations/common-workflows.md) - Step-by-step workflow guides
- [operations/formatting.md](operations/formatting.md) - Response formatting templates
---
**Remember:** This skill searches **cross-session persistent memory**, NOT current conversation. Start with index format for token efficiency. Use temporal triggers to differentiate from native Claude memory.
@@ -0,0 +1,124 @@
# Search by Concept
Find observations tagged with specific concepts.
## When to Use
- User asks: "What discoveries did we make?"
- User asks: "What patterns did we identify?"
- User asks: "What gotchas did we encounter?"
- Looking for observations with semantic tags
## Command
```bash
curl -s "http://localhost:37777/api/search/by-concept?concept=discovery&format=index&limit=5"
```
## Parameters
- **concept** (required): Concept tag to search for
- `discovery` - New discoveries and insights
- `problem-solution` - Problems and their solutions
- `what-changed` - Change descriptions
- `how-it-works` - Explanations of mechanisms
- `pattern` - Identified patterns
- `gotcha` - Edge cases and gotchas
- `change` - General changes
- **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews of observations by concept
- Finding IDs for deeper investigation
- Listing multiple results
- **Token cost: ~50-100 per result**
**Use format=full for:**
- Complete details including narrative, facts, files, concepts
- Understanding the full context of specific observations
- **Token cost: ~500-1000 per result**
## Example Response (format=index)
```json
{
"concept": "discovery",
"count": 3,
"format": "index",
"results": [
{
"id": 1240,
"type": "discovery",
"title": "Worker service uses PM2 for process management",
"subtitle": "Discovered persistent background worker pattern",
"created_at_epoch": 1699564800000,
"project": "claude-mem",
"concepts": ["discovery", "how-it-works"]
}
]
}
```
## How to Present Results
For format=index, present as a compact list:
```markdown
Found 3 observations tagged with "discovery":
🔵 **#1240** Worker service uses PM2 for process management
> Discovered persistent background worker pattern
> Nov 9, 2024 • claude-mem
> Tags: discovery, how-it-works
🔵 **#1241** FTS5 full-text search enables instant searches
> SQLite FTS5 virtual tables provide sub-100ms search
> Nov 9, 2024 • claude-mem
> Tags: discovery, pattern
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Available Concepts
| Concept | Description | When to Use |
|---------|-------------|-------------|
| `discovery` | New discoveries and insights | Finding what was learned |
| `problem-solution` | Problems and their solutions | Finding how issues were resolved |
| `what-changed` | Change descriptions | Understanding what changed |
| `how-it-works` | Explanations of mechanisms | Learning how things work |
| `pattern` | Identified patterns | Finding design patterns |
| `gotcha` | Edge cases and gotchas | Learning about pitfalls |
| `change` | General changes | Tracking modifications |
## Error Handling
**Missing concept parameter:**
```json
{"error": "Missing required parameter: concept"}
```
Fix: Add the concept parameter
**Invalid concept:**
```json
{"error": "Invalid concept: foobar. Valid concepts: discovery, problem-solution, what-changed, how-it-works, pattern, gotcha, change"}
```
Fix: Use one of the valid concept values
## Tips
1. Use format=index first to see overview
2. Start with limit=5-10 to avoid token overload
3. Combine concepts with type filtering for precision
4. Use `discovery` for learning what was found during investigation
5. Use `problem-solution` for finding how past issues were resolved
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result)
- Use format=full only for relevant items (~500-1000 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
@@ -0,0 +1,127 @@
# Search by File
Find all work related to a specific file path.
## When to Use
- User asks: "What changes to auth/login.ts?"
- User asks: "What work was done on this file?"
- User asks: "Show me the history of src/services/worker.ts"
- Looking for all observations that reference a file
## Command
```bash
curl -s "http://localhost:37777/api/search/by-file?filePath=src/services/worker-service.ts&format=index&limit=10"
```
## Parameters
- **filePath** (required): File path to search for (supports partial matching)
- Full path: `src/services/worker-service.ts`
- Partial path: `worker-service.ts`
- Directory: `src/hooks/`
- **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews of work on a file
- Finding IDs for deeper investigation
- Listing multiple changes
- **Token cost: ~50-100 per result**
**Use format=full for:**
- Complete details including narrative, facts, files, concepts
- Understanding the full context of specific changes
- **Token cost: ~500-1000 per result**
## Example Response (format=index)
```json
{
"filePath": "src/services/worker-service.ts",
"count": 8,
"format": "index",
"results": [
{
"id": 1245,
"type": "refactor",
"title": "Simplified worker health check logic",
"subtitle": "Removed redundant PM2 status check",
"created_at_epoch": 1699564800000,
"project": "claude-mem",
"files": ["src/services/worker-service.ts", "src/services/worker-utils.ts"]
}
]
}
```
## How to Present Results
For format=index, present as a compact list:
```markdown
Found 8 observations related to "src/services/worker-service.ts":
🔄 **#1245** Simplified worker health check logic
> Removed redundant PM2 status check
> Nov 9, 2024 • claude-mem
> Files: worker-service.ts, worker-utils.ts
🟣 **#1246** Added SSE endpoint for real-time updates
> Implemented Server-Sent Events for viewer UI
> Nov 8, 2024 • claude-mem
> Files: worker-service.ts
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Partial Path Matching
The file path parameter supports partial matching:
```bash
# These all match "src/services/worker-service.ts"
curl -s "http://localhost:37777/api/search/by-file?filePath=worker-service.ts&format=index"
curl -s "http://localhost:37777/api/search/by-file?filePath=services/worker&format=index"
curl -s "http://localhost:37777/api/search/by-file?filePath=worker-service&format=index"
```
## Directory Searches
Search for all work in a directory:
```bash
curl -s "http://localhost:37777/api/search/by-file?filePath=src/hooks/&format=index&limit=20"
```
## Error Handling
**Missing filePath parameter:**
```json
{"error": "Missing required parameter: filePath"}
```
Fix: Add the filePath parameter
**No results found:**
```json
{"filePath": "nonexistent.ts", "count": 0, "results": []}
```
Response: "No observations found for 'nonexistent.ts'. Try a partial path or check the spelling."
## Tips
1. Use format=index first to see overview of all changes
2. Start with partial paths (e.g., filename only) for broader matches
3. Use full paths when you need specific file matches
4. Combine with dateRange to see recent changes: `?filePath=worker.ts&dateRange[start]=2024-11-01`
5. Use directory searches to see all work in a module
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result)
- Use format=full only for relevant items (~500-1000 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
@@ -0,0 +1,123 @@
# Search by Type
Find observations by type: bugfix, feature, refactor, decision, discovery, or change.
## When to Use
- User asks: "What bugs did we fix?"
- User asks: "What features did we add?"
- User asks: "What decisions did we make?"
- Looking for specific types of work
## Command
```bash
curl -s "http://localhost:37777/api/search/by-type?type=bugfix&format=index&limit=5"
```
## Parameters
- **type** (required): One or more types (comma-separated)
- `bugfix` - Bug fixes
- `feature` - New features
- `refactor` - Code refactoring
- `decision` - Architectural/design decisions
- `discovery` - Discoveries and insights
- `change` - General changes
- **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews of work by type
- Finding IDs for deeper investigation
- Listing multiple results
- **Token cost: ~50-100 per result**
**Use format=full for:**
- Complete details including narrative, facts, files, concepts
- Understanding the full context of specific observations
- **Token cost: ~500-1000 per result**
## Example Response (format=index)
```json
{
"type": "bugfix",
"count": 5,
"format": "index",
"results": [
{
"id": 1235,
"type": "bugfix",
"title": "Fixed token expiration edge case",
"subtitle": "Handled race condition in refresh flow",
"created_at_epoch": 1699564800000,
"project": "api-server"
}
]
}
```
## How to Present Results
For format=index, present as a compact list with type emojis:
```markdown
Found 5 bugfixes:
🔴 **#1235** Fixed token expiration edge case
> Handled race condition in refresh flow
> Nov 9, 2024 • api-server
🔴 **#1236** Resolved memory leak in worker
> Fixed event listener cleanup
> Nov 8, 2024 • worker-service
```
**Type Emojis:**
- 🔴 bugfix
- 🟣 feature
- 🔄 refactor
- 🔵 discovery
- 🧠 decision
- ✅ change
For complete formatting guidelines, see [formatting.md](formatting.md).
## Multiple Types
To search for multiple types:
```bash
curl -s "http://localhost:37777/api/search/by-type?type=bugfix,feature&format=index&limit=10"
```
## Error Handling
**Missing type parameter:**
```json
{"error": "Missing required parameter: type"}
```
Fix: Add the type parameter
**Invalid type:**
```json
{"error": "Invalid type: foobar. Valid types: bugfix, feature, refactor, decision, discovery, change"}
```
Fix: Use one of the valid type values
## Tips
1. Use format=index first to see overview
2. Start with limit=5-10 to avoid token overload
3. Combine with dateRange for recent work: `?type=bugfix&dateRange[start]=2024-11-01`
4. Use project filtering when working on one codebase
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result)
- Use format=full only for relevant items (~500-1000 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
@@ -0,0 +1,251 @@
# Common Workflows
Step-by-step guides for typical user requests using the search API.
## Workflow 1: Understanding Past Work
**User asks:** "What did we do last session?" or "Catch me up on recent work"
**Steps:**
1. **Get recent context** (fastest path):
```bash
curl -s "http://localhost:37777/api/context/recent?limit=3"
```
2. **Present as narrative:**
```markdown
## Recent Work
### Session #545 - Nov 9, 2024
Implemented JWT authentication system
**Completed:**
- Added token-based auth with refresh tokens
- Created JWT signing and verification logic
**Key Learning:** JWT expiration requires careful handling of refresh race conditions
```
**Why this workflow:**
- Single request gets both sessions and observations
- Optimized for "catch me up" questions
- ~1,500-2,500 tokens for 3 sessions
---
## Workflow 2: Finding Specific Bug Fixes
**User asks:** "What bugs did we fix?" or "Show me recent bug fixes"
**Steps:**
1. **Search by type** (index format first):
```bash
curl -s "http://localhost:37777/api/search/by-type?type=bugfix&format=index&limit=5"
```
2. **Review index results**, identify relevant items
3. **Get full details** for specific bugs:
```bash
curl -s "http://localhost:37777/api/search/by-type?type=bugfix&format=full&limit=1&offset=2"
```
4. **Present findings:**
```markdown
Found 5 bug fixes:
🔴 **#1235** Fixed token expiration edge case
> Handled race condition in refresh flow
> Nov 9, 2024 • api-server
[Click for full details on #1235]
```
**Why this workflow:**
- Progressive disclosure: index first, full details selectively
- Type-specific search is more efficient than generic search
- ~250-500 tokens for index, ~750-1000 per full detail
---
## Workflow 3: Understanding File History
**User asks:** "What changes to auth/login.ts?" or "Show me work on this file"
**Steps:**
1. **Search by file** (index format):
```bash
curl -s "http://localhost:37777/api/search/by-file?filePath=auth/login.ts&format=index&limit=10"
```
2. **Review chronological changes**
3. **Get full details** for specific changes:
```bash
curl -s "http://localhost:37777/api/search/by-file?filePath=auth/login.ts&format=full&limit=1&offset=3"
```
4. **Present as file timeline:**
```markdown
## History of auth/login.ts
🟣 **#1230** Added JWT authentication (Nov 9)
🔴 **#1235** Fixed token expiration bug (Nov 9)
🔄 **#1240** Refactored auth flow (Nov 8)
```
**Why this workflow:**
- File-specific search finds all related work
- Index format shows chronological overview
- Selective full details for deep dives
---
## Workflow 4: Timeline Investigation
**User asks:** "What was happening when we deployed?" or "Show me context around that bug fix"
**Steps:**
1. **Find the event** using search:
```bash
curl -s "http://localhost:37777/api/search/observations?query=deployment&format=index&limit=5"
```
2. **Note observation ID** (e.g., #1234)
3. **Get timeline context**:
```bash
curl -s "http://localhost:37777/api/timeline/context?anchor=1234&depth_before=10&depth_after=10"
```
4. **Present as chronological narrative:**
```markdown
## Timeline: Deployment
### Before (10 records)
**2:45 PM** - 🟣 Prepared deployment scripts
**2:50 PM** - 💬 User asked: "Are we ready to deploy?"
### ⭐ Anchor Point (2:55 PM)
🎯 **Observation #1234**: Deployed to production
### After (10 records)
**3:00 PM** - 🔴 Fixed post-deployment routing issue
```
**Why this workflow:**
- Timeline shows temporal context (what happened before/after)
- Captures causality between events
- All record types (observations, sessions, prompts) interleaved
---
## Workflow 5: Quick Timeline (One Request)
**User asks:** "Timeline of authentication work"
**Steps:**
1. **Use timeline-by-query** (auto mode):
```bash
curl -s "http://localhost:37777/api/timeline/by-query?query=authentication&mode=auto&depth_before=10&depth_after=10"
```
2. **Present timeline directly:**
```markdown
## Timeline: Authentication
**Best Match:** 🟣 Observation #1234 - Implemented JWT authentication
### Context (21 records)
[... timeline around best match ...]
```
**Why this workflow:**
- Single request combines search + timeline
- Fastest path when query is specific
- Auto mode uses top result as anchor
**Alternative:** Use interactive mode for broad queries:
```bash
curl -s "http://localhost:37777/api/timeline/by-query?query=auth&mode=interactive&limit=5"
```
Then choose anchor manually.
---
## Workflow 6: Search Composition
**User asks:** "What features did we add to the authentication system recently?"
**Steps:**
1. **Combine filters** for precision:
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&type=feature&dateRange[start]=2024-11-01&format=index&limit=10"
```
2. **Review filtered results**
3. **Get full details** for relevant features:
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&type=feature&format=full&limit=1&offset=2"
```
4. **Present findings:**
```markdown
Found 10 authentication features added in November:
🟣 **#1234** Implemented JWT authentication (Nov 9)
🟣 **#1236** Added refresh token rotation (Nov 9)
🟣 **#1238** Implemented OAuth2 flow (Nov 7)
```
**Why this workflow:**
- Multiple filters narrow results before requesting full details
- Type + query + dateRange = precise targeting
- Progressive disclosure: index first, full details selectively
---
## Workflow Selection Guide
| User Request | Workflow | Operation | Token Cost |
|--------------|----------|-----------|------------|
| "What did we do last session?" | #1 | recent-context | 1,500-2,500 |
| "What bugs did we fix?" | #2 | by-type | 500-3,000 |
| "What changes to file.ts?" | #3 | by-file | 500-3,000 |
| "What was happening then?" | #4 | search → timeline | 3,500-6,000 |
| "Timeline of X work" | #5 | timeline-by-query | 3,000-4,000 |
| "Recent features added?" | #6 | observations + filters | 500-3,000 |
## General Principles
1. **Start with index format** - Always use `format=index` first
2. **Use specialized tools** - by-type, by-file, by-concept when applicable
3. **Compose operations** - Combine search + timeline for investigations
4. **Filter early** - Use type, dateRange, project to narrow before expanding
5. **Progressive disclosure** - Load full details only for relevant items
## Token Budget Awareness
**Quick queries** (500-1,500 tokens):
- Recent context (limit=3)
- Index search (limit=5-10)
- Filtered searches
**Medium queries** (1,500-4,000 tokens):
- Recent context (limit=5-10)
- Full details (3-5 items)
- Timeline (depth 10/10)
**Deep queries** (4,000-8,000 tokens):
- Timeline (depth 20/20)
- Full details (10+ items)
- Multiple composed operations
Always start with minimal token investment, expand only when needed.
@@ -0,0 +1,403 @@
# Response Formatting Guidelines
How to present search results to users for maximum clarity and usefulness.
## General Principles
1. **Progressive disclosure** - Show index results first, full details on demand
2. **Visual hierarchy** - Use emojis, bold, and structure for scannability
3. **Context-aware** - Tailor presentation to user's question
4. **Actionable** - Include IDs for follow-up queries
5. **Token-efficient** - Balance detail with token budget
---
## Format: Index Results
**When to use:** First response to searches, overviews, multiple results
**Structure:**
```markdown
Found {count} results for "{query}":
{emoji} **#{id}** {title}
> {subtitle}
> {date} • {project}
```
**Example:**
```markdown
Found 5 results for "authentication":
🟣 **#1234** Implemented JWT authentication
> Added token-based auth with refresh tokens
> Nov 9, 2024 • api-server
🔴 **#1235** Fixed token expiration edge case
> Handled race condition in refresh flow
> Nov 9, 2024 • api-server
```
**Type Emojis:**
- 🔴 bugfix
- 🟣 feature
- 🔄 refactor
- 🔵 discovery
- 🧠 decision
- ✅ change
- 🎯 session
- 💬 prompt
**What to include:**
- ✅ ID (for follow-up)
- ✅ Type emoji
- ✅ Title
- ✅ Subtitle (if available)
- ✅ Date (human-readable)
- ✅ Project name
- ❌ Don't include full narrative/facts/files in index format
---
## Format: Full Results
**When to use:** User requests details, specific items selected from index
**Structure:**
```markdown
## {emoji} {type} #{id}: {title}
**Summary:** {subtitle}
**What happened:**
{narrative}
**Key Facts:**
- {fact1}
- {fact2}
**Files modified:**
- {file1}
- {file2}
**Concepts:** {concepts}
**Date:** {human_readable_date}
**Project:** {project}
```
**Example:**
```markdown
## 🟣 Feature #1234: Implemented JWT authentication
**Summary:** Added token-based auth with refresh tokens
**What happened:**
Implemented a complete JWT authentication system with access and refresh tokens. Access tokens expire after 15 minutes, refresh tokens after 7 days. Added token signing with RS256 algorithm and proper key rotation infrastructure.
**Key Facts:**
- Access tokens use 15-minute expiration
- Refresh tokens stored in httpOnly cookies
- RS256 algorithm with key rotation support
- Token refresh endpoint handles race conditions gracefully
**Files modified:**
- src/auth/jwt.ts (created)
- src/auth/refresh.ts (created)
- src/middleware/auth.ts (modified)
**Concepts:** how-it-works, pattern
**Date:** November 9, 2024 at 2:55 PM
**Project:** api-server
```
**What to include:**
- ✅ Full title with emoji and ID
- ✅ Summary/subtitle
- ✅ Complete narrative
- ✅ All key facts
- ✅ All files (with status: created/modified/deleted)
- ✅ Concepts/tags
- ✅ Precise timestamp
- ✅ Project name
---
## Format: Timeline Results
**When to use:** Temporal investigations, "what was happening" questions
**Structure:**
```markdown
## Timeline: {anchor_description}
### Before ({count} records)
**{time}** - {emoji} {type} #{id}: {title}
**{time}** - {emoji} {type} #{id}: {title}
### ⭐ Anchor Point ({time})
{emoji} **{type} #{id}**: {title}
### After ({count} records)
**{time}** - {emoji} {type} #{id}: {title}
**{time}** - {emoji} {type} #{id}: {title}
```
**Example:**
```markdown
## Timeline: Deployment
### Before (10 records)
**2:30 PM** - 🟣 #1230: Prepared deployment scripts
**2:45 PM** - 🔄 #1232: Updated configuration files
**2:50 PM** - 💬 User asked: "Are we ready to deploy?"
### ⭐ Anchor Point (2:55 PM)
🎯 **Session #545**: Deployed to production
### After (10 records)
**3:00 PM** - 🔴 #1235: Fixed post-deployment routing issue
**3:10 PM** - 🔵 #1236: Discovered caching behavior in production
**3:15 PM** - 🧠 #1237: Decided to add health check endpoint
```
**What to include:**
- ✅ Chronological ordering (oldest to newest)
- ✅ Human-readable times (not epochs)
- ✅ Clear anchor point marker (⭐)
- ✅ Mix of all record types (observations, sessions, prompts)
- ✅ Concise titles (not full narratives)
- ✅ Type emojis for quick scanning
---
## Format: Session Summaries
**When to use:** Recent context, "what did we do" questions
**Structure:**
```markdown
## Recent Work on {project}
### 🎯 Session #{id} - {date}
**Request:** {user_request}
**Completed:**
- {completion1}
- {completion2}
**Key Learning:** {learning}
**Observations:**
- {emoji} **#{obs_id}** {obs_title}
- Files: {file_list}
```
**Example:**
```markdown
## Recent Work on api-server
### 🎯 Session #545 - November 9, 2024
**Request:** Add JWT authentication with refresh tokens
**Completed:**
- Implemented token-based auth with refresh logic
- Added JWT signing and verification
- Created refresh token rotation
**Key Learning:** JWT expiration requires careful handling of refresh race conditions
**Observations:**
- 🟣 **#1234** Implemented JWT authentication
- Files: jwt.ts, refresh.ts, middleware/auth.ts
- 🔴 **#1235** Fixed token expiration edge case
- Files: refresh.ts
```
**What to include:**
- ✅ Session ID and date
- ✅ Original user request
- ✅ What was completed (bulleted list)
- ✅ Key learnings/insights
- ✅ Linked observations with file lists
- ✅ Clear hierarchy (session → observations)
---
## Format: User Prompts
**When to use:** "What did I ask" questions, prompt searches
**Structure:**
```markdown
Found {count} user prompts:
💬 **Prompt #{id}** (Session #{session_id})
> "{preview_text}"
> {date} • {project}
```
**Example:**
```markdown
Found 5 user prompts about "authentication":
💬 **Prompt #1250** (Session #545)
> "How do I implement JWT authentication with refresh tokens? I need to handle token expiration..."
> Nov 9, 2024 • api-server
💬 **Prompt #1251** (Session #546)
> "The auth tokens are expiring too quickly. Can you help debug the refresh flow?"
> Nov 8, 2024 • api-server
```
**What to include:**
- ✅ Prompt ID
- ✅ Session ID (for context linking)
- ✅ Preview text (200 chars for index, full text for full format)
- ✅ Date and project
- ✅ Quote formatting for prompt text
---
## Error Responses
**No results found:**
```markdown
No results found for "{query}". Try:
- Different search terms
- Broader keywords
- Checking spelling
- Using partial paths (for file searches)
```
**Service unavailable:**
```markdown
The search service isn't available. Check if the worker is running:
```bash
pm2 list
```
If the worker is stopped, restart it:
```bash
npm run worker:restart
```
```
**Invalid parameters:**
```markdown
Invalid search parameters:
- {parameter}: {error_message}
See the [API help](help.md) for valid parameter options.
```
---
## Context-Aware Presentation
Tailor formatting to user's question:
**"What bugs did we fix?"**
→ Use index format, emphasize date/type, group by recency
**"How did we implement X?"**
→ Use full format for best match, include complete narrative and files
**"What was happening when..."**
→ Use timeline format, emphasize chronology and causality
**"Catch me up on recent work"**
→ Use session summary format, focus on high-level accomplishments
---
## Token Budget Guidelines
**Minimal presentation (~100-200 tokens):**
- Index format with 3-5 results
- Compact list structure
- Essential metadata only
**Standard presentation (~500-1,000 tokens):**
- Index format with 10-15 results
- Include subtitles and context
- Clear formatting and emojis
**Detailed presentation (~1,500-3,000 tokens):**
- Full format for 2-3 items
- Complete narratives and facts
- Timeline with 20-30 records
**Comprehensive presentation (~5,000+ tokens):**
- Multiple full results
- Deep timelines (40+ records)
- Session summaries with observations
Always start minimal, expand only when needed.
---
## Markdown Best Practices
1. **Use headers (##, ###)** for hierarchy
2. **Bold important elements** (IDs, titles, dates)
3. **Quote user text** (prompts, questions)
4. **Bullet lists** for facts and files
5. **Code blocks** for commands and examples
6. **Emojis** for type indicators
7. **Horizontal rules (---)** for section breaks
8. **Blockquotes (>)** for subtitles and previews
---
## Examples by Use Case
### Use Case 1: Quick Overview
User: "What did we do last session?"
```markdown
## Recent Work
### 🎯 Session #545 - November 9, 2024
Implemented JWT authentication system
**Key accomplishment:** Added token-based auth with refresh tokens
**Key learning:** JWT expiration requires careful handling of refresh race conditions
```
### Use Case 2: Specific Investigation
User: "How did we implement JWT authentication?"
```markdown
## 🟣 Feature #1234: Implemented JWT authentication
**What happened:**
Implemented a complete JWT authentication system with access and refresh tokens. Access tokens expire after 15 minutes, refresh tokens after 7 days. Added token signing with RS256 algorithm.
**Files:**
- src/auth/jwt.ts (created)
- src/auth/refresh.ts (created)
- src/middleware/auth.ts (modified)
**Key insight:** Refresh race conditions require atomic token exchange logic.
```
### Use Case 3: Timeline Investigation
User: "What was happening around the deployment?"
```markdown
## Timeline: Deployment
[... chronological timeline with before/after context ...]
```
Choose presentation style based on user's question and information needs.
+175
View File
@@ -0,0 +1,175 @@
# API Help
Get comprehensive API documentation for all search endpoints.
## When to Use
- User asks: "What search operations are available?"
- User asks: "How do I use the search API?"
- Need reference documentation for endpoints
- Want to see all available parameters
## Command
```bash
curl -s "http://localhost:37777/api/help"
```
## Response Structure
Returns complete API documentation:
```json
{
"version": "5.4.0",
"base_url": "http://localhost:37777/api",
"endpoints": [
{
"path": "/search/observations",
"method": "GET",
"description": "Search observations using full-text search",
"parameters": [
{
"name": "query",
"required": true,
"type": "string",
"description": "Search terms"
},
{
"name": "format",
"required": false,
"type": "string",
"default": "full",
"options": ["index", "full"],
"description": "Response format"
}
],
"example": "curl -s \"http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5\""
}
]
}
```
## How to Present Results
Present as reference documentation:
```markdown
## claude-mem Search API Reference (v5.4.0)
Base URL: `http://localhost:37777/api`
### Search Operations
**1. Search Observations**
- **Endpoint:** `GET /search/observations`
- **Description:** Search observations using full-text search
- **Parameters:**
- `query` (required, string): Search terms
- `format` (optional, string): "index" or "full" (default: "full")
- `limit` (optional, number): Max results (default: 20, max: 100)
- **Example:**
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"
```
[... continue for all endpoints ...]
```
## Endpoint Categories
The API help response organizes endpoints by category:
1. **Full-Text Search**
- `/search/observations`
- `/search/sessions`
- `/search/prompts`
2. **Filtered Search**
- `/search/by-type`
- `/search/by-concept`
- `/search/by-file`
3. **Context Retrieval**
- `/context/recent`
- `/timeline/context`
- `/timeline/by-query`
4. **Utilities**
- `/help`
## Common Parameters
Many endpoints share these parameters:
- **format**: "index" (summary) or "full" (complete details)
- **limit**: Number of results to return
- **offset**: Number of results to skip (for pagination)
- **project**: Filter by project name
- **dateRange**: Filter by date range
- `dateRange[start]`: Start date (ISO string or epoch)
- `dateRange[end]`: End date (ISO string or epoch)
## Error Handling
**Worker not running:**
Connection refused error. Response: "The search API isn't available. Check if worker is running: `pm2 list`"
**Invalid endpoint:**
```json
{"error": "Not found"}
```
Response: "Invalid API endpoint. Use /api/help to see available endpoints."
## Tips
1. Save help response for reference during investigation
2. Use examples as starting point for your queries
3. Check required parameters before making requests
4. Refer to format options for each endpoint
5. All endpoints use GET method with query parameters
**Token Efficiency:**
- Help response: ~2,000-3,000 tokens (complete API reference)
- Use sparingly - refer to operation-specific docs instead
- Keep help response cached for repeated reference
## When to Use Help
**Use help when:**
- Starting to use the search API
- Need complete parameter reference
- Forgot which endpoints are available
- Want to see all options at once
**Don't use help when:**
- You know which operation you need (use operation-specific docs)
- Just need examples (use common-workflows.md)
- Token budget is limited (help is comprehensive)
## Alternative to Help Endpoint
Instead of calling `/api/help`, you can:
1. **Use SKILL.md** - Quick decision guide with operation links
2. **Use operation docs** - Detailed guides for specific endpoints
3. **Use common-workflows.md** - Step-by-step examples
4. **Use formatting.md** - Response presentation templates
The help endpoint is most useful when you need complete API reference in one response.
## API Versioning
The help response includes version information:
```json
{
"version": "5.4.0",
"skill_migration": true,
"deprecated": {
"mcp_tools": "Replaced by HTTP API in v5.4.0"
}
}
```
Check version to ensure compatibility with documentation.
@@ -0,0 +1,103 @@
# Search Observations (Full-Text)
Search all observations using natural language queries.
## When to Use
- User asks: "How did we implement authentication?"
- User asks: "What bugs did we fix?"
- User asks: "What features did we add?"
- Looking for past work by keyword or topic
## Command
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"
```
## Parameters
- **query** (required): Search terms (e.g., "authentication", "bug fix", "database migration")
- **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews
- Finding IDs for deeper investigation
- Listing multiple results
- **Token cost: ~50-100 per result**
**Use format=full for:**
- Complete details including narrative, facts, files, concepts
- Understanding the full context of specific observations
- **Token cost: ~500-1000 per result**
## Example Response (format=index)
```json
{
"query": "authentication",
"count": 5,
"format": "index",
"results": [
{
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"subtitle": "Added token-based auth with refresh tokens",
"created_at_epoch": 1699564800000,
"project": "api-server",
"score": 0.95
}
]
}
```
## How to Present Results
For format=index, present as a compact list:
```markdown
Found 5 results for "authentication":
1. **#1234** [feature] Implemented JWT authentication
> Added token-based auth with refresh tokens
> Nov 9, 2024 • api-server
2. **#1235** [bugfix] Fixed token expiration edge case
> Handled race condition in refresh flow
> Nov 9, 2024 • api-server
```
**Include:** ID (for follow-up), type emoji (🔴 bugfix, 🟣 feature, 🔄 refactor, 🔵 discovery, 🧠 decision, ✅ change), title, subtitle, date, project.
For complete formatting guidelines, see formatting.md (documentation coming soon).
## Error Handling
**Missing query parameter:**
```json
{"error": "Missing required parameter: query"}
```
Fix: Add the query parameter
**No results found:**
```json
{"query": "foobar", "count": 0, "results": []}
```
Response: "No results found for 'foobar'. Try different search terms."
## Tips
1. Be specific: "authentication JWT" > "auth"
2. Start with format=index and limit=5-10
3. Use project filtering when working on one codebase
4. If no results, try broader terms or check spelling
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result)
- Use format=full only for relevant items (~500-1000 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
@@ -0,0 +1,125 @@
# Search User Prompts (Full-Text)
Search raw user prompts to find what was actually asked across all sessions.
## When to Use
- User asks: "What did I ask about authentication?"
- User asks: "Find my question about database migrations"
- User asks: "When did I ask about testing?"
- Looking for specific user questions or requests
## Command
```bash
curl -s "http://localhost:37777/api/search/prompts?query=authentication&format=index&limit=5"
```
## Parameters
- **query** (required): Search terms (e.g., "authentication", "how do I", "bug fix")
- **format**: "index" (truncated prompts) or "full" (complete prompt text). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews of what was asked
- Finding prompt IDs for full text
- Listing multiple prompts
- **Token cost: ~50-100 per result (truncated to 200 chars)**
**Use format=full for:**
- Complete prompt text
- Understanding the full user request
- **Token cost: Variable (depends on prompt length, typically 100-300 tokens)**
## Example Response (format=index)
```json
{
"query": "authentication",
"count": 5,
"format": "index",
"results": [
{
"id": 1250,
"session_id": "S545",
"prompt_preview": "How do I implement JWT authentication with refresh tokens? I need to handle token expiration...",
"created_at_epoch": 1699564800000,
"project": "api-server"
}
]
}
```
## How to Present Results
For format=index, present as a compact list:
```markdown
Found 5 user prompts about "authentication":
💬 **Prompt #1250** (Session #545)
> "How do I implement JWT authentication with refresh tokens? I need to handle token expiration..."
> Nov 9, 2024 • api-server
💬 **Prompt #1251** (Session #546)
> "The auth tokens are expiring too quickly. Can you help debug the refresh flow?"
> Nov 8, 2024 • api-server
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## What Gets Searched
User prompts search covers:
- All user messages sent to Claude Code
- Raw text as typed by the user
- Multi-turn conversations (each message is a separate prompt)
- Questions, requests, commands, and clarifications
## Error Handling
**Missing query parameter:**
```json
{"error": "Missing required parameter: query"}
```
Fix: Add the query parameter
**No results found:**
```json
{"query": "foobar", "count": 0, "results": []}
```
Response: "No user prompts found for 'foobar'. Try different search terms."
## Tips
1. Use exact phrases in quotes: `?query="how do I"` for precise matches
2. Start with format=index to see preview, then get full text if needed
3. Use dateRange to find recent questions: `?query=bug&dateRange[start]=2024-11-01`
4. Prompts show what was asked, sessions/observations show what was done
5. Combine with session search to see both question and answer
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result, prompt truncated to 200 chars)
- Use format=full only for relevant items (100-300 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
## When to Use Prompts vs Sessions
**Use prompts search when:**
- Looking for specific user questions
- Trying to remember what was asked
- Finding original request wording
**Use sessions search when:**
- Looking for what was accomplished
- Understanding work summaries
- Getting high-level context
**Combine both when:**
- Understanding the full conversation (what was asked + what was done)
- Investigating how a request was interpreted
@@ -0,0 +1,134 @@
# Get Recent Context
Get recent session summaries and observations for a project.
## When to Use
- User asks: "What did we do last session?"
- User asks: "What have we been working on recently?"
- User asks: "Catch me up on recent work"
- Starting a new session and need context
## Command
```bash
curl -s "http://localhost:37777/api/context/recent?project=api-server&limit=3"
```
## Parameters
- **project**: Project name (defaults to current working directory basename)
- **limit**: Number of recent sessions to retrieve (default: 3, max: 10)
## Response Structure
Returns combined context from recent sessions:
```json
{
"project": "api-server",
"limit": 3,
"sessions": [
{
"id": 545,
"session_id": "S545",
"title": "Implemented JWT authentication system",
"request": "Add JWT authentication with refresh tokens",
"completion": "Implemented token-based auth with refresh logic",
"learnings": "JWT expiration requires careful handling of refresh race conditions",
"created_at_epoch": 1699564800000,
"observations": [
{
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"subtitle": "Added token-based auth with refresh tokens",
"files": ["src/auth/jwt.ts", "src/auth/refresh.ts"]
}
]
}
]
}
```
## How to Present Results
Present as a chronological narrative:
```markdown
## Recent Work on api-server
### Session #545 - Nov 9, 2024
**Request:** Add JWT authentication with refresh tokens
**Completed:**
- Implemented token-based auth with refresh logic
- Added JWT signing and verification
- Created refresh token rotation
**Key Learning:** JWT expiration requires careful handling of refresh race conditions
**Observations:**
- 🟣 **#1234** Implemented JWT authentication
- Files: jwt.ts, refresh.ts
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Default Project Detection
If no project parameter is provided, uses current working directory:
```bash
# Auto-detects project from current directory
curl -s "http://localhost:37777/api/context/recent?limit=3"
```
## Error Handling
**No sessions found:**
```json
{"project": "new-project", "sessions": []}
```
Response: "No recent sessions found for 'new-project'. This might be a new project."
**Worker not running:**
Connection refused error. Inform user to check if worker is running: `pm2 list`
## Tips
1. Start with limit=3 for quick overview (default)
2. Increase to limit=5-10 for deeper context
3. Recent context is perfect for session start
4. Combines both sessions and observations in one request
5. Use this when user asks "what did we do last time?"
**Token Efficiency:**
- limit=3 sessions: ~1,500-2,500 tokens (includes observations)
- limit=5 sessions: ~2,500-4,000 tokens
- limit=10 sessions: ~5,000-8,000 tokens
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
## When to Use Recent Context
**Use recent-context when:**
- Starting a new session
- User asks about recent work
- Need quick catch-up on project activity
- Want both sessions and observations together
**Don't use recent-context when:**
- Looking for specific topics (use search instead)
- Need timeline around specific event (use timeline instead)
- Want only observations or only sessions (use search operations)
## Comparison with Other Operations
| Operation | Use Case | Token Cost |
|-----------|----------|------------|
| recent-context | Quick catch-up on recent work | 1,500-4,000 |
| sessions search | Find sessions by topic | 50-100 per result (index) |
| observations search | Find specific implementations | 50-100 per result (index) |
| timeline | Context around specific point | 3,000-6,000 |
Recent context is optimized for "what happened recently?" questions with minimal token usage.
@@ -0,0 +1,124 @@
# Search Sessions (Full-Text)
Search session summaries using natural language queries.
## When to Use
- User asks: "What did we work on last week?"
- User asks: "What sessions involved database work?"
- User asks: "Show me sessions where we fixed bugs"
- Looking for past sessions by topic or theme
## Command
```bash
curl -s "http://localhost:37777/api/search/sessions?query=authentication&format=index&limit=5"
```
## Parameters
- **query** (required): Search terms (e.g., "authentication", "database migration", "bug fixes")
- **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional)
## When to Use Each Format
**Use format=index for:**
- Quick overviews of past sessions
- Finding session IDs for deeper investigation
- Listing multiple sessions
- **Token cost: ~50-100 per result**
**Use format=full for:**
- Complete session summaries with requests, completions, learnings
- Understanding the full context of a session
- **Token cost: ~500-1000 per result**
## Example Response (format=index)
```json
{
"query": "authentication",
"count": 3,
"format": "index",
"results": [
{
"id": 545,
"session_id": "S545",
"title": "Implemented JWT authentication system",
"subtitle": "Added token-based auth with refresh tokens",
"created_at_epoch": 1699564800000,
"project": "api-server"
}
]
}
```
## How to Present Results
For format=index, present as a compact list:
```markdown
Found 3 sessions about "authentication":
🎯 **Session #545** Implemented JWT authentication system
> Added token-based auth with refresh tokens
> Nov 9, 2024 • api-server
🎯 **Session #546** Fixed authentication token expiration
> Resolved race condition in token refresh flow
> Nov 8, 2024 • api-server
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Session Summary Structure
Full session summaries include:
- **Session request**: What the user asked for
- **What was completed**: Summary of work done
- **Key learnings**: Important insights and discoveries
- **Files modified**: List of changed files
- **Observations**: Links to detailed observations
## Error Handling
**Missing query parameter:**
```json
{"error": "Missing required parameter: query"}
```
Fix: Add the query parameter
**No results found:**
```json
{"query": "foobar", "count": 0, "results": []}
```
Response: "No sessions found for 'foobar'. Try different search terms."
## Tips
1. Be specific: "JWT authentication implementation" > "auth"
2. Start with format=index and limit=5-10
3. Use dateRange for recent sessions: `?query=auth&dateRange[start]=2024-11-01`
4. Sessions provide high-level overview, observations provide details
5. Use project filtering when working on one codebase
**Token Efficiency:**
- Start with format=index (~50-100 tokens per result)
- Use format=full only for relevant items (~500-1000 tokens per result)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
## When to Use Sessions vs Observations
**Use sessions search when:**
- Looking for high-level work summaries
- Understanding what was done in past sessions
- Getting overview of recent activity
**Use observations search when:**
- Looking for specific implementation details
- Finding bugs, features, or decisions
- Need fine-grained context about code changes
@@ -0,0 +1,192 @@
# Timeline by Query
Search for observations and get timeline context in a single request. Combines search + timeline into one operation.
## When to Use
- User asks: "What was happening when we worked on authentication?"
- User asks: "Show me context around bug fixes"
- User asks: "Timeline of database work"
- Need to find something then see temporal context
## Command
```bash
# Auto mode: Uses top search result as timeline anchor
curl -s "http://localhost:37777/api/timeline/by-query?query=authentication&mode=auto&depth_before=10&depth_after=10"
# Interactive mode: Shows top N search results for manual selection
curl -s "http://localhost:37777/api/timeline/by-query?query=authentication&mode=interactive&limit=5"
```
## Parameters
- **query** (required): Search terms (e.g., "authentication", "bug fix", "database")
- **mode**: Search mode
- `auto` (default): Automatically uses top search result as timeline anchor
- `interactive`: Returns top N search results for manual anchor selection
- **depth_before**: Records before anchor (default: 10, max: 50) - for auto mode
- **depth_after**: Records after anchor (default: 10, max: 50) - for auto mode
- **limit**: Number of search results (default: 5, max: 20) - for interactive mode
- **project**: Filter by project name (optional)
## Auto Mode (Recommended)
Automatically gets timeline around best match:
```bash
curl -s "http://localhost:37777/api/timeline/by-query?query=JWT+authentication&mode=auto&depth_before=10&depth_after=10"
```
**Response:**
```json
{
"query": "JWT authentication",
"mode": "auto",
"best_match": {
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"score": 0.95
},
"timeline": [
// ... timeline records around observation #1234
]
}
```
**When to use auto mode:**
- You're confident the top result is what you want
- Want fastest path to timeline context
- Query is specific enough for accurate top result
## Interactive Mode
Shows top search results for manual review:
```bash
curl -s "http://localhost:37777/api/timeline/by-query?query=authentication&mode=interactive&limit=5"
```
**Response:**
```json
{
"query": "authentication",
"mode": "interactive",
"top_matches": [
{
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"subtitle": "Added token-based auth with refresh tokens",
"score": 0.95
},
{
"id": 1240,
"type": "bugfix",
"title": "Fixed authentication token expiration",
"subtitle": "Resolved race condition in refresh flow",
"score": 0.87
}
],
"next_step": "Use /api/timeline/context?anchor=<id>&depth_before=10&depth_after=10"
}
```
**When to use interactive mode:**
- Query is broad and may have multiple relevant results
- Want to review options before getting timeline
- Not sure which result is most relevant
## How to Present Results
**For auto mode:**
```markdown
## Timeline: JWT authentication
**Best Match:** 🟣 Observation #1234 - Implemented JWT authentication (score: 0.95)
### Before (10 records)
**2:45 PM** - 🟣 Added authentication middleware
### ⭐ Anchor Point (2:55 PM)
🟣 **Observation #1234**: Implemented JWT authentication
### After (10 records)
**3:00 PM** - 🎯 Session completed: JWT authentication system
```
**For interactive mode:**
```markdown
Found 5 matches for "authentication":
1. 🟣 **#1234** Implemented JWT authentication (score: 0.95)
> Added token-based auth with refresh tokens
2. 🔴 **#1240** Fixed authentication token expiration (score: 0.87)
> Resolved race condition in refresh flow
To see timeline context, use observation ID with timeline operation.
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Error Handling
**Missing query parameter:**
```json
{"error": "Missing required parameter: query"}
```
Fix: Add the query parameter
**No results found:**
```json
{"query": "foobar", "top_matches": []}
```
Response: "No results found for 'foobar'. Try different search terms."
## Tips
1. **Use auto mode** for specific queries: "JWT authentication implementation"
2. **Use interactive mode** for broad queries: "authentication"
3. Start with depth 10/10 for balanced context
4. Be specific in queries for better auto mode accuracy
5. This is fastest way to find + explore context in one request
**Token Efficiency:**
- Auto mode: ~3,000-4,000 tokens (search + timeline)
- Interactive mode: ~500-1,000 tokens (search results only)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
## Workflow Comparison
**timeline-by-query (auto):**
1. One request → get timeline around best match
2. ~3,000 tokens
**timeline-by-query (interactive) → timeline:**
1. First request → see top matches (~500 tokens)
2. Second request → get timeline for chosen match (~3,000 tokens)
3. Total: ~3,500 tokens
**observations search → timeline:**
1. Search observations (~500 tokens)
2. Get timeline for chosen result (~3,000 tokens)
3. Total: ~3,500 tokens
Use auto mode when you're confident about the query. Use interactive mode or separate search when you want more control.
## When to Use Timeline-by-Query
**Use timeline-by-query when:**
- Need to find something AND see temporal context
- Want one-request convenience (auto mode)
- Investigating "what was happening when we worked on X?"
- Don't have observation ID already
**Don't use timeline-by-query when:**
- Already have observation ID (use timeline instead)
- Just need search results (use observations search)
- Need recent work overview (use recent-context)
@@ -0,0 +1,174 @@
# Get Context Timeline
Get a chronological timeline of observations, sessions, and prompts around a specific point in time.
## When to Use
- User asks: "What was happening when we deployed?"
- User asks: "Show me context around that bug fix"
- User asks: "What happened before and after that change?"
- Need temporal context around an event
## Command
```bash
# Using observation ID as anchor
curl -s "http://localhost:37777/api/timeline/context?anchor=1234&depth_before=10&depth_after=10"
# Using session ID as anchor
curl -s "http://localhost:37777/api/timeline/context?anchor=S545&depth_before=10&depth_after=10"
# Using ISO timestamp as anchor
curl -s "http://localhost:37777/api/timeline/context?anchor=2024-11-09T12:00:00Z&depth_before=10&depth_after=10"
```
## Parameters
- **anchor** (required): Point in time to center timeline
- Observation ID: `1234`
- Session ID: `S545`
- ISO timestamp: `2024-11-09T12:00:00Z`
- **depth_before**: Number of records before anchor (default: 10, max: 50)
- **depth_after**: Number of records after anchor (default: 10, max: 50)
- **project**: Filter by project name (optional)
## Response Structure
Returns unified chronological timeline:
```json
{
"anchor": 1234,
"depth_before": 10,
"depth_after": 10,
"total_records": 21,
"timeline": [
{
"record_type": "observation",
"id": 1230,
"type": "feature",
"title": "Added authentication middleware",
"created_at_epoch": 1699564700000
},
{
"record_type": "prompt",
"id": 1250,
"session_id": "S545",
"prompt_preview": "How do I add JWT authentication?",
"created_at_epoch": 1699564750000
},
{
"record_type": "observation",
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"created_at_epoch": 1699564800000,
"is_anchor": true
},
{
"record_type": "session",
"id": 545,
"session_id": "S545",
"title": "Implemented JWT authentication system",
"created_at_epoch": 1699564900000
}
]
}
```
## How to Present Results
Present as chronological narrative with anchor highlighted:
```markdown
## Timeline around Observation #1234
### Before (10 records)
**2:45 PM** - 🟣 Observation #1230: Added authentication middleware
**2:50 PM** - 💬 User asked: "How do I add JWT authentication?"
### ⭐ Anchor Point (2:55 PM)
🟣 **Observation #1234**: Implemented JWT authentication
### After (10 records)
**3:00 PM** - 🎯 Session #545 completed: Implemented JWT authentication system
**3:05 PM** - 🔴 Observation #1235: Fixed token expiration edge case
```
For complete formatting guidelines, see [formatting.md](formatting.md).
## Anchor Types
**Observation ID:**
- Use when you know the specific observation ID
- Example: `anchor=1234`
**Session ID:**
- Use when you want context around a session
- Example: `anchor=S545`
**ISO Timestamp:**
- Use when you know approximate time
- Example: `anchor=2024-11-09T14:30:00Z`
## Error Handling
**Missing anchor parameter:**
```json
{"error": "Missing required parameter: anchor"}
```
Fix: Add the anchor parameter
**Anchor not found:**
```json
{"error": "Anchor not found: 9999"}
```
Response: "Observation #9999 not found. Check the ID or try a different anchor."
**Invalid timestamp:**
```json
{"error": "Invalid timestamp format"}
```
Fix: Use ISO 8601 format: `2024-11-09T14:30:00Z`
## Tips
1. Start with depth_before=10, depth_after=10 for balanced context
2. Increase depth for broader investigation (max: 50 each)
3. Use observation IDs from search results as anchors
4. Timelines show all record types interleaved chronologically
5. Perfect for understanding "what was happening when X occurred"
**Token Efficiency:**
- depth 10/10: ~3,000-4,000 tokens (21 records)
- depth 20/20: ~6,000-8,000 tokens (41 records)
- depth 50/50: ~15,000-20,000 tokens (101 records)
- See [../principles/progressive-disclosure.md](../principles/progressive-disclosure.md)
## When to Use Timeline
**Use timeline when:**
- Need context around specific event
- Understanding sequence of events
- Investigating "what was happening then?"
- Want all record types (observations, sessions, prompts) together
**Don't use timeline when:**
- Just need recent work (use recent-context)
- Looking for specific topics (use search)
- Don't have an anchor point (use timeline-by-query)
## Comparison with Timeline-by-Query
| Feature | timeline | timeline-by-query |
|---------|----------|-------------------|
| Requires anchor | Yes (ID or timestamp) | No (uses search query) |
| Best for | Known event investigation | Finding then exploring context |
| Steps | 1 (direct timeline) | 2 (search + timeline) |
| Use when | You have observation ID | You have search term |
Timeline is faster when you already know the anchor point.
@@ -0,0 +1,176 @@
# Anti-Pattern Catalogue
Common mistakes to avoid when using the HTTP search API. These anti-patterns address LLM training biases and prevent token-wasting behaviors.
## Anti-Pattern 1: Skipping Index Format
**The Mistake:**
```bash
# ❌ Bad: Jump straight to full format
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=full&limit=20"
```
**Why It's Wrong:**
- 20 × 750 tokens = 15,000 tokens
- May hit MCP token limits
- 99% wasted on irrelevant results
**The Correction:**
```bash
# ✅ Good: Start with index, review, then request full selectively
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"
# Review results, identify relevant items
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=full&limit=1&offset=2"
```
**What It Teaches:**
Progressive disclosure isn't optional - it's essential for scale.
**LLM Behavior Insight:**
LLMs trained on code examples may have seen `format=full` as "more complete" and default to it.
---
## Anti-Pattern 2: Over-Requesting Results
**The Mistake:**
```bash
# ❌ Bad: Request limit=20 without reviewing index first
curl -s "http://localhost:37777/api/search/observations?query=auth&format=index&limit=20"
```
**Why It's Wrong:**
- Most of 20 results will be irrelevant
- Wastes tokens and time
- Overwhelms review process
**The Correction:**
```bash
# ✅ Good: Start small, paginate if needed
curl -s "http://localhost:37777/api/search/observations?query=auth&format=index&limit=5"
# If needed, paginate:
curl -s "http://localhost:37777/api/search/observations?query=auth&format=index&limit=5&offset=5"
```
**What It Teaches:**
Start small (limit=3-5), review, paginate if needed.
**LLM Behavior Insight:**
LLMs may think "more results = more thorough" without considering relevance.
---
## Anti-Pattern 3: Ignoring Tool Specialization
**The Mistake:**
```bash
# ❌ Bad: Use generic search for everything
curl -s "http://localhost:37777/api/search/observations?query=bugfix&format=index&limit=10"
```
**Why It's Wrong:**
- Specialized tools (by-type, by-concept, by-file) are more efficient
- Generic search mixes all result types
- Misses filtering optimization
**The Correction:**
```bash
# ✅ Good: Use specialized endpoint when applicable
curl -s "http://localhost:37777/api/search/by-type?type=bugfix&format=index&limit=10"
```
**What It Teaches:**
The decision tree exists for a reason - follow it.
**LLM Behavior Insight:**
LLMs may gravitate toward "general purpose" tools to avoid decision-making.
---
## Anti-Pattern 4: Loading Full Context Prematurely
**The Mistake:**
```bash
# ❌ Bad: Request full format before understanding what's relevant
curl -s "http://localhost:37777/api/search/observations?query=database&format=full&limit=10"
```
**Why It's Wrong:**
- Can't filter relevance without seeing index first
- Wastes tokens on irrelevant full details
- 10 × 750 = 7,500 tokens for potentially zero useful results
**The Correction:**
```bash
# ✅ Good: Index first to identify relevance
curl -s "http://localhost:37777/api/search/observations?query=database&format=index&limit=10"
# Identify relevant: #1234 and #1250
curl -s "http://localhost:37777/api/search/observations?query=database+1234&format=full&limit=1"
curl -s "http://localhost:37777/api/search/observations?query=database+1250&format=full&limit=1"
```
**What It Teaches:**
Filtering is a prerequisite for expansion.
**LLM Behavior Insight:**
LLMs may try to "get everything at once" to avoid multiple tool calls.
---
## Anti-Pattern 5: Not Using Timeline Tools
**The Mistake:**
```bash
# ❌ Bad: Search for individual observations separately
curl -s "http://localhost:37777/api/search/observations?query=before+deployment"
curl -s "http://localhost:37777/api/search/observations?query=during+deployment"
curl -s "http://localhost:37777/api/search/observations?query=after+deployment"
```
**Why It's Wrong:**
- Misses context around events
- Inefficient (N searches vs 1 timeline)
- Temporal relationships lost
**The Correction:**
```bash
# ✅ Good: Use timeline tool for contextual investigation
curl -s "http://localhost:37777/api/timeline/by-query?query=deployment&depth_before=10&depth_after=10"
```
**What It Teaches:**
Tool composition - some tools are designed to work together.
**LLM Behavior Insight:**
LLMs may not naturally discover tool composition patterns.
---
## Why These Anti-Patterns Matter
**Addresses LLM Training Bias:**
LLMs default to "load everything" behavior from web scraping training data where thoroughness was rewarded.
**Teaches Protocol Awareness:**
HTTP APIs and MCP have real token limits that can break the system.
**Prevents User Frustration:**
Token limit errors confuse users and break workflows.
**Builds Good Habits:**
Anti-patterns teach the "why" behind best practices.
**Makes Implicit Explicit:**
Surfaces mental models that experienced users internalize but novices miss.
---
## What Happens If These Are Ignored
- **No progressive disclosure**: Every search loads limit=20 in full format → token exhaustion
- **Over-requesting**: 15,000 token searches for 2 relevant results
- **Wrong tool**: Generic search when specialized filters would be 10x faster
- **Premature expansion**: Load full details before knowing relevance
- **Missing composition**: Single-tool thinking, missing powerful multi-step workflows
**Bottom Line:** These anti-patterns waste 5-10x more tokens than necessary and frequently cause system failures.
@@ -0,0 +1,120 @@
# Progressive Disclosure Pattern (MANDATORY)
**Core Principle**: Find the smallest set of high-signal tokens first (index format), then drill down to full details only for relevant items.
## The 4-Step Workflow
### Step 1: Start with Index Format
**Action:**
- Use `format=index` (default in most operations)
- Set `limit=3-5` (not 20)
- Review titles and dates ONLY
**Token Cost:** ~50-100 tokens per result
**Why:** Minimal token investment for maximum signal. Get overview before committing to full details.
**Example:**
```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"
```
**Response:**
```json
{
"query": "authentication",
"count": 5,
"format": "index",
"results": [
{
"id": 1234,
"type": "feature",
"title": "Implemented JWT authentication",
"subtitle": "Added token-based auth with refresh tokens",
"created_at_epoch": 1699564800000,
"project": "api-server"
}
]
}
```
### Step 2: Identify Relevant Items
**Cognitive Task:**
- Scan index results for relevance
- Note which items need full details
- Discard irrelevant items
**Why:** Human-in-the-loop filtering before expensive operations. Don't load full details for items you'll ignore.
### Step 3: Request Full Details (Selectively)
**Action:**
- Use `format=full` ONLY for specific items of interest
- Target by ID or use refined search query
**Token Cost:** ~500-1000 tokens per result
**Principle:** Load only what you need
**Example:**
```bash
# After reviewing index, get full details for observation #1234
curl -s "http://localhost:37777/api/search/observations?query=authentication&format=full&limit=1&offset=2"
```
**Why:** Targeted token expenditure with high ROI. 10x cost difference means selectivity matters.
### Step 4: Refine with Filters (If Needed)
**Techniques:**
- Use `type`, `dateRange`, `concepts`, `files` filters
- Narrow scope BEFORE requesting more results
- Use `offset` for pagination instead of large limits
**Why:** Reduce result set first, then expand selectively. Don't load 20 results when filters could narrow to 3.
## Token Budget Awareness
**Costs:**
- Index result: ~50-100 tokens
- Full result: ~500-1000 tokens
- 10x cost difference
**Starting Points:**
- Start with `limit=3-5` (not 20)
- Reduce limit if hitting token errors
**Savings Example:**
- Naive: 10 items × 750 tokens (avg full) = 7,500 tokens
- Progressive: (5 items × 75 tokens index) + (2 items × 750 tokens full) = 1,875 tokens
- **Savings: 5,625 tokens (75% reduction)**
## What Problems This Solves
1. **Token exhaustion**: Without this, LLMs load everything in full format (9,000+ tokens for 10 items)
2. **Poor signal-to-noise**: Loading full details for irrelevant items wastes tokens
3. **MCP limits**: Large payloads hit protocol limits (system failures)
4. **Inefficiency**: Loading 20 full results when only 2 are relevant
## How It Scales
**With 10 records:**
- Index (500 tokens) → Full (2,000 tokens for 2 relevant) = 2,500 tokens
- Without pattern: Full (10,000 tokens for all 10) = 4x more expensive
**With 1,000 records:**
- Index (500 tokens for top 5) → Full (1,000 tokens for 1 relevant) = 1,500 tokens
- Without pattern: Would hit MCP limits before seeing relevant data
## Context Engineering Alignment
This pattern implements core context engineering principles:
- **Just-in-time context**: Load data dynamically at runtime
- **Progressive disclosure**: Lightweight identifiers (index) → full details as needed
- **Token efficiency**: Minimal high-signal tokens first, expand selectively
- **Attention budget**: Treat context as finite resource with diminishing returns
Always start with the smallest set of high-signal tokens that maximize likelihood of desired outcome.
+57 -331
View File
@@ -5,359 +5,85 @@ description: Diagnose and fix claude-mem installation issues. Checks PM2 worker
# Claude-Mem Troubleshooting Skill
This skill diagnoses and resolves common installation and operational issues with the claude-mem plugin.
Diagnose and resolve installation and operational issues with the claude-mem plugin.
## Quick Reference
## When to Use This Skill
**Common Issues:**
**Invoke this skill when:**
- Memory not persisting after `/clear`
- Viewer UI empty or not loading
- Worker service not running
- Database missing or corrupted
- Port conflicts
- Missing dependencies
- "Nothing is remembered" complaints
- Search results empty when they shouldn't be
## Diagnostic Workflow
**Do NOT invoke** for feature requests or usage questions (use regular documentation for that).
When invoked, follow these steps systematically:
## Quick Decision Guide
### 1. Check PM2 Worker Status
Once the skill is loaded, choose the appropriate operation:
First, verify if the worker service is running:
**What's the problem?**
- "Nothing is being remembered" → [operations/common-issues.md](operations/common-issues.md#nothing-remembered)
- "Viewer is empty" → [operations/common-issues.md](operations/common-issues.md#viewer-empty)
- "Worker won't start" → [operations/common-issues.md](operations/common-issues.md#worker-not-starting)
- "Want to run full diagnostics" → [operations/diagnostics.md](operations/diagnostics.md)
- "Need automated fix" → [operations/automated-fixes.md](operations/automated-fixes.md)
## Available Operations
Choose the appropriate operation file for detailed instructions:
### Diagnostic Workflows
1. **[Full System Diagnostics](operations/diagnostics.md)** - Comprehensive step-by-step diagnostic workflow
2. **[Worker Diagnostics](operations/worker.md)** - PM2 worker-specific troubleshooting
3. **[Database Diagnostics](operations/database.md)** - Database integrity and data checks
### Issue Resolution
4. **[Common Issues](operations/common-issues.md)** - Quick fixes for frequently encountered problems
5. **[Automated Fixes](operations/automated-fixes.md)** - One-command fix sequences
### Reference
6. **[Quick Commands](operations/reference.md)** - Essential commands for troubleshooting
## Quick Start
**Fast automated fix (try this first):**
```bash
# Check if PM2 is available
which pm2 || echo "PM2 not found in PATH"
# List PM2 processes
pm2 jlist 2>&1
# If pm2 is not found, try the local installation
~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2 jlist 2>&1
```
**Expected output:** JSON array with `claude-mem-worker` process showing `"status": "online"`
**If worker not running or status is not "online":**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
# Or use local pm2:
node_modules/.bin/pm2 start ecosystem.config.cjs
```
### 2. Check Worker Service Health
Test if the worker service responds to HTTP requests:
```bash
# Default port is 37777
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
pm2 delete claude-mem-worker 2>/dev/null; \
npm install && \
node_modules/.bin/pm2 start ecosystem.config.cjs && \
sleep 3 && \
curl -s http://127.0.0.1:37777/health
# Check custom port from settings
PORT=$(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT | grep -o '[0-9]\+' || echo "37777")
curl -s http://127.0.0.1:$PORT/health
```
**Expected output:** `{"status":"ok"}`
Expected output: `{"status":"ok"}`
**If connection refused:**
- Worker not running → Go back to step 1
- Port conflict → Check what's using the port:
```bash
lsof -i :37777 || netstat -tlnp | grep 37777
```
If that doesn't work, proceed to detailed diagnostics.
### 3. Check Database
## Response Format
Verify the database exists and contains data:
When troubleshooting:
1. **Identify the symptom** - What's the user reporting?
2. **Choose operation file** - Use the decision guide above
3. **Follow steps systematically** - Don't skip diagnostic steps
4. **Report findings** - Tell user what you found and what was fixed
5. **Verify resolution** - Confirm the issue is resolved
```bash
# Check if database file exists
ls -lh ~/.claude-mem/claude-mem.db
## Technical Notes
# Check database size (should be > 0 bytes)
du -h ~/.claude-mem/claude-mem.db
- **Worker port:** Default 37777 (configurable via `CLAUDE_MEM_WORKER_PORT`)
- **Database location:** `~/.claude-mem/claude-mem.db`
- **Plugin location:** `~/.claude/plugins/marketplaces/thedotmack/`
- **PM2 process name:** `claude-mem-worker`
# Query database for observation count (requires sqlite3)
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as observation_count FROM observations;" 2>&1
## Error Reporting
# Query for session count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as session_count FROM sessions;" 2>&1
If troubleshooting doesn't resolve the issue, collect diagnostic data and direct user to:
https://github.com/thedotmack/claude-mem/issues
# Check recent observations
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, type, title FROM observations ORDER BY created_at DESC LIMIT 5;" 2>&1
```
**Expected:**
- Database file exists (typically 100KB - 10MB+)
- Contains observations and sessions
- Recent observations visible
**If database missing or empty:**
- New installation - this is normal, database will populate as you work
- After `/clear` - sessions are marked complete but not deleted, data should persist
- Corrupted database - backup and recreate:
```bash
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
# Worker will recreate on next observation
```
### 4. Check Dependencies Installation
Verify all required npm packages are installed:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
# Check for critical packages
ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1
ls node_modules/better-sqlite3 2>&1 | head -1
ls node_modules/express 2>&1 | head -1
ls node_modules/pm2 2>&1 | head -1
```
**Expected:** All critical packages present
**If dependencies missing:**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
npm install
```
### 5. Check Worker Logs
Review recent worker logs for errors:
```bash
# View last 50 lines of worker logs
pm2 logs claude-mem-worker --lines 50 --nostream
# Or use local pm2:
cd ~/.claude/plugins/marketplaces/thedotmack/
node_modules/.bin/pm2 logs claude-mem-worker --lines 50 --nostream
# Check for specific errors
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error\|exception\|failed"
```
### 6. Test Viewer UI
Check if the web viewer is accessible:
```bash
# Test viewer endpoint
curl -s http://127.0.0.1:37777/ | head -20
# Test stats endpoint
curl -s http://127.0.0.1:37777/api/stats
```
**Expected:**
- `/` returns HTML page with React viewer
- `/api/stats` returns JSON with database counts
### 7. Check Port Configuration
Verify port settings and availability:
```bash
# Check if custom port is configured
cat ~/.claude-mem/settings.json 2>/dev/null
cat ~/.claude/settings.json 2>/dev/null
# Check what's listening on default port
lsof -i :37777 2>&1 || netstat -tlnp 2>&1 | grep 37777
# Test connectivity
nc -zv 127.0.0.1 37777 2>&1
```
## Automated Fix Sequence
If you're seeing issues, try this automated fix sequence:
```bash
# 1. Stop the worker
pm2 delete claude-mem-worker 2>/dev/null || true
# 2. Navigate to plugin directory
cd ~/.claude/plugins/marketplaces/thedotmack/
# 3. Ensure dependencies are installed
npm install
# 4. Start worker with local pm2
node_modules/.bin/pm2 start ecosystem.config.cjs
# 5. Wait for health check
sleep 3
curl -s http://127.0.0.1:37777/health
# 6. Check logs for any errors
node_modules/.bin/pm2 logs claude-mem-worker --lines 20 --nostream
```
## Common Issue Resolutions
### Issue: "Nothing is remembered after /clear"
**Root cause:** Sessions are marked complete but data should persist. This suggests:
- Worker not processing observations
- Database not being written to
- Context hook not reading from database
**Fix:**
1. Verify worker is running (Step 1)
2. Check database has recent observations (Step 3)
3. Restart worker and start new session
4. Create a test observation: `/skill version-bump` then cancel
5. Check if observation appears in viewer: http://127.0.0.1:37777
### Issue: "Viewer empty after every Claude restart"
**Root cause:**
- Database being recreated on startup (shouldn't happen)
- Worker reading from wrong database location
- Database permissions issue
**Fix:**
1. Check database file exists and has data (Step 3)
2. Check file permissions:
```bash
ls -la ~/.claude-mem/claude-mem.db
# Should be readable/writable by your user
```
3. Verify worker is using correct database path in logs
4. Test viewer connection manually
### Issue: "Old memory in Claude"
**Root cause:** Context hook injecting stale observations
**Fix:**
1. Check the observation count setting:
```bash
grep CLAUDE_MEM_CONTEXT_OBSERVATIONS ~/.claude/settings.json
```
2. Default is 50 observations - you can adjust this
3. Check database for actual observation dates:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, project, title FROM observations ORDER BY created_at DESC LIMIT 10;"
```
### Issue: "Worker not starting"
**Root cause:**
- Port already in use
- PM2 not installed or not in PATH
- Missing dependencies
**Fix:**
1. Try manual worker start:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
node plugin/scripts/worker-service.cjs
# Should start server on port 37777
```
2. If port in use, change it:
```bash
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
```
## Full System Diagnosis
Run this comprehensive diagnostic script:
```bash
#!/bin/bash
echo "=== Claude-Mem Troubleshooting Report ==="
echo ""
echo "1. Environment"
echo " OS: $(uname -s)"
echo ""
echo "2. Plugin Installation"
echo " Plugin directory exists: $([ -d ~/.claude/plugins/marketplaces/thedotmack ] && echo 'YES' || echo 'NO')"
echo " Package version: $(grep '"version"' ~/.claude/plugins/marketplaces/thedotmack/package.json 2>/dev/null | head -1)"
echo ""
echo "3. Database"
echo " Database exists: $([ -f ~/.claude-mem/claude-mem.db ] && echo 'YES' || echo 'NO')"
echo " Database size: $(du -h ~/.claude-mem/claude-mem.db 2>/dev/null | cut -f1)"
echo " Observation count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM observations;' 2>/dev/null || echo 'N/A')"
echo " Session count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;' 2>/dev/null || echo 'N/A')"
echo ""
echo "4. Worker Service"
PM2_PATH=$(which pm2 2>/dev/null || echo "~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2")
echo " PM2 path: $PM2_PATH"
WORKER_STATUS=$($PM2_PATH jlist 2>/dev/null | grep -o '"name":"claude-mem-worker".*"status":"[^"]*"' | grep -o 'status":"[^"]*"' | cut -d'"' -f3 || echo 'not running')
echo " Worker status: $WORKER_STATUS"
echo " Health check: $(curl -s http://127.0.0.1:37777/health 2>/dev/null || echo 'FAILED')"
echo ""
echo "5. Configuration"
echo " Port setting: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT || echo 'default (37777)')"
echo " Observation count: $(cat ~/.claude/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
echo ""
echo "6. Recent Activity"
echo " Latest observation: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM observations ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo " Latest session: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo ""
echo "=== End Report ==="
```
Save this as `/tmp/claude-mem-diagnostics.sh` and run:
```bash
bash /tmp/claude-mem-diagnostics.sh
```
## Reporting Issues
If troubleshooting doesn't resolve the issue, collect this information for a bug report:
1. Full diagnostic report (run script above)
2. Worker logs: `pm2 logs claude-mem-worker --lines 100 --nostream`
3. Your setup:
- Claude version: Check with Claude
- OS: `uname -a`
- Node version: `node --version`
- Plugin version: In package.json
4. Steps to reproduce the issue
5. Expected vs actual behavior
Post to: https://github.com/thedotmack/claude-mem/issues
## Prevention Tips
**Keep claude-mem healthy:**
- Regularly check viewer UI to see if observations are being captured
- Monitor database size (shouldn't grow unbounded)
- Update plugin when new versions are released
- Keep Claude Code updated
**Performance tuning:**
- Adjust `CLAUDE_MEM_CONTEXT_OBSERVATIONS` if context is too large/small
- Use `/clear` to mark sessions complete and start fresh
- Use MCP search tools to query specific memories instead of loading everything
## Quick Commands Reference
```bash
# Restart worker
pm2 restart claude-mem-worker
# View logs
pm2 logs claude-mem-worker
# Check health
curl http://127.0.0.1:37777/health
# View database stats
curl http://127.0.0.1:37777/api/stats
# Open viewer
open http://127.0.0.1:37777
# Delete and reinstall worker
pm2 delete claude-mem-worker
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
```
See [operations/diagnostics.md](operations/diagnostics.md#reporting-issues) for details on what to collect.
@@ -0,0 +1,151 @@
# Automated Fix Sequences
One-command fix sequences for common claude-mem issues.
## Quick Fix: Complete Reset and Restart
**Use when:** General issues, worker not responding, after updates
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
pm2 delete claude-mem-worker 2>/dev/null; \
npm install && \
node_modules/.bin/pm2 start ecosystem.config.cjs && \
sleep 3 && \
curl -s http://127.0.0.1:37777/health
```
**Expected output:** `{"status":"ok"}`
**What it does:**
1. Stops the worker (if running)
2. Ensures dependencies are installed
3. Starts worker with local PM2
4. Waits for startup
5. Verifies health
## Fix: Worker Not Running
**Use when:** PM2 shows worker as stopped or not listed
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
node_modules/.bin/pm2 start ecosystem.config.cjs && \
sleep 2 && \
pm2 status
```
**Expected output:** Worker shows as "online"
## Fix: Dependencies Missing
**Use when:** Worker won't start due to missing packages
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
npm install && \
pm2 restart claude-mem-worker
```
## Fix: Port Conflict
**Use when:** Error shows port already in use
```bash
# Change to port 37778
mkdir -p ~/.claude-mem && \
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json && \
pm2 restart claude-mem-worker && \
sleep 2 && \
curl -s http://127.0.0.1:37778/health
```
**Expected output:** `{"status":"ok"}`
## Fix: Database Issues
**Use when:** Database appears corrupted or out of sync
```bash
# Backup and test integrity
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup && \
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;" && \
pm2 restart claude-mem-worker
```
**If integrity check fails, recreate database:**
```bash
# WARNING: This deletes all memory data
mv ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.old && \
pm2 restart claude-mem-worker
```
## Fix: Clean Reinstall
**Use when:** All else fails, nuclear option
```bash
# Backup data first
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup 2>/dev/null
# Stop and remove worker
pm2 delete claude-mem-worker 2>/dev/null
# Reinstall dependencies
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
rm -rf node_modules && \
npm install
# Start worker
node_modules/.bin/pm2 start ecosystem.config.cjs && \
sleep 3 && \
curl -s http://127.0.0.1:37777/health
```
## Fix: Clear PM2 Logs
**Use when:** Logs are too large, want fresh start
```bash
pm2 flush claude-mem-worker && \
pm2 restart claude-mem-worker
```
## Verification Commands
**After running any fix, verify with these:**
```bash
# Check worker status
pm2 status | grep claude-mem-worker
# Check health
curl -s http://127.0.0.1:37777/health
# Check database
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
# Check viewer
curl -s http://127.0.0.1:37777/api/stats
# Check logs for errors
pm2 logs claude-mem-worker --lines 20 --nostream | grep -i error
```
**All checks should pass:**
- Worker status: "online"
- Health: `{"status":"ok"}`
- Database: Shows count (may be 0 if new)
- Stats: Returns JSON with counts
- Logs: No recent errors
## Troubleshooting the Fixes
**If automated fix fails:**
1. Run the diagnostic script from [diagnostics.md](diagnostics.md)
2. Check specific error in PM2 logs
3. Try manual worker start to see detailed error:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
node plugin/scripts/worker-service.cjs
```
@@ -0,0 +1,232 @@
# Common Issue Resolutions
Quick fixes for frequently encountered claude-mem problems.
## Issue: Nothing is Remembered After `/clear` {#nothing-remembered}
**Symptoms:**
- Data doesn't persist across sessions
- Context is empty after `/clear`
- Search returns no results for past work
**Root cause:** Sessions are marked complete but data should persist. This suggests:
- Worker not processing observations
- Database not being written to
- Context hook not reading from database
**Fix:**
1. Verify worker is running:
```bash
pm2 jlist | grep claude-mem-worker
```
2. Check database has recent observations:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations WHERE created_at > datetime('now', '-1 day');"
```
3. Restart worker and start new session:
```bash
pm2 restart claude-mem-worker
```
4. Create a test observation: `/skill version-bump` then cancel
5. Check if observation appears in viewer:
```bash
open http://127.0.0.1:37777
# Or manually check database:
sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM observations ORDER BY created_at DESC LIMIT 1;"
```
## Issue: Viewer Empty After Every Claude Restart {#viewer-empty}
**Symptoms:**
- Viewer shows no data at http://127.0.0.1:37777
- Stats endpoint returns all zeros
- Database appears empty in UI
**Root cause:**
- Database being recreated on startup (shouldn't happen)
- Worker reading from wrong database location
- Database permissions issue
**Fix:**
1. Check database file exists and has data:
```bash
ls -lh ~/.claude-mem/claude-mem.db
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
2. Check file permissions:
```bash
ls -la ~/.claude-mem/claude-mem.db
# Should be readable/writable by your user
```
3. Verify worker is using correct database path in logs:
```bash
pm2 logs claude-mem-worker --lines 50 --nostream | grep "Database"
```
4. Test viewer connection manually:
```bash
curl -s http://127.0.0.1:37777/api/stats
# Should show non-zero counts if data exists
```
## Issue: Old Memory in Claude {#old-memory}
**Symptoms:**
- Context contains outdated observations
- Irrelevant past work appearing in sessions
- Context feels stale
**Root cause:** Context hook injecting stale observations
**Fix:**
1. Check the observation count setting:
```bash
grep CLAUDE_MEM_CONTEXT_OBSERVATIONS ~/.claude/settings.json
```
2. Default is 50 observations - you can adjust this:
```json
{
"env": {
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "25"
}
}
```
3. Check database for actual observation dates:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, project, title FROM observations ORDER BY created_at DESC LIMIT 10;"
```
4. Consider filtering by project if working on multiple codebases
## Issue: Worker Not Starting {#worker-not-starting}
**Symptoms:**
- PM2 shows worker as "stopped" or "errored"
- Health check fails
- Viewer not accessible
**Root cause:**
- Port already in use
- PM2 not installed or not in PATH
- Missing dependencies
**Fix:**
1. Try manual worker start to see error:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
node plugin/scripts/worker-service.cjs
# Should start server on port 37777 or show error
```
2. If port in use, change it:
```bash
mkdir -p ~/.claude-mem
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
```
3. If dependencies missing:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
npm install
pm2 start ecosystem.config.cjs
```
## Issue: Search Results Empty
**Symptoms:**
- Search skill returns no results
- API endpoints return empty arrays
- Know there's data but can't find it
**Root cause:**
- FTS5 tables not synchronized
- Wrong project filter
- Database not being queried correctly
**Fix:**
1. Check if observations exist in database:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
2. Check FTS5 table sync:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations_fts;"
# Should match observation count
```
3. Try search via API directly:
```bash
curl "http://127.0.0.1:37777/api/search/observations?q=test&format=index"
```
4. If FTS5 out of sync, restart worker (triggers reindex):
```bash
pm2 restart claude-mem-worker
```
## Issue: Port Conflicts
**Symptoms:**
- Worker won't start
- Error: "EADDRINUSE: address already in use"
- Health check fails
**Fix:**
1. Check what's using port 37777:
```bash
lsof -i :37777
```
2. Either kill the conflicting process or change claude-mem port:
```bash
mkdir -p ~/.claude-mem
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
pm2 restart claude-mem-worker
```
## Issue: Database Corrupted
**Symptoms:**
- SQLite errors in logs
- Worker crashes on startup
- Queries fail
**Fix:**
1. Backup the database:
```bash
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
```
2. Try to repair:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"
```
3. If repair fails, recreate (loses data):
```bash
rm ~/.claude-mem/claude-mem.db
pm2 restart claude-mem-worker
# Worker will create new database
```
## Prevention Tips
**Keep claude-mem healthy:**
- Regularly check viewer UI to see if observations are being captured
- Monitor database size (shouldn't grow unbounded)
- Update plugin when new versions are released
- Keep Claude Code updated
**Performance tuning:**
- Adjust `CLAUDE_MEM_CONTEXT_OBSERVATIONS` if context is too large/small
- Use `/clear` to mark sessions complete and start fresh
- Use search skill to query specific memories instead of loading everything
@@ -0,0 +1,403 @@
# Database Diagnostics
SQLite database troubleshooting for claude-mem.
## Database Overview
Claude-mem uses SQLite3 for persistent storage:
- **Location:** `~/.claude-mem/claude-mem.db`
- **Library:** better-sqlite3 (synchronous, not bun:sqlite)
- **Features:** FTS5 full-text search, triggers, indexes
- **Tables:** observations, sessions, user_prompts, observations_fts, sessions_fts, prompts_fts
## Basic Database Checks
### Check Database Exists
```bash
# Check file exists
ls -lh ~/.claude-mem/claude-mem.db
# Check file size
du -h ~/.claude-mem/claude-mem.db
# Check permissions
ls -la ~/.claude-mem/claude-mem.db
```
**Expected:**
- File exists
- Size: 100KB - 10MB+ (depends on usage)
- Permissions: Readable/writable by your user
### Check Database Integrity
```bash
# Run integrity check
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"
```
**Expected output:** `ok`
**If errors appear:**
- Database corrupted
- Backup immediately: `cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup`
- Consider recreating (data loss)
## Data Inspection
### Count Records
```bash
# Observation count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
# Session count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM sessions;"
# User prompt count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM user_prompts;"
# FTS5 table counts (should match main tables)
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations_fts;"
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM sessions_fts;"
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM prompts_fts;"
```
### View Recent Records
```bash
# Recent observations
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
created_at,
type,
title,
project
FROM observations
ORDER BY created_at DESC
LIMIT 10;
"
# Recent sessions
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
created_at,
request,
project
FROM sessions
ORDER BY created_at DESC
LIMIT 5;
"
# Recent user prompts
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
created_at,
prompt
FROM user_prompts
ORDER BY created_at DESC
LIMIT 10;
"
```
### Check Projects
```bash
# List all projects
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT DISTINCT project
FROM observations
ORDER BY project;
"
# Count observations per project
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
project,
COUNT(*) as count
FROM observations
GROUP BY project
ORDER BY count DESC;
"
```
## Database Schema
### View Table Structure
```bash
# List all tables
sqlite3 ~/.claude-mem/claude-mem.db ".tables"
# Show observations table schema
sqlite3 ~/.claude-mem/claude-mem.db ".schema observations"
# Show all schemas
sqlite3 ~/.claude-mem/claude-mem.db ".schema"
```
### Expected Tables
- `observations` - Main observation records
- `observations_fts` - FTS5 virtual table for full-text search
- `sessions` - Session summary records
- `sessions_fts` - FTS5 virtual table for session search
- `user_prompts` - User prompt records
- `prompts_fts` - FTS5 virtual table for prompt search
## FTS5 Synchronization
The FTS5 tables should stay synchronized with main tables via triggers.
### Check FTS5 Sync
```bash
# Compare counts
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
(SELECT COUNT(*) FROM observations) as observations,
(SELECT COUNT(*) FROM observations_fts) as observations_fts,
(SELECT COUNT(*) FROM sessions) as sessions,
(SELECT COUNT(*) FROM sessions_fts) as sessions_fts,
(SELECT COUNT(*) FROM user_prompts) as prompts,
(SELECT COUNT(*) FROM prompts_fts) as prompts_fts;
"
```
**Expected:** All pairs should match (observations = observations_fts, etc.)
### Fix FTS5 Desync
If FTS5 counts don't match, triggers may have failed. Restart worker to rebuild:
```bash
pm2 restart claude-mem-worker
```
The worker will rebuild FTS5 indexes on startup if they're out of sync.
## Common Database Issues
### Issue: Database Doesn't Exist
**Cause:** First run, or database was deleted
**Fix:** Database will be created automatically on first observation. No action needed.
### Issue: Database is Empty (0 Records)
**Cause:**
- New installation (normal)
- Data was deleted
- Worker not processing observations
**Fix:**
1. Create test observation (use any skill and cancel)
2. Check worker logs for errors:
```bash
pm2 logs claude-mem-worker --lines 50 --nostream
```
3. Verify observation appears in database
### Issue: Database Permission Denied
**Cause:** File permissions wrong, database owned by different user
**Fix:**
```bash
# Check ownership
ls -la ~/.claude-mem/claude-mem.db
# Fix permissions (if needed)
chmod 644 ~/.claude-mem/claude-mem.db
chown $USER ~/.claude-mem/claude-mem.db
```
### Issue: Database Locked
**Cause:**
- Multiple processes accessing database
- Crash left lock file
- Long-running transaction
**Fix:**
```bash
# Check for lock file
ls -la ~/.claude-mem/claude-mem.db-wal
ls -la ~/.claude-mem/claude-mem.db-shm
# Remove lock files (only if worker is stopped!)
pm2 stop claude-mem-worker
rm ~/.claude-mem/claude-mem.db-wal ~/.claude-mem/claude-mem.db-shm
pm2 start claude-mem-worker
```
### Issue: Database Growing Too Large
**Cause:** Too many observations accumulated
**Check size:**
```bash
du -h ~/.claude-mem/claude-mem.db
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
```
**Options:**
1. Delete old observations (manual cleanup):
```bash
sqlite3 ~/.claude-mem/claude-mem.db "
DELETE FROM observations
WHERE created_at < datetime('now', '-90 days');
"
```
2. Vacuum to reclaim space:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "VACUUM;"
```
3. Archive and start fresh:
```bash
mv ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.archive
pm2 restart claude-mem-worker
```
## Database Recovery
### Backup Database
**Before any destructive operations:**
```bash
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
```
### Restore from Backup
```bash
pm2 stop claude-mem-worker
cp ~/.claude-mem/claude-mem.db.backup ~/.claude-mem/claude-mem.db
pm2 start claude-mem-worker
```
### Export Data
Export to JSON for safekeeping:
```bash
# Export observations
sqlite3 ~/.claude-mem/claude-mem.db -json "SELECT * FROM observations;" > observations.json
# Export sessions
sqlite3 ~/.claude-mem/claude-mem.db -json "SELECT * FROM sessions;" > sessions.json
# Export prompts
sqlite3 ~/.claude-mem/claude-mem.db -json "SELECT * FROM user_prompts;" > prompts.json
```
### Recreate Database
**WARNING: Data loss. Backup first!**
```bash
# Stop worker
pm2 stop claude-mem-worker
# Backup current database
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.old
# Delete database
rm ~/.claude-mem/claude-mem.db
# Start worker (creates new database)
pm2 start claude-mem-worker
```
## Database Statistics
### Storage Analysis
```bash
# Database file size
du -h ~/.claude-mem/claude-mem.db
# Record counts by type
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
type,
COUNT(*) as count
FROM observations
GROUP BY type
ORDER BY count DESC;
"
# Observations per month
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
strftime('%Y-%m', created_at) as month,
COUNT(*) as count
FROM observations
GROUP BY month
ORDER BY month DESC;
"
# Average observation size (characters)
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT
AVG(LENGTH(content)) as avg_content_length,
MAX(LENGTH(content)) as max_content_length
FROM observations;
"
```
## Advanced Queries
### Find Specific Observations
```bash
# Search by keyword (FTS5)
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT title, created_at
FROM observations_fts
WHERE observations_fts MATCH 'authentication'
ORDER BY created_at DESC;
"
# Find by type
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT title, created_at
FROM observations
WHERE type = 'bugfix'
ORDER BY created_at DESC
LIMIT 10;
"
# Find by file path
sqlite3 ~/.claude-mem/claude-mem.db "
SELECT title, created_at
FROM observations
WHERE file_path LIKE '%auth%'
ORDER BY created_at DESC;
"
```
## Database Maintenance
### Regular Maintenance Tasks
```bash
# Analyze for query optimization
sqlite3 ~/.claude-mem/claude-mem.db "ANALYZE;"
# Rebuild FTS5 indexes
sqlite3 ~/.claude-mem/claude-mem.db "
INSERT INTO observations_fts(observations_fts) VALUES('rebuild');
INSERT INTO sessions_fts(sessions_fts) VALUES('rebuild');
INSERT INTO prompts_fts(prompts_fts) VALUES('rebuild');
"
# Vacuum to reclaim space
sqlite3 ~/.claude-mem/claude-mem.db "VACUUM;"
```
**Run monthly to keep database healthy.**
@@ -0,0 +1,219 @@
# Full System Diagnostics
Comprehensive step-by-step diagnostic workflow for claude-mem issues.
## Diagnostic Workflow
Run these checks systematically to identify the root cause:
### 1. Check PM2 Worker Status
First, verify if the worker service is running:
```bash
# Check if PM2 is available
which pm2 || echo "PM2 not found in PATH"
# List PM2 processes
pm2 jlist 2>&1
# If pm2 is not found, try the local installation
~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2 jlist 2>&1
```
**Expected output:** JSON array with `claude-mem-worker` process showing `"status": "online"`
**If worker not running or status is not "online":**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
# Or use local pm2:
node_modules/.bin/pm2 start ecosystem.config.cjs
```
### 2. Check Worker Service Health
Test if the worker service responds to HTTP requests:
```bash
# Default port is 37777
curl -s http://127.0.0.1:37777/health
# Check custom port from settings
PORT=$(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT | grep -o '[0-9]\+' || echo "37777")
curl -s http://127.0.0.1:$PORT/health
```
**Expected output:** `{"status":"ok"}`
**If connection refused:**
- Worker not running → Go back to step 1
- Port conflict → Check what's using the port:
```bash
lsof -i :37777 || netstat -tlnp | grep 37777
```
### 3. Check Database
Verify the database exists and contains data:
```bash
# Check if database file exists
ls -lh ~/.claude-mem/claude-mem.db
# Check database size (should be > 0 bytes)
du -h ~/.claude-mem/claude-mem.db
# Query database for observation count (requires sqlite3)
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as observation_count FROM observations;" 2>&1
# Query for session count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as session_count FROM sessions;" 2>&1
# Check recent observations
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, type, title FROM observations ORDER BY created_at DESC LIMIT 5;" 2>&1
```
**Expected:**
- Database file exists (typically 100KB - 10MB+)
- Contains observations and sessions
- Recent observations visible
**If database missing or empty:**
- New installation - this is normal, database will populate as you work
- After `/clear` - sessions are marked complete but not deleted, data should persist
- Corrupted database - backup and recreate:
```bash
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
# Worker will recreate on next observation
```
### 4. Check Dependencies Installation
Verify all required npm packages are installed:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
# Check for critical packages
ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1
ls node_modules/better-sqlite3 2>&1 | head -1
ls node_modules/express 2>&1 | head -1
ls node_modules/pm2 2>&1 | head -1
```
**Expected:** All critical packages present
**If dependencies missing:**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
npm install
```
### 5. Check Worker Logs
Review recent worker logs for errors:
```bash
# View last 50 lines of worker logs
pm2 logs claude-mem-worker --lines 50 --nostream
# Or use local pm2:
cd ~/.claude/plugins/marketplaces/thedotmack/
node_modules/.bin/pm2 logs claude-mem-worker --lines 50 --nostream
# Check for specific errors
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error\|exception\|failed"
```
### 6. Test Viewer UI
Check if the web viewer is accessible:
```bash
# Test viewer endpoint
curl -s http://127.0.0.1:37777/ | head -20
# Test stats endpoint
curl -s http://127.0.0.1:37777/api/stats
```
**Expected:**
- `/` returns HTML page with React viewer
- `/api/stats` returns JSON with database counts
### 7. Check Port Configuration
Verify port settings and availability:
```bash
# Check if custom port is configured
cat ~/.claude-mem/settings.json 2>/dev/null
cat ~/.claude/settings.json 2>/dev/null
# Check what's listening on default port
lsof -i :37777 2>&1 || netstat -tlnp 2>&1 | grep 37777
# Test connectivity
nc -zv 127.0.0.1 37777 2>&1
```
## Full System Diagnosis Script
Run this comprehensive diagnostic script to collect all information:
```bash
#!/bin/bash
echo "=== Claude-Mem Troubleshooting Report ==="
echo ""
echo "1. Environment"
echo " OS: $(uname -s)"
echo ""
echo "2. Plugin Installation"
echo " Plugin directory exists: $([ -d ~/.claude/plugins/marketplaces/thedotmack ] && echo 'YES' || echo 'NO')"
echo " Package version: $(grep '"version"' ~/.claude/plugins/marketplaces/thedotmack/package.json 2>/dev/null | head -1)"
echo ""
echo "3. Database"
echo " Database exists: $([ -f ~/.claude-mem/claude-mem.db ] && echo 'YES' || echo 'NO')"
echo " Database size: $(du -h ~/.claude-mem/claude-mem.db 2>/dev/null | cut -f1)"
echo " Observation count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM observations;' 2>/dev/null || echo 'N/A')"
echo " Session count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;' 2>/dev/null || echo 'N/A')"
echo ""
echo "4. Worker Service"
PM2_PATH=$(which pm2 2>/dev/null || echo "~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2")
echo " PM2 path: $PM2_PATH"
WORKER_STATUS=$($PM2_PATH jlist 2>/dev/null | grep -o '"name":"claude-mem-worker".*"status":"[^"]*"' | grep -o 'status":"[^"]*"' | cut -d'"' -f3 || echo 'not running')
echo " Worker status: $WORKER_STATUS"
echo " Health check: $(curl -s http://127.0.0.1:37777/health 2>/dev/null || echo 'FAILED')"
echo ""
echo "5. Configuration"
echo " Port setting: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT || echo 'default (37777)')"
echo " Observation count: $(cat ~/.claude/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
echo ""
echo "6. Recent Activity"
echo " Latest observation: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM observations ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo " Latest session: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo ""
echo "=== End Report ==="
```
Save this as `/tmp/claude-mem-diagnostics.sh` and run:
```bash
bash /tmp/claude-mem-diagnostics.sh
```
## Reporting Issues
If troubleshooting doesn't resolve the issue, collect this information for a bug report:
1. Full diagnostic report (run script above)
2. Worker logs: `pm2 logs claude-mem-worker --lines 100 --nostream`
3. Your setup:
- Claude version: Check with Claude
- OS: `uname -a`
- Node version: `node --version`
- Plugin version: In package.json
4. Steps to reproduce the issue
5. Expected vs actual behavior
Post to: https://github.com/thedotmack/claude-mem/issues
@@ -0,0 +1,190 @@
# Quick Commands Reference
Essential commands for troubleshooting claude-mem.
## Worker Management
```bash
# Check worker status
pm2 status | grep claude-mem-worker
pm2 jlist | grep claude-mem-worker # JSON format
# Start worker
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
# Restart worker
pm2 restart claude-mem-worker
# Stop worker
pm2 stop claude-mem-worker
# Delete worker (for clean restart)
pm2 delete claude-mem-worker
# View logs
pm2 logs claude-mem-worker
# View last N lines
pm2 logs claude-mem-worker --lines 50 --nostream
# Clear logs
pm2 flush claude-mem-worker
```
## Health Checks
```bash
# Check worker health (default port)
curl -s http://127.0.0.1:37777/health
# Check viewer stats
curl -s http://127.0.0.1:37777/api/stats
# Open viewer in browser
open http://127.0.0.1:37777
# Test custom port
PORT=37778
curl -s http://127.0.0.1:$PORT/health
```
## Database Queries
```bash
# Observation count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
# Session count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM sessions;"
# Recent observations
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, type, title FROM observations ORDER BY created_at DESC LIMIT 10;"
# Recent sessions
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, request FROM sessions ORDER BY created_at DESC LIMIT 5;"
# Database size
du -h ~/.claude-mem/claude-mem.db
# Database integrity check
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"
# Projects in database
sqlite3 ~/.claude-mem/claude-mem.db "SELECT DISTINCT project FROM observations ORDER BY project;"
```
## Configuration
```bash
# View current settings
cat ~/.claude-mem/settings.json
cat ~/.claude/settings.json
# Change worker port
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
# Change context observation count
# Edit ~/.claude/settings.json and add:
{
"env": {
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "25"
}
}
# Change AI model
{
"env": {
"CLAUDE_MEM_MODEL": "claude-haiku-4-5"
}
}
```
## Plugin Management
```bash
# Navigate to plugin directory
cd ~/.claude/plugins/marketplaces/thedotmack/
# Check plugin version
grep '"version"' package.json
# Reinstall dependencies
npm install
# View package.json
cat package.json
```
## Port Diagnostics
```bash
# Check what's using port 37777
lsof -i :37777
netstat -tlnp | grep 37777
# Test port connectivity
nc -zv 127.0.0.1 37777
curl -v http://127.0.0.1:37777/health
```
## Log Analysis
```bash
# Search logs for errors
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error"
# Search for specific keyword
pm2 logs claude-mem-worker --lines 100 --nostream | grep "keyword"
# Follow logs in real-time
pm2 logs claude-mem-worker
# Show only error logs
pm2 logs claude-mem-worker --err
```
## File Locations
```bash
# Plugin directory
~/.claude/plugins/marketplaces/thedotmack/
# Database
~/.claude-mem/claude-mem.db
# Settings
~/.claude-mem/settings.json
~/.claude/settings.json
# Chroma vector database
~/.claude-mem/chroma/
# Usage logs
~/.claude-mem/usage-logs/
# PM2 logs
~/.pm2/logs/
```
## System Information
```bash
# OS version
uname -a
# Node version
node --version
# NPM version
npm --version
# PM2 version
pm2 --version
# SQLite version
sqlite3 --version
# Check disk space
df -h ~/.claude-mem/
```

Some files were not shown because too many files have changed in this diff Show More