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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user