@cachly-dev/mcp-server 0.10.55 โ†’ 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.
Files changed (329) hide show
  1. package/.github/workflows/eas-build-android.yml +58 -0
  2. package/.github/workflows/eas-update.yml +55 -0
  3. package/.github/workflows/publish.yml +2 -2
  4. package/BENCH.md +91 -0
  5. package/CHANGELOG.md +44 -0
  6. package/README.md +6 -34
  7. package/STRATEGY.md +228 -0
  8. package/apps/travel-chaos-organizer/.env.example +49 -0
  9. package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
  10. package/apps/travel-chaos-organizer/Makefile +57 -0
  11. package/apps/travel-chaos-organizer/README.md +159 -0
  12. package/apps/travel-chaos-organizer/backend/.env.example +10 -0
  13. package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
  14. package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
  15. package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
  16. package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
  17. package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
  18. package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
  19. package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
  20. package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
  21. package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
  22. package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
  23. package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
  24. package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
  25. package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
  26. package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
  27. package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
  28. package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
  29. package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
  30. package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
  31. package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
  32. package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
  33. package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
  34. package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
  35. package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
  36. package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
  37. package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
  38. package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
  39. package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
  40. package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
  41. package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
  42. package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
  43. package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
  44. package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
  45. package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
  46. package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
  47. package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
  48. package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
  49. package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
  50. package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
  51. package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
  52. package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
  53. package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
  54. package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
  55. package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
  56. package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
  57. package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
  58. package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
  59. package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
  60. package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
  61. package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
  62. package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
  63. package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
  64. package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
  65. package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
  66. package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
  67. package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
  68. package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
  69. package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
  70. package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
  71. package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
  72. package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
  73. package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
  74. package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
  75. package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
  76. package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
  77. package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
  78. package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
  79. package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
  80. package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
  81. package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
  82. package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
  83. package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
  84. package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
  85. package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
  86. package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
  87. package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
  88. package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
  89. package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
  90. package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
  91. package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
  92. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
  93. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
  94. package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
  95. package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
  96. package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
  97. package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
  98. package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
  99. package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
  100. package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
  101. package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
  102. package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
  103. package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
  104. package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
  105. package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
  106. package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
  107. package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
  108. package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
  109. package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
  110. package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
  111. package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
  112. package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
  113. package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
  114. package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
  115. package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
  116. package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
  117. package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
  118. package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
  119. package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
  120. package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
  121. package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
  122. package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
  123. package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
  124. package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
  125. package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
  126. package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
  127. package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
  128. package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
  129. package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
  130. package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
  131. package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
  132. package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
  133. package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
  134. package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
  135. package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
  136. package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
  137. package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
  138. package/apps/travel-chaos-organizer/frontend/package.json +44 -0
  139. package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
  140. package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
  141. package/apps/travel-chaos-organizer/landing/index.html +773 -0
  142. package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
  143. package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
  144. package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
  145. package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
  146. package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
  147. package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
  148. package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
  149. package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
  150. package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
  151. package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
  152. package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
  153. package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
  154. package/dist/packages/telegram-notify/client.d.ts +19 -0
  155. package/dist/packages/telegram-notify/client.js +75 -0
  156. package/dist/packages/telegram-notify/client.js.map +1 -0
  157. package/dist/src/__tests__/auth.test.js.map +1 -0
  158. package/dist/{__tests__ โ†’ src/__tests__}/brain-flow.test.js +141 -0
  159. package/dist/src/__tests__/brain-flow.test.js.map +1 -0
  160. package/dist/src/__tests__/confidence.test.js.map +1 -0
  161. package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
  162. package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
  163. package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
  164. package/dist/src/__tests__/e2e/config.js.map +1 -0
  165. package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
  166. package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
  167. package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
  168. package/dist/{__tests__ โ†’ src/__tests__}/handlers-unit.test.js +0 -2
  169. package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
  170. package/dist/src/__tests__/namespace.test.js.map +1 -0
  171. package/dist/src/__tests__/rerank.test.js +115 -0
  172. package/dist/src/__tests__/rerank.test.js.map +1 -0
  173. package/dist/src/auth.js.map +1 -0
  174. package/dist/src/bench/cachly-bench.d.ts +35 -0
  175. package/dist/src/bench/cachly-bench.js +182 -0
  176. package/dist/src/bench/cachly-bench.js.map +1 -0
  177. package/dist/src/bench/fixtures.d.ts +37 -0
  178. package/dist/src/bench/fixtures.js +225 -0
  179. package/dist/src/bench/fixtures.js.map +1 -0
  180. package/dist/src/ckg.js.map +1 -0
  181. package/dist/src/confidence.js.map +1 -0
  182. package/dist/src/embeddings.js.map +1 -0
  183. package/dist/src/handlers/advanced.js.map +1 -0
  184. package/dist/{handlers โ†’ src/handlers}/brain.js +335 -168
  185. package/dist/src/handlers/brain.js.map +1 -0
  186. package/dist/src/handlers/cache.js.map +1 -0
  187. package/dist/{handlers โ†’ src/handlers}/context.js +9 -4
  188. package/dist/src/handlers/context.js.map +1 -0
  189. package/dist/{handlers โ†’ src/handlers}/fedbrain.d.ts +6 -0
  190. package/dist/{handlers โ†’ src/handlers}/fedbrain.js +19 -7
  191. package/dist/src/handlers/fedbrain.js.map +1 -0
  192. package/dist/src/handlers/instances.js.map +1 -0
  193. package/dist/{handlers โ†’ src/handlers}/roadmap.js +7 -3
  194. package/dist/src/handlers/roadmap.js.map +1 -0
  195. package/dist/src/handlers/syndicate.js.map +1 -0
  196. package/dist/src/handlers/tco.d.ts +16 -0
  197. package/dist/src/handlers/tco.js +303 -0
  198. package/dist/src/handlers/tco.js.map +1 -0
  199. package/dist/{handlers โ†’ src/handlers}/team.js +57 -7
  200. package/dist/src/handlers/team.js.map +1 -0
  201. package/dist/{index.js โ†’ src/index.js} +209 -420
  202. package/dist/src/index.js.map +1 -0
  203. package/dist/src/namespace.js.map +1 -0
  204. package/dist/src/notifier.d.ts +5 -0
  205. package/dist/src/notifier.js +6 -0
  206. package/dist/src/notifier.js.map +1 -0
  207. package/dist/src/rerank.d.ts +55 -0
  208. package/dist/src/rerank.js +111 -0
  209. package/dist/src/rerank.js.map +1 -0
  210. package/dist/src/search.js.map +1 -0
  211. package/dist/src/search.test.d.ts +1 -0
  212. package/dist/src/search.test.js.map +1 -0
  213. package/dist/{tools.d.ts โ†’ src/tools.d.ts} +226 -0
  214. package/dist/{tools.js โ†’ src/tools.js} +196 -0
  215. package/dist/src/tools.js.map +1 -0
  216. package/dist/src/utils.js.map +1 -0
  217. package/package.json +8 -7
  218. package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
  219. package/packages/telegram-notify/client.ts +88 -0
  220. package/packages/telegram-notify/emojis.json +22 -0
  221. package/packages/telegram-notify/pyproject.toml +17 -0
  222. package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
  223. package/packages/telegram-notify/telegram_notify/client.py +87 -0
  224. package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
  225. package/scripts/generate-icons.js +28 -0
  226. package/server.json +3 -3
  227. package/smithery.yaml +21 -8
  228. package/src/__tests__/brain-flow.test.ts +174 -0
  229. package/src/__tests__/handlers-unit.test.ts +1 -2
  230. package/src/__tests__/rerank.test.ts +130 -0
  231. package/src/bench/cachly-bench.ts +217 -0
  232. package/src/bench/fixtures.ts +256 -0
  233. package/src/handlers/brain.ts +345 -172
  234. package/src/handlers/context.ts +9 -4
  235. package/src/handlers/fedbrain.ts +21 -7
  236. package/src/handlers/roadmap.ts +7 -3
  237. package/src/handlers/tco.ts +347 -0
  238. package/src/handlers/team.ts +67 -7
  239. package/src/index.ts +224 -406
  240. package/src/notifier.ts +5 -0
  241. package/src/rerank.ts +148 -0
  242. package/src/tools.ts +210 -0
  243. package/tsconfig.json +2 -3
  244. package/dist/__tests__/auth.test.js.map +0 -1
  245. package/dist/__tests__/brain-flow.test.js.map +0 -1
  246. package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
  247. package/dist/__tests__/brain-predict-failures.test.js +0 -339
  248. package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
  249. package/dist/__tests__/confidence.test.js.map +0 -1
  250. package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
  251. package/dist/__tests__/e2e/api-health.test.js.map +0 -1
  252. package/dist/__tests__/e2e/cli.test.js.map +0 -1
  253. package/dist/__tests__/e2e/config.js.map +0 -1
  254. package/dist/__tests__/e2e/funnel.test.js.map +0 -1
  255. package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
  256. package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
  257. package/dist/__tests__/handlers-unit.test.js.map +0 -1
  258. package/dist/__tests__/namespace.test.js.map +0 -1
  259. package/dist/auth.js.map +0 -1
  260. package/dist/ckg.js.map +0 -1
  261. package/dist/confidence.js.map +0 -1
  262. package/dist/embeddings.js.map +0 -1
  263. package/dist/handlers/advanced.js.map +0 -1
  264. package/dist/handlers/brain.js.map +0 -1
  265. package/dist/handlers/cache.js.map +0 -1
  266. package/dist/handlers/context.js.map +0 -1
  267. package/dist/handlers/fedbrain.js.map +0 -1
  268. package/dist/handlers/instances.js.map +0 -1
  269. package/dist/handlers/roadmap.js.map +0 -1
  270. package/dist/handlers/syndicate.js.map +0 -1
  271. package/dist/handlers/team.js.map +0 -1
  272. package/dist/index.js.map +0 -1
  273. package/dist/namespace.js.map +0 -1
  274. package/dist/search.js.map +0 -1
  275. package/dist/search.test.js.map +0 -1
  276. package/dist/tools.js.map +0 -1
  277. package/dist/utils.js.map +0 -1
  278. package/src/__tests__/brain-predict-failures.test.ts +0 -430
  279. /package/dist/{__tests__ โ†’ src/__tests__}/auth.test.d.ts +0 -0
  280. /package/dist/{__tests__ โ†’ src/__tests__}/auth.test.js +0 -0
  281. /package/dist/{__tests__ โ†’ src/__tests__}/brain-flow.test.d.ts +0 -0
  282. /package/dist/{__tests__ โ†’ src/__tests__}/confidence.test.d.ts +0 -0
  283. /package/dist/{__tests__ โ†’ src/__tests__}/confidence.test.js +0 -0
  284. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/api-auth.test.d.ts +0 -0
  285. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/api-auth.test.js +0 -0
  286. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/api-health.test.d.ts +0 -0
  287. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/api-health.test.js +0 -0
  288. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/cli.test.d.ts +0 -0
  289. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/cli.test.js +0 -0
  290. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/config.d.ts +0 -0
  291. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/config.js +0 -0
  292. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/funnel.test.d.ts +0 -0
  293. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/funnel.test.js +0 -0
  294. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
  295. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/mcp-tools.test.js +0 -0
  296. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/provisioning.test.d.ts +0 -0
  297. /package/dist/{__tests__ โ†’ src/__tests__}/e2e/provisioning.test.js +0 -0
  298. /package/dist/{__tests__ โ†’ src/__tests__}/handlers-unit.test.d.ts +0 -0
  299. /package/dist/{__tests__ โ†’ src/__tests__}/namespace.test.d.ts +0 -0
  300. /package/dist/{__tests__ โ†’ src/__tests__}/namespace.test.js +0 -0
  301. /package/dist/{search.test.d.ts โ†’ src/__tests__/rerank.test.d.ts} +0 -0
  302. /package/dist/{auth.d.ts โ†’ src/auth.d.ts} +0 -0
  303. /package/dist/{auth.js โ†’ src/auth.js} +0 -0
  304. /package/dist/{ckg.d.ts โ†’ src/ckg.d.ts} +0 -0
  305. /package/dist/{ckg.js โ†’ src/ckg.js} +0 -0
  306. /package/dist/{confidence.d.ts โ†’ src/confidence.d.ts} +0 -0
  307. /package/dist/{confidence.js โ†’ src/confidence.js} +0 -0
  308. /package/dist/{embeddings.d.ts โ†’ src/embeddings.d.ts} +0 -0
  309. /package/dist/{embeddings.js โ†’ src/embeddings.js} +0 -0
  310. /package/dist/{handlers โ†’ src/handlers}/advanced.d.ts +0 -0
  311. /package/dist/{handlers โ†’ src/handlers}/advanced.js +0 -0
  312. /package/dist/{handlers โ†’ src/handlers}/brain.d.ts +0 -0
  313. /package/dist/{handlers โ†’ src/handlers}/cache.d.ts +0 -0
  314. /package/dist/{handlers โ†’ src/handlers}/cache.js +0 -0
  315. /package/dist/{handlers โ†’ src/handlers}/context.d.ts +0 -0
  316. /package/dist/{handlers โ†’ src/handlers}/instances.d.ts +0 -0
  317. /package/dist/{handlers โ†’ src/handlers}/instances.js +0 -0
  318. /package/dist/{handlers โ†’ src/handlers}/roadmap.d.ts +0 -0
  319. /package/dist/{handlers โ†’ src/handlers}/syndicate.d.ts +0 -0
  320. /package/dist/{handlers โ†’ src/handlers}/syndicate.js +0 -0
  321. /package/dist/{handlers โ†’ src/handlers}/team.d.ts +0 -0
  322. /package/dist/{index.d.ts โ†’ src/index.d.ts} +0 -0
  323. /package/dist/{namespace.d.ts โ†’ src/namespace.d.ts} +0 -0
  324. /package/dist/{namespace.js โ†’ src/namespace.js} +0 -0
  325. /package/dist/{search.d.ts โ†’ src/search.d.ts} +0 -0
  326. /package/dist/{search.js โ†’ src/search.js} +0 -0
  327. /package/dist/{search.test.js โ†’ src/search.test.js} +0 -0
  328. /package/dist/{utils.d.ts โ†’ src/utils.d.ts} +0 -0
  329. /package/dist/{utils.js โ†’ src/utils.js} +0 -0
