@gavin-hu/my-pi-agent 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/README.md +61 -45
  2. package/README.zh-CN.md +54 -42
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +344 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
  6. package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
  7. package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
  8. package/docs/extensions/file-browse.md +554 -0
  9. package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
  10. package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
  11. package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
  12. package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
  14. package/docs/extensions/plan.md +976 -0
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +285 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
  19. package/docs/extensions/team.md +226 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
  25. package/extensions/board/commands.ts +87 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +72 -0
  28. package/extensions/board/index.ts +40 -0
  29. package/extensions/board/input.ts +54 -0
  30. package/extensions/board/join.ts +42 -0
  31. package/extensions/board/page.ts +312 -0
  32. package/extensions/board/result.ts +99 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +75 -0
  35. package/extensions/board/seat.ts +29 -0
  36. package/extensions/board/snapshot.ts +41 -0
  37. package/extensions/board/tools.ts +279 -0
  38. package/extensions/board/transport/router.ts +172 -0
  39. package/extensions/board/transport/server.ts +149 -0
  40. package/extensions/board/types.ts +54 -0
  41. package/extensions/board/view/actions.ts +306 -0
  42. package/extensions/board/view/client.ts +41 -0
  43. package/extensions/board/view/keys.ts +121 -0
  44. package/extensions/board/view/live.ts +397 -0
  45. package/extensions/board/view/page.ts +158 -0
  46. package/extensions/board/view/render.ts +496 -0
  47. package/extensions/board/view/styles.ts +211 -0
  48. package/extensions/board/view/team.ts +305 -0
  49. package/extensions/board/writes.ts +99 -0
  50. package/extensions/bytefly/notify/types.ts +1 -1
  51. package/extensions/bytefly/panel/commands.ts +40 -7
  52. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  53. package/extensions/bytefly/panel/settings.ts +11 -21
  54. package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
  55. package/extensions/bytefly/picker/discovery.ts +0 -2
  56. package/extensions/bytefly/picker/types.ts +0 -2
  57. package/extensions/bytefly/update/check.ts +39 -0
  58. package/extensions/bytefly/update/format.ts +15 -0
  59. package/extensions/bytefly/update/register.ts +73 -34
  60. package/extensions/bytefly/update/state.ts +6 -3
  61. package/extensions/context/tui.ts +1 -1
  62. package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
  63. package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
  64. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  65. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  66. package/extensions/file-browse/index.ts +4 -4
  67. package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
  68. package/extensions/file-browse/live/rev.ts +42 -0
  69. package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
  70. package/extensions/file-browse/transport/server.ts +110 -0
  71. package/extensions/file-browse/view/chrome.ts +83 -0
  72. package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
  73. package/extensions/file-browse/view/diff-html.ts +94 -0
  74. package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
  75. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  76. package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
  77. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
  78. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
  79. package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
  80. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  81. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  82. package/extensions/file-read/schema.ts +2 -1
  83. package/extensions/goal/plan-route.ts +8 -2
  84. package/extensions/job/index.ts +15 -7
  85. package/extensions/memory/store.ts +12 -5
  86. package/extensions/plan/blocks.ts +23 -2
  87. package/extensions/plan/commands.ts +77 -21
  88. package/extensions/plan/feedback.ts +409 -63
  89. package/extensions/plan/index.ts +7 -5
  90. package/extensions/plan/plan-index.ts +44 -10
  91. package/extensions/plan/plan-text.ts +51 -24
  92. package/extensions/plan/plans.ts +162 -57
  93. package/extensions/plan/policy.ts +20 -12
  94. package/extensions/plan/review-prompt.ts +93 -0
  95. package/extensions/plan/review.ts +51 -95
  96. package/extensions/plan/runtime.ts +22 -10
  97. package/extensions/plan/sections.ts +112 -0
  98. package/extensions/plan/steps.ts +63 -95
  99. package/extensions/plan/tools/enter.ts +8 -3
  100. package/extensions/plan/tools/exit.ts +13 -19
  101. package/extensions/plan/tools/write.ts +8 -3
  102. package/extensions/plan/tools.ts +1 -1
  103. package/extensions/plan/tui.ts +29 -15
  104. package/extensions/plan/types.ts +29 -3
  105. package/extensions/plan/web-chrome.ts +2 -1
  106. package/extensions/plan/web-client.ts +122 -178
  107. package/extensions/plan/web-collab.ts +207 -0
  108. package/extensions/plan/web-comments.ts +269 -0
  109. package/extensions/plan/web-page.ts +96 -17
  110. package/extensions/plan/web-router.ts +109 -47
  111. package/extensions/plan/web-share-session.ts +191 -0
  112. package/extensions/plan/web-share.ts +169 -0
  113. package/extensions/plan/web-streams.ts +124 -0
  114. package/extensions/plan/web-tokens.ts +63 -0
  115. package/extensions/plan/web.ts +245 -151
  116. package/extensions/question/dialogs.ts +91 -1
  117. package/extensions/question/tools.ts +2 -1
  118. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  119. package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
  120. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  121. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  122. package/extensions/rewind/index.ts +3 -3
  123. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  124. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  125. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  126. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  127. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  128. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  129. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  130. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  131. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  132. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  133. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  134. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  135. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  136. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  137. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  138. package/extensions/session-bus/commands.ts +113 -0
  139. package/extensions/session-bus/format.ts +101 -0
  140. package/extensions/session-bus/index.ts +75 -0
  141. package/extensions/session-bus/join-request.ts +40 -0
  142. package/extensions/session-bus/mail-watch.ts +134 -0
  143. package/extensions/session-bus/notice.ts +86 -0
  144. package/extensions/session-bus/overlap.ts +36 -0
  145. package/extensions/session-bus/protocol.ts +104 -0
  146. package/extensions/session-bus/resolve.ts +67 -0
  147. package/extensions/session-bus/result.ts +208 -0
  148. package/extensions/session-bus/rows.ts +171 -0
  149. package/extensions/session-bus/runtime.ts +466 -0
  150. package/extensions/session-bus/store.ts +365 -0
  151. package/extensions/session-bus/tools.ts +192 -0
  152. package/extensions/session-bus/tui.ts +394 -0
  153. package/extensions/session-bus/types.ts +98 -0
  154. package/extensions/subagent/agents.ts +9 -8
  155. package/extensions/subagent/invocation.ts +4 -2
  156. package/extensions/team/commands.ts +112 -0
  157. package/extensions/team/dispatch.ts +133 -0
  158. package/extensions/team/format.ts +105 -0
  159. package/extensions/team/index.ts +33 -0
  160. package/extensions/team/schema.ts +29 -0
  161. package/extensions/team/tools.ts +246 -0
  162. package/extensions/team/types.ts +48 -0
  163. package/extensions/watch/index.ts +15 -9
  164. package/extensions/web-access/fetch/page.ts +4 -1
  165. package/extensions/web-access/fetch/structured.ts +447 -0
  166. package/extensions/web-access/fetch/tool.ts +2 -1
  167. package/extensions/web-access/search/tool.ts +2 -1
  168. package/extensions/wechat/bridge.ts +76 -92
  169. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  170. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  171. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  172. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  173. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  174. package/extensions/wechat/dialog/channels.ts +136 -0
  175. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
  176. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
  177. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  178. package/extensions/wechat/index.ts +10 -10
  179. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  180. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  181. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
  182. package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
  183. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
  184. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  185. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  186. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  187. package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
  188. package/extensions/wechat/tools.ts +29 -12
  189. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  190. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
  191. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  192. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  193. package/extensions/wechat/transport/outbound.ts +142 -0
  194. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  195. package/extensions/wechat/types.ts +10 -1
  196. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  197. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  198. package/extensions/worktree/git.ts +5 -0
  199. package/extensions/worktree/guard.ts +3 -5
  200. package/extensions/worktree/registry.ts +11 -5
  201. package/extensions/worktree/status.ts +1 -3
  202. package/lib/README.md +12 -6
  203. package/lib/board/card.ts +290 -0
  204. package/lib/board/links.ts +117 -0
  205. package/lib/board/page-pointer.ts +256 -0
  206. package/lib/board/round-text.ts +72 -0
  207. package/lib/board/round.ts +89 -0
  208. package/lib/board/rules.ts +304 -0
  209. package/lib/board/session.ts +105 -0
  210. package/lib/board/store.ts +310 -0
  211. package/lib/board/view.ts +150 -0
  212. package/lib/errors.ts +12 -4
  213. package/lib/format.ts +13 -0
  214. package/lib/http/guard.ts +112 -0
  215. package/lib/http/index.ts +16 -5
  216. package/lib/http/router.ts +40 -13
  217. package/lib/http/serve.ts +234 -0
  218. package/lib/http/server.ts +29 -13
  219. package/lib/http/sink.ts +110 -0
  220. package/lib/http/token.ts +38 -0
  221. package/lib/interaction.ts +10 -0
  222. package/lib/notice.ts +29 -2
  223. package/lib/plan-entry.ts +7 -2
  224. package/lib/policy.ts +12 -2
  225. package/lib/qr.ts +64 -0
  226. package/lib/repo-key.ts +74 -0
  227. package/lib/report.ts +52 -33
  228. package/lib/requirements.ts +7 -9
  229. package/lib/session-label.ts +64 -0
  230. package/lib/session-roster.ts +130 -0
  231. package/lib/session-tool.ts +184 -0
  232. package/lib/tool-names.ts +25 -2
  233. package/lib/tui/README.md +16 -2
  234. package/lib/tui/dock-screen.ts +85 -17
  235. package/lib/tui/glyphs.ts +2 -0
  236. package/lib/tui/line-pane.ts +15 -4
  237. package/lib/tui/tool.ts +23 -4
  238. package/lib/web/README.md +5 -2
  239. package/lib/web/components.ts +6 -1
  240. package/lib/web/runtime.ts +7 -1
  241. package/lib/web/tokens.ts +25 -2
  242. package/package.json +27 -14
  243. package/skills/init/SKILL.md +80 -45
  244. package/skills/init/references/agents-md-quality.md +26 -2
  245. package/skills/init/references/interview-questions.md +36 -14
  246. package/skills/next/SKILL.md +77 -51
  247. package/skills/project-docs/SKILL.md +133 -0
  248. package/skills/project-docs/references/audit.md +131 -0
  249. package/skills/project-docs/references/changelog.md +105 -0
  250. package/skills/project-docs/references/discovery.md +127 -0
  251. package/skills/project-docs/references/readme.md +80 -0
  252. package/skills/project-docs/references/release.md +84 -0
  253. package/skills/project-docs/references/roadmap.md +93 -0
  254. package/skills/project-docs/scripts/state.ts +316 -0
  255. package/skills/refactor/SKILL.md +100 -125
  256. package/skills/refactor/references/handoff.md +35 -0
  257. package/skills/refactor/references/signals.md +54 -0
  258. package/skills/refactor/scripts/inventory.ts +587 -0
  259. package/extensions/file-browse/README.md +0 -467
  260. package/extensions/file-browse/server.ts +0 -134
  261. package/extensions/plan/README.md +0 -709
  262. package/extensions/plan/config.ts +0 -27
  263. package/extensions/wechat/outbound.ts +0 -100
  264. package/extensions/wechat/qrcode.ts +0 -46
  265. package/skills/changelog/SKILL.md +0 -148
  266. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  267. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  268. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  269. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  270. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  271. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  272. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  273. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  274. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -1,709 +0,0 @@
