a-memory 1.7.0__py3-none-any.whl

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 (550) hide show
  1. .ai-memory.toml +21 -0
  2. .claude/CLAUDE.md +109 -0
  3. .codegraph/.gitignore +5 -0
  4. .coderabbit.yaml +43 -0
  5. .cursorrules +0 -0
  6. .dockerignore +19 -0
  7. .gitattributes +22 -0
  8. .github/CODEOWNERS +11 -0
  9. .github/ISSUE_TEMPLATE/bug_report.yml +109 -0
  10. .github/ISSUE_TEMPLATE/config.yml +8 -0
  11. .github/ISSUE_TEMPLATE/feature_request.yml +54 -0
  12. .github/PULL_REQUEST_TEMPLATE.md +19 -0
  13. .github/codeql-config.yml +14 -0
  14. .github/coderabbit.yaml +7 -0
  15. .github/dependabot.yml +11 -0
  16. .github/labeler.yml +25 -0
  17. .github/release-drafter.yml +81 -0
  18. .github/release.yml +22 -0
  19. .github/workflows/auto-label.yml +20 -0
  20. .github/workflows/auto-merge-dependabot.yml +28 -0
  21. .github/workflows/ci.yml +130 -0
  22. .github/workflows/commit-lint.yml +25 -0
  23. .github/workflows/detect-secrets.yml +24 -0
  24. .github/workflows/docs.yml +46 -0
  25. .github/workflows/release-drafter.yml +22 -0
  26. .github/workflows/release.yml +34 -0
  27. .github/workflows/stale.yml +27 -0
  28. .gitignore +26 -0
  29. .hypothesis/.gitignore +9 -0
  30. .hypothesis/constants/0131918d5eaf61a7 +4 -0
  31. .hypothesis/constants/0217e1bf038fb749 +4 -0
  32. .hypothesis/constants/025685350222e199 +4 -0
  33. .hypothesis/constants/08df5bce2c067c67 +4 -0
  34. .hypothesis/constants/0d08164cc62ee81e +4 -0
  35. .hypothesis/constants/0d271cf9a853088f +4 -0
  36. .hypothesis/constants/0f3739f6a4bbceb3 +4 -0
  37. .hypothesis/constants/0f62f670d7d4556a +4 -0
  38. .hypothesis/constants/106fec097b637479 +4 -0
  39. .hypothesis/constants/1176a493ab7e441f +4 -0
  40. .hypothesis/constants/11a7f35b73b355d5 +4 -0
  41. .hypothesis/constants/17f994f04fca849d +4 -0
  42. .hypothesis/constants/185eaa1e42d08ff6 +4 -0
  43. .hypothesis/constants/189966a34e8bb83c +4 -0
  44. .hypothesis/constants/19674fb7b6e7fa25 +4 -0
  45. .hypothesis/constants/1a01b68ac676293a +4 -0
  46. .hypothesis/constants/1bf1ed0c3b1b2cf2 +4 -0
  47. .hypothesis/constants/1d15d5b976b2e007 +4 -0
  48. .hypothesis/constants/1ec990d09f06e8d9 +4 -0
  49. .hypothesis/constants/203497fc60177f0f +4 -0
  50. .hypothesis/constants/2075c0643eaa44c8 +4 -0
  51. .hypothesis/constants/211c68beb3822a01 +4 -0
  52. .hypothesis/constants/216d171530673e64 +4 -0
  53. .hypothesis/constants/21f42bb6b24338d5 +4 -0
  54. .hypothesis/constants/22f64a08a03d6fea +4 -0
  55. .hypothesis/constants/2713c3dcbfed8a65 +4 -0
  56. .hypothesis/constants/29e7f9bd76ba7954 +4 -0
  57. .hypothesis/constants/2a48faca228fb8b8 +4 -0
  58. .hypothesis/constants/2a720bdb9bcc758b +4 -0
  59. .hypothesis/constants/2a7d3223dfdacdc7 +4 -0
  60. .hypothesis/constants/2ab4878d9f8c321b +4 -0
  61. .hypothesis/constants/2dddcac69bb1261c +4 -0
  62. .hypothesis/constants/2e7ce396a7f9318d +4 -0
  63. .hypothesis/constants/31e48df20d82b835 +4 -0
  64. .hypothesis/constants/366007179f98843a +4 -0
  65. .hypothesis/constants/371b663ed83124ce +4 -0
  66. .hypothesis/constants/377b6e3a8331b432 +4 -0
  67. .hypothesis/constants/3797923938187bee +4 -0
  68. .hypothesis/constants/3799169d4dc29413 +4 -0
  69. .hypothesis/constants/3885295d020f5d92 +4 -0
  70. .hypothesis/constants/39007a22b040288b +4 -0
  71. .hypothesis/constants/39e3ef3d3b0df762 +4 -0
  72. .hypothesis/constants/3c661a25646e3c8d +4 -0
  73. .hypothesis/constants/3d2a9dd03bf5915a +4 -0
  74. .hypothesis/constants/3d3fff0fd8191af3 +4 -0
  75. .hypothesis/constants/3d45e3b69897b84b +4 -0
  76. .hypothesis/constants/3da2cc841c801589 +4 -0
  77. .hypothesis/constants/3e8b58eee7d8b4d5 +4 -0
  78. .hypothesis/constants/4348526455301b6f +4 -0
  79. .hypothesis/constants/447a859c04a7d280 +4 -0
  80. .hypothesis/constants/44ad6c5a9aca898d +4 -0
  81. .hypothesis/constants/45e32c74475b5981 +4 -0
  82. .hypothesis/constants/46b7f518cb403e97 +4 -0
  83. .hypothesis/constants/48000079506593bc +4 -0
  84. .hypothesis/constants/48a14ee521dfa3cb +4 -0
  85. .hypothesis/constants/4b13c601e5d8fa82 +4 -0
  86. .hypothesis/constants/4bb224fed2cc2464 +4 -0
  87. .hypothesis/constants/4bba663f9e3a787b +4 -0
  88. .hypothesis/constants/4c05aaf660e4a3b2 +4 -0
  89. .hypothesis/constants/4c7b96c079ec1ab7 +4 -0
  90. .hypothesis/constants/4e5e35c49498c848 +4 -0
  91. .hypothesis/constants/51621802fe75c4a6 +4 -0
  92. .hypothesis/constants/525e955b17448061 +4 -0
  93. .hypothesis/constants/540912285c94e4b0 +4 -0
  94. .hypothesis/constants/5536ab6571868683 +4 -0
  95. .hypothesis/constants/562262d029962266 +4 -0
  96. .hypothesis/constants/5925e89c57abb6ff +4 -0
  97. .hypothesis/constants/59d133a77d9576e9 +4 -0
  98. .hypothesis/constants/5b400234d99fd69b +4 -0
  99. .hypothesis/constants/5e83b4b2ff8af413 +4 -0
  100. .hypothesis/constants/5fe68c54a53f5ce4 +4 -0
  101. .hypothesis/constants/6131c8fae3ea4a1c +4 -0
  102. .hypothesis/constants/61b12d8090da6b04 +4 -0
  103. .hypothesis/constants/62a13eb5c6ae282e +4 -0
  104. .hypothesis/constants/632362028177075b +4 -0
  105. .hypothesis/constants/637fb6fc4de5ab59 +4 -0
  106. .hypothesis/constants/64581adcc197c14e +4 -0
  107. .hypothesis/constants/64ae85eda3f4fa14 +4 -0
  108. .hypothesis/constants/675e6b5dded87234 +4 -0
  109. .hypothesis/constants/68dd5c4801e6e3fe +4 -0
  110. .hypothesis/constants/6962c1e17c7d2d6e +4 -0
  111. .hypothesis/constants/6b5bb9273029269e +4 -0
  112. .hypothesis/constants/6b76dcda53857290 +4 -0
  113. .hypothesis/constants/6c479fc827b2cfbe +4 -0
  114. .hypothesis/constants/6c4ee0da13eebeae +4 -0
  115. .hypothesis/constants/6d715fa94e03f86b +4 -0
  116. .hypothesis/constants/6e1df95b5625e965 +4 -0
  117. .hypothesis/constants/6e275ef4cbf7ac61 +4 -0
  118. .hypothesis/constants/6fa9fff037e639f7 +4 -0
  119. .hypothesis/constants/71ace2ebb96b893f +4 -0
  120. .hypothesis/constants/71c4d835b625450b +4 -0
  121. .hypothesis/constants/7288eb701eee527d +4 -0
  122. .hypothesis/constants/7403d3ba4d2427c5 +4 -0
  123. .hypothesis/constants/75bbc0c2fcedafd3 +4 -0
  124. .hypothesis/constants/772c40cd27e8744e +4 -0
  125. .hypothesis/constants/79fa24e4d5da2d33 +4 -0
  126. .hypothesis/constants/7b6e9f1290db23f7 +4 -0
  127. .hypothesis/constants/7c6d5f5a208c10c5 +4 -0
  128. .hypothesis/constants/7dd927a870b92315 +4 -0
  129. .hypothesis/constants/7f6008018b3bba5d +4 -0
  130. .hypothesis/constants/8166dc74cd220aec +4 -0
  131. .hypothesis/constants/82db07106773e27e +4 -0
  132. .hypothesis/constants/837366e7d032fae3 +4 -0
  133. .hypothesis/constants/85c89ff18f5bf974 +4 -0
  134. .hypothesis/constants/865ed168629d6aa5 +4 -0
  135. .hypothesis/constants/86e6c616c92ab441 +4 -0
  136. .hypothesis/constants/8a33aed17bd34bab +4 -0
  137. .hypothesis/constants/8d467b62cb87a27e +4 -0
  138. .hypothesis/constants/8dd1a0f92678435a +4 -0
  139. .hypothesis/constants/8e89a1c503109280 +4 -0
  140. .hypothesis/constants/8fa301e58829e474 +4 -0
  141. .hypothesis/constants/91857b0d89aad21a +4 -0
  142. .hypothesis/constants/91a01a53d7020790 +4 -0
  143. .hypothesis/constants/920ae7e76de44d3f +4 -0
  144. .hypothesis/constants/924b1a5d241d153a +4 -0
  145. .hypothesis/constants/9373d27b0b0cf58f +4 -0
  146. .hypothesis/constants/93785fcb751d6e65 +4 -0
  147. .hypothesis/constants/939c8629bda15316 +4 -0
  148. .hypothesis/constants/940ce79a2837ad57 +4 -0
  149. .hypothesis/constants/94c8a35cd4ebef22 +4 -0
  150. .hypothesis/constants/94db69cd7ea46848 +4 -0
  151. .hypothesis/constants/952c7791b4ba4d5a +4 -0
  152. .hypothesis/constants/95ac562d657f621e +4 -0
  153. .hypothesis/constants/979151c821a00dcc +4 -0
  154. .hypothesis/constants/9991e42f1b4b8de3 +4 -0
  155. .hypothesis/constants/99dd57061d19ac3a +4 -0
  156. .hypothesis/constants/9c217d6d4d6f2c4a +4 -0
  157. .hypothesis/constants/9d52c13fb5c16e16 +4 -0
  158. .hypothesis/constants/9d777dde1f80fdb9 +4 -0
  159. .hypothesis/constants/9f37a692294d3003 +4 -0
  160. .hypothesis/constants/9fbe011fca4ea60c +4 -0
  161. .hypothesis/constants/a003e2c8581f010e +4 -0
  162. .hypothesis/constants/a01c724a454a8b07 +4 -0
  163. .hypothesis/constants/a19ddbb5bb485e6d +4 -0
  164. .hypothesis/constants/a34cec0af3c61213 +4 -0
  165. .hypothesis/constants/a388424d0fca2721 +4 -0
  166. .hypothesis/constants/a4a93b033d17cf24 +4 -0
  167. .hypothesis/constants/a560cce78032950e +4 -0
  168. .hypothesis/constants/a5a8f7b489f537fc +4 -0
  169. .hypothesis/constants/a67af5b4aeadcff2 +4 -0
  170. .hypothesis/constants/a6c8f5eb7163d884 +4 -0
  171. .hypothesis/constants/a722cf958fac9b3c +4 -0
  172. .hypothesis/constants/a821981d00e00216 +4 -0
  173. .hypothesis/constants/a963981095621930 +4 -0
  174. .hypothesis/constants/aadddacd15f6ac3a +4 -0
  175. .hypothesis/constants/ac677dd8b8917e26 +4 -0
  176. .hypothesis/constants/ad88faefd482e804 +4 -0
  177. .hypothesis/constants/adb5954c6bb49007 +4 -0
  178. .hypothesis/constants/adc83b19e793491b +4 -0
  179. .hypothesis/constants/b1c9c22b2744f127 +4 -0
  180. .hypothesis/constants/b2336f0cf3fc59dd +4 -0
  181. .hypothesis/constants/b239cb3f0b8646c1 +4 -0
  182. .hypothesis/constants/b2cacfb0b5256165 +4 -0
  183. .hypothesis/constants/b57853a780c67716 +4 -0
  184. .hypothesis/constants/b761cefd2d825687 +4 -0
  185. .hypothesis/constants/b7654ffb010d2a80 +4 -0
  186. .hypothesis/constants/b768678447dffe12 +4 -0
  187. .hypothesis/constants/b9379698f7948f1f +4 -0
  188. .hypothesis/constants/b9462bd5cd23c3e1 +4 -0
  189. .hypothesis/constants/bc536f7f8cec8029 +4 -0
  190. .hypothesis/constants/bc6dc851bd4abd68 +4 -0
  191. .hypothesis/constants/bd470a969c12eaea +4 -0
  192. .hypothesis/constants/c195b79b581a5e4a +4 -0
  193. .hypothesis/constants/c281c878a9cfffe7 +4 -0
  194. .hypothesis/constants/c32689b74b2df638 +4 -0
  195. .hypothesis/constants/c563f080b55d5a55 +4 -0
  196. .hypothesis/constants/c58cb290bf3a6af2 +4 -0
  197. .hypothesis/constants/c599575389481bca +4 -0
  198. .hypothesis/constants/c5d3925deea75efd +4 -0
  199. .hypothesis/constants/c70bdf7090eff52b +4 -0
  200. .hypothesis/constants/c92583fdbbd8025f +4 -0
  201. .hypothesis/constants/c9e899c314823e78 +4 -0
  202. .hypothesis/constants/cbc76d66f0269c37 +4 -0
  203. .hypothesis/constants/cdb3f019754ca1e6 +4 -0
  204. .hypothesis/constants/d13022c0afda1563 +4 -0
  205. .hypothesis/constants/d2610b9162e41d14 +4 -0
  206. .hypothesis/constants/d47087da8bc398e1 +4 -0
  207. .hypothesis/constants/d51ac9dbfded2877 +4 -0
  208. .hypothesis/constants/d83e43c1a00ef966 +4 -0
  209. .hypothesis/constants/d9b8c8c138bb6b21 +4 -0
  210. .hypothesis/constants/d9fa1f244511bb57 +4 -0
  211. .hypothesis/constants/da39a3ee5e6b4b0d +4 -0
  212. .hypothesis/constants/db1a7096187ad5e3 +4 -0
  213. .hypothesis/constants/db6f3b1f106a20fe +4 -0
  214. .hypothesis/constants/dbcc0a55f941df10 +4 -0
  215. .hypothesis/constants/de59ae4c0e9adc1e +4 -0
  216. .hypothesis/constants/de7a1e6e64930056 +4 -0
  217. .hypothesis/constants/df25569e1b33d3f1 +4 -0
  218. .hypothesis/constants/e05c7ba7895ef945 +4 -0
  219. .hypothesis/constants/e3e0949ea24f14cb +4 -0
  220. .hypothesis/constants/e4bebe96ce86ee3a +4 -0
  221. .hypothesis/constants/e4dbd43376140e77 +4 -0
  222. .hypothesis/constants/e515b936fbb56d29 +4 -0
  223. .hypothesis/constants/e55fd534f3c5eef8 +4 -0
  224. .hypothesis/constants/e59ee7a18d689b65 +4 -0
  225. .hypothesis/constants/ea468f1fbc755b35 +4 -0
  226. .hypothesis/constants/eb2261d5ee23ffb7 +4 -0
  227. .hypothesis/constants/ee936137cd0c418e +4 -0
  228. .hypothesis/constants/eea2d47be7a11d26 +4 -0
  229. .hypothesis/constants/eecb26b7b8b77207 +4 -0
  230. .hypothesis/constants/f05c8ae7929ace98 +4 -0
  231. .hypothesis/constants/f16bb017e9ee5af9 +4 -0
  232. .hypothesis/constants/f234592906cfe3fb +4 -0
  233. .hypothesis/constants/f37e913bdd621102 +4 -0
  234. .hypothesis/constants/f3e54889b30a59c0 +4 -0
  235. .hypothesis/constants/f59ffab710c767fd +4 -0
  236. .hypothesis/constants/f5f4ab83cb6aea25 +4 -0
  237. .hypothesis/constants/f9356a6ecf0ddc0a +4 -0
  238. .hypothesis/constants/fbd35b0283486407 +4 -0
  239. .hypothesis/constants/fc6e2235d01cc617 +4 -0
  240. .hypothesis/constants/fcf63ec14b415b9b +4 -0
  241. .hypothesis/constants/fd04c0c4b4afe562 +4 -0
  242. .hypothesis/constants/fe045c8a1d327a5f +4 -0
  243. .hypothesis/constants/fee5511b5a452741 +4 -0
  244. .hypothesis/constants/ffb2784e9f595389 +4 -0
  245. .hypothesis/constants/ffb8303a5a716659 +4 -0
  246. .hypothesis/unicode_data/14.0.0/charmap.json.gz +0 -0
  247. .hypothesis/unicode_data/14.0.0/codec-v2-utf-8.json.gz +0 -0
  248. .pre-commit-config.yaml +27 -0
  249. .windsurfrules +88 -0
  250. ARIEL_RULES.md +28 -0
  251. AUDIT_REPORT_20260629.md +114 -0
  252. CHANGELOG.md +400 -0
  253. CODE_OF_CONDUCT.md +128 -0
  254. CONTRIBUTING.md +68 -0
  255. Dockerfile +30 -0
  256. LICENSE +21 -0
  257. PKG-INFO +261 -0
  258. README.md +192 -0
  259. ROADMAP.md +209 -0
  260. SECURITY.md +63 -0
  261. a_memory-1.7.0.dist-info/METADATA +261 -0
  262. a_memory-1.7.0.dist-info/RECORD +550 -0
  263. a_memory-1.7.0.dist-info/WHEEL +4 -0
  264. a_memory-1.7.0.dist-info/entry_points.txt +4 -0
  265. a_memory-1.7.0.dist-info/licenses/LICENSE +21 -0
  266. alembic/README +1 -0
  267. alembic/env.py +94 -0
  268. alembic/script.py.mako +28 -0
  269. alembic/versions/4f9d5ab34719_add_audit_log_index_and_optimize.py +34 -0
  270. alembic/versions/a38d67fcd99e_init_v8_schema.py +291 -0
  271. alembic/versions/c7e21a94b0d5_layer_isolate_l3_l4.py +59 -0
  272. alembic/versions/e5b7d2f8a1c3_rag_chunks_float_blobs.py +42 -0
  273. alembic.ini +149 -0
  274. config.py +112 -0
  275. config.yaml +169 -0
  276. core/__init__.py +100 -0
  277. core/episodic.py +161 -0
  278. core/memory.py +232 -0
  279. core/projects.py +196 -0
  280. core/reflex.py +75 -0
  281. core/session.py +106 -0
  282. data/auth/keys.enc +0 -0
  283. data/auth/token.enc +1 -0
  284. docker-compose.yml +14 -0
  285. docs/CONTROL_MAP.md +109 -0
  286. docs/api/importance.md +9 -0
  287. docs/api/memory-types.md +9 -0
  288. docs/api/secrets.md +11 -0
  289. docs/architecture/connection.md +44 -0
  290. docs/architecture/diagrams.md +119 -0
  291. docs/architecture/layers.md +76 -0
  292. docs/architecture/overview.md +70 -0
  293. docs/changelog.md +17 -0
  294. docs/contributing.md +29 -0
  295. docs/core/episodic.md +20 -0
  296. docs/core/memory.md +29 -0
  297. docs/core/reflex.md +39 -0
  298. docs/core/session.md +20 -0
  299. docs/features/auth.md +28 -0
  300. docs/features/backup.md +28 -0
  301. docs/features/circuit-breaker.md +35 -0
  302. docs/features/compression.md +17 -0
  303. docs/features/dedup.md +25 -0
  304. docs/features/privacy.md +34 -0
  305. docs/features/rate-limiting.md +18 -0
  306. docs/features/secrets.md +48 -0
  307. docs/features/token-budget.md +27 -0
  308. docs/getting-started/configuration.md +69 -0
  309. docs/getting-started/installation.md +52 -0
  310. docs/getting-started/quickstart.md +42 -0
  311. docs/hooks/system.md +45 -0
  312. docs/index.md +90 -0
  313. docs/lifecycle/overview.md +35 -0
  314. docs/operations/deployment.md +58 -0
  315. docs/operations/monitoring.md +25 -0
  316. docs/operations/testing.md +39 -0
  317. docs/rag/conflict.md +33 -0
  318. docs/rag/engine.md +34 -0
  319. docs/rag/quantize.md +25 -0
  320. docs/rag/router.md +20 -0
  321. docs/rag/scoring.md +31 -0
  322. docs/research/RESEARCH-ideas.md +688 -0
  323. docs/tools/reference.md +125 -0
  324. docs/wiki/agent-wiki.md +20 -0
  325. docs/wiki/file-wiki.md +39 -0
  326. docs/wiki/overview.md +43 -0
  327. docs/wiki/user-wiki.md +20 -0
  328. features/__init__.py +9 -0
  329. features/audit_trail.py +136 -0
  330. features/auth/__init__.py +13 -0
  331. features/auth/api_key.py +82 -0
  332. features/auth/bearer.py +55 -0
  333. features/auth/models.py +18 -0
  334. features/auth/store.py +69 -0
  335. features/backup.py +99 -0
  336. features/backup_cron.py +239 -0
  337. features/compression.py +65 -0
  338. features/dashboard.py +222 -0
  339. features/db_maintenance.py +126 -0
  340. features/import_export.py +98 -0
  341. features/rate_limiting.py +114 -0
  342. features/secrets.py +158 -0
  343. features/typed_export.py +90 -0
  344. full_test_output.txt +12 -0
  345. graph/__init__.py +6 -0
  346. graph/epistemic.py +260 -0
  347. graph/temporal.py +139 -0
  348. hooks/__init__.py +11 -0
  349. hooks/agent_hooks.py +126 -0
  350. hooks/loader.py +5 -0
  351. hooks/models.py +18 -0
  352. hooks/registry.py +101 -0
  353. hooks/shared.py +112 -0
  354. hooks/user_hooks.py +163 -0
  355. lifecycle/__init__.py +7 -0
  356. lifecycle/consolidation.py +117 -0
  357. lifecycle/emotion/README.md +30 -0
  358. lifecycle/emotion/__init__.py +6 -0
  359. lifecycle/emotion/config.py +16 -0
  360. lifecycle/emotion/engine.py +86 -0
  361. lifecycle/emotion/models.py +27 -0
  362. lifecycle/emotion/trigger.py +58 -0
  363. lifecycle/forgetting.py +188 -0
  364. lifecycle/importance_scheduler.py +162 -0
  365. mcp_server/__init__.py +5 -0
  366. mcp_server/__main__.py +3 -0
  367. mcp_server/app.py +57 -0
  368. mcp_server/constants.py +11 -0
  369. mcp_server/context.py +55 -0
  370. mcp_server/endpoints/__init__.py +0 -0
  371. mcp_server/endpoints/auth.py +30 -0
  372. mcp_server/endpoints/backup.py +29 -0
  373. mcp_server/endpoints/common.py +36 -0
  374. mcp_server/endpoints/dashboard.py +66 -0
  375. mcp_server/endpoints/system.py +64 -0
  376. mcp_server/lifespan.py +110 -0
  377. mcp_server/middlewares.py +32 -0
  378. mcp_server/models.py +126 -0
  379. mcp_server/registry.py +32 -0
  380. mcp_server/schema.py +68 -0
  381. mcp_server/server.py +153 -0
  382. mcp_server/tools/base.py +223 -0
  383. mcp_server/tools/episodic.py +112 -0
  384. mcp_server/tools/graph.py +151 -0
  385. mcp_server/tools/memory.py +141 -0
  386. mcp_server/tools/ops.py +404 -0
  387. mcp_server/tools/primitives.py +532 -0
  388. mcp_server/tools/session.py +84 -0
  389. mcp_server/tools/wiki.py +75 -0
  390. mcp_server/tools_layer.py +74 -0
  391. mcp_server/utils/__init__.py +1 -0
  392. mcp_server/utils/privacy.py +31 -0
  393. mkdocs.yml +106 -0
  394. openapi.yaml +314 -0
  395. py.typed +1 -0
  396. pyproject.toml +196 -0
  397. rag/__init__.py +7 -0
  398. rag/chunking.py +59 -0
  399. rag/conflict.py +189 -0
  400. rag/engine.py +180 -0
  401. rag/ingestor.py +115 -0
  402. rag/models.py +34 -0
  403. rag/multi_source.py +175 -0
  404. rag/quantize.py +232 -0
  405. rag/router.py +292 -0
  406. rag/schema.py +75 -0
  407. rag/scoring.py +165 -0
  408. rag/search.py +253 -0
  409. rag/searcher.py +133 -0
  410. shared/__init__.py +47 -0
  411. shared/adaptive.py +91 -0
  412. shared/archived_memories.py +77 -0
  413. shared/assets/__init__.py +0 -0
  414. shared/assets/emotions.json +35 -0
  415. shared/cache.py +57 -0
  416. shared/connection.py +256 -0
  417. shared/constants.py +37 -0
  418. shared/crypto.py +58 -0
  419. shared/dream_buffer.py +147 -0
  420. shared/embeddings.py +179 -0
  421. shared/importance/README.md +33 -0
  422. shared/importance/__init__.py +4 -0
  423. shared/importance/models.py +58 -0
  424. shared/importance/scorer.py +118 -0
  425. shared/importance/signals/__init__.py +21 -0
  426. shared/importance/signals/base_signal.py +10 -0
  427. shared/importance/signals/base_type_signal.py +20 -0
  428. shared/importance/signals/emotion_signal.py +23 -0
  429. shared/importance/signals/length_signal.py +15 -0
  430. shared/importance/signals/noise_signal.py +19 -0
  431. shared/importance/signals/novelty_signal.py +10 -0
  432. shared/importance/signals/question_signal.py +10 -0
  433. shared/importance/signals/retrieval_signal.py +13 -0
  434. shared/importance/signals/tech_signal.py +26 -0
  435. shared/memory_types.py +259 -0
  436. shared/metrics.py +75 -0
  437. shared/middleware.py +222 -0
  438. shared/migrations.py +70 -0
  439. shared/path_safety.py +18 -0
  440. shared/read_only.py +101 -0
  441. shared/saga/README.md +36 -0
  442. shared/saga/__init__.py +36 -0
  443. shared/saga/engine.py +103 -0
  444. shared/saga/impl/__init__.py +0 -0
  445. shared/saga/impl/backup.py +90 -0
  446. shared/saga/impl/base.py +579 -0
  447. shared/saga/impl/consolidation.py +88 -0
  448. shared/saga/impl/crypto.py +91 -0
  449. shared/saga/persistence.py +84 -0
  450. shared/saga/schema.py +33 -0
  451. shared//320/237/320/273/320/260/320/275 /320/277/320/265/321/200/320/265/321/205/320/276/320/264/320/260 /320/275/320/260 aiosqlite.md" +100 -0
  452. test_output.txt +22 -0
  453. tests/__init__.py +0 -0
  454. tests/conftest.py +66 -0
  455. tests/features/test_api_key_auth.py +75 -0
  456. tests/features/test_auth_store.py +66 -0
  457. tests/features/test_bearer_auth.py +53 -0
  458. tests/shared/__init__.py +0 -0
  459. tests/shared/test_importance_models.py +30 -0
  460. tests/shared/test_importance_scorer.py +92 -0
  461. tests/shared/test_importance_scorer_extra.py +75 -0
  462. tests/shared/test_importance_signals.py +58 -0
  463. tests/shared/test_saga_engine.py +160 -0
  464. tests/shared/test_saga_engine_extra.py +118 -0
  465. tests/shared/test_saga_impl.py +143 -0
  466. tests/shared/test_saga_persistence.py +56 -0
  467. tests/shared/test_saga_schema.py +48 -0
  468. tests/test_auth_backup.py +84 -0
  469. tests/test_auth_crypto.py +87 -0
  470. tests/test_conflict_bm25.py +89 -0
  471. tests/test_core/__init__.py +0 -0
  472. tests/test_core/test_core.py +11 -0
  473. tests/test_db_maintenance.py +74 -0
  474. tests/test_dream_buffer_flow.py +69 -0
  475. tests/test_features/__init__.py +0 -0
  476. tests/test_features/test_backup_path_safety.py +31 -0
  477. tests/test_features/test_features.py +14 -0
  478. tests/test_features/test_import_export_path_safety.py +32 -0
  479. tests/test_features_coverage.py +439 -0
  480. tests/test_graph/__init__.py +0 -0
  481. tests/test_hooks/__init__.py +0 -0
  482. tests/test_hooks/test_registry_core.py +61 -0
  483. tests/test_hooks/test_registry_fire.py +57 -0
  484. tests/test_hypothesis.py +1361 -0
  485. tests/test_importance_middleware.py +74 -0
  486. tests/test_importance_scheduler.py +122 -0
  487. tests/test_importance_v2.py +116 -0
  488. tests/test_infra_overhaul.py +17 -0
  489. tests/test_integration.py +183 -0
  490. tests/test_lifecycle/__init__.py +0 -0
  491. tests/test_lifecycle/test_emotion_engine.py +99 -0
  492. tests/test_lifecycle/test_emotion_engine_extra.py +52 -0
  493. tests/test_lifecycle/test_emotion_models.py +39 -0
  494. tests/test_lifecycle/test_emotion_trigger_integration.py +85 -0
  495. tests/test_lifecycle/test_forgetting_edge.py +46 -0
  496. tests/test_mcp/__init__.py +0 -0
  497. tests/test_mcp/test_primitives.py +120 -0
  498. tests/test_mcp/test_tools_e2e.py +336 -0
  499. tests/test_mcp/test_tools_unit.py +167 -0
  500. tests/test_memory_types.py +72 -0
  501. tests/test_mib_quantize.py +101 -0
  502. tests/test_multi_source.py +117 -0
  503. tests/test_primitives.py +171 -0
  504. tests/test_project_audit.py +121 -0
  505. tests/test_projects.py +166 -0
  506. tests/test_rag/__init__.py +0 -0
  507. tests/test_rag/test_rag_engine_lifecycle.py +47 -0
  508. tests/test_rag/test_rag_ingestor.py +84 -0
  509. tests/test_rag/test_rag_schema.py +59 -0
  510. tests/test_rag/test_rag_searcher.py +91 -0
  511. tests/test_rag_binary_search.py +104 -0
  512. tests/test_rag_chunking.py +27 -0
  513. tests/test_rag_keep_float.py +49 -0
  514. tests/test_rag_no_legacy_api.py +40 -0
  515. tests/test_rag_scoring.py +128 -0
  516. tests/test_rag_search_facade.py +166 -0
  517. tests/test_router_improvements.py +106 -0
  518. tests/test_routing_integration.py +147 -0
  519. tests/test_saga_behavior.py +185 -0
  520. tests/test_secrets.py +87 -0
  521. tests/test_shared/__init__.py +0 -0
  522. tests/test_shared/test_connection.py +48 -0
  523. tests/test_shared/test_embeddings_unit.py +17 -0
  524. tests/test_shared/test_middleware_unit.py +68 -0
  525. tests/test_shared/test_migrations_coverage.py +43 -0
  526. tests/test_shared/test_path_safety.py +31 -0
  527. tests/test_shared/test_read_only_coverage.py +53 -0
  528. tests/test_shared/test_saga_crypto_coverage.py +108 -0
  529. tests/test_shared/test_saga_crypto_wiring.py +18 -0
  530. tests/test_shared/test_saga_unit.py +292 -0
  531. tests/test_shared/test_shared.py +63 -0
  532. tests/test_threshold_training.py +132 -0
  533. tests/test_typed_consolidation.py +57 -0
  534. tests/test_typed_forgetting.py +87 -0
  535. tests/test_wiki/__init__.py +0 -0
  536. tests/test_wiki/test_manager.py +128 -0
  537. tests/test_wiki/test_path_safety.py +46 -0
  538. tests/test_wiki/test_wiki.py +41 -0
  539. tests/test_wiki/test_wiki_index.py +145 -0
  540. tests/test_wiki/test_wiki_manager.py +125 -0
  541. tests/test_wiki/test_wiki_manager_extra.py +68 -0
  542. tests/test_wiki/test_wiki_parser.py +74 -0
  543. uv.lock +2699 -0
  544. wiki/README.md +32 -0
  545. wiki/__init__.py +23 -0
  546. wiki/index.py +207 -0
  547. wiki/manager.py +321 -0
  548. wiki/models.py +14 -0
  549. wiki/parser.py +72 -0
  550. wiki/shared.py +41 -0
