@polderlabs/bizar 6.3.0 → 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 (245) hide show
  1. package/.claude/hooks/sessionend-recall.mjs +3 -3
  2. package/.claude/hooks/worker-suggest.mjs +110 -0
  3. package/.claude/settings.json +9 -0
  4. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-CW6fWKds.js} +3 -3
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
  6. package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
  7. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
  8. package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
  9. package/bizar-dash/dist/assets/{MobileSettings-CEQNJNLJ.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
  10. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  12. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  14. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  16. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  17. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  18. package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
  19. package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
  20. package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
  21. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  22. package/bizar-dash/dist/assets/{markdown-tOLaD6nm.js.map → markdown-w2bDDkAH.js.map} +1 -1
  23. package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
  24. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DXYCA0hC.js.map} +1 -1
  25. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
  26. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
  27. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  28. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  29. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  30. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
  31. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
  32. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  33. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  34. package/bizar-dash/dist/index.html +12 -10
  35. package/bizar-dash/dist/mobile.html +7 -4
  36. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  37. package/bizar-dash/src/server/api.mjs +11 -0
  38. package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
  39. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  40. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  41. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  42. package/bizar-dash/src/web/App.tsx +4 -0
  43. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  44. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  45. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  46. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  47. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  48. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  49. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  51. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  52. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  53. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  54. package/bizar-dash/src/web/lib/types.ts +25 -0
  55. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  56. package/bizar-dash/src/web/styles/main.css +251 -0
  57. package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
  58. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  59. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  60. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  61. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  62. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  63. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  64. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  65. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  66. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  67. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  68. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  69. package/bizar-dash/src/web/ui/controls/controls.css +454 -0
  70. package/bizar-dash/src/web/ui/controls/index.ts +33 -0
  71. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  72. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  73. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  74. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  75. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  76. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  77. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  78. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  79. package/bizar-dash/src/web/ui/data/data.css +535 -0
  80. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  81. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  82. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  83. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  84. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  85. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  86. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  87. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  88. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  89. package/bizar-dash/src/web/ui/index.ts +56 -0
  90. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  91. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  92. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  93. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  94. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  95. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  96. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  97. package/bizar-dash/src/web/ui/layout/index.ts +33 -0
  98. package/bizar-dash/src/web/ui/layout/layout.css +413 -0
  99. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  100. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  101. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  102. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  103. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  104. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  105. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  106. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  107. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  108. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  109. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  110. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  111. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  112. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  113. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  114. package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
  115. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  116. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  117. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  118. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  119. package/bizar-dash/src/web/views/Agents.tsx +506 -266
  120. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  121. package/bizar-dash/src/web/views/Overview.tsx +996 -448
  122. package/bizar-dash/src/web/views/Tasks.tsx +277 -273
  123. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  124. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  125. package/bizar-dash/tests/event-log.test.tsx +61 -0
  126. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  127. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  128. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  129. package/bizar-dash/tests/setup.ts +31 -1
  130. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  131. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  132. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  133. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  134. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  135. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  136. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  137. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  138. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  139. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  140. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  141. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  142. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  143. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  144. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  145. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  146. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  147. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  148. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  149. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  151. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  152. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  153. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  154. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  155. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  156. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  157. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  158. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  159. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  160. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  161. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  163. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  164. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  165. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  166. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  167. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  168. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  169. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  170. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  171. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  172. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  173. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  174. package/bizar-dash/vitest.config.ts +3 -1
  175. package/cli/__tests__/cost-gate.test.mjs +301 -0
  176. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  177. package/cli/bin.mjs +38 -0
  178. package/cli/commands/claim.mjs +282 -0
  179. package/cli/commands/cost.mjs +279 -0
  180. package/cli/cost-gate.mjs +501 -0
  181. package/cli/feature-list-bridge.mjs +400 -0
  182. package/cli/memory-constants.mjs +17 -0
  183. package/cli/worker-dispatcher.mjs +259 -0
  184. package/cli/worker-dispatcher.test.mjs +204 -0
  185. package/config/trigger-patterns.json +186 -0
  186. package/package.json +2 -2
  187. package/packages/sdk/package.json +1 -1
  188. package/packages/sdk/src/agent-registry.ts +304 -0
  189. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  190. package/packages/sdk/src/consensus/index.ts +147 -0
  191. package/packages/sdk/src/consensus/queen.ts +173 -0
  192. package/packages/sdk/src/consensus/types.ts +199 -0
  193. package/packages/sdk/src/federation/audit.ts +156 -0
  194. package/packages/sdk/src/federation/budget.ts +269 -0
  195. package/packages/sdk/src/federation/envelope.ts +144 -0
  196. package/packages/sdk/src/federation/hmac.ts +160 -0
  197. package/packages/sdk/src/federation/index.ts +443 -0
  198. package/packages/sdk/src/federation/pii.ts +241 -0
  199. package/packages/sdk/src/federation/policy.ts +112 -0
  200. package/packages/sdk/src/federation/trust.ts +146 -0
  201. package/packages/sdk/src/index.ts +67 -0
  202. package/packages/sdk/src/mcp/server.ts +351 -1
  203. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  204. package/packages/sdk/src/router/index.ts +247 -0
  205. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  206. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  207. package/packages/sdk/src/router/model-router.ts +235 -0
  208. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  209. package/packages/sdk/src/swarm-topology.ts +279 -0
  210. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  211. package/packages/sdk/tests/consensus.test.ts +376 -0
  212. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  213. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  214. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  215. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  216. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  217. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  218. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  219. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  220. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  221. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  222. package/packages/sdk/tests/model-router.test.mjs +108 -0
  223. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  224. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  225. package/packages/sdk/tests/router.test.mjs +91 -0
  226. package/packages/sdk/tests/sdk.test.mjs +26 -2
  227. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  228. package/scripts/bh-full-e2e.mjs +11 -15
  229. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  230. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  231. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  232. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  233. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  234. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  235. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  236. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  237. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  238. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  239. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  240. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  241. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  242. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  243. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  244. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  245. package/packages/sdk/package-lock.json +0 -32
