/** * Pi adapter for the official codebase-memory-mcp hook frontend. * * CBM owns project resolution, canonical paths, query formatting, coverage, * deadlines, and fail-open behavior. This adapter only translates Pi events * into the documented `hook-augment` payload and attaches the returned * additionalContext to Pi's event/result surface. */ import type { ExtensionAPI, FindToolInput, GrepToolInput, ReadToolInput, } from "@earendil-works/pi-coding-agent"; import { describeError } from "./cbm-lifecycle.ts"; import { runOfficialHook, type HookPayload } from "./cbm-native.ts"; const DISABLED = /^(1|true)$/i.test(process.env.CBM_HOOKS_DISABLE ?? ""); const DEBUG = /^(1|true)$/i.test(process.env.CBM_HOOKS_DEBUG ?? ""); function debug(message: string): void { if (DEBUG) process.stderr.write(`[cbm-graph-context] ${message}\n`); } function textBlock(text: string): { type: "text"; text: string } { return { type: "text", text }; } function toolPayload( toolName: string, input: Record, cwd: string, ): HookPayload | null { if (toolName === "grep") { const grep = input as GrepToolInput; if (typeof grep.pattern !== "string" || !grep.pattern) return null; return { hook_event_name: "PreToolUse", cwd, tool_name: "Grep", tool_input: { pattern: grep.pattern, ...(typeof grep.path === "string" ? { path: grep.path } : {}), }, }; } if (toolName === "find") { const find = input as FindToolInput; if (typeof find.pattern !== "string" || !find.pattern) return null; return { hook_event_name: "PreToolUse", cwd, tool_name: "Glob", tool_input: { pattern: find.pattern, ...(typeof find.path === "string" ? { path: find.path } : {}), }, }; } if (toolName === "read") { const read = input as ReadToolInput; if (typeof read.path !== "string" || !read.path) return null; return { hook_event_name: "PostToolUse", cwd, tool_name: "Read", tool_input: { file_path: read.path }, }; } return null; } export default function (pi: ExtensionAPI): void { if (DISABLED) { debug("disabled by CBM_HOOKS_DISABLE"); return; } pi.on("before_agent_start", async (event, ctx) => { try { const payload: HookPayload = { hook_event_name: "SessionStart", cwd: ctx.cwd, }; const context = await runOfficialHook(payload, ctx.signal); if (!context || event.systemPrompt.includes(context.text)) return; return { systemPrompt: `${event.systemPrompt}\n\n${context.text}` }; } catch (error) { debug(`before_agent_start failed: ${describeError(error)}`); } }); pi.on("tool_result", async (event, ctx) => { if (event.isError) return; const payload = toolPayload(event.toolName, event.input, ctx.cwd); if (!payload) return; try { const context = await runOfficialHook(payload, ctx.signal); if (!context || event.content.some((item) => item.type === "text" && item.text.includes(context.text))) return; return { content: [textBlock(context.text), ...event.content] }; } catch (error) { debug(`tool_result failed: ${describeError(error)}`); } }); }