Implement layered maps and context retrieval

This commit is contained in:
2026-06-11 12:56:18 +02:00
parent 010e4b83eb
commit c6064f8d94
35 changed files with 4410 additions and 383 deletions
+67 -13
View File
@@ -4,6 +4,7 @@ import { patchFile } from "../patch.js";
import { validateMaps } from "../validate.js";
import { reinitPath } from "../init.js";
import { discoverProject } from "../discover.js";
import { retrieveContext } from "../retrieve.js";
import { createLLMClient, LLMError } from "../llm/llm-client.js";
import { loadConfig } from "../config.js";
import pc from "picocolors";
@@ -11,6 +12,8 @@ import pc from "picocolors";
const args = process.argv.slice(2);
const command = args[0];
type PatchMode = "auto" | "small" | "structural";
function printUsage() {
console.log(`${pc.bold("project-map")} — hierarchical project analysis for Pi agents
`);
@@ -30,6 +33,9 @@ function printUsage() {
console.log(
` project-map ${pc.cyan("--help")} Show this help message`,
);
console.log(
` project-map ${pc.cyan("context")} <query> Retrieve relevant project context for a query`,
);
console.log(
` project-map ${pc.cyan("--version")} Show version\n`,
);
@@ -40,19 +46,21 @@ function printUsage() {
console.log(
` --llm-model=<model> LLM model name (or set LLM_MODEL env var)`,
);
console.log(` --llm-base-url=<url> Custom base URL for LLM API\n`);
console.log(` --llm-base-url=<url> Custom base URL for LLM API`);
console.log(
` --patch-mode=<mode> Patch/repair mode: auto|small|structural\n`,
);
console.log(`${pc.bold("Examples:")}`);
console.log(` project-map init`);
console.log(` project-map patch src/components/Button.tsx`);
console.log(` project-map validate --fix`);
console.log(` project-map reinit`);
console.log(
` project-map init --llm-provider=kimi --llm-model=kimi-k2-6`,
);
console.log(` project-map context "authentication logic"`);
console.log(` project-map init --llm-provider=kimi --llm-model=kimi-k2-6`);
}
function printVersion() {
const pkg = require("../package.json");
const pkg = require("../../package.json");
console.log(pkg.version);
}
@@ -63,7 +71,12 @@ function formatCount(count: number, label: string): string {
return `${pc.bold(String(count))} ${count === 1 ? label : plural}`;
}
function renderProgressBar(completed: number, total: number, currentFile?: string, width = 30): string {
function renderProgressBar(
completed: number,
total: number,
currentFile?: string,
width = 30,
): string {
const pct = total > 0 ? completed / total : 0;
const filled = Math.round(width * pct);
const bar = "█".repeat(filled) + "░".repeat(width - filled);
@@ -77,6 +90,7 @@ function parseArgs(args: string[]): {
llmProvider?: string;
llmModel?: string;
llmBaseUrl?: string;
patchMode?: PatchMode;
positional: string[];
} {
let path = ".";
@@ -84,6 +98,7 @@ function parseArgs(args: string[]): {
let llmProvider: string | undefined;
let llmModel: string | undefined;
let llmBaseUrl: string | undefined;
let patchMode: PatchMode | undefined;
const positional: string[] = [];
for (const arg of args.slice(1)) {
@@ -95,13 +110,23 @@ function parseArgs(args: string[]): {
llmModel = arg.slice("--llm-model=".length);
} else if (arg.startsWith("--llm-base-url=")) {
llmBaseUrl = arg.slice("--llm-base-url=".length);
} else if (arg.startsWith("--patch-mode=")) {
patchMode = arg.slice("--patch-mode=".length) as PatchMode;
} else if (!arg.startsWith("-")) {
positional.push(arg);
path = arg;
}
}
return { path, fix, llmProvider, llmModel, llmBaseUrl, positional };
return {
path,
fix,
llmProvider,
llmModel,
llmBaseUrl,
patchMode,
positional,
};
}
function createClientFromArgs(args: ReturnType<typeof parseArgs>) {
@@ -142,8 +167,12 @@ async function main() {
llmClient: client,
cacheDir: targetPath,
onProgress: (info) => {
const line = renderProgressBar(info.completed, info.total, info.currentFile);
process.stdout.write("\r" + line.padEnd(lastLine.length));
const line = renderProgressBar(
info.completed,
info.total,
info.currentFile,
);
process.stdout.write(`\r${line.padEnd(lastLine.length)}`);
lastLine = line;
},
});
@@ -162,13 +191,22 @@ async function main() {
process.exit(1);
}
const client = createClientFromArgs(parsed);
await patchFile(parsed.positional[0], client, process.cwd());
await patchFile(parsed.positional[0], client, process.cwd(), {
patchMode: parsed.patchMode,
rootPath: process.cwd(),
});
console.log(`${pc.green("✓")} Patched`);
break;
}
case "validate": {
const { path, fix } = parsed;
const result = await validateMaps(path, { fix, verbose: true });
const result = await validateMaps(path, {
fix,
verbose: true,
llmClient: fix ? createClientFromArgs(parsed) : undefined,
cacheDir: process.cwd(),
patchMode: parsed.patchMode,
});
if (result.clean) {
console.log(`${pc.green("✓")} All .pi-map.md files are clean.`);
} else {
@@ -190,6 +228,18 @@ async function main() {
process.exit(result.clean ? 0 : 1);
break;
}
case "context": {
if (!parsed.positional[0]) {
console.error(
`${pc.red("Error:")} Missing query. Usage: project-map context <query>`,
);
process.exit(1);
}
const query = parsed.positional[0];
const bundle = retrieveContext(query, process.cwd());
console.log(bundle);
break;
}
case "reinit": {
const targetPath = parsed.positional[0] || ".";
const start = Date.now();
@@ -204,8 +254,12 @@ async function main() {
llmClient: client,
cacheDir: targetPath,
onProgress: (info) => {
const line = renderProgressBar(info.completed, info.total, info.currentFile);
process.stdout.write("\r" + line.padEnd(lastLine.length));
const line = renderProgressBar(
info.completed,
info.total,
info.currentFile,
);
process.stdout.write(`\r${line.padEnd(lastLine.length)}`);
lastLine = line;
},
});