@gavin-hu/my-pi-agent 0.21.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 (254) hide show
  1. package/README.md +46 -31
  2. package/README.zh-CN.md +40 -26
  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 -24
  9. package/docs/extensions/board.md +361 -147
  10. package/docs/extensions/bytefly.md +152 -111
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +30 -31
  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 +166 -126
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +34 -18
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +72 -33
  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 +56 -15
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +5 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +4 -40
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +13 -11
  38. package/extensions/board/result.ts +30 -16
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +42 -10
  41. package/extensions/board/snapshot.ts +2 -1
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +131 -44
  44. package/extensions/board/types.ts +36 -32
  45. package/extensions/board/view/card.ts +229 -0
  46. package/extensions/board/view/client.ts +33 -15
  47. package/extensions/board/view/dialog.ts +169 -6
  48. package/extensions/board/view/empty.ts +47 -0
  49. package/extensions/board/view/keys.ts +13 -2
  50. package/extensions/board/view/live.ts +89 -21
  51. package/extensions/board/view/names.ts +13 -4
  52. package/extensions/board/view/page.ts +23 -9
  53. package/extensions/board/view/render.ts +208 -271
  54. package/extensions/board/view/styles.ts +123 -31
  55. package/extensions/board/view/team.ts +297 -76
  56. package/extensions/bytefly/block/fallback.ts +1 -1
  57. package/extensions/bytefly/doctor/runtime.ts +5 -2
  58. package/extensions/bytefly/footer/collect/label.ts +60 -0
  59. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  60. package/extensions/bytefly/footer/config.ts +4 -2
  61. package/extensions/bytefly/footer/register.ts +33 -4
  62. package/extensions/bytefly/footer/render/lines.ts +7 -4
  63. package/extensions/bytefly/footer/render/segments.ts +37 -10
  64. package/extensions/bytefly/footer/render/status.ts +0 -11
  65. package/extensions/bytefly/footer/types.ts +7 -0
  66. package/extensions/bytefly/header/config.ts +1 -1
  67. package/extensions/bytefly/index.ts +32 -6
  68. package/extensions/bytefly/mode/editor.ts +115 -0
  69. package/extensions/bytefly/panel/commands.ts +210 -120
  70. package/extensions/bytefly/panel/format.ts +4 -4
  71. package/extensions/bytefly/panel/settings.ts +73 -6
  72. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  73. package/extensions/bytefly/update/cache.ts +1 -1
  74. package/extensions/bytefly/update/register.ts +4 -4
  75. package/extensions/bytefly/working/config.ts +5 -7
  76. package/extensions/bytefly/working/format.ts +10 -13
  77. package/extensions/bytefly/working/register.ts +22 -27
  78. package/extensions/check/commands.ts +3 -2
  79. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  80. package/extensions/context/commands.ts +2 -2
  81. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  82. package/extensions/file-browse/index.ts +67 -36
  83. package/extensions/file-read/render.ts +4 -4
  84. package/extensions/goal/plan-route.ts +2 -2
  85. package/extensions/job/child.ts +527 -0
  86. package/extensions/job/clock.ts +142 -0
  87. package/extensions/job/commands.ts +4 -2
  88. package/extensions/job/config.ts +8 -1
  89. package/extensions/job/format.ts +22 -1
  90. package/extensions/job/log-writer.ts +13 -2
  91. package/extensions/job/registry.ts +63 -7
  92. package/extensions/job/render.ts +34 -0
  93. package/extensions/job/runtime.ts +296 -655
  94. package/extensions/job/session.ts +132 -19
  95. package/extensions/job/store.ts +7 -7
  96. package/extensions/job/tools.ts +3 -3
  97. package/extensions/job/types.ts +47 -0
  98. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
  99. package/extensions/job/waiters.ts +44 -1
  100. package/extensions/memory/commands.ts +4 -2
  101. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  102. package/extensions/plan/commands.ts +12 -9
  103. package/extensions/plan/format.ts +8 -2
  104. package/extensions/plan/git-bash.ts +115 -16
  105. package/extensions/plan/index.ts +53 -52
  106. package/extensions/plan/plan-index.ts +4 -2
  107. package/extensions/plan/plans.ts +5 -54
  108. package/extensions/plan/policy.ts +8 -10
  109. package/extensions/plan/review-lazy.ts +85 -0
  110. package/extensions/plan/review.ts +2 -2
  111. package/extensions/plan/runtime.ts +49 -39
  112. package/extensions/plan/sections.ts +1 -1
  113. package/extensions/plan/steps.ts +3 -3
  114. package/extensions/plan/todos.ts +1 -1
  115. package/extensions/plan/tools/cancel.ts +27 -62
  116. package/extensions/plan/tools/enter.ts +55 -81
  117. package/extensions/plan/tools/exit.ts +221 -238
  118. package/extensions/plan/tools/refusal.ts +18 -0
  119. package/extensions/plan/tools/steps.ts +75 -92
  120. package/extensions/plan/tools/write.ts +77 -120
  121. package/extensions/plan/tools.ts +188 -30
  122. package/extensions/plan/tui.ts +5 -5
  123. package/extensions/plan/types.ts +48 -5
  124. package/extensions/plan/web-client.ts +3 -0
  125. package/extensions/plan/web-lazy.ts +53 -0
  126. package/extensions/plan/web-markdown.ts +9 -67
  127. package/extensions/plan/web-payload.ts +3 -3
  128. package/extensions/plan/web-router.ts +18 -9
  129. package/extensions/plan/web.ts +1 -12
  130. package/extensions/question/tools.ts +6 -2
  131. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  132. package/extensions/rewind/commands/commands.ts +6 -3
  133. package/extensions/rewind/state/runtime.ts +1 -1
  134. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  135. package/extensions/rewind/view/review-tui.ts +4 -4
  136. package/extensions/serve/config.ts +10 -8
  137. package/extensions/serve/index.ts +17 -3
  138. package/extensions/serve/registry.ts +26 -17
  139. package/extensions/session-bus/commands.ts +11 -6
  140. package/extensions/session-bus/join-request.ts +2 -1
  141. package/extensions/session-bus/mail-watch.ts +26 -17
  142. package/extensions/session-bus/protocol.ts +15 -3
  143. package/extensions/session-bus/resolve.ts +5 -7
  144. package/extensions/session-bus/runtime.ts +40 -12
  145. package/extensions/session-bus/store.ts +101 -19
  146. package/extensions/session-bus/types.ts +19 -0
  147. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  148. package/extensions/subagent/agents.ts +1 -1
  149. package/extensions/subagent/commands.ts +4 -2
  150. package/extensions/subagent/format.ts +15 -15
  151. package/extensions/subagent/index.ts +1 -1
  152. package/extensions/subagent/render.ts +3 -3
  153. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  154. package/extensions/team/commands.ts +24 -7
  155. package/extensions/team/dispatch.ts +18 -4
  156. package/extensions/team/format.ts +17 -2
  157. package/extensions/team/index.ts +9 -8
  158. package/extensions/team/schema.ts +6 -5
  159. package/extensions/team/tools.ts +107 -45
  160. package/extensions/team/types.ts +3 -2
  161. package/extensions/todo/commands.ts +4 -2
  162. package/extensions/todo/format.ts +11 -0
  163. package/extensions/todo/render.ts +57 -0
  164. package/extensions/todo/runtime.ts +1 -1
  165. package/extensions/todo/tools.ts +1 -1
  166. package/extensions/todo/view/todo-screen.ts +122 -0
  167. package/extensions/todo/widget.ts +47 -0
  168. package/extensions/watch/commands.ts +4 -2
  169. package/extensions/watch/format.ts +36 -7
  170. package/extensions/watch/render.ts +57 -0
  171. package/extensions/watch/runtime.ts +27 -22
  172. package/extensions/watch/tools.ts +4 -4
  173. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  174. package/extensions/web-access/fetch/tool.ts +12 -4
  175. package/extensions/wechat/commands/commands.ts +18 -14
  176. package/extensions/wechat/index.ts +72 -37
  177. package/extensions/wechat/state/credentials.ts +1 -1
  178. package/extensions/wechat/state/state.ts +1 -1
  179. package/extensions/wechat/text/status.ts +112 -0
  180. package/extensions/wechat/types.ts +2 -2
  181. package/extensions/wechat/view/status-screen.ts +4 -103
  182. package/extensions/worktree/commands.ts +11 -9
  183. package/extensions/worktree/enter.ts +2 -2
  184. package/extensions/worktree/exit.ts +2 -2
  185. package/extensions/worktree/index.ts +5 -5
  186. package/extensions/worktree/screen.ts +3 -3
  187. package/extensions/worktree/status.ts +2 -2
  188. package/extensions/worktree/tools.ts +113 -116
  189. package/lib/README.md +27 -13
  190. package/lib/board/actions.ts +111 -0
  191. package/lib/board/artifact.ts +149 -0
  192. package/lib/board/card.ts +163 -52
  193. package/lib/board/config.ts +58 -0
  194. package/lib/board/contract.ts +113 -0
  195. package/lib/board/join.ts +38 -0
  196. package/lib/board/lanes.ts +182 -0
  197. package/lib/board/lead.ts +130 -0
  198. package/lib/board/limits.ts +78 -0
  199. package/lib/board/links.ts +31 -10
  200. package/lib/board/page-pointer.ts +1 -1
  201. package/lib/board/read.ts +83 -0
  202. package/lib/board/reason.ts +102 -0
  203. package/lib/board/round-text.ts +68 -6
  204. package/lib/board/round.ts +31 -15
  205. package/lib/board/rules.ts +151 -307
  206. package/lib/board/seat.ts +35 -0
  207. package/lib/board/session.ts +136 -54
  208. package/lib/board/skipped.ts +22 -0
  209. package/lib/board/steps.ts +487 -0
  210. package/lib/board/store.ts +87 -30
  211. package/lib/board/tone.ts +92 -0
  212. package/lib/board/view.ts +70 -58
  213. package/{extensions/watch → lib}/child.ts +40 -21
  214. package/lib/env.ts +65 -7
  215. package/lib/format.ts +13 -0
  216. package/lib/http/index.ts +14 -12
  217. package/lib/http/port.ts +15 -10
  218. package/lib/lazy.ts +45 -0
  219. package/lib/plan-entry.ts +39 -10
  220. package/lib/plans/paths.ts +82 -0
  221. package/lib/policy.ts +9 -6
  222. package/lib/report.ts +3 -2
  223. package/lib/requirements.ts +8 -8
  224. package/lib/session-label.ts +111 -5
  225. package/lib/session-roster.ts +43 -1
  226. package/lib/session-tool.ts +5 -4
  227. package/lib/stray-fields.ts +7 -2
  228. package/lib/tool-names.ts +5 -10
  229. package/lib/tui/README.md +23 -8
  230. package/lib/tui/chrome.ts +16 -15
  231. package/lib/tui/cursor.ts +18 -3
  232. package/lib/tui/glyphs.ts +9 -7
  233. package/lib/tui/tokens.ts +33 -2
  234. package/lib/tui/tool.ts +3 -3
  235. package/lib/web/README.md +21 -0
  236. package/lib/web/components.ts +5 -1
  237. package/lib/web/index.ts +1 -0
  238. package/lib/web/markdown.ts +83 -0
  239. package/lib/web/markup.ts +2 -1
  240. package/lib/web/runtime.ts +9 -10
  241. package/package.json +6 -1
  242. package/skills/init/SKILL.md +5 -5
  243. package/skills/next/SKILL.md +3 -5
  244. package/extensions/board/seat.ts +0 -28
  245. package/extensions/todo/tui.ts +0 -212
  246. package/lib/tui/index.ts +0 -34
  247. package/skills/project-docs/SKILL.md +0 -133
  248. package/skills/project-docs/references/audit.md +0 -131
  249. package/skills/project-docs/references/changelog.md +0 -105
  250. package/skills/project-docs/references/discovery.md +0 -127
  251. package/skills/project-docs/references/readme.md +0 -80
  252. package/skills/project-docs/references/release.md +0 -84
  253. package/skills/project-docs/references/roadmap.md +0 -93
  254. package/skills/project-docs/scripts/state.ts +0 -316