@@ -3,59 +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.55';
9
+ const MCP_VERSION = '0.10.57';
9
10
  const WHATS_NEW = {
10
- '0.10.55': [
11
- `๐Ÿ†• **What's new in cachly MCP v0.10.55:**`,
12
- ` โšก UX: API failure now shows a clear error instead of silently blocking for 30s`,
13
- ` โšก UX: provisioning wait reduced from 25s โ†’ 8s (Brain is usually ready sooner)`,
14
- ` ๐Ÿ”ง VS Code: setup spinner is now cancellable โ€” dismiss and MCP auto-connects later`,
15
- ` ๐Ÿง  VS Code: Brain Health panel shows offline-queued lessons with pending sync count`,
16
- ],
17
- '0.10.54': [
18
- `๐Ÿ†• **What's new in cachly MCP v${MCP_VERSION}:**`,
19
- ` ๐Ÿ‘€ Perf: first tool call no longer blocked by auto-session (was freezing for 30-120s on large repos)`,
20
- ` ๐Ÿ‘€ Perf: git history bootstrap runs in background โ€” session_start responds instantly`,
21
- ` ๐Ÿž Fixed: VS Code lessons now include author attribution (broken since launch)`,
22
- ],
23
- '0.10.53': [
24
- `๐Ÿ†• **What's new in cachly MCP v${MCP_VERSION}:**`,
25
- ` ๐Ÿ› Fixed: Brain bootstrap summary now visible in session_start (was silently discarded)`,
26
- ` ๐Ÿ› Fixed: team lessons now indexed correctly (\`ts\` field was stored as \`timestamp\`)`,
27
- ` โœจ New: proactive follow-up hints after \`learn_from_attempts\` (CKG co-occurrence patterns)`,
28
- ` โœจ 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`,
29
17
  ],
30
18
  '0.10.52': [
31
- `๐Ÿ†• **What's new in cachly MCP v0.10.52:**`,
32
- ` ๐Ÿš€ Zero-click setup: Brain activates automatically on first call (no auth prompt needed)`,
33
- ` ๐Ÿ› Fixed: all Brain connections now use decrypted passwords (resolves AUTH errors for all users)`,
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`,
34
23
  ],
35
- '0.10.50': [
36
- `๐Ÿ†• **What's new in cachly MCP v0.10.50:**`,
37
- ` ๐Ÿ› Fixed: Brain connection now works out of the box (was failing with AUTH error)`,
38
- ` ๐Ÿ› Fixed: TLS defaults to off for free-tier instances (no more TLS handshake failure)`,
39
- ` โœ… \`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`,
40
27
  ],
41
- '0.10.46': [
42
- `๐Ÿ†• **What's new in v0.10.46:**`,
43
- ` โœ… \`brain_from_git\` โ€” auto-learns from your full git history on setup`,
44
- ` โœ… \`smart_recall\` โ€” semantic + keyword hybrid search, 3ร— more relevant results`,
45
- ` โœ… VS Code Extension โ€” Brain persists across all projects, zero config`,
46
- ` โœ… Device-flow auth โ€” zero-config login, no JWT needed in editor config`,
47
- ` โœ… Auto-provision โ€” Brain instance created automatically after first login`,
48
- ` โœ… JWT now persisted to ~/.claude/mcp.json โ€” survives editor restarts`,
49
- ` โœ… Confidence scoring โ€” every recalled fact shows staleness + reliability`,
50
- ` โœ… 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`,
51
35
  ],
52
- '0.10.0': [
53
- `๐Ÿ†• **What's new in v0.10.0:**`,
54
- ` โœ… Zero-friction setup โ€” auto-provisions your Brain, no manual config needed`,
55
- ` โœ… 10ร— faster briefings โ€” batch Redis fetches replace per-key loops`,
56
- ` โœ… Crash-proof data reads โ€” corrupted cache entries no longer break tools`,
57
- ` โœ… \`session_end\` warns when called without an active session`,
58
- ` โœ… \`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`,
59
42
  ],
60
43
  };
