feat(FN-1404): add run-audit instrumentation layer for agent run mutations

- Add shared RunAuditService in packages/engine/src/run-audit.ts for tracking agent run state transitions
- Instrument HeartbeatMonitor to log run mutations (create, complete, error, skip) with timestamps and context
- Instrument TaskExecutor to log run mutations during task execution lifecycle
- Instrument Merger to log run mutations during merge workflow
- Add run-audit pattern documentation to project memory
- Fix: remove audit calls from non-run recovery methods to avoid false positives
This commit is contained in:
gsxdsm
2026-04-10 02:56:57 -07:00
parent 2c06d72203
commit e8dd0f2416
5 changed files with 360 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ import { withRateLimitRetry } from "./rate-limit-retry.js";
import { resolveAgentInstructions, buildSystemPromptWithInstructions } from "./agent-instructions.js";
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { createRunAuditor, generateSyntheticRunId, type EngineRunContext } from "./run-audit.js";
/** Conflict type classification for merge conflict resolution */
export type ConflictType =
@@ -709,6 +710,18 @@ export async function aiMergeTask(
branchDeleted: false,
};
// Build merge-run context for audit instrumentation (FN-1404)
const mergeRunId = generateSyntheticRunId("merge", taskId);
const engineRunContext: EngineRunContext = {
runId: mergeRunId,
agentId: "merger",
taskId,
phase: "merge",
};
// Create run auditor for TaskStore-backed audit emission (no-ops if store doesn't support it)
const audit = createRunAuditor(store, engineRunContext);
if (!worktreePath) {
mergerLog.warn(`${taskId}: no worktree path set — skipping worktree cleanup`);
}
@@ -747,6 +760,8 @@ export async function aiMergeTask(
} catch {
// No commit SHA available — task will show summary fallback
}
// Audit trail: record merge completion (FN-1404)
await audit.database({ type: "task:move", target: taskId, metadata: { to: "done", merged: false } });
await completeTask(store, taskId, result);
return result;
}
@@ -771,11 +786,15 @@ export async function aiMergeTask(
cwd: rootDir,
stdio: "pipe",
});
// Audit trail: record git checkout (FN-1404)
await audit.git({ type: "branch:checkout", target: mainBranch });
}
} catch {
// Fallback: try checking out main directly
try {
execSync("git checkout main", { cwd: rootDir, stdio: "pipe" });
// Audit trail: record git checkout (FN-1404)
await audit.git({ type: "branch:checkout", target: "main" });
} catch {
mergerLog.warn(`${taskId}: unable to verify/checkout main branch — proceeding on current HEAD`);
}
@@ -860,6 +879,8 @@ export async function aiMergeTask(
mergerLog.log(`${taskId}: attempt ${attemptNum} failed, cleaning up for retry...`);
try {
execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
// Audit trail: record git reset for merge cleanup (FN-1404)
await audit.git({ type: "reset:hard", target: branch, metadata: { purpose: "merge-cleanup", attempt: attemptNum } });
} catch { /* ignore cleanup errors */ }
}
@@ -875,6 +896,8 @@ export async function aiMergeTask(
result._buildRetried = true;
try {
execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
// Audit trail: record git reset for build retry (FN-1404)
await audit.git({ type: "reset:hard", target: branch, metadata: { purpose: "build-retry" } });
} catch { /* ignore cleanup errors */ }
return false; // Retry
}
@@ -886,6 +909,8 @@ export async function aiMergeTask(
mergerLog.log(`${taskId}: attempt ${attemptNum} error, cleaning up for retry...`);
try {
execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
// Audit trail: record git reset for retry (FN-1404)
await audit.git({ type: "reset:hard", target: branch, metadata: { purpose: "merge-retry", attempt: attemptNum } });
} catch { /* ignore cleanup errors */ }
return false; // Allow retry
}
@@ -972,10 +997,14 @@ export async function aiMergeTask(
try {
execSync(`git branch -d "${branch}"`, { cwd: rootDir, stdio: "pipe" });
result.branchDeleted = true;
// Audit trail: record branch deletion (FN-1404)
await audit.git({ type: "branch:delete", target: branch });
} catch {
try {
execSync(`git branch -D "${branch}"`, { cwd: rootDir, stdio: "pipe" });
result.branchDeleted = true;
// Audit trail: record branch deletion (force) (FN-1404)
await audit.git({ type: "branch:delete", target: branch, metadata: { force: true } });
} catch { /* non-fatal */ }
}
@@ -994,6 +1023,8 @@ export async function aiMergeTask(
cwd: rootDir,
stdio: "pipe",
});
// Audit trail: record worktree removal (FN-1404)
await audit.git({ type: "worktree:remove", target: worktreePath });
result.worktreeRemoved = true;
} catch { /* non-fatal */ }
}
@@ -1008,6 +1039,18 @@ export async function aiMergeTask(
}
// 9. Move task to done
// Audit trail: record merge completion (FN-1404)
await audit.database({
type: "task:move",
target: taskId,
metadata: {
to: "done",
merged: true,
resolutionStrategy: result.resolutionStrategy,
resolutionMethod: result.resolutionMethod,
attemptsMade: result.attemptsMade,
},
});
await completeTask(store, taskId, result);
return result;
}