@@ -8,52 +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 |
44
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 |
45
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 |
46
44
  | Section navigation | the review screen, `n`/`p` | The screen names the section you are in and jumps between headings. | Shipped |
47
- | 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 |
48
46
 
49
47
  ### Collaboration
50
48
 
51
- | Capability | How | What you get | Status |
52
- |---|---|---|---|
53
- | 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 |
54
- | 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 |
55
- | 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 |
56
- | 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. |
57
55
 
58
56
  ## Quickstart
59
57
 
@@ -66,34 +64,43 @@ pi install ./ # install the package for a user
66
64
  ```text
67
65
  /plan # toggle plan mode
68
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
69
68
  ```
70
69
 
71
- The model calls `enter_plan_mode` before a non-trivial task; `/plan` and
72
- `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.
73
74
 
74
75
  ## Tools
75
76
 
76
- 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:
77
81
 
78
- <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%">
79
83
 
80
- <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%">
81
85
 
82
- All five tools are `executionMode: "sequential"` and closed-world. Two are active
83
- in normal mode and three are `defaultActive: false`, so the plan-mode tool set
84
- 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.
85
92
 
86
- | Tool | Active | Purpose |
93
+ | Action | Active | Purpose |
87
94
  |---|---|---|
88
- | `enter_plan_mode` | normal mode | Ask to enter plan mode; empty schema, confirmed with `ctx.ui.confirm`. |
89
- | `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. |
90
- | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. |
91
- | `cancel_plan_mode` | plan mode | Leave plan mode without a plan file or a review. Empty schema. |
92
- | `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. |
93
100
 
94
- ### `enter_plan_mode`
101
+ ### `enter`
95
102
 
96
- 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,
97
104
  because the model is changing the mode on the user's behalf; a declined confirm
98
105
  answers "stayed in normal mode" and the session keeps write access. Already
99
106
  planning, it says so. With no UI it fails with an actionable message instead of
@@ -102,12 +109,12 @@ deciding for the user.
102
109
  On a remote turn the confirmation is a short consent sentence in the peer's
103
110
  language, and the notice names how to leave.
104
111
 
105
- ### `write_plan`
112
+ ### `write`
106
113
 
107
114
  `title` is the file-name slug, `content` the full markdown, and `plan_path`
108
115
  overwrites an existing plan when refining. The result carries the absolute path,
109
- the relative path, and the byte count — the absolute path is what `exit_plan_mode`
110
- 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.
111
118
 
112
119
  The file lands in
113
120
  `<agent-dir>/plans/<repo-key>/<YYYY-MM-DD-HHmm>-<slug>.md`, keyed by the
@@ -117,8 +124,8 @@ repository the store is `<agent-dir>/plans`. Plans are outside the project, so
117
124
  they are never untracked files and never appear in a `rewind` snapshot; a store
118
125
  an older version left in `<repo>/.pi/plans` is moved in once, with a notice.
119
126
 
120
- The description documents a canonical outline — a `# <Title>`, then `## Goal`,
121
- `## 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
122
129
  `## Verification` — and the model is guided to follow it. Every example in the
