@cohortapp/agent-sdk 2.17.0 → 2.18.4
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/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
package/lib/rag/rag.test.mjs
DELETED
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maestro — RAG indexer + search tests (audit H10).
|
|
3
|
-
*
|
|
4
|
-
* Runs fully offline against a temp better-sqlite3 DB. Embeddings are
|
|
5
|
-
* injected via the additive `_embedTexts` (indexer) / `_embedQuery` (search)
|
|
6
|
-
* seams so no network call is ever made.
|
|
7
|
-
*
|
|
8
|
-
* Coverage:
|
|
9
|
-
* - FTS search ordering + result shape
|
|
10
|
-
* - L18: AND-first multi-term ranking (both-term doc beats one-term doc),
|
|
11
|
-
* with OR fallback when the strict conjunction has zero hits
|
|
12
|
-
* - L19: relevance is a faithful [0,1] from the NEGATIVE bm25() score,
|
|
13
|
-
* correctly ordered, and a zero-score row does not blow up
|
|
14
|
-
* - M12: a model/dim-mismatched embedding is NOT returned by semantic search
|
|
15
|
-
* - M12: re-index under a new model purges stale-dim embeddings
|
|
16
|
-
* - M13: an interrupted embed is completed on the next NON-force run
|
|
17
|
-
* - H11: the scoring SELECT does not pull body for all rows (bodies are
|
|
18
|
-
* materialized only for the returned topN), and a large candidate
|
|
19
|
-
* set is capped by the documented ceiling.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { test } from "node:test";
|
|
23
|
-
import assert from "node:assert/strict";
|
|
24
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
25
|
-
import { tmpdir } from "node:os";
|
|
26
|
-
import { join } from "node:path";
|
|
27
|
-
|
|
28
|
-
import { buildIndex } from "./indexer.mjs";
|
|
29
|
-
import { search, DEFAULT_MAX_SEMANTIC_CANDIDATES, ftsRelevance } from "./search.mjs";
|
|
30
|
-
import { serializeVector, l2Normalize } from "./embed.mjs";
|
|
31
|
-
import DatabaseCtor from "better-sqlite3";
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Helpers
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
function makeAgentRoot(files) {
|
|
38
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-rag-"));
|
|
39
|
-
for (const [rel, content] of Object.entries(files)) {
|
|
40
|
-
const abs = join(root, rel);
|
|
41
|
-
mkdirSync(join(abs, ".."), { recursive: true });
|
|
42
|
-
writeFileSync(abs, content, "utf-8");
|
|
43
|
-
}
|
|
44
|
-
return root;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function dbPathFor(root) {
|
|
48
|
-
return join(root, "state/rag/index/agent.db");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Deterministic fake embedder. Maps each text to a fixed-dim unit vector
|
|
53
|
-
* whose direction is keyed by which "concept" tokens it contains, so that
|
|
54
|
-
* texts sharing a concept are similar and the query lands nearest the
|
|
55
|
-
* intended doc. Returns the { vectors, model } shape of embed.mjs.
|
|
56
|
-
*/
|
|
57
|
-
function fakeEmbedder(model, dim) {
|
|
58
|
-
// Concept axes: each concept gets one hot dimension.
|
|
59
|
-
const CONCEPTS = ["alpha", "beta", "gamma"];
|
|
60
|
-
return async function _embedTexts(texts) {
|
|
61
|
-
const vectors = texts.map((t) => {
|
|
62
|
-
const v = new Float32Array(dim);
|
|
63
|
-
const lower = String(t || "").toLowerCase();
|
|
64
|
-
let any = false;
|
|
65
|
-
for (let i = 0; i < CONCEPTS.length && i < dim; i++) {
|
|
66
|
-
if (lower.includes(CONCEPTS[i])) { v[i] = 1; any = true; }
|
|
67
|
-
}
|
|
68
|
-
if (!any) v[dim - 1] = 1; // park empty/probe text on a junk axis
|
|
69
|
-
return l2Normalize(v);
|
|
70
|
-
});
|
|
71
|
-
return { vectors, model, usage: { total_tokens: 0 } };
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function fakeQuery(model, dim) {
|
|
76
|
-
const embedTexts = fakeEmbedder(model, dim);
|
|
77
|
-
return async function _embedQuery(q) {
|
|
78
|
-
const { vectors } = await embedTexts([q]);
|
|
79
|
-
return { vector: vectors[0], model };
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const SAMPLE = {
|
|
84
|
-
"memory/alpha.md": "# Alpha decision\n\nWe decided alpha is the strategy for Q3. alpha alpha.",
|
|
85
|
-
"memory/beta.md": "# Beta plan\n\nThe beta rollout covers beta markets. beta beta.",
|
|
86
|
-
"knowledge/gamma.md": "# Gamma notes\n\nGamma research on gamma topics. gamma gamma.",
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// FTS path — ordering + result shape
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
test("FTS search returns the matching doc with the documented result shape", async () => {
|
|
94
|
-
const root = makeAgentRoot(SAMPLE);
|
|
95
|
-
try {
|
|
96
|
-
const r = await buildIndex({ agentRoot: root, embeddings: false });
|
|
97
|
-
assert.equal(r.added, 3, "all three fixtures indexed");
|
|
98
|
-
assert.ok(r.chunks >= 3, "at least one chunk per fixture");
|
|
99
|
-
|
|
100
|
-
const hits = await search({ query: "alpha", agentRoot: root, mode: "fts", topN: 5 });
|
|
101
|
-
assert.ok(hits.length >= 1, "alpha query returns at least one hit");
|
|
102
|
-
assert.equal(hits[0].source_path, "memory/alpha.md", "best hit is the alpha doc");
|
|
103
|
-
|
|
104
|
-
const h = hits[0];
|
|
105
|
-
for (const k of ["source_path", "kind", "title", "heading", "snippet", "score", "relevance", "retrieval"]) {
|
|
106
|
-
assert.ok(k in h, `result has '${k}'`);
|
|
107
|
-
}
|
|
108
|
-
assert.equal(h.retrieval, "fts");
|
|
109
|
-
assert.equal(typeof h.relevance, "number");
|
|
110
|
-
assert.ok(h.relevance > 0 && h.relevance <= 1, "relevance in (0,1]");
|
|
111
|
-
assert.equal(typeof h.snippet, "string");
|
|
112
|
-
} finally {
|
|
113
|
-
rmSync(root, { recursive: true, force: true });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("FTS results are ordered by relevance (top hit first)", async () => {
|
|
118
|
-
const root = makeAgentRoot(SAMPLE);
|
|
119
|
-
try {
|
|
120
|
-
await buildIndex({ agentRoot: root, embeddings: false });
|
|
121
|
-
const hits = await search({ query: "gamma", agentRoot: root, mode: "fts", topN: 5 });
|
|
122
|
-
assert.ok(hits.length >= 1);
|
|
123
|
-
assert.equal(hits[0].source_path, "knowledge/gamma.md");
|
|
124
|
-
for (let i = 1; i < hits.length; i++) {
|
|
125
|
-
assert.ok(hits[i].relevance <= hits[i - 1].relevance + 1e-9, "non-increasing relevance");
|
|
126
|
-
}
|
|
127
|
-
} finally {
|
|
128
|
-
rmSync(root, { recursive: true, force: true });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
// L18 — AND-first multi-term ranking (precision) with OR fallback
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
|
|
136
|
-
// Corpus where the AND/OR distinction matters: one doc has BOTH query terms,
|
|
137
|
-
// one has only the first, one has only the second.
|
|
138
|
-
const TWO_TERM = {
|
|
139
|
-
"memory/both.md": "# Both\n\nThe orchard manifesto covers orchard and manifesto together. orchard manifesto.",
|
|
140
|
-
"memory/orchard.md": "# Orchard only\n\nThis note is about the orchard. orchard orchard orchard orchard.",
|
|
141
|
-
"memory/manifesto.md": "# Manifesto only\n\nThis note is about the manifesto. manifesto manifesto manifesto.",
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
test("L18: a two-term query ranks the doc containing BOTH terms first (AND-first)", async () => {
|
|
145
|
-
const root = makeAgentRoot(TWO_TERM);
|
|
146
|
-
try {
|
|
147
|
-
await buildIndex({ agentRoot: root, embeddings: false });
|
|
148
|
-
const hits = await search({ query: "orchard manifesto", agentRoot: root, mode: "fts", topN: 5 });
|
|
149
|
-
assert.ok(hits.length >= 1, "two-term query returns hits");
|
|
150
|
-
// AND-first means only the both-terms doc satisfies the strict conjunction.
|
|
151
|
-
assert.equal(hits[0].source_path, "memory/both.md", "doc with BOTH terms ranks first");
|
|
152
|
-
// The one-term-only docs must rank no higher than the both-terms doc.
|
|
153
|
-
const both = hits.find((h) => h.source_path === "memory/both.md");
|
|
154
|
-
for (const h of hits) {
|
|
155
|
-
if (h.source_path === "memory/both.md") continue;
|
|
156
|
-
assert.ok(
|
|
157
|
-
both.relevance >= h.relevance,
|
|
158
|
-
`both-terms doc (${both.relevance}) >= ${h.source_path} (${h.relevance})`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
} finally {
|
|
162
|
-
rmSync(root, { recursive: true, force: true });
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("L18: AND-first falls back to OR when no doc contains every term", async () => {
|
|
167
|
-
const root = makeAgentRoot(TWO_TERM);
|
|
168
|
-
try {
|
|
169
|
-
await buildIndex({ agentRoot: root, embeddings: false });
|
|
170
|
-
// "orchard" exists; "zucchini" exists in no doc. Strict AND → zero hits, so
|
|
171
|
-
// the OR fallback must still surface the orchard docs rather than nothing.
|
|
172
|
-
const hits = await search({ query: "orchard zucchini", agentRoot: root, mode: "fts", topN: 5 });
|
|
173
|
-
assert.ok(hits.length >= 1, "OR fallback returns the orchard docs");
|
|
174
|
-
assert.ok(
|
|
175
|
-
hits.some((h) => h.source_path === "memory/orchard.md" || h.source_path === "memory/both.md"),
|
|
176
|
-
"an orchard-bearing doc is recovered by the OR fallback",
|
|
177
|
-
);
|
|
178
|
-
} finally {
|
|
179
|
-
rmSync(root, { recursive: true, force: true });
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
// L19 — faithful [0,1] relevance from the NEGATIVE bm25() score; zero-safe
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
|
|
187
|
-
test("L19: FTS relevance is within [0,1], top hit is 1.0, and ordering matches rank", async () => {
|
|
188
|
-
const root = makeAgentRoot(TWO_TERM);
|
|
189
|
-
try {
|
|
190
|
-
await buildIndex({ agentRoot: root, embeddings: false });
|
|
191
|
-
const hits = await search({ query: "orchard manifesto", agentRoot: root, mode: "fts", topN: 5 });
|
|
192
|
-
assert.ok(hits.length >= 1);
|
|
193
|
-
for (const h of hits) {
|
|
194
|
-
assert.equal(typeof h.relevance, "number");
|
|
195
|
-
assert.ok(Number.isFinite(h.relevance), `relevance is finite (got ${h.relevance})`);
|
|
196
|
-
assert.ok(h.relevance >= 0 && h.relevance <= 1, `relevance in [0,1] (got ${h.relevance})`);
|
|
197
|
-
// bm25() is negative (lower = better); score must never be positive here.
|
|
198
|
-
assert.ok(h.score <= 0, `bm25 score is non-positive (got ${h.score})`);
|
|
199
|
-
}
|
|
200
|
-
assert.ok(Math.abs(hits[0].relevance - 1) < 1e-9, "top hit relevance is 1.0");
|
|
201
|
-
for (let i = 1; i < hits.length; i++) {
|
|
202
|
-
assert.ok(
|
|
203
|
-
hits[i].relevance <= hits[i - 1].relevance + 1e-9,
|
|
204
|
-
"relevance is non-increasing with rank",
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
} finally {
|
|
208
|
-
rmSync(root, { recursive: true, force: true });
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("L19: ftsRelevance transform is zero-safe and never Inf/NaN/out-of-range", () => {
|
|
213
|
-
// Direct unit test of the exported transform — the case the old code got
|
|
214
|
-
// wrong. bm25() is negative; 0 means "no overlap".
|
|
215
|
-
// - top hit (score === best, both negative) → exactly 1.0
|
|
216
|
-
// - a weaker (less-negative) score decays toward 0 and stays in [0,1]
|
|
217
|
-
// - a row score of 0 → 0 (the old `best / 0` produced Infinity here)
|
|
218
|
-
// - a best score of 0 (degenerate set) → 0, never a division blow-up
|
|
219
|
-
// - non-finite inputs → 0, never NaN
|
|
220
|
-
assert.ok(Math.abs(ftsRelevance(-5, -5) - 1) < 1e-9, "top hit (score===best) is 1.0");
|
|
221
|
-
const weak = ftsRelevance(-1, -5);
|
|
222
|
-
assert.ok(weak > 0 && weak < 1, `weaker score decays into (0,1) (got ${weak})`);
|
|
223
|
-
assert.ok(Math.abs(weak - 0.2) < 1e-9, "ratio best/score is faithful (-5/-1 = 0.2)");
|
|
224
|
-
assert.equal(ftsRelevance(0, -5), 0, "row score 0 maps to 0 (no Infinity)");
|
|
225
|
-
assert.equal(ftsRelevance(-5, 0), 0, "best score 0 maps to 0 (no division blow-up)");
|
|
226
|
-
assert.equal(ftsRelevance(0, 0), 0, "all-zero maps to 0");
|
|
227
|
-
assert.equal(ftsRelevance(Infinity, -5), 0, "non-finite score → 0");
|
|
228
|
-
assert.equal(ftsRelevance(-5, NaN), 0, "non-finite best → 0");
|
|
229
|
-
for (const v of [ftsRelevance(-5, -5), weak, ftsRelevance(0, -5), ftsRelevance(-5, 0)]) {
|
|
230
|
-
assert.ok(Number.isFinite(v) && v >= 0 && v <= 1, `relevance ${v} is finite & in [0,1]`);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("L19: a degenerate single-doc FTS result yields a finite, in-range relevance", async () => {
|
|
235
|
-
// End-to-end sanity: even the simplest corpus must never emit Inf/NaN
|
|
236
|
-
// relevance (the failure mode of the old `bestScore / r.score` ratio).
|
|
237
|
-
const root = makeAgentRoot({
|
|
238
|
-
"memory/solo.md": "# Solo\n\nwidget widget widget widget widget widget.",
|
|
239
|
-
});
|
|
240
|
-
try {
|
|
241
|
-
await buildIndex({ agentRoot: root, embeddings: false });
|
|
242
|
-
const hits = await search({ query: "widget", agentRoot: root, mode: "fts", topN: 5 });
|
|
243
|
-
assert.ok(hits.length >= 1, "single-doc corpus returns the doc");
|
|
244
|
-
for (const h of hits) {
|
|
245
|
-
assert.ok(Number.isFinite(h.relevance), "relevance finite on single-row result");
|
|
246
|
-
assert.ok(h.relevance >= 0 && h.relevance <= 1, "relevance in [0,1] on single-row result");
|
|
247
|
-
}
|
|
248
|
-
// The sole hit is the best hit → relevance pinned to 1.0 (best/best),
|
|
249
|
-
// never Infinity (which the old `bestScore / r.score` guard could emit).
|
|
250
|
-
assert.ok(Math.abs(hits[0].relevance - 1) < 1e-9, "sole/best hit relevance is exactly 1.0");
|
|
251
|
-
} finally {
|
|
252
|
-
rmSync(root, { recursive: true, force: true });
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// ---------------------------------------------------------------------------
|
|
257
|
-
// Semantic path — happy ordering via injected offline embeddings
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
|
|
260
|
-
test("semantic search ranks the concept-matching doc first (offline)", async () => {
|
|
261
|
-
const DIM = 8;
|
|
262
|
-
const MODEL = "fake-small";
|
|
263
|
-
const root = makeAgentRoot(SAMPLE);
|
|
264
|
-
try {
|
|
265
|
-
const r = await buildIndex({
|
|
266
|
-
agentRoot: root,
|
|
267
|
-
embeddings: true,
|
|
268
|
-
embedModel: MODEL,
|
|
269
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
270
|
-
});
|
|
271
|
-
assert.ok(r.embeds >= 3, "embeddings generated for all chunks");
|
|
272
|
-
|
|
273
|
-
const hits = await search({
|
|
274
|
-
query: "tell me about beta",
|
|
275
|
-
agentRoot: root,
|
|
276
|
-
mode: "semantic",
|
|
277
|
-
topN: 5,
|
|
278
|
-
embedModel: MODEL,
|
|
279
|
-
_embedQuery: fakeQuery(MODEL, DIM),
|
|
280
|
-
});
|
|
281
|
-
assert.ok(hits.length >= 1, "semantic returns hits");
|
|
282
|
-
assert.equal(hits[0].source_path, "memory/beta.md", "beta doc ranks first");
|
|
283
|
-
assert.equal(hits[0].retrieval, "semantic");
|
|
284
|
-
assert.ok(typeof hits[0].snippet === "string" && hits[0].snippet.length > 0, "snippet materialized for topN");
|
|
285
|
-
} finally {
|
|
286
|
-
rmSync(root, { recursive: true, force: true });
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// ---------------------------------------------------------------------------
|
|
291
|
-
// M12 — model/dim mismatch must NOT be returned
|
|
292
|
-
// ---------------------------------------------------------------------------
|
|
293
|
-
|
|
294
|
-
test("M12: a mismatched-dim embedding is excluded from semantic results", async () => {
|
|
295
|
-
const DIM = 8;
|
|
296
|
-
const MODEL = "fake-small";
|
|
297
|
-
const root = makeAgentRoot(SAMPLE);
|
|
298
|
-
try {
|
|
299
|
-
await buildIndex({
|
|
300
|
-
agentRoot: root,
|
|
301
|
-
embeddings: true,
|
|
302
|
-
embedModel: MODEL,
|
|
303
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// Corrupt one chunk's embedding to a different (model, dim): point the
|
|
307
|
-
// gamma chunk at a vector that, if naively scored, would be a strong
|
|
308
|
-
// match for a "gamma" query — but it has the wrong dim, so cosine would
|
|
309
|
-
// be 0 (length mismatch) and the M12 SQL filter excludes it outright.
|
|
310
|
-
const db = new DatabaseCtor(dbPathFor(root));
|
|
311
|
-
try {
|
|
312
|
-
const gammaRow = db.prepare(
|
|
313
|
-
"SELECT rowid FROM chunks WHERE source_path = 'knowledge/gamma.md' LIMIT 1"
|
|
314
|
-
).get();
|
|
315
|
-
assert.ok(gammaRow, "gamma chunk exists");
|
|
316
|
-
const wrong = l2Normalize(new Float32Array([0, 0, 1, 0])); // dim 4, not 8
|
|
317
|
-
db.prepare(
|
|
318
|
-
"UPDATE embeddings SET model = ?, dim = ?, vector = ? WHERE chunk_rowid = ?"
|
|
319
|
-
).run("other-model", 4, serializeVector(wrong), gammaRow.rowid);
|
|
320
|
-
} finally {
|
|
321
|
-
db.close();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const hits = await search({
|
|
325
|
-
query: "gamma",
|
|
326
|
-
agentRoot: root,
|
|
327
|
-
mode: "semantic",
|
|
328
|
-
topN: 5,
|
|
329
|
-
embedModel: MODEL,
|
|
330
|
-
_embedQuery: fakeQuery(MODEL, DIM),
|
|
331
|
-
});
|
|
332
|
-
// The gamma doc had its embedding moved to a foreign (model, dim) so it
|
|
333
|
-
// must NOT appear in results filtered to the query's (MODEL, DIM).
|
|
334
|
-
assert.ok(
|
|
335
|
-
!hits.some((h) => h.source_path === "knowledge/gamma.md"),
|
|
336
|
-
"mismatched-dim gamma row is excluded"
|
|
337
|
-
);
|
|
338
|
-
} finally {
|
|
339
|
-
rmSync(root, { recursive: true, force: true });
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test("M12: re-index under a new model purges stale-dim embeddings", async () => {
|
|
344
|
-
const root = makeAgentRoot(SAMPLE);
|
|
345
|
-
try {
|
|
346
|
-
await buildIndex({
|
|
347
|
-
agentRoot: root,
|
|
348
|
-
embeddings: true,
|
|
349
|
-
embedModel: "fake-small",
|
|
350
|
-
_embedTexts: fakeEmbedder("fake-small", 8),
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// Re-index forcing a fresh embed under a NEW model + dim. Force re-chunks
|
|
354
|
-
// so newChunks is non-empty, but the purge must also clear old-dim rows.
|
|
355
|
-
const r2 = await buildIndex({
|
|
356
|
-
agentRoot: root,
|
|
357
|
-
force: true,
|
|
358
|
-
embeddings: true,
|
|
359
|
-
embedModel: "fake-large",
|
|
360
|
-
_embedTexts: fakeEmbedder("fake-large", 16),
|
|
361
|
-
});
|
|
362
|
-
assert.ok(r2.purged >= 0, "purged field present");
|
|
363
|
-
|
|
364
|
-
const db = new DatabaseCtor(dbPathFor(root), { readonly: true });
|
|
365
|
-
try {
|
|
366
|
-
const models = db.prepare("SELECT DISTINCT model, dim FROM embeddings").all();
|
|
367
|
-
assert.equal(models.length, 1, "only one (model, dim) survives");
|
|
368
|
-
assert.equal(models[0].model, "fake-large");
|
|
369
|
-
assert.equal(models[0].dim, 16);
|
|
370
|
-
} finally {
|
|
371
|
-
db.close();
|
|
372
|
-
}
|
|
373
|
-
} finally {
|
|
374
|
-
rmSync(root, { recursive: true, force: true });
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// ---------------------------------------------------------------------------
|
|
379
|
-
// M13 — interrupted embed completed on next non-force run
|
|
380
|
-
// ---------------------------------------------------------------------------
|
|
381
|
-
|
|
382
|
-
test("M13: missing embeddings are backfilled on the next non-force run", async () => {
|
|
383
|
-
const DIM = 8;
|
|
384
|
-
const MODEL = "fake-small";
|
|
385
|
-
const root = makeAgentRoot(SAMPLE);
|
|
386
|
-
try {
|
|
387
|
-
const r1 = await buildIndex({
|
|
388
|
-
agentRoot: root,
|
|
389
|
-
embeddings: true,
|
|
390
|
-
embedModel: MODEL,
|
|
391
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
392
|
-
});
|
|
393
|
-
const chunkCount = r1.chunks;
|
|
394
|
-
assert.ok(chunkCount >= 3);
|
|
395
|
-
|
|
396
|
-
// Simulate a crash mid-embed: delete a subset of embeddings while leaving
|
|
397
|
-
// files.mtime untouched (the file is "indexed" but partly unembedded).
|
|
398
|
-
const db = new DatabaseCtor(dbPathFor(root));
|
|
399
|
-
let deleted;
|
|
400
|
-
try {
|
|
401
|
-
deleted = db.prepare(
|
|
402
|
-
"DELETE FROM embeddings WHERE chunk_rowid IN " +
|
|
403
|
-
"(SELECT rowid FROM chunks WHERE source_path = 'memory/beta.md')"
|
|
404
|
-
).run().changes;
|
|
405
|
-
assert.ok(deleted >= 1, "deleted at least one embedding to simulate interruption");
|
|
406
|
-
const before = db.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n;
|
|
407
|
-
assert.equal(before, chunkCount - deleted);
|
|
408
|
-
} finally {
|
|
409
|
-
db.close();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Re-run WITHOUT force. The unchanged-file skip would normally skip beta,
|
|
413
|
-
// but the missing-embedding sweep (M13) must re-embed the stranded chunks.
|
|
414
|
-
const r2 = await buildIndex({
|
|
415
|
-
agentRoot: root,
|
|
416
|
-
embeddings: true,
|
|
417
|
-
embedModel: MODEL,
|
|
418
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
419
|
-
});
|
|
420
|
-
assert.equal(r2.added, 0, "no new files");
|
|
421
|
-
assert.equal(r2.updated, 0, "no changed files (mtime unchanged)");
|
|
422
|
-
assert.equal(r2.embeds, deleted, "exactly the missing embeddings were re-embedded");
|
|
423
|
-
|
|
424
|
-
const db2 = new DatabaseCtor(dbPathFor(root), { readonly: true });
|
|
425
|
-
try {
|
|
426
|
-
const after = db2.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n;
|
|
427
|
-
assert.equal(after, chunkCount, "every chunk has an embedding again");
|
|
428
|
-
} finally {
|
|
429
|
-
db2.close();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// And the beta doc is searchable again semantically.
|
|
433
|
-
const hits = await search({
|
|
434
|
-
query: "beta", agentRoot: root, mode: "semantic", topN: 5,
|
|
435
|
-
embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
|
|
436
|
-
});
|
|
437
|
-
assert.ok(hits.some((h) => h.source_path === "memory/beta.md"), "beta re-embedded + retrievable");
|
|
438
|
-
} finally {
|
|
439
|
-
rmSync(root, { recursive: true, force: true });
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// ---------------------------------------------------------------------------
|
|
444
|
-
// H11 — body not pulled for all rows; large candidate set capped
|
|
445
|
-
// ---------------------------------------------------------------------------
|
|
446
|
-
|
|
447
|
-
test("H11: bodies are materialized only for the returned topN", async () => {
|
|
448
|
-
const DIM = 8;
|
|
449
|
-
const MODEL = "fake-small";
|
|
450
|
-
const root = makeAgentRoot(SAMPLE);
|
|
451
|
-
try {
|
|
452
|
-
await buildIndex({
|
|
453
|
-
agentRoot: root,
|
|
454
|
-
embeddings: true,
|
|
455
|
-
embedModel: MODEL,
|
|
456
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
const topN = 1;
|
|
460
|
-
const hits = await search({
|
|
461
|
-
query: "alpha", agentRoot: root, mode: "semantic", topN,
|
|
462
|
-
embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
|
|
463
|
-
});
|
|
464
|
-
assert.equal(hits.length, topN, "exactly topN results");
|
|
465
|
-
// The single returned hit carries a non-empty snippet (its body WAS
|
|
466
|
-
// fetched), proving snippets are built from the per-topN body fetch and
|
|
467
|
-
// not from a body column on the scoring scan.
|
|
468
|
-
assert.ok(hits[0].snippet.length > 0, "returned hit has a snippet from its body");
|
|
469
|
-
} finally {
|
|
470
|
-
rmSync(root, { recursive: true, force: true });
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test("H11: candidate set above the ceiling is capped, not OOM", async () => {
|
|
475
|
-
const DIM = 8;
|
|
476
|
-
const MODEL = "fake-small";
|
|
477
|
-
const root = makeAgentRoot(SAMPLE);
|
|
478
|
-
const prev = process.env.RAG_MAX_SEMANTIC_CANDIDATES;
|
|
479
|
-
try {
|
|
480
|
-
await buildIndex({
|
|
481
|
-
agentRoot: root,
|
|
482
|
-
embeddings: true,
|
|
483
|
-
embedModel: MODEL,
|
|
484
|
-
_embedTexts: fakeEmbedder(MODEL, DIM),
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// Force the ceiling below our (tiny) corpus so the cap path is exercised.
|
|
488
|
-
process.env.RAG_MAX_SEMANTIC_CANDIDATES = "1";
|
|
489
|
-
const hits = await search({
|
|
490
|
-
query: "alpha", agentRoot: root, mode: "semantic", topN: 5,
|
|
491
|
-
embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
|
|
492
|
-
});
|
|
493
|
-
// Cap == 1, so at most one candidate is scanned → at most one result.
|
|
494
|
-
assert.ok(hits.length <= 1, "scan capped to the ceiling");
|
|
495
|
-
} finally {
|
|
496
|
-
if (prev === undefined) delete process.env.RAG_MAX_SEMANTIC_CANDIDATES;
|
|
497
|
-
else process.env.RAG_MAX_SEMANTIC_CANDIDATES = prev;
|
|
498
|
-
rmSync(root, { recursive: true, force: true });
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
test("DEFAULT_MAX_SEMANTIC_CANDIDATES is exported and sane", () => {
|
|
503
|
-
assert.equal(typeof DEFAULT_MAX_SEMANTIC_CANDIDATES, "number");
|
|
504
|
-
assert.ok(DEFAULT_MAX_SEMANTIC_CANDIDATES > 0);
|
|
505
|
-
});
|