diff --git a/src/npx-cli/commands/runtime.ts b/src/npx-cli/commands/runtime.ts index 3e376026..a0781b63 100644 --- a/src/npx-cli/commands/runtime.ts +++ b/src/npx-cli/commands/runtime.ts @@ -101,6 +101,44 @@ export function runStatusCommand(): void { spawnBunWorkerCommand('status'); } +/** + * Stamp merged-worktree provenance on observations/summaries and keep Chroma + * metadata in lockstep. Delegates to the worker-service.cjs `adopt` subcommand + * so adoption runs in Bun (needed for bun:sqlite) while preserving the user's + * working directory — that's what the engine uses to locate the parent repo. + */ +export function runAdoptCommand(extraArgs: string[] = []): void { + ensureInstalledOrExit(); + const bunPath = resolveBunOrExit(); + const workerScript = workerServiceScriptPath(); + + if (!existsSync(workerScript)) { + console.error(pc.red(`Worker script not found at: ${workerScript}`)); + console.error('The installation may be corrupted. Try: npx claude-mem install'); + process.exit(1); + } + + // Pass user's cwd explicitly via --cwd because we override cwd on spawn to + // marketplaceDirectory() (required for the worker's own file resolution). + const userCwd = process.cwd(); + const args = [workerScript, 'adopt', '--cwd', userCwd, ...extraArgs]; + + const child = spawn(bunPath, args, { + stdio: 'inherit', + cwd: marketplaceDirectory(), + env: process.env, + }); + + child.on('error', (error) => { + console.error(pc.red(`Failed to start Bun: ${error.message}`)); + process.exit(1); + }); + + child.on('close', (exitCode) => { + process.exit(exitCode ?? 0); + }); +} + /** * Search the worker API at `GET /api/search?query=`. */ diff --git a/src/npx-cli/index.ts b/src/npx-cli/index.ts index a3f47379..a706eb7b 100644 --- a/src/npx-cli/index.ts +++ b/src/npx-cli/index.ts @@ -52,6 +52,7 @@ ${pc.bold('Runtime Commands')} (requires Bun, delegates to installed plugin): ${pc.cyan('npx claude-mem restart')} Restart worker service ${pc.cyan('npx claude-mem status')} Show worker status ${pc.cyan('npx claude-mem search ')} Search observations + ${pc.cyan('npx claude-mem adopt [--dry-run]')} Stamp merged worktrees into parent project ${pc.cyan('npx claude-mem transcript watch')} Start transcript watcher ${pc.bold('IDE Identifiers')}: @@ -145,6 +146,13 @@ async function main(): Promise { break; } + // -- Adopt merged worktrees ------------------------------------------- + case 'adopt': { + const { runAdoptCommand } = await import('./commands/runtime.js'); + runAdoptCommand(args.slice(1)); + break; + } + // -- Transcript -------------------------------------------------------- case 'transcript': { const subCommand = args[1]?.toLowerCase(); diff --git a/src/services/worker-service.ts b/src/services/worker-service.ts index e723d8a6..bf362b92 100644 --- a/src/services/worker-service.ts +++ b/src/services/worker-service.ts @@ -1208,6 +1208,36 @@ async function main() { break; } + case 'adopt': { + const dryRun = process.argv.includes('--dry-run'); + const branchIndex = process.argv.indexOf('--branch'); + const onlyBranch = branchIndex !== -1 ? process.argv[branchIndex + 1] : undefined; + // Honor an explicit --cwd override so the NPX CLI can pass through the + // user's working directory (the spawn sets cwd to the marketplace dir). + const cwdIndex = process.argv.indexOf('--cwd'); + const repoPath = cwdIndex !== -1 ? process.argv[cwdIndex + 1] : process.cwd(); + + const result = await adoptMergedWorktrees({ repoPath, dryRun, onlyBranch }); + + const tag = result.dryRun ? '(dry-run)' : '(applied)'; + console.log(`\nWorktree adoption ${tag}`); + console.log(` Parent project: ${result.parentProject || '(unknown)'}`); + console.log(` Repo: ${result.repoPath}`); + console.log(` Worktrees scanned: ${result.scannedWorktrees}`); + console.log(` Merged branches: ${result.mergedBranches.join(', ') || '(none)'}`); + console.log(` Observations adopted: ${result.adoptedObservations}`); + console.log(` Summaries adopted: ${result.adoptedSummaries}`); + console.log(` Chroma docs updated: ${result.chromaUpdates}`); + if (result.chromaFailed > 0) { + console.log(` Chroma sync failures: ${result.chromaFailed} (will retry on next run)`); + } + for (const err of result.errors) { + console.log(` ! ${err.worktree}: ${err.error}`); + } + process.exit(0); + break; + } + case '--daemon': default: { // GUARD 1: Refuse to start if another worker is already alive (PID check).