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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/README.md +52 -44
  2. package/README.zh-CN.md +46 -41
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +344 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +446 -404
  6. package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
  7. package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
  8. package/docs/extensions/file-browse.md +554 -0
  9. package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
  10. package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
  11. package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
  12. package/{extensions/job/README.md → docs/extensions/job.md} +61 -50
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +390 -317
  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 +285 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +226 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +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} +258 -162
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
  25. package/extensions/board/commands.ts +87 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +72 -0
  28. package/extensions/board/index.ts +40 -0
  29. package/extensions/board/input.ts +54 -0
  30. package/extensions/board/join.ts +42 -0
  31. package/extensions/board/page.ts +312 -0
  32. package/extensions/board/result.ts +99 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +75 -0
  35. package/extensions/board/seat.ts +29 -0
  36. package/extensions/board/snapshot.ts +41 -0
  37. package/extensions/board/tools.ts +279 -0
  38. package/extensions/board/transport/router.ts +172 -0
  39. package/extensions/board/transport/server.ts +149 -0
  40. package/extensions/board/types.ts +54 -0
  41. package/extensions/board/view/actions.ts +306 -0
  42. package/extensions/board/view/client.ts +41 -0
  43. package/extensions/board/view/keys.ts +121 -0
  44. package/extensions/board/view/live.ts +397 -0
  45. package/extensions/board/view/page.ts +158 -0
  46. package/extensions/board/view/render.ts +496 -0
  47. package/extensions/board/view/styles.ts +211 -0
  48. package/extensions/board/view/team.ts +305 -0
  49. package/extensions/board/writes.ts +99 -0
  50. package/extensions/bytefly/panel/commands.ts +40 -7
  51. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  52. package/extensions/bytefly/panel/settings.ts +3 -20
  53. package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
  54. package/extensions/bytefly/picker/discovery.ts +0 -2
  55. package/extensions/bytefly/picker/types.ts +0 -2
  56. package/extensions/bytefly/update/check.ts +39 -0
  57. package/extensions/bytefly/update/format.ts +15 -0
  58. package/extensions/bytefly/update/register.ts +73 -34
  59. package/extensions/bytefly/update/state.ts +6 -3
  60. package/extensions/context/tui.ts +1 -1
  61. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  62. package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
  63. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  64. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  65. package/extensions/file-browse/index.ts +4 -4
  66. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  67. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -15
  68. package/extensions/file-browse/transport/server.ts +110 -0
  69. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  70. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  71. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  72. package/extensions/file-browse/{html.ts → view/html.ts} +14 -12
  73. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  74. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
  75. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
  76. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  77. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  78. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  79. package/extensions/file-read/schema.ts +2 -1
  80. package/extensions/memory/store.ts +12 -5
  81. package/extensions/plan/blocks.ts +23 -2
  82. package/extensions/plan/commands.ts +21 -16
  83. package/extensions/plan/feedback.ts +147 -38
  84. package/extensions/plan/index.ts +6 -4
  85. package/extensions/plan/plan-index.ts +43 -9
  86. package/extensions/plan/plan-text.ts +33 -23
  87. package/extensions/plan/plans.ts +8 -11
  88. package/extensions/plan/policy.ts +20 -12
  89. package/extensions/plan/review.ts +3 -27
  90. package/extensions/plan/runtime.ts +21 -9
  91. package/extensions/plan/sections.ts +112 -0
  92. package/extensions/plan/steps.ts +3 -22
  93. package/extensions/plan/tools/exit.ts +11 -95
  94. package/extensions/plan/tui.ts +5 -34
  95. package/extensions/plan/types.ts +0 -8
  96. package/extensions/plan/web-client.ts +1 -1
  97. package/extensions/plan/web-page.ts +0 -7
  98. package/extensions/plan/web-router.ts +3 -57
  99. package/extensions/plan/web-share-session.ts +24 -30
  100. package/extensions/plan/web-streams.ts +14 -14
  101. package/extensions/plan/web-tokens.ts +2 -2
  102. package/extensions/plan/web.ts +66 -120
  103. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  104. package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
  105. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  106. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  107. package/extensions/rewind/index.ts +3 -3
  108. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  109. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  110. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  111. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  112. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  113. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  114. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  115. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  116. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  117. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  118. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  119. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  120. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  121. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  122. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  123. package/extensions/session-bus/commands.ts +17 -69
  124. package/extensions/session-bus/format.ts +22 -45
  125. package/extensions/session-bus/index.ts +13 -7
  126. package/extensions/session-bus/join-request.ts +40 -0
  127. package/extensions/session-bus/mail-watch.ts +134 -0
  128. package/extensions/session-bus/notice.ts +32 -4
  129. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  130. package/extensions/session-bus/protocol.ts +19 -49
  131. package/extensions/session-bus/resolve.ts +67 -0
  132. package/extensions/session-bus/result.ts +16 -51
  133. package/extensions/session-bus/rows.ts +4 -2
  134. package/extensions/session-bus/runtime.ts +129 -238
  135. package/extensions/session-bus/store.ts +42 -6
  136. package/extensions/session-bus/tools.ts +31 -66
  137. package/extensions/session-bus/tui.ts +4 -4
  138. package/extensions/session-bus/types.ts +13 -13
  139. package/extensions/subagent/agents.ts +9 -8
  140. package/extensions/subagent/invocation.ts +4 -2
  141. package/extensions/team/commands.ts +112 -0
  142. package/extensions/team/dispatch.ts +133 -0
  143. package/extensions/team/format.ts +105 -0
  144. package/extensions/team/index.ts +33 -0
  145. package/extensions/team/schema.ts +29 -0
  146. package/extensions/team/tools.ts +246 -0
  147. package/extensions/team/types.ts +48 -0
  148. package/extensions/web-access/fetch/page.ts +4 -1
  149. package/extensions/web-access/fetch/structured.ts +447 -0
  150. package/extensions/web-access/fetch/tool.ts +2 -1
  151. package/extensions/web-access/search/tool.ts +2 -1
  152. package/extensions/wechat/bridge.ts +76 -92
  153. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  154. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  155. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  156. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  157. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  158. package/extensions/wechat/dialog/channels.ts +136 -0
  159. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  160. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  161. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  162. package/extensions/wechat/index.ts +10 -10
  163. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  164. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  165. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  166. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  167. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  168. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  169. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  170. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  171. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  172. package/extensions/wechat/tools.ts +29 -12
  173. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  174. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  175. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  176. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  177. package/extensions/wechat/transport/outbound.ts +142 -0
  178. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  179. package/extensions/wechat/types.ts +10 -1
  180. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  181. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  182. package/extensions/worktree/guard.ts +3 -5
  183. package/lib/README.md +8 -6
  184. package/lib/board/card.ts +290 -0
  185. package/lib/board/links.ts +117 -0
  186. package/lib/board/page-pointer.ts +256 -0
  187. package/lib/board/round-text.ts +72 -0
  188. package/lib/board/round.ts +89 -0
  189. package/lib/board/rules.ts +304 -0
  190. package/lib/board/session.ts +105 -0
  191. package/lib/board/store.ts +310 -0
  192. package/lib/board/view.ts +150 -0
  193. package/lib/errors.ts +12 -4
  194. package/lib/format.ts +13 -0
  195. package/lib/http/guard.ts +112 -0
  196. package/lib/http/index.ts +16 -5
  197. package/lib/http/serve.ts +234 -0
  198. package/lib/http/server.ts +12 -10
  199. package/lib/http/sink.ts +110 -0
  200. package/lib/http/token.ts +38 -0
  201. package/lib/policy.ts +12 -2
  202. package/lib/requirements.ts +6 -7
  203. package/lib/session-label.ts +64 -0
  204. package/lib/session-roster.ts +130 -0
  205. package/lib/session-tool.ts +184 -0
  206. package/lib/tool-names.ts +25 -2
  207. package/lib/tui/glyphs.ts +2 -0
  208. package/lib/web/README.md +5 -2
  209. package/lib/web/components.ts +5 -0
  210. package/lib/web/runtime.ts +7 -1
  211. package/lib/web/tokens.ts +25 -2
  212. package/package.json +26 -14
  213. package/skills/next/SKILL.md +4 -3
  214. package/skills/project-docs/SKILL.md +133 -0
  215. package/skills/project-docs/references/audit.md +131 -0
  216. package/skills/project-docs/references/changelog.md +105 -0
  217. package/skills/project-docs/references/discovery.md +127 -0
  218. package/skills/project-docs/references/readme.md +80 -0
  219. package/skills/project-docs/references/release.md +84 -0
  220. package/skills/project-docs/references/roadmap.md +93 -0
  221. package/skills/project-docs/scripts/state.ts +316 -0
  222. package/skills/refactor/SKILL.md +100 -125
  223. package/skills/refactor/references/handoff.md +35 -0
  224. package/skills/refactor/references/signals.md +54 -0
  225. package/skills/refactor/scripts/inventory.ts +587 -0
  226. package/extensions/file-browse/README.md +0 -506
  227. package/extensions/file-browse/server.ts +0 -141
  228. package/extensions/plan/split.ts +0 -87
  229. package/extensions/session-bus/README.md +0 -247
  230. package/extensions/session-bus/team.ts +0 -102
  231. package/extensions/wechat/outbound.ts +0 -100
  232. package/lib/plans/sections.ts +0 -172
  233. package/lib/plans/setting.ts +0 -52
  234. package/lib/plans/split.ts +0 -215
  235. package/lib/plans/store.ts +0 -19
  236. package/lib/plans/team.ts +0 -151
  237. package/skills/changelog/SKILL.md +0 -148
  238. package/skills/parallel-dev/SKILL.md +0 -150
  239. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  240. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  241. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  242. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  243. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  244. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  245. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  246. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  247. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  248. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  249. /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. | 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. | Shipped |
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,158 @@ 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.
144
+
145
+ ### `exit_plan_mode`
146
+
147
+ `plan_path` names the file `write_plan` returned; the tool reads it back and
148
+ presents it for approval. A hand-edited or older file whose step list is over the
149
+ cap is refused before the review screen. The user approves, keeps planning, asks
150
+ for a refinement, or exits plan mode without approving; the last keeps the plan
151
+ file for a later review and records no todo items or goal. The review also offers
152
+ **Exit plan mode (discard)**, which leaves plan mode without approving.
153
+
154
+ On approval, plan mode ends, write access returns, and the plan's steps go into
155
+ the todo list:
156
+
157
+ - Steps are the top-level numbered/bulleted items of the plan's `Steps` section,
158
+ and approval records them with the `todo` tool (via `ctx.executeTool`). Steps
159
+ marked `- [x]` (or `[DONE:n]`) seed as completed, nested sub-bullets are notes,
160
+ and an indented line continues its step.
161
+ - The list is a merge, not a replacement: existing items keep their place and
162
+ status, a step whose text is already there is skipped, and the tool's item cap
163
+ defers the rest instead of dropping anything.
164
+ - The plan's `## Verification` section follows as the **last** todo item,
165
+ summarized on one line (`Verify: …`) — a fenced block contributes its
166
+ commands, not its fence lines. It is not a plan step, so it is ticked in the
167
+ todo list rather than with `set_plan_steps`, and it never counts against the
168
+ 50-step cap.
169
+ - A plan without a `Steps` heading keeps the older rule — the shallowest list in
170
+ the document — so prose bullets in a plan that follows the outline never become
171
+ todos, and its verification bullets are already covered.
172
+ - Each seeded item is matched back to its step by text, so ticking a step with
133
173
  `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.
166
174
 
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
- ```
175
+ **Goal-aware.** While planning, the injected context restates a pursued session
176
+ goal as one bullet, so the plan is designed against the objective instead of
177
+ beside it. On approval, a goal the goal workflow left as a draft (`planning`) is
178
+ finalized from the plan's own `## Goal` section — falling back to the title —
179
+ through the `goal` tool, with no extra dialog, because approving the plan *is*
180
+ the decision. A session with no goal at all is given the same objective, also
181
+ without asking. An `active` or `achieved` goal is never rewritten: a final
182
+ objective is the user's. No path spends a model turn.
176
183
 
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:
184
+ ### `cancel_plan_mode`
179
185
 
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"})
187
- ```
186
+ Leaves plan mode without a plan file or a review. Restores write access, records
187
+ no todo items or goal, and keeps any saved plan. Empty schema.
188
188
 
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.
189
+ ### `set_plan_steps`
194
190
 
195
- What the gate enforces, and why each rule is a refusal rather than a warning:
191
+ Keep an approved plan current: `plan_path`, plus `updates`, each a 1-based `step`
192
+ in the order `exit_plan_mode` listed, a `status` of `pending` or `completed`, and
193
+ `expected` — the step's text, so an update whose step number moved is refused
194
+ instead of ticking the wrong line. It is a single all-or-nothing write: a step out
195
+ of range, or an `expected` text that no longer matches the file, refuses every
196
+ entry. Only the checkboxes change, and a reopened step drops its `[DONE:n]`
197
+ marker.
196
198
 
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.
199
+ The todo item approval seeded from a step is ticked with it: the file is written
200
+ first and is authoritative, a renamed item is left alone and reported as
201
+ unmatched, and a failed todo write is reported in the result. The tool refuses
202
+ while plan mode is on — refine the plan with `write_plan` instead.
212
203
 
213
- ## Tools
204
+ ### Transcript
214
205
 
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. |
206
+ The three tool rows reuse the transcript component, and `exit_plan_mode`
207
+ separates the plan body from its header with a blank line; the collapse hint
208
+ binds to `app.tools.expand` (`ctrl+o` by default).
209
+
210
+ ```
211
+ enter_plan_mode requested plan mode
212
+ ✓ Plan mode enabled
213
+ ```
222
214
 
223
- ### Commands and flags
215
+ ```
216
+ write_plan Add rate limiting
217
+ ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
218
+ ```
219
+
220
+ ```
221
+ exit_plan_mode submitted a plan
222
+ ✓ Plan approved
223
+ ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
224
+
225
+ # Add rate limiting
226
+ 1. Add a token bucket …
227
+
228
+ … 12 more lines (ctrl+o to expand)
229
+ ```
230
+
231
+ ## Commands and flags
224
232
 
225
233
  | Path | Who starts it | Confirmation |
226
234
  |---|---|---|
@@ -229,6 +237,7 @@ terminal review is where a split is approved.
229
237
  | `/plan <prompt>` | the user, with the task | immediate |
230
238
  | `/plan list` | the user, browsing the plans directory | immediate |
231
239
  | `/plan review [path]` | the user, reviewing a plan in the browser | immediate |
240
+ | `/plan share [stop]` | the user, handing the review to a teammate | immediate |
232
241
  | `/plan cancel` | the user, leaving plan mode without approving | immediate |
233
242
  | `/skill:init` | the user, creating or updating `AGENTS.md` | enters plan mode; write waits for plan approval |
234
243
  | `Ctrl+Alt+P` | the user | immediate |
@@ -248,13 +257,28 @@ the toggle that works in every terminal (for example Zed's integrated terminal,
248
257
  which does not forward `Ctrl+Alt+P`). The footer shows `≡ plan` while plan mode
249
258
  is on, and `≡ plan · <plan-file>` once the model has written a plan.
250
259
 
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
260
+ `/plan list` walks the plans directory and shows every plan with its title,
261
+ modified time, and step progress, newest first — in the TUI as a docked list
262
+ where `Enter` opens the plan's review, elsewhere as a notice with each plan's
263
+ path. The header carries the store's own footprint for this repository: the bytes
264
+ its plans hold and how old the oldest one is, both derived from the walk that just
265
+ happened. The directory stays the index: no registry file, nothing deletes a plan,
266
+ and one bounded read per plan.
267
+
268
+ `/plan review` is the only subcommand that takes an argument. Bare `/plan review`
269
+ reviews the plan this session wrote, or, in a later session, the newest plan file
270
+ in the plans directory (newest modification time); `/plan review <path>.md` (or a
271
+ path with a separator) reviews that plan. A line that merely starts with the word
272
+ — `review the auth flow` — is still treated as a planning task, so the subcommand
256
273
  never swallows one.
257
274
 
275
+ `/plan share` prints the reviewer link and its QR, and `/plan share stop` revokes
276
+ every link; the page's **Share** button does the same. With no browser review open
277
+ it says so and points at `/plan review`. `/plan cancel` leaves plan mode without
278
+ approving, exactly like `cancel_plan_mode`: write access returns and the plan file
279
+ stays for a later review. Both are exact words — `share the tokens` and `cancel
280
+ that idea` stay tasks — as is `list`.
281
+
258
282
  ### `/skill:init`
259
283
 
260
284
  `/skill:init` loads the packaged `init` skill. The skill enters plan mode itself
@@ -300,51 +324,53 @@ The quality reference is
300
324
  [`skills/init/references/agents-md-quality.md`](../../skills/init/references/agents-md-quality.md).
301
325
  An existing instruction file is reviewed and updated only after approval, a root
302
326
  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.
327
+ file can produce a no-op plan, and an empty repository gets only known facts. It
328
+ never invents commands.
304
329
  The skill needs the plan extension for its review gate. It also ships with the
305
330
  package, so load the whole package (`pi -e .`) or install it; loading only
306
331
  `./extensions/plan` leaves `/skill:init` unavailable.
307
332
 
333
+ ## Routes
334
+
335
+ The browser review serves these routes on its loopback origin; `…` is the
336
+ session token, and the owner's token is the one that opened the page.
337
+
338
+ | Route | Result |
339
+ |---|---|
340
+ | `GET /?token=…` | the review page (a JSON data island plus `/theme.js` and `/client.js`) |
341
+ | `GET /theme.js` | the shared pre-paint theme bootstrap |
342
+ | `GET /client.js` | the first-party client script |
343
+ | `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 |
344
+ | `POST /api/comment?token=…` | reviewer only: write their own comments into the plan, replacing only their own; never ends the review |
345
+ | `POST /api/feedback?token=…` | owner only: write the owner's comments and return to the model |
346
+ | `POST /api/approve?token=…` | owner only: save, then approve |
347
+ | `POST /api/cancel?token=…` | owner only: end the session without saving |
348
+ | `POST /api/share?token=…` | owner only: mint the first reviewer link and its QR |
349
+ | `POST /api/share/link?token=…` | owner only: mint one more reviewer link, so each teammate holds their own |
350
+ | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
351
+ | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
352
+
308
353
  ## Behaviour by mode