123
130
  template is the shape wanted rather than a description of it, and each section
124
131
  has one job: the goal is one sentence, the context is the facts that force the
@@ -141,7 +148,7 @@ A plan with more than 50 top-level steps in its `Steps` section is refused with
141
148
  the cap and the fix named, and nothing is written.
142
149
 
143
150
  The file is the source of truth. On approval the model follows the plan file's
144
- 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
145
152
  the reviewed artifact is the executed artifact. The text a refinement replaces is
146
153
  kept beside it, as a dotfile named for the plan and the revision
147
154
  (`.2026-10-26-1008-add-rate-limiting-r001.md`), up to the newest five: that is
@@ -152,9 +159,9 @@ the review never offers it for approval — and a refinement that changed nothin
152
159
  keeps nothing, because the copy would be the plan itself. The write's result names
153
160
  the revision it kept.
154
161
 
155
- ### `exit_plan_mode`
162
+ ### `exit`
156
163
 
157
- `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
158
165
  presents it for approval. A hand-edited or older file whose step list is over the
159
166
  cap is refused before the review screen. The user approves, keeps planning, asks
160
167
  for a refinement, or exits plan mode without approving; the last keeps the plan
@@ -174,19 +181,19 @@ the todo list:
174
181
  - The plan's `## Verification` section follows as the **last** todo item,
