@gavin-hu/my-pi-agent 0.20.0 → 0.22.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 (298) hide show
  1. package/README.md +46 -30
  2. package/README.zh-CN.md +40 -25
  3. package/assets/screens/board.png +0 -0
  4. package/assets/screens/chrome.png +0 -0
  5. package/assets/screens/file-browse.png +0 -0
  6. package/assets/screens/plan-review.png +0 -0
  7. package/assets/screens/plan.png +0 -0
  8. package/docs/extensions/README.md +22 -23
  9. package/docs/extensions/board.md +397 -168
  10. package/docs/extensions/bytefly.md +234 -197
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +57 -63
  14. package/docs/extensions/file-open.md +9 -11
  15. package/docs/extensions/file-read.md +12 -14
  16. package/docs/extensions/goal.md +15 -17
  17. package/docs/extensions/job.md +54 -42
  18. package/docs/extensions/memory.md +17 -19
  19. package/docs/extensions/plan.md +197 -140
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +145 -0
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +83 -44
  26. package/docs/extensions/todo.md +18 -18
  27. package/docs/extensions/watch.md +31 -31
  28. package/docs/extensions/web-access.md +2 -4
  29. package/docs/extensions/wechat.md +34 -35
  30. package/docs/extensions/worktree.md +56 -50
  31. package/extensions/board/commands.ts +79 -25
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +17 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +5 -20
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +65 -93
  38. package/extensions/board/result.ts +30 -39
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +49 -11
  41. package/extensions/board/snapshot.ts +8 -6
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +154 -55
  44. package/extensions/board/transport/router.ts +3 -5
  45. package/extensions/board/transport/server.ts +59 -28
  46. package/extensions/board/types.ts +36 -42
  47. package/extensions/board/view/card.ts +229 -0
  48. package/extensions/board/view/client.ts +35 -16
  49. package/extensions/board/view/dialog.ts +169 -6
  50. package/extensions/board/view/empty.ts +47 -0
  51. package/extensions/board/view/keys.ts +13 -2
  52. package/extensions/board/view/live.ts +90 -28
  53. package/extensions/board/view/names.ts +13 -4
  54. package/extensions/board/view/page.ts +38 -13
  55. package/extensions/board/view/render.ts +219 -277
  56. package/extensions/board/view/styles.ts +130 -32
  57. package/extensions/board/view/team.ts +297 -76
  58. package/extensions/bytefly/block/fallback.ts +1 -1
  59. package/extensions/bytefly/doctor/runtime.ts +5 -2
  60. package/extensions/bytefly/footer/collect/label.ts +60 -0
  61. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  62. package/extensions/bytefly/footer/config.ts +7 -8
  63. package/extensions/bytefly/footer/register.ts +33 -4
  64. package/extensions/bytefly/footer/render/footer.ts +18 -4
  65. package/extensions/bytefly/footer/render/key.ts +46 -0
  66. package/extensions/bytefly/footer/render/layout.ts +13 -3
  67. package/extensions/bytefly/footer/render/lines.ts +10 -7
  68. package/extensions/bytefly/footer/render/segments.ts +41 -38
  69. package/extensions/bytefly/footer/render/status.ts +0 -11
  70. package/extensions/bytefly/footer/types.ts +7 -0
  71. package/extensions/bytefly/header/component.ts +4 -20
  72. package/extensions/bytefly/header/config.ts +1 -1
  73. package/extensions/bytefly/index.ts +32 -6
  74. package/extensions/bytefly/mode/editor.ts +115 -0
  75. package/extensions/bytefly/panel/commands.ts +210 -120
  76. package/extensions/bytefly/panel/format.ts +4 -4
  77. package/extensions/bytefly/panel/settings.ts +73 -6
  78. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  79. package/extensions/bytefly/update/cache.ts +1 -1
  80. package/extensions/bytefly/update/register.ts +4 -4
  81. package/extensions/bytefly/working/config.ts +5 -7
  82. package/extensions/bytefly/working/format.ts +10 -13
  83. package/extensions/bytefly/working/register.ts +22 -27
  84. package/extensions/check/commands.ts +3 -2
  85. package/extensions/check/render.ts +10 -1
  86. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  87. package/extensions/context/commands.ts +2 -2
  88. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  89. package/extensions/file-browse/config/config.ts +4 -19
  90. package/extensions/file-browse/index.ts +89 -79
  91. package/extensions/file-browse/live/live.ts +1 -1
  92. package/extensions/file-browse/transport/router.ts +12 -4
  93. package/extensions/file-browse/transport/server.ts +61 -28
  94. package/extensions/file-browse/view/chrome.ts +4 -4
  95. package/extensions/file-browse/view/client.ts +3 -1
  96. package/extensions/file-browse/view/html.ts +73 -40
  97. package/extensions/file-browse/view/live-client.ts +2 -2
  98. package/extensions/file-browse/view/markdown.ts +13 -14
  99. package/extensions/file-browse/view/palette.ts +2 -2
  100. package/extensions/file-browse/view/raw-html.ts +9 -9
  101. package/extensions/file-browse/view/tree.ts +1 -1
  102. package/extensions/file-browse/view/urls.ts +20 -6
  103. package/extensions/file-read/render.ts +4 -4
  104. package/extensions/goal/plan-route.ts +2 -2
  105. package/extensions/goal/tui.ts +12 -2
  106. package/extensions/job/child.ts +527 -0
  107. package/extensions/job/clock.ts +142 -0
  108. package/extensions/job/commands.ts +4 -2
  109. package/extensions/job/config.ts +8 -1
  110. package/extensions/job/format.ts +22 -1
  111. package/extensions/job/log-writer.ts +13 -2
  112. package/extensions/job/registry.ts +63 -7
  113. package/extensions/job/render.ts +34 -0
  114. package/extensions/job/runtime.ts +296 -655
  115. package/extensions/job/session.ts +132 -19
  116. package/extensions/job/store.ts +7 -7
  117. package/extensions/job/tools.ts +21 -9
  118. package/extensions/job/types.ts +47 -0
  119. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
  120. package/extensions/job/waiters.ts +44 -1
  121. package/extensions/memory/commands.ts +4 -2
  122. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  123. package/extensions/plan/commands.ts +12 -9
  124. package/extensions/plan/feedback.ts +62 -16
  125. package/extensions/plan/format.ts +8 -2
  126. package/extensions/plan/git-bash.ts +115 -16
  127. package/extensions/plan/index.ts +58 -51
  128. package/extensions/plan/plan-index.ts +4 -2
  129. package/extensions/plan/plan-text.ts +114 -0
  130. package/extensions/plan/plans.ts +5 -54
  131. package/extensions/plan/policy.ts +8 -10
  132. package/extensions/plan/review-lazy.ts +85 -0
  133. package/extensions/plan/review.ts +2 -2
  134. package/extensions/plan/runtime.ts +61 -41
  135. package/extensions/plan/sections.ts +1 -1
  136. package/extensions/plan/steps.ts +3 -3
  137. package/extensions/plan/todos.ts +1 -1
  138. package/extensions/plan/tools/cancel.ts +27 -62
  139. package/extensions/plan/tools/enter.ts +55 -81
  140. package/extensions/plan/tools/exit.ts +221 -238
  141. package/extensions/plan/tools/refusal.ts +18 -0
  142. package/extensions/plan/tools/steps.ts +75 -92
  143. package/extensions/plan/tools/write.ts +77 -120
  144. package/extensions/plan/tools.ts +188 -30
  145. package/extensions/plan/tui.ts +5 -5
  146. package/extensions/plan/types.ts +48 -5
  147. package/extensions/plan/web-blocks.ts +76 -5
  148. package/extensions/plan/web-chrome.ts +4 -0
  149. package/extensions/plan/web-client.ts +102 -2
  150. package/extensions/plan/web-collab.ts +4 -4
  151. package/extensions/plan/web-comments.ts +39 -8
  152. package/extensions/plan/web-composer.ts +14 -3
  153. package/extensions/plan/web-keys.ts +149 -25
  154. package/extensions/plan/web-lazy.ts +53 -0
  155. package/extensions/plan/web-markdown.ts +10 -68
  156. package/extensions/plan/web-outline.ts +22 -2
  157. package/extensions/plan/web-page.ts +56 -86
  158. package/extensions/plan/web-payload.ts +188 -0
  159. package/extensions/plan/web-plan.ts +23 -13
  160. package/extensions/plan/web-router.ts +18 -9
  161. package/extensions/plan/web-settle.ts +14 -7
  162. package/extensions/plan/web-share-session.ts +24 -55
  163. package/extensions/plan/web-share.ts +19 -37
  164. package/extensions/plan/web-write.ts +112 -0
  165. package/extensions/plan/web.ts +67 -131
  166. package/extensions/question/tools.ts +6 -2
  167. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  168. package/extensions/rewind/commands/commands.ts +6 -3
  169. package/extensions/rewind/state/runtime.ts +1 -1
  170. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  171. package/extensions/rewind/view/review-tui.ts +4 -4
  172. package/extensions/serve/config.ts +39 -0
  173. package/extensions/serve/index.ts +85 -0
  174. package/extensions/serve/registry.ts +260 -0
  175. package/extensions/session-bus/commands.ts +11 -6
  176. package/extensions/session-bus/join-request.ts +17 -9
  177. package/extensions/session-bus/mail-watch.ts +26 -17
  178. package/extensions/session-bus/protocol.ts +15 -3
  179. package/extensions/session-bus/resolve.ts +5 -7
  180. package/extensions/session-bus/result.ts +1 -27
  181. package/extensions/session-bus/runtime.ts +40 -12
  182. package/extensions/session-bus/store.ts +101 -19
  183. package/extensions/session-bus/tools.ts +5 -4
  184. package/extensions/session-bus/types.ts +19 -0
  185. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  186. package/extensions/subagent/agents.ts +1 -1
  187. package/extensions/subagent/commands.ts +4 -2
  188. package/extensions/subagent/format.ts +15 -15
  189. package/extensions/subagent/index.ts +1 -1
  190. package/extensions/subagent/render.ts +3 -3
  191. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  192. package/extensions/team/commands.ts +24 -5
  193. package/extensions/team/dispatch.ts +18 -4
  194. package/extensions/team/format.ts +17 -2
  195. package/extensions/team/index.ts +9 -8
  196. package/extensions/team/schema.ts +6 -5
  197. package/extensions/team/tools.ts +107 -45
  198. package/extensions/team/types.ts +3 -2
  199. package/extensions/todo/commands.ts +4 -2
  200. package/extensions/todo/format.ts +11 -0
  201. package/extensions/todo/render.ts +57 -0
  202. package/extensions/todo/runtime.ts +1 -1
  203. package/extensions/todo/tools.ts +1 -1
  204. package/extensions/todo/{tui.ts → view/todo-screen.ts} +18 -90
  205. package/extensions/todo/widget.ts +47 -0
  206. package/extensions/watch/commands.ts +4 -2
  207. package/extensions/watch/format.ts +36 -7
  208. package/extensions/watch/render.ts +57 -0
  209. package/extensions/watch/runtime.ts +27 -22
  210. package/extensions/watch/tools.ts +4 -4
  211. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  212. package/extensions/web-access/fetch/tool.ts +12 -4
  213. package/extensions/wechat/commands/commands.ts +18 -14
  214. package/extensions/wechat/index.ts +72 -37
  215. package/extensions/wechat/state/credentials.ts +1 -1
  216. package/extensions/wechat/state/state.ts +1 -1
  217. package/extensions/wechat/text/status.ts +112 -0
  218. package/extensions/wechat/types.ts +2 -2
  219. package/extensions/wechat/view/status-screen.ts +4 -103
  220. package/extensions/worktree/commands.ts +11 -9
  221. package/extensions/worktree/enter.ts +2 -2
  222. package/extensions/worktree/exit.ts +2 -2
  223. package/extensions/worktree/index.ts +5 -5
  224. package/extensions/worktree/screen.ts +3 -3
  225. package/extensions/worktree/status.ts +2 -2
  226. package/extensions/worktree/tools.ts +113 -116
  227. package/lib/README.md +28 -13
  228. package/lib/board/actions.ts +111 -0
  229. package/lib/board/artifact.ts +149 -0
  230. package/lib/board/card.ts +163 -52
  231. package/lib/board/config.ts +58 -0
  232. package/lib/board/contract.ts +113 -0
  233. package/lib/board/join.ts +38 -0
  234. package/lib/board/lanes.ts +182 -0
  235. package/lib/board/lead.ts +130 -0
  236. package/lib/board/limits.ts +78 -0
  237. package/lib/board/links.ts +31 -10
  238. package/lib/board/page-pointer.ts +30 -5
  239. package/lib/board/read.ts +83 -0
  240. package/lib/board/reason.ts +102 -0
  241. package/lib/board/round-text.ts +68 -6
  242. package/lib/board/round.ts +31 -15
  243. package/lib/board/rules.ts +151 -307
  244. package/lib/board/seat.ts +35 -0
  245. package/lib/board/session.ts +199 -51
  246. package/lib/board/skipped.ts +22 -0
  247. package/lib/board/steps.ts +487 -0
  248. package/lib/board/store.ts +119 -36
  249. package/lib/board/tone.ts +92 -0
  250. package/lib/board/view.ts +81 -63
  251. package/lib/bus-request.ts +83 -0
  252. package/{extensions/watch → lib}/child.ts +40 -21
  253. package/lib/env.ts +65 -7
  254. package/lib/format.ts +13 -0
  255. package/lib/http/index.ts +17 -11
  256. package/lib/http/mount.ts +142 -0
  257. package/lib/http/port.ts +30 -0
  258. package/lib/http/serve.ts +170 -46
  259. package/lib/http/server.ts +3 -3
  260. package/lib/lazy.ts +45 -0
  261. package/lib/plan-entry.ts +46 -21
  262. package/lib/plans/paths.ts +82 -0
  263. package/lib/policy.ts +9 -6
  264. package/lib/report.ts +3 -2
  265. package/lib/requirements.ts +10 -10
  266. package/lib/session-label.ts +129 -7
  267. package/lib/session-roster.ts +43 -1
  268. package/lib/session-tool.ts +39 -22
  269. package/lib/stray-fields.ts +7 -2
  270. package/lib/tool-names.ts +5 -10
  271. package/lib/tui/README.md +29 -11
  272. package/lib/tui/chrome.ts +16 -15
  273. package/lib/tui/cursor.ts +18 -3
  274. package/lib/tui/glyphs.ts +11 -11
  275. package/lib/tui/render-cache.ts +29 -0
  276. package/lib/tui/tokens.ts +33 -2
  277. package/lib/tui/tool.ts +33 -4
  278. package/lib/web/README.md +21 -0
  279. package/lib/web/client-runtime.ts +27 -1
  280. package/lib/web/components.ts +5 -1
  281. package/lib/web/index.ts +1 -0
  282. package/lib/web/markdown.ts +83 -0
  283. package/lib/web/markup.ts +20 -2
  284. package/lib/web/runtime.ts +9 -10
  285. package/package.json +8 -2
  286. package/skills/init/SKILL.md +5 -5
  287. package/skills/next/SKILL.md +3 -5
  288. package/extensions/board/config.ts +0 -36
  289. package/extensions/board/seat.ts +0 -28
  290. package/lib/tui/index.ts +0 -34
  291. package/skills/project-docs/SKILL.md +0 -133
  292. package/skills/project-docs/references/audit.md +0 -131
  293. package/skills/project-docs/references/changelog.md +0 -105
  294. package/skills/project-docs/references/discovery.md +0 -127
  295. package/skills/project-docs/references/readme.md +0 -80
  296. package/skills/project-docs/references/release.md +0 -84
  297. package/skills/project-docs/references/roadmap.md +0 -93
  298. package/skills/project-docs/scripts/state.ts +0 -316
