@polderlabs/bizar 6.2.5 → 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 (329) 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 +17 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/validate.mjs +252 -325
  128. package/cli/dev-link.test.mjs +11 -11
  129. package/cli/doctor.mjs +131 -213
  130. package/cli/install.mjs +33 -37
  131. package/cli/install.test.mjs +9 -9
  132. package/cli/provision-claude.mjs +893 -0
  133. package/cli/provision.mjs +224 -227
  134. package/cli/utils.mjs +72 -58
  135. package/config/AGENTS.md +28 -28
  136. package/install.sh +66 -44
  137. package/package.json +19 -11
  138. package/packages/sdk/ARCHITECTURE.md +40 -36
  139. package/packages/sdk/package-lock.json +32 -0
  140. package/packages/sdk/package.json +15 -5
  141. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  142. package/packages/sdk/src/fingerprint.ts +0 -0
  143. package/packages/sdk/src/index.ts +38 -73
  144. package/packages/sdk/src/mcp/bin.ts +51 -0
  145. package/packages/sdk/src/mcp/server.ts +498 -0
  146. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  147. package/packages/sdk/tests/sdk.test.mjs +148 -0
  148. package/packages/sdk/vitest.config.ts +1 -1
  149. package/scripts/bh-full-e2e.mjs +166 -363
  150. package/scripts/check-deps.mjs +28 -46
  151. package/scripts/mirror-claude-md.sh +78 -0
  152. package/scripts/test-in-container.sh +31 -9
  153. package/templates/clean-state-checklist.md +3 -3
  154. package/templates/sprint-contract.md +2 -2
  155. package/bizar-dash/.bizar/activity.log +0 -3
  156. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  157. package/bizar-dash/.omx/state/session.json +0 -10
  158. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  159. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  160. package/bizar-dash/bizar-design/canvas.html +0 -281
  161. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  162. package/bizar-dash/bizar-design/components.css +0 -1665
  163. package/bizar-dash/bizar-design/components.html +0 -557
  164. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  165. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  166. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  167. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  168. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  169. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  170. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  172. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  174. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/icons.svg +0 -44
  176. package/bizar-dash/bizar-design/index.html +0 -192
  177. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  178. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  179. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  180. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  181. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  182. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  183. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  185. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/prototype.js +0 -114
  189. package/bizar-dash/bizar-design/tokens.css +0 -106
  190. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  191. package/bizar-dash/node_modules/.package-lock.json +0 -6
  192. package/bizar-dash/package-lock.json +0 -6
  193. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  194. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  195. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  196. package/cli/commands/cline-cmd.mjs +0 -289
  197. package/cli/commands/validate.test.mjs +0 -379
  198. package/cli/doctor.test.mjs +0 -350
  199. package/config/cline.json.template +0 -342
  200. package/packages/sdk/src/client.ts +0 -188
  201. package/packages/sdk/src/cline-events.ts +0 -134
  202. package/packages/sdk/src/cline-types.ts +0 -66
  203. package/packages/sdk/src/cline.ts +0 -339
  204. package/packages/sdk/src/errors.ts +0 -129
  205. package/packages/sdk/src/events.ts +0 -153
  206. package/packages/sdk/src/types.ts +0 -176
  207. package/packages/sdk/tests/client.test.ts +0 -217
  208. package/packages/sdk/tests/errors.test.ts +0 -108
  209. package/packages/sdk/tests/events.test.ts +0 -139
  210. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  211. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  212. package/plugins/bizar/ARCHITECTURE.md +0 -142
  213. package/plugins/bizar/CONSTRAINTS.md +0 -67
  214. package/plugins/bizar/LICENSE +0 -21
  215. package/plugins/bizar/README.md +0 -448
  216. package/plugins/bizar/index.ts +0 -925
  217. package/plugins/bizar/package.json +0 -39
  218. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  219. package/plugins/bizar/src/background-state.ts +0 -641
  220. package/plugins/bizar/src/background.ts +0 -1806
  221. package/plugins/bizar/src/cline-runner.ts +0 -203
  222. package/plugins/bizar/src/clineruntime.ts +0 -227
  223. package/plugins/bizar/src/commands-impl.ts +0 -151
  224. package/plugins/bizar/src/commands.ts +0 -1799
  225. package/plugins/bizar/src/compaction.d.mts +0 -48
  226. package/plugins/bizar/src/compaction.mjs +0 -192
  227. package/plugins/bizar/src/dashboard-client.ts +0 -233
  228. package/plugins/bizar/src/event-stream.ts +0 -606
  229. package/plugins/bizar/src/fingerprint.ts +0 -120
  230. package/plugins/bizar/src/handoff.ts +0 -79
  231. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  232. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  233. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  234. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  235. package/plugins/bizar/src/http-client.ts +0 -467
  236. package/plugins/bizar/src/key-rotation.ts +0 -218
  237. package/plugins/bizar/src/logger.ts +0 -144
  238. package/plugins/bizar/src/loop-engineering.ts +0 -241
  239. package/plugins/bizar/src/loop.ts +0 -176
  240. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  241. package/plugins/bizar/src/odin.ts +0 -227
  242. package/plugins/bizar/src/options.ts +0 -470
  243. package/plugins/bizar/src/plan-fs.ts +0 -323
  244. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  245. package/plugins/bizar/src/report.ts +0 -178
  246. package/plugins/bizar/src/research-prompt.ts +0 -35
  247. package/plugins/bizar/src/serve-info.ts +0 -228
  248. package/plugins/bizar/src/serve.ts +0 -496
  249. package/plugins/bizar/src/settings.ts +0 -349
  250. package/plugins/bizar/src/state.ts +0 -298
  251. package/plugins/bizar/src/tool-discipline.ts +0 -105
  252. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  253. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  254. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  255. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  256. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  257. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  258. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  259. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  260. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  261. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  262. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  263. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  264. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  265. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  266. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  267. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  268. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  269. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  270. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  271. package/plugins/bizar/src/tools/sandbox.ts +0 -232
  272. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  273. package/plugins/bizar/src/tools/team-status.ts +0 -76
  274. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  275. package/plugins/bizar/src/trajectory.ts +0 -104
  276. package/plugins/bizar/tests/README.md +0 -99
  277. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  278. package/plugins/bizar/tests/background-state.test.ts +0 -277
  279. package/plugins/bizar/tests/background.test.ts +0 -402
  280. package/plugins/bizar/tests/block.test.ts +0 -195
  281. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  282. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  283. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  284. package/plugins/bizar/tests/commands.test.ts +0 -584
  285. package/plugins/bizar/tests/compaction.test.ts +0 -264
  286. package/plugins/bizar/tests/config.test.ts +0 -128
  287. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  288. package/plugins/bizar/tests/dispose.test.ts +0 -336
  289. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  290. package/plugins/bizar/tests/event.test.ts +0 -262
  291. package/plugins/bizar/tests/fingerprint.test.ts +0 -190
  292. package/plugins/bizar/tests/http-client.test.ts +0 -404
  293. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  294. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  295. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  296. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  297. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  298. package/plugins/bizar/tests/loop.test.ts +0 -397
  299. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  300. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  301. package/plugins/bizar/tests/odin.test.ts +0 -125
  302. package/plugins/bizar/tests/options.test.ts +0 -329
  303. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  304. package/plugins/bizar/tests/safety.test.ts +0 -281
  305. package/plugins/bizar/tests/serve.test.ts +0 -339
  306. package/plugins/bizar/tests/settings.test.ts +0 -351
  307. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  308. package/plugins/bizar/tests/state.test.ts +0 -276
  309. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  310. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  311. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  312. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  313. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  314. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  315. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  316. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  317. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  318. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  319. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  320. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  321. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  322. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  323. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  324. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  325. package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
  326. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  327. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  328. package/plugins/bizar/tsconfig.json +0 -29
  329. package/scripts/mirror-agents-md.sh +0 -69
