diff --git a/.claude/plans/2026-01-05-fix-81-test-failures.md b/.claude/plans/2026-01-05-fix-81-test-failures.md deleted file mode 100644 index b2c33f2c..00000000 --- a/.claude/plans/2026-01-05-fix-81-test-failures.md +++ /dev/null @@ -1,299 +0,0 @@ -# Plan: Fix 81 Test Failures from Incomplete Logger Mocks - -## Problem Summary - -**Root Cause**: NOT circular dependency (which is handled gracefully), but **incomplete logger mocks** that pollute across test files when Bun runs tests in alphabetical order. - -When `tests/context/` runs before `tests/utils/`, the incomplete mocks replace the real logger module globally, causing subsequent tests to fail with `TypeError: logger.formatTool is not a function`. - -## Phase 0: Documentation Discovery (COMPLETED) - -### Sources Consulted -- `src/utils/logger.ts` - Full logger interface (lines 136, 289-373) -- `tests/context/context-builder.test.ts` - Mock pattern (lines 22-29) -- `tests/context/observation-compiler.test.ts` - Mock pattern (lines 4-10) -- `tests/server/server.test.ts` - Mock pattern (lines 4-11) -- `tests/server/error-handler.test.ts` - Mock pattern (lines 5-12) -- `tests/worker/agents/response-processor.test.ts` - Mock pattern (lines 32-39) - -### Logger Methods (Complete List) -All 11 methods that must be in any logger mock: -1. `formatTool(toolName: string, toolInput?: any): string` (line 136) -2. `debug(component, message, context?, data?): void` (line 289) -3. `info(component, message, context?, data?): void` (line 293) -4. `warn(component, message, context?, data?): void` (line 297) -5. `error(component, message, context?, data?): void` (line 301) -6. `dataIn(component, message, context?, data?): void` (line 308) -7. `dataOut(component, message, context?, data?): void` (line 315) -8. `success(component, message, context?, data?): void` (line 322) -9. `failure(component, message, context?, data?): void` (line 329) -10. `timing(component, message, durationMs, context?): void` (line 336) -11. `happyPathError(message, context?): T` (line 362) - -### Files Requiring Updates -1. `tests/context/observation-compiler.test.ts` (lines 4-10) -2. `tests/context/context-builder.test.ts` (lines 22-29) -3. `tests/server/server.test.ts` (lines 4-11) -4. `tests/server/error-handler.test.ts` (lines 5-12) -5. `tests/worker/agents/response-processor.test.ts` (lines 32-39) - ---- - -## Phase 1: Create Shared Logger Mock Utility - -### Objective -Create a reusable complete logger mock to avoid duplication and ensure consistency. - -### Implementation - -**Create new file**: `tests/test-utils/mock-logger.ts` - -```typescript -/** - * Complete logger mock for tests. - * Includes ALL logger methods to prevent mock pollution across test files. - */ -import { mock } from 'bun:test'; - -export function createMockLogger() { - return { - logger: { - // Core logging methods - debug: mock(() => {}), - info: mock(() => {}), - warn: mock(() => {}), - error: mock(() => {}), - - // Data flow logging - dataIn: mock(() => {}), - dataOut: mock(() => {}), - - // Status logging - success: mock(() => {}), - failure: mock(() => {}), - - // Performance logging - timing: mock(() => {}), - - // Tool formatting - returns string - formatTool: mock((toolName: string, _toolInput?: any) => toolName), - - // Error helper - returns the message - happyPathError: mock((message: string, _context?: any) => message), - }, - }; -} -``` - -### Verification Checklist -- [ ] File created at `tests/test-utils/mock-logger.ts` -- [ ] All 11 logger methods included -- [ ] `formatTool` returns string (not void) -- [ ] `happyPathError` returns the message (not void) -- [ ] File compiles without errors: `bunx tsc --noEmit tests/test-utils/mock-logger.ts` - -### Anti-Patterns to Avoid -- ❌ Don't forget `formatTool` - it returns a string, not void -- ❌ Don't forget `happyPathError` - it's generic and returns the message -- ❌ Don't use `() => {}` for methods that return values - ---- - -## Phase 2: Update Affected Test Files - -### Objective -Replace incomplete logger mocks with the complete shared mock. - -### Files to Update (5 total) - -#### 2.1 `tests/context/observation-compiler.test.ts` - -**Current (lines 4-10)**: -```typescript -mock.module('../../src/utils/logger.js', () => ({ - logger: { - debug: mock(() => {}), - failure: mock(() => {}), - error: mock(() => {}), - }, -})); -``` - -**Replace with**: -```typescript -import { createMockLogger } from '../test-utils/mock-logger.js'; - -mock.module('../../src/utils/logger.js', () => createMockLogger()); -``` - -#### 2.2 `tests/context/context-builder.test.ts` - -**Current (lines 22-29)**: -```typescript -mock.module('../../src/utils/logger.js', () => ({ - logger: { - debug: mock(() => {}), - failure: mock(() => {}), - error: mock(() => {}), - info: mock(() => {}), - }, -})); -``` - -**Replace with**: -```typescript -import { createMockLogger } from '../test-utils/mock-logger.js'; - -mock.module('../../src/utils/logger.js', () => createMockLogger()); -``` - -#### 2.3 `tests/server/server.test.ts` - -**Current (lines 4-11)**: -```typescript -mock.module('../../src/utils/logger.js', () => ({ - logger: { - info: () => {}, - debug: () => {}, - warn: () => {}, - error: () => {}, - }, -})); -``` - -**Replace with**: -```typescript -import { createMockLogger } from '../test-utils/mock-logger.js'; - -mock.module('../../src/utils/logger.js', () => createMockLogger()); -``` - -#### 2.4 `tests/server/error-handler.test.ts` - -**Current (lines 5-12)**: -```typescript -mock.module('../../src/utils/logger.js', () => ({ - logger: { - info: () => {}, - debug: () => {}, - warn: () => {}, - error: () => {}, - }, -})); -``` - -**Replace with**: -```typescript -import { createMockLogger } from '../test-utils/mock-logger.js'; - -mock.module('../../src/utils/logger.js', () => createMockLogger()); -``` - -#### 2.5 `tests/worker/agents/response-processor.test.ts` - -**Current (lines 32-39)**: -```typescript -mock.module('../../../src/utils/logger.js', () => ({ - logger: { - info: () => {}, - debug: () => {}, - warn: () => {}, - error: () => {}, - }, -})); -``` - -**Replace with**: -```typescript -import { createMockLogger } from '../../test-utils/mock-logger.js'; - -mock.module('../../../src/utils/logger.js', () => createMockLogger()); -``` - -### Verification Checklist -- [ ] All 5 files updated with import statement -- [ ] All 5 files use `createMockLogger()` instead of inline mock -- [ ] Import paths are correct (relative to each file's location) -- [ ] Each file still has `mock.module` BEFORE the module imports it mocks - -### Anti-Patterns to Avoid -- ❌ Don't place import AFTER the mock.module call -- ❌ Don't use wrong relative path (../test-utils vs ../../test-utils) -- ❌ Don't forget the .js extension in imports - ---- - -## Phase 3: Verification - -### Objective -Confirm all 81 failures are fixed. - -### Test Commands - -```bash -# 1. Run individual test groups first -bun test tests/context/ -bun test tests/server/ -bun test tests/utils/ -bun test tests/shared/ -bun test tests/worker/ - -# 2. Run full suite -bun test - -# 3. Verify specific test counts -# Expected: 733+ tests pass (was 652 before) -``` - -### Verification Checklist -- [ ] `bun test tests/context/` - all pass -- [ ] `bun test tests/server/` - all pass -- [ ] `bun test tests/utils/` - all pass (including 56 formatTool tests) -- [ ] `bun test tests/shared/` - all pass (including 27 settings tests) -- [ ] `bun test` - 730+ tests pass, 0 failures -- [ ] No `TypeError: logger.formatTool is not a function` errors - -### Anti-Pattern Grep Checks - -```bash -# Check no incomplete logger mocks remain -grep -r "logger: {" tests/ --include="*.ts" | grep -v mock-logger - -# Verify all test files use createMockLogger -grep -r "createMockLogger" tests/ --include="*.ts" -``` - ---- - -## Phase 4: Commit - -### Commit Message - -``` -fix(tests): complete logger mocks to prevent cross-test pollution - -The 81 test failures were caused by incomplete logger mocks that -polluted the module cache when tests ran in alphabetical order. - -Changes: -- Create shared mock-logger.ts with all 11 logger methods -- Update 5 test files to use complete mock -- Fix TypeError: logger.formatTool is not a function - -🤖 Generated with [Claude Code](https://claude.com/claude-code) - -Co-Authored-By: Claude Opus 4.5 -``` - ---- - -## Summary - -| Phase | Files Changed | Purpose | -|-------|--------------|---------| -| 1 | 1 new file | Create shared mock utility | -| 2 | 5 files | Update to use shared mock | -| 3 | 0 files | Verification only | -| 4 | 0 files | Commit | - -**Total**: 6 files changed, fixing all 81 test failures. diff --git a/.claude/plans/animated-installer.md b/.claude/plans/animated-installer.md deleted file mode 100644 index 04667134..00000000 --- a/.claude/plans/animated-installer.md +++ /dev/null @@ -1,371 +0,0 @@ -# Comprehensive Claude-Mem Installer with @clack/prompts - -## Overview - -Build a beautiful, animated CLI installer for claude-mem using `@clack/prompts` (v1.0.1). Distributable via `npx claude-mem-installer` and `curl -fsSL https://install.cmem.ai | bash`. Replaces the need for users to manually clone, build, configure settings, and start the worker. - -**Worktree**: `feat/animated-installer` at `.claude/worktrees/animated-installer` - ---- - -## Phase 0: Documentation & API Reference - -### Allowed APIs (@clack/prompts v1.0.1, ESM-only) - -| API | Signature | Use Case | -|-----|-----------|----------| -| `intro(title?)` | `void` | Opening banner | -| `outro(message?)` | `void` | Completion message | -| `cancel(message?)` | `void` | User cancelled | -| `isCancel(value)` | `boolean` | Check if user pressed Ctrl+C | -| `text(opts)` | `Promise` | API key input, port, data dir | -| `password(opts)` | `Promise` | API key input (masked) | -| `select(opts)` | `Promise` | Provider, model, auth method | -| `multiselect(opts)` | `Promise` | IDE selection, observation types | -| `confirm(opts)` | `Promise` | Enable Chroma, start worker | -| `spinner()` | `SpinnerResult` | Installing deps, building, starting worker | -| `progress(opts)` | `ProgressResult` | Multi-step installation progress | -| `tasks(tasks[])` | `Promise` | Sequential install steps | -| `group(prompts, opts)` | `Promise` | Chain prompts with shared results | -| `note(message, title)` | `void` | Display settings summary, next steps | -| `log.info/success/warn/error(msg)` | `void` | Status messages | -| `box(message, title, opts)` | `void` | Welcome box, completion summary | - -### Anti-Patterns -- Do NOT use `require()` — package is ESM-only -- Do NOT call prompts without TTY check first — hangs indefinitely in non-TTY -- Do NOT forget `isCancel()` check after every prompt (or use `group()` with `onCancel`) -- Do NOT use `chalk` — use `picocolors` (clack's dep) for consistency -- `text()` has no numeric mode — validate manually for port numbers -- `spinner.stop()` does not accept status codes — use `spinner.error()` for failures - -### Distribution Patterns -- **npx**: `package.json` `bin` field → `"./dist/index.js"`, file needs `#!/usr/bin/env node` -- **curl|bash**: Shell bootstrap downloads JS, runs `node script.js` directly (preserves TTY) -- **esbuild**: Bundle to single ESM file, `platform: 'node'`, `banner` for shebang - -### Key Source Files to Reference -- Settings defaults: `src/shared/SettingsDefaultsManager.ts` (lines 73-125) -- Settings validation: `src/services/server/SettingsRoutes.ts` -- Worker startup: `src/services/worker-service.ts` (lines 337-359) -- Health check: `src/services/infrastructure/HealthMonitor.ts` -- Plugin registration: `plugin/.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` -- Marketplace sync: `scripts/sync-marketplace.cjs` -- Cursor integration: `src/services/integrations/CursorHooksInstaller.ts` -- Existing OpenClaw installer: `install/public/openclaw.sh` (reference for logic, not code to copy) - ---- - -## Phase 1: Project Scaffolding - -**Goal**: Set up the installer package structure with build tooling. - -### Tasks - -1. **Create directory structure** in the worktree: - ``` - installer/ - ├── src/ - │ ├── index.ts # Entry point with TTY guard - │ ├── steps/ - │ │ ├── welcome.ts # intro + version check - │ │ ├── dependencies.ts # bun, uv, git checks - │ │ ├── ide-selection.ts # IDE picker + registration - │ │ ├── provider.ts # AI provider + API key - │ │ ├── settings.ts # Additional settings config - │ │ ├── install.ts # Clone, build, register plugin - │ │ ├── worker.ts # Start worker + health check - │ │ └── complete.ts # Summary + next steps - │ └── utils/ - │ ├── system.ts # OS detection, command runner - │ ├── dependencies.ts # bun/uv/git install helpers - │ └── settings-writer.ts # Write ~/.claude-mem/settings.json - ├── build.mjs # esbuild config - ├── package.json # bin, type: module, deps - └── tsconfig.json - ``` - -2. **Create `package.json`**: - ```json - { - "name": "claude-mem-installer", - "version": "1.0.0", - "type": "module", - "bin": { "claude-mem-installer": "./dist/index.js" }, - "files": ["dist"], - "scripts": { - "build": "node build.mjs", - "dev": "node build.mjs && node dist/index.js" - }, - "dependencies": { - "@clack/prompts": "^1.0.1", - "picocolors": "^1.1.1" - }, - "devDependencies": { - "esbuild": "^0.24.0", - "typescript": "^5.7.0", - "@types/node": "^22.0.0" - }, - "engines": { "node": ">=18.0.0" } - } - ``` - -3. **Create `build.mjs`**: - - esbuild bundle: `entryPoints: ['src/index.ts']`, `format: 'esm'`, `platform: 'node'`, `target: 'node18'` - - Banner: `#!/usr/bin/env node` - - Output: `dist/index.js` - -4. **Create `tsconfig.json`**: - - `module: "ESNext"`, `target: "ES2022"`, `moduleResolution: "bundler"` - -5. **Run `npm install`** in installer/ directory - -### Verification -- [ ] `node build.mjs` succeeds -- [ ] `dist/index.js` exists with shebang -- [ ] `node dist/index.js` runs (even if empty installer) - ---- - -## Phase 2: Entry Point + Welcome Screen - -**Goal**: Create the main entry point with TTY detection and a beautiful welcome screen. - -### Tasks - -1. **`src/index.ts`** — Entry point: - - TTY guard: if `!process.stdin.isTTY`, print error directing user to `npx claude-mem-installer`, exit 1 - - Import and call `runInstaller()` from steps - - Top-level catch → `p.cancel()` + exit 1 - -2. **`src/steps/welcome.ts`** — Welcome step: - - `p.intro()` with styled title using picocolors: `" claude-mem installer "` - - Display version info via `p.log.info()` - - Check if already installed (detect `~/.claude-mem/settings.json` and `~/.claude/plugins/marketplaces/thedotmack/`) - - If upgrade detected, `p.confirm()`: "claude-mem is already installed. Upgrade?" - - `p.select()` for install mode: Fresh Install vs Upgrade vs Configure Only - -3. **`src/utils/system.ts`** — System utilities: - - `detectOS()`: returns 'macos' | 'linux' | 'windows' - - `commandExists(cmd)`: checks if command is in PATH - - `runCommand(cmd, args)`: executes shell command, returns { stdout, stderr, exitCode } - - `expandHome(path)`: resolves `~` to home directory - -### Verification -- [ ] Running `node dist/index.js` shows intro banner -- [ ] Ctrl+C triggers cancel message -- [ ] Non-TTY (piped) shows error and exits - ---- - -## Phase 3: Dependency Checks - -**Goal**: Check and install required dependencies (Bun, uv, git, Node.js version). - -### Tasks - -1. **`src/steps/dependencies.ts`** — Dependency checker: - - Use `p.tasks()` to check each dependency sequentially with animated spinners: - - **Node.js**: Verify >= 18.0.0 via `process.version` - - **git**: `commandExists('git')`, show install instructions per OS if missing - - **Bun**: Check PATH + common locations (`~/.bun/bin/bun`, `/usr/local/bin/bun`, `/opt/homebrew/bin/bun`). Min version 1.1.14. Offer to auto-install from `https://bun.sh/install` - - **uv**: Check PATH + common locations (`~/.local/bin/uv`, `~/.cargo/bin/uv`). Offer to auto-install from `https://astral.sh/uv/install.sh` - - For missing deps: `p.confirm()` to auto-install, or show manual instructions - - After install attempts, re-verify each dep - -2. **`src/utils/dependencies.ts`** — Install helpers: - - `installBun()`: downloads and runs bun install script - - `installUv()`: downloads and runs uv install script - - `findBinary(name, extraPaths[])`: searches PATH + known locations - - `checkVersion(binary, minVersion)`: parses `--version` output - -### Verification -- [ ] Shows green checkmarks for found dependencies -- [ ] Shows yellow warnings for missing deps with install option -- [ ] Auto-install actually installs bun/uv when confirmed -- [ ] Fails gracefully if git is missing (can't auto-install) - ---- - -## Phase 4: IDE Selection & Provider Configuration - -**Goal**: Let user choose IDEs and configure AI provider with API keys. - -### Tasks - -1. **`src/steps/ide-selection.ts`** — IDE picker: - - `p.multiselect()` with options: - - Claude Code (default selected, hint: "recommended") - - Cursor - - Windsurf (hint: "coming soon", disabled: true) - - For Claude Code: explain plugin will be registered via marketplace - - For Cursor: explain hooks will be installed via CursorHooksInstaller pattern - - Store selections for later installation steps - -2. **`src/steps/provider.ts`** — AI provider configuration: - - `p.select()` for provider: - - **Claude** (hint: "recommended — uses your Claude subscription") - - **Gemini** (hint: "free tier available") - - **OpenRouter** (hint: "free models available") - - **If Claude selected**: - - `p.select()` for auth method: "CLI (Max Plan subscription)" vs "API Key" - - If API key: `p.password()` for key input - - **If Gemini selected**: - - `p.password()` for API key (required) - - `p.select()` for model: gemini-2.5-flash-lite (default), gemini-2.5-flash, gemini-3-flash-preview - - `p.confirm()` for rate limiting (default: true) - - **If OpenRouter selected**: - - `p.password()` for API key (required) - - `p.text()` for model (default: `xiaomi/mimo-v2-flash:free`) - - Validate API keys where possible (non-empty, format check) - -### Verification -- [ ] Multiselect allows picking multiple IDEs -- [ ] Provider selection shows correct follow-up prompts -- [ ] API keys are masked during input -- [ ] Cancel at any step triggers graceful exit - ---- - -## Phase 5: Settings Configuration - -**Goal**: Configure additional settings with sensible defaults. - -### Tasks - -1. **`src/steps/settings.ts`** — Settings wizard: - - `p.confirm()`: "Use default settings?" (recommended) — if yes, skip detailed config - - If customizing, use `p.group()` for: - - **Worker port**: `p.text()` with default 37777, validate 1024-65535 - - **Data directory**: `p.text()` with default `~/.claude-mem` - - **Context observations**: `p.text()` with default 50, validate 1-200 - - **Log level**: `p.select()` — DEBUG, INFO (default), WARN, ERROR - - **Python version**: `p.text()` with default 3.13 - - **Chroma vector search**: `p.confirm()` (default: true) - - If yes, `p.select()` mode: local (default) vs remote - - If remote: `p.text()` for host, port, `p.confirm()` for SSL - - Show settings summary via `p.note()` before proceeding - -2. **`src/utils/settings-writer.ts`** — Write settings: - - Build flat key-value settings object matching SettingsDefaultsManager schema - - Merge with existing settings if upgrading (preserve user customizations) - - Write to `~/.claude-mem/settings.json` - - Create `~/.claude-mem/` directory if it doesn't exist - -### Verification -- [ ] Default settings mode skips all detailed prompts -- [ ] Custom settings validates all inputs -- [ ] Settings file written matches SettingsDefaultsManager schema exactly -- [ ] Existing settings preserved on upgrade - ---- - -## Phase 6: Installation Execution - -**Goal**: Clone repo, build plugin, register with IDEs, start worker. - -### Tasks - -1. **`src/steps/install.ts`** — Installation runner: - - Use `p.tasks()` for visual progress: - - **"Cloning claude-mem repository"**: `git clone --depth 1 https://github.com/thedotmack/claude-mem.git` to temp dir - - **"Installing dependencies"**: `npm install` in cloned repo - - **"Building plugin"**: `npm run build` in cloned repo - - **"Registering plugin"**: Copy plugin files to `~/.claude/plugins/marketplaces/thedotmack/` - - Create marketplace.json, plugin.json structure - - Register in `~/.claude/plugins/known_marketplaces.json` - - Add to `~/.claude/plugins/installed_plugins.json` - - Enable in `~/.claude/settings.json` under `enabledPlugins` - - **"Installing dependencies"** (in marketplace dir): `npm install` - - For Cursor (if selected): - - **"Configuring Cursor hooks"**: Run Cursor hooks installer logic - - Write hooks.json to `~/.cursor/` or project-level `.cursor/` - - Configure MCP in `.cursor/mcp.json` - -2. **`src/steps/worker.ts`** — Worker startup: - - Use `p.spinner()` for worker startup: - - Start worker: `bun plugin/scripts/worker-service.cjs` (from marketplace dir) - - Write PID file to `~/.claude-mem/worker.pid` - - Two-stage health check (copy pattern from OpenClaw installer): - - Stage 1: Poll `/api/health` — spinner message: "Starting worker service..." - - Stage 2: Poll `/api/readiness` — spinner message: "Initializing database..." - - Budget: 30 attempts, 1 second apart - - On success: `spinner.stop("Worker running on port {port}")` - - On failure: `spinner.error("Worker failed to start")`, show log path - -### Verification -- [ ] Plugin files exist at `~/.claude/plugins/marketplaces/thedotmack/` -- [ ] known_marketplaces.json updated -- [ ] installed_plugins.json updated -- [ ] settings.json has enabledPlugins entry -- [ ] Worker responds to `/api/health` with 200 -- [ ] Worker responds to `/api/readiness` with 200 - ---- - -## Phase 7: Completion & Summary - -**Goal**: Show success screen with configuration summary and next steps. - -### Tasks - -1. **`src/steps/complete.ts`** — Completion screen: - - `p.note()` with configuration summary: - - Provider + model - - IDEs configured - - Data directory - - Worker port - - Chroma enabled/disabled - - `p.note()` with next steps: - - "Open Claude Code and start a conversation — memory is automatic!" - - "View your memories: http://localhost:{port}" - - "Search past work: use /mem-search in Claude Code" - - If Cursor: "Open Cursor — hooks are active in your projects" - - `p.outro()` with styled completion message - -### Verification -- [ ] Summary accurately reflects chosen settings -- [ ] URLs use correct port from settings -- [ ] Next steps are relevant to selected IDEs - ---- - -## Phase 8: curl|bash Bootstrap Script - -**Goal**: Create the shell bootstrap script for `curl -fsSL https://install.cmem.ai | bash`. - -### Tasks - -1. **`install/public/install.sh`** — Bootstrap script: - - Check for Node.js >= 18 (required to run the installer) - - Download bundled installer JS to temp file - - Execute with `node` directly (preserves TTY for @clack/prompts) - - Cleanup temp file on exit (trap) - - Support `--non-interactive` flag passthrough - - Support `--provider=X --api-key=Y` flag passthrough - -2. **Update `install/vercel.json`** to serve `install.sh` alongside `openclaw.sh` - -### Verification -- [ ] `curl -fsSL https://install.cmem.ai | bash` downloads and runs installer -- [ ] Interactive prompts work after curl download -- [ ] Temp file cleaned up on success and failure -- [ ] Flags pass through correctly - ---- - -## Phase 9: Final Verification - -### Checks -- [ ] `npm run build` in installer/ produces single-file `dist/index.js` -- [ ] `node dist/index.js` runs full wizard flow -- [ ] Fresh install on clean system works end-to-end -- [ ] Upgrade path preserves existing settings -- [ ] Ctrl+C at any step exits cleanly -- [ ] Non-TTY shows error message -- [ ] All settings written match SettingsDefaultsManager.ts defaults schema -- [ ] Worker health check succeeds after install -- [ ] Plugin appears in Claude Code plugin list -- [ ] grep for deprecated/non-existent APIs returns 0 results -- [ ] No `require()` calls in source (ESM-only) -- [ ] No `chalk` imports (use picocolors) diff --git a/.claude/plans/bugfix-env-auth.md b/.claude/plans/bugfix-env-auth.md deleted file mode 100644 index 284f1f48..00000000 --- a/.claude/plans/bugfix-env-auth.md +++ /dev/null @@ -1,176 +0,0 @@ -# Bugfix Plan: Observer Sessions Authentication Failure - -## Problem Summary - -Observer sessions fail with "Invalid API key · Please run /login" because the `CLAUDE_CONFIG_DIR` environment variable is being set to an isolated directory (`~/.claude-mem/observer-config/`) that lacks authentication credentials. - -## Root Cause - -**File:** `src/services/worker/ProcessRegistry.ts` (lines 207-211) - -```typescript -const isolatedEnv = { - ...spawnOptions.env, - CLAUDE_CONFIG_DIR: OBSERVER_CONFIG_DIR // <-- This isolates auth credentials! -}; -``` - -This was added in Issue #832 to prevent observer sessions from polluting the `claude --resume` list. However, it also isolates the authentication credentials, breaking the SDK's ability to authenticate with the Anthropic API. - -## Evidence - -1. Running Claude with alternate config dir reproduces the error: - ```bash - CLAUDE_CONFIG_DIR=/tmp/test-claude claude --print "hello" - # Output: Invalid API key · Please run /login - ``` - -2. The observer config directory exists but only has cached feature flags, no authentication: - - `~/.claude-mem/observer-config/.claude.json` - feature flags only - - No credentials copied from main `~/.claude/` directory - -## Solution - -The fix must allow authentication while still isolating session history. Claude Code stores different data types in `CLAUDE_CONFIG_DIR`: -- Authentication credentials (needed) -- Session history/resume list (should be isolated) -- Feature flags and settings (can be shared or isolated) - -**Approach:** Do NOT override `CLAUDE_CONFIG_DIR`. Instead, find an alternative solution for Issue #832. - -### Alternative Approaches for Session Isolation - -1. **Use `--no-resume` flag** (if SDK supports it) - Prevent observer sessions from being resumable -2. **Accept pollution** - Observer sessions in resume list may be acceptable tradeoff -3. **Post-hoc cleanup** - Clean up observer session entries from history after completion -4. **SDK parameter** - Check if SDK has a session isolation option that doesn't affect auth - ---- - -## Phase 0: Documentation Discovery - -### Objective -Understand SDK options for session isolation without breaking authentication. - -### Tasks -1. Read SDK documentation/source for: - - Available `query()` options - - Session isolation mechanisms - - Authentication handling - -2. Read Issue #832 context: - - What was the original problem? - - How bad was the pollution? - - Are there alternative solutions mentioned? - -### Verification -- [ ] List all `query()` options available -- [ ] Identify if `--no-resume` or equivalent exists -- [ ] Document the tradeoffs - ---- - -## Phase 1: Fix Authentication - -### Objective -Remove the `CLAUDE_CONFIG_DIR` override to restore authentication. - -### File to Modify -`src/services/worker/ProcessRegistry.ts` - -### Change -Remove lines 207-211 that override `CLAUDE_CONFIG_DIR`: - -**Before:** -```typescript -const isolatedEnv = { - ...spawnOptions.env, - CLAUDE_CONFIG_DIR: OBSERVER_CONFIG_DIR -}; -``` - -**After:** -```typescript -const isolatedEnv = { - ...spawnOptions.env - // CLAUDE_CONFIG_DIR removed - observer sessions need access to auth credentials - // Session isolation addressed via [alternative approach] -}; -``` - -### Verification -- [ ] Build succeeds: `npm run build` -- [ ] Observer sessions authenticate successfully -- [ ] Observations are saved to database - ---- - -## Phase 2: Address Session Isolation (Issue #832) - -### Objective -Find alternative solution to prevent observer sessions from polluting `claude --resume` list. - -### Options to Evaluate - -1. **Option A: Accept the tradeoff** - - Observer sessions appear in resume list but users can ignore them - - No code changes needed beyond Phase 1 - -2. **Option B: Use isSynthetic flag** - - If SDK has a flag to mark sessions as non-resumable, use it - - Requires SDK documentation review - -3. **Option C: Post-processing cleanup** - - After session ends, remove observer entries from history - - More complex, may have race conditions - -### Decision Point -After Phase 0 documentation review, choose the appropriate option. - -### Verification -- [ ] Chosen approach documented -- [ ] If code changes made, tests pass -- [ ] Observer sessions either isolated OR tradeoff accepted - ---- - -## Phase 3: Testing - -### Manual Tests -1. Start a new Claude Code session with the plugin -2. Verify observations are being saved (check logs) -3. Check that no "Invalid API key" errors appear -4. Verify `claude --resume` behavior (acceptable level of observer entries) - -### Verification Checklist -- [ ] `npm run build` succeeds -- [ ] Worker service starts without errors -- [ ] Observations save to database -- [ ] No authentication errors in logs -- [ ] Issue #832 regression acceptable or addressed - ---- - -## Anti-Patterns to Avoid - -1. **DO NOT** add `ANTHROPIC_API_KEY` to environment - authentication is handled by Claude Code's built-in credential management -2. **DO NOT** copy credential files to observer config dir - credentials may be in keychain or other secure storage -3. **DO NOT** try to "fix" authentication by adding API key loading - that creates Issue #588 (unexpected API charges) - ---- - -## Files Involved - -| File | Purpose | -|------|---------| -| `src/services/worker/ProcessRegistry.ts` | Contains the problematic `CLAUDE_CONFIG_DIR` override | -| `src/shared/paths.ts` | Defines `OBSERVER_CONFIG_DIR` constant | -| `src/services/worker/SDKAgent.ts` | Uses `createPidCapturingSpawn` which sets the env | - ---- - -## Risk Assessment - -**Low Risk:** Removing the `CLAUDE_CONFIG_DIR` override is a simple, targeted change. - -**Regression Risk (Issue #832):** Observer sessions may appear in `claude --resume` list again. This is a cosmetic issue vs. complete authentication failure, so the tradeoff favors removing the override. diff --git a/.claude/plans/claude-md-path-validation-fix.md b/.claude/plans/claude-md-path-validation-fix.md deleted file mode 100644 index 84e80bad..00000000 --- a/.claude/plans/claude-md-path-validation-fix.md +++ /dev/null @@ -1,262 +0,0 @@ -# CLAUDE.md Path Validation Bug Fix - -## Problem Summary - -Claude-Mem 9.0's distributed CLAUDE.md feature has a **critical path validation bug** that creates invalid directories when Claude SDK agent outputs non-path strings in file tracking XML tags (``, ``). - -### Root Cause - -In `src/utils/claude-md-utils.ts:234-239`: -```typescript -if (projectRoot && !path.isAbsolute(filePath)) { - absoluteFilePath = path.join(projectRoot, filePath); -} -``` - -- `path.isAbsolute('~/.claude-mem/logs')` returns `false` (Node.js doesn't recognize `~`) -- Code joins: `path.join(projectRoot, '~/.claude-mem/logs')` → `/project/~/.claude-mem/logs` -- `mkdirSync` creates literal directories - -### Invalid Directories Currently in Repo - -``` -./~/ ← literal tilde directory -./PR #610 on thedotmack/ ← GitHub PR reference -./git diff for src/ ← git command text -./https:/code.claude.com/docs/en/ ← URL -``` - ---- - -## Implementation Plan - -### Phase 1: Add Path Validation Function - -**File:** `src/utils/claude-md-utils.ts` - -Add new validation function after the imports (around line 16): - -```typescript -/** - * Validate that a file path is safe for CLAUDE.md generation. - * Rejects tilde paths, URLs, command-like strings, and paths with invalid chars. - * - * @param filePath - The file path to validate - * @param projectRoot - Optional project root for boundary checking - * @returns true if path is valid for CLAUDE.md processing - */ -function isValidPathForClaudeMd(filePath: string, projectRoot?: string): boolean { - // Reject empty or whitespace-only - if (!filePath || !filePath.trim()) return false; - - // Reject tilde paths (Node.js doesn't expand ~) - if (filePath.startsWith('~')) return false; - - // Reject URLs - if (filePath.startsWith('http://') || filePath.startsWith('https://')) return false; - - // Reject paths with spaces (likely command text or PR references) - if (filePath.includes(' ')) return false; - - // Reject paths with # (GitHub issue/PR references) - if (filePath.includes('#')) return false; - - // If projectRoot provided, ensure resolved path stays within project - if (projectRoot) { - const resolved = path.resolve(projectRoot, filePath); - const normalizedRoot = path.resolve(projectRoot); - if (!resolved.startsWith(normalizedRoot + path.sep) && resolved !== normalizedRoot) { - return false; - } - } - - return true; -} -``` - -### Phase 2: Integrate Validation in updateFolderClaudeMdFiles - -**File:** `src/utils/claude-md-utils.ts` - -Modify the file path loop in `updateFolderClaudeMdFiles` (around line 232): - -```typescript -for (const filePath of filePaths) { - if (!filePath || filePath === '') continue; - - // VALIDATE PATH BEFORE PROCESSING - if (!isValidPathForClaudeMd(filePath, projectRoot)) { - logger.debug('FOLDER_INDEX', 'Skipping invalid file path', { - filePath, - reason: 'Failed path validation' - }); - continue; - } - - // ... rest of existing logic unchanged -} -``` - -### Phase 3: Add Unit Tests - -**File:** `tests/utils/claude-md-utils.test.ts` - -Add new test block after existing tests: - -```typescript -describe('path validation in updateFolderClaudeMdFiles', () => { - it('should reject tilde paths', async () => { - const fetchMock = mock(() => Promise.resolve({ ok: true } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['~/.claude-mem/logs/worker.log'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - - it('should reject URLs', async () => { - const fetchMock = mock(() => Promise.resolve({ ok: true } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['https://example.com/file.ts'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - - it('should reject paths with spaces', async () => { - const fetchMock = mock(() => Promise.resolve({ ok: true } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['PR #610 on thedotmack/CLAUDE.md'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - - it('should reject paths with hash symbols', async () => { - const fetchMock = mock(() => Promise.resolve({ ok: true } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['issue#123/file.ts'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - - it('should reject path traversal outside project', async () => { - const fetchMock = mock(() => Promise.resolve({ ok: true } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['../../../etc/passwd'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - - it('should accept valid relative paths', async () => { - const apiResponse = { - content: [{ text: '| #123 | 4:30 PM | 🔵 | Test | ~100 |' }] - }; - const fetchMock = mock(() => Promise.resolve({ - ok: true, - json: () => Promise.resolve(apiResponse) - } as Response)); - global.fetch = fetchMock; - - await updateFolderClaudeMdFiles( - ['src/utils/logger.ts'], - 'test-project', - 37777, - tempDir - ); - - expect(fetchMock).toHaveBeenCalledTimes(1); - }); -}); -``` - -### Phase 4: Update .gitignore - -**File:** `.gitignore` - -Add at end of file: - -```gitignore -# Prevent literal tilde directories (path validation bug artifacts) -~*/ - -# Prevent other malformed path directories -http*/ -https*/ -``` - -### Phase 5: Clean Up Invalid Directories - -**Command sequence:** -```bash -rm -rf "~/." -rm -rf "PR #610 on thedotmack" -rm -rf "git diff for src" -rm -rf "https:" -``` - -### Phase 6: Verify and Commit - -1. Run test suite: `npm test` -2. Run build: `npm run build` -3. Verify no invalid directories remain -4. Commit with message: `fix: Add path validation to CLAUDE.md distribution to prevent invalid directory creation` - ---- - -## Files Modified - -| File | Change | -|------|--------| -| `src/utils/claude-md-utils.ts` | Add `isValidPathForClaudeMd()` function + integrate in loop | -| `tests/utils/claude-md-utils.test.ts` | Add 6 new path validation tests | -| `.gitignore` | Add `~*/`, `http*/`, `https*/` patterns | - -## Files Deleted - -| Path | Reason | -|------|--------| -| `~/` (directory tree) | Invalid literal tilde directory | -| `PR #610 on thedotmack/` | Invalid PR reference directory | -| `git diff for src/` | Invalid git command directory | -| `https:/` | Invalid URL directory | - ---- - -## Risk Assessment - -**Low Risk:** -- Validation is additive (only skips invalid paths, doesn't change valid path handling) -- Existing tests remain unchanged -- Fire-and-forget design means failures are logged but don't break hooks - -**Testing Coverage:** -- 6 new unit tests covering all rejection cases -- Existing 27 tests verify valid path behavior unchanged diff --git a/.claude/plans/cleanup-worker-service-nonsense-logic.md b/.claude/plans/cleanup-worker-service-nonsense-logic.md deleted file mode 100644 index 4d0a7d14..00000000 --- a/.claude/plans/cleanup-worker-service-nonsense-logic.md +++ /dev/null @@ -1,314 +0,0 @@ -# Plan: Cleanup worker-service.ts Unjustified Logic - -**Created:** 2026-01-13 -**Source:** `docs/reports/nonsense-logic.md` -**Target:** `src/services/worker-service.ts` (813 lines) -**Goal:** Address 23 identified issues, prioritizing safe deletions first - ---- - -## Phase 0: Documentation Discovery (COMPLETED) - -### Evidence Gathered - -**Exit Code Strategy (CLAUDE.md:44-54):** -``` -- Exit 0: Success or graceful shutdown (Windows Terminal closes tabs) -- Exit 1: Non-blocking error -- Exit 2: Blocking error -Philosophy: Exit 0 prevents Windows Terminal tab accumulation -``` - -**Signal Handler Pattern (ProcessManager.ts:294-317):** -- Uses mutable reference object `isShuttingDownRef` -- Factory function `createSignalHandler()` returns handler with embedded state -- Current implementation has 3-hop indirection - -**MCP Client Pattern (worker-service.ts:157-160, ChromaSync.ts:124-136):** -```typescript -this.mcpClient = new Client({ - name: 'worker-search-proxy', - version: '1.0.0' -}, { capabilities: {} }); -``` - -**Verification Results:** -- `runInteractiveSetup` (lines 439-639): **NEVER CALLED** - grep shows only definition -- `import * as fs from 'fs'` (line 13): **UNUSED** - no `fs.` usage found -- `import { spawn } from 'child_process'` (line 14): **UNUSED** - no `spawn(` calls -- `homedir` (line 15): Only used in `runInteractiveSetup` (dead code) -- `processPendingQueues` default `= 10`: Never used, all callers pass explicit args - ---- - -## Phase 1: Safe Deletions (Dead Code & Unused Imports) - -### 1.1 Delete `runInteractiveSetup` Function - -**What:** Delete lines 435-639 (~201 lines) -**Why:** Function is defined but never called. Setup happens via `handleCursorCommand()`. -**Evidence:** `grep -n "runInteractiveSetup" src/services/worker-service.ts` returns only definition - -**Pattern to follow:** N/A - straight deletion - -**Steps:** -1. Read worker-service.ts lines 435-650 -2. Delete the entire function including section comment (lines 435-639) -3. Run `npm run build` to verify no compile errors - -**Verification:** -- `grep "runInteractiveSetup" src/` returns nothing -- Build succeeds - -### 1.2 Remove Unused Imports - -**What:** Delete lines 13, 14, 17 - -**Current (delete these):** -```typescript -import * as fs from 'fs'; // Line 13 - UNUSED -import { spawn } from 'child_process'; // Line 14 - UNUSED -import * as readline from 'readline'; // Line 17 - Only in dead code -``` - -**Keep:** -```typescript -import { homedir } from 'os'; // Line 15 - DELETE (only in dead code) -import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs'; // Line 16 - CHECK USAGE -``` - -**Steps:** -1. After deleting `runInteractiveSetup`, grep for remaining usages: - - `grep "homedir" src/services/worker-service.ts` - - `grep "readline" src/services/worker-service.ts` - - `grep "detectClaudeCode\|findCursorHooksDir\|installCursorHooks\|configureCursorMcp" src/services/worker-service.ts` -2. Delete imports with zero usages -3. Run `npm run build` - -**Verification:** -- No TypeScript unused import warnings -- Build succeeds - -### 1.3 Clean Up Cursor Integration Imports - -After deleting `runInteractiveSetup`, some CursorHooksInstaller imports become unused: -- `detectClaudeCode` - only in runInteractiveSetup -- `findCursorHooksDir` - only in runInteractiveSetup -- `installCursorHooks` - only in runInteractiveSetup -- `configureCursorMcp` - only in runInteractiveSetup - -**Steps:** -1. Grep each import after dead code removal -2. Remove any that are now unused -3. Keep `updateCursorContextForProject` (re-exported) and `handleCursorCommand` (used in main) - -**Verification:** -- `grep "detectClaudeCode\|findCursorHooksDir\|installCursorHooks\|configureCursorMcp" src/services/worker-service.ts` returns nothing -- Build succeeds - ---- - -## Phase 2: Low-Risk Simplifications - -### 2.1 Remove Unused Default Parameter - -**What:** Line 350 - `async processPendingQueues(sessionLimit: number = 10)` -**Why:** Default never used. All callers pass explicit args (50 in startup, dynamic in HTTP) - -**Change from:** -```typescript -async processPendingQueues(sessionLimit: number = 10): Promise<{...}> -``` - -**Change to:** -```typescript -async processPendingQueues(sessionLimit: number): Promise<{...}> -``` - -**Verification:** -- Build succeeds -- All call sites provide explicit values - -### 2.2 Simplify onRestart Callback - -**Location:** Lines 395-396 (approximate, find exact) -**Issue:** `onShutdown` and `onRestart` both call `this.shutdown()` - -**Find pattern:** -```typescript -onShutdown: () => this.shutdown(), -onRestart: () => this.shutdown() -``` - -**Options:** -1. **Keep as-is** if restart semantically differs from shutdown (future-proofing) -2. **Add comment** explaining intentional parity -3. **Remove onRestart** if never used differently - -**Investigation needed:** Grep for `onRestart` usage in Server.ts to understand contract - -**Steps:** -1. Grep `onRestart` in `src/services/server/` -2. If Server.ts treats them identically, add clarifying comment -3. If different, document why both map to shutdown - -### 2.3 Fix Over-Commented Lines (Sample Only) - -**Strategy:** Do NOT strip all comments. Only remove comments that describe obvious code. - -**Anti-pattern (remove):** -```typescript -// WHAT: Imports centralized logging utility with structured output -// WHY: All worker logs go through this for consistent formatting -import { logger } from '../utils/logger.js'; -``` - -**Pattern to follow:** Remove WHAT/WHY on simple imports. Keep architectural comments. - -**Scope:** Sample 5-10 obvious comment removals to demonstrate approach, not exhaustive - ---- - -## Phase 3: Medium-Risk Improvements - -### 3.1 Simplify Signal Handler Pattern - -**Current (worker-service.ts:180-192 + ProcessManager.ts:294-317):** -```typescript -// 3-hop indirection with mutable reference -const shutdownRef = { value: this.isShuttingDown }; -const handler = createSignalHandler(() => this.shutdown(), shutdownRef); -process.on('SIGTERM', () => { - this.isShuttingDown = shutdownRef.value; // Sync back - handler('SIGTERM'); -}); -``` - -**Simplified approach:** -```typescript -private registerSignalHandlers(): void { - const handler = async (signal: string) => { - if (this.isShuttingDown) { - logger.warn('SYSTEM', `Received ${signal} but shutdown already in progress`); - return; - } - this.isShuttingDown = true; - logger.info('SYSTEM', `Received ${signal}, shutting down...`); - try { - await this.shutdown(); - process.exit(0); - } catch (error) { - logger.error('SYSTEM', 'Error during shutdown', {}, error as Error); - process.exit(0); - } - }; - - process.on('SIGTERM', () => handler('SIGTERM')); - process.on('SIGINT', () => handler('SIGINT')); -} -``` - -**Decision needed:** Does `createSignalHandler` serve other callers? If yes, keep factory but simplify worker usage. - -**Steps:** -1. Grep `createSignalHandler` usage across codebase -2. If only worker-service uses it, inline and simplify -3. If shared, simplify worker's usage while keeping factory - -### 3.2 Unify Dual Initialization Tracking - -**Current (lines 111, 129-130):** -```typescript -private initializationCompleteFlag: boolean = false; -private initializationComplete: Promise; -``` - -**Recommendation:** Keep both but add clarifying comments: -- Promise: For async waiters (HTTP handlers) -- Flag: For sync checks (status endpoints) - -**Alternative:** Use Promise with inspection pattern: -```typescript -private initializationComplete = false; -private initializationPromise: Promise; -// Flag derived from promise state via finally() callback -``` - -**Steps:** -1. Add documentation comment explaining dual tracking purpose -2. Consider if flag can be derived from promise state instead - -### 3.3 Reduce 5-Minute Timeout - -**Location:** Lines 464-478 (approximate) -**Current:** `const timeoutMs = 300000; // 5 minutes` -**Recommendation:** Reduce to 30-60 seconds for HTTP handler, keep 5min for MCP init - -**Caution:** MCP initialization can legitimately be slow (ChromaDB, model loading). May need different timeouts per use case. - -**Steps:** -1. Find exact line for context inject timeout -2. Verify this is separate from MCP init timeout -3. Reduce HTTP handler timeout to 30-60 seconds -4. Keep MCP init timeout at 5 minutes - ---- - -## Phase 4: Deferred / Low Priority - -These items are noted but NOT part of this cleanup: - -| Issue | Reason to Defer | -|-------|-----------------| -| Exit code 0 always | Documented Windows Terminal workaround - intentional | -| Re-export for circular import | Works correctly, architectural fix is separate work | -| Fallback agent verification | Behavioral change, needs feature design | -| MCP version hardcoding | Low impact, separate version management issue | -| Empty capabilities | Documentation issue only | -| Unsafe `as Error` casts | Common TS pattern, low risk | - ---- - -## Phase 5: Verification - -### 5.1 Build Verification -```bash -npm run build -``` -Expected: No errors - -### 5.2 Test Suite -```bash -npm test -``` -Expected: All tests pass - -### 5.3 Grep for Anti-patterns -```bash -# Verify dead code removed -grep -r "runInteractiveSetup" src/ - -# Verify unused imports removed -grep "import \* as fs from 'fs'" src/services/worker-service.ts -grep "import { spawn }" src/services/worker-service.ts -``` -Expected: No matches - -### 5.4 Runtime Check -```bash -npm run build-and-sync -# Start worker and verify basic operation -``` - ---- - -## Summary - -| Phase | Items | Estimated Reduction | -|-------|-------|---------------------| -| Phase 1 | Dead code + unused imports | ~210 lines | -| Phase 2 | Low-risk simplifications | ~5 lines + clarity | -| Phase 3 | Medium-risk improvements | ~30 lines | -| Total | | ~245 lines (~30% reduction) | - -**Execution Order:** Phase 1 → Phase 2 → Phase 3 → Phase 5 (verification after each) diff --git a/.claude/plans/fix-claudemd-worktree-bug.md b/.claude/plans/fix-claudemd-worktree-bug.md deleted file mode 100644 index 4f139f3e..00000000 --- a/.claude/plans/fix-claudemd-worktree-bug.md +++ /dev/null @@ -1,516 +0,0 @@ -# Fix CLAUDE.md Worktree Bug - Implementation Plan - -## Problem Statement - -CLAUDE.md files are being written to the wrong directory when using git worktrees. The worker service writes files relative to its own `process.cwd()` instead of the project's working directory (`cwd`) from the observation. - -**Reproduction scenario:** -1. Start Claude Code in `budapest` worktree → worker starts with `cwd=budapest` -2. Run Claude Code in `~/Scripts/claude-mem/` (main repo) -3. Observations created with relative file paths (e.g., `src/utils/foo.ts`) -4. `updateFolderClaudeMdFiles` writes to `budapest/src/utils/CLAUDE.md` instead of main repo - -## Root Cause Analysis - -The `cwd` (project root path) IS captured and stored: -- `SessionRoutes.ts:309,403` - receives `cwd` from hooks -- `PendingMessageStore.ts:70` - stores `cwd` in database -- `SDKAgent.ts:295` - passes `cwd` to prompt builder - -But `cwd` is NOT passed to file writing: -- `ResponseProcessor.ts:222-225` - calls `updateFolderClaudeMdFiles(allFilePaths, session.project, port)` without `cwd` -- `claude-md-utils.ts:219` - uses `path.dirname(filePath)` which produces relative paths -- Relative paths resolve against worker's `process.cwd()`, not project root - ---- - -## Phase 0: Documentation & API Inventory - -### Allowed APIs (from codebase analysis) - -**File: `src/utils/claude-md-utils.ts`** -```typescript -export async function updateFolderClaudeMdFiles( - filePaths: string[], - project: string, - port: number -): Promise -``` - -**File: `src/sdk/parser.ts`** -```typescript -export interface ParsedObservation { - type: string; - title: string | null; - subtitle: string | null; - facts: string[]; - narrative: string | null; - concepts: string[]; - files_read: string[]; - files_modified: string[]; - // NOTE: Does NOT include cwd -} -``` - -**File: `src/services/worker-types.ts`** -```typescript -export interface PendingMessage { - type: 'observation' | 'summarize'; - tool_name?: string; - tool_input?: unknown; - tool_response?: unknown; - prompt_number?: number; - cwd?: string; // <-- Source of project root - last_assistant_message?: string; -} -``` - -**File: `src/shared/paths.ts`** - Path utilities -```typescript -import path from 'path'; -// Standard pattern: path.join(baseDir, relativePath) -``` - -### Anti-Patterns to Avoid - -1. **DO NOT** add `cwd` to `ParsedObservation` - it comes from message, not agent response -2. **DO NOT** use `process.cwd()` for project-specific paths -3. **DO NOT** assume file paths are absolute - they are relative from agent response -4. **DO NOT** modify the parser - file paths come from agent XML output - ---- - -## Phase 1: Add `projectRoot` Parameter to `updateFolderClaudeMdFiles` - -### What to implement - -Modify the function signature to accept an optional `projectRoot` parameter for resolving relative paths to absolute paths. - -### Files to modify - -**File: `src/utils/claude-md-utils.ts`** - -**Location: Lines 206-210 (function signature)** - -Current: -```typescript -export async function updateFolderClaudeMdFiles( - filePaths: string[], - project: string, - port: number -): Promise -``` - -New: -```typescript -export async function updateFolderClaudeMdFiles( - filePaths: string[], - project: string, - port: number, - projectRoot?: string -): Promise -``` - -**Location: Lines 215-228 (folder extraction logic)** - -Current: -```typescript -const folderPaths = new Set(); -for (const filePath of filePaths) { - if (!filePath || filePath === '') continue; - const folderPath = path.dirname(filePath); - if (folderPath && folderPath !== '.' && folderPath !== '/') { - if (isProjectRoot(folderPath)) { - logger.debug('FOLDER_INDEX', 'Skipping project root CLAUDE.md', { folderPath }); - continue; - } - folderPaths.add(folderPath); - } -} -``` - -New: -```typescript -const folderPaths = new Set(); -for (const filePath of filePaths) { - if (!filePath || filePath === '') continue; - - // Resolve relative paths to absolute using projectRoot - let absoluteFilePath = filePath; - if (projectRoot && !path.isAbsolute(filePath)) { - absoluteFilePath = path.join(projectRoot, filePath); - } - - const folderPath = path.dirname(absoluteFilePath); - if (folderPath && folderPath !== '.' && folderPath !== '/') { - if (isProjectRoot(folderPath)) { - logger.debug('FOLDER_INDEX', 'Skipping project root CLAUDE.md', { folderPath }); - continue; - } - folderPaths.add(folderPath); - } -} -``` - -### Documentation references - -- Pattern for `path.isAbsolute()`: Standard Node.js path module -- Pattern for `path.join(base, relative)`: Used throughout `src/shared/paths.ts` - -### Verification checklist - -1. [ ] `grep -n "updateFolderClaudeMdFiles" src/utils/claude-md-utils.ts` shows new signature -2. [ ] `grep -n "path.isAbsolute" src/utils/claude-md-utils.ts` confirms new check added -3. [ ] `grep -n "projectRoot" src/utils/claude-md-utils.ts` shows parameter usage -4. [ ] Existing callers still compile (optional param is backward compatible) - -### Anti-pattern guards - -- **DO NOT** make `projectRoot` required - breaks existing callers -- **DO NOT** use `process.cwd()` as default - defeats purpose of fix -- **DO NOT** modify the API endpoint format - path resolution is caller's responsibility - ---- - -## Phase 2: Pass `cwd` from Message to `updateFolderClaudeMdFiles` - -### What to implement - -Extract `cwd` from the original messages being processed and pass it to `updateFolderClaudeMdFiles`. - -### Challenge - -The `syncAndBroadcastObservations` function receives `ParsedObservation[]` which does NOT include `cwd`. The `cwd` is in the original `PendingMessage` but is consumed during prompt generation. - -### Solution - -Add `projectRoot` parameter to `syncAndBroadcastObservations` and `processAgentResponse`, sourced from `session` or passed through from message processing. - -### Files to modify - -**File: `src/services/worker/agents/ResponseProcessor.ts`** - -**Step 1: Update `processAgentResponse` signature (lines 46-55)** - -Current: -```typescript -export async function processAgentResponse( - text: string, - session: ActiveSession, - dbManager: DatabaseManager, - sessionManager: SessionManager, - worker: WorkerRef | undefined, - discoveryTokens: number, - originalTimestamp: number | null, - agentName: string -): Promise -``` - -New: -```typescript -export async function processAgentResponse( - text: string, - session: ActiveSession, - dbManager: DatabaseManager, - sessionManager: SessionManager, - worker: WorkerRef | undefined, - discoveryTokens: number, - originalTimestamp: number | null, - agentName: string, - projectRoot?: string -): Promise -``` - -**Step 2: Pass `projectRoot` to `syncAndBroadcastObservations` (line 101-109)** - -Current: -```typescript -await syncAndBroadcastObservations( - observations, - result, - session, - dbManager, - worker, - discoveryTokens, - agentName -); -``` - -New: -```typescript -await syncAndBroadcastObservations( - observations, - result, - session, - dbManager, - worker, - discoveryTokens, - agentName, - projectRoot -); -``` - -**Step 3: Update `syncAndBroadcastObservations` signature (lines 153-161)** - -Current: -```typescript -async function syncAndBroadcastObservations( - observations: ParsedObservation[], - result: StorageResult, - session: ActiveSession, - dbManager: DatabaseManager, - worker: WorkerRef | undefined, - discoveryTokens: number, - agentName: string -): Promise -``` - -New: -```typescript -async function syncAndBroadcastObservations( - observations: ParsedObservation[], - result: StorageResult, - session: ActiveSession, - dbManager: DatabaseManager, - worker: WorkerRef | undefined, - discoveryTokens: number, - agentName: string, - projectRoot?: string -): Promise -``` - -**Step 4: Update `updateFolderClaudeMdFiles` call (lines 222-229)** - -Current: -```typescript -if (allFilePaths.length > 0) { - updateFolderClaudeMdFiles( - allFilePaths, - session.project, - getWorkerPort() - ).catch(error => { - logger.warn('FOLDER_INDEX', 'CLAUDE.md update failed (non-critical)', { project: session.project }, error as Error); - }); -} -``` - -New: -```typescript -if (allFilePaths.length > 0) { - updateFolderClaudeMdFiles( - allFilePaths, - session.project, - getWorkerPort(), - projectRoot - ).catch(error => { - logger.warn('FOLDER_INDEX', 'CLAUDE.md update failed (non-critical)', { project: session.project }, error as Error); - }); -} -``` - -### Verification checklist - -1. [ ] `grep -n "projectRoot" src/services/worker/agents/ResponseProcessor.ts` shows parameter throughout -2. [ ] `grep -n "processAgentResponse" src/services/worker/*.ts` to find all callers -3. [ ] TypeScript compiles without errors - -### Anti-pattern guards - -- **DO NOT** extract `cwd` from `ParsedObservation` - it doesn't have one -- **DO NOT** store `cwd` on session globally - messages may come from different cwds (edge case) - ---- - -## Phase 3: Update Agent Callers to Pass `cwd` - -### What to implement - -Update SDKAgent, GeminiAgent, and OpenRouterAgent to pass `message.cwd` to `processAgentResponse`. - -### Files to modify - -**File: `src/services/worker/SDKAgent.ts`** - -Find the `processAgentResponse` call and add the `projectRoot` parameter from `message.cwd`. - -**Pattern to follow (from SDKAgent.ts:289-296):** -```typescript -const obsPrompt = buildObservationPrompt({ - id: 0, - tool_name: message.tool_name!, - tool_input: JSON.stringify(message.tool_input), - tool_output: JSON.stringify(message.tool_response), - created_at_epoch: Date.now(), - cwd: message.cwd // <-- This is available -}); -``` - -**Challenge:** `processAgentResponse` is called after the SDK response, not in the message loop. Need to track `lastCwd` from messages. - -**Solution:** Store `lastCwd` from messages being processed and pass to `processAgentResponse`. - -**File: `src/services/worker/GeminiAgent.ts`** - Same pattern -**File: `src/services/worker/OpenRouterAgent.ts`** - Same pattern - -### Implementation pattern for each agent - -Add tracking variable: -```typescript -let lastCwd: string | undefined; -``` - -In message loop, capture cwd: -```typescript -if (message.cwd) { - lastCwd = message.cwd; -} -``` - -In `processAgentResponse` call: -```typescript -await processAgentResponse( - responseText, - session, - this.dbManager, - this.sessionManager, - worker, - discoveryTokens, - originalTimestamp, - 'SDK', // or 'Gemini' or 'OpenRouter' - lastCwd -); -``` - -### Verification checklist - -1. [ ] `grep -n "lastCwd" src/services/worker/SDKAgent.ts` shows tracking -2. [ ] `grep -n "lastCwd" src/services/worker/GeminiAgent.ts` shows tracking -3. [ ] `grep -n "lastCwd" src/services/worker/OpenRouterAgent.ts` shows tracking -4. [ ] `grep -n "processAgentResponse.*lastCwd" src/services/worker/` shows all calls updated - -### Anti-pattern guards - -- **DO NOT** use `session.cwd` - sessions can have messages from multiple cwds -- **DO NOT** default to `process.cwd()` - defeats the fix - ---- - -## Phase 4: Update Tests - -### What to implement - -Update existing tests and add new tests for the `projectRoot` functionality. - -### Files to modify - -**File: `tests/utils/claude-md-utils.test.ts`** - -Add test cases for: -1. Relative paths with `projectRoot` resolve correctly -2. Absolute paths ignore `projectRoot` -3. Missing `projectRoot` maintains backward compatibility - -### Test pattern to copy - -From `tests/utils/claude-md-utils.test.ts:245-266` (folder deduplication test): -```typescript -it('should deduplicate folders from multiple files', async () => { - mockFetch.mockResolvedValue({ - ok: true, - json: () => Promise.resolve({ content: [{ text: mockApiResponse }] }) - }); - - await updateFolderClaudeMdFiles( - ['/project/src/utils/file1.ts', '/project/src/utils/file2.ts'], - 'test-project', - 37777 - ); - - // Should only call API once for the deduplicated folder - expect(mockFetch).toHaveBeenCalledTimes(1); -}); -``` - -### New test to add - -```typescript -it('should resolve relative paths using projectRoot', async () => { - mockFetch.mockResolvedValue({ - ok: true, - json: () => Promise.resolve({ content: [{ text: mockApiResponse }] }) - }); - - await updateFolderClaudeMdFiles( - ['src/utils/file.ts'], // relative path - 'test-project', - 37777, - '/home/user/my-project' // projectRoot - ); - - // Should write to absolute path /home/user/my-project/src/utils/CLAUDE.md - expect(mockWriteClaudeMd).toHaveBeenCalledWith( - '/home/user/my-project/src/utils', - expect.any(String) - ); -}); -``` - -### Verification checklist - -1. [ ] `bun test tests/utils/claude-md-utils.test.ts` passes -2. [ ] New test case for `projectRoot` exists and passes - ---- - -## Phase 5: Final Verification - -### Verification commands - -```bash -# 1. Confirm new parameter exists -grep -n "projectRoot" src/utils/claude-md-utils.ts -grep -n "projectRoot" src/services/worker/agents/ResponseProcessor.ts -grep -n "lastCwd" src/services/worker/SDKAgent.ts - -# 2. Confirm path.isAbsolute check added -grep -n "path.isAbsolute" src/utils/claude-md-utils.ts - -# 3. Confirm all agents updated -grep -n "processAgentResponse.*lastCwd" src/services/worker/*.ts - -# 4. Run tests -bun test tests/utils/claude-md-utils.test.ts - -# 5. Build and verify no TypeScript errors -npm run build -``` - -### Anti-pattern grep checks - -```bash -# Should NOT find process.cwd() in updateFolderClaudeMdFiles path logic -grep -n "process.cwd" src/utils/claude-md-utils.ts - -# Should NOT find cwd in ParsedObservation interface -grep -A 10 "interface ParsedObservation" src/sdk/parser.ts | grep cwd -``` - -### Manual testing - -1. Start worker in one directory -2. Run Claude Code in a different directory (worktree) -3. Make a code change that creates an observation -4. Verify CLAUDE.md is written to the correct project directory - ---- - -## Summary of Changes - -| File | Change | -|------|--------| -| `src/utils/claude-md-utils.ts` | Add `projectRoot` param, resolve relative paths | -| `src/services/worker/agents/ResponseProcessor.ts` | Pass `projectRoot` through call chain | -| `src/services/worker/SDKAgent.ts` | Track `lastCwd`, pass to `processAgentResponse` | -| `src/services/worker/GeminiAgent.ts` | Track `lastCwd`, pass to `processAgentResponse` | -| `src/services/worker/OpenRouterAgent.ts` | Track `lastCwd`, pass to `processAgentResponse` | -| `tests/utils/claude-md-utils.test.ts` | Add tests for `projectRoot` behavior | diff --git a/.claude/plans/fix-empty-claude-md-files.md b/.claude/plans/fix-empty-claude-md-files.md deleted file mode 100644 index f7744918..00000000 --- a/.claude/plans/fix-empty-claude-md-files.md +++ /dev/null @@ -1,266 +0,0 @@ -# Plan: Fix Empty CLAUDE.md File Generation - -## Problem Statement - -Currently the CLAUDE.md generator creates files with wasteful content: -1. **Empty files with "No recent activity"** - Files are created even when there are zero observations for a folder -2. **Redundant HTML comment** - "" is unnecessary since the `` tag already conveys this information - -These issues create noisy, wasteful context that loads automatically and provides no value. - -## Phase 0: Documentation Discovery - -### Allowed APIs (from code analysis) -- `formatTimelineForClaudeMd(timelineText: string): string` - src/utils/claude-md-utils.ts:139 -- `formatObservationsForClaudeMd(observations, folderPath): string` - scripts/regenerate-claude-md.ts:238 -- `writeClaudeMdToFolder(folderPath, newContent): void` - src/utils/claude-md-utils.ts:94 -- `updateFolderClaudeMdFiles(filePaths, project, port, projectRoot): Promise` - src/utils/claude-md-utils.ts:257 -- `replaceTaggedContent(existingContent, newContent): string` - src/utils/claude-md-utils.ts:64 - -### Key Locations -| File | Lines | Purpose | -|------|-------|---------| -| `src/utils/claude-md-utils.ts` | 139-235 | Main formatting function | -| `src/utils/claude-md-utils.ts` | 143 | HTML comment generation | -| `src/utils/claude-md-utils.ts` | 209-211 | "No recent activity" handling | -| `src/utils/claude-md-utils.ts` | 322-323 | Write decision point | -| `scripts/regenerate-claude-md.ts` | 238-286 | Regeneration script formatting | -| `scripts/regenerate-claude-md.ts` | 242 | HTML comment generation (duplicate) | -| `scripts/regenerate-claude-md.ts` | 245-247 | "No recent activity" handling | -| `scripts/regenerate-claude-md.ts` | 452-453 | Write decision point | -| `tests/utils/claude-md-utils.test.ts` | 96-109 | Tests for "No recent activity" behavior | - -### Anti-patterns to avoid -- Do NOT add new configuration options for this behavior - just fix it -- Do NOT add logging for skipped files (unnecessary noise) - ---- - -## Phase 1: Modify formatTimelineForClaudeMd to Return Empty on No Observations - -### Task 1.1: Update formatTimelineForClaudeMd return behavior -**File:** `src/utils/claude-md-utils.ts` -**Lines:** 139-235 - -**Changes:** -1. Remove HTML comment line at line 143 -2. Change the empty observations case (lines 209-211) to return an empty string instead of "No recent activity" - -**Before (lines 141-144):** -```typescript -lines.push('# Recent Activity'); -lines.push(''); -lines.push(''); -lines.push(''); -``` - -**After:** -```typescript -lines.push('# Recent Activity'); -lines.push(''); -``` - -**Before (lines 209-212):** -```typescript -if (observations.length === 0) { - lines.push('*No recent activity*'); - return lines.join('\n'); -} -``` - -**After:** -```typescript -if (observations.length === 0) { - return ''; -} -``` - -### Verification -- Run `bun test tests/utils/claude-md-utils.test.ts` -- Tests at lines 96-109 will FAIL (expected - they test for "No recent activity") -- Update tests to expect empty string for empty input - ---- - -## Phase 2: Update updateFolderClaudeMdFiles to Skip Empty Content - -### Task 2.1: Add empty content check before writing -**File:** `src/utils/claude-md-utils.ts` -**Lines:** 322-323 - -**Changes:** -After formatting, check if result is empty and skip writing if so. - -**Before (lines 321-325):** -```typescript -const formatted = formatTimelineForClaudeMd(result.content[0].text); -writeClaudeMdToFolder(folderPath, formatted); - -logger.debug('FOLDER_INDEX', 'Updated CLAUDE.md', { folderPath }); -``` - -**After:** -```typescript -const formatted = formatTimelineForClaudeMd(result.content[0].text); -if (!formatted) { - logger.debug('FOLDER_INDEX', 'No observations for folder, skipping', { folderPath }); - continue; -} -writeClaudeMdToFolder(folderPath, formatted); - -logger.debug('FOLDER_INDEX', 'Updated CLAUDE.md', { folderPath }); -``` - -### Verification -- Grep for files containing "No recent activity": should find none after running - ---- - -## Phase 3: Update Regeneration Script - -### Task 3.1: Remove HTML comment from formatObservationsForClaudeMd -**File:** `scripts/regenerate-claude-md.ts` -**Lines:** 238-286 - -**Changes:** -1. Remove HTML comment line at line 242 -2. Change empty observations case (lines 245-247) to return empty string - -**Before (lines 240-244):** -```typescript -lines.push('# Recent Activity'); -lines.push(''); -lines.push(''); -lines.push(''); -``` - -**After:** -```typescript -lines.push('# Recent Activity'); -lines.push(''); -``` - -**Before (lines 245-248):** -```typescript -if (observations.length === 0) { - lines.push('*No recent activity*'); - return lines.join('\n'); -} -``` - -**After:** -```typescript -if (observations.length === 0) { - return ''; -} -``` - -### Task 3.2: Update regenerateFolder to handle empty formatted content -**File:** `scripts/regenerate-claude-md.ts` -**Lines:** 432-459 - -The script already skips folders with no observations (lines 443-444), so this change is already compatible. The `formatObservationsForClaudeMd` returning empty string doesn't change behavior since observations are checked before calling it. - -### Verification -- Run `bun scripts/regenerate-claude-md.ts --dry-run` in the project -- Should NOT show any folders with 0 observations - ---- - -## Phase 4: Update Tests - -### Task 4.1: Update tests for new empty behavior -**File:** `tests/utils/claude-md-utils.test.ts` -**Lines:** 96-109 - -**Changes:** -Update the two tests that expect "No recent activity" to expect empty string instead. - -**Before (lines 96-101):** -```typescript -it('should return "No recent activity" for empty input', () => { - const result = formatTimelineForClaudeMd(''); - - expect(result).toContain('# Recent Activity'); - expect(result).toContain('*No recent activity*'); -}); -``` - -**After:** -```typescript -it('should return empty string for empty input', () => { - const result = formatTimelineForClaudeMd(''); - - expect(result).toBe(''); -}); -``` - -**Before (lines 103-109):** -```typescript -it('should return "No recent activity" when no table rows exist', () => { - const input = 'Just some plain text without table rows'; - - const result = formatTimelineForClaudeMd(input); - - expect(result).toContain('*No recent activity*'); -}); -``` - -**After:** -```typescript -it('should return empty string when no table rows exist', () => { - const input = 'Just some plain text without table rows'; - - const result = formatTimelineForClaudeMd(input); - - expect(result).toBe(''); -}); -``` - -### Task 4.2: Remove HTML comment assertions from any other tests -Search for tests that assert on "auto-generated" comment and update accordingly. - -### Verification -- Run full test suite: `bun test` -- All tests should pass - ---- - -## Phase 5: Cleanup Existing Empty Files - -### Task 5.1: Run cleanup to remove existing empty CLAUDE.md files -**Command:** -```bash -bun scripts/regenerate-claude-md.ts --clean -``` - -This will: -- Find all CLAUDE.md files with `` tags -- Strip the tagged section -- Delete files that become empty after stripping -- Preserve files that have user content outside the tags - -### Verification -- `grep -r "No recent activity" . --include="CLAUDE.md"` should return no results -- `grep -r "auto-generated by claude-mem" . --include="CLAUDE.md"` should return no results - ---- - -## Summary of Changes - -| File | Change | -|------|--------| -| `src/utils/claude-md-utils.ts:143` | Remove HTML comment line | -| `src/utils/claude-md-utils.ts:209-211` | Return empty string instead of "No recent activity" | -| `src/utils/claude-md-utils.ts:322` | Skip writing if formatted content is empty | -| `scripts/regenerate-claude-md.ts:242` | Remove HTML comment line | -| `scripts/regenerate-claude-md.ts:245-247` | Return empty string instead of "No recent activity" | -| `tests/utils/claude-md-utils.test.ts:96-109` | Update tests to expect empty string | - -## Final Verification Checklist -- [ ] `bun test` passes -- [ ] No "No recent activity" CLAUDE.md files exist -- [ ] No "auto-generated" comments in CLAUDE.md files -- [ ] Build succeeds: `npm run build-and-sync` -- [ ] New observations correctly generate CLAUDE.md files with content -- [ ] Folders without observations get no CLAUDE.md file created diff --git a/.claude/plans/fix-stale-session-resume-crash.md b/.claude/plans/fix-stale-session-resume-crash.md deleted file mode 100644 index 5d19332d..00000000 --- a/.claude/plans/fix-stale-session-resume-crash.md +++ /dev/null @@ -1,252 +0,0 @@ -# Plan: Fix Stale Session Resume Crash - -## Problem Summary - -The worker crashes repeatedly with "Claude Code process exited with code 1" when attempting to resume into a stale/non-existent SDK session. - -**Root Cause:** In `SDKAgent.ts:94`, the resume parameter is passed whenever `memorySessionId` exists in the database, regardless of whether this is an INIT prompt or CONTINUATION prompt. When a worker restarts or re-initializes a session, it loads a stale `memorySessionId` from a previous SDK session and tries to resume into a session that no longer exists in Claude's context. - -**Evidence from logs:** -``` -[17:30:21.773] Starting SDK query { - hasRealMemorySessionId=true, ← DB has old memorySessionId - resume_parameter=5439891b-..., ← Trying to resume with it - lastPromptNumber=1 ← But this is a NEW SDK session! -} -[17:30:24.450] Generator failed {error=Claude Code process exited with code 1} -``` - ---- - -## Phase 0: Documentation Discovery (COMPLETED) - -### Allowed APIs (from subagent research) - -**V1 SDK API (currently used):** -```typescript -// From @anthropic-ai/claude-agent-sdk -function query(options: { - prompt: string | AsyncIterable; - options: { - model: string; - resume?: string; // SESSION ID - only use for CONTINUATION - disallowedTools?: string[]; - abortController?: AbortController; - pathToClaudeCodeExecutable?: string; - } -}): AsyncIterable -``` - -**Resume Parameter Rules (from docs/context/agent-sdk-v2-preview.md and SESSION_ID_ARCHITECTURE.md):** -- `resume` should only be used when continuing an existing SDK conversation -- For INIT prompts (first prompt in a fresh SDK session), no resume parameter should be passed -- Session ID is captured from first SDK message and stored for subsequent prompts - -### Anti-Patterns to Avoid -- Passing `resume` parameter with INIT prompts (causes crash) -- Using `contentSessionId` for resume (contaminates user session) -- Assuming memorySessionId validity without checking prompt context - ---- - -## Phase 1: Fix the Resume Parameter Logic - -### What to Implement - -Modify `src/services/worker/SDKAgent.ts` line 94 to check BOTH conditions: -1. `hasRealMemorySessionId` - memorySessionId exists and is non-null -2. `session.lastPromptNumber > 1` - this is a CONTINUATION, not an INIT prompt - -### Current Code (line 89-99): -```typescript -const queryResult = query({ - prompt: messageGenerator, - options: { - model: modelId, - // Resume with captured memorySessionId (null on first prompt, real ID on subsequent) - ...(hasRealMemorySessionId && { resume: session.memorySessionId }), - disallowedTools, - abortController: session.abortController, - pathToClaudeCodeExecutable: claudePath - } -}); -``` - -### Fixed Code: -```typescript -const queryResult = query({ - prompt: messageGenerator, - options: { - model: modelId, - // Only resume if BOTH: (1) we have a memorySessionId AND (2) this isn't the first prompt - // On worker restart, memorySessionId may exist from a previous SDK session but we - // need to start fresh since the SDK context was lost - ...(hasRealMemorySessionId && session.lastPromptNumber > 1 && { resume: session.memorySessionId }), - disallowedTools, - abortController: session.abortController, - pathToClaudeCodeExecutable: claudePath - } -}); -``` - -### Also Update the Comment at Line 66-68: -```typescript -// CRITICAL: Only resume if: -// 1. memorySessionId exists (was captured from a previous SDK response) -// 2. lastPromptNumber > 1 (this is a continuation within the same SDK session) -// On worker restart or crash recovery, memorySessionId may exist from a previous -// SDK session but we must NOT resume because the SDK context was lost. -// NEVER use contentSessionId for resume - that would inject messages into the user's transcript! -``` - -### Verification Checklist -- [ ] `grep "hasRealMemorySessionId && session.lastPromptNumber > 1" src/services/worker/SDKAgent.ts` returns the fix -- [ ] Build succeeds: `npm run build` -- [ ] No TypeScript errors - ---- - -## Phase 2: Add Logging for Debugging - -### What to Implement - -Enhance the alignment log at line 81-85 to clearly indicate when resume is skipped due to INIT prompt: - -```typescript -// Debug-level alignment logs for detailed tracing -if (session.lastPromptNumber > 1) { - const willResume = hasRealMemorySessionId; - logger.debug('SDK', `[ALIGNMENT] Resume Decision | contentSessionId=${session.contentSessionId} | memorySessionId=${session.memorySessionId} | prompt#=${session.lastPromptNumber} | hasRealMemorySessionId=${hasRealMemorySessionId} | willResume=${willResume} | resumeWith=${willResume ? session.memorySessionId : 'NONE'}`); -} else { - // INIT prompt - never resume even if memorySessionId exists (stale from previous session) - const hasStaleMemoryId = hasRealMemorySessionId; - logger.debug('SDK', `[ALIGNMENT] First Prompt (INIT) | contentSessionId=${session.contentSessionId} | prompt#=${session.lastPromptNumber} | hasStaleMemoryId=${hasStaleMemoryId} | action=START_FRESH | Will capture new memorySessionId from SDK response`); - if (hasStaleMemoryId) { - logger.warn('SDK', `Skipping resume for INIT prompt despite existing memorySessionId=${session.memorySessionId} - SDK context was lost (worker restart or crash recovery)`); - } -} -``` - -### Verification Checklist -- [ ] Build succeeds: `npm run build` -- [ ] Log message appears when running with stale session scenario - ---- - -## Phase 3: Add Unit Tests - -### What to Implement - -Create tests in `tests/sdk-agent-resume.test.ts` following patterns from `tests/session_id_usage_validation.test.ts`: - -```typescript -import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'; - -describe('SDKAgent Resume Parameter Logic', () => { - describe('hasRealMemorySessionId check', () => { - it('should NOT pass resume parameter when lastPromptNumber === 1 even if memorySessionId exists', () => { - // Scenario: Worker restart with stale memorySessionId - const session = { - memorySessionId: 'stale-session-id-from-previous-run', - lastPromptNumber: 1, // INIT prompt - }; - - const hasRealMemorySessionId = !!session.memorySessionId; - const shouldResume = hasRealMemorySessionId && session.lastPromptNumber > 1; - - expect(hasRealMemorySessionId).toBe(true); // memorySessionId exists - expect(shouldResume).toBe(false); // but should NOT resume - }); - - it('should pass resume parameter when lastPromptNumber > 1 AND memorySessionId exists', () => { - // Scenario: Normal continuation within same SDK session - const session = { - memorySessionId: 'valid-session-id', - lastPromptNumber: 2, // CONTINUATION prompt - }; - - const hasRealMemorySessionId = !!session.memorySessionId; - const shouldResume = hasRealMemorySessionId && session.lastPromptNumber > 1; - - expect(hasRealMemorySessionId).toBe(true); - expect(shouldResume).toBe(true); - }); - - it('should NOT pass resume parameter when memorySessionId is null', () => { - // Scenario: Fresh session, no captured ID yet - const session = { - memorySessionId: null, - lastPromptNumber: 1, - }; - - const hasRealMemorySessionId = !!session.memorySessionId; - const shouldResume = hasRealMemorySessionId && session.lastPromptNumber > 1; - - expect(hasRealMemorySessionId).toBe(false); - expect(shouldResume).toBe(false); - }); - }); -}); -``` - -### Documentation Reference -- Pattern: `tests/session_id_usage_validation.test.ts` lines 1-50 for test structure -- Mock pattern: `tests/worker/agents/response-processor.test.ts` for session mocking - -### Verification Checklist -- [ ] Tests pass: `bun test tests/sdk-agent-resume.test.ts` -- [ ] Test file follows project conventions - ---- - -## Phase 4: Build and Deploy - -### What to Implement - -1. Build the plugin: `npm run build-and-sync` -2. Verify worker restarts with fix applied - -### Verification Checklist -- [ ] `npm run build-and-sync` succeeds -- [ ] Worker health check passes: `curl http://localhost:37777/api/health` -- [ ] No "Claude Code process exited with code 1" errors in logs after restart - ---- - -## Phase 5: Final Verification - -### Verification Commands - -```bash -# 1. Verify fix is in place -grep -n "hasRealMemorySessionId && session.lastPromptNumber > 1" src/services/worker/SDKAgent.ts - -# 2. Verify no crashes in recent logs -tail -100 ~/.claude-mem/logs/claude-mem-$(date +%Y-%m-%d).log | grep -c "exited with code 1" - -# 3. Run tests -bun test tests/sdk-agent-resume.test.ts - -# 4. Check for anti-patterns (should return 0 results) -grep -n "hasRealMemorySessionId && { resume" src/services/worker/SDKAgent.ts -``` - -### Success Criteria -- [ ] Fix in place at SDKAgent.ts:94 -- [ ] Zero "exited with code 1" errors related to stale resume -- [ ] All tests pass -- [ ] Worker stable for 10+ minutes without crash loop - ---- - -## Files to Modify - -1. `src/services/worker/SDKAgent.ts` - Fix resume logic (Phase 1 & 2) -2. `tests/sdk-agent-resume.test.ts` - New test file (Phase 3) - -## Estimated Complexity - -- **Phase 1**: Low - Single line change with updated condition -- **Phase 2**: Low - Enhanced logging -- **Phase 3**: Medium - New test file following existing patterns -- **Phase 4-5**: Low - Standard build/verify process diff --git a/.claude/plans/folder-claude-md-generator.md b/.claude/plans/folder-claude-md-generator.md deleted file mode 100644 index e73c1048..00000000 --- a/.claude/plans/folder-claude-md-generator.md +++ /dev/null @@ -1,298 +0,0 @@ -# Folder CLAUDE.md Generator - -## CORE DIRECTIVE (NON-NEGOTIABLE) - -**EXTEND THE EXISTING CURSOR RULES TIMELINE GENERATION SYSTEM TO ALSO WRITE CLAUDE.MD FILES** - -- DO NOT create new services -- DO NOT create new orchestrators -- DO NOT create new HTTP routes -- DO NOT create new database query functions -- EXTEND existing functions to add folder-level output - ---- - -## Approved Directives (From Planning Conversation) - -### Trigger Mechanism -- Observation save triggers folder CLAUDE.md regeneration **INLINE** -- NO batching -- NO debouncing -- NO Set-based queuing -- NO session-end hook -- Synchronous: `observation.save()` → update folder CLAUDE.md files → done - -### Tag Strategy -- Wrap ONLY auto-generated content with `` tags -- Everything outside tags is untouched (user's manual content preserved) -- If tags are deleted, just regenerate them -- NO backup system -- NO manual content markers - -### Git Behavior -- CLAUDE.md files SHOULD be committed (intentional) -- `` tag is searchable fingerprint for GitHub analytics -- NO .gitignore for these files - -### Phasing -- **Phase 1**: CLAUDE.md generation only (THIS PLAN) -- **Phase 2**: IDE symlinks (FUTURE) - -### REJECTED -- Cross-folder linking — NO -- Semantic grouping — deferred enhancement only -- Team sync — future phase - -### DEFERRED -- Priority weighting by observation type -- IDE-specific template refinements - ---- - -## Phase 0: Documentation Discovery (COMPLETED) - -### Existing APIs to USE (Not Rebuild) - -| Function | Location | Purpose | -|----------|----------|---------| -| `findByFile(filePath, options)` | `src/services/sqlite/SessionSearch.ts:342` | Query observations by folder prefix (already supports LIKE wildcards) | -| `updateCursorContextForProject()` | `src/services/integrations/CursorHooksInstaller.ts:98` | Write context files after observation save | -| `writeContextFile()` | `src/utils/cursor-utils.ts:97` | Atomic file write with temp file + rename | -| `extractFirstFile()` | `src/shared/timeline-formatting.ts` | Extract file paths from JSON arrays | -| `groupByDate()` | `src/shared/timeline-formatting.ts` | Group items chronologically | -| `formatTime()`, `formatDate()` | `src/shared/timeline-formatting.ts` | Time formatting | - -### Existing Integration Points - -| Location | What Happens | Extension Point | -|----------|--------------|-----------------| -| `ResponseProcessor.ts:266` | Calls `updateCursorContextForProject()` after summary save | Add folder CLAUDE.md update here | -| `CursorHooksInstaller.ts:98` | `updateCursorContextForProject()` fetches context and writes file | Add sibling function for folder updates | - -### Anti-Patterns to AVOID -- Creating `FolderIndexOrchestrator.ts` — NO -- Creating `FolderTimelineCompiler.ts` — NO -- Creating `FolderDiscovery.ts` — NO -- Creating `ClaudeMdGenerator.ts` — NO -- Creating `FolderIndexRoutes.ts` — NO -- Adding new HTTP endpoints — NO -- Adding new settings in `SettingsDefaultsManager.ts` — NO (use sensible defaults inline) - ---- - -## Phase 1: Extend CursorHooksInstaller - -### What to Implement - -Add ONE new function to `src/services/integrations/CursorHooksInstaller.ts`: - -```typescript -/** - * Update CLAUDE.md files for folders touched by an observation. - * Called inline after observation save, similar to updateCursorContextForProject. - */ -export async function updateFolderClaudeMd( - workspacePath: string, - filesModified: string[], - filesRead: string[], - project: string, - port: number -): Promise -``` - -### Implementation Pattern (Copy From) - -Follow the EXACT pattern of `updateCursorContextForProject()` at line 98: -1. Extract unique folder paths from filesModified and filesRead -2. For each folder, fetch timeline via existing `/api/search/file?files=` endpoint -3. Format as simple timeline (reuse existing formatters) -4. Write to `/CLAUDE.md` preserving content outside `` tags - -### Tag Preservation Logic - -```typescript -function replaceTaggedContent(existingContent: string, newContent: string): string { - const startTag = ''; - const endTag = ''; - - // If no existing content, wrap new content in tags - if (!existingContent) { - return `${startTag}\n${newContent}\n${endTag}`; - } - - // If existing has tags, replace only tagged section - const startIdx = existingContent.indexOf(startTag); - const endIdx = existingContent.indexOf(endTag); - - if (startIdx !== -1 && endIdx !== -1) { - return existingContent.substring(0, startIdx) + - `${startTag}\n${newContent}\n${endTag}` + - existingContent.substring(endIdx + endTag.length); - } - - // If no tags exist, append tagged content at end - return existingContent + `\n\n${startTag}\n${newContent}\n${endTag}`; -} -``` - -### Verification Checklist -- [ ] Function added to CursorHooksInstaller.ts -- [ ] Uses existing `findByFile` endpoint (no new database queries) -- [ ] Preserves content outside `` tags -- [ ] Atomic writes (temp file + rename) -- [ ] Build passes: `npm run build` - ---- - -## Phase 2: Hook Into ResponseProcessor - -### What to Implement - -Add call to `updateFolderClaudeMd()` in `src/services/worker/agents/ResponseProcessor.ts`, right after the existing `updateCursorContextForProject()` call at line 266. - -### Code Location - -In `syncAndBroadcastSummary()` function, after line 269: - -```typescript -// EXISTING: Update Cursor context file for registered projects (fire-and-forget) -updateCursorContextForProject(session.project, getWorkerPort()).catch(error => { - logger.warn('CURSOR', 'Context update failed (non-critical)', { project: session.project }, error as Error); -}); - -// NEW: Update folder CLAUDE.md files for touched folders (fire-and-forget) -// Extract file paths from the saved observations -updateFolderClaudeMd( - workspacePath, // From registry lookup - filesModified, // From observations - filesRead, // From observations - session.project, - getWorkerPort() -).catch(error => { - logger.warn('FOLDER_INDEX', 'CLAUDE.md update failed (non-critical)', { project: session.project }, error as Error); -}); -``` - -### Data Flow -1. `processAgentResponse()` saves observations → gets back `observationIds` -2. Fetch observation records to get `files_read` and `files_modified` -3. Pass to `updateFolderClaudeMd()` - -### Verification Checklist -- [ ] Call added to ResponseProcessor.ts -- [ ] Fire-and-forget pattern (non-blocking, errors logged) -- [ ] Uses existing observation data (no new queries) -- [ ] Build passes: `npm run build` - ---- - -## Phase 3: Timeline Formatting - -### What to Implement - -Create a minimal timeline formatter for CLAUDE.md output. This can be: -1. A simple function in CursorHooksInstaller.ts, OR -2. Reuse existing `ResultFormatter.formatSearchResults()` from `src/services/worker/search/ResultFormatter.ts` - -### Output Format - -```markdown -# Recent Activity - - - - -### 2026-01-04 - -| Time | Type | Title | -|------|------|-------| -| 4:30pm | feature | Added folder index support | -| 3:15pm | bugfix | Fixed file path handling | - -### 2026-01-03 - -| Time | Type | Title | -|------|------|-------| -| 11:00am | refactor | Cleaned up cursor utils | - - -``` - -### Key Points -- Compact format (time, type emoji, title only) -- Grouped by date -- Limited to last N days or observations (sensible default: 10) -- NO token counts -- NO file columns (redundant - we're IN the folder) - -### Verification Checklist -- [ ] Formatter produces clean markdown -- [ ] Output is concise (not verbose) -- [ ] Grouped by date -- [ ] Build passes: `npm run build` - ---- - -## Phase 4: Verification - -### Functional Tests - -1. **Manual Test**: - - Start worker: `npm run dev` - - Create a test observation touching `src/services/sqlite/` - - Verify `src/services/sqlite/CLAUDE.md` is created/updated - - Verify `` tags are present - - Verify manual content outside tags is preserved - -2. **Build Check**: - ```bash - npm run build - ``` - -3. **Grep for Anti-Patterns**: - ```bash - # Should find NOTHING - grep -r "FolderIndexOrchestrator" src/ - grep -r "FolderTimelineCompiler" src/ - grep -r "FolderDiscovery" src/ - grep -r "ClaudeMdGenerator" src/ - grep -r "FolderIndexRoutes" src/ - ``` - -4. **Grep for Correct Implementation**: - ```bash - # Should find the new function - grep -r "updateFolderClaudeMd" src/ - ``` - -### Tag Preservation Test - -1. Create `src/test-folder/CLAUDE.md` with manual content: - ```markdown - # My Notes - This is manual content I wrote. - ``` - -2. Trigger observation save touching files in `src/test-folder/` - -3. Verify result: - ```markdown - # My Notes - This is manual content I wrote. - - - ### 2026-01-04 - | Time | Type | Title | - ... - - ``` - ---- - -## Summary - -This is a **~100 line change** spread across 2 files: -1. `CursorHooksInstaller.ts` — Add `updateFolderClaudeMd()` function (~60 lines) -2. `ResponseProcessor.ts` — Add call to the new function (~10 lines) - -NO new files. NO new services. NO new routes. Just extending existing patterns. diff --git a/.claude/plans/folder-claude-md-refactor.md b/.claude/plans/folder-claude-md-refactor.md deleted file mode 100644 index d8028d36..00000000 --- a/.claude/plans/folder-claude-md-refactor.md +++ /dev/null @@ -1,378 +0,0 @@ -# Folder CLAUDE.md Refactor - Extract to Shared Utils - -## CORE DIRECTIVE - -**DECOUPLE FOLDER CLAUDE.MD WRITING FROM CURSOR INTEGRATION** - -The current implementation incorrectly couples folder-level CLAUDE.md generation to Cursor-specific registry lookups. The file paths from observations are already absolute - no workspace registry lookup is needed. - ---- - -## Phase 0: Documentation Discovery (COMPLETED) - -### Current Implementation Location - -| Function | Location | Lines | Purpose | -|----------|----------|-------|---------| -| `updateFolderClaudeMd` | CursorHooksInstaller.ts | 128-199 | Orchestrates folder CLAUDE.md updates | -| `formatTimelineForClaudeMd` | CursorHooksInstaller.ts | 221-295 | Parses API response to markdown | -| `replaceTaggedContent` | CursorHooksInstaller.ts | 300-321 | Preserves user content outside tags | -| `writeFolderClaudeMd` | CursorHooksInstaller.ts | 326-353 | Atomic file write | - -### Integration Point - -**File:** `src/services/worker/agents/ResponseProcessor.ts:274-298` - -Current (problematic) code: -```typescript -const registry = readCursorRegistry(); -const registryEntry = registry[session.project]; - -if (registryEntry && (filesModified.length > 0 || filesRead.length > 0)) { - updateFolderClaudeMd( - registryEntry.workspacePath, // <-- PROBLEM: Needs Cursor registry - filesModified, - filesRead, - session.project, - getWorkerPort() - ).catch(error => { ... }); -} -``` - -### The Problem - -1. `filesModified` and `filesRead` already contain **absolute paths** -2. We don't need `workspacePath` - just extract folder from file path directly -3. Cursor registry is only populated when Cursor hooks are installed -4. This makes folder CLAUDE.md a Cursor-only feature (unintended) - -### Project Utils Pattern - -**From `src/utils/cursor-utils.ts:97-122`:** -- Pure functions with paths as parameters -- Atomic write pattern: temp file + rename -- `mkdirSync(dir, { recursive: true })` for directory creation - -### Related Utils - -**`src/utils/tag-stripping.ts`** - Handles *stripping* tags (input filtering) -- `stripMemoryTagsFromJson()` - removes `` content -- `stripMemoryTagsFromPrompt()` - removes `` content - -Our `replaceTaggedContent` handles *preserving/replacing* (output writing) - complementary, not duplicative. - ---- - -## Phase 1: Create Shared Utils File - -### What to Implement - -Create `src/utils/claude-md-utils.ts` with extracted and simplified functions. - -### File Structure - -```typescript -/** - * CLAUDE.md File Utilities - * - * Shared utilities for writing folder-level CLAUDE.md files with - * auto-generated context sections. Preserves user content outside - * tags. - */ - -import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'fs'; -import path from 'path'; -import { logger } from './logger.js'; - -/** - * Replace tagged content in existing file, preserving content outside tags. - * - * Handles three cases: - * 1. No existing content → wraps new content in tags - * 2. Has existing tags → replaces only tagged section - * 3. No tags in existing content → appends tagged content at end - */ -export function replaceTaggedContent(existingContent: string, newContent: string): string { - // Copy from CursorHooksInstaller.ts:300-321 -} - -/** - * Write CLAUDE.md file to folder with atomic writes. - * Creates directory structure if needed. - * - * @param folderPath - Absolute path to the folder - * @param newContent - Content to write inside tags - */ -export function writeClaudeMdToFolder(folderPath: string, newContent: string): void { - // Simplified from writeFolderClaudeMd - no workspacePath needed - // Copy atomic write pattern from CursorHooksInstaller.ts:326-353 -} - -/** - * Format timeline text from API response to compact CLAUDE.md format. - * - * @param timelineText - Raw API response text - * @returns Formatted markdown with date headers and compact table - */ -export function formatTimelineForClaudeMd(timelineText: string): string { - // Copy from CursorHooksInstaller.ts:221-295 -} -``` - -### Key Simplification - -**OLD `writeFolderClaudeMd` signature:** -```typescript -async function writeFolderClaudeMd( - workspacePath: string, // <-- REMOVE - folderPath: string, - newContent: string -): Promise -``` - -**NEW `writeClaudeMdToFolder` signature:** -```typescript -export function writeClaudeMdToFolder( - folderPath: string, // Must be absolute path - newContent: string -): void // Sync is fine, atomic anyway -``` - -### Verification Checklist -- [ ] File created at `src/utils/claude-md-utils.ts` -- [ ] `replaceTaggedContent` exported and handles all 3 cases -- [ ] `writeClaudeMdToFolder` exported with atomic writes -- [ ] `formatTimelineForClaudeMd` exported -- [ ] Build passes: `npm run build` - ---- - -## Phase 2: Create Folder Index Service Function - -### What to Implement - -Create a new orchestrating function that replaces `updateFolderClaudeMd`. This should NOT be in CursorHooksInstaller - it's a general feature. - -**Option A:** Add to `src/utils/claude-md-utils.ts` (keeps it simple) -**Option B:** Create `src/services/folder-index-service.ts` (follows service pattern) - -Recommend **Option A** for simplicity - it's just one function. - -### New Function - -```typescript -/** - * Update CLAUDE.md files for folders containing the given files. - * Fetches timeline from worker API and writes formatted content. - * - * @param filePaths - Array of absolute file paths (modified or read) - * @param project - Project identifier for API query - * @param port - Worker API port - */ -export async function updateFolderClaudeMdFiles( - filePaths: string[], - project: string, - port: number -): Promise { - // Extract unique folder paths from file paths - const folderPaths = new Set(); - for (const filePath of filePaths) { - if (!filePath || filePath === '') continue; - const folderPath = path.dirname(filePath); - if (folderPath && folderPath !== '.' && folderPath !== '/') { - folderPaths.add(folderPath); - } - } - - if (folderPaths.size === 0) return; - - logger.debug('FOLDER_INDEX', 'Updating CLAUDE.md files', { - project, - folderCount: folderPaths.size - }); - - // Process each folder - for (const folderPath of folderPaths) { - try { - // Fetch timeline via existing API - const response = await fetch( - `http://127.0.0.1:${port}/api/search/by-file?filePath=${encodeURIComponent(folderPath)}&limit=10&project=${encodeURIComponent(project)}` - ); - - if (!response.ok) { - logger.warn('FOLDER_INDEX', 'Failed to fetch timeline', { folderPath, status: response.status }); - continue; - } - - const result = await response.json(); - if (!result.content?.[0]?.text) { - logger.debug('FOLDER_INDEX', 'No content for folder', { folderPath }); - continue; - } - - const formatted = formatTimelineForClaudeMd(result.content[0].text); - writeClaudeMdToFolder(folderPath, formatted); - - logger.debug('FOLDER_INDEX', 'Updated CLAUDE.md', { folderPath }); - } catch (error) { - logger.warn('FOLDER_INDEX', 'Failed to update CLAUDE.md', { folderPath }, error as Error); - } - } -} -``` - -### Verification Checklist -- [ ] `updateFolderClaudeMdFiles` function added -- [ ] Takes only `filePaths`, `project`, `port` (no workspacePath) -- [ ] Extracts folder paths from absolute file paths -- [ ] Uses `writeClaudeMdToFolder` for atomic writes -- [ ] Build passes: `npm run build` - ---- - -## Phase 3: Update ResponseProcessor Integration - -### What to Implement - -Simplify the call site in `src/services/worker/agents/ResponseProcessor.ts`. - -### Current Code (lines 274-298) -```typescript -// Update folder CLAUDE.md files for touched folders (fire-and-forget) -const filesModified: string[] = []; -const filesRead: string[] = []; - -for (const obs of observations) { - filesModified.push(...(obs.files_modified || [])); - filesRead.push(...(obs.files_read || [])); -} - -// Get workspace path from project registry -const registry = readCursorRegistry(); -const registryEntry = registry[session.project]; - -if (registryEntry && (filesModified.length > 0 || filesRead.length > 0)) { - updateFolderClaudeMd( - registryEntry.workspacePath, - filesModified, - filesRead, - session.project, - getWorkerPort() - ).catch(error => { - logger.warn('FOLDER_INDEX', 'CLAUDE.md update failed (non-critical)', { project: session.project }, error as Error); - }); -} -``` - -### New Code -```typescript -// Update folder CLAUDE.md files for touched folders (fire-and-forget) -const allFilePaths: string[] = []; -for (const obs of observations) { - allFilePaths.push(...(obs.files_modified || [])); - allFilePaths.push(...(obs.files_read || [])); -} - -if (allFilePaths.length > 0) { - updateFolderClaudeMdFiles( - allFilePaths, - session.project, - getWorkerPort() - ).catch(error => { - logger.warn('FOLDER_INDEX', 'CLAUDE.md update failed (non-critical)', { project: session.project }, error as Error); - }); -} -``` - -### Import Changes - -**Remove:** -```typescript -import { updateFolderClaudeMd, readCursorRegistry } from '../../integrations/CursorHooksInstaller.js'; -``` - -**Add:** -```typescript -import { updateFolderClaudeMdFiles } from '../../../utils/claude-md-utils.js'; -``` - -**Keep (if still needed for Cursor context):** -```typescript -import { updateCursorContextForProject } from '../../worker-service.js'; -``` - -### Verification Checklist -- [ ] Import updated to use `claude-md-utils.ts` -- [ ] `readCursorRegistry` import removed (if no longer needed) -- [ ] Call site simplified - no registry lookup -- [ ] Fire-and-forget pattern preserved -- [ ] Build passes: `npm run build` - ---- - -## Phase 4: Clean Up CursorHooksInstaller - -### What to Implement - -Remove the extracted functions from `src/services/integrations/CursorHooksInstaller.ts`. - -### Functions to Remove -- `updateFolderClaudeMd` (lines 128-199) -- `formatTimelineForClaudeMd` (lines 221-295) -- `replaceTaggedContent` (lines 300-321) -- `writeFolderClaudeMd` (lines 326-353) - -### Verification Checklist -- [ ] All 4 functions removed from CursorHooksInstaller.ts -- [ ] No dangling references to removed functions -- [ ] CursorHooksInstaller still exports what it needs for Cursor integration -- [ ] Build passes: `npm run build` -- [ ] Grep shows no references to old function locations - ---- - -## Phase 5: Verification - -### Build Check -```bash -npm run build -``` - -### Anti-Pattern Grep (should find NOTHING in CursorHooksInstaller) -```bash -grep -n "updateFolderClaudeMd\|formatTimelineForClaudeMd\|replaceTaggedContent\|writeFolderClaudeMd" src/services/integrations/CursorHooksInstaller.ts -``` - -### Correct Location Grep (should find in claude-md-utils) -```bash -grep -rn "updateFolderClaudeMdFiles\|writeClaudeMdToFolder\|formatTimelineForClaudeMd" src/utils/ -``` - -### Integration Check -```bash -grep -n "updateFolderClaudeMdFiles" src/services/worker/agents/ResponseProcessor.ts -``` - -### No Cursor Registry Dependency -```bash -grep -n "readCursorRegistry" src/services/worker/agents/ResponseProcessor.ts -# Should return nothing (or only for Cursor context, not folder index) -``` - ---- - -## Summary - -**~150 lines moved** from CursorHooksInstaller.ts to claude-md-utils.ts with simplification: - -| Before | After | -|--------|-------| -| 4 functions in CursorHooksInstaller | 4 functions in claude-md-utils | -| Requires Cursor registry lookup | Works with absolute paths directly | -| `updateFolderClaudeMd(workspacePath, ...)` | `updateFolderClaudeMdFiles(filePaths, ...)` | -| Coupled to Cursor integration | Independent utility | - -**Files Changed:** -1. `src/utils/claude-md-utils.ts` - NEW (create) -2. `src/services/worker/agents/ResponseProcessor.ts` - UPDATE (simplify call site) -3. `src/services/integrations/CursorHooksInstaller.ts` - UPDATE (remove extracted functions) diff --git a/.claude/plans/folder-claude-md-timeline-format.md b/.claude/plans/folder-claude-md-timeline-format.md deleted file mode 100644 index e41c1a8a..00000000 --- a/.claude/plans/folder-claude-md-timeline-format.md +++ /dev/null @@ -1,186 +0,0 @@ -# Plan: Change Folder CLAUDE.md to Timeline Format - -## Goal - -Replace the simple table format in folder-level CLAUDE.md files with the timeline format used by search results. - -## Current vs Target Format - -### Current Format (Simple) -```markdown -# Recent Activity - -### Recent - -| Time | Type | Title | -|------|------|-------| -| 6:33pm | feature | Multiple CLAUDE.md files generated | -| 6:32pm | feature | CLAUDE.md file successfully generated | -``` - -### Target Format (Timeline) -```markdown -# Recent Activity - -### Jan 4, 2026 - -**src/services/worker/agents/ResponseProcessor.ts** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #37110 | 6:35 PM | 🔴 | Folder CLAUDE.md updates moved from summary | ~85 | -| #37109 | " | ✅ | ResponseProcessor.ts modified | ~92 | - -**General** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #37108 | 6:33 PM | 🟣 | Multiple CLAUDE.md files generated | ~78 | -``` - -## Key Changes - -1. **Group by date** - Use `### Jan 4, 2026` instead of `### Recent` -2. **Group by file within each date** - Add `**filename**` headers -3. **Expand columns** - Add ID and Read columns: `| ID | Time | T | Title | Read |` -4. **Use type emojis** - Use `🔴` `🟣` `✅` etc. instead of text -5. **Show ditto marks** - Use `"` for repeated times - ---- - -## Phase 1: Refactor formatTimelineForClaudeMd - -**File:** `src/utils/claude-md-utils.ts` - -**Tasks:** - -1. Add imports from shared utilities: - ```typescript - import { formatDate, formatTime, extractFirstFile, estimateTokens, groupByDate } from '../shared/timeline-formatting.js'; - import { ModeManager } from '../services/domain/ModeManager.js'; - ``` - -2. Replace `formatTimelineForClaudeMd()` (lines 78-151) with new implementation that: - - Parses API response to extract full observation data (id, time, type emoji, title, files) - - Groups observations by date using `groupByDate()` - - Within each date, groups by file using a Map - - Renders file sections with `**filename**` headers - - Uses search table format: `| ID | Time | T | Title | Read |` - - Uses ditto marks for repeated times - -**Pattern to Copy From:** `src/services/worker/search/ResultFormatter.ts` lines 56-108 - -**Key APIs:** -- `groupByDate(items, getDate)` - from `src/shared/timeline-formatting.ts:104-127` -- `formatTime(epoch)` - from `src/shared/timeline-formatting.ts:46-53` -- `formatDate(epoch)` - from `src/shared/timeline-formatting.ts:59-66` -- `extractFirstFile(filesModified, cwd)` - from `src/shared/timeline-formatting.ts:81-84` -- `estimateTokens(text)` - from `src/shared/timeline-formatting.ts:89-92` -- `ModeManager.getInstance().getTypeIcon(type)` - from `src/services/domain/ModeManager.ts` - -**Verification:** -1. Run `npm run build` - no errors -2. Restart worker: `npm run worker:restart` -3. Make a test edit to trigger observation -4. Check generated CLAUDE.md files for new format - ---- - -## Phase 2: Parse Full Observation Data from API - -**Context:** The current regex parsing extracts only time, type emoji, and title. Need to also extract: -- Observation ID (for `#123` column) -- File path (from files_modified in API response, for grouping) -- Token estimate (for `Read` column) - -**Challenge:** The current API returns formatted text, not structured data. We need to: -1. Parse the existing text format more thoroughly, OR -2. Use a different API endpoint that returns JSON - -**Decision Point:** Check what data the `/api/search/by-file` endpoint returns. If it returns structured JSON with observations, use that. Otherwise, enhance parsing. - -**Investigation Required:** -- Read `src/services/worker/http/routes/SearchRoutes.ts` to see by-file response format -- Determine if we can access raw observation data or just formatted text - -**Verification:** -- Confirm API response structure -- Update parsing to extract all needed fields - ---- - -## Phase 3: Integrate File-Based Grouping - -**File:** `src/utils/claude-md-utils.ts` - -**Tasks:** - -1. Create helper to group by file: - ```typescript - function groupByFile(observations: ParsedObservation[]): Map { - const byFile = new Map(); - for (const obs of observations) { - const file = obs.file || 'General'; - if (!byFile.has(file)) byFile.set(file, []); - byFile.get(file)!.push(obs); - } - return byFile; - } - ``` - -2. Render with file sections: - ```typescript - for (const [file, fileObs] of resultsByFile) { - lines.push(`**${file}**`); - lines.push(`| ID | Time | T | Title | Read |`); - lines.push(`|----|------|---|-------|------|`); - // render rows with ditto marks - } - ``` - -**Pattern to Copy From:** `ResultFormatter.formatSearchResults()` lines 60-108 - -**Verification:** -- Generated CLAUDE.md shows file grouping -- Files are displayed as relative paths when possible - ---- - -## Phase 4: Final Verification - -**Checklist:** - -1. **Build passes:** `npm run build` -2. **Worker restarts cleanly:** `npm run worker:restart` -3. **Format matches target:** - - Date headers: `### Jan 4, 2026` - - File sections: `**filename**` - - Table columns: `| ID | Time | T | Title | Read |` - - Type emojis: `🔴` `🟣` `✅` not text - - Ditto marks: `"` for repeated times -4. **Anti-pattern checks:** - - No hardcoded type maps (use ModeManager) - - No invented APIs - - Reuses existing formatters from shared utils -5. **Graceful degradation:** Empty results still show `*No recent activity*` - ---- - -## Files to Modify - -| File | Change | -|------|--------| -| `src/utils/claude-md-utils.ts` | Replace `formatTimelineForClaudeMd()` with timeline format | - -## Files to Read (Patterns to Copy) - -| File | Pattern | -|------|---------| -| `src/services/worker/search/ResultFormatter.ts:56-108` | Date/file grouping logic | -| `src/shared/timeline-formatting.ts` | All formatting utilities | -| `src/services/domain/ModeManager.ts` | Type icon lookup | - -## Anti-Patterns to Avoid - -- ❌ Creating new hardcoded type→emoji maps (use ModeManager) -- ❌ Parsing dates manually (use shared formatters) -- ❌ Skipping the existing groupByDate utility -- ❌ Not handling ditto marks for repeated times diff --git a/.claude/plans/intentional-patterns-execution.md b/.claude/plans/intentional-patterns-execution.md deleted file mode 100644 index d8f6a3a3..00000000 --- a/.claude/plans/intentional-patterns-execution.md +++ /dev/null @@ -1,356 +0,0 @@ -# Execution Plan: Intentional Patterns Validation Actions - -**Created:** 2026-01-13 -**Source:** `docs/reports/intentional-patterns-validation.md` -**Target:** `src/services/worker-service.ts` and related files - ---- - -## Phase 0: Documentation Discovery (COMPLETED) - -### Evidence Gathered - -**Files Analyzed:** -- `docs/reports/intentional-patterns-validation.md` - Pattern verdicts and recommendations -- `docs/reports/nonsense-logic.md` - Original 23 issues identified -- `.claude/plans/cleanup-worker-service-nonsense-logic.md` - Existing cleanup plan -- `src/services/worker-service.ts` (813 lines) - Current state - -**Current State:** -- File has been reduced from 1445 lines to 813 lines in prior refactoring -- `runInteractiveSetup` still exists at line 439 (~200 lines of dead code) -- Re-export at line 78: `export { updateCursorContextForProject };` -- MCP version hardcoded "1.0.0" at line 159 -- Fallback agents set at lines 144-146 without verification -- Unused imports: `fs`, `spawn`, `homedir`, `readline` at lines 13-17 - -**Allowed APIs (from validation report):** -- Exit code 0 pattern: **KEEP** (documented Windows Terminal workaround) -- `as Error` casts: **KEEP** (documented project policy) -- Dual init tracking: **KEEP** (serves async + sync callers) -- Signal handler ref pattern: **KEEP** (standard JS mutable state sharing) -- Empty MCP capabilities: **KEEP** (correct per MCP spec) - -**Actions Required:** -| Pattern | Action | Priority | -|---------|--------|----------| -| Re-export for circular import | Remove (no actual circular dep) | LOW | -| Fallback agent without check | Add availability verification | HIGH | -| MCP version hardcoded | Update to use package.json | LOW | -| Dead code `runInteractiveSetup` | Delete (~200 lines) | HIGH | -| Unused imports | Delete | LOW | - ---- - -## Phase 1: Delete Dead Code (HIGH PRIORITY) - -### 1.1 Delete `runInteractiveSetup` Function - -**What:** Delete lines 435-639 (approximately 200 lines) -**File:** `src/services/worker-service.ts` - -**Location confirmed:** Line 439 starts `async function runInteractiveSetup(): Promise` - -**Steps:** -1. Read worker-service.ts lines 435-650 to find exact boundaries -2. Delete the section comment and entire function -3. Run build to verify no compile errors - -**Verification:** -```bash -grep -n "runInteractiveSetup" src/services/worker-service.ts -# Expected: No output (function deleted) -npm run build -# Expected: No errors -``` - -### 1.2 Remove Unused Imports - -**What:** Delete imports only used by dead code -**Lines to delete:** 13-17 (check each) - -**Current imports to remove:** -```typescript -import * as fs from 'fs'; // Line 13 - UNUSED (namespace never accessed) -import { spawn } from 'child_process'; // Line 14 - UNUSED (MCP uses StdioClientTransport) -import { homedir } from 'os'; // Line 15 - Only in dead code -import * as readline from 'readline'; // Line 17 - Only in dead code -``` - -**Keep:** -```typescript -import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs'; // Line 16 - CHECK -``` - -**Steps:** -1. After deleting `runInteractiveSetup`, grep each import -2. Delete any with zero usages -3. Run build to verify - -**Verification:** -```bash -grep -n "^import \* as fs" src/services/worker-service.ts -grep -n "import { spawn }" src/services/worker-service.ts -# Expected: No output -npm run build -``` - -### 1.3 Remove Unused CursorHooksInstaller Imports - -**After deleting dead code, check:** -```typescript -import { - updateCursorContextForProject, // KEEP (re-exported) - handleCursorCommand, // KEEP (used in main) - detectClaudeCode, // DELETE (only in dead code) - findCursorHooksDir, // DELETE (only in dead code) - installCursorHooks, // DELETE (only in dead code) - configureCursorMcp // DELETE (only in dead code) -} from './integrations/CursorHooksInstaller.js'; -``` - -**Verification:** -```bash -grep "detectClaudeCode\|findCursorHooksDir\|installCursorHooks\|configureCursorMcp" src/services/worker-service.ts -# Expected: Only import line (which gets trimmed) -``` - ---- - -## Phase 2: Fix Fallback Agent Oversight (HIGH PRIORITY) - -### 2.1 Add SDKAgent Availability Check - -**Problem:** Lines 144-146 set Claude SDK as fallback without verifying it's configured -```typescript -this.geminiAgent.setFallbackAgent(this.sdkAgent); -this.openRouterAgent.setFallbackAgent(this.sdkAgent); -``` - -**Risk:** User chooses Gemini because they lack Claude credentials → transient Gemini error → fallback to Claude SDK → cascading failure - -**Solution Options:** - -**Option A: Add isConfigured() method to SDKAgent** -1. Add method to SDKAgent that checks for valid Claude SDK credentials -2. Only set fallback if `sdkAgent.isConfigured()` returns true -3. Log warning when fallback unavailable - -**Pattern to follow (from SDKAgent.ts constructor):** -```typescript -// Check if Claude SDK can be initialized -public isConfigured(): boolean { - // Claude SDK uses subprocess, check if claude command exists - try { - // Check for ANTHROPIC_API_KEY or claude CLI availability - return !!process.env.ANTHROPIC_API_KEY || this.checkClaudeCliAvailable(); - } catch { - return false; - } -} -``` - -**Option B: Document limitation (minimal fix)** -Add comment explaining the risk: -```typescript -// NOTE: Fallback to Claude SDK may fail if user lacks Claude credentials -// Consider adding availability check in future (Issue #XXX) -this.geminiAgent.setFallbackAgent(this.sdkAgent); -``` - -**Recommended: Option A** - -**Steps:** -1. Read SDKAgent.ts to understand initialization pattern -2. Add `isConfigured()` method that checks Claude CLI/credentials -3. Update worker-service.ts to conditionally set fallback -4. Add warning log when fallback unavailable -5. Run tests - -**Verification:** -```bash -grep -n "isConfigured" src/services/worker/SDKAgent.ts -# Expected: Method definition -grep -n "setFallbackAgent" src/services/worker-service.ts -# Expected: Conditional calls with isConfigured check -npm test -``` - ---- - -## Phase 3: Remove Unnecessary Re-Export (LOW PRIORITY) - -### 3.1 Fix Misleading Re-Export - -**Current (worker-service.ts:77-78):** -```typescript -// Re-export updateCursorContextForProject for SDK agents -export { updateCursorContextForProject }; -``` - -**Issue:** Comment implies avoiding circular import, but investigation found NO circular dependency exists. - -**Import chain:** -``` -CursorHooksInstaller.ts (defines) → worker-service.ts (imports, re-exports) → ResponseProcessor.ts (imports) -``` - -**ResponseProcessor.ts could import directly from CursorHooksInstaller.ts** - -**Options:** -1. **Remove re-export entirely** - Update ResponseProcessor.ts to import from CursorHooksInstaller directly -2. **Fix comment** - Update to reflect actual reason (API surface simplification) - -**Recommended: Option 1 (cleaner)** - -**Steps:** -1. Update `src/services/worker/agents/ResponseProcessor.ts`: - - Change: `import { updateCursorContextForProject } from '../../worker-service.js';` - - To: `import { updateCursorContextForProject } from '../../integrations/CursorHooksInstaller.js';` -2. Delete re-export from worker-service.ts (lines 77-78) -3. Run build to verify - -**Verification:** -```bash -grep -n "export { updateCursorContextForProject" src/services/worker-service.ts -# Expected: No output -grep -n "updateCursorContextForProject" src/services/worker/agents/ResponseProcessor.ts -# Expected: Import from CursorHooksInstaller -npm run build -``` - ---- - -## Phase 4: Update MCP Version (LOW PRIORITY) - -### 4.1 Use Package Version for MCP Client - -**Current (worker-service.ts:157-160):** -```typescript -this.mcpClient = new Client({ - name: 'worker-search-proxy', - version: '1.0.0' // Hardcoded, should match package.json (9.0.4) -}, { capabilities: {} }); -``` - -**Also affects (from report):** -- `src/services/sync/ChromaSync.ts:126-131` -- MCP server (separate file) - -**Pattern to follow:** -```typescript -import { version } from '../../package.json' assert { type: 'json' }; - -this.mcpClient = new Client({ - name: 'worker-search-proxy', - version: version -}, { capabilities: {} }); -``` - -**Alternative (if JSON import not supported):** -```typescript -import { readFileSync } from 'fs'; -const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8')); - -this.mcpClient = new Client({ - name: 'worker-search-proxy', - version: pkg.version -}, { capabilities: {} }); -``` - -**Steps:** -1. Check if JSON import assertion works in project -2. Update worker-service.ts MCP client initialization -3. Update ChromaSync.ts similarly -4. Run build to verify - -**Verification:** -```bash -grep -n "version: '1.0.0'" src/services/worker-service.ts src/services/sync/ChromaSync.ts -# Expected: No output -npm run build -``` - -### 4.2 Add MCP Capabilities Comment - -**Current:** -```typescript -}, { capabilities: {} }); -``` - -**Add clarifying comment:** -```typescript -}, { - // MCP spec: Clients accept all server capabilities; no declaration needed - capabilities: {} -}); -``` - ---- - -## Phase 5: Verification - -### 5.1 Build Check -```bash -npm run build -``` -**Expected:** No TypeScript errors - -### 5.2 Test Suite -```bash -npm test -``` -**Expected:** All tests pass - -### 5.3 Grep for Anti-Patterns -```bash -# Verify dead code removed -grep -r "runInteractiveSetup" src/ -# Expected: No matches - -# Verify unused imports removed -grep "import \* as fs from 'fs'" src/services/worker-service.ts -# Expected: No match - -# Verify re-export removed -grep "export { updateCursorContextForProject" src/services/worker-service.ts -# Expected: No match - -# Verify fallback has check -grep -A2 "setFallbackAgent" src/services/worker-service.ts -# Expected: Conditional with isConfigured check -``` - -### 5.4 Runtime Check -```bash -npm run build-and-sync -# Manually verify worker starts and basic operations work -``` - ---- - -## Summary - -| Phase | Description | Lines Changed | Priority | -|-------|-------------|---------------|----------| -| Phase 1 | Delete dead code + imports | ~200 deleted | HIGH | -| Phase 2 | Add fallback verification | ~10 added | HIGH | -| Phase 3 | Remove re-export | ~5 changed | LOW | -| Phase 4 | Update MCP version | ~3 changed | LOW | -| Phase 5 | Verification | N/A | N/A | - -**Execution Order:** Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 - -**Note:** Each phase should be followed by verification (build + test) before proceeding. - ---- - -## Patterns Confirmed KEEP (No Action) - -These patterns were validated as intentional: - -1. **Exit code 0 always** - Windows Terminal tab accumulation workaround (commit 222a73da) -2. **`as Error` casts** - Documented project policy with anti-pattern detection -3. **Dual init tracking** - Promise for async, flag for sync callers -4. **Signal handler ref pattern** - Standard JS mutable state sharing -5. **Empty MCP capabilities** - Correct per MCP client spec diff --git a/.claude/plans/pr-610-review-fixes.md b/.claude/plans/pr-610-review-fixes.md deleted file mode 100644 index cce4a78d..00000000 --- a/.claude/plans/pr-610-review-fixes.md +++ /dev/null @@ -1,144 +0,0 @@ -# Plan: Address PR #610 Review Issues - -## Overview -This plan addresses the issues identified in the PR review for PR #610 "fix: Update hooks for Claude Code 2.1.0/1 - SessionStart no longer shows user messages". - -## Phase 0: Verification and Discovery - -### 0.1 Verify Test Failure -- **File**: `tests/hook-constants.test.ts` -- **Issue**: Lines 61-63 test for `HOOK_EXIT_CODES.USER_MESSAGE_ONLY` which was removed -- **Verification**: Run `bun test tests/hook-constants.test.ts` to confirm failure - -### 0.2 Verify No Code References USER_MESSAGE_ONLY -- **Finding**: Grep found references only in: - - `tests/hook-constants.test.ts` (test file - needs fix) - - `src/services/CLAUDE.md` (memory context - auto-generated, not code) - - `plugin/scripts/CLAUDE.md` (memory context - auto-generated, not code) -- **Conclusion**: Only the test file needs updating; CLAUDE.md files are memory records - -### 0.3 Verify CLAUDE.md Files Are Legitimate -- **Clarification**: The PR reviewer mentioned "user-specific CLAUDE.md files starting with ~/" -- **Finding**: All CLAUDE.md files in the commit are within the repository (`docs/`, `src/`, `plugin/`) -- **Conclusion**: These are legitimate in-repo context files, not user-specific paths - ---- - -## Phase 1: Fix Test File (REQUIRED) - -### Task 1.1: Remove USER_MESSAGE_ONLY Test -**File**: `tests/hook-constants.test.ts` -**Action**: Delete lines 61-63 that test for the removed constant - -```typescript -// DELETE THESE LINES: -it('should define USER_MESSAGE_ONLY exit code', () => { - expect(HOOK_EXIT_CODES.USER_MESSAGE_ONLY).toBe(3); -}); -``` - -### Task 1.2: Add Test for BLOCKING_ERROR -**File**: `tests/hook-constants.test.ts` -**Action**: Add test for the new `BLOCKING_ERROR` constant (exit code 2) that replaced it - -```typescript -// ADD THIS TEST: -it('should define BLOCKING_ERROR exit code', () => { - expect(HOOK_EXIT_CODES.BLOCKING_ERROR).toBe(2); -}); -``` - -### Verification -- Run `bun test tests/hook-constants.test.ts` -- Expect: All tests pass - ---- - -## Phase 2: Documentation Consistency (NICE TO HAVE) - -### Issue -Three similar notes about Claude Code 2.1.0 have slightly different wording: - -1. `docs/public/architecture/hooks.mdx:254`: - > "SessionStart hooks no longer display any user-visible messages. Context is still injected via `hookSpecificOutput.additionalContext` but users don't see startup output in the UI." - -2. `docs/public/hooks-architecture.mdx:31`: - > "SessionStart hooks no longer display any user-visible messages. Context is silently injected via `hookSpecificOutput.additionalContext`." - -3. `docs/public/hooks-architecture.mdx:441`: - > "SessionStart hooks output is never displayed to users. Context is injected silently via `hookSpecificOutput.additionalContext`." - -### Task 2.1: Standardize Note Wording -**Action**: Use consistent wording across all three locations - -**Standard text**: -``` -As of Claude Code 2.1.0 (ultrathink update), SessionStart hooks no longer display user-visible messages. Context is silently injected via `hookSpecificOutput.additionalContext`. -``` - -### Files to Update -1. `docs/public/architecture/hooks.mdx:253-255` - Update Note block -2. `docs/public/hooks-architecture.mdx:30-32` - Update Note block -3. `docs/public/hooks-architecture.mdx:440-442` - Update Note block - -### Verification -- Grep for the standard text in all three files -- Visual review of documentation - ---- - -## Phase 3: Code Quality Improvements (OPTIONAL) - -### Issue 3.1: Hardcoded Promotional Message -**File**: `src/hooks/context-hook.ts:66-68` -**Current code**: -```typescript -const enhancedContext = `${text} - -Access 300k tokens of past research & decisions for just 19,008t. Use MCP search tools to access memories by ID.`; -``` - -### Options -1. **Leave as-is**: The token count is a rough estimate and doesn't need to be exact -2. **Make configurable**: Add to settings (over-engineering for this use case) -3. **Remove hardcoded numbers**: Use relative language instead - -### Recommendation -Leave as-is for now. The token counts are marketing copy, not critical functionality. Creating a PR just for this adds unnecessary complexity. - ---- - -## Phase 4: Final Verification - -### 4.1 Run Full Test Suite -```bash -bun test -``` - -### 4.2 Build Verification -```bash -npm run build -``` - -### 4.3 Grep Verification -```bash -grep -r "USER_MESSAGE_ONLY" src/ --include="*.ts" --include="*.js" -``` -Expected: No results (CLAUDE.md files excluded as they're memory records) - ---- - -## Summary - -| Phase | Priority | Effort | Description | -|-------|----------|--------|-------------| -| 1 | REQUIRED | 5 min | Fix test file - remove USER_MESSAGE_ONLY test, add BLOCKING_ERROR test | -| 2 | Nice to have | 10 min | Standardize documentation note wording | -| 3 | Skip | - | Hardcoded token counts are fine as-is | -| 4 | REQUIRED | 5 min | Run tests and build to verify | - -## Expected Outcome -- All tests pass -- Build succeeds -- No code references to removed USER_MESSAGE_ONLY constant -- Documentation uses consistent wording (if Phase 2 is done) diff --git a/.claude/plans/pr-628-polish.md b/.claude/plans/pr-628-polish.md deleted file mode 100644 index ab165642..00000000 --- a/.claude/plans/pr-628-polish.md +++ /dev/null @@ -1,223 +0,0 @@ -# Plan: PR #628 Polish Items - -**PR**: #628 - Windows Terminal Tab Accumulation & Windows 11 Compatibility -**Status**: APPROVED by 3 reviewers with minor suggestions -**Branch**: `feature/no-more-hook-files` - ---- - -## Phase 0: Documentation Discovery (Completed by Orchestrator) - -### Allowed APIs and Patterns - -**Exit Code Constants** - `src/shared/hook-constants.ts:18-23`: -```typescript -export const HOOK_EXIT_CODES = { - SUCCESS: 0, - FAILURE: 1, - BLOCKING_ERROR: 2, -} as const; -``` - -**Timeout Constants** - `src/shared/hook-constants.ts:1-8`: -```typescript -export const HOOK_TIMEOUTS = { - DEFAULT: 300000, - HEALTH_CHECK: 30000, - WORKER_STARTUP_WAIT: 1000, - WORKER_STARTUP_RETRIES: 300, - PRE_RESTART_SETTLE_DELAY: 2000, - WINDOWS_MULTIPLIER: 1.5 -} as const; -``` - -**Platform Timeout Function** - `src/services/infrastructure/ProcessManager.ts:70-73`: -```typescript -export function getPlatformTimeout(baseMs: number): number { - const WINDOWS_MULTIPLIER = 2.0; - return process.platform === 'win32' ? Math.round(baseMs * WINDOWS_MULTIPLIER) : baseMs; -} -``` - -**Migration Guide Pattern** - `docs/public/architecture/pm2-to-bun-migration.mdx`: -- Uses MDX format with frontmatter -- Starts with `` for historical context -- Uses `` for before/after comparisons -- Includes executive summary, key benefits, migration impact sections - -**Exit Code Documentation** - `private/context/claude-code/exit-codes.md`: -- Defines exit code 0, 2, and other behaviors -- Per-hook event behavior table - -### Files to Modify - -| File | Change | Lines | -|------|--------|-------| -| `src/services/infrastructure/ProcessManager.ts` | Add POWERSHELL_TIMEOUT constant, reduce from 60000 to 10000 | 93, 123, 175, 241 | -| `src/shared/hook-constants.ts` | Add POWERSHELL_TIMEOUT constant | After line 8 | -| `CLAUDE.md` | Document exit code strategy | Architecture section | - -### Anti-Patterns to Avoid - -- DO NOT invent new exit code values (only 0, 1, 2 exist) -- DO NOT change Windows multiplier (1.5x in hooks, 2.0x in ProcessManager - they serve different purposes) -- DO NOT add upper bound PID validation (not in existing pattern, reviewers marked as "nice to have") -- DO NOT create migration guide for Cursor (shell scripts still exist in cursor-hooks/, not removed) - ---- - -## Phase 1: Extract PowerShell Timeout Constant - -### What to Implement - -Add a `POWERSHELL_TIMEOUT` constant to centralize the magic number `60000` and reduce to `10000` (10 seconds) as recommended by reviewers. - -### Documentation References - -1. Copy constant pattern from `src/shared/hook-constants.ts:1-8` -2. Copy usage pattern from `src/services/infrastructure/ProcessManager.ts:93` - -### Implementation Steps - -1. **Add constant to hook-constants.ts** after line 8: - ```typescript - POWERSHELL_COMMAND: 10000, // PowerShell process enumeration (10s - typically completes in <1s) - ``` - -2. **Import and use in ProcessManager.ts**: - - Import `HOOK_TIMEOUTS` from `../../shared/hook-constants.js` - - Replace `{ timeout: 60000 }` with `{ timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND }` at lines 93, 123, 175, 241 - -### Verification Checklist - -- [ ] `grep -n "60000" src/services/infrastructure/ProcessManager.ts` returns 0 matches -- [ ] `grep -n "POWERSHELL_COMMAND" src/services/infrastructure/ProcessManager.ts` returns 4 matches -- [ ] `npm run build` succeeds -- [ ] `npm test` passes (22/22 PowerShell tests still pass) - -### Anti-Pattern Guards - -- DO NOT use `getPlatformTimeout()` for PowerShell commands (they already run only on Windows) -- DO NOT change timeout values in other files (only ProcessManager.ts uses PowerShell) - ---- - -## Phase 2: Document Exit Code Strategy in CLAUDE.md - -### What to Implement - -Add an "Exit Code Strategy" section to the main CLAUDE.md to explain the graceful exit philosophy adopted in this PR. - -### Documentation References - -1. Copy exit code definitions from `private/context/claude-code/exit-codes.md` -2. Follow format of existing CLAUDE.md sections - -### Implementation Steps - -1. **Add section after "File Locations"** in `/Users/alexnewman/Scripts/claude-mem/CLAUDE.md`: - -```markdown -## Exit Code Strategy - -Claude-mem hooks use specific exit codes per Claude Code's hook contract: - -- **Exit 0**: Success or graceful shutdown (Windows Terminal closes tabs) -- **Exit 1**: Non-blocking error (stderr shown to user, continues) -- **Exit 2**: Blocking error (stderr fed to Claude for processing) - -**Philosophy**: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics. - -See `private/context/claude-code/exit-codes.md` for full hook behavior matrix. -``` - -### Verification Checklist - -- [ ] `grep -n "Exit Code Strategy" CLAUDE.md` returns 1 match -- [ ] Section appears after "File Locations" section -- [ ] No duplicate sections added - -### Anti-Pattern Guards - -- DO NOT copy the full exit-codes.md table (keep it brief, reference the source) -- DO NOT change actual exit code behavior in code files - ---- - -## Phase 3: Update Tests for New Timeout Constant - -### What to Implement - -Add test coverage for the new `POWERSHELL_COMMAND` timeout constant. - -### Documentation References - -1. Copy test pattern from `tests/hook-constants.test.ts:26-48` - -### Implementation Steps - -1. **Add test to hook-constants.test.ts** after line 42: - ```typescript - test('POWERSHELL_COMMAND timeout is 10000ms', () => { - expect(HOOK_TIMEOUTS.POWERSHELL_COMMAND).toBe(10000); - }); - ``` - -### Verification Checklist - -- [ ] `npm test -- tests/hook-constants.test.ts` passes -- [ ] New test appears in test output -- [ ] All 22 PowerShell parsing tests still pass - -### Anti-Pattern Guards - -- DO NOT modify PowerShell parsing tests (they test parsing, not timeouts) -- DO NOT add integration tests for actual PowerShell execution (out of scope) - ---- - -## Phase 4: Final Verification - -### Verification Checklist - -1. **Build passes**: `npm run build` -2. **All tests pass**: `npm test` -3. **No magic numbers remain**: `grep -rn "60000" src/services/infrastructure/ProcessManager.ts` returns 0 -4. **Exit code documentation exists**: `grep -n "Exit Code Strategy" CLAUDE.md` returns 1 -5. **Constant is used**: `grep -rn "POWERSHELL_COMMAND" src/` returns multiple matches - -### Anti-Pattern Grep Checks - -- [ ] `grep -rn "timeout: 60000" src/` returns 0 matches (no hardcoded 60s timeouts in ProcessManager) -- [ ] `grep -rn "process.exit(3)" src/` returns 0 matches (exit code 3 not used) - -### Commit Message Template - -``` -polish: extract PowerShell timeout constant and document exit code strategy - -- Extract magic number 60000ms to HOOK_TIMEOUTS.POWERSHELL_COMMAND (10000ms) -- Reduce PowerShell timeout from 60s to 10s per review feedback -- Document exit code strategy in CLAUDE.md -- Add test coverage for new constant - -Addresses review feedback from PR #628 - -Co-Authored-By: Claude Opus 4.5 -``` - ---- - -## Summary - -| Phase | Description | Files Changed | Verification | -|-------|-------------|---------------|--------------| -| 0 | Documentation Discovery | N/A | Patterns identified | -| 1 | Extract PowerShell timeout | hook-constants.ts, ProcessManager.ts | grep + build + test | -| 2 | Document exit strategy | CLAUDE.md | grep | -| 3 | Add test coverage | hook-constants.test.ts | npm test | -| 4 | Final verification | N/A | All checks pass | - -**Estimated Changes**: ~20 lines added/modified across 4 files -**Risk Level**: Low (constants extraction, documentation only) -**Breaking Changes**: None diff --git a/.claude/plans/remove-worker-start-calls.md b/.claude/plans/remove-worker-start-calls.md deleted file mode 100644 index c51f0c24..00000000 --- a/.claude/plans/remove-worker-start-calls.md +++ /dev/null @@ -1,394 +0,0 @@ -# Plan: Remove Worker Start Calls - In-Process Architecture - -## Problem Statement - -Current architecture has problematic spawn patterns: -1. `hooks.json` calls `worker-service.cjs start` which spawns a daemon -2. Spawning is buggy on Windows - **HARD RULE: NO SPAWN** -3. `user-message` hook is deprecated -4. `smart-install` was supposed to chain: `smart-install && stop && context` - -## Target Architecture - -**NO SPAWN - Worker runs in-process within hook command** - -``` -SessionStart: - smart-install && stop && context -``` - -Flow: -1. `smart-install` - Install dependencies if needed -2. `stop` - Kill any existing worker (clean slate) -3. `context` - Hook starts worker IN-PROCESS, becomes the worker - -**Key insight:** The first hook that needs the worker **becomes** the worker. No spawn, no daemon. The hook process IS the worker process. - ---- - -## Current vs Target hooks.json - -### Current (BROKEN) -```json -"SessionStart": [ - { "hooks": [ - { "command": "node smart-install.js" }, - { "command": "bun worker-service.cjs start" }, // REMOVE - spawn - { "command": "bun worker-service.cjs hook ... context" }, - { "command": "bun worker-service.cjs hook ... user-message" } // REMOVE - deprecated - ]} -] -``` - -### Target -```json -"SessionStart": [ - { "hooks": [ - { "command": "node smart-install.js && bun worker-service.cjs stop && bun worker-service.cjs hook claude-code context" } - ]} -] -``` - ---- - -## Files Involved - -| File | Changes | -|------|---------| -| `plugin/hooks/hooks.json` | Restructure to chained commands, remove start/user-message | -| `src/services/worker-service.ts` | `hook` case: start worker in-process if not running | -| `src/cli/handlers/*.ts` | May need adjustment for in-process execution | -| `src/shared/worker-utils.ts` | `ensureWorkerRunning()` → adapt for in-process | - ---- - -## Phase 0: Documentation Discovery - -### Available APIs - -**From `src/services/infrastructure/HealthMonitor.ts`:** -- `isPortInUse(port): Promise` -- `waitForHealth(port, timeoutMs): Promise` -- `httpShutdown(port): Promise` - -**From `src/services/worker-service.ts`:** -- `WorkerService` class - the actual worker -- `stop` command - shuts down worker via HTTP -- `--daemon` case - starts WorkerService (currently only used after spawn) - -**BANNED (spawn patterns):** -- ~~`spawnDaemon()`~~ - NO SPAWN -- ~~`fork()`~~ - NO SPAWN -- ~~`spawn()` with detached~~ - NO SPAWN - -### Anti-Patterns -- **NO SPAWN** - Hard rule, Windows buggy -- No `restart` command - removed for same reason -- No detached processes - ---- - -## Phase 1: Modify `hook` Case for In-Process Worker - -### Location -`src/services/worker-service.ts:564-576` - -### Current Code -```typescript -case 'hook': { - const platform = process.argv[3]; - const event = process.argv[4]; - if (!platform || !event) { - console.error('Usage: claude-mem hook '); - process.exit(1); - } - const { hookCommand } = await import('../cli/hook-command.js'); - await hookCommand(platform, event); - break; -} -``` - -### Target Code -```typescript -case 'hook': { - const platform = process.argv[3]; - const event = process.argv[4]; - if (!platform || !event) { - console.error('Usage: claude-mem hook '); - process.exit(1); - } - - // Check if worker already running (port in use = valid, another process has it) - const portInUse = await isPortInUse(port); - if (portInUse) { - // Port in use - either healthy worker or something else - // Proceed with hook via HTTP to existing worker - const { hookCommand } = await import('../cli/hook-command.js'); - await hookCommand(platform, event); - break; - } - - // Port free - start worker IN THIS PROCESS (no spawn!) - logger.info('SYSTEM', 'Starting worker in-process for hook'); - const worker = new WorkerService(); - - // Start worker (non-blocking, returns when server listening) - await worker.start(); - - // Now execute hook logic - worker is running in this process - // Can call handler directly (in-process) or via HTTP to self - const { hookCommand } = await import('../cli/hook-command.js'); - await hookCommand(platform, event); - - // DON'T exit - this process IS the worker now - // Worker stays alive serving requests - break; -} -``` - -### Key Behavior -- If port in use → hook runs via HTTP to existing worker, then exits -- If port free → start worker in-process, run hook, process stays alive as worker - -### Verification -- [ ] Stop worker, run hook command → should start worker and stay alive -- [ ] Worker already running, run hook command → should complete and exit -- [ ] `lsof -i :37777` shows hook process IS the worker - ---- - -## Phase 2: Update hooks.json - Chained Commands - -### Location -`plugin/hooks/hooks.json` - -### Target Structure -```json -{ - "description": "Claude-mem memory system hooks", - "hooks": { - "SessionStart": [ - { - "matcher": "startup|clear|compact", - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" stop && bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code context", - "timeout": 300 - } - ] - } - ], - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code session-init", - "timeout": 60 - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code observation", - "timeout": 120 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code summarize", - "timeout": 120 - } - ] - } - ] - } -} -``` - -### Changes Summary -1. SessionStart: Chain `smart-install && stop && context` in single command -2. Remove `user-message` hook (deprecated) -3. Remove all separate `start` commands -4. Other hooks unchanged (just hook command, auto-starts if needed) - -### Verification -- [ ] JSON valid: `cat plugin/hooks/hooks.json | jq .` -- [ ] No `start` command: `grep -c '"start"' plugin/hooks/hooks.json` = 0 -- [ ] No `user-message`: `grep -c 'user-message' plugin/hooks/hooks.json` = 0 - ---- - -## Phase 3: Handle "Port In Use" Gracefully - -### Scenario -Another process has port 37777 (not our worker). Hook should handle gracefully. - -### Current Behavior -`ensureWorkerRunning()` polls for 15 seconds, then throws error. - -### Target Behavior -If port in use but not healthy (not our worker): -- Hook is "valid" - don't block Claude Code -- Return graceful response (empty context, etc.) -- Log warning for debugging - -### Location -`src/shared/worker-utils.ts:117-141` - -### Changes -```typescript -export async function ensureWorkerRunning(): Promise { - const port = getWorkerPort(); - - // Quick health check (2 seconds max) - try { - if (await isWorkerHealthy()) { - await checkWorkerVersion(); - return true; // Worker healthy - } - } catch (e) { - // Not healthy - } - - // Port might be in use by something else - // Return false but don't throw - let caller decide - logger.warn('SYSTEM', 'Worker not healthy, hook will proceed gracefully'); - return false; -} -``` - -### Handler Updates -Update handlers to handle `ensureWorkerRunning()` returning false: -```typescript -const workerReady = await ensureWorkerRunning(); -if (!workerReady) { - // Return graceful empty response - return { output: '', exitCode: HOOK_EXIT_CODES.SUCCESS }; -} -``` - -### Verification -- [ ] Start non-worker process on 37777, run hook → completes gracefully -- [ ] No 15-second hang when port blocked - ---- - -## Phase 4: Remove Deprecated Code - -### Remove `user-message` Handler (if unused elsewhere) -- [ ] Check if `user-message.ts` is used anywhere else -- [ ] Remove from `src/cli/handlers/index.ts` if safe -- [ ] Consider keeping file but removing from hooks.json only - -### Remove `start` Command (optional) -The `start` command in worker-service.ts can stay for manual use: -```bash -bun worker-service.cjs start # Manual start if needed -``` -But it should NOT be called from hooks.json. - -### Verification -- [ ] `npm run build` succeeds -- [ ] No references to removed handlers in hooks.json - ---- - -## Phase 5: Update Handler `ensureWorkerRunning()` Calls - -### Context -Each handler currently calls `ensureWorkerRunning()` which polls for 15 seconds. - -With in-process architecture: -- If hook started worker in-process → worker is THIS process, no HTTP needed -- If worker already running → HTTP to existing worker - -### Decision -**Keep handler calls** but modify `ensureWorkerRunning()` to: -1. Return quickly if port is in use (assume valid) -2. Return true if in-process worker (detect via global flag?) -3. Graceful false return instead of throwing - -### Files -- `src/cli/handlers/context.ts:15` -- `src/cli/handlers/session-init.ts:15` -- `src/cli/handlers/observation.ts:14` -- `src/cli/handlers/summarize.ts:17` -- `src/cli/handlers/file-edit.ts:15` - -### Verification -- [ ] Handlers don't hang on port-in-use scenarios -- [ ] In-process worker scenario works - ---- - -## Phase 6: Final Verification - -### Tests -- [ ] `bun test` - All tests pass -- [ ] `npm run build-and-sync` - Build succeeds - -### Manual Tests - -**Test 1: Clean Start** -```bash -bun plugin/scripts/worker-service.cjs stop -# Start new Claude Code session -# Verify: context hook starts worker in-process -# Verify: lsof -i :37777 shows the hook process -``` - -**Test 2: Worker Already Running** -```bash -bun plugin/scripts/worker-service.cjs stop -bun plugin/scripts/worker-service.cjs hook claude-code context & -# Wait for worker to start -bun plugin/scripts/worker-service.cjs hook claude-code observation -# Verify: observation hook exits after completing (doesn't stay alive) -``` - -**Test 3: Port Blocked** -```bash -bun plugin/scripts/worker-service.cjs stop -nc -l 37777 & # Block port with netcat -bun plugin/scripts/worker-service.cjs hook claude-code context -# Verify: completes gracefully, doesn't hang -kill %1 # Clean up netcat -``` - -**Test 4: Full Session** -```bash -# Start fresh Claude Code session -# Do some work (creates observations) -# End session (Ctrl+C or /exit) -# Verify: summarize hook ran, observations saved -``` - ---- - -## Risk Assessment - -| Risk | Mitigation | -|------|------------| -| Hook stays alive forever | Expected - it's the worker now | -| Multiple hooks compete for port | First one wins, others use HTTP | -| Graceful shutdown on session end | Stop command in chain handles this | -| Windows compatibility | No spawn = no Windows issues | - -## Rollback Plan - -If issues arise: -1. Restore hooks.json with separate start commands -2. Revert worker-service.ts hook case changes -3. No database changes to rollback diff --git a/.claude/plans/workflow-agents-integration.md b/.claude/plans/workflow-agents-integration.md deleted file mode 100644 index 14f46bd3..00000000 --- a/.claude/plans/workflow-agents-integration.md +++ /dev/null @@ -1,196 +0,0 @@ -# Plan: Integrate Workflow Agents and Commands into Claude-Mem - -## Executive Summary - -This plan integrates the `/make-plan` and `/do` orchestration workflow from `~/.claude/commands/` into the claude-mem plugin as project-level development tools. - -## Dependency Analysis - -### Commands to Copy (from `~/.claude/commands/`) - -| File | Purpose | Dependencies | -|------|---------|--------------| -| `make-plan.md` | Orchestrator for LLM-friendly phased planning | Uses Task tool with subagents | -| `do.md` | Orchestrator for executing plans via subagents | Uses Task tool with subagents | -| `anti-pattern-czar.md` | Error handling anti-pattern detection/fixing | Uses Read, Edit, Bash tools | - -### Specialized Agents Referenced - -The `/make-plan` and `/do` commands reference these **conceptual agent roles** (not actual agent files): - -| Agent Role | Referenced In | Description | -|------------|---------------|-------------| -| "Documentation Discovery" | make-plan.md | Fact-gathering from docs/examples | -| "Verification" | make-plan.md, do.md | Verify implementation matches plan | -| "Implementation" | do.md | Execute implementation tasks | -| "Anti-pattern" | do.md | Grep for known bad patterns | -| "Code Quality" | do.md | Review code changes | -| "Commit" | do.md | Commit after verification passes | -| "Branch/Sync" | do.md | Push and prepare phase handoffs | - -**Key Finding**: These are **role descriptions**, not separate agent files. The Task tool's `general-purpose` subagent_type executes all roles. The commands define *what* each role should do, not separate agent implementations. - -### Existing Project Assets - -Located in `.claude/`: -- `agents/github-morning-reporter.md` - Already in project -- `skills/version-bump/SKILL.md` - Already in project -- No existing commands directory - ---- - -## Phase 0: Documentation Discovery (Complete) - -### Sources Consulted -1. `/Users/alexnewman/.claude/commands/make-plan.md` (62 lines) -2. `/Users/alexnewman/.claude/commands/do.md` (39 lines) -3. `/Users/alexnewman/.claude/commands/anti-pattern-czar.md` (122 lines) -4. `/Users/alexnewman/.claude/settings.json` (36 lines) -5. `.claude/skills/CLAUDE.md` (30 lines) -6. `.claude/agents/github-morning-reporter.md` (102 lines) - -### Allowed APIs/Patterns -- **Commands**: `.claude/commands/*.md` files with `#$ARGUMENTS` placeholder for user input -- **Skills**: `.claude/skills//SKILL.md` with YAML frontmatter (name, description) -- **Agents**: `.claude/agents/*.md` with YAML frontmatter (name, description, model) - -### Anti-Patterns to Avoid -- Skills require YAML frontmatter; commands do not -- Commands use `#$ARGUMENTS` for input; skills/agents receive prompts differently -- Don't create separate agent files for role descriptions - the Task tool handles routing - ---- - -## Phase 1: Create Commands Directory - -### What to Implement -1. Create `.claude/commands/` directory -2. Copy `make-plan.md` from `~/.claude/commands/make-plan.md` -3. Copy `do.md` from `~/.claude/commands/do.md` -4. Copy `anti-pattern-czar.md` from `~/.claude/commands/anti-pattern-czar.md` - -### Documentation References -- Pattern: `~/.claude/commands/*.md` (source files) -- Existing example: `.claude/skills/version-bump/SKILL.md` for claude-mem project tools - -### Verification Checklist -```bash -# Verify files exist -ls -la .claude/commands/ - -# Verify content matches source -diff ~/.claude/commands/make-plan.md .claude/commands/make-plan.md -diff ~/.claude/commands/do.md .claude/commands/do.md -diff ~/.claude/commands/anti-pattern-czar.md .claude/commands/anti-pattern-czar.md - -# Verify #$ARGUMENTS placeholder exists -grep '\$ARGUMENTS' .claude/commands/*.md -``` - -### Anti-Pattern Guards -- Do NOT add YAML frontmatter to commands (they don't need it) -- Do NOT modify the source content (copy verbatim) - ---- - -## Phase 2: Create CLAUDE.md Documentation - -### What to Implement -Create `.claude/commands/CLAUDE.md` documenting the commands directory (following pattern from `.claude/skills/CLAUDE.md`) - -### Content Template -```markdown -# Project-Level Commands - -This directory contains slash commands **for developing and maintaining the claude-mem project itself**. - -## Commands in This Directory - -### /make-plan -Orchestrator for creating LLM-friendly implementation plans in phases. Deploys subagents for documentation discovery and fact gathering. - -**Usage**: `/make-plan ` - -### /do -Orchestrator for executing plans via subagents. Deploys specialized subagents for implementation, verification, and code quality review. - -**Usage**: `/do ` - -### /anti-pattern-czar -Interactive workflow for detecting and fixing error handling anti-patterns using the automated scanner. - -**Usage**: `/anti-pattern-czar` - -## Adding New Commands - -Commands are markdown files with `#$ARGUMENTS` placeholder for user input. -``` - -### Verification Checklist -```bash -# Verify file exists -cat .claude/commands/CLAUDE.md -``` - ---- - -## Phase 3: Update Settings (if needed) - -### What to Implement -Check if `.claude/settings.json` needs any permission updates for the new commands. - -### Verification Checklist -```bash -# Check current settings -cat .claude/settings.json - -# Verify commands work by listing them -# (After Claude Code restart, commands should appear in slash-command list) -``` - -### Anti-Pattern Guards -- Do NOT add skill permissions for commands (they're different) -- Commands don't require explicit permissions - ---- - -## Phase 4: Final Verification - -### Verification Checklist -1. All three command files exist in `.claude/commands/` -2. Content matches source files exactly (byte-for-byte if possible) -3. CLAUDE.md documentation exists -4. Git status shows new files ready for commit - -```bash -# Full verification -ls -la .claude/commands/ -wc -l .claude/commands/*.md -git status -``` - -### Commit Message Template -``` -feat: add /make-plan, /do, and /anti-pattern-czar workflow commands - -Add project-level orchestration commands for claude-mem development: -- /make-plan: Create LLM-friendly implementation plans in phases -- /do: Execute plans via coordinated subagents -- /anti-pattern-czar: Detect and fix error handling anti-patterns - -These commands enable structured, agent-driven development workflows. -``` - ---- - -## Summary - -**Files to Create**: -1. `.claude/commands/make-plan.md` (copy from ~/.claude/commands/) -2. `.claude/commands/do.md` (copy from ~/.claude/commands/) -3. `.claude/commands/anti-pattern-czar.md` (copy from ~/.claude/commands/) -4. `.claude/commands/CLAUDE.md` (new documentation) - -**No Agent Files Needed**: The "agents" referenced in make-plan.md and do.md are role descriptions, not separate files. The Task tool's built-in subagent types handle execution. - -**Confidence**: High - analysis complete with full source file reads. diff --git a/.claude/worktrees/amazing-matsumoto b/.claude/worktrees/amazing-matsumoto deleted file mode 160000 index 8e4d109d..00000000 --- a/.claude/worktrees/amazing-matsumoto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8e4d109db0a4161860b1b4a60dd74a0f268fd286 diff --git a/.claude/worktrees/animated-installer b/.claude/worktrees/animated-installer deleted file mode 160000 index 1b68c557..00000000 --- a/.claude/worktrees/animated-installer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b68c5576366bb15910217f8373154593c4a9ed8 diff --git a/.claude/worktrees/hardcore-black b/.claude/worktrees/hardcore-black deleted file mode 160000 index c27314f8..00000000 --- a/.claude/worktrees/hardcore-black +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c27314f896e7ad7378f782d4967c1d8b77a52bf2 diff --git a/.claude/worktrees/kind-noyce b/.claude/worktrees/kind-noyce deleted file mode 160000 index 1b68c557..00000000 --- a/.claude/worktrees/kind-noyce +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b68c5576366bb15910217f8373154593c4a9ed8 diff --git a/.gitignore b/.gitignore index a7c4e1d9..90b186d3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ dist/ .claude/settings.local.json .claude/agents/ .claude/skills/ +.claude/plans/ +.claude/worktrees/ plugin/data/ plugin/data.backup/ package-lock.json