feat: Release v4.0.0 - Plugin data directory and auto-starting worker
BREAKING CHANGES:
- Data directory moved from ~/.claude-mem/ to ${CLAUDE_PLUGIN_ROOT}/data/
- Fresh start required - no migration from v3.x databases
- Worker service now auto-starts on SessionStart hook
New Features:
- MCP Search Server with 6 specialized search tools
- FTS5 full-text search across observations and sessions
- Auto-starting worker service in SessionStart hook
- Citation support for search results (claude-mem:// URIs)
Changes:
- Updated paths.ts to use CLAUDE_PLUGIN_ROOT for data directory
- Added worker auto-start logic to context hook
- Updated worker service to write port file to plugin data dir
- Bumped version to 4.0.0 in package.json and plugin.json
- Created comprehensive CHANGELOG.md documenting v4.0.0 changes
- Updated README.md with v4.0.0 breaking changes and features
- Rebuilt all hooks and worker service
Technical Improvements:
- Improved error handling and graceful degradation
- Structured logging across all components
- Enhanced plugin integration with Claude Code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { spawn } from 'child_process';
|
||||
import { SessionStore } from '../services/sqlite/SessionStore.js';
|
||||
import { getWorkerPortFilePath, getPackageRoot } from '../shared/paths.js';
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id?: string;
|
||||
@@ -10,6 +13,61 @@ export interface SessionStartInput {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure worker service is running
|
||||
* Auto-starts worker if not running (v4.0.0 feature)
|
||||
*/
|
||||
function ensureWorkerRunning(): void {
|
||||
try {
|
||||
const portFile = getWorkerPortFilePath();
|
||||
|
||||
// Check if worker is already running
|
||||
if (existsSync(portFile)) {
|
||||
// Worker appears to be running (port file exists)
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('[claude-mem] Worker not running, starting...');
|
||||
|
||||
// Find worker service path
|
||||
const packageRoot = getPackageRoot();
|
||||
const workerPath = path.join(packageRoot, 'dist', 'worker-service.cjs');
|
||||
|
||||
if (!existsSync(workerPath)) {
|
||||
console.error(`[claude-mem] Worker service not found at ${workerPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to start with PM2 first (preferred for production)
|
||||
const ecosystemPath = path.join(packageRoot, 'ecosystem.config.cjs');
|
||||
if (existsSync(ecosystemPath)) {
|
||||
try {
|
||||
spawn('pm2', ['start', ecosystemPath], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
cwd: packageRoot
|
||||
}).unref();
|
||||
console.error('[claude-mem] Worker started with PM2');
|
||||
return;
|
||||
} catch (pm2Error) {
|
||||
console.error('[claude-mem] PM2 not available, using direct spawn');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: spawn worker directly
|
||||
spawn('node', [workerPath], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: { ...process.env, NODE_ENV: 'production' }
|
||||
}).unref();
|
||||
console.error('[claude-mem] Worker started in background');
|
||||
|
||||
} catch (error: any) {
|
||||
// Don't fail the hook if worker start fails
|
||||
console.error(`[claude-mem] Failed to start worker: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Hook - SessionStart
|
||||
* Shows user what happened in recent sessions
|
||||
@@ -17,6 +75,8 @@ export interface SessionStartInput {
|
||||
* Output: Returns formatted context string to be wrapped in hookSpecificOutput
|
||||
*/
|
||||
export function contextHook(input?: SessionStartInput): string {
|
||||
// v4.0.0: Ensure worker is running before loading context
|
||||
ensureWorkerRunning();
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const project = cwd ? path.basename(cwd) : 'unknown-project';
|
||||
|
||||
|
||||
+2
-3
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
import { SessionStore } from '../services/sqlite/SessionStore.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { getWorkerPortFilePath } from '../shared/paths.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
@@ -14,10 +15,8 @@ export interface UserPromptSubmitInput {
|
||||
*/
|
||||
async function getWorkerPort(): Promise<number | null> {
|
||||
const { readFileSync, existsSync } = await import('fs');
|
||||
const { join } = await import('path');
|
||||
const { homedir } = await import('os');
|
||||
|
||||
const portFile = join(homedir(), '.claude-mem', 'worker.port');
|
||||
const portFile = getWorkerPortFilePath();
|
||||
|
||||
if (!existsSync(portFile)) {
|
||||
return null;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { parseObservations, parseSummary } from '../sdk/parser.js';
|
||||
import type { SDKSession } from '../sdk/prompts.js';
|
||||
import { findAvailablePort } from '../utils/port-allocator.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { getWorkerPortFilePath, ensureAllDataDirs } from '../shared/paths.js';
|
||||
|
||||
const MODEL = 'claude-sonnet-4-5';
|
||||
const DISALLOWED_TOOLS = ['Glob', 'Grep', 'ListMcpResourcesTool', 'WebSearch'];
|
||||
@@ -91,10 +92,10 @@ class WorkerService {
|
||||
|
||||
// Write port to file for hooks to discover
|
||||
const { writeFileSync } = require('fs');
|
||||
const { join } = require('path');
|
||||
const { homedir } = require('os');
|
||||
const portFile = join(homedir(), '.claude-mem', 'worker.port');
|
||||
ensureAllDataDirs(); // Ensure data directory exists
|
||||
const portFile = getWorkerPortFilePath();
|
||||
writeFileSync(portFile, port.toString(), 'utf8');
|
||||
logger.info('SYSTEM', `Port file written to ${portFile}`);
|
||||
|
||||
resolve();
|
||||
}).on('error', reject);
|
||||
|
||||
+21
-1
@@ -7,10 +7,23 @@ import { fileURLToPath } from 'url';
|
||||
/**
|
||||
* Simple path configuration for claude-mem
|
||||
* Standard paths based on Claude Code conventions
|
||||
*
|
||||
* v4.0.0: Data directory now uses CLAUDE_PLUGIN_ROOT when available
|
||||
*/
|
||||
|
||||
// Base directories
|
||||
export const DATA_DIR = process.env.CLAUDE_MEM_DATA_DIR || join(homedir(), '.claude-mem');
|
||||
// Priority: CLAUDE_PLUGIN_ROOT/data > CLAUDE_MEM_DATA_DIR > ~/.claude-mem
|
||||
const getDataDir = (): string => {
|
||||
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
||||
return join(process.env.CLAUDE_PLUGIN_ROOT, 'data');
|
||||
}
|
||||
if (process.env.CLAUDE_MEM_DATA_DIR) {
|
||||
return process.env.CLAUDE_MEM_DATA_DIR;
|
||||
}
|
||||
return join(homedir(), '.claude-mem');
|
||||
};
|
||||
|
||||
export const DATA_DIR = getDataDir();
|
||||
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
||||
|
||||
// Data subdirectories
|
||||
@@ -40,6 +53,13 @@ export function getWorkerSocketPath(sessionId: number): string {
|
||||
return join(DATA_DIR, `worker-${sessionId}.sock`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get worker port file path
|
||||
*/
|
||||
export function getWorkerPortFilePath(): string {
|
||||
return join(DATA_DIR, 'worker.port');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a directory exists
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user