Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "agentblame",
"displayName": "Agent Blame",
"description": "Track AI-generated vs human-written code. Provides git notes storage, CLI, and GitHub PR attribution.",
"version": "0.2.6",
"version": "0.2.10",
"private": true,
"license": "Apache-2.0",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mesadev/agentblame",
"version": "0.2.9",
"version": "0.2.10",
"description": "CLI to track AI-generated vs human-written code",
"license": "Apache-2.0",
"repository": {
Expand Down
69 changes: 40 additions & 29 deletions packages/cli/src/lib/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,10 @@ export interface InsertEditParams {
}

/**
* Insert a new AI edit into the database
* Insert a new AI edit into the database.
* Uses an explicit transaction to ensure atomicity - either all data
* is written (edit + lines) or none. This is especially important
* when running async hooks where the process could be interrupted.
*/
export function insertEdit(params: InsertEditParams): number {
const db = getDatabase();
Expand All @@ -217,41 +220,49 @@ export function insertEdit(params: InsertEditParams): number {
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);

const result = editStmt.run(
params.timestamp,
params.provider,
params.filePath,
params.model,
params.content,
params.contentHash,
params.contentHashNormalized,
params.editType,
params.oldContent || null,
params.sessionId || null,
params.toolUseId || null
);

const editId = Number(result.lastInsertRowid);

// Insert lines with line numbers and context
const lineStmt = db.prepare(`
INSERT INTO lines (edit_id, content, hash, hash_normalized, line_number, context_before, context_after)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);

for (const line of params.lines) {
lineStmt.run(
editId,
line.content,
line.hash,
line.hashNormalized,
line.lineNumber || null,
line.contextBefore || null,
line.contextAfter || null
// Wrap in transaction for atomicity
db.exec("BEGIN TRANSACTION");
try {
const result = editStmt.run(
params.timestamp,
params.provider,
params.filePath,
params.model,
params.content,
params.contentHash,
params.contentHashNormalized,
params.editType,
params.oldContent || null,
params.sessionId || null,
params.toolUseId || null
);
}

return editId;
const editId = Number(result.lastInsertRowid);

// Insert lines with line numbers and context
for (const line of params.lines) {
lineStmt.run(
editId,
line.content,
line.hash,
line.hashNormalized,
line.lineNumber || null,
line.contextBefore || null,
line.contextAfter || null
);
}

db.exec("COMMIT");
return editId;
} catch (err) {
db.exec("ROLLBACK");
throw err;
}
}

// =============================================================================
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,10 @@ export async function installClaudeHooks(repoRoot: string): Promise<boolean> {
)
);

// Add the new hook
// Add the new hook with async: true for non-blocking execution
config.hooks.PostToolUse.push({
matcher: "Edit|Write|MultiEdit",
hooks: [{ type: "command", command: hookCommand }],
hooks: [{ type: "command", command: hookCommand, async: true }],
});

await fs.promises.writeFile(
Expand Down