diff --git a/pi/files/agent/extensions/hooks-resolution.ts b/pi/files/agent/extensions/hooks-resolution.ts index df279e6..e08626a 100644 --- a/pi/files/agent/extensions/hooks-resolution.ts +++ b/pi/files/agent/extensions/hooks-resolution.ts @@ -1,11 +1,9 @@ -import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs"; +import { existsSync, readFileSync, statSync } from "node:fs"; import { spawn } from "node:child_process"; -import * as os from "node:os"; -import { dirname, join, resolve } from "node:path"; +import { basename, dirname, join, resolve } from "node:path"; import type { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@mariozechner/pi-coding-agent"; const HOOK_TIMEOUT_MS = 10 * 60 * 1000; -const LOG_FILE = join(os.homedir(), ".pi", "hooks-resolution.log"); type HookEventName = "PostToolUse" | "PostToolUseFailure"; @@ -30,18 +28,6 @@ type CommandRunResult = { timedOut: boolean; }; -function logHook(message: string): void { - const line = `[${new Date().toISOString()}] ${message}`; - console.error(`[hooks-resolution] ${line}`); - - try { - mkdirSync(dirname(LOG_FILE), { recursive: true }); - appendFileSync(LOG_FILE, `${line}\n`); - } catch { - // ignore logging failures - } -} - function isFile(path: string): boolean { try { return statSync(path).isFile(); @@ -471,6 +457,23 @@ function hookEventNameForResult(event: ToolResultEvent): HookEventName { return event.isError ? "PostToolUseFailure" : "PostToolUse"; } +function formatDuration(elapsedMs: number): string { + if (elapsedMs < 1000) { + return `${elapsedMs}ms`; + } + return `${(elapsedMs / 1000).toFixed(1)}s`; +} + +function hookName(command: string): string { + const shPathMatch = command.match(/[^\s|;&]+\.sh\b/); + if (shPathMatch) { + return basename(shPathMatch[0]); + } + + const firstToken = command.trim().split(/\s+/)[0] ?? "hook"; + return basename(firstToken); +} + export default function(pi: ExtensionAPI) { let state: HookState = { projectDir: process.cwd(), @@ -479,13 +482,6 @@ export default function(pi: ExtensionAPI) { const refreshHooks = (cwd: string) => { state = loadHooks(cwd); - const postCount = state.hooks.filter((hook) => hook.eventName === "PostToolUse").length; - const failureCount = state.hooks.filter( - (hook) => hook.eventName === "PostToolUseFailure", - ).length; - logHook( - `loaded hooks projectDir=${state.projectDir} postToolUse=${postCount} postToolUseFailure=${failureCount}`, - ); }; pi.on("session_start", (_event, ctx) => { @@ -514,31 +510,26 @@ export default function(pi: ExtensionAPI) { for (const hook of matchingHooks) { if (executedCommands.has(hook.command)) { - logHook( - `deduped event=${eventName} tool=${event.toolName} source=${hook.source} command=${JSON.stringify(hook.command)}`, - ); continue; } executedCommands.add(hook.command); - logHook( - `run event=${eventName} tool=${event.toolName} matcher=${hook.matcherText ?? "*"} source=${hook.source} command=${JSON.stringify(hook.command)}`, - ); - const result = await runCommandHook(hook.command, state.projectDir, payload); - logHook( - `done event=${eventName} tool=${event.toolName} code=${result.code} durationMs=${result.elapsedMs} timedOut=${result.timedOut}`, - ); + const name = hookName(hook.command); + const duration = formatDuration(result.elapsedMs); - if (result.code !== 0) { - const matcherLabel = hook.matcherText ?? "*"; - const errorLine = - result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`; - ctx.ui.notify( - `Hook failed (${matcherLabel}) from ${hook.source}: ${errorLine}`, - "warning", - ); + if (result.code === 0) { + ctx.ui.notify(`󰛢 Hook \`${name}\` executed, took ${duration}`, "info"); + continue; } + + const matcherLabel = hook.matcherText ?? "*"; + const errorLine = + result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`; + ctx.ui.notify( + `󰛢 Hook \`${name}\` failed after ${duration} (${matcherLabel}) from ${hook.source}: ${errorLine}`, + "warning", + ); } }); }