From 2c891acf4ecf62055717aee5ba93eb5c17860dcf Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 4 Feb 2026 14:47:59 -0800 Subject: [PATCH 1/5] Add support for local agentblame config, implement optional hook tracing --- packages/cli/src/capture.ts | 22 ++++++++++++++++++++-- packages/cli/src/index.ts | 1 + packages/cli/src/lib/config.ts | 2 ++ packages/cli/src/lib/hooks.ts | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/capture.ts b/packages/cli/src/capture.ts index c9af7e1..e62c92b 100644 --- a/packages/cli/src/capture.ts +++ b/packages/cli/src/capture.ts @@ -10,7 +10,6 @@ * echo '{"payload": ...}' | bun run capture.ts --provider claude * echo '{"payload": ...}' | bun run capture.ts --provider opencode */ - import * as fs from "node:fs"; import * as path from "node:path"; import { @@ -51,6 +50,7 @@ import { getLastDeltaForFile, } from "./lib/delta"; import type { AiAgent } from "./lib/types"; +import { writeHookTrace } from "./lib/hooks"; // ============================================================================= // Types @@ -232,6 +232,8 @@ async function setupCaptureContext( conversationId: string, model: string | null ): Promise { + // this will override the config used before we had a payload from the agent. Once we have a payload, + // prefer the paths indicated in the payload. const repoRoot = await findRepoRoot(filePath); if (!repoRoot) { if (process.env.AGENTBLAME_DEBUG) { @@ -817,11 +819,27 @@ export async function runCapture(): Promise { if (process.env.AGENTBLAME_DEBUG) { console.error(`[agentblame] Capture: provider=${provider}, event=${event}`); } - + if (!input.trim()) { + console.warn(`[agentblame] Capture: input empty, returning 0`) process.exit(0); } + // make best-effort to load an existing config using cwd- payload may override this config dir later in the process + const repoRoot = await findRepoRoot(process.cwd()); + if (!repoRoot) { + console.warn(`[agentblame] Capture: could not detect repo root via cwd, configs may not work until we get a payload`) + } + + const traceHooks = repoRoot ? await getConfig(repoRoot, 'traceHooks') : false; + + if (traceHooks && repoRoot) { + const traceFile = writeHookTrace(repoRoot, input); + if (traceFile && process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] Trace written: ${traceFile}`); + } + } + const data = JSON.parse(input); const payload = data.payload || data; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3349cc6..a909ab7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -253,6 +253,7 @@ async function cleanupGlobalInstall(): Promise<{ async function runInit(initArgs: string[] = []): Promise { const forceCleanup = initArgs.includes("--force") || initArgs.includes("-f"); + const traceHooks = initArgs.includes("--trace-hooks"); // Check if Bun is installed (required for hooks) if (!isBunInstalled()) { diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index d2a8070..ef42b9b 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -13,10 +13,12 @@ import { runGit } from "./git/gitCli"; export interface AgentBlameConfig { /** Whether to store actual prompt content (default: true) */ storePromptContent: boolean; + traceHooks: boolean; } const CONFIG_DEFAULTS: AgentBlameConfig = { storePromptContent: false, // Default: don't store prompt text (privacy-first) + traceHooks: false, // don't leave debug trace files around unless user opts in }; const CONFIG_PREFIX = "agentblame"; diff --git a/packages/cli/src/lib/hooks.ts b/packages/cli/src/lib/hooks.ts index f699a3c..3bbc254 100644 --- a/packages/cli/src/lib/hooks.ts +++ b/packages/cli/src/lib/hooks.ts @@ -786,3 +786,35 @@ export async function uninstallGitHubAction(repoRoot: string): Promise return false; } } + + +/** + * Write a trace file and log entry + * @param agentblameDir - The .agentblame directory + * @param input - The raw input string to trace + * @returns The trace filename if written, null if tracing disabled + */ +export function writeHookTrace(repoRoot: string, input: string): string | null { + + // Create traces directory if needed + const agentblameDir = path.join(repoRoot, ".agentblame"); + const tracesDir = path.join(agentblameDir, "traces"); + if (!fs.existsSync(tracesDir)) { + fs.mkdirSync(tracesDir, { recursive: true }); + } + + // Generate filename with timestamp + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const filename = `${timestamp}.json`; + const tracePath = path.join(tracesDir, filename); + + // Write the trace file + fs.writeFileSync(tracePath, input, "utf8"); + + // Append to debug.log + const logPath = path.join(agentblameDir, "debug.log"); + const logLine = `${new Date().toISOString()} ${filename}\n`; + fs.appendFileSync(logPath, logLine, "utf8"); + + return filename; +} From 2aa3ec196bbe154634ff1c40e083d528a85d793b Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 4 Feb 2026 14:48:12 -0800 Subject: [PATCH 2/5] Put trace log in the right dir --- packages/cli/src/capture.ts | 3 ++- packages/cli/src/lib/hooks.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/capture.ts b/packages/cli/src/capture.ts index e62c92b..ffc1897 100644 --- a/packages/cli/src/capture.ts +++ b/packages/cli/src/capture.ts @@ -10,6 +10,7 @@ * echo '{"payload": ...}' | bun run capture.ts --provider claude * echo '{"payload": ...}' | bun run capture.ts --provider opencode */ + import * as fs from "node:fs"; import * as path from "node:path"; import { @@ -819,7 +820,7 @@ export async function runCapture(): Promise { if (process.env.AGENTBLAME_DEBUG) { console.error(`[agentblame] Capture: provider=${provider}, event=${event}`); } - + if (!input.trim()) { console.warn(`[agentblame] Capture: input empty, returning 0`) process.exit(0); diff --git a/packages/cli/src/lib/hooks.ts b/packages/cli/src/lib/hooks.ts index 3bbc254..e98eac0 100644 --- a/packages/cli/src/lib/hooks.ts +++ b/packages/cli/src/lib/hooks.ts @@ -812,7 +812,7 @@ export function writeHookTrace(repoRoot: string, input: string): string | null { fs.writeFileSync(tracePath, input, "utf8"); // Append to debug.log - const logPath = path.join(agentblameDir, "debug.log"); + const logPath = path.join(tracesDir, "traces.log"); const logLine = `${new Date().toISOString()} ${filename}\n`; fs.appendFileSync(logPath, logLine, "utf8"); From c301b6e879e70fafc613b19707363c9d669c93db Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 4 Feb 2026 14:48:17 -0800 Subject: [PATCH 3/5] Store config on init --- packages/cli/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a909ab7..2457fdb 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -319,6 +319,10 @@ async function runInit(initArgs: string[] = []): Promise { setDatabasePath(dbPath); initDatabase(); results.push({ name: "Database (.git/agentblame/)", success: true }); + if (traceHooks) { + setConfig(repoRoot, 'traceHooks', traceHooks); + results.push({ name: "Config (traceHooks = true)", success: true }); + } } catch (err) { results.push({ name: "Database", success: false }); } From 02b08bf3c81a2de76146f715d787b59686658402 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 4 Feb 2026 14:48:22 -0800 Subject: [PATCH 4/5] Support the other config key at startup --- packages/cli/src/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 2457fdb..823a10a 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -252,8 +252,10 @@ async function cleanupGlobalInstall(): Promise<{ } async function runInit(initArgs: string[] = []): Promise { + // TODO: map these CL switches to config value enum keys const forceCleanup = initArgs.includes("--force") || initArgs.includes("-f"); const traceHooks = initArgs.includes("--trace-hooks"); + const storePromptContent = initArgs.includes("--store-prompt-content"); // Check if Bun is installed (required for hooks) if (!isBunInstalled()) { @@ -319,9 +321,10 @@ async function runInit(initArgs: string[] = []): Promise { setDatabasePath(dbPath); initDatabase(); results.push({ name: "Database (.git/agentblame/)", success: true }); - if (traceHooks) { + if (traceHooks || storePromptContent) { setConfig(repoRoot, 'traceHooks', traceHooks); - results.push({ name: "Config (traceHooks = true)", success: true }); + setConfig(repoRoot, 'storePromptContent', storePromptContent); + results.push({ name: `Config values`, success: true }); } } catch (err) { results.push({ name: "Database", success: false }); From 60730e8ea19c206d46c8efa0b3d5749bb606c6fb Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 4 Feb 2026 14:48:31 -0800 Subject: [PATCH 5/5] Await config set calls to avoid git lock issues --- packages/cli/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 823a10a..3b86c82 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -322,8 +322,8 @@ async function runInit(initArgs: string[] = []): Promise { initDatabase(); results.push({ name: "Database (.git/agentblame/)", success: true }); if (traceHooks || storePromptContent) { - setConfig(repoRoot, 'traceHooks', traceHooks); - setConfig(repoRoot, 'storePromptContent', storePromptContent); + await setConfig(repoRoot, 'traceHooks', traceHooks); + await setConfig(repoRoot, 'storePromptContent', storePromptContent); results.push({ name: `Config values`, success: true }); } } catch (err) {