175
182
  summarized on one line (`Verify: …`) — a fenced block contributes its
176
183
  commands, not its fence lines. It is not a plan step, so it is ticked in the
177
- 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
178
185
  50-step cap.
179
186
  - A plan without a `Steps` heading keeps the older rule — the shallowest list in
180
187
  the document — so prose bullets in a plan that follows the outline never become
181
188
  todos, and its verification bullets are already covered.
182
189
  - Each seeded item is matched back to its step by text, so ticking a step with
183
- `set_plan_steps` completes the item too.
190
+ `steps` completes the item too.
184
191
  - An approval records the step texts it seeded, per plan path, as a `plan-seeded`
185
- 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
186
193
  mode, so a second round is the way back — and that second approval **replaces the
187
194
  leftovers its own earlier approval made**: an untouched item whose step was
188
195
  renamed or dropped goes, while one somebody started or finished is kept and
189
- 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
190
197
  list is stale when it refines a plan that was approved before.
191
198
 
192
199
  **Goal-aware.** While planning, the injected context restates a pursued session
@@ -198,15 +205,10 @@ the decision. A session with no goal at all is given the same objective, also
198
205
  without asking. An `active` or `achieved` goal is never rewritten: a final
199
206
  objective is the user's. No path spends a model turn.
200
207
 
