Skip to main content

Development Guide

Building from Source

Prerequisites

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

Clone and Build

# 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/search-server.mjs
  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)
  • Smart installer: smart-install.js (ESM format)
  • Worker service: worker-service.cjs (CJS format)
  • Search server: search-server.mjs (ESM format)
  • Viewer UI: viewer.html (self-contained HTML bundle)

Build Scripts

# 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

npm run build

3. Test

# 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

# 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

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

# 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:
import React from 'react';

export interface YourCardProps {
  // Your data structure
}

export const YourCard: React.FC<YourCardProps> = ({ ... }) => {
  return (
    <div className="card">
      {/* Your UI */}
    </div>
  );
};
  1. Import and use in Feed.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:
#!/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:
{
  "YourHook": [{
    "hooks": [{
      "type": "command",
      "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js",
      "timeout": 120
    }]
  }]
}
  1. Rebuild:
npm run build

Modifying Database Schema

  1. Add migration to src/services/sqlite/migrations.ts:
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:
export interface Observation {
  // ... existing fields
  new_field?: string;
}
  1. Update database methods in src/services/sqlite/SessionStore.ts:
createObservation(obs: Observation) {
  // Include new_field in INSERT
}
  1. Test migration:
# 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:
export function buildObservationPrompt(observation: Observation): string {
  return `
    <observation>
      <!-- Add new XML structure -->
    </observation>
  `;
}
  1. Update parser in src/sdk/parser.ts:
export function parseObservation(xml: string): ParsedObservation {
  // Parse new XML fields
}
  1. Test:
npm test

Adding MCP Search Tools

  1. Add tool definition in src/servers/search-server.ts:
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:
yourNewSearch(params: YourParams): SearchResult[] {
  // Implement FTS5 search
}
  1. Rebuild and test:
npm run build
npm test

Testing

Running Tests

# All tests
npm test

# Specific test file
node --test tests/your-test.test.ts

# With coverage (if configured)
npm test -- --coverage

Writing Tests

Create test files in tests/:
import { describe, it } from 'node:test';
import assert from 'node:assert';

describe('YourFeature', () => {
  it('should do something', () => {
    // Test implementation
    assert.strictEqual(result, expected);
  });
});

Test Database

Use a separate test database:
import { SessionStore } from '../src/services/sqlite/SessionStore';

const store = new SessionStore(':memory:'); // In-memory database

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

/**
 * 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

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

Inspect Database

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:
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:
# 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

# 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
# Use the version bump skill (recommended as of v4.3.0)
# In Claude Code, run: /skill version-bump
# This updates package.json, marketplace.json, and CLAUDE.md

# Or manually:
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

# 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:
    rm -rf node_modules
    npm install
    
  2. Check Node.js version:
    node --version  # Should be >= 18.0.0
    
  3. Check for syntax errors:
    npx tsc --noEmit
    

Tests Fail

  1. Check database:
    rm ~/.claude-mem/claude-mem.db
    npm test
    
  2. Check worker status:
    npm run worker:status
    
  3. View logs:
    npm run worker:logs
    

Worker Won’t Start

  1. Kill existing process:
    pm2 delete claude-mem-worker
    
  2. Check port:
    lsof -i :37777
    
  3. Try custom port:
    export CLAUDE_MEM_WORKER_PORT=38000
    npm run worker:start
    

Next Steps