@@ -8,50 +8,50 @@ executes.
8
8
 
9
9
  ## Capabilities
10
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
11
  ### Planning lifecycle
14
12
 
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 |
13
+ | Capability | How | What you get |
14
+ |---|---|---|
15
+ | Read-only planning | `plan` action `enter`, `/plan`, `Shift+Tab` (bytefly), `--plan` | The session keeps only its readers, so the model investigates and proposes without touching the tree. |
16
+ | Confirmed model entry | `plan` action `enter` | The model asks before it takes write access away, and the user answers first. |
17
+ | 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. |
18
+ | 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. |
19
+ | 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. |
20
+ | One writable artifact | `plan` action `write` | The plan lands as markdown under the agent directory, one store per repository, shared by its worktrees. |
21
+ | Approval gate | `plan` action `exit` | The plan is read back and presented; nothing is written until the user approves it. |
22
+ | 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. |
23
+ | Steps as todos | `plan` action `exit` | 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. |
24
+ | Plan kept current | `plan` action `steps` | Ticking a step rewrites its checkbox in the file and its todo item in one call. |
25
+ | Stale-step protection | `expected` in `plan` action `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. |
26
+ | Cancel without a plan | `plan` action `cancel`, `/plan cancel` | Write access returns, and no todo items or goal are recorded. |
27
+ | 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. |
28
+ | 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. |
29
+ | Instruction-file bootstrap | `/skill:init` | The packaged `init` skill plans and gates a project's `AGENTS.md`, writing only after approval. |
30
+ | Branch-following mode | the `plan-mode` session entry | `/resume` and `/tree` restore the mode and its tool set for the branch they land on. |
33
31
 
34
32
  ### Review
35
33
 
36
34
  | Capability | How | What you get | Status |
37
35
  |---|---|---|---|
38
- | Review screen | `exit_plan_mode` | A scrollable, themed plan with approve, refine, keep, browser, and exit in the footer. | Shipped |
36
+ | Review screen | `plan` action `exit` | A scrollable, themed plan with approve, refine, keep, browser, and exit in the footer. | Shipped |
39
37
  | 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
38
  | 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
39
  | 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
40
  | 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 |
41
+ | Changed sections | the `exit` review screen, the `/plan review` outline | After a refinement, only the sections that changed are marked. | Shipped |
42
+ | Block-level change marks | the `/plan review` page's **Changed** view | After a refinement, each block that is new or edited is marked — an inserted step marks itself and leaves the rest of its section alone — and a section that lost blocks says how much shorter it became. **All** / **Changed** / **Comments** switch what the page shows, `v` cycles them, `n`/`N` walk the blocks that carry a comment, and a printed review is always the whole plan. | Unreleased |
43
+ | Settled comments | the page's `Resolve` / `Reopen` on a note you wrote | The note keeps its place, its quote and its history and is written `> [!REVIEW resolved]`, where a later review and the model read it as closed; the panel counts `2 open · 1 resolved`, and every card names who wrote it. | Unreleased |
44
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 |
45
+ | Remote review | `plan` action `exit` on a WeChat turn | The plan file is sent, then one compact prompt to approve, keep planning, or exit. | Shipped |
46
46
 
47
47
  ### Collaboration
48
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 |
49
+ | Capability | How | What you get |
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. |
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. |
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. |
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. |
55
55
 
56
56
  ## Quickstart
57
57
 
@@ -64,34 +64,43 @@ pi install ./ # install the package for a user
64
64
  ```text