201
- ### `cancel_plan_mode`
202
-
203
- Leaves plan mode without a plan file or a review. Restores write access, records
204
- no todo items or goal, and keeps any saved plan. Empty schema.
205
-
206
- ### `set_plan_steps`
208
+ ### `steps`
207
209
 
208
210
  Keep an approved plan current: `plan_path`, plus `updates`, each a 1-based `step`
209
- 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
210
212
  `expected` — the step's text, so an update whose step number moved is refused
211
213
  instead of ticking the wrong line. It is a single all-or-nothing write: a step out
212
214
  of range, or an `expected` text that no longer matches the file, refuses every
@@ -215,27 +217,32 @@ marker.
215
217
 
216
218
  The todo item approval seeded from a step is ticked with it: the file is written
217
219
  first and is authoritative, a renamed item is left alone and reported as
218
- unmatched, and a failed todo write is reported in the result. The tool refuses
219
- 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.
220
227
 
221
228
  ### Transcript
222
229
 
223
- The three tool rows reuse the transcript component, and `exit_plan_mode`
224
- separates the plan body from its header with a blank line; the collapse hint
225
- 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).
226
233
 
227
234
  ```
228
- enter_plan_mode requested plan mode
235
+ plan enter
229
236
  ✓ Plan mode enabled
230
237
  ```
231
238
 
232
239
  ```
233
- write_plan Add rate limiting
240
+ plan write Add rate limiting
234
241
  ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
235
242
  ```
236
243
 
237
244
  ```
238
- exit_plan_mode submitted a plan
245
+ plan exit 2026-10-09-1809-add-rate-limiting
239
246
  ✓ Plan approved
240
247
  ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
241
248
 
@@ -249,7 +256,7 @@ exit_plan_mode submitted a plan
249
256
 
250
257
  | Path | Who starts it | Confirmation |
251
258
  |---|---|---|
252
- | `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` |
253
260
  | `/plan` | the user | immediate |
254
261
  | `/plan <prompt>` | the user, with the task | immediate |
255
262
  | `/plan list` | the user, browsing the plans directory | immediate |
@@ -257,7 +264,7 @@ exit_plan_mode submitted a plan
257
264
  | `/plan share [stop]` | the user, handing the review to a teammate | immediate |
258
265
  | `/plan cancel` | the user, leaving plan mode without approving | immediate |
259
266
  | `/skill:init` | the user, creating or updating `AGENTS.md` | enters plan mode; write waits for plan approval |
260
- | `Ctrl+Alt+P` | the user | immediate |
267
+ | `Shift+Tab` (bytefly) | the user | immediate |
261
268
  | `--plan` | the user, at launch | immediate |
262
269
 
263
270
  `/plan` with no argument toggles plan mode; `/plan <prompt>` enters it (if it is
@@ -269,10 +276,13 @@ already runs read-only:
269
276
  ```
270
277
 
271
278
  `/plan <prompt>` never disables plan mode; only the argument-less `/plan` (or
272
- `Ctrl+Alt+P`) toggles. Because slash commands go through Pi directly, `/plan` is
273
- the toggle that works in every terminal (for example Zed's integrated terminal,
274
- which does not forward `Ctrl+Alt+P`). The footer shows `≡ plan` while plan mode
275
- 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.
276
286
 
277
287
  `/plan list` walks the plans directory and shows every plan with its title,
278
288
  modified time, and step progress, newest first — in the TUI as a docked list
@@ -293,7 +303,7 @@ never swallows one.
293
303
  `/plan share` prints the reviewer link and its QR, and `/plan share stop` revokes
294
304
  every link; the page's **Share** button does the same. With no browser review open
295
305
  it says so and points at `/plan review`. `/plan cancel` leaves plan mode without
296
- 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
297
307
  stays for a later review. Both are exact words — `share the tokens` and `cancel
298
308
  that idea` stay tasks — as is `list`.
299
309
 
@@ -312,7 +322,7 @@ approval. Its workflow is:
312
322
  └─ unavailable/cancelled ──> evidence + assumptions
313
323
  └─> draft: new file, exact edits, or an honest no-op
314
324
  └─> quality gate
315
- └─> write_plan ──> exit_plan_mode
325
+ └─> plan · write ──> plan · exit
316
326
  ├─ not approved ──> refine the same plan
317
327
  └─ approved ──────> write/edit the target
318
328
  └─ read back + quality checklist
@@ -368,9 +378,13 @@ session token, and the owner's token is the one that opened the page.
368
378
  | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
369
379
  | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
370
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
+
371
385
  ## Behaviour by mode
372
386
 