@@ -0,0 +1,893 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli/provision-claude.mjs
4
+ *
5
+ * v6.3.0 — Claude Code-native installer + updater.
6
+ *
7
+ * Replaces `cli/provision.mjs` (the Cline-era provisioner). The install
8
+ * / update flow for Claude Code is different from Cline:
9
+ *
10
+ * - Config root is `~/.claude/` (NOT `~/.cline/`).
11
+ * - The configuration file is `~/.claude/settings.json` (NOT
12
+ * `~/.cline/cline.json`).
13
+ * - MCP servers are registered via `claude mcp add` (NOT by
14
+ * patching a `plugin[]` array).
15
+ * - Plugins are managed via `claude plugin install ...` (NOT
16
+ * copied into `~/.cline/plugins/bizar/`).
17
+ * - Skills live under `~/.claude/skills/<name>/SKILL.md`.
18
+ * - Commands live under `~/.claude/commands/<name>.md`.
19
+ * - Rules live under `~/.claude/rules/<name>.md`.
20
+ * - Hooks are wired into `~/.claude/settings.json` (NOT installed
21
+ * as executable files in `~/.cline/hooks/`).
22
+ * - `~/.claude/CLAUDE.md` mirrors the repo's `AGENTS.md`.
23
+ *
24
+ * Public API:
25
+ * runProvision({ mode, dryRun, force, yes, restart, ... })
26
+ * Runs the full provision flow for `mode` ('install' | 'update').
27
+ * detectState()
28
+ * Probe what's installed without modifying anything.
29
+ * Returns a structured state object.
30
+ *
31
+ * Both modes are idempotent and safe to re-run.
32
+ *
33
+ * Flags (parsed by the CLI shim at the bottom):
34
+ * --mode=install|update|install-only-system
35
+ * --dry-run Print actions, make no changes.
36
+ * --force Overwrite existing files.
37
+ * --yes / -y Same as --force.
38
+ * --non-interactive Same as --yes.
39
+ */
40
+
41
+ import chalk from 'chalk';
42
+ import { execFileSync, execSync, spawn, spawnSync } from 'node:child_process';
43
+ import {
44
+ chmodSync,
45
+ copyFileSync,
46
+ existsSync,
47
+ mkdirSync,
48
+ readdirSync,
49
+ readFileSync,
50
+ rmSync,
51
+ statSync,
52
+ writeFileSync,
53
+ } from 'node:fs';
54
+ import { homedir } from 'node:os';
55
+ import { dirname, join, resolve, sep } from 'node:path';
56
+ import { fileURLToPath } from 'node:url';
57
+
58
+ const __filename = fileURLToPath(import.meta.url);
59
+ const __dirname = dirname(__filename);
60
+ const HOME = homedir();
61
+
62
+ // Repo root = `<pkg>/cli/provision-claude.mjs` → one level up.
63
+ // This works for source checkouts AND global npm installs.
64
+ export const REPO_ROOT = resolve(__dirname, '..');
65
+
66
+ // ─── Paths ────────────────────────────────────────────────────────────────────
67
+
68
+ /**
69
+ * Resolve the Claude Code config directory.
70
+ *
71
+ * Resolution order (matches Claude Code's own resolver, see
72
+ * https://code.claude.com/docs/en/cli-reference):
73
+ * 1. `process.env.CLAUDE_CONFIG_DIR` (explicit override)
74
+ * 2. `$HOME/.claude` (default)
75
+ * 3. (Windows only) `%APPDATA%\Claude`
76
+ */
77
+ export function resolveClaudeDir() {
78
+ if (process.env.CLAUDE_CONFIG_DIR && process.env.CLAUDE_CONFIG_DIR.trim()) {
79
+ return process.env.CLAUDE_CONFIG_DIR.trim();
80
+ }
81
+ if (process.platform === 'win32') {
82
+ return process.env.APPDATA
83
+ ? join(process.env.APPDATA, 'Claude')
84
+ : join(HOME, '.claude');
85
+ }
86
+ return join(HOME, '.claude');
87
+ }
88
+
89
+ export const CLAUDE_DIR = resolveClaudeDir();
90
+
91
+ /** `~/.bizar_home/` — Bizar-specific runtime state (NOT under ~/.claude/). */
92
+ export const BIZAR_HOME = process.env.BIZAR_HOME
93
+ || join(process.env.XDG_CONFIG_HOME || join(HOME, '.config'), 'bizar');
94
+
95
+ // Standard Claude Code subdirectories (v6.3.0 — Claude Code layout)
96
+ export const CLAUDE_AGENTS_DIR = join(CLAUDE_DIR, 'agents');
97
+ export const CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, 'skills');
98
+ export const CLAUDE_COMMANDS_DIR = join(CLAUDE_DIR, 'commands');
99
+ export const CLAUDE_HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
100
+ export const CLAUDE_RULES_DIR = join(CLAUDE_DIR, 'rules');
101
+ export const CLAUDE_PLUGINS_DIR = join(CLAUDE_DIR, 'plugins');
102
+
103
+ // Service / dashboard pid files
104
+ const SERVICE_PID_FILE = join(BIZAR_HOME, 'service.pid');
105
+ const DASHBOARD_PID_FILE = join(BIZAR_HOME, 'dashboard.pid');
106
+ const DASHBOARD_PORT_FILE = join(BIZAR_HOME, 'dashboard.port');
107
+
108
+ // ─── Tiny utilities ──────────────────────────────────────────────────────────
109
+
110
+ function haveCmd(cmd) {
111
+ try {
112
+ execSync(`command -v ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] });
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ function readTextSafe(file, fallback = '') {
120
+ try {
121
+ if (!existsSync(file)) return fallback;
122
+ return readFileSync(file, 'utf8');
123
+ } catch {
124
+ return fallback;
125
+ }
126
+ }
127
+
128
+ function readJsonSafe(file, fallback = null) {
129
+ try {
130
+ if (!existsSync(file)) return fallback;
131
+ return JSON.parse(readFileSync(file, 'utf8'));
132
+ } catch {
133
+ return fallback;
134
+ }
135
+ }
136
+
137
+ function ensureDir(dir) {
138
+ mkdirSync(dir, { recursive: true });
139
+ }
140
+
141
+ function copyDirContents(src, dest) {
142
+ ensureDir(dest);
143
+ for (const name of readdirSync(src)) {
144
+ const sp = join(src, name);
145
+ const dp = join(dest, name);
146
+ const st = statSync(sp);
147
+ if (st.isDirectory()) copyDirContents(sp, dp);
148
+ else copyFileSync(sp, dp);
149
+ }
150
+ }
151
+
152
+ function rimrafSafe(p) {
153
+ try { rmSync(p, { recursive: true, force: true }); } catch { /* ignore */ }
154
+ }
155
+
156
+ function logOk(msg) { console.log(chalk.green(' ✓') + ' ' + msg); }
157
+ function logInfo(msg) { console.log(chalk.cyan(' →') + ' ' + msg); }
158
+ function logWarn(msg) { console.log(chalk.yellow(' ⚠') + ' ' + msg); }
159
+ function logErr(msg) { console.log(chalk.red(' ✗') + ' ' + msg); }
160
+ function section(title) { console.log('\n' + chalk.bold.cyan(`── ${title} ──`)); }
161
+
162
+ // ─── Bizar HOME bootstrap ────────────────────────────────────────────────────
163
+
164
+ /**
165
+ * Ensure `~/.bizar_home/` (or `$BIZAR_HOME`) exists with the runtime
166
+ * subdirectories Bizar needs (memory vault, loops dir, dashboard port
167
+ * file). Idempotent.
168
+ */
169
+ export function ensureBizarHome({ dryRun = false } = {}) {
170
+ if (dryRun) {
171
+ return { ok: true, message: `[dry-run] would ensure ${BIZAR_HOME}` };
172
+ }
173
+ mkdirSync(BIZAR_HOME, { recursive: true });
174
+ mkdirSync(join(BIZAR_HOME, 'memory-vault'), { recursive: true });
175
+ mkdirSync(join(BIZAR_HOME, 'loops'), { recursive: true });
176
+ // Touch the dashboard port file so downstream readers can probe it.
177
+ if (!existsSync(DASHBOARD_PORT_FILE)) {
178
+ writeFileSync(DASHBOARD_PORT_FILE, '4321\n');
179
+ }
180
+ return { ok: true, message: `${BIZAR_HOME} ready`, path: BIZAR_HOME };
181
+ }
182
+
183
+ // ─── Toolchain ───────────────────────────────────────────────────────────────
184
+
185
+ function checkToolchain() {
186
+ section('Toolchain');
187
+ if (haveCmd('node')) logOk(`node ${execSync('node --version').toString().trim()}`);
188
+ else { logErr('node not on PATH — install Node.js 18+ from https://nodejs.org'); process.exit(1); }
189
+
190
+ if (haveCmd('npm')) logOk(`npm ${execSync('npm --version').toString().trim()}`);
191
+ else logWarn('npm not on PATH — Claude Code install will need npm');
192
+
193
+ if (haveCmd('git')) logOk(`git ${execSync('git --version').toString().trim().split(' ')[2]}`);
194
+ else logWarn('git not on PATH — optional for many tasks');
195
+
196
+ if (haveCmd('claude')) logOk(`claude ${execSync('claude --version').toString().trim().split('\n')[0]}`);
197
+ else logWarn('claude not on PATH — will install via npm');
198
+
199
+ if (haveCmd('agent-browser')) logOk(`agent-browser ${execSync('agent-browser --version').toString().trim()}`);
200
+ else logInfo('agent-browser not installed (optional; npm install -g agent-browser)');
201
+ }
202
+
203
+ /** Install Claude Code CLI + SDK globally via npm. Idempotent. */
204
+ function installClaudeCli({ force = false, dryRun = false } = {}) {
205
+ if (haveCmd('claude') && !force) return;
206
+ section('Installing Claude Code CLI');
207
+ logInfo('npm install -g @anthropic-ai/claude-agent-sdk @anthropic-ai/claude-code');
208
+ if (dryRun) return;
209
+ try {
210
+ execSync(
211
+ 'npm install -g @anthropic-ai/claude-agent-sdk @anthropic-ai/claude-code',
212
+ { stdio: 'inherit' },
213
+ );
214
+ logOk('Claude Code CLI installed');
215
+ } catch (err) {
216
+ logErr(`npm install failed: ${err.message}`);
217
+ logWarn('retry manually: npm install -g @anthropic-ai/claude-code');
218
+ }
219
+ }
220
+
221
+ // ─── File sync helpers ──────────────────────────────────────────────────────
222
+
223
+ /**
224
+ * Mirror a source directory into a destination directory, copying
225
+ * files and preserving subdirectory structure. Skips dotfiles by default.
226
+ */
227
+ function syncDir(srcDir, destDir, opts = {}) {
228
+ if (!existsSync(srcDir)) return { copied: 0, skipped: 0 };
229
+ ensureDir(destDir);
230
+ let copied = 0;
231
+ let skipped = 0;
232
+ for (const name of readdirSync(srcDir)) {
233
+ if (name.startsWith('.')) continue;
234
+ const sp = join(srcDir, name);
235
+ const dp = join(destDir, name);
236
+ const st = statSync(sp);
237
+ if (opts.filter && !opts.filter(name, sp)) { skipped++; continue; }
238
+ if (st.isDirectory()) {
239
+ const r = syncDir(sp, dp, opts);
240
+ copied += r.copied;
241
+ skipped += r.skipped;
242
+ } else {
243
+ copyFileSync(sp, dp);
244
+ copied++;
245
+ }
246
+ }
247
+ return { copied, skipped };
248
+ }
249
+
250
+ /**
251
+ * Copy `config/agents/*.md` → `~/.claude/agents/*.md`.
252
+ *
253
+ * v6.3.0 — Agent files use Claude Code frontmatter (name / description
254
+ * / tools / model at the top). The source files in `config/agents/`
255
+ * have already been migrated by a parallel task; this helper is a
256
+ * straight copy with no YAML regeneration (Claude Code reads `.md`
257
+ * directly — no `.yaml` shadow needed).
258
+ */
259
+ export async function syncAgentFiles({ dryRun = false, force = false } = {}) {
260
+ const src = join(REPO_ROOT, 'config', 'agents');
261
+ const dest = CLAUDE_AGENTS_DIR;
262
+ if (!existsSync(src)) {
263
+ return { ok: true, message: `no agents source at ${src}`, copied: 0, skipped: 0 };
264
+ }
265
+ if (dryRun) {
266
+ return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
267
+ }
268
+ ensureDir(dest);
269
+ ensureDir(join(dest, '_shared'));
270
+ // Top-level .md files
271
+ let { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') });
272
+ // _shared/ — always overwrite (it's tiny + ships agent defaults).
273
+ const sharedSrc = join(src, '_shared');
274
+ if (existsSync(sharedSrc)) {
275
+ for (const entry of readdirSync(sharedSrc, { withFileTypes: true })) {
276
+ if (!entry.isFile()) continue;
277
+ copyFileSync(join(sharedSrc, entry.name), join(dest, '_shared', entry.name));
278
+ copied++;
279
+ }
280
+ }
281
+ return { ok: true, message: `${copied} agent(s) synced (${skipped} kept)`, copied, skipped };
282
+ }
283
+
284
+ /**
285
+ * Copy `config/skills/<name>/` → `~/.claude/skills/<name>/`.
286
+ *
287
+ * Each skill is mirrored as its own subdirectory under
288
+ * `~/.claude/skills/<name>/`. Honors `force` to overwrite.
289
+ */
290
+ export async function syncSkillFiles({ dryRun = false, force = false } = {}) {
291
+ const src = join(REPO_ROOT, 'config', 'skills');
292
+ const dest = CLAUDE_SKILLS_DIR;
293
+ if (!existsSync(src)) {
294
+ return { ok: true, message: `no skills source at ${src}`, copied: 0, skipped: 0 };
295
+ }
296
+ if (dryRun) {
297
+ return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
298
+ }
299
+ ensureDir(dest);
300
+ copyDirContents(src, dest);
301
+
302
+ // Mirror AGENT_BASELINE.md → ~/.claude/skills/agent-baseline/SKILL.md
303
+ // (the Claude Code skill loader resolves the `name: agent-baseline`
304
+ // reference via this path).
305
+ const sharedBaseline = join(REPO_ROOT, 'config', 'agents', '_shared', 'AGENT_BASELINE.md');
306
+ if (existsSync(sharedBaseline)) {
307
+ const baselineDir = join(dest, 'agent-baseline');
308
+ ensureDir(baselineDir);
309
+ copyFileSync(sharedBaseline, join(baselineDir, 'SKILL.md'));
310
+ }
311
+
312
+ const count = readdirSync(dest).filter((n) => {
313
+ try { return statSync(join(dest, n)).isDirectory(); } catch { return false; }
314
+ }).length;
315
+ return { ok: true, message: `${count} skill(s) synced`, copied: count, skipped: 0 };
316
+ }
317
+
318
+ /**
319
+ * Copy `.claude/commands/*.md` → `~/.claude/commands/*.md`.
320
+ *
321
+ * Slash commands live in the project's `.claude/commands/` directory
322
+ * (project-scoped). The global mirror in `~/.claude/commands/` makes
323
+ * them available across every Claude Code session for the user.
324
+ */
325
+ export async function syncCommandFiles({ dryRun = false, force = false } = {}) {
326
+ // Project-scoped commands first, then legacy `config/commands/` as
327
+ // a fallback for older Bizar installs.
328
+ const candidates = [
329
+ join(REPO_ROOT, '.claude', 'commands'),
330
+ join(REPO_ROOT, 'config', 'commands'),
331
+ ];
332
+ let src = null;
333
+ for (const c of candidates) if (existsSync(c)) { src = c; break; }
334
+ if (!src) {
335
+ return { ok: true, message: 'no commands source found', copied: 0, skipped: 0 };
336
+ }
337
+ const dest = CLAUDE_COMMANDS_DIR;
338
+ if (dryRun) {
339
+ return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
340
+ }
341
+ ensureDir(dest);
342
+ const { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') });
343
+ return { ok: true, message: `${copied} command(s) synced (${skipped} kept)`, copied, skipped };
344
+ }
345
+
346
+ /**
347
+ * Copy `config/rules/*.md` → `~/.claude/rules/*.md`.
348
+ *
349
+ * Claude Code auto-loads every `.md` file under `~/.claude/rules/` as
350
+ * an always-on rule (https://code.claude.com/docs/en/rules).
351
+ */
352
+ export async function syncRulesFiles({ dryRun = false, force = false } = {}) {
353
+ const src = join(REPO_ROOT, 'config', 'rules');
354
+ const dest = CLAUDE_RULES_DIR;
355
+ if (!existsSync(src)) {
356
+ return { ok: true, message: `no rules source at ${src}`, copied: 0, skipped: 0 };
357
+ }
358
+ if (dryRun) {
359
+ return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
360
+ }
361
+ ensureDir(dest);
362
+ const { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') || n.endsWith('.txt') });
363
+ return { ok: true, message: `${copied} rule(s) synced (${skipped} kept)`, copied, skipped };
364
+ }
365
+
366
+ /**
367
+ * Copy `.claude/hooks/*.mjs` → `~/.claude/hooks/*.mjs`.
368
+ *
369
+ * Hook adapter scripts are real Node modules — Claude Code spawns them
370
+ * and pipes JSON on stdin / stdout. They must be `chmod +x` so Claude
371
+ * Code can `execve` them directly.
372
+ */
373
+ export async function syncHookFiles({ dryRun = false, force = false } = {}) {
374
+ const src = join(REPO_ROOT, '.claude', 'hooks');
375
+ const dest = CLAUDE_HOOKS_DIR;
376
+ ensureDir(dest);
377
+ if (!existsSync(src)) {
378
+ return { ok: true, message: `no hooks source at ${src}`, copied: 0, skipped: 0 };
379
+ }
380
+ if (dryRun) {
381
+ return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
382
+ }
383
+ copyDirContents(src, dest);
384
+ // Make all scripts executable.
385
+ for (const name of readdirSync(dest)) {
386
+ const fp = join(dest, name);
387
+ try {
388
+ const st = statSync(fp);
389
+ if (st.isFile() && (name.endsWith('.sh') || name.endsWith('.mjs'))) {
390
+ chmodSync(fp, 0o755);
391
+ }
392
+ } catch { /* ignore */ }
393
+ }
394
+ return { ok: true, message: 'hook scripts installed', copied: readdirSync(dest).length, skipped: 0 };
395
+ }
396
+
397
+ // ─── settings.json ───────────────────────────────────────────────────────────
398
+
399
+ /**
400
+ * Generate `~/.claude/settings.json` with the Bizar harness surface:
401
+ * - $schema pointing to https://json.schemastore.org/claude-code-settings.json
402
+ * - mcpServers block registering the `bizar` MCP server (stdio)
403
+ * - permissions.allow for mcp__bizar__*
404
+ * - permissions.deny for dangerous patterns
405
+ * - hooks.PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd
406
+ * - env block with BIZAR_HOME, BIZAR_MEMORY_VAULT
407
+ *
408
+ * Idempotent — we merge with any existing settings.json (preserve
409
+ * user-added keys, never overwrite).
410
+ */
411
+ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
412
+ const fp = join(CLAUDE_DIR, 'settings.json');
413
+ const existing = readJsonSafe(fp, {}) || {};
414
+
415
+ const bizarSettings = {
416
+ $schema: 'https://json.schemastore.org/claude-code-settings.json',
417
+ mcpServers: {
418
+ bizar: {
419
+ type: 'stdio',
420
+ command: 'npx',
421
+ args: ['-y', '@polderlabs/bizar-sdk', 'mcp'],
422
+ env: {
423
+ BIZAR_HOME,
424
+ BIZAR_MEMORY_VAULT: process.env.BIZAR_MEMORY_VAULT || join(BIZAR_HOME, 'memory-vault'),
425
+ },
426
+ },
427
+ },
428
+ permissions: {
429
+ allow: ['mcp__bizar__*'],
430
+ deny: [
431
+ 'Read(./.env)',
432
+ 'Read(./.env.*)',
433
+ 'Bash(rm -rf /)',
434
+ 'Bash(sudo *)',
435
+ 'Write(./node_modules/**)',
436
+ 'Write(./package-lock.json)',
437
+ ],
438
+ },
439
+ env: {
440
+ BIZAR_HOME,
441
+ BIZAR_MEMORY_VAULT: process.env.BIZAR_MEMORY_VAULT || join(BIZAR_HOME, 'memory-vault'),
442
+ },
443
+ hooks: {
444
+ PreToolUse: [
445
+ {
446
+ matcher: 'Write|Edit|MultiEdit|Bash',
447
+ hooks: [
448
+ {
449
+ type: 'command',
450
+ command: join(CLAUDE_HOOKS_DIR, 'pretooluse-editwrite.mjs'),
451
+ timeout: 10,
452
+ },
453
+ ],
454
+ },
455
+ ],
456
+ PostToolUse: [
457
+ {
458
+ matcher: 'Edit|Write|MultiEdit',
459
+ hooks: [
460
+ {
461
+ type: 'command',
462
+ command: join(CLAUDE_HOOKS_DIR, 'posttooluse-editwrite.mjs'),
463
+ timeout: 10,
464
+ },
465
+ ],
466
+ },
467
+ ],
468
+ UserPromptSubmit: [
469
+ {
470
+ hooks: [
471
+ {
472
+ type: 'command',
473
+ command: join(CLAUDE_HOOKS_DIR, 'userpromptsubmit-tag.mjs'),
474
+ timeout: 10,
475
+ },
476
+ ],
477
+ },
478
+ ],
479
+ SessionStart: [
480
+ {
481
+ hooks: [
482
+ {
483
+ type: 'command',
484
+ command: join(CLAUDE_HOOKS_DIR, 'sessionstart-prime.mjs'),
485
+ timeout: 10,
486
+ },
487
+ ],
488
+ },
489
+ ],
490
+ SessionEnd: [
491
+ {
492
+ hooks: [
493
+ {
494
+ type: 'command',
495
+ command: join(CLAUDE_HOOKS_DIR, 'sessionend-record.mjs'),
496
+ timeout: 10,
497
+ },
498
+ ],
499
+ },
500
+ ],
501
+ },
502
+ };
503
+
504
+ // Merge bizarre-specific keys into existing settings, preserve user's
505
+ // unrelated keys (model, attribution, theme, etc.).
506
+ const merged = { ...existing };
507
+ if (force) {
508
+ Object.assign(merged, bizarSettings);
509
+ } else {
510
+ merged.$schema = merged.$schema || bizarSettings.$schema;
511
+ merged.mcpServers = { ...(existing.mcpServers || {}), ...bizarSettings.mcpServers };
512
+ merged.permissions = {
513
+ allow: [...new Set([...(existing.permissions?.allow || []), ...bizarSettings.permissions.allow])],
514
+ deny: [...new Set([...(existing.permissions?.deny || []), ...bizarSettings.permissions.deny])],
515
+ ask: [...new Set([...(existing.permissions?.ask || [])])],
516
+ };
517
+ merged.env = { ...(existing.env || {}), ...bizarSettings.env };
518
+ merged.hooks = { ...(existing.hooks || {}), ...bizarSettings.hooks };
519
+ }
520
+
521
+ if (dryRun) {
522
+ return { ok: true, message: `[dry-run] would write ${fp}` };
523
+ }
524
+ ensureDir(CLAUDE_DIR);
525
+ writeFileSync(fp, JSON.stringify(merged, null, 2) + '\n');
526
+ return { ok: true, message: `wrote ${fp}`, path: fp };
527
+ }
528
+
529
+ // ─── CLAUDE.md mirror ──────────────────────────────────────────────────────
530
+
531
+ /**
532
+ * Mirror the repo's `AGENTS.md` into `~/.claude/CLAUDE.md`.
533
+ *
534
+ * Claude Code reads `~/.claude/CLAUDE.md` at session start as a global
535
+ * memory file. Mirroring `AGENTS.md` (the canonical harness entry
536
+ * point) makes the project's always-on rules available to every
537
+ * Claude Code session for the user, not just project-scoped sessions.
538
+ */
539
+ export function writeClaudeMdMirror({ dryRun = false, force = false } = {}) {
540
+ const srcCandidates = [
541
+ join(REPO_ROOT, 'AGENTS.md'),
542
+ join(REPO_ROOT, 'CLAUDE.md'),
543
+ ];
544
+ const src = srcCandidates.find((p) => existsSync(p));
545
+ const dest = join(CLAUDE_DIR, 'CLAUDE.md');
546
+ if (!src) {
547
+ return { ok: false, message: 'no CLAUDE.md / AGENTS.md found in repo root' };
548
+ }
549
+ if (existsSync(dest) && !force) {
550
+ return { ok: true, message: `${dest} already exists — pass --force to overwrite` };
551
+ }
552
+ if (dryRun) {
553
+ return { ok: true, message: `[dry-run] would mirror ${src} → ${dest}` };
554
+ }
555
+ ensureDir(CLAUDE_DIR);
556
+ // Add a banner so users know this is auto-generated.
557
+ const body = readFileSync(src, 'utf8');
558
+ const banner =
559
+ `# CLAUDE.md — Mirror of AGENTS.md for Claude Code compatibility\n\n` +
560
+ `> Auto-generated by \`cli/provision-claude.mjs:writeClaudeMdMirror\`.\n` +
561
+ `> DO NOT EDIT THIS FILE DIRECTLY — edit the project root's \`AGENTS.md\`\n` +
562
+ `> and re-run \`bizar update\` (or \`make mirror-claude-md\`) to refresh.\n\n` +
563
+ `---\n\n`;
564
+ writeFileSync(dest, banner + body);
565
+ return { ok: true, message: `mirrored ${src} → ${dest}`, path: dest };
566
+ }
567
+
568
+ // ─── MCP server registration ─────────────────────────────────────────────────
569
+
570
+ /**
571
+ * Register the `bizar` MCP server via `claude mcp add`. Idempotent —
572
+ * if the server is already registered in settings.json, we skip the
573
+ * `claude mcp add` call.
574
+ *
575
+ * Per https://code.claude.com/docs/en/mcp, the canonical way to add a
576
+ * stdio MCP server is:
577
+ *
578
+ * claude mcp add <name> -- <command> [args...]
579
+ */
580
+ export function setupMcpServer({ dryRun = false } = {}) {
581
+ // Idempotency probe: read existing settings.json and look for a
582
+ // bizar MCP server entry. If present, skip.
583
+ const settingsPath = join(CLAUDE_DIR, 'settings.json');
584
+ const existing = readJsonSafe(settingsPath, null);
585
+ if (existing && existing.mcpServers && existing.mcpServers.bizar) {
586
+ return { ok: true, message: `bizar MCP server already registered in ${settingsPath}` };
587
+ }
588
+
589
+ if (!haveCmd('claude')) {
590
+ return { ok: false, message: 'claude CLI not on PATH — install @anthropic-ai/claude-code first' };
591
+ }
592
+ if (dryRun) {
593
+ return { ok: true, message: '[dry-run] would run: claude mcp add bizar -- npx -y @polderlabs/bizar-sdk mcp' };
594
+ }
595
+
596
+ const r = spawnSync('claude', [
597
+ 'mcp', 'add', '-f', '-s', 'user', 'bizar', '--',
598
+ 'npx', '-y', '@polderlabs/bizar-sdk', 'mcp',
599
+ ], { stdio: 'inherit', timeout: 60_000 });
600
+
601
+ if (r.status !== 0) {
602
+ return { ok: false, message: `claude mcp add exited with code ${r.status}` };
603
+ }
604
+ return { ok: true, message: 'bizar MCP server registered via `claude mcp add`' };
605
+ }
606
+
607
+ /**
608
+ * If `./.claude-plugin/plugin.json` exists, install the Bizar plugin
609
+ * via `claude plugin install .`. Per
610
+ * https://code.claude.com/docs/en/plugins, a Claude Code plugin is a
611
+ * directory containing a `.claude-plugin/plugin.json` manifest.
612
+ */
613
+ export function setupClaudePlugin({ dryRun = false } = {}) {
614
+ const manifestPath = join(REPO_ROOT, '.claude-plugin', 'plugin.json');
615
+ if (!existsSync(manifestPath)) {
616
+ return { ok: true, message: `no plugin manifest at ${manifestPath} — skipping plugin install` };
617
+ }
618
+ if (!haveCmd('claude')) {
619
+ return { ok: false, message: 'claude CLI not on PATH — install @anthropic-ai/claude-code first' };
620
+ }
621
+ if (dryRun) {
622
+ return { ok: true, message: `[dry-run] would run: claude plugin install ${REPO_ROOT}` };
623
+ }
624
+
625
+ const r = spawnSync('claude', ['plugin', 'install', '.'], {
626
+ stdio: 'inherit',
627
+ timeout: 120_000,
628
+ cwd: REPO_ROOT,
629
+ });
630
+ if (r.status !== 0) {
631
+ return { ok: false, message: `claude plugin install exited with code ${r.status}` };
632
+ }
633
+ return { ok: true, message: `claude plugin installed from ${REPO_ROOT}` };
634
+ }
635
+
636
+ // ─── Dashboard service ───────────────────────────────────────────────────────
637
+
638
+ function installDashboardService({ dryRun = false, start = true } = {}) {
639
+ const bin = join(REPO_ROOT, 'cli', 'bin.mjs');
640
+ if (!existsSync(bin)) {
641
+ return { ok: false, message: `cli/bin.mjs not found at ${bin} — service registration deferred` };
642
+ }
643
+ if (dryRun) {
644
+ return { ok: true, message: `[dry-run] would run: node ${bin} service install` };
645
+ }
646
+ const r = spawnSync('node', [bin, 'service', 'install'], { stdio: 'inherit' });
647
+ if (r.status !== 0) {
648
+ return { ok: false, message: `service install returned ${r.status}` };
649
+ }
650
+ return { ok: true, message: 'dashboard service registered' };
651
+ }
652
+
653
+ // ─── Detection ───────────────────────────────────────────────────────────────
654
+
655
+ /**
656
+ * Probe the current state without modifying anything. Both install and
657
+ * update flows start here so they can decide what to skip.
658
+ *
659
+ * Returned shape:
660
+ * {
661
+ * claudeDir: string,
662
+ * claudeCli: { available, version },
663
+ * agentsDir: { exists, count },
664
+ * skillsDir: { exists, count },
665
+ * commandsDir: { exists, count },
666
+ * hooksDir: { exists, count },
667
+ * rulesDir: { exists, count },
668
+ * settingsFile: { exists, parses },
669
+ * mcpServer: { registered, type, command },
670
+ * pluginManifest: { exists },
671
+ * bizarHome: { exists },
672
+ * gitRepo: boolean,
673
+ * }
674
+ */
675
+ export function detectState() {
676
+ const settingsPath = join(CLAUDE_DIR, 'settings.json');
677
+ const settings = readJsonSafe(settingsPath, null);
678
+
679
+ let claudeVersion = null;
680
+ try {
681
+ claudeVersion = execFileSync('claude', ['--version'], { encoding: 'utf8', timeout: 5000 })
682
+ .toString().trim().split('\n')[0] || null;
683
+ } catch { /* not installed */ }
684
+
685
+ const countMd = (dir) => {
686
+ if (!existsSync(dir)) return 0;
687
+ try {
688
+ return readdirSync(dir, { withFileTypes: true })
689
+ .filter((e) => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mjs'))).length;
690
+ } catch { return 0; }
691
+ };
692
+ const countSkillDirs = (dir) => {
693
+ if (!existsSync(dir)) return 0;
694
+ try {
695
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
696
+ } catch { return 0; }
697
+ };
698
+
699
+ const mcpServer = settings && settings.mcpServers && settings.mcpServers.bizar
700
+ ? {
701
+ registered: true,
702
+ type: settings.mcpServers.bizar.type || 'stdio',
703
+ command: settings.mcpServers.bizar.command || 'npx',
704
+ }
705
+ : { registered: false };
706
+
707
+ return {
708
+ claudeDir: CLAUDE_DIR,
709
+ claudeCli: { available: !!claudeVersion, version: claudeVersion },
710
+ agentsDir: { exists: existsSync(CLAUDE_AGENTS_DIR), count: countMd(CLAUDE_AGENTS_DIR) },
711
+ skillsDir: { exists: existsSync(CLAUDE_SKILLS_DIR), count: countSkillDirs(CLAUDE_SKILLS_DIR) },
712
+ commandsDir: { exists: existsSync(CLAUDE_COMMANDS_DIR), count: countMd(CLAUDE_COMMANDS_DIR) },
713
+ hooksDir: { exists: existsSync(CLAUDE_HOOKS_DIR), count: countMd(CLAUDE_HOOKS_DIR) },
714
+ rulesDir: { exists: existsSync(CLAUDE_RULES_DIR), count: countMd(CLAUDE_RULES_DIR) },
715
+ settingsFile: {
716
+ exists: existsSync(settingsPath),
717
+ parses: settings !== null && typeof settings === 'object',
718
+ path: settingsPath,
719
+ },
720
+ mcpServer,
721
+ pluginManifest: { exists: existsSync(join(REPO_ROOT, '.claude-plugin', 'plugin.json')) },
722
+ bizarHome: { exists: existsSync(BIZAR_HOME), path: BIZAR_HOME },
723
+ gitRepo: existsSync(join(REPO_ROOT, '.git')),
724
+ };
725
+ }
726
+
727
+ /** Read-only JSON snapshot of detectState() — for diagnostics + tests. */
728
+ export function detectStateJson() {
729
+ return JSON.stringify(detectState(), null, 2);
730
+ }
731
+
732
+ // ─── Top-level orchestration ─────────────────────────────────────────────────
733
+
734
+ /**
735
+ * The unified provision flow. `mode` is 'install' or 'update'.
736
+ *
737
+ * Steps performed:
738
+ * 1. Detect state (no side effects).
739
+ * 2. Ensure ~/.bizar_home/ exists (memory-vault, loops, port file).
740
+ * 3. Check toolchain + install Claude Code CLI if missing.
741
+ * 4. Sync agent files to ~/.claude/agents/.
742
+ * 5. Sync skills to ~/.claude/skills/.
743
+ * 6. Sync commands to ~/.claude/commands/.
744
+ * 7. Sync rules to ~/.claude/rules/.
745
+ * 8. Sync hook adapter scripts to ~/.claude/hooks/.
746
+ * 9. Write ~/.claude/settings.json with MCP + hooks + permissions + env.
747
+ * 10. Register the Bizar MCP server via `claude mcp add`.
748
+ * 11. Install the Bizar plugin via `claude plugin install .` (optional).
749
+ * 12. Mirror AGENTS.md → ~/.claude/CLAUDE.md.
750
+ * 13. (install) Register dashboard service.
751
+ * 14. Print summary.
752
+ *
753
+ * Every step is idempotent — running this twice is safe.
754
+ */
755
+ export async function runProvision(opts = {}) {
756
+ const {
757
+ mode = 'install',
758
+ dryRun = false,
759
+ force = false,
760
+ yes = false,
761
+ start = true,
762
+ } = opts;
763
+
764
+ const effectiveMode = mode === 'update' ? 'update' : 'install';
765
+
766
+ console.log('');
767
+ console.log(chalk.bold.cyan(' ⚡ BizarHarness Provisioner v6.3.0 (Claude Code)'));
768
+ console.log(chalk.dim(` Mode: ${effectiveMode}${force ? ' (force)' : ''}${dryRun ? ' (dry-run)' : ''}`));
769
+ console.log('');
770
+
771
+ // ── 1. Detect state ──────────────────────────────────────────────────
772
+ const state = detectState();
773
+
774
+ // ── 2. Bizar HOME bootstrap ──────────────────────────────────────────
775
+ section('Bizar HOME');
776
+ const bizarHomeStep = ensureBizarHome({ dryRun });
777
+ if (bizarHomeStep.ok) logOk(bizarHomeStep.message);
778
+ else logErr(bizarHomeStep.message);
779
+
780
+ // ── 3. Toolchain ────────────────────────────────────────────────────
781
+ checkToolchain();
782
+ installClaudeCli({ force, dryRun });
783
+
784
+ // ── 4-8. File sync ──────────────────────────────────────────────────
785
+ const stepResults = [];
786
+ const runStep = async (label, fn) => {
787
+ section(label);
788
+ const r = await fn();
789
+ const marker = r.ok ? '✓' : '✗';
790
+ console.log(` ${marker} ${r.message}`);
791
+ stepResults.push({ label, ...r });
792
+ return r;
793
+ };
794
+
795
+ await runStep('Syncing agents', () => syncAgentFiles({ dryRun, force }));
796
+ await runStep('Syncing skills', () => syncSkillFiles({ dryRun, force }));
797
+ await runStep('Syncing commands', () => syncCommandFiles({ dryRun, force }));
798
+ await runStep('Syncing rules', () => syncRulesFiles({ dryRun, force }));
799
+ await runStep('Syncing hooks', () => syncHookFiles({ dryRun, force }));
800
+
801
+ // ── 9. settings.json (MCP + hooks + permissions + env) ──────────────
802
+ section('Writing settings.json');
803
+ const settingsStep = writeClaudeSettings({ dryRun, force });
804
+ if (settingsStep.ok) logOk(settingsStep.message); else logErr(settingsStep.message);
805
+ stepResults.push({ label: 'settings.json', ...settingsStep });
806
+
807
+ // ── 10. MCP server registration ──────────────────────────────────────
808
+ section('MCP server (bizar)');
809
+ const mcpStep = setupMcpServer({ dryRun });
810
+ if (mcpStep.ok) logOk(mcpStep.message); else logWarn(mcpStep.message);
811
+ stepResults.push({ label: 'mcp-server', ...mcpStep });
812
+
813
+ // ── 11. Plugin install (optional) ───────────────────────────────────
814
+ section('Claude plugin');
815
+ const pluginStep = setupClaudePlugin({ dryRun });
816
+ if (pluginStep.ok) logOk(pluginStep.message); else logWarn(pluginStep.message);
817
+ stepResults.push({ label: 'claude-plugin', ...pluginStep });
818
+
819
+ // ── 12. CLAUDE.md mirror ────────────────────────────────────────────
820
+ section('CLAUDE.md mirror');
821
+ const claudeMdStep = writeClaudeMdMirror({ dryRun, force });
822
+ if (claudeMdStep.ok) logOk(claudeMdStep.message); else logWarn(claudeMdStep.message);
823
+ stepResults.push({ label: 'claude-md', ...claudeMdStep });
824
+
825
+ // ── 13. Dashboard service ───────────────────────────────────────────
826
+ if (start && effectiveMode === 'install') {
827
+ section('Dashboard service');
828
+ const svcStep = installDashboardService({ dryRun });
829
+ if (svcStep.ok) logOk(svcStep.message); else logWarn(svcStep.message);
830
+ stepResults.push({ label: 'service', ...svcStep });
831
+ }
832
+
833
+ // ── 14. Summary ─────────────────────────────────────────────────────
834
+ section('Provision complete');
835
+ const finalState = detectState();
836
+ console.log(chalk.dim(JSON.stringify(finalState, null, 2)));
837
+ console.log('');
838
+ const anyFail = stepResults.some((r) => !r.ok);
839
+ if (anyFail) {
840
+ console.log(chalk.yellow(' ⚠ Some steps had issues. See messages above.'));
841
+ } else {
842
+ console.log(chalk.bold.green(' ✓ Bizar is ready.'));
843
+ console.log(chalk.dim(' Next: restart your Claude Code session to pick up the new config.'));
844
+ }
845
+ console.log('');
846
+ return { ok: !anyFail, mode: effectiveMode, state: finalState, stepResults };
847
+ }
848
+
849
+ // ─── CLI entry ───────────────────────────────────────────────────────────────
850
+
851
+ function parseFlags(argv) {
852
+ const opts = {
853
+ mode: 'install',
854
+ dryRun: false,
855
+ force: false,
856
+ yes: false,
857
+ start: true,
858
+ update: false,
859
+ };
860
+ for (let i = 0; i < argv.length; i++) {
861
+ const a = argv[i];
862
+ if (a === '--mode=install') opts.mode = 'install';
863
+ else if (a === '--mode=update') opts.mode = 'update';
864
+ else if (a === '--mode=install-only-system') opts.mode = 'install-only-system';
865
+ else if (a === '--mode') {
866
+ const v = argv[++i];
867
+ if (v === 'install' || v === 'update' || v === 'install-only-system') opts.mode = v;
868
+ }
869
+ else if (a === '--dry-run') opts.dryRun = true;
870
+ else if (a === '--force') opts.force = true;
871
+ else if (a === '--yes' || a === '-y') opts.yes = true;
872
+ else if (a === '--non-interactive') opts.yes = true;
873
+ else if (a === '--no-service') opts.start = false;
874
+ else if (a === '--update') opts.mode = 'update';
875
+ else if (a === '--help' || a === '-h') {
876
+ console.log(
877
+ 'Usage: node cli/provision-claude.mjs [--mode=install|update|install-only-system]\n' +
878
+ ' [--dry-run] [--force] [--yes] [--non-interactive] [--no-service]',
879
+ );
880
+ process.exit(0);
881
+ }
882
+ }
883
+ if (opts.mode === 'update') opts.update = true;
884
+ return opts;
885
+ }
886
+
887
+ if (import.meta.url === `file://${process.argv[1]}`) {
888
+ const opts = parseFlags(process.argv.slice(2));
889
+ runProvision(opts).catch((err) => {
890
+ logErr(`provision failed: ${err?.message || err}`);
891
+ process.exit(1);
892
+ });
893
+ }