@@ -0,0 +1,247 @@
1
+ /**
2
+ * router/index.ts — orchestrator for Bizar's self-learning router.
3
+ *
4
+ * v6.4.0 — F-033. Composes the three sub-routers
5
+ * (`codemod-intent`, `model-router`, `q-learning-router`) into a
6
+ * single precedence chain:
7
+ *
8
+ * explicit-route (caller-provided) >
9
+ * codemod-intent ($0 Tier-1 short-circuit) >
10
+ * q-learning-router (8-action agent pick) >
11
+ * model-router (Thompson bandit for tier) >
12
+ * default ("odin", "mid", confidence 0.5)
13
+ *
14
+ * The orchestrator is what the `hooks_route` MCP tool (and any
15
+ * in-process caller such as the Odin agent) calls to make a routing
16
+ * decision. It surfaces two human-readable tags on `stdout`-equivalent
17
+ * surfaces:
18
+ *
19
+ * - `[CODEMOD_AVAILABLE] <intent>` — printed BEFORE the model call
20
+ * would happen, telling the model that a deterministic $0 codemod
21
+ * could short-circuit the work. Honoured by the Odin prompt.
22
+ * - `[TASK_MODEL_RECOMMENDATION] <tier> (conf=<n>)` — appended to
23
+ * prompt-side telemetry so downstream model selection knows the
24
+ * bandit-learner thinks this task should be routed to `flash` /
25
+ * `mid` / `expensive`.
26
+ *
27
+ * Persistence: when constructed via `getRouter({ persist: true })`,
28
+ * the orchestrator holds a single shared `ModelRouter` +
29
+ * `QLearningRouter` whose state lives at `.harness/router-state.json`
30
+ * and `.harness/q-router-state.json` respectively. Callers can also
31
+ * pass `modelRouter` / `qRouter` for in-memory-only operation
32
+ * (preferred in tests).
33
+ */
34
+
35
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
36
+ import { dirname, isAbsolute, join, resolve } from "node:path";
37
+
38
+ import {
39
+ detectCodemodIntent,
40
+ type CodemodIntent,
41
+ CODEMOD_CONFIDENCE_THRESHOLD,
42
+ } from "./codemod-intent.js";
43
+ import {
44
+ ModelRouter,
45
+ type ModelTier,
46
+ type RouteDecision,
47
+ } from "./model-router.js";
48
+ import {
49
+ QLearningRouter,
50
+ AGENT_ACTIONS,
51
+ type AgentRouteDecision,
52
+ } from "./q-learning-router.js";
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Public types
56
+ // ---------------------------------------------------------------------------
57
+
58
+ export interface RouteInput {
59
+ task: string;
60
+ /** Caller-provided explicit override for the agent (highest precedence). */
61
+ explicitAgent?: string;
62
+ /** Optional embedding — currently ignored (reserved for future ANN
63
+ * lookup that could refine the state bucket). */
64
+ embedding?: number[];
65
+ }
66
+
67
+ export interface RouteDecisionOutput {
68
+ agent: string;
69
+ modelTier: ModelTier;
70
+ agentConfidence: number;
71
+ modelConfidence: number;
72
+ /** Null unless the prompt matched a Tier-1 codemod intent. */
73
+ codemodIntent: CodemodIntent | null;
74
+ /** `[CODEMOD_AVAILABLE] var-to-const` style surface for prompt pre-injection. */
75
+ surfacedTags: string[];
76
+ }
77
+
78
+ export interface RouterBundle {
79
+ modelRouter: ModelRouter;
80
+ qRouter: QLearningRouter;
81
+ decideAgent(input: RouteInput): RouteDecisionOutput;
82
+ /** Persist both routers to `path`'s sibling state files. */
83
+ saveTo(path: string): void;
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Factory
88
+ // ---------------------------------------------------------------------------
89
+
90
+ export interface GetRouterOpts {
91
+ /** When provided, the orchestrator attempts to load priors + Q-table
92
+ * from `<basePath>.{model,q}` if those files exist; missing files
93
+ * fall back to defaults. */
94
+ basePath?: string;
95
+ /** In-memory overrides — useful for tests. */
96
+ modelRouter?: ModelRouter;
97
+ qRouter?: QLearningRouter;
98
+ }
99
+
100
+ /** Default state-file paths (kept beside the orchestrator's caller). */
101
+ export const DEFAULT_MODEL_STATE_PATH = ".harness/router-state.json";
102
+ export const DEFAULT_Q_STATE_PATH = ".harness/q-router-state.json";
103
+
104
+ /**
105
+ * Build (or rehydrate) a router bundle.
106
+ *
107
+ * Default state files are stored under `.harness/` in the current
108
+ * working directory. Tests typically pass `modelRouter` / `qRouter`
109
+ * to keep things in-process.
110
+ */
111
+ export function getRouter(opts: GetRouterOpts = {}): RouterBundle {
112
+ const basePath = opts.basePath ?? join(process.cwd(), ".harness", "router-state");
113
+ const modelPath = `${basePath}.model`;
114
+ const qPath = `${basePath}.q`;
115
+
116
+ const modelRouter =
117
+ opts.modelRouter ??
118
+ (existsSync(modelPath) ? ModelRouter.loadFrom(modelPath) : new ModelRouter());
119
+ const qRouter =
120
+ opts.qRouter ??
121
+ (existsSync(qPath) ? QLearningRouter.loadFrom(qPath) : new QLearningRouter());
122
+
123
+ return {
124
+ modelRouter,
125
+ qRouter,
126
+ decideAgent(input: RouteInput): RouteDecisionOutput {
127
+ return decideAgentWith(modelRouter, qRouter, input);
128
+ },
129
+ saveTo(path: string): void {
130
+ const fp = isAbsolute(path) ? path : resolve(path);
131
+ mkdirSync(dirname(fp), { recursive: true });
132
+ modelRouter.saveTo(modelPath);
133
+ qRouter.saveTo(qPath);
134
+ // And a tiny pointer file for callers that only know the bundle name.
135
+ writeFileSync(
136
+ fp,
137
+ JSON.stringify({ modelPath, qPath, version: 1 }, null, 2),
138
+ "utf-8",
139
+ );
140
+ },
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Pure, testable entry point — the actual precedence chain.
146
+ *
147
+ * Order:
148
+ * 1. caller-supplied `explicitAgent` wins outright.
149
+ * 2. Tier-1 codemod intent → agent="heimdall" (routine implementation),
150
+ * tier="flash" ($0), confidence 1.0, surface `[CODEMOD_AVAILABLE]`.
151
+ * 3. Q-learning agent pick (clamped to known agent names).
152
+ * 4. Thompson-bandit model-tier pick.
153
+ * 5. Defaults: agent="odin", tier="mid".
154
+ */
155
+ export function decideAgentWith(
156
+ modelRouter: ModelRouter,
157
+ qRouter: QLearningRouter,
158
+ input: RouteInput,
159
+ ): RouteDecisionOutput {
160
+ const task = String(input?.task ?? "");
161
+ const surfacedTags: string[] = [];
162
+
163
+ // 1. Explicit override — highest precedence.
164
+ if (input.explicitAgent && AGENT_ACTIONS.includes(input.explicitAgent)) {
165
+ // Still pick a model tier (bandit) so we can surface
166
+ // [TASK_MODEL_RECOMMENDATION] downstream.
167
+ const tier: RouteDecision = modelRouter.route(task);
168
+ surfacedTags.push(tierTag(tier));
169
+ return {
170
+ agent: input.explicitAgent,
171
+ modelTier: tier.tier,
172
+ agentConfidence: 1.0,
173
+ modelConfidence: tier.confidence,
174
+ codemodIntent: null,
175
+ surfacedTags,
176
+ };
177
+ }
178
+
179
+ // 2. Codemod short-circuit (only when there's a task string).
180
+ const cm = task ? detectCodemodIntent(task) : null;
181
+ if (cm !== null) {
182
+ surfacedTags.push(codemodTag(cm.intent, cm.confidence));
183
+ // Tier-1 codemod: $0, model tier = "flash" but the call is
184
+ // supposed to be skipped (deterministic). We still set
185
+ // modelTier/Confidence so downstream telemetry has consistent
186
+ // shape.
187
+ surfacedTags.push(tierTag({ tier: "flash", confidence: 1.0 }));
188
+ return {
189
+ agent: "heimdall",
190
+ modelTier: "flash",
191
+ agentConfidence: 1.0,
192
+ modelConfidence: 1.0,
193
+ codemodIntent: cm.intent,
194
+ surfacedTags,
195
+ };
196
+ }
197
+
198
+ // 3. Q-learning agent pick.
199
+ const agentDecision: AgentRouteDecision = qRouter.selectAgent(task);
200
+ // 4. Thompson bandit for model tier.
201
+ const tierDecision: RouteDecision = modelRouter.route(task);
202
+ surfacedTags.push(tierTag(tierDecision));
203
+
204
+ return {
205
+ agent: agentDecision.agent,
206
+ modelTier: tierDecision.tier,
207
+ agentConfidence: agentDecision.confidence,
208
+ modelConfidence: tierDecision.confidence,
209
+ codemodIntent: null,
210
+ surfacedTags,
211
+ };
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Tag formatters — the strings the spec asks us to surface to the prompt.
216
+ // ---------------------------------------------------------------------------
217
+
218
+ export function codemodTag(intent: CodemodIntent, confidence: number): string {
219
+ return `[CODEMOD_AVAILABLE] ${intent} (conf=${confidence.toFixed(2)} >= ${CODEMOD_CONFIDENCE_THRESHOLD.toFixed(2)})`;
220
+ }
221
+
222
+ export function tierTag(decision: RouteDecision): string {
223
+ return `[TASK_MODEL_RECOMMENDATION] ${decision.tier} (conf=${decision.confidence.toFixed(2)})`;
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Convenience re-exports so callers can `import { ... } from "..."/router"
228
+ // ---------------------------------------------------------------------------
229
+
230
+ export {
231
+ detectCodemodIntent,
232
+ CODEMOD_CONFIDENCE_THRESHOLD,
233
+ } from "./codemod-intent.js";
234
+ export type { CodemodIntent, CodemodIntentHit } from "./codemod-intent.js";
235
+
236
+ export {
237
+ ModelRouter,
238
+ type ModelTier,
239
+ type RouteDecision,
240
+ } from "./model-router.js";
241
+
242
+ export {
243
+ QLearningRouter,
244
+ AGENT_ACTIONS,
245
+ type AgentName,
246
+ type AgentRouteDecision,
247
+ } from "./q-learning-router.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * router/memory-distillation-shim.mjs — runtime-only ESM re-export of
3
+ * `memory-distillation.ts` so the `memory_distill` MCP tool can
4
+ * dynamically `import()` the pipeline without taking a static
5
+ * dependency on the heavier consolidation logic.
6
+ *
7
+ * v6.4.0 — F-033 (Self-Learning, ADR-174). Lives next to its sibling
8
+ * `memory-distillation.ts`; the parent keeps the strict TS surface,
9
+ * this shim keeps the runtime import ESM-clean for the MCP tool.
10
+ */
11
+
12
+ export {
13
+ runDistillation,
14
+ retrieve,
15
+ judge,
16
+ distill,
17
+ PROVENANCE_TIERS,
18
+ } from "./memory-distillation.js";
@@ -0,0 +1,249 @@
1
+ /**
2
+ * router/memory-distillation.ts — RETRIEVE → JUDGE → DISTILL →
3
+ * CONSOLIDATE (ADR-174) as a pure TypeScript module.
4
+ *
5
+ * v6.4.0 — ported from ruflo `@claude-flow/neural/src/reasoning-bank.ts`
6
+ * (the 4-step pipeline) + ADR-174 (provenance-tiered promote gate).
7
+ *
8
+ * This is the $0 default path — deterministic, no LLM, no I/O.
9
+ * Living inside the SDK (rather than the dashboard .mjs files) so
10
+ * the `memory_distill` MCP tool can call it via a static TypeScript
11
+ * import without crossing the ESM/CJS boundary at runtime.
12
+ *
13
+ * The dashboard's `bizar-dash/src/server/memory-{distillation,
14
+ * consolidator}.mjs` re-export the same semantics and add the
15
+ * vault I/O (read notes, write promoted patterns back). Both paths
16
+ * agree on the `Pattern` shape so the schema is one-of.
17
+ */
18
+
19
+ export const PROVENANCE_TIERS = [
20
+ "oracle:test-exec",
21
+ "proxy:structural",
22
+ "judge:fable",
23
+ ] as const;
24
+
25
+ export type ProvenanceTier = (typeof PROVENANCE_TIERS)[number];
26
+
27
+ export interface MemoryEntryLike {
28
+ memory_id?: string;
29
+ id?: string;
30
+ path?: string;
31
+ relPath?: string;
32
+ frontmatter?: Record<string, unknown>;
33
+ body?: string;
34
+ kind?: string;
35
+ type?: string;
36
+ created?: string;
37
+ createdAt?: string;
38
+ tags?: unknown;
39
+ }
40
+
41
+ export interface Retrieval {
42
+ complexity: "simple" | "moderate" | "complex";
43
+ testExec: boolean;
44
+ hasCodeBlock: boolean;
45
+ length: number;
46
+ }
47
+
48
+ export interface Pattern {
49
+ id: string;
50
+ kind: "pattern";
51
+ summary: string;
52
+ uses: string[];
53
+ evidence: string[];
54
+ provenance_tier: ProvenanceTier;
55
+ promoted: boolean;
56
+ score: number;
57
+ }
58
+
59
+ export interface DistillationResult {
60
+ patterns: Pattern[];
61
+ promoted: Pattern[];
62
+ byTier: Record<ProvenanceTier, number>;
63
+ }
64
+
65
+ const COMPLEXITY_SIMPLE_MAX = 600;
66
+ const COMPLEXITY_MODERATE_MAX = 2400;
67
+
68
+ function firstLine(s: string): string {
69
+ if (!s) return "";
70
+ const i = s.indexOf("\n");
71
+ return (i < 0 ? s : s.slice(0, i)).trim();
72
+ }
73
+
74
+ function snippet(body: string): string {
75
+ const cleaned = body.replace(/\s+/g, " ").trim();
76
+ return cleaned.length <= 200 ? cleaned : cleaned.slice(0, 200) + "…";
77
+ }
78
+
79
+ function shorten(s: string): string {
80
+ let h = 5381 >>> 0;
81
+ for (let i = 0; i < s.length; i++) {
82
+ h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
83
+ }
84
+ return h.toString(36).slice(0, 10);
85
+ }
86
+
87
+ function wordSet(s: string): Set<string> {
88
+ const out = new Set<string>();
89
+ const norm = (s || "").toLowerCase().replace(/[^a-z0-9\s]+/g, " ");
90
+ for (const w of norm.split(/\s+/)) {
91
+ if (w.length > 2) out.add(w);
92
+ }
93
+ return out;
94
+ }
95
+
96
+ function jaccard(a: Set<string>, b: Set<string>): number {
97
+ if (a.size === 0 && b.size === 0) return 0;
98
+ let inter = 0;
99
+ for (const x of a) if (b.has(x)) inter += 1;
100
+ const union = a.size + b.size - inter;
101
+ return union === 0 ? 0 : inter / union;
102
+ }
103
+
104
+ function dedupe<T>(arr: T[]): T[] {
105
+ const seen = new Set<T>();
106
+ const out: T[] = [];
107
+ for (const x of arr) if (!seen.has(x)) { seen.add(x); out.push(x); }
108
+ return out;
109
+ }
110
+
111
+ export function retrieve(entry: MemoryEntryLike): Retrieval {
112
+ const body = String(entry?.body ?? "");
113
+ const fm = entry?.frontmatter ?? {};
114
+ const hasCodeBlock = /```/.test(body);
115
+ const tagsArr = Array.isArray(fm.tags) ? fm.tags : [];
116
+ const testExec =
117
+ Boolean(fm.test_exec) ||
118
+ Boolean(fm.testExec) ||
119
+ tagsArr.some((t) => String(t).includes("test-exec")) ||
120
+ String(fm.kind ?? "") === "feedback";
121
+ const len = body.length;
122
+ let complexity: Retrieval["complexity"];
123
+ if (hasCodeBlock || len > COMPLEXITY_MODERATE_MAX) complexity = "complex";
124
+ else if (len > COMPLEXITY_SIMPLE_MAX) complexity = "moderate";
125
+ else complexity = "simple";
126
+ return { complexity, testExec, hasCodeBlock, length: len };
127
+ }
128
+
129
+ export function judge(_entry: MemoryEntryLike, retrieval: Retrieval): ProvenanceTier {
130
+ if (retrieval.testExec) return "oracle:test-exec";
131
+ return "proxy:structural";
132
+ }
133
+
134
+ export function distill(entry: MemoryEntryLike): Omit<Pattern, "provenance_tier" | "promoted" | "score"> | null {
135
+ const fm = entry?.frontmatter ?? {};
136
+ const body = String(entry?.body ?? "").trim();
137
+ const id = String(entry?.memory_id ?? entry?.id ?? entry?.path ?? entry?.relPath ?? "");
138
+ const kindRaw = String(fm.kind ?? entry?.kind ?? entry?.type ?? "").toLowerCase();
139
+
140
+ if (kindRaw === "coding_convention" || kindRaw === "bug_pattern") {
141
+ return {
142
+ id: `pat_${shorten(id || body.slice(0, 24))}`,
143
+ kind: "pattern",
144
+ summary: firstLine(body) || "(empty)",
145
+ uses: id ? [id] : [],
146
+ evidence: [snippet(body)],
147
+ };
148
+ }
149
+
150
+ const head = firstLine(body).toLowerCase();
151
+ if (
152
+ kindRaw === "task_summary" ||
153
+ kindRaw === "session_summary" ||
154
+ /\bpattern\s*:/.test(head) ||
155
+ /^\s*(use|avoid|do|don't)\s*:/.test(head)
156
+ ) {
157
+ return {
158
+ id: `pat_${shorten(id || head.replace(/\W+/g, " ").slice(0, 24))}`,
159
+ kind: "pattern",
160
+ summary: firstLine(body) || "(empty)",
161
+ uses: id ? [id] : [],
162
+ evidence: [snippet(body)],
163
+ };
164
+ }
165
+ return null;
166
+ }
167
+
168
+ /**
169
+ * Distillation candidates carry their tier separately; we shift it on
170
+ * to `provenance_tier` here and set the `promoted` flag.
171
+ */
172
+ interface Candidate {
173
+ id: string;
174
+ kind: "pattern";
175
+ summary: string;
176
+ uses: string[];
177
+ evidence: string[];
178
+ _tier: ProvenanceTier;
179
+ }
180
+
181
+ export function runDistillation(entries: MemoryEntryLike[]): DistillationResult {
182
+ const empty = {
183
+ patterns: [] as Pattern[],
184
+ promoted: [] as Pattern[],
185
+ byTier: { "oracle:test-exec": 0, "proxy:structural": 0, "judge:fable": 0 } as Record<ProvenanceTier, number>,
186
+ };
187
+ if (!Array.isArray(entries) || entries.length === 0) return empty;
188
+
189
+ const candidates: Candidate[] = [];
190
+ for (const entry of entries) {
191
+ const retrieval = retrieve(entry);
192
+ const tier = judge(entry, retrieval);
193
+ const pattern = distill(entry);
194
+ if (!pattern) continue;
195
+ candidates.push({ ...pattern, _tier: tier });
196
+ }
197
+
198
+ // Cluster by Jaccard similarity ≥ 0.5 over (summary + evidence).
199
+ const groups: { rep: Candidate; members: Candidate[] }[] = [];
200
+ for (const c of candidates) {
201
+ const ws = wordSet(c.summary + " " + c.evidence.join(" "));
202
+ let placed = false;
203
+ for (const g of groups) {
204
+ const gs = wordSet(g.rep.summary + " " + g.rep.evidence.join(" "));
205
+ if (jaccard(ws, gs) >= 0.5) {
206
+ g.members.push(c);
207
+ placed = true;
208
+ break;
209
+ }
210
+ }
211
+ if (!placed) groups.push({ rep: c, members: [c] });
212
+ }
213
+
214
+ const byTier: Record<ProvenanceTier, number> = {
215
+ "oracle:test-exec": 0,
216
+ "proxy:structural": 0,
217
+ "judge:fable": 0,
218
+ };
219
+ const patterns: Pattern[] = [];
220
+ for (const g of groups) {
221
+ const usesSet = new Set<string>();
222
+ const evidence: string[] = [];
223
+ for (const m of g.members) {
224
+ for (const u of m.uses ?? []) usesSet.add(u);
225
+ for (const e of m.evidence ?? []) evidence.push(e);
226
+ }
227
+ const tiers = g.members.map((m) => m._tier);
228
+ let tier: ProvenanceTier = "proxy:structural";
229
+ if (tiers.includes("oracle:test-exec")) tier = "oracle:test-exec";
230
+ else if (tiers.includes("judge:fable")) tier = "judge:fable";
231
+
232
+ const promoted = usesSet.size >= 1 && (tier === "oracle:test-exec" || tier === "judge:fable");
233
+ const pattern: Pattern = {
234
+ id: g.rep.id,
235
+ kind: "pattern",
236
+ summary: g.rep.summary,
237
+ uses: Array.from(usesSet),
238
+ evidence: dedupe(evidence).slice(0, 5),
239
+ provenance_tier: tier,
240
+ promoted,
241
+ score: 0.5 + usesSet.size * 0.1,
242
+ };
243
+ patterns.push(pattern);
244
+ byTier[tier] += 1;
245
+ }
246
+
247
+ const promoted = patterns.filter((p) => p.promoted);
248
+ return { patterns, promoted, byTier };
249
+ }