61
44
  export const BRAIN_TOOL_NAMES = new Set([
@@ -111,6 +94,19 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
111
94
  await ckgUpdateEdge(redis, cId, 'contradicts', resId, false);
112
95
  }
113
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 */ }
114
110
  contradictionWarning.push(`๐Ÿ—ณ๏ธ Run \`madc_deliberate(topic="${topic}")\` to resolve via expert agent voting.`);
115
111
  }
116
112
  }
@@ -156,7 +152,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
156
152
  const depTopics = safeJsonParse(existing, []);
157
153
  if (!depTopics.includes(topic))
158
154
  depTopics.push(topic);
159
- await redis.set(depKey, JSON.stringify(depTopics), 'EX', 365 * 86400); // 1-year TTL
155
+ await redis.set(depKey, JSON.stringify(depTopics), 'EX', 90 * 86400);
160
156
  }
161
157
  const lessonObj = {
162
158
  topic,
@@ -178,11 +174,11 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
178
174
  version: 3,
179
175
  };
180
176
  const lesson = JSON.stringify(lessonObj);
181
- // Always append to the history list (audit log); cap at 200 entries to prevent unbounded growth
177
+ // Always append to the history list (audit log); keep last 100 entries, 90-day TTL
182
178
  const listKey = `cachly:lessons:${topic}`;