309
354
 
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.
355
+ The review screen is TUI-only: `exit_plan_mode` opens `PlanViewComponent`, and `w`
356
+ on it opens the browser review. RPC and other non-interactive runs get no screen.
357
+
358
+ Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt instead: on a
359
+ remote turn the plan file is sent first, then a compact prompt carrying the
360
+ title, step count, first steps, mode status, and three actions — `1` approve,
361
+ `2` keep planning, `3` exit. If the upload fails, the plan's text is sent
362
+ instead. Any other reply is a refinement, so "select Refine, then type" is gone.
363
+ A WeChat turn takes the same dialog path and is answered over
364
+ WeChat, even though the session is in TUI mode: the choice comes from
365
+ `askHuman(ctx, { custom, dialogs, body })` in `lib/interaction.ts`, not from
366
+ `ctx.mode`. Without any UI the tools fail with an actionable message instead of
367
+ deciding for the user.
368
+
369
+ `/plan list` is a docked list screen in the TUI and a notice with each plan's
370
+ path everywhere else; both name the store's size and its oldest plan. The dock is
371
+ the list's alone while it is open — the goal and todo rails hide, as they do for
372
+ the other list screens — and the screen counts its own frame, so a full list keeps
373
+ its closing rule on a short terminal.
348
374
 
349
375
  Browser review is a local-turn feature: the server binds `127.0.0.1`, so a remote
