fix: remove conflicting tree-sitter-go and tree-sitter-python dependencies
These grammars require tree-sitter@^0.25.0 but we use 0.21.1. They already failed at runtime with try/catch graceful fallback. Removing them fixes npm peer dependency conflict during pi install. - Remove tree-sitter-go and tree-sitter-python from dependencies - ast-extract.ts already handles missing grammars gracefully - Heuristics + LLM layer cover Go/Python analysis anyway
This commit is contained in:
+133
-69
@@ -1,44 +1,62 @@
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
import { execSync } from "child_process";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { readFileSync, readdirSync, statSync } from "fs";
|
||||
import { join, relative } from "path";
|
||||
import {
|
||||
initProject,
|
||||
patchFile,
|
||||
validateMaps,
|
||||
reinitPath,
|
||||
} from "./src/index.js";
|
||||
import { createLLMClient } from "./src/llm-client.js";
|
||||
import { LLMError } from "./src/llm-error.js";
|
||||
|
||||
function runCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
): { stdout: string; stderr: string; success: boolean } {
|
||||
try {
|
||||
const result = execSync(`npx project-map ${command} ${args.join(" ")}`, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return { stdout: result, stderr: "", success: true };
|
||||
} catch (error: any) {
|
||||
return {
|
||||
stdout: error.stdout || "",
|
||||
stderr: error.stderr || error.message,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the LLM client for Pi runtime.
|
||||
*
|
||||
* When running inside Pi, we ALWAYS use Pi's native LLM.
|
||||
* If Pi's LLM is not accessible, this throws a hard error.
|
||||
* We ignore all external configuration (env vars, config files, etc.)
|
||||
* because inside Pi we must use Pi's model exclusively.
|
||||
*/
|
||||
function getPiLLMClient(ctx: any) {
|
||||
return createLLMClient("pi", { extensionContext: ctx });
|
||||
}
|
||||
|
||||
function findPiMapFiles(cwd: string): string[] {
|
||||
try {
|
||||
const result = execSync('find . -name ".pi-map.md" -type f', {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
});
|
||||
return result.trim().split("\n").filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
const results: string[] = [];
|
||||
function walk(dir: string) {
|
||||
let entries: import("fs").Dirent[];
|
||||
try {
|
||||
entries = readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (
|
||||
entry.isDirectory() &&
|
||||
!entry.name.startsWith(".") &&
|
||||
entry.name !== "node_modules"
|
||||
) {
|
||||
walk(join(dir, entry.name));
|
||||
}
|
||||
}
|
||||
try {
|
||||
statSync(join(dir, ".pi-map.md"));
|
||||
results.push(relative(cwd, join(dir, ".pi-map.md")));
|
||||
} catch {
|
||||
// no map in this dir
|
||||
}
|
||||
}
|
||||
walk(cwd);
|
||||
return results;
|
||||
}
|
||||
|
||||
function isDirty(content: string): boolean {
|
||||
return content.includes("## dirty") && !content.includes("## dirty\n-");
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
// Register custom tools
|
||||
pi.registerTool({
|
||||
name: "project_map_init",
|
||||
label: "Project Map Init",
|
||||
@@ -58,16 +76,26 @@ export default function (pi: ExtensionAPI) {
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const targetPath = params.path || ctx.cwd;
|
||||
const result = runCommand(
|
||||
"init",
|
||||
targetPath === ctx.cwd ? [] : [targetPath],
|
||||
ctx.cwd,
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: result.stdout || result.stderr }],
|
||||
details: { success: result.success, cwd: ctx.cwd },
|
||||
};
|
||||
try {
|
||||
const targetPath = params.path || ctx.cwd;
|
||||
const client = getPiLLMClient(ctx);
|
||||
await initProject(targetPath, { verbose: false, llmClient: client });
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Generated .pi-map.md files for ${targetPath}`,
|
||||
},
|
||||
],
|
||||
details: { success: true, cwd: ctx.cwd },
|
||||
};
|
||||
} catch (err: any) {
|
||||
const msg = err instanceof LLMError ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${msg}` }],
|
||||
details: { success: false, error: msg },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,11 +115,25 @@ export default function (pi: ExtensionAPI) {
|
||||
}),
|
||||
}),
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const result = runCommand("patch", [params.file_path], ctx.cwd);
|
||||
return {
|
||||
content: [{ type: "text", text: result.stdout || result.stderr }],
|
||||
details: { success: result.success },
|
||||
};
|
||||
try {
|
||||
const client = getPiLLMClient(ctx);
|
||||
await patchFile(params.file_path, client);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Patched map for ${params.file_path}`,
|
||||
},
|
||||
],
|
||||
details: { success: true },
|
||||
};
|
||||
} catch (err: any) {
|
||||
const msg = err instanceof LLMError ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${msg}` }],
|
||||
details: { success: false, error: msg },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -112,19 +154,29 @@ export default function (pi: ExtensionAPI) {
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const targetPath = params.path || ctx.cwd;
|
||||
const result = runCommand(
|
||||
"validate",
|
||||
targetPath === ctx.cwd ? [] : [targetPath],
|
||||
ctx.cwd,
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: result.stdout || result.stderr }],
|
||||
details: {
|
||||
success: result.success,
|
||||
clean: result.stdout.includes("clean"),
|
||||
},
|
||||
};
|
||||
try {
|
||||
const targetPath = params.path || ctx.cwd;
|
||||
const result = await validateMaps(targetPath, {
|
||||
fix: false,
|
||||
verbose: false,
|
||||
});
|
||||
const text = result.clean
|
||||
? "All .pi-map.md files are clean."
|
||||
: `Found ${result.discrepancies.length} discrepancies:\n` +
|
||||
result.discrepancies
|
||||
.map((d) => ` [${d.type}] ${d.path}: ${d.message}`)
|
||||
.join("\n");
|
||||
return {
|
||||
content: [{ type: "text", text }],
|
||||
details: { success: true, clean: result.clean },
|
||||
};
|
||||
} catch (err: any) {
|
||||
const msg = err instanceof LLMError ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${msg}` }],
|
||||
details: { success: false, error: msg },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -145,12 +197,26 @@ export default function (pi: ExtensionAPI) {
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const args = params.path ? [params.path] : [];
|
||||
const result = runCommand("reinit", args, ctx.cwd);
|
||||
return {
|
||||
content: [{ type: "text", text: result.stdout || result.stderr }],
|
||||
details: { success: result.success },
|
||||
};
|
||||
try {
|
||||
const targetPath = params.path || ctx.cwd;
|
||||
const client = getPiLLMClient(ctx);
|
||||
await reinitPath(targetPath, { verbose: false, llmClient: client });
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Regenerated maps for ${targetPath}`,
|
||||
},
|
||||
],
|
||||
details: { success: true },
|
||||
};
|
||||
} catch (err: any) {
|
||||
const msg = err instanceof LLMError ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${msg}` }],
|
||||
details: { success: false, error: msg },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -159,11 +225,10 @@ export default function (pi: ExtensionAPI) {
|
||||
const mapFiles = findPiMapFiles(ctx.cwd);
|
||||
if (mapFiles.length === 0) return;
|
||||
|
||||
// Check for dirty markers
|
||||
const dirtyFiles = mapFiles.filter((f) => {
|
||||
try {
|
||||
const content = readFileSync(join(ctx.cwd, f), "utf8");
|
||||
return content.includes("## dirty") && !content.includes("## dirty\n-");
|
||||
return isDirty(content);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -179,7 +244,6 @@ export default function (pi: ExtensionAPI) {
|
||||
|
||||
// Inject maintenance instructions before agent starts
|
||||
pi.on("before_agent_start", async (_event, _ctx) => {
|
||||
// Only inject if .pi-map.md files exist
|
||||
const mapFiles = findPiMapFiles(_ctx.cwd);
|
||||
if (mapFiles.length === 0) return {};
|
||||
|
||||
@@ -188,7 +252,7 @@ export default function (pi: ExtensionAPI) {
|
||||
customType: "pi-project-map-hint",
|
||||
content:
|
||||
"📋 Project map active: If you modify any source file, run `project_map_patch` with the file path. If you suspect staleness, run `project_map_validate`.",
|
||||
display: false, // Don't show in UI, only in LLM context
|
||||
display: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user