@cachly-dev/mcp-server 0.10.47 → 0.10.49
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/STRATEGY.md +209 -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 +13037 -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 +143 -2
- 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 +1 -1
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/auth.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 +135 -31
- 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 +15 -2
- 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 +8 -4
- 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 +1 -1
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js → src/index.js} +145 -97
- 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/{search.js → src/search.js} +3 -3
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
- package/dist/{tools.js → src/tools.js} +178 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +3 -2
- 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/smithery.yaml +21 -10
- package/src/__tests__/brain-flow.test.ts +176 -2
- package/src/__tests__/handlers-unit.test.ts +1 -1
- package/src/handlers/brain.ts +139 -32
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +17 -2
- package/src/handlers/roadmap.ts +8 -4
- package/src/handlers/tco.ts +347 -0
- package/src/handlers/team.ts +1 -1
- package/src/index.ts +137 -147
- package/src/notifier.ts +5 -0
- package/src/search.ts +3 -3
- package/src/tools.ts +191 -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/{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.test.d.ts → src/search.test.d.ts} +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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telegram-notify — shared Telegram notification client.
|
|
3
|
+
*
|
|
4
|
+
* Used by cachly-mcp (TypeScript) and TCO backend (Python via client.py).
|
|
5
|
+
* Single source of truth for: emoji map, message format, env var convention.
|
|
6
|
+
*
|
|
7
|
+
* Interface: notify(app, event, payload)
|
|
8
|
+
*
|
|
9
|
+
* Env vars:
|
|
10
|
+
* TELEGRAM_BOT_TOKEN — bot token from @BotFather
|
|
11
|
+
* {APP_UPPER}_TELEGRAM_CHAT_ID — app-specific channel (e.g. CACHLY_TELEGRAM_CHAT_ID)
|
|
12
|
+
* TELEGRAM_CHAT_ID — fallback for all apps
|
|
13
|
+
*
|
|
14
|
+
* No-op when unconfigured. Never throws.
|
|
15
|
+
*
|
|
16
|
+
* Note: emoji map is kept in sync with emojis.json (Python reads the JSON file at runtime).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
declare const process: { env: Record<string, string | undefined> };
|
|
21
|
+
|
|
22
|
+
const EMOJI: Record<string, string> = {
|
|
23
|
+
tool_call: '🔧',
|
|
24
|
+
tool_error: '🔴',
|
|
25
|
+
cache_hit: '⚡',
|
|
26
|
+
cache_miss: '💭',
|
|
27
|
+
auth_ok: '🔑',
|
|
28
|
+
auth_fail: '🚫',
|
|
29
|
+
startup: '🚀',
|
|
30
|
+
waitlist_signup: '📬',
|
|
31
|
+
parse_file: '📄',
|
|
32
|
+
parse_text: '📝',
|
|
33
|
+
parse_url: '🔗',
|
|
34
|
+
trip_created: '🗺️',
|
|
35
|
+
trip_deleted: '🗑️',
|
|
36
|
+
error: '🔴',
|
|
37
|
+
stripe_payment: '💳',
|
|
38
|
+
stripe_checkout: '🛒',
|
|
39
|
+
new_user: '🆕',
|
|
40
|
+
pdf_export: '📑',
|
|
41
|
+
trip_search: '🔍',
|
|
42
|
+
default: '🔔',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function getToken(): string {
|
|
46
|
+
return process.env.TELEGRAM_BOT_TOKEN ?? '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getChatId(app: string): string {
|
|
50
|
+
const key = `${app.toUpperCase()}_TELEGRAM_CHAT_ID`;
|
|
51
|
+
return process.env[key] ?? process.env.TELEGRAM_CHAT_ID ?? '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatMessage(
|
|
55
|
+
app: string,
|
|
56
|
+
event: string,
|
|
57
|
+
payload: Record<string, unknown>,
|
|
58
|
+
): string {
|
|
59
|
+
const icon = EMOJI[event] ?? EMOJI['default'];
|
|
60
|
+
const lines = [`${icon} *${app.toUpperCase()}* · \`${event}\``];
|
|
61
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
62
|
+
if (v !== null && v !== undefined && v !== '') {
|
|
63
|
+
lines.push(` ${k}: \`${String(v)}\``);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return lines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function notify(
|
|
70
|
+
app: string,
|
|
71
|
+
event: string,
|
|
72
|
+
payload: Record<string, unknown> = {},
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
const token = getToken();
|
|
75
|
+
const chatId = getChatId(app);
|
|
76
|
+
if (!token || !chatId) return;
|
|
77
|
+
try {
|
|
78
|
+
const text = formatMessage(app, event, payload);
|
|
79
|
+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({ chat_id: chatId, text, parse_mode: 'Markdown' }),
|
|
83
|
+
signal: AbortSignal.timeout(5000),
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
// never throws
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tool_call": "🔧",
|
|
3
|
+
"tool_error": "🔴",
|
|
4
|
+
"cache_hit": "⚡",
|
|
5
|
+
"cache_miss": "💭",
|
|
6
|
+
"auth_ok": "🔑",
|
|
7
|
+
"auth_fail": "🚫",
|
|
8
|
+
"startup": "🚀",
|
|
9
|
+
"waitlist_signup": "📬",
|
|
10
|
+
"parse_file": "📄",
|
|
11
|
+
"parse_text": "📝",
|
|
12
|
+
"parse_url": "🔗",
|
|
13
|
+
"trip_created": "🗺️",
|
|
14
|
+
"trip_deleted": "🗑️",
|
|
15
|
+
"error": "🔴",
|
|
16
|
+
"stripe_payment": "💳",
|
|
17
|
+
"stripe_checkout": "🛒",
|
|
18
|
+
"new_user": "🆕",
|
|
19
|
+
"pdf_export": "📑",
|
|
20
|
+
"trip_search": "🔍",
|
|
21
|
+
"default": "🔔"
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "telegram-notify"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared Telegram notification client for cachly-mcp monorepo"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["httpx>=0.27"]
|
|
11
|
+
|
|
12
|
+
[tool.setuptools.packages.find]
|
|
13
|
+
where = ["."]
|
|
14
|
+
include = ["telegram_notify*"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.package-data]
|
|
17
|
+
telegram_notify = ["emojis.json"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
telegram-notify — shared Telegram notification client.
|
|
3
|
+
|
|
4
|
+
Used by TCO backend (Python) and importable as a pip local package.
|
|
5
|
+
Single source of truth for: emoji map, message format, env var convention.
|
|
6
|
+
|
|
7
|
+
Interface: notify(app, event, payload)
|
|
8
|
+
notify_user(db, user_id, message) — direct DM via stored chat_id
|
|
9
|
+
|
|
10
|
+
Env vars:
|
|
11
|
+
TELEGRAM_BOT_TOKEN — bot token from @BotFather
|
|
12
|
+
{APP_UPPER}_TELEGRAM_CHAT_ID — app-specific channel (e.g. TCO_TELEGRAM_CHAT_ID)
|
|
13
|
+
TELEGRAM_CHAT_ID — fallback for all apps
|
|
14
|
+
|
|
15
|
+
No-op when unconfigured. Never throws.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
_EMOJI: dict[str, str] = json.loads(
|
|
25
|
+
(Path(__file__).parent / "emojis.json").read_text()
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _token() -> str:
|
|
30
|
+
return os.getenv("TELEGRAM_BOT_TOKEN", "")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _chat_id(app: str) -> str:
|
|
34
|
+
key = f"{app.upper()}_TELEGRAM_CHAT_ID"
|
|
35
|
+
return os.getenv(key) or os.getenv("TELEGRAM_CHAT_ID", "")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_message(app: str, event: str, payload: dict[str, Any]) -> str:
|
|
39
|
+
icon = _EMOJI.get(event, _EMOJI["default"])
|
|
40
|
+
lines = [f"{icon} *{app.upper()}* · `{event}`"]
|
|
41
|
+
for k, v in payload.items():
|
|
42
|
+
if v is not None and v != "":
|
|
43
|
+
lines.append(f" {k}: `{v}`")
|
|
44
|
+
return "\n".join(lines)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def notify(app: str, event: str, payload: dict[str, Any] | None = None) -> None:
|
|
48
|
+
"""Send event notification to the app's configured Telegram channel."""
|
|
49
|
+
token = _token()
|
|
50
|
+
chat_id = _chat_id(app)
|
|
51
|
+
if not token or not chat_id:
|
|
52
|
+
return
|
|
53
|
+
try:
|
|
54
|
+
import httpx
|
|
55
|
+
text = format_message(app, event, payload or {})
|
|
56
|
+
async with httpx.AsyncClient(timeout=5) as client:
|
|
57
|
+
await client.post(
|
|
58
|
+
f"https://api.telegram.org/bot{token}/sendMessage",
|
|
59
|
+
json={"chat_id": chat_id, "text": text, "parse_mode": "Markdown"},
|
|
60
|
+
)
|
|
61
|
+
except Exception:
|
|
62
|
+
pass # never raises
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def notify_user(db: Any, user_id: str, message: str) -> None:
|
|
66
|
+
"""Send a direct Telegram message to a user by their DB user_id.
|
|
67
|
+
Looks up telegram_chat_id from the users table. No-op if not linked."""
|
|
68
|
+
token = _token()
|
|
69
|
+
if not token:
|
|
70
|
+
return
|
|
71
|
+
try:
|
|
72
|
+
from sqlalchemy import text as sql_text
|
|
73
|
+
result = await db.execute(
|
|
74
|
+
sql_text("SELECT telegram_chat_id FROM users WHERE id = :uid"),
|
|
75
|
+
{"uid": user_id},
|
|
76
|
+
)
|
|
77
|
+
row = result.fetchone()
|
|
78
|
+
if not row or not row[0]:
|
|
79
|
+
return
|
|
80
|
+
import httpx
|
|
81
|
+
async with httpx.AsyncClient(timeout=5) as client:
|
|
82
|
+
await client.post(
|
|
83
|
+
f"https://api.telegram.org/bot{token}/sendMessage",
|
|
84
|
+
json={"chat_id": row[0], "text": message, "parse_mode": "Markdown"},
|
|
85
|
+
)
|
|
86
|
+
except Exception:
|
|
87
|
+
pass # never raises
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tool_call": "🔧",
|
|
3
|
+
"tool_error": "🔴",
|
|
4
|
+
"cache_hit": "⚡",
|
|
5
|
+
"cache_miss": "💭",
|
|
6
|
+
"auth_ok": "🔑",
|
|
7
|
+
"auth_fail": "🚫",
|
|
8
|
+
"startup": "🚀",
|
|
9
|
+
"waitlist_signup": "📬",
|
|
10
|
+
"parse_file": "📄",
|
|
11
|
+
"parse_text": "📝",
|
|
12
|
+
"parse_url": "🔗",
|
|
13
|
+
"trip_created": "🗺️",
|
|
14
|
+
"trip_deleted": "🗑️",
|
|
15
|
+
"error": "🔴",
|
|
16
|
+
"stripe_payment": "💳",
|
|
17
|
+
"stripe_checkout": "🛒",
|
|
18
|
+
"new_user": "🆕",
|
|
19
|
+
"pdf_export": "📑",
|
|
20
|
+
"trip_search": "🔍",
|
|
21
|
+
"default": "🔔"
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Converts SVG icon assets to PNG for Expo.
|
|
4
|
+
* Run: node scripts/generate-icons.js
|
|
5
|
+
* Requires: npm install -g sharp-cli OR npx sharp-cli
|
|
6
|
+
*/
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const frontend = path.join(__dirname, '..', 'apps', 'travel-chaos-organizer', 'frontend');
|
|
11
|
+
|
|
12
|
+
const conversions = [
|
|
13
|
+
{ input: 'assets/icon.svg', output: 'assets/icon.png', width: 1024, height: 1024 },
|
|
14
|
+
{ input: 'assets/adaptive-icon.svg', output: 'assets/adaptive-icon.png', width: 1024, height: 1024 },
|
|
15
|
+
{ input: 'assets/icon.svg', output: 'assets/splash.png', width: 1284, height: 2778 },
|
|
16
|
+
{ input: 'assets/icon.svg', output: 'assets/favicon.png', width: 32, height: 32 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
for (const { input, output, width, height } of conversions) {
|
|
20
|
+
const src = path.join(frontend, input);
|
|
21
|
+
const dst = path.join(frontend, output);
|
|
22
|
+
try {
|
|
23
|
+
execSync(`npx sharp-cli --input "${src}" --output "${dst}" resize ${width} ${height}`, { stdio: 'inherit' });
|
|
24
|
+
console.log(`✓ ${output}`);
|
|
25
|
+
} catch {
|
|
26
|
+
console.error(`✗ ${output} — install sharp-cli: npm i -g sharp-cli`);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/smithery.yaml
CHANGED
|
@@ -7,19 +7,15 @@ description: >
|
|
|
7
7
|
BM25+ search in 11 languages. 80 MCP tools. Free tier. GDPR. German servers.
|
|
8
8
|
Zero-config: sign in once via browser, auto-provisions your Brain instance.
|
|
9
9
|
|
|
10
|
-
# Streamable HTTP — hosted on our infrastructure (mcp.cachly.dev).
|
|
11
|
-
# Multi-tenant: per-request CACHLY_JWT + instance ID resolved from
|
|
12
|
-
# Authorization header + X-Cachly-Instance-Id, or Smithery ?config= param.
|
|
13
10
|
startCommand:
|
|
14
|
-
type:
|
|
15
|
-
url: https://mcp.cachly.dev/mcp
|
|
11
|
+
type: stdio
|
|
16
12
|
configSchema:
|
|
17
13
|
type: object
|
|
18
14
|
properties:
|
|
19
15
|
CACHLY_JWT:
|
|
20
16
|
type: string
|
|
21
|
-
title: "API Key"
|
|
22
|
-
description: "
|
|
17
|
+
title: "API Key (optional — sign in via browser on first use)"
|
|
18
|
+
description: "Leave blank for zero-config: on your first tool call the server opens a browser sign-in (OAuth Device Flow) and auto-provisions your Brain instance. Or pre-fill with your API key from cachly.dev/setup-ai."
|
|
23
19
|
examples:
|
|
24
20
|
- "cky_live_abc123..."
|
|
25
21
|
CACHLY_BRAIN_INSTANCE_ID:
|
|
@@ -29,6 +25,21 @@ startCommand:
|
|
|
29
25
|
pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
30
26
|
examples:
|
|
31
27
|
- "9d4077aa-bfa2-468b-89cd-0a8d8f3ec483"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
CACHLY_API_URL:
|
|
29
|
+
type: string
|
|
30
|
+
title: "API URL (optional)"
|
|
31
|
+
description: "Leave blank unless you self-host Cachly."
|
|
32
|
+
default: "https://api.cachly.dev"
|
|
33
|
+
required: []
|
|
34
|
+
commandFunction: |-
|
|
35
|
+
(config) => ({
|
|
36
|
+
command: "npx",
|
|
37
|
+
args: ["-y", "@cachly-dev/mcp-server@latest"],
|
|
38
|
+
env: Object.fromEntries(
|
|
39
|
+
Object.entries({
|
|
40
|
+
CACHLY_JWT: config.CACHLY_JWT,
|
|
41
|
+
CACHLY_BRAIN_INSTANCE_ID: config.CACHLY_BRAIN_INSTANCE_ID,
|
|
42
|
+
CACHLY_API_URL: config.CACHLY_API_URL ?? "https://api.cachly.dev",
|
|
43
|
+
}).filter(([, v]) => v != null && v !== "")
|
|
44
|
+
)
|
|
45
|
+
})
|
|
@@ -17,6 +17,7 @@ import { EventEmitter } from 'node:events';
|
|
|
17
17
|
import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge } from '../ckg.js';
|
|
18
18
|
import { EMBED_PROVIDER, hasEmbedProvider, embedProviderHint, embedConfig, setEmbedJwt } from '../embeddings.js';
|
|
19
19
|
import { keywordSearch } from '../search.js';
|
|
20
|
+
import { handleBrainTool } from '../handlers/brain.js';
|
|
20
21
|
import type { Redis } from 'ioredis';
|
|
21
22
|
|
|
22
23
|
// ── In-memory Redis mock ──────────────────────────────────────────────────────
|
|
@@ -67,6 +68,21 @@ class MockRedis {
|
|
|
67
68
|
return [...(this.sets.get(key) ?? [])];
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
async mget(...keys: string[]): Promise<(string | null)[]> {
|
|
72
|
+
return keys.map(k => this.store.get(k) ?? null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async exists(key: string): Promise<number> {
|
|
76
|
+
return this.store.has(key) ? 1 : 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async incrbyfloat(key: string, increment: number): Promise<string> {
|
|
80
|
+
const cur = parseFloat(this.store.get(key) ?? '0');
|
|
81
|
+
const next = cur + increment;
|
|
82
|
+
this.store.set(key, String(next));
|
|
83
|
+
return String(next);
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
/** Simplified scanStream: returns all matching keys in a single 'data' event. */
|
|
71
87
|
scanStream(opts: { match: string; count?: number }): EventEmitter {
|
|
72
88
|
const emitter = new EventEmitter();
|
|
@@ -83,13 +99,13 @@ class MockRedis {
|
|
|
83
99
|
/** Minimal pipeline: batches GET calls, returns [err, value][] */
|
|
84
100
|
pipeline() {
|
|
85
101
|
const commands: Array<{ cmd: string; key: string }> = [];
|
|
86
|
-
const
|
|
102
|
+
const store = this.store;
|
|
87
103
|
return {
|
|
88
104
|
get(key: string) { commands.push({ cmd: 'get', key }); return this; },
|
|
89
105
|
async exec(): Promise<Array<[null, string | null]>> {
|
|
90
106
|
const out: Array<[null, string | null]> = [];
|
|
91
107
|
for (const c of commands) {
|
|
92
|
-
out.push([null,
|
|
108
|
+
out.push([null, store.get(c.key) ?? null]);
|
|
93
109
|
}
|
|
94
110
|
return out;
|
|
95
111
|
},
|
|
@@ -872,3 +888,161 @@ describe('Brain Flow: advanced scenarios', () => {
|
|
|
872
888
|
});
|
|
873
889
|
});
|
|
874
890
|
});
|
|
891
|
+
|
|
892
|
+
// ── New feature coverage (0.10.41 / 0.10.42) ─────────────────────────────────
|
|
893
|
+
// Trust badges, Proven Laws crystallization, and focus-less predictive warning.
|
|
894
|
+
// These exercise the REAL handleBrainTool code path with the MockRedis.
|
|
895
|
+
|
|
896
|
+
const noopApiFetch = (async () => null) as unknown as Parameters<typeof handleBrainTool>[3];
|
|
897
|
+
|
|
898
|
+
/** Seed a best-solution lesson with an explicit recall_count / authors / pinned. */
|
|
899
|
+
async function seedLesson(
|
|
900
|
+
redis: MockRedis,
|
|
901
|
+
topic: string,
|
|
902
|
+
opts: {
|
|
903
|
+
outcome?: 'success' | 'failure' | 'partial';
|
|
904
|
+
what_worked?: string;
|
|
905
|
+
what_failed?: string;
|
|
906
|
+
recall_count?: number;
|
|
907
|
+
severity?: 'critical' | 'major' | 'minor';
|
|
908
|
+
pinned?: boolean;
|
|
909
|
+
author?: string;
|
|
910
|
+
tags?: string[];
|
|
911
|
+
} = {},
|
|
912
|
+
): Promise<void> {
|
|
913
|
+
const ts = new Date().toISOString();
|
|
914
|
+
const obj = {
|
|
915
|
+
topic,
|
|
916
|
+
outcome: opts.outcome ?? 'success',
|
|
917
|
+
what_worked: opts.what_worked ?? `solution for ${topic}`,
|
|
918
|
+
what_failed: opts.what_failed,
|
|
919
|
+
severity: opts.severity ?? 'minor',
|
|
920
|
+
recall_count: opts.recall_count ?? 0,
|
|
921
|
+
ts,
|
|
922
|
+
verified_at: ts,
|
|
923
|
+
confidence: 1.0,
|
|
924
|
+
tags: opts.tags ?? [],
|
|
925
|
+
...(opts.pinned ? { pinned: true } : {}),
|
|
926
|
+
...(opts.author ? { author: opts.author } : {}),
|
|
927
|
+
};
|
|
928
|
+
await redis.set(`cachly:lesson:best:${topic}`, JSON.stringify(obj));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
describe('Trust badges (recall_best_solution)', () => {
|
|
932
|
+
let redis: MockRedis;
|
|
933
|
+
const getConn = async () => redis as unknown as Redis;
|
|
934
|
+
beforeEach(() => { redis = new MockRedis(); });
|
|
935
|
+
|
|
936
|
+
it('shows "Battle-tested" when recall_count crosses 10', async () => {
|
|
937
|
+
await seedLesson(redis, 'redis:pool', { recall_count: 9, what_worked: 'set max_retries=3' });
|
|
938
|
+
const out = (await handleBrainTool('recall_best_solution', { instance_id: 'i1', topic: 'redis:pool' }, getConn, noopApiFetch))!;
|
|
939
|
+
expect(out).toContain('Battle-tested');
|
|
940
|
+
expect(out).toContain('recalled 10×');
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('shows "Proven" when recall_count is between 5 and 9', async () => {
|
|
944
|
+
await seedLesson(redis, 'api:auth', { recall_count: 4 });
|
|
945
|
+
const out = (await handleBrainTool('recall_best_solution', { instance_id: 'i1', topic: 'api:auth' }, getConn, noopApiFetch))!;
|
|
946
|
+
expect(out).toContain('Proven');
|
|
947
|
+
expect(out).toContain('recalled 5×');
|
|
948
|
+
expect(out).not.toContain('Battle-tested');
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('shows no trust badge for a fresh low-recall lesson', async () => {
|
|
952
|
+
await seedLesson(redis, 'misc:thing', { recall_count: 0 });
|
|
953
|
+
const out = (await handleBrainTool('recall_best_solution', { instance_id: 'i1', topic: 'misc:thing' }, getConn, noopApiFetch))!;
|
|
954
|
+
expect(out).not.toContain('Battle-tested');
|
|
955
|
+
expect(out).not.toContain('Proven —');
|
|
956
|
+
expect(out).not.toContain('Team-verified');
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('increments recall_count and persists it', async () => {
|
|
960
|
+
await seedLesson(redis, 'deploy:x', { recall_count: 2 });
|
|
961
|
+
await handleBrainTool('recall_best_solution', { instance_id: 'i1', topic: 'deploy:x' }, getConn, noopApiFetch);
|
|
962
|
+
const stored = JSON.parse((await redis.get('cachly:lesson:best:deploy:x'))!);
|
|
963
|
+
expect(stored.recall_count).toBe(3);
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
describe('Proven Laws crystallization (session_start)', () => {
|
|
968
|
+
let redis: MockRedis;
|
|
969
|
+
const getConn = async () => redis as unknown as Redis;
|
|
970
|
+
beforeEach(async () => {
|
|
971
|
+
redis = new MockRedis();
|
|
972
|
+
// Provide a last session so session_start does not attempt git reconstruction.
|
|
973
|
+
await redis.set('cachly:session:last', JSON.stringify({ summary: 'prior work', ts: new Date().toISOString(), files_changed: [] }));
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
it('surfaces lessons recalled >= 5 in the Proven Laws section', async () => {
|
|
977
|
+
await seedLesson(redis, 'infra:k3s', { recall_count: 7, what_worked: 'add WireGuard IP to TLS-SAN' });
|
|
978
|
+
await seedLesson(redis, 'random:note', { recall_count: 1 });
|
|
979
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
980
|
+
expect(out).toContain('Proven Laws');
|
|
981
|
+
expect(out).toContain('infra:k3s');
|
|
982
|
+
expect(out).toContain('recalled 7×');
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it('surfaces an explicitly pinned lesson even with low recall', async () => {
|
|
986
|
+
await seedLesson(redis, 'team:convention', { recall_count: 0, pinned: true, what_worked: 'always snake_case handlers' });
|
|
987
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
988
|
+
expect(out).toContain('Proven Laws');
|
|
989
|
+
expect(out).toContain('team:convention');
|
|
990
|
+
expect(out).toContain('pinned');
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it('omits the Proven Laws section when nothing qualifies', async () => {
|
|
994
|
+
await seedLesson(redis, 'small:thing', { recall_count: 1 });
|
|
995
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
996
|
+
expect(out).not.toContain('Proven Laws');
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
it('does not crystallize failure lessons', async () => {
|
|
1000
|
+
await seedLesson(redis, 'broken:flow', { recall_count: 9, outcome: 'failure', what_failed: 'never works' });
|
|
1001
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
1002
|
+
expect(out).not.toContain('Proven Laws');
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
describe('Predictive pre-warning without explicit focus (session_start)', () => {
|
|
1007
|
+
let redis: MockRedis;
|
|
1008
|
+
const getConn = async () => redis as unknown as Redis;
|
|
1009
|
+
beforeEach(() => { redis = new MockRedis(); });
|
|
1010
|
+
|
|
1011
|
+
it('derives danger area from last session files and warns', async () => {
|
|
1012
|
+
// Last session touched payment files → likely to continue there.
|
|
1013
|
+
await redis.set('cachly:session:last', JSON.stringify({
|
|
1014
|
+
summary: 'worked on payment integration',
|
|
1015
|
+
ts: new Date().toISOString(),
|
|
1016
|
+
files_changed: ['api/payment/stripe_webhook.go'],
|
|
1017
|
+
}));
|
|
1018
|
+
// A known failure in the payment area.
|
|
1019
|
+
await seedLesson(redis, 'payment:webhook', { outcome: 'failure', what_failed: 'missed idempotency key → double charge', tags: ['payment'] });
|
|
1020
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
1021
|
+
expect(out).toContain('PRE-WARNING');
|
|
1022
|
+
expect(out).toContain('payment:webhook');
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it('does not warn when last-session area has no known failures', async () => {
|
|
1026
|
+
await redis.set('cachly:session:last', JSON.stringify({
|
|
1027
|
+
summary: 'wrote docs',
|
|
1028
|
+
ts: new Date().toISOString(),
|
|
1029
|
+
files_changed: ['docs/readme.md'],
|
|
1030
|
+
}));
|
|
1031
|
+
await seedLesson(redis, 'payment:webhook', { outcome: 'failure', what_failed: 'x', tags: ['payment'] });
|
|
1032
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
1033
|
+
expect(out).not.toContain('PRE-WARNING');
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
it('filters path noise so common segments do not trigger false matches', async () => {
|
|
1037
|
+
// Last session only touched generic noise paths (src/lib/index).
|
|
1038
|
+
await redis.set('cachly:session:last', JSON.stringify({
|
|
1039
|
+
summary: 'refactor',
|
|
1040
|
+
ts: new Date().toISOString(),
|
|
1041
|
+
files_changed: ['src/lib/index.ts'],
|
|
1042
|
+
}));
|
|
1043
|
+
// A failure tagged 'src' — should NOT match because 'src' is noise.
|
|
1044
|
+
await seedLesson(redis, 'src:thing', { outcome: 'failure', what_failed: 'noise', tags: ['src'] });
|
|
1045
|
+
const out = (await handleBrainTool('session_start', { instance_id: 'i1' }, getConn, noopApiFetch))!;
|
|
1046
|
+
expect(out).not.toContain('PRE-WARNING');
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
@@ -334,7 +334,7 @@ describe('handleRoadmapTool', () => {
|
|
|
334
334
|
// Add and complete one item
|
|
335
335
|
const addResult = await handleRoadmapTool('roadmap_add', { instance_id: INSTANCE, title: 'Finished task', priority: 'low' }, getConnection, apiFetch);
|
|
336
336
|
const idMatch = addResult!.match(/`(rm_[a-z0-9_]+)`/);
|
|
337
|
-
const id = idMatch
|
|
337
|
+
const id = idMatch![1];
|
|
338
338
|
await handleRoadmapTool('roadmap_update', { instance_id: INSTANCE, id, status: 'done' }, getConnection, apiFetch);
|
|
339
339
|
|
|
340
340
|
const result = await handleRoadmapTool('roadmap_list', { instance_id: INSTANCE, status: 'done' }, getConnection, apiFetch);
|