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:
+119
-30
@@ -1,40 +1,129 @@
|
||||
export interface PackageMapData {
|
||||
path: string;
|
||||
role: string;
|
||||
files: FileEntry[];
|
||||
arch: string;
|
||||
dirty?: string;
|
||||
path: string;
|
||||
role: string;
|
||||
files: FileEntry[];
|
||||
arch: string;
|
||||
dirty?: string;
|
||||
}
|
||||
|
||||
export interface FileEntry {
|
||||
name: string;
|
||||
purpose: string;
|
||||
exports: string[];
|
||||
deps: string[];
|
||||
name: string;
|
||||
purpose: string;
|
||||
exports: string[];
|
||||
deps: string[];
|
||||
}
|
||||
|
||||
export function renderPackageMap(data: PackageMapData): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`# ${data.path}`);
|
||||
lines.push(`## role`);
|
||||
lines.push(data.role);
|
||||
lines.push(`## files`);
|
||||
for (const file of data.files) {
|
||||
const exp = file.exports.length > 0 ? `exp: ${file.exports.join(', ')}` : '';
|
||||
const dep = file.deps.length > 0 ? `dep: ${file.deps.join(', ')}` : '';
|
||||
const parts = [`- ${file.name} | ${file.purpose}`];
|
||||
if (exp) parts.push(exp);
|
||||
if (dep) parts.push(dep);
|
||||
lines.push(parts.join(' | '));
|
||||
}
|
||||
lines.push(`## arch`);
|
||||
lines.push(data.arch);
|
||||
lines.push(`## dirty`);
|
||||
lines.push(data.dirty || '-');
|
||||
return `${lines.join('\n')}\n`;
|
||||
const lines: string[] = [];
|
||||
lines.push(`# ${data.path}`);
|
||||
lines.push(`## role`);
|
||||
lines.push(data.role);
|
||||
lines.push(`## files`);
|
||||
for (const file of data.files) {
|
||||
const exp =
|
||||
file.exports.length > 0 ? `exp: ${file.exports.join(", ")}` : "";
|
||||
const dep = file.deps.length > 0 ? `dep: ${file.deps.join(", ")}` : "";
|
||||
const parts = [`- ${file.name} | ${file.purpose}`];
|
||||
if (exp) parts.push(exp);
|
||||
if (dep) parts.push(dep);
|
||||
lines.push(parts.join(" | "));
|
||||
}
|
||||
lines.push(`## arch`);
|
||||
lines.push(data.arch);
|
||||
lines.push(`## dirty`);
|
||||
lines.push(data.dirty || "-");
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
export function parsePackageMap(_markdown: string): PackageMapData {
|
||||
// TODO: implement robust parser
|
||||
throw new Error('parsePackageMap not yet implemented');
|
||||
export function parsePackageMap(markdown: string): PackageMapData {
|
||||
const lines = markdown.split("\n").map((l) => l.trimEnd());
|
||||
const result: PackageMapData = {
|
||||
path: "",
|
||||
role: "",
|
||||
files: [],
|
||||
arch: "",
|
||||
dirty: "-",
|
||||
};
|
||||
|
||||
let section: "none" | "role" | "files" | "arch" | "dirty" = "none";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("# ")) {
|
||||
result.path = line.slice(2).trim();
|
||||
continue;
|
||||
}
|
||||
if (line === "## role") {
|
||||
section = "role";
|
||||
continue;
|
||||
}
|
||||
if (line === "## files") {
|
||||
section = "files";
|
||||
continue;
|
||||
}
|
||||
if (line === "## arch") {
|
||||
section = "arch";
|
||||
continue;
|
||||
}
|
||||
if (line === "## dirty") {
|
||||
section = "dirty";
|
||||
continue;
|
||||
}
|
||||
if (line === "") continue;
|
||||
|
||||
switch (section) {
|
||||
case "role":
|
||||
result.role = line;
|
||||
break;
|
||||
case "files": {
|
||||
if (!line.startsWith("- ")) continue;
|
||||
const entry = parseFileLine(line);
|
||||
if (entry) result.files.push(entry);
|
||||
break;
|
||||
}
|
||||
case "arch":
|
||||
result.arch = result.arch ? `${result.arch}\n${line}` : line;
|
||||
break;
|
||||
case "dirty":
|
||||
result.dirty = line === "-" ? undefined : line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseFileLine(line: string): FileEntry | null {
|
||||
// Format: - filename | purpose | exp: ... | dep: ...
|
||||
const withoutPrefix = line.slice(2).trim();
|
||||
const parts = withoutPrefix.split(" | ").map((p) => p.trim());
|
||||
|
||||
if (parts.length < 2) return null;
|
||||
|
||||
const name = parts[0];
|
||||
const purpose = parts[1];
|
||||
const exports: string[] = [];
|
||||
const deps: string[] = [];
|
||||
|
||||
for (let i = 2; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part.startsWith("exp: ")) {
|
||||
exports.push(
|
||||
...part
|
||||
.slice(5)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
} else if (part.startsWith("dep: ")) {
|
||||
deps.push(
|
||||
...part
|
||||
.slice(5)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { name, purpose, exports, deps };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user