@@ -0,0 +1,48 @@
1
+ from shared.saga.schema import SagaStatus, SagaStepState, SagaState
2
+ import time
3
+
4
+
5
+ def test_saga_status_enum():
6
+ assert SagaStatus.PENDING == "pending"
7
+ assert SagaStatus.STUCK == "stuck"
8
+
9
+
10
+ def test_saga_step_state_validation():
11
+ step = SagaStepState(name="test_step", status=SagaStatus.PENDING, result={"foo": "bar"})
12
+ assert step.name == "test_step"
13
+ assert step.status == SagaStatus.PENDING
14
+ assert step.result == {"foo": "bar"}
15
+ assert step.error is None
16
+
17
+
18
+ def test_saga_state_validation():
19
+ saga_id = "abc12345"
20
+ state = SagaState(
21
+ saga_id=saga_id,
22
+ name="test_saga",
23
+ context={"user_id": 1},
24
+ steps=[SagaStepState(name="step1", status=SagaStatus.COMPLETED, result={"ok": True})],
25
+ current_step_index=0,
26
+ status=SagaStatus.RUNNING,
27
+ started_at=time.time(),
28
+ )
29
+ assert state.saga_id == saga_id
30
+ assert len(state.steps) == 1
31
+ assert state.status == SagaStatus.RUNNING
32
+
33
+
34
+ def test_saga_state_serialization():
35
+ state = SagaState(saga_id="deadbeef", name="test", context={}, steps=[], current_step_index=0, status=SagaStatus.PENDING, started_at=time.time())
36
+ json_data = state.model_dump_json()
37
+ assert "deadbeef" in json_data
38
+
39
+ restored = SagaState.model_validate_json(json_data)
40
+ assert restored.saga_id == "deadbeef"
41
+
42
+
43
+ if __name__ == "__main__":
44
+ test_saga_status_enum()
45
+ test_saga_step_state_validation()
46
+ test_saga_state_validation()
47
+ test_saga_state_serialization()
48
+ print("All tests passed!")
@@ -0,0 +1,84 @@
1
+ """
2
+ Tests for auth — unique tests only.
3
+ """
4
+
5
+ import pytest
6
+
7
+
8
+ @pytest.mark.asyncio
9
+ async def test_api_key_create():
10
+ from features.auth import APIKeyAuth
11
+
12
+ auth = APIKeyAuth()
13
+ key = auth.create_key("alice", "test key")
14
+ assert key.startswith("ak_")
15
+ assert len(key) > 20
16
+
17
+
18
+ @pytest.mark.asyncio
19
+ async def test_api_key_verify():
20
+ from features.auth import APIKeyAuth
21
+
22
+ auth = APIKeyAuth()
23
+ key = auth.create_key("alice", "test key")
24
+ info = auth.verify(key)
25
+ assert info is not None
26
+ assert info["user_id"] == "alice"
27
+ assert info["label"] == "test key"
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_api_key_revoke():
32
+ from features.auth import APIKeyAuth
33
+
34
+ auth = APIKeyAuth()
35
+ key = auth.create_key("alice", "test key")
36
+ assert auth.verify(key) is not None
37
+ revoked = auth.revoke(key)
38
+ assert revoked is True
39
+ assert auth.verify(key) is None
40
+
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_api_key_list():
44
+ from features.auth import APIKeyAuth
45
+
46
+ auth = APIKeyAuth()
47
+ auth.create_key("alice", "key1")
48
+ auth.create_key("alice", "key2")
49
+ keys = auth.list_keys()
50
+ assert len(keys) >= 2
51
+
52
+
53
+ @pytest.mark.asyncio
54
+ async def test_bearer_auth():
55
+ from features.auth import BearerAuth
56
+
57
+ ba = BearerAuth()
58
+ token = ba.get_token()
59
+ assert token.startswith("mt_")
60
+ assert ba.verify("Bearer " + token) is True
61
+ assert ba.verify("Bearer invalid") is False
62
+ assert ba.verify("") is False
63
+
64
+
65
+ @pytest.mark.asyncio
66
+ async def test_bearer_rotate():
67
+ from features.auth import BearerAuth
68
+
69
+ ba = BearerAuth()
70
+ old_token = ba.get_token()
71
+ new_token = ba.rotate()
72
+ assert old_token != new_token
73
+ assert ba.verify("Bearer " + new_token) is True
74
+
75
+
76
+ @pytest.mark.asyncio
77
+ async def test_mcp_lifespan():
78
+ from mcp_server.server import lifespan, mcp
79
+
80
+ async with lifespan(mcp) as ctx:
81
+ assert ctx is not None
82
+ assert hasattr(ctx, "mm")
83
+ assert hasattr(ctx, "user_wiki")
84
+ assert hasattr(ctx, "agent_wiki")
@@ -0,0 +1,87 @@
1
+ """End-to-end tests for auth with encryption."""
2
+
3
+ import json
4
+ import os
5
+
6
+ import pytest
7
+
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def restore_master_key():
11
+ """Restore MCP_MASTER_KEY after each test to prevent cross-test contamination."""
12
+ original = os.environ.get("MCP_MASTER_KEY")
13
+ yield
14
+ if original is not None:
15
+ os.environ["MCP_MASTER_KEY"] = original
16
+ else:
17
+ os.environ.pop("MCP_MASTER_KEY", None)
18
+ from features import secrets
19
+
20
+ secrets._master_cache.clear()
21
+
22
+
23
+ def test_legacy_plain_json_gets_rotated(tmp_path):
24
+ """If file is plain JSON, it should be auto-encrypted on load."""
25
+ keys_file = tmp_path / "api_keys.json"
26
+ keys_file.write_text(json.dumps({"ak_old": {"user_id": "u1", "label": "old", "enabled": True, "created_at": 1.0}}))
27
+ from features.auth import APIKeyAuth
28
+
29
+ auth = APIKeyAuth(keys_file=str(keys_file))
30
+ keys = auth.list_keys()
31
+ assert len(keys) == 1
32
+ assert keys[0]["user_id"] == "u1"
33
+
34
+ # Verify data survived the rotation — read with fresh instance
35
+ auth2 = APIKeyAuth(keys_file=str(keys_file))
36
+ keys2 = auth2.list_keys()
37
+ assert len(keys2) == 1
38
+ assert keys2[0]["user_id"] == "u1"
39
+
40
+
41
+ def test_create_then_verify_round_trip(tmp_path):
42
+ keys_file = tmp_path / "api_keys.json"
43
+ from features.auth import APIKeyAuth
44
+
45
+ auth = APIKeyAuth(keys_file=str(keys_file))
46
+ key = auth.create_key("alice", "test-key")
47
+ out = auth.verify(key)
48
+ assert out == {"user_id": "alice", "label": "test-key"}
49
+
50
+
51
+ def test_wrong_master_key_fails(tmp_path):
52
+ keys_file = tmp_path / "api_keys.json"
53
+ from features.auth import APIKeyAuth
54
+
55
+ auth = APIKeyAuth(keys_file=str(keys_file))
56
+ auth.create_key("bob", "test-label")
57
+
58
+ # Change master key
59
+ os.environ["MCP_MASTER_KEY"] = "different-secret"
60
+ from features import secrets
61
+
62
+ secrets._master_cache.clear()
63
+
64
+ # Should fail to decrypt (returns empty dict, not exception)
65
+ auth2 = APIKeyAuth(keys_file=str(keys_file))
66
+ assert auth2.list_keys() == [] # cannot read old keys
67
+
68
+
69
+ def test_bearer_token_encrypted(tmp_path):
70
+ token_file = tmp_path / "bearer_token.json"
71
+ from features.auth import BearerAuth
72
+
73
+ auth = BearerAuth(token_file=str(token_file))
74
+ token = auth.get_token()
75
+ assert token.startswith("mt_")
76
+
77
+ # Verify round-trip works
78
+ assert auth.verify(f"Bearer {token}") is True
79
+ assert auth.verify("Bearer invalid") is False
80
+
81
+ # Rotation produces a new token
82
+ old_token = token
83
+ new_token = auth.rotate()
84
+ assert new_token.startswith("mt_")
85
+ assert new_token != old_token
86
+ assert auth.verify(f"Bearer {new_token}") is True
87
+ assert auth.verify(f"Bearer {old_token}") is False
@@ -0,0 +1,89 @@
1
+ """Tests for B3: BM25 + char-trigram conflict similarity."""
2
+
3
+ import pytest
4
+
5
+ from rag.conflict import (
6
+ ConflictResolver,
7
+ bm25_pair_similarity,
8
+ char_ngram_jaccard,
9
+ smart_similarity,
10
+ )
11
+
12
+
13
+ def test_jaccard_basic():
14
+ assert char_ngram_jaccard("hello", "hello") == 1.0
15
+ assert char_ngram_jaccard("abc", "xyz") == 0.0
16
+
17
+
18
+ def test_bm25_pair_basic():
19
+ s = bm25_pair_similarity("redis cluster pipelining", "redis cluster issue")
20
+ assert 0.0 < s < 1.0
21
+ s_unrelated = bm25_pair_similarity("redis cluster", "cats and dogs playing")
22
+ assert s > s_unrelated
23
+
24
+
25
+ def test_smart_similarity_short_uses_ngrams():
26
+ a = "Python is best"
27
+ b = "Python best"
28
+ s = smart_similarity(a, b)
29
+ char_ngram_jaccard(a, b, 3)
30
+ assert s > 0.5
31
+
32
+
33
+ def test_smart_similarity_long_uses_both_signals():
34
+ a = "Python multi-paragraph text about multi-threading and memory model"
35
+ b = "Python multi-paragraph text about concurrency and memory model"
36
+ s = smart_similarity(a, b)
37
+ assert s > 0.4
38
+
39
+
40
+ def test_smart_similarity_rejects_unrelated():
41
+ s = smart_similarity("Python is the best language", "Cats love fish in rivers")
42
+ assert s < 0.15
43
+
44
+
45
+ def test_smart_similarity_empty_inputs():
46
+ assert smart_similarity("", "abc") == 0.0
47
+ assert smart_similarity("abc", "") == 0.0
48
+ assert smart_similarity("", "") == 0.0
49
+
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_resolve_archives_and_audits(tmp_path):
53
+ from shared.connection import AsyncConnectionManager
54
+
55
+ cm = AsyncConnectionManager(base_dir=str(tmp_path))
56
+ cr = ConflictResolver(cm=cm)
57
+ await cr._init_db()
58
+
59
+ # Create conflict group
60
+ conn = await cm.get("memory.db")
61
+ # Init audit table for logging
62
+ from features.audit_trail import AuditTrail
63
+
64
+ at = AuditTrail(cm=cm)
65
+ await at._init_db()
66
+
67
+ await conn.execute(
68
+ "INSERT INTO memory_conflicts (user_id, content, is_conflict, conflict_group_id) VALUES (?, ?, 1, ?)", ("u", "old version", "grp1")
69
+ )
70
+ await conn.execute(
71
+ "INSERT INTO memory_conflicts (user_id, content, is_conflict, conflict_group_id) VALUES (?, ?, 1, ?)", ("u", "new version", "grp1")
72
+ )
73
+ await conn.commit()
74
+
75
+ # Resolve: keep_id=2
76
+ result = await cr.resolve("grp1", keep_id=2)
77
+ assert result is True
78
+
79
+ # Verify only kept entry remains (conflict_group_id set to NULL after resolve)
80
+ cur = await conn.execute("SELECT id, content FROM memory_conflicts WHERE user_id='u'")
81
+ rows = await cur.fetchall()
82
+ assert len(rows) == 1
83
+ assert rows[0][1] == "new version"
84
+
85
+ # Verify audit log
86
+ cur = await conn.execute("SELECT action, details FROM audit_log WHERE action='conflict_resolved'")
87
+ audit_row = await cur.fetchone()
88
+ assert audit_row is not None
89
+ assert "conflict_group_id" in str(audit_row[1])
File without changes
@@ -0,0 +1,11 @@
1
+ """Tests for core/ module — unique tests only."""
2
+
3
+ from core.reflex import ReflexBuffer
4
+
5
+
6
+ def test_reflex_buffer():
7
+ buf = ReflexBuffer(max_size=5)
8
+ buf.add(role="user", content="Hello", tokens=5)
9
+ buf.add(role="assistant", content="Hi", tokens=3)
10
+ assert buf.size() == 2
11
+ assert buf.get_recent(1)[0].content == "Hi"
@@ -0,0 +1,74 @@
1
+ """features.db_maintenance: size thresholds + auto-VACUUM on fragmentation."""
2
+
3
+ import sqlite3
4
+
5
+ import pytest
6
+
7
+ from features.db_maintenance import run_db_maintenance
8
+ from shared.connection import AsyncConnectionManager
9
+
10
+
11
+ @pytest.fixture
12
+ async def cm(tmp_path, monkeypatch):
13
+ manager = AsyncConnectionManager(base_dir=str(tmp_path))
14
+ # maintenance reads connection_manager.base_dir for the file listing —
15
+ # point it at this test's tmp and restore afterwards.
16
+ from shared import connection as conn_mod
17
+
18
+ orig_dir = conn_mod.connection_manager.base_dir
19
+ orig_conns = dict(conn_mod.connection_manager._conns)
20
+ conn_mod.connection_manager.base_dir = tmp_path
21
+ conn_mod.connection_manager._conns.clear()
22
+ yield manager
23
+ conn_mod.connection_manager.base_dir = orig_dir
24
+ conn_mod.connection_manager._conns.clear()
25
+ conn_mod.connection_manager._conns.update(orig_conns)
26
+
27
+
28
+ async def _make_fragmented(path, rows: int) -> None:
29
+ """Create a DB with real dead pages: bulk insert then delete."""
30
+ conn = sqlite3.connect(str(path))
31
+ conn.execute("CREATE TABLE t (v TEXT)")
32
+ conn.executemany("INSERT INTO t VALUES (?)", [("x" * 2000,)] * rows)
33
+ conn.commit()
34
+ conn.execute("DELETE FROM t")
35
+ conn.commit()
36
+ page_count = conn.execute("PRAGMA page_count").fetchone()[0]
37
+ freelist = conn.execute("PRAGMA freelist_count").fetchone()[0]
38
+ assert freelist / max(page_count, 1) > 0.25, "fixture must be fragmented"
39
+ conn.close()
40
+
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_vacuum_reclaims_fragmented_db(cm, tmp_path):
44
+ db = tmp_path / "memory.db"
45
+ await _make_fragmented(db, rows=3000)
46
+
47
+ # Direct probe first: the vacuum primitive itself must reclaim space.
48
+ from features.db_maintenance import _vacuum_if_fragmented
49
+
50
+ freed, did = await _vacuum_if_fragmented(cm, db, min_mb=1, ratio_threshold=0.25)
51
+ assert did and freed > 0, f"direct vacuum: did={did} freed={freed}"
52
+
53
+ reports = await run_db_maintenance(cm=cm, vacuum_min_mb=50)
54
+ mem = [r for r in reports if r.name == "memory.db"]
55
+ assert mem, "memory.db must be reported"
56
+
57
+ conn = sqlite3.connect(str(db))
58
+ assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
59
+ assert conn.execute("PRAGMA freelist_count").fetchone()[0] == 0
60
+
61
+
62
+ @pytest.mark.asyncio
63
+ async def test_thresholds_reported(cm):
64
+ """Small clean DB -> ok level; report carries all data-dir files."""
65
+ import sqlite3 as sq
66
+
67
+ conn = sq.connect(str(cm.base_dir / "projects.db"))
68
+ conn.execute("CREATE TABLE t (v TEXT)")
69
+ conn.commit()
70
+ conn.close()
71
+ reports = await run_db_maintenance(cm=cm)
72
+ names = {r.name for r in reports}
73
+ assert "projects.db" in names
74
+ assert all(r.level in ("ok", "warn", "alert") for r in reports)
@@ -0,0 +1,69 @@
1
+ """DreamBuffer restore: hook stages output, sweep consolidates into L4."""
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ import pytest
6
+
7
+ from core.projects import ProjectMemory # noqa: F401 — ensures package import parity
8
+ from shared.connection import AsyncConnectionManager
9
+ from shared.dream_buffer import DreamBuffer
10
+ from shared.migrations import MigrationManager
11
+
12
+
13
+ @pytest.fixture
14
+ async def cm(tmp_path):
15
+ manager = AsyncConnectionManager(base_dir=tmp_path)
16
+ await MigrationManager(cm=manager).migrate()
17
+ return manager
18
+
19
+
20
+ def _make_ctx(app):
21
+ ctx = MagicMock()
22
+ ctx.request_context.lifespan_context = app
23
+ return ctx
24
+
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_dream_hook_stages_into_buffer(tmp_path):
28
+ """The user-layer dream_buffer handler stages summaries via the caller's cm."""
29
+ from hooks.user_hooks import UserHooks
30
+
31
+ cm = AsyncConnectionManager(base_dir=tmp_path)
32
+
33
+ hooks = UserHooks(user_id="du")
34
+ mem = MagicMock()
35
+ mem._cm = cm
36
+ res = await hooks._dream_buffer({"summary": "deployed v2 to prod", "user_id": "du"}, mem=mem)
37
+ assert res["action"] == "add_to_staging"
38
+
39
+ buf = DreamBuffer(cm=cm, layer="user")
40
+ items = await buf.get_staging("du")
41
+ assert len(items) == 1 and "v2" in items[0]["content"], f"staged={items}"
42
+
43
+ # agent layer stays empty — staging is layer-isolated
44
+ agent_buf = DreamBuffer(cm=cm, layer="agent")
45
+ assert await agent_buf.get_staging("du") == []
46
+
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_sweep_consolidates_staging_into_l4(cm):
50
+ """consolidate_staging promotes staged content; clear empties the buffer."""
51
+ from lifecycle.consolidation import ConsolidationEngine
52
+
53
+ buf = DreamBuffer(cm=cm, layer="user")
54
+ await buf.add("su", session_id="dream", content="critical decision: switched to wal mode always", importance=0.9)
55
+
56
+ items = await buf.get_staging("su")
57
+ engine = ConsolidationEngine(cm=cm, layer="user")
58
+ res = await engine.consolidate_staging("su", items, min_importance=0.7)
59
+ assert res["promoted"] == 1
60
+
61
+ await buf.clear_staging("su")
62
+ assert await buf.get_staging("su") == []
63
+
64
+ # landed in L4 as a fact
65
+ from core.memory import CoreMemory
66
+
67
+ l4 = CoreMemory(cm=cm, layer="user")
68
+ hits = await l4.search("su", "wal mode", limit=5)
69
+ assert any("wal mode" in h["value"] for h in hits)
File without changes
@@ -0,0 +1,31 @@
1
+ """Tests for backup path traversal prevention."""
2
+
3
+ import asyncio
4
+ import json
5
+
6
+ import pytest
7
+
8
+ from features.backup import BackupManager
9
+
10
+
11
+ @pytest.fixture
12
+ def bm(tmp_path):
13
+ data_dir = tmp_path / "data"
14
+ data_dir.mkdir()
15
+ return BackupManager(base_dir=str(data_dir))
16
+
17
+
18
+ @pytest.mark.parametrize(
19
+ ("manifest_files", "should_reject"),
20
+ [
21
+ (["../../etc/crontab", "memory.db"], True),
22
+ (["/etc/passwd"], True),
23
+ ],
24
+ )
25
+ def test_restore_rejects_traversal(bm, manifest_files, should_reject):
26
+ backup_dir = bm.backup_dir / "crafted"
27
+ backup_dir.mkdir()
28
+ manifest = {"files": manifest_files, "created_at": "2026-01-01T00:00:00"}
29
+ (backup_dir / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
30
+ with pytest.raises(ValueError, match="escapes base directory"):
31
+ asyncio.run(bm.restore("crafted"))
@@ -0,0 +1,14 @@
1
+ """Tests for features/ module — unique tests only."""
2
+
3
+ import asyncio
4
+
5
+
6
+ def test_compression():
7
+ from features.compression import MemoryCompressor
8
+
9
+ async def t():
10
+ mc = MemoryCompressor()
11
+ stats = await mc.get_stats("test_feat")
12
+ assert "core_memory" in stats
13
+
14
+ asyncio.run(t())
@@ -0,0 +1,32 @@
1
+ """Tests for import_export path traversal prevention."""
2
+
3
+ import pytest
4
+
5
+ from features.import_export import ImportExport
6
+
7
+
8
+ @pytest.fixture
9
+ def ie(tmp_path):
10
+ export_dir = tmp_path / "exports"
11
+ export_dir.mkdir()
12
+
13
+ class FakeCM:
14
+ def __init__(self, base):
15
+ self._base = base
16
+
17
+ @property
18
+ def base_dir(self):
19
+ return self._base
20
+
21
+ obj = ImportExport.__new__(ImportExport)
22
+ obj._cm = FakeCM(tmp_path)
23
+ obj.export_dir = export_dir
24
+ return obj
25
+
26
+
27
+ @pytest.mark.parametrize("path", ["../../etc/passwd", "/etc/passwd"])
28
+ def test_import_rejects_traversal(ie, path):
29
+ import asyncio
30
+
31
+ with pytest.raises(ValueError, match="escapes base directory"):
32
+ asyncio.run(ie.import_user(path))