Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/cli/src/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
getLastDeltaForFile,
} from "./lib/delta";
import type { AiAgent } from "./lib/types";
import { writeHookTrace } from "./lib/hooks";

// =============================================================================
// Types
Expand Down Expand Up @@ -232,6 +233,8 @@ async function setupCaptureContext(
conversationId: string,
model: string | null
): Promise<CaptureContext | null> {
// 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) {
Expand Down Expand Up @@ -819,9 +822,25 @@ export async function runCapture(): Promise<void> {
}

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;

Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,10 @@ async function cleanupGlobalInstall(): Promise<{
}

async function runInit(initArgs: string[] = []): Promise<void> {
// 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()) {
Expand Down Expand Up @@ -318,6 +321,11 @@ async function runInit(initArgs: string[] = []): Promise<void> {
setDatabasePath(dbPath);
initDatabase();
results.push({ name: "Database (.git/agentblame/)", success: true });
if (traceHooks || storePromptContent) {
await setConfig(repoRoot, 'traceHooks', traceHooks);
await setConfig(repoRoot, 'storePromptContent', storePromptContent);
results.push({ name: `Config values`, success: true });
}
} catch (err) {
results.push({ name: "Database", success: false });
}
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
32 changes: 32 additions & 0 deletions packages/cli/src/lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,3 +786,35 @@ export async function uninstallGitHubAction(repoRoot: string): Promise<boolean>
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(tracesDir, "traces.log");
const logLine = `${new Date().toISOString()} ${filename}\n`;
fs.appendFileSync(logPath, logLine, "utf8");

return filename;
}