65
65
  /plan # toggle plan mode
66
66
  /plan add rate limiting to the public API # enter plan mode and send the task
67
+ Shift+Tab # toggle too, from bytefly's editor
67
68
  ```
68
69
 
69
- The model calls `enter_plan_mode` before a non-trivial task; `/plan` and
70
- `Ctrl+Alt+P` are the user's own switch.
70
+ The model calls `plan` with action `enter` before a non-trivial task; `/plan` and
71
+ `Shift+Tab` are the user's own switch. Shift+Tab is bytefly's: the editor binds
72
+ it and asks plan through `PLAN_TOGGLE_REQUEST`, so a session that loads `plan`
73
+ without bytefly uses `/plan` (or `--plan`) instead.
71
74
 
72
75
  ## Tools
73
76
 
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:
77
+ One tool with five actions — `enter`, `write`, `exit`, `steps`, `cancel` — because
78
+ the verbs share one domain, the way `board` and `todo` keep theirs in one tool. How
79
+ the `write` and `exit` calls and their results look in the transcript — the call
80
+ line as a headline and the body indented under it:
75
81
 
76
- <img src="../../assets/tools/write_plan.png" alt="The write_plan tool's call and result: the call line and an indented body with the saved plan path and byte count" width="100%">
82
+ <img src="../../assets/tools/plan-write.png" alt="The plan tool's write action: the call line and an indented body with the saved plan path" width="100%">
77
83
 
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%">
84
+ <img src="../../assets/tools/plan-exit.png" alt="The plan tool's exit action: the call line and an indented body with the approval line and the approved plan preview" width="100%">
79
85
 
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.
86
+ The tool is `executionMode: "sequential"`, closed-world, and active in both modes;
87
+ each action refuses outside its own, so plan mode never has to hide it. Its one
88
+ annotation set is the strictest of the five, because `write` overwrites a plan file
89
+ with `plan_path`; the actions that only change the mode are exempted by the guard
90
+ in `index.ts`, the one place that can still tell them apart, and the policy's
91
+ `isAllowed` stays a statement about files rather than about this tool.
83
92
 
84
- | Tool | Active | Purpose |
93
+ | Action | Active | Purpose |
85
94
  |---|---|---|
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. |
95
+ | `enter` | either mode | Ask to enter plan mode; no fields, confirmed with `ctx.ui.confirm`. |
96
+ | `write` | 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. |
97
+ | `exit` | plan mode | Read the plan file (`plan_path`) and present it for approval. |
98
+ | `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. |
99
+ | `cancel` | plan mode | Leave plan mode without a plan file or a review. No fields. |
91
100
 
92
- ### `enter_plan_mode`
101
+ ### `enter`
93
102
 
94
- No parameters. It confirms with `ctx.ui.confirm` before switching the tool set,
103
+ No fields. It confirms with `ctx.ui.confirm` before switching the tool set,
95
104
  because the model is changing the mode on the user's behalf; a declined confirm
