ontology_diff
Compare domain ontology between two git revisions to reveal added, removed, or changed concepts. Tracks vocabulary evolution across commits and helps identify naming inconsistencies in pull requests.
Instructions
Compare the domain ontology between two git revisions — shows concepts that were added, removed, or changed. Useful for understanding how the project's vocabulary evolved across commits or for reviewing whether a PR introduced naming inconsistencies.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| since | No | Git ref to diff from (default: HEAD~5) |
Implementation Reference
- pi/extensions/index.ts:192-207 (schema)Schema definition for ontology_diff tool: defines mcpName, label, description, promptSnippet, and parameters (optional 'since' string defaulting to HEAD~5).
{ mcpName: "ontology_diff", label: "Ontology Diff", description: "Compare the domain ontology between git revisions — shows concepts " + "added, removed, or changed.", promptSnippet: "ontomics_ontology_diff: diff domain vocabulary across git revisions", parameters: Type.Object({ since: Type.Optional( Type.String({ description: "Git ref to diff from (default: HEAD~5)", }), ), }), }, - pi/extensions/index.ts:362-387 (registration)Registration loop that iterates over toolDefs() and calls pi.registerTool for each, including ontology_diff. The execute handler delegates to an MCP subprocess (ontomics serve) via callTool.
for (const def of toolDefs()) { pi.registerTool({ name: `ontomics_${def.mcpName}`, label: def.label, description: def.description, promptSnippet: def.promptSnippet, promptGuidelines: [ "Use ontomics tools BEFORE grep/glob for semantic codebase questions.", ], parameters: def.parameters, async execute(_toolCallId, params, _signal, onUpdate, _ctx) { onUpdate?.({ content: [{ type: "text", text: `Querying ontomics: ${def.mcpName}...` }], }); try { const mcp = await getClient(); const text = await mcp.callTool(def.mcpName, cleanArgs(params)); return { content: [{ type: "text", text }] }; } catch (err) { throw new Error( `ontomics ${def.mcpName} failed: ${err instanceof Error ? err.message : String(err)}`, ); } }, }); } - pi/extensions/index.ts:372-385 (handler)The execute handler for ontology_diff (and all registered tools). It calls the external ontomics MCP server over stdio using McpClient.callTool, passing the tool name and cleaned arguments.
async execute(_toolCallId, params, _signal, onUpdate, _ctx) { onUpdate?.({ content: [{ type: "text", text: `Querying ontomics: ${def.mcpName}...` }], }); try { const mcp = await getClient(); const text = await mcp.callTool(def.mcpName, cleanArgs(params)); return { content: [{ type: "text", text }] }; } catch (err) { throw new Error( `ontomics ${def.mcpName} failed: ${err instanceof Error ? err.message : String(err)}`, ); } }, - pi/extensions/index.ts:13-99 (helper)McpClient class that communicates with the ontomics binary via JSON-RPC 2.0 over stdio. Used by the execute handler to call the ontology_diff tool on the external server.
class McpClient { private proc: ChildProcess; private rl: ReadlineInterface; private nextId = 1; private pending = new Map< number, { resolve: (v: unknown) => void; reject: (e: Error) => void } >(); private constructor(proc: ChildProcess) { this.proc = proc; this.rl = createInterface({ input: proc.stdout! }); this.rl.on("line", (line: string) => this.onLine(line)); proc.stderr?.on("data", () => {}); } static async start(binaryPath: string, cwd: string): Promise<McpClient> { const proc = spawn(binaryPath, ["serve"], { cwd, stdio: ["pipe", "pipe", "pipe"], }); const client = new McpClient(proc); await client.request("initialize", { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "pi-ontomics", version: "1.0.0" }, }); client.notify("notifications/initialized", {}); return client; } async callTool( name: string, args: Record<string, unknown>, ): Promise<string> { const result = (await this.request("tools/call", { name, arguments: args, })) as { content?: Array<{ text?: string }> }; const text = result.content?.[0]?.text ?? JSON.stringify(result); return text; } dispose(): void { this.proc.kill(); this.rl.close(); } get alive(): boolean { return !this.proc.killed && this.proc.exitCode === null; } private request(method: string, params: unknown): Promise<unknown> { const id = this.nextId++; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.write({ jsonrpc: "2.0", id, method, params }); }); } private notify(method: string, params: unknown): void { this.write({ jsonrpc: "2.0", method, params }); } private write(msg: unknown): void { this.proc.stdin!.write(JSON.stringify(msg) + "\n"); } private onLine(line: string): void { if (!line.trim()) return; try { const msg = JSON.parse(line) as { id?: number; result?: unknown; error?: { message: string }; }; if (msg.id != null && this.pending.has(msg.id)) { const { resolve, reject } = this.pending.get(msg.id)!; this.pending.delete(msg.id); if (msg.error) reject(new Error(msg.error.message)); else resolve(msg.result); } } catch { // ignore non-JSON lines (e.g. stderr leaks) } } }