Files
claude-mem/CLAUDE.md
T

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

  1. SessionStartcontext-hook.ts runs

    • Smart installer checks dependencies (cached, only runs on version changes)
    • Starts PM2 worker if not healthy
    • Injects context from previous sessions (configurable observation count)
  2. UserPromptSubmitnew-hook.ts runs

    • Creates session record in SQLite
    • Saves raw user prompt for FTS5 search
  3. PostToolUsesave-hook.ts runs

    • Captures your tool executions
    • Sends to worker service for AI compression
  4. Summary → Summary hook generates session summaries

  5. SessionEndcleanup-hook.ts runs

    • Marks session complete (graceful, not DELETE)
    • Skips on /clear to preserve ongoing sessions

Key Components

Hooks (src/hooks/*.ts)

  • Built to plugin/scripts/*-hook.js (ESM format)
  • Must output valid JSON to hookSpecificOutput field
  • 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

  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)

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

  1. YAGNI (You Aren't Gonna Need It): Don't build it until you need it
  2. DRY (Don't Repeat Yourself): Extract patterns after the second duplication, not before
  3. Fail Fast: Explicit errors beat silent failures
  4. Simple First: Write the obvious solution, then optimize only if needed
  5. 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

  1. Create src/hooks/new-hook.ts
  2. Add to scripts/build-hooks.js build list
  3. Add configuration to plugin/hooks/hooks.json
  4. Build and sync: npm run build && npm run sync-marketplace

Modifying Database Schema

  1. Update schema in src/services/sqlite/schema.ts
  2. Update SessionStore/SessionSearch classes
  3. Migration strategy: The plugin currently recreates on schema changes (acceptable for alpha)
  4. 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

  1. Make changes in src/
  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

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-version file
  • Only runs npm install when actually needed (first time, version change, missing deps)

Files Changed:

  • scripts/smart-install.js - New smart caching installer
  • plugin/hooks/hooks.json - Use smart-install instead of raw npm install
  • src/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