Implement all stubs: parsePackageMap, LLM heuristics, tree-sitter AST, patch rewrite, reinit

- format.ts: Full parsePackageMap implementation with round-trip support
- llm-extract.ts: Heuristic-based extraction without LLM API (regex exports,
  imports, purpose inference from filename patterns)
- ast-extract.ts: Tree-sitter integration for TypeScript/TSX with fallback
  for other languages
- init.ts: Refactored to expose generateDirectoryMap helper, reinitPath
  delegates to initProject
- patch.ts: Small packages now use generateDirectoryMap for full rewrite
- discover.ts: Cleaned up require('fs') to use proper ES import
- Tests: 8 tests covering format round-trip and LLM heuristics
- All TypeScript compiles clean, all tests pass
This commit is contained in:
2026-06-09 18:04:57 +02:00
parent fd958671ca
commit d311c8fb3c
14 changed files with 1032 additions and 305 deletions
+52 -38
View File
@@ -1,48 +1,62 @@
import { dirname, join } from 'path';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { parsePackageMap, renderPackageMap } from './format.js';
import { extractFileLLM } from './llm-extract.js';
import { extractFileAST } from './ast-extract.js';
import { mergeFileData } from './merge.js';
import { readdirSync } from 'fs';
import { dirname, join, basename, relative } from "path";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { parsePackageMap, renderPackageMap } from "./format.js";
import { extractFileLLM } from "./llm-extract.js";
import { extractFileAST } from "./ast-extract.js";
import { mergeFileData } from "./merge.js";
import { generateDirectoryMap } from "./init.js";
import { readdirSync, statSync } from "fs";
const SMALL_PACKAGE_THRESHOLD = 10;
export async function patchFile(filePath: string): Promise<void> {
const dirPath = dirname(filePath);
const mapPath = join(dirPath, '.pi-map.md');
const dirPath = dirname(filePath);
const mapPath = join(dirPath, ".pi-map.md");
if (!existsSync(mapPath)) {
// No map exists yet — would need to generate from scratch
console.warn(`No .pi-map.md found in ${dirPath}`);
return;
}
if (!existsSync(mapPath)) {
// No map exists yet — would need to generate from scratch
console.warn(`No .pi-map.md found in ${dirPath}`);
return;
}
const allFiles = readdirSync(dirPath).filter((f: string) => !f.startsWith('.') && !f.endsWith('.md'));
const isSmallPackage = allFiles.length < SMALL_PACKAGE_THRESHOLD;
const allFiles = readdirSync(dirPath).filter(
(f: string) => !f.startsWith(".") && !f.endsWith(".md"),
);
const isSmallPackage = allFiles.length < SMALL_PACKAGE_THRESHOLD;
if (isSmallPackage) {
// Full rewrite for small packages
// TODO: import and reuse init logic for a single directory
console.log(`Full rewrite of ${mapPath} (small package: ${allFiles.length} files)`);
} else {
// Section-level patch
const existing = parsePackageMap(readFileSync(mapPath, 'utf8'));
const llmData = await extractFileLLM(filePath);
const astData = await extractFileAST(filePath);
const fileName = filePath.split('/').pop()!;
const updatedFile = mergeFileData(fileName, llmData, astData);
if (isSmallPackage) {
// Full rewrite for small packages
const files = allFiles.filter((f) => {
const st = statSync(join(dirPath, f));
return st.isFile();
});
const relDir = relative(process.cwd(), dirPath) || ".";
await generateDirectoryMap({
dirPath,
relativePath: relDir,
files,
});
console.log(
`Full rewrite of ${mapPath} (small package: ${allFiles.length} files)`,
);
} else {
// Section-level patch
const existing = parsePackageMap(readFileSync(mapPath, "utf8"));
const llmData = await extractFileLLM(filePath);
const astData = await extractFileAST(filePath);
const fileName = basename(filePath);
const updatedFile = mergeFileData(fileName, llmData, astData);
// Replace the matching file entry
const idx = existing.files.findIndex(f => f.name === updatedFile.name);
if (idx >= 0) {
existing.files[idx] = updatedFile;
} else {
existing.files.push(updatedFile);
}
// Replace the matching file entry
const idx = existing.files.findIndex((f) => f.name === updatedFile.name);
if (idx >= 0) {
existing.files[idx] = updatedFile;
} else {
existing.files.push(updatedFile);
}
existing.dirty = `${new Date().toISOString()}: ${fileName} patched (section-level)`;
writeFileSync(mapPath, renderPackageMap(existing));
console.log(`Patched ${mapPath}`);
}
existing.dirty = `${new Date().toISOString()}: ${fileName} patched (section-level)`;
writeFileSync(mapPath, renderPackageMap(existing));
console.log(`Patched ${mapPath}`);
}
}