@ilya-lesikov/pi-pi 0.1.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 (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,228 @@
1
+ # Plannotator for Pi
2
+
3
+ Plannotator integration for the [Pi coding agent](https://github.com/earendil-works/pi). Adds file-based plan mode with a visual browser UI for reviewing, annotating, and approving agent plans.
4
+
5
+ ## Install
6
+
7
+ **From npm** (recommended):
8
+
9
+ ```bash
10
+ pi install npm:@plannotator/pi-extension
11
+ ```
12
+
13
+ **From source:**
14
+
15
+ ```bash
16
+ git clone https://github.com/backnotprop/plannotator.git
17
+ pi install ./plannotator/apps/pi-extension
18
+ ```
19
+
20
+ **Try without installing:**
21
+
22
+ ```bash
23
+ pi -e npm:@plannotator/pi-extension
24
+ ```
25
+
26
+ ## Build from source
27
+
28
+ If installing from a local clone, build the HTML assets first:
29
+
30
+ ```bash
31
+ cd plannotator
32
+ bun install
33
+ bun run build:pi
34
+ ```
35
+
36
+ This builds the plan review and code review UIs and copies them into `apps/pi-extension/`.
37
+
38
+ ## Usage
39
+
40
+ ### Plan mode
41
+
42
+ Start Pi in plan mode:
43
+
44
+ ```bash
45
+ pi --plan
46
+ ```
47
+
48
+ Or toggle it during a session with `/plannotator` or `Ctrl+Alt+P`. The command accepts an optional file path argument (`/plannotator plans/auth.md`) or prompts you to choose one interactively.
49
+
50
+ In plan mode the agent is restricted — destructive commands are blocked, writes are limited to the plan file. It explores your codebase, then writes a plan using markdown checklists:
51
+
52
+ ```markdown
53
+ - [ ] Add validation to the login form
54
+ - [ ] Write tests for the new validation logic
55
+ - [ ] Update error messages in the UI
56
+ ```
57
+
58
+ When the agent calls `plannotator_submit_plan`, the Plannotator UI opens in your browser. You can:
59
+
60
+ - **Approve** the plan to begin execution
61
+ - **Deny with annotations** to send structured feedback back to the agent
62
+ - **Approve with notes** to proceed but include implementation guidance
63
+
64
+ The agent iterates on the plan until you approve, then executes with full tool access. On resubmission, Plan Diff highlights what changed since the previous version.
65
+
66
+ ### Configuring per-phase behavior
67
+
68
+ Plannotator loads configuration in three layers:
69
+
70
+ 1. Built-in base config shipped with the package: `plannotator.json`
71
+ 2. Global user config: `~/.pi/agent/plannotator.json`
72
+ 3. Project-local config: `<cwd>/.pi/plannotator.json`
73
+
74
+ Later layers overwrite earlier ones. If a field is omitted, it inherits the value from lower-precedence layers. If a value is set to `null`, an empty string, or an empty array, it clears the inherited value instead of merging it. You can also set `defaults` or an entire phase object to `null` to clear all inherited settings from lower-precedence layers.
75
+
76
+ #### Top-level shape
77
+
78
+ ```json
79
+ {
80
+ "defaults": {
81
+ "model": { "provider": "anthropic", "id": "claude-sonnet-4-5" },
82
+ "thinking": "medium",
83
+ "activeTools": ["read", "bash"],
84
+ "statusLabel": "Ready",
85
+ "systemPrompt": "Optional prompt template"
86
+ },
87
+ "phases": {
88
+ "planning": {
89
+ "model": null,
90
+ "thinking": null,
91
+ "activeTools": ["grep", "find", "ls", "plannotator_submit_plan"],
92
+ "statusLabel": "⏸ plan",
93
+ "systemPrompt": "[PLANNING]\nPlan file: ${planFilePath}"
94
+ },
95
+ "executing": {
96
+ "model": { "provider": "anthropic", "id": "claude-sonnet-4-5" },
97
+ "thinking": "high",
98
+ "activeTools": [],
99
+ "statusLabel": "",
100
+ "systemPrompt": "[EXECUTING]\nRemaining steps:\n${todoList}"
101
+ },
102
+ "reviewing": {
103
+ "systemPrompt": "..."
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ #### Option reference
110
+
111
+ | Option | Type | Meaning |
112
+ |--------|------|---------|
113
+ | `defaults` | object | Base values applied to every phase before phase-specific overrides |
114
+ | `phases` | object | Phase-specific overrides |
115
+ | `phases.planning` | object | Settings for planning mode |
116
+ | `phases.executing` | object | Settings for execution mode |
117
+ | `phases.reviewing` | object | Reserved for future review-mode customization |
118
+ | `model` | `{ provider, id }` \| `null` | Sets the model for the phase; `null` leaves the current model unchanged |
119
+ | `thinking` | `minimal` \| `low` \| `medium` \| `high` \| `xhigh` \| `null` | Sets the thinking level; `null` leaves the current level unchanged |
120
+ | `activeTools` | string[] \| `null` | Extra tools to enable for the phase; `[]` or `null` means no extra phase tools |
121
+ | `statusLabel` | string \| `null` | Optional UI label for the phase; empty/null clears it |
122
+ | `systemPrompt` | string \| `null` | Phase system prompt template; empty/null disables prompt injection |
123
+
124
+ #### Prompt variables
125
+
126
+ Use these inside `systemPrompt` strings:
127
+
128
+ - `${planFilePath}` — current plan file path
129
+ - `${todoList}` — remaining checklist items as markdown checkboxes
130
+ - `${completedCount}` — completed checklist count
131
+ - `${totalCount}` — total checklist count
132
+ - `${remainingCount}` — remaining checklist count
133
+ - `${phase}` — current runtime phase (`planning`, `executing`, `reviewing`, or `idle`)
134
+
135
+ #### Behavior notes
136
+
137
+ - Unknown template variables trigger a warning in the UI and are rendered as empty strings.
138
+ - `activeTools` are additive with the tools currently active in the session, so Plannotator still preserves tools provided by other extensions.
139
+ - Execution progress remains dynamic (`[DONE:n]` + checklist tracking), even if `statusLabel` is set.
140
+
141
+ #### Example files
142
+
143
+ - Built-in base config shipped with the package: `apps/pi-extension/plannotator.json`
144
+ - Global user override: `~/.pi/agent/plannotator.json`
145
+ - Project-local override: `<cwd>/.pi/plannotator.json`
146
+
147
+ ### Code review
148
+
149
+ Run `/plannotator-review` to open your current git changes in the code review UI. Annotate specific lines, switch between diff views (uncommitted, staged, last commit, branch), and submit feedback that gets sent to the agent.
150
+
151
+ ### Shared Plannotator event API
152
+
153
+ Plannotator also listens on the shared `plannotator:request` event channel so other extensions can reuse the same browser review flows without importing Plannotator internals.
154
+
155
+ Supported actions and payloads:
156
+
157
+ - `plan-review`: `{ planContent, planFilePath? }`
158
+ - `review-status`: `{ reviewId }`
159
+ - `code-review`: `{ cwd?, defaultBranch?, diffType? }`
160
+ - `annotate`: `{ filePath, markdown?, mode?, folderPath? }`
161
+ - `annotate-last`: `{ markdown? }`
162
+ - `archive`: `{ customPlanPath? }`
163
+
164
+ Plan review is asynchronous:
165
+
166
+ - callers send `plannotator:request` with action `plan-review`
167
+ - Plannotator opens the browser review and immediately responds with `{ status: "handled", result: { status: "pending", reviewId } }`
168
+ - when the human approves or rejects in the browser, Plannotator emits `plannotator:review-result` with `{ reviewId, approved, feedback, savedPath?, agentSwitch?, permissionMode? }`
169
+ - callers can query `review-status` with the same `reviewId` to recover from startup races or session restarts
170
+
171
+ The other shared actions remain request/response flows. Payloads are intentionally minimal and only include fields the shared implementation actually uses.
172
+
173
+ ### Markdown annotation
174
+
175
+ Run `/plannotator-annotate <file.md>` to open any markdown file in the annotation UI. Useful for reviewing documentation or design specs with the agent.
176
+
177
+ ### Annotate last message
178
+
179
+ Run `/plannotator-last` to annotate the agent's most recent response. The message opens in the annotation UI where you can highlight text, add comments, and send structured feedback back to the agent.
180
+
181
+ ### Archive browser
182
+
183
+ The Plannotator archive browser is available through the shared event API as `archive`, which opens the saved plan/decision browser for future callers. The orchestrator does not expose a dedicated archive command yet.
184
+
185
+ ### Progress tracking
186
+
187
+ During execution, the agent marks completed steps with `[DONE:n]` markers. Progress is shown in the status line and as a checklist widget in the terminal.
188
+
189
+ ## Commands
190
+
191
+ | Command | Description |
192
+ |---------|-------------|
193
+ | `/plannotator` | Toggle plan mode. The agent writes a markdown plan file anywhere in the working directory and submits its path |
194
+ | `/plannotator-review` | Open code review UI for current changes |
195
+ | `/plannotator-annotate <file>` | Open markdown file in annotation UI |
196
+ | `/plannotator-last` | Annotate the last assistant message |
197
+
198
+ ## Flags
199
+
200
+ | Flag | Description |
201
+ |------|-------------|
202
+ | `--plan` | Start in plan mode |
203
+
204
+ ## Keyboard shortcuts
205
+
206
+ | Shortcut | Description |
207
+ |----------|-------------|
208
+ | `Ctrl+Alt+P` | Toggle plan mode |
209
+
210
+ ## How it works
211
+
212
+ The extension manages a state machine: **idle** → **planning** → **executing** → **idle**.
213
+
214
+ During **planning**:
215
+ - All tools from other extensions remain available
216
+ - Bash is unrestricted — the agent is guided by the system prompt not to run destructive commands
217
+ - Writes and edits restricted to the plan file only
218
+
219
+ During **executing**:
220
+ - Full tool access: `read`, `bash`, `edit`, `write`
221
+ - Progress tracked via `[DONE:n]` markers in agent responses
222
+ - Plan re-read from disk each turn to stay current
223
+
224
+ State persists across session restarts via Pi's `appendEntry` API.
225
+
226
+ ## Requirements
227
+
228
+ - [Pi](https://github.com/earendil-works/pi) >= 0.74.0
@@ -0,0 +1,128 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ type AssistantTextBlock = { type?: string; text?: string };
4
+
5
+ type AssistantMessageLike = {
6
+ role?: unknown;
7
+ content?: unknown;
8
+ };
9
+
10
+ type SessionEntryLike = {
11
+ id: string;
12
+ type: string;
13
+ timestamp?: unknown;
14
+ message?: AssistantMessageLike;
15
+ };
16
+
17
+ export type LastAssistantMessageSnapshot = {
18
+ entryId: string;
19
+ text: string;
20
+ };
21
+
22
+ export type RecentAssistantMessage = {
23
+ messageId: string;
24
+ text: string;
25
+ timestamp?: string;
26
+ };
27
+
28
+ // Pi's SDK currently types `SessionEntryBase.timestamp` as `string`, but the
29
+ // picker contract everywhere else is ISO and we don't want a silent drift if
30
+ // that ever changes. Accept string/number(ms)/Date; drop anything else.
31
+ function normalizeTimestamp(value: unknown): string | undefined {
32
+ if (value instanceof Date) {
33
+ return Number.isNaN(value.getTime()) ? undefined : value.toISOString();
34
+ }
35
+ if (typeof value === "number" && Number.isFinite(value)) {
36
+ const d = new Date(value);
37
+ return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
38
+ }
39
+ if (typeof value === "string" && value.trim()) {
40
+ const d = new Date(value);
41
+ return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
42
+ }
43
+ return undefined;
44
+ }
45
+
46
+ function isAssistantMessage(message: AssistantMessageLike): message is { role: "assistant"; content: AssistantTextBlock[] } {
47
+ return message.role === "assistant" && Array.isArray(message.content);
48
+ }
49
+
50
+ function getTextContent(message: { content: AssistantTextBlock[] }): string {
51
+ return message.content
52
+ .filter((block): block is { type: "text"; text: string } => block.type === "text")
53
+ .map((block) => block.text)
54
+ .join("\n");
55
+ }
56
+
57
+ function isRecord(value: unknown): value is Record<string, unknown> {
58
+ return typeof value === "object" && value !== null;
59
+ }
60
+
61
+ export function getAssistantMessageText(message: unknown): string | null {
62
+ if (!isRecord(message)) return null;
63
+ const candidate = { role: message.role, content: message.content };
64
+ if (!isAssistantMessage(candidate)) return null;
65
+ const text = getTextContent(candidate);
66
+ return text.trim() ? text : null;
67
+ }
68
+
69
+ function getCurrentBranch(ctx: ExtensionContext): SessionEntryLike[] {
70
+ return ctx.sessionManager.getBranch() as SessionEntryLike[];
71
+ }
72
+
73
+ export function getLastAssistantMessageSnapshot(ctx: ExtensionContext): LastAssistantMessageSnapshot | null {
74
+ // "Last" means the active conversation branch, not the newest message anywhere
75
+ // in the append-only session file.
76
+ const branch = getCurrentBranch(ctx);
77
+ for (let i = branch.length - 1; i >= 0; i--) {
78
+ const entry = branch[i];
79
+ if (entry.type === "message" && entry.message) {
80
+ const text = getAssistantMessageText(entry.message);
81
+ if (text) return { entryId: entry.id, text };
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ export function getLastAssistantMessageText(ctx: ExtensionContext): string | null {
88
+ return getLastAssistantMessageSnapshot(ctx)?.text ?? null;
89
+ }
90
+
91
+ export function findAssistantMessageByEntryId(
92
+ ctx: ExtensionContext,
93
+ entryId: string,
94
+ ): LastAssistantMessageSnapshot | null {
95
+ const branch = getCurrentBranch(ctx);
96
+ for (const entry of branch) {
97
+ if (entry.id !== entryId || entry.type !== "message" || !entry.message) continue;
98
+ const text = getAssistantMessageText(entry.message);
99
+ if (text) return { entryId: entry.id, text };
100
+ }
101
+ return null;
102
+ }
103
+
104
+ export function getRecentAssistantMessages(
105
+ ctx: ExtensionContext,
106
+ limit: number,
107
+ ): RecentAssistantMessage[] {
108
+ const branch = getCurrentBranch(ctx);
109
+ const out: RecentAssistantMessage[] = [];
110
+ for (let i = branch.length - 1; i >= 0 && out.length < limit; i--) {
111
+ const entry = branch[i];
112
+ if (entry.type !== "message" || !entry.message) continue;
113
+ const text = getAssistantMessageText(entry.message);
114
+ if (!text) continue;
115
+ out.push({ messageId: entry.id, text, timestamp: normalizeTimestamp(entry.timestamp) });
116
+ }
117
+ return out;
118
+ }
119
+
120
+ export function hasSessionMovedPastEntry(ctx: ExtensionContext, entryId: string): boolean {
121
+ if (!ctx.isIdle()) return true;
122
+
123
+ const branch = getCurrentBranch(ctx);
124
+ const index = branch.findIndex((entry) => entry.id === entryId);
125
+ if (index === -1) return true;
126
+
127
+ return branch.slice(index + 1).some((entry) => entry.type === "message");
128
+ }
@@ -0,0 +1,166 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { loadPlannotatorConfig, formatTodoList, renderTemplate, resolvePhaseProfile } from "./config";
6
+
7
+ const tempDirs: string[] = [];
8
+ const originalHome = process.env.HOME;
9
+
10
+ function makeTempDir(prefix: string): string {
11
+ const dir = mkdtempSync(join(tmpdir(), prefix));
12
+ tempDirs.push(dir);
13
+ return dir;
14
+ }
15
+
16
+ afterEach(() => {
17
+ if (originalHome === undefined) {
18
+ delete process.env.HOME;
19
+ } else {
20
+ process.env.HOME = originalHome;
21
+ }
22
+
23
+ for (const dir of tempDirs.splice(0)) {
24
+ rmSync(dir, { recursive: true, force: true });
25
+ }
26
+ });
27
+
28
+ describe("plannotator config", () => {
29
+ test("loads the shipped internal base config", () => {
30
+ const cwdDir = makeTempDir("plannotator-config-base-");
31
+ process.env.HOME = makeTempDir("plannotator-config-home-base-");
32
+
33
+ const loaded = loadPlannotatorConfig(cwdDir);
34
+ const planning = resolvePhaseProfile(loaded.config, "planning");
35
+
36
+ expect(loaded.warnings).toEqual([]);
37
+ expect(planning.statusLabel).toBe("⏸ plan");
38
+ expect(planning.activeTools).toEqual(["grep", "find", "ls", "plannotator_submit_plan"]);
39
+ });
40
+
41
+ test("allows a project config to clear an inherited phase with null", () => {
42
+ const homeDir = makeTempDir("plannotator-config-home-null-");
43
+ const cwdDir = makeTempDir("plannotator-config-cwd-null-");
44
+ process.env.HOME = homeDir;
45
+
46
+ const globalConfigDir = join(homeDir, ".pi", "agent");
47
+ const projectConfigDir = join(cwdDir, ".pi");
48
+ mkdirSync(globalConfigDir, { recursive: true });
49
+ mkdirSync(projectConfigDir, { recursive: true });
50
+ writeFileSync(
51
+ join(globalConfigDir, "plannotator.json"),
52
+ JSON.stringify({
53
+ phases: { planning: { statusLabel: "global", activeTools: ["bash"] } },
54
+ }),
55
+ "utf-8",
56
+ );
57
+ writeFileSync(
58
+ join(projectConfigDir, "plannotator.json"),
59
+ JSON.stringify({
60
+ phases: { planning: null },
61
+ }),
62
+ "utf-8",
63
+ );
64
+
65
+ const loaded = loadPlannotatorConfig(cwdDir);
66
+ const planning = resolvePhaseProfile(loaded.config, "planning");
67
+
68
+ expect(loaded.warnings).toEqual([]);
69
+ expect(planning.statusLabel).toBeUndefined();
70
+ expect(planning.activeTools).toBeUndefined();
71
+ });
72
+
73
+ test("loads global and project configs with project precedence", () => {
74
+ const homeDir = makeTempDir("plannotator-config-home-");
75
+ const cwdDir = makeTempDir("plannotator-config-cwd-");
76
+ process.env.HOME = homeDir;
77
+
78
+ const globalConfigDir = join(homeDir, ".pi", "agent");
79
+ const projectConfigDir = join(cwdDir, ".pi");
80
+ mkdirSync(globalConfigDir, { recursive: true });
81
+ mkdirSync(projectConfigDir, { recursive: true });
82
+ writeFileSync(
83
+ join(globalConfigDir, "plannotator.json"),
84
+ JSON.stringify({
85
+ defaults: {
86
+ thinking: "low",
87
+ model: { provider: "anthropic", id: "claude-sonnet-4-5" },
88
+ },
89
+ phases: { planning: { statusLabel: "global", activeTools: ["bash"] } },
90
+ }),
91
+ "utf-8",
92
+ );
93
+ writeFileSync(
94
+ join(projectConfigDir, "plannotator.json"),
95
+ JSON.stringify({
96
+ defaults: { thinking: null, model: null },
97
+ phases: { planning: { statusLabel: "project", activeTools: [] } },
98
+ }),
99
+ "utf-8",
100
+ );
101
+
102
+ const loaded = loadPlannotatorConfig(cwdDir);
103
+ const planning = resolvePhaseProfile(loaded.config, "planning");
104
+
105
+ expect(loaded.warnings).toEqual([]);
106
+ expect(planning.thinking).toBeUndefined();
107
+ expect(planning.model).toBeUndefined();
108
+ expect(planning.statusLabel).toBe("project");
109
+ expect(planning.activeTools).toEqual([]);
110
+ });
111
+
112
+ test("treats empty strings as clearing values", () => {
113
+ const profile = resolvePhaseProfile(
114
+ {
115
+ defaults: { statusLabel: "base", systemPrompt: "base prompt", activeTools: ["bash"] },
116
+ phases: { planning: { statusLabel: "", systemPrompt: "", activeTools: [] } },
117
+ },
118
+ "planning",
119
+ );
120
+
121
+ expect(profile.statusLabel).toBeUndefined();
122
+ expect(profile.systemPrompt).toBeUndefined();
123
+ expect(profile.activeTools).toEqual([]);
124
+ });
125
+
126
+ test("allows clearing an entire phase with null", () => {
127
+ const profile = resolvePhaseProfile(
128
+ {
129
+ defaults: { thinking: "low", activeTools: ["bash"], statusLabel: "base" },
130
+ phases: { planning: null },
131
+ },
132
+ "planning",
133
+ );
134
+
135
+ expect(profile.thinking).toBe("low");
136
+ expect(profile.activeTools).toEqual(["bash"]);
137
+ expect(profile.statusLabel).toBe("base");
138
+ });
139
+
140
+ test("renders prompt templates and reports unknown variables", () => {
141
+ const rendered = renderTemplate("Hello ${name} ${missing}", {
142
+ planFilePath: "PLAN.md",
143
+ todoList: "- [ ] A",
144
+ completedCount: 1,
145
+ totalCount: 2,
146
+ remainingCount: 1,
147
+ phase: "planning",
148
+ });
149
+
150
+ expect(rendered.text).toBe("Hello ");
151
+ expect(rendered.unknownVariables).toEqual(["name", "missing"]);
152
+ });
153
+
154
+ test("formats todo lists from checklist items", () => {
155
+ const stats = formatTodoList([
156
+ { step: 1, text: "First", completed: true },
157
+ { step: 2, text: "Second", completed: false },
158
+ { step: 3, text: "Third", completed: false },
159
+ ]);
160
+
161
+ expect(stats.completedCount).toBe(1);
162
+ expect(stats.totalCount).toBe(3);
163
+ expect(stats.remainingCount).toBe(2);
164
+ expect(stats.todoList).toBe("- [ ] 2. Second\n- [ ] 3. Third");
165
+ });
166
+ });