@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.
Files changed (309) hide show
  1. package/.github/workflows/eas-build-android.yml +58 -0
  2. package/.github/workflows/eas-update.yml +55 -0
  3. package/STRATEGY.md +209 -0
  4. package/apps/travel-chaos-organizer/.env.example +49 -0
  5. package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
  6. package/apps/travel-chaos-organizer/Makefile +57 -0
  7. package/apps/travel-chaos-organizer/README.md +159 -0
  8. package/apps/travel-chaos-organizer/backend/.env.example +10 -0
  9. package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
  10. package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
  11. package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
  12. package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
  13. package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
  14. package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
  15. package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
  16. package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
  17. package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
  18. package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
  19. package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
  20. package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
  21. package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
  22. package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
  23. package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
  24. package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
  25. package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
  26. package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
  27. package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
  28. package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
  29. package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
  30. package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
  31. package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
  32. package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
  33. package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
  34. package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
  35. package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
  36. package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
  37. package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
  38. package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
  39. package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
  40. package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
  41. package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
  42. package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
  43. package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
  44. package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
  45. package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
  46. package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
  47. package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
  48. package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
  49. package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
  50. package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
  51. package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
  52. package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
  53. package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
  54. package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
  55. package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
  56. package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
  57. package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
  58. package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
  59. package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
  60. package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
  61. package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
  62. package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
  63. package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
  64. package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
  65. package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
  66. package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
  67. package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
  68. package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
  69. package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
  70. package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
  71. package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
  72. package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
  73. package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
  74. package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
  75. package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
  76. package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
  77. package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
  78. package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
  79. package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
  80. package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
  81. package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
  82. package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
  83. package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
  84. package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
  85. package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
  86. package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
  87. package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
  88. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
  89. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
  90. package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
  91. package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
  92. package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
  93. package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
  94. package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
  95. package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
  96. package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
  97. package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
  98. package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
  99. package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
  100. package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
  101. package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
  102. package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
  103. package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
  104. package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
  105. package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
  106. package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
  107. package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
  108. package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
  109. package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
  110. package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
  111. package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
  112. package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
  113. package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
  114. package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
  115. package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
  116. package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
  117. package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
  118. package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
  119. package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
  120. package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
  121. package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
  122. package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
  123. package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
  124. package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
  125. package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
  126. package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
  127. package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
  128. package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
  129. package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
  130. package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
  131. package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
  132. package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
  133. package/apps/travel-chaos-organizer/frontend/package-lock.json +13037 -0
  134. package/apps/travel-chaos-organizer/frontend/package.json +44 -0
  135. package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
  136. package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
  137. package/apps/travel-chaos-organizer/landing/index.html +773 -0
  138. package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
  139. package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
  140. package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
  141. package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
  142. package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
  143. package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
  144. package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
  145. package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
  146. package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
  147. package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
  148. package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
  149. package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
  150. package/dist/packages/telegram-notify/client.d.ts +19 -0
  151. package/dist/packages/telegram-notify/client.js +75 -0
  152. package/dist/packages/telegram-notify/client.js.map +1 -0
  153. package/dist/src/__tests__/auth.test.js.map +1 -0
  154. package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +143 -2
  155. package/dist/src/__tests__/brain-flow.test.js.map +1 -0
  156. package/dist/src/__tests__/confidence.test.js.map +1 -0
  157. package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
  158. package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
  159. package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
  160. package/dist/src/__tests__/e2e/config.js.map +1 -0
  161. package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
  162. package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
  163. package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
  164. package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +1 -1
  165. package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
  166. package/dist/src/__tests__/namespace.test.js.map +1 -0
  167. package/dist/src/auth.js.map +1 -0
  168. package/dist/src/ckg.js.map +1 -0
  169. package/dist/src/confidence.js.map +1 -0
  170. package/dist/src/embeddings.js.map +1 -0
  171. package/dist/src/handlers/advanced.js.map +1 -0
  172. package/dist/{handlers → src/handlers}/brain.js +135 -31
  173. package/dist/src/handlers/brain.js.map +1 -0
  174. package/dist/src/handlers/cache.js.map +1 -0
  175. package/dist/{handlers → src/handlers}/context.js +9 -4
  176. package/dist/src/handlers/context.js.map +1 -0
  177. package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
  178. package/dist/{handlers → src/handlers}/fedbrain.js +15 -2
  179. package/dist/src/handlers/fedbrain.js.map +1 -0
  180. package/dist/src/handlers/instances.js.map +1 -0
  181. package/dist/{handlers → src/handlers}/roadmap.js +8 -4
  182. package/dist/src/handlers/roadmap.js.map +1 -0
  183. package/dist/src/handlers/syndicate.js.map +1 -0
  184. package/dist/src/handlers/tco.d.ts +16 -0
  185. package/dist/src/handlers/tco.js +303 -0
  186. package/dist/src/handlers/tco.js.map +1 -0
  187. package/dist/{handlers → src/handlers}/team.js +1 -1
  188. package/dist/src/handlers/team.js.map +1 -0
  189. package/dist/{index.js → src/index.js} +145 -97
  190. package/dist/src/index.js.map +1 -0
  191. package/dist/src/namespace.js.map +1 -0
  192. package/dist/src/notifier.d.ts +5 -0
  193. package/dist/src/notifier.js +6 -0
  194. package/dist/src/notifier.js.map +1 -0
  195. package/dist/{search.js → src/search.js} +3 -3
  196. package/dist/src/search.js.map +1 -0
  197. package/dist/src/search.test.js.map +1 -0
  198. package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
  199. package/dist/{tools.js → src/tools.js} +178 -0
  200. package/dist/src/tools.js.map +1 -0
  201. package/dist/src/utils.js.map +1 -0
  202. package/package.json +3 -2
  203. package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
  204. package/packages/telegram-notify/client.ts +88 -0
  205. package/packages/telegram-notify/emojis.json +22 -0
  206. package/packages/telegram-notify/pyproject.toml +17 -0
  207. package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
  208. package/packages/telegram-notify/telegram_notify/client.py +87 -0
  209. package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
  210. package/scripts/generate-icons.js +28 -0
  211. package/smithery.yaml +21 -10
  212. package/src/__tests__/brain-flow.test.ts +176 -2
  213. package/src/__tests__/handlers-unit.test.ts +1 -1
  214. package/src/handlers/brain.ts +139 -32
  215. package/src/handlers/context.ts +9 -4
  216. package/src/handlers/fedbrain.ts +17 -2
  217. package/src/handlers/roadmap.ts +8 -4
  218. package/src/handlers/tco.ts +347 -0
  219. package/src/handlers/team.ts +1 -1
  220. package/src/index.ts +137 -147
  221. package/src/notifier.ts +5 -0
  222. package/src/search.ts +3 -3
  223. package/src/tools.ts +191 -0
  224. package/tsconfig.json +2 -3
  225. package/dist/__tests__/auth.test.js.map +0 -1
  226. package/dist/__tests__/brain-flow.test.js.map +0 -1
  227. package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
  228. package/dist/__tests__/brain-predict-failures.test.js +0 -339
  229. package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
  230. package/dist/__tests__/confidence.test.js.map +0 -1
  231. package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
  232. package/dist/__tests__/e2e/api-health.test.js.map +0 -1
  233. package/dist/__tests__/e2e/cli.test.js.map +0 -1
  234. package/dist/__tests__/e2e/config.js.map +0 -1
  235. package/dist/__tests__/e2e/funnel.test.js.map +0 -1
  236. package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
  237. package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
  238. package/dist/__tests__/handlers-unit.test.js.map +0 -1
  239. package/dist/__tests__/namespace.test.js.map +0 -1
  240. package/dist/auth.js.map +0 -1
  241. package/dist/ckg.js.map +0 -1
  242. package/dist/confidence.js.map +0 -1
  243. package/dist/embeddings.js.map +0 -1
  244. package/dist/handlers/advanced.js.map +0 -1
  245. package/dist/handlers/brain.js.map +0 -1
  246. package/dist/handlers/cache.js.map +0 -1
  247. package/dist/handlers/context.js.map +0 -1
  248. package/dist/handlers/fedbrain.js.map +0 -1
  249. package/dist/handlers/instances.js.map +0 -1
  250. package/dist/handlers/roadmap.js.map +0 -1
  251. package/dist/handlers/syndicate.js.map +0 -1
  252. package/dist/handlers/team.js.map +0 -1
  253. package/dist/index.js.map +0 -1
  254. package/dist/namespace.js.map +0 -1
  255. package/dist/search.js.map +0 -1
  256. package/dist/search.test.js.map +0 -1
  257. package/dist/tools.js.map +0 -1
  258. package/dist/utils.js.map +0 -1
  259. package/src/__tests__/brain-predict-failures.test.ts +0 -430
  260. /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
  261. /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
  262. /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
  263. /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
  264. /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
  265. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
  266. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
  267. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
  268. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
  269. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
  270. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
  271. /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
  272. /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
  273. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
  274. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
  275. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
  276. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
  277. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
  278. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
  279. /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
  280. /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
  281. /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
  282. /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
  283. /package/dist/{auth.js → src/auth.js} +0 -0
  284. /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
  285. /package/dist/{ckg.js → src/ckg.js} +0 -0
  286. /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
  287. /package/dist/{confidence.js → src/confidence.js} +0 -0
  288. /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
  289. /package/dist/{embeddings.js → src/embeddings.js} +0 -0
  290. /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
  291. /package/dist/{handlers → src/handlers}/advanced.js +0 -0
  292. /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
  293. /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
  294. /package/dist/{handlers → src/handlers}/cache.js +0 -0
  295. /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
  296. /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
  297. /package/dist/{handlers → src/handlers}/instances.js +0 -0
  298. /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
  299. /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
  300. /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
  301. /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
  302. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  303. /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
  304. /package/dist/{namespace.js → src/namespace.js} +0 -0
  305. /package/dist/{search.d.ts → src/search.d.ts} +0 -0
  306. /package/dist/{search.test.d.ts → src/search.test.d.ts} +0 -0
  307. /package/dist/{search.test.js → src/search.test.js} +0 -0
  308. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
  309. /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,3 @@
1
+ from .client import notify, notify_user, format_message
2
+
3
+ __all__ = ["notify", "notify_user", "format_message"]
@@ -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: http
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: "Get your free API key at https://cachly.dev/setup-ai (no credit card)."
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
- required:
33
- - CACHLY_JWT
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 self = this;
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, self.store.get(c.key) ?? 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?.[1]!;
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);