350
376
  (WeChat) turn cannot reach it and `/plan review` there refuses with a notice
@@ -356,49 +382,35 @@ Aborting the turn closes a browser review. `exit_plan_mode` forwards the call's
356
382
  abort signal to the review server, so an aborted turn returns "not approved"
357
383
  and stops listening instead of waiting on a page nobody is watching.
358
384
 
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
385
  ## Security
387
386
 
388
387
  Plan mode is a **guard rail, not a sandbox**: extensions run with Pi's OS
389
388
  permissions, so it prevents accidental writes while planning, not a hostile
390
389
  command. The read-only guarantee is layered:
391
390
 
391
+ - **Tool set at entry.** Tools that are not read-only are removed from the active
392
+ set or blocked. `write` and `edit` are hidden, raw shell is limited to read-only
393
+ git commands through `bash` (chained with `&&`/`;`), `powershell` is blocked
394
+ outright, and other mutating tools (worktree mutations, MCP tools) are blocked
395
+ until you exit plan mode. `subagent` stays active but is forced read-only. The
396
+ only permitted write is the plan control tool `write_plan`, which can only
397
+ create or overwrite markdown plan files directly inside the plans directory —
398
+ never dotfiles and never through a symlink.
392
399
  - **Capability policy.** A tool is allowed only when it is a known structured
