@gavin-hu/my-pi-agent 0.18.0 → 0.20.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 (324) hide show
  1. package/README.md +66 -52
  2. package/README.zh-CN.md +56 -46
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +507 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
  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 +561 -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} +62 -52
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +298 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +254 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
  25. package/extensions/board/commands.ts +105 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +170 -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 +388 -0
  32. package/extensions/board/result.ts +108 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +90 -0
  35. package/extensions/board/seat.ts +28 -0
  36. package/extensions/board/snapshot.ts +42 -0
  37. package/extensions/board/tools.ts +444 -0
  38. package/extensions/board/transport/router.ts +91 -0
  39. package/extensions/board/transport/server.ts +138 -0
  40. package/extensions/board/types.ts +121 -0
  41. package/extensions/board/view/client.ts +51 -0
  42. package/extensions/board/view/dialog.ts +43 -0
  43. package/extensions/board/view/keys.ts +125 -0
  44. package/extensions/board/view/live.ts +231 -0
  45. package/extensions/board/view/names.ts +91 -0
  46. package/extensions/board/view/page.ts +118 -0
  47. package/extensions/board/view/render.ts +519 -0
  48. package/extensions/board/view/styles.ts +228 -0
  49. package/extensions/board/view/team.ts +327 -0
  50. package/extensions/bytefly/footer/budget.ts +6 -5
  51. package/extensions/bytefly/footer/collect/entries.ts +12 -4
  52. package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
  53. package/extensions/bytefly/footer/config.ts +19 -8
  54. package/extensions/bytefly/footer/price/price.ts +30 -8
  55. package/extensions/bytefly/footer/register.ts +5 -5
  56. package/extensions/bytefly/footer/render/format.ts +18 -0
  57. package/extensions/bytefly/footer/render/lines.ts +23 -7
  58. package/extensions/bytefly/footer/render/segments.ts +117 -31
  59. package/extensions/bytefly/footer/types.ts +29 -5
  60. package/extensions/bytefly/footer/unpriced.ts +42 -17
  61. package/extensions/bytefly/panel/commands.ts +67 -7
  62. package/extensions/bytefly/panel/filter.ts +68 -0
  63. package/extensions/bytefly/panel/format.ts +16 -15
  64. package/extensions/bytefly/panel/join.ts +56 -13
  65. package/extensions/bytefly/panel/prices.ts +43 -0
  66. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  67. package/extensions/bytefly/panel/settings.ts +75 -43
  68. package/extensions/bytefly/panel/tui/entries.ts +26 -8
  69. package/extensions/bytefly/panel/tui/frame.ts +130 -93
  70. package/extensions/bytefly/panel/tui/keys.ts +352 -0
  71. package/extensions/bytefly/panel/tui/list-view.ts +25 -46
  72. package/extensions/bytefly/panel/tui/panes.ts +76 -0
  73. package/extensions/bytefly/panel/tui/section-view.ts +126 -0
  74. package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
  75. package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
  76. package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
  77. package/extensions/bytefly/panel/tui.ts +306 -259
  78. package/extensions/bytefly/picker/discovery.ts +0 -2
  79. package/extensions/bytefly/picker/types.ts +0 -2
  80. package/extensions/bytefly/update/check.ts +39 -0
  81. package/extensions/bytefly/update/format.ts +15 -0
  82. package/extensions/bytefly/update/register.ts +73 -34
  83. package/extensions/bytefly/update/state.ts +6 -3
  84. package/extensions/check/commands.ts +2 -1
  85. package/extensions/context/tui.ts +1 -1
  86. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  87. package/extensions/file-browse/files/classify.ts +136 -0
  88. package/extensions/file-browse/files/files.ts +12 -0
  89. package/extensions/file-browse/files/index.ts +90 -0
  90. package/extensions/file-browse/files/list.ts +261 -0
  91. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  92. package/extensions/file-browse/files/tree.ts +101 -0
  93. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  94. package/extensions/file-browse/index.ts +4 -4
  95. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  96. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
  97. package/extensions/file-browse/transport/server.ts +110 -0
  98. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  99. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  100. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  101. package/extensions/file-browse/view/emit.ts +51 -0
  102. package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
  103. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  104. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
  105. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
  106. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  107. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  108. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  109. package/extensions/file-read/schema.ts +2 -1
  110. package/extensions/job/runtime.ts +4 -4
  111. package/extensions/memory/store.ts +12 -5
  112. package/extensions/plan/blocks.ts +23 -2
  113. package/extensions/plan/commands.ts +21 -16
  114. package/extensions/plan/feedback.ts +185 -52
  115. package/extensions/plan/format.ts +10 -3
  116. package/extensions/plan/index.ts +23 -5
  117. package/extensions/plan/plan-files.ts +88 -0
  118. package/extensions/plan/plan-index.ts +43 -9
  119. package/extensions/plan/plan-text.ts +33 -23
  120. package/extensions/plan/plans.ts +27 -34
  121. package/extensions/plan/policy.ts +30 -17
  122. package/extensions/plan/review.ts +3 -27
  123. package/extensions/plan/runtime.ts +52 -11
  124. package/extensions/plan/sections.ts +112 -0
  125. package/extensions/plan/steps.ts +3 -22
  126. package/extensions/plan/todos.ts +51 -3
  127. package/extensions/plan/tools/exit.ts +20 -95
  128. package/extensions/plan/tools/todo-seed.ts +4 -2
  129. package/extensions/plan/tools/write.ts +12 -1
  130. package/extensions/plan/tui.ts +5 -34
  131. package/extensions/plan/types.ts +15 -8
  132. package/extensions/plan/web-blocks.ts +89 -0
  133. package/extensions/plan/web-chrome.ts +3 -5
  134. package/extensions/plan/web-client.ts +179 -330
  135. package/extensions/plan/web-collab.ts +3 -3
  136. package/extensions/plan/web-comments.ts +18 -8
  137. package/extensions/plan/web-composer.ts +80 -0
  138. package/extensions/plan/web-draft.ts +40 -0
  139. package/extensions/plan/web-keys.ts +134 -0
  140. package/extensions/plan/web-outline.ts +192 -0
  141. package/extensions/plan/web-page.ts +4 -8
  142. package/extensions/plan/web-plan.ts +150 -0
  143. package/extensions/plan/web-router.ts +3 -58
  144. package/extensions/plan/web-settle.ts +194 -0
  145. package/extensions/plan/web-share-session.ts +24 -30
  146. package/extensions/plan/web-streams.ts +14 -14
  147. package/extensions/plan/web-tokens.ts +2 -2
  148. package/extensions/plan/web.ts +66 -120
  149. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  150. package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
  151. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  152. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  153. package/extensions/rewind/index.ts +3 -3
  154. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  155. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  156. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  157. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  158. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  159. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  160. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  161. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  162. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  163. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  164. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  165. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  166. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  167. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  168. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  169. package/extensions/session-bus/commands.ts +17 -69
  170. package/extensions/session-bus/format.ts +22 -45
  171. package/extensions/session-bus/index.ts +13 -7
  172. package/extensions/session-bus/join-request.ts +40 -0
  173. package/extensions/session-bus/mail-watch.ts +134 -0
  174. package/extensions/session-bus/notice.ts +32 -4
  175. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  176. package/extensions/session-bus/protocol.ts +19 -49
  177. package/extensions/session-bus/resolve.ts +67 -0
  178. package/extensions/session-bus/result.ts +16 -51
  179. package/extensions/session-bus/rows.ts +11 -5
  180. package/extensions/session-bus/runtime.ts +220 -267
  181. package/extensions/session-bus/store.ts +55 -7
  182. package/extensions/session-bus/tools.ts +31 -66
  183. package/extensions/session-bus/tui.ts +5 -5
  184. package/extensions/session-bus/types.ts +23 -14
  185. package/extensions/subagent/agents.ts +9 -8
  186. package/extensions/subagent/commands.ts +2 -1
  187. package/extensions/subagent/invocation.ts +4 -2
  188. package/extensions/team/commands.ts +145 -0
  189. package/extensions/team/dispatch.ts +133 -0
  190. package/extensions/team/format.ts +119 -0
  191. package/extensions/team/index.ts +34 -0
  192. package/extensions/team/schema.ts +29 -0
  193. package/extensions/team/tools.ts +267 -0
  194. package/extensions/team/types.ts +65 -0
  195. package/extensions/todo/index.ts +4 -1
  196. package/extensions/todo/nudge.ts +8 -2
  197. package/extensions/web-access/fetch/page.ts +4 -1
  198. package/extensions/web-access/fetch/structured.ts +447 -0
  199. package/extensions/web-access/fetch/tool.ts +2 -1
  200. package/extensions/web-access/search/tool.ts +2 -1
  201. package/extensions/wechat/bridge.ts +106 -251
  202. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  203. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  204. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  205. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  206. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  207. package/extensions/wechat/dialog/channels.ts +136 -0
  208. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  209. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  210. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  211. package/extensions/wechat/index.ts +10 -10
  212. package/extensions/wechat/intake.ts +245 -0
  213. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  214. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  215. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  216. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  217. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  218. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  219. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  220. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  221. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  222. package/extensions/wechat/tools.ts +29 -12
  223. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  224. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  225. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  226. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  227. package/extensions/wechat/transport/outbound.ts +142 -0
  228. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  229. package/extensions/wechat/types.ts +10 -1
  230. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  231. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  232. package/extensions/worktree/commands.ts +24 -17
  233. package/extensions/worktree/exit.ts +12 -6
  234. package/extensions/worktree/git.ts +22 -14
  235. package/extensions/worktree/guard.ts +18 -290
  236. package/extensions/worktree/handoff.ts +10 -2
  237. package/extensions/worktree/index.ts +2 -1
  238. package/extensions/worktree/lifecycle.ts +2 -0
  239. package/extensions/worktree/registry.ts +20 -11
  240. package/extensions/worktree/root-tools.ts +13 -3
  241. package/extensions/worktree/runtime.ts +8 -2
  242. package/extensions/worktree/screen.ts +15 -8
  243. package/extensions/worktree/shell.ts +303 -0
  244. package/extensions/worktree/state.ts +1 -1
  245. package/extensions/worktree/status.ts +42 -9
  246. package/extensions/worktree/tools.ts +19 -13
  247. package/lib/README.md +11 -8
  248. package/lib/board/card.ts +479 -0
  249. package/lib/board/links.ts +141 -0
  250. package/lib/board/page-pointer.ts +310 -0
  251. package/lib/board/round-text.ts +81 -0
  252. package/lib/board/round.ts +94 -0
  253. package/lib/board/rules.ts +437 -0
  254. package/lib/board/session.ts +105 -0
  255. package/lib/board/store.ts +497 -0
  256. package/lib/board/view.ts +234 -0
  257. package/lib/errors.ts +12 -4
  258. package/lib/format.ts +35 -0
  259. package/lib/http/guard.ts +112 -0
  260. package/lib/http/index.ts +16 -5
  261. package/lib/http/serve.ts +237 -0
  262. package/lib/http/server.ts +12 -10
  263. package/lib/http/sink.ts +135 -0
  264. package/lib/http/token.ts +38 -0
  265. package/lib/plan-entry.ts +11 -0
  266. package/lib/policy.ts +25 -2
  267. package/lib/requirements.ts +6 -7
  268. package/lib/session-label.ts +64 -0
  269. package/lib/session-roster.ts +201 -0
  270. package/lib/session-tool.ts +184 -0
  271. package/lib/spawn.ts +37 -3
  272. package/lib/stray-fields.ts +45 -0
  273. package/lib/tool-names.ts +38 -2
  274. package/lib/tui/glyphs.ts +3 -2
  275. package/lib/web/README.md +57 -4
  276. package/lib/web/client-runtime.ts +123 -0
  277. package/lib/web/components.ts +5 -0
  278. package/lib/web/index.ts +4 -2
  279. package/lib/web/markup.ts +88 -1
  280. package/lib/web/names.ts +42 -0
  281. package/lib/web/runtime.ts +7 -1
  282. package/lib/web/tokens.ts +25 -2
  283. package/package.json +26 -14
  284. package/skills/next/SKILL.md +4 -3
  285. package/skills/project-docs/SKILL.md +133 -0
  286. package/skills/project-docs/references/audit.md +131 -0
  287. package/skills/project-docs/references/changelog.md +105 -0
  288. package/skills/project-docs/references/discovery.md +127 -0
  289. package/skills/project-docs/references/readme.md +80 -0
  290. package/skills/project-docs/references/release.md +84 -0
  291. package/skills/project-docs/references/roadmap.md +93 -0
  292. package/skills/project-docs/scripts/state.ts +316 -0
  293. package/skills/refactor/SKILL.md +100 -125
  294. package/skills/refactor/references/handoff.md +35 -0
  295. package/skills/refactor/references/signals.md +54 -0
  296. package/skills/refactor/scripts/inventory.ts +587 -0
  297. package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
  298. package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
  299. package/extensions/file-browse/README.md +0 -506
  300. package/extensions/file-browse/files.ts +0 -564
  301. package/extensions/file-browse/server.ts +0 -141
  302. package/extensions/job/process.ts +0 -35
  303. package/extensions/plan/split.ts +0 -87
  304. package/extensions/session-bus/README.md +0 -247
  305. package/extensions/session-bus/team.ts +0 -102
  306. package/extensions/wechat/outbound.ts +0 -100
  307. package/lib/plans/sections.ts +0 -172
  308. package/lib/plans/setting.ts +0 -52
  309. package/lib/plans/split.ts +0 -215
  310. package/lib/plans/store.ts +0 -19
  311. package/lib/plans/team.ts +0 -151
  312. package/skills/changelog/SKILL.md +0 -148
  313. package/skills/parallel-dev/SKILL.md +0 -150
  314. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  315. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  316. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  317. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  318. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  319. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  320. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  321. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  322. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  323. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  324. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -6,13 +6,70 @@ but the only thing it may write is the plan file itself. The plan is saved under
