// THE one wrapper every manifest writer goes through -- the routes (through // lib/report/guard.ts, which types it) and the `umtool window` CLI alike. // // A manifest with `generatedBy` is rebuilt by its generator, and the rebuild // overwrites edits made here (the banner on the project page, the bench and // the On-screen section says so). The edit is still made; what this adds is a // record of it: the manifest is read before and after the write, and every // change becomes an `edit` note in the project's notes.json for the agent to // port into the generator's inputs (./edit-notes.mjs). A hand-edited manifest // (no `generatedBy`) is written exactly as before. // // The notes are written AFTER the manifest and never fail the write: the edit // is saved either way, and `editNotes.errors` says when its note is not. // // Plain .mjs so the CLI can run it with bare node; the routes read it through // guard.ts. import { projectTargetFor } from "../annotations/targets.mjs"; import { readManifest } from "../projects/report.mjs"; import { editsBetween, recordEdits } from "./edit-notes.mjs"; /** * @template T * @param {{ id: string, dir: string, kind: string }} project * @param {() => Promise} write * @param {{ reportsRoot?: string }} [opts] the reports root notes may live under (tests) * @returns {Promise<{ result: T, editNotes: { generatedBy: string, added: number, updated: number, deleted: number, errors: string[] } | null }>} */ export async function withEditNotes(project, write, opts = {}) { const before = await readManifest(project.dir); const result = await write(); const generatedBy = typeof before?.generatedBy === "string" ? before.generatedBy.trim() : ""; if (!generatedBy) return { result, editNotes: null }; const after = await readManifest(project.dir); const edits = editsBetween(before, after); if (!edits.length) return { result, editNotes: { generatedBy, added: 0, updated: 0, deleted: 0, errors: [] } }; try { const target = await projectTargetFor(project, opts.reportsRoot ? { reportsRoot: opts.reportsRoot } : undefined); const counts = await recordEdits(target, edits, generatedBy); return { result, editNotes: { generatedBy, ...counts } }; } catch (e) { return { result, editNotes: { generatedBy, added: 0, updated: 0, deleted: 0, errors: [e instanceof Error ? e.message : String(e)] }, }; } }