393
400
  reader or declares the MCP `readOnlyHint`; `write`, `edit`, and unknown
394
- mutators are denied.
401
+ mutators are denied. Classification defaults to *deny* and is driven by the
402
+ shared [`lib/policy.ts`](../../lib/policy.ts), so unknown tools are safe by
403
+ default and the MCP `readOnlyHint` is the only thing that opens one up.
395
404
  - **Path backstop.** A `readOnlyHint` is only a claim, so a tool that takes a
396
405
  file path is refused unless it is a known plan-safe reader. `path-guard.ts`
397
406
  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.
407
+ - **Control-tool identity.** The `tool_call` guard exempts plan's own tools —
408
+ `enter_plan_mode`, `cancel_plan_mode`, `write_plan`, and `exit_plan_mode` — only
409
+ when the registered tool is this extension's, matched by source path, so a
410
+ same-named tool from another extension cannot borrow the exemption.
411
+ `enter_plan_mode` is exempted so a call already in flight when plan mode came on
412
+ is answered with "Already in plan mode" instead of being refused as not
413
+ read-only; the policy still denies it, so it stays hidden while planning.
402
414
  - **Contained writes.** `write_plan` only writes markdown non-dotfiles whose
403
415
  real parent resolves inside the plans directory, and every plan write —
