@polderlabs/bizar 6.2.5 → 7.0.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 (540) 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/hooks/worker-suggest.mjs +110 -0
  41. package/.claude/settings.json +125 -0
  42. package/.claude/skills/9router/SKILL.md +80 -0
  43. package/.claude/skills/9router-chat/SKILL.md +73 -0
  44. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  45. package/.claude/skills/9router-image/SKILL.md +86 -0
  46. package/.claude/skills/9router-stt/SKILL.md +79 -0
  47. package/.claude/skills/9router-tts/SKILL.md +80 -0
  48. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  49. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  50. package/.claude/skills/bizar/README.md +9 -0
  51. package/.claude/skills/bizar/SKILL.md +450 -0
  52. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  53. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  54. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  55. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  56. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  57. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  58. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  59. package/.claude/skills/cpp-testing/README.md +28 -0
  60. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  61. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  62. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  63. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  64. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  65. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  66. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  67. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  68. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  69. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  70. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  71. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  72. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  73. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  74. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  75. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  76. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  78. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  79. package/.claude/skills/glyph/SKILL.md +163 -0
  80. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  81. package/.claude/skills/lightrag/SKILL.md +81 -0
  82. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  83. package/.claude/skills/obsidian/SKILL.md +306 -0
  84. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  85. package/.claude/skills/self-improvement/SKILL.md +64 -0
  86. package/README.md +87 -59
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-CW6fWKds.js} +3 -3
  88. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
  89. package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
  90. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
  91. package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
  92. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
  93. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  94. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  95. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  96. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  97. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  98. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  99. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  100. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  101. package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
  102. package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
  103. package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
  104. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  105. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-w2bDDkAH.js.map} +1 -1
  106. package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
  107. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DXYCA0hC.js.map} +1 -1
  108. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
  109. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
  110. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  111. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  112. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  113. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
  114. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
  115. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  116. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  117. package/bizar-dash/dist/index.html +12 -10
  118. package/bizar-dash/dist/mobile.html +7 -4
  119. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  120. package/bizar-dash/src/server/api.mjs +15 -4
  121. package/bizar-dash/src/server/background-store.mjs +15 -16
  122. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  123. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  124. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  125. package/bizar-dash/src/server/claude-info.mjs +412 -0
  126. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  127. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  128. package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
  129. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  130. package/bizar-dash/src/server/providers-store.mjs +22 -49
  131. package/bizar-dash/src/server/routes/background.mjs +24 -20
  132. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  133. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  134. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  135. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  136. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  137. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  138. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  139. package/bizar-dash/src/web/App.tsx +4 -0
  140. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  141. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  142. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  143. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  144. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  145. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  146. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  147. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  148. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  149. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  150. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  151. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  152. package/bizar-dash/src/web/lib/types.ts +25 -0
  153. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  154. package/bizar-dash/src/web/styles/main.css +251 -0
  155. package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
  156. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  157. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  158. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  159. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  160. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  161. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  162. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  163. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  164. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  165. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  166. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  167. package/bizar-dash/src/web/ui/controls/controls.css +454 -0
  168. package/bizar-dash/src/web/ui/controls/index.ts +33 -0
  169. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  170. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  171. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  172. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  173. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  174. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  175. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  176. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  177. package/bizar-dash/src/web/ui/data/data.css +535 -0
  178. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  179. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  180. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  181. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  182. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  183. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  184. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  185. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  186. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  187. package/bizar-dash/src/web/ui/index.ts +56 -0
  188. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  189. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  190. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  191. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  192. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  193. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  194. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  195. package/bizar-dash/src/web/ui/layout/index.ts +33 -0
  196. package/bizar-dash/src/web/ui/layout/layout.css +413 -0
  197. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  198. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  199. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  200. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  201. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  202. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  203. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  204. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  205. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  206. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  207. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  208. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  209. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  210. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  211. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  212. package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
  213. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  214. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  215. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  216. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  217. package/bizar-dash/src/web/views/Agents.tsx +506 -266
  218. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  219. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  220. package/bizar-dash/src/web/views/Overview.tsx +996 -448
  221. package/bizar-dash/src/web/views/Tasks.tsx +277 -273
  222. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  223. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  224. package/bizar-dash/tests/event-log.test.tsx +61 -0
  225. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  226. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  227. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  228. package/bizar-dash/tests/setup.ts +31 -1
  229. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  230. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  231. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  232. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  233. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  234. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  235. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  236. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  237. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  238. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  239. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  240. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  241. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  242. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  243. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  244. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  245. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  246. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  247. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  248. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  249. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  250. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  251. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  252. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  253. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  254. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  255. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  256. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  257. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  258. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  259. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  260. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  261. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  262. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  263. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  264. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  265. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  266. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  267. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  268. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  269. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  270. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  271. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  272. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  273. package/bizar-dash/vitest.config.ts +3 -1
  274. package/cli/__tests__/cost-gate.test.mjs +301 -0
  275. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  276. package/cli/bin.mjs +55 -18
  277. package/cli/commands/claim.mjs +282 -0
  278. package/cli/commands/claude-cmd.mjs +348 -0
  279. package/cli/commands/cost.mjs +279 -0
  280. package/cli/commands/install.mjs +12 -11
  281. package/cli/commands/validate.mjs +252 -325
  282. package/cli/cost-gate.mjs +501 -0
  283. package/cli/dev-link.test.mjs +11 -11
  284. package/cli/doctor.mjs +131 -213
  285. package/cli/feature-list-bridge.mjs +400 -0
  286. package/cli/install.mjs +33 -37
  287. package/cli/install.test.mjs +9 -9
  288. package/cli/memory-constants.mjs +17 -0
  289. package/cli/provision-claude.mjs +893 -0
  290. package/cli/provision.mjs +224 -227
  291. package/cli/utils.mjs +72 -58
  292. package/cli/worker-dispatcher.mjs +259 -0
  293. package/cli/worker-dispatcher.test.mjs +204 -0
  294. package/config/AGENTS.md +28 -28
  295. package/config/trigger-patterns.json +186 -0
  296. package/install.sh +66 -44
  297. package/package.json +19 -11
  298. package/packages/sdk/ARCHITECTURE.md +40 -36
  299. package/packages/sdk/package.json +15 -5
  300. package/packages/sdk/src/agent-registry.ts +304 -0
  301. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  302. package/packages/sdk/src/consensus/index.ts +147 -0
  303. package/packages/sdk/src/consensus/queen.ts +173 -0
  304. package/packages/sdk/src/consensus/types.ts +199 -0
  305. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  306. package/packages/sdk/src/federation/audit.ts +156 -0
  307. package/packages/sdk/src/federation/budget.ts +269 -0
  308. package/packages/sdk/src/federation/envelope.ts +144 -0
  309. package/packages/sdk/src/federation/hmac.ts +160 -0
  310. package/packages/sdk/src/federation/index.ts +443 -0
  311. package/packages/sdk/src/federation/pii.ts +241 -0
  312. package/packages/sdk/src/federation/policy.ts +112 -0
  313. package/packages/sdk/src/federation/trust.ts +146 -0
  314. package/packages/sdk/src/fingerprint.ts +0 -0
  315. package/packages/sdk/src/index.ts +101 -69
  316. package/packages/sdk/src/mcp/bin.ts +51 -0
  317. package/packages/sdk/src/mcp/server.ts +848 -0
  318. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  319. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  320. package/packages/sdk/src/router/index.ts +247 -0
  321. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  322. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  323. package/packages/sdk/src/router/model-router.ts +235 -0
  324. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  325. package/packages/sdk/src/swarm-topology.ts +279 -0
  326. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  327. package/packages/sdk/tests/consensus.test.ts +376 -0
  328. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  329. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  330. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  331. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  332. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  333. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  334. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  335. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  336. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  337. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  338. package/packages/sdk/tests/model-router.test.mjs +108 -0
  339. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  340. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  341. package/packages/sdk/tests/router.test.mjs +91 -0
  342. package/packages/sdk/tests/sdk.test.mjs +172 -0
  343. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  344. package/packages/sdk/vitest.config.ts +1 -1
  345. package/scripts/bh-full-e2e.mjs +167 -368
  346. package/scripts/check-deps.mjs +28 -46
  347. package/scripts/mirror-claude-md.sh +78 -0
  348. package/scripts/test-in-container.sh +31 -9
  349. package/templates/clean-state-checklist.md +3 -3
  350. package/templates/sprint-contract.md +2 -2
  351. package/bizar-dash/.bizar/activity.log +0 -3
  352. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  353. package/bizar-dash/.omx/state/session.json +0 -10
  354. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  355. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  356. package/bizar-dash/bizar-design/canvas.html +0 -281
  357. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  358. package/bizar-dash/bizar-design/components.css +0 -1665
  359. package/bizar-dash/bizar-design/components.html +0 -557
  360. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  361. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  362. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  363. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  364. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  365. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  366. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  367. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  368. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  369. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  370. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  371. package/bizar-dash/bizar-design/icons.svg +0 -44
  372. package/bizar-dash/bizar-design/index.html +0 -192
  373. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  374. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  375. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  376. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  377. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  378. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  379. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  380. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  381. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  382. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  383. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  384. package/bizar-dash/bizar-design/prototype.js +0 -114
  385. package/bizar-dash/bizar-design/tokens.css +0 -106
  386. package/bizar-dash/dist/assets/MobileChat-BnKN_Ks_.js +0 -1
  387. package/bizar-dash/dist/assets/MobileSettings-DjCPxC-Q.js +0 -1
  388. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  389. package/bizar-dash/dist/assets/main-DYiZqMrn.js +0 -18
  390. package/bizar-dash/dist/assets/main-DYiZqMrn.js.map +0 -1
  391. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  392. package/bizar-dash/dist/assets/markdown-C6mXtQxD.js +0 -1
  393. package/bizar-dash/dist/assets/mobile-CMHqtLV2.js +0 -1
  394. package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js +0 -2
  395. package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js.map +0 -1
  396. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  397. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  398. package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js +0 -2
  399. package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js.map +0 -1
  400. package/bizar-dash/dist/assets/vendor-CeHGtduv.js +0 -29
  401. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  402. package/bizar-dash/node_modules/.package-lock.json +0 -6
  403. package/bizar-dash/package-lock.json +0 -6
  404. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  405. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  406. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  407. package/cli/commands/cline-cmd.mjs +0 -289
  408. package/cli/commands/validate.test.mjs +0 -379
  409. package/cli/doctor.test.mjs +0 -350
  410. package/config/cline.json.template +0 -342
  411. package/packages/sdk/src/client.ts +0 -188
  412. package/packages/sdk/src/cline-events.ts +0 -134
  413. package/packages/sdk/src/cline-types.ts +0 -66
  414. package/packages/sdk/src/cline.ts +0 -339
  415. package/packages/sdk/src/errors.ts +0 -129
  416. package/packages/sdk/src/events.ts +0 -153
  417. package/packages/sdk/src/types.ts +0 -176
  418. package/packages/sdk/tests/client.test.ts +0 -217
  419. package/packages/sdk/tests/errors.test.ts +0 -108
  420. package/packages/sdk/tests/events.test.ts +0 -139
  421. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  422. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  423. package/plugins/bizar/ARCHITECTURE.md +0 -142
  424. package/plugins/bizar/CONSTRAINTS.md +0 -67
  425. package/plugins/bizar/LICENSE +0 -21
  426. package/plugins/bizar/README.md +0 -448
  427. package/plugins/bizar/index.ts +0 -925
  428. package/plugins/bizar/package.json +0 -39
  429. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  430. package/plugins/bizar/src/background-state.ts +0 -641
  431. package/plugins/bizar/src/background.ts +0 -1806
  432. package/plugins/bizar/src/cline-runner.ts +0 -203
  433. package/plugins/bizar/src/clineruntime.ts +0 -227
  434. package/plugins/bizar/src/commands-impl.ts +0 -151
  435. package/plugins/bizar/src/commands.ts +0 -1799
  436. package/plugins/bizar/src/compaction.d.mts +0 -48
  437. package/plugins/bizar/src/compaction.mjs +0 -192
  438. package/plugins/bizar/src/dashboard-client.ts +0 -233
  439. package/plugins/bizar/src/event-stream.ts +0 -606
  440. package/plugins/bizar/src/fingerprint.ts +0 -120
  441. package/plugins/bizar/src/handoff.ts +0 -79
  442. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  443. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  444. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  445. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  446. package/plugins/bizar/src/http-client.ts +0 -467
  447. package/plugins/bizar/src/key-rotation.ts +0 -218
  448. package/plugins/bizar/src/logger.ts +0 -144
  449. package/plugins/bizar/src/loop-engineering.ts +0 -241
  450. package/plugins/bizar/src/loop.ts +0 -176
  451. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  452. package/plugins/bizar/src/odin.ts +0 -227
  453. package/plugins/bizar/src/options.ts +0 -470
  454. package/plugins/bizar/src/plan-fs.ts +0 -323
  455. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  456. package/plugins/bizar/src/report.ts +0 -178
  457. package/plugins/bizar/src/research-prompt.ts +0 -35
  458. package/plugins/bizar/src/serve-info.ts +0 -228
  459. package/plugins/bizar/src/serve.ts +0 -496
  460. package/plugins/bizar/src/settings.ts +0 -349
  461. package/plugins/bizar/src/state.ts +0 -298
  462. package/plugins/bizar/src/tool-discipline.ts +0 -105
  463. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  464. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  465. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  466. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  467. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  468. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  469. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  470. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  471. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  472. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  473. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  474. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  475. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  476. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  477. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  478. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  479. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  480. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  481. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  482. package/plugins/bizar/src/tools/sandbox.ts +0 -232
  483. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  484. package/plugins/bizar/src/tools/team-status.ts +0 -76
  485. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  486. package/plugins/bizar/src/trajectory.ts +0 -104
  487. package/plugins/bizar/tests/README.md +0 -99
  488. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  489. package/plugins/bizar/tests/background-state.test.ts +0 -277
  490. package/plugins/bizar/tests/background.test.ts +0 -402
  491. package/plugins/bizar/tests/block.test.ts +0 -195
  492. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  493. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  494. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  495. package/plugins/bizar/tests/commands.test.ts +0 -584
  496. package/plugins/bizar/tests/compaction.test.ts +0 -264
  497. package/plugins/bizar/tests/config.test.ts +0 -128
  498. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  499. package/plugins/bizar/tests/dispose.test.ts +0 -336
  500. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  501. package/plugins/bizar/tests/event.test.ts +0 -262
  502. package/plugins/bizar/tests/fingerprint.test.ts +0 -190
  503. package/plugins/bizar/tests/http-client.test.ts +0 -404
  504. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  505. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  506. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  507. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  508. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  509. package/plugins/bizar/tests/loop.test.ts +0 -397
  510. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  511. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  512. package/plugins/bizar/tests/odin.test.ts +0 -125
  513. package/plugins/bizar/tests/options.test.ts +0 -329
  514. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  515. package/plugins/bizar/tests/safety.test.ts +0 -281
  516. package/plugins/bizar/tests/serve.test.ts +0 -339
  517. package/plugins/bizar/tests/settings.test.ts +0 -351
  518. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  519. package/plugins/bizar/tests/state.test.ts +0 -276
  520. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  521. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  522. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  523. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  524. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  525. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  526. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  527. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  528. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  529. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  530. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  531. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  532. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  533. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  534. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  535. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  536. package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
  537. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  538. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  539. package/plugins/bizar/tsconfig.json +0 -29
  540. package/scripts/mirror-agents-md.sh +0 -69