96
105
  answers "stayed in normal mode" and the session keeps write access. Already
97
106
  planning, it says so. With no UI it fails with an actionable message instead of
@@ -100,12 +109,12 @@ deciding for the user.
100
109
  On a remote turn the confirmation is a short consent sentence in the peer's
101
110
  language, and the notice names how to leave.
102
111
 
103
- ### `write_plan`
112
+ ### `write`
104
113
 
105
114
  `title` is the file-name slug, `content` the full markdown, and `plan_path`
106
115
  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.
116
+ the relative path, and the byte count — the absolute path is what `exit` takes. A
117
+ call with neither `title` nor `content` is refused before anything is written.
109
118
 
110
119
  The file lands in
111
120
  `<agent-dir>/plans/<repo-key>/<YYYY-MM-DD-HHmm>-<slug>.md`, keyed by the
@@ -115,8 +124,8 @@ repository the store is `<agent-dir>/plans`. Plans are outside the project, so
115
124
  they are never untracked files and never appear in a `rewind` snapshot; a store
116
125
  an older version left in `<repo>/.pi/plans` is moved in once, with a notice.
117
126
 
118
- The description documents a canonical outline — a `# <Title>`, then `## Goal`,
119
- `## Context`, `## Approach`, `## Visual`, `## Steps`, `## Risks`, and
127
+ The tool's description documents a canonical outline — a `# <Title>`, then
128
+ `## Goal`, `## Context`, `## Approach`, `## Visual`, `## Steps`, `## Risks`, and
120
129
  `## Verification` — and the model is guided to follow it. Every example in the
121
130
  template is the shape wanted rather than a description of it, and each section
122
131
  has one job: the goal is one sentence, the context is the facts that force the
@@ -139,7 +148,7 @@ A plan with more than 50 top-level steps in its `Steps` section is refused with
139
148
  the cap and the fix named, and nothing is written.
140
149
 
141
150
  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
151
+ steps, and refinements rewrite the same file (`write` with `plan_path`), so
143
152
  the reviewed artifact is the executed artifact. The text a refinement replaces is
144
153
  kept beside it, as a dotfile named for the plan and the revision
145
154
  (`.2026-10-26-1008-add-rate-limiting-r001.md`), up to the newest five: that is
@@ -150,9 +159,9 @@ the review never offers it for approval — and a refinement that changed nothin
150
159
  keeps nothing, because the copy would be the plan itself. The write's result names
151
160
  the revision it kept.
152
161
 
153
- ### `exit_plan_mode`
162
+ ### `exit`
154
163
 
155
- `plan_path` names the file `write_plan` returned; the tool reads it back and
164
+ `plan_path` names the file `write` returned; the action reads it back and
156
165
  presents it for approval. A hand-edited or older file whose step list is over the
157
166
  cap is refused before the review screen. The user approves, keeps planning, asks
158
167
  for a refinement, or exits plan mode without approving; the last keeps the plan
@@ -172,19 +181,19 @@ the todo list:
172
181
  - The plan's `## Verification` section follows as the **last** todo item,
173
182
  summarized on one line (`Verify: …`) — a fenced block contributes its
174
183
  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
184
+ todo list rather than with the `steps` action, and it never counts against the
176
185
  50-step cap.
177
186
  - A plan without a `Steps` heading keeps the older rule — the shallowest list in
178
187
  the document — so prose bullets in a plan that follows the outline never become
179
188
  todos, and its verification bullets are already covered.
180
189
  - Each seeded item is matched back to its step by text, so ticking a step with
181
- `set_plan_steps` completes the item too.
190
+ `steps` completes the item too.
182
191
  - 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
192
+ entry. A plan refined after approval is approved again — `write` needs plan
184
193
  mode, so a second round is the way back — and that second approval **replaces the
185
194
  leftovers its own earlier approval made**: an untouched item whose step was
186
195
  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
196
+ named, because deleting it would erase work a reader did. `write` says the
188
197
  list is stale when it refines a plan that was approved before.
189
198
 
190
199
  **Goal-aware.** While planning, the injected context restates a pursued session
@@ -196,15 +205,10 @@ the decision. A session with no goal at all is given the same objective, also
196
205
  without asking. An `active` or `achieved` goal is never rewritten: a final
197
206
  objective is the user's. No path spends a model turn.
198
207
 
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`
208
+ ### `steps`
205
209
 
206
210
  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
211
+ in the order `exit` listed, a `status` of `pending` or `completed`, and
208
212
  `expected` — the step's text, so an update whose step number moved is refused
209
213
  instead of ticking the wrong line. It is a single all-or-nothing write: a step out
210
214
  of range, or an `expected` text that no longer matches the file, refuses every
@@ -213,27 +217,32 @@ marker.
213
217
 
214
218
  The todo item approval seeded from a step is ticked with it: the file is written
215
219
  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.
220
+ unmatched, and a failed todo write is reported in the result. The action refuses
221
+ while plan mode is on — refine the plan with `write` instead.
222
+
223
+ ### `cancel`
224
+
225
+ Leaves plan mode without a plan file or a review. Restores write access, records
226
+ no todo items or goal, and keeps any saved plan. No fields.
218
227
 
219
228
  ### Transcript
220
229
 
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).
230
+ The blocks reuse the transcript component, and `exit` separates the plan body from
231
+ its header with a blank line; the collapse hint binds to `app.tools.expand`
232
+ (`ctrl+o` by default).
224
233
 
225
234
  ```
226
- enter_plan_mode requested plan mode
235
+ plan enter
227
236
  ✓ Plan mode enabled
228
237
  ```
229
238
 
230
239
  ```
231
- write_plan Add rate limiting
240
+ plan write Add rate limiting
232
241
  ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
233
242
  ```
234
243
 
235
244
  ```
236
- exit_plan_mode submitted a plan
245
+ plan exit 2026-10-09-1809-add-rate-limiting
237
246
  ✓ Plan approved
238
247
  ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
239
248
 
@@ -247,7 +256,7 @@ exit_plan_mode submitted a plan
247
256
 
248
257
  | Path | Who starts it | Confirmation |
249
258
  |---|---|---|
250
- | `enter_plan_mode` tool | the model, before a non-trivial task | `ctx.ui.confirm` |
259
+ | `plan` (action `enter`) | the model, before a non-trivial task | `ctx.ui.confirm` |
251
260
  | `/plan` | the user | immediate |
252
261
  | `/plan <prompt>` | the user, with the task | immediate |
253
262
  | `/plan list` | the user, browsing the plans directory | immediate |
@@ -255,7 +264,7 @@ exit_plan_mode submitted a plan
255
264
  | `/plan share [stop]` | the user, handing the review to a teammate | immediate |
256
265
  | `/plan cancel` | the user, leaving plan mode without approving | immediate |
257
266
  | `/skill:init` | the user, creating or updating `AGENTS.md` | enters plan mode; write waits for plan approval |
258
- | `Ctrl+Alt+P` | the user | immediate |
267
+ | `Shift+Tab` (bytefly) | the user | immediate |
259
268
  | `--plan` | the user, at launch | immediate |
260
269
 