404
416
  create, refine, `set_plan_steps`, browser feedback — goes through a hidden
@@ -428,9 +440,10 @@ conservative allowlist in `git-bash.ts`. `powershell` is not allowed at all, so
428
440
  plan mode uses Git Bash on native Windows: the same read-only git commands work
429
441
  there, and a backslash before an ordinary character is accepted as a Windows
430
442
  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`).
443
+ Everything else uses the structured readers — `read`, `grep`, `find`, `ls`, and
444
+ `read_doc` for a PDF, Word, OpenDocument, RTF, or Excel file — and the other
445
+ read-only tools already available (`web_search`, `web_fetch`,
446
+ `ask_user_question`, `todo`, `goal`).
434
447
 
435
448
  ```bash
436
449
  git status && git diff --stat # allowed: two read-only git commands
@@ -441,6 +454,10 @@ git status | sh # blocked: pipes are never allowed
441
454
  git diff > out.patch # blocked: redirection is a write
442
455
  ```
443
456
 
457
+ A refused shell command explains the fix: the working directory is already the
458
+ project root (so `cd` is unnecessary), and files belong to the `read`, `grep`,
459
+ `find`, and `ls` tools.
460
+
444
461
  A string parser can never be the boundary, so capability gating replaced the
445
462
  earlier segment-based command allowlist. The git shell guard is a narrow,
446
463
  deliberate exception — an allowlist over read-only git command shapes, not a
@@ -452,7 +469,7 @@ first or wait for approval.
452
469
  While planning, `subagent` stays available but every call is **forced
453
470
  read-only**. Plan mode sets `readOnly: true` on the call in the `tool_call`
454
471
  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
472
+ list (`read`, `grep`, `find`, `ls`, `read_doc`, `web_search`, `web_fetch`). The child
456
473
  process enforces it, so a delegated `explorer`, `researcher`, or even `worker`
457
474
  cannot write, edit, or run a shell — regardless of its prompt or an external
458
475
  agent file that overrides it.
@@ -472,20 +489,46 @@ agent file that overrides it.
472
489
 
473
490
  ## Review screen
474
491
 
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%">
492
+ <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
493
 
477
494
  `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.
