@polderlabs/bizar 6.2.4 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +31 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/sandbox.mjs +220 -0
  128. package/cli/commands/validate.mjs +252 -288
  129. package/cli/dev-link.test.mjs +11 -11
  130. package/cli/doctor.mjs +131 -213
  131. package/cli/install.mjs +33 -37
  132. package/cli/install.test.mjs +9 -9
  133. package/cli/provision-claude.mjs +893 -0
  134. package/cli/provision.mjs +330 -226
  135. package/cli/provision.test.mjs +102 -0
  136. package/cli/utils.mjs +72 -58
  137. package/config/AGENTS.md +28 -28
  138. package/config/agents/_shared/AGENT_BASELINE.md +104 -684
  139. package/config/skills/bizar/SKILL.md +197 -0
  140. package/config/skills/cubesandbox/SKILL.md +148 -0
  141. package/config/skills/harness-engineering/SKILL.md +142 -0
  142. package/install.sh +66 -44
  143. package/package.json +19 -11
  144. package/packages/sdk/ARCHITECTURE.md +40 -36
  145. package/packages/sdk/package-lock.json +32 -0
  146. package/packages/sdk/package.json +15 -5
  147. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  148. package/packages/sdk/src/fingerprint.ts +0 -0
  149. package/packages/sdk/src/index.ts +38 -73
  150. package/packages/sdk/src/mcp/bin.ts +51 -0
  151. package/packages/sdk/src/mcp/server.ts +498 -0
  152. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  153. package/packages/sdk/tests/sdk.test.mjs +148 -0
  154. package/packages/sdk/vitest.config.ts +1 -1
  155. package/scripts/bh-full-e2e.mjs +166 -363
  156. package/scripts/check-deps.mjs +28 -46
  157. package/scripts/mirror-claude-md.sh +78 -0
  158. package/scripts/test-in-container.sh +157 -0
  159. package/templates/clean-state-checklist.md +3 -3
  160. package/templates/sprint-contract.md +2 -2
  161. package/bizar-dash/.bizar/activity.log +0 -3
  162. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  163. package/bizar-dash/.omx/state/session.json +0 -10
  164. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  165. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  166. package/bizar-dash/bizar-design/canvas.html +0 -281
  167. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  168. package/bizar-dash/bizar-design/components.css +0 -1665
  169. package/bizar-dash/bizar-design/components.html +0 -557
  170. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  172. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  174. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  176. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  177. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  178. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  179. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  180. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  181. package/bizar-dash/bizar-design/icons.svg +0 -44
  182. package/bizar-dash/bizar-design/index.html +0 -192
  183. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  185. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  189. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  190. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  191. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  192. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  193. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  194. package/bizar-dash/bizar-design/prototype.js +0 -114
  195. package/bizar-dash/bizar-design/tokens.css +0 -106
  196. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  197. package/bizar-dash/node_modules/.package-lock.json +0 -6
  198. package/bizar-dash/package-lock.json +0 -6
  199. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  200. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  201. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  202. package/cli/commands/cline-cmd.mjs +0 -289
  203. package/cli/commands/validate.test.mjs +0 -344
  204. package/cli/doctor.test.mjs +0 -350
  205. package/config/cline.json.template +0 -342
  206. package/packages/sdk/src/client.ts +0 -188
  207. package/packages/sdk/src/cline-events.ts +0 -134
  208. package/packages/sdk/src/cline-types.ts +0 -66
  209. package/packages/sdk/src/cline.ts +0 -339
  210. package/packages/sdk/src/errors.ts +0 -129
  211. package/packages/sdk/src/events.ts +0 -153
  212. package/packages/sdk/src/types.ts +0 -176
  213. package/packages/sdk/tests/client.test.ts +0 -217
  214. package/packages/sdk/tests/errors.test.ts +0 -108
  215. package/packages/sdk/tests/events.test.ts +0 -139
  216. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  217. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  218. package/plugins/bizar/ARCHITECTURE.md +0 -142
  219. package/plugins/bizar/CONSTRAINTS.md +0 -67
  220. package/plugins/bizar/LICENSE +0 -21
  221. package/plugins/bizar/README.md +0 -448
  222. package/plugins/bizar/index.ts +0 -872
  223. package/plugins/bizar/package.json +0 -39
  224. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  225. package/plugins/bizar/src/background-state.ts +0 -641
  226. package/plugins/bizar/src/background.ts +0 -1806
  227. package/plugins/bizar/src/cline-runner.ts +0 -203
  228. package/plugins/bizar/src/clineruntime.ts +0 -227
  229. package/plugins/bizar/src/commands-impl.ts +0 -151
  230. package/plugins/bizar/src/commands.ts +0 -1799
  231. package/plugins/bizar/src/compaction.d.mts +0 -48
  232. package/plugins/bizar/src/compaction.mjs +0 -192
  233. package/plugins/bizar/src/dashboard-client.ts +0 -233
  234. package/plugins/bizar/src/event-stream.ts +0 -606
  235. package/plugins/bizar/src/fingerprint.ts +0 -120
  236. package/plugins/bizar/src/handoff.ts +0 -79
  237. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  238. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  239. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  240. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  241. package/plugins/bizar/src/http-client.ts +0 -467
  242. package/plugins/bizar/src/key-rotation.ts +0 -218
  243. package/plugins/bizar/src/logger.ts +0 -144
  244. package/plugins/bizar/src/loop-engineering.ts +0 -241
  245. package/plugins/bizar/src/loop.ts +0 -176
  246. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  247. package/plugins/bizar/src/odin.ts +0 -227
  248. package/plugins/bizar/src/options.ts +0 -470
  249. package/plugins/bizar/src/plan-fs.ts +0 -323
  250. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  251. package/plugins/bizar/src/report.ts +0 -178
  252. package/plugins/bizar/src/research-prompt.ts +0 -35
  253. package/plugins/bizar/src/serve-info.ts +0 -228
  254. package/plugins/bizar/src/serve.ts +0 -496
  255. package/plugins/bizar/src/settings.ts +0 -349
  256. package/plugins/bizar/src/state.ts +0 -298
  257. package/plugins/bizar/src/tool-discipline.ts +0 -105
  258. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  259. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  260. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  261. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  262. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  263. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  264. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  265. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  266. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  267. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  268. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  269. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  270. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  271. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  272. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  273. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  274. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  275. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  276. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  277. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  278. package/plugins/bizar/src/tools/team-status.ts +0 -76
  279. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  280. package/plugins/bizar/src/trajectory.ts +0 -104
  281. package/plugins/bizar/tests/README.md +0 -99
  282. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  283. package/plugins/bizar/tests/background-state.test.ts +0 -277
  284. package/plugins/bizar/tests/background.test.ts +0 -402
  285. package/plugins/bizar/tests/block.test.ts +0 -195
  286. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  287. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  288. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  289. package/plugins/bizar/tests/commands.test.ts +0 -584
  290. package/plugins/bizar/tests/compaction.test.ts +0 -264
  291. package/plugins/bizar/tests/config.test.ts +0 -128
  292. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  293. package/plugins/bizar/tests/dispose.test.ts +0 -336
  294. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  295. package/plugins/bizar/tests/event.test.ts +0 -262
  296. package/plugins/bizar/tests/fingerprint.test.ts +0 -162
  297. package/plugins/bizar/tests/http-client.test.ts +0 -404
  298. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  299. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  300. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  301. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  302. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  303. package/plugins/bizar/tests/loop.test.ts +0 -397
  304. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  305. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  306. package/plugins/bizar/tests/odin.test.ts +0 -125
  307. package/plugins/bizar/tests/options.test.ts +0 -329
  308. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  309. package/plugins/bizar/tests/safety.test.ts +0 -256
  310. package/plugins/bizar/tests/serve.test.ts +0 -339
  311. package/plugins/bizar/tests/settings.test.ts +0 -351
  312. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  313. package/plugins/bizar/tests/state.test.ts +0 -276
  314. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  315. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  316. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  317. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  318. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  319. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  320. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  321. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  322. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  323. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  324. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  325. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  326. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  327. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  328. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  329. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  330. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  331. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  332. package/plugins/bizar/tsconfig.json +0 -29
