Files
a4180p daa26faa2b fix(pi): persist approved plan checklist progress (#1496)
* fix(pi): persist approved plan checklist progress

* test(pi): restore checklist progress from prior sessions

* feat(pi): mark plan steps during execution

* fix(pi): align mark_done instructions and harden checklist rendering

Review fixes for #1496:

- phase-tools-runtime: pin plannotator_mark_done in the executing-phase
  active tool union and its release at agent_end.
- Align the plan-approved doneMsg with the executing framing: call
  plannotator_mark_done after each completed step, [DONE:n] markers as
  the fallback for interrupted executions.
- Harden renderCompletedChecklist: restrict the shared checklist
  pattern's whitespace to [^\S\n] so a whitespace-only checkbox line
  can never swallow the next line (the root cause the review named —
  \s+ crossing newlines), replace the marker at the match's own capture
  position instead of first-occurrence line.replace("[ ]", ...), and
  mirror parseChecklist's empty-text skip so ordinals stay aligned.
  Regression test: blank checkbox placeholders around real steps; the
  real step's box flips, the placeholders are never touched
  (revert-verified).
- Document the one-turn ordinal-desync window when an agent edits the
  plan's checkboxes mid-turn, bounded by upgrade-only writes plus the
  per-turn re-parse.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-09-10 16:41:39 -07:00

79 lines
2.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { parseChecklist, renderCompletedChecklist } from "./checklist";
describe("renderCompletedChecklist", () => {
test("marks completed items without changing other Markdown", () => {
const content = [
"# Plan",
"",
"- [ ] First step",
" Supporting detail",
"* [x] Second step",
"- [ ] Third step",
"",
].join("\r\n");
const items = parseChecklist(content);
items[0]!.completed = true;
items[2]!.completed = true;
expect(renderCompletedChecklist(content, items)).toBe([
"# Plan",
"",
"- [x] First step",
" Supporting detail",
"* [x] Second step",
"- [x] Third step",
"",
].join("\r\n"));
});
test("a whitespace-only checkbox line never receives another step's marker", () => {
// Degenerate plan: blank checkbox placeholders sit between the real
// steps. Before hardening, the shared pattern's \s+ crossed the newline,
// pairing the blank line's marker with the next real line, and the
// first-occurrence `replace("[ ]", ...)` then wrote the [x] into the
// blank placeholder instead of the real step's box.
const content = [
"# Plan",
"",
"- [ ]",
"- [ ] Step one",
"- [ ] ",
"- [ ] Step two",
"",
].join("\n");
const items = parseChecklist(content);
expect(items.map((item) => item.text)).toEqual(["Step one", "Step two"]);
items[0]!.completed = true;
expect(renderCompletedChecklist(content, items)).toBe([
"# Plan",
"",
"- [ ]",
"- [x] Step one",
"- [ ] ",
"- [ ] Step two",
"",
].join("\n"));
items[1]!.completed = true;
expect(renderCompletedChecklist(content, items)).toBe([
"# Plan",
"",
"- [ ]",
"- [x] Step one",
"- [ ] ",
"- [x] Step two",
"",
].join("\n"));
});
test("does not clear existing completed items", () => {
const content = "- [x] Completed step\n- [ ] Unfinished step\n";
const items = parseChecklist(content);
items[0]!.completed = false;
expect(renderCompletedChecklist(content, items)).toBe(content);
});
});