6
6
  the agent directory; the user reviews that file, and it is the artifact the model
7
7
  executes.
8
8
 
9
+ ## Capabilities
10
+
11
+ Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
12
+
13
+ ### Planning lifecycle
14
+
15
+ | Capability | How | What you get | Status |
16
+ |---|---|---|---|
17
+ | Read-only planning | `enter_plan_mode`, `/plan`, `Ctrl+Alt+P`, `--plan` | The session keeps only its readers, so the model investigates and proposes without touching the tree. | Shipped |
18
+ | Confirmed model entry | `enter_plan_mode` | The model asks before it takes write access away, and the user answers first. | Shipped |
19
+ | Read-only git shell | `bash` with the read-only git allowlist | Plan mode can inspect `git status`, `diff`, `log`, and `show`; pipes, redirects, and non-git commands are refused. | Shipped |
20
+ | Read-only delegation | `subagent` with forced `readOnly: true` | A delegated agent can read and search but cannot write, edit, or run a shell while the plan is being researched. | Shipped |
21
+ | Absolute-path guard | `path-guard.ts` | A path outside the project is refused unless it is a known plan-safe reader, so planning cannot write through an absolute path. | Shipped |
22
+ | One writable artifact | `write_plan` | The plan lands as markdown under the agent directory, one store per repository, shared by its worktrees. | Shipped |
23
+ | Approval gate | `exit_plan_mode` | The plan is read back and presented; nothing is written until the user approves it. | Shipped |
24
+ | Session goal at approval | `goal` at approval | A `planning` goal is promoted to the plan's objective, and a session with none gets it, without a second dialog; an active goal is left alone. | Shipped |
25
+ | Steps as todos | `exit_plan_mode` | Approval seeds the plan's steps into the todo list, with the verification section as the last item; approving the same plan again replaces the items its earlier approval seeded. | Shipped |
26
+ | Plan kept current | `set_plan_steps` | Ticking a step rewrites its checkbox in the file and its todo item in one call. | Shipped |
27
+ | Stale-step protection | `expected` in `set_plan_steps` | A step whose number moved, is out of range, or no longer matches its text refuses the whole batch instead of ticking the wrong line. | Shipped |
28
+ | Cancel without a plan | `cancel_plan_mode`, `/plan cancel` | Write access returns, and no todo items or goal are recorded. | Shipped |
29
+ | Plans index | `/plan list` | Every saved plan with its title, modified time, and step progress, newest first, under a header naming the store's size and its oldest plan. | Shipped |
30
+ | Plan-store migration | `<repo>/.pi/plans` to the agent-dir store | A store left by an older version is moved in once, with a notice, so worktrees and main see the same index. | Shipped |
31
+ | Instruction-file bootstrap | `/skill:init` | The packaged `init` skill plans and gates a project's `AGENTS.md`, writing only after approval. | Shipped |
32
+ | Branch-following mode | the `plan-mode` session entry | `/resume` and `/tree` restore the mode and its tool set for the branch they land on. | Shipped |
33
+
34
+ ### Review
35
+
36
+ | Capability | How | What you get | Status |
37
+ |---|---|---|---|
38
+ | Review screen | `exit_plan_mode` | A scrollable, themed plan with approve, refine, keep, browser, and exit in the footer. | Shipped |
39
+ | Browser page | `/plan review`, `w` on the review screen | The plan opens in a local browser as a reading-first page with live section and step progress. | Partial |
40
+ | Approval in the browser | the `/plan review` page's **Approve & run** | The plan is approved without returning to the terminal, and the steps are seeded. | Shipped |
41
+ | Inline comments | the page's `+ Comment`, **Save feedback** | Comments are saved into the plan file as `> [!REVIEW]` lines and returned to the model. | Shipped |
42
+ | Live page | the page's `events` stream, the plan-file poll | An edit made elsewhere repaints every open page without a reload. | Shipped |
43
+ | Changed sections | the `exit_plan_mode` review screen, the `/plan review` outline | After a refinement, only the sections that changed are marked. | Shipped |
44
+ | Section navigation | the review screen, `n`/`p` | The screen names the section you are in and jumps between headings. | Shipped |
45
+ | Remote review | `exit_plan_mode` on a WeChat turn | The plan file is sent, then one compact prompt to approve, keep planning, or exit. | Shipped |
46
+
47
+ ### Collaboration
48
+
49
+ | Capability | How | What you get | Status |
50
+ |---|---|---|---|
51
+ | Shared review | `/plan share`, **Share** | A reviewer link, with a QR a phone can scan, opens the same plan from another machine on this Wi-Fi. | Shipped |
52
+ | One link per teammate | **New link**, **Revoke**, `/plan share stop` | Each teammate holds a link of their own, and one is revoked without disturbing the rest. | Shipped |
53
+ | Two roles | the owner's `token`, a reviewer's link | Only the owner saves, approves, cancels, and manages links; a reviewer reads and comments. | Shipped |
54
+ | Signed comments | the page's `name` control | A reviewer's name binds to her link on its first post, so her notes are hers and the file records who wrote them. | Shipped |
55
+
56
+ ## Quickstart
57
+
9
58
  ```bash
10
59
  pi --extension ./extensions/plan # load just this extension
11
60
  pi -e . # load the whole @gavin-hu/my-pi-agent package
12
- pi --plan # start in plan mode
61
+ pi install ./ # install the package for a user
62
+ ```
63
+
64
+ ```text
65
+ /plan # toggle plan mode
66
+ /plan add rate limiting to the public API # enter plan mode and send the task
13
67
  ```
