@polderlabs/bizar 3.23.1 → 4.2.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 (361) hide show
  1. package/README.md +83 -16
  2. package/bizar-dash/.bizar/activity.log +3 -0
  3. package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
  4. package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
  5. package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
  6. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
  7. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
  8. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
  9. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
  10. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
  11. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
  12. package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
  13. package/bizar-dash/.bizar/graph/graph.html +307 -0
  14. package/bizar-dash/.bizar/graph/graph.json +40267 -0
  15. package/bizar-dash/.bizar/graph/manifest.json +762 -0
  16. package/bizar-dash/.graphifyignore +40 -0
  17. package/bizar-dash/.obsidian/README.md +14 -0
  18. package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
  19. package/bizar-dash/.obsidian/vault.json +7 -0
  20. package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
  21. package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
  22. package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
  23. package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
  24. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
  25. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
  26. package/bizar-dash/CHANGELOG.md +474 -0
  27. package/bizar-dash/DESIGN.md +136 -0
  28. package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
  29. package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
  30. package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
  31. package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
  32. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
  33. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
  34. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
  35. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
  36. package/bizar-dash/dist/index.html +40 -0
  37. package/bizar-dash/dist/mobile.html +22 -0
  38. package/bizar-dash/plans/debug-plan/comments.json +1 -0
  39. package/bizar-dash/plans/debug-plan/meta.json +10 -0
  40. package/bizar-dash/plans/debug-plan/plan.json +174 -0
  41. package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
  42. package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
  43. package/bizar-dash/src/cli.mjs +522 -0
  44. package/bizar-dash/src/server/activity-log.mjs +174 -0
  45. package/bizar-dash/src/server/agents-store.mjs +465 -0
  46. package/bizar-dash/src/server/api.mjs +138 -0
  47. package/bizar-dash/src/server/artifact-template.html +810 -0
  48. package/bizar-dash/src/server/artifacts-store.mjs +733 -0
  49. package/bizar-dash/src/server/auth.mjs +381 -0
  50. package/bizar-dash/src/server/background-store.mjs +447 -0
  51. package/bizar-dash/src/server/bg-poller.mjs +305 -0
  52. package/bizar-dash/src/server/bg-retry.mjs +574 -0
  53. package/bizar-dash/src/server/browser.mjs +40 -0
  54. package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
  55. package/bizar-dash/src/server/dialog-poller.mjs +105 -0
  56. package/bizar-dash/src/server/dialog-store.mjs +177 -0
  57. package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
  58. package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
  59. package/bizar-dash/src/server/memory-git.mjs +329 -0
  60. package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
  61. package/bizar-dash/src/server/memory-schema.mjs +145 -0
  62. package/bizar-dash/src/server/memory-secrets.mjs +84 -0
  63. package/bizar-dash/src/server/memory-store.mjs +471 -0
  64. package/bizar-dash/src/server/mod-security.mjs +398 -0
  65. package/bizar-dash/src/server/mods-loader.mjs +1067 -0
  66. package/bizar-dash/src/server/notifications-store.mjs +243 -0
  67. package/bizar-dash/src/server/obsidian-store.mjs +318 -0
  68. package/bizar-dash/src/server/opencode-runner.mjs +279 -0
  69. package/bizar-dash/src/server/pair-store.mjs +138 -0
  70. package/bizar-dash/src/server/projects-store.mjs +364 -0
  71. package/bizar-dash/src/server/providers-store.mjs +789 -0
  72. package/bizar-dash/src/server/routes/_shared.mjs +415 -0
  73. package/bizar-dash/src/server/routes/activity.mjs +110 -0
  74. package/bizar-dash/src/server/routes/agents.mjs +134 -0
  75. package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
  76. package/bizar-dash/src/server/routes/auth.mjs +69 -0
  77. package/bizar-dash/src/server/routes/background.mjs +165 -0
  78. package/bizar-dash/src/server/routes/chat.mjs +485 -0
  79. package/bizar-dash/src/server/routes/config.mjs +129 -0
  80. package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
  81. package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
  82. package/bizar-dash/src/server/routes/fs.mjs +429 -0
  83. package/bizar-dash/src/server/routes/history.mjs +78 -0
  84. package/bizar-dash/src/server/routes/memory.mjs +658 -0
  85. package/bizar-dash/src/server/routes/misc.mjs +89 -0
  86. package/bizar-dash/src/server/routes/mods.mjs +359 -0
  87. package/bizar-dash/src/server/routes/notifications.mjs +54 -0
  88. package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
  89. package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
  90. package/bizar-dash/src/server/routes/overview.mjs +91 -0
  91. package/bizar-dash/src/server/routes/pair.mjs +67 -0
  92. package/bizar-dash/src/server/routes/projects.mjs +175 -0
  93. package/bizar-dash/src/server/routes/providers.mjs +47 -0
  94. package/bizar-dash/src/server/routes/schedules.mjs +104 -0
  95. package/bizar-dash/src/server/routes/settings.mjs +51 -0
  96. package/bizar-dash/src/server/routes/skills.mjs +76 -0
  97. package/bizar-dash/src/server/routes/tasks.mjs +468 -0
  98. package/bizar-dash/src/server/routes/themes.mjs +51 -0
  99. package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
  100. package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
  101. package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
  102. package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
  103. package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
  104. package/bizar-dash/src/server/schedules-runner.mjs +369 -0
  105. package/bizar-dash/src/server/schedules-store.mjs +268 -0
  106. package/bizar-dash/src/server/search-store.mjs +220 -0
  107. package/bizar-dash/src/server/serve-info.mjs +684 -0
  108. package/bizar-dash/src/server/server.mjs +796 -0
  109. package/bizar-dash/src/server/settings-store.mjs +45 -0
  110. package/bizar-dash/src/server/skills-store.mjs +300 -0
  111. package/bizar-dash/src/server/state.mjs +437 -0
  112. package/bizar-dash/src/server/tailscale-store.mjs +113 -0
  113. package/bizar-dash/src/server/task-delegator.mjs +824 -0
  114. package/bizar-dash/src/server/tasks-store.mjs +508 -0
  115. package/bizar-dash/src/server/tui.mjs +923 -0
  116. package/bizar-dash/src/server/update-store.mjs +168 -0
  117. package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
  118. package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
  119. package/bizar-dash/src/server/watcher.mjs +81 -0
  120. package/bizar-dash/src/server/yaml.mjs +238 -0
  121. package/bizar-dash/src/web/App.tsx +718 -0
  122. package/bizar-dash/src/web/MobileApp.tsx +408 -0
  123. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
  124. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
  125. package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
  126. package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
  127. package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
  128. package/bizar-dash/src/web/components/Button.tsx +56 -0
  129. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
  130. package/bizar-dash/src/web/components/Card.tsx +40 -0
  131. package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
  132. package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
  133. package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
  134. package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
  135. package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
  136. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
  137. package/bizar-dash/src/web/components/Modal.tsx +215 -0
  138. package/bizar-dash/src/web/components/Notifications.tsx +241 -0
  139. package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
  140. package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
  141. package/bizar-dash/src/web/components/Spinner.tsx +19 -0
  142. package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
  143. package/bizar-dash/src/web/components/Tag.tsx +28 -0
  144. package/bizar-dash/src/web/components/Toast.tsx +150 -0
  145. package/bizar-dash/src/web/components/Topbar.tsx +368 -0
  146. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
  147. package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
  148. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
  149. package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
  150. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
  151. package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
  152. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
  153. package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
  154. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
  155. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
  156. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
  157. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
  158. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
  159. package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
  160. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
  161. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
  162. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
  163. package/bizar-dash/src/web/components/chat/index.ts +20 -0
  164. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
  165. package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
  166. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
  167. package/bizar-dash/src/web/index.html +37 -0
  168. package/bizar-dash/src/web/lib/api.ts +193 -0
  169. package/bizar-dash/src/web/lib/markdown.tsx +59 -0
  170. package/bizar-dash/src/web/lib/types.ts +668 -0
  171. package/bizar-dash/src/web/lib/utils.ts +114 -0
  172. package/bizar-dash/src/web/lib/ws.ts +176 -0
  173. package/bizar-dash/src/web/main.tsx +34 -0
  174. package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
  175. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
  176. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
  177. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
  178. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
  179. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
  180. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
  181. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
  182. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
  183. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
  184. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
  185. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
  186. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
  187. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
  188. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
  189. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
  190. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
  191. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
  192. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
  193. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
  194. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
  195. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
  196. package/bizar-dash/src/web/mobile.html +20 -0
  197. package/bizar-dash/src/web/mobile.tsx +13 -0
  198. package/bizar-dash/src/web/styles/chat.css +198 -0
  199. package/bizar-dash/src/web/styles/glyphs.css +960 -0
  200. package/bizar-dash/src/web/styles/main.css +8855 -0
  201. package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
  202. package/bizar-dash/src/web/styles/mobile.css +1960 -0
  203. package/bizar-dash/src/web/views/Activity.tsx +1203 -0
  204. package/bizar-dash/src/web/views/Agents.tsx +701 -0
  205. package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
  206. package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
  207. package/bizar-dash/src/web/views/Chat.tsx +190 -0
  208. package/bizar-dash/src/web/views/Config.tsx +2063 -0
  209. package/bizar-dash/src/web/views/History.tsx +335 -0
  210. package/bizar-dash/src/web/views/ModView.tsx +207 -0
  211. package/bizar-dash/src/web/views/Mods.tsx +693 -0
  212. package/bizar-dash/src/web/views/Overview.tsx +812 -0
  213. package/bizar-dash/src/web/views/Providers.tsx +302 -0
  214. package/bizar-dash/src/web/views/Schedules.tsx +802 -0
  215. package/bizar-dash/src/web/views/Settings.tsx +1787 -0
  216. package/bizar-dash/src/web/views/Skills.tsx +431 -0
  217. package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
  218. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
  219. package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
  220. package/bizar-dash/templates/mod/FORMAT.md +76 -0
  221. package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
  222. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
  223. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
  224. package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
  225. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
  226. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
  227. package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
  228. package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
  229. package/bizar-dash/tests/memory-cli.test.mjs +542 -0
  230. package/bizar-dash/tests/memory-config.test.mjs +422 -0
  231. package/bizar-dash/tests/memory-git.test.mjs +246 -0
  232. package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
  233. package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
  234. package/bizar-dash/tests/memory-schema.test.mjs +198 -0
  235. package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
  236. package/bizar-dash/tests/memory-store.test.mjs +390 -0
  237. package/bizar-dash/tests/memory-sync.test.mjs +141 -0
  238. package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
  239. package/bizar-dash/tests/mod-security.test.mjs +273 -0
  240. package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
  241. package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
  242. package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
  243. package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
  244. package/bizar-dash/tests/path-safe.test.mjs +108 -0
  245. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
  246. package/bizar-dash/tests/smoke-v2.mjs +212 -0
  247. package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
  248. package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
  249. package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
  250. package/bizar-dash/tests/yaml.test.mjs +128 -0
  251. package/bizar-dash/tsconfig.json +23 -0
  252. package/bizar-dash/vite.config.ts +27 -0
  253. package/cli/atomic.mjs +73 -0
  254. package/cli/banner.mjs +1 -1
  255. package/cli/bin.mjs +44 -47
  256. package/cli/bootstrap.mjs +1 -1
  257. package/cli/copy.mjs +25 -16
  258. package/cli/dev-link.mjs +17 -1
  259. package/cli/doctor.mjs +4 -4
  260. package/cli/doctor.test.mjs +2 -2
  261. package/cli/init.mjs +135 -1
  262. package/cli/install.mjs +31 -39
  263. package/cli/memory-constants.mjs +59 -0
  264. package/cli/memory.mjs +1436 -0
  265. package/cli/service.mjs +5 -6
  266. package/cli/utils.mjs +6 -3
  267. package/config/AGENTS.md +7 -7
  268. package/config/agents/_shared/AGENT_BASELINE.md +59 -61
  269. package/config/opencode.json +13 -38
  270. package/config/skills/memory-protocol/SKILL.md +105 -0
  271. package/config/skills/obsidian/SKILL.md +270 -65
  272. package/install.sh +59 -19
  273. package/package.json +49 -14
  274. package/packages/sdk/LICENSE +21 -0
  275. package/packages/sdk/README.md +80 -0
  276. package/packages/sdk/src/client.ts +183 -0
  277. package/packages/sdk/src/errors.ts +120 -0
  278. package/packages/sdk/src/events.ts +153 -0
  279. package/packages/sdk/src/index.ts +63 -0
  280. package/packages/sdk/src/types.ts +176 -0
  281. package/packages/sdk/src/version.ts +4 -0
  282. package/packages/sdk/tests/client.test.ts +217 -0
  283. package/packages/sdk/tests/errors.test.ts +108 -0
  284. package/packages/sdk/tests/events.test.ts +139 -0
  285. package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
  286. package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
  287. package/packages/sdk/tsconfig.json +25 -0
  288. package/packages/sdk/vitest.config.ts +9 -0
  289. package/plugins/bizar/LICENSE +21 -0
  290. package/plugins/bizar/README.md +448 -0
  291. package/plugins/bizar/dist/index.js +29901 -0
  292. package/plugins/bizar/index.ts +1491 -0
  293. package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
  294. package/plugins/bizar/src/background-state.ts +551 -0
  295. package/plugins/bizar/src/background.ts +1250 -0
  296. package/plugins/bizar/src/commands-impl.ts +464 -0
  297. package/plugins/bizar/src/commands.ts +1168 -0
  298. package/plugins/bizar/src/dashboard-client.ts +233 -0
  299. package/plugins/bizar/src/event-stream.ts +606 -0
  300. package/plugins/bizar/src/fingerprint.ts +120 -0
  301. package/plugins/bizar/src/handoff.ts +79 -0
  302. package/plugins/bizar/src/http-client.ts +467 -0
  303. package/plugins/bizar/src/key-rotation.ts +218 -0
  304. package/plugins/bizar/src/logger.ts +144 -0
  305. package/plugins/bizar/src/loop.ts +176 -0
  306. package/plugins/bizar/src/opencode-runner.ts +390 -0
  307. package/plugins/bizar/src/options.ts +421 -0
  308. package/plugins/bizar/src/plan-fs.ts +323 -0
  309. package/plugins/bizar/src/reasoning-clean.ts +454 -0
  310. package/plugins/bizar/src/report.ts +178 -0
  311. package/plugins/bizar/src/research-prompt.ts +35 -0
  312. package/plugins/bizar/src/serve-info.ts +228 -0
  313. package/plugins/bizar/src/serve.ts +496 -0
  314. package/plugins/bizar/src/settings.ts +349 -0
  315. package/plugins/bizar/src/state.ts +298 -0
  316. package/plugins/bizar/src/tools/bg-collect.ts +104 -0
  317. package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
  318. package/plugins/bizar/src/tools/bg-kill.ts +87 -0
  319. package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
  320. package/plugins/bizar/src/tools/bg-status.ts +99 -0
  321. package/plugins/bizar/src/tools/open-kb.ts +191 -0
  322. package/plugins/bizar/src/tools/plan-action.ts +767 -0
  323. package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
  324. package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
  325. package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
  326. package/plugins/bizar/tests/background-state.test.ts +277 -0
  327. package/plugins/bizar/tests/background.test.ts +402 -0
  328. package/plugins/bizar/tests/block.test.ts +195 -0
  329. package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
  330. package/plugins/bizar/tests/commands-impl.test.ts +442 -0
  331. package/plugins/bizar/tests/commands.test.ts +584 -0
  332. package/plugins/bizar/tests/config.test.ts +122 -0
  333. package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
  334. package/plugins/bizar/tests/dispose.test.ts +336 -0
  335. package/plugins/bizar/tests/event-stream.test.ts +409 -0
  336. package/plugins/bizar/tests/event.test.ts +262 -0
  337. package/plugins/bizar/tests/fingerprint.test.ts +162 -0
  338. package/plugins/bizar/tests/http-client.test.ts +404 -0
  339. package/plugins/bizar/tests/init-helpers.test.ts +203 -0
  340. package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
  341. package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
  342. package/plugins/bizar/tests/key-rotation.test.ts +396 -0
  343. package/plugins/bizar/tests/loop.test.ts +397 -0
  344. package/plugins/bizar/tests/options.test.ts +276 -0
  345. package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
  346. package/plugins/bizar/tests/serve.test.ts +339 -0
  347. package/plugins/bizar/tests/settings.test.ts +351 -0
  348. package/plugins/bizar/tests/stall-think.test.ts +750 -0
  349. package/plugins/bizar/tests/state.test.ts +276 -0
  350. package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
  351. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
  352. package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
  353. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
  354. package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
  355. package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
  356. package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
  357. package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
  358. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
  359. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
  360. package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
  361. package/plugins/bizar/tsconfig.json +29 -0
