@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.
- package/README.md +83 -16
- package/bizar-dash/.bizar/activity.log +3 -0
- package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
- package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
- package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
- package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
- package/bizar-dash/.bizar/graph/graph.html +307 -0
- package/bizar-dash/.bizar/graph/graph.json +40267 -0
- package/bizar-dash/.bizar/graph/manifest.json +762 -0
- package/bizar-dash/.graphifyignore +40 -0
- package/bizar-dash/.obsidian/README.md +14 -0
- package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
- package/bizar-dash/.obsidian/vault.json +7 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
- package/bizar-dash/CHANGELOG.md +474 -0
- package/bizar-dash/DESIGN.md +136 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
- package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
- package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
- package/bizar-dash/dist/index.html +40 -0
- package/bizar-dash/dist/mobile.html +22 -0
- package/bizar-dash/plans/debug-plan/comments.json +1 -0
- package/bizar-dash/plans/debug-plan/meta.json +10 -0
- package/bizar-dash/plans/debug-plan/plan.json +174 -0
- package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
- package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
- package/bizar-dash/src/cli.mjs +522 -0
- package/bizar-dash/src/server/activity-log.mjs +174 -0
- package/bizar-dash/src/server/agents-store.mjs +465 -0
- package/bizar-dash/src/server/api.mjs +138 -0
- package/bizar-dash/src/server/artifact-template.html +810 -0
- package/bizar-dash/src/server/artifacts-store.mjs +733 -0
- package/bizar-dash/src/server/auth.mjs +381 -0
- package/bizar-dash/src/server/background-store.mjs +447 -0
- package/bizar-dash/src/server/bg-poller.mjs +305 -0
- package/bizar-dash/src/server/bg-retry.mjs +574 -0
- package/bizar-dash/src/server/browser.mjs +40 -0
- package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
- package/bizar-dash/src/server/dialog-poller.mjs +105 -0
- package/bizar-dash/src/server/dialog-store.mjs +177 -0
- package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
- package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
- package/bizar-dash/src/server/memory-git.mjs +329 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
- package/bizar-dash/src/server/memory-schema.mjs +145 -0
- package/bizar-dash/src/server/memory-secrets.mjs +84 -0
- package/bizar-dash/src/server/memory-store.mjs +471 -0
- package/bizar-dash/src/server/mod-security.mjs +398 -0
- package/bizar-dash/src/server/mods-loader.mjs +1067 -0
- package/bizar-dash/src/server/notifications-store.mjs +243 -0
- package/bizar-dash/src/server/obsidian-store.mjs +318 -0
- package/bizar-dash/src/server/opencode-runner.mjs +279 -0
- package/bizar-dash/src/server/pair-store.mjs +138 -0
- package/bizar-dash/src/server/projects-store.mjs +364 -0
- package/bizar-dash/src/server/providers-store.mjs +789 -0
- package/bizar-dash/src/server/routes/_shared.mjs +415 -0
- package/bizar-dash/src/server/routes/activity.mjs +110 -0
- package/bizar-dash/src/server/routes/agents.mjs +134 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
- package/bizar-dash/src/server/routes/auth.mjs +69 -0
- package/bizar-dash/src/server/routes/background.mjs +165 -0
- package/bizar-dash/src/server/routes/chat.mjs +485 -0
- package/bizar-dash/src/server/routes/config.mjs +129 -0
- package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
- package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
- package/bizar-dash/src/server/routes/fs.mjs +429 -0
- package/bizar-dash/src/server/routes/history.mjs +78 -0
- package/bizar-dash/src/server/routes/memory.mjs +658 -0
- package/bizar-dash/src/server/routes/misc.mjs +89 -0
- package/bizar-dash/src/server/routes/mods.mjs +359 -0
- package/bizar-dash/src/server/routes/notifications.mjs +54 -0
- package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
- package/bizar-dash/src/server/routes/overview.mjs +91 -0
- package/bizar-dash/src/server/routes/pair.mjs +67 -0
- package/bizar-dash/src/server/routes/projects.mjs +175 -0
- package/bizar-dash/src/server/routes/providers.mjs +47 -0
- package/bizar-dash/src/server/routes/schedules.mjs +104 -0
- package/bizar-dash/src/server/routes/settings.mjs +51 -0
- package/bizar-dash/src/server/routes/skills.mjs +76 -0
- package/bizar-dash/src/server/routes/tasks.mjs +468 -0
- package/bizar-dash/src/server/routes/themes.mjs +51 -0
- package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
- package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
- package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
- package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
- package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
- package/bizar-dash/src/server/schedules-runner.mjs +369 -0
- package/bizar-dash/src/server/schedules-store.mjs +268 -0
- package/bizar-dash/src/server/search-store.mjs +220 -0
- package/bizar-dash/src/server/serve-info.mjs +684 -0
- package/bizar-dash/src/server/server.mjs +796 -0
- package/bizar-dash/src/server/settings-store.mjs +45 -0
- package/bizar-dash/src/server/skills-store.mjs +300 -0
- package/bizar-dash/src/server/state.mjs +437 -0
- package/bizar-dash/src/server/tailscale-store.mjs +113 -0
- package/bizar-dash/src/server/task-delegator.mjs +824 -0
- package/bizar-dash/src/server/tasks-store.mjs +508 -0
- package/bizar-dash/src/server/tui.mjs +923 -0
- package/bizar-dash/src/server/update-store.mjs +168 -0
- package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
- package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
- package/bizar-dash/src/server/watcher.mjs +81 -0
- package/bizar-dash/src/server/yaml.mjs +238 -0
- package/bizar-dash/src/web/App.tsx +718 -0
- package/bizar-dash/src/web/MobileApp.tsx +408 -0
- package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
- package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
- package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
- package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
- package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
- package/bizar-dash/src/web/components/Button.tsx +56 -0
- package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
- package/bizar-dash/src/web/components/Card.tsx +40 -0
- package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
- package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
- package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
- package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
- package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
- package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
- package/bizar-dash/src/web/components/Modal.tsx +215 -0
- package/bizar-dash/src/web/components/Notifications.tsx +241 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
- package/bizar-dash/src/web/components/Spinner.tsx +19 -0
- package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
- package/bizar-dash/src/web/components/Tag.tsx +28 -0
- package/bizar-dash/src/web/components/Toast.tsx +150 -0
- package/bizar-dash/src/web/components/Topbar.tsx +368 -0
- package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
- package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
- package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
- package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
- package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
- package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
- package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
- package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
- package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
- package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
- package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
- package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
- package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
- package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
- package/bizar-dash/src/web/components/chat/index.ts +20 -0
- package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
- package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
- package/bizar-dash/src/web/index.html +37 -0
- package/bizar-dash/src/web/lib/api.ts +193 -0
- package/bizar-dash/src/web/lib/markdown.tsx +59 -0
- package/bizar-dash/src/web/lib/types.ts +668 -0
- package/bizar-dash/src/web/lib/utils.ts +114 -0
- package/bizar-dash/src/web/lib/ws.ts +176 -0
- package/bizar-dash/src/web/main.tsx +34 -0
- package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
- package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
- package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
- package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
- package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
- package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
- package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
- package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
- package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
- package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
- package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
- package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
- package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
- package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
- package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
- package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
- package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
- package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
- package/bizar-dash/src/web/mobile.html +20 -0
- package/bizar-dash/src/web/mobile.tsx +13 -0
- package/bizar-dash/src/web/styles/chat.css +198 -0
- package/bizar-dash/src/web/styles/glyphs.css +960 -0
- package/bizar-dash/src/web/styles/main.css +8855 -0
- package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
- package/bizar-dash/src/web/styles/mobile.css +1960 -0
- package/bizar-dash/src/web/views/Activity.tsx +1203 -0
- package/bizar-dash/src/web/views/Agents.tsx +701 -0
- package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
- package/bizar-dash/src/web/views/Chat.tsx +190 -0
- package/bizar-dash/src/web/views/Config.tsx +2063 -0
- package/bizar-dash/src/web/views/History.tsx +335 -0
- package/bizar-dash/src/web/views/ModView.tsx +207 -0
- package/bizar-dash/src/web/views/Mods.tsx +693 -0
- package/bizar-dash/src/web/views/Overview.tsx +812 -0
- package/bizar-dash/src/web/views/Providers.tsx +302 -0
- package/bizar-dash/src/web/views/Schedules.tsx +802 -0
- package/bizar-dash/src/web/views/Settings.tsx +1787 -0
- package/bizar-dash/src/web/views/Skills.tsx +431 -0
- package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
- package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
- package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
- package/bizar-dash/templates/mod/FORMAT.md +76 -0
- package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
- package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
- package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
- package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
- package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
- package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
- package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
- package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
- package/bizar-dash/tests/memory-cli.test.mjs +542 -0
- package/bizar-dash/tests/memory-config.test.mjs +422 -0
- package/bizar-dash/tests/memory-git.test.mjs +246 -0
- package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
- package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
- package/bizar-dash/tests/memory-schema.test.mjs +198 -0
- package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
- package/bizar-dash/tests/memory-store.test.mjs +390 -0
- package/bizar-dash/tests/memory-sync.test.mjs +141 -0
- package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
- package/bizar-dash/tests/mod-security.test.mjs +273 -0
- package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
- package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
- package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
- package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
- package/bizar-dash/tests/path-safe.test.mjs +108 -0
- package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
- package/bizar-dash/tests/smoke-v2.mjs +212 -0
- package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
- package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
- package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
- package/bizar-dash/tests/yaml.test.mjs +128 -0
- package/bizar-dash/tsconfig.json +23 -0
- package/bizar-dash/vite.config.ts +27 -0
- package/cli/atomic.mjs +73 -0
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +44 -47
- package/cli/bootstrap.mjs +1 -1
- package/cli/copy.mjs +25 -16
- package/cli/dev-link.mjs +17 -1
- package/cli/doctor.mjs +4 -4
- package/cli/doctor.test.mjs +2 -2
- package/cli/init.mjs +135 -1
- package/cli/install.mjs +31 -39
- package/cli/memory-constants.mjs +59 -0
- package/cli/memory.mjs +1436 -0
- package/cli/service.mjs +5 -6
- package/cli/utils.mjs +6 -3
- package/config/AGENTS.md +7 -7
- package/config/agents/_shared/AGENT_BASELINE.md +59 -61
- package/config/opencode.json +13 -38
- package/config/skills/memory-protocol/SKILL.md +105 -0
- package/config/skills/obsidian/SKILL.md +270 -65
- package/install.sh +59 -19
- package/package.json +49 -14
- package/packages/sdk/LICENSE +21 -0
- package/packages/sdk/README.md +80 -0
- package/packages/sdk/src/client.ts +183 -0
- package/packages/sdk/src/errors.ts +120 -0
- package/packages/sdk/src/events.ts +153 -0
- package/packages/sdk/src/index.ts +63 -0
- package/packages/sdk/src/types.ts +176 -0
- package/packages/sdk/src/version.ts +4 -0
- package/packages/sdk/tests/client.test.ts +217 -0
- package/packages/sdk/tests/errors.test.ts +108 -0
- package/packages/sdk/tests/events.test.ts +139 -0
- package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
- package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
- package/packages/sdk/tsconfig.json +25 -0
- package/packages/sdk/vitest.config.ts +9 -0
- package/plugins/bizar/LICENSE +21 -0
- package/plugins/bizar/README.md +448 -0
- package/plugins/bizar/dist/index.js +29901 -0
- package/plugins/bizar/index.ts +1491 -0
- package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
- package/plugins/bizar/src/background-state.ts +551 -0
- package/plugins/bizar/src/background.ts +1250 -0
- package/plugins/bizar/src/commands-impl.ts +464 -0
- package/plugins/bizar/src/commands.ts +1168 -0
- package/plugins/bizar/src/dashboard-client.ts +233 -0
- package/plugins/bizar/src/event-stream.ts +606 -0
- package/plugins/bizar/src/fingerprint.ts +120 -0
- package/plugins/bizar/src/handoff.ts +79 -0
- package/plugins/bizar/src/http-client.ts +467 -0
- package/plugins/bizar/src/key-rotation.ts +218 -0
- package/plugins/bizar/src/logger.ts +144 -0
- package/plugins/bizar/src/loop.ts +176 -0
- package/plugins/bizar/src/opencode-runner.ts +390 -0
- package/plugins/bizar/src/options.ts +421 -0
- package/plugins/bizar/src/plan-fs.ts +323 -0
- package/plugins/bizar/src/reasoning-clean.ts +454 -0
- package/plugins/bizar/src/report.ts +178 -0
- package/plugins/bizar/src/research-prompt.ts +35 -0
- package/plugins/bizar/src/serve-info.ts +228 -0
- package/plugins/bizar/src/serve.ts +496 -0
- package/plugins/bizar/src/settings.ts +349 -0
- package/plugins/bizar/src/state.ts +298 -0
- package/plugins/bizar/src/tools/bg-collect.ts +104 -0
- package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
- package/plugins/bizar/src/tools/bg-kill.ts +87 -0
- package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
- package/plugins/bizar/src/tools/bg-status.ts +99 -0
- package/plugins/bizar/src/tools/open-kb.ts +191 -0
- package/plugins/bizar/src/tools/plan-action.ts +767 -0
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
- package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
- package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
- package/plugins/bizar/tests/background-state.test.ts +277 -0
- package/plugins/bizar/tests/background.test.ts +402 -0
- package/plugins/bizar/tests/block.test.ts +195 -0
- package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
- package/plugins/bizar/tests/commands-impl.test.ts +442 -0
- package/plugins/bizar/tests/commands.test.ts +584 -0
- package/plugins/bizar/tests/config.test.ts +122 -0
- package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
- package/plugins/bizar/tests/dispose.test.ts +336 -0
- package/plugins/bizar/tests/event-stream.test.ts +409 -0
- package/plugins/bizar/tests/event.test.ts +262 -0
- package/plugins/bizar/tests/fingerprint.test.ts +162 -0
- package/plugins/bizar/tests/http-client.test.ts +404 -0
- package/plugins/bizar/tests/init-helpers.test.ts +203 -0
- package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
- package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
- package/plugins/bizar/tests/key-rotation.test.ts +396 -0
- package/plugins/bizar/tests/loop.test.ts +397 -0
- package/plugins/bizar/tests/options.test.ts +276 -0
- package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
- package/plugins/bizar/tests/serve.test.ts +339 -0
- package/plugins/bizar/tests/settings.test.ts +351 -0
- package/plugins/bizar/tests/stall-think.test.ts +750 -0
- package/plugins/bizar/tests/state.test.ts +276 -0
- package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
- package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
- package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
- package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
- package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
- package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
- package/plugins/bizar/tsconfig.json +29 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/memory-schema.mjs
|
|
3
|
+
*
|
|
4
|
+
* Validates frontmatter against the Bizar Memory Schema. The schema requires
|
|
5
|
+
* a set of standard fields (memory_id, type, project_id, status, confidence,
|
|
6
|
+
* created, updated, tags) with enumerated values for type, status, and
|
|
7
|
+
* confidence.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const REQUIRED_FIELDS = [
|
|
11
|
+
'memory_id',
|
|
12
|
+
'type',
|
|
13
|
+
'project_id',
|
|
14
|
+
'status',
|
|
15
|
+
'confidence',
|
|
16
|
+
'created',
|
|
17
|
+
'updated',
|
|
18
|
+
'tags',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const VALID_TYPES = [
|
|
22
|
+
'project_overview',
|
|
23
|
+
'architecture_decision',
|
|
24
|
+
'coding_convention',
|
|
25
|
+
'bug_pattern',
|
|
26
|
+
'command',
|
|
27
|
+
'api_contract',
|
|
28
|
+
'dependency_note',
|
|
29
|
+
'environment_fact',
|
|
30
|
+
'task_summary',
|
|
31
|
+
'session_summary',
|
|
32
|
+
'user_preference',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const VALID_STATUSES = [
|
|
36
|
+
'active',
|
|
37
|
+
'superseded',
|
|
38
|
+
'stale',
|
|
39
|
+
'conflict',
|
|
40
|
+
'draft',
|
|
41
|
+
'archived',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const VALID_CONFIDENCES = ['verified', 'inferred', 'speculative'];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate a note's frontmatter and body against the schema.
|
|
48
|
+
*
|
|
49
|
+
* @param {Record<string, unknown>} frontmatter
|
|
50
|
+
* @param {string} body
|
|
51
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
52
|
+
*/
|
|
53
|
+
export function validateNote(frontmatter, body) {
|
|
54
|
+
const errors = [];
|
|
55
|
+
const warnings = [];
|
|
56
|
+
|
|
57
|
+
if (!frontmatter || typeof frontmatter !== 'object') {
|
|
58
|
+
errors.push('frontmatter must be a plain object');
|
|
59
|
+
return { valid: false, errors, warnings };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Required fields
|
|
63
|
+
for (const field of REQUIRED_FIELDS) {
|
|
64
|
+
if (!(field in frontmatter) || frontmatter[field] === undefined || frontmatter[field] === null || frontmatter[field] === '') {
|
|
65
|
+
errors.push(`missing required field: ${field}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Type enum
|
|
70
|
+
if (frontmatter.type && !VALID_TYPES.includes(frontmatter.type)) {
|
|
71
|
+
errors.push(`invalid type: '${frontmatter.type}'. Must be one of: ${VALID_TYPES.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Status enum
|
|
75
|
+
if (frontmatter.status && !VALID_STATUSES.includes(frontmatter.status)) {
|
|
76
|
+
errors.push(`invalid status: '${frontmatter.status}'. Must be one of: ${VALID_STATUSES.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Confidence enum
|
|
80
|
+
if (frontmatter.confidence && !VALID_CONFIDENCES.includes(frontmatter.confidence)) {
|
|
81
|
+
errors.push(`invalid confidence: '${frontmatter.confidence}'. Must be one of: ${VALID_CONFIDENCES.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Tags must be an array
|
|
85
|
+
if (frontmatter.tags !== undefined && !Array.isArray(frontmatter.tags)) {
|
|
86
|
+
errors.push('tags must be an array');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// memory_id format check: should be non-empty string
|
|
90
|
+
if (frontmatter.memory_id && typeof frontmatter.memory_id !== 'string') {
|
|
91
|
+
errors.push('memory_id must be a string');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// project_id format check
|
|
95
|
+
if (frontmatter.project_id && typeof frontmatter.project_id !== 'string') {
|
|
96
|
+
errors.push('project_id must be a string');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ISO-8601 date fields
|
|
100
|
+
for (const dateField of ['created', 'updated']) {
|
|
101
|
+
if (frontmatter[dateField]) {
|
|
102
|
+
const val = String(frontmatter[dateField]);
|
|
103
|
+
if (!/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/.test(val)) {
|
|
104
|
+
warnings.push(`${dateField} is not a valid ISO-8601 date: '${val}'`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Body presence check
|
|
110
|
+
if (!body || !body.trim()) {
|
|
111
|
+
warnings.push('body is empty — notes should have substantive content');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
valid: errors.length === 0,
|
|
116
|
+
errors,
|
|
117
|
+
warnings,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build a default frontmatter object from a partial override.
|
|
123
|
+
* Fills in all missing required fields with sensible defaults.
|
|
124
|
+
*
|
|
125
|
+
* @param {Partial<Record<string, unknown>>} partial
|
|
126
|
+
* @returns {Record<string, unknown>}
|
|
127
|
+
*/
|
|
128
|
+
export function defaultFrontmatter(partial = {}) {
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
const timestamp = Date.now();
|
|
131
|
+
const type = partial.type || 'session_summary';
|
|
132
|
+
const memoryId = partial.memory_id || `${type}_${timestamp}`;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
memory_id: memoryId,
|
|
136
|
+
type: type,
|
|
137
|
+
project_id: partial.project_id || '',
|
|
138
|
+
status: partial.status || 'draft',
|
|
139
|
+
confidence: partial.confidence || 'inferred',
|
|
140
|
+
created: partial.created || now,
|
|
141
|
+
updated: partial.updated || now,
|
|
142
|
+
tags: Array.isArray(partial.tags) ? partial.tags : [],
|
|
143
|
+
...partial,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/memory-secrets.mjs
|
|
3
|
+
*
|
|
4
|
+
* Regex-based secret scanner for structured long-term memory notes.
|
|
5
|
+
* Detects HIGH and MEDIUM severity patterns (API keys, tokens, private keys,
|
|
6
|
+
* absolute paths, private IPv4 addresses). Designed to run before every
|
|
7
|
+
* commit to prevent accidental secret leakage.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* All secret patterns. severity is 'HIGH' or 'MEDIUM'.
|
|
12
|
+
*
|
|
13
|
+
* @type {Array<{ id: string, severity: 'HIGH' | 'MEDIUM', pattern: RegExp }>}
|
|
14
|
+
*/
|
|
15
|
+
export const SECRET_PATTERNS = [
|
|
16
|
+
// HIGH severity
|
|
17
|
+
{ id: 'private_key_pem', severity: 'HIGH', pattern: /-----BEGIN (RSA|EC|OPENSSH|DSA|PGP) PRIVATE KEY-----/ },
|
|
18
|
+
{ id: 'aws_access_key', severity: 'HIGH', pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
19
|
+
{ id: 'aws_secret_key', severity: 'HIGH', pattern: /\baws_secret_access_key\s*[:=]\s*[A-Za-z0-9\/+=]{40}\b/ },
|
|
20
|
+
{ id: 'github_pat_classic', severity: 'HIGH', pattern: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/ },
|
|
21
|
+
{ id: 'github_pat_fine', severity: 'HIGH', pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/ },
|
|
22
|
+
{ id: 'slack_token', severity: 'HIGH', pattern: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/ },
|
|
23
|
+
{ id: 'stripe_live', severity: 'HIGH', pattern: /\bsk_live_[0-9a-zA-Z]{24,}\b/ },
|
|
24
|
+
{ id: 'stripe_test', severity: 'HIGH', pattern: /\bsk_test_[0-9a-zA-Z]{24,}\b/ },
|
|
25
|
+
// Bearer token: refined to require base64-like chars after "Bearer "
|
|
26
|
+
// and terminate on whitespace, punctuation, or end-of-string
|
|
27
|
+
{
|
|
28
|
+
id: 'bearer_token',
|
|
29
|
+
severity: 'HIGH',
|
|
30
|
+
pattern: /\bBearer\s+[A-Za-z0-9+\/=_\-]{20,}(?:\s|$|[,;.])/,
|
|
31
|
+
},
|
|
32
|
+
// MEDIUM severity
|
|
33
|
+
{
|
|
34
|
+
id: 'api_key_assignment',
|
|
35
|
+
severity: 'MEDIUM',
|
|
36
|
+
// Non-greedy {16,}? so the end-of-line assertion has something to match
|
|
37
|
+
pattern: /\b(api[_-]?key|apikey|secret)\s*[:=]\s*['"]?[A-Za-z0-9_\-]{16,}?['"]?(?:\s*$|[,;.\n])/im,
|
|
38
|
+
},
|
|
39
|
+
{ id: 'absolute_path', severity: 'MEDIUM', pattern: /\/(Users|home)\/[A-Za-z0-9._-]+/ },
|
|
40
|
+
{ id: 'private_ipv4', severity: 'MEDIUM', pattern: /\b(10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+)\b/ },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Scan text for secret patterns. Returns a structured scan result.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} text — raw text to scan (usually the full body of a note)
|
|
47
|
+
* @returns {{ safe: boolean, findings: Array<{ id: string, severity: 'HIGH' | 'MEDIUM', line: number, snippet: string }> }}
|
|
48
|
+
*/
|
|
49
|
+
export function scan(text) {
|
|
50
|
+
if (typeof text !== 'string') return { safe: true, findings: [] };
|
|
51
|
+
|
|
52
|
+
const findings = [];
|
|
53
|
+
const lines = text.split('\n');
|
|
54
|
+
|
|
55
|
+
for (const { id, severity, pattern } of SECRET_PATTERNS) {
|
|
56
|
+
// Reset lastIndex each iteration for global patterns
|
|
57
|
+
pattern.lastIndex = 0;
|
|
58
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
59
|
+
const line = lines[lineNum];
|
|
60
|
+
pattern.lastIndex = 0;
|
|
61
|
+
const match = pattern.exec(line);
|
|
62
|
+
if (match) {
|
|
63
|
+
const snippet = line.slice(Math.max(0, match.index - 20), match.index + match[0].length + 20);
|
|
64
|
+
findings.push({ id, severity, line: lineNum + 1, snippet });
|
|
65
|
+
// Only report first match per pattern per line
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
safe: findings.filter((f) => f.severity === 'HIGH').length === 0,
|
|
72
|
+
findings,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns true if the scan result contains any HIGH-severity findings.
|
|
78
|
+
*
|
|
79
|
+
* @param {{ safe: boolean, findings: Array<{ severity: string }> }} result
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
export function hasHighFindings(result) {
|
|
83
|
+
return result.findings.some((f) => f.severity === 'HIGH');
|
|
84
|
+
}
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/memory-store.mjs
|
|
3
|
+
*
|
|
4
|
+
* MarkdownMemoryStore — reads/writes Obsidian-compatible `.md` notes with YAML
|
|
5
|
+
* frontmatter for the Bizar Memory Service. Supports three modes:
|
|
6
|
+
* local-only — vault at <projectRoot>/.obsidian/
|
|
7
|
+
* managed — vault at ~/.local/share/bizar/memory/<repoName>/projects/<projectId>/
|
|
8
|
+
* linked — alias for managed
|
|
9
|
+
*
|
|
10
|
+
* The project namespace directory is created LAZY on first writeNote, not at
|
|
11
|
+
* initVault time (F7 invariant).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
15
|
+
import { execFileSync } from 'node:child_process';
|
|
16
|
+
import { join, dirname, relative, resolve as pathResolve, sep } from 'node:path';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { parseFrontmatter, serializeFrontmatter } from './yaml.mjs';
|
|
19
|
+
import { validateNote } from './memory-schema.mjs';
|
|
20
|
+
import { scan as scanSecrets, hasHighFindings } from './memory-secrets.mjs';
|
|
21
|
+
import { atomicWriteJson, safeReadJSON, safeReadText } from '../../../cli/atomic.mjs';
|
|
22
|
+
import * as memoryGit from './memory-git.mjs';
|
|
23
|
+
|
|
24
|
+
const HOME = homedir();
|
|
25
|
+
const BIZAR_MEMORY_ROOT = join(HOME, '.local', 'share', 'bizar', 'memory');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Path safety: reject any relPath that would escape the vault root.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} vaultRoot
|
|
31
|
+
* @param {string} relPath
|
|
32
|
+
* @returns {string | null} — resolved absolute path, or null if unsafe
|
|
33
|
+
*/
|
|
34
|
+
function resolveSafe(vaultRoot, relPath) {
|
|
35
|
+
if (!relPath || relPath.includes('\0')) return null;
|
|
36
|
+
const abs = pathResolve(vaultRoot, relPath);
|
|
37
|
+
const rel = relative(vaultRoot, abs);
|
|
38
|
+
if (rel.startsWith('..') || abs !== pathResolve(abs)) return null;
|
|
39
|
+
return abs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read the per-project memory config file (.bizar/memory.json).
|
|
44
|
+
*
|
|
45
|
+
* @param {string} projectRoot
|
|
46
|
+
* @returns {{ exists: boolean, config: Record<string, unknown>, error?: string }}
|
|
47
|
+
*/
|
|
48
|
+
export function loadConfig(projectRoot) {
|
|
49
|
+
const configPath = join(projectRoot, '.bizar', 'memory.json');
|
|
50
|
+
const raw = safeReadJSON(configPath, null);
|
|
51
|
+
if (raw === null) {
|
|
52
|
+
return {
|
|
53
|
+
exists: false,
|
|
54
|
+
config: {
|
|
55
|
+
mode: 'local-only',
|
|
56
|
+
projectId: projectRoot.split('/').pop() || 'unknown',
|
|
57
|
+
repoName: '',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { exists: true, config: raw };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Write the per-project memory config file.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} projectRoot
|
|
68
|
+
* @param {Record<string, unknown>} config
|
|
69
|
+
* @returns {{ ok: boolean, error?: string }}
|
|
70
|
+
*/
|
|
71
|
+
export function saveConfig(projectRoot, config) {
|
|
72
|
+
try {
|
|
73
|
+
const configDir = join(projectRoot, '.bizar');
|
|
74
|
+
if (!existsSync(configDir)) {
|
|
75
|
+
mkdirSync(configDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const configPath = join(configDir, 'memory.json');
|
|
78
|
+
atomicWriteJson(configPath, config);
|
|
79
|
+
return { ok: true };
|
|
80
|
+
} catch (err) {
|
|
81
|
+
return { ok: false, error: err.message };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the effective vault root, namespaces, and related paths based on mode.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} projectRoot
|
|
89
|
+
* @returns {{ mode: string, projectId: string, vaultRoot: string, repoPath: string | null, configDir: string, gitRemote: string | null, branch: string, lightragDir: string, namespaces: object }}
|
|
90
|
+
*/
|
|
91
|
+
export function resolveVault(projectRoot) {
|
|
92
|
+
const { config } = loadConfig(projectRoot);
|
|
93
|
+
const mr = config.memoryRepo || {};
|
|
94
|
+
const mode = mr.mode || config.mode || 'local-only';
|
|
95
|
+
const projectId = config.projectId || projectRoot.split('/').pop() || 'unknown';
|
|
96
|
+
const branch = mr.branch || config.branch || 'main';
|
|
97
|
+
const gitRemote = mr.remote || config.gitRemote || null;
|
|
98
|
+
|
|
99
|
+
let vaultRoot;
|
|
100
|
+
let repoPath;
|
|
101
|
+
if (mode === 'local-only') {
|
|
102
|
+
vaultRoot = join(projectRoot, '.obsidian');
|
|
103
|
+
repoPath = null;
|
|
104
|
+
} else {
|
|
105
|
+
// managed or linked — path may be absolute, ~-relative, or a sibling of projectRoot
|
|
106
|
+
const rawPath = mr.path || config.repoName || join(projectRoot, '.bizar', 'memory');
|
|
107
|
+
const expanded = rawPath.startsWith('~') ? join(HOME, rawPath.slice(1)) : rawPath;
|
|
108
|
+
repoPath = expanded;
|
|
109
|
+
vaultRoot = join(expanded, 'projects', projectId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const configDir = join(projectRoot, '.bizar');
|
|
113
|
+
const lightragDir = join(projectRoot, '.bizar', 'memory-cache');
|
|
114
|
+
const namespaces = config.namespaces || {
|
|
115
|
+
project: `projects/${projectId}`,
|
|
116
|
+
global: 'global/bizar',
|
|
117
|
+
user: `users/${process.env.USER || process.env.USERNAME || 'local'}`,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return { mode, projectId, vaultRoot, repoPath, configDir, gitRemote, branch, lightragDir, namespaces };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Initialize the vault for a project. In local-only mode, creates .obsidian/
|
|
125
|
+
* and standard subdirectories. In managed mode, the shared repo is assumed to
|
|
126
|
+
* already exist; this function only ensures the .bizar/memory.json is set up.
|
|
127
|
+
*
|
|
128
|
+
* NOTE: The project namespace directory (projects/<id>/) is NOT created here.
|
|
129
|
+
* It is created lazily on first writeNote (F7 invariant).
|
|
130
|
+
*
|
|
131
|
+
* @param {string} projectRoot
|
|
132
|
+
* @returns {{ ok: boolean, vaultRoot: string, created: string[], error?: string }}
|
|
133
|
+
*/
|
|
134
|
+
export function initVault(projectRoot) {
|
|
135
|
+
const { config } = loadConfig(projectRoot);
|
|
136
|
+
const mr = config.memoryRepo || {};
|
|
137
|
+
const mode = mr.mode || config.mode || 'local-only';
|
|
138
|
+
const created = [];
|
|
139
|
+
|
|
140
|
+
if (mode === 'local-only') {
|
|
141
|
+
const vaultRoot = join(projectRoot, '.obsidian');
|
|
142
|
+
const subdirs = ['daily', 'decisions', 'patterns', 'api', 'tasks', 'global', 'users'];
|
|
143
|
+
if (!existsSync(vaultRoot)) {
|
|
144
|
+
mkdirSync(vaultRoot, { recursive: true });
|
|
145
|
+
created.push('.obsidian/');
|
|
146
|
+
}
|
|
147
|
+
for (const sub of subdirs) {
|
|
148
|
+
const subPath = join(vaultRoot, sub);
|
|
149
|
+
if (!existsSync(subPath)) {
|
|
150
|
+
mkdirSync(subPath, { recursive: true });
|
|
151
|
+
created.push(`.obsidian/${sub}/`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, vaultRoot, created };
|
|
155
|
+
} else {
|
|
156
|
+
// managed / linked — create the shared repo at repoPath with `git init`
|
|
157
|
+
const { repoPath, vaultRoot } = resolveVault(projectRoot);
|
|
158
|
+
if (!existsSync(repoPath)) {
|
|
159
|
+
mkdirSync(repoPath, { recursive: true });
|
|
160
|
+
created.push(repoPath);
|
|
161
|
+
}
|
|
162
|
+
// git init if not already a git repo
|
|
163
|
+
const isGit = existsSync(join(repoPath, '.git'));
|
|
164
|
+
if (!isGit) {
|
|
165
|
+
try {
|
|
166
|
+
execFileSync('git', ['init', '-b', 'main'], { cwd: repoPath, stdio: 'pipe' });
|
|
167
|
+
created.push(`${repoPath}/.git/`);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return { ok: false, vaultRoot, created, error: `git init failed: ${err.message}` };
|
|
170
|
+
}
|
|
171
|
+
// Write and commit a .gitignore for runtime artifacts
|
|
172
|
+
const gitignorePath = join(repoPath, '.gitignore');
|
|
173
|
+
if (!existsSync(gitignorePath)) {
|
|
174
|
+
writeFileSync(gitignorePath,
|
|
175
|
+
'# Lock files — runtime state, not memory\n' +
|
|
176
|
+
'**/.sync.lock\n' +
|
|
177
|
+
'**/*.pid\n' +
|
|
178
|
+
'**/*.log\n' +
|
|
179
|
+
'**/.DS_Store\n' +
|
|
180
|
+
'\n' +
|
|
181
|
+
'# Local-only metadata\n' +
|
|
182
|
+
'**/.bizar-memory/cache/\n' +
|
|
183
|
+
'**/.bizar-memory/index-ledger.local.json\n',
|
|
184
|
+
'utf8');
|
|
185
|
+
created.push(`${repoPath}/.gitignore`);
|
|
186
|
+
try {
|
|
187
|
+
execFileSync('git', ['add', '.gitignore'], { cwd: repoPath });
|
|
188
|
+
execFileSync('git', ['commit', '-m', 'chore: initial .gitignore for memory repo'], {
|
|
189
|
+
cwd: repoPath,
|
|
190
|
+
env: {
|
|
191
|
+
...process.env,
|
|
192
|
+
GIT_AUTHOR_NAME: 'Bizar Memory',
|
|
193
|
+
GIT_AUTHOR_EMAIL: 'bizar-memory@local',
|
|
194
|
+
GIT_COMMITTER_NAME: 'Bizar Memory',
|
|
195
|
+
GIT_COMMITTER_EMAIL: 'bizar-memory@local',
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
} catch (e) {
|
|
199
|
+
// best-effort; not fatal
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Ensure projects/, global/, users/ exist so writeNote can find them without
|
|
204
|
+
// creating the project-specific namespace (F7 invariant — only the projects/<id>
|
|
205
|
+
// subdirectory is created lazily on first writeNote)
|
|
206
|
+
const projectsDir = join(repoPath, 'projects');
|
|
207
|
+
if (!existsSync(projectsDir)) {
|
|
208
|
+
mkdirSync(projectsDir, { recursive: true });
|
|
209
|
+
created.push(`${repoPath}/projects/`);
|
|
210
|
+
}
|
|
211
|
+
const globalDir = join(repoPath, 'global', 'bizar');
|
|
212
|
+
if (!existsSync(globalDir)) {
|
|
213
|
+
mkdirSync(globalDir, { recursive: true });
|
|
214
|
+
created.push(`${repoPath}/global/bizar/`);
|
|
215
|
+
}
|
|
216
|
+
const usersDir = join(repoPath, 'users');
|
|
217
|
+
if (!existsSync(usersDir)) {
|
|
218
|
+
mkdirSync(usersDir, { recursive: true });
|
|
219
|
+
created.push(`${repoPath}/users/`);
|
|
220
|
+
}
|
|
221
|
+
return { ok: true, vaultRoot, created };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* List all notes in the vault. Returns enriched note metadata.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} projectRoot
|
|
229
|
+
* @param {{ namespace?: string }} [opts]
|
|
230
|
+
* @returns {Array<{ relPath: string, mtime: number, size: number, frontmatter: Record<string, unknown>, body: string, schemaValid: boolean }>}
|
|
231
|
+
*/
|
|
232
|
+
export function listNotes(projectRoot, opts = {}) {
|
|
233
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
234
|
+
if (!existsSync(vaultRoot)) return [];
|
|
235
|
+
|
|
236
|
+
const namespace = opts.namespace || '';
|
|
237
|
+
const searchRoot = namespace ? join(vaultRoot, namespace) : vaultRoot;
|
|
238
|
+
if (!existsSync(searchRoot)) return [];
|
|
239
|
+
|
|
240
|
+
const out = [];
|
|
241
|
+
function walk(dir, prefix) {
|
|
242
|
+
let entries;
|
|
243
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); }
|
|
244
|
+
catch { return; }
|
|
245
|
+
for (const e of entries) {
|
|
246
|
+
const full = join(dir, e.name);
|
|
247
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
248
|
+
if (e.isDirectory()) {
|
|
249
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
250
|
+
walk(full, rel);
|
|
251
|
+
} else if (e.name.endsWith('.md')) {
|
|
252
|
+
try {
|
|
253
|
+
const st = statSync(full);
|
|
254
|
+
const raw = readFileSync(full, 'utf8');
|
|
255
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
256
|
+
const { valid } = validateNote(frontmatter, body);
|
|
257
|
+
out.push({ relPath: rel, mtime: st.mtimeMs, size: st.size, frontmatter, body, schemaValid: valid });
|
|
258
|
+
} catch { /* skip unreadable */ }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
walk(searchRoot, '');
|
|
263
|
+
return out.sort((a, b) => b.mtime - a.mtime);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Read a single note by relative path. Returns null if not found.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} projectRoot
|
|
270
|
+
* @param {string} relPath
|
|
271
|
+
* @returns {{ relPath: string, frontmatter: Record<string, unknown>, body: string, raw: string, mtime: number, size: number, schemaValid: boolean } | null}
|
|
272
|
+
*/
|
|
273
|
+
export function readNote(projectRoot, relPath) {
|
|
274
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
275
|
+
const filePath = resolveSafe(vaultRoot, relPath);
|
|
276
|
+
if (!filePath || !existsSync(filePath)) return null;
|
|
277
|
+
try {
|
|
278
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
279
|
+
const st = statSync(filePath);
|
|
280
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
281
|
+
const { valid } = validateNote(frontmatter, body);
|
|
282
|
+
return { relPath, frontmatter, body, raw, mtime: st.mtimeMs, size: st.size, schemaValid: valid };
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Write a note. Validates frontmatter, scans for secrets, writes the file.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} projectRoot
|
|
292
|
+
* @param {string} relPath
|
|
293
|
+
* @param {{ frontmatter: Record<string, unknown>, body: string }} note
|
|
294
|
+
* @returns {{ relPath: string, frontmatter: Record<string, unknown>, body: string, raw: string, mtime: number, size: number, schemaValid: boolean }}
|
|
295
|
+
* @throws {Error} if schema validation fails or HIGH-severity secret found
|
|
296
|
+
*/
|
|
297
|
+
export function writeNote(projectRoot, relPath, { frontmatter, body }) {
|
|
298
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
299
|
+
|
|
300
|
+
// Validate frontmatter
|
|
301
|
+
const { valid, errors, warnings } = validateNote(frontmatter, body);
|
|
302
|
+
if (!valid) {
|
|
303
|
+
const err = new Error(`schema validation failed: ${errors.join('; ')}`);
|
|
304
|
+
err.code = 'SCHEMA_VALIDATION_FAILED';
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Scan for secrets
|
|
309
|
+
const secretResult = scanSecrets(body);
|
|
310
|
+
if (hasHighFindings(secretResult)) {
|
|
311
|
+
const snippets = secretResult.findings
|
|
312
|
+
.filter((f) => f.severity === 'HIGH')
|
|
313
|
+
.map((f) => `line ${f.line}: ${f.snippet}`)
|
|
314
|
+
.join('\n');
|
|
315
|
+
const err = new Error(`HIGH-severity secret detected:\n${snippets}`);
|
|
316
|
+
err.code = 'SECRET_DETECTED';
|
|
317
|
+
err.findings = secretResult.findings;
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Build file content
|
|
322
|
+
const filePath = resolveSafe(vaultRoot, relPath);
|
|
323
|
+
if (!filePath) {
|
|
324
|
+
throw new Error(`Invalid note path: ${relPath}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Lazily create the project namespace directory (F7 — not created at initVault)
|
|
328
|
+
const nsDir = dirname(filePath);
|
|
329
|
+
if (!existsSync(nsDir)) {
|
|
330
|
+
mkdirSync(nsDir, { recursive: true });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const yamlBlock = serializeFrontmatter(frontmatter);
|
|
334
|
+
const content = `---\n${yamlBlock}\n---\n\n${body}`;
|
|
335
|
+
writeFileSync(filePath, content, 'utf8');
|
|
336
|
+
|
|
337
|
+
// Auto-commit the written file to git (if configured).
|
|
338
|
+
// The lock serializes commits, not writes — concurrent writes will each
|
|
339
|
+
// trigger their own commit, which is the intended behaviour.
|
|
340
|
+
(() => {
|
|
341
|
+
try {
|
|
342
|
+
if (!memoryGit.isGitInstalled()) return;
|
|
343
|
+
const { config } = loadConfig(projectRoot);
|
|
344
|
+
if (config.mode === 'local-only') return;
|
|
345
|
+
if (config.git?.autoCommitOnMemoryWrite !== true) return;
|
|
346
|
+
const lockResult = memoryGit.acquireLock(vaultRoot);
|
|
347
|
+
if (!lockResult || lockResult.error) return; // skip if locked or error
|
|
348
|
+
try {
|
|
349
|
+
memoryGit.addFile(vaultRoot, relPath);
|
|
350
|
+
const summary = frontmatter.title || relPath;
|
|
351
|
+
const message = (config.git.commitMessageTemplate || 'memory(BizarHarness): {summary}')
|
|
352
|
+
.replace('{summary}', summary);
|
|
353
|
+
memoryGit.commit(vaultRoot, message, { author: config.git.commitAuthor });
|
|
354
|
+
} finally {
|
|
355
|
+
lockResult.release();
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
// Log but never re-throw — the write already succeeded; a failed
|
|
359
|
+
// auto-commit is recoverable on next write or manual commit.
|
|
360
|
+
console.error('[memory-store] autoCommitOnMemoryWrite failed:', err?.message || err);
|
|
361
|
+
}
|
|
362
|
+
})();
|
|
363
|
+
|
|
364
|
+
const st = statSync(filePath);
|
|
365
|
+
return {
|
|
366
|
+
relPath,
|
|
367
|
+
frontmatter,
|
|
368
|
+
body,
|
|
369
|
+
raw: content,
|
|
370
|
+
mtime: st.mtimeMs,
|
|
371
|
+
size: st.size,
|
|
372
|
+
schemaValid: true,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Delete a note by relative path.
|
|
378
|
+
*
|
|
379
|
+
* @param {string} projectRoot
|
|
380
|
+
* @param {string} relPath
|
|
381
|
+
* @returns {boolean}
|
|
382
|
+
*/
|
|
383
|
+
export function deleteNote(projectRoot, relPath) {
|
|
384
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
385
|
+
const filePath = resolveSafe(vaultRoot, relPath);
|
|
386
|
+
if (!filePath || !existsSync(filePath)) return false;
|
|
387
|
+
unlinkSync(filePath);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Full-text search across vault notes.
|
|
393
|
+
*
|
|
394
|
+
* @param {string} projectRoot
|
|
395
|
+
* @param {string} query
|
|
396
|
+
* @param {{ limit?: number }} [opts]
|
|
397
|
+
* @returns {Array<{ relPath: string, snippet: string, score: number, mtime: number }>}
|
|
398
|
+
*/
|
|
399
|
+
export function searchVault(projectRoot, query, { limit = 25 } = {}) {
|
|
400
|
+
if (!query || typeof query !== 'string') return [];
|
|
401
|
+
const q = query.toLowerCase();
|
|
402
|
+
const tokens = q.split(/\s+/).filter((t) => t.length >= 2);
|
|
403
|
+
if (tokens.length === 0) return [];
|
|
404
|
+
|
|
405
|
+
const results = [];
|
|
406
|
+
for (const note of listNotes(projectRoot)) {
|
|
407
|
+
const lower = (note.body + ' ' + JSON.stringify(note.frontmatter)).toLowerCase();
|
|
408
|
+
let score = 0;
|
|
409
|
+
for (const t of tokens) {
|
|
410
|
+
if (lower.includes(t)) score += (lower.split(t).length - 1);
|
|
411
|
+
}
|
|
412
|
+
if (score === 0) continue;
|
|
413
|
+
const idx = lower.indexOf(tokens[0]);
|
|
414
|
+
const start = Math.max(0, idx - 60);
|
|
415
|
+
const end = Math.min(lower.length, idx + 160);
|
|
416
|
+
const snippet = (start > 0 ? '…' : '') + lower.slice(start, end).replace(/\s+/g, ' ').trim();
|
|
417
|
+
results.push({ relPath: note.relPath, snippet, score, mtime: note.mtime });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Scan a single note for secrets.
|
|
425
|
+
*
|
|
426
|
+
* @param {string} projectRoot
|
|
427
|
+
* @param {string} relPath
|
|
428
|
+
* @returns {{ safe: boolean, findings: Array<{ id: string, severity: string, line: number, snippet: string }> }}
|
|
429
|
+
*/
|
|
430
|
+
export function scanForSecrets(projectRoot, relPath) {
|
|
431
|
+
const note = readNote(projectRoot, relPath);
|
|
432
|
+
if (!note) return { safe: true, findings: [] };
|
|
433
|
+
return scanSecrets(note.body);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Validate all notes in the vault against the schema.
|
|
438
|
+
*
|
|
439
|
+
* @param {string} projectRoot
|
|
440
|
+
* @returns {Array<{ relPath: string, errors: string[] }>}
|
|
441
|
+
*/
|
|
442
|
+
export function validateAll(projectRoot) {
|
|
443
|
+
const notes = listNotes(projectRoot);
|
|
444
|
+
const results = [];
|
|
445
|
+
for (const note of notes) {
|
|
446
|
+
const { errors } = validateNote(note.frontmatter, note.body);
|
|
447
|
+
if (errors.length > 0) {
|
|
448
|
+
results.push({ relPath: note.relPath, errors });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return results;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ── LightRAG integration (v4.1.0) ──────────────────────────────────────────
|
|
455
|
+
//
|
|
456
|
+
// Re-export the LightRAG orchestrator from the memory-store module so the
|
|
457
|
+
// dashboard, CLI, and tests have a single import surface. The actual
|
|
458
|
+
// implementation lives in `memory-lightrag.mjs`.
|
|
459
|
+
|
|
460
|
+
export {
|
|
461
|
+
resolveLightRAGConfig,
|
|
462
|
+
isInstalled as isLightRAGInstalled,
|
|
463
|
+
isRunning as isLightRAGRunning,
|
|
464
|
+
startServer as startLightRAG,
|
|
465
|
+
stopServer as stopLightRAG,
|
|
466
|
+
ensureRunning as ensureLightRAGRunning,
|
|
467
|
+
insertNote as insertLightRAGNote,
|
|
468
|
+
insertAllNotes as insertLightRAGAll,
|
|
469
|
+
reindexVault,
|
|
470
|
+
query as queryLightRAG,
|
|
471
|
+
} from './memory-lightrag.mjs';
|