183
179
  await redis.rpush(listKey, lesson);
184
- await redis.ltrim(listKey, -200, -1);
185
- await redis.expire(listKey, 365 * 86400); // 1-year TTL
180
+ await redis.ltrim(listKey, -100, -1);
181
+ await redis.expire(listKey, 90 * 86400);
186
182
  // Update best key for success/partial; for failure only update if no success exists
187
183
  if (outcome === 'success' || outcome === 'partial') {
188
184
  await redis.set(`cachly:lesson:best:${topic}`, lesson);
@@ -316,46 +312,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
316
312
  }
317
313
  }
318
314
  catch { /* CKG updates are non-critical */ }
319
- // โ”€โ”€ Proactive follow-up suggestion (CKG co-occurrence patterns) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
320
- // After a successful fix, check if related concepts commonly follow it.
321
- // Uses CKG co-occurs edges with confidence > 0.4 and at least 2 trials.
322
- const proactiveSuggestions = [];
323
- if (outcome === 'success' || outcome === 'partial') {
324
- try {
325
- const conceptId = ckgSlug(topic);
326
- const fromEdgeKeys = await redis.smembers(`cachly:ckg:idx:from:${conceptId}`);
327
- for (const ek of fromEdgeKeys.slice(0, 20)) {
328
- const er = await redis.get(ek);
329
- if (!er)
330
- continue;
331
- const edge = safeJsonParse(er, null);
332
- if (!edge || edge.confidence < 0.4 || (edge.trials ?? 0) < 2)
333
- continue;
334
- if (edge.edgeType !== 'co-occurs' && edge.edgeType !== 'causes')
335
- continue;
336
- // Resolve related concept to a lesson key
337
- const relTopic = edge.to
338
- .replace(/^cause:/, '')
339
- .replace(/^tag:/, '')
340
- .replace(/^problem:/, '')
341
- .replace(/-/g, ':')
342
- .replace(/_/g, '-');
343
- const relRaw = await redis.get(`cachly:lesson:best:${relTopic}`);
344
- if (!relRaw)
345
- continue;
346
- const relLesson = safeJsonParse(relRaw, null);
347
- const displayTopic = relLesson?.topic ?? relTopic;
348
- if (!relLesson || !relLesson.what_worked || displayTopic === topic)
349
- continue;
350
- const pct = Math.round(edge.confidence * 100);
351
- if (proactiveSuggestions.length === 0)
352
- proactiveSuggestions.push('');
353
- 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}")\``);
354
- break; // one proactive hint is enough
355
- }
356
- }
357
- catch { /* non-critical */ }
358
- }
359
315
  const emoji = outcome === 'success' ? 'โœ…' : outcome === 'partial' ? 'โš ๏ธ' : 'โŒ';
