UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

69 lines (68 loc) • 3.16 kB
import { AsyncLocalStorage } from "node:async_hooks"; import { createHash } from "node:crypto"; import * as path from "node:path"; const writes = new AsyncLocalStorage(); /** Scope receipts to one queued mutation, including its asynchronous claim sync. */ export async function captureTaskWrites(fn) { const state = {}; return writes.run(state, async () => ({ result: await fn(), write: state.write?.receipt ?? null })); } /** Called only after the task file's atomic rename succeeds. */ export function recordTaskWrite(file, content) { const state = writes.getStore(); if (state) state.write = { receipt: { path: path.resolve(file), commit: null }, content }; } /** * Observe commits made by task claim sync without changing when it commits. * Keep the autosave's hash even if a later fetch/merge moves HEAD or push fails. * Reading HEAD alone would also acknowledge unrelated or failed commits. */ export function trackTaskWriteCommits(git) { return async (cwd, args) => { const write = writes.getStore()?.write; const committing = args[0] === "commit" || (args[0] === "-c" && args[2] === "commit"); if (!write || !committing) return git(cwd, args); let before; try { before = await git(cwd, ["rev-parse", "--verify", "HEAD"]); } catch { return git(cwd, args); } const result = await git(cwd, args); if (!result.ok) return result; // Verification is best effort and must never turn a completed write into a failure. try { const relative = path.relative(path.resolve(cwd), write.receipt.path).split(path.sep).join("/"); if (relative.startsWith("../") || path.isAbsolute(relative)) return result; const head = await git(cwd, ["rev-list", "--parents", "-n", "1", "HEAD"]); if (!head.ok) return result; const [commit, ...parents] = head.output.trim().split(/\s+/); if (!/^[0-9a-f]{40,64}$/.test(commit)) return result; if (before.ok ? parents.length !== 1 || parents[0] !== before.output.trim() : parents.length !== 0) return result; const tree = await git(cwd, ["ls-tree", commit, "--", relative]); const blob = tree.ok && /^\d+ blob ([0-9a-f]+)\t/.exec(tree.output)?.[1]; if (!blob) return result; const expected = createHash(blob.length === 64 ? "sha256" : "sha1") .update(`blob ${Buffer.byteLength(write.content)}\0`).update(write.content).digest("hex"); if (blob !== expected) return result; if (parents.length) { const prior = await git(cwd, ["ls-tree", parents[0], "--", relative]); if (!prior.ok || /^\d+ blob ([0-9a-f]+)\t/.exec(prior.output)?.[1] === blob) return result; } write.receipt.commit = commit; } catch { /* Leave the receipt uncommitted when verification is unavailable. */ } return result; }; }