495
+ rendered with Pi's own `Markdown` component and `getMarkdownTheme()`, so
496
+ headings, lists, inline code, code blocks, links, and quotes carry the active
497
+ theme's colours; task items get the shared `◼` (done) or `◻` (pending) marker,
498
+ like the bytefly screen and the question dialog. A plan taller than the terminal
499
+ scrolls under a gauge that names the section at the top of the viewport and the
500
+ line range it shows, and the footer lists only the keys that apply.
501
+
502
+ - **Actions.** The footer colours the primary actions (`a approve & run` in
503
+ `success`, `r refine` in `accent`). `a` asks `y/N` in the footer, then ends
504
+ plan mode, restores write access, and seeds `todo`; `y` or `Enter` approves and
505
+ `n`, `q`, or `Esc` goes back to reading. `r` keeps the plan mounted and opens
506
+ an inline editor beneath it (`Enter` submits, an empty buffer does nothing,
507
+ `Esc` returns to the plan); `Esc` keeps planning and tells the model the plan
508
+ was not approved; `x` leaves plan mode without approving and keeps the plan
509
+ file; `w` opens the browser review.
510
+ - **Sections.** The screen splits the body at its headings, names the section you
511
+ are in, and jumps between sections with `n`/`p`; the scroll row names the
512
+ current section when the plan has more than one.
513
+ - **What changed since the last review.** After a refinement, the screen names
514
+ how many sections changed, marks the current one in the scroll row, and the
515
+ browser outline dots each changed section, so a re-review does not mean
516
+ re-reading the whole plan. The baseline is one digest per heading section of
517
+ what the previous review showed, kept as a session entry, so a resumed session
518
+ or a `/tree` restore marks only what changed on its own branch.
519
+ - **Scrolling.** `↑`/`↓` or `j`/`k`, `space`/`b` or `PgUp`/`PgDn`, `d`/`u`,
520
+ `g`/`G`, and the mouse wheel. When the plan overflows, a gauge row above the
521
+ hints shows the scroll position, the percent, and the visible line range (the
522
+ range drops first on a narrow terminal); the gauge comes from the design
523
+ system's `gaugeBar` and stays visible while the refine editor is open, since the
524
+ body above is still clipped.
525
+ - **Narrow widths.** Hints are dropped whole as the terminal narrows, and each
526
+ footer leads with its safe exit (`Esc keep plan` in the review, `Esc back to
527
+ plan` in the inline editor) so the exit survives even when only one hint fits.
485
528
 
