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,49 @@
1
+ import type { Task, TaskStatus } from "../tool/types.js";
2
+ import type { TaskState } from "./state.js";
3
+
4
+ /** Tasks excluding deleted tombstones — the canonical "what's visible". */
5
+ export function selectVisibleTasks(state: TaskState): readonly Task[] {
6
+ return state.tasks.filter((t) => t.status !== "deleted");
7
+ }
8
+
9
+ /**
10
+ * Group visible tasks by status. Iteration order at the call site uses
11
+ * (`completed`, `inProgress`, `pending`) to match the `/todos` header part
12
+ * order pinned by `todo.command.test.ts`.
13
+ */
14
+ export interface TasksByStatus {
15
+ pending: readonly Task[];
16
+ inProgress: readonly Task[];
17
+ deferred: readonly Task[];
18
+ completed: readonly Task[];
19
+ }
20
+ export function selectTasksByStatus(state: TaskState): TasksByStatus {
21
+ const visible = selectVisibleTasks(state);
22
+ return {
23
+ pending: visible.filter((t) => t.status === "pending"),
24
+ inProgress: visible.filter((t) => t.status === "in_progress"),
25
+ deferred: visible.filter((t) => t.status === "deferred"),
26
+ completed: visible.filter((t) => t.status === "completed"),
27
+ };
28
+ }
29
+
30
+ /** Total counts for the `/todos` header. */
31
+ export interface TodoCounts {
32
+ total: number;
33
+ pending: number;
34
+ inProgress: number;
35
+ deferred: number;
36
+ completed: number;
37
+ }
38
+ export function selectTodoCounts(state: TaskState): TodoCounts {
39
+ const groups = selectTasksByStatus(state);
40
+ return {
41
+ total: groups.pending.length + groups.inProgress.length + groups.deferred.length + groups.completed.length,
42
+ pending: groups.pending.length,
43
+ inProgress: groups.inProgress.length,
44
+ deferred: groups.deferred.length,
45
+ completed: groups.completed.length,
46
+ };
47
+ }
48
+
49
+ export const ACTIVE_STATUSES: ReadonlySet<TaskStatus> = new Set(["pending", "in_progress"]);
@@ -0,0 +1,362 @@
1
+ import type { Task, TaskAction, TaskMutationParams, TaskPriority, TaskStatus } from "../tool/types.js";
2
+ import { isTransitionValid } from "./invariants.js";
3
+ import type { TaskState } from "./state.js";
4
+ import { detectCycle } from "./task-graph.js";
5
+
6
+ export type Op =
7
+ | { kind: "create"; taskId: number }
8
+ | { kind: "update"; id: number; fromStatus: TaskStatus; toStatus: TaskStatus }
9
+ | { kind: "batch_create"; ids: number[] }
10
+ | { kind: "batch_update"; ids: number[] }
11
+ | { kind: "delete"; id: number; subject: string }
12
+ | { kind: "list"; statusFilter?: TaskStatus; priorityFilter?: TaskPriority; tagFilter?: string; blockedOnly: boolean; includeDeleted: boolean }
13
+ | { kind: "get"; task: Task }
14
+ | { kind: "clear"; count: number }
15
+ | { kind: "export"; format: "json" | "markdown"; statusFilter?: TaskStatus; priorityFilter?: TaskPriority; tagFilter?: string; blockedOnly: boolean; includeDeleted: boolean }
16
+ | { kind: "import"; count: number; replaced: boolean }
17
+ | { kind: "error"; message: string };
18
+
19
+ export interface ApplyResult {
20
+ state: TaskState;
21
+ op: Op;
22
+ }
23
+
24
+ function errorResult(state: TaskState, message: string): ApplyResult {
25
+ return { state, op: { kind: "error", message } };
26
+ }
27
+
28
+ function uniqueNumbers(values: number[] | undefined): number[] {
29
+ return [...new Set((values ?? []).filter((value) => Number.isFinite(value)))];
30
+ }
31
+
32
+ function normalizeTags(tags: string[] | undefined): string[] | undefined {
33
+ const normalized = [...new Set((tags ?? []).map((tag) => tag.trim()).filter(Boolean))];
34
+ return normalized.length ? normalized : undefined;
35
+ }
36
+
37
+ function findTask(state: TaskState, id: number): Task | undefined {
38
+ return state.tasks.find((task) => task.id === id);
39
+ }
40
+
41
+ function validateLiveReference(state: TaskState, field: string, id: number): string | undefined {
42
+ const task = findTask(state, id);
43
+ if (!task) return `${field}: #${id} not found`;
44
+ if (task.status === "deleted") return `${field}: #${id} is deleted`;
45
+ return undefined;
46
+ }
47
+
48
+ function wouldCreateParentCycle(tasks: readonly Task[], id: number, parentId: number): boolean {
49
+ let cursor: number | undefined = parentId;
50
+ const seen = new Set<number>();
51
+ while (cursor !== undefined) {
52
+ if (cursor === id) return true;
53
+ if (seen.has(cursor)) return true;
54
+ seen.add(cursor);
55
+ cursor = tasks.find((task) => task.id === cursor)?.parentId;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ function validateImportedTasks(tasks: readonly Task[]): string | undefined {
61
+ const ids = new Set<number>();
62
+ for (const task of tasks) {
63
+ if (!Number.isFinite(task.id) || task.id <= 0) return `invalid task id: ${task.id}`;
64
+ if (ids.has(task.id)) return `duplicate task id: #${task.id}`;
65
+ if (!task.subject?.trim()) return `subject required for #${task.id}`;
66
+ ids.add(task.id);
67
+ }
68
+ for (const task of tasks) {
69
+ if (task.parentId !== undefined && !ids.has(task.parentId)) return `parentId: #${task.parentId} not found`;
70
+ for (const dep of task.blockedBy ?? []) {
71
+ if (!ids.has(dep)) return `blockedBy: #${dep} not found`;
72
+ }
73
+ if (task.parentId !== undefined && wouldCreateParentCycle(tasks, task.id, task.parentId)) {
74
+ return `parentId would create a cycle for #${task.id}`;
75
+ }
76
+ if (detectCycle(tasks, task.id, task.blockedBy ?? [])) return `blockedBy would create a cycle for #${task.id}`;
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ function coerceTask(value: unknown, fallbackId: number): Task | undefined {
82
+ if (!value || typeof value !== "object") return undefined;
83
+ const v = value as Record<string, unknown>;
84
+ const subject = typeof v.subject === "string" ? v.subject.trim() : "";
85
+ if (!subject) return undefined;
86
+ const status = v.status === "in_progress" || v.status === "deferred" || v.status === "completed" || v.status === "deleted" ? v.status : "pending";
87
+ const task: Task = { id: typeof v.id === "number" && Number.isFinite(v.id) ? v.id : fallbackId, subject, status };
88
+ if (typeof v.description === "string" && v.description) task.description = v.description;
89
+ if (typeof v.activeForm === "string" && v.activeForm) task.activeForm = v.activeForm;
90
+ if (v.priority === "low" || v.priority === "medium" || v.priority === "high" || v.priority === "urgent") task.priority = v.priority;
91
+ if (typeof v.parentId === "number" && Number.isFinite(v.parentId)) task.parentId = v.parentId;
92
+ const blockedBy = Array.isArray(v.blockedBy) ? uniqueNumbers(v.blockedBy as number[]) : undefined;
93
+ if (blockedBy?.length) task.blockedBy = blockedBy;
94
+ const tags = Array.isArray(v.tags) ? normalizeTags(v.tags.filter((tag): tag is string => typeof tag === "string")) : undefined;
95
+ if (tags) task.tags = tags;
96
+ if (typeof v.owner === "string" && v.owner) task.owner = v.owner;
97
+ if (v.metadata && typeof v.metadata === "object" && !Array.isArray(v.metadata)) task.metadata = { ...(v.metadata as Record<string, unknown>) };
98
+ return task;
99
+ }
100
+
101
+ function parseJsonImport(content: string): Task[] | undefined {
102
+ const parsed = JSON.parse(content) as unknown;
103
+ let rawTasks: unknown[] | undefined;
104
+ if (Array.isArray(parsed)) {
105
+ rawTasks = parsed;
106
+ } else if (parsed && typeof parsed === "object" && Array.isArray((parsed as { tasks?: unknown }).tasks)) {
107
+ rawTasks = (parsed as { tasks: unknown[] }).tasks;
108
+ }
109
+ if (!rawTasks) return undefined;
110
+ return rawTasks.map((value, index) => coerceTask(value, index + 1)).filter((task): task is Task => task !== undefined);
111
+ }
112
+
113
+ function parseMarkdownImport(content: string): Task[] {
114
+ const tasks: Task[] = [];
115
+ const stack: Array<{ indent: number; id: number }> = [];
116
+ for (const line of content.split(/\r?\n/)) {
117
+ const match = /^(\s*)- \[([ xX])\](?: #(\d+))?(?: \((low|medium|high|urgent)\))? (.+?)(?: \[(#[^\]]+)\])?$/.exec(line);
118
+ if (!match) continue;
119
+ const indent = match[1].length;
120
+ const id = match[3] ? Number(match[3]) : tasks.length + 1;
121
+ const tags = match[6] ? normalizeTags(match[6].split(/\s+/).map((tag) => tag.replace(/^#/, ""))) : undefined;
122
+ const subjectParts = match[5].split(/\s+⛓\s+/);
123
+ const blockedBy = subjectParts[1] ? uniqueNumbers(subjectParts[1].split(/\s*,\s*/).map((ref) => Number(ref.replace(/^#/, "")))) : [];
124
+ while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
125
+ const rawSubject = subjectParts[0];
126
+ const isDeferred = /\s+\{deferred\}$/.test(rawSubject);
127
+ let status: TaskStatus = "pending";
128
+ if (match[2].toLowerCase() === "x") status = "completed";
129
+ else if (isDeferred) status = "deferred";
130
+ const task: Task = {
131
+ id,
132
+ subject: rawSubject.replace(/\s+\{deferred\}$/, "").trim(),
133
+ status,
134
+ };
135
+ if (match[4]) task.priority = match[4] as TaskPriority;
136
+ if (stack.length) task.parentId = stack[stack.length - 1].id;
137
+ if (blockedBy.length) task.blockedBy = blockedBy;
138
+ if (tags) task.tags = tags;
139
+ tasks.push(task);
140
+ stack.push({ indent, id });
141
+ }
142
+ return tasks;
143
+ }
144
+
145
+ function remapImportedTasks(imported: readonly Task[], state: TaskState, replace: boolean): Task[] {
146
+ if (replace) {
147
+ return imported.map((task) => ({
148
+ ...task,
149
+ blockedBy: task.blockedBy ? [...task.blockedBy] : undefined,
150
+ tags: task.tags ? [...task.tags] : undefined,
151
+ }));
152
+ }
153
+ const idMap = new Map<number, number>();
154
+ imported.forEach((task, index) => idMap.set(task.id, state.nextId + index));
155
+ return imported.map((task) => ({
156
+ ...task,
157
+ id: idMap.get(task.id) ?? task.id,
158
+ parentId: task.parentId !== undefined ? (idMap.get(task.parentId) ?? task.parentId) : undefined,
159
+ blockedBy: task.blockedBy?.map((id) => idMap.get(id) ?? id),
160
+ tags: task.tags ? [...task.tags] : undefined,
161
+ }));
162
+ }
163
+
164
+ export function applyTaskMutation(state: TaskState, action: TaskAction, params: TaskMutationParams): ApplyResult {
165
+ switch (action) {
166
+ case "create": {
167
+ if (!params.subject?.trim()) return errorResult(state, "subject required for create");
168
+ if (params.parentId !== undefined && params.parentId !== null) {
169
+ const err = validateLiveReference(state, "parentId", params.parentId);
170
+ if (err) return errorResult(state, err);
171
+ }
172
+ for (const dep of uniqueNumbers(params.blockedBy)) {
173
+ const err = validateLiveReference(state, "blockedBy", dep);
174
+ if (err) return errorResult(state, err);
175
+ }
176
+ const newTask: Task = { id: state.nextId, subject: params.subject.trim(), status: "pending" };
177
+ if (params.description) newTask.description = params.description;
178
+ if (params.activeForm) newTask.activeForm = params.activeForm;
179
+ if (params.priority) newTask.priority = params.priority;
180
+ if (params.parentId !== undefined && params.parentId !== null) newTask.parentId = params.parentId;
181
+ const blockedBy = uniqueNumbers(params.blockedBy);
182
+ if (blockedBy.length) newTask.blockedBy = blockedBy;
183
+ const tags = normalizeTags(params.tags);
184
+ if (tags) newTask.tags = tags;
185
+ if (params.owner) newTask.owner = params.owner;
186
+ if (params.metadata) newTask.metadata = { ...params.metadata };
187
+ return { state: { tasks: [...state.tasks, newTask], nextId: state.nextId + 1 }, op: { kind: "create", taskId: newTask.id } };
188
+ }
189
+
190
+ case "update": {
191
+ if (params.id === undefined) return errorResult(state, "id required for update");
192
+ const idx = state.tasks.findIndex((t) => t.id === params.id);
193
+ if (idx === -1) return errorResult(state, `#${params.id} not found`);
194
+ const current = state.tasks[idx];
195
+ const hasMutation =
196
+ params.subject !== undefined || params.description !== undefined || params.activeForm !== undefined || params.status !== undefined ||
197
+ params.priority !== undefined || params.parentId !== undefined || params.clearParent === true || params.owner !== undefined ||
198
+ params.metadata !== undefined || params.tags !== undefined || (params.addTags?.length ?? 0) > 0 || (params.removeTags?.length ?? 0) > 0 ||
199
+ (params.addBlockedBy?.length ?? 0) > 0 || (params.removeBlockedBy?.length ?? 0) > 0;
200
+ if (!hasMutation) return errorResult(state, "update requires at least one mutable field");
201
+
202
+ let newStatus = current.status;
203
+ if (params.status !== undefined) {
204
+ if (!isTransitionValid(current.status, params.status)) return errorResult(state, `illegal transition ${current.status} → ${params.status}`);
205
+ newStatus = params.status;
206
+ }
207
+
208
+ let newParentId = current.parentId;
209
+ if (params.clearParent === true) newParentId = undefined;
210
+ if (params.parentId !== undefined) {
211
+ if (params.parentId === null) newParentId = undefined;
212
+ else {
213
+ if (params.parentId === current.id) return errorResult(state, `cannot parent #${current.id} on itself`);
214
+ const err = validateLiveReference(state, "parentId", params.parentId);
215
+ if (err) return errorResult(state, err);
216
+ if (wouldCreateParentCycle(state.tasks, current.id, params.parentId)) return errorResult(state, "parentId would create a cycle in the task tree");
217
+ newParentId = params.parentId;
218
+ }
219
+ }
220
+
221
+ let newBlockedBy = current.blockedBy ? [...current.blockedBy] : [];
222
+ if (params.removeBlockedBy?.length) newBlockedBy = newBlockedBy.filter((dep) => !new Set(params.removeBlockedBy).has(dep));
223
+ if (params.addBlockedBy?.length) {
224
+ for (const dep of params.addBlockedBy) {
225
+ if (dep === current.id) return errorResult(state, `cannot block #${current.id} on itself`);
226
+ const err = validateLiveReference(state, "addBlockedBy", dep);
227
+ if (err) return errorResult(state, err);
228
+ if (!newBlockedBy.includes(dep)) newBlockedBy.push(dep);
229
+ }
230
+ if (detectCycle(state.tasks, current.id, newBlockedBy)) return errorResult(state, "addBlockedBy would create a cycle in the blockedBy graph");
231
+ }
232
+
233
+ let newTags = params.tags !== undefined ? normalizeTags(params.tags) : current.tags ? [...current.tags] : undefined;
234
+ if (params.addTags?.length) newTags = normalizeTags([...(newTags ?? []), ...params.addTags]);
235
+ if (params.removeTags?.length && newTags) {
236
+ const remove = new Set(params.removeTags.map((tag) => tag.trim()).filter(Boolean));
237
+ newTags = normalizeTags(newTags.filter((tag) => !remove.has(tag)));
238
+ }
239
+
240
+ let newMetadata = current.metadata;
241
+ if (params.metadata !== undefined) {
242
+ const merged: Record<string, unknown> = { ...(current.metadata ?? {}) };
243
+ for (const [k, v] of Object.entries(params.metadata)) v === null ? delete merged[k] : (merged[k] = v);
244
+ newMetadata = Object.keys(merged).length ? merged : undefined;
245
+ }
246
+
247
+ const updated: Task = { ...current, status: newStatus };
248
+ if (params.subject !== undefined) updated.subject = params.subject;
249
+ if (params.description !== undefined) updated.description = params.description;
250
+ if (params.activeForm !== undefined) updated.activeForm = params.activeForm;
251
+ if (params.priority !== undefined) updated.priority = params.priority;
252
+ if (params.owner !== undefined) updated.owner = params.owner;
253
+ if (newParentId === undefined) delete updated.parentId;
254
+ else updated.parentId = newParentId;
255
+ if (newBlockedBy.length) updated.blockedBy = newBlockedBy;
256
+ else delete updated.blockedBy;
257
+ if (newTags) updated.tags = newTags;
258
+ else delete updated.tags;
259
+ if (newMetadata === undefined) delete updated.metadata;
260
+ else updated.metadata = newMetadata;
261
+
262
+ const newTasks = [...state.tasks];
263
+ newTasks[idx] = updated;
264
+ return { state: { tasks: newTasks, nextId: state.nextId }, op: { kind: "update", id: updated.id, fromStatus: current.status, toStatus: newStatus } };
265
+ }
266
+
267
+ case "batch_create": {
268
+ if (!params.items?.length) return errorResult(state, "items required for batch_create");
269
+ let working = state;
270
+ const ids: number[] = [];
271
+ for (let i = 0; i < params.items.length; i++) {
272
+ const result = applyTaskMutation(working, "create", { ...params.items[i], action: "create" });
273
+ if (result.op.kind === "error") return errorResult(state, `item ${i + 1}: ${result.op.message}`);
274
+ if (result.op.kind === "create") ids.push(result.op.taskId);
275
+ working = result.state;
276
+ }
277
+ return { state: working, op: { kind: "batch_create", ids } };
278
+ }
279
+
280
+ case "batch_update": {
281
+ if (!params.items?.length) return errorResult(state, "items required for batch_update");
282
+ let working = state;
283
+ const ids: number[] = [];
284
+ for (let i = 0; i < params.items.length; i++) {
285
+ const result = applyTaskMutation(working, "update", { ...params.items[i], action: "update" });
286
+ if (result.op.kind === "error") return errorResult(state, `item ${i + 1}: ${result.op.message}`);
287
+ if (result.op.kind === "update") ids.push(result.op.id);
288
+ working = result.state;
289
+ }
290
+ return { state: working, op: { kind: "batch_update", ids } };
291
+ }
292
+
293
+ case "list":
294
+ return {
295
+ state,
296
+ op: {
297
+ kind: "list",
298
+ includeDeleted: params.includeDeleted === true,
299
+ blockedOnly: params.blockedOnly === true,
300
+ ...(params.status ? { statusFilter: params.status } : {}),
301
+ ...(params.priority ? { priorityFilter: params.priority } : {}),
302
+ ...(params.tag ? { tagFilter: params.tag } : {}),
303
+ },
304
+ };
305
+
306
+ case "get": {
307
+ if (params.id === undefined) return errorResult(state, "id required for get");
308
+ const task = findTask(state, params.id);
309
+ if (!task) return errorResult(state, `#${params.id} not found`);
310
+ return { state, op: { kind: "get", task } };
311
+ }
312
+
313
+ case "delete": {
314
+ if (params.id === undefined) return errorResult(state, "id required for delete");
315
+ const idx = state.tasks.findIndex((t) => t.id === params.id);
316
+ if (idx === -1) return errorResult(state, `#${params.id} not found`);
317
+ const current = state.tasks[idx];
318
+ if (current.status === "deleted") return errorResult(state, `#${current.id} is already deleted`);
319
+ const dependents = state.tasks.filter((task) => task.status !== "deleted" && task.blockedBy?.includes(current.id));
320
+ if (dependents.length > 0) return errorResult(state, `cannot delete #${current.id}; still blocks ${dependents.map((task) => `#${task.id}`).join(", ")}`);
321
+ const children = state.tasks.filter((task) => task.status !== "deleted" && task.parentId === current.id);
322
+ if (children.length > 0) return errorResult(state, `cannot delete #${current.id}; still has subtasks ${children.map((task) => `#${task.id}`).join(", ")}`);
323
+ const newTasks = [...state.tasks];
324
+ newTasks[idx] = { ...current, status: "deleted" };
325
+ return { state: { tasks: newTasks, nextId: state.nextId }, op: { kind: "delete", id: current.id, subject: current.subject } };
326
+ }
327
+
328
+ case "clear":
329
+ return { state: { tasks: [], nextId: 1 }, op: { kind: "clear", count: state.tasks.length } };
330
+
331
+ case "export":
332
+ return {
333
+ state,
334
+ op: {
335
+ kind: "export",
336
+ format: params.format ?? "json",
337
+ includeDeleted: params.includeDeleted === true,
338
+ blockedOnly: params.blockedOnly === true,
339
+ ...(params.status ? { statusFilter: params.status } : {}),
340
+ ...(params.priority ? { priorityFilter: params.priority } : {}),
341
+ ...(params.tag ? { tagFilter: params.tag } : {}),
342
+ },
343
+ };
344
+
345
+ case "import": {
346
+ if (!params.content?.trim()) return errorResult(state, "content required for import");
347
+ let imported: Task[];
348
+ try {
349
+ imported = (params.format ?? "json") === "markdown" ? parseMarkdownImport(params.content) : (parseJsonImport(params.content) ?? []);
350
+ } catch (err) {
351
+ return errorResult(state, `invalid import content: ${(err as Error).message}`);
352
+ }
353
+ if (imported.length === 0) return errorResult(state, "import content contained no tasks");
354
+ const replace = params.replace === true;
355
+ const remapped = remapImportedTasks(imported, state, replace);
356
+ const merged = replace ? remapped : [...state.tasks, ...remapped];
357
+ const err = validateImportedTasks(merged);
358
+ if (err) return errorResult(state, err);
359
+ return { state: { tasks: merged, nextId: Math.max(0, ...merged.map((task) => task.id)) + 1 }, op: { kind: "import", count: remapped.length, replaced: replace } };
360
+ }
361
+ }
362
+ }
@@ -0,0 +1,18 @@
1
+ import type { Task } from "../tool/types.js";
2
+
3
+ /**
4
+ * Canonical state for the todo tool. Single source of truth — both the reducer
5
+ * (`state/state-reducer.ts`) and the live store cell (`state/store.ts`) read
6
+ * this shape. Replay (`state/replay.ts`) returns a fresh `TaskState`; the
7
+ * lifecycle handlers in `index.ts` write it via `replaceState`.
8
+ *
9
+ * The shape is intentionally minimal — no derived caches or runtime cells.
10
+ * Selectors in `state/selectors.ts` are pure of `TaskState` and own all
11
+ * derivations (visible/grouped/counted/etc).
12
+ */
13
+ export interface TaskState {
14
+ tasks: Task[];
15
+ nextId: number;
16
+ }
17
+
18
+ export const EMPTY_STATE: TaskState = { tasks: [], nextId: 1 };
@@ -0,0 +1,52 @@
1
+ import type { Task } from "../tool/types.js";
2
+ import { EMPTY_STATE, type TaskState } from "./state.js";
3
+
4
+ /**
5
+ * Module-level live state cell. Pre-refactor this lived as bare `tasks` /
6
+ * `nextId` consts in `todo.ts`; centralizing here keeps the store as the
7
+ * single mutation seam and lets the reducer remain pure.
8
+ */
9
+ let state: TaskState = { tasks: [...EMPTY_STATE.tasks], nextId: EMPTY_STATE.nextId };
10
+
11
+ /**
12
+ * Live tasks accessor. Returned `readonly Task[]` so callers cannot mutate the
13
+ * live cell. Consumers must not cast back.
14
+ */
15
+ export function getTodos(): readonly Task[] {
16
+ return state.tasks;
17
+ }
18
+
19
+ export function getNextId(): number {
20
+ return state.nextId;
21
+ }
22
+
23
+ /** Snapshot accessor used by reducer callers to pass canonical state in. */
24
+ export function getState(): TaskState {
25
+ return state;
26
+ }
27
+
28
+ /**
29
+ * Replay seam. Lifecycle handlers in `index.ts` call this on
30
+ * `session_start` / `session_compact` / `session_tree` after
31
+ * `replayFromBranch` decodes the latest snapshot.
32
+ */
33
+ export function replaceState(next: TaskState): void {
34
+ state = next;
35
+ }
36
+
37
+ /**
38
+ * Post-reducer commit seam. Tool execute() calls this with the reducer's
39
+ * `state` output to publish the new canonical state to live readers.
40
+ */
41
+ export function commitState(next: TaskState): void {
42
+ state = next;
43
+ }
44
+
45
+ /**
46
+ * Test-setup reset. Wired into the global `test/setup.ts` `beforeEach` via
47
+ * the existing `__resetState` import path. Name preserved verbatim — see
48
+ * Plan §Decisions §Decision 7.
49
+ */
50
+ export function __resetState(): void {
51
+ state = { tasks: [...EMPTY_STATE.tasks], nextId: EMPTY_STATE.nextId };
52
+ }
@@ -0,0 +1,57 @@
1
+ import type { Task } from "../tool/types.js";
2
+
3
+ /**
4
+ * Detect whether merging `newBlockedBy` into `taskId`'s `blockedBy` set would
5
+ * introduce a cycle in the dependency graph.
6
+ *
7
+ * Pure of any module state; takes the existing `taskList` and the proposed
8
+ * additions explicitly so the reducer can ask "would this update cycle?"
9
+ * without mutating state first.
10
+ */
11
+ export function detectCycle(taskList: readonly Task[], taskId: number, newBlockedBy: readonly number[]): boolean {
12
+ const edges = new Map<number, number[]>();
13
+ for (const t of taskList) {
14
+ if (t.id === taskId) {
15
+ const merged = new Set([...(t.blockedBy ?? []), ...newBlockedBy]);
16
+ edges.set(t.id, [...merged]);
17
+ } else {
18
+ edges.set(t.id, t.blockedBy ? [...t.blockedBy] : []);
19
+ }
20
+ }
21
+
22
+ const visiting = new Set<number>();
23
+ const visited = new Set<number>();
24
+ const hasCycleFrom = (node: number): boolean => {
25
+ if (visiting.has(node)) return true;
26
+ if (visited.has(node)) return false;
27
+ visiting.add(node);
28
+ for (const nb of edges.get(node) ?? []) {
29
+ if (hasCycleFrom(nb)) return true;
30
+ }
31
+ visiting.delete(node);
32
+ visited.add(node);
33
+ return false;
34
+ };
35
+
36
+ for (const node of edges.keys()) {
37
+ if (hasCycleFrom(node)) return true;
38
+ }
39
+ return false;
40
+ }
41
+
42
+ /**
43
+ * Build the inverse adjacency map: for each task `T`, which other tasks list
44
+ * `T` in their `blockedBy`. Consumed by the `get` action's "blocks: #x, #y"
45
+ * suffix line.
46
+ */
47
+ export function deriveBlocks(taskList: readonly Task[]): Map<number, number[]> {
48
+ const blocks = new Map<number, number[]>();
49
+ for (const t of taskList) {
50
+ for (const dep of t.blockedBy ?? []) {
51
+ const arr = blocks.get(dep) ?? [];
52
+ arr.push(t.id);
53
+ blocks.set(dep, arr);
54
+ }
55
+ }
56
+ return blocks;
57
+ }