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:
+52
-38
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user