Back to Claude Mem

Development

docs/public/development.mdx

13.10.3-community-edge.017.1 KB
Original Source

Development Guide

Building from Source

Prerequisites

  • Node.js 20.0.0 or higher
  • npm (comes with Node.js)
  • Git

Clone and Build

bash
# Clone repository
git clone https://github.com/thedotmack/claude-mem.git
cd claude-mem

# Install dependencies
npm install

# Build all components
npm run build

Build Process

The build process uses esbuild to compile TypeScript:

  1. Compiles TypeScript to JavaScript
  2. Creates standalone executables for each hook in plugin/scripts/
  3. Bundles MCP search server to plugin/scripts/mcp-server.cjs
  4. Bundles worker service to plugin/scripts/worker-service.cjs
  5. Bundles web viewer UI to plugin/ui/viewer.html

Build Output:

  • Hook executables: *-hook.js (ESM format)
  • Setup version-check: version-check.js (ESM format, sub-100ms)
  • Worker service: worker-service.cjs (CJS format)
  • MCP server: mcp-server.cjs (CJS format)
  • Viewer UI: viewer.html (self-contained HTML bundle)

Build Scripts

bash
# Build everything
npm run build

# Build only hooks
npm run build:hooks

# The build script is defined in scripts/build-hooks.js

Development Workflow

1. Make Changes

Edit TypeScript source files in src/:

src/
├── hooks/           # Hook implementations (entry points + logic)
├── services/        # Worker service and database
├── servers/         # MCP search server
├── sdk/             # Claude Agent SDK integration
├── shared/          # Shared utilities
├── ui/
│   └── viewer/      # React web viewer UI components
└── utils/           # General utilities

2. Build

bash
npm run build

3. Test

bash
# Run all tests
npm test

# Test specific file
node --test tests/session-lifecycle.test.ts

# Test context injection
npm run test:context

# Verbose context test
npm run test:context:verbose

4. Manual Testing

bash
# Start worker manually
npm run worker:start

# Check worker status
npm run worker:status

# View logs
npm run worker:logs

# Test hooks manually
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

Run the worker from a local build (without syncing to the marketplace)

CLAUDE_MEM_WORKER_SCRIPT_PATH pins the worker bundle the daemon spawns/respawns, so you can test a local build in isolation without overwriting your installed plugin:

bash
npm run build   # bundles src → plugin/scripts/worker-service.cjs
CLAUDE_MEM_WORKER_SCRIPT_PATH="$PWD/plugin/scripts/worker-service.cjs" \
  CLAUDE_MEM_DATA_DIR=/tmp/cm-dev \
  CLAUDE_MEM_WORKER_PORT=37999 \
  bun plugin/scripts/worker-service.cjs start

This supersedes the implicit cwd fallback (which only applies when the worker happens to be launched from the repo root and the marketplace path is absent). Unset the variable to return to the installed-plugin resolution.

<Note> If the pinned bundle's version differs from your **installed** plugin's version, the installed hooks may try to recycle the worker to match the plugin version and fight the pin (a restart loop) once Claude Code sessions start firing hooks against it. For a clean isolated run, either pin a bundle matching your installed plugin version, or drive the worker directly (as above) without an active session hammering it. Use a dedicated `CLAUDE_MEM_DATA_DIR` + `CLAUDE_MEM_WORKER_PORT` to keep it off your real install. </Note>

5. Iterate

Repeat steps 1-4 until your changes work as expected.

Viewer UI Development

Working with the React Viewer

The web viewer UI is a React application built into a self-contained HTML bundle.

Location: src/ui/viewer/

Structure:

src/ui/viewer/
├── index.tsx              # Entry point
├── App.tsx                # Main application component
├── components/            # React components
│   ├── Header.tsx         # Header with logo and actions
│   ├── Sidebar.tsx        # Project filter sidebar
│   ├── Feed.tsx           # Main feed with infinite scroll
│   ├── cards/             # Card components
│   │   ├── ObservationCard.tsx
│   │   ├── PromptCard.tsx
│   │   ├── SummaryCard.tsx
│   │   └── SkeletonCard.tsx
├── hooks/                 # Custom React hooks
│   ├── useSSE.ts          # Server-Sent Events connection
│   ├── usePagination.ts   # Infinite scroll pagination
│   ├── useSettings.ts     # Settings persistence
│   └── useStats.ts        # Database statistics
├── utils/                 # Utilities
│   ├── constants.ts       # Constants (API URLs, etc.)
│   ├── formatters.ts      # Date/time formatting
│   └── merge.ts           # Data merging and deduplication
└── assets/                # Static assets (fonts, logos)

Building Viewer UI

bash
# Build everything including viewer
npm run build

# The viewer is built to plugin/ui/viewer.html
# It's a self-contained HTML file with inlined JS and CSS

Testing Viewer Changes

  1. Make changes to React components in src/ui/viewer/
  2. Build: npm run build
  3. Sync to installed plugin: npm run sync-marketplace
  4. Restart worker: npm run worker:restart
  5. Refresh browser at http://localhost:37777

Hot Reload: Not currently supported. Full rebuild + restart required for changes.

Adding New Viewer Features

Example: Adding a new card type

  1. Create component in src/ui/viewer/components/cards/YourCard.tsx:
tsx
import React from 'react';

export interface YourCardProps {
  // Your data structure
}

export const YourCard: React.FC<YourCardProps> = ({ ... }) => {
  return (
    <div className="card">
    </div>
  );
};
  1. Import and use in Feed.tsx:
tsx
import { YourCard } from './cards/YourCard';

// In render logic:
{item.type === 'your_type' && <YourCard {...item} />}
  1. Update types if needed in src/ui/viewer/types.ts

  2. Rebuild and test

Viewer UI Architecture

Data Flow:

  1. Worker service exposes HTTP + SSE endpoints
  2. React app fetches initial data via HTTP (paginated)
  3. SSE connection provides real-time updates
  4. Custom hooks handle state management and data merging
  5. Components render cards based on item type

Key Patterns:

  • Infinite Scroll: usePagination hook with Intersection Observer
  • Real-Time Updates: useSSE hook with auto-reconnection
  • Deduplication: merge.ts utilities prevent duplicate items
  • Settings Persistence: useSettings hook with localStorage
  • Theme Support: CSS variables with light/dark/system themes

Adding New Features

Adding a New Hook

  1. Create hook implementation in src/hooks/your-hook.ts:
typescript
#!/usr/bin/env node
import { readStdin } from '../shared/stdin';

async function main() {
  const input = await readStdin();

  // Hook implementation
  const result = {
    hookSpecificOutput: 'Optional output'
  };

  console.log(JSON.stringify(result));
}

main().catch(console.error);

Note: As of v4.3.1, hooks are self-contained files. The shebang will be added automatically by esbuild during the build process.

  1. Add to plugin/hooks/hooks.json:
json
{
  "YourHook": [{
    "hooks": [{
      "type": "command",
      "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js",
      "timeout": 120
    }]
  }]
}
  1. Rebuild:
bash
npm run build

Modifying Database Schema

  1. Add migration to src/services/sqlite/migrations.ts:
typescript
export const migration011: Migration = {
  version: 11,
  up: (db: Database) => {
    db.run(`
      ALTER TABLE observations ADD COLUMN new_field TEXT;
    `);
  },
  down: (db: Database) => {
    // Optional: define rollback
  }
};
  1. Update types in src/services/sqlite/types.ts:
typescript
export interface Observation {
  // ... existing fields
  new_field?: string;
}
  1. Update database methods in src/services/sqlite/SessionStore.ts:
typescript
createObservation(obs: Observation) {
  // Include new_field in INSERT
}
  1. Test migration:
bash
# Backup database first!
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup

# Run tests
npm test

Extending SDK Prompts

  1. Modify prompts in src/sdk/prompts.ts:
typescript
export function buildObservationPrompt(observation: Observation): string {
  return `
    <observation>
      
    </observation>
  `;
}
  1. Update parser in src/sdk/parser.ts:
typescript
export function parseObservation(xml: string): ParsedObservation {
  // Parse new XML fields
}
  1. Test:
bash
npm test

Adding MCP Search Tools

  1. Add tool definition in src/servers/mcp-server.ts:
typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'your_new_tool') {
    // Implement tool logic
    const results = await search.yourNewSearch(params);
    return formatResults(results);
  }
});
  1. Add search method in src/services/sqlite/SessionSearch.ts:
typescript
yourNewSearch(params: YourParams): SearchResult[] {
  // Implement FTS5 search
}
  1. Rebuild and test:
bash
npm run build
npm test

Testing

Testing Philosophy

Claude-mem relies on real-world usage and manual testing rather than traditional unit tests. The project philosophy prioritizes:

  1. Manual verification - Testing features in actual Claude Code sessions
  2. Integration testing - Running the full system end-to-end
  3. Database inspection - Verifying data correctness via SQLite queries
  4. CLI tools - Interactive tools for checking system state
  5. Observability - Comprehensive logging and worker health checks

This approach was chosen because:

  • Hook behavior depends heavily on Claude Code's runtime environment
  • SDK interactions require real API calls and responses
  • SQLite and Bun runtime provide stability guarantees
  • Manual testing catches integration issues that unit tests miss

Manual Testing Workflow

When developing new features:

  1. Build and sync:

    bash
    npm run build
    npm run sync-marketplace
    npm run worker:restart
    
  2. Test in real session:

    • Start Claude Code
    • Trigger the feature you're testing
    • Verify expected behavior
  3. Check database state:

    bash
    sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM your_table;"
    
  4. Monitor worker logs:

    bash
    npm run worker:logs
    
  5. Verify queue health (for recovery features):

    bash
    bun scripts/check-pending-queue.ts
    

Testing Tools

Health Checks:

bash
# Worker status
npm run worker:status

# Queue inspection
curl http://localhost:37777/api/pending-queue

# Database integrity
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"

Hook Testing:

bash
# Test context hook manually
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

# Test new hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","prompt":"test"}' | node plugin/scripts/new-hook.js

Data Verification:

bash
# Check recent observations
sqlite3 ~/.claude-mem/claude-mem.db "
  SELECT id, tool_name, created_at
  FROM observations
  ORDER BY created_at_epoch DESC
  LIMIT 10;
"

# Check summaries
sqlite3 ~/.claude-mem/claude-mem.db "
  SELECT id, request, completed
  FROM session_summaries
  ORDER BY created_at_epoch DESC
  LIMIT 5;
"

Recovery Feature Testing

For manual recovery features specifically:

  1. Simulate stuck messages:

    bash
    # Manually create stuck message (for testing only)
    sqlite3 ~/.claude-mem/claude-mem.db "
      UPDATE pending_messages
      SET status = 'processing',
          started_processing_at_epoch = strftime('%s', 'now', '-10 minutes') * 1000
      WHERE id = 123;
    "
    
  2. Test recovery:

    bash
    bun scripts/check-pending-queue.ts
    
  3. Verify results:

    bash
    curl http://localhost:37777/api/pending-queue | jq '.queue'
    

Regression Testing