373
- 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`
374
388
  on it opens the browser review. RPC and other non-interactive runs get no screen.
375
389
 
376
390
  Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt instead: on a
@@ -393,10 +407,10 @@ its closing rule on a short terminal.
393
407
  Browser review is a local-turn feature: the server binds `127.0.0.1`, so a remote
394
408
  (WeChat) turn cannot reach it and `/plan review` there refuses with a notice
395
409
  instead of opening a server nobody can use. The browser's **Approve & run** is
396
- 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
397
411
  leaving plan mode; `/plan review` stays comment-only.
398
412
 
399
- 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
400
414
  abort signal to the review server, so an aborted turn returns "not approved"
401
415
  and stops listening instead of waiting on a page nobody is watching.
402
416
 
@@ -411,7 +425,7 @@ command. The read-only guarantee is layered:
411
425
  git commands through `bash` (chained with `&&`/`;`), `powershell` is blocked
412
426
  outright, and other mutating tools (worktree mutations, MCP tools) are blocked
413
427
  until you exit plan mode. `subagent` stays active but is forced read-only. The
414
- 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
415
429
  create or overwrite markdown plan files directly inside the plans directory —
416
430
  never dotfiles and never through a symlink.
417
431
  - **Capability policy.** A tool is allowed only when it is a known structured
@@ -423,18 +437,18 @@ command. The read-only guarantee is layered:
423
437
  file path is refused unless it is a known plan-safe reader. `path-guard.ts`
424
438
  scans nested arguments and path-shaped values, not just fixed top-level keys.
425
439
  - **Control-tool identity.** The `tool_call` guard exempts plan's own tools —
426
- `enter_plan_mode`, `cancel_plan_mode`, `write_plan`, and `exit_plan_mode` — only
440
+ the `plan` tool's `write`, `exit`, and `cancel` actions — only
427
441
  when the registered tool is this extension's, matched by source path, so a
428
442
  same-named tool from another extension cannot borrow the exemption.
429
- `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"
430
444
  is answered with "Already in plan mode" instead of being refused as not
431
445
  read-only; the policy still denies it, so it stays hidden while planning.
432
- - **Contained writes.** `write_plan` only writes markdown non-dotfiles whose
446
+ - **Contained writes.** `write` only writes markdown non-dotfiles whose
433
447
  real parent resolves inside the plans directory, and every plan write —
434
- create, refine, `set_plan_steps`, browser feedback — goes through a hidden
448
+ create, refine, `steps`, browser feedback — goes through a hidden
435
449
  temp file followed by a rename, so an interrupted write cannot leave a
436
450
  truncated plan.
437
- - **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`
438
452
  strips control characters from the plan body before rendering it in the
439
453
  transcript and in non-TUI notices; a plan file cannot drive the terminal.
440
454
  - **Loopback browser review.** The review server binds `127.0.0.1` and requires a
@@ -470,8 +484,19 @@ git diff -- extensions\plan # allowed: a Windows path separator
470
484
  git status && rm -rf x # blocked: the second segment is not git
471
485
  git status | sh # blocked: pipes are never allowed
472
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
473
489
  ```
474
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
+
475
500
  A refused shell command explains the fix: the working directory is already the
476
501
  project root (so `cd` is unnecessary), and files belong to the `read`, `grep`,
477
502
  `find`, and `ls` tools.
@@ -509,7 +534,7 @@ agent file that overrides it.
509
534
 
510
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%">
511
536
 
512
- `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
513
538
  rendered with Pi's own `Markdown` component and `getMarkdownTheme()`, so
514
539
  headings, lists, inline code, code blocks, links, and quotes carry the active
515
540
  theme's colours; task items get the shared `◼` (done) or `◻` (pending) marker,
@@ -637,7 +662,7 @@ file and returns them to the model.
637
662
  Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
638
663
  documentation, not feedback. The file is the only copy of a saved comment; the
639
664
  ones this tab has not saved yet live in its own `sessionStorage` and nowhere else.
640
- - **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
641
666
  through `carryFeedback`, which re-attaches each comment by the strongest evidence
642
667
  that survived: the anchored block's text, per occurrence, so a plan that repeats
643
668
  a step keeps the comments apart; then the block whose wording still overlaps it —
@@ -750,13 +775,13 @@ both.
750
775
  keeps no registry, so it sees an external edit or a hand-copied plan
751
776
  immediately; it lists a file over the 256 KiB plan limit without reading it,
752
777
  and does not index the agent directory's plans while a worktree is active.
753
- - Approval tracks at most 50 steps: `write_plan` refuses a larger plan, and
754
- `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
755
780
  dropped. A step longer than 200 characters is shortened in the todo list and
