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