pi-ui-extend 0.1.1

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 (420) hide show
  1. package/README.md +307 -0
  2. package/bin/pix.mjs +219 -0
  3. package/dist/app/app.d.ts +96 -0
  4. package/dist/app/app.js +871 -0
  5. package/dist/app/blink-controller.d.ts +23 -0
  6. package/dist/app/blink-controller.js +82 -0
  7. package/dist/app/cli.d.ts +8 -0
  8. package/dist/app/cli.js +83 -0
  9. package/dist/app/clipboard.d.ts +1 -0
  10. package/dist/app/clipboard.js +24 -0
  11. package/dist/app/command-controller.d.ts +18 -0
  12. package/dist/app/command-controller.js +58 -0
  13. package/dist/app/command-host.d.ts +44 -0
  14. package/dist/app/command-host.js +1 -0
  15. package/dist/app/command-model-actions.d.ts +12 -0
  16. package/dist/app/command-model-actions.js +176 -0
  17. package/dist/app/command-navigation-actions.d.ts +19 -0
  18. package/dist/app/command-navigation-actions.js +267 -0
  19. package/dist/app/command-registry.d.ts +32 -0
  20. package/dist/app/command-registry.js +225 -0
  21. package/dist/app/command-runtime.d.ts +5 -0
  22. package/dist/app/command-runtime.js +32 -0
  23. package/dist/app/command-session-actions.d.ts +20 -0
  24. package/dist/app/command-session-actions.js +295 -0
  25. package/dist/app/constants.d.ts +52 -0
  26. package/dist/app/constants.js +103 -0
  27. package/dist/app/conversation-entry-renderer.d.ts +21 -0
  28. package/dist/app/conversation-entry-renderer.js +81 -0
  29. package/dist/app/conversation-shell-renderer.d.ts +5 -0
  30. package/dist/app/conversation-shell-renderer.js +43 -0
  31. package/dist/app/conversation-tool-renderer.d.ts +16 -0
  32. package/dist/app/conversation-tool-renderer.js +216 -0
  33. package/dist/app/conversation-viewport.d.ts +55 -0
  34. package/dist/app/conversation-viewport.js +252 -0
  35. package/dist/app/dcp-stats.d.ts +2 -0
  36. package/dist/app/dcp-stats.js +116 -0
  37. package/dist/app/editor-layout-renderer.d.ts +31 -0
  38. package/dist/app/editor-layout-renderer.js +211 -0
  39. package/dist/app/editor-panels.d.ts +4 -0
  40. package/dist/app/editor-panels.js +130 -0
  41. package/dist/app/extension-actions-controller.d.ts +22 -0
  42. package/dist/app/extension-actions-controller.js +60 -0
  43. package/dist/app/extension-event-bus.d.ts +3 -0
  44. package/dist/app/extension-event-bus.js +23 -0
  45. package/dist/app/extension-ui-controller.d.ts +57 -0
  46. package/dist/app/extension-ui-controller.js +532 -0
  47. package/dist/app/file-link-opener.d.ts +2 -0
  48. package/dist/app/file-link-opener.js +66 -0
  49. package/dist/app/file-links.d.ts +10 -0
  50. package/dist/app/file-links.js +117 -0
  51. package/dist/app/guards.d.ts +3 -0
  52. package/dist/app/guards.js +9 -0
  53. package/dist/app/icons.d.ts +37 -0
  54. package/dist/app/icons.js +97 -0
  55. package/dist/app/id.d.ts +1 -0
  56. package/dist/app/id.js +4 -0
  57. package/dist/app/image-click-targets.d.ts +5 -0
  58. package/dist/app/image-click-targets.js +32 -0
  59. package/dist/app/image-opener.d.ts +2 -0
  60. package/dist/app/image-opener.js +64 -0
  61. package/dist/app/input-action-controller.d.ts +47 -0
  62. package/dist/app/input-action-controller.js +209 -0
  63. package/dist/app/input-controller.d.ts +60 -0
  64. package/dist/app/input-controller.js +425 -0
  65. package/dist/app/input-paste-handler.d.ts +27 -0
  66. package/dist/app/input-paste-handler.js +146 -0
  67. package/dist/app/menu-items-controller.d.ts +32 -0
  68. package/dist/app/menu-items-controller.js +182 -0
  69. package/dist/app/message-content.d.ts +8 -0
  70. package/dist/app/message-content.js +115 -0
  71. package/dist/app/model-ref.d.ts +13 -0
  72. package/dist/app/model-ref.js +50 -0
  73. package/dist/app/model-usage-controller.d.ts +35 -0
  74. package/dist/app/model-usage-controller.js +99 -0
  75. package/dist/app/model-usage-status.d.ts +125 -0
  76. package/dist/app/model-usage-status.js +749 -0
  77. package/dist/app/mouse-controller.d.ts +182 -0
  78. package/dist/app/mouse-controller.js +968 -0
  79. package/dist/app/native-modifiers.d.ts +3 -0
  80. package/dist/app/native-modifiers.js +60 -0
  81. package/dist/app/nerd-font-controller.d.ts +11 -0
  82. package/dist/app/nerd-font-controller.js +90 -0
  83. package/dist/app/popup-action-controller.d.ts +44 -0
  84. package/dist/app/popup-action-controller.js +278 -0
  85. package/dist/app/popup-menu-controller.d.ts +183 -0
  86. package/dist/app/popup-menu-controller.js +1070 -0
  87. package/dist/app/prompt-enhancer-controller.d.ts +40 -0
  88. package/dist/app/prompt-enhancer-controller.js +215 -0
  89. package/dist/app/queued-message-controller.d.ts +62 -0
  90. package/dist/app/queued-message-controller.js +377 -0
  91. package/dist/app/render-controller.d.ts +41 -0
  92. package/dist/app/render-controller.js +378 -0
  93. package/dist/app/render-text.d.ts +19 -0
  94. package/dist/app/render-text.js +67 -0
  95. package/dist/app/request-history.d.ts +23 -0
  96. package/dist/app/request-history.js +131 -0
  97. package/dist/app/runtime.d.ts +39 -0
  98. package/dist/app/runtime.js +195 -0
  99. package/dist/app/screen-selection.d.ts +6 -0
  100. package/dist/app/screen-selection.js +9 -0
  101. package/dist/app/screen-styler.d.ts +34 -0
  102. package/dist/app/screen-styler.js +168 -0
  103. package/dist/app/scroll-controller.d.ts +51 -0
  104. package/dist/app/scroll-controller.js +207 -0
  105. package/dist/app/session-event-controller.d.ts +69 -0
  106. package/dist/app/session-event-controller.js +338 -0
  107. package/dist/app/session-history.d.ts +23 -0
  108. package/dist/app/session-history.js +164 -0
  109. package/dist/app/session-lifecycle-controller.d.ts +55 -0
  110. package/dist/app/session-lifecycle-controller.js +116 -0
  111. package/dist/app/session-search.d.ts +24 -0
  112. package/dist/app/session-search.js +215 -0
  113. package/dist/app/shell-command.d.ts +27 -0
  114. package/dist/app/shell-command.js +176 -0
  115. package/dist/app/shell-controller.d.ts +28 -0
  116. package/dist/app/shell-controller.js +124 -0
  117. package/dist/app/slash-commands.d.ts +6 -0
  118. package/dist/app/slash-commands.js +75 -0
  119. package/dist/app/startup-checks.d.ts +8 -0
  120. package/dist/app/startup-checks.js +59 -0
  121. package/dist/app/startup-info.d.ts +3 -0
  122. package/dist/app/startup-info.js +176 -0
  123. package/dist/app/status-controller.d.ts +35 -0
  124. package/dist/app/status-controller.js +105 -0
  125. package/dist/app/status-line-renderer.d.ts +68 -0
  126. package/dist/app/status-line-renderer.js +453 -0
  127. package/dist/app/subagents-files.d.ts +10 -0
  128. package/dist/app/subagents-files.js +193 -0
  129. package/dist/app/subagents-model.d.ts +23 -0
  130. package/dist/app/subagents-model.js +224 -0
  131. package/dist/app/subagents-widget-controller.d.ts +43 -0
  132. package/dist/app/subagents-widget-controller.js +311 -0
  133. package/dist/app/tab-line-renderer.d.ts +26 -0
  134. package/dist/app/tab-line-renderer.js +222 -0
  135. package/dist/app/tabs-controller.d.ts +100 -0
  136. package/dist/app/tabs-controller.js +885 -0
  137. package/dist/app/terminal-controller.d.ts +40 -0
  138. package/dist/app/terminal-controller.js +135 -0
  139. package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
  140. package/dist/app/terminal-edit-shortcuts.js +138 -0
  141. package/dist/app/terminal-output-buffer.d.ts +20 -0
  142. package/dist/app/terminal-output-buffer.js +52 -0
  143. package/dist/app/toast-controller.d.ts +13 -0
  144. package/dist/app/toast-controller.js +40 -0
  145. package/dist/app/toast-renderer.d.ts +9 -0
  146. package/dist/app/toast-renderer.js +79 -0
  147. package/dist/app/todo-model.d.ts +22 -0
  148. package/dist/app/todo-model.js +179 -0
  149. package/dist/app/todo-widget-controller.d.ts +21 -0
  150. package/dist/app/todo-widget-controller.js +59 -0
  151. package/dist/app/tool-block-renderer.d.ts +26 -0
  152. package/dist/app/tool-block-renderer.js +439 -0
  153. package/dist/app/types.d.ts +550 -0
  154. package/dist/app/types.js +1 -0
  155. package/dist/app/update.d.ts +36 -0
  156. package/dist/app/update.js +315 -0
  157. package/dist/app/voice-controller.d.ts +52 -0
  158. package/dist/app/voice-controller.js +600 -0
  159. package/dist/app/workspace-actions-controller.d.ts +40 -0
  160. package/dist/app/workspace-actions-controller.js +215 -0
  161. package/dist/app/workspace-undo.d.ts +44 -0
  162. package/dist/app/workspace-undo.js +215 -0
  163. package/dist/config.d.ts +62 -0
  164. package/dist/config.js +527 -0
  165. package/dist/context-progress-bar.d.ts +17 -0
  166. package/dist/context-progress-bar.js +48 -0
  167. package/dist/default-pix-config.d.ts +1 -0
  168. package/dist/default-pix-config.js +375 -0
  169. package/dist/fuzzy.d.ts +23 -0
  170. package/dist/fuzzy.js +165 -0
  171. package/dist/input-editor-files.d.ts +15 -0
  172. package/dist/input-editor-files.js +62 -0
  173. package/dist/input-editor.d.ts +186 -0
  174. package/dist/input-editor.js +835 -0
  175. package/dist/main.d.ts +1 -0
  176. package/dist/main.js +12 -0
  177. package/dist/markdown-format.d.ts +22 -0
  178. package/dist/markdown-format.js +542 -0
  179. package/dist/sdk.d.ts +3 -0
  180. package/dist/sdk.js +1 -0
  181. package/dist/syntax-highlight.d.ts +22 -0
  182. package/dist/syntax-highlight.js +528 -0
  183. package/dist/terminal-width.d.ts +6 -0
  184. package/dist/terminal-width.js +245 -0
  185. package/dist/theme.d.ts +56 -0
  186. package/dist/theme.js +118 -0
  187. package/dist/tool-renderers/apply-patch.d.ts +2 -0
  188. package/dist/tool-renderers/apply-patch.js +36 -0
  189. package/dist/tool-renderers/ast.d.ts +2 -0
  190. package/dist/tool-renderers/ast.js +5 -0
  191. package/dist/tool-renderers/compress.d.ts +2 -0
  192. package/dist/tool-renderers/compress.js +126 -0
  193. package/dist/tool-renderers/index.d.ts +3 -0
  194. package/dist/tool-renderers/index.js +44 -0
  195. package/dist/tool-renderers/question.d.ts +2 -0
  196. package/dist/tool-renderers/question.js +88 -0
  197. package/dist/tool-renderers/read.d.ts +2 -0
  198. package/dist/tool-renderers/read.js +36 -0
  199. package/dist/tool-renderers/repo.d.ts +2 -0
  200. package/dist/tool-renderers/repo.js +13 -0
  201. package/dist/tool-renderers/shell.d.ts +3 -0
  202. package/dist/tool-renderers/shell.js +27 -0
  203. package/dist/tool-renderers/skill.d.ts +2 -0
  204. package/dist/tool-renderers/skill.js +132 -0
  205. package/dist/tool-renderers/subagents.d.ts +2 -0
  206. package/dist/tool-renderers/subagents.js +51 -0
  207. package/dist/tool-renderers/todo.d.ts +2 -0
  208. package/dist/tool-renderers/todo.js +9 -0
  209. package/dist/tool-renderers/types.d.ts +42 -0
  210. package/dist/tool-renderers/types.js +1 -0
  211. package/dist/tool-renderers/utils.d.ts +31 -0
  212. package/dist/tool-renderers/utils.js +230 -0
  213. package/dist/tool-renderers/web.d.ts +3 -0
  214. package/dist/tool-renderers/web.js +9 -0
  215. package/dist/tool-renderers/write.d.ts +2 -0
  216. package/dist/tool-renderers/write.js +42 -0
  217. package/dist/ui.d.ts +56 -0
  218. package/dist/ui.js +94 -0
  219. package/docs/release.md +81 -0
  220. package/extensions/question/contract.ts +100 -0
  221. package/extensions/question/index.ts +34 -0
  222. package/extensions/question/render.ts +28 -0
  223. package/extensions/question/result.ts +86 -0
  224. package/extensions/question/tool-description.ts +11 -0
  225. package/extensions/question/tui.ts +629 -0
  226. package/extensions/question/types.ts +123 -0
  227. package/extensions/session-title/config.ts +169 -0
  228. package/extensions/session-title/index.ts +459 -0
  229. package/extensions/terminal-bell/index.ts +315 -0
  230. package/external/pi-tools-suite/README.md +242 -0
  231. package/external/pi-tools-suite/index.ts +1 -0
  232. package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
  233. package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
  234. package/external/pi-tools-suite/package.json +53 -0
  235. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
  236. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
  237. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
  238. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
  239. package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
  240. package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
  241. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
  242. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
  243. package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
  244. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
  245. package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
  246. package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
  247. package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
  248. package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
  249. package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
  250. package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
  251. package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
  252. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
  253. package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
  254. package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
  255. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
  256. package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
  257. package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
  258. package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
  259. package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
  260. package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
  261. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
  262. package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
  263. package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
  264. package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
  265. package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
  266. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
  267. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
  268. package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
  269. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
  270. package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
  271. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
  272. package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
  273. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
  274. package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
  275. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
  276. package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
  277. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
  278. package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
  279. package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
  280. package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
  281. package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
  282. package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
  283. package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
  284. package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
  285. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
  286. package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
  287. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
  288. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
  289. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
  290. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
  291. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
  292. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
  293. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
  294. package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
  295. package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
  296. package/external/pi-tools-suite/src/compress/commands.ts +440 -0
  297. package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
  298. package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
  299. package/external/pi-tools-suite/src/compress/config.ts +310 -0
  300. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
  301. package/external/pi-tools-suite/src/compress/index.ts +397 -0
  302. package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
  303. package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
  304. package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
  305. package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
  306. package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
  307. package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
  308. package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
  309. package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
  310. package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
  311. package/external/pi-tools-suite/src/compress/state.ts +486 -0
  312. package/external/pi-tools-suite/src/compress/ui.ts +308 -0
  313. package/external/pi-tools-suite/src/config.ts +176 -0
  314. package/external/pi-tools-suite/src/context-usage.ts +31 -0
  315. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
  316. package/external/pi-tools-suite/src/index.ts +46 -0
  317. package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
  318. package/external/pi-tools-suite/src/lib/project.ts +42 -0
  319. package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
  320. package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
  321. package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
  322. package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
  323. package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
  324. package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
  325. package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
  326. package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
  327. package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
  328. package/external/pi-tools-suite/src/lsp/async.ts +29 -0
  329. package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
  330. package/external/pi-tools-suite/src/lsp/client.ts +340 -0
  331. package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
  332. package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
  333. package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
  334. package/external/pi-tools-suite/src/lsp/index.ts +31 -0
  335. package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
  336. package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
  337. package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
  338. package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
  339. package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
  340. package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
  341. package/external/pi-tools-suite/src/lsp/types.ts +15 -0
  342. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
  343. package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
  344. package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
  345. package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
  346. package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
  347. package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
  348. package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
  349. package/external/pi-tools-suite/src/startup-section.ts +13 -0
  350. package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
  351. package/external/pi-tools-suite/src/todo/index.ts +201 -0
  352. package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
  353. package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
  354. package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
  355. package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
  356. package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
  357. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
  358. package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
  359. package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
  360. package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
  361. package/external/pi-tools-suite/src/todo/todo.ts +487 -0
  362. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
  363. package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
  364. package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
  365. package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
  366. package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
  367. package/external/pi-tools-suite/src/usage/index.ts +152 -0
  368. package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
  369. package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
  370. package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
  371. package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
  372. package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
  373. package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
  374. package/external/pi-tools-suite/src/web-search/index.ts +397 -0
  375. package/package.json +89 -0
  376. package/skills/context7/SKILL.md +69 -0
  377. package/skills/context7/scripts/context7.sh +73 -0
  378. package/skills/handoff/SKILL.md +15 -0
  379. package/skills/pdf/LICENSE.txt +30 -0
  380. package/skills/pdf/SKILL.md +314 -0
  381. package/skills/pdf/forms.md +294 -0
  382. package/skills/pdf/reference.md +612 -0
  383. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  384. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  385. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  386. package/skills/pdf/scripts/create_validation_image.py +37 -0
  387. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  388. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  389. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  390. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  391. package/skills/playwright-cli/SKILL.md +388 -0
  392. package/skills/playwright-cli/references/element-attributes.md +23 -0
  393. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  394. package/skills/playwright-cli/references/request-mocking.md +87 -0
  395. package/skills/playwright-cli/references/running-code.md +241 -0
  396. package/skills/playwright-cli/references/session-management.md +225 -0
  397. package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  398. package/skills/playwright-cli/references/storage-state.md +275 -0
  399. package/skills/playwright-cli/references/test-generation.md +134 -0
  400. package/skills/playwright-cli/references/tracing.md +139 -0
  401. package/skills/playwright-cli/references/video-recording.md +143 -0
  402. package/skills/simplify/SKILL.md +51 -0
  403. package/skills/skill-creator/LICENSE.txt +202 -0
  404. package/skills/skill-creator/SKILL.md +485 -0
  405. package/skills/skill-creator/agents/analyzer.md +274 -0
  406. package/skills/skill-creator/agents/comparator.md +202 -0
  407. package/skills/skill-creator/agents/grader.md +223 -0
  408. package/skills/skill-creator/assets/eval_review.html +146 -0
  409. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  410. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  411. package/skills/skill-creator/references/schemas.md +430 -0
  412. package/skills/skill-creator/scripts/__init__.py +0 -0
  413. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  414. package/skills/skill-creator/scripts/generate_report.py +326 -0
  415. package/skills/skill-creator/scripts/improve_description.py +247 -0
  416. package/skills/skill-creator/scripts/package_skill.py +136 -0
  417. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  418. package/skills/skill-creator/scripts/run_eval.py +310 -0
  419. package/skills/skill-creator/scripts/run_loop.py +328 -0
  420. package/skills/skill-creator/scripts/utils.py +47 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * todo tool + /todos command — thin registration shell.