360
316
  const sevEmoji = severity === 'critical' ? '๐Ÿ”ด' : severity === 'major' ? '๐ŸŸก' : '๐ŸŸข';
361
317
  const action = isUpdate ? 'updated' : 'stored';
@@ -378,7 +334,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
378
334
  : '',
379
335
  ...templateWarnings,
380
336
  ...iWasWrongWarning,
381
- ...proactiveSuggestions,
382
337
  ].filter(l => l !== '').join('\n');
383
338
  }
384
339
  case 'recall_best_solution': {
@@ -419,8 +374,29 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
419
374
  const suggestPin = updatedLesson.recall_count === 3 && !lesson.pinned
420
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.`
421
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
+ }
422
397
  return [
423
398
  rememberWhen,
399
+ trustBadge,
424
400
  `${badge} **Best solution for \`${topic}\`** ${sevEmoji}${lesson.severity ? ` (${lesson.severity})` : ''} ยท recalled ${updatedLesson.recall_count}ร—`,
425
401
  ``,
426
402
  `**What worked:** ${lesson.what_worked}`,
@@ -470,57 +446,37 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
470
446
  const { instance_id, query, threshold = 0.78, } = args;
471
447
  const redis = await getConnection(instance_id);
472
448
  // โ”€โ”€ Layer 1: Keyword search across ALL brain data (always works, no embedding) โ”€โ”€
473
- const kwMatches = await keywordSearch(redis, ['cachly:ctx:*', 'cachly:lesson:best:*', 'cachly:idx:*'], query, 10);
474
- const lines = [`๐Ÿง  **Smart Recall** for: _"${query}"_\n`];
475
- // Show sub-query info if multi-topic was detected
476
- const subQueries = splitMultiQuery(query);
477
- if (subQueries.length > 1) {
478
- lines.push(`_Detected ${subQueries.length} sub-topics:_ ${subQueries.map((s, i) => `${i + 1}. "${s}"`).join(', ')}\n`);
479
- }
480
- if (kwMatches.length > 0) {
481
- lines.push(`### ๐Ÿ” BM25 Matches (${kwMatches.length})\n`);
482
- // Group by sub-query if multi-topic
483
- if (subQueries.length > 1) {
484
- const grouped = new Map();
485
- for (const m of kwMatches.slice(0, 12)) {
486
- const sq = m.subQuery ?? query;
487
- if (!grouped.has(sq))
488
- grouped.set(sq, []);
489
- grouped.get(sq).push(m);
490
- }
491
- for (const [sq, matches] of grouped) {
492
- lines.push(`**Topic: "${sq}"** (${matches.length} results)\n`);
493
- for (const m of matches.slice(0, 4)) {
494
- const label = m.key
495
- .replace('cachly:ctx:', '๐Ÿ“ ')
496
- .replace('cachly:lesson:best:', '๐Ÿ’ก ')
497
- .replace('cachly:idx:', '๐Ÿ“‚ ');
498
- const preview = m.content.slice(0, 300).replace(/\n/g, ' ');
499
- lines.push(` **${label}** _(BM25: ${m.score.toFixed(2)}, matched: ${m.matchedWords.join(', ')})_`);
500
- 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
+ });
501
473
  }
502
474
  }
503
- // Summary: which sub-queries had matches
504
- const matched = [...grouped.keys()];
505
- const unmatched = subQueries.filter(sq => !matched.includes(sq));
506
- if (unmatched.length > 0) {
507
- lines.push(`\nโš ๏ธ **No results for:** ${unmatched.map(s => `"${s}"`).join(', ')}`);
508
- }
509
- }
510
- else {
511
- for (const m of kwMatches.slice(0, 8)) {
512
- const label = m.key
513
- .replace('cachly:ctx:', '๐Ÿ“ ')
514
- .replace('cachly:lesson:best:', '๐Ÿ’ก ')
515
- .replace('cachly:idx:', '๐Ÿ“‚ ');
516
- const preview = m.content.slice(0, 400).replace(/\n/g, ' ');
517
- lines.push(`**${label}** _(BM25: ${m.score.toFixed(2)}, matched: ${m.matchedWords.join(', ')})_`);
518
- lines.push(`> ${preview}${m.content.length > 400 ? 'โ€ฆ' : ''}\n`);
519
- }
520
475
  }