@@ -1,203 +0,0 @@
1
- /**
2
- * Init-helpers regression tests (v0.5.2).
3
- *
4
- * Covers the postmortem-2026-06-18 Layer 1 fix: `client.session.list()`
5
- * is now raced against a 1-second timeout. Without this, a slow or
6
- * broken session store would hang the plugin's `init()` forever and
7
- * stall the UI on a blank screen.
8
- *
9
- * Two layers of test:
10
- * 1) `withTimeout` — pure unit tests on the timeout helper itself.
11
- * 2) `readValidSessionIds` — integration-style test that passes a
12
- * fake `PluginInput` whose `client.session.list()` never resolves
13
- * and verifies that the function returns within ~1.1s.
14
- *
15
- * The second test is what would have caught the original bug: the
16
- * function must NOT block forever on a slow session store.
17
- */
18
-
19
- import { describe, test, expect } from "bun:test";
20
-
21
- import {
22
- withTimeout,
23
- readValidSessionIds,
24
- } from "../index.js";
25
-
26
- // ---------------------------------------------------------------------------
27
- // `withTimeout` — pure unit tests
28
- // ---------------------------------------------------------------------------
29
-
30
- describe("withTimeout", () => {
31
- test("resolves with the promise's value when it completes in time", async () => {
32
- const result = await withTimeout(
33
- Promise.resolve("ok"),
34
- 1000,
35
- "test",
36
- );
37
- expect(result).toBe("ok");
38
- });
39
-
40
- test("rejects with a labeled error when the promise takes too long", async () => {
41
- const hanging = new Promise<string>(() => {
42
- // never resolves
43
- });
44
- const start = Date.now();
45
- let caught: Error | null = null;
46
- try {
47
- await withTimeout(hanging, 100, "hanging-promise");
48
- } catch (err) {
49
- caught = err as Error;
50
- }
51
- const elapsed = Date.now() - start;
52
- expect(caught).not.toBeNull();
53
- expect(caught?.message).toContain("hanging-promise");
54
- expect(caught?.message).toContain("100ms");
55
- // Should fire within ~100ms (allow 50ms of slack for event-loop jitter).
56
- expect(elapsed).toBeGreaterThanOrEqual(95);
57
- expect(elapsed).toBeLessThan(500);
58
- });
59
-
60
- test("propagates rejections from the underlying promise", async () => {
61
- const failing = Promise.reject(new Error("underlying failure"));
62
- let caught: Error | null = null;
63
- try {
64
- await withTimeout(failing, 1000, "test");
65
- } catch (err) {
66
- caught = err as Error;
67
- }
68
- expect(caught).not.toBeNull();
69
- expect(caught?.message).toBe("underlying failure");
70
- });
71
-
72
- test("clears the timer on success (no late rejection)", async () => {
73
- // Resolve after 50ms, well within the 1000ms timeout. The timer
74
- // should be cleared; we can't directly observe the cleanup, but
75
- // the test verifies the function returns the value without ever
76
- // throwing (the cleared timer's callback never fires).
77
- const result = await withTimeout(
78
- new Promise((resolve) => setTimeout(() => resolve("late"), 50)),
79
- 1000,
80
- "test",
81
- );
82
- expect(result).toBe("late");
83
- // Wait long enough that a leaked timer would have fired.
84
- await new Promise((r) => setTimeout(r, 100));
85
- });
86
- });
87
-
88
- // ---------------------------------------------------------------------------
89
- // `readValidSessionIds` — the postmortem Layer 1 fix
90
- // ---------------------------------------------------------------------------
91
-
92
- /**
93
- * Minimal `PluginInput` shape for testing `readValidSessionIds`. We
94
- * only need `client`; the other fields are never read by the
95
- * function under test.
96
- */
97
- function makeInput(client: unknown): { client: unknown } {
98
- return { client } as never;
99
- }
100
-
101
- describe("readValidSessionIds — postmortem Layer 1 fix (v0.5.2)", () => {
102
- test("returns the session IDs when client.session.list() resolves", async () => {
103
- const input = makeInput({
104
- session: {
105
- list: async () => ({
106
- data: [
107
- { id: "ses_a" },
108
- { id: "ses_b" },
109
- ],
110
- }),
111
- },
112
- });
113
- const ids = await readValidSessionIds(input as never);
114
- expect(ids).toBeInstanceOf(Set);
115
- expect([...ids].sort()).toEqual(["ses_a", "ses_b"]);
116
- });
117
-
118
- test("returns the session IDs when client.session.list() returns an array directly", async () => {
119
- const input = makeInput({
120
- session: {
121
- list: async () => [{ id: "ses_x" }, { id: "ses_y" }],
122
- },
123
- });
124
- const ids = await readValidSessionIds(input as never);
125
- expect([...ids].sort()).toEqual(["ses_x", "ses_y"]);
126
- });
127
-
128
- test("returns empty set when client.session is missing", async () => {
129
- const input = makeInput({});
130
- const ids = await readValidSessionIds(input as never);
131
- expect(ids.size).toBe(0);
132
- });
133
-
134
- test("returns empty set when client.session.list is not a function", async () => {
135
- const input = makeInput({ session: { list: "not-a-function" } });
136
- const ids = await readValidSessionIds(input as never);
137
- expect(ids.size).toBe(0);
138
- });
139
-
140
- test("returns empty set when client.session.list() rejects", async () => {
141
- const input = makeInput({
142
- session: {
143
- list: async () => {
144
- throw new Error("session store down");
145
- },
146
- },
147
- });
148
- const ids = await readValidSessionIds(input as never);
149
- expect(ids.size).toBe(0);
150
- });
151
-
152
- test(
153
- "REGRESSION: returns within 1.5s when client.session.list() hangs forever (postmortem Layer 1)",
154
- async () => {
155
- // This is the actual bug from the 2026-06-18 postmortem. Before
156
- // the fix, the call would hang forever and the plugin's `init()`
157
- // would never resolve.
158
- const input = makeInput({
159
- session: {
160
- list: () => new Promise(() => {
161
- // never resolves — simulates a stuck session store
162
- }),
163
- },
164
- });
165
- const start = Date.now();
166
- const ids = await readValidSessionIds(input as never);
167
- const elapsed = Date.now() - start;
168
- // The function should return within ~1s (the timeout). We allow
169
- // 500ms of slack for event-loop jitter.
170
- expect(elapsed).toBeLessThan(1500);
171
- // The fallback is an empty set — the age-based cleanup still
172
- // runs (spec §4.6).
173
- expect(ids.size).toBe(0);
174
- },
175
- );
176
-
177
- test(
178
- "REGRESSION: late rejection from hanging list() does not crash the process",
179
- async () => {
180
- // After the 1s timeout fires, the original `list()` call may
181
- // eventually reject (or it may never). The function attaches a
182
- // no-op `.catch(() => undefined)` to suppress the unhandled-
183
- // rejection. We can't directly observe unhandled rejections in
184
- // bun:test, but we can verify the function returns cleanly.
185
- let rejectFn: ((err: Error) => void) | undefined;
186
- const input = makeInput({
187
- session: {
188
- list: () => new Promise((_, reject: (err: Error) => void) => {
189
- rejectFn = reject;
190
- }),
191
- },
192
- });
193
- const ids = await readValidSessionIds(input as never);
194
- expect(ids.size).toBe(0);
195
- // Now reject the hanging promise AFTER the function returned.
196
- // If the no-op catch is missing, this would be an unhandled
197
- // rejection. With it, the process keeps running.
198
- rejectFn?.(new Error("late rejection"));
199
- // Give the microtask queue a chance to run.
200
- await new Promise((r) => setTimeout(r, 10));
201
- },
202
- );
203
- });
@@ -1,349 +0,0 @@
1
- import { type AgentTool } from "@cline/sdk";
2
- /**
3
- * slash-command.test.ts — Integration tests for the full slash-command
4
- * hook → parser → side-effect → file-I/O path.
5
- *
6
- * Exercises:
7
- * - `/plan new foo` → `plans/foo/meta.json` + `plans/foo/plan.json` on disk
8
- * - `/plan list` → response contains slugs of seeded plans
9
- * - `/plan add foo --title X --type task` → element written to `plan.json`
10
- * - `/plan status foo approved` → `meta.json` status updated
11
- * - `/plan delete foo <elementId>` → element removed from `plan.json`
12
- * - `/plan wait foo` → deferred response (< 100 ms, no blocking)
13
- * - `/plan new foo` on existing slug → refusal, no clobber
14
- * - `/plan comment foo "text"` → comment written to `plan.json`
15
- * - `/plan comments foo` → response lists the comment
16
- */
17
-
18
- import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test";
19
- import {
20
- mkdtempSync,
21
- rmSync,
22
- existsSync,
23
- readFileSync,
24
- writeFileSync,
25
- mkdirSync,
26
- } from "node:fs";
27
- import { join } from "node:path";
28
- import { tmpdir } from "node:os";
29
-
30
- import { parseSlashCommand } from "../../src/commands";
31
- import {
32
- executeSideEffect,
33
- type ExecutorContext,
34
- type ExecuteOptions,
35
- } from "../../src/commands-impl";
36
- import { createPlanActionTool } from "../../src/tools/plan-action";
37
- import { createBgGetCommentsTool } from "../../src/tools/bg-get-comments";
38
- import { createWaitForFeedbackTool } from "../../src/tools/wait-for-feedback";
39
- import { DEFAULT_PLAN_SETTINGS, type PlanSettings } from "../../src/settings";
40
-
41
- // ---------------------------------------------------------------------------
42
- // Mock logger
43
- // ---------------------------------------------------------------------------
44
-
45
- class MockLogger {
46
- messages: Array<{ level: string; message: string }> = [];
47
- log(opts: { level: string; message: string }) { this.messages.push(opts); }
48
- debug(m: string) { this.messages.push({ level: "debug", message: m }); }
49
- info(m: string) { this.messages.push({ level: "info", message: m }); }
50
- warn(m: string) { this.messages.push({ level: "warn", message: m }); }
51
- error(m: string) { this.messages.push({ level: "error", message: m }); }
52
- }
53
-
54
- // ---------------------------------------------------------------------------
55
- // Per-test temp worktree
56
- // ---------------------------------------------------------------------------
57
-
58
- let worktree: string;
59
- let logger: MockLogger;
60
-
61
- beforeEach(() => {
62
- worktree = mkdtempSync(join(tmpdir(), "bizar-integration-"));
63
- logger = new MockLogger();
64
- });
65
-
66
- afterEach(() => {
67
- if (worktree) rmSync(worktree, { recursive: true, force: true });
68
- });
69
-
70
- afterAll(() => {
71
- // already cleaned in afterEach, but keep for safety
72
- if (worktree) rmSync(worktree, { recursive: true, force: true });
73
- });
74
-
75
- // ---------------------------------------------------------------------------
76
- // Helper: build minimal ExecutorContext + ExecuteOptions
77
- // ---------------------------------------------------------------------------
78
-
79
- function makeCtx(): ExecutorContext {
80
- return { worktree, directory: worktree, logger };
81
- }
82
-
83
- function makeOpts(): ExecuteOptions {
84
- return {
85
- tools: {
86
- bizar_plan_action: createPlanActionTool({ worktree, logger }) as unknown as AgentTool<unknown, unknown>,
87
- bizar_get_plan_comments: createBgGetCommentsTool({ worktree, logger }) as unknown as AgentTool<unknown, unknown>,
88
- bizar_wait_for_feedback: createWaitForFeedbackTool({ worktree, logger }) as unknown as AgentTool<unknown, unknown>,
89
- },
90
- defaultTemplate: "blank",
91
- defaultPort: 4321,
92
- };
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // Test cases
97
- // ---------------------------------------------------------------------------
98
-
99
- describe("integration: /plan new", () => {
100
- test("/plan new foo → creates plans/foo/meta.json and plan.json with correct content", async () => {
101
- const result = parseSlashCommand("/plan new foo", {
102
- currentSettings: DEFAULT_PLAN_SETTINGS,
103
- availablePlanSlugs: [],
104
- defaultPort: 4321,
105
- });
106
- expect(result).not.toBeNull();
107
- expect(result!.sideEffect!.kind).toBe("create_plan");
108
-
109
- await executeSideEffect(result!.sideEffect!, makeCtx(), makeOpts());
110
-
111
- const metaPath = join(worktree, "plans", "foo", "meta.json");
112
- const planPath = join(worktree, "plans", "foo", "plan.json");
113
- expect(existsSync(metaPath)).toBe(true);
114
- expect(existsSync(planPath)).toBe(true);
115
-
116
- const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
117
- expect(meta.status).toBe("draft");
118
- expect(meta.slug).toBeUndefined(); // slug not stored, title is titleCase
119
- expect(typeof meta.lastEdited).toBe("string");
120
-
121
- const canvas = JSON.parse(readFileSync(planPath, "utf-8"));
122
- expect(canvas.schemaVersion).toBe(2);
123
- expect(canvas.elements).toEqual([]);
124
- expect(canvas.connections).toEqual([]);
125
- expect(canvas.comments).toEqual([]);
126
- });
127
- });
128
-
129
- describe("integration: /plan list", () => {
130
- test("/plan list after creating foo and bar → response contains both slugs", async () => {
131
- // Seed two plans
132
- for (const slug of ["foo", "bar"]) {
133
- const r = parseSlashCommand(`/plan new ${slug}`, {
134
- currentSettings: DEFAULT_PLAN_SETTINGS,
135
- availablePlanSlugs: [],
136
- defaultPort: 4321,
137
- });
138
- await executeSideEffect(r!.sideEffect!, makeCtx(), makeOpts());
139
- }
140
-
141
- const listResult = parseSlashCommand("/plan list", {
142
- currentSettings: DEFAULT_PLAN_SETTINGS,
143
- availablePlanSlugs: ["foo", "bar"],
144
- defaultPort: 4321,
145
- });
146
- expect(listResult).not.toBeNull();
147
- const execResult = await executeSideEffect(listResult!.sideEffect!, makeCtx(), makeOpts());
148
- const response = execResult.responseOverride ?? listResult!.response;
149
- expect(response).toMatch(/foo/);
150
- expect(response).toMatch(/bar/);
151
- });
152
- });
153
-
154
- describe("integration: /plan add", () => {
155
- test("/plan add foo --title X --type task → plan.json contains the element", async () => {
156
- // First create the plan
157
- const newR = parseSlashCommand("/plan new foo", {
158
- currentSettings: DEFAULT_PLAN_SETTINGS,
159
- availablePlanSlugs: [],
160
- defaultPort: 4321,
161
- });
162
- await executeSideEffect(newR!.sideEffect!, makeCtx(), makeOpts());
163
-
164
- // Now add an element
165
- const addR = parseSlashCommand('/plan add foo --title X --type task', {
166
- currentSettings: DEFAULT_PLAN_SETTINGS,
167
- availablePlanSlugs: ["foo"],
168
- defaultPort: 4321,
169
- });
170
- expect(addR).not.toBeNull();
171
- expect(addR!.sideEffect!.kind).toBe("tool_invocation");
172
- const execR = await executeSideEffect(addR!.sideEffect!, makeCtx(), makeOpts());
173
-
174
- const planPath = join(worktree, "plans", "foo", "plan.json");
175
- const canvas = JSON.parse(readFileSync(planPath, "utf-8"));
176
- expect(canvas.elements).toHaveLength(1);
177
- expect(canvas.elements[0]!.title).toBe("X");
178
- expect(canvas.elements[0]!.type).toBe("task");
179
- });
180
- });
181
-
182
- describe("integration: /plan status", () => {
183
- test("/plan status foo approved → meta.json status is approved", async () => {
184
- // Create the plan first
185
- const newR = parseSlashCommand("/plan new foo", {
186
- currentSettings: DEFAULT_PLAN_SETTINGS,
187
- availablePlanSlugs: [],
188
- defaultPort: 4321,
189
- });
190
- await executeSideEffect(newR!.sideEffect!, makeCtx(), makeOpts());
191
-
192
- const statusR = parseSlashCommand("/plan status foo approved", {
193
- currentSettings: DEFAULT_PLAN_SETTINGS,
194
- availablePlanSlugs: ["foo"],
195
- defaultPort: 4321,
196
- });
197
- await executeSideEffect(statusR!.sideEffect!, makeCtx(), makeOpts());
198
-
199
- const metaPath = join(worktree, "plans", "foo", "meta.json");
200
- const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
201
- expect(meta.status).toBe("approved");
202
- });
203
- });
204
-
205
- describe("integration: /plan delete", () => {
206
- test("/plan delete foo <elementId> → element removed from plan.json", async () => {
207
- // Create plan and add an element
208
- const newR = parseSlashCommand("/plan new foo", {
209
- currentSettings: DEFAULT_PLAN_SETTINGS,
210
- availablePlanSlugs: [],
211
- defaultPort: 4321,
212
- });
213
- await executeSideEffect(newR!.sideEffect!, makeCtx(), makeOpts());
214
-
215
- const addR = parseSlashCommand('/plan add foo --title Elem1 --type task', {
216
- currentSettings: DEFAULT_PLAN_SETTINGS,
217
- availablePlanSlugs: ["foo"],
218
- defaultPort: 4321,
219
- });
220
- await executeSideEffect(addR!.sideEffect!, makeCtx(), makeOpts());
221
-
222
- // Read back to get element id
223
- const planPath = join(worktree, "plans", "foo", "plan.json");
224
- let canvas = JSON.parse(readFileSync(planPath, "utf-8")) as {
225
- elements: Array<{ id: string; title: string }>;
226
- };
227
- expect(canvas.elements).toHaveLength(1);
228
- const elementId = canvas.elements[0]!.id;
229
-
230
- // Now delete it
231
- const delR = parseSlashCommand(`/plan delete foo ${elementId}`, {
232
- currentSettings: DEFAULT_PLAN_SETTINGS,
233
- availablePlanSlugs: ["foo"],
234
- defaultPort: 4321,
235
- });
236
- await executeSideEffect(delR!.sideEffect!, makeCtx(), makeOpts());
237
-
238
- canvas = JSON.parse(readFileSync(planPath, "utf-8"));
239
- expect(canvas.elements).toHaveLength(0);
240
- });
241
- });
242
-
243
- describe("integration: /plan wait", () => {
244
- test("/plan wait foo → returns deferred response, does NOT block hook (< 100ms)", async () => {
245
- const r = parseSlashCommand("/plan wait foo", {
246
- currentSettings: DEFAULT_PLAN_SETTINGS,
247
- availablePlanSlugs: ["foo"],
248
- defaultPort: 4321,
249
- });
250
- expect(r).not.toBeNull();
251
- expect(r!.response).toMatch(/deferred/i);
252
- expect(r!.response).toMatch(/bizar_wait_for_feedback/);
253
- expect(r!.sideEffect).toBeUndefined(); // wait is NOT a tool_invocation
254
-
255
- // Verify it returns instantly (no async I/O)
256
- const start = Date.now();
257
- await executeSideEffect({ kind: "list_plans" }, makeCtx(), makeOpts()); // no-op
258
- const elapsed = Date.now() - start;
259
- expect(elapsed).toBeLessThan(100);
260
- });
261
- });
262
-
263
- describe("integration: /plan new on existing slug", () => {
264
- test("/plan new foo on existing slug → refusal response, existing files intact", async () => {
265
- // Create the plan
266
- const r1 = parseSlashCommand("/plan new foo", {
267
- currentSettings: DEFAULT_PLAN_SETTINGS,
268
- availablePlanSlugs: [],
269
- defaultPort: 4321,
270
- });
271
- await executeSideEffect(r1!.sideEffect!, makeCtx(), makeOpts());
272
-
273
- const planDir = join(worktree, "plans", "foo");
274
- const metaBefore = readFileSync(join(planDir, "meta.json"), "utf-8");
275
- const planBefore = readFileSync(join(planDir, "plan.json"), "utf-8");
276
-
277
- // Try to create again
278
- const r2 = parseSlashCommand("/plan new foo", {
279
- currentSettings: DEFAULT_PLAN_SETTINGS,
280
- availablePlanSlugs: ["foo"],
281
- defaultPort: 4321,
282
- });
283
- const execR = await executeSideEffect(r2!.sideEffect!, makeCtx(), makeOpts());
284
-
285
- // Should have refused
286
- expect(execR.responseOverride).toMatch(/already exists/);
287
-
288
- // Files should be unchanged
289
- expect(readFileSync(join(planDir, "meta.json"), "utf-8")).toBe(metaBefore);
290
- expect(readFileSync(join(planDir, "plan.json"), "utf-8")).toBe(planBefore);
291
- });
292
- });
293
-
294
- describe("integration: /plan comment", () => {
295
- test('/plan comment foo "first comment" → plan.json contains the comment', async () => {
296
- // Create the plan first
297
- const newR = parseSlashCommand("/plan new foo", {
298
- currentSettings: DEFAULT_PLAN_SETTINGS,
299
- availablePlanSlugs: [],
300
- defaultPort: 4321,
301
- });
302
- await executeSideEffect(newR!.sideEffect!, makeCtx(), makeOpts());
303
-
304
- const commentR = parseSlashCommand('/plan comment foo "first comment"', {
305
- currentSettings: DEFAULT_PLAN_SETTINGS,
306
- availablePlanSlugs: ["foo"],
307
- defaultPort: 4321,
308
- });
309
- await executeSideEffect(commentR!.sideEffect!, makeCtx(), makeOpts());
310
-
311
- const planPath = join(worktree, "plans", "foo", "plan.json");
312
- const canvas = JSON.parse(readFileSync(planPath, "utf-8")) as {
313
- comments: Array<{ id: string; text: string; author: string }>;
314
- };
315
- expect(canvas.comments).toHaveLength(1);
316
- expect(canvas.comments[0]!.text).toBe("first comment");
317
- expect(canvas.comments[0]!.author).toBe("user");
318
- });
319
- });
320
-
321
- describe("integration: /plan comments", () => {
322
- test("/plan comments foo → response lists the comment", async () => {
323
- // Create the plan first
324
- const newR = parseSlashCommand("/plan new foo", {
325
- currentSettings: DEFAULT_PLAN_SETTINGS,
326
- availablePlanSlugs: [],
327
- defaultPort: 4321,
328
- });
329
- await executeSideEffect(newR!.sideEffect!, makeCtx(), makeOpts());
330
-
331
- // Add a comment
332
- const commentR = parseSlashCommand('/plan comment foo "Hello world"', {
333
- currentSettings: DEFAULT_PLAN_SETTINGS,
334
- availablePlanSlugs: ["foo"],
335
- defaultPort: 4321,
336
- });
337
- await executeSideEffect(commentR!.sideEffect!, makeCtx(), makeOpts());
338
-
339
- // Now list comments
340
- const listR = parseSlashCommand("/plan comments foo", {
341
- currentSettings: DEFAULT_PLAN_SETTINGS,
342
- availablePlanSlugs: ["foo"],
343
- defaultPort: 4321,
344
- });
345
- const execR = await executeSideEffect(listR!.sideEffect!, makeCtx(), makeOpts());
346
- const response = execR.responseOverride ?? listR!.response;
347
- expect(response).toMatch(/Hello world/);
348
- });
349
- });
@@ -1,98 +0,0 @@
1
- /**
2
- * tool-routing.test.ts — Integration tests for the tool_invocation path
3
- * in slash commands. Phase 2 — Cline SDK port: tests the new
4
- * `AgentToolContext` shape and the Cline `createTool` API.
5
- */
6
-
7
- import { describe, test, expect, beforeAll, afterAll } from "bun:test";
8
- import { mkdtempSync, rmSync } from "node:fs";
9
- import { join } from "node:path";
10
- import { tmpdir } from "node:os";
11
-
12
- import {
13
- executeSideEffect,
14
- buildSyntheticToolContext,
15
- type ExecutorContext,
16
- type ExecuteOptions,
17
- } from "../../src/commands-impl";
18
- import { createPlanActionTool } from "../../src/tools/plan-action";
19
- import { createBgGetCommentsTool } from "../../src/tools/bg-get-comments";
20
- import { createWaitForFeedbackTool } from "../../src/tools/wait-for-feedback";
21
- import type { SideEffect } from "../../src/commands";
22
- import type { AgentTool, AgentToolContext } from "@cline/sdk";
23
-
24
- class MockLogger {
25
- messages: Array<{ level: string; message: string }> = [];
26
- debug(m: string) { this.messages.push({ level: "debug", message: m }); }
27
- info(m: string) { this.messages.push({ level: "info", message: m }); }
28
- warn(m: string) { this.messages.push({ level: "warn", message: m }); }
29
- error(m: string) { this.messages.push({ level: "error", message: m }); }
30
- log(_o: unknown) { /* swallow */ }
31
- }
32
-
33
- let worktree: string;
34
- const logger = new MockLogger();
35
-
36
- beforeAll(() => { worktree = mkdtempSync(join(tmpdir(), "bizar-tool-routing-")); });
37
- afterAll(() => { if (worktree) rmSync(worktree, { recursive: true, force: true }); });
38
-
39
- function ctx(): ExecutorContext { return { worktree, directory: worktree, logger }; }
40
-
41
- function optsWith(tools: Record<string, AgentTool>): ExecuteOptions {
42
- return { tools, defaultTemplate: "blank", defaultPort: 4321 };
43
- }
44
-
45
- const planTools = (): Record<string, AgentTool> => ({
46
- bizar_plan_action: createPlanActionTool({ worktree, logger }) as unknown as AgentTool,
47
- bizar_get_plan_comments: createBgGetCommentsTool({ worktree, logger }) as unknown as AgentTool,
48
- bizar_wait_for_feedback: createWaitForFeedbackTool({ worktree, logger }) as unknown as AgentTool,
49
- });
50
-
51
- describe("ToolContext — required fields", () => {
52
- test("synthetic context has all required fields", () => {
53
- const tctx = buildSyntheticToolContext(ctx()) as unknown as Record<string, unknown>;
54
- expect(tctx.sessionId).toBe("slash-command");
55
- expect(tctx.agentId).toBe("slash-command");
56
- expect(tctx.runId).toBeDefined();
57
- expect(tctx.iteration).toBe(0);
58
- expect(tctx.signal).toBeDefined();
59
- expect(tctx.metadata).toBeDefined();
60
- });
61
-
62
- test("abort.signal is NOT aborted", () => {
63
- const tctx = buildSyntheticToolContext(ctx()) as unknown as { signal: AbortSignal };
64
- expect(tctx.signal.aborted).toBe(false);
65
- });
66
-
67
- test("each call gets a fresh AbortSignal (not shared)", () => {
68
- const a = buildSyntheticToolContext(ctx()) as unknown as { signal: AbortSignal };
69
- const b = buildSyntheticToolContext(ctx()) as unknown as { signal: AbortSignal };
70
- expect(a.signal).not.toBe(b.signal);
71
- });
72
- });
73
-
74
- describe("ToolInvocation — args validation via Zod", () => {
75
- test("malformed args (invalid slug) → refusal response, tool NOT invoked", async () => {
76
- const sideEffect: SideEffect = { kind: "tool_invocation", toolName: "bizar_plan_action", args: { action: "get_canvas", planSlug: "UPPERCASE" } };
77
- const res = await executeSideEffect(sideEffect, ctx(), optsWith(planTools()));
78
- expect(res.responseOverride).toBeDefined();
79
- expect(res.responseOverride).toMatch(/Invalid arguments/);
80
- });
81
-
82
- test("missing required arg (no planSlug) → refusal response, tool NOT invoked", async () => {
83
- const sideEffect: SideEffect = { kind: "tool_invocation", toolName: "bizar_plan_action", args: { action: "get_canvas" } };
84
- const res = await executeSideEffect(sideEffect, ctx(), optsWith(planTools()));
85
- expect(res.responseOverride).toBeDefined();
86
- expect(res.responseOverride).toMatch(/Invalid arguments/);
87
- });
88
-
89
- test("valid args → tool IS invoked (spy verifies factory call)", async () => {
90
- // Pre-create a plan first.
91
- await executeSideEffect({ kind: "create_plan", slug: "routing-spy", template: null }, ctx(), optsWith(planTools()));
92
- const sideEffect: SideEffect = { kind: "tool_invocation", toolName: "bizar_plan_action", args: { action: "get_canvas", planSlug: "routing-spy" } };
93
- const res = await executeSideEffect(sideEffect, ctx(), optsWith(planTools()));
94
- expect(res.responseOverride).toBeDefined();
95
- // Should not be a validation error
96
- expect(res.responseOverride).not.toMatch(/Invalid arguments/);
97
- });
98
- });