14
68
 
15
- ## Tool rendering
69
+ The model calls `enter_plan_mode` before a non-trivial task; `/plan` and
70
+ `Ctrl+Alt+P` are the user's own switch.
71
+
72
+ ## Tools
16
73
 
17
74
  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
75
 
@@ -20,207 +77,173 @@ How the `write_plan` and `exit_plan_mode` calls and their results look in the tr
20
77
 
21
78
  <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
79
 
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, shared by the whole repository.** The model saves the plan with
48
- `write_plan`; the file lands in
49
- `<agent-dir>/plans/<repo-key>/<YYYY-MM-DD-HHmm>-<slug>.md`, keyed by the
50
- repository's common git directory, so a plan approved in one worktree is the
51
- same plan from the main checkout and from every other session. Outside a
52
- repository the store is `<agent-dir>/plans`. Plans are outside the project, so
53
- they are never untracked files and never appear in a `rewind` snapshot; a store
54
- an older version left in `<repo>/.pi/plans` is moved in once, with a notice.
55
- - **A discoverable index.** `/plan list` walks the plans directory and shows
56
- every plan with its title, modified time, and step progress, newest first — in
57
- the TUI as a docked list where `Enter` opens the plan's review, elsewhere as a
58
- notice with each plan's path. The directory stays the index: no registry file,
59
- one bounded read per plan.
60
- - **A shape that reads.** `write_plan` documents a canonical outline — a
61
- `# <Title>`, then `## Goal`, `## Context`, `## Approach`, `## Visual`,
62
- `## Steps`, `## Risks`, and `## Verification` — and the model is guided to
63
- follow it. Every example in the template is the shape wanted rather than a
64
- description of it, and each section has one job: the goal is one sentence, the
65
- context is the facts that force the approach, the approach names the
66
- alternative it rejected, and verification is a fence of the commands to run.
67
- `Visual` carries the end state in an untagged fence, with one line naming what to
68
- look at: the same surface before and after when the change alters one that
69
- already exists, one frame when the surface is new, because a reviewer judges the
70
- result before the steps that build it; a change with no visible surface drops it.
71
- The description calls the shape a guide rather than a form: `Context`,
72
- `Approach`, `Visual`, and `Risks` are dropped when they have nothing to say,
73
- while `Steps` and `Verification` are kept, because the steps are read from that
74
- section (without it, the shallowest list anywhere becomes the steps) and
75
- Verification becomes the last todo item. Within `Steps` only the shallowest
76
- list is tracked, so nested bullets stay notes instead of becoming todos. This
77
- is guidance only: any markdown is accepted, stored, and rendered as written,
78
- and a refinement is asked to keep its section headings so the review can still
79
- mark only what changed.
80
- - **Trackable steps.** Steps are written as `- [ ]` checkboxes in the `## Steps`
81
- section; approval seeds them into `todo`, with the `## Verification` section as
82
- the last item so the definition of done travels with the work. A plan with more
83
- than 50 top-level steps is refused rather than truncated, so approval never
84
- drops a step you reviewed.
85
- - **A navigable review.** The review screen splits the body at its headings,
86
- names the section you are in, and jumps between sections with `n`/`p`.
87
- - **What changed since the last review.** After a refinement, the review screen
88
- names how many sections changed, marks the current one in the scroll row, and
89
- the browser outline dots each changed section, so a re-review does not mean
90
- re-reading the whole plan. The baseline is one digest per heading section of
91
- what the previous review showed, kept as a session entry, so a resumed session
92
- or a `/tree` restore marks only what changed on its own branch.
93
- - **Kept current.** After approval, `set_plan_steps` flips the checkboxes of
94
- the steps you name in the file, so the plan mirrors what has actually been
95
- done, and ticks the todo item approval seeded from that step with it, so the
96
- widget and the plan do not drift apart. Pass the step's text as `expected` and
97
- an update whose step number moved is refused instead of ticking the wrong line.
98
- - **Reviewable plan.** `exit_plan_mode` reads the plan file back and presents
99
- it for approval. The user approves, keeps planning, asks for a refinement, or
100
- exits plan mode without approving; the last keeps the plan file for a later
101
- review and records no todo items or goal.
102
- On a remote turn (WeChat) the plan file is sent first, then one compact editor
103
- prompt: the title, step count, first steps, mode status, and three actions
104
- (approve, keep, exit). Any other reply is a refinement request; if the file
105
- upload fails, its text is sent instead.
106
- - **Browser review on demand.** Press `w` on the review screen (or run
107
- `/plan review [path]`) to open the plan in a local browser: a reading-first
108
- page with the plan's H1 as its title, GitHub's Markdown content column, a
109
- comment rail in
110
- the left margin, and live section and step progress — the step count comes from
111
- the same `Steps` section list approval seeds. Select text to leave an
112
- inline comment, and save them back into the plan file itself as
113
- `> [!REVIEW]` lines. Saved comments return to the model as a refinement
114
- request.
115
- - **The file is the source of truth.** On approval the model follows the plan
116
- file's steps; refinements rewrite the same file (`write_plan` with
117
- `plan_path`), so the reviewed artifact is the executed artifact.
118
- - **Steps become todos.** On approval, the top-level numbered/bulleted items of
119
- the plan's `Steps` section are recorded with the `todo` tool (via
120
- `ctx.executeTool`); steps marked `- [x]` (or `[DONE:n]`) seed as completed, and
121
- nested sub-bullets are treated as notes while an indented line continues its
122
- step. The list is a merge, not a replacement: existing items keep their place
123
- and status, a step whose text is already there is skipped, and the tool's item
124
- cap defers the rest instead of dropping anything. The plan's `## Verification`
125
- section follows as the **last**
126
- todo item, summarized on one line (`Verify: …`) — a fenced block contributes its
127
- commands, not its fence lines. It is not a plan step, so it is ticked in the todo
128
- list rather than with `set_plan_steps`, and it never counts against the 50-step
129
- cap. A plan without a `Steps` heading keeps the older rule —
130
- the shallowest list in the document — so prose bullets in a plan that follows the
131
- outline never become todos, and its verification bullets are already covered.
132
- Each seeded item is matched back to its step by text, so ticking a step with
80
+ All five tools are `executionMode: "sequential"` and closed-world. Two are active
81
+ in normal mode and three are `defaultActive: false`, so the plan-mode tool set
82
+ stays small.
83
+
84
+ | Tool | Active | Purpose |
85
+ |---|---|---|
86
+ | `enter_plan_mode` | normal mode | Ask to enter plan mode; empty schema, confirmed with `ctx.ui.confirm`. |
87
+ | `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. |
88
+ | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. |
89
+ | `cancel_plan_mode` | plan mode | Leave plan mode without a plan file or a review. Empty schema. |
90
+ | `set_plan_steps` | normal mode | Set the checkboxes of the given top-level steps of an approved plan in one write, and their todo items with them. |
91
+
92
+ ### `enter_plan_mode`
93
+
94
+ No parameters. It confirms with `ctx.ui.confirm` before switching the tool set,
95
+ because the model is changing the mode on the user's behalf; a declined confirm
96
+ answers "stayed in normal mode" and the session keeps write access. Already
97
+ planning, it says so. With no UI it fails with an actionable message instead of
98
+ deciding for the user.
99
+
100
+ On a remote turn the confirmation is a short consent sentence in the peer's
101
+ language, and the notice names how to leave.
102
+
103
+ ### `write_plan`
104
+
105
+ `title` is the file-name slug, `content` the full markdown, and `plan_path`
106
+ overwrites an existing plan when refining. The result carries the absolute path,
107
+ the relative path, and the byte count — the absolute path is what `exit_plan_mode`
108
+ takes.
109
+
110
+ The file lands in
111
+ `<agent-dir>/plans/<repo-key>/<YYYY-MM-DD-HHmm>-<slug>.md`, keyed by the
112
+ repository's common git directory, so a plan approved in one worktree is the
113
+ same plan from the main checkout and from every other session. Outside a
114
+ repository the store is `<agent-dir>/plans`. Plans are outside the project, so
115
+ they are never untracked files and never appear in a `rewind` snapshot; a store
116
+ an older version left in `<repo>/.pi/plans` is moved in once, with a notice.
117
+
118
+ The description documents a canonical outline — a `# <Title>`, then `## Goal`,
119
+ `## Context`, `## Approach`, `## Visual`, `## Steps`, `## Risks`, and
120
+ `## Verification` — and the model is guided to follow it. Every example in the
121
+ template is the shape wanted rather than a description of it, and each section
122
+ has one job: the goal is one sentence, the context is the facts that force the
123
+ approach, the approach names the alternative it rejected, and verification is a
124
+ fence of the commands to run. `Visual` carries the end state in an untagged
125
+ fence, with one line naming what to look at: the same surface before and after
126
+ when the change alters one that already exists, one frame when the surface is
127
+ new, because a reviewer judges the result before the steps that build it; a
128
+ change with no visible surface drops it. The description calls the shape a guide
129
+ rather than a form: `Context`, `Approach`, `Visual`, and `Risks` are dropped when
130
+ they have nothing to say, while `Steps` and `Verification` are kept, because the
131
+ steps are read from that section (without it, the shallowest list anywhere
132
+ becomes the steps) and Verification becomes the last todo item. Within `Steps`
133
+ only the shallowest list is tracked, so nested bullets stay notes instead of
134
+ becoming todos. This is guidance only: any markdown is accepted, stored, and
135
+ rendered as written, and a refinement is asked to keep its section headings so
136
+ the review can still mark only what changed.
137
+
138
+ A plan with more than 50 top-level steps in its `Steps` section is refused with
139
+ the cap and the fix named, and nothing is written.
140
+
141
+ The file is the source of truth. On approval the model follows the plan file's
142
+ steps, and refinements rewrite the same file (`write_plan` with `plan_path`), so
143
+ the reviewed artifact is the executed artifact. The text a refinement replaces is
144
+ kept beside it, as a dotfile named for the plan and the revision
145
+ (`.2026-10-26-1008-add-rate-limiting-r001.md`), up to the newest five: that is
146
+ what makes two revisions diffable, and what lets a comment `carryFeedback` moved
147
+ onto another block be checked against the text the reviewer actually read. A
148
+ revision is a dotfile, so no scan lists it as a plan, no `plan_path` opens it, and
149
+ the review never offers it for approval — and a refinement that changed nothing
150
+ keeps nothing, because the copy would be the plan itself. The write's result names
151
+ the revision it kept.
152
+
153
+ ### `exit_plan_mode`
154
+
155
+ `plan_path` names the file `write_plan` returned; the tool reads it back and
156
+ presents it for approval. A hand-edited or older file whose step list is over the
157
+ cap is refused before the review screen. The user approves, keeps planning, asks
158
+ for a refinement, or exits plan mode without approving; the last keeps the plan
159
+ file for a later review and records no todo items or goal. The review also offers
160
+ **Exit plan mode (discard)**, which leaves plan mode without approving.
161
+
162
+ On approval, plan mode ends, write access returns, and the plan's steps go into
163
+ the todo list:
164
+
165
+ - Steps are the top-level numbered/bulleted items of the plan's `Steps` section,
166
+ and approval records them with the `todo` tool (via `ctx.executeTool`). Steps
167
+ marked `- [x]` (or `[DONE:n]`) seed as completed, nested sub-bullets are notes,
168
+ and an indented line continues its step.
169
+ - The list is a merge, not a replacement: existing items keep their place and
170
+ status, a step whose text is already there is skipped, and the tool's item cap
171
+ defers the rest instead of dropping anything.
172
+ - The plan's `## Verification` section follows as the **last** todo item,
173
+ summarized on one line (`Verify: …`) — a fenced block contributes its
174
+ commands, not its fence lines. It is not a plan step, so it is ticked in the
175
+ todo list rather than with `set_plan_steps`, and it never counts against the
176
+ 50-step cap.
177
+ - A plan without a `Steps` heading keeps the older rule — the shallowest list in
178
+ the document — so prose bullets in a plan that follows the outline never become
179
+ todos, and its verification bullets are already covered.
180
+ - Each seeded item is matched back to its step by text, so ticking a step with
133
181
  `set_plan_steps` completes the item too.