486
529
  ## Browser review
487
530
 
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%">
531
+ <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
532
 
490
533
  Press `w` on the review screen — or run `/plan review [path]` — to open the plan
491
534
  in the default browser on `127.0.0.1`. The page is a reading-first review
@@ -500,7 +543,7 @@ text first to quote it. **Save feedback** writes the comments into the plan
500
543
  file and returns them to the model.
501
544
 
502
545
  - **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
546
+ syntax-token colours as `file-browse` (`lib/web/`), follows the system light/dark
504
547
  preference until the header toggle sets an explicit choice, and remembers that
505
548
  choice under the shared `pi-web-theme` key.
506
549
  - **One content column, one rail.** The document is GitHub's Markdown content
@@ -556,7 +599,6 @@ file and returns them to the model.
556
599
  - **Highlighted code.** Fenced code is tokenized by the shared
557
600
  `lib/highlight.ts`; the client copies only text and `tok-*` spans through
558
601
  `DOMParser`, so a code line is never parsed as markup.
559
-
560
602
  - **The file is the store.** Each comment is written as
561
603
  `> [!REVIEW] <comment>` on the line after the block it anchors to, tagged with
562
604
  its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`). Saving is
@@ -568,10 +610,14 @@ file and returns them to the model.
568
610
  - **A revision keeps the review.** `write_plan` passes the file it is replacing
569
611
  through `carryFeedback`, which re-attaches each comment by the strongest evidence
570
612
  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.
613
+ a step keeps the comments apart; then the block whose wording still overlaps it —
614
+ half the longer block's words — so a reworded step keeps its note, and a step
615
+ that shifted down under an inserted one keeps it too; then the gap it left
616
+ between the steps that survived; then the last block that section still has. A
617
+ comment whose section is gone too moves to the plan's last block rather than
618
+ disappearing, and every one of those placements lands beside a block, never after
619
+ a blank line, so the page keeps the thread under its block. Only a plan with no
620
+ block at all leaves a comment in the unattached list.
575
621
  - **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
576
622
  source-line ranges; a comment is placed after the whole anchored block, so a
577
623
  fenced code block is never split. A save re-checks the file against what the
@@ -594,20 +640,7 @@ file and returns them to the model.
594
640
  so refreshing or reloading a conflicted page does not end the review. A
595
641
  reviewer's tab opening or closing is not the review's lifetime.
596
642
 
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 |
643
+ The routes it serves are in [Routes](#routes).
611
644
 
612
645
  ## Shared review
613
646
 
@@ -716,9 +749,9 @@ both.
716
749
 
717
750
  | Integration point | How |
718
751
  |---|---|
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. |
752
+ | 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`. |
753
+ | 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. |
754
+ | 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
755
  | 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