Before releasing:

  1. Test all hook triggers:

    • SessionStart: Start new Claude Code session
    • UserPromptSubmit: Submit a prompt
    • PostToolUse: Use a tool like Read
    • Summary: Let session complete
    • SessionEnd: Close Claude Code
  2. Test core features:

    • Context injection (recent sessions appear)
    • Observation processing (summaries generated)
    • MCP search tools (search returns results)
    • Viewer UI (loads at http://localhost:37777)
    • Manual recovery (stuck messages recovered)
  3. Test edge cases:

    • Worker crash recovery
    • Database locks
    • Port conflicts
    • Large databases
  4. Cross-platform (if applicable):

    • macOS
    • Linux
    • Windows

Code Style

TypeScript Guidelines

  • Use TypeScript strict mode
  • Define interfaces for all data structures
  • Use async/await for asynchronous code
  • Handle errors explicitly
  • Add JSDoc comments for public APIs

Formatting

  • Follow existing code formatting
  • Use 2-space indentation
  • Use single quotes for strings
  • Add trailing commas in objects/arrays

Example

typescript
/**
 * Create a new observation in the database
 */
export async function createObservation(
  obs: Observation
): Promise<number> {
  try {
    const result = await db.insert('observations', {
      session_id: obs.session_id,
      tool_name: obs.tool_name,
      // ...
    });
    return result.id;
  } catch (error) {
    logger.error('Failed to create observation', error);
    throw error;
  }
}

Debugging

Enable Debug Logging

bash
export DEBUG=claude-mem:*
npm run worker:restart
npm run worker:logs

Inspect Database

bash
sqlite3 ~/.claude-mem/claude-mem.db

# View schema
.schema observations

# Query data
SELECT * FROM observations LIMIT 10;

Trace Observations

Use correlation IDs to trace observations through the pipeline:

bash
sqlite3 ~/.claude-mem/claude-mem.db
SELECT correlation_id, tool_name, created_at
FROM observations
WHERE session_id = 'YOUR_SESSION_ID'
ORDER BY created_at;

Debug Hooks

Run hooks manually with test input:

bash
# Test context hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

# Test new hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","prompt":"test"}' | node plugin/scripts/new-hook.js

Publishing

NPM Publishing

bash
# Update version in package.json
npm version patch  # or minor, or major

# Build
npm run build

# Publish to NPM
npm run release

The release script:

  1. Runs tests
  2. Builds all components
  3. Publishes to NPM registry

Creating a Release

  1. Update version in package.json
  2. Update CHANGELOG.md
  3. Commit changes
  4. Create git tag
  5. Push to GitHub
  6. Publish to NPM
bash
# Manual version bump:
# 1. Update version in package.json
# 2. Update version in plugin/.claude-plugin/plugin.json
# 3. Update version at top of CLAUDE.md
# 4. Update version badge in README.md
# 5. Run: npm run build && npm run sync-marketplace

# Or use npm version command:
npm version 4.3.2

# Update changelog
# Edit CHANGELOG.md manually

# Commit
git add .
git commit -m "chore: Release v4.3.2"

# Tag
git tag v4.3.2

# Push
git push origin main --tags

# Publish to NPM
npm run release

Contributing

Contribution Workflow

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Write tests
  5. Update documentation
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Pull Request Guidelines

  • Clear title: Describe what the PR does
  • Description: Explain why the change is needed
  • Tests: Include tests for new features
  • Documentation: Update docs as needed
  • Changelog: Add entry to CHANGELOG.md
  • Commits: Use clear, descriptive commit messages

Code Review Process

  1. Automated tests must pass
  2. Code review by maintainer
  3. Address feedback
  4. Final approval
  5. Merge to main

Development Tools

  • TypeScript
  • ESLint
  • Prettier
  • SQLite Viewer

Useful Commands

bash
# Check TypeScript types
npx tsc --noEmit

# Lint code (if configured)
npm run lint

# Format code (if configured)
npm run format

# Clean build artifacts
rm -rf plugin/scripts/*.js plugin/scripts/*.cjs

Troubleshooting Development

Build Fails

  1. Clean node_modules:

    bash
    rm -rf node_modules
    npm install
    
  2. Check Node.js version:

    bash
    node --version  # Should be >= 20.0.0
    
  3. Check for syntax errors:

    bash
    npx tsc --noEmit
    

Tests Fail

  1. Check database:

    bash
    rm ~/.claude-mem/claude-mem.db
    npm test
    
  2. Check worker status:

    bash
    npm run worker:status
    
  3. View logs:

    bash
    npm run worker:logs
    

Worker Won't Start

  1. Kill existing process:

    bash
    npm run worker:stop
    
  2. Check port:

    bash
    lsof -i :37777
    
  3. Try custom port:

    bash
    export CLAUDE_MEM_WORKER_PORT=38000
    npm run worker:start
    

Next Steps