134
- - **Goal-aware.** While planning, the injected context restates a pursued
135
- session goal as one bullet, so the plan is designed against the objective
136
- instead of beside it. On approval, a goal the goal workflow left as a draft
137
- (`planning`) is finalized from the plan's own `## Goal` section — falling back
138
- to the title — through the `goal` tool, with no extra dialog, because approving
139
- the plan *is* the decision. A session with no goal at all is given the same
140
- objective, also without asking. An `active` or `achieved` goal is never
141
- rewritten: a final objective is the user's. No path spends a model turn.
142
- - **A split, when the switch is on.** With `plan.split` enabled (off by default;
143
- a `/bytefly settings` row), `exit_plan_mode` also accepts `units`: the plan's
144
- slices, each with a name, path prefixes, a test, and the units it depends on.
145
- The split is validated *before* the review opens, so a refusal costs no review,
146
- and the review screen shows it as a read-only `Split` section. On approval the
147
- team is written into the plan as a `## Team` table — a sentence naming the lead's
148
- unit, then one row per unit — and the result carries a roster card: the exact
149
- commands for the sessions this package cannot start, the merge order, which unit
150
- is the lead's own, and the lead's own label or the rename that sets it. The team
151
- section is what a joining session reads, so it appears only after approval; a
152
- refinement rewrites the plan, and with it the team ceases to exist until the plan
153
- is approved again. Refusals name the pair: fewer than two units (with the
154
- skeleton advice), overlapping prefixes, a `dependsOn` cycle, more than two owners
155
- plus the lead, a unit with no test, an owner unit that claims a shared file, and a
156
- value that would break the table. See [Splitting a plan](#splitting-a-plan).
157
- - **Branch-aware.** The enabled flag and the review baseline are custom session
158
- entries, so `/resume` and `/tree` restore the right mode and keep the
159
- changed-section markers.
160
-
161
- ## Splitting a plan
162
-
163
- Off by default: enable **Split plans into slices** in `/bytefly settings`, or set
164
- `"plan": { "split": true }` in the project's `.pi/bytefly.json` so a repository
165
- switches its own team on.
182
+ - An approval records the step texts it seeded, per plan path, as a `plan-seeded`
183
+ entry. A plan refined after approval is approved again — `write_plan` needs plan
184
+ mode, so a second round is the way back — and that second approval **replaces the
185
+ leftovers its own earlier approval made**: an untouched item whose step was
186
+ renamed or dropped goes, while one somebody started or finished is kept and
187
+ named, because deleting it would erase work a reader did. `write_plan` says the
188
+ list is stale when it refines a plan that was approved before.
189
+
190
+ **Goal-aware.** While planning, the injected context restates a pursued session
191
+ goal as one bullet, so the plan is designed against the objective instead of
192
+ beside it. On approval, a goal the goal workflow left as a draft (`planning`) is
193
+ finalized from the plan's own `## Goal` section — falling back to the title —
194
+ through the `goal` tool, with no extra dialog, because approving the plan *is*
195
+ the decision. A session with no goal at all is given the same objective, also
196
+ without asking. An `active` or `achieved` goal is never rewritten: a final
197
+ objective is the user's. No path spends a model turn.
198
+
199
+ ### `cancel_plan_mode`
200
+
201
+ Leaves plan mode without a plan file or a review. Restores write access, records
202
+ no todo items or goal, and keeps any saved plan. Empty schema.
203
+
204
+ ### `set_plan_steps`
205
+
206
+ Keep an approved plan current: `plan_path`, plus `updates`, each a 1-based `step`
207
+ in the order `exit_plan_mode` listed, a `status` of `pending` or `completed`, and
208
+ `expected` — the step's text, so an update whose step number moved is refused
209
+ instead of ticking the wrong line. It is a single all-or-nothing write: a step out
210
+ of range, or an `expected` text that no longer matches the file, refuses every
211
+ entry. Only the checkboxes change, and a reopened step drops its `[DONE:n]`
212
+ marker.
213
+
214
+ The todo item approval seeded from a step is ticked with it: the file is written
215
+ first and is authoritative, a renamed item is left alone and reported as
216
+ unmatched, and a failed todo write is reported in the result. The tool refuses
217
+ while plan mode is on — refine the plan with `write_plan` instead.
166
218
 
167
- ```text
168
- exit_plan_mode({
169
- plan_path: "~/.pi/agent/plans/a1f9c02/2026-11-04-1802-billing.md",
170
- units: [
171
- { name: "contract", paths: ["src/billing/contract"], test: "bun test src/billing/contract", lead: true },
172
- { name: "core", paths: ["src/billing/core"], test: "bun test src/billing/core", dependsOn: ["contract"] }
173
- ]
174
- })
175
- ```
219
+ ### Transcript
176
220
 
177
- The result prints the roster card, which is all the package can do for the sessions
178
- it cannot start — and the one line it can act on for the session it is in:
221
+ The three tool rows reuse the transcript component, and `exit_plan_mode`
222
+ separates the plan body from its header with a blank line; the collapse hint
223
+ binds to `app.tools.expand` (`ctrl+o` by default).
179
224
 
180
- ```text
181
- Split: 2 units · ~/.pi/agent/plans/a1f9c02/2026-11-04-1802-billing.md
182
- You start and switch every session; nothing here can.
183
- core: enter_worktree core · /sessions name core · /sessions watch wake
184
- session({action:"join", team:"2026-11-04-1802-billing", unit:"core"})
185
- merge order: contract → core · your unit: contract
186
- your label: none — name this session: session({action:"name", label:"contract"})
225
+ ```
226
+ enter_plan_mode requested plan mode
227
+ ✓ Plan mode enabled
187
228
  ```
188
229
 
189
- The last line is the lead's own label. The card's other lines are for the sessions
190
- the user starts; this one is the lead's own, so it names the label when the session
191
- already has one, and otherwise prints the rename to run. Once set, it reads
192
- `your label: billing-lead — owners send their report there`, which is what an
193
- owner addresses its completion report to.
194
-
195
- What the gate enforces, and why each rule is a refusal rather than a warning:
230
+ ```
231
+ write_plan Add rate limiting
232
+ ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
233
+ ```
196
234
 
197
- | Refusal | Why |
198
- |---|---|
199
- | fewer than two units | One path is not a split: approve as one flow, or build the thinnest end-to-end path first. |
200
- | overlapping prefixes | Two owners on one path is a merge conflict by construction. |
201
- | a `dependsOn` cycle | No unit can start. |
202
- | more than two owners | The user switches to every owner each round; three means the split is wrong. |
203
- | a unit with no test | The unit's definition of done would be undocumented. |
204
- | an owner unit claiming a shared file | `package.json`, the changelog, the test manifests, and the shared name modules belong to the lead. |
205
- | no unit marked lead | The approving session needs a unit of its own, or it is a queue. |
206
-
207
- The table itself is a wire format: [`session-bus`](../session-bus/) parses it with
208
- the same module that writes it, so a hand-edited or stale section is refused
209
- rather than half-read. The browser review cannot approve a split plan — a section
210
- written at approval has no source lines for its comments to anchor to — so the
211
- terminal review is where a split is approved.
235
+ ```
236
+ exit_plan_mode submitted a plan
237
+ ✓ Plan approved
238
+ ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
212
239
 
213
- ## Tools
240
+ # Add rate limiting
241
+ 1. Add a token bucket …
214
242
 
215
- | Tool | Active | Purpose |
216
- |---|---|---|
217
- | `enter_plan_mode` | normal mode | Ask to enter plan mode; empty schema, confirmed with `ctx.ui.confirm`. |
218
- | `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. |
219
- | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. Optional `units` proposes a split when `plan.split` is on; it is validated before the review, written into the plan on approval, and ignored with one note while the switch is off. 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. |
220
- | `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. |
221
- | `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. |
243
+ … 12 more lines (ctrl+o to expand)
244
+ ```
222
245
 
223
- ### Commands and flags
246
+ ## Commands and flags
224
247
 
225
248
  | Path | Who starts it | Confirmation |
226
249
  |---|---|---|
@@ -229,6 +252,7 @@ terminal review is where a split is approved.
229
252
  | `/plan <prompt>` | the user, with the task | immediate |
230
253
  | `/plan list` | the user, browsing the plans directory | immediate |
231
254
  | `/plan review [path]` | the user, reviewing a plan in the browser | immediate |
255
+ | `/plan share [stop]` | the user, handing the review to a teammate | immediate |
232
256
  | `/plan cancel` | the user, leaving plan mode without approving | immediate |
233
257
  | `/skill:init` | the user, creating or updating `AGENTS.md` | enters plan mode; write waits for plan approval |
234
258
  | `Ctrl+Alt+P` | the user | immediate |
@@ -248,13 +272,29 @@ the toggle that works in every terminal (for example Zed's integrated terminal,
248
272
  which does not forward `Ctrl+Alt+P`). The footer shows `≡ plan` while plan mode
249
273
  is on, and `≡ plan · <plan-file>` once the model has written a plan.
250
274
 
251
- `/plan review` is the one reserved subcommand. Bare `/plan review` reviews the
252
- plan this session wrote, or, in a later session, the newest plan file in the
253
- plans directory (newest modification time); `/plan review <path>.md` (or a path
254
- with a separator) reviews that plan. A line that merely starts with the word —
255
- `review the auth flow` — is still treated as a planning task, so the subcommand
275
+ `/plan list` walks the plans directory and shows every plan with its title,
276
+ modified time, and step progress, newest first — in the TUI as a docked list
277
+ where `Enter` opens the plan's review, elsewhere as a notice with each plan's
278
+ path. The header carries the store's own footprint for this repository: the bytes
279
+ its plans hold and how old the oldest one is, both derived from the walk that just
280
+ happened. The directory stays the index: no registry file, nothing deletes a plan,
281
+ and one bounded read per plan.
282
+
283
+ `/plan review` is the only subcommand that takes a path; `/plan share` takes the
284
+ one word `stop` after it, and `list` and `cancel` take nothing. Bare `/plan review`
285
+ reviews the plan this session wrote, or, in a later session, the newest plan file
286
+ in the plans directory (newest modification time); `/plan review <path>.md` (or a
287
+ path with a separator) reviews that plan. A line that merely starts with the word
288
+ — `review the auth flow` — is still treated as a planning task, so the subcommand
256
289
  never swallows one.
257
290
 
291
+ `/plan share` prints the reviewer link and its QR, and `/plan share stop` revokes
292
+ every link; the page's **Share** button does the same. With no browser review open
293
+ it says so and points at `/plan review`. `/plan cancel` leaves plan mode without
294
+ approving, exactly like `cancel_plan_mode`: write access returns and the plan file
295
+ stays for a later review. Both are exact words — `share the tokens` and `cancel
296
+ that idea` stay tasks — as is `list`.
297
+
258
298
  ### `/skill:init`
259
299
 
260
300
  `/skill:init` loads the packaged `init` skill. The skill enters plan mode itself
@@ -300,51 +340,53 @@ The quality reference is
300
340
  [`skills/init/references/agents-md-quality.md`](../../skills/init/references/agents-md-quality.md).
301
341
  An existing instruction file is reviewed and updated only after approval, a root
302
342
  override is edited in place or removed explicitly when consolidating, an accurate
303
- file can produce a no-op plan, and an empty repository gets only known facts.
343
+ file can produce a no-op plan, and an empty repository gets only known facts. It
344
+ never invents commands.
304
345
  The skill needs the plan extension for its review gate. It also ships with the
305
346
  package, so load the whole package (`pi -e .`) or install it; loading only
306
347
  `./extensions/plan` leaves `/skill:init` unavailable.
307
348
 
349
+ ## Routes
350
+
351
+ The browser review serves these routes on its loopback origin; `…` is the
352
+ session token, and the owner's token is the one that opened the page.
353
+
354
+ | Route | Result |
355
+ |---|---|
356
+ | `GET /?token=…` | the review page (a JSON data island plus `/theme.js` and `/client.js`) |
357
+ | `GET /theme.js` | the shared pre-paint theme bootstrap |
358
+ | `GET /client.js` | the first-party client script |
359
+ | `GET /events?token=…` | Server-Sent Events stream: a `comment`, `plan`, or `share` frame when something changed, `closed` when the review ends, `revoked` when sharing stops, and a dropped stream to detect a closed tab |
360
+ | `POST /api/comment?token=…` | reviewer only: write their own comments into the plan, replacing only their own; never ends the review |
361
+ | `POST /api/feedback?token=…` | owner only: write the owner's comments and return to the model |
362
+ | `POST /api/approve?token=…` | owner only: save, then approve |
363
+ | `POST /api/cancel?token=…` | owner only: end the session without saving |
364
+ | `POST /api/share?token=…` | owner only: mint the first reviewer link and its QR |
365
+ | `POST /api/share/link?token=…` | owner only: mint one more reviewer link, so each teammate holds their own |
366
+ | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
367
+ | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
368
+
308
369
  ## Behaviour by mode
309
370
 
310
- The review screen is TUI-only. `exit_plan_mode` opens `PlanViewComponent`: a
311
- scrollable, width-safe view of the plan with approve/refine/keep/exit in the footer.
312
- The body is rendered with Pi's own `Markdown` component and `getMarkdownTheme()`,
313
- so headings, lists, inline code, code blocks, links, and quotes carry the active
314
- theme's colors; task items get the shared `◼` (done) or `◻` (pending) marker,
315
- like the bytefly screen and the question dialog. The footer colors the primary
316
- actions (`a approve & run` in `success`, `r refine` in `accent`).
317
- `a` asks `y/N` in the footer, then ends plan mode, restores write access, and
318
- seeds `todo`; `y` or `Enter` approves and `n`, `q`, or `Esc` goes back to
319
- reading. `r` keeps the plan
320
- mounted and opens an inline editor beneath it (`Enter` submits, an empty buffer
321
- does nothing, `Esc` returns to the plan); `Esc` keeps planning and tells the
322
- model the plan was not approved; `x` leaves plan mode without approving and
323
- keeps the plan file. The dialog path is one editor prompt: `1` approve, `2` full
324
- plan, `3` keep, `4` exit; any other text refines the plan. Dismissing or timing
325
- out the prompt exits without approving. When the plan changed since the previous review
326
- on this branch, a summary line under the title names how many sections changed
327
- and the scroll row appends `· changed` to the current section. Scroll with `↑`/`↓` or `j`/`k`, `space`/`b` or
328
- `PgUp`/`PgDn`, `d`/`u`, `g`/`G`, and the mouse wheel. Press `n`/`p` to jump to
329
- the next or previous heading; the scroll row names the current section when the
330
- plan has more than one. When the plan overflows,
331
- a gauge row above the hints shows the scroll position, the percent, and the
332
- visible line range (the range drops first on a narrow terminal); the gauge comes
333
- from the design system's `gaugeBar` and stays
334
- visible while the refine editor is open, since the body above is still clipped.
335
- Hints are
336
- dropped whole as the terminal narrows, and each footer leads with its safe exit
337
- (`Esc keep plan` in the review, `Esc back to plan` in the inline editor) so
338
- the exit survives even when only one hint fits.
339
-
340
- Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt; a non-action
341
- reply is the refinement, so “select Refine, then type” is gone. A WeChat turn
342
- takes the same dialog path and is answered over WeChat, even though the session
343
- is in TUI mode: the choice comes from `askHuman(ctx, { custom, dialogs, body })`
344
- in `lib/interaction.ts`, not from `ctx.mode`. No plan file is attached; **full
345
- plan** posts the text and asks again.
346
- Without any UI the tools fail with an actionable message instead of deciding for
347
- the user.
371
+ The review screen is TUI-only: `exit_plan_mode` opens `PlanViewComponent`, and `w`
372
+ on it opens the browser review. RPC and other non-interactive runs get no screen.
373
+
374
+ Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt instead: on a
375
+ remote turn the plan file is sent first, then a compact prompt carrying the
376
+ title, step count, first steps, mode status, and three actions — `1` approve,
377
+ `2` keep planning, `3` exit. If the upload fails, the plan's text is sent
378
+ instead. Any other reply is a refinement, so "select Refine, then type" is gone.
379
+ A WeChat turn takes the same dialog path and is answered over
380
+ WeChat, even though the session is in TUI mode: the choice comes from
381
+ `askHuman(ctx, { custom, dialogs, body })` in `lib/interaction.ts`, not from
382
+ `ctx.mode`. Without any UI the tools fail with an actionable message instead of
383
+ deciding for the user.
384
+
385
+ `/plan list` is a docked list screen in the TUI and a notice with each plan's
386
+ path everywhere else; both name the store's size and its oldest plan. The dock is
387
+ the list's alone while it is open — the goal and todo rails hide, as they do for
388
+ the other list screens — and the screen counts its own frame, so a full list keeps
389
+ its closing rule on a short terminal.
348
390
 
349
391
  Browser review is a local-turn feature: the server binds `127.0.0.1`, so a remote
350
392
  (WeChat) turn cannot reach it and `/plan review` there refuses with a notice
@@ -356,49 +398,35 @@ Aborting the turn closes a browser review. `exit_plan_mode` forwards the call's
356
398
  abort signal to the review server, so an aborted turn returns "not approved"
357
399
  and stops listening instead of waiting on a page nobody is watching.
358
400
 
359
- ### Transcript
360
-
361
- The three tool rows reuse the transcript component, and `exit_plan_mode`
362
- separates the plan body from its header with a blank line; the collapse hint
363
- binds to `app.tools.expand` (`ctrl+o` by default).
364
-
365
- ```
366
- enter_plan_mode requested plan mode
367
- ✓ Plan mode enabled
368
- ```
369
-
370
- ```
371
- write_plan Add rate limiting
372
- ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
373
- ```
374
-
375
- ```
376
- exit_plan_mode submitted a plan
377
- ✓ Plan approved
378
- ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
379
-
380
- # Add rate limiting
381
- 1. Add a token bucket …
382
-
383
- … 12 more lines (ctrl+o to expand)
384
- ```
385
-
386
401
  ## Security
387
402
 
388
403
  Plan mode is a **guard rail, not a sandbox**: extensions run with Pi's OS
389
404
  permissions, so it prevents accidental writes while planning, not a hostile
390
405
  command. The read-only guarantee is layered:
391
406
 
407
+ - **Tool set at entry.** Tools that are not read-only are removed from the active
408
+ set or blocked. `write` and `edit` are hidden, raw shell is limited to read-only
409
+ git commands through `bash` (chained with `&&`/`;`), `powershell` is blocked
410
+ outright, and other mutating tools (worktree mutations, MCP tools) are blocked
411
+ until you exit plan mode. `subagent` stays active but is forced read-only. The
412
+ only permitted write is the plan control tool `write_plan`, which can only
413
+ create or overwrite markdown plan files directly inside the plans directory —
414
+ never dotfiles and never through a symlink.
392
415
  - **Capability policy.** A tool is allowed only when it is a known structured
393
416
  reader or declares the MCP `readOnlyHint`; `write`, `edit`, and unknown
394
- mutators are denied.
417
+ mutators are denied. Classification defaults to *deny* and is driven by the
418
+ shared [`lib/policy.ts`](../../lib/policy.ts), so unknown tools are safe by
419
+ default and the MCP `readOnlyHint` is the only thing that opens one up.
395
420
  - **Path backstop.** A `readOnlyHint` is only a claim, so a tool that takes a
396
421
  file path is refused unless it is a known plan-safe reader. `path-guard.ts`
397
422
  scans nested arguments and path-shaped values, not just fixed top-level keys.
398
- - **Control-tool identity.** The `tool_call` guard exempts `write_plan` /
399
- `exit_plan_mode` only when the registered tool is this extension's own,
400
- matched by source path, so a same-named tool from another extension cannot
401
- borrow the exemption.
423
+ - **Control-tool identity.** The `tool_call` guard exempts plan's own tools —
424
+ `enter_plan_mode`, `cancel_plan_mode`, `write_plan`, and `exit_plan_mode` — only
425
+ when the registered tool is this extension's, matched by source path, so a
426
+ same-named tool from another extension cannot borrow the exemption.
427
+ `enter_plan_mode` is exempted so a call already in flight when plan mode came on
428
+ is answered with "Already in plan mode" instead of being refused as not
429
+ read-only; the policy still denies it, so it stays hidden while planning.
402
430
  - **Contained writes.** `write_plan` only writes markdown non-dotfiles whose
403
431
  real parent resolves inside the plans directory, and every plan write —
404
432
  create, refine, `set_plan_steps`, browser feedback — goes through a hidden
@@ -428,9 +456,10 @@ conservative allowlist in `git-bash.ts`. `powershell` is not allowed at all, so
428
456
  plan mode uses Git Bash on native Windows: the same read-only git commands work
429
457
  there, and a backslash before an ordinary character is accepted as a Windows
430
458
  path separator while a backslash that escapes a metacharacter is still refused.
431
- Everything else uses `read`, `grep`, `find`,
432
- `ls`, and the other read-only tools already available (`web_search`,
433
- `web_fetch`, `ask_user_question`, `todo`, `goal`).
459
+ Everything else uses the structured readers — `read`, `grep`, `find`, `ls`, and
460
+ `read_doc` for a PDF, Word, OpenDocument, RTF, or Excel file — and the other
461
+ read-only tools already available (`web_search`, `web_fetch`,
462
+ `ask_user_question`, `todo`, `goal`).
434
463
 
435
464
  ```bash
436
465
  git status && git diff --stat # allowed: two read-only git commands
@@ -441,6 +470,10 @@ git status | sh # blocked: pipes are never allowed
441
470
  git diff > out.patch # blocked: redirection is a write
442
471
  ```
443
472
 
473
+ A refused shell command explains the fix: the working directory is already the
474
+ project root (so `cd` is unnecessary), and files belong to the `read`, `grep`,
475
+ `find`, and `ls` tools.
476
+
444
477
  A string parser can never be the boundary, so capability gating replaced the
445
478
  earlier segment-based command allowlist. The git shell guard is a narrow,
446
479
  deliberate exception — an allowlist over read-only git command shapes, not a
@@ -452,7 +485,7 @@ first or wait for approval.
452
485
  While planning, `subagent` stays available but every call is **forced
453
486
  read-only**. Plan mode sets `readOnly: true` on the call in the `tool_call`
454
487
  guard, and `subagent` in turn gives each spawned agent a reader-only `--tools`
455
- list (`read`, `grep`, `find`, `ls`, `web_search`, `web_fetch`). The child
488
+ list (`read`, `grep`, `find`, `ls`, `read_doc`, `web_search`, `web_fetch`). The child
456
489
  process enforces it, so a delegated `explorer`, `researcher`, or even `worker`
457
490
  cannot write, edit, or run a shell — regardless of its prompt or an external
458
491
  agent file that overrides it.
@@ -472,24 +505,54 @@ agent file that overrides it.
472
505
 
473
506
  ## Review screen
474
507
 
475
- <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%">
508
+ <img src="../../assets/screens/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%">
476
509
 
477
510
  `exit_plan_mode` opens this screen on the plan file it just wrote. The body is
478
- Pi's Markdown renderer with the active theme, so headings, lists, code, and
479
- links read as they do in the transcript; the checkboxes in `Steps` use the
480
- shared `◼`/`◻` vocabulary. A plan taller than the terminal scrolls under a gauge
481
- that names the section at the top of the viewport and the line range it shows,
482
- and the footer lists only the keys that apply. `Esc` keeps planning, `a` asks
483
- `y/N` and then approves and runs, `r` refines inline, and `w` opens the browser
484
- review below.
511
+ rendered with Pi's own `Markdown` component and `getMarkdownTheme()`, so
512
+ headings, lists, inline code, code blocks, links, and quotes carry the active
513
+ theme's colours; task items get the shared `◼` (done) or `◻` (pending) marker,
514
+ like the bytefly screen and the question dialog. A plan taller than the terminal
515
+ scrolls under a gauge that names the section at the top of the viewport and the
516
+ line range it shows, and the footer lists only the keys that apply.
517
+
518
+ - **Actions.** The footer colours the primary actions (`a approve & run` in
519
+ `success`, `r refine` in `accent`). `a` asks `y/N` in the footer, then ends
520
+ plan mode, restores write access, and seeds `todo`; `y` or `Enter` approves and
521
+ `n`, `q`, or `Esc` goes back to reading. `r` keeps the plan mounted and opens
522
+ an inline editor beneath it (`Enter` submits, an empty buffer does nothing,
523
+ `Esc` returns to the plan); `Esc` keeps planning and tells the model the plan
524
+ was not approved; `x` leaves plan mode without approving and keeps the plan
525
+ file; `w` opens the browser review.
526
+ - **Sections.** The screen splits the body at its headings, names the section you
527
+ are in, and jumps between sections with `n`/`p`; the scroll row names the
528
+ current section when the plan has more than one.
529
+ - **What changed since the last review.** After a refinement, the screen names
530
+ how many sections changed, marks the current one in the scroll row, and the
531
+ browser outline dots each changed section, so a re-review does not mean
532
+ re-reading the whole plan. The baseline is one digest per heading section of
533
+ what the previous review showed, kept as a session entry, so a resumed session
534
+ or a `/tree` restore marks only what changed on its own branch.
535
+ - **Scrolling.** `↑`/`↓` or `j`/`k`, `space`/`b` or `PgUp`/`PgDn`, `d`/`u`,
536
+ `g`/`G`, and the mouse wheel. When the plan overflows, a gauge row above the
537
+ hints shows the scroll position, the percent, and the visible line range (the
538
+ range drops first on a narrow terminal); the gauge comes from the design
539
+ system's `gaugeBar` and stays visible while the refine editor is open, since the
540
+ body above is still clipped.
541
+ - **Narrow widths.** Hints are dropped whole as the terminal narrows, and each
542
+ footer leads with its safe exit (`Esc keep plan` in the review, `Esc back to
543
+ plan` in the inline editor) so the exit survives even when only one hint fits.
485
544
 
486
545
  ## Browser review
487
546
 
488
- <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 Share action, a side panel with step progress and a section outline, and the plan as one measured column with a comment rail holding the owner's own comment and a teammate's @priya comment" width="100%">
547
+ <img src="../../assets/screens/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 Share action, a side panel with step progress and a section outline, and the plan as one measured column with a comment rail holding the owner's own comment and a teammate's @priya comment" width="100%">
489
548
 
490
549
  Press `w` on the review screen — or run `/plan review [path]` — to open the plan
491
- in the default browser on `127.0.0.1`. The page is a reading-first review
492
- surface: a sticky header carrying the plan's H1 (falling back to the file-name
550
+ in the default browser on `127.0.0.1`. Its tab reads `plan review · <the plan's
551
+ title>` — the page's name as the header's lockup says it, then the subject, the shape
552
+ `lib/web/markup.ts`'s `tabTitle` gives every served page — rendered with the document
553
+ rather than set by a script, so a strip of tabs names the plan before the page has
554
+ finished loading. The page is a
555
+ reading-first review surface: a sticky header carrying the plan's H1 (falling back to the file-name
493
556
  slug), a live status chip (connection, comment count, and unsaved changes), the
