@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
@@ -6,7 +6,12 @@ import json
6
6
  from pathlib import Path
7
7
 
8
8
  from skcapstone.pillars.identity import generate_identity
9
- from skcapstone.pillars.security import audit_event, initialize_security
9
+ from skcapstone.pillars.security import (
10
+ AuditEntry,
11
+ audit_event,
12
+ initialize_security,
13
+ read_audit_log,
14
+ )
10
15
  from skcapstone.pillars.trust import initialize_trust, record_trust_state
11
16
  from skcapstone.models import PillarStatus
12
17
 
@@ -64,20 +69,50 @@ class TestSecurityPillar:
64
69
  """Tests for security initialization and audit logging."""
65
70
 
66
71
  def test_initialize_creates_audit_log(self, tmp_agent_home: Path):
67
- """initialize_security should create the audit log."""
72
+ """initialize_security should create a structured JSONL audit log."""
68
73
  initialize_security(tmp_agent_home)
69
74
  audit_log = tmp_agent_home / "security" / "audit.log"
70
75
  assert audit_log.exists()
71
- assert "INIT" in audit_log.read_text()
72
76
 
73
- def test_audit_event_appends(self, tmp_agent_home: Path):
74
- """audit_event should append entries to the log."""
77
+ line = audit_log.read_text().strip()
78
+ data = json.loads(line)
79
+ assert data["event_type"] == "INIT"
80
+ assert "timestamp" in data
81
+ assert "host" in data
82
+
83
+ def test_audit_event_appends_structured(self, tmp_agent_home: Path):
84
+ """audit_event should append structured JSON entries."""
75
85
  initialize_security(tmp_agent_home)
76
- audit_event(tmp_agent_home, "TEST", "unit test event")
86
+ entry = audit_event(tmp_agent_home, "TEST", "unit test event")
87
+
88
+ assert isinstance(entry, AuditEntry)
89
+ assert entry.event_type == "TEST"
90
+ assert entry.detail == "unit test event"
91
+
92
+ lines = (tmp_agent_home / "security" / "audit.log").read_text().splitlines()
93
+ assert len(lines) == 2
94
+ parsed = json.loads(lines[1])
95
+ assert parsed["event_type"] == "TEST"
96
+ assert parsed["detail"] == "unit test event"
97
+
98
+ def test_audit_event_with_metadata(self, tmp_agent_home: Path):
99
+ """audit_event should store optional agent and metadata fields."""
100
+ initialize_security(tmp_agent_home)
101
+ entry = audit_event(
102
+ tmp_agent_home,
103
+ "TOKEN_ISSUE",
104
+ "Issued token abc123",
105
+ agent="opus",
106
+ metadata={"token_id": "abc123", "capabilities": ["read"]},
107
+ )
77
108
 
78
- log_content = (tmp_agent_home / "security" / "audit.log").read_text()
79
- assert "TEST" in log_content
80
- assert "unit test event" in log_content
109
+ assert entry.agent == "opus"
110
+ assert entry.metadata["token_id"] == "abc123"
111
+
112
+ lines = (tmp_agent_home / "security" / "audit.log").read_text().splitlines()
113
+ parsed = json.loads(lines[-1])
114
+ assert parsed["agent"] == "opus"
115
+ assert parsed["metadata"]["token_id"] == "abc123"
81
116
 
82
117
  def test_audit_event_creates_dir_if_missing(self, tmp_path: Path):
83
118
  """audit_event should create security dir if it doesn't exist."""
@@ -85,3 +120,46 @@ class TestSecurityPillar:
85
120
  fresh_home.mkdir()
86
121
  audit_event(fresh_home, "BOOT", "first event")
87
122
  assert (fresh_home / "security" / "audit.log").exists()
