diff --git a/src/commands/init.ts b/src/commands/init.ts index 2a47f7bd..e95a58b8 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -19,7 +19,7 @@ import { } from './init-hooks'; import { buildMcpConfig } from './init-mcp'; import { createContainer } from '../infrastructure/di'; -import { createStderrProgressHandler, liberateWithProgress } from './progress'; +import { createStderrProgressHandler } from './progress'; interface IInitCommandOptions { yes?: boolean; @@ -107,6 +107,7 @@ export function ensureEnvPlaceholder(cwd: string): void { // ── Main command ───────────────────────────────────────────────────── +/** Run unified project setup: hooks, MCP config, .gitignore, .env, and optional liberate. */ export async function initCommand(options: IInitCommandOptions, logger?: ILogger): Promise { const log = logger?.child({ command: 'init' }); const cwd = process.cwd(); @@ -191,21 +192,16 @@ export async function initCommand(options: IInitCommandOptions, logger?: ILogger if (apiKey) { process.env.ANTHROPIC_API_KEY = apiKey; - console.log(`Liberating knowledge from ${commitCount} commits with LLM enrichment...`); + console.log(`Extracting knowledge from ${commitCount} commits with LLM enrichment...`); const container = createContainer({ logger, scope: 'init', enrich: true }); const { liberateService } = container.cradle; - const onProgress = createStderrProgressHandler(); - - const result = await liberateWithProgress( - () => liberateService.liberate({ - maxCommits: commitCount, - enrich: true, - onProgress, - }), - onProgress, - ); + const result = await liberateService.liberate({ + maxCommits: commitCount, + enrich: true, + onProgress: createStderrProgressHandler(), + }); console.log( `Commits scanned: ${result.commitsScanned} | ` + diff --git a/src/commands/liberate.ts b/src/commands/liberate.ts index 62f56084..d0b108c2 100644 --- a/src/commands/liberate.ts +++ b/src/commands/liberate.ts @@ -4,7 +4,7 @@ import { createContainer } from '../infrastructure/di'; import type { ILogger } from '../domain/interfaces/ILogger'; -import { createStderrProgressHandler, liberateWithProgress } from './progress'; +import { createStderrProgressHandler } from './progress'; interface ILiberateCommandOptions { since?: string; @@ -14,6 +14,7 @@ interface ILiberateCommandOptions { enrich?: boolean; } +/** Scan git history, score commits for interest, and extract memories. */ export async function liberateCommand(options: ILiberateCommandOptions, logger?: ILogger): Promise { const container = createContainer({ logger, scope: 'liberate', enrich: options.enrich }); const { liberateService, llmClient, logger: log } = container.cradle; @@ -49,19 +50,14 @@ export async function liberateCommand(options: ILiberateCommandOptions, logger?: console.log('Dry run — no notes will be written.\n'); } - const onProgress = createStderrProgressHandler(); - - const result = await liberateWithProgress( - () => liberateService.liberate({ - since, - maxCommits, - dryRun: options.dryRun, - threshold, - enrich: options.enrich, - onProgress, - }), - onProgress, - ); + const result = await liberateService.liberate({ + since, + maxCommits, + dryRun: options.dryRun, + threshold, + enrich: options.enrich, + onProgress: createStderrProgressHandler(), + }); console.log(`Commits scanned: ${result.commitsScanned}`); console.log(`Commits annotated: ${result.commitsAnnotated}`); diff --git a/src/commands/progress.ts b/src/commands/progress.ts index cae38798..095490df 100644 --- a/src/commands/progress.ts +++ b/src/commands/progress.ts @@ -6,53 +6,18 @@ import type { ILiberateProgress } from '../application/interfaces/ILiberateServi /** * Create a progress callback that writes liberate progress to stderr. - * Uses in-place `\r` updates when stderr is a TTY, newline-based otherwise. + * Each progress update is written on its own line. */ export function createStderrProgressHandler(): (p: ILiberateProgress) => void { - const isTTY = process.stderr.isTTY; - let lastLineLength = 0; - let inPlaceStarted = false; - return (p: ILiberateProgress): void => { if (p.phase === 'triage') { process.stderr.write(`Found ${p.total} high-interest commits to analyze.\n`); } else if (p.phase === 'processing') { const sha = p.sha.slice(0, 7); const subject = p.subject.length > 60 ? p.subject.slice(0, 57) + '...' : p.subject; - const line = ` [${p.current}/${p.total}] ${sha} ${subject} (${p.factsExtracted} facts)`; - - if (isTTY) { - const padded = line.padEnd(lastLineLength, ' '); - lastLineLength = line.length; - inPlaceStarted = true; - process.stderr.write(`\r${padded}`); - } else { - process.stderr.write(`${line}\n`); - } + process.stderr.write(` [${p.current}/${p.total}] ${sha} ${subject} (${p.factsExtracted} facts)\n`); } else if (p.phase === 'complete') { - if (isTTY && inPlaceStarted) { - process.stderr.write('\n'); - inPlaceStarted = false; - } + process.stderr.write(`Done — ${p.factsExtracted} facts extracted from ${p.total} commits.\n`); } }; } - -/** - * Wrap a liberate call so that if it throws after writing in-place progress, - * a trailing newline is still written to stderr. - */ -export async function liberateWithProgress( - fn: () => Promise, - onProgress: ReturnType, -): Promise { - try { - return await fn(); - } finally { - // Ensure terminal is clean if progress was mid-line when error occurred. - // The 'complete' phase handler already writes \n, but if liberate threw - // before emitting 'complete', we need a fallback. We rely on the handler's - // internal state via one final 'complete' call. - onProgress({ phase: 'complete', current: 0, total: 0, sha: '', subject: '', factsExtracted: 0 }); - } -}