494
557
  theme toggle, and the review actions, over a hairline reading-progress bar. A
495
558
  sticky side panel shows the plan's `Steps` section progress and comment count above a
@@ -500,7 +563,7 @@ text first to quote it. **Save feedback** writes the comments into the plan
500
563
  file and returns them to the model.
501
564
 
502
565
  - **Shared theme.** The page uses the same Bytefly tokens, base rules, and
503
- syntax-token colors as `file-browse` (`lib/web/`), follows the system light/dark
566
+ syntax-token colours as `file-browse` (`lib/web/`), follows the system light/dark
504
567
  preference until the header toggle sets an explicit choice, and remembers that
505
568
  choice under the shared `pi-web-theme` key.
506
569
  - **One content column, one rail.** The document is GitHub's Markdown content
@@ -556,10 +619,11 @@ file and returns them to the model.
556
619
  - **Highlighted code.** Fenced code is tokenized by the shared
557
620
  `lib/highlight.ts`; the client copies only text and `tok-*` spans through
558
621
  `DOMParser`, so a code line is never parsed as markup.
559
-
560
622
  - **The file is the store.** Each comment is written as
561
623
  `> [!REVIEW] <comment>` on the line after the block it anchors to, tagged with
562
- its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`). Saving is
624
+ its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`), and
625
+ marked `> [!REVIEW moved]` when a refinement carried it off the text it was
626
+ written on. Saving is
563
627
  idempotent per writer: that writer's previous markers are replaced and everyone