@@ -0,0 +1,301 @@
1
+ /**
2
+ * cli/__tests__/cost-gate.test.mjs
3
+ *
4
+ * F-035 — tests for cli/cost-gate.mjs (atomic SQLite-backed room budget tracker).
5
+ *
6
+ * Uses Node's built-in `node:test`. No external framework.
7
+ *
8
+ * Coverage:
9
+ * 1. registerRoom — idempotent, accepts valid cap, rejects invalid.
10
+ * 2. status — reflects spentUsd / remaining correctly.
11
+ * 3. listRooms — returns every room with up-to-date totals.
12
+ * 4. reserve → commit: happy path, final remaining matches.
13
+ * 5. reserve → release: spentUsd stays 0, remaining recovers.
14
+ * 6. BUDGET_EXCEEDED: refusal when projected > cap.
15
+ * 7. Reservation expiry: state flips to 'expired' on sweepExpired.
16
+ * 8. Late commit: COMMIT_AFTER_EXPIRY warning + 'committed_post_expiry' state.
17
+ * 9. Concurrent reserves: one succeeds, the other BUDGET_EXCEEDEDs (BEGIN
18
+ * IMMEDIATE atomicity).
19
+ * 10. ALREADY_FINALIZED on double commit / double release.
20
+ * 11. ROOM_NOT_FOUND path.
21
+ * 12. Expiry clamping [5s, 300s] per ADR-164.1 §3.2.
22
+ */
23
+
24
+ import { test, describe, before, afterEach, beforeEach } from 'node:test';
25
+ import assert from 'node:assert/strict';
26
+
27
+ import { mkdtempSync, rmSync } from 'node:fs';
28
+ import { tmpdir } from 'node:os';
29
+ import { join } from 'node:path';
30
+
31
+ const {
32
+ CostGate,
33
+ RESERVATION_EXPIRY_FLOOR_MS,
34
+ RESERVATION_EXPIRY_CEILING_MS,
35
+ clampReservationExpiry,
36
+ } = await import('../cost-gate.mjs');
37
+
38
+ let tmpdir_created = false;
39
+ let tmp;
40
+
41
+ before(() => {
42
+ if (!tmpdir_created) {
43
+ tmp = mkdtempSync(join(tmpdir(), 'bizar-cost-gate-'));
44
+ tmpdir_created = true;
45
+ }
46
+ });
47
+
48
+ afterEach(() => {
49
+ // each test creates its own gate; nothing to clean up
50
+ });
51
+
52
+ function freshGate(clock) {
53
+ const dbPath = join(tmp, `gate-${Math.random().toString(36).slice(2, 8)}-${Date.now()}.db`);
54
+ return new CostGate({
55
+ dbPath,
56
+ clock: clock || (() => Date.now()),
57
+ });
58
+ }
59
+
60
+ describe('registerRoom()', () => {
61
+ let gate;
62
+ beforeEach(() => { gate = freshGate(); });
63
+ afterEach(() => gate.close());
64
+
65
+ test('inserts a new room', () => {
66
+ const r = gate.registerRoom('titan-cluster', 50);
67
+ assert.equal(r.ok, true);
68
+ assert.equal(r.roomId, 'titan-cluster');
69
+ assert.equal(r.capUsd, 50);
70
+ });
71
+
72
+ test('upsert on conflict — idempotent', () => {
73
+ gate.registerRoom('titan-cluster', 50);
74
+ gate.registerRoom('titan-cluster', 75); // raises cap
75
+ const s = gate.status('titan-cluster');
76
+ assert.equal(s.capUsd, 75);
77
+ });
78
+
79
+ test('rejects invalid roomId', () => {
80
+ assert.throws(() => gate.registerRoom('', 10), /roomId/);
81
+ assert.throws(() => gate.registerRoom(null, 10), /roomId/);
82
+ });
83
+
84
+ test('rejects negative cap', () => {
85
+ assert.throws(() => gate.registerRoom('r', -1), /capUsd/);
86
+ assert.throws(() => gate.registerRoom('r', NaN), /capUsd/);
87
+ });
88
+ });
89
+
90
+ describe('status() and listRooms()', () => {
91
+ let gate;
92
+ beforeEach(() => { gate = freshGate(); });
93
+ afterEach(() => gate.close());
94
+
95
+ test('status returns null for unknown room', () => {
96
+ assert.equal(gate.status('nope'), null);
97
+ });
98
+
99
+ test('status reflects spentUsd and remaining correctly', () => {
100
+ gate.registerRoom('alpha', 100);
101
+ gate.reserve('alpha', 'tester', 30);
102
+ // Capture txId deterministically for the commit
103
+ const rsv = gate.reserve('alpha', 'tester', 10);
104
+ gate.commit(rsv.txId, 10);
105
+ const s = gate.status('alpha');
106
+ assert.equal(s.spentUsd, 10, 'only the committed 10 is spent');
107
+ assert.equal(s.reservedUsd, 30, 'the 30 reservation is still live');
108
+ assert.equal(s.remainingUsd, 100 - 10 - 30);
109
+ assert.ok(s.txCount >= 2);
110
+ });
111
+
112
+ test('listRooms returns every registered room', () => {
113
+ gate.registerRoom('a', 1);
114
+ gate.registerRoom('b', 2);
115
+ gate.registerRoom('c', 3);
116
+ const all = gate.listRooms();
117
+ const ids = all.map((r) => r.roomId).sort();
118
+ assert.deepEqual(ids, ['a', 'b', 'c']);
119
+ });
120
+ });
121
+
122
+ describe('reserve → commit happy path', () => {
123
+ let gate;
124
+ beforeEach(() => { gate = freshGate(); });
125
+ afterEach(() => gate.close());
126
+
127
+ test('reserve then commit with smaller amount', () => {
128
+ gate.registerRoom('r', 100);
129
+ const rsv = gate.reserve('r', 'agent1', 40);
130
+ assert.equal(rsv.ok, true);
131
+ assert.ok(rsv.txId.startsWith('tx-'));
132
+ const cm = gate.commit(rsv.txId, 25);
133
+ assert.equal(cm.ok, true);
134
+ assert.equal(cm.committed, true);
135
+ const s = gate.status('r');
136
+ assert.equal(s.spentUsd, 25);
137
+ assert.equal(s.remainingUsd, 75);
138
+ });
139
+
140
+ test('reserve then commit with same amount', () => {
141
+ gate.registerRoom('r', 100);
142
+ const rsv = gate.reserve('r', 'agent1', 40);
143
+ const cm = gate.commit(rsv.txId, 40);
144
+ assert.equal(cm.committed, true);
145
+ assert.equal(gate.status('r').spentUsd, 40);
146
+ });
147
+ });
148
+
149
+ describe('reserve → release', () => {
150
+ let gate;
151
+ beforeEach(() => { gate = freshGate(); });
152
+ afterEach(() => gate.close());
153
+
154
+ test('release frees the budget without charging it', () => {
155
+ gate.registerRoom('r', 100);
156
+ const rsv = gate.reserve('r', 'a', 30);
157
+ const rel = gate.release(rsv.txId);
158
+ assert.equal(rel.ok, true);
159
+ assert.equal(rel.released, true);
160
+ const s = gate.status('r');
161
+ assert.equal(s.spentUsd, 0);
162
+ assert.equal(s.reservedUsd, 0);
163
+ assert.equal(s.remainingUsd, 100);
164
+ });
165
+
166
+ test('release on already-released tx returns ALREADY_FINALIZED', () => {
167
+ gate.registerRoom('r', 100);
168
+ const rsv = gate.reserve('r', 'a', 10);
169
+ gate.release(rsv.txId);
170
+ const rel = gate.release(rsv.txId);
171
+ assert.equal(rel.ok, false);
172
+ assert.equal(rel.error, 'ALREADY_FINALIZED');
173
+ });
174
+ });
175
+
176
+ describe('BUDGET_EXCEEDED refusal', () => {
177
+ let gate;
178
+ beforeEach(() => { gate = freshGate(); });
179
+ afterEach(() => gate.close());
180
+
181
+ test('refuses reserve that would exceed cap', () => {
182
+ gate.registerRoom('r', 100);
183
+ const r1 = gate.reserve('r', 'a', 80);
184
+ assert.equal(r1.ok, true);
185
+ const r2 = gate.reserve('r', 'a', 30);
186
+ assert.equal(r2.ok, false);
187
+ assert.equal(r2.error, 'BUDGET_EXCEEDED');
188
+ assert.equal(r2.capUsd, 100);
189
+ assert.equal(r2.committed, 0);
190
+ assert.equal(r2.reserved, 80);
191
+ });
192
+
193
+ test('committing an exact-fit reservation is allowed (with epsilon)', () => {
194
+ gate.registerRoom('r', 100);
195
+ const rsv = gate.reserve('r', 'a', 100);
196
+ assert.equal(rsv.ok, true);
197
+ assert.equal(rsv.remainingAfterReserve, 0);
198
+ });
199
+ });
200
+
201
+ describe('expiry + late commit', () => {
202
+ let gate;
203
+ let now;
204
+ beforeEach(() => {
205
+ now = 1_700_000_000_000;
206
+ gate = freshGate(() => now);
207
+ });
208
+ afterEach(() => gate.close());
209
+
210
+ test('sweepExpired flips reserved rows to expired', () => {
211
+ gate.registerRoom('r', 100);
212
+ const rsv = gate.reserve('r', 'a', 10, { expiryMs: 5_000 });
213
+ assert.equal(rsv.ok, true);
214
+ now += 6_000; // past expiry
215
+ const swept = gate.sweepExpired();
216
+ assert.equal(swept, 1);
217
+ const tx = gate.listTransactions('r').find((t) => t.tx_id === rsv.txId);
218
+ assert.equal(tx.kind, 'expired');
219
+ });
220
+
221
+ test('commit after expiry returns COMMIT_AFTER_EXPIRY warning + spends the actual amount', () => {
222
+ gate.registerRoom('r', 100);
223
+ const rsv = gate.reserve('r', 'a', 10, { expiryMs: 5_000 });
224
+ now += 6_000; // past expiry
225
+ const cm = gate.commit(rsv.txId, 7.5);
226
+ assert.equal(cm.ok, true);
227
+ assert.equal(cm.warned, 'COMMIT_AFTER_EXPIRY');
228
+ const tx = gate.listTransactions('r').find((t) => t.tx_id === rsv.txId);
229
+ assert.equal(tx.kind, 'committed_post_expiry');
230
+ const s = gate.status('r');
231
+ assert.equal(s.spentUsd, 7.5);
232
+ assert.equal(s.remainingUsd, 92.5);
233
+ });
234
+
235
+ test('commit twice returns ALREADY_FINALIZED', () => {
236
+ gate.registerRoom('r', 100);
237
+ const rsv = gate.reserve('r', 'a', 10);
238
+ gate.commit(rsv.txId, 8);
239
+ const cm2 = gate.commit(rsv.txId, 8);
240
+ assert.equal(cm2.ok, false);
241
+ assert.equal(cm2.error, 'ALREADY_FINALIZED');
242
+ });
243
+ });
244
+
245
+ describe('CONCURRENT reserve race (BEGIN IMMEDIATE atomicity)', () => {
246
+ test('only one of N concurrent reserves can exceed the cap when strictly under', () => {
247
+ const gate = freshGate();
248
+ try {
249
+ gate.registerRoom('cr', 10);
250
+ // Fire 10 reserves of $1.50 against a $10 cap. The first 6
251
+ // succeed ($9 projected); the 7th hits BUDGET_EXCEEDED. We use
252
+ // a tight loop so they pile up against the immediate write lock.
253
+ const results = [];
254
+ for (let i = 0; i < 20; i++) {
255
+ const r = gate.reserve('cr', `caller-${i}`, 1.5);
256
+ results.push(r);
257
+ }
258
+ const ok = results.filter((r) => r.ok).length;
259
+ const denied = results.filter((r) => !r.ok).length;
260
+ assert.equal(ok + denied, 20);
261
+ assert.equal(ok, 6, `expected 6 reserves to fit (6 × $1.50 = $9 ≤ $10), got ${ok}`);
262
+ assert.equal(denied, 14);
263
+ assert.ok(
264
+ results.some((r) => !r.ok && r.error === 'BUDGET_EXCEEDED'),
265
+ 'at least one must hit BUDGET_EXCEEDED',
266
+ );
267
+ const s = gate.status('cr');
268
+ assert.equal(s.reservedUsd, 9);
269
+ assert.equal(s.spentUsd, 0);
270
+ } finally {
271
+ gate.close();
272
+ }
273
+ });
274
+ });
275
+
276
+ describe('ROOM_NOT_FOUND', () => {
277
+ let gate;
278
+ beforeEach(() => { gate = freshGate(); });
279
+ afterEach(() => gate.close());
280
+
281
+ test('reserve returns ROOM_NOT_FOUND', () => {
282
+ const r = gate.reserve('nope', 'a', 10);
283
+ assert.equal(r.ok, false);
284
+ assert.equal(r.error, 'ROOM_NOT_FOUND');
285
+ });
286
+ });
287
+
288
+ describe('clampReservationExpiry — ADR-164.1 §3.2', () => {
289
+ test('floors at 5_000 ms', () => {
290
+ assert.equal(clampReservationExpiry(100), RESERVATION_EXPIRY_FLOOR_MS);
291
+ });
292
+ test('caps at 300_000 ms', () => {
293
+ assert.equal(clampReservationExpiry(60 * 60 * 1000), RESERVATION_EXPIRY_CEILING_MS);
294
+ });
295
+ test('passes through valid values', () => {
296
+ assert.equal(clampReservationExpiry(30_000), 30_000);
297
+ });
298
+ test('uses 60_000 ms default for undefined', () => {
299
+ assert.equal(clampReservationExpiry(undefined), 60_000);
300
+ });
301
+ });
@@ -0,0 +1,371 @@
1
+ /**
2
+ * cli/__tests__/feature-list-bridge.test.mjs
3
+ *
4
+ * F-035 — tests for cli/feature-list-bridge.mjs (claim protocol over
5
+ * feature_list.json).
6
+ *
7
+ * Uses Node's built-in `node:test`. No external framework.
8
+ *
9
+ * Coverage:
10
+ * 1. claim — first claim succeeds; second claim rejected ALREADY_CLAIMED.
11
+ * 2. release — reverts to unclaimed; subsequent release errors.
12
+ * 3. handoff — moves claimant to recipient atomically.
13
+ * 4. steal — moves claimant with a stealReason recorded in history.
14
+ * 5. transition — claimStatus moves through allowed states.
15
+ * 6. list — filters by status, claimantId, type.
16
+ * 7. Bad transition — non-allowed status change errors.
17
+ * 8. unknown feature — NOT_FOUND.
18
+ * 9. Persistence: round-trips through file storage.
19
+ */
20
+
21
+ import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
22
+ import assert from 'node:assert/strict';
23
+
24
+ import { mkdtempSync, writeFileSync, readFileSync, rmSync, existsSync } from 'node:fs';
25
+ import { tmpdir } from 'node:os';
26
+ import { join } from 'node:path';
27
+
28
+ const {
29
+ FeatureListBridge,
30
+ CLAIM_STATUSES,
31
+ STEAL_REASONS,
32
+ ClaimError,
33
+ } = await import('../feature-list-bridge.mjs');
34
+
35
+ // ── Fixture loader ────────────────────────────────────────────────────
36
+
37
+ let tmp;
38
+ let bridgeFactory;
39
+
40
+ function writeFixture(dir, name, features) {
41
+ const file = join(dir, name);
42
+ const payload = { $schema: 'https://bizar.dev/schemas/feature_list.v1.json', features };
43
+ writeFileSync(file, JSON.stringify(payload, null, 2));
44
+ return file;
45
+ }
46
+
47
+ const minimalFeature = (id, extra = {}) => ({
48
+ id,
49
+ behavior: `Behavior for ${id}`,
50
+ verification: 'make check',
51
+ state: 'not_started',
52
+ layer: 'L1',
53
+ category: 'core',
54
+ created: '2026-07-11',
55
+ ...extra,
56
+ });
57
+
58
+ before(() => {
59
+ tmp = mkdtempSync(join(tmpdir(), 'bizar-bridge-'));
60
+ bridgeFactory = (filePath) => new FeatureListBridge({
61
+ filePath,
62
+ backup: false, // speed up tests
63
+ });
64
+ });
65
+
66
+ after(() => {
67
+ if (tmp && existsSync(tmp)) rmSync(tmp, { recursive: true, force: true });
68
+ });
69
+
70
+ // ── claim ─────────────────────────────────────────────────────────────
71
+
72
+ describe('claim()', () => {
73
+ let file;
74
+ let br;
75
+ beforeEach(() => {
76
+ file = writeFixture(tmp, `claim-${Math.random().toString(36).slice(2, 8)}.json`, [
77
+ minimalFeature('F-A'),
78
+ minimalFeature('F-B', { state: 'passing' }),
79
+ ]);
80
+ br = bridgeFactory(file);
81
+ });
82
+ afterEach(() => { /* tmp cleaned in after() */ });
83
+
84
+ test('first claim succeeds', () => {
85
+ const result = br.claim('F-A', { type: 'human', id: 'odin', name: 'Odin' }, 'first-run');
86
+ assert.equal(result.ok, true);
87
+ const f = result.feature;
88
+ assert.equal(f.claimant.id, 'odin');
89
+ assert.equal(f.claimStatus, 'claimed');
90
+ assert.ok(f.claimedAt);
91
+ assert.equal(f.claimReason, 'first-run');
92
+ assert.equal(f.claimHistory.length, 1);
93
+ assert.equal(f.claimHistory[0].event, 'claim');
94
+ });
95
+
96
+ test('second claim rejected with ALREADY_CLAIMED', () => {
97
+ br.claim('F-A', { type: 'human', id: 'odin', name: 'Odin' }, 'first-run');
98
+ assert.throws(
99
+ () => br.claim('F-A', { type: 'agent', id: 'tyr' }, 'second-run'),
100
+ (err) => err instanceof ClaimError && err.code === 'ALREADY_CLAIMED',
101
+ );
102
+ });
103
+
104
+ test('claim against unknown feature rejected with NOT_FOUND', () => {
105
+ assert.throws(
106
+ () => br.claim('F-NONE', { type: 'agent', id: 'x' }, ''),
107
+ (err) => err instanceof ClaimError && err.code === 'NOT_FOUND',
108
+ );
109
+ });
110
+
111
+ test('claim rejects malformed claimant', () => {
112
+ assert.throws(
113
+ () => br.claim('F-A', { type: 'robot', id: 'x' }, ''),
114
+ (err) => err instanceof ClaimError && err.code === 'BAD_CLAIMANT',
115
+ );
116
+ });
117
+
118
+ test('can re-claim a completed feature', () => {
119
+ br.claim('F-A', { type: 'human', id: 'odin' }, '');
120
+ br.transition('F-A', 'completed', { id: 'odin' }, '');
121
+ // released -> re-claimable
122
+ const r2 = br.claim('F-A', { type: 'agent', id: 'tyr' }, 'second life');
123
+ assert.equal(r2.ok, true);
124
+ });
125
+ });
126
+
127
+ // ── release ───────────────────────────────────────────────────────────
128
+
129
+ describe('release()', () => {
130
+ let file;
131
+ let br;
132
+ beforeEach(() => {
133
+ file = writeFixture(tmp, `rel-${Math.random().toString(36).slice(2, 8)}.json`, [minimalFeature('F-X')]);
134
+ br = bridgeFactory(file);
135
+ });
136
+
137
+ test('release after claim sets status to unclaimed', () => {
138
+ br.claim('F-X', { type: 'human', id: 'odin' }, '');
139
+ const result = br.release('F-X', { id: 'odin' }, 'done');
140
+ assert.equal(result.ok, true);
141
+ assert.equal(result.feature.claimStatus, 'unclaimed');
142
+ assert.equal(result.feature.claimant, null);
143
+ assert.equal(result.feature.claimHistory.length, 2);
144
+ });
145
+
146
+ test('release without claim errors with NOT_CLAIMED', () => {
147
+ assert.throws(
148
+ () => br.release('F-X', { id: 'odin' }, ''),
149
+ (err) => err instanceof ClaimError && err.code === 'NOT_CLAIMED',
150
+ );
151
+ });
152
+
153
+ test('release with wrong claimant errors with WRONG_CLAIMANT', () => {
154
+ br.claim('F-X', { type: 'human', id: 'odin' }, '');
155
+ assert.throws(
156
+ () => br.release('F-X', { id: 'tyr' }, ''),
157
+ (err) => err instanceof ClaimError && err.code === 'WRONG_CLAIMANT',
158
+ );
159
+ });
160
+ });
161
+
162
+ // ── handoff ───────────────────────────────────────────────────────────
163
+
164
+ describe('handoff()', () => {
165
+ let file;
166
+ let br;
167
+ beforeEach(() => {
168
+ file = writeFixture(tmp, `h-${Math.random().toString(36).slice(2, 8)}.json`, [minimalFeature('F-H')]);
169
+ br = bridgeFactory(file);
170
+ });
171
+
172
+ test('handoff moves claimant', () => {
173
+ br.claim('F-H', { type: 'human', id: 'odin' }, '');
174
+ const result = br.handoff(
175
+ 'F-H',
176
+ { type: 'human', id: 'odin' },
177
+ { type: 'agent', id: 'tyr', name: 'Tyr' },
178
+ 'context finished',
179
+ );
180
+ assert.equal(result.ok, true);
181
+ assert.equal(result.feature.claimant.id, 'tyr');
182
+ assert.equal(result.feature.claimStatus, 'claimed');
183
+ // history: claim + handoff-initiated + handoff-accepted
184
+ const events = result.feature.claimHistory.map((e) => e.event);
185
+ assert.deepEqual(events, ['claim', 'handoff-initiated', 'handoff-accepted']);
186
+ });
187
+
188
+ test('handoff with no current claim errors', () => {
189
+ assert.throws(
190
+ () => br.handoff(
191
+ 'F-H',
192
+ { type: 'human', id: 'odin' },
193
+ { type: 'agent', id: 'tyr' },
194
+ '',
195
+ ),
196
+ (err) => err instanceof ClaimError && err.code === 'NOT_CLAIMED',
197
+ );
198
+ });
199
+ });
200
+
201
+ // ── steal ─────────────────────────────────────────────────────────────
202
+
203
+ describe('steal()', () => {
204
+ let file;
205
+ let br;
206
+ beforeEach(() => {
207
+ file = writeFixture(tmp, `s-${Math.random().toString(36).slice(2, 8)}.json`, [minimalFeature('F-S')]);
208
+ br = bridgeFactory(file);
209
+ });
210
+
211
+ test('steal moves the claim', () => {
212
+ br.claim('F-S', { type: 'human', id: 'odin' }, '');
213
+ for (const reason of STEAL_REASONS) {
214
+ // Steal a fresh feature for each reason to avoid ALREADY_CLAIMED.
215
+ const f = minimalFeature(`F-S-${reason}`);
216
+ const fFile = writeFixture(tmp, `s-${reason}.json`, [f]);
217
+ const subBridge = bridgeFactory(fFile);
218
+ subBridge.claim(`F-S-${reason}`, { type: 'agent', id: 'previous' }, '');
219
+ const result = subBridge.steal(`F-S-${reason}`, { type: 'agent', id: 'tyr' }, reason);
220
+ assert.equal(result.ok, true);
221
+ assert.equal(result.previousClaimant.id, 'previous');
222
+ assert.equal(result.feature.claimant.id, 'tyr');
223
+ assert.equal(result.stealReason, reason);
224
+ assert.ok(
225
+ result.feature.claimHistory.some((e) => e.event === 'steal' && e.reason === reason),
226
+ );
227
+ }
228
+ });
229
+
230
+ test('steal rejects unknown reason', () => {
231
+ br.claim('F-S', { type: 'human', id: 'odin' }, '');
232
+ assert.throws(
233
+ () => br.steal('F-S', { type: 'agent', id: 'tyr' }, 'bogus-reason'),
234
+ (err) => err instanceof ClaimError && err.code === 'UNKNOWN_STEAL_REASON',
235
+ );
236
+ });
237
+ });
238
+
239
+ // ── transition ────────────────────────────────────────────────────────
240
+
241
+ describe('transition()', () => {
242
+ let file;
243
+ let br;
244
+ beforeEach(() => {
245
+ file = writeFixture(tmp, `t-${Math.random().toString(36).slice(2, 8)}.json`, [minimalFeature('F-T')]);
246
+ br = bridgeFactory(file);
247
+ });
248
+
249
+ test('claim → active → paused → active → completed', () => {
250
+ br.claim('F-T', { type: 'human', id: 'odin' }, '');
251
+ br.transition('F-T', 'active', { id: 'odin' }, '');
252
+ assert.equal(br.get('F-T').claimStatus, 'active');
253
+ br.transition('F-T', 'paused', { id: 'odin' }, 'lunch');
254
+ assert.equal(br.get('F-T').claimStatus, 'paused');
255
+ br.transition('F-T', 'active', { id: 'odin' }, '');
256
+ assert.equal(br.get('F-T').claimStatus, 'active');
257
+ br.transition('F-T', 'completed', { id: 'odin' }, '');
258
+ assert.equal(br.get('F-T').claimStatus, 'completed');
259
+ });
260
+
261
+ test('illegal transition rejected', () => {
262
+ br.claim('F-T', { type: 'human', id: 'odin' }, '');
263
+ // claimed → blocked is NOT in ALLOWED_TRANSITIONS['claimed']
264
+ assert.throws(
265
+ () => br.transition('F-T', 'blocked', { id: 'odin' }, ''),
266
+ (err) => err instanceof ClaimError && err.code === 'BAD_TRANSITION',
267
+ );
268
+ });
269
+
270
+ test('unknown status rejected', () => {
271
+ br.claim('F-T', { type: 'human', id: 'odin' }, '');
272
+ assert.throws(
273
+ () => br.transition('F-T', 'bogus', { id: 'odin' }, ''),
274
+ (err) => err instanceof ClaimError && err.code === 'UNKNOWN_STATUS',
275
+ );
276
+ });
277
+ });
278
+
279
+ // ── list ──────────────────────────────────────────────────────────────
280
+
281
+ describe('list()', () => {
282
+ let file;
283
+ let br;
284
+ beforeEach(() => {
285
+ file = writeFixture(tmp, `l-${Math.random().toString(36).slice(2, 8)}.json`, [
286
+ minimalFeature('F-1'),
287
+ minimalFeature('F-2'),
288
+ minimalFeature('F-3'),
289
+ minimalFeature('F-4'),
290
+ ]);
291
+ br = bridgeFactory(file);
292
+ });
293
+
294
+ test('claims 2, handoffs 1, steals 1 — list filters', () => {
295
+ br.claim('F-1', { type: 'human', id: 'odin' }, '');
296
+ br.claim('F-2', { type: 'agent', id: 'tyr' }, '');
297
+ br.claim('F-3', { type: 'human', id: 'odin' }, '');
298
+ br.handoff('F-3', { type: 'human', id: 'odin' }, { type: 'agent', id: 'tyr' }, '');
299
+ br.claim('F-4', { type: 'human', id: 'mimir' }, '');
300
+ br.steal('F-4', { type: 'agent', id: 'tyr' }, 'voluntary');
301
+
302
+ const all = br.list();
303
+ assert.equal(all.length, 4);
304
+ const claimed = br.list({ status: 'claimed' });
305
+ assert.equal(claimed.length, 4, 'all four are claimed at this point');
306
+ const humanClaims = br.list({ claimedByHuman: true });
307
+ // All four claimants are human-type except F-2 (initial agent) and F-3 (after handoff).
308
+ // Once F-3 is handed off it has an agent claimant. F-4 was stolen by an agent.
309
+ const humanIds = humanClaims.map((f) => f.id);
310
+ assert.ok(humanIds.includes('F-1'), 'F-1 stayed human');
311
+ assert.ok(!humanIds.includes('F-4'), 'F-4 is now an agent claim');
312
+
313
+ const byOdin = br.list({ claimantId: 'odin' });
314
+ assert.equal(byOdin.length, 1, 'only F-1 still belongs to odin');
315
+ assert.equal(byOdin[0].id, 'F-1');
316
+ });
317
+
318
+ test('list with no filters returns every feature', () => {
319
+ const all = br.list();
320
+ assert.equal(all.length, 4);
321
+ });
322
+ });
323
+
324
+ // ── Persistence ───────────────────────────────────────────────────────
325
+
326
+ describe('persistence', () => {
327
+ test('bridges are independent — each gets its own file', () => {
328
+ const file = writeFixture(tmp, `p-${Math.random().toString(36).slice(2, 6)}.json`, [minimalFeature('F-P')]);
329
+ const a = bridgeFactory(file);
330
+ a.claim('F-P', { type: 'human', id: 'odin' }, '');
331
+ const b = bridgeFactory(file);
332
+ assert.equal(b.get('F-P').claimant.id, 'odin');
333
+ assert.equal(b.get('F-P').claimStatus, 'claimed');
334
+ });
335
+
336
+ test('atomic write produces a parseable file', () => {
337
+ const file = writeFixture(tmp, `pw-${Math.random().toString(36).slice(2, 6)}.json`, [minimalFeature('F-PW')]);
338
+ const a = bridgeFactory(file);
339
+ a.claim('F-PW', { type: 'agent', id: 'first' }, '');
340
+ a.release('F-PW', { id: 'first' }, 'round-trip test');
341
+ a.claim('F-PW', { type: 'agent', id: 'second' }, 're-claim after release');
342
+ // verify file is valid JSON
343
+ const txt = readFileSync(file, 'utf8');
344
+ const data = JSON.parse(txt);
345
+ assert.ok(Array.isArray(data.features));
346
+ assert.equal(data.features.length, 1);
347
+ const f = data.features[0];
348
+ assert.equal(f.claimant.id, 'second');
349
+ assert.equal(f.claimStatus, 'claimed');
350
+ assert.ok(Array.isArray(f.claimHistory));
351
+ assert.equal(f.claimHistory.length, 3);
352
+ });
353
+ });
354
+
355
+ // ── CLAIM_STATUSES + STEAL_REASONS shape ──────────────────────────────
356
+
357
+ describe('exported constants', () => {
358
+ test('CLAIM_STATUSES matches the spec', () => {
359
+ assert.deepEqual(
360
+ [...CLAIM_STATUSES].sort(),
361
+ ['active', 'blocked', 'claimed', 'completed', 'handoff-pending', 'paused', 'unclaimed'].sort(),
362
+ );
363
+ });
364
+
365
+ test('STEAL_REASONS matches the spec', () => {
366
+ assert.deepEqual(
367
+ [...STEAL_REASONS].sort(),
368
+ ['blocked-timeout', 'overloaded', 'stale', 'voluntary'].sort(),
369
+ );
370
+ });
371
+ });