521
476
  }
522
- // โ”€โ”€ Layer 2: Semantic search (optional, only if embedding provider + vector_token available) โ”€โ”€
477
+ // โ”€โ”€ Layer 2: Semantic search (parallel, optional) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
523
478
  const inst = await apiFetch(`/api/v1/instances/${instance_id}`);
479
+ const semHits = [];
524
480
  if (inst.vector_token && hasEmbedProvider()) {
525
481
  try {
526
482
  const embedding = await computeEmbedding(query);
@@ -532,16 +488,13 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
532
488
  });
533
489
  if (searchRes.ok) {
534
490
  const results = (await searchRes.json());
535
- const semHits = results.filter(r => r.found && r.id);
536
- if (semHits.length > 0) {
537
- lines.push(`\n### ๐ŸŽฏ Semantic Matches (${semHits.length})\n`);
538
- for (const hit of semHits) {
539
- const parts = hit.id.replace('ctx:', '').split(':');
540
- const category = parts[0];
541
- const key = parts.slice(1).join(':');
542
- const content = await redis.get(`cachly:ctx:${category}:${key}`);
543
- lines.push(`**${key}** _(${((hit.similarity ?? 0) * 100).toFixed(0)}% similar)_`, `> ${content?.slice(0, 300) ?? '(evicted)'}${(content?.length ?? 0) > 300 ? 'โ€ฆ' : ''}\n`);
544
- }
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)' });
545
498
  }
