12 KiB
Claude-Mem: AI Development Instructions
What This Project Is
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future 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.0.3
Critical Architecture Knowledge
The Lifecycle Flow
-
SessionStart →
context-hook.tsruns- Smart installer checks dependencies (cached, only runs on version changes)
- Starts PM2 worker if not healthy
- Injects context from previous sessions (configurable observation count)
-
UserPromptSubmit →
new-hook.tsruns- Creates session record in SQLite
- Saves raw user prompt for FTS5 search
-
PostToolUse →
save-hook.tsruns- Captures your tool executions
- Sends to worker service for AI compression
-
Summary → Summary hook generates session summaries
-
SessionEnd →
cleanup-hook.tsruns- Marks session complete (graceful, not DELETE)
- Skips on
/clearto preserve ongoing sessions
Key Components
Hooks (src/hooks/*.ts)
- Built to
plugin/scripts/*-hook.js(ESM format) - Must output valid JSON to
hookSpecificOutputfield - Called by Claude Code lifecycle events
Worker Service (src/services/worker-service.ts)
- Express.js API on port 37777 (configurable via
CLAUDE_MEM_WORKER_PORT) - Managed by PM2 (auto-started by hooks)
- Built to
plugin/worker-service.cjs(CJS format) - Handles AI processing asynchronously to avoid hook timeouts
Database (src/services/sqlite/)
- SQLite3 with better-sqlite3 (NOT bun:sqlite - that's legacy)
- Location:
~/.claude-mem/claude-mem.db - FTS5 virtual tables for full-text search
SessionStore= CRUD,SessionSearch= FTS5 queries
MCP Search Server (src/servers/search-server.ts)
- Exposes 8 search tools to Claude Code
- Configured in
plugin/.mcp.json - Built to
plugin/search-server.js(ESM format)
How to Make Changes
When You Modify Hooks
npm run build
npm run sync-marketplace
Changes take effect on next Claude Code session. No worker restart needed.
When You Modify Worker Service
npm run build
npm run sync-marketplace
npm run worker:restart
Must restart PM2 worker for changes to take effect.
When You Modify MCP Server
npm run build
npm run sync-marketplace
# Restart Claude Code for MCP changes
Build Pipeline
npm run build→ Compiles TypeScript, outputs toplugin/npm run sync-marketplace→ Syncs to~/.claude/plugins/marketplaces/thedotmack/- Changes are live for next session (hooks/MCP) or after restart (worker)
Coding Standards: DRY, YAGNI, and Anti-Patterns
Philosophy: Write the dumb, obvious thing first. Add complexity only when you actually hit the problem.
Common Anti-Patterns to Avoid
1. Wrapper Functions for Constants
// ❌ DON'T: Ceremonial wrapper that adds zero value
export function getWorkerPort(): number {
return FIXED_PORT;
}
// ✅ DO: Export the constant directly
export const WORKER_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || "37777", 10);
2. Unused Default Parameters
// ❌ DON'T: Defaults that are never actually used
async function isHealthy(timeout: number = 3000) { ... }
// Every call: isHealthy(1000) - the default is dead code
// ✅ DO: Remove the default if no one uses it
async function isHealthy(timeout: number) { ... }
3. Magic Numbers Everywhere
// ❌ DON'T: Unexplained magic numbers scattered throughout
if (await isWorkerHealthy(1000)) { ... }
await waitForHealth(10000);
setTimeout(resolve, 100);
// ✅ DO: Named constants with context
const HEALTH_CHECK_TIMEOUT_MS = 1000;
const HEALTH_CHECK_MAX_WAIT_MS = 10000;
const HEALTH_CHECK_POLL_INTERVAL_MS = 100;
4. Overengineered Error Handling
// ❌ DON'T: Silent failures and defensive programming for ghosts
checkProcess.on("close", (code) => {
// PM2 list can fail, but we should still continue - just assume worker isn't running
resolve(); // <- Silent failure!
});
// ✅ DO: Fail fast with clear errors
checkProcess.on("close", (code) => {
if (code !== 0) {
reject(new Error(`PM2 not found - install dependencies first`));
}
resolve();
});
5. Fragile String Parsing
// ❌ DON'T: Parse human-readable output with string matching
const isRunning = output.includes("claude-mem-worker") && output.includes("online");
// ✅ DO: Use structured output (JSON)
const processes = JSON.parse(execSync('pm2 jlist'));
const isRunning = processes.some(p => p.name === 'claude-mem-worker' && p.pm2_env.status === 'online');
6. Duplicated Promise Wrappers
// ❌ DON'T: Copy-paste the same promise pattern multiple times
await new Promise((resolve, reject) => {
process1.on("error", reject);
process1.on("close", (code) => { /* ... */ });
});
// ... later ...
await new Promise((resolve, reject) => {
process2.on("error", reject);
process2.on("close", (code) => { /* ... same pattern */ });
});
// ✅ DO: Extract a helper function
async function waitForProcess(process: ChildProcess, validateExitCode = false): Promise<void> {
return new Promise((resolve, reject) => {
process.on("error", reject);
process.on("close", (code) => {
if (validateExitCode && code !== 0 && code !== null) {
reject(new Error(`Process failed with exit code ${code}`));
} else {
resolve();
}
});
});
}
7. YAGNI Violations - Solving Problems You Don't Have
// ❌ DON'T: 50+ lines checking PM2 status before starting
const checkProcess = spawn(pm2Path, ["list", "--no-color"]);
// ... parse output ...
// ... check if running ...
// ... then maybe start it ...
// ✅ DO: Just start it (PM2 start is idempotent)
if (!await isWorkerHealthy()) {
await startWorker(); // PM2 handles "already running" gracefully
if (!await waitForWorkerHealth()) {
throw new Error("Worker failed to become healthy");
}
}
Why These Patterns Appear
These anti-patterns often emerge from:
- Training bias: Code that looks "professional" is often overengineered
- Risk aversion: Optimizing for "what could go wrong" instead of "what do you actually need"
- Pattern matching: Seeing a problem and immediately scaffolding a framework
- No real-world pain: Not debugging at 2am means not feeling the cost of complexity
The Actual Standard
- YAGNI (You Aren't Gonna Need It): Don't build it until you need it
- DRY (Don't Repeat Yourself): Extract patterns after the second duplication, not before
- Fail Fast: Explicit errors beat silent failures
- Simple First: Write the obvious solution, then optimize only if needed
- Delete Aggressively: Less code = fewer bugs
Reference: See worker-utils.ts critique (conversation 2025-11-05) for detailed examples.
Common Tasks
Adding a New Hook
- Create
src/hooks/new-hook.ts - Add to
scripts/build-hooks.jsbuild list - Add configuration to
plugin/hooks/hooks.json - Build and sync:
npm run build && npm run sync-marketplace
Modifying Database Schema
- Update schema in
src/services/sqlite/schema.ts - Update SessionStore/SessionSearch classes
- Migration strategy: The plugin currently recreates on schema changes (acceptable for alpha)
- TODO: Add proper migrations for production
Debugging Worker Issues
pm2 list # Check worker status
npm run worker:logs # View logs
npm run worker:restart # Restart if needed
pm2 delete claude-mem-worker # Force clean start
Testing Changes Locally
- Make changes in
src/ npm run build && npm run sync-marketplace- Start new Claude Code session (hooks) or restart worker (worker changes)
- Check
~/.claude-mem/claude-mem.dbfor database state - Use MCP search tools to verify behavior
Version Bumps
Use the version-bump skill:
/skill version-bump
Choose patch/minor/major. Updates package.json, marketplace.json, plugin.json, and CLAUDE.md.
Investigation Best Practices
When investigations are failing persistently, use Task agents for comprehensive file analysis instead of grep/search:
❌ Don't: Repeatedly grep and search for patterns when failing to find the issue
✅ Do: Deploy a Task agent to read files in full and answer specific questions
"Read these files in full and answer: [specific questions about the implementation]"
- Reduces token usage by delegating to a specialized agent
- Provides comprehensive analysis in one pass
- Finds issues that grep might miss due to poor query formulation
- More efficient than multiple rounds of searching
Example:
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
3. Answer: How do these files work together? What's the current implementation state?
4. Find any bugs or inconsistencies between them
Use this when:
- Investigating how multiple files interact
- Search queries aren't finding what you expect
- Need complete implementation context
- Issue might be a subtle inconsistency between files
Recent Changes (v5.0.3)
Key Fix: Smart caching installer for Windows compatibility
- Eliminated redundant npm install on every SessionStart (2-5s → 10ms)
- Caches version in
.install-versionfile - Only runs npm install when actually needed (first time, version change, missing deps)
Files Changed:
scripts/smart-install.js- New smart caching installerplugin/hooks/hooks.json- Use smart-install instead of raw npm installsrc/shared/worker-utils.ts- Health checks before worker operations
Why This Matters: Every SessionStart hook was running npm install, causing 2-5s delays even when nothing changed. Now it's ~10ms for cached installs (200x faster).
Configuration Users Can Set
Model Selection (~/.claude/settings.json):
{
"env": {
"CLAUDE_MEM_MODEL": "claude-haiku-4-5" // or sonnet-4-5, opus-4, etc.
}
}
Context Observation Count (~/.claude/settings.json):
{
"env": {
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "50" // default, adjust based on needs
}
}
Worker Port (~/.claude/settings.json):
{
"env": {
"CLAUDE_MEM_WORKER_PORT": "37777" // default
}
}
Key Design Decisions
Why PM2 Instead of Direct Process
Hooks have strict timeout limits. PM2 manages a persistent background worker, allowing AI processing to continue after hooks complete.
Why SQLite FTS5
Enables instant full-text search across thousands of observations without external dependencies. Automatic sync triggers keep FTS5 tables synchronized.
Why Graceful Cleanup (v4.1.0)
Changed from aggressive DELETE requests to marking sessions complete. Prevents interrupting summary generation and other async operations.
Why Smart Install Caching (v5.0.3)
npm install is expensive (2-5s). Caching version state and only installing on changes makes SessionStart nearly instant (10ms).
File Locations
Source: /Users/alexnewman/Scripts/claude-mem/src/
Built Plugin: /Users/alexnewman/Scripts/claude-mem/plugin/
Installed Plugin: ~/.claude/plugins/marketplaces/thedotmack/
Database: ~/.claude-mem/claude-mem.db
Usage Logs: ~/.claude-mem/usage-logs/usage-YYYY-MM-DD.jsonl
Quick Reference
Build: npm run build
Sync: npm run sync-marketplace
Worker Restart: npm run worker:restart
Worker Logs: npm run worker:logs
Version Bump: /skill version-bump
Usage Analysis: npm run usage:today