feat(cli): npx claude-mem adopt [--dry-run] [--branch X]

Adds a manual escape hatch for the worktree adoption engine. Covers
squash-merges where git branch --merged HEAD returns nothing, and
lets users re-run adoption on demand.

Wired through worker-service.cjs (same pattern as generate/clean)
so the command runs under Bun with bun:sqlite, keeping npx-cli/
pure Node. --cwd flag passes the user's working directory through
the spawn so the engine resolves the correct parent repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-16 19:28:17 -07:00
parent 0b90495391
commit 5664fabce4
3 changed files with 76 additions and 0 deletions

View File

@@ -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=<query>`.
*/

View File

@@ -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 <query>')} 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<void> {
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();

View File

@@ -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).