3
+ *
4
+ * Tool/command identity, schema, types, reducer, store, replay, response
5
+ * envelope, selectors, and view formatters live in the layered modules under
6
+ * `tool/`, `state/`, and `view/`. This file is the package-root registration
7
+ * surface — it mirrors `packages/rpiv-ask-user-question/ask-user-question.ts`
8
+ * which keeps the tool registration at the package root.
9
+ *
10
+ * Public re-exports below preserve the pre-refactor import surface so that
11
+ * `index.ts` and the global `test/setup.ts` `beforeEach`
12
+ * continue to import from `./todo.js`.
13
+ */
14
+
15
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
16
+ import { TODO_TOOL_DESCRIPTION } from "../tool-descriptions.js";
17
+ import {
18
+ disablePersistence,
19
+ getTodoPlanPath,
20
+ loadPersistedPlan,
21
+ savePersistedPlan,
22
+ syncPersistedPlan,
23
+ } from "./state/persistence.js";
24
+ import { AUTO_CLEAR_COMPLETED_MESSAGE, autoClearCompletedTodos } from "./state/auto-clear.js";
25
+ import { replayFromBranch } from "./state/replay.js";
26
+ import { selectTasksByStatus, selectTodoCounts } from "./state/selectors.js";
27
+ import { applyTaskMutation } from "./state/state-reducer.js";
28
+ import { commitState, getState, replaceState } from "./state/store.js";
29
+ import { buildToolResult, formatContent } from "./tool/response-envelope.js";
30
+ import {
31
+ COMMAND_NAME,
32
+ ERR_REQUIRES_INTERACTIVE,
33
+ MSG_NO_TODOS,
34
+ type Task,
35
+ type TaskAction,
36
+ type TaskMutationParams,
37
+ type TaskPriority,
38
+ type TaskStatus,
39
+ TOOL_LABEL,
40
+ TOOL_NAME,
41
+ TodoParamsSchema,
42
+ } from "./tool/types.js";
43
+ import { formatCommandTaskLine } from "./view/format.js";
44
+ import { formatStatusLabel } from "./view/labels.js";
45
+
46
+ const SECTION_PENDING = "── Pending ──";
47
+ const SECTION_IN_PROGRESS = "── In Progress ──";
48
+ const SECTION_DEFERRED = "── Deferred ──";
49
+ const SECTION_COMPLETED = "── Completed ──";
50
+ const PERSIST_COMMAND_NAME = "todos-persist";
51
+ const SCOPE_COMMAND_NAME = "todos-scope";
52
+ export const TODO_STATE_EVENT = "pi-tools-suite:todo:state";
53
+
54
+ type CommandCompletion = { value: string; label: string; description?: string };
55
+
56
+ const TODOS_ARGUMENT_COMPLETIONS: CommandCompletion[] = [
57
+ { value: "persist on", label: "persist on", description: "Enable project plan persistence" },
58
+ { value: "persist status", label: "persist status", description: "Show project plan persistence status" },
59
+ { value: "persist off", label: "persist off", description: "Disable persistence and remove the project plan file" },
60
+ { value: "persist clear", label: "persist clear", description: "Alias for persist off" },
61
+ { value: "scope ", label: "scope <id...>", description: "Keep selected items active and defer out-of-scope items" },
62
+ { value: "--active", label: "--active", description: "Show pending/in_progress tasks" },
63
+ { value: "--ready", label: "--ready", description: "Show pending tasks whose blockers are completed" },
64
+ { value: "--blocked", label: "--blocked", description: "Show tasks with blockers" },
65
+ { value: "--tree", label: "--tree", description: "Show parent/subtask tree" },
66
+ { value: "--tag ", label: "--tag <tag>", description: "Filter by tag" },
67
+ { value: "--priority ", label: "--priority <level>", description: "Filter by priority" },
68
+ { value: "--status ", label: "--status <status>", description: "Filter by status" },
69
+ { value: "--export markdown", label: "--export markdown", description: "Export visible todos as Markdown" },
70
+ { value: "--export json", label: "--export json", description: "Export visible todos as JSON" },
71
+ ];
72
+
73
+ const PERSIST_ARGUMENT_COMPLETIONS: CommandCompletion[] = [
74
+ { value: "on", label: "on", description: "Enable project plan persistence" },
75
+ { value: "status", label: "status", description: "Show persistence status" },
76
+ { value: "off", label: "off", description: "Disable persistence and remove the plan file" },
77
+ { value: "clear", label: "clear", description: "Alias for off" },
78
+ ];
79
+
80
+ interface TodoToolHooks {
81
+ afterCommit?: (state: ReturnType<typeof getState>, ctx: ExtensionContext) => void | Promise<void>;
82
+ }
83
+
84
+ type TodoStateEventContext = { sessionManager?: { getSessionFile?: () => unknown } };
85
+ type TodoStateEventEmitter = { events?: { emit?: (channel: string, data: unknown) => void } };
86
+
87
+ interface TodosCommandOptions {
88
+ status?: TaskStatus;
89
+ priority?: TaskPriority;
90
+ tag?: string;
91
+ blockedOnly: boolean;
92
+ readyOnly: boolean;
93
+ activeOnly: boolean;
94
+ includeDeleted: boolean;
95
+ tree: boolean;
96
+ exportFormat?: "json" | "markdown";
97
+ }
98
+
99
+ function parseTodosArgs(args: unknown): TodosCommandOptions {
100
+ const text = typeof args === "string" ? args : Array.isArray(args) ? args.join(" ") : "";
101
+ const tokens = text.trim().split(/\s+/).filter(Boolean);
102
+ const options: TodosCommandOptions = {
103
+ blockedOnly: false,
104
+ readyOnly: false,
105
+ activeOnly: false,
106
+ includeDeleted: false,
107
+ tree: false,
108
+ };
109
+ for (let i = 0; i < tokens.length; i++) {
110
+ const token = tokens[i];
111
+ const next = tokens[i + 1];
112
+ switch (token) {
113
+ case "--blocked":
114
+ options.blockedOnly = true;
115
+ break;
116
+ case "--ready":
117
+ options.readyOnly = true;
118
+ break;
119
+ case "--active":
120
+ options.activeOnly = true;
121
+ break;
122
+ case "--tree":
123
+ options.tree = true;
124
+ break;
125
+ case "--include-deleted":
126
+ options.includeDeleted = true;
127
+ break;
128
+ case "--tag":
129
+ if (next) options.tag = tokens[++i].replace(/^#/, "");
130
+ break;
131
+ case "--priority":
132
+ if (next === "low" || next === "medium" || next === "high" || next === "urgent") options.priority = tokens[++i] as TaskPriority;
133
+ break;
134
+ case "--status":
135
+ if (next === "pending" || next === "in_progress" || next === "deferred" || next === "completed" || next === "deleted") options.status = tokens[++i] as TaskStatus;
136
+ break;
137
+ case "--export":
138
+ options.exportFormat = next === "markdown" ? "markdown" : "json";
139
+ if (next === "json" || next === "markdown") i++;
140
+ break;
141
+ }
142
+ }
143
+ return options;
144
+ }
145
+
146
+ function getCommandText(args: unknown): string {
147
+ return typeof args === "string" ? args : Array.isArray(args) ? args.join(" ") : "";
148
+ }
149
+
150
+ function getCommandTokens(args: unknown): string[] {
151
+ return getCommandText(args).trim().split(/\s+/).filter(Boolean);
152
+ }
153
+
154
+ function completeCommandArguments(prefix: string, completions: readonly CommandCompletion[]): CommandCompletion[] {
155
+ const normalizedPrefix = prefix.trimStart();
156
+ return completions.filter((completion) => completion.value.startsWith(normalizedPrefix));
157
+ }
158
+
159
+ function parseScopeIds(tokens: readonly string[]): number[] {
160
+ const ids = tokens
161
+ .flatMap((token) => token.split(/[\s,]+/))
162
+ .map((token) => Number(token.replace(/^#/, "")))
163
+ .filter((id) => Number.isFinite(id) && id > 0);
164
+ return [...new Set(ids)];
165
+ }
166
+
167
+ function scopeStatus(task: Task, selected: ReadonlySet<number>): TaskStatus {
168
+ if (selected.has(task.id)) {
169
+ if (task.status === "deferred") return "pending";
170
+ return task.status;
171
+ }
172
+ if (task.status === "pending" || task.status === "in_progress") return "deferred";
173
+ return task.status;
174
+ }
175
+
176
+ type NotifyLevel = "info" | "warning" | "error";
177
+
178
+ function notifyCommand(ctx: { hasUI?: boolean; ui?: { notify?: (message: string, level?: NotifyLevel) => void } }, message: string, level: NotifyLevel = "info"): void {
179
+ if (ctx.hasUI && ctx.ui?.notify) {
180
+ ctx.ui.notify(message, level);
181
+ return;
182
+ }
183
+ if (level === "error") console.error(message);
184
+ else console.log(message);
185
+ }
186
+
187
+ function sessionFileFromContext(ctx: unknown): string | undefined {
188
+ const sessionFile = (ctx as TodoStateEventContext | undefined)?.sessionManager?.getSessionFile?.();
189
+ return typeof sessionFile === "string" && sessionFile.trim() ? sessionFile : undefined;
190
+ }
191
+
192
+ export function publishTodoState(
193
+ pi: TodoStateEventEmitter,
194
+ ctx: unknown,
195
+ action: TaskAction = "list",
196
+ params: Record<string, unknown> = {},
197
+ ): void {
198
+ const state = getState();
199
+ const sessionFile = sessionFileFromContext(ctx);
200
+ pi.events?.emit?.(TODO_STATE_EVENT, {
201
+ version: 1,
202
+ details: {
203
+ action,
204
+ params,
205
+ tasks: state.tasks,
206
+ nextId: state.nextId,
207
+ },
208
+ ...(sessionFile ? { sessionFile } : {}),
209
+ checkedAt: Date.now(),
210
+ });
211
+ }
212
+
213
+ function handlePersistCommand(args: unknown, ctx: { cwd?: string; hasUI?: boolean; ui?: { notify?: (message: string, level?: NotifyLevel) => void } }): boolean {
214
+ const [command, subcommand = "status"] = getCommandTokens(args);
215
+ if (command !== "persist") return false;
216
+
217
+ const cwd = ctx.cwd;
218
+ switch (subcommand) {
219
+ case "on": {
220
+ const path = savePersistedPlan(cwd, getState());
221
+ notifyCommand(ctx, `Todo persistence enabled: ${path}`);
222
+ return true;
223
+ }
224
+ case "off":
225
+ case "clear": {
226
+ const path = disablePersistence(cwd);
227
+ notifyCommand(ctx, `Todo persistence disabled and project plan removed: ${path}`);
228
+ return true;
229
+ }
230
+ case "status": {
231
+ const loaded = loadPersistedPlan(cwd);
232
+ if (!loaded) {
233
+ notifyCommand(ctx, `Todo persistence is off (${getTodoPlanPath(cwd)} not found)`);
234
+ return true;
235
+ }
236
+ const visible = loaded.state.tasks.filter((task) => task.status !== "deleted").length;
237
+ const active = loaded.state.tasks.filter((task) => task.status === "pending" || task.status === "in_progress").length;
238
+ notifyCommand(ctx, `Todo persistence is on: ${loaded.path}\n${visible} visible tasks, ${active} active.`);
239
+ return true;
240
+ }
241
+ default:
242
+ notifyCommand(ctx, "Usage: /todos persist on|off|clear|status", "error");
243
+ return true;
244
+ }
245
+ }
246
+
247
+ function handleScopeCommand(
248
+ args: unknown,
249
+ ctx: { cwd?: string; hasUI?: boolean; ui?: { notify?: (message: string, level?: NotifyLevel) => void } },
250
+ afterCommit?: () => void,
251
+ ): boolean {
252
+ const [command, ...rest] = getCommandTokens(args);
253
+ if (command !== "scope") return false;
254
+ const selectedIds = parseScopeIds(rest);
255
+ if (selectedIds.length === 0) {
256
+ notifyCommand(ctx, "Usage: /todos scope <id> [id...]", "error");
257
+ return true;
258
+ }
259
+
260
+ const selected = new Set(selectedIds);
261
+ let nextState = getState();
262
+ const changed: number[] = [];
263
+ for (const task of nextState.tasks) {
264
+ if (task.status === "deleted" || task.status === "completed") continue;
265
+ const nextStatus = scopeStatus(task, selected);
266
+ if (nextStatus === task.status) continue;
267
+ const result = applyTaskMutation(nextState, "update", { action: "update", id: task.id, status: nextStatus });
268
+ if (result.op.kind === "error") {
269
+ notifyCommand(ctx, result.op.message, "error");
270
+ return true;
271
+ }
272
+ nextState = result.state;
273
+ changed.push(task.id);
274
+ }
275
+ commitState(nextState);
276
+ afterCommit?.();
277
+ const sync = syncPersistedPlan(ctx.cwd, nextState);
278
+ const selectedText = selectedIds.map((id) => `#${id}`).join(", ");
279
+ const persistedText = sync ? `\nPersisted plan updated: ${sync.path}` : "";
280
+ notifyCommand(ctx, `Todo scope selected: ${selectedText}\nDeferred out-of-scope active tasks: ${changed.length}${persistedText}`);
281
+ return true;
282
+ }
283
+
284
+ function filterCommandTasks(tasks: readonly Task[], options: TodosCommandOptions): Task[] {
285
+ const byId = new Map(tasks.map((task) => [task.id, task]));
286
+ let view = [...tasks];
287
+ if (!options.includeDeleted) view = view.filter((task) => task.status !== "deleted");
288
+ if (options.activeOnly) view = view.filter((task) => task.status === "pending" || task.status === "in_progress");
289
+ if (options.readyOnly) {
290
+ view = view.filter(
291
+ (task) =>
292
+ task.status === "pending" &&
293
+ (task.blockedBy ?? []).every((id) => byId.get(id)?.status === "completed"),
294
+ );
295
+ }
296
+ if (options.status) view = view.filter((task) => task.status === options.status);
297
+ if (options.priority) view = view.filter((task) => task.priority === options.priority);
298
+ const tag = options.tag;
299
+ if (tag) view = view.filter((task) => task.tags?.includes(tag));
300
+ if (options.blockedOnly) view = view.filter((task) => (task.blockedBy?.length ?? 0) > 0);
301
+ return view;
302
+ }
303
+
304
+ function commandGlyph(status: TaskStatus): string {
305
+ switch (status) {
306
+ case "pending":
307
+ return "○";
308
+ case "in_progress":
309
+ return "◐";
310
+ case "deferred":
311
+ return "◌";
312
+ case "completed":
313
+ return "✓";
314
+ case "deleted":
315
+ return "⊘";
316
+ }
317
+ }
318
+
319
+ function formatCommandTree(tasks: readonly Task[]): string[] {
320
+ const byParent = new Map<number | undefined, Task[]>();
321
+ const ids = new Set(tasks.map((task) => task.id));
322
+ for (const task of tasks) {
323
+ const parent = task.parentId !== undefined && ids.has(task.parentId) ? task.parentId : undefined;
324
+ const siblings = byParent.get(parent) ?? [];
325
+ siblings.push(task);
326
+ byParent.set(parent, siblings);
327
+ }
328
+ const lines: string[] = [];
329
+ const seen = new Set<number>();
330
+ const visit = (task: Task, depth: number) => {
331
+ if (seen.has(task.id)) return;
332
+ seen.add(task.id);
333
+ lines.push(`${" ".repeat(depth)}${formatCommandTaskLine(task, commandGlyph(task.status)).trimStart()}`);
334
+ for (const child of byParent.get(task.id) ?? []) visit(child, depth + 1);
335
+ };
336
+ for (const root of byParent.get(undefined) ?? []) visit(root, 0);
337
+ for (const task of tasks) visit(task, 0);
338
+ return lines;
339
+ }
340
+
341
+ // ---------------------------------------------------------------------------
342
+ // Public re-exports — pre-refactor consumers (tests, index.ts) keep
343
+ // importing from `./todo.js`. New code may opt into deeper imports.
344
+ // ---------------------------------------------------------------------------
345
+
346
+ export { isTransitionValid } from "./state/invariants.js";
347
+ export { applyTaskMutation } from "./state/state-reducer.js";
348
+ export { __resetState, getNextId, getTodos } from "./state/store.js";
349
+ export { deriveBlocks, detectCycle } from "./state/task-graph.js";
350
+ export type { Task, TaskAction, TaskDetails, TaskPriority, TaskStatus } from "./tool/types.js";
351
+ export { TOOL_NAME } from "./tool/types.js";
352
+
353
+ /**
354
+ * Backward-compat replay shim. Pre-refactor `reconstructTodoState(ctx)`
355
+ * mutated module state directly; the new replay seam (`state/replay.ts`)
356
+ * returns a `TaskState` and the caller commits via `replaceState`.
357
+ */
358
+ export function reconstructTodoState(ctx: Parameters<typeof replayFromBranch>[0]): void {
359
+ replaceState(replayFromBranch(ctx));
360
+ }
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // Tool registration
364
+ // ---------------------------------------------------------------------------
365
+
366
+ export const DEFAULT_PROMPT_SNIPPET = TODO_TOOL_DESCRIPTION.promptSnippet ?? "";
367
+ export const DEFAULT_PROMPT_GUIDELINES: string[] = TODO_TOOL_DESCRIPTION.promptGuidelines ?? [];
368
+
369
+ export function registerTodoTool(pi: ExtensionAPI, hooks: TodoToolHooks = {}): void {
370
+ pi.registerTool({
371
+ ...TODO_TOOL_DESCRIPTION,
372
+ name: TOOL_NAME,
373
+ label: TOOL_LABEL,
374
+ promptSnippet: DEFAULT_PROMPT_SNIPPET,
375
+ promptGuidelines: DEFAULT_PROMPT_GUIDELINES,
376
+ parameters: TodoParamsSchema,
377
+
378
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
379
+ const result = applyTaskMutation(getState(), params.action, params as TaskMutationParams);
380
+ if (result.op.kind === "error") {
381
+ throw new Error(result.op.message);
382
+ }
383
+ const autoClear = autoClearCompletedTodos(result.state);
384
+ commitState(autoClear.state);
385
+ publishTodoState(pi as TodoStateEventEmitter, _ctx, params.action, params as Record<string, unknown>);
386
+ await hooks.afterCommit?.(autoClear.state, _ctx as ExtensionContext);
387
+ const toolResult = buildToolResult(params.action, params as TaskMutationParams, autoClear.state, result.op);
388
+ if (!autoClear.cleared) return toolResult;
389
+ return {
390
+ ...toolResult,
391
+ content: [{ type: "text" as const, text: `${toolResult.content[0]?.text ?? ""}\n${AUTO_CLEAR_COMPLETED_MESSAGE}`.trim() }],
392
+ };
393
+ },
394
+
395
+ });
396
+ }
397
+
398
+ // ---------------------------------------------------------------------------
399
+ // /todos slash command
400
+ // ---------------------------------------------------------------------------
401
+
402
+ export function registerTodosCommand(pi: ExtensionAPI): void {
403
+ pi.registerCommand(COMMAND_NAME, {
404
+ description: "Show todos on the current branch. Flags: --active, --ready, --blocked, --tree, --tag <tag>, --priority <level>, --status <status>, --export [json|markdown]. Commands: persist on|off|clear|status, scope <id...>",
405
+ getArgumentCompletions: (prefix) => completeCommandArguments(String(prefix ?? ""), TODOS_ARGUMENT_COMPLETIONS),
406
+ handler: async (args, ctx) => {
407
+ if (handlePersistCommand(args, ctx)) return;
408
+ if (handleScopeCommand(args, ctx, () => publishTodoState(pi as TodoStateEventEmitter, ctx))) return;
409
+ if (!ctx.hasUI) {
410
+ console.error(ERR_REQUIRES_INTERACTIVE);
411
+ return;
412
+ }
413
+ const state = getState();
414
+ const options = parseTodosArgs(args);
415
+ if (options.exportFormat) {
416
+ const exportState = { tasks: filterCommandTasks(state.tasks, options), nextId: state.nextId };
417
+ const op = {
418
+ kind: "export" as const,
419
+ format: options.exportFormat,
420
+ includeDeleted: options.includeDeleted,
421
+ blockedOnly: options.blockedOnly,
422
+ ...(options.status ? { statusFilter: options.status } : {}),
423
+ ...(options.priority ? { priorityFilter: options.priority } : {}),
424
+ ...(options.tag ? { tagFilter: options.tag } : {}),
425
+ };
426
+ ctx.ui.notify(formatContent(op, exportState), "info");
427
+ return;
428
+ }
429
+
430
+ const visible = filterCommandTasks(state.tasks, options);
431
+ if (visible.length === 0) {
432
+ ctx.ui.notify(MSG_NO_TODOS, "info");
433
+ return;
434
+ }
435
+ const filteredState = { tasks: visible, nextId: state.nextId };
436
+ const groups = selectTasksByStatus(filteredState);
437
+ const counts = selectTodoCounts(filteredState);
438
+
439
+ const header: string[] = [];
440
+ if (counts.completed > 0) header.push(`${counts.completed}/${counts.total} ${formatStatusLabel("completed")}`);
441
+ if (counts.inProgress > 0) header.push(`${counts.inProgress} ${formatStatusLabel("in_progress")}`);
442
+ if (counts.pending > 0) header.push(`${counts.pending} ${formatStatusLabel("pending")}`);
443
+ if (counts.deferred > 0) header.push(`${counts.deferred} ${formatStatusLabel("deferred")}`);
444
+
445
+ const lines: string[] = [header.join(" · ")];
446
+ if (options.tree) {
447
+ lines.push("── Tree ──");
448
+ lines.push(...formatCommandTree(visible));
449
+ ctx.ui.notify(lines.join("\n"), "info");
450
+ return;
451
+ }
452
+ if (groups.pending.length > 0) {
453
+ lines.push(SECTION_PENDING);
454
+ for (const task of groups.pending) lines.push(formatCommandTaskLine(task, "○"));
455
+ }
456
+ if (groups.inProgress.length > 0) {
457
+ lines.push(SECTION_IN_PROGRESS);
458
+ for (const task of groups.inProgress) lines.push(formatCommandTaskLine(task, "◐"));
459
+ }
460
+ if (groups.deferred.length > 0) {
461
+ lines.push(SECTION_DEFERRED);
462
+ for (const task of groups.deferred) lines.push(formatCommandTaskLine(task, "◌"));
463
+ }
464
+ if (groups.completed.length > 0) {
465
+ lines.push(SECTION_COMPLETED);
466
+ for (const task of groups.completed) lines.push(formatCommandTaskLine(task, "✓"));
467
+ }
468
+
469
+ ctx.ui.notify(lines.join("\n"), "info");
470
+ },
471
+ });
472
+
473
+ pi.registerCommand(PERSIST_COMMAND_NAME, {
474
+ description: "Enable, disable, clear, or inspect project todo persistence. Args: on|off|clear|status.",
475
+ getArgumentCompletions: (prefix) => completeCommandArguments(String(prefix ?? ""), PERSIST_ARGUMENT_COMPLETIONS),
476
+ handler: async (args, ctx) => {
477
+ handlePersistCommand(`persist ${getCommandText(args)}`, ctx);
478
+ },
479
+ });
480
+
481
+ pi.registerCommand(SCOPE_COMMAND_NAME, {
482
+ description: "Select todo ids to continue from a persisted plan; pending/in_progress items outside the scope become deferred.",
483
+ handler: async (args, ctx) => {
484
+ handleScopeCommand(`scope ${getCommandText(args)}`, ctx, () => publishTodoState(pi as TodoStateEventEmitter, ctx));
485
+ },
486
+ });
487
+ }
@@ -0,0 +1,143 @@
1
+ import type { TaskState } from "../state/state.js";
2
+ import type { Op } from "../state/state-reducer.js";
3
+ import { deriveBlocks } from "../state/task-graph.js";
4
+ import type { Task, TaskAction, TaskDetails, TaskMutationParams } from "./types.js";
5
+
6
+ /**
7
+ * Format a single task for the LLM-facing `list` response. The `/todos`
8
+ * command uses `view/format.ts` for its grouped text presentation.
9
+ */
10
+ function formatListLine(t: Task): string {
11
+ const block = t.blockedBy?.length ? ` ⛓ ${t.blockedBy.map((id) => `#${id}`).join(",")}` : "";
12
+ const parent = t.parentId !== undefined ? ` ↳ #${t.parentId}` : "";
13
+ const priority = t.priority ? ` (${t.priority})` : "";
14
+ const tags = t.tags?.length ? ` ${t.tags.map((tag) => `#${tag}`).join(" ")}` : "";
15
+ const form = t.status === "in_progress" && t.activeForm ? ` (${t.activeForm})` : "";
16
+ return `[${t.status}] #${t.id} ${t.subject}${priority}${form}${parent}${block}${tags}`;
17
+ }
18
+
19
+ /**
20
+ * Multi-line presentation for the `get` action. Keep the most actionable task
21
+ * fields near the top, then dependency and ownership metadata.
22
+ */
23
+ function formatGetLines(task: Task, state: TaskState): string {
24
+ const blocks = deriveBlocks(state.tasks).get(task.id) ?? [];
25
+ const lines = [`#${task.id} [${task.status}] ${task.subject}`];
26
+ if (task.description) lines.push(` description: ${task.description}`);
27
+ if (task.activeForm) lines.push(` activeForm: ${task.activeForm}`);
28
+ if (task.priority) lines.push(` priority: ${task.priority}`);
29
+ if (task.parentId !== undefined) lines.push(` parentId: #${task.parentId}`);
30
+ if (task.blockedBy?.length) {
31
+ lines.push(` blockedBy: ${task.blockedBy.map((id) => `#${id}`).join(", ")}`);
32
+ }
33
+ if (blocks.length) {
34
+ lines.push(` blocks: ${blocks.map((id) => `#${id}`).join(", ")}`);
35
+ }
36
+ if (task.tags?.length) lines.push(` tags: ${task.tags.map((tag) => `#${tag}`).join(" ")}`);
37
+ if (task.owner) lines.push(` owner: ${task.owner}`);
38
+ return lines.join("\n");
39
+ }
40
+
41
+ function filterTasks(op: Extract<Op, { kind: "list" | "export" }>, state: TaskState): Task[] {
42
+ let view = state.tasks;
43
+ if (!op.includeDeleted) view = view.filter((t) => t.status !== "deleted");
44
+ if (op.statusFilter) view = view.filter((t) => t.status === op.statusFilter);
45
+ if (op.priorityFilter) view = view.filter((t) => t.priority === op.priorityFilter);
46
+ const tagFilter = op.tagFilter;
47
+ if (tagFilter) view = view.filter((t) => t.tags?.includes(tagFilter));
48
+ if (op.blockedOnly) view = view.filter((t) => (t.blockedBy?.length ?? 0) > 0);
49
+ return view;
50
+ }
51
+
52
+ function formatMarkdownExport(tasks: readonly Task[]): string {
53
+ const byParent = new Map<number | undefined, Task[]>();
54
+ for (const task of tasks) {
55
+ const siblings = byParent.get(task.parentId) ?? [];
56
+ siblings.push(task);
57
+ byParent.set(task.parentId, siblings);
58
+ }
59
+ const lines: string[] = [];
60
+ const seen = new Set<number>();
61
+ const visit = (task: Task, depth: number) => {
62
+ if (seen.has(task.id)) return;
63
+ seen.add(task.id);
64
+ const checked = task.status === "completed" ? "x" : " ";
65
+ const priority = task.priority ? ` (${task.priority})` : "";
66
+ const status = task.status === "deferred" ? " {deferred}" : "";
67
+ const tags = task.tags?.length ? ` [${task.tags.map((tag) => `#${tag}`).join(" ")}]` : "";
68
+ const blocked = task.blockedBy?.length ? ` ⛓ ${task.blockedBy.map((id) => `#${id}`).join(",")}` : "";
69
+ lines.push(`${" ".repeat(depth)}- [${checked}] #${task.id}${priority} ${task.subject}${status}${blocked}${tags}`);
70
+ for (const child of byParent.get(task.id) ?? []) visit(child, depth + 1);
71
+ };
72
+ for (const root of byParent.get(undefined) ?? []) visit(root, 0);
73
+ for (const task of tasks) visit(task, 0);
74
+ return lines.join("\n");
75
+ }
76
+
77
+ /**
78
+ * Pure formatter: `(op, state) → string`. Closed switch on `op.kind` —
79
+ * adding a new `Op` variant fails to compile here until a branch is added.
80
+ * The strings on each branch are byte-equivalent to pre-refactor `todo.ts`
81
+ * reducer output.
82
+ */
83
+ export function formatContent(op: Op, state: TaskState): string {
84
+ switch (op.kind) {
85
+ case "create": {
86
+ const t = state.tasks.find((x) => x.id === op.taskId);
87
+ // Defensive — `op.taskId` always resolves on success path.
88
+ if (!t) return `Created #${op.taskId}`;
89
+ return `Created #${t.id}: ${t.subject} (pending)`;
90
+ }
91
+ case "update": {
92
+ const transition = op.fromStatus !== op.toStatus ? ` (${op.fromStatus} → ${op.toStatus})` : "";
93
+ return `Updated #${op.id}${transition}`;
94
+ }
95
+ case "batch_create":
96
+ return `Created ${op.ids.length} tasks: ${op.ids.map((id) => `#${id}`).join(", ")}`;
97
+ case "batch_update":
98
+ return `Updated ${op.ids.length} tasks: ${op.ids.map((id) => `#${id}`).join(", ")}`;
99
+ case "delete":
100
+ return `Deleted #${op.id}: ${op.subject}`;
101
+ case "clear":
102
+ return `Cleared ${op.count} tasks`;
103
+ case "list": {
104
+ const view = filterTasks(op, state);
105
+ return view.length === 0 ? "No tasks" : view.map(formatListLine).join("\n");
106
+ }
107
+ case "get":
108
+ return formatGetLines(op.task, state);
109
+ case "export": {
110
+ const tasks = filterTasks(op, state);
111
+ if (op.format === "markdown") return formatMarkdownExport(tasks);
112
+ return JSON.stringify({ tasks, nextId: state.nextId }, null, 2);
113
+ }
114
+ case "import":
115
+ return `Imported ${op.count} tasks${op.replaced ? " (replaced existing tasks)" : ""}`;
116
+ case "error":
117
+ return `Error: ${op.message}`;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Build the LLM-facing tool envelope after the store has committed the
123
+ * reducer's new state. `details` is the persistence + replay snapshot —
124
+ * `state/replay.ts` consumes this exact shape on session lifecycle events.
125
+ *
126
+ * Mirrors `packages/rpiv-ask-user-question/tool/response-envelope.ts:13-47`.
127
+ */
128
+ export function buildToolResult(
129
+ action: TaskAction,
130
+ params: TaskMutationParams,
131
+ state: TaskState,
132
+ op: Op,
133
+ ): { content: Array<{ type: "text"; text: string }>; details: TaskDetails } {
134
+ const text = formatContent(op, state);
135
+ const details: TaskDetails = {
136
+ action,
137
+ params: params as Record<string, unknown>,
138
+ tasks: state.tasks,
139
+ nextId: state.nextId,
140
+ ...(op.kind === "error" ? { error: op.message } : {}),
141
+ };
142
+ return { content: [{ type: "text", text }], details };
143
+ }