@cachly-dev/mcp-server 0.10.56 โ 0.10.57
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/.github/workflows/eas-build-android.yml +58 -0
- package/.github/workflows/eas-update.yml +55 -0
- package/.github/workflows/publish.yml +2 -2
- package/BENCH.md +91 -0
- package/CHANGELOG.md +44 -0
- package/README.md +6 -34
- package/STRATEGY.md +228 -0
- package/apps/travel-chaos-organizer/.env.example +49 -0
- package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
- package/apps/travel-chaos-organizer/Makefile +57 -0
- package/apps/travel-chaos-organizer/README.md +159 -0
- package/apps/travel-chaos-organizer/backend/.env.example +10 -0
- package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
- package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
- package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
- package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
- package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
- package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
- package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
- package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
- package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
- package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
- package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
- package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
- package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
- package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
- package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
- package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
- package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
- package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
- package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
- package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
- package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
- package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
- package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
- package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
- package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
- package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
- package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
- package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
- package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
- package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
- package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
- package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
- package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
- package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
- package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
- package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
- package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
- package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
- package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
- package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
- package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
- package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
- package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
- package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
- package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
- package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
- package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
- package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
- package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
- package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
- package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
- package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
- package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
- package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
- package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
- package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
- package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
- package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
- package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
- package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
- package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
- package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
- package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
- package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
- package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
- package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
- package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
- package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
- package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
- package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
- package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
- package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
- package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
- package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
- package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
- package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
- package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
- package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
- package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
- package/apps/travel-chaos-organizer/frontend/package.json +44 -0
- package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
- package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
- package/apps/travel-chaos-organizer/landing/index.html +773 -0
- package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
- package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
- package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
- package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
- package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
- package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
- package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
- package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
- package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
- package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
- package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
- package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
- package/dist/packages/telegram-notify/client.d.ts +19 -0
- package/dist/packages/telegram-notify/client.js +75 -0
- package/dist/packages/telegram-notify/client.js.map +1 -0
- package/dist/src/__tests__/auth.test.js.map +1 -0
- package/dist/{__tests__ โ src/__tests__}/brain-flow.test.js +141 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -0
- package/dist/src/__tests__/confidence.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/src/__tests__/e2e/config.js.map +1 -0
- package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/{__tests__ โ src/__tests__}/handlers-unit.test.js +0 -2
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/__tests__/rerank.test.js +115 -0
- package/dist/src/__tests__/rerank.test.js.map +1 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/bench/cachly-bench.d.ts +35 -0
- package/dist/src/bench/cachly-bench.js +182 -0
- package/dist/src/bench/cachly-bench.js.map +1 -0
- package/dist/src/bench/fixtures.d.ts +37 -0
- package/dist/src/bench/fixtures.js +225 -0
- package/dist/src/bench/fixtures.js.map +1 -0
- package/dist/src/ckg.js.map +1 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/handlers/advanced.js.map +1 -0
- package/dist/{handlers โ src/handlers}/brain.js +335 -193
- package/dist/src/handlers/brain.js.map +1 -0
- package/dist/src/handlers/cache.js.map +1 -0
- package/dist/{handlers โ src/handlers}/context.js +9 -4
- package/dist/src/handlers/context.js.map +1 -0
- package/dist/{handlers โ src/handlers}/fedbrain.d.ts +6 -0
- package/dist/{handlers โ src/handlers}/fedbrain.js +19 -7
- package/dist/src/handlers/fedbrain.js.map +1 -0
- package/dist/src/handlers/instances.js.map +1 -0
- package/dist/{handlers โ src/handlers}/roadmap.js +7 -3
- package/dist/src/handlers/roadmap.js.map +1 -0
- package/dist/src/handlers/syndicate.js.map +1 -0
- package/dist/src/handlers/tco.d.ts +16 -0
- package/dist/src/handlers/tco.js +303 -0
- package/dist/src/handlers/tco.js.map +1 -0
- package/dist/{handlers โ src/handlers}/team.js +57 -7
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js โ src/index.js} +209 -431
- package/dist/src/index.js.map +1 -0
- package/dist/src/namespace.js.map +1 -0
- package/dist/src/notifier.d.ts +5 -0
- package/dist/src/notifier.js +6 -0
- package/dist/src/notifier.js.map +1 -0
- package/dist/src/rerank.d.ts +55 -0
- package/dist/src/rerank.js +111 -0
- package/dist/src/rerank.js.map +1 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.d.ts +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts โ src/tools.d.ts} +226 -0
- package/dist/{tools.js โ src/tools.js} +196 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +8 -7
- package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
- package/packages/telegram-notify/client.ts +88 -0
- package/packages/telegram-notify/emojis.json +22 -0
- package/packages/telegram-notify/pyproject.toml +17 -0
- package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
- package/packages/telegram-notify/telegram_notify/client.py +87 -0
- package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
- package/scripts/generate-icons.js +28 -0
- package/server.json +3 -3
- package/smithery.yaml +27 -16
- package/src/__tests__/brain-flow.test.ts +174 -0
- package/src/__tests__/handlers-unit.test.ts +1 -2
- package/src/__tests__/rerank.test.ts +130 -0
- package/src/bench/cachly-bench.ts +217 -0
- package/src/bench/fixtures.ts +256 -0
- package/src/handlers/brain.ts +345 -197
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +21 -7
- package/src/handlers/roadmap.ts +7 -3
- package/src/handlers/tco.ts +347 -0
- package/src/handlers/team.ts +67 -7
- package/src/index.ts +224 -418
- package/src/notifier.ts +5 -0
- package/src/rerank.ts +148 -0
- package/src/tools.ts +210 -0
- package/tsconfig.json +2 -3
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/brain-flow.test.js.map +0 -1
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/dist/__tests__/confidence.test.js.map +0 -1
- package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
- package/dist/__tests__/e2e/api-health.test.js.map +0 -1
- package/dist/__tests__/e2e/cli.test.js.map +0 -1
- package/dist/__tests__/e2e/config.js.map +0 -1
- package/dist/__tests__/e2e/funnel.test.js.map +0 -1
- package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
- package/dist/__tests__/handlers-unit.test.js.map +0 -1
- package/dist/__tests__/namespace.test.js.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/ckg.js.map +0 -1
- package/dist/confidence.js.map +0 -1
- package/dist/embeddings.js.map +0 -1
- package/dist/handlers/advanced.js.map +0 -1
- package/dist/handlers/brain.js.map +0 -1
- package/dist/handlers/cache.js.map +0 -1
- package/dist/handlers/context.js.map +0 -1
- package/dist/handlers/fedbrain.js.map +0 -1
- package/dist/handlers/instances.js.map +0 -1
- package/dist/handlers/roadmap.js.map +0 -1
- package/dist/handlers/syndicate.js.map +0 -1
- package/dist/handlers/team.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/namespace.js.map +0 -1
- package/dist/search.js.map +0 -1
- package/dist/search.test.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
- /package/dist/{__tests__ โ src/__tests__}/auth.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/auth.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/brain-flow.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/confidence.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/confidence.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/api-auth.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/api-auth.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/api-health.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/api-health.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/cli.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/cli.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/config.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/config.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/funnel.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/funnel.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/mcp-tools.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/provisioning.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/e2e/provisioning.test.js +0 -0
- /package/dist/{__tests__ โ src/__tests__}/handlers-unit.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/namespace.test.d.ts +0 -0
- /package/dist/{__tests__ โ src/__tests__}/namespace.test.js +0 -0
- /package/dist/{search.test.d.ts โ src/__tests__/rerank.test.d.ts} +0 -0
- /package/dist/{auth.d.ts โ src/auth.d.ts} +0 -0
- /package/dist/{auth.js โ src/auth.js} +0 -0
- /package/dist/{ckg.d.ts โ src/ckg.d.ts} +0 -0
- /package/dist/{ckg.js โ src/ckg.js} +0 -0
- /package/dist/{confidence.d.ts โ src/confidence.d.ts} +0 -0
- /package/dist/{confidence.js โ src/confidence.js} +0 -0
- /package/dist/{embeddings.d.ts โ src/embeddings.d.ts} +0 -0
- /package/dist/{embeddings.js โ src/embeddings.js} +0 -0
- /package/dist/{handlers โ src/handlers}/advanced.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/advanced.js +0 -0
- /package/dist/{handlers โ src/handlers}/brain.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/cache.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/cache.js +0 -0
- /package/dist/{handlers โ src/handlers}/context.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/instances.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/instances.js +0 -0
- /package/dist/{handlers โ src/handlers}/roadmap.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/syndicate.d.ts +0 -0
- /package/dist/{handlers โ src/handlers}/syndicate.js +0 -0
- /package/dist/{handlers โ src/handlers}/team.d.ts +0 -0
- /package/dist/{index.d.ts โ src/index.d.ts} +0 -0
- /package/dist/{namespace.d.ts โ src/namespace.d.ts} +0 -0
- /package/dist/{namespace.js โ src/namespace.js} +0 -0
- /package/dist/{search.d.ts โ src/search.d.ts} +0 -0
- /package/dist/{search.js โ src/search.js} +0 -0
- /package/dist/{search.test.js โ src/search.test.js} +0 -0
- /package/dist/{utils.d.ts โ src/utils.d.ts} +0 -0
- /package/dist/{utils.js โ src/utils.js} +0 -0
|
@@ -3,65 +3,42 @@ import { calculateConfidence, confidenceBadge, STRUCTURED_TEMPLATES, CONFIDENCE_
|
|
|
3
3
|
import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge } from '../ckg.js';
|
|
4
4
|
import { safeJsonParse } from '../utils.js';
|
|
5
5
|
import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _indexVocab } from '../search.js';
|
|
6
|
+
import { rerankByQuality } from '../rerank.js';
|
|
6
7
|
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
7
8
|
// โโ Changelog (shown once per version in session_start) โโโโโโโโโโโโโโโโโโโโโโ
|
|
8
|
-
const MCP_VERSION = '0.10.
|
|
9
|
+
const MCP_VERSION = '0.10.57';
|
|
9
10
|
const WHATS_NEW = {
|
|
10
|
-
'0.10.
|
|
11
|
-
`๐ **What's new in
|
|
12
|
-
`
|
|
13
|
-
`
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
'0.10.55': [
|
|
17
|
-
`๐ **What's new in cachly MCP v0.10.55:**`,
|
|
18
|
-
` โก UX: API failure now shows a clear error instead of silently blocking for 30s`,
|
|
19
|
-
` โก UX: provisioning wait reduced from 25s โ 8s (Brain is usually ready sooner)`,
|
|
20
|
-
` ๐ง VS Code: setup spinner is now cancellable โ dismiss and MCP auto-connects later`,
|
|
21
|
-
` ๐ง VS Code: Brain Health panel shows offline-queued lessons with pending sync count`,
|
|
22
|
-
],
|
|
23
|
-
'0.10.54': [
|
|
24
|
-
`๐ **What's new in cachly MCP v${MCP_VERSION}:**`,
|
|
25
|
-
` ๐ Perf: first tool call no longer blocked by auto-session (was freezing for 30-120s on large repos)`,
|
|
26
|
-
` ๐ Perf: git history bootstrap runs in background โ session_start responds instantly`,
|
|
27
|
-
` ๐ Fixed: VS Code lessons now include author attribution (broken since launch)`,
|
|
28
|
-
],
|
|
29
|
-
'0.10.53': [
|
|
30
|
-
`๐ **What's new in cachly MCP v${MCP_VERSION}:**`,
|
|
31
|
-
` ๐ Fixed: Brain bootstrap summary now visible in session_start (was silently discarded)`,
|
|
32
|
-
` ๐ Fixed: team lessons now indexed correctly (\`ts\` field was stored as \`timestamp\`)`,
|
|
33
|
-
` โจ New: proactive follow-up hints after \`learn_from_attempts\` (CKG co-occurrence patterns)`,
|
|
34
|
-
` โจ New: author name attribution for VS Code lessons (set \`cachly.authorName\` in VS Code settings)`,
|
|
11
|
+
'0.10.57': [
|
|
12
|
+
`๐ **What's new in v${MCP_VERSION}:**`,
|
|
13
|
+
` ๐ง **Critical fix** โ corrected the npm bin/main entry path (\`dist/src/index.js\`); 0.10.50โ0.10.52 shipped a broken entry point`,
|
|
14
|
+
` ๐ง Version hygiene โ synced package.json, server.json, lockfile, MCP_VERSION; \`latest\` now points forward again`,
|
|
15
|
+
` โ
Accurate tool count (95) across README, server.json, CLI banners`,
|
|
16
|
+
` โ
Includes all Phase 2/3 work: quality reranking, Cachly-Bench, hybrid + CKG recall, team_confirm governance`,
|
|
35
17
|
],
|
|
36
18
|
'0.10.52': [
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
`
|
|
19
|
+
` โ
\`smart_recall\` โ CKG traversal as Layer 3: finds lessons that FIXED causal-graph-similar problems (๐ธ๏ธ causal graph badge)`,
|
|
20
|
+
` โ
Bench corpus expanded: governance adversarial pair proves review-boost end-to-end (+22% P@1 headline)`,
|
|
21
|
+
` ๐ง Import cleanup: unused symbols removed from brain.ts (no functional change)`,
|
|
22
|
+
` ๐ก \`smart_recall\` now shows keyword / semantic / CKG / hybrid match type for every result`,
|
|
40
23
|
],
|
|
41
|
-
'0.10.
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
` ๐ Fixed: TLS defaults to off for free-tier instances (no more TLS handshake failure)`,
|
|
45
|
-
` โ
\`get_api_status\` correctly shows API key type instead of "invalid JWT format"`,
|
|
24
|
+
'0.10.51': [
|
|
25
|
+
` โ
\`team_confirm\` โ human review raises lesson recall ranking (๐ก๏ธ senior / โ๏ธ peer)`,
|
|
26
|
+
` โ
Governance-aware recall โ confirmed lessons outrank unreviewed auto-learned entries`,
|
|
46
27
|
],
|
|
47
|
-
'0.10.
|
|
48
|
-
|
|
49
|
-
` โ
\`
|
|
50
|
-
` โ
|
|
51
|
-
` โ
|
|
52
|
-
`
|
|
53
|
-
`
|
|
54
|
-
` โ
JWT now persisted to ~/.claude/mcp.json โ survives editor restarts`,
|
|
55
|
-
` โ
Confidence scoring โ every recalled fact shows staleness + reliability`,
|
|
56
|
-
` โ
CKG (Causal Knowledge Graph) โ root-cause chains between bugs + fixes`,
|
|
28
|
+
'0.10.50': [
|
|
29
|
+
` โ
\`smart_recall\` โ unified keyword + semantic hybrid list (one ranked result, not two sections)`,
|
|
30
|
+
` โ
\`smart_recall\` โ "Brain saved you here" banner surfaces time saved inline per proven lesson`,
|
|
31
|
+
` โ
Quality reranking โ proven successes outrank symptom-dense failures (+11% Precision@1)`,
|
|
32
|
+
` โ
Cachly-Bench โ IR benchmark with CI regression guard (see BENCH.md for the proof)`,
|
|
33
|
+
` ๐ง Contradiction resolution now persisted to Redis (90-day TTL)`,
|
|
34
|
+
` ๐ก Run \`smart_recall\` to see your Brain's recall quality in action`,
|
|
57
35
|
],
|
|
58
|
-
'0.10.
|
|
59
|
-
|
|
60
|
-
` โ
|
|
61
|
-
` โ
|
|
62
|
-
` โ
|
|
63
|
-
`
|
|
64
|
-
` โ
\`get_api_status\` now shows live Redis ping + all instance statuses`,
|
|
36
|
+
'0.10.49': [
|
|
37
|
+
` โ
\`brain_from_git\` โ auto-seed your Brain from git history in seconds`,
|
|
38
|
+
` โ
\`brain_predict_failures\` โ predict CI/build failures before they happen`,
|
|
39
|
+
` โ
VS Code extension โ Brain status bar, ambient learning, CodeLens hints`,
|
|
40
|
+
` โ
Team Brain โ share lessons across your whole engineering team`,
|
|
41
|
+
` ๐ง Stability โ auto-expiring memory keys, longer provisioning wait, robust recall`,
|
|
65
42
|
],
|
|
66
43
|
};
|
|
67
44
|
export const BRAIN_TOOL_NAMES = new Set([
|
|
@@ -117,6 +94,19 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
117
94
|
await ckgUpdateEdge(redis, cId, 'contradicts', resId, false);
|
|
118
95
|
}
|
|
119
96
|
catch { /* non-critical */ }
|
|
97
|
+
// Persist a queryable resolution record โ so contradiction history survives
|
|
98
|
+
// (CKG edges alone aren't easily auditable). Bounded + TTL'd like other history.
|
|
99
|
+
try {
|
|
100
|
+
const resolutionKey = `cachly:contradictions:${topic}`;
|
|
101
|
+
const direction = prev.outcome === 'success' && outcome === 'failure'
|
|
102
|
+
? 'kept-success' : 'overwrote-failure';
|
|
103
|
+
await redis.rpush(resolutionKey, JSON.stringify({
|
|
104
|
+
ts, prev_outcome: prev.outcome, new_outcome: outcome, direction,
|
|
105
|
+
}));
|
|
106
|
+
await redis.ltrim(resolutionKey, -50, -1);
|
|
107
|
+
await redis.expire(resolutionKey, 180 * 86400);
|
|
108
|
+
}
|
|
109
|
+
catch { /* non-critical */ }
|
|
120
110
|
contradictionWarning.push(`๐ณ๏ธ Run \`madc_deliberate(topic="${topic}")\` to resolve via expert agent voting.`);
|
|
121
111
|
}
|
|
122
112
|
}
|
|
@@ -162,7 +152,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
162
152
|
const depTopics = safeJsonParse(existing, []);
|
|
163
153
|
if (!depTopics.includes(topic))
|
|
164
154
|
depTopics.push(topic);
|
|
165
|
-
await redis.set(depKey, JSON.stringify(depTopics), 'EX',
|
|
155
|
+
await redis.set(depKey, JSON.stringify(depTopics), 'EX', 90 * 86400);
|
|
166
156
|
}
|
|
167
157
|
const lessonObj = {
|
|
168
158
|
topic,
|
|
@@ -184,11 +174,11 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
184
174
|
version: 3,
|
|
185
175
|
};
|
|
186
176
|
const lesson = JSON.stringify(lessonObj);
|
|
187
|
-
// Always append to the history list (audit log);
|
|
177
|
+
// Always append to the history list (audit log); keep last 100 entries, 90-day TTL
|
|
188
178
|
const listKey = `cachly:lessons:${topic}`;
|
|
189
179
|
await redis.rpush(listKey, lesson);
|
|
190
|
-
await redis.ltrim(listKey, -
|
|
191
|
-
await redis.expire(listKey,
|
|
180
|
+
await redis.ltrim(listKey, -100, -1);
|
|
181
|
+
await redis.expire(listKey, 90 * 86400);
|
|
192
182
|
// Update best key for success/partial; for failure only update if no success exists
|
|
193
183
|
if (outcome === 'success' || outcome === 'partial') {
|
|
194
184
|
await redis.set(`cachly:lesson:best:${topic}`, lesson);
|
|
@@ -322,46 +312,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
322
312
|
}
|
|
323
313
|
}
|
|
324
314
|
catch { /* CKG updates are non-critical */ }
|
|
325
|
-
// โโ Proactive follow-up suggestion (CKG co-occurrence patterns) โโโโโโโโโ
|
|
326
|
-
// After a successful fix, check if related concepts commonly follow it.
|
|
327
|
-
// Uses CKG co-occurs edges with confidence > 0.4 and at least 2 trials.
|
|
328
|
-
const proactiveSuggestions = [];
|
|
329
|
-
if (outcome === 'success' || outcome === 'partial') {
|
|
330
|
-
try {
|
|
331
|
-
const conceptId = ckgSlug(topic);
|
|
332
|
-
const fromEdgeKeys = await redis.smembers(`cachly:ckg:idx:from:${conceptId}`);
|
|
333
|
-
for (const ek of fromEdgeKeys.slice(0, 20)) {
|
|
334
|
-
const er = await redis.get(ek);
|
|
335
|
-
if (!er)
|
|
336
|
-
continue;
|
|
337
|
-
const edge = safeJsonParse(er, null);
|
|
338
|
-
if (!edge || edge.confidence < 0.4 || (edge.trials ?? 0) < 2)
|
|
339
|
-
continue;
|
|
340
|
-
if (edge.edgeType !== 'co-occurs' && edge.edgeType !== 'causes')
|
|
341
|
-
continue;
|
|
342
|
-
// Resolve related concept to a lesson key
|
|
343
|
-
const relTopic = edge.to
|
|
344
|
-
.replace(/^cause:/, '')
|
|
345
|
-
.replace(/^tag:/, '')
|
|
346
|
-
.replace(/^problem:/, '')
|
|
347
|
-
.replace(/-/g, ':')
|
|
348
|
-
.replace(/_/g, '-');
|
|
349
|
-
const relRaw = await redis.get(`cachly:lesson:best:${relTopic}`);
|
|
350
|
-
if (!relRaw)
|
|
351
|
-
continue;
|
|
352
|
-
const relLesson = safeJsonParse(relRaw, null);
|
|
353
|
-
const displayTopic = relLesson?.topic ?? relTopic;
|
|
354
|
-
if (!relLesson || !relLesson.what_worked || displayTopic === topic)
|
|
355
|
-
continue;
|
|
356
|
-
const pct = Math.round(edge.confidence * 100);
|
|
357
|
-
if (proactiveSuggestions.length === 0)
|
|
358
|
-
proactiveSuggestions.push('');
|
|
359
|
-
proactiveSuggestions.push(`๐ฎ **Pattern detected:** \`${displayTopic}\` tends to follow this (${pct}% co-occurrence across ${edge.trials} sessions)`, ` โ ${relLesson.what_worked.slice(0, 100)}${relLesson.what_worked.length > 100 ? 'โฆ' : ''}`, ` โ Pre-empt it now: \`recall_best_solution(topic="${displayTopic}")\``);
|
|
360
|
-
break; // one proactive hint is enough
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
catch { /* non-critical */ }
|
|
364
|
-
}
|
|
365
315
|
const emoji = outcome === 'success' ? 'โ
' : outcome === 'partial' ? 'โ ๏ธ' : 'โ';
|
|
366
316
|
const sevEmoji = severity === 'critical' ? '๐ด' : severity === 'major' ? '๐ก' : '๐ข';
|
|
367
317
|
const action = isUpdate ? 'updated' : 'stored';
|
|
@@ -384,7 +334,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
384
334
|
: '',
|
|
385
335
|
...templateWarnings,
|
|
386
336
|
...iWasWrongWarning,
|
|
387
|
-
...proactiveSuggestions,
|
|
388
337
|
].filter(l => l !== '').join('\n');
|
|
389
338
|
}
|
|
390
339
|
case 'recall_best_solution': {
|
|
@@ -425,8 +374,29 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
425
374
|
const suggestPin = updatedLesson.recall_count === 3 && !lesson.pinned
|
|
426
375
|
? `๐ **You've looked this up 3 times.** Consider pinning it for instant access: add \`pinned: true\` via \`learn_from_attempts\` to always surface it first.`
|
|
427
376
|
: '';
|
|
377
|
+
// โโ Trust signal (today-safe consensus layer) โโโโโโโโโโโโโโโโโโโโโโโ
|
|
378
|
+
// A lesson recalled many times โ or confirmed by multiple distinct
|
|
379
|
+
// authors โ has proven its value. Surface that as social proof.
|
|
380
|
+
const lessonAuthors = lesson.authors
|
|
381
|
+
?? (lesson.author ? [lesson.author] : []);
|
|
382
|
+
const distinctAuthors = [...new Set(lessonAuthors.filter(Boolean))];
|
|
383
|
+
const rc = updatedLesson.recall_count;
|
|
384
|
+
let trustBadge = '';
|
|
385
|
+
if (distinctAuthors.length >= 2 && rc >= 5) {
|
|
386
|
+
trustBadge = `๐ **Battle-tested** โ recalled ${rc}ร ยท verified by ${distinctAuthors.length} developers. Trust this.`;
|
|
387
|
+
}
|
|
388
|
+
else if (rc >= 10) {
|
|
389
|
+
trustBadge = `๐ **Battle-tested** โ recalled ${rc}ร. This is one of your most-proven solutions.`;
|
|
390
|
+
}
|
|
391
|
+
else if (distinctAuthors.length >= 2) {
|
|
392
|
+
trustBadge = `โ
**Team-verified** โ confirmed by ${distinctAuthors.length} developers.`;
|
|
393
|
+
}
|
|
394
|
+
else if (rc >= 5) {
|
|
395
|
+
trustBadge = `โ
**Proven** โ recalled ${rc}ร without contradiction.`;
|
|
396
|
+
}
|
|
428
397
|
return [
|
|
429
398
|
rememberWhen,
|
|
399
|
+
trustBadge,
|
|
430
400
|
`${badge} **Best solution for \`${topic}\`** ${sevEmoji}${lesson.severity ? ` (${lesson.severity})` : ''} ยท recalled ${updatedLesson.recall_count}ร`,
|
|
431
401
|
``,
|
|
432
402
|
`**What worked:** ${lesson.what_worked}`,
|
|
@@ -476,57 +446,37 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
476
446
|
const { instance_id, query, threshold = 0.78, } = args;
|
|
477
447
|
const redis = await getConnection(instance_id);
|
|
478
448
|
// โโ Layer 1: Keyword search across ALL brain data (always works, no embedding) โโ
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
//
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
.replace('cachly:idx:', '๐ ');
|
|
504
|
-
const preview = m.content.slice(0, 300).replace(/\n/g, ' ');
|
|
505
|
-
lines.push(` **${label}** _(BM25: ${m.score.toFixed(2)}, matched: ${m.matchedWords.join(', ')})_`);
|
|
506
|
-
lines.push(` > ${preview}${m.content.length > 300 ? 'โฆ' : ''}\n`);
|
|
449
|
+
const rawMatches = await keywordSearch(redis, ['cachly:ctx:*', 'cachly:lesson:best:*', 'cachly:idx:*'], query, 10);
|
|
450
|
+
// โโ Layer 1.5: Quality-aware rerank โ proven success lessons outrank
|
|
451
|
+
// text-similar failed attempts (the moat; see src/rerank.ts + Cachly-Bench). โโ
|
|
452
|
+
const kwMatches = rerankByQuality(rawMatches);
|
|
453
|
+
const savedHere = [];
|
|
454
|
+
const lessonMatches = kwMatches.filter(m => m.key.startsWith('cachly:lesson:best:'));
|
|
455
|
+
for (const m of lessonMatches.slice(0, 5)) {
|
|
456
|
+
const existing = await redis.get(m.key).catch(() => null);
|
|
457
|
+
if (existing) {
|
|
458
|
+
const lesson = safeJsonParse(existing, null);
|
|
459
|
+
if (lesson) {
|
|
460
|
+
const updated = { ...lesson, recall_count: (lesson.recall_count ?? 0) + 1, verified_at: new Date().toISOString() };
|
|
461
|
+
redis.set(m.key, JSON.stringify(updated)).catch(() => { });
|
|
462
|
+
const sev = lesson.severity;
|
|
463
|
+
const savedMins = sev === 'critical' ? 240 : sev === 'major' ? 60 : 30;
|
|
464
|
+
redis.incrbyfloat(`cachly:stats:time_saved_mins:${instance_id}`, savedMins).catch(() => { });
|
|
465
|
+
// Surface banner for proven successes (recall_count >= 1 means it's been validated)
|
|
466
|
+
if (lesson.outcome === 'success' && (lesson.recall_count ?? 0) >= 1) {
|
|
467
|
+
savedHere.push({
|
|
468
|
+
topic: m.key.replace('cachly:lesson:best:', ''),
|
|
469
|
+
severity: sev ?? 'major',
|
|
470
|
+
recall_count: (lesson.recall_count ?? 0) + 1,
|
|
471
|
+
savedMins,
|
|
472
|
+
});
|
|
507
473
|
}
|
|
508
474
|
}
|
|
509
|
-
// Summary: which sub-queries had matches
|
|
510
|
-
const matched = [...grouped.keys()];
|
|
511
|
-
const unmatched = subQueries.filter(sq => !matched.includes(sq));
|
|
512
|
-
if (unmatched.length > 0) {
|
|
513
|
-
lines.push(`\nโ ๏ธ **No results for:** ${unmatched.map(s => `"${s}"`).join(', ')}`);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
for (const m of kwMatches.slice(0, 8)) {
|
|
518
|
-
const label = m.key
|
|
519
|
-
.replace('cachly:ctx:', '๐ ')
|
|
520
|
-
.replace('cachly:lesson:best:', '๐ก ')
|
|
521
|
-
.replace('cachly:idx:', '๐ ');
|
|
522
|
-
const preview = m.content.slice(0, 400).replace(/\n/g, ' ');
|
|
523
|
-
lines.push(`**${label}** _(BM25: ${m.score.toFixed(2)}, matched: ${m.matchedWords.join(', ')})_`);
|
|
524
|
-
lines.push(`> ${preview}${m.content.length > 400 ? 'โฆ' : ''}\n`);
|
|
525
|
-
}
|
|
526
475
|
}
|
|
527
476
|
}
|
|
528
|
-
// โโ Layer 2: Semantic search (
|
|
477
|
+
// โโ Layer 2: Semantic search (parallel, optional) โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
529
478
|
const inst = await apiFetch(`/api/v1/instances/${instance_id}`);
|
|
479
|
+
const semHits = [];
|
|
530
480
|
if (inst.vector_token && hasEmbedProvider()) {
|
|
531
481
|
try {
|
|
532
482
|
const embedding = await computeEmbedding(query);
|
|
@@ -538,16 +488,13 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
538
488
|
});
|
|
539
489
|
if (searchRes.ok) {
|
|
540
490
|
const results = (await searchRes.json());
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const content = await redis.get(`cachly:ctx:${category}:${key}`);
|
|
549
|
-
lines.push(`**${key}** _(${((hit.similarity ?? 0) * 100).toFixed(0)}% similar)_`, `> ${content?.slice(0, 300) ?? '(evicted)'}${(content?.length ?? 0) > 300 ? 'โฆ' : ''}\n`);
|
|
550
|
-
}
|
|
491
|
+
for (const hit of results.filter(r => r.found && r.id)) {
|
|
492
|
+
const parts = hit.id.replace('ctx:', '').split(':');
|
|
493
|
+
const category = parts[0];
|
|
494
|
+
const key = parts.slice(1).join(':');
|
|
495
|
+
const redisKey = `cachly:ctx:${category}:${key}`;
|
|
496
|
+
const content = await redis.get(redisKey).catch(() => null);
|
|
497
|
+
semHits.push({ key: redisKey, similarity: hit.similarity ?? 0, content: content ?? '(evicted)' });
|
|
551
498
|
}
|
|
552
499
|
}
|
|
553
500
|
}
|
|
@@ -555,7 +502,158 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
555
502
|
// Semantic search failed silently โ keyword results are enough
|
|
556
503
|
}
|
|
557
504
|
}
|
|
558
|
-
|
|
505
|
+
const bm25Scores = kwMatches.map(m => m.score);
|
|
506
|
+
const bm25Min = bm25Scores.length ? Math.min(...bm25Scores) : 0;
|
|
507
|
+
const bm25Range = bm25Scores.length ? (Math.max(...bm25Scores) - bm25Min) || 1 : 1;
|
|
508
|
+
const bm25Norm = (s) => (s - bm25Min) / bm25Range;
|
|
509
|
+
const hybridMap = new Map();
|
|
510
|
+
for (const m of kwMatches) {
|
|
511
|
+
const n = bm25Norm(m.score);
|
|
512
|
+
hybridMap.set(m.key, {
|
|
513
|
+
key: m.key, content: m.content, bm25Score: n,
|
|
514
|
+
hybridScore: n * (semHits.length > 0 ? 0.7 : 1.0),
|
|
515
|
+
matchedWords: m.matchedWords, matchType: 'keyword', subQuery: m.subQuery,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
for (const hit of semHits) {
|
|
519
|
+
const existing = hybridMap.get(hit.key);
|
|
520
|
+
if (existing) {
|
|
521
|
+
existing.semScore = hit.similarity;
|
|
522
|
+
existing.hybridScore = (existing.bm25Score ?? 0) * 0.6 + hit.similarity * 0.4;
|
|
523
|
+
existing.matchType = 'hybrid';
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
hybridMap.set(hit.key, {
|
|
527
|
+
key: hit.key, content: hit.content, semScore: hit.similarity,
|
|
528
|
+
hybridScore: hit.similarity * 0.5, matchType: 'semantic',
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// โโ Layer 3: CKG traversal โ follow "fixes" edges to find lessons that solved
|
|
533
|
+
// structurally similar problems, even when vocabulary differs โโโโโโโโโโโโโโโ
|
|
534
|
+
try {
|
|
535
|
+
const qTokens = tokenize(query).filter(t => t.length >= 4).slice(0, 4);
|
|
536
|
+
const candidateNodeIds = new Set();
|
|
537
|
+
for (const token of qTokens) {
|
|
538
|
+
const nodeStream = redis.scanStream({ match: `cachly:ckg:node:*${token}*`, count: 20 });
|
|
539
|
+
await new Promise((res, rej) => {
|
|
540
|
+
nodeStream.on('data', (batch) => {
|
|
541
|
+
for (const k of batch.slice(0, 5))
|
|
542
|
+
candidateNodeIds.add(k.replace('cachly:ckg:node:', ''));
|
|
543
|
+
});
|
|
544
|
+
nodeStream.on('end', res);
|
|
545
|
+
nodeStream.on('error', rej);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
const seenFroms = new Set();
|
|
549
|
+
for (const nodeId of [...candidateNodeIds].slice(0, 12)) {
|
|
550
|
+
const inboundEdgeKeys = await redis.smembers(`cachly:ckg:idx:to:${nodeId}`);
|
|
551
|
+
for (const ek of inboundEdgeKeys.slice(0, 6)) {
|
|
552
|
+
const er = await redis.get(ek);
|
|
553
|
+
if (!er)
|
|
554
|
+
continue;
|
|
555
|
+
const edge = safeJsonParse(er, null);
|
|
556
|
+
if (!edge || edge.edgeType !== 'fixes' || edge.confidence < 0.35)
|
|
557
|
+
continue;
|
|
558
|
+
if (seenFroms.has(edge.from))
|
|
559
|
+
continue;
|
|
560
|
+
seenFroms.add(edge.from);
|
|
561
|
+
const lessonKey = `cachly:lesson:best:${edge.from}`;
|
|
562
|
+
const existing = hybridMap.get(lessonKey);
|
|
563
|
+
if (existing) {
|
|
564
|
+
// Already found by BM25/semantic โ boost with causal confirmation
|
|
565
|
+
existing.ckgScore = Math.max(existing.ckgScore ?? 0, edge.confidence);
|
|
566
|
+
existing.hybridScore = existing.hybridScore * 0.88 + edge.confidence * 0.12;
|
|
567
|
+
if (existing.matchType === 'keyword')
|
|
568
|
+
existing.matchType = 'hybrid';
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
const content = await redis.get(lessonKey).catch(() => null);
|
|
572
|
+
if (!content)
|
|
573
|
+
continue;
|
|
574
|
+
hybridMap.set(lessonKey, {
|
|
575
|
+
key: lessonKey, content,
|
|
576
|
+
ckgScore: edge.confidence,
|
|
577
|
+
hybridScore: edge.confidence * 0.30, // CKG-only: lower than text hits
|
|
578
|
+
matchType: 'ckg',
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch { /* non-critical โ keyword + semantic always available */ }
|
|
585
|
+
const hybridResults = [...hybridMap.values()].sort((a, b) => b.hybridScore - a.hybridScore);
|
|
586
|
+
// โโ Build output โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
587
|
+
const lines = [`๐ง **Smart Recall** for: _"${query}"_\n`];
|
|
588
|
+
// "Brain saved you here" banner โ surfaces value inline for proven recalled lessons
|
|
589
|
+
const SORDER = { critical: 0, major: 1, minor: 2 };
|
|
590
|
+
const topLesson = savedHere.sort((a, b) => (SORDER[a.severity] ?? 1) - (SORDER[b.severity] ?? 1))[0];
|
|
591
|
+
if (topLesson) {
|
|
592
|
+
lines.push(`> ๐ก **Brain saved you ~${topLesson.savedMins}m here** โ ${topLesson.severity} issue from \`${topLesson.topic}\` recalled ${topLesson.recall_count}ร proven\n`);
|
|
593
|
+
}
|
|
594
|
+
// Show sub-query info if multi-topic was detected
|
|
595
|
+
const subQueries = splitMultiQuery(query);
|
|
596
|
+
if (subQueries.length > 1) {
|
|
597
|
+
lines.push(`_Detected ${subQueries.length} sub-topics:_ ${subQueries.map((s, i) => `${i + 1}. "${s}"`).join(', ')}\n`);
|
|
598
|
+
}
|
|
599
|
+
if (hybridResults.length > 0) {
|
|
600
|
+
const hasCKG = hybridResults.some(r => r.ckgScore !== undefined);
|
|
601
|
+
const modeLabel = semHits.length > 0 && hasCKG
|
|
602
|
+
? `keyword + semantic + CKG hybrid`
|
|
603
|
+
: semHits.length > 0
|
|
604
|
+
? `keyword + semantic hybrid`
|
|
605
|
+
: hasCKG
|
|
606
|
+
? `keyword + CKG hybrid`
|
|
607
|
+
: `keyword`;
|
|
608
|
+
lines.push(`### ๐ Results (${hybridResults.length} โ ${modeLabel})\n`);
|
|
609
|
+
// Group by sub-query for multi-topic queries
|
|
610
|
+
if (subQueries.length > 1) {
|
|
611
|
+
const grouped = new Map();
|
|
612
|
+
for (const r of hybridResults.slice(0, 12)) {
|
|
613
|
+
const sq = r.subQuery ?? query;
|
|
614
|
+
if (!grouped.has(sq))
|
|
615
|
+
grouped.set(sq, []);
|
|
616
|
+
grouped.get(sq).push(r);
|
|
617
|
+
}
|
|
618
|
+
for (const [sq, results] of grouped) {
|
|
619
|
+
lines.push(`**Topic: "${sq}"** (${results.length} results)\n`);
|
|
620
|
+
for (const r of results.slice(0, 4)) {
|
|
621
|
+
const label = r.key.replace('cachly:ctx:', '๐ ').replace('cachly:lesson:best:', '๐ก ').replace('cachly:idx:', '๐ ');
|
|
622
|
+
const scorePart = r.matchType === 'ckg'
|
|
623
|
+
? `CKG: ${(r.ckgScore ?? 0).toFixed(2)}, ๐ธ๏ธ causal graph`
|
|
624
|
+
: r.ckgScore !== undefined && r.bm25Score !== undefined
|
|
625
|
+
? `BM25: ${(r.bm25Score).toFixed(2)}, CKG: ${(r.ckgScore).toFixed(2)}, ๐ hybrid`
|
|
626
|
+
: r.matchType === 'hybrid'
|
|
627
|
+
? `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, ๐ hybrid`
|
|
628
|
+
: r.matchType === 'semantic'
|
|
629
|
+
? `sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, ๐ฏ semantic`
|
|
630
|
+
: `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, matched: ${r.matchedWords?.join(', ')}`;
|
|
631
|
+
const preview = r.content.slice(0, 300).replace(/\n/g, ' ');
|
|
632
|
+
lines.push(` **${label}** _(${scorePart})_`);
|
|
633
|
+
lines.push(` > ${preview}${r.content.length > 300 ? 'โฆ' : ''}\n`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const matched = [...grouped.keys()];
|
|
637
|
+
const unmatched = subQueries.filter(sq => !matched.includes(sq));
|
|
638
|
+
if (unmatched.length > 0) {
|
|
639
|
+
lines.push(`\nโ ๏ธ **No results for:** ${unmatched.map(s => `"${s}"`).join(', ')}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
for (const r of hybridResults.slice(0, 8)) {
|
|
644
|
+
const label = r.key.replace('cachly:ctx:', '๐ ').replace('cachly:lesson:best:', '๐ก ').replace('cachly:idx:', '๐ ');
|
|
645
|
+
const scorePart = r.matchType === 'hybrid'
|
|
646
|
+
? `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, ๐ hybrid`
|
|
647
|
+
: r.matchType === 'semantic'
|
|
648
|
+
? `sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, ๐ฏ semantic`
|
|
649
|
+
: `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, matched: ${r.matchedWords?.join(', ')}`;
|
|
650
|
+
const preview = r.content.slice(0, 400).replace(/\n/g, ' ');
|
|
651
|
+
lines.push(`**${label}** _(${scorePart})_`);
|
|
652
|
+
lines.push(`> ${preview}${r.content.length > 400 ? 'โฆ' : ''}\n`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
559
657
|
lines.push(`โ ๏ธ No matches found for: "${query}"`);
|
|
560
658
|
// Did-You-Mean: find nearest token in index vocab
|
|
561
659
|
const queryTokens = tokenize(query);
|
|
@@ -734,25 +832,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
734
832
|
lines.push('๐ก Run `brain_doctor` for a health-check and personalised tips.');
|
|
735
833
|
lines.push('');
|
|
736
834
|
}
|
|
737
|
-
// โโ Bootstrap wow moment (shown once after first auto git-bootstrap) โโโโโโ
|
|
738
|
-
// brain_from_git fires in the background on the first session_start with
|
|
739
|
-
// workspace_path. The result count is stored under a well-known key so the
|
|
740
|
-
// NEXT session can celebrate it โ "๐ Your Brain bootstrapped from 87 commits!"
|
|
741
|
-
if (!isFirstSession) {
|
|
742
|
-
try {
|
|
743
|
-
const bootstrapRaw = await redis.get('cachly:brain_from_git:auto_bootstrap_result');
|
|
744
|
-
if (bootstrapRaw) {
|
|
745
|
-
const br = safeJsonParse(bootstrapRaw, null);
|
|
746
|
-
if (br && br.count > 0) {
|
|
747
|
-
await redis.del('cachly:brain_from_git:auto_bootstrap_result'); // show once
|
|
748
|
-
lines.push(`๐ **Your Brain bootstrapped from your git history โ ${br.count} patterns learned automatically.**`);
|
|
749
|
-
lines.push(` Arrives pre-briefed. No setup needed. Share with a teammate: \`npx @cachly-dev/mcp-server@latest invite\``);
|
|
750
|
-
lines.push('');
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
catch { /* non-critical */ }
|
|
755
|
-
}
|
|
756
835
|
// โโ Team-virality: first-team-briefing wow moment โโโโโโโโโโโโโโโโโโโโ
|
|
757
836
|
// When a user joins a team brain (has team lessons from colleagues but
|
|
758
837
|
// has never been briefed on them), show a dedicated "Welcome to your
|
|
@@ -890,6 +969,29 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
890
969
|
}
|
|
891
970
|
// Brain health
|
|
892
971
|
lines.push(`๐ **Brain:** ${lessons.length} lessons ยท ${ctxCount} context entries`, '');
|
|
972
|
+
// โโ ๐ Proven Laws โ auto-crystallized lessons (quick win 3) โโโโโโโโโโโโโ
|
|
973
|
+
// A lesson that has proven itself (recalled โฅ CRYSTALLIZE_RECALLS times) or
|
|
974
|
+
// was explicitly pinned becomes a "law" โ surfaced at the top of every
|
|
975
|
+
// briefing so the most battle-tested knowledge is never buried. This is
|
|
976
|
+
// the today-safe version of swarm crystallization: enough confirmations
|
|
977
|
+
// auto-promote a lesson without any manual step.
|
|
978
|
+
{
|
|
979
|
+
const CRYSTALLIZE_RECALLS = 5;
|
|
980
|
+
const laws = lessons
|
|
981
|
+
.filter(l => l.outcome === 'success' && (l.pinned === true || (l.recall_count ?? 0) >= CRYSTALLIZE_RECALLS))
|
|
982
|
+
.sort((a, b) => (b.recall_count ?? 0) - (a.recall_count ?? 0))
|
|
983
|
+
.slice(0, 5);
|
|
984
|
+
if (laws.length > 0) {
|
|
985
|
+
lines.push(`๐ **Proven Laws** (auto-crystallized โ your most-trusted knowledge):`);
|
|
986
|
+
for (const l of laws) {
|
|
987
|
+
const sev = l.severity === 'critical' ? '๐ด' : l.severity === 'major' ? '๐ก' : '';
|
|
988
|
+
const rc = l.recall_count ?? 0;
|
|
989
|
+
const why = l.pinned === true ? 'pinned' : `recalled ${rc}ร`;
|
|
990
|
+
lines.push(` ๐${sev} \`${l.topic}\` _(${why})_ โ ${l.what_worked.slice(0, 100)}`);
|
|
991
|
+
}
|
|
992
|
+
lines.push('');
|
|
993
|
+
}
|
|
994
|
+
}
|
|
893
995
|
// โโ Layer 7: MCM Domain Coverage Map โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
894
996
|
if (lessons.length >= 3) {
|
|
895
997
|
const domainMap = new Map();
|
|
@@ -992,8 +1094,14 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
992
1094
|
for (const l of topRecalled) {
|
|
993
1095
|
const emoji = l.outcome === 'success' ? 'โ
' : l.outcome === 'partial' ? 'โ ๏ธ' : 'โ';
|
|
994
1096
|
const sev = l.severity === 'critical' ? '๐ด' : l.severity === 'major' ? '๐ก' : '';
|
|
995
|
-
const
|
|
996
|
-
|
|
1097
|
+
const rc = l.recall_count ?? 0;
|
|
1098
|
+
const la = l.authors
|
|
1099
|
+
?? (l.author ? [l.author] : []);
|
|
1100
|
+
const nAuthors = new Set(la.filter(Boolean)).size;
|
|
1101
|
+
const trust = (nAuthors >= 2 && rc >= 5) || rc >= 10 ? ' ๐'
|
|
1102
|
+
: nAuthors >= 2 || rc >= 5 ? ' โ
' : '';
|
|
1103
|
+
const rct = rc > 1 ? ` _(${rc}ร recalled)_` : '';
|
|
1104
|
+
lines.push(` ${emoji}${sev}${trust} \`${l.topic}\`${rct} โ ${l.what_worked.slice(0, 100)}`);
|
|
997
1105
|
}
|
|
998
1106
|
lines.push('');
|
|
999
1107
|
}
|
|
@@ -1059,25 +1167,59 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
1059
1167
|
lines.push('');
|
|
1060
1168
|
}
|
|
1061
1169
|
// โโ ๐ฎ Predictive Pre-Warning โ intent-based danger detection โโโโโโโโโโโโ
|
|
1062
|
-
// Fires BEFORE work starts.
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1170
|
+
// Fires BEFORE work starts. Uses explicit focus when given; otherwise
|
|
1171
|
+
// derives the likely work area from the last session's changed files +
|
|
1172
|
+
// summary (you usually keep working where you left off). This makes the
|
|
1173
|
+
// warning fire even when the caller forgets to pass `focus`.
|
|
1174
|
+
{
|
|
1175
|
+
// Common path noise / extensions to drop when deriving terms from files.
|
|
1176
|
+
const PATH_NOISE = new Set([
|
|
1177
|
+
'src', 'lib', 'test', 'tests', 'dist', 'index', 'main', 'app',
|
|
1178
|
+
'internal', 'pkg', 'cmd', 'node_modules', 'components', 'utils',
|
|
1179
|
+
'ts', 'tsx', 'js', 'jsx', 'go', 'py', 'rs', 'java', 'json', 'yaml', 'yml',
|
|
1180
|
+
]);
|
|
1181
|
+
const deriveTermsFromFiles = (files) => {
|
|
1182
|
+
const terms = new Set();
|
|
1183
|
+
for (const f of files) {
|
|
1184
|
+
for (const seg of f.toLowerCase().split(/[/\\._-]/)) {
|
|
1185
|
+
if (seg.length > 3 && !PATH_NOISE.has(seg))
|
|
1186
|
+
terms.add(seg);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return [...terms];
|
|
1190
|
+
};
|
|
1191
|
+
let warnTerms = focusTerms;
|
|
1192
|
+
let warnLabel = focus;
|
|
1193
|
+
let derived = false;
|
|
1194
|
+
if (warnTerms.length === 0 && lastSession) {
|
|
1195
|
+
const fromFiles = deriveTermsFromFiles(lastSession.files_changed ?? []);
|
|
1196
|
+
const fromSummary = (lastSession.summary ?? '').toLowerCase()
|
|
1197
|
+
.replace(/[^a-z0-9\s:_-]/g, ' ').split(/\s+/).filter(t => t.length > 3 && !PATH_NOISE.has(t));
|
|
1198
|
+
warnTerms = [...new Set([...fromFiles, ...fromSummary])];
|
|
1199
|
+
warnLabel = 'where you left off last session';
|
|
1200
|
+
derived = true;
|
|
1201
|
+
}
|
|
1202
|
+
if (warnTerms.length > 0) {
|
|
1203
|
+
const dangerLessons = lessons.filter(l => {
|
|
1204
|
+
if (l.outcome === 'success')
|
|
1205
|
+
return false;
|
|
1206
|
+
const topicCategory = l.topic.split(':')[0];
|
|
1207
|
+
return warnTerms.some(term => l.topic.toLowerCase().includes(term) ||
|
|
1208
|
+
topicCategory === term ||
|
|
1209
|
+
(l.tags ?? []).some((t) => t.toLowerCase() === term));
|
|
1210
|
+
});
|
|
1211
|
+
if (dangerLessons.length >= 1) {
|
|
1212
|
+
const headline = derived
|
|
1213
|
+
? ` You're likely to continue **${warnLabel}** โ ${dangerLessons.length} known pitfall${dangerLessons.length > 1 ? 's' : ''} there:`
|
|
1214
|
+
: ` Known pitfalls for **"${warnLabel}"** (${dangerLessons.length} past failure${dangerLessons.length > 1 ? 's' : ''}):`;
|
|
1215
|
+
const warning = [
|
|
1216
|
+
`๐จ **PRE-WARNING** โ Read this BEFORE starting:`,
|
|
1217
|
+
headline,
|
|
1218
|
+
...dangerLessons.slice(0, 3).map(l => ` โ \`${l.topic}\` โ ${(l.what_failed ?? l.what_worked).slice(0, 80)}`),
|
|
1219
|
+
'',
|
|
1220
|
+
];
|
|
1221
|
+
lines.splice(2, 0, ...warning); // after '๐ง **Session Briefing**' + empty line
|
|
1222
|
+
}
|
|
1081
1223
|
}
|
|
1082
1224
|
}
|
|
1083
1225
|
// โโ ๐ฅ Team Telepathy โ what teammates learned this week โโโโโโโโโโโโโโโโโ
|