123
+
124
+ def test_read_audit_log_parses_entries(self, tmp_agent_home: Path):
125
+ """read_audit_log should return structured AuditEntry objects."""
126
+ initialize_security(tmp_agent_home)
127
+ audit_event(tmp_agent_home, "AUTH", "key verified")
128
+ audit_event(tmp_agent_home, "SYNC_PUSH", "seed pushed")
129
+
130
+ entries = read_audit_log(tmp_agent_home)
131
+ assert len(entries) == 3
132
+ assert entries[0].event_type == "INIT"
133
+ assert entries[1].event_type == "AUTH"
134
+ assert entries[2].event_type == "SYNC_PUSH"
135
+
136
+ def test_read_audit_log_handles_legacy(self, tmp_agent_home: Path):
137
+ """read_audit_log should gracefully handle old plain-text entries."""
138
+ security_dir = tmp_agent_home / "security"
139
+ security_dir.mkdir(parents=True, exist_ok=True)
140
+ log = security_dir / "audit.log"
141
+ log.write_text(
142
+ "[2026-02-22T12:00:00+00:00] INIT — old format\n"
143
+ "[2026-02-22T12:01:00+00:00] AUTH — legacy auth\n"
144
+ )
145
+
146
+ entries = read_audit_log(tmp_agent_home)
147
+ assert len(entries) == 2
148
+ assert all(e.event_type == "LEGACY" for e in entries)
149
+ assert "old format" in entries[0].detail
150
+
151
+ def test_read_audit_log_with_limit(self, tmp_agent_home: Path):
152
+ """read_audit_log with limit returns only the newest N entries."""
153
+ initialize_security(tmp_agent_home)
154
+ for i in range(5):
155
+ audit_event(tmp_agent_home, "EVENT", f"entry {i}")
156
+
157
+ entries = read_audit_log(tmp_agent_home, limit=2)
158
+ assert len(entries) == 2
159
+ assert "entry 3" in entries[0].detail
160
+ assert "entry 4" in entries[1].detail
161
+
162
+ def test_read_audit_log_empty(self, tmp_path: Path):
163
+ """read_audit_log returns empty list when no log exists."""
164
+ entries = read_audit_log(tmp_path / "nonexistent")
165
+ assert entries == []
@@ -0,0 +1,484 @@
1
+ """Tests for preflight system checks and auto-install."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import platform
7
+ from pathlib import Path
8
+ from unittest.mock import patch, MagicMock, mock_open
9
+
10
+ import pytest
11
+
12
+ from skcapstone.preflight import (
13
+ GIT_DOWNLOAD_DEFAULT,
14
+ GIT_DOWNLOAD_URLS,
15
+ GitPreflightResult,
16
+ PreflightResult,
17
+ ToolCheck,
18
+ ToolStatus,
19
+ auto_install_tool,
20
+ check_git,
21
+ check_gpg,
22
+ check_python,
23
+ check_syncthing,
24
+ git_install_hint_for_doctor,
25
+ run_preflight,
26
+ CheckResult,
27
+ PreflightChecker,
28
+ )
29
+
30
+
31
+ class TestCheckPython:
32
+ """Tests for check_python()."""
33
+
34
+ def test_returns_toolcheck(self) -> None:
35
+ """Returns a ToolCheck with installed status."""
36
+ result = check_python()
37
+ assert isinstance(result, ToolCheck)
38
+ assert result.name == "Python"
39
+ assert result.installed is True
40
+ assert result.required is True
41
+
42
+ def test_has_version(self) -> None:
43
+ """Version string contains major.minor."""
44
+ result = check_python()
45
+ assert "3." in result.version
46
+
47
+
48
+ class TestCheckGpg:
49
+ """Tests for check_gpg()."""
50
+
51
+ def test_returns_toolcheck(self) -> None:
52
+ """Returns a ToolCheck."""
53
+ result = check_gpg()
54
+ assert isinstance(result, ToolCheck)
55
+ assert result.name == "GnuPG"
56
+ assert result.required is True
57
+
58
+ @patch("skcapstone.preflight.shutil.which", return_value=None)
59
+ def test_missing_has_install_info(self, mock_which: MagicMock) -> None:
60
+ """When missing, provides install command and download URL."""
61
+ result = check_gpg()
62
+ assert result.status == ToolStatus.MISSING
63
+ assert result.download_url != ""
64
+
65
+
66
+ class TestCheckGit:
67
+ """Tests for check_git()."""
68
+
69
+ def test_returns_toolcheck(self) -> None:
70
+ """Returns a ToolCheck."""
71
+ result = check_git()
72
+ assert isinstance(result, ToolCheck)
73
+ assert result.name == "Git"
74
+
75
+ def test_not_required_by_default(self) -> None:
76
+ """Git is not required by default."""
77
+ result = check_git(required=False)
78
+ assert result.required is False
79
+
80
+ def test_required_when_specified(self) -> None:
81
+ """Git is required when flag is True."""
82
+ result = check_git(required=True)
83
+ assert result.required is True
84
+
85
+ @patch("skcapstone.preflight.shutil.which", return_value=None)
86
+ def test_missing_has_install_cmd(self, mock_which: MagicMock) -> None:
87
+ """When missing, provides platform-specific install command."""
88
+ result = check_git(required=True)
89
+ assert result.status == ToolStatus.MISSING
90
+ assert result.install_cmd != "" or result.download_url != ""
91
+
92
+
93
+ class TestCheckSyncthing:
94
+ """Tests for check_syncthing()."""
95
+
96
+ def test_returns_toolcheck(self) -> None:
97
+ """Returns a ToolCheck."""
98
+ result = check_syncthing()
99
+ assert isinstance(result, ToolCheck)
100
+ assert result.name == "Syncthing"
101
+
102
+ @patch("skcapstone.preflight.shutil.which", return_value=None)
103
+ def test_missing_has_download_url(self, mock_which: MagicMock) -> None:
104
+ """When missing, provides download URL."""
105
+ result = check_syncthing()
106
+ assert result.status == ToolStatus.MISSING
107
+ assert "syncthing.net" in result.download_url
108
+
109
+
110
+ class TestRunPreflight:
111
+ """Tests for run_preflight()."""
112
+
113
+ def test_returns_preflight_result(self) -> None:
114
+ """Returns a PreflightResult with all checks."""
115
+ result = run_preflight()
116
+ assert isinstance(result, PreflightResult)
117
+ assert isinstance(result.python, ToolCheck)
118
+ assert isinstance(result.gpg, ToolCheck)
119
+ assert isinstance(result.git, ToolCheck)
120
+ assert isinstance(result.syncthing, ToolCheck)
121
+
122
+ def test_all_ok_when_optional_missing(self) -> None:
123
+ """all_ok is True when only optional tools are missing."""
124
+ result = run_preflight(require_git=False, require_syncthing=False)
125
+ if result.python.installed and result.gpg.installed:
126
+ assert result.all_ok is True
127
+
128
+ def test_required_missing_list(self) -> None:
129
+ """required_missing lists only required missing tools."""
130
+ result = run_preflight()
131
+ for check in result.required_missing:
132
+ assert check.required is True
133
+ assert check.installed is False
134
+
135
+
136
+ class TestToolCheck:
137
+ """Tests for ToolCheck properties."""
138
+
139
+ def test_installed_property(self) -> None:
140
+ """installed is True when status is INSTALLED."""
141
+ check = ToolCheck(name="Test", status=ToolStatus.INSTALLED, required=True)
142
+ assert check.installed is True
143
+ assert check.ok is True
144
+
145
+ def test_missing_required(self) -> None:
146
+ """ok is False when required and missing."""
147
+ check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=True)
148
+ assert check.installed is False
149
+ assert check.ok is False
150
+
151
+ def test_missing_optional(self) -> None:
152
+ """ok is True when optional and missing."""
153
+ check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=False)
154
+ assert check.installed is False
155
+ assert check.ok is True
156
+
157
+
158
+ class TestAutoInstallTool:
159
+ """Tests for auto_install_tool()."""
160
+
161
+ def test_already_installed(self) -> None:
162
+ """Returns True if tool is already installed."""
163
+ check = ToolCheck(name="Test", status=ToolStatus.INSTALLED, required=True)
164
+ assert auto_install_tool(check) is True
165
+
166
+ def test_no_install_cmd(self) -> None:
167
+ """Returns False if no install command available."""
168
+ check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=True, install_cmd="")
169
+ assert auto_install_tool(check) is False
170
+
171
+ @patch("skcapstone.preflight.subprocess.run")
172
+ def test_runs_install_cmd(self, mock_run: MagicMock) -> None:
173
+ """Runs the install command when provided."""
174
+ mock_run.return_value = MagicMock(returncode=0)
175
+ check = ToolCheck(
176
+ name="Test", status=ToolStatus.MISSING, required=True,
177
+ install_cmd="sudo apt install -y test-tool",
178
+ )
179
+ result = auto_install_tool(check)
180
+ assert result is True
181
+ mock_run.assert_called_once()
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # Legacy compatibility
186
+ # ---------------------------------------------------------------------------
187
+
188
+ class TestGitDownloadUrls:
189
+ """Tests for legacy platform download URL mapping."""
190
+
191
+ def test_windows_url(self) -> None:
192
+ assert "Windows" in GIT_DOWNLOAD_URLS
193
+ assert "git-scm.com" in GIT_DOWNLOAD_URLS["Windows"]
194
+
195
+ def test_linux_url(self) -> None:
196
+ assert "Linux" in GIT_DOWNLOAD_URLS
197
+ assert "git-scm.com" in GIT_DOWNLOAD_URLS["Linux"]
198
+
199
+ def test_darwin_url(self) -> None:
200
+ assert "Darwin" in GIT_DOWNLOAD_URLS
201
+ assert "git-scm.com" in GIT_DOWNLOAD_URLS["Darwin"]
202
+
203
+ def test_default_url_exists(self) -> None:
204
+ assert "git-scm.com" in GIT_DOWNLOAD_DEFAULT
205
+
206
+
207
+ class TestGitPreflightResult:
208
+ """Tests for legacy GitPreflightResult."""
209
+
210
+ def test_run_returns_result(self) -> None:
211
+ r = GitPreflightResult.run()
212
+ assert isinstance(r.installed, bool)
213
+ assert isinstance(r.platform_label, str)
214
+ assert isinstance(r.message, str)
215
+ assert isinstance(r.download_url, str)
216
+
217
+ @patch("skcapstone.preflight.shutil.which", return_value=None)
218
+ def test_not_installed_has_url(self, mock_which: MagicMock) -> None:
219
+ """When git is missing, download_url is populated."""
220
+ r = GitPreflightResult.run()
221
+ assert r.installed is False
222
+ assert "git-scm.com" in r.download_url
223
+
224
+
225
+ class TestGitInstallHintForDoctor:
226
+ """Tests for legacy git_install_hint_for_doctor()."""
227
+
228
+ def test_returns_string(self) -> None:
229
+ hint = git_install_hint_for_doctor()
230
+ assert isinstance(hint, str)
231
+
232
+
233
+ # ---------------------------------------------------------------------------
234
+ # PreflightChecker tests
235
+ # ---------------------------------------------------------------------------
236
+
237
+ class TestCheckResult:
238
+ """Tests for CheckResult dataclass."""
239
+
240
+ def test_ok_status(self) -> None:
241
+ r = CheckResult("test", "ok", "all good")
242
+ assert r.ok is True
243
+ assert r.failed is False
244
+ assert r.warned is False
245
+
246
+ def test_warn_status(self) -> None:
247
+ r = CheckResult("test", "warn", "needs attention", critical=False)
248
+ assert r.ok is False
249
+ assert r.warned is True
250
+ assert r.failed is False
251
+
252
+ def test_fail_status(self) -> None:
253
+ r = CheckResult("test", "fail", "broken")
254
+ assert r.failed is True
255
+ assert r.ok is False
256
+
257
+
258
+ class TestPreflightCheckerPython:
259
+ """Tests for PreflightChecker.check_python()."""
260
+
261
+ def test_current_python_passes(self, tmp_path: Path) -> None:
262
+ """Running Python should be >= 3.11, so check passes."""
263
+ checker = PreflightChecker(home=tmp_path)
264
+ result = checker.check_python()
265
+ assert isinstance(result, CheckResult)
266
+ assert result.name == "python"
267
+ # CI may run older Python; just verify shape
268
+ assert result.status in ("ok", "fail")
269
+
270
+ def test_old_python_fails(self, tmp_path: Path) -> None:
271
+ from collections import namedtuple
272
+ VI = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])
273
+ vi = VI(3, 10, 0, "final", 0)
274
+ checker = PreflightChecker(home=tmp_path)
275
+ with patch("skcapstone.preflight.sys.version_info", vi):
276
+ result = checker.check_python()
277
+ assert result.status == "fail"
278
+ assert result.critical is True
279
+
280
+ def test_311_passes(self, tmp_path: Path) -> None:
281
+ from collections import namedtuple
282
+ VI = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])
283
+ vi = VI(3, 11, 0, "final", 0)
284
+ checker = PreflightChecker(home=tmp_path)
285
+ with patch("skcapstone.preflight.sys.version_info", vi):
286
+ result = checker.check_python()
287
+ assert result.status == "ok"
288
+
289
+
290
+ class TestPreflightCheckerPackages:
291
+ """Tests for PreflightChecker.check_packages()."""
292
+
293
+ def test_returns_check_result(self, tmp_path: Path) -> None:
294
+ checker = PreflightChecker(home=tmp_path)
295
+ result = checker.check_packages()
296
+ assert isinstance(result, CheckResult)
297
+ assert result.name == "packages"
298
+
299
+ def test_missing_package_fails(self, tmp_path: Path) -> None:
300
+ import builtins
301
+ real_import = builtins.__import__
302
+
303
+ def _mock_import(name, *args, **kwargs):
304
+ if name == "skcomm":
305
+ raise ImportError("no module named skcomm")
306
+ return real_import(name, *args, **kwargs)
307
+
308
+ checker = PreflightChecker(home=tmp_path)
309
+ with patch("builtins.__import__", side_effect=_mock_import):
310
+ result = checker.check_packages()
311
+ assert result.status == "fail"
312
+ assert "skcomm" in result.message
313
+
314
+ def test_all_present_ok(self, tmp_path: Path) -> None:
315
+ checker = PreflightChecker(home=tmp_path)
316
+ with patch("builtins.__import__", return_value=MagicMock()):
317
+ result = checker.check_packages()
318
+ assert result.status == "ok"
319
+
320
+
321
+ class TestPreflightCheckerOllama:
322
+ """Tests for PreflightChecker.check_ollama()."""
323
+
324
+ def test_ollama_unreachable_warns(self, tmp_path: Path) -> None:
325
+ """If Ollama is not running, result is warn (non-critical)."""
326
+ checker = PreflightChecker(home=tmp_path)
327
+ with patch("urllib.request.urlopen", side_effect=OSError("refused")):
328
+ result = checker.check_ollama()
329
+ assert result.status == "warn"
330
+ assert result.critical is False
331
+
332
+ def test_ollama_running_with_models(self, tmp_path: Path) -> None:
333
+ checker = PreflightChecker(home=tmp_path)
334
+ mock_resp = MagicMock()
335
+ mock_resp.read.return_value = json.dumps(
336
+ {"models": [{"name": "llama3.2"}, {"name": "mistral"}]}
337
+ ).encode()
338
+ mock_resp.__enter__ = lambda s: s
339
+ mock_resp.__exit__ = MagicMock(return_value=False)
340
+ with patch("urllib.request.urlopen", return_value=mock_resp):
341
+ result = checker.check_ollama()
342
+ assert result.status == "ok"
343
+ assert "llama3.2" in result.message
344
+
345
+ def test_ollama_running_no_models_warns(self, tmp_path: Path) -> None:
346
+ checker = PreflightChecker(home=tmp_path)
347
+ mock_resp = MagicMock()
348
+ mock_resp.read.return_value = json.dumps({"models": []}).encode()
349
+ mock_resp.__enter__ = lambda s: s
350
+ mock_resp.__exit__ = MagicMock(return_value=False)
351
+ with patch("urllib.request.urlopen", return_value=mock_resp):
352
+ result = checker.check_ollama()
353
+ assert result.status == "warn"
354
+
355
+
356
+ class TestPreflightCheckerIdentity:
357
+ """Tests for PreflightChecker.check_identity()."""
358
+
359
+ def test_identity_json_found(self, tmp_path: Path) -> None:
360
+ identity_dir = tmp_path / "identity"
361
+ identity_dir.mkdir()
362
+ (identity_dir / "identity.json").write_text(
363
+ json.dumps({"name": "opus", "fingerprint": "ABCD1234EFGH5678"}),
364
+ encoding="utf-8",
365
+ )
366
+ checker = PreflightChecker(home=tmp_path)
367
+ result = checker.check_identity()
368
+ assert result.status == "ok"
369
+ assert "opus" in result.message
370
+
371
+ def test_no_identity_fails(self, tmp_path: Path) -> None:
372
+ checker = PreflightChecker(home=tmp_path)
373
+ result = checker.check_identity()
374
+ assert result.status == "fail"
375
+ assert result.critical is True
376
+
377
+ def test_manifest_only_warns(self, tmp_path: Path) -> None:
378
+ (tmp_path / "manifest.json").write_text("{}", encoding="utf-8")
379
+ checker = PreflightChecker(home=tmp_path)
380
+ result = checker.check_identity()
381
+ assert result.status == "warn"
382
+ assert result.critical is False
383
+
384
+
385
+ class TestPreflightCheckerHomeDirs:
386
+ """Tests for PreflightChecker.check_home_dirs()."""
387
+
388
+ def test_all_dirs_present(self, tmp_path: Path) -> None:
389
+ for d in ("memory", "trust", "identity", "config"):
390
+ (tmp_path / d).mkdir()
391
+ checker = PreflightChecker(home=tmp_path)
392
+ result = checker.check_home_dirs()
393
+ assert result.status == "ok"
394
+
395
+ def test_missing_dirs_fail(self, tmp_path: Path) -> None:
396
+ checker = PreflightChecker(home=tmp_path)
397
+ result = checker.check_home_dirs()
398
+ assert result.status == "fail"
399
+ assert "memory" in result.message or "trust" in result.message
400
+
401
+
402
+ class TestPreflightCheckerConfig:
403
+ """Tests for PreflightChecker.check_config()."""
404
+
405
+ def test_no_config_warns(self, tmp_path: Path) -> None:
406
+ checker = PreflightChecker(home=tmp_path)
407
+ result = checker.check_config()
408
+ assert result.status == "warn"
409
+ assert result.critical is False
410
+
411
+ def test_valid_config_ok(self, tmp_path: Path) -> None:
412
+ cfg_dir = tmp_path / "config"
413
+ cfg_dir.mkdir()
414
+ (cfg_dir / "consciousness.yaml").write_text(
415
+ "enabled: true\n", encoding="utf-8"
416
+ )
417
+ checker = PreflightChecker(home=tmp_path)
418
+ result = checker.check_config()
419
+ assert result.status == "ok"
420
+
421
+ def test_invalid_yaml_fails(self, tmp_path: Path) -> None:
422
+ cfg_dir = tmp_path / "config"
423
+ cfg_dir.mkdir()
424
+ (cfg_dir / "consciousness.yaml").write_text(
425
+ "enabled: [\nbad yaml", encoding="utf-8"
426
+ )
427
+ checker = PreflightChecker(home=tmp_path)
428
+ result = checker.check_config()
429
+ assert result.status == "fail"
430
+
431
+
432
+ class TestPreflightCheckerDiskSpace:
433
+ """Tests for PreflightChecker.check_disk_space()."""
434
+
435
+ def test_plenty_of_space_ok(self, tmp_path: Path) -> None:
436
+ import shutil
437
+ mock_usage = shutil.disk_usage.__class__
438
+ # Return 100 GB free
439
+ with patch("shutil.disk_usage") as mock_du:
440
+ mock_du.return_value = MagicMock(free=100 * 1024 ** 3)
441
+ checker = PreflightChecker(home=tmp_path)
442
+ result = checker.check_disk_space()
443
+ assert result.status == "ok"
444
+
445
+ def test_low_disk_warns(self, tmp_path: Path) -> None:
446
+ with patch("shutil.disk_usage") as mock_du:
447
+ mock_du.return_value = MagicMock(free=2 * 1024 ** 3) # 2 GB
448
+ checker = PreflightChecker(home=tmp_path)
449
+ result = checker.check_disk_space()
450
+ assert result.status == "warn"
451
+ assert result.critical is False
452
+
453
+
454
+ class TestPreflightCheckerRunAll:
455
+ """Tests for PreflightChecker.run_all()."""
456
+
457
+ def test_returns_summary_dict(self, tmp_path: Path) -> None:
458
+ checker = PreflightChecker(home=tmp_path)
459
+ summary = checker.run_all()
460
+ assert isinstance(summary, dict)
461
+ assert "ok" in summary
462
+ assert "checks" in summary
463
+ assert "warnings" in summary
464
+ assert "failures" in summary
465
+ assert "critical_failures" in summary
466
+
467
+ def test_all_checks_present(self, tmp_path: Path) -> None:
468
+ checker = PreflightChecker(home=tmp_path)
469
+ summary = checker.run_all()
470
+ names = {c["name"] for c in summary["checks"]}
471
+ assert names == {
472
+ "python", "packages", "ollama", "identity",
473
+ "home_dirs", "config", "disk_space",
474
+ }
475
+
476
+ def test_ok_false_on_critical_failure(self, tmp_path: Path) -> None:
477
+ """If home dirs missing and no identity, ok should be False."""
478
+ checker = PreflightChecker(home=tmp_path)
479
+ # No identity, no dirs — critical failures expected
480
+ summary = checker.run_all()
481
+ assert isinstance(summary["ok"], bool)
482
+ # At minimum, critical_failures + failures are ints
483
+ assert isinstance(summary["critical_failures"], int)
484
+ assert isinstance(summary["failures"], int)