756
781
  the approval listing — a display rule only, because the plan file always keeps
757
782
  the full text.
758
783
  - The todo item for a step is matched by its text. An item renamed after
759
- 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
760
785
  its result; the plan file is still updated.
761
786
  - Browser review needs a local browser: the server is loopback-only, so a remote
762
787
  turn cannot drive it.
@@ -781,12 +806,18 @@ both.
781
806
  upgrade reads differently there.
782
807
  - A shared review holds at most eight links. Each is a live bearer credential, so
783
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.
784
815
 
785
816
  ## Non-goals
786
817
 
787
818
  - **Not a sandbox.** Extensions share Pi's OS permissions.
788
819
  - **No progress tracker of record.** `todo` owns completion state; the plan
789
- 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
790
821
  back out.
791
822
  - **No auto-execution.** Approval hands control back to the model.
792
823
 
@@ -794,10 +825,11 @@ both.
794
825
 
795
826
  | Integration point | How |
796
827
  |---|---|
797
- | 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`. |
798
- | 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. |
799
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. |
800
- | 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. |
801
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. |
802
834
  | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
803
835
 
@@ -805,16 +837,16 @@ both.
805
837
 
806
838
  - **Consent is one dialog, whichever extension asks.** The goal workflow offers
807
839
  plan mode as a phase of the goal, but a command context cannot call
808
- `enter_plan_mode` (`ExtensionCommandContext` has no `executeTool`). So the
840
+ the `plan` tool (`ExtensionCommandContext` has no `executeTool`). So the
809
841
  consent sentence left `policy.ts` for `lib/plan-entry.ts`, and the entry itself
810
842
  is a `pi.events` request: the asker shows the question, plan enables silently
811
- 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
812
844
  same sentence, so the model-initiated path is unchanged and the two texts
813
845
  cannot drift. A request that nobody answers resolves `false` rather than
814
846
  hanging, so `goal` loaded alone still works.
815
847
  - **One review loop owns the human gate.** `review.ts` asks however the session
816
848
  is attached — TUI screen, dialog plus editor, remote file plus digest, or
817
- browser — and returns one `ReviewDecision`, so `exit_plan_mode` and
849
+ browser — and returns one `ReviewDecision`, so `exit` and
818
850
  `/plan review` no longer carry two copies of the browser lifecycle and the
819
851
  tool's own `for(;;)` with a `continue` per outcome. The loop also owns the
820
852
  baseline rule (mark what the screen showed, after it showed it) and forwards
@@ -822,9 +854,11 @@ both.
822
854
  - **Two tools, not one.** `EnterPlanMode`/`ExitPlanMode` are a pair: entering is
823
855
  consent to explore read-only, exiting is consent to act. A single toggle tool
824
856
  cannot express "the model wants to plan but the user declines".
825
- - **User entry is instant, model entry confirmed.** `/plan`, `Ctrl+Alt+P`, and
826
- `--plan` are the user's own action and take effect at once; `enter_plan_mode`
827
- 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.
828
862
  - **Review the file, decide in a screen.** The plan is a file, so it is the
829
863
  artifact already on screen. `ctx.ui.confirm()` is a non-scrollable selector
830
864
  that pushes buttons off-screen for a long plan, so the TUI gets a scrollable
@@ -832,7 +866,7 @@ both.
832
866
  renders the body with Pi's `Markdown` component so the plan reads like any
833
867
  other markdown in the app; the plan text is sanitized before it reaches the
834
868
  renderer, so only the theme's own ANSI survives.
835
- - **`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
836
870
  capability policy and path backstop stay intact; a `plan_path` must name a
837
871
  markdown non-dotfile whose real parent resolves inside the plans directory,
838
872
  so symlinks and the directory's own `.gitignore` are never writable targets.
@@ -855,7 +889,7 @@ both.
855
889
  authoritative, so a failed nested write is reported in the result text — once
856
890
  one artifact has changed there is no previous state to hand back.
857
891
  - **A cap is enforced where it can still be fixed.** Both refusal points —
