feat: add progress bar to init/reinit with per-file tracking

- processFiles() now accepts onProgress callback (completed, total, currentFile)
- InitOptions.onProgress receives structured ProgressInfo object
- initProject aggregates progress across all directories
- CLI renders ASCII progress bar: [████░░░░░░] 3/20 | filename.ts
- Pi extension sends structured updates with percentage + file details
- All 52 tests passing
This commit is contained in:
2026-06-10 18:43:42 +02:00
parent 78c60f9ca1
commit 9bdb862cca
5 changed files with 655 additions and 568 deletions
+48 -7
View File
@@ -12,11 +12,19 @@ import { writeFileSync } from "fs";
import { join } from "path";
import type { LLMClient } from "./llm/llm-client.js";
export interface ProgressInfo {
message: string;
completed: number;
total: number;
currentFile?: string;
dir?: string;
}
export interface InitOptions {
verbose?: boolean;
llmClient?: LLMClient;
cacheDir?: string;
onProgress?: (message: string) => void;
onProgress?: (info: ProgressInfo) => void;
}
export async function initProject(
@@ -24,15 +32,40 @@ export async function initProject(
options: InitOptions = {},
): Promise<void> {
const entries = discoverProject(rootPath);
options.onProgress?.(`Scanning ${entries.length} directories...`);
const totalFiles = entries.reduce((sum, e) => sum + e.files.length, 0);
let globalCompleted = 0;
options.onProgress?.({
message: `Scanning ${entries.length} directories (${totalFiles} files)...`,
completed: 0,
total: totalFiles,
});
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
options.onProgress?.(`[${i + 1}/${entries.length}] Analyzing ${entry.relativePath} (${entry.files.length} files)...`);
await generateDirectoryMap(entry, options.llmClient, options.cacheDir, options.onProgress);
await generateDirectoryMap(
entry,
options.llmClient,
options.cacheDir,
(info) => {
globalCompleted = info.completed + entries
.slice(0, i)
.reduce((sum, e) => sum + e.files.length, 0);
options.onProgress?.({
...info,
completed: globalCompleted,
total: totalFiles,
dir: entry.relativePath,
});
},
);
}
options.onProgress?.(`Generated ${entries.length} .pi-map.md files`);
options.onProgress?.({
message: `Generated ${entries.length} .pi-map.md files`,
completed: totalFiles,
total: totalFiles,
});
if (options.verbose !== false) {
console.log(`Generated ${entries.length} .pi-map.md files`);
}
@@ -42,19 +75,27 @@ export async function generateDirectoryMap(
entry: DirectoryEntry,
llmClient?: LLMClient,
cacheDir?: string,
onProgress?: (message: string) => void,
onProgress?: (info: ProgressInfo) => void,
): Promise<FileEntry[]> {
// Process files in parallel (4 concurrent) with retry logic
const fileData = await processFiles(
entry.files,
async (file) => {
const filePath = join(entry.dirPath, file);
onProgress?.(`${file}`);
const llmData = await extractFileLLM(filePath, llmClient, cacheDir);
const astData = await extractFileAST(filePath);
return mergeFileData(file, llmData, astData);
},
{ concurrency: 4, batchDelayMs: 100, maxRetries: 2 },
(completed, total, currentFile) => {
onProgress?.({
message: `${currentFile}`,
completed,
total,
currentFile,
dir: entry.relativePath,
});
},
);
const packageData = await extractPackageLLM(