@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,674 @@
1
+ """
2
+ Config file validator for SKCapstone.
3
+
4
+ Validates consciousness.yaml, model_profiles.yaml, identity.json,
5
+ and soul blueprints. Reports errors with file paths and line numbers.
6
+
7
+ Usage:
8
+ from skcapstone.config_validator import validate_all
9
+ report = validate_all(home_path)
10
+ if not report.is_valid:
11
+ for r in report.results:
12
+ for issue in r.errors:
13
+ print(issue)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import re
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+ from typing import Any, Optional
23
+
24
+ import yaml
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Result models
29
+ # ---------------------------------------------------------------------------
30
+
31
+
32
+ @dataclass
33
+ class ValidationIssue:
34
+ """A single validation problem in a config file."""
35
+
36
+ severity: str # "error" | "warning"
37
+ message: str
38
+ field: Optional[str] = None
39
+ line: Optional[int] = None
40
+
41
+ def __str__(self) -> str:
42
+ loc = f"line {self.line}" if self.line else ""
43
+ field_str = f" [{self.field}]" if self.field else ""
44
+ suffix = f" ({loc})" if loc else ""
45
+ return f"{self.severity.upper()}{field_str}{suffix}: {self.message}"
46
+
47
+
48
+ @dataclass
49
+ class FileValidationResult:
50
+ """Validation outcome for a single config file."""
51
+
52
+ config_name: str
53
+ file_path: Path
54
+ found: bool = True
55
+ issues: list[ValidationIssue] = field(default_factory=list)
56
+
57
+ @property
58
+ def errors(self) -> list[ValidationIssue]:
59
+ return [i for i in self.issues if i.severity == "error"]
60
+
61
+ @property
62
+ def warnings(self) -> list[ValidationIssue]:
63
+ return [i for i in self.issues if i.severity == "warning"]
64
+
65
+ @property
66
+ def is_valid(self) -> bool:
67
+ """True when there are no errors.
68
+
69
+ A missing file is considered valid — it simply falls back to
70
+ built-in defaults and produces a warning, not an error.
71
+ """
72
+ return len(self.errors) == 0
73
+
74
+
75
+ @dataclass
76
+ class ConfigValidationReport:
77
+ """Aggregated validation report across all config files."""
78
+
79
+ results: list[FileValidationResult] = field(default_factory=list)
80
+
81
+ @property
82
+ def is_valid(self) -> bool:
83
+ return all(r.is_valid for r in self.results)
84
+
85
+ @property
86
+ def total_errors(self) -> int:
87
+ return sum(len(r.errors) for r in self.results)
88
+
89
+ @property
90
+ def total_warnings(self) -> int:
91
+ return sum(len(r.warnings) for r in self.results)
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # YAML AST helpers for line-number extraction
96
+ # ---------------------------------------------------------------------------
97
+
98
+
99
+ def _yaml_key_line(text: str, key: str) -> Optional[int]:
100
+ """Return the 1-based line number of a top-level YAML key.
101
+
102
+ Args:
103
+ text: Raw YAML text.
104
+ key: Key name to locate.
105
+
106
+ Returns:
107
+ Line number (1-based) or None if not found / parse fails.
108
+ """
109
+ try:
110
+ node = yaml.compose(text)
111
+ if not isinstance(node, yaml.MappingNode):
112
+ return None
113
+ for key_node, _ in node.value:
114
+ if isinstance(key_node, yaml.ScalarNode) and key_node.value == key:
115
+ return key_node.start_mark.line + 1
116
+ except Exception:
117
+ pass
118
+ return None
119
+
120
+
121
+ def _yaml_seq_item_line(text: str, seq_key: str, idx: int, subkey: str) -> Optional[int]:
122
+ """Return the line of *subkey* inside a sequence-item mapping.
123
+
124
+ Navigates ``{seq_key: [{subkey: …}, …]}`` and returns the line of
125
+ *subkey* in the *idx*-th item. Falls back to the item's opening line
126
+ if *subkey* is absent.
127
+
128
+ Args:
129
+ text: Raw YAML text.
130
+ seq_key: Top-level sequence key (e.g. ``"profiles"``).
131
+ idx: Zero-based index into the sequence.
132
+ subkey: Key to locate inside the item mapping.
133
+
134
+ Returns:
135
+ Line number (1-based) or None.
136
+ """
137
+ try:
138
+ root = yaml.compose(text)
139
+ if not isinstance(root, yaml.MappingNode):
140
+ return None
141
+ for k, v in root.value:
142
+ if k.value == seq_key and isinstance(v, yaml.SequenceNode):
143
+ if idx < len(v.value):
144
+ item = v.value[idx]
145
+ if isinstance(item, yaml.MappingNode):
146
+ for kk, _ in item.value:
147
+ if kk.value == subkey:
148
+ return kk.start_mark.line + 1
149
+ return item.start_mark.line + 1
150
+ except Exception:
151
+ pass
152
+ return None
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # Allowed value sets
157
+ # ---------------------------------------------------------------------------
158
+
159
+ _VALID_FALLBACK_BACKENDS = frozenset([
160
+ "ollama", "grok", "kimi", "nvidia", "anthropic",
161
+ "openai", "passthrough", "groq", "mistral", "perplexity",
162
+ ])
163
+ _VALID_SYSTEM_PROMPT_MODES = frozenset(["standard", "separate_param", "omit"])
164
+ _VALID_STRUCTURE_FORMATS = frozenset(["xml", "markdown", "plain"])
165
+ _VALID_THINKING_MODES = frozenset(["none", "budget", "toggle", "auto"])
166
+ _VALID_TOOL_FORMATS = frozenset(["openai", "anthropic", "mistral"])
167
+
168
+ _CONSCIOUSNESS_KNOWN_KEYS = frozenset([
169
+ "enabled", "use_inotify", "inotify_debounce_ms", "response_timeout",
170
+ "max_context_tokens", "max_history_messages", "auto_memory", "auto_ack",
171
+ "privacy_default", "max_concurrent_requests", "fallback_chain",
172
+ "desktop_notifications",
173
+ ])
174
+
175
+ _IDENTITY_FINGERPRINT_RE = re.compile(r"^[0-9A-Fa-f]{40}$")
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # consciousness.yaml validator
180
+ # ---------------------------------------------------------------------------
181
+
182
+
183
+ def validate_consciousness_yaml(path: Path) -> FileValidationResult:
184
+ """Validate consciousness.yaml against the ConsciousnessConfig schema.
185
+
186
+ Checks types, value ranges, fallback chain entries, and unknown keys.
187
+
188
+ Args:
189
+ path: Path to the consciousness.yaml file.
190
+
191
+ Returns:
192
+ FileValidationResult with any errors/warnings found.
193
+ """
194
+ result = FileValidationResult(config_name="consciousness.yaml", file_path=path)
195
+
196
+ if not path.exists():
197
+ result.found = False
198
+ result.issues.append(ValidationIssue(
199
+ severity="warning",
200
+ message="File not found — defaults will be used",
201
+ ))
202
+ return result
203
+
204
+ text = path.read_text(encoding="utf-8")
205
+
206
+ try:
207
+ raw: Any = yaml.safe_load(text)
208
+ except yaml.YAMLError as exc:
209
+ line = None
210
+ if hasattr(exc, "problem_mark") and exc.problem_mark:
211
+ line = exc.problem_mark.line + 1
212
+ result.issues.append(ValidationIssue(
213
+ severity="error", message=f"YAML parse error: {exc}", line=line,
214
+ ))
215
+ return result
216
+
217
+ if not raw:
218
+ result.issues.append(ValidationIssue(
219
+ severity="warning", message="Empty file — defaults will be used",
220
+ ))
221
+ return result
222
+
223
+ if not isinstance(raw, dict):
224
+ result.issues.append(ValidationIssue(
225
+ severity="error",
226
+ message="Top-level value must be a YAML mapping",
227
+ line=1,
228
+ ))
229
+ return result
230
+
231
+ # ── Bool fields ──────────────────────────────────────────────────────
232
+ for bf in ("enabled", "use_inotify", "auto_memory", "auto_ack",
233
+ "privacy_default", "desktop_notifications"):
234
+ if bf in raw and not isinstance(raw[bf], bool):
235
+ result.issues.append(ValidationIssue(
236
+ severity="error", field=bf,
237
+ line=_yaml_key_line(text, bf),
238
+ message=f"Expected bool, got {type(raw[bf]).__name__}",
239
+ ))
240
+
241
+ # ── Positive-int fields ──────────────────────────────────────────────
242
+ _pos_int: dict[str, int] = {
243
+ "inotify_debounce_ms": 0,
244
+ "response_timeout": 1,
245
+ "max_context_tokens": 1,
246
+ "max_history_messages": 0,
247
+ "max_concurrent_requests": 1,
248
+ }
249
+ for fi, min_val in _pos_int.items():
250
+ if fi not in raw:
251
+ continue
252
+ v = raw[fi]
253
+ line = _yaml_key_line(text, fi)
254
+ if not isinstance(v, int):
255
+ result.issues.append(ValidationIssue(
256
+ severity="error", field=fi, line=line,
257
+ message=f"Expected int, got {type(v).__name__}",
258
+ ))
259
+ elif v <= min_val:
260
+ result.issues.append(ValidationIssue(
261
+ severity="error", field=fi, line=line,
262
+ message=f"Must be > {min_val}, got {v}",
263
+ ))
264
+
265
+ # ── fallback_chain ───────────────────────────────────────────────────
266
+ if "fallback_chain" in raw:
267
+ chain = raw["fallback_chain"]
268
+ line = _yaml_key_line(text, "fallback_chain")
269
+ if not isinstance(chain, list):
270
+ result.issues.append(ValidationIssue(
271
+ severity="error", field="fallback_chain", line=line,
272
+ message=f"Expected list, got {type(chain).__name__}",
273
+ ))
274
+ else:
275
+ for i, item in enumerate(chain):
276
+ if not isinstance(item, str):
277
+ result.issues.append(ValidationIssue(
278
+ severity="error",
279
+ field=f"fallback_chain[{i}]",
280
+ message=f"Expected string, got {type(item).__name__}",
281
+ ))
282
+ elif item not in _VALID_FALLBACK_BACKENDS:
283
+ result.issues.append(ValidationIssue(
284
+ severity="warning",
285
+ field=f"fallback_chain[{i}]",
286
+ message=f"Unknown backend '{item}' (may be a custom provider)",
287
+ ))
288
+
289
+ # ── Unknown keys → warnings ──────────────────────────────────────────
290
+ for key in raw:
291
+ if key not in _CONSCIOUSNESS_KNOWN_KEYS:
292
+ result.issues.append(ValidationIssue(
293
+ severity="warning", field=key,
294
+ line=_yaml_key_line(text, key),
295
+ message=f"Unknown config key '{key}'",
296
+ ))
297
+
298
+ return result
299
+
300
+
301
+ # ---------------------------------------------------------------------------
302
+ # model_profiles.yaml validator
303
+ # ---------------------------------------------------------------------------
304
+
305
+
306
+ def validate_model_profiles_yaml(path: Path) -> FileValidationResult:
307
+ """Validate model_profiles.yaml profile list and field types.
308
+
309
+ Args:
310
+ path: Path to model_profiles.yaml.
311
+
312
+ Returns:
313
+ FileValidationResult with any errors/warnings found.
314
+ """
315
+ result = FileValidationResult(config_name="model_profiles.yaml", file_path=path)
316
+
317
+ if not path.exists():
318
+ result.found = False
319
+ result.issues.append(ValidationIssue(
320
+ severity="warning",
321
+ message="File not found — bundled defaults will be used",
322
+ ))
323
+ return result
324
+
325
+ text = path.read_text(encoding="utf-8")
326
+
327
+ try:
328
+ raw: Any = yaml.safe_load(text)
329
+ except yaml.YAMLError as exc:
330
+ line = None
331
+ if hasattr(exc, "problem_mark") and exc.problem_mark:
332
+ line = exc.problem_mark.line + 1
333
+ result.issues.append(ValidationIssue(
334
+ severity="error", message=f"YAML parse error: {exc}", line=line,
335
+ ))
336
+ return result
337
+
338
+ if not isinstance(raw, dict):
339
+ result.issues.append(ValidationIssue(
340
+ severity="error",
341
+ message="Top-level value must be a YAML mapping",
342
+ line=1,
343
+ ))
344
+ return result
345
+
346
+ if "profiles" not in raw:
347
+ result.issues.append(ValidationIssue(
348
+ severity="error", field="profiles",
349
+ message="Required top-level key 'profiles' is missing",
350
+ line=1,
351
+ ))
352
+ return result
353
+
354
+ profiles = raw["profiles"]
355
+ profiles_line = _yaml_key_line(text, "profiles")
356
+
357
+ if not isinstance(profiles, list):
358
+ result.issues.append(ValidationIssue(
359
+ severity="error", field="profiles", line=profiles_line,
360
+ message=f"'profiles' must be a list, got {type(profiles).__name__}",
361
+ ))
362
+ return result
363
+
364
+ if len(profiles) == 0:
365
+ result.issues.append(ValidationIssue(
366
+ severity="warning", field="profiles", line=profiles_line,
367
+ message="'profiles' list is empty",
368
+ ))
369
+ return result
370
+
371
+ _enum_checks: list[tuple[str, frozenset[str]]] = [
372
+ ("system_prompt_mode", _VALID_SYSTEM_PROMPT_MODES),
373
+ ("structure_format", _VALID_STRUCTURE_FORMATS),
374
+ ("thinking_mode", _VALID_THINKING_MODES),
375
+ ("tool_format", _VALID_TOOL_FORMATS),
376
+ ]
377
+
378
+ for i, profile in enumerate(profiles):
379
+ prefix = f"profiles[{i}]"
380
+ item_line = _yaml_seq_item_line(text, "profiles", i, "model_pattern")
381
+
382
+ if not isinstance(profile, dict):
383
+ result.issues.append(ValidationIssue(
384
+ severity="error", field=prefix, line=item_line,
385
+ message=f"Each profile must be a mapping, got {type(profile).__name__}",
386
+ ))
387
+ continue
388
+
389
+ # Required: model_pattern + family
390
+ for req in ("model_pattern", "family"):
391
+ if req not in profile:
392
+ result.issues.append(ValidationIssue(
393
+ severity="error",
394
+ field=f"{prefix}.{req}",
395
+ line=item_line,
396
+ message=f"Required field '{req}' is missing",
397
+ ))
398
+ elif not isinstance(profile[req], str):
399
+ result.issues.append(ValidationIssue(
400
+ severity="error",
401
+ field=f"{prefix}.{req}",
402
+ line=_yaml_seq_item_line(text, "profiles", i, req),
403
+ message=f"Expected str, got {type(profile[req]).__name__}",
404
+ ))
405
+
406
+ # model_pattern must be a valid regex
407
+ pat = profile.get("model_pattern")
408
+ if isinstance(pat, str):
409
+ try:
410
+ re.compile(pat)
411
+ except re.error as exc:
412
+ result.issues.append(ValidationIssue(
413
+ severity="error",
414
+ field=f"{prefix}.model_pattern",
415
+ line=_yaml_seq_item_line(text, "profiles", i, "model_pattern"),
416
+ message=f"Invalid regex: {exc}",
417
+ ))
418
+
419
+ # Enum fields
420
+ for fname, valid_set in _enum_checks:
421
+ if fname in profile:
422
+ val = profile[fname]
423
+ if isinstance(val, str) and val not in valid_set:
424
+ result.issues.append(ValidationIssue(
425
+ severity="error",
426
+ field=f"{prefix}.{fname}",
427
+ line=_yaml_seq_item_line(text, "profiles", i, fname),
428
+ message=(
429
+ f"Invalid value '{val}', "
430
+ f"expected one of: {sorted(valid_set)}"
431
+ ),
432
+ ))
433
+
434
+ # Bool fields
435
+ for bf in ("thinking_enabled", "no_few_shot",
436
+ "no_cot_instructions", "supports_tool_calling"):
437
+ if bf in profile and not isinstance(profile[bf], bool):
438
+ result.issues.append(ValidationIssue(
439
+ severity="error",
440
+ field=f"{prefix}.{bf}",
441
+ line=_yaml_seq_item_line(text, "profiles", i, bf),
442
+ message=f"Expected bool, got {type(profile[bf]).__name__}",
443
+ ))
444
+
445
+ return result
446
+
447
+
448
+ # ---------------------------------------------------------------------------
449
+ # identity.json validator
450
+ # ---------------------------------------------------------------------------
451
+
452
+
453
+ def validate_identity_json(path: Path) -> FileValidationResult:
454
+ """Validate identity.json required fields and formats.
455
+
456
+ Checks that ``name`` and ``fingerprint`` are present and well-formed.
457
+
458
+ Args:
459
+ path: Path to identity.json.
460
+
461
+ Returns:
462
+ FileValidationResult with any errors/warnings found.
463
+ """
464
+ result = FileValidationResult(config_name="identity.json", file_path=path)
465
+
466
+ if not path.exists():
467
+ result.found = False
468
+ result.issues.append(ValidationIssue(
469
+ severity="warning",
470
+ message="File not found — run 'skcapstone init' to create identity",
471
+ ))
472
+ return result
473
+
474
+ text = path.read_text(encoding="utf-8")
475
+
476
+ try:
477
+ raw: Any = json.loads(text)
478
+ except json.JSONDecodeError as exc:
479
+ result.issues.append(ValidationIssue(
480
+ severity="error",
481
+ message=f"JSON parse error: {exc.msg}",
482
+ line=exc.lineno,
483
+ ))
484
+ return result
485
+
486
+ if not isinstance(raw, dict):
487
+ result.issues.append(ValidationIssue(
488
+ severity="error",
489
+ message="Top-level value must be a JSON object",
490
+ line=1,
491
+ ))
492
+ return result
493
+
494
+ # Required: name
495
+ if "name" not in raw:
496
+ result.issues.append(ValidationIssue(
497
+ severity="error", field="name",
498
+ message="Required field 'name' is missing",
499
+ ))
500
+ elif not isinstance(raw["name"], str) or not raw["name"].strip():
501
+ result.issues.append(ValidationIssue(
502
+ severity="error", field="name",
503
+ message="'name' must be a non-empty string",
504
+ ))
505
+
506
+ # Required: fingerprint
507
+ if "fingerprint" not in raw:
508
+ result.issues.append(ValidationIssue(
509
+ severity="error", field="fingerprint",
510
+ message="Required field 'fingerprint' is missing",
511
+ ))
512
+ elif not isinstance(raw["fingerprint"], str):
513
+ result.issues.append(ValidationIssue(
514
+ severity="error", field="fingerprint",
515
+ message=f"Expected str, got {type(raw['fingerprint']).__name__}",
516
+ ))
517
+ elif not _IDENTITY_FINGERPRINT_RE.match(raw["fingerprint"]):
518
+ result.issues.append(ValidationIssue(
519
+ severity="warning", field="fingerprint",
520
+ message=(
521
+ f"Fingerprint '{raw['fingerprint']}' does not look like a "
522
+ "PGP fingerprint (expected 40 hex characters)"
523
+ ),
524
+ ))
525
+
526
+ # Optional: email must be str if present
527
+ if "email" in raw and raw["email"] is not None:
528
+ if not isinstance(raw["email"], str):
529
+ result.issues.append(ValidationIssue(
530
+ severity="error", field="email",
531
+ message=f"Expected str, got {type(raw['email']).__name__}",
532
+ ))
533
+
534
+ # Optional: capauth_managed must be bool if present
535
+ if "capauth_managed" in raw and not isinstance(raw["capauth_managed"], bool):
536
+ result.issues.append(ValidationIssue(
537
+ severity="error", field="capauth_managed",
538
+ message=f"Expected bool, got {type(raw['capauth_managed']).__name__}",
539
+ ))
540
+
541
+ return result
542
+
543
+
544
+ # ---------------------------------------------------------------------------
545
+ # Soul blueprint JSON validator
546
+ # ---------------------------------------------------------------------------
547
+
548
+
549
+ def validate_soul_blueprint_json(path: Path) -> FileValidationResult:
550
+ """Validate a soul blueprint JSON file (base.json or installed/*.json).
551
+
552
+ Checks that ``name`` and ``display_name`` are present, and that
553
+ ``core_traits`` / ``emotional_topology`` have the expected types.
554
+
555
+ Args:
556
+ path: Path to the soul JSON file.
557
+
558
+ Returns:
559
+ FileValidationResult with any errors/warnings found.
560
+ """
561
+ config_name = f"soul/{path.name}"
562
+ result = FileValidationResult(config_name=config_name, file_path=path)
563
+
564
+ if not path.exists():
565
+ result.found = False
566
+ result.issues.append(ValidationIssue(
567
+ severity="warning", message="File not found",
568
+ ))
569
+ return result
570
+
571
+ text = path.read_text(encoding="utf-8")
572
+
573
+ try:
574
+ raw: Any = json.loads(text)
575
+ except json.JSONDecodeError as exc:
576
+ result.issues.append(ValidationIssue(
577
+ severity="error",
578
+ message=f"JSON parse error: {exc.msg}",
579
+ line=exc.lineno,
580
+ ))
581
+ return result
582
+
583
+ if not isinstance(raw, dict):
584
+ result.issues.append(ValidationIssue(
585
+ severity="error", message="Expected a JSON object", line=1,
586
+ ))
587
+ return result
588
+
589
+ # Required: name + display_name
590
+ for req in ("name", "display_name"):
591
+ if req not in raw:
592
+ result.issues.append(ValidationIssue(
593
+ severity="error", field=req,
594
+ message=f"Required field '{req}' is missing",
595
+ ))
596
+ elif not isinstance(raw[req], str) or not raw[req].strip():
597
+ result.issues.append(ValidationIssue(
598
+ severity="error", field=req,
599
+ message=f"'{req}' must be a non-empty string",
600
+ ))
601
+
602
+ # core_traits: list
603
+ if "core_traits" in raw and not isinstance(raw["core_traits"], list):
604
+ result.issues.append(ValidationIssue(
605
+ severity="error", field="core_traits",
606
+ message=f"Expected list, got {type(raw['core_traits']).__name__}",
607
+ ))
608
+
609
+ # emotional_topology: dict[str, float|int]
610
+ if "emotional_topology" in raw:
611
+ et = raw["emotional_topology"]
612
+ if not isinstance(et, dict):
613
+ result.issues.append(ValidationIssue(
614
+ severity="error", field="emotional_topology",
615
+ message=f"Expected dict, got {type(et).__name__}",
616
+ ))
617
+ else:
618
+ for k, v in et.items():
619
+ if not isinstance(v, (int, float)):
620
+ result.issues.append(ValidationIssue(
621
+ severity="error",
622
+ field=f"emotional_topology.{k}",
623
+ message=f"Expected numeric value, got {type(v).__name__}",
624
+ ))
625
+
626
+ return result
627
+
628
+
629
+ # ---------------------------------------------------------------------------
630
+ # Top-level entry point
631
+ # ---------------------------------------------------------------------------
632
+
633
+
634
+ def validate_all(home: Path) -> ConfigValidationReport:
635
+ """Validate all config files in an agent home directory.
636
+
637
+ Checks:
638
+ - ``{home}/config/consciousness.yaml``
639
+ - ``{home}/config/model_profiles.yaml``
640
+ - ``{home}/identity/identity.json``
641
+ - ``{home}/soul/base.json``
642
+ - ``{home}/soul/installed/*.json``
643
+
644
+ Files that do not exist are reported as warnings (not errors), since
645
+ missing files fall back to built-in defaults.
646
+
647
+ Args:
648
+ home: Agent home directory (e.g. ``~/.skcapstone``).
649
+
650
+ Returns:
651
+ ConfigValidationReport aggregating all results.
652
+ """
653
+ report = ConfigValidationReport()
654
+ config_dir = home / "config"
655
+
656
+ report.results.append(
657
+ validate_consciousness_yaml(config_dir / "consciousness.yaml")
658
+ )
659
+ report.results.append(
660
+ validate_model_profiles_yaml(config_dir / "model_profiles.yaml")
661
+ )
662
+ report.results.append(
663
+ validate_identity_json(home / "identity" / "identity.json")
664
+ )
665
+
666
+ soul_dir = home / "soul"
667
+ if soul_dir.exists():
668
+ report.results.append(validate_soul_blueprint_json(soul_dir / "base.json"))
669
+ installed_dir = soul_dir / "installed"
670
+ if installed_dir.exists():
671
+ for bp_file in sorted(installed_dir.glob("*.json")):
672
+ report.results.append(validate_soul_blueprint_json(bp_file))
673
+
674
+ return report
@@ -4,3 +4,31 @@ Platform connectors — windows into the sovereign agent.
4
4
  Every connector talks to the same AgentRuntime.
5
5
  The platform is just a viewport. The agent is the truth.
6
6
  """
7
+
8
+ from .base import (
9
+ FRAME_FORMAT,
10
+ FRAME_HEADER_SIZE,
11
+ ConnectorBackend,
12
+ ConnectorInfo,
13
+ ConnectorStatus,
14
+ ConnectorType,
15
+ UnixSocketConnector,
16
+ )
17
+ from .cursor import CursorConnector
18
+ from .registry import ConnectorRegistry
19
+ from .terminal import TerminalConnector
20
+ from .vscode import VSCodeConnector
21
+
22
+ __all__ = [
23
+ "FRAME_FORMAT",
24
+ "FRAME_HEADER_SIZE",
25
+ "ConnectorBackend",
26
+ "ConnectorInfo",
27
+ "ConnectorRegistry",
28
+ "ConnectorStatus",
29
+ "ConnectorType",
30
+ "CursorConnector",
31
+ "TerminalConnector",
32
+ "UnixSocketConnector",
33
+ "VSCodeConnector",
34
+ ]