@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,529 @@
1
+ """
2
+ Tests for skcapstone.config_validator.
3
+
4
+ Covers:
5
+ - validate_consciousness_yaml: valid, type errors, syntax error, unknown key
6
+ - validate_model_profiles_yaml: valid, missing key, bad regex, enum error
7
+ - validate_identity_json: valid, missing field, JSON parse error, bad fingerprint
8
+ - validate_soul_blueprint_json: valid, missing required field, bad type
9
+ - validate_all: integration — correct files collected, missing files are warnings
10
+ - CLI smoke: skcapstone config validate --json-out
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+ import yaml
20
+ from click.testing import CliRunner
21
+
22
+ from skcapstone.config_validator import (
23
+ ConfigValidationReport,
24
+ FileValidationResult,
25
+ ValidationIssue,
26
+ validate_all,
27
+ validate_consciousness_yaml,
28
+ validate_identity_json,
29
+ validate_model_profiles_yaml,
30
+ validate_soul_blueprint_json,
31
+ )
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Fixtures
36
+ # ---------------------------------------------------------------------------
37
+
38
+
39
+ @pytest.fixture
40
+ def agent_home(tmp_path: Path) -> Path:
41
+ """Create a minimal agent home directory structure."""
42
+ home = tmp_path / ".skcapstone"
43
+ for d in ("config", "identity", "soul", "soul/installed"):
44
+ (home / d).mkdir(parents=True, exist_ok=True)
45
+ return home
46
+
47
+
48
+ def _write(path: Path, content: str) -> Path:
49
+ path.write_text(content, encoding="utf-8")
50
+ return path
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # consciousness.yaml tests
55
+ # ---------------------------------------------------------------------------
56
+
57
+
58
+ class TestConsciousnessYaml:
59
+ """Tests for validate_consciousness_yaml()."""
60
+
61
+ def test_valid_config_passes(self, tmp_path: Path) -> None:
62
+ """Happy path: well-formed consciousness.yaml produces no errors."""
63
+ path = tmp_path / "consciousness.yaml"
64
+ _write(path, """
65
+ enabled: true
66
+ use_inotify: true
67
+ inotify_debounce_ms: 200
68
+ response_timeout: 120
69
+ max_context_tokens: 8000
70
+ max_history_messages: 10
71
+ auto_memory: true
72
+ auto_ack: true
73
+ privacy_default: false
74
+ max_concurrent_requests: 3
75
+ fallback_chain:
76
+ - ollama
77
+ - anthropic
78
+ - passthrough
79
+ desktop_notifications: true
80
+ """)
81
+ result = validate_consciousness_yaml(path)
82
+ assert result.is_valid, f"Unexpected errors: {result.errors}"
83
+ assert result.errors == []
84
+
85
+ def test_missing_file_is_warning_not_error(self, tmp_path: Path) -> None:
86
+ """Missing file produces a warning, not an error."""
87
+ path = tmp_path / "consciousness.yaml"
88
+ result = validate_consciousness_yaml(path)
89
+ assert not result.found
90
+ assert result.is_valid # missing → valid (uses defaults)
91
+ assert len(result.warnings) == 1
92
+ assert result.errors == []
93
+
94
+ def test_bool_field_wrong_type_is_error(self, tmp_path: Path) -> None:
95
+ """Passing a string for a bool field reports an error with the field name."""
96
+ path = tmp_path / "consciousness.yaml"
97
+ _write(path, "enabled: yes_please\n")
98
+ result = validate_consciousness_yaml(path)
99
+ err_fields = [e.field for e in result.errors]
100
+ assert "enabled" in err_fields
101
+
102
+ def test_int_field_wrong_type_is_error(self, tmp_path: Path) -> None:
103
+ """Passing a string for an int field reports an error."""
104
+ path = tmp_path / "consciousness.yaml"
105
+ _write(path, "response_timeout: fast\n")
106
+ result = validate_consciousness_yaml(path)
107
+ err_fields = [e.field for e in result.errors]
108
+ assert "response_timeout" in err_fields
109
+
110
+ def test_int_field_zero_is_error(self, tmp_path: Path) -> None:
111
+ """response_timeout: 0 must report an error (must be > 0)."""
112
+ path = tmp_path / "consciousness.yaml"
113
+ _write(path, "response_timeout: 0\n")
114
+ result = validate_consciousness_yaml(path)
115
+ assert any(e.field == "response_timeout" for e in result.errors)
116
+
117
+ def test_yaml_syntax_error_reports_line(self, tmp_path: Path) -> None:
118
+ """A YAML syntax error is an error and the line number is set."""
119
+ path = tmp_path / "consciousness.yaml"
120
+ _write(path, "enabled: true\nbroken: [\n")
121
+ result = validate_consciousness_yaml(path)
122
+ assert len(result.errors) == 1
123
+ assert result.errors[0].line is not None
124
+ assert result.errors[0].line >= 1
125
+
126
+ def test_unknown_key_is_warning(self, tmp_path: Path) -> None:
127
+ """An unknown top-level key generates a warning, not an error."""
128
+ path = tmp_path / "consciousness.yaml"
129
+ _write(path, "enabled: true\nmy_custom_setting: 42\n")
130
+ result = validate_consciousness_yaml(path)
131
+ assert result.is_valid # no errors
132
+ warn_fields = [w.field for w in result.warnings]
133
+ assert "my_custom_setting" in warn_fields
134
+
135
+ def test_fallback_chain_unknown_backend_is_warning(self, tmp_path: Path) -> None:
136
+ """An unknown backend in fallback_chain is a warning, not an error."""
137
+ path = tmp_path / "consciousness.yaml"
138
+ _write(path, "fallback_chain:\n - my_custom_backend\n")
139
+ result = validate_consciousness_yaml(path)
140
+ assert result.is_valid
141
+ assert any("my_custom_backend" in w.message for w in result.warnings)
142
+
143
+ def test_fallback_chain_not_list_is_error(self, tmp_path: Path) -> None:
144
+ """A non-list fallback_chain reports an error."""
145
+ path = tmp_path / "consciousness.yaml"
146
+ _write(path, "fallback_chain: ollama\n")
147
+ result = validate_consciousness_yaml(path)
148
+ assert any(e.field == "fallback_chain" for e in result.errors)
149
+
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # model_profiles.yaml tests
153
+ # ---------------------------------------------------------------------------
154
+
155
+
156
+ class TestModelProfilesYaml:
157
+ """Tests for validate_model_profiles_yaml()."""
158
+
159
+ def test_valid_profiles_pass(self, tmp_path: Path) -> None:
160
+ """Happy path: valid profiles list produces no errors."""
161
+ path = tmp_path / "model_profiles.yaml"
162
+ _write(path, """
163
+ profiles:
164
+ - model_pattern: "claude-.*"
165
+ family: claude
166
+ system_prompt_mode: separate_param
167
+ structure_format: xml
168
+ thinking_enabled: true
169
+ thinking_mode: budget
170
+ tool_format: anthropic
171
+ """)
172
+ result = validate_model_profiles_yaml(path)
173
+ assert result.is_valid, f"Unexpected errors: {result.errors}"
174
+
175
+ def test_missing_profiles_key_is_error(self, tmp_path: Path) -> None:
176
+ """YAML without a top-level 'profiles' key is an error."""
177
+ path = tmp_path / "model_profiles.yaml"
178
+ _write(path, "family: openai\n")
179
+ result = validate_model_profiles_yaml(path)
180
+ err_fields = [e.field for e in result.errors]
181
+ assert "profiles" in err_fields
182
+
183
+ def test_missing_required_field_in_profile(self, tmp_path: Path) -> None:
184
+ """A profile missing 'model_pattern' or 'family' reports an error."""
185
+ path = tmp_path / "model_profiles.yaml"
186
+ _write(path, """
187
+ profiles:
188
+ - family: openai
189
+ """)
190
+ result = validate_model_profiles_yaml(path)
191
+ assert any("model_pattern" in (e.field or "") for e in result.errors)
192
+
193
+ def test_invalid_regex_in_model_pattern(self, tmp_path: Path) -> None:
194
+ """An invalid regex in model_pattern reports an error."""
195
+ path = tmp_path / "model_profiles.yaml"
196
+ _write(path, """
197
+ profiles:
198
+ - model_pattern: "[invalid("
199
+ family: broken
200
+ """)
201
+ result = validate_model_profiles_yaml(path)
202
+ assert any("model_pattern" in (e.field or "") for e in result.errors)
203
+ assert any("regex" in e.message.lower() for e in result.errors)
204
+
205
+ def test_invalid_enum_value_is_error(self, tmp_path: Path) -> None:
206
+ """An out-of-range enum value (e.g. structure_format) is an error."""
207
+ path = tmp_path / "model_profiles.yaml"
208
+ _write(path, """
209
+ profiles:
210
+ - model_pattern: ".*"
211
+ family: generic
212
+ structure_format: html
213
+ """)
214
+ result = validate_model_profiles_yaml(path)
215
+ assert any("structure_format" in (e.field or "") for e in result.errors)
216
+
217
+ def test_line_numbers_reported_for_profile_error(self, tmp_path: Path) -> None:
218
+ """Error on a known profile field includes a non-None line number."""
219
+ path = tmp_path / "model_profiles.yaml"
220
+ content = (
221
+ "profiles:\n"
222
+ " - model_pattern: \"[bad(\"\n"
223
+ " family: x\n"
224
+ )
225
+ _write(path, content)
226
+ result = validate_model_profiles_yaml(path)
227
+ regex_errors = [e for e in result.errors if "model_pattern" in (e.field or "")]
228
+ assert regex_errors
229
+ assert regex_errors[0].line is not None
230
+
231
+ def test_missing_file_is_warning(self, tmp_path: Path) -> None:
232
+ """Missing model_profiles.yaml is a warning (bundled defaults used)."""
233
+ path = tmp_path / "model_profiles.yaml"
234
+ result = validate_model_profiles_yaml(path)
235
+ assert not result.found
236
+ assert result.is_valid
237
+ assert len(result.warnings) == 1
238
+
239
+ def test_yaml_syntax_error_is_error(self, tmp_path: Path) -> None:
240
+ """A YAML syntax error in model_profiles.yaml is reported as an error."""
241
+ path = tmp_path / "model_profiles.yaml"
242
+ _write(path, "profiles:\n - broken: [\n")
243
+ result = validate_model_profiles_yaml(path)
244
+ assert len(result.errors) == 1
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # identity.json tests
249
+ # ---------------------------------------------------------------------------
250
+
251
+
252
+ class TestIdentityJson:
253
+ """Tests for validate_identity_json()."""
254
+
255
+ def test_valid_identity_passes(self, tmp_path: Path) -> None:
256
+ """Happy path: identity.json with all required fields passes."""
257
+ path = tmp_path / "identity.json"
258
+ _write(path, json.dumps({
259
+ "name": "Opus",
260
+ "fingerprint": "A" * 40,
261
+ "email": "opus@skworld.io",
262
+ "capauth_managed": True,
263
+ }))
264
+ result = validate_identity_json(path)
265
+ assert result.is_valid, f"Unexpected errors: {result.errors}"
266
+
267
+ def test_missing_name_is_error(self, tmp_path: Path) -> None:
268
+ """identity.json without 'name' is an error."""
269
+ path = tmp_path / "identity.json"
270
+ _write(path, json.dumps({"fingerprint": "A" * 40}))
271
+ result = validate_identity_json(path)
272
+ assert any(e.field == "name" for e in result.errors)
273
+
274
+ def test_missing_fingerprint_is_error(self, tmp_path: Path) -> None:
275
+ """identity.json without 'fingerprint' is an error."""
276
+ path = tmp_path / "identity.json"
277
+ _write(path, json.dumps({"name": "Opus"}))
278
+ result = validate_identity_json(path)
279
+ assert any(e.field == "fingerprint" for e in result.errors)
280
+
281
+ def test_short_fingerprint_is_warning(self, tmp_path: Path) -> None:
282
+ """A fingerprint that doesn't match 40-hex-char format is a warning."""
283
+ path = tmp_path / "identity.json"
284
+ _write(path, json.dumps({"name": "Opus", "fingerprint": "DEADBEEF"}))
285
+ result = validate_identity_json(path)
286
+ assert result.is_valid # warning only
287
+ assert any(e.field == "fingerprint" for e in result.warnings)
288
+
289
+ def test_json_parse_error_reports_line(self, tmp_path: Path) -> None:
290
+ """A JSON syntax error is an error and the line number is set."""
291
+ path = tmp_path / "identity.json"
292
+ _write(path, '{"name": "Opus"\n broken\n}')
293
+ result = validate_identity_json(path)
294
+ assert len(result.errors) == 1
295
+ assert result.errors[0].line is not None
296
+ assert result.errors[0].line >= 1
297
+
298
+ def test_missing_file_is_warning(self, tmp_path: Path) -> None:
299
+ """Missing identity.json is a warning (not yet initialised)."""
300
+ path = tmp_path / "identity.json"
301
+ result = validate_identity_json(path)
302
+ assert not result.found
303
+ assert result.is_valid
304
+ assert len(result.warnings) == 1
305
+
306
+ def test_empty_name_is_error(self, tmp_path: Path) -> None:
307
+ """An empty string for 'name' is an error."""
308
+ path = tmp_path / "identity.json"
309
+ _write(path, json.dumps({"name": " ", "fingerprint": "A" * 40}))
310
+ result = validate_identity_json(path)
311
+ assert any(e.field == "name" for e in result.errors)
312
+
313
+
314
+ # ---------------------------------------------------------------------------
315
+ # Soul blueprint JSON tests
316
+ # ---------------------------------------------------------------------------
317
+
318
+
319
+ class TestSoulBlueprintJson:
320
+ """Tests for validate_soul_blueprint_json()."""
321
+
322
+ def test_valid_blueprint_passes(self, tmp_path: Path) -> None:
323
+ """Happy path: blueprint with all required fields passes."""
324
+ path = tmp_path / "lumina.json"
325
+ _write(path, json.dumps({
326
+ "name": "lumina",
327
+ "display_name": "Lumina",
328
+ "category": "sovereign",
329
+ "vibe": "warmth and clarity",
330
+ "core_traits": ["empathy", "precision"],
331
+ "emotional_topology": {"warmth": 0.9, "precision": 0.8},
332
+ }))
333
+ result = validate_soul_blueprint_json(path)
334
+ assert result.is_valid, f"Unexpected errors: {result.errors}"
335
+
336
+ def test_missing_name_is_error(self, tmp_path: Path) -> None:
337
+ """Blueprint without 'name' is an error."""
338
+ path = tmp_path / "missing_name.json"
339
+ _write(path, json.dumps({"display_name": "Lumina"}))
340
+ result = validate_soul_blueprint_json(path)
341
+ assert any(e.field == "name" for e in result.errors)
342
+
343
+ def test_missing_display_name_is_error(self, tmp_path: Path) -> None:
344
+ """Blueprint without 'display_name' is an error."""
345
+ path = tmp_path / "missing_dn.json"
346
+ _write(path, json.dumps({"name": "lumina"}))
347
+ result = validate_soul_blueprint_json(path)
348
+ assert any(e.field == "display_name" for e in result.errors)
349
+
350
+ def test_core_traits_wrong_type_is_error(self, tmp_path: Path) -> None:
351
+ """core_traits must be a list."""
352
+ path = tmp_path / "bad_traits.json"
353
+ _write(path, json.dumps({
354
+ "name": "lumina", "display_name": "Lumina",
355
+ "core_traits": "empathy, precision",
356
+ }))
357
+ result = validate_soul_blueprint_json(path)
358
+ assert any(e.field == "core_traits" for e in result.errors)
359
+
360
+ def test_emotional_topology_non_numeric_is_error(self, tmp_path: Path) -> None:
361
+ """emotional_topology values must be numeric."""
362
+ path = tmp_path / "bad_topo.json"
363
+ _write(path, json.dumps({
364
+ "name": "lumina", "display_name": "Lumina",
365
+ "emotional_topology": {"warmth": "high"},
366
+ }))
367
+ result = validate_soul_blueprint_json(path)
368
+ assert any("emotional_topology" in (e.field or "") for e in result.errors)
369
+
370
+ def test_json_parse_error_is_error(self, tmp_path: Path) -> None:
371
+ """Invalid JSON in a soul file is reported as an error with a line number."""
372
+ path = tmp_path / "broken.json"
373
+ _write(path, '{"name": "x"\n oops\n}')
374
+ result = validate_soul_blueprint_json(path)
375
+ assert len(result.errors) == 1
376
+ assert result.errors[0].line is not None
377
+
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # validate_all integration tests
381
+ # ---------------------------------------------------------------------------
382
+
383
+
384
+ class TestValidateAll:
385
+ """Integration tests for validate_all()."""
386
+
387
+ def test_empty_home_produces_warnings_not_errors(self, agent_home: Path) -> None:
388
+ """validate_all on a home with no config files produces warnings only."""
389
+ report = validate_all(agent_home)
390
+ assert report.total_errors == 0
391
+ # At least consciousness.yaml + model_profiles.yaml + identity.json are checked
392
+ assert len(report.results) >= 3
393
+ # All missing files produce warnings
394
+ assert report.total_warnings >= 3
395
+
396
+ def test_valid_configs_produce_clean_report(self, agent_home: Path) -> None:
397
+ """A home with all valid configs reports no errors or warnings (except soul)."""
398
+ # consciousness.yaml
399
+ _write(agent_home / "config" / "consciousness.yaml",
400
+ "enabled: true\nfallback_chain:\n - ollama\n")
401
+ # identity.json
402
+ _write(agent_home / "identity" / "identity.json", json.dumps({
403
+ "name": "Opus",
404
+ "fingerprint": "A" * 40,
405
+ }))
406
+
407
+ report = validate_all(agent_home)
408
+ assert report.total_errors == 0
409
+
410
+ def test_identity_error_is_counted(self, agent_home: Path) -> None:
411
+ """An error in identity.json is reflected in the report totals."""
412
+ _write(agent_home / "identity" / "identity.json",
413
+ json.dumps({"fingerprint": "A" * 40})) # missing 'name'
414
+ report = validate_all(agent_home)
415
+ assert report.total_errors >= 1
416
+
417
+ def test_soul_installed_blueprints_are_validated(self, agent_home: Path) -> None:
418
+ """Installed soul blueprints are included in validate_all."""
419
+ soul_dir = agent_home / "soul" / "installed"
420
+ _write(soul_dir / "lumina.json", json.dumps({
421
+ "name": "lumina", "display_name": "Lumina",
422
+ }))
423
+ _write(soul_dir / "broken.json", '{"name": "x"}') # missing display_name
424
+
425
+ report = validate_all(agent_home)
426
+ config_names = [r.config_name for r in report.results]
427
+ assert any("lumina.json" in n for n in config_names)
428
+ assert any("broken.json" in n for n in config_names)
429
+ # broken.json is missing display_name → error
430
+ broken = next(r for r in report.results if "broken.json" in r.config_name)
431
+ assert not broken.is_valid
432
+
433
+ def test_report_is_valid_iff_no_errors(self, agent_home: Path) -> None:
434
+ """ConfigValidationReport.is_valid is False when any result has errors."""
435
+ _write(agent_home / "identity" / "identity.json",
436
+ json.dumps({"fingerprint": "tooshort"})) # missing 'name'
437
+ report = validate_all(agent_home)
438
+ assert not report.is_valid
439
+
440
+ def test_validate_all_soul_dir_absent(self, tmp_path: Path) -> None:
441
+ """When soul/ directory doesn't exist, validate_all still runs."""
442
+ home = tmp_path / ".skcapstone"
443
+ (home / "config").mkdir(parents=True)
444
+ (home / "identity").mkdir()
445
+ # soul/ intentionally absent
446
+ report = validate_all(home)
447
+ config_names = [r.config_name for r in report.results]
448
+ assert not any("soul/" in n for n in config_names)
449
+
450
+
451
+ # ---------------------------------------------------------------------------
452
+ # CLI smoke tests
453
+ # ---------------------------------------------------------------------------
454
+
455
+
456
+ class TestConfigValidateCli:
457
+ """CLI smoke tests for `skcapstone config validate`."""
458
+
459
+ def test_cli_json_output_structure(self, agent_home: Path) -> None:
460
+ """--json-out emits a JSON object with 'valid', 'files', etc."""
461
+ from skcapstone.cli import main
462
+
463
+ runner = CliRunner()
464
+ result = runner.invoke(
465
+ main,
466
+ ["--agent", "", "config", "validate",
467
+ "--home", str(agent_home), "--json-out"],
468
+ )
469
+ assert result.exit_code in (0, 1), result.output
470
+ data = json.loads(result.output)
471
+ assert "valid" in data
472
+ assert "total_errors" in data
473
+ assert "total_warnings" in data
474
+ assert "files" in data
475
+ assert isinstance(data["files"], list)
476
+
477
+ def test_cli_exits_zero_when_valid(self, agent_home: Path) -> None:
478
+ """CLI exits 0 when all configs are valid (missing files are warnings)."""
479
+ from skcapstone.cli import main
480
+
481
+ # Write a valid identity so the only issues are warnings (missing files)
482
+ _write(agent_home / "identity" / "identity.json", json.dumps({
483
+ "name": "TestAgent",
484
+ "fingerprint": "A" * 40,
485
+ }))
486
+ # A fully valid consciousness config
487
+ _write(agent_home / "config" / "consciousness.yaml",
488
+ "enabled: true\n")
489
+
490
+ runner = CliRunner()
491
+ result = runner.invoke(
492
+ main,
493
+ ["config", "validate", "--home", str(agent_home)],
494
+ )
495
+ # Warnings are present (missing model_profiles, soul) but exit 0
496
+ assert result.exit_code == 0
497
+
498
+ def test_cli_exits_one_on_error(self, agent_home: Path) -> None:
499
+ """CLI exits 1 when identity.json has a schema error."""
500
+ from skcapstone.cli import main
501
+
502
+ _write(agent_home / "identity" / "identity.json",
503
+ json.dumps({"fingerprint": "BADFINGERPRINT"})) # missing 'name'
504
+
505
+ runner = CliRunner()
506
+ result = runner.invoke(
507
+ main,
508
+ ["config", "validate", "--home", str(agent_home)],
509
+ )
510
+ assert result.exit_code == 1
511
+
512
+ def test_cli_strict_exits_one_on_warnings(self, agent_home: Path) -> None:
513
+ """--strict causes exit 1 when only warnings are present."""
514
+ from skcapstone.cli import main
515
+
516
+ # Valid identity but missing other configs → warnings
517
+ _write(agent_home / "identity" / "identity.json", json.dumps({
518
+ "name": "TestAgent",
519
+ "fingerprint": "A" * 40,
520
+ }))
521
+
522
+ runner = CliRunner()
523
+ result = runner.invoke(
524
+ main,
525
+ ["config", "validate", "--home", str(agent_home), "--strict"],
526
+ )
527
+ # model_profiles.yaml and consciousness.yaml are missing → warnings
528
+ # --strict turns those into a failure
529
+ assert result.exit_code == 1