ktx/packages/cli/src/demo-progress.ts
2026-05-10 23:51:24 +02:00

77 lines
3.6 KiB
TypeScript

import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow';
import type { KtxDemoIo } from './demo.js';
function plural(n: number, one: string, many = `${one}s`): string {
return `${n} ${n === 1 ? one : many}`;
}
function formatDiff(added: number, modified: number, deleted: number, unchanged: number): string {
const parts: string[] = [];
if (added > 0) parts.push(`+${added} new`);
if (modified > 0) parts.push(`~${modified} changed`);
if (deleted > 0) parts.push(`-${deleted} removed`);
if (unchanged > 0) parts.push(`=${unchanged} unchanged`);
return parts.length > 0 ? parts.join(', ') : 'no changes';
}
export function formatMemoryFlowEventLine(event: MemoryFlowEvent): string | null {
switch (event.type) {
case 'source_acquired':
return `[connect] Connected ${event.adapter} - ${plural(event.fileCount, 'database file')} (${event.trigger})`;
case 'scope_detected':
return event.fingerprint
? `[scope] Scope locked: ${event.fingerprint}`
: '[scope] Reviewing the whole warehouse (no scope filter)';
case 'raw_snapshot_written':
return `[snapshot] Captured snapshot ${event.syncId} - ${plural(event.rawFileCount, 'file')}`;
case 'diff_computed':
return `[diff] Tables: ${formatDiff(event.added, event.modified, event.deleted, event.unchanged)}`;
case 'chunks_planned':
return event.evictionCount > 0
? `[plan] Grouped ${plural(event.workUnitCount, 'table')} into ${plural(event.chunkCount, 'business area')} (${plural(event.evictionCount, 'removal')})`
: `[plan] Grouped ${plural(event.workUnitCount, 'table')} into ${plural(event.chunkCount, 'business area')}`;
case 'stage_skipped':
return `[skip] ${event.stage} skipped: ${event.reason}`;
case 'work_unit_started':
return `[analyze] Reviewing "${event.unitKey}" - budget ${plural(event.stepBudget, 'agent step')}`;
case 'work_unit_step':
return null;
case 'candidate_action': {
const target = event.target === 'sl' ? 'semantic-layer' : 'wiki';
return `[draft] ${event.unitKey} -> ${target}: ${event.action} ${event.key}`;
}
case 'work_unit_finished':
if (event.status === 'success') {
return `[done] ${event.unitKey} reviewed`;
}
return `[fail] ${event.unitKey} needs attention${event.reason ? ` - ${event.reason}` : ''}`;
case 'reconciliation_finished': {
const conflicts = event.conflictCount === 0 ? 'no conflicts' : plural(event.conflictCount, 'conflict');
const fallbacks = event.fallbackCount === 0 ? 'nothing flagged for review' : `${plural(event.fallbackCount, 'item')} flagged for review`;
return `[validate] Reconciled drafts - ${conflicts}, ${fallbacks}`;
}
case 'saved': {
const total = event.wikiCount + event.slCount;
const commit = event.commitSha ? ` - commit ${event.commitSha.slice(0, 7)}` : '';
return `[memory] Saved ${plural(total, 'memory', 'memories')} (${event.wikiCount} wiki, ${event.slCount} semantic-layer)${commit}`;
}
case 'provenance_recorded':
return `[trace] Recorded provenance for ${plural(event.rowCount, 'row')}`;
case 'report_created':
return `[report] Run report ready: ${event.runId}`;
}
}
export function createPlainProgressEmitter(io: KtxDemoIo): (snapshot: MemoryFlowReplayInput) => void {
let printed = 0;
return (snapshot) => {
while (printed < snapshot.events.length) {
const event = snapshot.events[printed++];
if (!event) continue;
const line = formatMemoryFlowEventLine(event);
if (line !== null) {
io.stdout.write(`${line}\n`);
}
}
};
}