261
270
  `/plan` with no argument toggles plan mode; `/plan <prompt>` enters it (if it is
@@ -267,10 +276,13 @@ already runs read-only:
267
276
  ```
268
277
 
269
278
  `/plan <prompt>` never disables plan mode; only the argument-less `/plan` (or
270
- `Ctrl+Alt+P`) toggles. Because slash commands go through Pi directly, `/plan` is
271
- the toggle that works in every terminal (for example Zed's integrated terminal,
272
- which does not forward `Ctrl+Alt+P`). The footer shows `≡ plan` while plan mode
273
- is on, and `≡ plan · <plan-file>` once the model has written a plan.
279
+ `Shift+Tab`, through bytefly) toggles. Because slash commands go through Pi
280
+ directly, `/plan` is the toggle that works with or without bytefly. Bytefly's
281
+ footer shows `▶ auto` in accent in its mode segment, and `≡ plan` in warning there
282
+ while plan mode is on; the bar draws that word itself, so the plan file name the
283
+ chip carries never reaches it. `auto` is Pi's default mode — every tool as it ships,
284
+ writes included — so the chip says which mode the session is in, not that a decision
285
+ was taken for you.
274
286
 
275
287
  `/plan list` walks the plans directory and shows every plan with its title,
276
288
  modified time, and step progress, newest first — in the TUI as a docked list
@@ -291,7 +303,7 @@ never swallows one.
291
303
  `/plan share` prints the reviewer link and its QR, and `/plan share stop` revokes
292
304
  every link; the page's **Share** button does the same. With no browser review open
293
305
  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
306
+ approving, exactly like `cancel`: write access returns and the plan file
295
307
  stays for a later review. Both are exact words — `share the tokens` and `cancel
296
308
  that idea` stay tasks — as is `list`.
297
309
 
@@ -310,7 +322,7 @@ approval. Its workflow is:
310
322
  └─ unavailable/cancelled ──> evidence + assumptions
311
323
  └─> draft: new file, exact edits, or an honest no-op
312
324
  └─> quality gate
313
- └─> write_plan ──> exit_plan_mode
325
+ └─> plan · write ──> plan · exit
314
326
  ├─ not approved ──> refine the same plan
315
327
  └─ approved ──────> write/edit the target
316
328
  └─ read back + quality checklist
@@ -366,9 +378,13 @@ session token, and the owner's token is the one that opened the page.
366
378
  | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
367
379
  | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
368
380
 
381
+ Every route that reads a body takes JSON and only JSON: a form-encoded, `text/plain`,
382
+ or headerless body is refused `415` before the role check, so a form cannot reach the
383
+ parser even from the page's own origin.
384
+
369
385
  ## Behaviour by mode
370
386
 
371
- The review screen is TUI-only: `exit_plan_mode` opens `PlanViewComponent`, and `w`
387
+ The review screen is TUI-only: `exit` opens `PlanViewComponent`, and `w`
372
388
  on it opens the browser review. RPC and other non-interactive runs get no screen.
373
389
 
374
390
  Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt instead: on a
@@ -391,10 +407,10 @@ its closing rule on a short terminal.
391
407
  Browser review is a local-turn feature: the server binds `127.0.0.1`, so a remote
392
408
  (WeChat) turn cannot reach it and `/plan review` there refuses with a notice
393
409
  instead of opening a server nobody can use. The browser's **Approve & run** is
394
- offered only for the review `exit_plan_mode` opens, since only that flow owns
410
+ offered only for the review `exit` opens, since only that flow owns
395
411
  leaving plan mode; `/plan review` stays comment-only.
396
412
 
397
- Aborting the turn closes a browser review. `exit_plan_mode` forwards the call's
413
+ Aborting the turn closes a browser review. `exit` forwards the call's
398
414
  abort signal to the review server, so an aborted turn returns "not approved"
399
415
  and stops listening instead of waiting on a page nobody is watching.
400
416
 
@@ -409,7 +425,7 @@ command. The read-only guarantee is layered:
409
425
  git commands through `bash` (chained with `&&`/`;`), `powershell` is blocked
410
426
  outright, and other mutating tools (worktree mutations, MCP tools) are blocked
411
427
  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
428
+ only permitted write is the `plan` tool's `write` action, which can only
413
429
  create or overwrite markdown plan files directly inside the plans directory —
414
430
  never dotfiles and never through a symlink.
415
431
  - **Capability policy.** A tool is allowed only when it is a known structured
@@ -421,18 +437,18 @@ command. The read-only guarantee is layered:
421
437
  file path is refused unless it is a known plan-safe reader. `path-guard.ts`
422
438
  scans nested arguments and path-shaped values, not just fixed top-level keys.
423
439
  - **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
440
+ the `plan` tool's `write`, `exit`, and `cancel` actions — only
425
441
  when the registered tool is this extension's, matched by source path, so a
426
442
  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
443
+ the tool is exempted as a whole, and its `enter` action answers "already"
428
444
  is answered with "Already in plan mode" instead of being refused as not
429
445
  read-only; the policy still denies it, so it stays hidden while planning.
430
- - **Contained writes.** `write_plan` only writes markdown non-dotfiles whose
446
+ - **Contained writes.** `write` only writes markdown non-dotfiles whose
431
447
  real parent resolves inside the plans directory, and every plan write —
432
- create, refine, `set_plan_steps`, browser feedback — goes through a hidden
448
+ create, refine, `steps`, browser feedback — goes through a hidden
433
449
  temp file followed by a rename, so an interrupted write cannot leave a
434
450
  truncated plan.
435
- - **Sanitized model text.** The plan file is model-authored, so `exit_plan_mode`
451
+ - **Sanitized model text.** The plan file is model-authored, so `exit`
436
452
  strips control characters from the plan body before rendering it in the
437
453
  transcript and in non-TUI notices; a plan file cannot drive the terminal.
438
454
  - **Loopback browser review.** The review server binds `127.0.0.1` and requires a
@@ -468,8 +484,19 @@ git diff -- extensions\plan # allowed: a Windows path separator
468
484
  git status && rm -rf x # blocked: the second segment is not git
469
485
  git status | sh # blocked: pipes are never allowed
470
486
  git diff > out.patch # blocked: redirection is a write
487
+ git config --get user.email # allowed: a read
488
+ git config --add core.hooksPath x # blocked: it writes .git/config
471
489
  ```
472
490
 
491
+ `git config` reads only. Its flags are an allowlist — `--get`, `--get-all`,
492
+ `--get-regexp`, `--list`, and the modifiers that shape them (`--show-origin`,
493
+ `--show-scope`, `--local`, `--global`, `--null`) — one bare `git config <name>` reads a
494
+ key, and every action that writes is refused by name: `--add`, `--replace-all`, `--unset`,
495
+ `--unset-all`, `--rename-section`, `--remove-section`, `--edit`, or a `<name> <value>`
496
+ pair. A write action used to pass as long as a read flag appeared beside it, so
497
+ `git config --show-scope --replace-all core.fsmonitor /tmp/hook.sh` configured a hook from
498
+ inside a plan and the next `git status` ran it.
499
+
473
500
  A refused shell command explains the fix: the working directory is already the
474
501
  project root (so `cd` is unnecessary), and files belong to the `read`, `grep`,
475
502
  `find`, and `ls` tools.
@@ -507,7 +534,7 @@ agent file that overrides it.
507
534
 
508
535
  <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%">
509
536
 
510
- `exit_plan_mode` opens this screen on the plan file it just wrote. The body is
537
+ `exit` opens this screen on the plan file it just wrote. The body is
511
538
  rendered with Pi's own `Markdown` component and `getMarkdownTheme()`, so
512
539
  headings, lists, inline code, code blocks, links, and quotes carry the active
513
540
  theme's colours; task items get the shared `◼` (done) or `◻` (pending) marker,
@@ -562,6 +589,12 @@ reveals a **+ Comment** action on hover; select
562
589
  text first to quote it. **Save feedback** writes the comments into the plan
563
590
  file and returns them to the model.
564
591
 
592
+ - **A comment can be settled.** **Resolve** (on your own note) keeps it — its place, its
593
+ quote, its history — and marks it `> [!REVIEW resolved]` in the file, where a later review
594
+ and the model both read it as closed; **Reopen** puts it back. The panel counts
595
+ `2 open · 1 resolved`. A note you wrote is labelled **You** on your page and **The owner**
596
+ on somebody else's, a reviewer's is `@name`, and one sentence beside the facts says what
597
+ **Approve & run** and **Save feedback** each do.
565
598
  - **Shared theme.** The page uses the same Bytefly tokens, base rules, and
566
599
  syntax-token colours as `file-browse` (`lib/web/`), follows the system light/dark
567
600
  preference until the header toggle sets an explicit choice, and remembers that
@@ -629,7 +662,7 @@ file and returns them to the model.
629
662
  Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
630
663
  documentation, not feedback. The file is the only copy of a saved comment; the
631
664
  ones this tab has not saved yet live in its own `sessionStorage` and nowhere else.
632
- - **A revision keeps the review.** `write_plan` passes the file it is replacing
665
+ - **A revision keeps the review.** `write` passes the file it is replacing
633
666
  through `carryFeedback`, which re-attaches each comment by the strongest evidence
634
667
  that survived: the anchored block's text, per occurrence, so a plan that repeats
635
668
  a step keeps the comments apart; then the block whose wording still overlaps it —
@@ -654,14 +687,15 @@ file and returns them to the model.
654
687
  save that carries no comments asks for nothing, so the review screen comes back
655
688
  instead. `/plan review` sends them as a user message (a plan-mode revision or a
656
689
  plain update, depending on the mode).
657
- - **Loopback first, tokenized.** The server binds `127.0.0.1`, carries a random
658
- per-session token in the URL, rejects a `Host` header it was not bound on,
659
- refuses a cross-origin POST, serves only `GET`, `HEAD`, and `POST`, and caps the
660
- request body. Sharing adds a second listener (below); it never widens the first. A save or cancel
661
- resolves the session only after its HTTP response is delivered, so the browser
662
- never sees a false "Failed to fetch". The last bound port is remembered per
663
- plan, so reviewing the same plan again reuses the origin (and the browser's
664
- saved theme with it). It is closed on the owner's save or cancel, on the owner's
690
+ - **Loopback first, tokenized.** The review mounts `/plan/<id>` on the session's one
691
+ server, which binds `127.0.0.1`; the URL carries a random per-session token, a `Host`
692
+ header the server was not bound on is rejected, a cross-origin POST is refused, only
693
+ `GET`, `HEAD`, and `POST` are served, and the request body is capped. Sharing asks that
694
+ mount for one LAN listener (below); it never widens the loopback socket. A save or
695
+ cancel resolves the session only after its HTTP response is delivered, so the browser
696
+ never sees a false "Failed to fetch". The prefix is the review's, on the session's one
697
+ origin, so the browser's saved theme survives a review. It is closed on the owner's
698
+ save or cancel, on the owner's
665
699
  page going away, or on `session_shutdown`; a reload reconnects inside that grace,
666
700
  so refreshing or reloading a conflicted page does not end the review. A
667
701
  reviewer's tab opening or closing is not the review's lifetime.
@@ -741,13 +775,13 @@ both.
741
775
  keeps no registry, so it sees an external edit or a hand-copied plan
742
776
  immediately; it lists a file over the 256 KiB plan limit without reading it,
743
777
  and does not index the agent directory's plans while a worktree is active.
744
- - Approval tracks at most 50 steps: `write_plan` refuses a larger plan, and
745
- `exit_plan_mode` refuses to present one, so no reviewed step is silently
778
+ - Approval tracks at most 50 steps: `write` refuses a larger plan, and
779
+ `exit` refuses to present one, so no reviewed step is silently
746
780
  dropped. A step longer than 200 characters is shortened in the todo list and
747
781
  the approval listing — a display rule only, because the plan file always keeps
748
782
  the full text.
749
783
  - The todo item for a step is matched by its text. An item renamed after
750
- approval no longer matches, so `set_plan_steps` leaves it alone and says so in
784
+ approval no longer matches, so `steps` leaves it alone and says so in
751
785
  its result; the plan file is still updated.
752
786
  - Browser review needs a local browser: the server is loopback-only, so a remote
753
787
  turn cannot drive it.
@@ -766,14 +800,24 @@ both.
766
800
  refuses a file it did not render (`409`). It is a poll rather than a file
767
801
  watcher, so a change on a filesystem that reports nothing at all is still seen
768
802
  at the next request, because every page read re-compares the file.
803
+ - **A settled note is a flag in the marker.** `> [!REVIEW resolved]` (and
804
+ `moved resolved`) is read as open when absent; a build from before this one does not know
805
+ the flag and shows the marker line as body text, so a file reviewed on both sides of the
806
+ upgrade reads differently there.
769
807
  - A shared review holds at most eight links. Each is a live bearer credential, so
770
808
  a ninth is refused with the cap named rather than minted.
809
+ - **One tool means one filter.** Pi's per-tool filter is all-or-nothing, so a
810
+ session that disables `plan` loses every action with it, and plan mode
811
+ force-activates the tool while it is on (and puts the user's set back when it
812
+ ends). `rewind` also snapshots a run at the tool's first call rather than at
813
+ the first real write, because one annotation set cannot say that `enter` writes
814
+ nothing; it is still one snapshot per run, of the same tree.
771
815
 
772
816
  ## Non-goals
773
817
 
774
818
  - **Not a sandbox.** Extensions share Pi's OS permissions.
775
819
  - **No progress tracker of record.** `todo` owns completion state; the plan
776
- file only mirrors it (`set_plan_steps`), and plan mode does not read completion
820
+ file only mirrors it (`steps`), and plan mode does not read completion
777
821
  back out.
778
822
  - **No auto-execution.** Approval hands control back to the model.
779
823
 
@@ -781,10 +825,11 @@ both.
781
825
 
782
826
  | Integration point | How |
783
827
  |---|---|
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. |
828
+ | Tools | one `plan` tool, active in both modes, with five actions: `enter`, `write`, `exit`, `steps`, `cancel`. It is `executionMode: "sequential"`, closed-world, and carries one annotation set — `readOnlyHint: false`, `destructiveHint: true`, `idempotentHint: false` — because `write` overwrites a plan file: the actions that only change the mode are exempted by the guard in `index.ts`, the only place that can tell them apart. Each action returns typed `details` discriminated by `action` (`EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`); a refusal carries `RefusedPlanDetails` (`refused`, `error`) and no action's state; no `outputSchema`/`structuredContent`. |
829
+ | 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. `Shift+Tab` is bytefly's editor chord, not a registered shortcut: `app.thinking.cycle` is reserved, so the editor requests a toggle over the event bus instead. |
786
830
  | 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. |
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`. |
831
+ | Entry requests | `pi.events` channels from `lib/plan-entry.ts`: `PLAN_ENTER_REQUEST` (the `goal` workflow asks, with the shared consent sentence, and plan enables the mode on the caller's context without a second dialog) and `PLAN_TOGGLE_REQUEST` (bytefly's Shift+Tab asks to flip it and gets the state back). Both share one `PlanModeRequest` payload and `isPlanExtensionLoaded`. Subscribed on `session_start`, released on `session_shutdown`. |
832
+ | `board` while planning | The board is one action-shaped tool, so its annotation cannot say it reads: plan mode reads the action instead, through `lib/policy.ts`'s `isBoardRead` and `lib/board/actions.ts`'s table. The refusal is built by `boardWriteRefusal` in that same module — the board's words, because the sentence names the board's order: claim a card before you plan, and record its plan once it is approved. |
788
833
  | 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. |
789
834
  | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
790
835
 
@@ -792,16 +837,16 @@ both.
792
837
 
793
838
  - **Consent is one dialog, whichever extension asks.** The goal workflow offers
794
839
  plan mode as a phase of the goal, but a command context cannot call
795
- `enter_plan_mode` (`ExtensionCommandContext` has no `executeTool`). So the
840
+ the `plan` tool (`ExtensionCommandContext` has no `executeTool`). So the
796
841
  consent sentence left `policy.ts` for `lib/plan-entry.ts`, and the entry itself
797
842
  is a `pi.events` request: the asker shows the question, plan enables silently
798
- with the asker's context. `enter_plan_mode` keeps its own confirm, using the
843
+ with the asker's context. The tool's `enter` action keeps its own confirm, using the
799
844
  same sentence, so the model-initiated path is unchanged and the two texts
800
845
  cannot drift. A request that nobody answers resolves `false` rather than
801
846
  hanging, so `goal` loaded alone still works.
802
847
  - **One review loop owns the human gate.** `review.ts` asks however the session
803
848
  is attached — TUI screen, dialog plus editor, remote file plus digest, or
804
- browser — and returns one `ReviewDecision`, so `exit_plan_mode` and
849
+ browser — and returns one `ReviewDecision`, so `exit` and
805
850
  `/plan review` no longer carry two copies of the browser lifecycle and the
806
851
  tool's own `for(;;)` with a `continue` per outcome. The loop also owns the
807
852
  baseline rule (mark what the screen showed, after it showed it) and forwards
@@ -809,9 +854,11 @@ both.
809
854
  - **Two tools, not one.** `EnterPlanMode`/`ExitPlanMode` are a pair: entering is
810
855
  consent to explore read-only, exiting is consent to act. A single toggle tool
811
856
  cannot express "the model wants to plan but the user declines".
812
- - **User entry is instant, model entry confirmed.** `/plan`, `Ctrl+Alt+P`, and
813
- `--plan` are the user's own action and take effect at once; `enter_plan_mode`
814
- changes the mode on the user's behalf, so it confirms first.
857
+ - **User entry is instant, model entry confirmed.** `/plan`, `Shift+Tab`, and
858
+ `--plan` are the user's own action and take effect at once; the `enter` action
859
+ changes the mode on the user's behalf, so it confirms first. Shift+Tab travels
860
+ through bytefly's editor and `PLAN_TOGGLE_REQUEST`, so the mode stays owned by
861
+ `plan` even when the key is pressed in another extension's surface.
815
862
  - **Review the file, decide in a screen.** The plan is a file, so it is the
816
863
  artifact already on screen. `ctx.ui.confirm()` is a non-scrollable selector
817
864
  that pushes buttons off-screen for a long plan, so the TUI gets a scrollable
@@ -819,7 +866,7 @@ both.
819
866
  renders the body with Pi's `Markdown` component so the plan reads like any
820
867
  other markdown in the app; the plan text is sanitized before it reaches the
821
868
  renderer, so only the theme's own ANSI survives.
822
- - **`write_plan` is the only write.** It is not the builtin `write`, so the
869
+ - **`write` is the only write.** It is not the builtin `write`, so the
823
870
  capability policy and path backstop stay intact; a `plan_path` must name a
824
871
  markdown non-dotfile whose real parent resolves inside the plans directory,
825
872
  so symlinks and the directory's own `.gitignore` are never writable targets.
@@ -842,7 +889,7 @@ both.
842
889
  authoritative, so a failed nested write is reported in the result text — once
843
890
  one artifact has changed there is no previous state to hand back.
844
891
  - **A cap is enforced where it can still be fixed.** Both refusal points —
845
- `write_plan` and `exit_plan_mode` — sit before the write and before the review,
892
+ `write` and `exit` — sit before the write and before the review,
846
893
  because those are the moments the model can still regroup the steps. A note on
847
894
  a successful write would arrive after the file existed and the review had
848
895
  already listed every step, which is exactly when the overflow becomes lost
@@ -856,7 +903,7 @@ both.
856
903
  a session entry rather than the previous text, so the markers survive a resume
857
904
  without copying the plan file into the branch, and the reader and the writer
858
905
  share one normalization so a tab cannot read as a change.
859
- - **The outline guides, it does not enforce.** `write_plan` documents the
906
+ - **The outline guides, it does not enforce.** `write` documents the
860
907
  canonical shape in its description and stores whatever markdown it is given;
861
908
  no section is required and nothing is rewritten. The description names the two
862
909
  headings that do carry behavior — `Steps` is what approval reads as steps, and
@@ -868,7 +915,7 @@ both.
868
915
  source-line map, so `tui.ts` splits the body at its headings and renders each
869
916
  section separately. That gives exact offsets for the current-section label and
870
917
  `n`/`p`; the single-section case is byte-for-byte the old render.
871
- - **`set_plan_steps` is a second contained write.** It changes only the named
918
+ - **`steps` is a second contained write.** It changes only the named
872
919
  steps' checkboxes, in an existing markdown plan inside the plans directory
873
920
  (`isContainedPlan`), in a single all-or-nothing write — refusing every entry
874
921
  if any step is out of range or an entry's `expected` text no longer matches —
@@ -925,20 +972,21 @@ both.
925
972
 
926
973
  | File | Responsibility |
927
974
  |---|---|
928
- | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
929
- | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`. |
975
+ | `index.ts` | Wiring: tools, command, flag, events, context injection. |
976
+ | `types.ts` | `PLAN_ACTIONS`, `PlanModeEntry`, the five action `details` types and their `PlanDetails` union, and `RefusedPlanDetails`. |
930
977
  | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
931
978
  | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
932
- | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
979
+ | `plans.ts` | Plan-file store: slug/stamp naming, the store's directory (`<agent-dir>/plans/`, keyed by the repository, unkeyed outside git, through `lib/plans/paths.ts`), `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
980
+ | `lib/plans/paths.ts` | Where a plan lives, and which paths are one: `plansDir` for `<agent-dir>/plans/` (keyed by the repository, unkeyed outside it), the markdown/non-dotfile name rule, symlink-safe containment, and `resolvePlanArg`. Shared with `board`, which records the file a card is executed against. |
933
981
  | `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. |
934
982
  | `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. |
935
983
  | `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
984
  | `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. |
937
985
  | `format.ts` | Pure result/approval text: the plan preview, the step-cap diagnosis, step labels, the merge summary, and the verification note. |
938
986
  | `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. |
939
- | `runtime.ts` | Enabled state, tool gating, persistence (mode and review baseline), control-tool identity, footer status. |
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). |
987
+ | `runtime.ts` | Enabled state, tool gating, persistence (mode and review baseline), the `plan` tool's identity and its place in the active set, footer status. |
988
+ | `tools.ts` | The one registration: the flat action schema, the dispatch over the five actions, and the call and result blocks they draw. |
989
+ | `tools/` | One module per action (`enter.ts`, `write.ts`, `exit.ts`, `cancel.ts`, `steps.ts`), each exporting its `execute`/`call`/`text` seam; plus `refusal.ts` (the one rejected-call shape), `todo-seed.ts` (the two nested `todo` writes) and `nested.ts` (reading a nested result). |
942
990
  | `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. |
943
991
  | `review-prompt.ts` | Pure rendering and parsing for the compact remote review: one action table behind both the prompt and the reply parser. |
944
992
  | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, `/plan list`, `/plan share [stop]`, and `/plan cancel`. |
@@ -946,14 +994,18 @@ both.
946
994
  | `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
995
  | `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
996
  | `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. |
997
+ | `review-lazy.ts` | The review gate behind a dynamic import: `reviewPlan` and `runBrowserReview` load `review.ts` (and its TUI screen) on the first review, so registration, `/plan cancel`, and the tool schemas never do. The decision and baseline types live here, where a type import costs nothing. |
998
+ | `web-lazy.ts` | The review behind `lib/lazy`: `NO_SERVER_DEPS`, and the proxy `/plan` and the tools share, so `web.ts` loads on the first review and shutdown closes only an opened one |
999
+ | `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, and the router wiring — the island is `web-payload.ts` and the writes are `web-write.ts`. |
950
1000
  | `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. |
951
1001
  | `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. |
952
1002
  | `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. |
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. |
1003
+ | `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation, share and name dialogs) and inline CSS. |
1004
+ | `web-payload.ts` | The page's data island: `ReviewPageData` and `reviewPageData`, which builds every fact the browser renders — the blocks, the stored comments, the outline and its change marks, the step progress, the highlighted code, the caps, and who is asking — from one read of the plan, with no server or socket in sight. |
1005
+ | `web-write.ts` | The review's two write paths over the plan store: `saveOwnerAnnotations` (the owner's save, which returns the plan it wrote) and `postReviewerAnnotations` (a reviewer's post, signed and scoped to its link), sharing one 404/409 check so a comment can never land beside text nobody read. |
954
1006
  | `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. |
955
1007
  | `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. |
1008
+ | `web-keys.ts` | The client's keyboard as typed source (`WEB_KEYS_JS`), and the one table the page's keys come from: `webKeys()` gives each binding its keys, the shortcut sheet's sentence, the sidebar hint's words and whether the page offers it — a function rather than a constant, because the bundle carries a body and `String()` of a table would drop the closures — read by `handleKey` (Esc peels the composer before closing, a field and the sheet own their keys, `s` is the write the page offers) and by the sheet and hint line the page renders; plus `rememberSelection`, which keeps the text a reader selected as the next comment's quote, and the `keyRows`/`surfaceOf` the handler and the page's own sheet both read, spliced with them for the same reason. |
957
1009
  | `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
1010
  | `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
1011
  | `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`. |
@@ -962,7 +1014,7 @@ both.
962
1014
  | `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. |
963
1015
  | `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
964
1016
  | `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. |
965
- | `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
1017
+ | `web-markdown.ts` | The client's block half: tables, fenced code, headings, quotes, and lists, with each block's text handed to the shared `renderInline` from `lib/web/markdown.ts` (interpolated into `web-client.ts`). |
966
1018
  | `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
1019
  | `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. |
968
1020
  | `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
@@ -973,7 +1025,7 @@ both.
973
1025
  | [`../../lib/qr.ts`](../../lib/qr.ts) | QR matrix, terminal half-blocks, and inline SVG for the share link (shared with `wechat`). |
974
1026
  | [`../../lib/http/`](../../lib/http/) | The one loopback server entry, the bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
975
1027
  | [`../../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. |
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. |
1028
+ | [`../../lib/http/index.ts`](../../lib/http/index.ts) | The served-page kit shared with `file-browse`: the page policy and header sets, `ServeResponse`, the request-envelope helpers, bind/respond/close. Reached from the handler that serves a page; the mount contract is the leaf [`mount.ts`](../../lib/http/mount.ts). |
977
1029
  | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |
978
1030
  | `../../lib/tui/chrome.ts` | Shared screen header, viewport-row helper, and styled key-hint footer. |
979
1031
 
@@ -983,21 +1035,27 @@ both.
983
1035
  bun test extensions/plan
984
1036
  ```
985
1037
 
986
- Twelve co-located suites, declared in `test/suite-manifest.ts`:
1038
+ Thirteen co-located suites, declared in `test/suite-manifest.ts`:
987
1039
 
988
- - `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
989
- writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
990
- step extraction.
1040
+ - `plans.test.ts` — the plan-file store: naming, the store's directory, atomic
1041
+ writes, `read`/`latest`/`index`, `setSteps`, `saveFeedback`, and step extraction;
1042
+ the location and path rules it runs through (`plansDir`, `plansDirFor`, the name
1043
+ rule, symlink-safe containment) are `lib/plans/paths.ts` and its own suite.
991
1044
  - `feedback.test.ts` — the comment codec: authored markers, stripping and
992
1045
  parsing, who a reviewer's write is signed by, carrying feedback onto a
993
1046
  revision, and displaying stored comments.
994
- - `tools.test.ts` — registration and all five tools, the approval merge into
1047
+ - `tools.test.ts` — all five actions against a real plan store, the approval merge into
995
1048
  `todo`, the session goal, and the call/result renderings.
1049
+ - `tool-registration.test.ts` — the registration itself: the flat action schema,
1050
+ the strictest-of-five annotations, and the refusal each action gives when it
1051
+ needs a field or runs outside its mode.
996
1052
  - `review-prompt.test.ts` — the compact remote prompt and its reply parser
997
1053
  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.
1054
+ - `web-*.test.ts` — the browser review, one capability per suite: the comment
1055
+ codec and the router (`web-router`), the page roles and its shell (`web-page`),
1056
+ the client script's rules (`web-client`, `web-outline`, `web-composer`,
1057
+ `web-settlement`), the stream and the plan-file poll (`web-stream`), the
1058
+ loopback session (`web-session`), and the share rules (`web-shared-review`).
1001
1059
  - `web-behaviour.test.ts` — the served review, driven in a real browser through
