Skip to main content

Prerequisites

Node.js ≥ 18

Required to build and run the project. Check with node --version.

Bun ≥ 1.0

Manages the worker service and runs tests. Auto-installed if missing.

Git

Required to clone and contribute to the repository.

Building from source

1

Clone the repository

git clone https://github.com/thedotmack/claude-mem.git
cd claude-mem
2

Install dependencies

npm install
3

Build all components

npm run build

What the build produces

The build uses esbuild to compile TypeScript into several output formats:
OutputFormatLocation
Hook scripts (*-hook.js, smart-install.js)ESMplugin/scripts/
Worker serviceCJSplugin/scripts/worker-service.cjs
MCP search serverCJSplugin/scripts/mcp-server.cjs
Web viewer UISelf-contained HTMLplugin/ui/viewer.html

Build commands

npm run build
build-and-sync is the standard development command — it builds, syncs to the marketplace install location, and restarts the worker in one step.

Source layout

src/
├── hooks/           # Hook entry points and logic
├── services/        # Worker service, database, and sync
│   └── sqlite/      # SQLite schema, migrations, search
├── servers/         # MCP search server
├── sdk/             # Claude Agent SDK integration
├── shared/          # Shared utilities
├── ui/
│   └── viewer/      # React web viewer components
└── utils/           # General utilities

Development workflow

1

Make changes

Edit TypeScript source files in src/.
2

Build and sync

npm run build-and-sync
This builds, copies to the installed plugin, and restarts the worker.
3

Test manually

Start a Claude Code session to test the feature end-to-end, or run hooks directly (see Manual hook testing below).
4

Check logs

npm run worker:logs
5

Iterate

Repeat until the feature works as expected.

Running tests

Claude Mem uses Bun’s built-in test runner. Tests are organized by component.

Test commands

npm test

Testing philosophy

Claude Mem prioritizes manual and integration testing over traditional unit tests, because:
  • Hook behavior depends on Claude Code’s runtime environment
  • SDK interactions require real API calls
  • Integration issues that unit tests miss are common at system boundaries
The preferred testing approach is:
  1. Build and sync changes
  2. Test in a real Claude Code session
  3. Inspect the database to verify data correctness
  4. Monitor worker logs

Manual hook testing

Run hooks directly with test input to verify behavior without starting a full Claude Code session:
# Test context hook (SessionStart)
node plugin/scripts/bun-runner.js plugin/scripts/worker-service.cjs hook claude-code context

# Test session-init hook (UserPromptSubmit)
node plugin/scripts/bun-runner.js plugin/scripts/worker-service.cjs hook claude-code session-init

# Test observation hook (PostToolUse)
node plugin/scripts/bun-runner.js plugin/scripts/worker-service.cjs hook claude-code observation

Health checks

# 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;"

Data verification

# 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;
"

Worker management commands

The worker service runs as a background Bun process on port 37777.
npm run worker:start

Queue management

# Check pending queue status
npm run queue

# Process pending queue automatically
npm run queue:process

# Clear failed queue entries
npm run queue:clear

Viewer UI development

The web viewer is a React application built into a self-contained viewer.html bundle. Source location: src/ui/viewer/
src/ui/viewer/
├── index.tsx              # Entry point
├── App.tsx                # Root component
├── components/
│   ├── Header.tsx
│   ├── Sidebar.tsx
│   ├── Feed.tsx
│   └── cards/
│       ├── ObservationCard.tsx
│       ├── PromptCard.tsx
│       ├── SummaryCard.tsx
│       └── SkeletonCard.tsx
├── hooks/
│   ├── useSSE.ts          # Server-Sent Events
│   ├── usePagination.ts   # Infinite scroll
│   ├── useSettings.ts     # Settings persistence
│   └── useStats.ts        # Database statistics
└── utils/
    ├── constants.ts
    ├── formatters.ts
    └── merge.ts
Hot reload is not supported. Every change requires a full rebuild and worker restart before you can see updates in the browser.
Viewer update workflow:
npm run build
npm run sync-marketplace
npm run worker:restart
# Refresh browser at http://localhost:37777