@@ -0,0 +1,658 @@
1
+ /**
2
+ * src/server/routes/memory.mjs
3
+ *
4
+ * REST surface for the Bizar Memory Service.
5
+ *
6
+ * Mounted at /api/memory/*.
7
+ *
8
+ * All note shapes returned are the "rich shape":
9
+ * { relPath, frontmatter, body, raw, mtime, size, schemaValid }
10
+ */
11
+
12
+ import { Router } from 'express';
13
+ import { join, dirname } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
16
+
17
+ const SERVER_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
18
+ const memoryStore = await import(`${SERVER_ROOT}/memory-store.mjs`).then((m) => m);
19
+ const memorySchema = await import(`${SERVER_ROOT}/memory-schema.mjs`).then((m) => m);
20
+ const memorySecrets = await import(`${SERVER_ROOT}/memory-secrets.mjs`).then((m) => m);
21
+ const memoryGit = await import(`${SERVER_ROOT}/memory-git.mjs`).then((m) => m);
22
+ const { atomicWriteJson } = await import(`${SERVER_ROOT}/../../../cli/atomic.mjs`).then((m) => m);
23
+
24
+ const { wrap } = await import('./_shared.mjs').then((m) => m);
25
+
26
+ // Lazy import to avoid circular dependency with memory-lightrag.mjs
27
+ async function getMemoryLightrag() {
28
+ return import(`${SERVER_ROOT}/memory-lightrag.mjs`).then((m) => m);
29
+ }
30
+
31
+ /**
32
+ * Redact apiKey in the lightrag block before sending to the UI.
33
+ * apiKey '***' means it was already redacted.
34
+ * Any non-empty string that is not '***' is replaced with '***'.
35
+ */
36
+ function redactLightRAGConfig(config) {
37
+ if (!config || !config.lightrag) return config;
38
+ const { lightrag, ...rest } = config;
39
+ const redactedLightrag = {
40
+ ...lightrag,
41
+ apiKey: lightrag.apiKey && lightrag.apiKey !== '' && lightrag.apiKey !== '***'
42
+ ? '***'
43
+ : lightrag.apiKey || undefined,
44
+ };
45
+ return { ...rest, lightrag: redactedLightrag };
46
+ }
47
+
48
+ export function createMemoryRouter({ projectRoot }) {
49
+ const router = Router();
50
+
51
+ // GET /memory/status
52
+ router.get('/memory/status', wrap(async (_req, res) => {
53
+ const { loadConfig, resolveVault, listNotes } = memoryStore;
54
+ const { isGitInstalled, status: gitStatus } = memoryGit;
55
+
56
+ const { config, exists } = loadConfig(projectRoot);
57
+ if (!exists) {
58
+ res.json({ initialized: false, mode: null, projectId: null, vaultRoot: null });
59
+ return;
60
+ }
61
+
62
+ const { vaultRoot, mode, projectId, branch } = resolveVault(projectRoot);
63
+ const notes = existsSync(vaultRoot) ? listNotes(projectRoot) : [];
64
+
65
+ let gitClean = null;
66
+ let gitBranch = null;
67
+ if ((mode === 'managed' || mode === 'linked') && existsSync(vaultRoot) && isGitInstalled()) {
68
+ const gs = gitStatus(vaultRoot);
69
+ gitClean = gs.clean;
70
+ gitBranch = gs.branch;
71
+ }
72
+
73
+ const cacheDir = join(projectRoot, '.bizar', 'memory-cache');
74
+ const reindexMarker = join(cacheDir, 'last-reindex-attempt.json');
75
+ let lastSecretScan = null;
76
+ if (existsSync(reindexMarker)) {
77
+ try {
78
+ const data = JSON.parse(readFileSync(reindexMarker, 'utf8'));
79
+ lastSecretScan = data.attemptedAt || null;
80
+ } catch { /* ignore */ }
81
+ }
82
+
83
+ res.json({
84
+ initialized: true,
85
+ mode,
86
+ projectId,
87
+ vaultRoot,
88
+ branch: gitBranch || branch,
89
+ gitClean,
90
+ noteCount: notes.length,
91
+ lastSecretScan,
92
+ lightrag: { enabled: false, stub: true },
93
+ });
94
+ }));
95
+
96
+ // POST /memory/init
97
+ router.post('/memory/init', wrap(async (_req, res) => {
98
+ const { initVault } = memoryStore;
99
+ const result = initVault(projectRoot);
100
+ res.json({ ok: true, vaultRoot: result.vaultRoot, created: result.created });
101
+ }));
102
+
103
+ // GET /memory/config
104
+ router.get('/memory/config', wrap(async (_req, res) => {
105
+ const { loadConfig } = memoryStore;
106
+ const { config, exists } = loadConfig(projectRoot);
107
+ res.json({ exists, config: redactLightRAGConfig(config) });
108
+ }));
109
+
110
+ // POST /memory/config
111
+ router.post('/memory/config', wrap(async (req, res) => {
112
+ const { loadConfig, saveConfig } = memoryStore;
113
+
114
+ // ── Patch mode: merge only the lightrag block ──────────────────────────
115
+ if (req.body && req.body.patch === true) {
116
+ const { writeLightRAGConfig } = await getMemoryLightrag();
117
+ const { lightrag, patch: _patch, ...restPatch } = req.body;
118
+
119
+ // Validate no unexpected top-level fields in patch
120
+ if (Object.keys(restPatch).length > 0) {
121
+ res.status(400).json({ error: 'bad_request', message: 'patch mode only accepts a "lightrag" field' });
122
+ return;
123
+ }
124
+ if (!lightrag || typeof lightrag !== 'object') {
125
+ res.status(400).json({ error: 'bad_request', message: 'patch mode requires a "lightrag" object' });
126
+ return;
127
+ }
128
+
129
+ const result = writeLightRAGConfig(projectRoot, lightrag);
130
+ if (!result.ok) {
131
+ res.status(400).json({ error: 'validation_error', message: result.error });
132
+ return;
133
+ }
134
+
135
+ // Return full config with redacted lightrag block
136
+ const { config: full } = loadConfig(projectRoot);
137
+ const redacted = redactLightRAGConfig(full);
138
+ res.json({ ok: true, config: redacted });
139
+ return;
140
+ }
141
+
142
+ // ── Full replace mode (existing behaviour) ───────────────────────────────
143
+ const { config: newConfig, confirm } = req.body || {};
144
+ if (!newConfig || typeof newConfig !== 'object') {
145
+ res.status(400).json({ error: 'bad_request', message: 'body must include config object' });
146
+ return;
147
+ }
148
+
149
+ const { config: existing } = loadConfig(projectRoot);
150
+
151
+ // Changing mode requires confirm flag
152
+ if (newConfig.mode && newConfig.mode !== existing.mode && !confirm) {
153
+ res.status(400).json({
154
+ error: 'confirm_required',
155
+ message: 'changing mode requires confirm: true',
156
+ });
157
+ return;
158
+ }
159
+
160
+ const merged = { ...existing, ...newConfig };
161
+ const result = saveConfig(projectRoot, merged);
162
+ if (!result.ok) {
163
+ res.status(500).json({ error: 'write_failed', message: result.error });
164
+ return;
165
+ }
166
+ const redacted = redactLightRAGConfig(merged);
167
+ res.json({ ok: true, config: redacted });
168
+ }));
169
+
170
+ // GET /memory/notes
171
+ router.get('/memory/notes', wrap(async (_req, res) => {
172
+ const { listNotes } = memoryStore;
173
+ const notes = listNotes(projectRoot);
174
+ res.json({ notes });
175
+ }));
176
+
177
+ // POST /memory/notes
178
+ router.post('/memory/notes', wrap(async (req, res) => {
179
+ const { writeNote } = memoryStore;
180
+ const { path: relPath, frontmatter, body } = req.body || {};
181
+
182
+ if (!relPath || typeof relPath !== 'string') {
183
+ res.status(400).json({ error: 'bad_request', message: 'path is required' });
184
+ return;
185
+ }
186
+ if (!relPath.endsWith('.md')) {
187
+ res.status(400).json({ error: 'bad_request', message: 'path must end in .md' });
188
+ return;
189
+ }
190
+
191
+ try {
192
+ const note = writeNote(projectRoot, relPath, { frontmatter: frontmatter || {}, body: body || '' });
193
+ res.status(201).json(note);
194
+ } catch (err) {
195
+ if (err.code === 'SCHEMA_VALIDATION_FAILED' || err.code === 'SECRET_DETECTED') {
196
+ res.status(400).json({ error: err.code, message: err.message, findings: err.findings });
197
+ } else {
198
+ res.status(400).json({ error: 'bad_request', message: err.message });
199
+ }
200
+ }
201
+ }));
202
+
203
+ // GET /memory/notes/* — single note
204
+ router.get('/memory/notes/*', wrap(async (req, res) => {
205
+ const { readNote } = memoryStore;
206
+ const relPath = req.params[0];
207
+ const note = readNote(projectRoot, relPath);
208
+ if (!note) {
209
+ res.status(404).json({ error: 'not_found' });
210
+ return;
211
+ }
212
+ res.json(note);
213
+ }));
214
+
215
+ // DELETE /memory/notes/*
216
+ router.delete('/memory/notes/*', wrap(async (req, res) => {
217
+ const { deleteNote } = memoryStore;
218
+ const relPath = req.params[0];
219
+ const ok = deleteNote(projectRoot, relPath);
220
+ if (!ok) {
221
+ res.status(404).json({ error: 'not_found' });
222
+ return;
223
+ }
224
+ res.status(204).end();
225
+ }));
226
+
227
+ // GET /memory/search?q=...
228
+ router.get('/memory/search', wrap(async (req, res) => {
229
+ const { searchVault } = memoryStore;
230
+ const q = req.query.q || '';
231
+ const limit = Math.min(parseInt(req.query.limit, 10) || 25, 100);
232
+ const results = searchVault(projectRoot, q, { limit });
233
+ res.json({ query: q, results });
234
+ }));
235
+
236
+ // POST /memory/schema/validate — validate a single note
237
+ router.post('/memory/schema/validate', wrap(async (req, res) => {
238
+ const { readNote } = memoryStore;
239
+ const { path: relPath } = req.body || {};
240
+ if (!relPath) {
241
+ res.status(400).json({ error: 'bad_request', message: 'path is required' });
242
+ return;
243
+ }
244
+ const note = readNote(projectRoot, relPath);
245
+ if (!note) {
246
+ res.status(404).json({ error: 'not_found' });
247
+ return;
248
+ }
249
+ const result = memorySchema.validateNote(note.frontmatter, note.body);
250
+ res.json({ relPath, ...result });
251
+ }));
252
+
253
+ // POST /memory/secrets/scan — scan a single note
254
+ router.post('/memory/secrets/scan', wrap(async (req, res) => {
255
+ const { scanForSecrets } = memoryStore;
256
+ const { path: relPath } = req.body || {};
257
+ if (!relPath) {
258
+ res.status(400).json({ error: 'bad_request', message: 'path is required' });
259
+ return;
260
+ }
261
+ const result = scanForSecrets(projectRoot, relPath);
262
+ res.json({ relPath, ...result });
263
+ }));
264
+
265
+ // GET /memory/git/status
266
+ router.get('/memory/git/status', wrap(async (_req, res) => {
267
+ const { resolveVault } = memoryStore;
268
+ const { isGitInstalled, status: gitStatus } = memoryGit;
269
+ const { vaultRoot, mode } = resolveVault(projectRoot);
270
+
271
+ if (mode === 'local-only') {
272
+ res.json({ ok: true, mode: 'local-only' });
273
+ return;
274
+ }
275
+
276
+ if (!isGitInstalled()) {
277
+ res.status(503).json({ error: 'git_not_installed' });
278
+ return;
279
+ }
280
+
281
+ const gs = gitStatus(vaultRoot);
282
+ res.json({ ok: true, mode, ...gs });
283
+ }));
284
+
285
+ // POST /memory/git/pull
286
+ router.post('/memory/git/pull', wrap(async (_req, res) => {
287
+ const { resolveVault } = memoryStore;
288
+ const { pull, isGitInstalled } = memoryGit;
289
+ const { vaultRoot, mode } = resolveVault(projectRoot);
290
+
291
+ if (mode === 'local-only') {
292
+ res.status(400).json({ error: 'local_only_mode' });
293
+ return;
294
+ }
295
+ if (!isGitInstalled()) {
296
+ res.status(503).json({ error: 'git_not_installed' });
297
+ return;
298
+ }
299
+
300
+ const result = pull(vaultRoot);
301
+ if (!result.ok) {
302
+ res.status(500).json({ error: 'pull_failed', message: result.error });
303
+ return;
304
+ }
305
+ res.json({ ok: true, output: result.output });
306
+ }));
307
+
308
+ // POST /memory/git/commit — body { message? }
309
+ router.post('/memory/git/commit', wrap(async (req, res) => {
310
+ const { resolveVault } = memoryStore;
311
+ const { commit: gitCommit, addAll, isGitInstalled } = memoryGit;
312
+ const { vaultRoot, mode } = resolveVault(projectRoot);
313
+
314
+ if (mode === 'local-only') {
315
+ res.status(400).json({ error: 'local_only_mode' });
316
+ return;
317
+ }
318
+ if (!isGitInstalled()) {
319
+ res.status(503).json({ error: 'git_not_installed' });
320
+ return;
321
+ }
322
+
323
+ const addResult = addAll(vaultRoot);
324
+ if (!addResult.ok) {
325
+ res.status(500).json({ error: 'git_add_failed', message: addResult.error });
326
+ return;
327
+ }
328
+
329
+ const message = req.body?.message || `[memory-sync] ${new Date().toISOString().replace(/T.*/, '')} vault sync`;
330
+ const result = gitCommit(vaultRoot, message);
331
+ if (!result.ok) {
332
+ res.status(500).json({ error: 'commit_failed', message: result.error });
333
+ return;
334
+ }
335
+ res.json({ ok: true, message });
336
+ }));
337
+
338
+ // POST /memory/git/push
339
+ router.post('/memory/git/push', wrap(async (_req, res) => {
340
+ const { resolveVault, loadConfig } = memoryStore;
341
+ const { push: gitPush, isGitInstalled } = memoryGit;
342
+ const { vaultRoot, mode, branch } = resolveVault(projectRoot);
343
+ const { config } = loadConfig(projectRoot);
344
+
345
+ if (mode === 'local-only') {
346
+ res.status(400).json({ error: 'local_only_mode' });
347
+ return;
348
+ }
349
+ if (!isGitInstalled()) {
350
+ res.status(503).json({ error: 'git_not_installed' });
351
+ return;
352
+ }
353
+
354
+ const result = gitPush(vaultRoot, { remote: config.gitRemote || 'origin', branch });
355
+ if (!result.ok) {
356
+ res.status(500).json({ error: 'push_failed', message: result.error });
357
+ return;
358
+ }
359
+ res.json({ ok: true });
360
+ }));
361
+
362
+ // POST /memory/git/sync — full orchestrator
363
+ router.post('/memory/git/sync', wrap(async (req, res) => {
364
+ const { resolveVault, listNotes, validateAll, scanForSecrets } = memoryStore;
365
+ const { pull, addAll, commit: gitCommit, push: gitPush, status: gitStatus, acquireLock, isGitInstalled } = memoryGit;
366
+ const { vaultRoot, mode, branch } = resolveVault(projectRoot);
367
+ const { config } = memoryStore.loadConfig(projectRoot);
368
+
369
+ if (mode === 'local-only') {
370
+ res.status(400).json({ error: 'local_only_mode' });
371
+ return;
372
+ }
373
+ if (!isGitInstalled()) {
374
+ res.status(503).json({ error: 'git_not_installed' });
375
+ return;
376
+ }
377
+
378
+ const lock = acquireLock(vaultRoot);
379
+ if (lock.error) {
380
+ res.status(423).json({ error: 'locked', message: 'vault is locked by another process' });
381
+ return;
382
+ }
383
+
384
+ let pushRequested = req.body?.push === true;
385
+
386
+ try {
387
+ // Pull
388
+ const pullResult = pull(vaultRoot);
389
+ if (!pullResult.ok) {
390
+ // Non-fatal — may be up to date or have no remote
391
+ }
392
+
393
+ // Check status
394
+ const gs = gitStatus(vaultRoot);
395
+ if (gs.clean) {
396
+ res.json({ ok: true, message: 'nothing to sync', clean: true });
397
+ return;
398
+ }
399
+
400
+ // Validate changed notes
401
+ const allNotes = listNotes(projectRoot);
402
+ const changedSet = new Set([...gs.modified, ...gs.untracked]);
403
+ const invalidNotes = allNotes.filter((n) => !n.schemaValid && changedSet.has(n.relPath));
404
+ if (invalidNotes.length > 0) {
405
+ res.status(400).json({
406
+ error: 'schema_validation_failed',
407
+ invalidNotes: invalidNotes.map((n) => ({ relPath: n.relPath })),
408
+ });
409
+ return;
410
+ }
411
+
412
+ // Secret scan changed files
413
+ const allFindings = [];
414
+ for (const relPath of changedSet) {
415
+ const result = scanForSecrets(projectRoot, relPath);
416
+ if (!result.safe) allFindings.push(...result.findings);
417
+ }
418
+
419
+ const highFindings = allFindings.filter((f) => f.severity === 'HIGH');
420
+ if (highFindings.length > 0) {
421
+ res.status(422).json({
422
+ error: 'secrets_blocked',
423
+ findings: highFindings,
424
+ });
425
+ return;
426
+ }
427
+
428
+ // Add all
429
+ const addResult = addAll(vaultRoot);
430
+ if (!addResult.ok) {
431
+ res.status(500).json({ error: 'git_add_failed' });
432
+ return;
433
+ }
434
+
435
+ // Commit
436
+ const date = new Date().toISOString().replace(/T.*/, '');
437
+ const summary = allNotes[0]?.relPath?.slice(0, 60) || 'vault sync';
438
+ const message = `[memory-sync] ${date} ${summary}`;
439
+ const commitResult = gitCommit(vaultRoot, message);
440
+ if (!commitResult.ok) {
441
+ res.status(500).json({ error: 'commit_failed', message: commitResult.error });
442
+ return;
443
+ }
444
+
445
+ // Push if requested
446
+ if (pushRequested && config.gitRemote) {
447
+ const pushResult = gitPush(vaultRoot, { remote: config.gitRemote, branch });
448
+ if (!pushResult.ok) {
449
+ res.status(500).json({ error: 'push_failed', message: pushResult.error });
450
+ return;
451
+ }
452
+ }
453
+
454
+ res.json({
455
+ ok: true,
456
+ message,
457
+ pushed: pushRequested && !!config.gitRemote,
458
+ mediumFindings: allFindings.filter((f) => f.severity === 'MEDIUM'),
459
+ });
460
+ } finally {
461
+ lock.release();
462
+ }
463
+ }));
464
+
465
+ // GET /memory/conflicts
466
+ router.get('/memory/conflicts', wrap(async (_req, res) => {
467
+ const { listNotes, readNote } = memoryStore;
468
+ const { vaultRoot } = memoryStore.resolveVault(projectRoot);
469
+ const notes = listNotes(projectRoot);
470
+ const conflicts = [];
471
+
472
+ for (const note of notes) {
473
+ if (note.frontmatter?.status === 'conflict') {
474
+ conflicts.push({ relPath: note.relPath, reason: 'frontmatter: status=conflict' });
475
+ }
476
+ }
477
+
478
+ // Check git conflict markers
479
+ try {
480
+ const { readFileSync: rf } = await import('node:fs');
481
+ for (const note of notes) {
482
+ const filePath = join(vaultRoot, note.relPath);
483
+ const content = rf(filePath, 'utf8');
484
+ if (/^<{7}\s|^={7}\s|>{7}\s/.test(content)) {
485
+ conflicts.push({ relPath: note.relPath, reason: 'git conflict markers' });
486
+ }
487
+ }
488
+ } catch { /* ignore */ }
489
+
490
+ res.json({ conflicts });
491
+ }));
492
+
493
+ // POST /memory/reindex — v4.1.0 real LightRAG population
494
+ router.post('/memory/reindex', wrap(async (_req, res) => {
495
+ const { reindexVault } = memoryStore;
496
+ const result = await reindexVault(projectRoot, {});
497
+ if (!result.ok && result.inserted === 0) {
498
+ res.status(422).json(result);
499
+ return;
500
+ }
501
+ res.json(result);
502
+ }));
503
+
504
+ // GET /memory/query?q=...&topK=10 — merged lexical + semantic search
505
+ router.get('/memory/query', wrap(async (req, res) => {
506
+ const q = String(req.query.q || '').trim();
507
+ if (!q) {
508
+ res.status(400).json({ error: 'q required' });
509
+ return;
510
+ }
511
+ const topK = Math.min(parseInt(req.query.topK, 10) || 10, 50);
512
+ const { searchVault, queryLightRAG } = memoryStore;
513
+ const lexical = searchVault(projectRoot, q, { limit: topK });
514
+ let semantic = null;
515
+ try {
516
+ semantic = await queryLightRAG(projectRoot, q, { topK });
517
+ } catch (err) {
518
+ semantic = { ok: false, error: err.message };
519
+ }
520
+ res.json({ ok: true, q, lexical, semantic });
521
+ }));
522
+
523
+ // GET /memory/doctor
524
+ router.get('/memory/doctor', wrap(async (_req, res) => {
525
+ const { loadConfig, resolveVault, listNotes, validateAll } = memoryStore;
526
+ const { isGitInstalled, status: gitStatus } = memoryGit;
527
+
528
+ const checks = [];
529
+
530
+ checks.push({
531
+ name: 'git_binary',
532
+ pass: isGitInstalled(),
533
+ detail: isGitInstalled() ? 'present' : 'missing',
534
+ });
535
+
536
+ const { config, exists: configExists } = loadConfig(projectRoot);
537
+ checks.push({ name: 'config_exists', pass: configExists, detail: configExists ? 'present' : 'missing' });
538
+
539
+ if (configExists) {
540
+ const { vaultRoot } = resolveVault(projectRoot);
541
+ checks.push({ name: 'vault_exists', pass: existsSync(vaultRoot), detail: vaultRoot });
542
+
543
+ if (config.mode === 'managed' || config.mode === 'linked') {
544
+ if (existsSync(vaultRoot) && isGitInstalled()) {
545
+ const gs = gitStatus(vaultRoot);
546
+ checks.push({ name: 'git_repo', pass: gs.ok, detail: gs.ok ? 'yes' : 'no' });
547
+ }
548
+ }
549
+
550
+ const notes = listNotes(projectRoot);
551
+ checks.push({ name: 'note_count', pass: notes.length > 0, detail: `${notes.length} note(s)` });
552
+
553
+ const validationResults = validateAll(projectRoot);
554
+ checks.push({
555
+ name: 'schema_valid',
556
+ pass: validationResults.length === 0,
557
+ detail: validationResults.length === 0 ? 'all valid' : `${validationResults.length} invalid`,
558
+ });
559
+ }
560
+
561
+ const allPassed = checks.every((c) => c.pass);
562
+ res.json({ ok: allPassed, checks });
563
+ }));
564
+
565
+ // GET /memory/lightrag/status
566
+ router.get('/memory/lightrag/status', wrap(async (_req, res) => {
567
+ const { resolveLightRAGConfig, isRunning } = await getMemoryLightrag();
568
+ const cfg = resolveLightRAGConfig(projectRoot);
569
+ const pidFile = join(cfg.workingDir, 'lightrag.pid');
570
+
571
+ // Inline readPidAlive logic
572
+ let pid = null;
573
+ let alive = false;
574
+ if (existsSync(pidFile)) {
575
+ try {
576
+ pid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);
577
+ if (Number.isFinite(pid) && pid > 0) {
578
+ try {
579
+ process.kill(pid, 0);
580
+ alive = true;
581
+ } catch {
582
+ alive = false;
583
+ }
584
+ } else {
585
+ pid = null;
586
+ }
587
+ } catch {
588
+ pid = null;
589
+ }
590
+ }
591
+
592
+ const running = alive && (await isRunning(cfg));
593
+ const logFile = join(cfg.workingDir, 'lightrag.log');
594
+ const logTail = [];
595
+
596
+ if (existsSync(logFile)) {
597
+ try {
598
+ const content = readFileSync(logFile, 'utf8');
599
+ const lines = content.split('\n');
600
+ logTail.push(...lines.slice(-30));
601
+ } catch {}
602
+ }
603
+
604
+ let lastError = null;
605
+ if (!running && pid !== null) {
606
+ lastError = 'process not responding to health checks';
607
+ }
608
+
609
+ res.json({
610
+ running,
611
+ pid: running ? pid : null,
612
+ host: cfg.host,
613
+ port: cfg.port,
614
+ llmBinding: cfg.llmBinding,
615
+ embeddingBinding: cfg.embeddingBinding,
616
+ llmModel: cfg.llmModel,
617
+ embeddingModel: cfg.embeddingModel,
618
+ lastError,
619
+ logTail,
620
+ });
621
+ }));
622
+
623
+ // POST /memory/lightrag/start
624
+ router.post('/memory/lightrag/start', wrap(async (_req, res) => {
625
+ const { resolveLightRAGConfig, startServer } = await getMemoryLightrag();
626
+ const cfg = resolveLightRAGConfig(projectRoot);
627
+ const result = await startServer(cfg, {});
628
+ res.json(result);
629
+ }));
630
+
631
+ // POST /memory/lightrag/stop
632
+ router.post('/memory/lightrag/stop', wrap(async (_req, res) => {
633
+ const { resolveLightRAGConfig, stopServer } = await getMemoryLightrag();
634
+ const cfg = resolveLightRAGConfig(projectRoot);
635
+ const result = await stopServer(cfg, {});
636
+ res.json(result);
637
+ }));
638
+
639
+ // GET /memory/lightrag/log
640
+ router.get('/memory/lightrag/log', wrap(async (_req, res) => {
641
+ const { resolveLightRAGConfig } = await getMemoryLightrag();
642
+ const cfg = resolveLightRAGConfig(projectRoot);
643
+ const logFile = join(cfg.workingDir, 'lightrag.log');
644
+ if (!existsSync(logFile)) {
645
+ res.json({ lines: [] });
646
+ return;
647
+ }
648
+ try {
649
+ const content = readFileSync(logFile, 'utf8');
650
+ const lines = content.split('\n');
651
+ res.json({ lines: lines.slice(-200) });
652
+ } catch (err) {
653
+ res.status(500).json({ error: err.message });
654
+ }
655
+ }));
656
+
657
+ return router;
658
+ }