@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,344 +0,0 @@
1
- /**
2
- * cli/commands/validate.test.mjs
3
- *
4
- * v6.2.0 — Unit tests for the new `bizar validate` subcommand.
5
- *
6
- * We exercise `runValidate()` directly. CLINE_DIR is mocked via
7
- * `process.env.CLINE_DIR`; a minimal-but-complete Bizar install is
8
- * staged inside a tmpdir. We then progressively delete files to
9
- * verify each check fails with a sensible message.
10
- */
11
- import { test, describe, beforeEach, afterEach } from 'node:test';
12
- import assert from 'node:assert/strict';
13
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, chmodSync } from 'node:fs';
14
- import { tmpdir } from 'node:os';
15
- import { join } from 'node:path';
16
- import { Writable } from 'node:stream';
17
-
18
- const { runValidate } = await import('./validate.mjs');
19
-
20
- const ORIG_CLINE_DIR = process.env.CLINE_DIR;
21
- const ORIG_HOME = process.env.HOME;
22
-
23
- let workDir;
24
-
25
- function freshWorkDir() {
26
- const root = mkdtempSync(join(tmpdir(), 'bizar-validate-'));
27
- process.env.CLINE_DIR = root;
28
- return root;
29
- }
30
-
31
- function makeFakeClineInstall(root) {
32
- writeFileSync(
33
- join(root, 'cline.json'),
34
- JSON.stringify({
35
- $schema: 'https://docs.cline.bot/config.json',
36
- plugin: [[
37
- './plugins/bizar',
38
- { loopThresholdWarn: 5, loopThresholdEscalate: 8, loopThresholdBlock: 12, loopWindowSize: 10 },
39
- ]],
40
- default_agent: 'odin',
41
- permission: 'allow',
42
- snapshot: false,
43
- instructions: ['.cline/instructions/bizar-tools.md'],
44
- provider: {
45
- '9router': {
46
- baseUrl: 'http://localhost:20128/v1',
47
- apiKey: '${env:NINEROUTER_KEY}',
48
- models: { 'minimax/MiniMax-M3': { reasoning: true } },
49
- },
50
- minimax: { models: {} },
51
- },
52
- }, null, 2),
53
- );
54
- const agentsDir = join(root, 'agents');
55
- mkdirSync(agentsDir, { recursive: true });
56
- for (const f of [
57
- 'odin.md', 'vor.md', 'frigg.md', 'quick.md',
58
- 'mimir.md', 'heimdall.md', 'hermod.md', 'thor.md', 'baldr.md',
59
- 'tyr.md', 'vidarr.md', 'forseti.md',
60
- 'semble-search.md', 'agent-browser.md',
61
- ]) {
62
- writeFileSync(join(agentsDir, f), '# fake agent');
63
- }
64
- writeFileSync(join(agentsDir, 'odin.yaml'), 'name: odin\ndescription: odin\n');
65
- const cmdsDir = join(root, 'commands');
66
- mkdirSync(cmdsDir, { recursive: true });
67
- for (const f of [
68
- 'audit.md', 'bizar.md', 'explain.md', 'init.md', 'learn.md',
69
- 'plan.md', 'plow-through.md', 'pr-review.md', 'tailscale-serve.md',
70
- 'visual-plan.md',
71
- 'team.md', 'test.md', 'validate.md', 'setup-provider.md',
72
- ]) {
73
- writeFileSync(join(cmdsDir, f), '# fake command');
74
- }
75
- const skillsDir = join(root, 'skills');
76
- mkdirSync(skillsDir, { recursive: true });
77
- for (const skill of ['9router', 'bizar', 'obsidian']) {
78
- mkdirSync(join(skillsDir, skill), { recursive: true });
79
- writeFileSync(join(skillsDir, skill, 'SKILL.md'), '# fake skill');
80
- }
81
- const rulesDir = join(root, 'rules');
82
- mkdirSync(rulesDir, { recursive: true });
83
- for (const r of ['general.md', 'git.md', 'javascript.md', 'python.md', 'testing.md', 'thinking.md', 'uncertainty.md']) {
84
- writeFileSync(join(rulesDir, r), '# fake rule');
85
- }
86
- const hooksDir = join(root, 'hooks');
87
- mkdirSync(hooksDir, { recursive: true });
88
- for (const h of ['PreToolUse', 'PostToolUse', 'TaskStart', 'TaskResume', 'UserPromptSubmit']) {
89
- // Cline hooks must be executable scripts with a shebang line.
90
- writeFileSync(join(hooksDir, h), '#!/usr/bin/env node\n// fake hook\n');
91
- chmodSync(join(hooksDir, h), 0o755);
92
- }
93
- const pluginDir = join(root, 'plugins', 'bizar');
94
- mkdirSync(pluginDir, { recursive: true });
95
- writeFileSync(join(pluginDir, 'index.ts'), '// fake plugin entry — >100 bytes to pass size check\n'.repeat(10));
96
- mkdirSync(join(pluginDir, 'src'), { recursive: true });
97
- writeFileSync(
98
- join(pluginDir, 'src', 'clineruntime.ts'),
99
- 'export class ClineRuntime { config = { enableAgentTeams: true }; }\n',
100
- );
101
- // node_modules with the required runtime deps
102
- const nmDir = join(pluginDir, 'node_modules');
103
- mkdirSync(nmDir, { recursive: true });
104
- mkdirSync(join(nmDir, 'zod'), { recursive: true });
105
- writeFileSync(join(nmDir, 'zod', 'package.json'), '{}');
106
- mkdirSync(join(nmDir, '@cline'), { recursive: true });
107
- for (const dep of ['@cline/sdk', '@cline/core', '@cline/shared']) {
108
- mkdirSync(join(nmDir, dep), { recursive: true });
109
- writeFileSync(join(nmDir, dep, 'package.json'), '{}');
110
- }
111
- }
112
-
113
- beforeEach(() => {
114
- workDir = freshWorkDir();
115
- makeFakeClineInstall(workDir);
116
- });
117
-
118
- afterEach(() => {
119
- if (workDir && existsSync(workDir)) rmSync(workDir, { recursive: true, force: true });
120
- if (ORIG_CLINE_DIR === undefined) delete process.env.CLINE_DIR;
121
- else process.env.CLINE_DIR = ORIG_CLINE_DIR;
122
- if (ORIG_HOME === undefined) delete process.env.HOME;
123
- else process.env.HOME = ORIG_HOME;
124
- });
125
-
126
- /**
127
- * Capture stdout + exit code for a single runValidate() call.
128
- * The test runner writes its own progress to stdout, so we swap
129
- * process.stdout for a sink Writable during the call.
130
- */
131
- async function captureRun(opts) {
132
- const captured = [];
133
- const sink = new Writable({
134
- write(chunk, _enc, cb) {
135
- captured.push(Buffer.from(chunk));
136
- cb();
137
- },
138
- });
139
- const origStdoutDescriptor = Object.getOwnPropertyDescriptor(process, 'stdout');
140
- const origExit = process.exit;
141
- let exitCode = null;
142
- Object.defineProperty(process, 'stdout', { value: sink, configurable: true, writable: true });
143
- process.exit = (code) => { exitCode = code; };
144
- try {
145
- await runValidate(opts);
146
- } finally {
147
- if (origStdoutDescriptor) Object.defineProperty(process, 'stdout', origStdoutDescriptor);
148
- process.exit = origExit;
149
- }
150
- return { stdout: Buffer.concat(captured).toString('utf8'), exitCode };
151
- }
152
-
153
- describe('runValidate() — JSON output', () => {
154
- test('returns structured JSON with passed/failed/results', async () => {
155
- const { stdout, exitCode } = await captureRun({ json: true });
156
- assert.equal(exitCode, 0, 'should exit 0 on success');
157
- const parsed = JSON.parse(stdout);
158
- assert.equal(typeof parsed.passed, 'number');
159
- assert.equal(typeof parsed.failed, 'number');
160
- assert.ok(Array.isArray(parsed.results));
161
- assert.ok(parsed.results.length >= 18, 'expected at least 18 checks');
162
- for (const r of parsed.results) {
163
- assert.ok(typeof r.name === 'string');
164
- assert.ok(typeof r.ok === 'boolean');
165
- assert.ok(typeof r.message === 'string');
166
- }
167
- });
168
-
169
- test('fails on missing team command', async () => {
170
- rmSync(join(workDir, 'commands', 'team.md'));
171
- const { exitCode } = await captureRun({ json: true });
172
- assert.equal(exitCode, 1, 'should exit 1 on team-command-present failure');
173
- });
174
-
175
- test('fails on missing test command', async () => {
176
- rmSync(join(workDir, 'commands', 'test.md'));
177
- const { exitCode } = await captureRun({ json: true });
178
- assert.equal(exitCode, 1);
179
- });
180
-
181
- test('fails on missing validate command', async () => {
182
- rmSync(join(workDir, 'commands', 'validate.md'));
183
- const { exitCode } = await captureRun({ json: true });
184
- assert.equal(exitCode, 1);
185
- });
186
-
187
- test('fails on missing agent file', async () => {
188
- rmSync(join(workDir, 'agents', 'mimir.md'));
189
- const { exitCode } = await captureRun({ json: true });
190
- assert.equal(exitCode, 1);
191
- });
192
-
193
- test('fails when cline.json missing', async () => {
194
- rmSync(join(workDir, 'cline.json'));
195
- const { exitCode } = await captureRun({ json: true });
196
- assert.equal(exitCode, 1);
197
- });
198
-
199
- test('fails when enableAgentTeams is false in clineruntime.ts', async () => {
200
- writeFileSync(
201
- join(workDir, 'plugins', 'bizar', 'src', 'clineruntime.ts'),
202
- 'export class ClineRuntime { config = { enableAgentTeams: false }; }\n',
203
- );
204
- const { exitCode } = await captureRun({ json: true });
205
- assert.equal(exitCode, 1, 'should fail when enableAgentTeams is false');
206
- });
207
-
208
- test('passes when no provider is configured (v6.2.2+ — user must configure)', async () => {
209
- // v6.2.2: the installer no longer touches provider config. The
210
- // user must add their own provider. The provider-config check
211
- // is therefore ALWAYS lenient — it just reports state.
212
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
213
- delete cfg.provider;
214
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
215
- const { exitCode, stdout } = await captureRun({ json: true });
216
- assert.equal(exitCode, 0, 'no-provider must not fail validation (v6.2.2+ contract)');
217
- const parsed = JSON.parse(stdout);
218
- const p = parsed.results.find((r) => r.name === 'provider-config');
219
- assert.ok(p, 'provider-config check should be present');
220
- assert.equal(p.ok, true, 'provider-config always returns ok (lenient)');
221
- assert.match(p.message, /no provider configured/);
222
- });
223
-
224
- test('reports user-configured providers when present', async () => {
225
- // User has set up their own provider (anything goes).
226
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
227
- cfg.provider = {
228
- 'my-custom-provider': {
229
- baseUrl: 'https://my-llm.example/v1',
230
- apiKey: 'sk-test',
231
- models: { 'my-model-1': {}, 'my-model-2': {} },
232
- },
233
- };
234
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
235
- const { exitCode, stdout } = await captureRun({ json: true });
236
- assert.equal(exitCode, 0);
237
- const parsed = JSON.parse(stdout);
238
- const p = parsed.results.find((r) => r.name === 'provider-config');
239
- assert.match(p.message, /my-custom-provider/);
240
- assert.match(p.message, /2 models/);
241
- });
242
-
243
- test('reports both providers if user has multiple', async () => {
244
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
245
- cfg.provider = {
246
- 'a': { baseUrl: 'https://a.example/v1', models: { 'm1': {} } },
247
- 'b': { baseUrl: 'https://b.example/v1', models: { 'm2': {}, 'm3': {} } },
248
- };
249
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
250
- const { exitCode, stdout } = await captureRun({ json: true });
251
- assert.equal(exitCode, 0);
252
- const parsed = JSON.parse(stdout);
253
- const p = parsed.results.find((r) => r.name === 'provider-config');
254
- assert.match(p.message, /\ba\b/);
255
- assert.match(p.message, /\bb\b/);
256
- });
257
-
258
- test('default mode is lenient on 9router-unreachable', async () => {
259
- const { stdout, exitCode } = await captureRun({ json: true });
260
- const parsed = JSON.parse(stdout);
261
- const nineR = parsed.results.find((r) => r.name === '9router-reachable');
262
- assert.ok(nineR, '9router-reachable check should be present');
263
- if (!nineR.ok) {
264
- // 9router is not running. Default mode must be lenient.
265
- assert.equal(exitCode, 0, 'default mode should be lenient when 9router is down');
266
- } else {
267
- // 9router is running (some test envs have it). Just confirm the
268
- // check is in the list and exit code is sane.
269
- assert.equal(exitCode, 0, 'expected exit 0 when all critical checks pass');
270
- }
271
- });
272
-
273
- test('--strict fails when 9router is unreachable', async () => {
274
- // We can't easily kill 9router in a test, but we can prove that
275
- // --strict mode would fail if 9router were down: set NINEROUTER_URL
276
- // to a port nothing is listening on.
277
- const origUrl = process.env.NINEROUTER_URL;
278
- process.env.NINEROUTER_URL = 'http://127.0.0.1:1'; // nothing here
279
- try {
280
- const { exitCode } = await captureRun({ json: true, strict: true });
281
- assert.equal(exitCode, 1, 'strict mode should fail when 9router is unreachable');
282
- } finally {
283
- if (origUrl === undefined) delete process.env.NINEROUTER_URL;
284
- else process.env.NINEROUTER_URL = origUrl;
285
- }
286
- });
287
-
288
- test('--only filters to a single check', async () => {
289
- const { stdout, exitCode } = await captureRun({ json: true, only: 'team-command-present' });
290
- assert.equal(exitCode, 0);
291
- const parsed = JSON.parse(stdout);
292
- assert.equal(parsed.results.length, 1);
293
- assert.equal(parsed.results[0].name, 'team-command-present');
294
- assert.equal(parsed.results[0].ok, true);
295
- });
296
-
297
- test('--only on a missing file fails with exit 1', async () => {
298
- rmSync(join(workDir, 'commands', 'team.md'));
299
- const { exitCode } = await captureRun({ json: true, only: 'team-command-present' });
300
- assert.equal(exitCode, 1);
301
- });
302
-
303
- test('--only with an unknown name exits 2', async () => {
304
- const { exitCode } = await captureRun({ json: true, only: 'this-does-not-exist' });
305
- assert.equal(exitCode, 2);
306
- });
307
-
308
- test('v6.2.4 — mistake-limit-floor warns when clineruntimeMaxConsecutiveMistakes is < 10', async () => {
309
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
310
- cfg.plugin = [['./plugins/bizar', { clineruntimeMaxConsecutiveMistakes: 3 }]];
311
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
312
- const { stdout, exitCode } = await captureRun({ json: true });
313
- assert.equal(exitCode, 0, 'mistake-limit-floor is lenient so exit stays 0');
314
- const parsed = JSON.parse(stdout);
315
- const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
316
- assert.ok(check, 'mistake-limit-floor check should be present');
317
- assert.equal(check.ok, false, 'low mistake limit should warn');
318
- assert.match(check.message, /BELOW the recommended minimum/);
319
- assert.match(check.message, /clineruntimeMaxConsecutiveMistakes=3/);
320
- });
321
-
322
- test('v6.2.4 — mistake-limit-floor OK when clineruntimeMaxConsecutiveMistakes >= 10', async () => {
323
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
324
- cfg.plugin = [['./plugins/bizar', { clineruntimeMaxConsecutiveMistakes: 15 }]];
325
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
326
- const { stdout, exitCode } = await captureRun({ json: true });
327
- assert.equal(exitCode, 0);
328
- const parsed = JSON.parse(stdout);
329
- const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
330
- assert.equal(check.ok, true);
331
- assert.match(check.message, /15/);
332
- });
333
-
334
- test('v6.2.4 — mistake-limit-floor OK when no plugin entry (legacy cline.json)', async () => {
335
- const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
336
- delete cfg.plugin;
337
- writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
338
- const { stdout } = await captureRun({ json: true });
339
- const parsed = JSON.parse(stdout);
340
- const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
341
- assert.match(check.message, /no plugin entries/);
342
- assert.equal(check.ok, true);
343
- });
344
- });
@@ -1,350 +0,0 @@
1
- /**
2
- * cli/doctor.test.mjs
3
- *
4
- * Tests for the `bizar doctor` subcommand. Uses Node's built-in
5
- * node:test (no external test framework).
6
- *
7
- * Strategy: mock HOME so clineConfigDir() resolves inside a
8
- * tmpdir, and craft the cline.json + agents/ directory to drive
9
- * specific pass/fail outcomes. For checks that depend on global
10
- * npm-installed packages or external CLIs (e.g. cline --version,
11
- * npm root -g, which headroom), we accept that they may pass or fail
12
- * depending on the test environment and just verify the framework
13
- * returns the right shape.
14
- */
15
- import { test, describe, beforeEach, afterEach } from 'node:test';
16
- import assert from 'node:assert/strict';
17
- import {
18
- mkdtempSync,
19
- mkdirSync,
20
- writeFileSync,
21
- rmSync,
22
- existsSync,
23
- utimesSync,
24
- } from 'node:fs';
25
- import { tmpdir } from 'node:os';
26
- import { join, dirname } from 'node:path';
27
- import { fileURLToPath } from 'node:url';
28
-
29
- const __dirname = dirname(fileURLToPath(import.meta.url));
30
- const PROJECT_ROOT = join(__dirname, '..');
31
-
32
- const { runDoctor } = await import('./doctor.mjs');
33
-
34
- // ── HOME mocking ────────────────────────────────────────────────────────────
35
-
36
- const ORIG_HOME = process.env.HOME;
37
- const ORIG_XDG = process.env.XDG_CONFIG_HOME;
38
-
39
- /**
40
- * Mock HOME so clineConfigDir() resolves to `<tmpdir>/.cline`.
41
- * Returns the tmpdir path.
42
- *
43
- * We don't set XDG_CONFIG_HOME here for the same reason as dev-link.test.mjs:
44
- * the helper treats a set XDG_CONFIG_HOME as the direct parent (so
45
- * `XDG_CONFIG_HOME=~/.config` → `~/.cline`).
46
- */
47
- function freshHome() {
48
- const home = mkdtempSync(join(tmpdir(), 'bizar-doctor-'));
49
- process.env.HOME = home;
50
- delete process.env.CLINE_DIR;
51
- delete process.env.BIZAR_LEGACY_CLINE_DIR;
52
- return home;
53
- }
54
-
55
- afterEach(() => {
56
- if (ORIG_HOME === undefined) delete process.env.HOME;
57
- else process.env.HOME = ORIG_HOME;
58
- if (ORIG_XDG === undefined) {
59
- delete process.env.CLINE_DIR;
60
- delete process.env.BIZAR_LEGACY_CLINE_DIR;
61
- } else {
62
- process.env.XDG_CONFIG_HOME = ORIG_XDG;
63
- }
64
- });
65
-
66
- // ── Shape & silent-mode tests ───────────────────────────────────────────────
67
-
68
- describe('runDoctor() shape', () => {
69
- let home;
70
-
71
- beforeEach(() => {
72
- home = freshHome();
73
- });
74
-
75
- afterEach(() => {
76
- if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
77
- });
78
-
79
- test('returns { passed, failed, results }', async () => {
80
- const result = await runDoctor({ silent: true });
81
- assert.equal(typeof result.passed, 'number');
82
- assert.equal(typeof result.failed, 'number');
83
- assert.ok(Array.isArray(result.results));
84
- // results should have one entry per check
85
- assert.equal(result.results.length, 9);
86
- assert.equal(result.passed + result.failed, 9);
87
- // Each result has the expected fields
88
- for (const r of result.results) {
89
- assert.equal(typeof r.name, 'string');
90
- assert.equal(typeof r.ok, 'boolean');
91
- assert.equal(typeof r.message, 'string');
92
- }
93
- });
94
-
95
- test('silent mode suppresses per-check output but still prints summary on failure', async () => {
96
- // Capture stdout by overriding console.log.
97
- const lines = [];
98
- const origLog = console.log;
99
- console.log = (...args) => {
100
- lines.push(args.join(' '));
101
- };
102
-
103
- try {
104
- // Empty home → many checks will fail → summary should print.
105
- const result = await runDoctor({ silent: true });
106
- assert.ok(result.failed > 0, 'precondition: some checks should fail');
107
- } finally {
108
- console.log = origLog;
109
- }
110
-
111
- // In silent mode with failures, only the summary line should print
112
- // (an empty leading line is also fine — it's a visual separator).
113
- const summaryLines = lines.filter((l) => /checks passed.*failed/.test(l));
114
- assert.equal(
115
- summaryLines.length,
116
- 1,
117
- `expected exactly 1 summary line in silent+failed mode, got ${summaryLines.length}:\n${lines.join('\n')}`,
118
- );
119
- assert.match(summaryLines[0], /checks passed.*failed/);
120
- });
121
-
122
- test('silent mode suppresses ALL output when nothing fails', async () => {
123
- // Build a fully healthy environment. We can't make cline reachable
124
- // or headroom installed without modifying PATH, so we settle for: no failures
125
- // means no output at all. Since we know some checks will fail in a
126
- // bare tmpdir, this test is structured to assert the negative case
127
- // differently: we verify silent+no-fail produces 0 lines.
128
- //
129
- // For this we mock nothing — the test just confirms the framework
130
- // doesn't print per-check lines when silent is true.
131
- const lines = [];
132
- const origLog = console.log;
133
- console.log = (...args) => {
134
- lines.push(args.join(' '));
135
- };
136
-
137
- try {
138
- await runDoctor({ silent: true });
139
- // Some checks failed (because the env isn't fully healthy).
140
- // What we want to verify is that per-check lines did NOT print.
141
- const perCheckLines = lines.filter((l) =>
142
- /^\s+[✓✗]\s/.test(l) // chalk check markers
143
- );
144
- assert.equal(
145
- perCheckLines.length,
146
- 0,
147
- `silent mode should suppress per-check output, got:\n${perCheckLines.join('\n')}`,
148
- );
149
- } finally {
150
- console.log = origLog;
151
- }
152
- });
153
-
154
- test('non-silent mode prints per-check output', async () => {
155
- const lines = [];
156
- const origLog = console.log;
157
- console.log = (...args) => {
158
- lines.push(args.join(' '));
159
- };
160
-
161
- try {
162
- await runDoctor();
163
- } finally {
164
- console.log = origLog;
165
- }
166
-
167
- // Should have at least 9 per-check lines plus the summary.
168
- const perCheckLines = lines.filter((l) => /^\s+[✓✗]\s/.test(l));
169
- assert.ok(
170
- perCheckLines.length >= 9,
171
- `expected ≥9 per-check lines, got ${perCheckLines.length}`,
172
- );
173
- });
174
- });
175
-
176
- // ── Fixture-driven pass/fail tests ──────────────────────────────────────────
177
-
178
- describe('runDoctor() with fixture HOME', () => {
179
- let home;
180
-
181
- beforeEach(() => {
182
- home = freshHome();
183
- });
184
-
185
- afterEach(() => {
186
- if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
187
- });
188
-
189
- function writeClineConfig(json) {
190
- const cfgDir = join(home, '.cline');
191
- mkdirSync(cfgDir, { recursive: true });
192
- writeFileSync(join(cfgDir, 'cline.json'), JSON.stringify(json), 'utf8');
193
- return cfgDir;
194
- }
195
-
196
- function writeAgents(...files) {
197
- const agentsDir = join(home, '.cline', 'agents');
198
- mkdirSync(agentsDir, { recursive: true });
199
- for (const f of files) {
200
- writeFileSync(join(agentsDir, f), `# ${f}`, 'utf8');
201
- }
202
- return agentsDir;
203
- }
204
-
205
- function findCheck(result, name) {
206
- return result.results.find((r) => r.name === name);
207
- }
208
-
209
- test('config-valid passes for parseable JSON', async () => {
210
- writeClineConfig({ provider: {} });
211
- const result = await runDoctor({ silent: true });
212
- const r = findCheck(result, 'cline-config-valid');
213
- assert.equal(r.ok, true, `expected config-valid to pass: ${r.message}`);
214
- });
215
-
216
- test('config-valid fails for missing cline.json', async () => {
217
- // No cline.json written.
218
- const result = await runDoctor({ silent: true });
219
- const r = findCheck(result, 'cline-config-valid');
220
- assert.equal(r.ok, false);
221
- assert.match(r.message, /not found/);
222
- });
223
-
224
- test('config-valid fails for invalid JSON', async () => {
225
- const cfgDir = writeClineConfig({});
226
- // Overwrite with garbage
227
- writeFileSync(join(cfgDir, 'cline.json'), '{ this is not json', 'utf8');
228
- const result = await runDoctor({ silent: true });
229
- const r = findCheck(result, 'cline-config-valid');
230
- assert.equal(r.ok, false);
231
- assert.match(r.message, /invalid JSON/);
232
- });
233
-
234
- test('plugin-entry-present fails when plugin[] is empty', async () => {
235
- writeClineConfig({ plugin: [] });
236
- const result = await runDoctor({ silent: true });
237
- const r = findCheck(result, 'plugin-entry-present');
238
- assert.equal(r.ok, false);
239
- });
240
-
241
- test('plugin-entry-present fails when plugin[] has no bizar', async () => {
242
- writeClineConfig({ plugin: ['something-else'] });
243
- const result = await runDoctor({ silent: true });
244
- const r = findCheck(result, 'plugin-entry-present');
245
- assert.equal(r.ok, false);
246
- });
247
-
248
- test('plugin-entry-present passes when plugin[] has bizar string', async () => {
249
- writeClineConfig({ plugin: ['bizar'] });
250
- const result = await runDoctor({ silent: true });
251
- const r = findCheck(result, 'plugin-entry-present');
252
- assert.equal(r.ok, true, r.message);
253
- });
254
-
255
- test('plugin-entry-present passes when plugin[] has bizar object', async () => {
256
- writeClineConfig({ plugin: [{ name: 'bizar', path: './plugins/bizar' }] });
257
- const result = await runDoctor({ silent: true });
258
- const r = findCheck(result, 'plugin-entry-present');
259
- assert.equal(r.ok, true, r.message);
260
- });
261
-
262
- test('plugin-entry-present passes when plugin[] has [path, options] tuple', async () => {
263
- writeClineConfig({ plugin: [['./plugins/bizar/index.ts', { loopThresholdWarn: 5 }]] });
264
- const result = await runDoctor({ silent: true });
265
- const r = findCheck(result, 'plugin-entry-present');
266
- assert.equal(r.ok, true, r.message);
267
- });
268
-
269
- test('plugin-entry-present fails when tuple path does not contain "bizar"', async () => {
270
- writeClineConfig({ plugin: [['./plugins/other/index.ts', {}]] });
271
- const result = await runDoctor({ silent: true });
272
- const r = findCheck(result, 'plugin-entry-present');
273
- assert.equal(r.ok, false);
274
- });
275
-
276
- test('plugin-path-resolves passes when tuple path resolves', async () => {
277
- const home = process.env.HOME;
278
- const pluginsDir = join(home, '.cline', 'plugins', 'bizar');
279
- mkdirSync(pluginsDir, { recursive: true });
280
- writeFileSync(join(pluginsDir, 'index.ts'), '// fake plugin\n', 'utf8');
281
- writeClineConfig({ plugin: [['./plugins/bizar/index.ts', {}]] });
282
- const result = await runDoctor({ silent: true });
283
- const r = findCheck(result, 'plugin-path-resolves');
284
- assert.equal(r.ok, true, r.message);
285
- });
286
-
287
- test('agent-files-installed fails when core agents missing', async () => {
288
- writeClineConfig({});
289
- writeAgents('odin.md'); // missing the other 13
290
- const result = await runDoctor({ silent: true });
291
- const r = findCheck(result, 'agent-files-installed');
292
- assert.equal(r.ok, false);
293
- assert.match(r.message, /missing/);
294
- });
295
-
296
- test('agent-files-installed passes when all 14 agents present', async () => {
297
- writeClineConfig({});
298
- // v3.20.11: doctor now expects all 14 agents (was 4 in v3.20.10).
299
- writeAgents(
300
- 'odin.md', 'vor.md', 'frigg.md', 'quick.md',
301
- 'mimir.md', 'heimdall.md', 'hermod.md', 'thor.md', 'baldr.md',
302
- 'tyr.md', 'vidarr.md', 'forseti.md',
303
- 'semble-search.md', 'agent-browser.md',
304
- );
305
- const result = await runDoctor({ silent: true });
306
- const r = findCheck(result, 'agent-files-installed');
307
- assert.equal(r.ok, true, r.message);
308
- });
309
-
310
- test('provider-config-sanity warns (not fails) without minimax block', async () => {
311
- // v5.x: checkProviderConfigSanity warns instead of throwing when the
312
- // provider.minimax block is missing, since provision.mjs auto-adds it.
313
- writeClineConfig({ provider: {} });
314
- const result = await runDoctor({ silent: true });
315
- const r = findCheck(result, 'provider-config-sanity');
316
- assert.equal(r.ok, true, 'should pass with warning, not throw');
317
- assert.match(r.message, /warn.*minimax|minimax.*missing/i);
318
- });
319
-
320
- test('provider-config-sanity fails when models lack interleaved+reasoning', async () => {
321
- writeClineConfig({
322
- provider: {
323
- minimax: {
324
- models: { 'some-model': { id: 'foo' } },
325
- },
326
- },
327
- });
328
- const result = await runDoctor({ silent: true });
329
- const r = findCheck(result, 'provider-config-sanity');
330
- assert.equal(r.ok, false);
331
- assert.match(r.message, /interleaved.*reasoning/);
332
- });
333
-
334
- test('provider-config-sanity passes with interleaved+reasoning', async () => {
335
- writeClineConfig({
336
- provider: {
337
- minimax: {
338
- models: {
339
- 'MiniMax/MiniMax-M3': { interleaved: true, reasoning: true },
340
- },
341
- },
342
- },
343
- });
344
- const result = await runDoctor({ silent: true });
345
- const r = findCheck(result, 'provider-config-sanity');
346
- assert.equal(r.ok, true, r.message);
347
- });
348
- });
349
-
350
- console.log(' doctor.mjs tests loaded — run with: node --test cli/doctor.test.mjs');