@smilintux/skcapstone 0.1.0 → 0.2.3

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 (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,339 @@
1
+ """Tests for the Memory Adapter — bridge between skcapstone and skmemory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+
10
+ from skcapstone.memory_adapter import (
11
+ _LAYER_FROM_SKMEMORY,
12
+ _LAYER_TO_SKMEMORY,
13
+ entry_to_memory,
14
+ get_unified,
15
+ memory_to_entry,
16
+ verify_sync,
17
+ reindex_all,
18
+ )
19
+ from skcapstone.models import MemoryEntry, MemoryLayer
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Layer mapping
24
+ # ---------------------------------------------------------------------------
25
+
26
+
27
+ class TestLayerMapping:
28
+ """Tests for layer name conversion."""
29
+
30
+ def test_to_skmemory_mapping(self) -> None:
31
+ """skcapstone layers map to skmemory strings."""
32
+ assert _LAYER_TO_SKMEMORY[MemoryLayer.SHORT_TERM] == "short-term"
33
+ assert _LAYER_TO_SKMEMORY[MemoryLayer.MID_TERM] == "mid-term"
34
+ assert _LAYER_TO_SKMEMORY[MemoryLayer.LONG_TERM] == "long-term"
35
+
36
+ def test_from_skmemory_mapping(self) -> None:
37
+ """skmemory strings map back to skcapstone layers."""
38
+ assert _LAYER_FROM_SKMEMORY["short-term"] == MemoryLayer.SHORT_TERM
39
+ assert _LAYER_FROM_SKMEMORY["mid-term"] == MemoryLayer.MID_TERM
40
+ assert _LAYER_FROM_SKMEMORY["long-term"] == MemoryLayer.LONG_TERM
41
+
42
+ def test_roundtrip(self) -> None:
43
+ """Layer mappings are invertible."""
44
+ for layer in MemoryLayer:
45
+ sk_str = _LAYER_TO_SKMEMORY[layer]
46
+ assert _LAYER_FROM_SKMEMORY[sk_str] == layer
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # entry_to_memory conversion
51
+ # ---------------------------------------------------------------------------
52
+
53
+
54
+ class TestEntryToMemory:
55
+ """Tests for converting MemoryEntry to skmemory Memory."""
56
+
57
+ def test_basic_conversion(self) -> None:
58
+ """Basic entry converts to Memory with correct fields."""
59
+ entry = MemoryEntry(
60
+ memory_id="abc123",
61
+ content="Test memory content",
62
+ tags=["test", "unit"],
63
+ source="cli",
64
+ layer=MemoryLayer.SHORT_TERM,
65
+ importance=0.7,
66
+ )
67
+ mem = entry_to_memory(entry)
68
+ assert mem.id == "abc123"
69
+ assert mem.content == "Test memory content"
70
+ assert mem.tags == ["test", "unit"]
71
+ assert mem.source == "cli"
72
+ assert mem.layer.value == "short-term"
73
+
74
+ def test_title_from_content(self) -> None:
75
+ """Title is derived from first 80 chars of content."""
76
+ entry = MemoryEntry(
77
+ memory_id="abc",
78
+ content="A" * 100,
79
+ )
80
+ mem = entry_to_memory(entry)
81
+ assert len(mem.title) == 80
82
+
83
+ def test_emotional_intensity_from_importance(self) -> None:
84
+ """Importance maps to emotional intensity (x10)."""
85
+ entry = MemoryEntry(
86
+ memory_id="abc",
87
+ content="Test",
88
+ importance=0.8,
89
+ )
90
+ mem = entry_to_memory(entry)
91
+ assert mem.emotional.intensity == 8.0
92
+
93
+ def test_metadata_preserved(self) -> None:
94
+ """Extra metadata fields are preserved."""
95
+ entry = MemoryEntry(
96
+ memory_id="abc",
97
+ content="Test",
98
+ metadata={"custom_key": "custom_value"},
99
+ access_count=5,
100
+ soul_context="lumina",
101
+ )
102
+ mem = entry_to_memory(entry)
103
+ assert mem.metadata["access_count"] == 5
104
+ assert mem.metadata["soul_context"] == "lumina"
105
+ assert mem.metadata["custom_key"] == "custom_value"
106
+
107
+ def test_all_layers(self) -> None:
108
+ """All layer types convert correctly."""
109
+ for layer in MemoryLayer:
110
+ entry = MemoryEntry(
111
+ memory_id=f"id-{layer.value}",
112
+ content="Test",
113
+ layer=layer,
114
+ )
115
+ mem = entry_to_memory(entry)
116
+ assert mem.layer.value == _LAYER_TO_SKMEMORY[layer]
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # memory_to_entry conversion
121
+ # ---------------------------------------------------------------------------
122
+
123
+
124
+ class TestMemoryToEntry:
125
+ """Tests for converting skmemory Memory to MemoryEntry."""
126
+
127
+ def test_roundtrip(self) -> None:
128
+ """Entry → Memory → Entry preserves key fields."""
129
+ original = MemoryEntry(
130
+ memory_id="round-trip",
131
+ content="Round trip test",
132
+ tags=["test"],
133
+ source="mcp",
134
+ layer=MemoryLayer.MID_TERM,
135
+ importance=0.6,
136
+ access_count=3,
137
+ )
138
+ mem = entry_to_memory(original)
139
+ restored = memory_to_entry(mem)
140
+ assert restored.memory_id == original.memory_id
141
+ assert restored.content == original.content
142
+ assert restored.tags == original.tags
143
+ assert restored.source == original.source
144
+ assert restored.layer == original.layer
145
+ assert abs(restored.importance - original.importance) < 0.01
146
+ assert restored.access_count == original.access_count
147
+
148
+ def test_importance_clamped(self) -> None:
149
+ """Importance is clamped to [0.0, 1.0]."""
150
+ from skmemory.models import EmotionalSnapshot, Memory, MemoryLayer as SKLayer
151
+
152
+ mem = Memory(
153
+ id="test",
154
+ title="test",
155
+ content="test",
156
+ layer=SKLayer("short-term"),
157
+ tags=[],
158
+ source="test",
159
+ emotional=EmotionalSnapshot(intensity=10.0), # 10 / 10 = 1.0
160
+ metadata={},
161
+ )
162
+ entry = memory_to_entry(mem)
163
+ assert entry.importance <= 1.0
164
+ assert entry.importance >= 0.0
165
+
166
+ def test_soul_context_extracted(self) -> None:
167
+ """soul_context is extracted from metadata."""
168
+ original = MemoryEntry(
169
+ memory_id="soul-test",
170
+ content="Soul context test",
171
+ soul_context="opus",
172
+ )
173
+ mem = entry_to_memory(original)
174
+ restored = memory_to_entry(mem)
175
+ assert restored.soul_context == "opus"
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # get_unified singleton
180
+ # ---------------------------------------------------------------------------
181
+
182
+
183
+ class TestGetUnified:
184
+ """Tests for the unified store singleton."""
185
+
186
+ def test_returns_none_without_skmemory(self) -> None:
187
+ """Returns None when skmemory is not available."""
188
+ import skcapstone.memory_adapter as ma
189
+
190
+ old_checked = ma._unified_checked
191
+ old_store = ma._unified_store
192
+ try:
193
+ ma._unified_checked = False
194
+ ma._unified_store = None
195
+ with patch.object(ma, "_skmemory_available", return_value=False):
196
+ with patch.object(ma, "_get_store", return_value=None):
197
+ result = get_unified()
198
+ assert result is None
199
+ finally:
200
+ ma._unified_checked = old_checked
201
+ ma._unified_store = old_store
202
+
203
+ def test_caches_result(self) -> None:
204
+ """Second call returns cached store, doesn't recreate."""
205
+ import skcapstone.memory_adapter as ma
206
+
207
+ old_checked = ma._unified_checked
208
+ old_store = ma._unified_store
209
+ try:
210
+ ma._unified_checked = False
211
+ ma._unified_store = None
212
+ mock_store = MagicMock()
213
+ with patch.object(ma, "_get_store", return_value=mock_store) as mock_get:
214
+ first = get_unified()
215
+ second = get_unified()
216
+ assert first is second
217
+ assert mock_get.call_count == 1
218
+ finally:
219
+ ma._unified_checked = old_checked
220
+ ma._unified_store = old_store
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # verify_sync
225
+ # ---------------------------------------------------------------------------
226
+
227
+
228
+ class TestVerifySync:
229
+ """Tests for sync verification."""
230
+
231
+ def test_returns_not_available_without_skmemory(self) -> None:
232
+ """Returns error dict when no store available."""
233
+ import skcapstone.memory_adapter as ma
234
+
235
+ old_checked = ma._unified_checked
236
+ old_store = ma._unified_store
237
+ try:
238
+ ma._unified_checked = True
239
+ ma._unified_store = None
240
+ result = verify_sync()
241
+ assert result["synced"] is False
242
+ assert "not available" in result["reason"]
243
+ finally:
244
+ ma._unified_checked = old_checked
245
+ ma._unified_store = old_store
246
+
247
+ def test_detects_sync(self) -> None:
248
+ """Reports synced when counts match."""
249
+ mock_store = MagicMock()
250
+ mock_store.health.return_value = {
251
+ "primary": {"ok": True},
252
+ "vector": {"ok": True, "point_count": 10},
253
+ }
254
+ mock_store.primary.stats.return_value = {"total": 10}
255
+
256
+ import skcapstone.memory_adapter as ma
257
+
258
+ old_checked = ma._unified_checked
259
+ old_store = ma._unified_store
260
+ try:
261
+ ma._unified_checked = True
262
+ ma._unified_store = mock_store
263
+ result = verify_sync()
264
+ assert result["synced"] is True
265
+ finally:
266
+ ma._unified_checked = old_checked
267
+ ma._unified_store = old_store
268
+
269
+ def test_detects_mismatch(self) -> None:
270
+ """Reports not synced when counts differ."""
271
+ mock_store = MagicMock()
272
+ mock_store.health.return_value = {
273
+ "primary": {"ok": True},
274
+ "vector": {"ok": True, "point_count": 5},
275
+ }
276
+ mock_store.primary.stats.return_value = {"total": 10}
277
+
278
+ import skcapstone.memory_adapter as ma
279
+
280
+ old_checked = ma._unified_checked
281
+ old_store = ma._unified_store
282
+ try:
283
+ ma._unified_checked = True
284
+ ma._unified_store = mock_store
285
+ result = verify_sync()
286
+ assert result["synced"] is False
287
+ assert "mismatch" in result.get("reason", "").lower()
288
+ finally:
289
+ ma._unified_checked = old_checked
290
+ ma._unified_store = old_store
291
+
292
+
293
+ # ---------------------------------------------------------------------------
294
+ # reindex_all
295
+ # ---------------------------------------------------------------------------
296
+
297
+
298
+ class TestReindexAll:
299
+ """Tests for full reindex."""
300
+
301
+ def test_returns_not_available_without_store(self) -> None:
302
+ """Returns error when no store available."""
303
+ import skcapstone.memory_adapter as ma
304
+
305
+ old_checked = ma._unified_checked
306
+ old_store = ma._unified_store
307
+ try:
308
+ ma._unified_checked = True
309
+ ma._unified_store = None
310
+ result = reindex_all()
311
+ assert result["ok"] is False
312
+ finally:
313
+ ma._unified_checked = old_checked
314
+ ma._unified_store = old_store
315
+
316
+ def test_reindexes_all_memories(self) -> None:
317
+ """Reindex processes all memories from primary."""
318
+ mock_mem = MagicMock()
319
+ mock_mem.id = "test-1"
320
+ mock_store = MagicMock()
321
+ mock_store.list_memories.return_value = [mock_mem, mock_mem]
322
+ mock_store.vector = MagicMock()
323
+ mock_store.graph = MagicMock()
324
+
325
+ import skcapstone.memory_adapter as ma
326
+
327
+ old_checked = ma._unified_checked
328
+ old_store = ma._unified_store
329
+ try:
330
+ ma._unified_checked = True
331
+ ma._unified_store = mock_store
332
+ result = reindex_all()
333
+ assert result["ok"] is True
334
+ assert result["total"] == 2
335
+ assert result["vector_indexed"] == 2
336
+ assert result["graph_indexed"] == 2
337
+ finally:
338
+ ma._unified_checked = old_checked
339
+ ma._unified_store = old_store
@@ -0,0 +1,218 @@
1
+ """Tests for the memory curator module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from skcapstone.memory_engine import list_memories, recall, store
10
+ from skcapstone.memory_curator import (
11
+ CurationResult,
12
+ MemoryCurator,
13
+ _content_hash,
14
+ _suggest_tags,
15
+ )
16
+ from skcapstone.models import MemoryLayer
17
+ from skcapstone.pillars.memory import initialize_memory
18
+
19
+
20
+ @pytest.fixture
21
+ def curator_home(tmp_agent_home: Path) -> Path:
22
+ """Provide an agent home with memory initialized."""
23
+ initialize_memory(tmp_agent_home)
24
+ return tmp_agent_home
25
+
26
+
27
+ class TestSuggestTags:
28
+ """Tests for the auto-tag suggestion function."""
29
+
30
+ def test_detects_capauth(self):
31
+ """Finds capauth mentions."""
32
+ tags = _suggest_tags("The capauth system handles identity", [])
33
+ assert "capauth" in tags
34
+
35
+ def test_skips_existing_tags(self):
36
+ """Doesn't suggest tags already present."""
37
+ tags = _suggest_tags("The capauth system", ["capauth"])
38
+ assert "capauth" not in tags
39
+
40
+ def test_multiple_suggestions(self):
41
+ """Detects multiple patterns in one text."""
42
+ tags = _suggest_tags("skcapstone uses MCP and PGP for security", [])
43
+ assert "skcapstone" in tags
44
+ assert "mcp" in tags
45
+ assert "pgp" in tags
46
+
47
+ def test_no_suggestions_for_generic(self):
48
+ """Generic text gets no tag suggestions."""
49
+ tags = _suggest_tags("The weather is nice today", [])
50
+ assert tags == []
51
+
52
+
53
+ class TestContentHash:
54
+ """Tests for the deduplication hash function."""
55
+
56
+ def test_identical_content(self):
57
+ """Same content produces same hash."""
58
+ assert _content_hash("hello world") == _content_hash("hello world")
59
+
60
+ def test_case_insensitive(self):
61
+ """Hash is case-insensitive."""
62
+ assert _content_hash("Hello World") == _content_hash("hello world")
63
+
64
+ def test_whitespace_normalized(self):
65
+ """Extra whitespace is normalized before hashing."""
66
+ assert _content_hash("hello world") == _content_hash("hello world")
67
+
68
+ def test_different_content(self):
69
+ """Different content produces different hashes."""
70
+ assert _content_hash("alpha") != _content_hash("beta")
71
+
72
+
73
+ class TestCuratorAutoTag:
74
+ """Tests for the auto-tagging pass."""
75
+
76
+ def test_adds_missing_tags(self, curator_home: Path):
77
+ """Auto-tag adds relevant tags to untagged memories."""
78
+ store(curator_home, "The skcapstone MCP server exposes tools", tags=[])
79
+ curator = MemoryCurator(curator_home)
80
+ result = curator.curate(promote=False, dedupe=False)
81
+
82
+ assert len(result.tagged) >= 1
83
+ memories = list_memories(curator_home)
84
+ tagged_mem = next((m for m in memories if "skcapstone" in m.tags), None)
85
+ assert tagged_mem is not None
86
+
87
+ def test_dry_run_no_changes(self, curator_home: Path):
88
+ """Dry run reports changes without applying them."""
89
+ store(curator_home, "The capauth PGP system", tags=[])
90
+ curator = MemoryCurator(curator_home)
91
+ result = curator.curate(dry_run=True, promote=False, dedupe=False)
92
+
93
+ assert len(result.tagged) >= 1
94
+ memories = list_memories(curator_home)
95
+ for m in memories:
96
+ assert "capauth" not in m.tags
97
+
98
+
99
+ class TestCuratorPromote:
100
+ """Tests for the promotion pass."""
101
+
102
+ def test_promotes_high_access(self, curator_home: Path):
103
+ """Memories with high access count get promoted."""
104
+ entry = store(curator_home, "Frequently accessed memory", importance=0.3)
105
+ entry.access_count = 5
106
+ from skcapstone.memory_engine import _save_entry
107
+ _save_entry(curator_home, entry)
108
+
109
+ curator = MemoryCurator(curator_home)
110
+ result = curator.curate(auto_tag=False, dedupe=False)
111
+
112
+ assert len(result.promoted) >= 1
113
+
114
+ def test_promotes_high_importance(self, curator_home: Path):
115
+ """High importance short-term memories get promoted."""
116
+ store(curator_home, "Very important memory", importance=0.8)
117
+
118
+ curator = MemoryCurator(curator_home)
119
+ result = curator.curate(auto_tag=False, dedupe=False)
120
+
121
+ # Reason: importance >= 0.7 auto-promotes to mid-term at store time,
122
+ # so this won't be in short-term. Store at 0.65 instead.
123
+ store(curator_home, "Almost important", importance=0.65)
124
+ entry = list_memories(curator_home, limit=1)[0]
125
+ entry.access_count = 4
126
+ _save_entry = __import__("skcapstone.memory_engine", fromlist=["_save_entry"])._save_entry
127
+ _save_entry(curator_home, entry)
128
+
129
+ result2 = curator.curate(auto_tag=False, dedupe=False)
130
+ # At least the high-access one should promote
131
+ assert result2.total_scanned >= 1
132
+
133
+
134
+ class TestCuratorDedupe:
135
+ """Tests for the deduplication pass."""
136
+
137
+ def test_removes_exact_duplicates(self, curator_home: Path):
138
+ """Identical content is deduplicated."""
139
+ store(curator_home, "Duplicate memory content here", importance=0.4)
140
+ store(curator_home, "Duplicate memory content here", importance=0.3)
141
+
142
+ curator = MemoryCurator(curator_home)
143
+ result = curator.curate(auto_tag=False, promote=False)
144
+
145
+ assert len(result.deduped) >= 1
146
+
147
+ def test_keeps_higher_tier(self, curator_home: Path):
148
+ """When deduping, the higher-tier memory is kept."""
149
+ store(curator_home, "Keep this important memory", importance=0.9)
150
+ store(curator_home, "Keep this important memory", importance=0.3)
151
+
152
+ before = len(list_memories(curator_home))
153
+ curator = MemoryCurator(curator_home)
154
+ curator.curate(auto_tag=False, promote=False)
155
+ after = len(list_memories(curator_home))
156
+
157
+ assert after < before
158
+
159
+ def test_no_false_positives(self, curator_home: Path):
160
+ """Different content is not deduplicated."""
161
+ store(curator_home, "First unique memory about cats")
162
+ store(curator_home, "Second unique memory about dogs")
163
+
164
+ curator = MemoryCurator(curator_home)
165
+ result = curator.curate(auto_tag=False, promote=False)
166
+
167
+ assert len(result.deduped) == 0
168
+
169
+
170
+ class TestCuratorStats:
171
+ """Tests for get_stats()."""
172
+
173
+ def test_empty_stats(self, curator_home: Path):
174
+ """Stats on empty store returns zero counts."""
175
+ curator = MemoryCurator(curator_home)
176
+ stats = curator.get_stats()
177
+ assert stats["total"] == 0
178
+
179
+ def test_stats_with_memories(self, curator_home: Path):
180
+ """Stats reflect stored memories."""
181
+ store(curator_home, "First memory", tags=["alpha"])
182
+ store(curator_home, "Second memory", tags=["beta"])
183
+ store(curator_home, "Untagged memory")
184
+
185
+ curator = MemoryCurator(curator_home)
186
+ stats = curator.get_stats()
187
+
188
+ assert stats["total"] == 3
189
+ assert stats["tag_coverage"] > 0.0
190
+ assert stats["avg_importance"] > 0.0
191
+
192
+ def test_top_tags(self, curator_home: Path):
193
+ """Top tags are reported correctly."""
194
+ for _ in range(3):
195
+ store(curator_home, "PGP encryption test", tags=["pgp"])
196
+ store(curator_home, "Other stuff", tags=["misc"])
197
+
198
+ curator = MemoryCurator(curator_home)
199
+ stats = curator.get_stats()
200
+
201
+ tag_names = [t[0] for t in stats["top_tags"]]
202
+ assert "pgp" in tag_names
203
+
204
+
205
+ class TestFullCuration:
206
+ """End-to-end curation test."""
207
+
208
+ def test_full_pass(self, curator_home: Path):
209
+ """Full curation pass runs all three phases."""
210
+ store(curator_home, "The skcapstone architecture uses pillars")
211
+ store(curator_home, "The skcapstone architecture uses pillars")
212
+ store(curator_home, "Security requires PGP encryption", tags=[])
213
+
214
+ curator = MemoryCurator(curator_home)
215
+ result = curator.curate()
216
+
217
+ assert result.total_scanned >= 3
218
+ assert isinstance(result, CurationResult)
@@ -22,6 +22,12 @@ from skcapstone.memory_engine import (
22
22
  from skcapstone.models import MemoryEntry, MemoryLayer
23
23
 
24
24
 
25
+ @pytest.fixture(autouse=True)
26
+ def no_unified_backend(monkeypatch):
27
+ """Disable the unified skmemory backend so tests use only file-based storage."""
28
+ monkeypatch.setattr("skcapstone.memory_engine._get_unified", lambda: None)
29
+
30
+
25
31
  @pytest.fixture
26
32
  def agent_home(tmp_path: Path) -> Path:
27
33
  """Create a temporary agent home with memory directories."""