858
- `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,
859
893
  because those are the moments the model can still regroup the steps. A note on
860
894
  a successful write would arrive after the file existed and the review had
861
895
  already listed every step, which is exactly when the overflow becomes lost
@@ -869,7 +903,7 @@ both.
869
903
  a session entry rather than the previous text, so the markers survive a resume
870
904
  without copying the plan file into the branch, and the reader and the writer
871
905
  share one normalization so a tab cannot read as a change.
872
- - **The outline guides, it does not enforce.** `write_plan` documents the
906
+ - **The outline guides, it does not enforce.** `write` documents the
873
907
  canonical shape in its description and stores whatever markdown it is given;
874
908
  no section is required and nothing is rewritten. The description names the two
875
909
  headings that do carry behavior — `Steps` is what approval reads as steps, and
@@ -881,7 +915,7 @@ both.
881
915
  source-line map, so `tui.ts` splits the body at its headings and renders each
882
916
  section separately. That gives exact offsets for the current-section label and
883
917
  `n`/`p`; the single-section case is byte-for-byte the old render.
884
- - **`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
885
919
  steps' checkboxes, in an existing markdown plan inside the plans directory
886
920
  (`isContainedPlan`), in a single all-or-nothing write — refusing every entry
887
921
  if any step is out of range or an entry's `expected` text no longer matches —
@@ -938,20 +972,21 @@ both.
938
972
 
939
973
  | File | Responsibility |
940
974
  |---|---|
941
- | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
942
- | `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`. |
943
977
  | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
944
978
  | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
945
- | `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. |
946
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. |
947
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. |
948
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. |
949
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. |
950
985
  | `format.ts` | Pure result/approval text: the plan preview, the step-cap diagnosis, step labels, the merge summary, and the verification note. |
951
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. |
952
- | `runtime.ts` | Enabled state, tool gating, persistence (mode and review baseline), control-tool identity, footer status. |
953
- | `tools.ts` | Registration entry: composes the five tool registrations in the order the session sees them. |
954
- | `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). |
955
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. |
956
991
  | `review-prompt.ts` | Pure rendering and parsing for the compact remote review: one action table behind both the prompt and the reply parser. |
957
992
  | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, `/plan list`, `/plan share [stop]`, and `/plan cancel`. |
@@ -959,6 +994,8 @@ both.
959
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). |
960
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. |
961
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. |
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 |
962
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`. |
963
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. |
964
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. |
@@ -977,7 +1014,7 @@ both.
977
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. |
978
1015
  | `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
979
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. |
980
- | `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`). |
981
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`). |
982
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. |
983
1020
  | `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
@@ -988,7 +1025,7 @@ both.
988
1025
  | [`../../lib/qr.ts`](../../lib/qr.ts) | QR matrix, terminal half-blocks, and inline SVG for the share link (shared with `wechat`). |
989
1026
  | [`../../lib/http/`](../../lib/http/) | The one loopback server entry, the bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
990
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. |
991
- | [`../../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). |
992
1029
  | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |
993
1030
  | `../../lib/tui/chrome.ts` | Shared screen header, viewport-row helper, and styled key-hint footer. |
994
1031
 
@@ -998,16 +1035,20 @@ both.
998
1035
  bun test extensions/plan
999
1036
  ```
1000
1037
 
1001
- Twelve co-located suites, declared in `test/suite-manifest.ts`:
1038
+ Thirteen co-located suites, declared in `test/suite-manifest.ts`:
1002
1039
 
1003
- - `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
1004
- writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
1005
- 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.
1006
1044
  - `feedback.test.ts` — the comment codec: authored markers, stripping and
1007
1045
  parsing, who a reviewer's write is signed by, carrying feedback onto a
1008
1046
  revision, and displaying stored comments.
1009
- - `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
1010
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.
1011
1052
  - `review-prompt.test.ts` — the compact remote prompt and its reply parser
1012
1053
  against one action table.
1013
1054
  - `web-*.test.ts` — the browser review, one capability per suite: the comment
@@ -1023,9 +1064,9 @@ Twelve co-located suites, declared in `test/suite-manifest.ts`:
1023
1064
  - `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
1024
1065
  its cap, and the reasons a share is unavailable.
1025
1066
  - `commands.test.ts` — `/plan review`, `/plan list`, `/plan share`, and the one
1026
- 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
1027
1068
  baseline rules.
1028
- - `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,
1029
1070
  the review baseline, branch restore, the read-only git allowlist, and the
1030
1071
  path-argument scan.
1031
1072
  - `tui.test.ts` — the review screen at fixed widths: Markdown body, scrolling,
@@ -1033,6 +1074,5 @@ Twelve co-located suites, declared in `test/suite-manifest.ts`:
1033
1074
  refine editor, and the changed-section markers.
1034
1075
  - `sections.test.ts` — the fence-aware scan: heading shape, fences, the preamble,
1035
1076
  source lines and levels, and the occurrence key.
1036
- - `extension.test.ts` — the Pi wiring: registration, the entry request, the
1037
- session lifecycle, the tool-call guard, injected context, the command, and the
1038
- 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.