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
6 changes: 6 additions & 0 deletions docs/writeback-evolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ fclt ai evolve verify EV-00001 \
--note "The producing loop no longer repeats the failure"
```

`evolve show <id> --json` inspects a proposal without changing it. Repeating
`evolve draft <id>` preserves an existing draft, patch, and review state; use
`--append` only for an intentional substantive revision. If a patch is missing,
drafting rebuilds it from the existing authored draft rather than replacing that
draft with generated boilerplate.

Applying a proposal moves its source writebacks into an awaiting-verification state. Verification
then records one of `improved`, `unchanged`, `regressed`, or `inconclusive`. Improved evidence
resolves the writebacks; unchanged or regressed evidence returns them to the pending queue;
Expand Down
36 changes: 36 additions & 0 deletions src/ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1367,12 +1367,48 @@ describe("ai writeback", () => {
"Require post-apply effectiveness verification."
);

const beforeRepeat = await showProposal(proposal!.id, {
homeDir: tempHome,
rootDir,
});
const authoredDraftPath = join(
facultAiDraftDir(tempHome, rootDir),
`${proposal!.id}.md`
);
const authoredPatchPath = join(
facultAiDraftDir(tempHome, rootDir),
`${proposal!.id}.patch`
);
const authoredDraft = await readFile(authoredDraftPath, "utf8");
const journalBefore = await readFile(
facultAiJournalPath(tempHome, rootDir),
"utf8"
);
expect(
await draftProposal(proposal!.id, { homeDir: tempHome, rootDir })
).toEqual(beforeRepeat!);
expect(await readFile(authoredDraftPath, "utf8")).toBe(authoredDraft);
expect(await readFile(authoredPatchPath, "utf8")).toBe(revisedPatch);
expect(await readFile(facultAiJournalPath(tempHome, rootDir), "utf8")).toBe(
journalBefore
);

await rm(authoredPatchPath);
await draftProposal(proposal!.id, { homeDir: tempHome, rootDir });
expect((await readFile(authoredDraftPath, "utf8")).trimEnd()).toBe(
authoredDraft.trimEnd()
);
expect(await readFile(authoredPatchPath, "utf8")).toBe(revisedPatch);

const accepted = await acceptProposal(proposal!.id, {
homeDir: tempHome,
rootDir,
});
expect(accepted.status).toBe("accepted");
expect(accepted.review?.status).toBe("accepted");
expect(
await draftProposal(proposal!.id, { homeDir: tempHome, rootDir })
).toEqual(accepted);

const applied = await applyProposal(proposal!.id, {
homeDir: tempHome,
Expand Down
29 changes: 18 additions & 11 deletions src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2205,6 +2205,20 @@ export async function draftProposal(
if (!current) {
throw new Error(`Proposal not found: ${id}`);
}
const draftPath = draftRefForProposal(homeDir, args.rootDir, id);
const patchPath = patchRefForProposal(homeDir, args.rootDir, id);
const existingDraftPath = await firstExistingFile([
draftPath,
...current.draftRefs.filter((pathValue) => pathValue.endsWith(".md")),
]);
if (
current.status !== "proposed" &&
!args.append &&
existingDraftPath &&
(await fileExists(patchPath))
) {
return current;
}
const writebacks = (
await Promise.all(
current.sourceWritebacks.map(async (writebackId) => {
Expand All @@ -2220,24 +2234,17 @@ export async function draftProposal(
homeDir,
rootDir: args.rootDir,
});
const draftPath = draftRefForProposal(homeDir, args.rootDir, id);
const patchPath = patchRefForProposal(homeDir, args.rootDir, id);
await mkdir(dirname(draftPath), { recursive: true });
const generatedBody = renderDraftBody(current, writebacks);
const existingDraftPath = await firstExistingFile([
draftPath,
...current.draftRefs.filter((pathValue) => pathValue.endsWith(".md")),
]);
const priorDraft =
args.append && existingDraftPath
? await readFile(existingDraftPath, "utf8")
: null;
const priorDraft = existingDraftPath
? await readFile(existingDraftPath, "utf8")
: null;
const baseDraft = priorDraft ?? generatedBody;
const draftBody = args.append
? isAppendProposalKind(current.kind)
? insertDraftRevision(id, baseDraft, args.append)
: `${baseDraft.trimEnd()}\n\n## Draft Revision\n${args.append.trim()}\n`
: generatedBody;
: baseDraft;
await Bun.write(draftPath, `${draftBody}\n`);
const currentText = (await fileExists(targetNode.path!))
? await readFile(targetNode.path!, "utf8")
Expand Down
Loading