Add standalone hook entry points for context, new, save, summary, and worker

- Implemented context-hook.ts for handling session start events.
- Created new-hook.ts for user prompt submission events.
- Developed save-hook.ts for post tool use events.
- Added summary-hook.ts for handling stop events.
- Introduced worker.ts as a standalone background process for the SDK agent.
- Each hook reads input from stdin, processes it, and handles errors gracefully.
This commit is contained in:
Alex Newman
2025-10-16 15:39:30 -04:00
parent 723f1f5374
commit 834cf4095e
20 changed files with 1563 additions and 63 deletions
+308
View File
@@ -0,0 +1,308 @@
# Plugin Development Guide
This guide helps developers work with the claude-mem plugin structure during development.
## Quick Start
### 1. Build the Plugin
```bash
# Build both CLI and hooks
npm run build
# Or build separately
npm run build:cli # Just the CLI
npm run build:hooks # Just the hooks
```
### 2. Test Hooks Locally
Test individual hooks by piping JSON input:
```bash
# Test context hook (SessionStart)
printf '{"session_id":"test-123","cwd":"/Users/you/project","source":"startup"}' | \
bun scripts/hooks/context-hook.js
# Test new hook (UserPromptSubmit)
printf '{"session_id":"test-123","cwd":"/Users/you/project","prompt":"help me code"}' | \
bun scripts/hooks/new-hook.js
# Test save hook (PostToolUse)
printf '{"session_id":"test-123","cwd":"/Users/you/project","tool_name":"Read","tool_input":{},"tool_output":{}}' | \
bun scripts/hooks/save-hook.js
# Test summary hook (Stop)
printf '{"session_id":"test-123","cwd":"/Users/you/project"}' | \
bun scripts/hooks/summary-hook.js
# Test worker (requires valid session ID in database)
bun scripts/hooks/worker.js 999
```
### 3. Test Worker with Plugin Root
Verify the new-hook correctly detects plugin mode:
```bash
# Without CLAUDE_PLUGIN_ROOT (traditional mode)
printf '{"session_id":"test-new","cwd":"/path","prompt":"test"}' | \
bun scripts/hooks/new-hook.js
# With CLAUDE_PLUGIN_ROOT (plugin mode)
CLAUDE_PLUGIN_ROOT=$(pwd) printf '{"session_id":"test-plugin","cwd":"/path","prompt":"test"}' | \
bun scripts/hooks/new-hook.js
```
In plugin mode, the new-hook will attempt to spawn `bun ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/worker.js`.
In traditional mode, it will attempt to spawn `claude-mem worker`.
### 4. Test With No Input
Each hook should handle missing input gracefully:
```bash
echo '' | bun scripts/hooks/context-hook.js
# Output: No input provided - this script is designed to run as a Claude Code SessionStart hook
```
## Local Plugin Testing
### Option 1: Dev Marketplace (Recommended)
Create a development marketplace to test your plugin:
```bash
# Create marketplace structure
mkdir -p ~/dev-marketplace/.claude-plugin
# Create marketplace manifest
cat > ~/dev-marketplace/.claude-plugin/marketplace.json << 'EOF'
{
"name": "dev-marketplace",
"owner": {
"name": "Developer"
},
"plugins": [
{
"name": "claude-mem",
"source": "./claude-mem-plugin",
"description": "Persistent memory system for Claude Code"
}
]
}
EOF
# Symlink your working directory
ln -s /path/to/your/claude-mem ~/dev-marketplace/claude-mem-plugin
```
Then in Claude Code:
```
/plugin marketplace add /absolute/path/to/dev-marketplace
/plugin install claude-mem@dev-marketplace
```
### Option 2: Direct Testing
Test the CLI commands directly:
```bash
# Build first
npm run build
# Test CLI commands
./dist/claude-mem.min.js --version
./dist/claude-mem.min.js status
./dist/claude-mem.min.js --help
```
## Development Workflow
### Making Changes to Hooks
1. **Edit TypeScript source** in `src/hooks/` or `src/bin/hooks/`
2. **Rebuild hooks**: `npm run build:hooks`
3. **Test locally**: Use echo piping method above
4. **Reinstall plugin** (if testing in Claude Code):
```
/plugin uninstall claude-mem@dev-marketplace
/plugin install claude-mem@dev-marketplace
```
### Making Changes to CLI
1. **Edit TypeScript source** in `src/`
2. **Rebuild CLI**: `npm run build:cli`
3. **Test directly**: `./dist/claude-mem.min.js [command]`
### Making Changes to Commands
1. **Edit markdown files** in `commands/`
2. **No rebuild needed** (commands are read directly)
3. **Reinstall plugin** to pick up changes:
```
/plugin uninstall claude-mem@dev-marketplace
/plugin install claude-mem@dev-marketplace
```
## Debugging
### Enable Verbose Logging
Set environment variables for more detailed output:
```bash
DEBUG=claude-mem:* bun scripts/hooks/context-hook.js
```
### Check Hook Output
Hooks write to stdout/stderr. Capture output:
```bash
echo '{"session_id":"test","cwd":"/path"}' | \
bun scripts/hooks/context-hook.js 2>&1 | tee hook-output.log
```
### Verify Plugin Root Variable
Test that `${CLAUDE_PLUGIN_ROOT}` resolves correctly:
```bash
# Manually set it for testing
export CLAUDE_PLUGIN_ROOT=/path/to/your/plugin
echo '{"session_id":"test","cwd":"/path"}' | \
bun ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/context-hook.js
```
## Build System Details
### Hook Build Process
The `scripts/build-hooks.js` script:
1. Reads each entry point from `src/bin/hooks/`
2. Bundles with Bun build system
3. Minifies output
4. Adds shebang for direct execution
5. Sets executable permissions
6. Outputs to `scripts/hooks/`
### CLI Build Process
The `scripts/build.js` script:
1. Bundles main CLI from `src/bin/cli.ts`
2. Externalizes large dependencies
3. Minifies output
4. Adds shebang
5. Sets executable permissions
6. Outputs to `dist/claude-mem.min.js`
### Build Configuration
Both builds use similar Bun configuration:
- **Target**: `bun` runtime
- **Minify**: `true`
- **External**: `bun:sqlite` (native module)
- **Define**: `__DEFAULT_PACKAGE_VERSION__` from package.json
## Testing
### Run Tests
```bash
bun test tests/
```
### Test Database Operations
```bash
# Test hooks database
bun test tests/hooks-database-integration.test.ts
# Test session lifecycle
bun test tests/session-lifecycle.test.ts
```
## Publishing
### Pre-publish Checklist
- [ ] All tests pass: `bun test tests/`
- [ ] Build succeeds: `npm run build`
- [ ] Version updated in `package.json`
- [ ] Changelog updated in `docs/CHANGELOG.md`
- [ ] Plugin.json version matches package.json
- [ ] Hooks tested locally
- [ ] CLI tested locally
### Publish to npm
```bash
npm run publish:npm
```
This will:
1. Run `prepublishOnly` script (builds everything)
2. Publish to npm registry
3. Include files listed in `package.json` "files" array
### Files Included in Package
The npm package includes:
- `dist/` - Compiled CLI
- `scripts/` - Compiled hooks
- `commands/` - Slash command definitions
- `hooks/` - Hook configuration
- `.claude-plugin/` - Plugin metadata
- `src/` - TypeScript source (for reference)
- `docs/` - Documentation
- `.mcp.json` - MCP server configuration
## Troubleshooting
### Build Fails
**Problem**: `bun: command not found`
**Solution**: Install Bun from https://bun.sh
**Problem**: Build errors with external dependencies
**Solution**: Check that `bun:sqlite` is not bundled (should be external)
### Hooks Don't Execute
**Problem**: `Permission denied` when executing hooks
**Solution**: Ensure scripts are executable: `chmod +x scripts/hooks/*.js`
**Problem**: Hooks exit silently
**Solution**: Check error handling - hooks catch all errors and exit gracefully
### Plugin Not Found
**Problem**: `/plugin install` can't find claude-mem
**Solution**:
1. Verify marketplace is added: `/plugin marketplace list`
2. Check marketplace manifest includes claude-mem
3. Refresh marketplace: `/plugin marketplace refresh`
## Tips
1. **Use symlinks** in dev marketplace for faster iteration
2. **Test hooks with edge cases** (empty input, malformed JSON)
3. **Check file sizes** after build to catch bloat
4. **Version everything together** (CLI, hooks, plugin.json)
5. **Document breaking changes** in CHANGELOG.md
## Resources
- [Plugin Structure Documentation](./PLUGIN_STRUCTURE.md)
- [Plugin Installation Guide](./PLUGIN_INSTALLATION.md)
- [Build Documentation](./BUILD.md)
- [Claude Code Plugins Docs](https://docs.claude.com/en/docs/claude-code/plugins)
- [Bun Documentation](https://bun.sh/docs)
## Getting Help
- **Issues**: https://github.com/thedotmack/claude-mem/issues
- **Discussions**: https://github.com/thedotmack/claude-mem/discussions
+242
View File
@@ -0,0 +1,242 @@
# Claude-mem Plugin Structure
This document describes the complete plugin structure for claude-mem, which enables self-contained installation via Claude Code's plugin system.
## Directory Structure
```
claude-mem/
├── .claude-plugin/
│ └── plugin.json # Plugin metadata
├── commands/
│ ├── claude-mem.md # /claude-mem slash command
│ ├── remember.md # /remember slash command
│ └── save.md # /save slash command
├── hooks/
│ └── hooks.json # Hook definitions using ${CLAUDE_PLUGIN_ROOT}
├── scripts/
│ ├── build-hooks.js # Build script for compiling hooks
│ └── hooks/ # Compiled hook executables
│ ├── context-hook.js # SessionStart hook (4KB)
│ ├── new-hook.js # UserPromptSubmit hook (4KB)
│ ├── save-hook.js # PostToolUse hook (4KB)
│ ├── summary-hook.js # Stop hook (4KB)
│ └── worker.js # Background SDK worker (232KB)
├── src/
│ ├── bin/
│ │ ├── cli.ts # Main CLI entry point
│ │ └── hooks/ # Hook entry point sources
│ │ ├── context-hook.ts # SessionStart entry point
│ │ ├── new-hook.ts # UserPromptSubmit entry point
│ │ ├── save-hook.ts # PostToolUse entry point
│ │ └── summary-hook.ts # Stop entry point
│ ├── hooks/ # Hook implementation functions
│ │ ├── context.ts
│ │ ├── new.ts
│ │ ├── save.ts
│ │ └── summary.ts
│ └── ... # Other source files
└── dist/
└── claude-mem.min.js # Bundled CLI executable
```
## How It Works
### 1. Plugin Installation
When users install the plugin via `/plugin install claude-mem`, Claude Code:
1. Downloads the plugin from the marketplace
2. Installs it to the local plugins directory
3. Registers the hooks from `hooks/hooks.json`
4. Makes slash commands from `commands/` directory available
### 2. Self-Contained Execution
The hooks are compiled as standalone executables that:
- **Don't require global CLI installation**: All dependencies are bundled
- **Use plugin-relative paths**: `${CLAUDE_PLUGIN_ROOT}` resolves to plugin directory
- **Work with Bun runtime**: Scripts are compiled for Bun and include shebang
### 3. Hook Configuration
The `hooks/hooks.json` file uses `${CLAUDE_PLUGIN_ROOT}` to reference bundled scripts:
```json
{
"description": "Claude-mem memory system hooks",
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/context-hook.js",
"timeout": 180000
}]
}],
...
}
}
```
### 4. Hook Entry Points
Each hook has a standalone entry point in `src/bin/hooks/` that:
- Reads JSON input from stdin
- Calls the hook implementation function
- Handles errors gracefully
- Exits with appropriate status codes
Example from `context-hook.ts`:
```typescript
#!/usr/bin/env bun
import { contextHook } from '../../hooks/context.js';
const input = await Bun.stdin.text();
const parsed = input.trim() ? JSON.parse(input) : undefined;
contextHook(parsed);
```
### 5. Build Process
The build system compiles both the CLI and hook scripts:
```bash
npm run build # Build both CLI and hooks
npm run build:cli # Build only the CLI
npm run build:hooks # Build only the hooks
```
The hook build process:
1. Compiles each hook entry point with Bun
2. Bundles all dependencies (except bun:sqlite)
3. Minifies the output
4. Adds shebang (`#!/usr/bin/env bun`)
5. Makes executable (`chmod +x`)
6. Outputs to `scripts/hooks/`
## Benefits
### ✅ Self-Contained
- No global CLI installation required
- All dependencies bundled with plugin
- Plugin directory has everything needed
### ✅ Easy Installation
- Single command: `/plugin install claude-mem`
- Hooks automatically registered
- Slash commands immediately available
### ✅ Version Control
- Plugin version tied to specific hook versions
- No version mismatch between CLI and hooks
- Easy updates via `/plugin update`
### ✅ Development Friendly
- Source code in TypeScript
- Compiled to optimized JavaScript
- Fast execution with Bun runtime
## Usage
### For Users
Install the plugin:
```
/plugin install claude-mem@marketplace-name
```
The plugin provides:
- **Hooks**: Automatic memory capture on SessionStart, UserPromptSubmit, PostToolUse, Stop
- **Commands**: `/claude-mem`, `/save`, `/remember` slash commands
- **MCP Integration**: Chroma vector database access via MCP tools
### For Developers
Build the plugin:
```bash
npm run build
```
Test hooks locally:
```bash
echo '{"session_id":"test","cwd":"/path"}' | bun scripts/hooks/context-hook.js
```
Publish to marketplace:
```bash
npm run publish:npm
```
## Worker Process Handling
### Background Worker
The `worker.js` is a special bundled script that runs as a long-lived background process. It:
- Runs an SDK agent session in the background
- Listens on a Unix socket for messages from hooks
- Processes tool observations and generates summaries
- Stores results in the database
### Spawning the Worker
The `new-hook` (UserPromptSubmit) is responsible for spawning the worker. It uses intelligent fallback:
```typescript
// Plugin mode: Use bundled worker with CLAUDE_PLUGIN_ROOT
if (process.env.CLAUDE_PLUGIN_ROOT) {
const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js');
spawn('bun', [workerPath, sessionId.toString()], { detached: true });
}
// Traditional mode: Use global CLI
else {
spawn('claude-mem', ['worker', sessionId.toString()], { detached: true });
}
```
### Why This Approach?
1. **Self-contained plugin**: When installed as a plugin, uses bundled worker
2. **Backwards compatible**: When installed traditionally, uses global CLI
3. **No user intervention**: Automatically detects mode via environment variable
## File Sizes
Compiled hook scripts are optimized and small:
- `context-hook.js`: ~4.1 KB
- `new-hook.js`: ~4.0 KB
- `save-hook.js`: ~4.2 KB
- `summary-hook.js`: ~3.9 KB
- `worker.js`: ~232 KB (includes SDK dependencies)
Total package overhead: ~248 KB for all hook scripts combined.
## Dependencies
### Runtime
- **Bun**: Required for executing hook scripts
- **bun:sqlite**: Native SQLite module (not bundled)
### Build-time
- **Bun**: Used as bundler for compilation
- **Node.js**: Required for build scripts
## Backwards Compatibility
The plugin structure maintains backwards compatibility:
- CLI commands still work: `claude-mem context`, etc.
- Traditional installation still supported: `npm install -g claude-mem`
- Users can choose plugin OR CLI installation
## Future Enhancements
Potential improvements:
- [ ] Add more hooks (e.g., PreToolUse, Error)
- [ ] Support Node.js runtime in addition to Bun
- [ ] Add hook configuration UI
- [ ] Implement hook hot-reloading during development
- [ ] Create plugin marketplace for distribution
## See Also
- [Plugin Installation Guide](./PLUGIN_INSTALLATION.md) - User-facing installation instructions
- [Build Documentation](./BUILD.md) - Build system details
- [Claude Code Plugins Docs](https://docs.claude.com/en/docs/claude-code/plugins) - Official plugin documentation
+272
View File
@@ -0,0 +1,272 @@
# Worker Process Architecture
This document explains how the SDK worker process is handled in both plugin and traditional installation modes.
## Overview
The worker is a critical background process that:
- Runs the Claude Agent SDK in a long-lived session
- Listens on a Unix socket for messages from hooks
- Processes tool observations in real-time
- Generates session summaries
- Stores observations and summaries in the database
## Architecture Diagram
```
┌─────────────────┐
│ Claude Code │
│ (Main UI) │
└────────┬────────┘
├─ SessionStart Hook ──> context-hook.js
├─ UserPromptSubmit ───> new-hook.js ──┐
│ │
│ ├──> Spawns Worker
│ │ (Background Process)
│ │
├─ PostToolUse Hook ───> save-hook.js ─┼──> Unix Socket
│ │
│ │ ┌──────────────┐
│ └───>│ worker.js │
│ │ │
│ │ - SDK Agent │
└─ Stop Hook ───────────> summary-hook.js ──┼─>- Socket Srv│
│ - Database │
└──────────────┘
```
## Worker Lifecycle
### 1. Session Initialization (UserPromptSubmit)
When a user submits a prompt, `new-hook.js` is triggered:
```typescript
// 1. Check if session already exists
const existing = db.findActiveSDKSession(session_id);
if (existing) { /* already running */ }
// 2. Create new SDK session record
const sessionId = db.createSDKSession(session_id, project, prompt);
// 3. Spawn worker process
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
if (pluginRoot) {
// Plugin mode: use bundled worker
spawn('bun', [`${pluginRoot}/scripts/hooks/worker.js`, sessionId]);
} else {
// Traditional mode: use global CLI
spawn('claude-mem', ['worker', sessionId]);
}
```
### 2. Worker Startup
The worker process starts and:
1. Loads session info from database
2. Creates Unix socket at `~/.claude-mem/sockets/session-{id}.sock`
3. Starts listening for messages
4. Initializes SDK agent with streaming input
### 3. Tool Observation Flow (PostToolUse)
Every time Claude uses a tool, `save-hook.js` is triggered:
```typescript
// 1. Find active SDK session
const session = db.findActiveSDKSession(session_id);
// 2. Connect to worker's Unix socket
const socketPath = getWorkerSocketPath(session.id);
const client = net.connect(socketPath);
// 3. Send observation message
client.write(JSON.stringify({
type: 'observation',
tool_name: 'Read',
tool_input: '{"file_path": "/path/to/file"}',
tool_output: '{"content": "..."}'
}));
```
The worker receives the message and:
1. Queues it in `pendingMessages`
2. Yields it to SDK agent via message generator
3. Receives agent's analysis
4. Parses and stores observations in database
### 4. Session Finalization (Stop)
When the session ends, `summary-hook.js` is triggered:
```typescript
// 1. Find active session
const session = db.findActiveSDKSession(session_id);
// 2. Send finalize message to worker
const client = net.connect(socketPath);
client.write(JSON.stringify({
type: 'finalize'
}));
```
The worker:
1. Stops accepting new observations
2. Sends finalize prompt to SDK agent
3. Receives and parses session summary
4. Stores summary in database
5. Cleans up socket and exits
## Plugin vs Traditional Mode
### Plugin Mode (Self-Contained)
When installed as a plugin:
- `CLAUDE_PLUGIN_ROOT` environment variable is set by Claude Code
- Hooks use bundled scripts: `${CLAUDE_PLUGIN_ROOT}/scripts/hooks/`
- Worker is spawned as: `bun ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/worker.js`
- **No global CLI installation required**
```bash
# Plugin mode execution
/plugin install claude-mem
# Hooks automatically use bundled worker
```
### Traditional Mode (Global CLI)
When installed via npm:
- `CLAUDE_PLUGIN_ROOT` is not set
- Hooks installed via `claude-mem install`
- Worker is spawned as: `claude-mem worker`
- **Requires global CLI installation**
```bash
# Traditional mode installation
npm install -g claude-mem
claude-mem install
```
## Worker Binary Size
The worker is the largest bundled script because it includes:
- Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`)
- Prompt templates and parsers
- Socket server implementation
- Database operations
**Size**: ~232 KB (minified)
This is acceptable because:
- Only spawned once per session (not per tool use)
- Runs in background (doesn't block UI)
- Contains full SDK functionality
## Worker Communication Protocol
### Message Types
#### 1. Observation Message
```json
{
"type": "observation",
"tool_name": "Read",
"tool_input": "{\"file_path\": \"/path\"}",
"tool_output": "{\"content\": \"...\"}"
}
```
#### 2. Finalize Message
```json
{
"type": "finalize"
}
```
### Socket Protocol
- **Transport**: Unix domain socket
- **Format**: JSON messages separated by newlines (`\n`)
- **Location**: `~/.claude-mem/sockets/session-{id}.sock`
- **Lifecycle**: Created on worker startup, deleted on worker exit
## Error Handling
### Worker Startup Failures
If the worker fails to start:
- New-hook logs error but doesn't block Claude Code
- Session record remains in "pending" state
- No observations are captured (graceful degradation)
### Socket Communication Failures
If hooks can't connect to worker socket:
- Hook logs error but doesn't block Claude Code
- Tool use continues normally
- Observations are skipped for that session
### Worker Crashes
If the worker crashes mid-session:
- Database marks session as "failed"
- Socket is cleaned up automatically
- Next session will spawn new worker
## Testing
### Test Worker Directly
```bash
# Build the worker
npm run build:hooks
# Test worker (needs valid session ID in DB)
bun scripts/hooks/worker.js 123
```
### Test Worker Spawning
```bash
# Plugin mode (with CLAUDE_PLUGIN_ROOT)
CLAUDE_PLUGIN_ROOT=$(pwd) printf '{"session_id":"test","cwd":"/path","prompt":"help"}' | \
bun scripts/hooks/new-hook.js
# Traditional mode (without CLAUDE_PLUGIN_ROOT)
printf '{"session_id":"test","cwd":"/path","prompt":"help"}' | \
bun scripts/hooks/new-hook.js
```
### Monitor Worker Logs
Worker logs to stderr:
```bash
# Watch worker logs in real-time
tail -f ~/.claude-mem/logs/worker-*.log
```
## Benefits of This Architecture
1. **Self-contained**: Plugin bundles everything needed
2. **Backwards compatible**: Works with global CLI too
3. **Automatic detection**: Uses environment variable to choose mode
4. **Isolated execution**: Worker runs in separate process
5. **Async communication**: Hooks don't block on SDK operations
6. **Graceful degradation**: Failures don't break Claude Code
## Future Improvements
Potential enhancements:
- [ ] Worker health checks and auto-restart
- [ ] Multiple workers for concurrent sessions
- [ ] Worker pool management
- [ ] WebSocket support for remote workers
- [ ] Worker performance metrics
## See Also
- [Plugin Structure Documentation](./PLUGIN_STRUCTURE.md)
- [Plugin Development Guide](./PLUGIN_DEVELOPMENT.md)
- [Build Documentation](./BUILD.md)