docs/public/development.mdx
# Clone repository
git clone https://github.com/thedotmack/claude-mem.git
cd claude-mem
# Install dependencies
npm install
# Build all components
npm run build
The build process uses esbuild to compile TypeScript:
plugin/scripts/plugin/scripts/mcp-server.cjsplugin/scripts/worker-service.cjsplugin/ui/viewer.htmlBuild Output:
*-hook.js (ESM format)version-check.js (ESM format, sub-100ms)worker-service.cjs (CJS format)mcp-server.cjs (CJS format)viewer.html (self-contained HTML bundle)# Build everything
npm run build
# Build only hooks
npm run build:hooks
# The build script is defined in scripts/build-hooks.js
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
npm run build
# 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
# 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
Repeat steps 1-4 until your changes work as expected.
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)
# 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
src/ui/viewer/npm run buildnpm run sync-marketplacenpm run worker:restartHot Reload: Not currently supported. Full rebuild + restart required for changes.
Example: Adding a new card type
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">
</div>
);
};
Feed.tsx:import { YourCard } from './cards/YourCard';
// In render logic:
{item.type === 'your_type' && <YourCard {...item} />}
Update types if needed in src/ui/viewer/types.ts
Rebuild and test
Data Flow:
Key Patterns:
usePagination hook with Intersection ObserveruseSSE hook with auto-reconnectionmerge.ts utilities prevent duplicate itemsuseSettings hook with localStoragesrc/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.
plugin/hooks/hooks.json:{
"YourHook": [{
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js",
"timeout": 120
}]
}]
}
npm run build
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
}
};
src/services/sqlite/types.ts:export interface Observation {
// ... existing fields
new_field?: string;
}
src/services/sqlite/SessionStore.ts:createObservation(obs: Observation) {
// Include new_field in INSERT
}
# Backup database first!
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
# Run tests
npm test
src/sdk/prompts.ts:export function buildObservationPrompt(observation: Observation): string {
return `
<observation>
</observation>
`;
}
src/sdk/parser.ts:export function parseObservation(xml: string): ParsedObservation {
// Parse new XML fields
}
npm test
src/servers/mcp-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);
}
});
src/services/sqlite/SessionSearch.ts:yourNewSearch(params: YourParams): SearchResult[] {
// Implement FTS5 search
}
npm run build
npm test
Claude-mem relies on real-world usage and manual testing rather than traditional unit tests. The project philosophy prioritizes:
This approach was chosen because:
When developing new features:
Build and sync:
npm run build
npm run sync-marketplace
npm run worker:restart
Test in real session:
Check database state:
sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM your_table;"
Monitor worker logs:
npm run worker:logs
Verify queue health (for recovery features):
bun scripts/check-pending-queue.ts
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;"
Hook Testing:
# 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:
# 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;
"
For manual recovery features specifically:
Simulate stuck messages:
# 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;
"
Test recovery:
bun scripts/check-pending-queue.ts
Verify results:
curl http://localhost:37777/api/pending-queue | jq '.queue'
Before releasing:
Test all hook triggers:
Test core features:
Test edge cases:
Cross-platform (if applicable):
/**
* 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;
}
}
export DEBUG=claude-mem:*
npm run worker:restart
npm run worker:logs
sqlite3 ~/.claude-mem/claude-mem.db
# View schema
.schema observations
# Query data
SELECT * FROM observations LIMIT 10;
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;
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
# 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:
package.jsonCHANGELOG.md# 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
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# 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
Clean node_modules:
rm -rf node_modules
npm install
Check Node.js version:
node --version # Should be >= 18.0.0
Check for syntax errors:
npx tsc --noEmit
Check database:
rm ~/.claude-mem/claude-mem.db
npm test
Check worker status:
npm run worker:status
View logs:
npm run worker:logs
Kill existing process:
npm run worker:stop
Check port:
lsof -i :37777
Try custom port:
export CLAUDE_MEM_WORKER_PORT=38000
npm run worker:start