@cohortapp/agent-sdk 2.3.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/.claude/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- package/workflows/weekly/strategic-memo.yaml +79 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — RAG search (sqlite-fts5)
|
|
3
|
+
*
|
|
4
|
+
* Query the indexed agent corpus and return top-N matching chunks with
|
|
5
|
+
* BM25 scores and snippet text. Used by:
|
|
6
|
+
* - `rag_search` tool exposed to voice/Claude sessions
|
|
7
|
+
* - voice context-loader for at-call pre-loading of relevant chunks
|
|
8
|
+
* - daemon's classifier-side lookups when full-context is overkill
|
|
9
|
+
*
|
|
10
|
+
* Public API:
|
|
11
|
+
* search({ query, topN?, kind?, agentRoot? }) →
|
|
12
|
+
* [{ source_path, kind, title, heading, snippet, score, relevance }]
|
|
13
|
+
*
|
|
14
|
+
* BM25 score range (SQLite FTS5 `bm25()`): NEGATIVE, where a more-negative
|
|
15
|
+
* value means a better match (0 is the worst possible — no term overlap).
|
|
16
|
+
* `ORDER BY bm25(chunks)` ascending therefore puts the best hit first. We
|
|
17
|
+
* expose a normalized `relevance` field in [0,1] (1.0 = top hit in this
|
|
18
|
+
* result set, → 0 = weakest) so callers don't need to interpret the raw,
|
|
19
|
+
* sign-inverted BM25 value directly. See `ftsRelevance()` for the transform.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { join, resolve } from "node:path";
|
|
24
|
+
|
|
25
|
+
let _Database = null;
|
|
26
|
+
async function loadDatabase() {
|
|
27
|
+
if (_Database) return _Database;
|
|
28
|
+
const mod = await import("better-sqlite3");
|
|
29
|
+
_Database = mod.default || mod;
|
|
30
|
+
return _Database;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const FTS_DB_REL = "state/rag/index/agent.db";
|
|
34
|
+
|
|
35
|
+
// Stop-word list: common English words that add noise to BM25 ranking
|
|
36
|
+
// without carrying meaning. Anything < 3 chars is also dropped.
|
|
37
|
+
const STOPWORDS = new Set([
|
|
38
|
+
"the", "and", "for", "are", "but", "not", "you", "all", "any", "can",
|
|
39
|
+
"had", "her", "was", "one", "our", "out", "day", "get", "has", "him",
|
|
40
|
+
"his", "how", "man", "new", "now", "old", "see", "two", "way", "who",
|
|
41
|
+
"boy", "did", "its", "let", "put", "say", "she", "too", "use",
|
|
42
|
+
"with", "have", "this", "that", "from", "will", "what", "your", "when",
|
|
43
|
+
"been", "were", "they", "them", "than", "then", "would", "could",
|
|
44
|
+
"should", "about", "which", "their", "there", "where", "while",
|
|
45
|
+
"into", "onto", "upon", "over", "under", "after", "before", "between",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Tokenize + sanitize user input into FTS5-safe MATCH tokens. Strategy:
|
|
50
|
+
* 1. Lowercase + strip non-alphanumeric → bare tokens.
|
|
51
|
+
* 2. Drop stop-words and tokens < 3 chars (they're noise for BM25).
|
|
52
|
+
* 3. Take the top ~16 tokens by length (longer = more specific).
|
|
53
|
+
* 4. Each token is double-quoted (with any embedded quotes stripped) so we
|
|
54
|
+
* never pass an FTS5 operator (NEAR, NOT, OR, AND, *, …) from user input.
|
|
55
|
+
*
|
|
56
|
+
* Returns the array of quoted tokens (e.g. `["\"alpha\"", "\"beta\""]`) so
|
|
57
|
+
* callers can choose the boolean joiner — see `buildMatch()` for AND-first /
|
|
58
|
+
* OR-fallback. Returns `[]` when nothing survives sanitization.
|
|
59
|
+
*/
|
|
60
|
+
function tokenizeQuery(q) {
|
|
61
|
+
const all = String(q || "")
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
64
|
+
.split(/\s+/)
|
|
65
|
+
.filter((t) => t.length >= 3 && !STOPWORDS.has(t));
|
|
66
|
+
if (!all.length) return [];
|
|
67
|
+
// Dedup + prefer specific terms (longer first), cap at 16.
|
|
68
|
+
const ranked = Array.from(new Set(all)).sort((a, b) => b.length - a.length).slice(0, 16);
|
|
69
|
+
return ranked.map((t) => `"${t.replace(/"/g, "")}"`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build an FTS5 MATCH expression from sanitized tokens.
|
|
74
|
+
* - mode "and": require every token (`"a" AND "b"`). High precision; used
|
|
75
|
+
* first for short multi-term queries so a doc matching ALL terms ranks
|
|
76
|
+
* above one matching just a common term (audit L14).
|
|
77
|
+
* - mode "or": any token may match (`"a" OR "b"`). Used as the zero-hit
|
|
78
|
+
* fallback so a strict AND never silently returns nothing.
|
|
79
|
+
* A single token is identical under both modes.
|
|
80
|
+
*/
|
|
81
|
+
function buildMatch(tokens, mode) {
|
|
82
|
+
if (!tokens.length) return null;
|
|
83
|
+
return tokens.join(mode === "or" ? " OR " : " AND ");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Back-compat shim: the original API returned an OR-joined MATCH string.
|
|
88
|
+
* Kept so any external caller importing the old behaviour still works;
|
|
89
|
+
* internal callers use `tokenizeQuery` + `buildMatch` directly.
|
|
90
|
+
*/
|
|
91
|
+
function escapeQuery(q) {
|
|
92
|
+
const tokens = tokenizeQuery(q);
|
|
93
|
+
return tokens.length ? buildMatch(tokens, "or") : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @typedef {object} RagHit
|
|
98
|
+
* @property {string} source_path
|
|
99
|
+
* @property {string} kind
|
|
100
|
+
* @property {string} title
|
|
101
|
+
* @property {string|null} heading
|
|
102
|
+
* @property {string} snippet
|
|
103
|
+
* @property {number} score BM25 score (negative; lower = better) — kept for compat
|
|
104
|
+
* @property {number} relevance Normalized 0..1 (1.0 = best)
|
|
105
|
+
* @property {string} [retrieval] "fts" | "semantic" | "hybrid"
|
|
106
|
+
* @property {number} [bm25_rank] Rank in FTS5 results (1-based)
|
|
107
|
+
* @property {number} [semantic_rank] Rank in semantic results (1-based)
|
|
108
|
+
* @property {number} [semantic_score] Raw cosine similarity (0..1)
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Search the indexed corpus.
|
|
113
|
+
*
|
|
114
|
+
* Modes (auto-detected unless `mode` is set):
|
|
115
|
+
* - "fts" Pure BM25 keyword search (default if no embeddings available
|
|
116
|
+
* or `mode: "fts"` is set explicitly). Fast, free, exact terms.
|
|
117
|
+
* - "semantic" Pure cosine similarity over embeddings. Best for paraphrase
|
|
118
|
+
* queries ("anything about culture last quarter") where the
|
|
119
|
+
* exact terms aren't in the corpus.
|
|
120
|
+
* - "hybrid" Run both, fuse via Reciprocal Rank Fusion. Default when
|
|
121
|
+
* embeddings are available — best of both worlds.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} opts
|
|
124
|
+
* @param {string} opts.query User query (free-text)
|
|
125
|
+
* @param {number} [opts.topN] Max results (default: 8)
|
|
126
|
+
* @param {string} [opts.kind] Filter by chunk.kind
|
|
127
|
+
* @param {string} [opts.agentRoot]
|
|
128
|
+
* @param {boolean} [opts.raw] For "fts" mode: trust caller's MATCH syntax
|
|
129
|
+
* @param {"fts"|"semantic"|"hybrid"} [opts.mode]
|
|
130
|
+
* @param {string} [opts.embedModel] Override embedding model for query
|
|
131
|
+
* @returns {Promise<Array<RagHit>>}
|
|
132
|
+
*/
|
|
133
|
+
export async function search(opts = {}) {
|
|
134
|
+
if (!opts.query) return [];
|
|
135
|
+
const Database = await loadDatabase();
|
|
136
|
+
const root = resolve(opts.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd());
|
|
137
|
+
const dbPath = join(root, FTS_DB_REL);
|
|
138
|
+
if (!existsSync(dbPath)) return [];
|
|
139
|
+
|
|
140
|
+
const topN = Math.min(Math.max(parseInt(opts.topN, 10) || 8, 1), 50);
|
|
141
|
+
const db = new Database(dbPath, { readonly: true });
|
|
142
|
+
try {
|
|
143
|
+
// Detect whether embeddings are available + decide mode.
|
|
144
|
+
const hasEmbeds = db.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n > 0;
|
|
145
|
+
let mode = opts.mode || (hasEmbeds && process.env.OPENAI_API_KEY ? "hybrid" : "fts");
|
|
146
|
+
if (mode !== "fts" && !hasEmbeds) mode = "fts";
|
|
147
|
+
|
|
148
|
+
if (mode === "fts") return runFts(db, opts, topN);
|
|
149
|
+
if (mode === "semantic") return await runSemantic(db, opts, topN);
|
|
150
|
+
return await runHybrid(db, opts, topN);
|
|
151
|
+
} finally { db.close(); }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// FTS-only path
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Map a row's raw FTS5 bm25() score to a faithful [0,1] relevance, given the
|
|
160
|
+
* best (most-negative) score in the same result set (audit L15).
|
|
161
|
+
*
|
|
162
|
+
* FTS5 bm25() is NEGATIVE — more negative = better — and 0 means "no overlap".
|
|
163
|
+
* The old `bestScore / r.score` ratio silently assumed a POSITIVE BM25, so it
|
|
164
|
+
* inverted the ordering for the (negative) real values and, when `r.score` was
|
|
165
|
+
* 0, produced Infinity before clamping. Here we anchor relevance at the best
|
|
166
|
+
* score: the top hit is exactly 1.0, weaker (less-negative, larger) scores
|
|
167
|
+
* decay toward 0, and a 0 score (no overlap) maps to 0. All paths are finite —
|
|
168
|
+
* there is no division that can hit 0 or produce Inf/NaN.
|
|
169
|
+
*
|
|
170
|
+
* Exported (additively) so the offline test suite can assert the zero-score
|
|
171
|
+
* and out-of-range guards directly without a contrived FTS row.
|
|
172
|
+
*
|
|
173
|
+
* @param {number} score this row's bm25() value (≤ 0)
|
|
174
|
+
* @param {number} bestScore most-negative bm25() in the set (≤ 0)
|
|
175
|
+
* @returns {number} relevance in [0,1]
|
|
176
|
+
*/
|
|
177
|
+
export function ftsRelevance(score, bestScore) {
|
|
178
|
+
const s = Number(score);
|
|
179
|
+
const best = Number(bestScore);
|
|
180
|
+
if (!Number.isFinite(s) || !Number.isFinite(best)) return 0;
|
|
181
|
+
// Best score 0 means even the top hit has no measurable overlap → no signal.
|
|
182
|
+
if (best === 0) return 0;
|
|
183
|
+
// A row's own score of 0 (no overlap) maps to 0.
|
|
184
|
+
if (s === 0) return 0;
|
|
185
|
+
// Both s and best are < 0 here, and best is the most-negative (best) score,
|
|
186
|
+
// so |s| ≤ |best| and ratio = s / best ∈ (0, 1]: the top hit (s === best) is
|
|
187
|
+
// exactly 1.0, and a weaker (less-negative, i.e. closer-to-0) score yields a
|
|
188
|
+
// smaller fraction. (Using best / s would invert this and make weak hits look
|
|
189
|
+
// perfect.) The clamp is a belt-and-braces guard against FP edge cases.
|
|
190
|
+
const ratio = s / best;
|
|
191
|
+
if (!Number.isFinite(ratio)) return 0;
|
|
192
|
+
return Math.max(0, Math.min(1, ratio));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function runFts(db, opts, topN) {
|
|
196
|
+
if (opts.raw) {
|
|
197
|
+
return queryFts(db, opts.query, opts, topN);
|
|
198
|
+
}
|
|
199
|
+
const tokens = tokenizeQuery(opts.query);
|
|
200
|
+
if (!tokens.length) return [];
|
|
201
|
+
// AND-first for precision on short multi-term queries; fall back to OR only
|
|
202
|
+
// when the strict conjunction returns nothing (audit L14).
|
|
203
|
+
const andMatch = buildMatch(tokens, "and");
|
|
204
|
+
let rows = queryFts(db, andMatch, opts, topN);
|
|
205
|
+
if (rows.length === 0 && tokens.length > 1) {
|
|
206
|
+
const orMatch = buildMatch(tokens, "or");
|
|
207
|
+
rows = queryFts(db, orMatch, opts, topN);
|
|
208
|
+
}
|
|
209
|
+
return rows;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Execute one FTS5 MATCH and shape the rows (incl. faithful relevance). */
|
|
213
|
+
function queryFts(db, matchQuery, opts, topN) {
|
|
214
|
+
if (!matchQuery) return [];
|
|
215
|
+
let sql = `
|
|
216
|
+
SELECT
|
|
217
|
+
rowid,
|
|
218
|
+
body,
|
|
219
|
+
source_path,
|
|
220
|
+
kind,
|
|
221
|
+
title,
|
|
222
|
+
heading,
|
|
223
|
+
bm25(chunks) AS score,
|
|
224
|
+
snippet(chunks, 0, '«', '»', '…', 32) AS snippet
|
|
225
|
+
FROM chunks
|
|
226
|
+
WHERE chunks MATCH ?
|
|
227
|
+
`;
|
|
228
|
+
const params = [matchQuery];
|
|
229
|
+
if (opts.kind) { sql += " AND kind = ?"; params.push(opts.kind); }
|
|
230
|
+
sql += " ORDER BY bm25(chunks) LIMIT ?";
|
|
231
|
+
params.push(topN);
|
|
232
|
+
|
|
233
|
+
const rows = db.prepare(sql).all(...params);
|
|
234
|
+
if (rows.length === 0) return [];
|
|
235
|
+
const bestScore = rows[0].score; // most-negative (best) thanks to ORDER BY
|
|
236
|
+
return rows.map((r, i) => ({
|
|
237
|
+
source_path: r.source_path,
|
|
238
|
+
kind: r.kind,
|
|
239
|
+
title: r.title,
|
|
240
|
+
heading: r.heading || null,
|
|
241
|
+
snippet: r.snippet,
|
|
242
|
+
score: r.score,
|
|
243
|
+
relevance: ftsRelevance(r.score, bestScore),
|
|
244
|
+
retrieval: "fts",
|
|
245
|
+
bm25_rank: i + 1,
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Semantic path — cosine similarity over all stored embeddings
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
// Candidate ceiling for the brute-force cosine scan. Above this many
|
|
254
|
+
// matching-(model,dim) embeddings we cap the scan and warn rather than
|
|
255
|
+
// pulling the entire BLOB column into JS (audit H11). Override via
|
|
256
|
+
// RAG_MAX_SEMANTIC_CANDIDATES. Normal-size corpora (< ceiling) are
|
|
257
|
+
// scored in full and behave exactly as before.
|
|
258
|
+
export const DEFAULT_MAX_SEMANTIC_CANDIDATES = 50000;
|
|
259
|
+
|
|
260
|
+
function maxSemanticCandidates() {
|
|
261
|
+
const n = parseInt(process.env.RAG_MAX_SEMANTIC_CANDIDATES, 10);
|
|
262
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_SEMANTIC_CANDIDATES;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function runSemantic(db, opts, topN) {
|
|
266
|
+
const embedMod = await import("./embed.mjs");
|
|
267
|
+
// Resolve the query embedding AND the model/dim it was produced with, so
|
|
268
|
+
// we can constrain scoring to compatible embeddings only (audit M12). A
|
|
269
|
+
// re-index under a new embedModel leaves old-dim rows whose cosine would
|
|
270
|
+
// be 0 on length mismatch — silently collapsing recall. Filtering on
|
|
271
|
+
// (model, dim) makes that impossible instead of relying on a 0 coincidence.
|
|
272
|
+
let queryVec, queryModel;
|
|
273
|
+
if (typeof opts._embedQuery === "function") {
|
|
274
|
+
// Offline/test injection seam: returns { vector, model } or a bare vector.
|
|
275
|
+
const r = await opts._embedQuery(opts.query, { model: opts.embedModel });
|
|
276
|
+
if (r && r.vector) { queryVec = r.vector; queryModel = r.model; }
|
|
277
|
+
else { queryVec = r; queryModel = opts.embedModel; }
|
|
278
|
+
} else {
|
|
279
|
+
const r = await embedMod.embedTexts([opts.query || ""], { model: opts.embedModel });
|
|
280
|
+
queryVec = r.vectors[0];
|
|
281
|
+
queryModel = r.model;
|
|
282
|
+
}
|
|
283
|
+
if (!queryVec) return [];
|
|
284
|
+
const queryDim = queryVec.length;
|
|
285
|
+
if (!queryModel) queryModel = opts.embedModel || "text-embedding-3-small";
|
|
286
|
+
|
|
287
|
+
// Bind the (model, dim) filter (+ optional kind) on every embeddings read.
|
|
288
|
+
const filterSql = " WHERE e.model = ? AND e.dim = ?" + (opts.kind ? " AND c.kind = ?" : "");
|
|
289
|
+
const filterParams = opts.kind ? [queryModel, queryDim, opts.kind] : [queryModel, queryDim];
|
|
290
|
+
|
|
291
|
+
// Enforce a documented candidate ceiling (audit H11): count first, and if
|
|
292
|
+
// the compatible-embedding count exceeds the ceiling, cap the scan with a
|
|
293
|
+
// LIMIT and warn rather than risk OOM on an unbounded BLOB pull.
|
|
294
|
+
const ceiling = maxSemanticCandidates();
|
|
295
|
+
const candidateCount = db.prepare(
|
|
296
|
+
"SELECT COUNT(*) AS n FROM chunks c JOIN embeddings e ON e.chunk_rowid = c.rowid" + filterSql
|
|
297
|
+
).get(...filterParams).n;
|
|
298
|
+
if (candidateCount === 0) return [];
|
|
299
|
+
let limitClause = "";
|
|
300
|
+
const scanParams = [...filterParams];
|
|
301
|
+
if (candidateCount > ceiling) {
|
|
302
|
+
console.warn(
|
|
303
|
+
`[rag] semantic candidate set ${candidateCount} exceeds ceiling ${ceiling}; ` +
|
|
304
|
+
`capping scan (set RAG_MAX_SEMANTIC_CANDIDATES to raise, or add an ANN index).`
|
|
305
|
+
);
|
|
306
|
+
limitClause = " LIMIT ?";
|
|
307
|
+
scanParams.push(ceiling);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Scoring SELECT deliberately omits c.body (audit H11): bodies are large
|
|
311
|
+
// and we only need them for the final topN, fetched by rowid below.
|
|
312
|
+
const rows = db.prepare(
|
|
313
|
+
"SELECT c.rowid, c.source_path, c.kind, c.title, c.heading, e.vector " +
|
|
314
|
+
"FROM chunks c JOIN embeddings e ON e.chunk_rowid = c.rowid" + filterSql + limitClause
|
|
315
|
+
).all(...scanParams);
|
|
316
|
+
if (rows.length === 0) return [];
|
|
317
|
+
|
|
318
|
+
// Brute-force cosine over the (now body-free) candidate rows.
|
|
319
|
+
const scored = rows.map((r) => {
|
|
320
|
+
const v = embedMod.deserializeVector(r.vector);
|
|
321
|
+
return {
|
|
322
|
+
rowid: r.rowid,
|
|
323
|
+
source_path: r.source_path,
|
|
324
|
+
kind: r.kind,
|
|
325
|
+
title: r.title,
|
|
326
|
+
heading: r.heading || null,
|
|
327
|
+
similarity: embedMod.cosineSimilarity(queryVec, v),
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
331
|
+
const top = scored.slice(0, topN);
|
|
332
|
+
|
|
333
|
+
// Fetch bodies ONLY for the final topN (by rowid) to build snippets.
|
|
334
|
+
const bodyById = new Map();
|
|
335
|
+
if (top.length) {
|
|
336
|
+
const placeholders = top.map(() => "?").join(",");
|
|
337
|
+
for (const br of db.prepare(
|
|
338
|
+
`SELECT rowid, body FROM chunks WHERE rowid IN (${placeholders})`
|
|
339
|
+
).all(...top.map((r) => r.rowid))) {
|
|
340
|
+
bodyById.set(br.rowid, br.body);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return top.map((r, i) => ({
|
|
345
|
+
source_path: r.source_path,
|
|
346
|
+
kind: r.kind,
|
|
347
|
+
title: r.title,
|
|
348
|
+
heading: r.heading,
|
|
349
|
+
snippet: bareSnippet(bodyById.get(r.rowid) || "", opts.query),
|
|
350
|
+
score: r.similarity, // for compat
|
|
351
|
+
relevance: Math.max(0.01, Math.min(1, (r.similarity - top[top.length - 1].similarity) /
|
|
352
|
+
(top[0].similarity - top[top.length - 1].similarity + 1e-9))),
|
|
353
|
+
retrieval: "semantic",
|
|
354
|
+
semantic_rank: i + 1,
|
|
355
|
+
semantic_score: r.similarity,
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Hybrid path — Reciprocal Rank Fusion of BM25 + cosine rankings
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
const RRF_K = 60; // standard constant in the RRF literature (Cormack et al. 2009)
|
|
364
|
+
const FETCH_PER_LIST = 30; // pull more from each list than topN so the fusion has material to rank
|
|
365
|
+
|
|
366
|
+
async function runHybrid(db, opts, topN) {
|
|
367
|
+
// Run FTS5 first (cheap, in-process).
|
|
368
|
+
const ftsResults = runFts(db, { ...opts, topN: FETCH_PER_LIST }, FETCH_PER_LIST);
|
|
369
|
+
// And semantic in parallel.
|
|
370
|
+
let semanticResults = [];
|
|
371
|
+
try {
|
|
372
|
+
semanticResults = await runSemantic(db, { ...opts, topN: FETCH_PER_LIST }, FETCH_PER_LIST);
|
|
373
|
+
} catch { /* embeddings unavailable; we fall back to FTS-only below */ }
|
|
374
|
+
|
|
375
|
+
if (semanticResults.length === 0) return ftsResults.slice(0, topN);
|
|
376
|
+
if (ftsResults.length === 0) return semanticResults.slice(0, topN);
|
|
377
|
+
|
|
378
|
+
// Reciprocal Rank Fusion: each result's contribution from each list is
|
|
379
|
+
// 1 / (k + rank). Sum across lists, sort, take top N.
|
|
380
|
+
const fused = new Map(); // source_path|heading → aggregator
|
|
381
|
+
const keyOf = (r) => `${r.source_path}|${r.heading || ""}`;
|
|
382
|
+
|
|
383
|
+
for (let i = 0; i < ftsResults.length; i++) {
|
|
384
|
+
const r = ftsResults[i];
|
|
385
|
+
const k = keyOf(r);
|
|
386
|
+
fused.set(k, {
|
|
387
|
+
...r,
|
|
388
|
+
rrf_score: 1 / (RRF_K + (i + 1)),
|
|
389
|
+
bm25_rank: i + 1,
|
|
390
|
+
semantic_rank: null,
|
|
391
|
+
semantic_score: null,
|
|
392
|
+
retrieval: "hybrid",
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
for (let i = 0; i < semanticResults.length; i++) {
|
|
396
|
+
const r = semanticResults[i];
|
|
397
|
+
const k = keyOf(r);
|
|
398
|
+
if (fused.has(k)) {
|
|
399
|
+
const f = fused.get(k);
|
|
400
|
+
f.rrf_score += 1 / (RRF_K + (i + 1));
|
|
401
|
+
f.semantic_rank = i + 1;
|
|
402
|
+
f.semantic_score = r.semantic_score;
|
|
403
|
+
} else {
|
|
404
|
+
fused.set(k, {
|
|
405
|
+
source_path: r.source_path,
|
|
406
|
+
kind: r.kind,
|
|
407
|
+
title: r.title,
|
|
408
|
+
heading: r.heading,
|
|
409
|
+
snippet: r.snippet,
|
|
410
|
+
score: r.score,
|
|
411
|
+
rrf_score: 1 / (RRF_K + (i + 1)),
|
|
412
|
+
bm25_rank: null,
|
|
413
|
+
semantic_rank: i + 1,
|
|
414
|
+
semantic_score: r.semantic_score,
|
|
415
|
+
retrieval: "hybrid",
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const arr = Array.from(fused.values()).sort((a, b) => b.rrf_score - a.rrf_score).slice(0, topN);
|
|
421
|
+
if (arr.length === 0) return [];
|
|
422
|
+
const bestRrf = arr[0].rrf_score;
|
|
423
|
+
return arr.map((r) => ({
|
|
424
|
+
source_path: r.source_path,
|
|
425
|
+
kind: r.kind,
|
|
426
|
+
title: r.title,
|
|
427
|
+
heading: r.heading,
|
|
428
|
+
snippet: r.snippet,
|
|
429
|
+
score: r.score,
|
|
430
|
+
relevance: bestRrf === 0 ? 1 : Math.max(0.01, Math.min(1, r.rrf_score / bestRrf)),
|
|
431
|
+
retrieval: "hybrid",
|
|
432
|
+
bm25_rank: r.bm25_rank,
|
|
433
|
+
semantic_rank: r.semantic_rank,
|
|
434
|
+
semantic_score: r.semantic_score,
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Produce a snippet centered on the first query term that appears in the body. */
|
|
439
|
+
function bareSnippet(body, query) {
|
|
440
|
+
const text = String(body || "").replace(/\s+/g, " ").trim();
|
|
441
|
+
if (!text) return "";
|
|
442
|
+
const terms = String(query).toLowerCase().split(/\s+/).filter((t) => t.length > 3);
|
|
443
|
+
let idx = -1;
|
|
444
|
+
for (const t of terms) {
|
|
445
|
+
idx = text.toLowerCase().indexOf(t);
|
|
446
|
+
if (idx >= 0) break;
|
|
447
|
+
}
|
|
448
|
+
if (idx < 0) idx = 0;
|
|
449
|
+
const start = Math.max(0, idx - 60);
|
|
450
|
+
const end = Math.min(text.length, idx + 200);
|
|
451
|
+
return (start > 0 ? "…" : "") + text.slice(start, end) + (end < text.length ? "…" : "");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* @returns {Promise<{ files: number, chunks: number, by_kind: object, db_size_bytes: number }>}
|
|
456
|
+
*/
|
|
457
|
+
export async function stats(opts = {}) {
|
|
458
|
+
const Database = await loadDatabase();
|
|
459
|
+
const root = resolve(opts.agentRoot || process.env.AGENT_ROOT || process.cwd());
|
|
460
|
+
const dbPath = join(root, FTS_DB_REL);
|
|
461
|
+
if (!existsSync(dbPath)) return { files: 0, chunks: 0, by_kind: {}, db_size_bytes: 0 };
|
|
462
|
+
const { statSync } = await import("node:fs");
|
|
463
|
+
const db = new Database(dbPath, { readonly: true });
|
|
464
|
+
try {
|
|
465
|
+
const files = db.prepare("SELECT COUNT(*) AS n FROM files").get().n;
|
|
466
|
+
const chunks = db.prepare("SELECT COUNT(*) AS n FROM chunks").get().n;
|
|
467
|
+
const byKind = {};
|
|
468
|
+
for (const row of db.prepare("SELECT kind, COUNT(*) AS n FROM files GROUP BY kind").all()) {
|
|
469
|
+
byKind[row.kind] = row.n;
|
|
470
|
+
}
|
|
471
|
+
return { files, chunks, by_kind: byKind, db_size_bytes: statSync(dbPath).size };
|
|
472
|
+
} finally { db.close(); }
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export default { search, stats };
|