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
Clone the repository
git clone https://github.com/thedotmack/claude-mem.git
cd claude-mem
What the build produces
The build uses esbuild to compile TypeScript into several output formats:
Output Format Location Hook scripts (*-hook.js, smart-install.js) ESM plugin/scripts/Worker service CJS plugin/scripts/worker-service.cjsMCP search server CJS plugin/scripts/mcp-server.cjsWeb viewer UI Self-contained HTML plugin/ui/viewer.html
Build commands
Build everything
Build and sync to installed plugin
Build only hooks
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
Make changes
Edit TypeScript source files in src/.
Build and sync
This builds, copies to the installed plugin, and restarts the worker.
Test manually
Start a Claude Code session to test the feature end-to-end, or run hooks directly (see Manual hook testing below).
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
Run all tests
SQLite and database tests
Agent processing tests
Search tests
Context injection tests
Infrastructure tests
Server tests
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:
Build and sync changes
Test in a real Claude Code session
Inspect the database to verify data correctness
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.
Start
Stop
Restart
Check status
View logs
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
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 >
);
};
Register in Feed.tsx
import { YourCard } from './cards/YourCard' ;
// In render logic:
{ item . type === 'your_type' && < YourCard { ... item } /> }
Update types if needed
Add your data shape to src/ui/viewer/types.ts.
Adding new features
Adding a new hook
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.
Register in hooks.json
{
"YourHook" : [{
"hooks" : [{
"type" : "command" ,
"command" : "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js" ,
"timeout" : 120
}]
}]
}
Modifying the database schema
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;
` );
}
};
Update types
// src/services/sqlite/types.ts
export interface Observation {
// ... existing fields
new_field ?: string ;
}
Update database methods
// src/services/sqlite/SessionStore.ts
createObservation ( obs : Observation ) {
// include new_field in INSERT
}
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
Example: well-typed function
/**
* 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:
This collects worker status, recent logs, database statistics, and environment information to include when filing a GitHub issue.
Contributing
Workflow
Fork and branch
git checkout -b feature/your-feature-name
Make changes
Edit source files in src/. Follow the code style guidelines above.
Test
Run npm test and verify your feature manually in a Claude Code session.
Update documentation
Update relevant MDX files in docs/public/ if your change affects user-facing behavior.
Commit and push
git commit -m 'feat: add your feature'
git push origin feature/your-feature-name
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
Bump the version
Update the version in:
package.json
plugin/.claude-plugin/plugin.json
CLAUDE.md header
README.md version badge
Build and sync
npm run build && npm run sync-marketplace
Commit and tag
git add .
git commit -m "chore: release v4.3.2"
git tag v4.3.2
git push origin main --tags
Publish to npm
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.