---
title: "Development"
description: "Build from source, run tests, and contribute to Claude-Mem"
---

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

### 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 the worker URL printed by `npm run worker:status`

**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">
      {/* Your UI */}
    </div>
  );
};
```

2. Import and use in `Feed.tsx`:

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

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

3. Update types if needed in `src/ui/viewer/types.ts`

4. 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.

2. Add to `plugin/hooks/hooks.json`:

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

4. 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
  }
};
```

2. Update types in `src/services/sqlite/types.ts`:

```typescript
export interface Observation {
  // ... existing fields
  new_field?: string;
}
```

3. Update database methods in `src/services/sqlite/SessionStore.ts`:

```typescript
createObservation(obs: Observation) {
  // Include new_field in INSERT
}
```

4. 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>
  `;
}
```

2. Update parser in `src/sdk/parser.ts`:

```typescript
export function parseObservation(xml: string): ParsedObservation {
  // Parse new XML fields
}
```

3. 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);
  }
});
```

2. Add search method in `src/services/sqlite/SessionSearch.ts`:

```typescript
yourNewSearch(params: YourParams): SearchResult[] {
  // Implement FTS5 search
}
```

3. 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
WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
curl http://127.0.0.1:$WORKER_PORT/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
   WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
   curl http://127.0.0.1:$WORKER_PORT/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 the worker URL)
   - 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

Production releases happen from `main` only. `core-dev` and `community-edge`
are source-run branches; see [Release Branches](branches).

### Version Bump

Update every manifest that carries the package version:

- `package.json`
- `plugin/package.json`
- `.claude-plugin/marketplace.json`
- `.claude-plugin/plugin.json`
- `plugin/.claude-plugin/plugin.json`
- `.codex-plugin/plugin.json`
- `plugin/.codex-plugin/plugin.json`
- `openclaw/openclaw.plugin.json`

Verify the old version is gone and the new version appears everywhere expected:

```bash
git grep -n '"version": "NEW_VERSION"'
git grep -n '"version": "OLD_VERSION"'
```

### Build, Tag, and Publish

```bash
npm run build-and-sync
git add -A
git commit -m "chore: bump version to NEW_VERSION"
git tag -a vNEW_VERSION -m "Version NEW_VERSION"
git push origin main
git push origin vNEW_VERSION
```

Publishing to npm requires the maintainer's npm credentials and 2FA:

```bash
npm publish
npm view claude-mem@NEW_VERSION version
```

Only after npm resolves the version, create the GitHub release and regenerate the
changelog:

```bash
gh release create vNEW_VERSION --title "vNEW_VERSION" --notes "..."
npm run changelog:generate
git add CHANGELOG.md
git commit -m "docs: regenerate changelog for vNEW_VERSION"
git push origin main
```

Do not hand-edit `CHANGELOG.md`; it is generated from GitHub releases.

## 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**: Do not hand-edit `CHANGELOG.md`; it is generated during release
- **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

### Recommended VSCode Extensions

- 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
   WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
   lsof -i :$WORKER_PORT
   ```

3. Try custom port:
   ```bash
   export CLAUDE_MEM_WORKER_PORT=38000
   npm run worker:start
   ```

## Next Steps

- [Architecture Overview](architecture/overview) - Understand the system
- [Configuration](configuration) - Customize Claude-Mem
- [Troubleshooting](troubleshooting) - Common issues