546
499
  }
547
500
  }
@@ -549,7 +502,158 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
549
502
  // Semantic search failed silently โ€” keyword results are enough
550
503
  }
551
504
  }
552
- if (kwMatches.length === 0) {
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 {
553
657
  lines.push(`โš ๏ธ No matches found for: "${query}"`);
554
658
  // Did-You-Mean: find nearest token in index vocab
555
659
  const queryTokens = tokenize(query);
@@ -865,6 +969,29 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
865
969
  }
866
970
  // Brain health
867
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
+ }
868
995
  // โ”€โ”€ Layer 7: MCM Domain Coverage Map โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
869
996
  if (lessons.length >= 3) {
870
997
  const domainMap = new Map();
@@ -967,8 +1094,14 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
967
1094
  for (const l of topRecalled) {
968
1095
  const emoji = l.outcome === 'success' ? 'โœ…' : l.outcome === 'partial' ? 'โš ๏ธ' : 'โŒ';
969
1096
  const sev = l.severity === 'critical' ? '๐Ÿ”ด' : l.severity === 'major' ? '๐ŸŸก' : '';
970
- const rct = l.recall_count && l.recall_count > 1 ? ` _(${l.recall_count}ร— recalled)_` : '';
971
- lines.push(` ${emoji}${sev} \`${l.topic}\`${rct} โ€” ${l.what_worked.slice(0, 100)}`);
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)}`);
972
1105
  }
973
1106
  lines.push('');
974
1107
  }
@@ -1034,25 +1167,59 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
1034
1167
  lines.push('');
1035
1168
  }
1036
1169
  // โ”€โ”€ ๐Ÿ”ฎ Predictive Pre-Warning โ€” intent-based danger detection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1037
- // Fires BEFORE work starts. If focus area has known failure patterns โ†’ warn loudly.
1038
- if (focusTerms.length > 0) {
1039
- const dangerLessons = lessons.filter(l => {
1040
- if (l.outcome === 'success')
1041
- return false;
1042
- const topicCategory = l.topic.split(':')[0];
1043
- return focusTerms.some(term => l.topic.toLowerCase().includes(term) ||
1044
- topicCategory === term ||
1045
- (l.tags ?? []).some((t) => t.toLowerCase() === term));
1046
- });
1047
- if (dangerLessons.length >= 1) {
1048
- // Insert warning block right after the title line (index 1 = blank line after title)
1049
- const warning = [
1050
- `๐Ÿšจ **PRE-WARNING** โ€” Read this BEFORE starting:`,
1051
- ` Known pitfalls for **"${focus}"** (${dangerLessons.length} past failure${dangerLessons.length > 1 ? 's' : ''}):`,
1052
- ...dangerLessons.slice(0, 3).map(l => ` โŒ \`${l.topic}\` โ€” ${(l.what_failed ?? l.what_worked).slice(0, 80)}`),
1053
- '',
1054
- ];
1055
- lines.splice(2, 0, ...warning); // after '๐Ÿง  **Session Briefing**' + empty line
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
+ }
1056
1223
  }
1057
1224
  }
1058
1225
  // โ”€โ”€ ๐Ÿ‘ฅ Team Telepathy โ€” what teammates learned this week โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€