756
  | 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
757
  | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
@@ -861,30 +894,32 @@ both.
861
894
  | File | Responsibility |
862
895
  |---|---|
863
896
  | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
864
- | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`. |
897
+ | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`. |
865
898
  | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
866
899
  | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
867
900
  | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, atomic `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
868
901
  | `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. |
902
+ | `steps.ts` | Plan-document structure: the outline, step extraction and the `Steps`-section scope, the plan's own goal (`goalSection`), and the checkbox edit. |
903
+ | `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
904
  | `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`. |
905
+ | `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
906
  | `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). |
907
+ | `tools.ts` | Registration entry: composes the five tool registrations in the order the session sees them. |
908
+ | `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
909
  | `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]`. |
910
+ | `review-prompt.ts` | Pure rendering and parsing for the compact remote review: one action table behind both the prompt and the reply parser. |
911
+ | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, `/plan list`, `/plan share [stop]`, and `/plan cancel`. |
877
912
  | `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. |
913
+ | `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). |
914
+ | `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. |
915
+ | `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. |
916
+ | `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
917
  | `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
918
  | `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
919
  | `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
920
  | `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
921
  | `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. |
922
+ | `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. |
888
923
  | `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. |
889
924
  | `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
890
925
  | `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. |
@@ -896,8 +931,46 @@ both.
896
931
  | [`../../lib/shell.ts`](../../lib/shell.ts) | The shell-escape rule the read-only git guard shares with the worktree isolation guard. |
897
932
  | [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer (shared with `file-browse`). |
898
933
  | [`../../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. |
934
+ | [`../../lib/http/`](../../lib/http/) | The one loopback server entry, the bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
900
935
  | [`../../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
936
  | [`../../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
937
  | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |
903
938
  | `../../lib/tui/chrome.ts` | Shared screen header, viewport-row helper, and styled key-hint footer. |
939
+
940
+ ## Testing
941
+
942
+ ```bash
943
+ bun test extensions/plan
944
+ ```
945
+
946
+ Eleven co-located suites, declared in `test/suite-manifest.ts`:
947
+
948
+ - `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
949
+ writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
950
+ step extraction.
951
+ - `feedback.test.ts` — the comment codec: authored markers, stripping and
952
+ parsing, who a reviewer's write is signed by, carrying feedback onto a
953
+ revision, and displaying stored comments.
954
+ - `tools.test.ts` — registration and all five tools, the approval merge into
955
+ `todo`, the session goal, and the call/result renderings.
956
+ - `review-prompt.test.ts` — the compact remote prompt and its reply parser
957
+ against one action table.
958
+ - `web.test.ts` — the browser review: the comment codec, the router, the page
959
+ roles and page shell, the client script, the stream, the plan-file poll, the
960
+ share rules, and a real loopback session.
961
+ - `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
962
+ its cap, and the reasons a share is unavailable.
963
+ - `commands.test.ts` — `/plan review`, `/plan list`, `/plan share`, and the one
964
+ review gate shared by `exit_plan_mode` and the browser, including the abort and
965
+ baseline rules.
966
+ - `runtime.test.ts` — tool gating, persistence and status, control-tool identity,
967
+ the review baseline, branch restore, the read-only git allowlist, and the
968
+ path-argument scan.
969
+ - `tui.test.ts` — the review screen at fixed widths: Markdown body, scrolling,
970
+ section navigation, the gauge, footer hints, the approve question, the inline
971
+ refine editor, and the changed-section markers.
972
+ - `sections.test.ts` — the fence-aware scan: heading shape, fences, the preamble,
973
+ source lines and levels, and the occurrence key.
974
+ - `extension.test.ts` — the Pi wiring: registration, the entry request, the
975
+ session lifecycle, the tool-call guard, injected context, the command, and the
976
+ shortcut.