564
628
  else's are kept, so two people reviewing the same plan cannot erase each other.
565
629
  Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
@@ -568,10 +632,18 @@ file and returns them to the model.
568
632
  - **A revision keeps the review.** `write_plan` passes the file it is replacing
569
633
  through `carryFeedback`, which re-attaches each comment by the strongest evidence
570
634
  that survived: the anchored block's text, per occurrence, so a plan that repeats
571
- a step keeps the comments apart; then the same section at the same position, so a
572
- reworded step keeps its note; then the end of that section. A comment whose
573
- section is gone too moves to the end of the file, where the page shows it as
574
- unattached, rather than disappearing.
635
+ a step keeps the comments apart; then the block whose wording still overlaps it —
636
+ half the longer block's words — so a reworded step keeps its note, and a step
637
+ that shifted down under an inserted one keeps it too; then the gap it left
638
+ between the steps that survived; then the last block that section still has. A
639
+ comment whose section is gone too moves to the plan's last block rather than
640
+ disappearing, and every one of those placements lands beside a block, never after
641
+ a blank line, so the page keeps the thread under its block. Only a plan with no
642
+ block at all leaves a comment in the unattached list. Everything past the first
643
+ step is a guess, so a placement that is not the anchored text itself is marked:
644
+ the card carries a `moved` chip whose tooltip says the block changed and the
645
+ note was placed here, and the marker records it in the file, so the fact
646
+ survives a reload, a hand-off, and a save by its own author.
575
647
  - **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
