import assert from "node:assert/strict"; import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import hippoExtension from "../hippo-memory-pi/index.ts"; test("official Hippo extension registers five tools and runs lifecycle CLI calls", async () => { const root = await mkdtemp(join(tmpdir(), "my-pi-hippo-extension-")); const bin = join(root, "bin"); const project = join(root, "project"); const log = join(root, "hippo.log"); await mkdir(bin, { recursive: true }); await mkdir(join(project, ".hippo"), { recursive: true }); const hippo = join(bin, "hippo"); await writeFile( hippo, `#!/bin/sh\nprintf '%s\\n' "$*" >> "$HIPPO_TEST_LOG"\ncase "$1" in\n status) printf '%s\\n' 'Hippo status ok' ;;\n context) printf '%s\\n' 'Remembered project context for testing' ;;\n recall) printf '%s\\n' 'Relevant project memory' ;;\n remember) printf '%s\\n' 'Remembered [test]' ;;\n outcome) printf '%s\\n' 'Outcome recorded' ;;\n sleep) printf '%s\\n' 'Sleep complete' ;;\nesac\n`, "utf8", ); await chmod(hippo, 0o755); const previousPath = process.env.PATH; const previousLog = process.env.HIPPO_TEST_LOG; process.env.PATH = `${bin}:${previousPath ?? ""}`; process.env.HIPPO_TEST_LOG = log; try { const handlers = new Map Promise>>(); const tools = new Map(); hippoExtension({ on(name: string, handler: (event: any, ctx: any) => Promise) { const entries = handlers.get(name) ?? []; entries.push(handler); handlers.set(name, entries); }, registerTool(tool: any) { tools.set(tool.name, tool); }, }); assert.deepEqual( [...tools.keys()].sort(), ["hippo_context", "hippo_outcome", "hippo_recall", "hippo_remember", "hippo_status"], ); const ctx = { cwd: project }; const startResult = await handlers.get("session_start")?.[0]?.({}, ctx); assert.match(startResult.systemPromptAppend, /Remembered project context/); const rememberResult = await tools.get("hippo_remember").execute("call-1", { text: "A durable lesson" }, undefined, undefined, ctx); assert.match(rememberResult.content[0].text, /Remembered \[test\]/); await handlers.get("tool_result")?.[0]?.( { isError: true, toolName: "build", content: "Compilation failed because the generated symbol was missing" }, ctx, ); await handlers.get("session_shutdown")?.[0]?.({}, ctx); const calls = await readFile(log, "utf8"); assert.match(calls, /context --auto/); assert.match(calls, /remember A durable lesson/); assert.match(calls, /remember Tool 'build' failed:/); assert.match(calls, /sleep/); } finally { process.env.PATH = previousPath; if (previousLog === undefined) delete process.env.HIPPO_TEST_LOG; else process.env.HIPPO_TEST_LOG = previousLog; } });