@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,587 @@
1
+ /**
2
+ * consensus/byzantine.ts — In-process PBFT-style consensus for Bizar.
3
+ *
4
+ * F-039 — thin port of ruflo's `v3/@claude-flow/swarm/src/consensus/byzantine.ts`.
5
+ * Bizar's review/decision steps need a 3-of-5 majority with view-change
6
+ * on faulty proposers, but not the full PBFT transport stack. This
7
+ * class is the in-memory consensus surface; it tracks one proposal
8
+ * at a time per (cluster, quorum) tuple, and exposes the four PBFT
9
+ * phases (`pre-prepare → prepare → commit → reply`) plus `view-change`.
10
+ *
11
+ * Design choices:
12
+ *
13
+ * • No network transport. Peers are passed in the constructor; the
14
+ * class is a single-node simulator that an outer orchestrator
15
+ * (or the MCP tool) drives by calling `onPrePrepare` / `onPrepare`
16
+ * with each peer's vote. This is exactly the surface Bizar needs
17
+ * for review-gate decisions; a real transport lives in a separate
18
+ * concern (would be `transport.ts` in ruflo — not ported).
19
+ *
20
+ * • Determinism. Quorum math is purely set-based. The only ordering
21
+ * input is the round-robin proposer election in `queen.ts`, and
22
+ * even that accepts an explicit `proposerSeed` so tests can pin
23
+ * the head.
24
+ *
25
+ * • Replay protection. `propose(payload)` is keyed on the payload's
26
+ * stable hash. Re-submitting the same payload returns the cached
27
+ * proposal id without re-running pre-prepare.
28
+ *
29
+ * • Fault detection. A "faulty" proposer is one that votes `no` in
30
+ * the prepare phase (or doesn't vote at all within the timeout).
31
+ * `viewChange(reason)` walks the round-robin schedule to the next
32
+ * peer and increments `viewNumber`.
33
+ */
34
+
35
+ import { createHash, randomUUID } from "node:crypto";
36
+
37
+ import { QueenCoordinator } from "./queen.js";
38
+ import {
39
+ DEFAULT_MAX_FAULTS,
40
+ DEFAULT_PEER_COUNT,
41
+ DEFAULT_QUORUM,
42
+ PHASE_ORDER,
43
+ type CommitResult,
44
+ type ConsensusOpts,
45
+ type ConsensusStatus,
46
+ type Decision,
47
+ type Phase,
48
+ type ProposeResult,
49
+ type Proposal,
50
+ type ProposalSnapshot,
51
+ type ProposalStatus,
52
+ type Vote,
53
+ type VoteResult,
54
+ type ViewChangeResult,
55
+ } from "./types.js";
56
+
57
+ // ─── Status derivation ─────────────────────────────────────────────────
58
+
59
+ function deriveStatus(p: Proposal, now: Date, timeoutMs: number): ProposalStatus {
60
+ if (p.phase === "reply" && p.committedAt) return "committed";
61
+ if (p.phase === "reply" && p.viewChangeReason) return "view-change";
62
+ if (p.phase === "reply" && !p.committedAt) return "rejected";
63
+ if (p.phase === "commit") return "commit";
64
+ if (p.phase === "prepare") {
65
+ if (now.getTime() - new Date(p.createdAt).getTime() > timeoutMs) return "expired";
66
+ if (p.rejections.size > 0) return "rejected";
67
+ return "prepare";
68
+ }
69
+ if (p.phase === "pre-prepare") {
70
+ if (now.getTime() - new Date(p.createdAt).getTime() > timeoutMs) return "expired";
71
+ return "pre-prepare";
72
+ }
73
+ return "pending";
74
+ }
75
+
76
+ function snapshotOf(p: Proposal, now: Date, timeoutMs: number): ProposalSnapshot {
77
+ return {
78
+ id: p.id,
79
+ status: deriveStatus(p, now, timeoutMs),
80
+ phase: p.phase,
81
+ proposer: p.proposer,
82
+ quorum: p.quorum,
83
+ peerCount: p.peerCount,
84
+ approvals: p.approvals.size,
85
+ rejections: p.rejections.size,
86
+ abstentions: p.abstentions.size,
87
+ createdAt: p.createdAt,
88
+ committedAt: p.committedAt,
89
+ finalizedAt: p.finalizedAt,
90
+ viewChangeReason: p.viewChangeReason,
91
+ payload: p.payload,
92
+ };
93
+ }
94
+
95
+ // ─── ByzantineConsensus class ──────────────────────────────────────────
96
+
97
+ export interface ByzantineConsensusOpts extends ConsensusOpts {
98
+ /** Wall-clock timeout (ms) after which a pending proposal expires.
99
+ * Defaults to 5 seconds. */
100
+ timeoutMs?: number;
101
+ /** Maximum number of proposals to retain in the history (LRU eviction).
102
+ * Defaults to 100. */
103
+ historyLimit?: number;
104
+ }
105
+
106
+ /**
107
+ * In-process Byzantine fault-tolerant consensus. One instance per
108
+ * (cluster, quorum) tuple. Thread-unsafe — single-threaded JS so
109
+ * the concern is theoretical, but document it for callers that wrap
110
+ * the instance in worker threads.
111
+ */
112
+ export class ByzantineConsensus {
113
+ private readonly localAgentId: string;
114
+ private readonly peers: string[];
115
+ private readonly quorum: number;
116
+ private readonly maxFaults: number;
117
+ private readonly timeoutMs: number;
118
+ private readonly historyLimit: number;
119
+ private readonly now: () => Date;
120
+
121
+ private readonly queen: QueenCoordinator;
122
+ private viewNumber = 0;
123
+
124
+ /** Live proposals, keyed by proposalId. */
125
+ private readonly proposals = new Map<string, Proposal>();
126
+ /** Payload digest → proposalId, for replay protection. */
127
+ private readonly payloadIndex = new Map<string, string>();
128
+ /** Total counts (status === 'committed' / 'rejected' / 'expired'). */
129
+ private committed = 0;
130
+ private rejected = 0;
131
+ private expired = 0;
132
+ private viewChanges = 0;
133
+
134
+ constructor(opts: ByzantineConsensusOpts) {
135
+ if (!opts || typeof opts.localAgentId !== "string" || opts.localAgentId.trim().length === 0) {
136
+ throw new Error("ByzantineConsensus: `localAgentId` is required");
137
+ }
138
+ if (!Array.isArray(opts.peers) || opts.peers.length < 2) {
139
+ throw new Error("ByzantineConsensus: `peers` must be a non-empty array of >=2 ids");
140
+ }
141
+ if (!opts.peers.includes(opts.localAgentId)) {
142
+ throw new Error(
143
+ `ByzantineConsensus: localAgentId "${opts.localAgentId}" must be in the peer list`,
144
+ );
145
+ }
146
+ const peerCount = opts.peers.length;
147
+ const quorum = opts.quorum ?? (peerCount >= DEFAULT_PEER_COUNT
148
+ ? DEFAULT_QUORUM
149
+ : Math.max(1, Math.floor(peerCount / 2) + 1));
150
+ if (quorum < 1 || quorum > peerCount) {
151
+ throw new Error(
152
+ `ByzantineConsensus: quorum (${quorum}) must be in [1, peerCount=${peerCount}]`,
153
+ );
154
+ }
155
+ const maxFaults = opts.maxFaults ?? DEFAULT_MAX_FAULTS;
156
+ // PBFT requires n >= 3f+1 for true Byzantine tolerance. We allow
157
+ // smaller clusters (the F-039 spec uses 3-of-5 simple majority,
158
+ // not full BFT), but warn at the API boundary so callers know.
159
+ if (peerCount < 3 * maxFaults + 1) {
160
+ // Best-effort — we don't throw because the orchestrator can
161
+ // still drive a useful simple-majority consensus on smaller
162
+ // clusters. The QueenCoordinator tracks the same `maxFaults`
163
+ // value for proposer-skip math.
164
+ void maxFaults; // surfaces in `this.maxFaults` getter below.
165
+ }
166
+ this.localAgentId = opts.localAgentId;
167
+ this.peers = [...opts.peers];
168
+ this.quorum = quorum;
169
+ this.maxFaults = maxFaults;
170
+ this.timeoutMs = opts.timeoutMs ?? 5_000;
171
+ this.historyLimit = opts.historyLimit ?? 100;
172
+ this.now = opts.now ?? (() => new Date());
173
+ this.queen = new QueenCoordinator({
174
+ peers: this.peers,
175
+ maxFaults: this.maxFaults,
176
+ seed: opts.proposerSeed,
177
+ });
178
+ }
179
+
180
+ // ─── Read-only accessors ───────────────────────────────────────────
181
+
182
+ /** Identity of the local agent (mirrors `opts.localAgentId`). */
183
+ getLocalAgentId(): string {
184
+ return this.localAgentId;
185
+ }
186
+
187
+ /** Maximum Byzantine faults the cluster is sized for. */
188
+ getMaxFaults(): number {
189
+ return this.maxFaults;
190
+ }
191
+
192
+ /** Maximum proposal history retained (LRU eviction). */
193
+ getHistoryLimit(): number {
194
+ return this.historyLimit;
195
+ }
196
+
197
+ /** Current proposer (head of the round-robin schedule). */
198
+ getCurrentProposer(): string {
199
+ return this.queen.currentProposer();
200
+ }
201
+
202
+ getViewNumber(): number {
203
+ return this.viewNumber;
204
+ }
205
+
206
+ getQuorum(): number {
207
+ return this.quorum;
208
+ }
209
+
210
+ getPeers(): string[] {
211
+ return [...this.peers];
212
+ }
213
+
214
+ // ─── Proposal lifecycle ─────────────────────────────────────────────
215
+
216
+ /**
217
+ * Initiate pre-prepare for a new proposal. Generates a stable
218
+ * proposalId from the payload + viewNumber. If the same payload is
219
+ * re-submitted in the same view, the existing proposal is returned
220
+ * (replay protection — see F-039 spec).
221
+ */
222
+ propose(payload: unknown): ProposeResult {
223
+ this.evictExpired();
224
+ const digest = this.payloadDigest(payload, this.viewNumber);
225
+ const existingId = this.payloadIndex.get(digest);
226
+ if (existingId) {
227
+ const existing = this.proposals.get(existingId);
228
+ if (existing) {
229
+ return {
230
+ proposalId: existing.id,
231
+ status: deriveStatus(existing, this.now(), this.timeoutMs),
232
+ phase: existing.phase,
233
+ };
234
+ }
235
+ }
236
+
237
+ const proposer = this.queen.currentProposer();
238
+ const id = `bft-v${this.viewNumber}-${randomUUID().slice(0, 8)}`;
239
+ const proposal: Proposal = {
240
+ id,
241
+ payload,
242
+ proposer,
243
+ quorum: this.quorum,
244
+ peerCount: this.peers.length,
245
+ phase: "pre-prepare",
246
+ approvals: new Set(),
247
+ rejections: new Set(),
248
+ abstentions: new Set(),
249
+ createdAt: this.now().toISOString(),
250
+ };
251
+ this.proposals.set(id, proposal);
252
+ this.payloadIndex.set(digest, id);
253
+
254
+ // Pre-prepare completes immediately for the local proposer; the
255
+ // proposer's own vote is added by `onPrepare` if the caller wants
256
+ // it counted. We do NOT auto-vote here — the caller decides whether
257
+ // the proposer participates in its own proposal (typical: yes).
258
+ this.advanceToPrepare(proposal);
259
+
260
+ return { proposalId: id, status: deriveStatus(proposal, this.now(), this.timeoutMs), phase: proposal.phase };
261
+ }
262
+
263
+ /**
264
+ * Cast a vote for a peer during the `prepare` phase. Returns the
265
+ * updated tally; if the proposal reaches quorum the phase auto-
266
+ * advances to `commit` and the `reply` snapshot is recorded.
267
+ *
268
+ * If the proposer themselves votes `no`, the proposal is
269
+ * immediately rejected (self-fault detection) — view-change is the
270
+ * caller's responsibility (call `viewChange`).
271
+ */
272
+ castVote(proposalId: string, agentId: string, decision: Decision, signature?: string): VoteResult {
273
+ this.evictExpired();
274
+ const proposal = this.proposals.get(proposalId);
275
+ if (!proposal) {
276
+ throw new Error(`castVote: unknown proposalId: ${proposalId}`);
277
+ }
278
+ if (!this.peers.includes(agentId)) {
279
+ throw new Error(`castVote: agent "${agentId}" is not in the peer list`);
280
+ }
281
+ if (proposal.phase === "commit" || proposal.phase === "reply") {
282
+ // Late vote — already committed/rejected. Return current tally
283
+ // (matches ruflo's `vote` early-return when status !== 'pending').
284
+ return this.snapshotVoteResult(proposal);
285
+ }
286
+ if (proposal.phase !== "prepare") {
287
+ throw new Error(
288
+ `castVote: proposal ${proposalId} is in phase "${proposal.phase}", not "prepare"`,
289
+ );
290
+ }
291
+
292
+ // Self-fault: proposer voting "no" is a Byzantine signal.
293
+ if (decision === "no" && agentId === proposal.proposer) {
294
+ proposal.rejections.add(agentId);
295
+ proposal.phase = "reply";
296
+ proposal.viewChangeReason = `proposer-${agentId}-self-rejected`;
297
+ proposal.finalizedAt = this.now().toISOString();
298
+ this.rejected++;
299
+ this.viewChanges++;
300
+ this.queen.recordFault(agentId);
301
+ // Advance the round-robin + view number so the next proposal
302
+ // lands on a non-faulted proposer.
303
+ this.queen.advance();
304
+ this.viewNumber++;
305
+ // Clear the payload digest so the same payload can be re-proposed
306
+ // under the new view.
307
+ const digest = this.payloadDigest(proposal.payload, this.viewNumber - 1);
308
+ if (this.payloadIndex.get(digest) === proposal.id) {
309
+ this.payloadIndex.delete(digest);
310
+ }
311
+ return this.snapshotVoteResult(proposal);
312
+ }
313
+
314
+ // Idempotency: re-voting replaces the prior vote.
315
+ proposal.approvals.delete(agentId);
316
+ proposal.rejections.delete(agentId);
317
+ proposal.abstentions.delete(agentId);
318
+ if (decision === "yes") proposal.approvals.add(agentId);
319
+ else if (decision === "no") proposal.rejections.add(agentId);
320
+ else proposal.abstentions.add(agentId);
321
+
322
+ // Track the vote in the proposal's audit log (for `status()` output).
323
+ const vote: Vote = {
324
+ agentId,
325
+ phase: "prepare",
326
+ decision,
327
+ signature,
328
+ timestamp: this.now().toISOString(),
329
+ };
330
+ void vote; // vote is captured by the Set membership; kept in the
331
+ // interface for callers that want to log externally.
332
+
333
+ // Quorum check — promote on majority `yes`.
334
+ if (proposal.approvals.size >= proposal.quorum) {
335
+ proposal.phase = "commit";
336
+ proposal.committedAt = this.now().toISOString();
337
+ // Auto-advance to reply immediately (synchronous thin port).
338
+ proposal.phase = "reply";
339
+ proposal.finalizedAt = this.now().toISOString();
340
+ this.committed++;
341
+ } else if (proposal.rejections.size > 0 && !this.canStillReachQuorum(proposal)) {
342
+ proposal.phase = "reply";
343
+ proposal.finalizedAt = this.now().toISOString();
344
+ this.rejected++;
345
+ }
346
+
347
+ return this.snapshotVoteResult(proposal);
348
+ }
349
+
350
+ /**
351
+ * External entry-point that lets the orchestrator deliver a peer
352
+ * vote that arrived over a transport. The `proposalId` is passed
353
+ * explicitly (it's not part of the `Vote` shape — Votes are scoped
354
+ * to a single proposal inside the cluster).
355
+ */
356
+ onPrepare(proposalId: string, vote: Vote): VoteResult {
357
+ return this.castVote(proposalId, vote.agentId, vote.decision, vote.signature);
358
+ }
359
+
360
+ /**
361
+ * External entry-point for a peer that has accepted the
362
+ * pre-prepare. In the in-memory thin port this is mostly a
363
+ * confirmation that the peer transitioned to `prepare` — we
364
+ * record the pre-prepare accept and let `onPrepare` / `castVote`
365
+ * carry the actual vote.
366
+ */
367
+ onPrePrepare(peerId: string, proposalId: string): { proposalId: string; phase: Phase } {
368
+ const proposal = this.proposals.get(proposalId);
369
+ if (!proposal) {
370
+ throw new Error(`onPrePrepare: unknown proposalId: ${proposalId}`);
371
+ }
372
+ if (!this.peers.includes(peerId)) {
373
+ throw new Error(`onPrePrepare: peer "${peerId}" is not in the peer list`);
374
+ }
375
+ if (proposal.phase !== "pre-prepare" && proposal.phase !== "prepare") {
376
+ throw new Error(
377
+ `onPrePrepare: proposal ${proposalId} is past pre-prepare (phase "${proposal.phase}")`,
378
+ );
379
+ }
380
+ // Pre-prepare → prepare happens automatically in `propose`; this
381
+ // method exists to satisfy the spec surface and let the orchestrator
382
+ // record that peerId acknowledged the pre-prepare.
383
+ return { proposalId, phase: proposal.phase };
384
+ }
385
+
386
+ /**
387
+ * Mark a proposal as committed (used when the orchestrator wants
388
+ * to force a commit, e.g. for tests that skip the vote tally).
389
+ */
390
+ commit(proposalId: string): CommitResult {
391
+ const proposal = this.proposals.get(proposalId);
392
+ if (!proposal) {
393
+ throw new Error(`commit: unknown proposalId: ${proposalId}`);
394
+ }
395
+ proposal.phase = "commit";
396
+ proposal.committedAt = this.now().toISOString();
397
+ proposal.phase = "reply";
398
+ proposal.finalizedAt = this.now().toISOString();
399
+ this.committed++;
400
+ return {
401
+ proposalId,
402
+ status: "committed",
403
+ committedAt: proposal.committedAt,
404
+ finalizedAt: proposal.finalizedAt,
405
+ approvals: proposal.approvals.size,
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Trigger a view-change. Advances the round-robin schedule to the
411
+ * next eligible peer (lowest fault count), increments viewNumber,
412
+ * and increments the viewChanges counter. The currently-active
413
+ * proposal (if any) is marked with the given `reason` and frozen
414
+ * at its current phase — the orchestrator can re-submit the same
415
+ * payload under the new view.
416
+ */
417
+ viewChange(reason: string, proposalId?: string): ViewChangeResult {
418
+ const previousProposer = this.queen.currentProposer();
419
+ this.queen.recordFault(previousProposer);
420
+ this.queen.advance();
421
+ this.viewNumber++;
422
+ this.viewChanges++;
423
+
424
+ let frozenProposalId = proposalId ?? "";
425
+ if (frozenProposalId) {
426
+ const proposal = this.proposals.get(frozenProposalId);
427
+ if (proposal) {
428
+ proposal.viewChangeReason = reason;
429
+ if (proposal.phase !== "reply") {
430
+ proposal.phase = "reply";
431
+ proposal.finalizedAt = this.now().toISOString();
432
+ // A view-change counts as neither committed nor rejected —
433
+ // the new view will re-propose the payload.
434
+ if (proposal.approvals.size < proposal.quorum) {
435
+ this.rejected++;
436
+ }
437
+ }
438
+ // Bump the payload digest index so the new view produces a
439
+ // fresh proposalId for the same payload (otherwise replay
440
+ // protection would short-circuit the retry).
441
+ const digest = this.payloadDigest(proposal.payload, this.viewNumber - 1);
442
+ if (this.payloadIndex.get(digest) === proposal.id) {
443
+ this.payloadIndex.delete(digest);
444
+ }
445
+ }
446
+ }
447
+
448
+ return {
449
+ previousProposer,
450
+ newProposer: this.queen.currentProposer(),
451
+ viewNumber: this.viewNumber,
452
+ reason,
453
+ proposalId: frozenProposalId,
454
+ };
455
+ }
456
+
457
+ // ─── Status / snapshot ─────────────────────────────────────────────
458
+
459
+ /** Read-only snapshot of every proposal the instance has tracked. */
460
+ status(): ConsensusStatus {
461
+ this.evictExpired();
462
+ const snapshots: ProposalSnapshot[] = [];
463
+ for (const p of this.proposals.values()) {
464
+ snapshots.push(snapshotOf(p, this.now(), this.timeoutMs));
465
+ }
466
+ // Most-recent first (lex order by createdAt desc is fine for in-memory).
467
+ snapshots.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
468
+ return {
469
+ proposals: snapshots,
470
+ currentProposer: this.queen.currentProposer(),
471
+ viewNumber: this.viewNumber,
472
+ committed: this.committed,
473
+ rejected: this.rejected,
474
+ expired: this.expired,
475
+ viewChanges: this.viewChanges,
476
+ };
477
+ }
478
+
479
+ /** Snapshot of a single proposal, or `undefined` if unknown. */
480
+ getProposal(proposalId: string): ProposalSnapshot | undefined {
481
+ const p = this.proposals.get(proposalId);
482
+ if (!p) return undefined;
483
+ return snapshotOf(p, this.now(), this.timeoutMs);
484
+ }
485
+
486
+ // ─── Helpers ────────────────────────────────────────────────────────
487
+
488
+ private snapshotVoteResult(proposal: Proposal): VoteResult {
489
+ return {
490
+ proposalId: proposal.id,
491
+ status: deriveStatus(proposal, this.now(), this.timeoutMs),
492
+ phase: proposal.phase,
493
+ approvals: proposal.approvals.size,
494
+ rejections: proposal.rejections.size,
495
+ abstentions: proposal.abstentions.size,
496
+ committed: proposal.phase === "reply" && !!proposal.committedAt,
497
+ };
498
+ }
499
+
500
+ /**
501
+ * Promote a proposal from `pre-prepare` to `prepare`. In the thin
502
+ * port this is synchronous — we don't simulate network latency.
503
+ */
504
+ private advanceToPrepare(proposal: Proposal): void {
505
+ if (proposal.phase !== "pre-prepare") return;
506
+ proposal.phase = "prepare";
507
+ }
508
+
509
+ /**
510
+ * Returns true if a proposal in `prepare` can still reach quorum
511
+ * given the remaining un-voted peers. Used to short-circuit
512
+ * dead proposals into `rejected` early.
513
+ */
514
+ private canStillReachQuorum(proposal: Proposal): boolean {
515
+ const voted = proposal.approvals.size + proposal.rejections.size + proposal.abstentions.size;
516
+ const remaining = proposal.peerCount - voted;
517
+ const maxYes = proposal.approvals.size + remaining;
518
+ return maxYes >= proposal.quorum;
519
+ }
520
+
521
+ /**
522
+ * Walk the proposal map, mark anything older than `timeoutMs` as
523
+ * expired, and trim history to `historyLimit` entries.
524
+ */
525
+ private evictExpired(): void {
526
+ const now = this.now();
527
+ const toExpire: string[] = [];
528
+ for (const [id, p] of this.proposals) {
529
+ if (p.phase === "reply") continue;
530
+ if (now.getTime() - new Date(p.createdAt).getTime() > this.timeoutMs) {
531
+ toExpire.push(id);
532
+ }
533
+ }
534
+ for (const id of toExpire) {
535
+ const p = this.proposals.get(id);
536
+ if (!p) continue;
537
+ p.phase = "reply";
538
+ p.finalizedAt = now.toISOString();
539
+ // Strip the payload digest so a future re-submit gets a fresh
540
+ // proposalId under the same view.
541
+ const digest = this.payloadDigest(p.payload, this.viewNumber);
542
+ if (this.payloadIndex.get(digest) === id) {
543
+ this.payloadIndex.delete(digest);
544
+ }
545
+ this.expired++;
546
+ }
547
+ // LRU trim.
548
+ if (this.proposals.size > this.historyLimit) {
549
+ const ordered = Array.from(this.proposals.values())
550
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt));
551
+ const drop = ordered.slice(0, this.proposals.size - this.historyLimit);
552
+ for (const p of drop) {
553
+ this.proposals.delete(p.id);
554
+ }
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Stable hash of a payload for replay protection. The viewNumber is
560
+ * included so the same payload in a new view produces a fresh digest
561
+ * (view-changes are explicitly allowed to re-propose).
562
+ */
563
+ private payloadDigest(payload: unknown, viewNumber: number): string {
564
+ return createHash("sha256")
565
+ .update(JSON.stringify(payload ?? null))
566
+ .update(`|v${viewNumber}`)
567
+ .digest("hex");
568
+ }
569
+ }
570
+
571
+ // ─── Re-exports for callers that want them in one import ───────────────
572
+
573
+ export type {
574
+ CommitResult,
575
+ ConsensusOpts,
576
+ ConsensusStatus,
577
+ Decision,
578
+ Phase,
579
+ ProposeResult,
580
+ Proposal,
581
+ ProposalSnapshot,
582
+ ProposalStatus,
583
+ Vote,
584
+ VoteResult,
585
+ ViewChangeResult,
586
+ };
587
+ export { PHASE_ORDER };