576
648
  source-line ranges; a comment is placed after the whole anchored block, so a
577
649
  fenced code block is never split. A save re-checks the file against what the
@@ -594,20 +666,7 @@ file and returns them to the model.
594
666
  so refreshing or reloading a conflicted page does not end the review. A
595
667
  reviewer's tab opening or closing is not the review's lifetime.
596
668
 
597
- | Route | Result |
598
- |---|---|
599
- | `GET /?token=…` | the review page (a JSON data island plus `/theme.js` and `/client.js`) |
600
- | `GET /theme.js` | the shared pre-paint theme bootstrap |
601
- | `GET /client.js` | the first-party client script |
602
- | `GET /events?token=…` | Server-Sent Events stream: a `comment`, `plan`, or `share` frame when something changed, `closed` when the review ends, `revoked` when sharing stops, and a dropped stream to detect a closed tab |
603
- | `POST /api/comment?token=…` | reviewer only: write their own comments into the plan, replacing only their own; never ends the review |
604
- | `POST /api/feedback?token=…` | owner only: write the owner's comments and return to the model |
605
- | `POST /api/approve?token=…` | owner only: save, then approve |
606
- | `POST /api/cancel?token=…` | owner only: end the session without saving |
607
- | `POST /api/share?token=…` | owner only: mint the first reviewer link and its QR |
608
- | `POST /api/share/link?token=…` | owner only: mint one more reviewer link, so each teammate holds their own |
609
- | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
610
- | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
669
+ The routes it serves are in [Routes](#routes).
611
670
 
612
671
  ## Shared review
613
672
 
@@ -672,6 +731,12 @@ both.
672
731
  Git Bash (`bash`), which must be installed for any shell command to run.
673
732
  - The read-only guarantee is a guard rail against accidental writes, not a
674
733
  security boundary (see [Security](#security)).
734
+ - **A carried comment's new home is a guess, and only the page says so.** A note a
735
+ refinement could not pin to surviving text is placed by wording overlap, by the
736
+ gap it left, or at the plan's last block; the `moved` chip is what tells a
737
+ reviewer that. A reader of the plan file alone sees `> [!REVIEW moved]`, and the
738
+ terminal review shows the comment as a quote with no chip, so the marker is the
739
+ fact and the chip is one reader's way of showing it.
675
740
  - The plans directory is the only index. `/plan list` walks it on every open and
676
741
  keeps no registry, so it sees an external edit or a hand-copied plan
677
742
  immediately; it lists a file over the 256 KiB plan limit without reading it,
@@ -716,9 +781,9 @@ both.
716
781
 
717
782
  | Integration point | How |
718
783
  |---|---|
719
- | 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`. |
720
- | 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. |
721
- | 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 given the same objective, and an `active`/`achieved` goal is left alone — no path asks first. 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. |
784
+ | Tools | `enter_plan_mode` (active; `readOnlyHint: true`), `write_plan` (`defaultActive: false`; `readOnlyHint: false`, `destructiveHint: true`), `exit_plan_mode` (`defaultActive: false`; `readOnlyHint: true`), `cancel_plan_mode` (`defaultActive: false`; `readOnlyHint: true`), `set_plan_steps` (active; `readOnlyHint: false`, `idempotentHint: true`). All five are `executionMode: "sequential"` and closed-world, and the read-only policy default-denies `set_plan_steps` while planning. Each returns typed `details` (`EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`), and `ExitPlanModeDetails` carries the review's `annotations` when a browser approval or save supplied comments; no `outputSchema`/`structuredContent`. |
785
+ | Commands and flags | `/plan` (toggle, or enter and send a task), `/plan list` (plans index), `/plan review [path]` (browser review), `/plan share [stop]` (reviewer link), `/plan cancel` (leave without approving), `/skill:init` (skill-backed `AGENTS.md` bootstrap), `--plan` launch flag, `Ctrl+Alt+P` shortcut. |
786
+ | 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 })` when the `goal` tool is registered: a `planning` goal is promoted to the plan's `## Goal`, a session with none is given the same objective, and an `active`/`achieved` goal is left alone — no path asks first. 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. |
722
787
  | 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`. |
723
788
  | 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. |
724
789
  | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
@@ -861,43 +926,96 @@ both.
861
926
  | File | Responsibility |
862
927
  |---|---|
863
928
  | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
864
- | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`. |
929
+ | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`. |
865
930
  | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
866
931
  | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
867
- | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, atomic `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
932
+ | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
933
+ | `plan-files.ts` | How a plan file reaches the disk: `writeAtomic` (a dotfile temp, then a rename, so a crash cannot truncate the plan a review is reading) and `keepRevision`, which keeps the text a refinement replaces beside it, numbered, up to the newest five. |
868
934
  | `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. |
869
- | `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. |
935
+ | `steps.ts` | Plan-document structure: the outline, step extraction and the `Steps`-section scope, the plan's own goal (`goalSection`), and the checkbox edit. |
936
+ | `sections.ts` | The one fence-aware heading scan: `extractSections`, `sectionKeys` (the key that tells two identically titled sections apart), and the `HEADING` / `FENCE` / `normalizeHeadingText` vocabulary `blocks.ts` reads a heading through. |
870
937
  | `format.ts` | Pure result/approval text: the plan preview, the step-cap diagnosis, step labels, the merge summary, and the verification note. |
871
- | `plan-text.ts` | The plan text a review shows and compares: `sanitizePlanText`, the one normalization both sides use, the per-section `sectionDigests`, and `changedSections`. |
938
+ | `plan-text.ts` | The plan text a review shows and compares: `sanitizePlanText`, the one normalization both sides use, the per-section `sectionDigests`, and `changedSections`, which hands each section back with its flag. |
872
939
  | `runtime.ts` | Enabled state, tool gating, persistence (mode and review baseline), control-tool identity, footer status. |
873
- | `tools.ts` | Registration entry: composes the four tool registrations in the order the session sees them. |
874
- | `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). |
940
+ | `tools.ts` | Registration entry: composes the five tool registrations in the order the session sees them. |
941
+ | `tools/` | One module per tool (`enter.ts`, `write.ts`, `exit.ts`, `cancel.ts`, `steps.ts`), plus `todo-seed.ts` (the two nested `todo` writes) and `nested.ts` (reading a nested result). |
875
942
  | `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. |
876
- | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, and `/plan share [stop]`. |
943
+ | `review-prompt.ts` | Pure rendering and parsing for the compact remote review: one action table behind both the prompt and the reply parser. |
944
+ | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, `/plan list`, `/plan share [stop]`, and `/plan cancel`. |
877
945
  | `tui.ts` | `PlanViewComponent` (sectioned Markdown body, changed-section summary and marker, section jump, scroll gauge, the approve `y/N` question, approve/refine/browser/keep). |
878
- | `plan-index.ts` | `/plan list`: the notice text and the docked index screen (title, step progress, age, focused path; Enter reviews). |
879
- | `blocks.ts` | Source-line block model (fence-aware headings, paragraphs, list items, code, quotes, tables, rules, blanks). |
880
- | `feedback.ts` | Comment codec: authored markers, `stripFeedback`, `parseFeedback`, `formatFeedback` (writer-scoped), `displayFeedback`, `carryFeedback`, `resolveAnchor`, `feedbackSummary`, and payload validation. |
881
- | `web.ts` | `createPlanWebReview`: the review session (per-token roles, the change stream, the plan-file poll, the body cap, settle-after-flush, abort) over the shared bind, writer, and close. |
946
+ | `plan-index.ts` | `/plan list`: the notice text and the docked index screen (store size and oldest plan, title, step progress, age, focused path; Enter reviews). |
947
+ | `blocks.ts` | Source-line block model (fence-aware headings, paragraphs, list items, code, quotes, tables, rules, blanks), and `contentBlockAt`: the block with content a comment beside blank space belongs to. |
948
+ | `feedback.ts` | Comment codec: authored markers, `stripFeedback`, `readFeedback`/`parseFeedback` (body, blocks, and comments out of one parse), `formatFeedback` (writer-scoped), `displayFeedback`, `carryFeedback`, `feedbackSummary`, and payload validation. |
949
+ | `web.ts` | `createPlanWebReview`: the review session (per-token roles, the change stream, the plan-file poll, the body cap, settle-after-flush, abort) over `lib/http`'s one loopback server. |
882
950
  | `web-tokens.ts` | The token table: the role each token names, the collision rule that keeps two roles off one token, and the names the other live links have bound. |
883
951
  | `web-streams.ts` | The event streams: who is listening, what a frame is addressed to, and the closed-tab grace that ends the review when the owner's page goes away. |
884
952
  | `web-router.ts` | Pure request handler: token role, `Host` allowlist, origin, and method checks through one POST-and-role guard, then the page, comment, owner-save, approve, cancel, and the four share routes. |
885
953
  | `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation, share and name dialogs), its data island, and inline CSS. |
886
954
  | `web-share.ts` | Pure sharing rules: the reachable LAN addresses from an injected snapshot, one tokenized URL, the link table (mint, find, revoke, list, state) with its cap, and the reasons a share is unavailable. |
887
- | `web-share-session.ts` | The shared listener's lifecycle: its bound address, the link table over the session's tokens, `ShareState`, and invite/revoke/stop/close, injected with the request handler, role map, and frame hooks. |
888
- | `web-client.ts` | The `/client.js` source: state and wiring (comment rail, save/approve/back, keyboard nav, the stream's lifetime) over the three fragments below. |
955
+ | `web-share-session.ts` | The shared listener: its bound address, the link table over the session's tokens, `ShareState`, and invite/revoke/stop/close, injected with the request handler, role map, and frame hooks. |
956
+ | `web-keys.ts` | The client's keyboard as typed source (`WEB_KEYS_JS`): `handleKey` — Esc peels the composer before closing, a field and the shortcut sheet own their keys, and `s` is the write the page offers — and `rememberSelection`, which keeps the text a reader selected as the next comment's quote. |
957
+ | `web-settle.ts` | The client's settlement as typed source (`WEB_SETTLE_JS`): `submitAnnotations` — one POST shape whose 409 disables the writes, offers a reload and says the server's own sentence — and the four ways a review ends, of which only the owner's save, approve and close settle it. |
958
+ | `web-plan.ts` | The client's frame as typed source (`WEB_PLAN_JS`, `renderPlanFrame`): it draws every block and its comment rail, then puts the reader back — focus only when it was inside the plan, the active block in place rather than by scrolling. Reads arrive as getters, mutations leave as callbacks, so the bundle keeps every variable. |
959
+ | `web-composer.ts` | The client's composer lifecycle as typed source (`WEB_COMPOSER_JS`): `closeComposer` (over one `ComposerDeps` the client builds where the state is), `reopenComposer` for the draft a reload interrupted, and `commentOnActive`, which returns the index so the client keeps its one `activeIndex`. |
960
+ | `web-draft.ts` | The client's draft guard as typed source (`WEB_DRAFT_JS`): `draftTextarea` answers the open composer's textarea when it holds text, and `guardDraft` refuses the ways off the page, names the reason once, and puts the cursor back in it. |
961
+ | `web-blocks.ts` | The client's block navigation as typed source (`WEB_BLOCKS_JS`): `blockElements`, `setActive` and `moveActive` — which return the index they computed, so the client keeps the one `activeIndex` — and `blockAtLine`, which needs both the rendered block and its island entry. |
962
+ | `web-client.ts` | The `/client.js` source: the composition root, and nothing else by decision — the element handles, the state each rule is handed, one zero-argument adapter per rule, the listeners that attach them, the `EventSource` that keeps a tab's presence known, and the boot order. Every rule it used to hold lives in the `web-*.ts` modules below, where it can be typed and driven. |
889
963
  | `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
890
964
  | `web-collab.ts` | The client's collaboration half: the link list with copy and per-link revoke, the reviewer's name, and the island refetch a stream frame triggers. |
891
965
  | `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
892
966
  | `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, and reading progress (interpolated into `web-client.ts`). |
967
+ | `web-outline.ts` | The client's outline as typed source (`WEB_OUTLINE_JS`): `sectionFor` — the last heading at or before a line — `renderOutline`, which builds the list and hands back the counters it made, `observeSections`, which watches the blocks and returns the one watcher the client keeps, and `renderFacts`, which paints the step progress and counts each comment under its heading. |
893
968
  | `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
894
969
  | [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform default-browser opener (shared with `file-browse`). |
895
970
  | [`../../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. |
896
971
  | [`../../lib/shell.ts`](../../lib/shell.ts) | The shell-escape rule the read-only git guard shares with the worktree isolation guard. |
897
972
  | [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer (shared with `file-browse`). |
898
973
  | [`../../lib/qr.ts`](../../lib/qr.ts) | QR matrix, terminal half-blocks, and inline SVG for the share link (shared with `wechat`). |
899
- | [`../../lib/http/`](../../lib/http/) | The bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
974
+ | [`../../lib/http/`](../../lib/http/) | The one loopback server entry, the bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
900
975
  | [`../../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. |
901
976
  | [`../../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. |
902
977
  | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |
903
978
  | `../../lib/tui/chrome.ts` | Shared screen header, viewport-row helper, and styled key-hint footer. |
979
+
980
+ ## Testing
981
+
982
+ ```bash
983
+ bun test extensions/plan
984
+ ```
985
+
986
+ Twelve co-located suites, declared in `test/suite-manifest.ts`:
987
+
988
+ - `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
989
+ writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
990
+ step extraction.
991
+ - `feedback.test.ts` — the comment codec: authored markers, stripping and
992
+ parsing, who a reviewer's write is signed by, carrying feedback onto a
993
+ revision, and displaying stored comments.
994
+ - `tools.test.ts` — registration and all five tools, the approval merge into
995
+ `todo`, the session goal, and the call/result renderings.
996
+ - `review-prompt.test.ts` — the compact remote prompt and its reply parser
997
+ against one action table.
998
+ - `web.test.ts` — the browser review: the comment codec, the router, the page
999
+ roles and page shell, the client script, the stream, the plan-file poll, the
1000
+ share rules, and a real loopback session.
1001
+ - `web-behaviour.test.ts` — the served review, driven in a real browser through
1002
+ the router: the blocks and the plan's name, a block's comment control opening the
1003
+ composer with Escape peeling it before the review, a stored comment in its block's
1004
+ rail and the header's count, and the outline jumping to the section it names. It
1005
+ skips where no browser is installed, naming `PI_CHROME`.
1006
+ - `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
1007
+ its cap, and the reasons a share is unavailable.
1008
+ - `commands.test.ts` — `/plan review`, `/plan list`, `/plan share`, and the one
1009
+ review gate shared by `exit_plan_mode` and the browser, including the abort and
1010
+ baseline rules.
1011
+ - `runtime.test.ts` — tool gating, persistence and status, control-tool identity,
1012
+ the review baseline, branch restore, the read-only git allowlist, and the
1013
+ path-argument scan.
1014
+ - `tui.test.ts` — the review screen at fixed widths: Markdown body, scrolling,
1015
+ section navigation, the gauge, footer hints, the approve question, the inline
1016
+ refine editor, and the changed-section markers.
1017
+ - `sections.test.ts` — the fence-aware scan: heading shape, fences, the preamble,
1018
+ source lines and levels, and the occurrence key.
1019
+ - `extension.test.ts` — the Pi wiring: registration, the entry request, the
1020
+ session lifecycle, the tool-call guard, injected context, the command, and the
1021
+ shortcut.