From 5698901dee4f79118ab3c5b1a3f555214d59d4f9 Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Thu, 12 Feb 2026 23:29:58 +0000 Subject: [PATCH 1/3] refactor: use newline-per-line progress output instead of in-place \r (GIT-64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies progress handler to always write each update on its own line. Removes TTY detection, padEnd line clearing, and liberateWithProgress try/finally wrapper — none needed with newline output. Co-Authored-By: Claude Opus 4.6 --- src/commands/init.ts | 17 ++++++----------- src/commands/liberate.ts | 23 +++++++++------------- src/commands/progress.ts | 41 ++-------------------------------------- 3 files changed, 17 insertions(+), 64 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 2a47f7bd..f48b8563 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; @@ -196,16 +196,11 @@ export async function initCommand(options: IInitCommandOptions, logger?: ILogger 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..a578f83e 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; @@ -49,19 +49,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..fbf6c221 100644 --- a/src/commands/progress.ts +++ b/src/commands/progress.ts @@ -6,53 +6,16 @@ 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`); - } - } else if (p.phase === 'complete') { - if (isTTY && inPlaceStarted) { - process.stderr.write('\n'); - inPlaceStarted = false; - } + process.stderr.write(` [${p.current}/${p.total}] ${sha} ${subject} (${p.factsExtracted} facts)\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 }); - } -} From ce209ea9a6445be921911be6d5e93b253f92f1ab Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Fri, 13 Feb 2026 08:18:58 +0000 Subject: [PATCH 2/3] fix: handle complete phase in stderr progress handler (GIT-64) Co-Authored-By: Claude Opus 4.6 --- src/commands/progress.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/progress.ts b/src/commands/progress.ts index fbf6c221..095490df 100644 --- a/src/commands/progress.ts +++ b/src/commands/progress.ts @@ -16,6 +16,8 @@ export function createStderrProgressHandler(): (p: ILiberateProgress) => void { const sha = p.sha.slice(0, 7); const subject = p.subject.length > 60 ? p.subject.slice(0, 57) + '...' : p.subject; process.stderr.write(` [${p.current}/${p.total}] ${sha} ${subject} (${p.factsExtracted} facts)\n`); + } else if (p.phase === 'complete') { + process.stderr.write(`Done — ${p.factsExtracted} facts extracted from ${p.total} commits.\n`); } }; } From e1941ff277396be9bbfbe52cdf74e136562be056 Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Fri, 13 Feb 2026 08:21:52 +0000 Subject: [PATCH 3/3] docs: add docstrings to liberateCommand and initCommand (GIT-64) Co-Authored-By: Claude Opus 4.6 --- src/commands/init.ts | 3 ++- src/commands/liberate.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index f48b8563..e95a58b8 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -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,7 +192,7 @@ 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; diff --git a/src/commands/liberate.ts b/src/commands/liberate.ts index a578f83e..d0b108c2 100644 --- a/src/commands/liberate.ts +++ b/src/commands/liberate.ts @@ -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;