1002
1060
  the router: the blocks and the plan's name, a block's comment control opening the
1003
1061
  composer with Escape peeling it before the review, a stored comment in its block's
@@ -1006,9 +1064,9 @@ Twelve co-located suites, declared in `test/suite-manifest.ts`:
1006
1064
  - `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
1007
1065
  its cap, and the reasons a share is unavailable.
1008
1066
  - `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
1067
+ review gate shared by `exit` and the browser, including the abort and
1010
1068
  baseline rules.
1011
- - `runtime.test.ts` — tool gating, persistence and status, control-tool identity,
1069
+ - `runtime.test.ts` — tool gating, persistence and status, the `plan` tool's identity,
1012
1070
  the review baseline, branch restore, the read-only git allowlist, and the
1013
1071
  path-argument scan.
1014
1072
  - `tui.test.ts` — the review screen at fixed widths: Markdown body, scrolling,
@@ -1016,6 +1074,5 @@ Twelve co-located suites, declared in `test/suite-manifest.ts`:
1016
1074
  refine editor, and the changed-section markers.
1017
1075
  - `sections.test.ts` — the fence-aware scan: heading shape, fences, the preamble,
1018
1076
  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.
1077
+ - `extension.test.ts` — the Pi wiring: registration, the enter and toggle requests,
1078
+ the session lifecycle, the tool-call guard, injected context, and the command.