1
- # plan — file-backed planning for Pi
2
-
3
- A read-only exploration mode modelled on Claude Code's `EnterPlanMode` /
4
- `ExitPlanMode` pair. While plan mode is on, the model investigates and proposes,
5
- but the only thing it may write is the plan file itself. The plan is saved under
6
- `.pi/plans`; the user reviews that file, and it is the artifact the model
7
- executes.
8
-
9
- ```bash
10
- pi --extension ./extensions/plan # load just this extension
11
- pi -e . # load the whole @gavin-hu/my-pi-agent package
12
- pi --plan # start in plan mode
13
- ```
14
-
15
- ## Tool rendering
16
-
17
- How the `write_plan` and `exit_plan_mode` calls and their results look in the transcript — the call line as a headline and the body indented under it:
18
-
19
- <img src="../../assets/tools/write_plan.png" alt="The write_plan tool's call and result: the call line and an indented body with the saved plan path and byte count" width="100%">
20
-
21
- <img src="../../assets/tools/exit_plan_mode.png" alt="The exit_plan_mode tool's call and result: the call line and an indented body with the approval line and the approved plan preview" width="100%">
22
-
23
- ## What it does
24
-
25
- - **Read-only gating, with one exception.** Tools that are not read-only are
26
- removed from the active set or blocked. `write` and `edit` are hidden, raw
27
- shell is limited to read-only git commands through `bash` (chained with
28
- `&&`/`;`), `powershell` is blocked outright, and other mutating tools (worktree
29
- mutations, MCP tools) are blocked until you exit plan mode. `subagent` stays
30
- active but is forced read-only. The only permitted write is the plan control
31
- tool `write_plan`, which can only create or overwrite markdown plan files
32
- directly inside the plans directory — never dotfiles and never through a
33
- symlink. Classification defaults to *deny* and is driven by the shared
34
- [`lib/policy.ts`](../../lib/policy.ts), so unknown tools are safe by default
35
- and the MCP `readOnlyHint` is the only thing that opens one up. A refused
36
- shell command explains the fix: the working directory is already the project
37
- root (so `cd` is unnecessary), and files belong to the `read`, `grep`, `find`,
38
- and `ls` tools.
39
- - **Model entry point.** The model can call `enter_plan_mode` to ask for plan
40
- mode before a non-trivial task; the user confirms.
41
- - **AGENTS.md bootstrap.** The packaged `init` skill (`/skill:init`) enters plan
42
- mode, reconnoiters the repository, runs one built-in interview of at
43
- most four questions, creates or updates `AGENTS.md`, gates the draft through a
44
- quality checklist, and writes only after plan approval. It supports existing and
45
- empty repositories, and falls back to evidence with recorded assumptions when
46
- the interview is unavailable or cancelled; it never invents commands.
47
- - **Plan files.** The model saves the plan with `write_plan`; the file lands in
48
- `<repo-root>/.pi/plans/<YYYY-MM-DD-HHmm>-<slug>.md` — the entered worktree's
49
- root when one is active — or `<agent-dir>/plans` outside a repository. The
50
- plans directory is self-ignoring, so plan files never show up as untracked
51
- files or in `rewind` snapshots.
52
- - **A discoverable index.** `/plan list` walks the plans directory and shows
53
- every plan with its title, modified time, and step progress, newest first — in
54
- the TUI as a docked list where `Enter` opens the plan's review, elsewhere as a
55
- notice with each plan's path. The directory stays the index: no registry file,
56
- one bounded read per plan.
57
- - **A shape that reads.** `write_plan` documents a canonical outline — a
58
- `# <Title>`, then `## Goal`, `## Context`, `## Approach`, `## Visual`,
59
- `## Steps`, `## Risks`, and `## Verification` — and the model is guided to
60
- follow it. Every example in the template is the shape wanted rather than a
61
- description of it, and each section has one job: the goal is one sentence, the
62
- context is the facts that force the approach, the approach names the
63
- alternative it rejected, and verification is a fence of the commands to run.
64
- `Visual` carries the end state in an untagged fence, with one line naming what to
65
- look at: the same surface before and after when the change alters one that
66
- already exists, one frame when the surface is new, because a reviewer judges the
67
- result before the steps that build it; a change with no visible surface drops it.
68
- The description calls the shape a guide rather than a form: `Context`,
69
- `Approach`, `Visual`, and `Risks` are dropped when they have nothing to say,
70
- while `Steps` and `Verification` are kept, because the steps are read from that
71
- section (without it, the shallowest list anywhere becomes the steps) and
72
- Verification becomes the last todo item. Within `Steps` only the shallowest
73
- list is tracked, so nested bullets stay notes instead of becoming todos. This
74
- is guidance only: any markdown is accepted, stored, and rendered as written,
75
- and a refinement is asked to keep its section headings so the review can still
76
- mark only what changed.
77
- - **Trackable steps.** Steps are written as `- [ ]` checkboxes in the `## Steps`
78
- section; approval seeds them into `todo`, with the `## Verification` section as
79
- the last item so the definition of done travels with the work. A plan with more
80
- than 50 top-level steps is refused rather than truncated, so approval never
81
- drops a step you reviewed.
82
- - **A navigable review.** The review screen splits the body at its headings,
83
- names the section you are in, and jumps between sections with `n`/`p`.
84
- - **What changed since the last review.** After a refinement, the review screen
85
- names how many sections changed, marks the current one in the scroll row, and
86
- the browser outline dots each changed section, so a re-review does not mean
87
- re-reading the whole plan. The baseline is one digest per heading section of
88
- what the previous review showed, kept as a session entry, so a resumed session
89
- or a `/tree` restore marks only what changed on its own branch.
90
- - **Kept current.** After approval, `set_plan_steps` flips the checkboxes of
91
- the steps you name in the file, so the plan mirrors what has actually been
92
- done, and ticks the todo item approval seeded from that step with it, so the
93
- widget and the plan do not drift apart. Pass the step's text as `expected` and
94
- an update whose step number moved is refused instead of ticking the wrong line.
95
- - **Reviewable plan.** `exit_plan_mode` reads the plan file back and presents
96
- it for approval. The user approves, keeps planning, asks for a refinement, or
97
- exits plan mode without approving; the last keeps the plan file for a later
98
- review and records no todo items or goal.
99
- On a remote turn (WeChat) the plan file is sent first, then a digest of its
100
- title, step count, and first steps, then the choices, so the review is still
101
- answerable when the attachment is not opened. The choices include **Send the
102
- full plan**, which posts the plan text and asks again.
103
- - **Browser review on demand.** Press `w` on the review screen (or run
104
- `/plan review [path]`) to open the plan in a local browser: a reading-first
105
- page with the plan's H1 as its title, GitHub's Markdown content column, a
106
- comment rail in
107
- the left margin, and live section and step progress — the step count comes from
108
- the same `Steps` section list approval seeds. Select text to leave an
109
- inline comment, and save them back into the plan file itself as
110
- `> [!REVIEW]` lines. Saved comments return to the model as a refinement
111
- request.
112
- - **The file is the source of truth.** On approval the model follows the plan
113
- file's steps; refinements rewrite the same file (`write_plan` with
114
- `plan_path`), so the reviewed artifact is the executed artifact.
115
- - **Steps become todos.** On approval, the top-level numbered/bulleted items of
116
- the plan's `Steps` section are recorded with the `todo` tool (via
117
- `ctx.executeTool`); steps marked `- [x]` (or `[DONE:n]`) seed as completed, and
118
- nested sub-bullets are treated as notes. The list is a merge, not a
119
- replacement: existing items keep their place and status, a step whose text is
120
- already there is skipped, and the tool's item cap defers the rest instead of
121
- dropping anything. The plan's `## Verification` section follows as the **last**
122
- todo item, summarized on one line (`Verify: …`) — a fenced block contributes its
123
- commands, not its fence lines. It is not a plan step, so it is ticked in the todo
124
- list rather than with `set_plan_steps`, and it never counts against the 50-step
125
- cap. A plan without a `Steps` heading keeps the older rule —
126
- the shallowest list in the document — so prose bullets in a plan that follows the
127
- outline never become todos, and its verification bullets are already covered.
128
- Each seeded item is matched back to its step by text, so ticking a step with
129
- `set_plan_steps` completes the item too.
130
- - **Goal-aware.** While planning, the injected context restates a pursued
131
- session goal as one bullet, so the plan is designed against the objective
132
- instead of beside it. On approval, a goal the goal workflow left as a draft
133
- (`planning`) is finalized from the plan's own `## Goal` section — falling back
134
- to the title — through the `goal` tool, with no extra dialog, because approving
135
- the plan *is* the decision. A session with no goal at all gets one confirm to
136
- record the same objective, unless `plan.goalPrompt` is off, which records it
137
- without asking. An `active` or `achieved` goal is never rewritten: a final
138
- objective is the user's. No path spends a model turn.
139
- - **Branch-aware.** The enabled flag and the review baseline are custom session
140
- entries, so `/resume` and `/tree` restore the right mode and keep the
141
- changed-section markers.
142
-
143
- ## Tools
144
-
145
- | Tool | Active | Purpose |
146
- |---|---|---|
147
- | `enter_plan_mode` | normal mode | Ask to enter plan mode; empty schema, confirmed with `ctx.ui.confirm`. |
148
- | `write_plan` | plan mode | Save the plan: `title` (file-name slug), `content` (full markdown in the documented outline), optional `plan_path` to overwrite a refinement. Returns the absolute path. A plan with more than 50 top-level steps in its `Steps` section is refused, with the cap and the fix named, and nothing is written. |
149
- | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. The review also offers **Exit plan mode (discard)**, which leaves plan mode without approving. Refuses a file whose step list is over the cap, before the review screen. |
150
- | `cancel_plan_mode` | plan mode | Leave plan mode without a plan file or a review. Restores write access; records no todo items or goal; keeps any saved plan. Empty schema. |
151
- | `set_plan_steps` | normal mode | Keep an approved plan current: set the checkboxes of the given top-level steps (`updates`, each a 1-based `step` in the order `exit_plan_mode` listed, plus `status`, and `expected` — the step's text, so a reordered plan is refused) to `pending` or `completed` in one write. Only the checkboxes change, and a reopened step drops its `[DONE:n]` marker. The todo item approval seeded from a step is ticked with it: the file is written first and is authoritative, a renamed item is left alone, and a failed todo write is reported in the result. |
152
-
153
- ### Commands and flags
154
-
155
- | Path | Who starts it | Confirmation |
156
- |---|---|---|
157
- | `enter_plan_mode` tool | the model, before a non-trivial task | `ctx.ui.confirm` |
158
- | `/plan` | the user | immediate |
159
- | `/plan <prompt>` | the user, with the task | immediate |
160
- | `/plan list` | the user, browsing the plans directory | immediate |
161
- | `/plan review [path]` | the user, reviewing a plan in the browser | immediate |
162
- | `/plan cancel` | the user, leaving plan mode without approving | immediate |
163
- | `/skill:init` | the user, creating or updating `AGENTS.md` | enters plan mode; write waits for plan approval |
164
- | `Ctrl+Alt+P` | the user | immediate |
165
- | `--plan` | the user, at launch | immediate |
166
-
167
- `/plan` with no argument toggles plan mode; `/plan <prompt>` enters it (if it is
168
- not already on) and sends the rest of the line as the task, so the first turn
169
- already runs read-only:
170
-
171
- ```bash
172
- /plan add rate limiting to the public API
173
- ```
174
-
175
- `/plan <prompt>` never disables plan mode; only the argument-less `/plan` (or
176
- `Ctrl+Alt+P`) toggles. Because slash commands go through Pi directly, `/plan` is
177
- the toggle that works in every terminal (for example Zed's integrated terminal,
178
- which does not forward `Ctrl+Alt+P`). The footer shows `≡ plan` while plan mode
179
- is on, and `≡ plan · <plan-file>` once the model has written a plan.
180
-
181
- `/plan review` is the one reserved subcommand. Bare `/plan review` reviews the
182
- plan this session wrote, or, in a later session, the newest plan file in the
183
- plans directory (newest modification time); `/plan review <path>.md` (or a path
184
- with a separator) reviews that plan. A line that merely starts with the word —
185
- `review the auth flow` — is still treated as a planning task, so the subcommand
186
- never swallows one.
187
-
188
- ### `/skill:init`
189
-
190
- `/skill:init` loads the packaged `init` skill. The skill enters plan mode itself
191
- when it is not active, then decides what to do; nothing is written before
192
- approval. Its workflow is:
193
-
194
- ```text
195
- /skill:init
196
- └─> plan mode (read-only)
197
- └─> recon: manifests, docs, CI, tests, existing instructions
198
- └─> interview: built-in bank, one call of at most 4 questions
199
- ├─ answered ───────────────> reconcile with evidence
200
- └─ unavailable/cancelled ──> evidence + assumptions
201
- └─> draft: new file, exact edits, or an honest no-op
202
- └─> quality gate
203
- └─> write_plan ──> exit_plan_mode
204
- ├─ not approved ──> refine the same plan
205
- └─ approved ──────> write/edit AGENTS.md
206
- └─ read back + quality checklist
207
- ```
208
-
209
- The interview comes from a built-in bank in
210
- [`skills/init/references/interview-questions.md`](../../skills/init/references/interview-questions.md):
211
- core questions cover purpose, verification, non-obvious rules, isolation,
212
- completion, sensitive paths, and dependency or network limits, with state-specific
213
- entries for an existing file, an empty repository, and related instruction files.
214
- The first call uses a deterministic four-question set for the repository state;
215
- a second call is allowed only for a material ambiguity. If the tool is inactive,
216
- there is no UI, or the user cancels, the skill records the gaps as assumptions
217
- and continues from repository evidence.
218
-
219
- The quality reference is
220
- [`skills/init/references/agents-md-quality.md`](../../skills/init/references/agents-md-quality.md).
221
- An existing `AGENTS.md` is reviewed and updated only after approval, an accurate
222
- file can produce a no-op plan, and an empty repository gets only known facts.
223
- The skill needs the plan extension for its review gate. It also ships with the
224
- package, so load the whole package (`pi -e .`) or install it; loading only
225
- `./extensions/plan` leaves `/skill:init` unavailable.
226
-
227
- ## Behaviour by mode
228
-
229
- The review screen is TUI-only. `exit_plan_mode` opens `PlanViewComponent`: a
230
- scrollable, width-safe view of the plan with approve/refine/keep/exit in the footer.
231
- The body is rendered with Pi's own `Markdown` component and `getMarkdownTheme()`,
232
- so headings, lists, inline code, code blocks, links, and quotes carry the active
233
- theme's colors; task items get the shared `◼` (done) or `◻` (pending) marker,
234
- like the bytefly screen and the question dialog. The footer colors the primary
235
- actions (`a approve & run` in `success`, `r refine` in `accent`).
236
- `a` ends plan mode, restores write access, and seeds `todo`; `r` keeps the plan
237
- mounted and opens an inline editor beneath it (`Enter` submits, an empty buffer
238
- does nothing, `Esc` returns to the plan); `Esc` keeps planning and tells the
239
- model the plan was not approved; `x` leaves plan mode without approving and
240
- keeps the plan file. The dialog path offers the same five outcomes, including
241
- **Exit plan mode (discard)**. Dismissing the dialog (or a timeout) is also an
242
- exit, not a silent `keep`; only the explicit **Keep planning** option stays in
243
- plan mode. When the plan changed since the previous review
244
- on this branch, a summary line under the title names how many sections changed
245
- and the scroll row appends `· changed` to the current section. Scroll with `↑`/`↓` or `j`/`k`, `space`/`b` or
246
- `PgUp`/`PgDn`, `d`/`u`, `g`/`G`, and the mouse wheel. Press `n`/`p` to jump to
247
- the next or previous heading; the scroll row names the current section when the
248
- plan has more than one. When the plan overflows,
249
- a gauge row above the hints shows the scroll position, the percent, and the
250
- visible line range (the range drops first on a narrow terminal); the gauge comes
251
- from the design system's `gaugeBar` and stays
252
- visible while the refine editor is open, since the body above is still clipped.
253
- Hints are
254
- dropped whole as the terminal narrows, and each footer leads with its safe exit
255
- (`Esc keep plan` in the review, `Esc back to plan` in the inline editor) so
256
- the exit survives even when only one hint fits.
257
-
258
- Dialog-capable non-TUI modes (RPC) show the same five choices as a select menu
259
- plus the refine editor; cancelling the editor returns to the select instead of
260
- being reported as "not approved". Dismissing the plan select is an exit. A WeChat turn takes the same dialog path and
261
- is answered over WeChat, even though the session is in TUI mode: the choice
262
- comes from `askHuman(ctx, { custom, dialogs, file, body })` in
263
- `lib/interaction.ts`, not from `ctx.mode`. A remote turn also receives the plan
264
- **file** first (falling back to the plan text if the upload fails) so the whole
265
- plan is reviewable there; RPC has no channel and still sees only the choices.
266
- Without any UI the tools fail with an actionable message instead of deciding for
267
- the user.
268
-
269
- Browser review is a local-turn feature: the server binds `127.0.0.1`, so a remote
270
- (WeChat) turn cannot reach it and `/plan review` there refuses with a notice
271
- instead of opening a server nobody can use. The browser's **Approve & run** is
272
- offered only for the review `exit_plan_mode` opens, since only that flow owns
273
- leaving plan mode; `/plan review` stays comment-only.
274
-
275
- Aborting the turn closes a browser review. `exit_plan_mode` forwards the call's
276
- abort signal to the review server, so an aborted turn returns "not approved"
277
- and stops listening instead of waiting on a page nobody is watching.
278
-
279
- ### Transcript
280
-
281
- The three tool rows reuse the transcript component, and `exit_plan_mode`
282
- separates the plan body from its header with a blank line; the collapse hint
283
- binds to `app.tools.expand` (`ctrl+o` by default).
284
-
285
- ```
286
- enter_plan_mode requested plan mode
287
- ✓ Plan mode enabled
288
- ```
289
-
290
- ```
291
- write_plan Add rate limiting
292
- ✓ Saved plan .pi/plans/2026-10-09-1809-add-rate-limiting.md
293
- ```
294
-
295
- ```
296
- exit_plan_mode submitted a plan
297
- ✓ Plan approved
298
- .pi/plans/2026-10-09-1809-add-rate-limiting.md
299
-
300
- # Add rate limiting
301
- 1. Add a token bucket …
302
-
303
- … 12 more lines (ctrl+o to expand)
304
- ```
305
-
306
- ## Configuration
307
-
308
- The `plan` section of the shared `bytefly.json` (global then project), or the
309
- **Offer the session goal** row in `/bytefly settings`. It is read when a plan is
310
- approved, so a change applies to the next approval without a reload.
311
-
312
- | Key | Default | Meaning |
313
- |---|---|---|
314
- | `goalPrompt` | `true` | Ask before recording an approved plan's objective as the session goal; `false` records it without asking. |
315
-
316
- ## Security
317
-
318
- Plan mode is a **guard rail, not a sandbox**: extensions run with Pi's OS
319
- permissions, so it prevents accidental writes while planning, not a hostile
320
- command. The read-only guarantee is layered:
321
-
322
- - **Capability policy.** A tool is allowed only when it is a known structured
323
- reader or declares the MCP `readOnlyHint`; `write`, `edit`, and unknown
324
- mutators are denied.
325
- - **Path backstop.** A `readOnlyHint` is only a claim, so a tool that takes a
326
- file path is refused unless it is a known plan-safe reader. `path-guard.ts`
327
- scans nested arguments and path-shaped values, not just fixed top-level keys.
328
- - **Control-tool identity.** The `tool_call` guard exempts `write_plan` /
329
- `exit_plan_mode` only when the registered tool is this extension's own,
330
- matched by source path, so a same-named tool from another extension cannot
331
- borrow the exemption.
332
- - **Contained writes.** `write_plan` only writes markdown non-dotfiles whose
333
- real parent resolves inside the plans directory, and every plan write —
334
- create, refine, `set_plan_steps`, browser feedback — goes through a hidden
335
- temp file followed by a rename, so an interrupted write cannot leave a
336
- truncated plan.
337
- - **Sanitized model text.** The plan file is model-authored, so `exit_plan_mode`
338
- strips control characters from the plan body before rendering it in the
339
- transcript and in non-TUI notices; a plan file cannot drive the terminal.
340
- - **Loopback browser review.** The review server binds `127.0.0.1` and requires a
341
- random per-session token in the URL, rejects a missing or foreign `Host`
342
- header, serves only `GET`, `HEAD`, and `POST`, and caps the request body; the
343
- page renders plan and comment text with DOM text nodes, never `innerHTML`, so a
344
- plan cannot script the reviewer. The page also refuses to be framed
345
- (`frame-ancestors 'none'` and `X-Frame-Options: DENY`), so a local page cannot
346
- clickjack **Approve & run**.
347
-
348
- ## Read-only git shell
349
-
350
- Plan mode does not run a general shell. `bash` stays active only so the model
351
- can run **read-only git commands** — for example `git status`,
352
- `git diff --stat`, `git log --oneline`, `git show HEAD`, or `git branch -a`.
353
- Several may be chained with `&&` or `;` in one call, but every segment must
354
- itself be a read-only git command, so a chain only combines allowed reads.
355
- Anything else (a non-git command, a mutating git subcommand, a piped or
356
- redirected command, substitution, or a conditional `||`) is blocked by a
357
- conservative allowlist in `git-bash.ts`. `powershell` is not allowed at all, so
358
- plan mode uses Git Bash on native Windows: the same read-only git commands work
359
- there, and a backslash before an ordinary character is accepted as a Windows
360
- path separator while a backslash that escapes a metacharacter is still refused.
361
- Everything else uses `read`, `grep`, `find`,
362
- `ls`, and the other read-only tools already available (`web_search`,
363
- `web_fetch`, `ask_user_question`, `todo`, `goal`).
364
-
365
- ```bash
366
- git status && git diff --stat # allowed: two read-only git commands
367
- git status; git log --oneline -5 # allowed
368
- git diff -- extensions\plan # allowed: a Windows path separator
369
- git status && rm -rf x # blocked: the second segment is not git
370
- git status | sh # blocked: pipes are never allowed
371
- git diff > out.patch # blocked: redirection is a write
372
- ```
373
-
374
- A string parser can never be the boundary, so capability gating replaced the
375
- earlier segment-based command allowlist. The git shell guard is a narrow,
376
- deliberate exception — an allowlist over read-only git command shapes, not a
377
- general shell parser. If you need anything else during a plan, exit plan mode
378
- first or wait for approval.
379
-
380
- ## Read-only delegation
381
-
382
- While planning, `subagent` stays available but every call is **forced
383
- read-only**. Plan mode sets `readOnly: true` on the call in the `tool_call`
384
- guard, and `subagent` in turn gives each spawned agent a reader-only `--tools`
385
- list (`read`, `grep`, `find`, `ls`, `web_search`, `web_fetch`). The child
386
- process enforces it, so a delegated `explorer`, `researcher`, or even `worker`
387
- cannot write, edit, or run a shell — regardless of its prompt or an external
388
- agent file that overrides it.
389
-
390
- ```jsonc
391
- // The model just calls subagent; plan mode supplies the readOnly flag.
392
- { "agent": "explorer", "task": "Map how the plan extension gates tools" }
393
- ```
394
-
395
- - A `subagent` whose registered schema does not declare `readOnly` is refused
396
- (default-deny), so an old or unrelated tool cannot be run read-write while
397
- planning.
398
- - The delegated run keeps its own isolated context window, so broad exploration
399
- does not fill the planning session.
400
- - `readOnly` is also a normal `subagent` parameter; outside plan mode a caller
401
- may set it to run any agent read-only on purpose.
402
-
403
- ## Review screen
404
-
405
- <img src="../../assets/plan.png" alt="The terminal plan review: a Plan Review title rule, a notice that one section changed since the last review, the plan body with its headings and ◼/◻ task checkboxes, a scroll gauge naming the current section and the visible line range, and the Esc/a/r/w, scroll, and section-jump hints" width="100%">
406
-
407
- `exit_plan_mode` opens this screen on the plan file it just wrote. The body is
408
- Pi's Markdown renderer with the active theme, so headings, lists, code, and
409
- links read as they do in the transcript; the checkboxes in `Steps` use the
410
- shared `◼`/`◻` vocabulary. A plan taller than the terminal scrolls under a gauge
411
- that names the section at the top of the viewport and the line range it shows,
412
- and the footer lists only the keys that apply. `Esc` keeps planning, `a` approves
413
- and runs, `r` refines inline, and `w` opens the browser review below.
414
-
415
- ## Browser review
416
-
417
- <img src="../../assets/plan-review.png" alt="The browser-based plan review: a sticky header with the plan title, a status chip, and the Approve & run, Save feedback, and Back to Pi actions, a side panel with step progress and a section outline, and the plan as one measured column with a comment rail and an inline review thread" width="100%">
418
-
419
- Press `w` on the review screen — or run `/plan review [path]` — to open the plan
420
- in the default browser on `127.0.0.1`. The page is a reading-first review
421
- surface: a sticky header carrying the plan's H1 (falling back to the file-name
422
- slug), a live status chip (connection, comment count, and unsaved changes), the
423
- theme toggle, and the review actions, over a hairline reading-progress bar. A
424
- sticky side panel shows the plan's `Steps` section progress and comment count above a
425
- **section outline**, and the document is GitHub's Markdown content column — one centred
426
- measure of at most 1012px with a comment rail in its left margin. Every block
427
- reveals a **+ Comment** action on hover; select
428
- text first to quote it. **Save feedback** writes the comments into the plan
429
- file and returns them to the model.
430
-
431
- - **Shared theme.** The page uses the same Bytefly tokens, base rules, and
432
- syntax-token colors as `file-browse` (`lib/web/`), follows the system light/dark
433
- preference until the header toggle sets an explicit choice, and remembers that
434
- choice under the shared `pi-web-theme` key.
435
- - **One content column, one rail.** The document is GitHub's Markdown content
436
- column (a centred measure of at most 1012px), so a plan reads the way the same
437
- file does on GitHub; each block carries its `+ Comment` button and
438
- comment-count badge
439
- in a rail in the left margin, so annotating never reflows the text.
440
- - **Section outline and facts.** Headings become a sticky outline with jump
441
- links, an active-section highlight, and a per-section comment count; the panel
442
- above it reports the `Steps` section's progress (`n/m`, the same list approval
443
- seeds) and total comments. A
444
- section that changed since the previous review on this branch carries a dot
445
- (its `title` and accessible label say so); an unchanged plan shows none.
446
- - **Inline threads.** A comment renders directly under the block it anchors to,
447
- labelled and indented as a thread, so it reads in place and the plan keeps one
448
- measure at every width. There is no separate column to reflow.
449
- - **Nothing typed is lost.** **Save feedback**, **Approve & run**, and **Back to
450
- Pi** refuse while a composer holds text and put the cursor back in it, and a
451
- deleted comment is offered back in the header for a few seconds. The page keeps
452
- no draft of its own — these are the three ways an open comment could disappear.
453
- - **Prints as a document.** Printing uses the light palette whatever the screen
454
- shows, drops the header, panel, rail, and progress bar, and lets the comments
455
- stay with the blocks they belong to.
456
- - **Keyboard first.** `j`/`k` move to the next or previous block, `c` comments
457
- on the current block, `a` approves (the approval flow only), `s` saves, `Esc`
458
- closes the composer or the sheet and then the review, and `?` opens a shortcut
459
- sheet; the header's `?` button opens the same sheet. Either dialog traps
460
- `Tab` and hands focus back to whatever opened it.
461
- - **Decision in place.** A review opened for approval (`w` on the review screen)
462
- shows **Approve & run** beside Save feedback: it persists the current comments,
463
- ends plan mode, restores write access, seeds `todo`, and hands control back to
464
- the model. Because that cannot be undone from the page, the button and `a`
465
- open a confirmation that says what happens and starts on **Cancel**; only its
466
- own **Approve & run** posts. **Save feedback** keeps plan mode and returns the
467
- comments as a refinement; **Back to Pi** closes the review with no decision.
468
- Each action settles the page on a terminal state — `Approved`, `Saved`, or
469
- `Closed` — so the tab never falls to Offline. `/plan review` is comment-only:
470
- it renders no approve action and the server refuses one.
471
- - **Markdown that reads.** Headings, lists, task items, blockquotes, fenced
472
- code, GFM tables, `---` rules, and bare-URL autolinks render; a list item's
473
- continuation lines hang under the item's text, inline code is a soft fill
474
- rather than a bordered chip, tables honour the alignment row and scroll
475
- horizontally when narrow.
476
- - **Highlighted code.** Fenced code is tokenized by the shared
477
- `lib/highlight.ts`; the client copies only text and `tok-*` spans through
478
- `DOMParser`, so a code line is never parsed as markup.
479
-
480
- - **The file is the store.** Each comment is written as
481
- `> [!REVIEW] <comment>` on the line after the block it anchors to. Saving is
482
- idempotent: the previous markers are stripped first, so a second save replaces
483
- rather than appends. Stripping is fence-aware: a `> [!REVIEW]` line inside a
484
- code block is documentation, not feedback. The browser keeps no copy of its own.
485
- - **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
486
- source-line ranges; a comment is placed after the whole anchored block, so a
487
- fenced code block is never split. A save re-checks the file against what the
488
- page rendered and answers `409`; the page then offers a **Reload** action,
489
- because only a reload can re-anchor the comments to the new text.
490
- - **Back to the model.** A save from the review screen returns `refined: true`
491
- with the comments, so the model stays in plan mode and revises the same file. A
492
- save that carries no comments asks for nothing, so the review screen comes back
493
- instead. `/plan review` sends them as a user message (a plan-mode revision or a
494
- plain update, depending on the mode).
495
- - **Loopback and tokenized.** The server binds `127.0.0.1`, carries a random
496
- per-session token in the URL, rejects a missing or foreign `Host` header,
497
- serves only `GET`, `HEAD`, and `POST`, and caps the request body. A save or cancel
498
- resolves the session only after its HTTP response is delivered, so the browser
499
- never sees a false "Failed to fetch". The last bound port is remembered per
500
- plan, so reviewing the same plan again reuses the origin (and the browser's
501
- saved theme with it). It is closed on save, cancel, a dropped event stream, or
502
- `session_shutdown`; a reload reconnects inside that grace, so refreshing or
503
- reloading a conflicted page does not end the review.
504
-
505
- | Route | Result |
506
- |---|---|
507
- | `GET /?token=…` | the review page (a JSON data island plus `/theme.js` and `/client.js`) |
508
- | `GET /theme.js` | the shared pre-paint theme bootstrap |
509
- | `GET /client.js` | the first-party client script |
510
- | `GET /events?token=…` | Server-Sent Events stream, used only to detect a closed tab |
511
- | `POST /api/feedback?token=…` | validate and write the annotations into the plan |
512
- | `POST /api/cancel?token=…` | end the session without saving |
513
-
514
- ## Limitations
515
-
516
- - PowerShell is not available while planning. On native Windows the shell is
517
- Git Bash (`bash`), which must be installed for any shell command to run.
518
- - The read-only guarantee is a guard rail against accidental writes, not a
519
- security boundary (see [Security](#security)).
520
- - The plans directory is the only index. `/plan list` walks it on every open and
521
- keeps no registry, so it sees an external edit or a hand-copied plan
522
- immediately; it lists a file over the 256 KiB plan limit without reading it,
523
- and does not index the agent directory's plans while a worktree is active.
524
- - Approval tracks at most 50 steps: `write_plan` refuses a larger plan, and
525
- `exit_plan_mode` refuses to present one, so no reviewed step is silently
526
- dropped. A step longer than 200 characters is shortened in the todo list and
527
- the approval listing — a display rule only, because the plan file always keeps
528
- the full text.
529
- - The todo item for a step is matched by its text. An item renamed after
530
- approval no longer matches, so `set_plan_steps` leaves it alone and says so in
531
- its result; the plan file is still updated.
532
- - Browser review needs a local browser: the server is loopback-only, so a remote
533
- turn cannot drive it.
534
- - A browser that never connects leaves the review waiting until the user
535
- cancels, closes the tab, or aborts the turn.
536
- - Feedback stripping follows fenced code blocks; an unterminated fence makes the
537
- rest of the file code, so a marker-looking line after it is treated as content
538
- rather than stored feedback.
539
-
540
- ## Non-goals
541
-
542
- - **Not a sandbox.** Extensions share Pi's OS permissions.
543
- - **No progress tracker of record.** `todo` owns completion state; the plan
544
- file only mirrors it (`set_plan_steps`), and plan mode does not read completion
545
- back out.
546
- - **No auto-execution.** Approval hands control back to the model.
547
-
548
- ## Pi integration
549
-
550
- | Integration point | How |
551
- |---|---|
552
- | Tools | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, and `set_plan_steps` registered with `executionMode: "sequential"`. `enter_plan_mode` is active normally with `readOnlyHint: true`; `write_plan` (`readOnlyHint: false`) and `exit_plan_mode` (`readOnlyHint: true`) are `defaultActive: false`. `set_plan_steps` is active normally (`readOnlyHint: false`, `idempotentHint: true`); the read-only policy default-denies it in plan mode. Each returns typed `details` (`EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`), and `ExitPlanModeDetails` carries the review's `annotations` when a browser approval or save supplied comments; no `outputSchema`/`structuredContent`. |
553
- | Commands and flags | `/plan` (toggle, or enter and send a task), `/plan list` (plans index), `/plan review [path]` (browser review), `/skill:init` (skill-backed `AGENTS.md` bootstrap), `--plan` launch flag, `Ctrl+Alt+P` shortcut. |
554
- | State | Enabled flag and the tools it hid persisted with `pi.appendEntry("plan-mode", { enabled, removed })`, and each review's baseline persisted with `pi.appendEntry("plan-baseline", { path, sections })` as one digest per heading section; both rebuilt from `ctx.sessionManager.getBranch()`, so `/resume` and `/tree` restore the mode and the changed markers of the branch they land on. Plan writes a goal only at approval, through `ctx.executeTool("goal", { objective })`: a `planning` goal is promoted to the plan's `## Goal`, a session with none is offered one unless `plan.goalPrompt` is off, and an `active`/`achieved` goal is left alone. A review that shows the same content appends nothing. The last plan path lives in runtime memory for the footer chip, and a bare `/plan review` falls back to the newest plan file in the directory. |
555
- | Entry requests | `pi.events` channel `PLAN_ENTER_REQUEST` from `lib/plan-entry.ts`: the `goal` workflow asks, with the shared consent sentence, and plan enables the mode on the caller's context without a second dialog. Subscribed on `session_start`, released on `session_shutdown`. |
556
- | Hooks | `tool_call` blocks non-read-only calls and forces `subagent` read-only; `before_agent_start` injects the hidden `[PLAN MODE ACTIVE]` context, with a pursued session goal read from the branch through `lib/goal-state.ts`; `context` keeps only the newest plan context while enabled and drops stale ones otherwise. |
557
- | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
558
-
559
- ## Design notes
560
-
561
- - **Consent is one dialog, whichever extension asks.** The goal workflow offers
562
- plan mode as a phase of the goal, but a command context cannot call
563
- `enter_plan_mode` (`ExtensionCommandContext` has no `executeTool`). So the
564
- consent sentence left `policy.ts` for `lib/plan-entry.ts`, and the entry itself
565
- is a `pi.events` request: the asker shows the question, plan enables silently
566
- with the asker's context. `enter_plan_mode` keeps its own confirm, using the
567
- same sentence, so the model-initiated path is unchanged and the two texts
568
- cannot drift. A request that nobody answers resolves `false` rather than
569
- hanging, so `goal` loaded alone still works.
570
- - **One review loop owns the human gate.** `review.ts` asks however the session
571
- is attached — TUI screen, dialog plus editor, remote file plus digest, or
572
- browser — and returns one `ReviewDecision`, so `exit_plan_mode` and
573
- `/plan review` no longer carry two copies of the browser lifecycle and the
574
- tool's own `for(;;)` with a `continue` per outcome. The loop also owns the
575
- baseline rule (mark what the screen showed, after it showed it) and forwards
576
- the call's abort signal, which is what closes a review on an aborted turn.
577
- - **Two tools, not one.** `EnterPlanMode`/`ExitPlanMode` are a pair: entering is
578
- consent to explore read-only, exiting is consent to act. A single toggle tool
579
- cannot express "the model wants to plan but the user declines".
580
- - **User entry is instant, model entry confirmed.** `/plan`, `Ctrl+Alt+P`, and
581
- `--plan` are the user's own action and take effect at once; `enter_plan_mode`
582
- changes the mode on the user's behalf, so it confirms first.
583
- - **Review the file, decide in a screen.** The plan is a file, so it is the
584
- artifact already on screen. `ctx.ui.confirm()` is a non-scrollable selector
585
- that pushes buttons off-screen for a long plan, so the TUI gets a scrollable
586
- review screen and dialog-capable modes get a `select` plus editor. The screen
587
- renders the body with Pi's `Markdown` component so the plan reads like any
588
- other markdown in the app; the plan text is sanitized before it reaches the
589
- renderer, so only the theme's own ANSI survives.
590
- - **`write_plan` is the only write.** It is not the builtin `write`, so the
591
- capability policy and path backstop stay intact; a `plan_path` must name a
592
- markdown non-dotfile whose real parent resolves inside the plans directory,
593
- so symlinks and the directory's own `.gitignore` are never writable targets.
594
- - **Approval seeds `todo` via `ctx.executeTool`.** The event bus carries no
595
- `ctx`, so a listener could not refresh the todo widget; calling the real tool
596
- runs its validation and widget sync and needs no change to `todo`. Only the
597
- top-level list items of the `Steps` section become steps (the whole document
598
- is the fallback when that heading is absent), so the outline's prose lists
599
- never seed the list. The list is whole-list replacement, so the current items
600
- are read back from the branch's last `todo` result (the published state
601
- channel; no import from that extension) and the steps are merged into them.
602
- A rejected seed reports the nested tool's own reason in the approval message
603
- instead of failing silently.
604
- - **Ticking a step ticks its item, through the same channel.** The seed's text is
605
- the step's text (`PlanStep.content`, already elided for a long step), so the
606
- mirror matches on that text and changes only `status`: an item the user
607
- renamed is left alone and reported as unmatched rather than overwritten, and a
608
- repeated call writes nothing. The file is written first and stays
609
- authoritative, so a failed nested write is reported in the result text — once
610
- one artifact has changed there is no previous state to hand back.
611
- - **A cap is enforced where it can still be fixed.** Both refusal points —
612
- `write_plan` and `exit_plan_mode` — sit before the write and before the review,
613
- because those are the moments the model can still regroup the steps. A note on
614
- a successful write would arrive after the file existed and the review had
615
- already listed every step, which is exactly when the overflow becomes lost
616
- work.
617
- - **Changed markers compare what was shown, not revisions on disk.** A review
618
- result is only meaningful next to the text the reviewer already read, so every
619
- review marks the sections it presented (keyed by resolved path) and
620
- `plan-text.ts` compares the next plan against those digests. Stored feedback is
621
- stripped from both sides, so a review comment never reads as an edit, and the
622
- same helper feeds the terminal summary and the browser outline. The digests are
623
- a session entry rather than the previous text, so the markers survive a resume
624
- without copying the plan file into the branch, and the reader and the writer
625
- share one normalization so a tab cannot read as a change.
626
- - **The outline guides, it does not enforce.** `write_plan` documents the
627
- canonical shape in its description and stores whatever markdown it is given;
628
- no section is required and nothing is rewritten. The description names the two
629
- headings that do carry behavior — `Steps` is what approval reads as steps, and
630
- `Verification` the trailing check — so the model is told the cost of dropping
631
- one instead of having the plan refused. The review's baseline is keyed by
632
- heading text, so a refinement is asked to keep its headings; a rename reads as
633
- an edit, which is the one change the markers cannot tell from a rewrite.
634
- - **Sections are rendered one at a time.** Pi's `Markdown` exposes no
635
- source-line map, so `tui.ts` splits the body at its headings and renders each
636
- section separately. That gives exact offsets for the current-section label and
637
- `n`/`p`; the single-section case is byte-for-byte the old render.
638
- - **`set_plan_steps` is a second contained write.** It changes only the named
639
- steps' checkboxes, in an existing markdown plan inside the plans directory
640
- (`isContainedPlan`), in a single all-or-nothing write — refusing every entry
641
- if any step is out of range or an entry's `expected` text no longer matches —
642
- and refuses while plan mode is on, so the read-only guarantee and the path
643
- backstop are unchanged. A required `expected` is what makes an ordinal safe:
644
- a plan edited between approval and ticking is caught instead of silently
645
- marking the step that moved into that number.
646
- - **Direction: enforce capabilities at the tool boundary.** (1) A shared
647
- default-deny policy classifies every call as read-only or mutating instead of
648
- maintaining a mutator list. (2) Read-only delegation is enforced in the child
649
- process through `--tools`, not by trusting the agent name or prompt. (3) Raw
650
- shell is removed except the narrow read-only git allowlist in `git-bash.ts`;
651
- no general shell parser. (4) A recursive path guard blocks hint-carrying tools
652
- that take a file path unless they are known plan-safe readers.
653
- - **State is a custom entry.** `{ enabled, removed }` is excluded from model
654
- context and reconstructed on start and tree navigation, so exiting plan mode
655
- restores the tools the branch it was left on hid; the `--plan` flag only
656
- applies when the branch has no persisted entry, so a later disable survives
657
- `/resume`.
658
- - **Browser comments live in the plan, not a sidecar.** A review comment is a
659
- blockquote in the file the model already owns, so there is one artifact to
660
- read, revise, and approve; `blocks.ts` supplies the source-line anchors the
661
- existing heading-only split cannot, and the page renders text with DOM nodes
662
- so review never widens the plan's trust surface. The browser opener is shared
663
- through `lib/browser.ts` rather than imported from `file-browse`.
664
- - **Comments render in the document flow, not a second column.** The anchor is
665
- already a source line, so the browser needs no paired column: a thread is
666
- appended after its block and the plan keeps one measure at every width. The
667
- per-block affordance lives in a narrow rail inside the measure's left margin,
668
- so annotating never moves the text. That removes the two-column grid, the
669
- `bf-review--no-rail` collapse, and the breakpoint ladder, and matches the TUI,
670
- where neither review header carries a progress counter.
671
-
672
- ## Files
673
-
674
- | File | Responsibility |
675
- |---|---|
676
- | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
677
- | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`. |
678
- | `config.ts` | The `plan` section loader: `goalPrompt`, default on. |
679
- | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
680
- | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
681
- | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, atomic `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
682
- | `todos.ts` | Approval-time todo merge (pure): the last list replayed from the branch, and the merge of the plan's steps into it with the cap respected. |
683
- | `steps.ts` | Plan-document structure (pure): the outline, step extraction and the `Steps`-section scope, heading sections, the plan's own goal (`goalSection`), and the checkbox edit. |
684
- | `format.ts` | Pure result/approval text: the plan preview, the step-cap diagnosis, step labels, the merge summary, and the verification note. |
685
- | `plan-text.ts` | The plan text a review shows and compares: `sanitizePlanText`, the one normalization both sides use, the per-section `sectionDigests`, and `changedSections`. |
686
- | `runtime.ts` | Enabled state, tool gating, persistence (mode and review baseline), control-tool identity, footer status. |
687
- | `tools.ts` | Registration entry: composes the four tool registrations in the order the session sees them. |
688
- | `tools/` | One module per tool (`enter.ts`, `write.ts`, `exit.ts`, `steps.ts`), plus `todo-seed.ts` (the two nested `todo` writes) and `nested.ts` (reading a nested result). |
689
- | `review.ts` | The review gate: one decision from the TUI screen, a dialog, or the browser, the shared browser lifecycle, and the "mark what was shown" baseline rule. |
690
- | `commands.ts` | `/plan` (enter, optional task) and the `/plan review [path]` browser subcommand. |
691
- | `tui.ts` | `PlanViewComponent` (sectioned Markdown body, changed-section summary and marker, section jump, scroll gauge, approve/refine/browser/keep). |
692
- | `plan-index.ts` | `/plan list`: the notice text and the docked index screen (title, step progress, age, focused path; Enter reviews). |
693
- | `blocks.ts` | Source-line block model (fence-aware headings, paragraphs, list items, code, quotes, tables, rules, blanks). |
694
- | `feedback.ts` | Browser-comment codec: `stripFeedback`, `parseFeedback`, `formatFeedback`, `feedbackSummary`, and payload validation. |
695
- | `web.ts` | `createPlanWebReview`: the review session (SSE close detection, the body cap, settle-after-flush, abort) over the shared loopback bind, writer, and close. |
696
- | `web-router.ts` | Pure request handler: token/`Host`/method checks and the review routes, over the shared page and JSON header sets. |
697
- | `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation), its data island, and inline CSS. |
698
- | `web-client.ts` | The `/client.js` source: state and wiring (comment rail, comments, selection quotes, save/approve/back, keyboard nav) over the two fragments below. |
699
- | `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
700
- | `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, reading progress, and the stream's lifetime (interpolated into `web-client.ts`). |
701
- | `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
702
- | [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform default-browser opener (shared with `file-browse`). |
703
- | [`../../lib/plan-entry.ts`](../../lib/plan-entry.ts) | Plan-mode entry shared with `goal`: the read-only summary, the consent sentence, and the `pi.events` request `plan` answers by enabling the mode. |
704
- | [`../../lib/shell.ts`](../../lib/shell.ts) | The shell-escape rule the read-only git guard shares with the worktree isolation guard. |
705
- | [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer (shared with `file-browse`). |
706
- | [`../../lib/web/index.ts`](../../lib/web/index.ts) | Shared Bytefly web brand system: tokens, base rules, `tok-*` colors, layout, the bootstrap, the toggle wiring, the shortcut sheet (`keysSheet`, `keysDialogScript`), and shared markup. |
707
- | [`../../lib/http/index.ts`](../../lib/http/index.ts) | Loopback HTTP layer shared with `file-browse`: the page policy and header sets, `ServeResponse`, the request-envelope helpers, bind/respond/close. |
708
- | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |
709
- | `../../lib/tui/chrome.ts` | Shared screen header, viewport-row helper, and styled key-hint footer. |