feat(pi): mirror plan checklist to pi-todos - #1139
Merged
Merged
Conversation
added 2 commits
July 27, 2026 11:41
Closes the one-way half of backnotprop#484: on plan approval, feature-detect an editable todo provider and mirror the approved checklist into it, then reflect [DONE:n] markers onto todo status as execution proceeds. pi-todos is the first provider. It exposes no API to other extensions, so the integration surface is its on-disk format (.pi/todos/<id>.md with JSON front-matter, <id>.lock taken with O_EXCL). The provider writes through that contract, reconciles by tag so repeated syncs are idempotent, closes steps dropped from an edited plan, and skips any todo another session holds a lock on. Provider state resets on return to idle, so a second plan re-detects rather than inheriting the first plan's decision. The mirror is additive: the existing progress widget is untouched. pi-todos has no live surface of its own -- its list renders on demand in /todos -- so suppressing the widget would trade a visible tracker for files behind a keystroke. Sync is one-way, so provider-side edits can never desync plan execution. oh-my-pi's native todo panel is the obvious second provider and slots in behind the same interface, but is not implementable from an extension today: the panel repaint is gated to the built-in `todo` tool, and the session handed to an extension-registered tool is a read-only projection without the todo accessors. Details in todo-providers/index.ts. Gated by PLANNOTATOR_TODO_PROVIDER / config.todoProvider, and inert when no provider is detected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the one-way sync from #484: feature-detect a todo provider on plan approval, push the checklist, reflect
[DONE:n]markers onto todo status.One deliberate departure from the shape sketched in the thread: the mirror is additive rather than a replacement — the existing progress widget stays on unconditionally instead of being swapped out for provider todos. pi-todos has no live surface to swap to, so replacing the widget would be a downgrade. Reasoning and evidence below; it's a one-line change if you'd rather have the original behaviour.
What this does
On plan approval the Pi extension looks for an editable todo provider. If one is present, the approved checklist is mirrored into it, and each
[DONE:n]marker reflects onto todo status as execution proceeds. Sync is strictly one-way, so a user reordering or rewording todos can never desync plan execution.Gated by
PLANNOTATOR_TODO_PROVIDER/config.todoProvider(autodefault,offto disable), and inert when no provider is detected.Two findings that shaped the implementation
1. The mirror is additive — the progress widget is untouched.
The thread framed this as todos instead of the
setWidgettracker. Tracing pi-todos, that turns out to be a downgrade: it registers onlysession_start, atodotool, and the/todoscommand. No fs watcher, no interval, no widget/status/footer surface. Its list renders on demand when the user opens/todos, so external writes are invisible until then. Suppressing the live tracker would trade a visible progress display for files behind a keystroke.So the widget stays as-is and the provider adds editable, session-durable todos alongside it. This also means a false-positive detection costs nothing.
2. oh-my-pi's native panel isn't reachable from an extension today.
Worth flagging since the thread mentioned it as a target. On omp 17.1.5 I traced two independent blocks:
modes/controllers/event-controller.tsonly callssetTodos(details.phases)whenevent.toolName === "todo", andsession/agent-session.tscarries the same guard. A third-party tool returningdetails.phasesrepaints nothing.ToolSessiondoes exposegetTodoPhases?()/setTodoPhases?(), but extension-registered tools receiveExtensionContext, whosesessionManageris a read-onlyPick<>without those accessors.setTodoswith phases is genuinely current — it's just scoped to omp's own built-intodotool. So this PR ships pi-todos behind aTodoProviderinterface, and an omp provider drops in with no caller changes the moment upstream opens that path (one allowlist entry, or adetails.phases-driven repaint, would do it). Reasoning is recorded intodo-providers/index.ts.pi-todos integration surface
pi-todos exposes no API to other extensions, so its on-disk format is the contract. Verified against mitsuhiko/agent-stuff
extensions/todos.ts@a3f8ab11:$PI_TODO_PATH, else<cwd>/.pi/todos<8 hex>.md— JSON front-matter, blank line, markdown body<8 hex>.locktaken withO_EXCLstatusfree-form;closed/donecount as closedassigned_to_sessionBehaviour that follows from that:
plannotator,plannotator:plan:<path>,plannotator:step:<n>), so repeated syncs converge instead of duplicating. Calls on one provider instance are queued because pi-todos has no atomic upsert; overlapping read/create passes cannot mint duplicate steps.sortTodosorders bycreated_atwith no index field, andDate.now()is ms-resolution, so a tight creation loop would collide and fall back to readdir order over random hex filenames.created_atis stampedbase + iso todo order matches plan order. Titles carry the step number, so order stays legible after completed items sort to the bottom.Tests
apps/pi-extension/todo-provider-sync.test.ts(6 tests) drives the real extension through a Pi host stub against a real.pi/todosdirectory: approval writes the checklist with the Pi session id,[DONE:1]closes the matching todo, widget and mirror coexist, no-provider sessions remain widget-only, a provider appearing later is picked up by plan two, and a deterministic write failure notifies once while widget progress continues.apps/pi-extension/todo-providers/pi-todos.test.ts(18 tests) round-trips written files through a re-derived reader — format drift in either direction fails loudly. Covers actual filename shape, ordering under upstream's sort, repeated and overlapping syncs, DONE reflection and assignment clearing, dropped/empty-plan closing, plan isolation, lock skipping/release, and detection includingPI_TODO_PATH.The oracle is an independent implementation written from the documented format, not a copy — agent-stuff is Apache-2.0 and this repo is
MIT OR Apache-2.0, and a different parsing strategy makes it a real cross-check rather than the same code twice.resolveTodoProviderEnabledgets its own block inpackages/shared/config.test.ts(4 tests). It's deliberately not in theconfig.json boolean coerciontable: that table asserts boolean coercion, and likeresolveSharingEnabledthis key is a string enum.Verification
bun run typecheck— cleanmainbaselineDOM_TESTS=1 bun test packages/ui— 570 pass, 0 failbun pm pack --dry-run— all fourtodo-providers/files present in the tarballThis adds 28 tests. Two caveats, stated plainly. The full suite is not green on this ARM64 machine:
node-pty/pty, process-tree timeout, and port-sensitive tests fluctuate under load, so I compared the affected scope against a recorded untouched-mainbaseline; two apparent full-suite deltas passed isolated reruns. And pi-todos itself isn't installed here: the extension is exercised end-to-end against a real.pi/todosdirectory, and the file format is pinned by round-trip against its documented contract, but I haven't run a live pi + plannotator + pi-todos session. Happy to do whatever additional verification you'd want.Not in scope
Bidirectional editing — user edits in
/todosflowing back into plan execution — as flagged in the thread. That needs conflict rules for steps added, removed, or reordered on the provider side, and is a much larger change than this.Implements the one-way half of #484. The issue is currently closed; the maintainer noted it would be reopened alongside a PR.