Adding a new card type

1

Create the component

// src/ui/viewer/components/cards/YourCard.tsx
import React from 'react';

export interface YourCardProps {
  // define your data structure
}

export const YourCard: React.FC<YourCardProps> = (props) => {
  return (
    <div className="card">
      {/* your UI */}
    </div>
  );
};
2

Register in Feed.tsx

import { YourCard } from './cards/YourCard';

// In render logic:
{item.type === 'your_type' && <YourCard {...item} />}
3

Update types if needed

Add your data shape to src/ui/viewer/types.ts.
4

Rebuild and test

npm run build-and-sync

Adding new features

Adding a new hook

1

Create the hook implementation

// src/hooks/your-hook.ts
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);
As of v4.3.1, hooks are self-contained files. The shebang line is added automatically by esbuild during the build.
2

Register in hooks.json

{
  "YourHook": [{
    "hooks": [{
      "type": "command",
      "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js",
      "timeout": 120
    }]
  }]
}
3

Rebuild

npm run build

Modifying the database schema

1

Add a migration

// src/services/sqlite/migrations.ts
export const migration011: Migration = {
  version: 11,
  up: (db: Database) => {
    db.run(`
      ALTER TABLE observations ADD COLUMN new_field TEXT;
    `);
  }
};
2

Update types

// src/services/sqlite/types.ts
export interface Observation {
  // ... existing fields
  new_field?: string;
}
3

Update database methods

// src/services/sqlite/SessionStore.ts
createObservation(obs: Observation) {
  // include new_field in INSERT
}
4

Test with a database backup

cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
npm test

Debugging

Enable debug logging

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

Inspect the database

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

# View schema
.schema observations

# Recent activity
SELECT created_at, tool_name FROM observations ORDER BY created_at DESC LIMIT 10;

# Table counts
SELECT 'sessions', COUNT(*) FROM sdk_sessions
UNION ALL SELECT 'observations', COUNT(*) FROM observations
UNION ALL SELECT 'summaries', COUNT(*) FROM session_summaries;

Trace observations by session

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;
"

Code style

  • Use strict mode
  • Define interfaces for all data structures
  • Use async/await for asynchronous code
  • Handle errors explicitly with try/catch
  • Add JSDoc comments for public APIs
  • 2-space indentation
  • Single quotes for strings
  • Trailing commas in objects and arrays
  • Follow the existing code style in the file you’re editing
/**
 * 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;
  }
}

Bug reports

Generate a bug report with system diagnostics:
npm run bug-report
This collects worker status, recent logs, database statistics, and environment information to include when filing a GitHub issue.

Contributing

Workflow

1

Fork and branch

git checkout -b feature/your-feature-name
2

Make changes

Edit source files in src/. Follow the code style guidelines above.
3

Test

Run npm test and verify your feature manually in a Claude Code session.
4

Update documentation

Update relevant MDX files in docs/public/ if your change affects user-facing behavior.
5

Commit and push

git commit -m 'feat: add your feature'
git push origin feature/your-feature-name
6

Open a pull request

Open a PR against main. Include a clear description of what changed and why.

Pull request checklist

  • Clear title describing what the PR does
  • Description explaining why the change is needed
  • Tests for new functionality
  • Documentation updated where applicable
  • Entry added to CHANGELOG.md
  • TypeScript compiles without errors (npx tsc --noEmit)

Releasing

1

Bump the version

Update the version in:
  • package.json
  • plugin/.claude-plugin/plugin.json
  • CLAUDE.md header
  • README.md version badge
2

Build and sync

npm run build && npm run sync-marketplace
3

Commit and tag

git add .
git commit -m "chore: release v4.3.2"
git tag v4.3.2
git push origin main --tags
4

Publish to npm

npm run release
The release script runs tests, builds all components, and publishes to the npm registry.
The changelog is generated automatically — do not edit CHANGELOG.md manually.

Build docs developers (and LLMs) love