@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,257 @@
1
+ """Tests for the skcapstone doctor diagnostics module.
2
+
3
+ Covers:
4
+ - DiagnosticReport structure and properties
5
+ - Package checks (installed vs missing)
6
+ - Agent home directory checks
7
+ - Identity checks (present vs missing)
8
+ - Memory store checks
9
+ - CLI integration (doctor command via CliRunner)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from pathlib import Path
16
+ from unittest.mock import patch
17
+
18
+ import pytest
19
+ import yaml
20
+ from click.testing import CliRunner
21
+
22
+ from skcapstone.doctor import (
23
+ Check,
24
+ DiagnosticReport,
25
+ _check_agent_home,
26
+ _check_identity,
27
+ _check_memory,
28
+ _check_packages,
29
+ run_diagnostics,
30
+ )
31
+
32
+
33
+ @pytest.fixture
34
+ def agent_home(tmp_path):
35
+ """Create a fully populated agent home for testing."""
36
+ home = tmp_path / ".skcapstone"
37
+ for d in ["identity", "memory", "trust", "security", "sync", "config",
38
+ "memory/short-term", "memory/mid-term", "memory/long-term",
39
+ "sync/outbox", "sync/inbox"]:
40
+ (home / d).mkdir(parents=True, exist_ok=True)
41
+
42
+ (home / "manifest.json").write_text(json.dumps({
43
+ "name": "TestAgent", "version": "0.1.0",
44
+ }))
45
+ (home / "identity" / "identity.json").write_text(json.dumps({
46
+ "name": "TestAgent",
47
+ "fingerprint": "AABBCCDD11223344",
48
+ "capauth_managed": True,
49
+ }))
50
+ (home / "config" / "config.yaml").write_text(yaml.dump({"agent_name": "TestAgent"}))
51
+ (home / "memory" / "index.json").write_text("{}")
52
+
53
+ # Add a memory file
54
+ (home / "memory" / "short-term" / "mem1.json").write_text(json.dumps({
55
+ "memory_id": "mem1", "content": "test",
56
+ }))
57
+
58
+ return home
59
+
60
+
61
+ @pytest.fixture
62
+ def empty_home(tmp_path):
63
+ """A non-existent agent home path."""
64
+ return tmp_path / ".skcapstone-nonexistent"
65
+
66
+
67
+ class TestCheck:
68
+ """Test Check dataclass."""
69
+
70
+ def test_passing_check(self):
71
+ """Passing check has correct attributes."""
72
+ c = Check(name="test", description="A test", passed=True, detail="ok")
73
+ assert c.passed
74
+ assert c.detail == "ok"
75
+
76
+ def test_failing_check_with_fix(self):
77
+ """Failing check carries a fix suggestion."""
78
+ c = Check(name="test", description="Broken", passed=False, fix="run this")
79
+ assert not c.passed
80
+ assert c.fix == "run this"
81
+
82
+
83
+ class TestDiagnosticReport:
84
+ """Test DiagnosticReport properties."""
85
+
86
+ def test_counts(self):
87
+ """Report counts passed and failed correctly."""
88
+ report = DiagnosticReport(checks=[
89
+ Check(name="a", description="A", passed=True),
90
+ Check(name="b", description="B", passed=True),
91
+ Check(name="c", description="C", passed=False),
92
+ ])
93
+
94
+ assert report.passed_count == 2
95
+ assert report.failed_count == 1
96
+ assert report.total_count == 3
97
+ assert not report.all_passed
98
+
99
+ def test_all_passed(self):
100
+ """all_passed is True when everything passes."""
101
+ report = DiagnosticReport(checks=[
102
+ Check(name="a", description="A", passed=True),
103
+ ])
104
+ assert report.all_passed
105
+
106
+ def test_to_dict(self):
107
+ """to_dict produces JSON-serializable output."""
108
+ report = DiagnosticReport(
109
+ agent_home="/test",
110
+ checks=[Check(name="x", description="X", passed=True, detail="ok")],
111
+ )
112
+ d = report.to_dict()
113
+
114
+ assert d["agent_home"] == "/test"
115
+ assert d["passed"] == 1
116
+ assert d["total"] == 1
117
+ assert len(d["checks"]) == 1
118
+ json.dumps(d)
119
+
120
+
121
+ class TestCheckAgentHome:
122
+ """Test agent home directory checks."""
123
+
124
+ def test_existing_home(self, agent_home):
125
+ """Fully populated home passes all checks."""
126
+ checks = _check_agent_home(agent_home)
127
+
128
+ names = {c.name for c in checks}
129
+ assert "home:exists" in names
130
+ assert "home:manifest" in names
131
+ assert all(c.passed for c in checks), [c.name for c in checks if not c.passed]
132
+
133
+ def test_missing_home(self, empty_home):
134
+ """Missing home directory fails immediately."""
135
+ checks = _check_agent_home(empty_home)
136
+
137
+ assert len(checks) == 1
138
+ assert checks[0].name == "home:exists"
139
+ assert not checks[0].passed
140
+
141
+
142
+ class TestCheckIdentity:
143
+ """Test identity checks."""
144
+
145
+ def test_identity_present(self, agent_home):
146
+ """Valid identity file passes checks."""
147
+ checks = _check_identity(agent_home)
148
+
149
+ profile_check = next(c for c in checks if c.name == "identity:profile")
150
+ assert profile_check.passed
151
+ assert "AABBCCDD" in profile_check.detail
152
+
153
+ def test_identity_missing(self, empty_home):
154
+ """Missing identity directory fails."""
155
+ empty_home.mkdir(parents=True, exist_ok=True)
156
+ (empty_home / "identity").mkdir()
157
+
158
+ checks = _check_identity(empty_home)
159
+ profile_check = next(c for c in checks if c.name == "identity:profile")
160
+ assert not profile_check.passed
161
+
162
+
163
+ class TestCheckMemory:
164
+ """Test memory store checks."""
165
+
166
+ def test_memory_healthy(self, agent_home):
167
+ """Populated memory store passes."""
168
+ checks = _check_memory(agent_home)
169
+
170
+ store_check = next(c for c in checks if c.name == "memory:store")
171
+ assert store_check.passed
172
+ assert "1 memories" in store_check.detail
173
+
174
+ index_check = next(c for c in checks if c.name == "memory:index")
175
+ assert index_check.passed
176
+
177
+ def test_memory_missing(self, empty_home):
178
+ """Missing memory directory fails."""
179
+ empty_home.mkdir(parents=True, exist_ok=True)
180
+
181
+ checks = _check_memory(empty_home)
182
+ assert len(checks) == 1
183
+ assert not checks[0].passed
184
+
185
+
186
+ class TestCheckPackages:
187
+ """Test Python package checks."""
188
+
189
+ def test_skcapstone_is_installed(self):
190
+ """skcapstone itself should always be importable in tests."""
191
+ checks = _check_packages()
192
+ skcap = next(c for c in checks if c.name == "pkg:skcapstone")
193
+ assert skcap.passed
194
+
195
+ def test_missing_package_detected(self):
196
+ """A fake package should fail the check."""
197
+ with patch("skcapstone.doctor.importlib.import_module", side_effect=ImportError):
198
+ checks = _check_packages()
199
+ assert all(not c.passed for c in checks)
200
+ assert all(c.fix for c in checks)
201
+
202
+
203
+ class TestRunDiagnostics:
204
+ """Test the full diagnostic run."""
205
+
206
+ def test_full_run_on_populated_home(self, agent_home):
207
+ """Full diagnostics on a populated home produces a report."""
208
+ report = run_diagnostics(agent_home)
209
+
210
+ assert report.total_count > 0
211
+ assert report.passed_count > 0
212
+ assert report.agent_home == str(agent_home)
213
+
214
+ def test_full_run_on_empty_home(self, empty_home):
215
+ """Full diagnostics on missing home still produces a report."""
216
+ report = run_diagnostics(empty_home)
217
+
218
+ assert report.total_count > 0
219
+ assert report.failed_count > 0
220
+
221
+
222
+ class TestCLIDoctorCommand:
223
+ """Test the CLI doctor command via CliRunner."""
224
+
225
+ def test_doctor_help(self):
226
+ """doctor --help works."""
227
+ from skcapstone.cli import main
228
+
229
+ runner = CliRunner()
230
+ result = runner.invoke(main, ["doctor", "--help"])
231
+ assert result.exit_code == 0
232
+ assert "Diagnose" in result.output
233
+ assert "--json-out" in result.output
234
+
235
+ def test_doctor_json_output(self, agent_home):
236
+ """doctor --json-out produces valid JSON."""
237
+ from skcapstone.cli import main
238
+
239
+ runner = CliRunner()
240
+ result = runner.invoke(main, ["doctor", "--home", str(agent_home), "--json-out"])
241
+ assert result.exit_code == 0
242
+
243
+ data = json.loads(result.output)
244
+ assert "passed" in data
245
+ assert "failed" in data
246
+ assert "checks" in data
247
+ assert isinstance(data["checks"], list)
248
+
249
+ def test_doctor_human_output(self, agent_home):
250
+ """doctor without --json produces human-readable output."""
251
+ from skcapstone.cli import main
252
+
253
+ runner = CliRunner()
254
+ result = runner.invoke(main, ["doctor", "--home", str(agent_home)])
255
+ assert result.exit_code == 0
256
+ assert "Python Packages" in result.output
257
+ assert "passed" in result.output or "checks" in result.output.lower()
@@ -0,0 +1,351 @@
1
+ """Tests for skcapstone doctor --fix auto-remediation.
2
+
3
+ Covers:
4
+ - run_fixes() creates missing home directory
5
+ - run_fixes() creates missing subdirectories
6
+ - run_fixes() writes a default manifest.json when absent
7
+ - run_fixes() creates memory store layer directories
8
+ - run_fixes() rebuilds a missing memory index from existing files
9
+ - run_fixes() creates sync dir with outbox + inbox
10
+ - run_fixes() skips unfixable checks (packages, identity key)
11
+ - CLI doctor --fix flag appears in --help
12
+ - CLI doctor --fix auto-creates structure and reports fixes
13
+ - CLI doctor --fix --json-out includes fixes key in output
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from pathlib import Path
20
+
21
+ import pytest
22
+ from click.testing import CliRunner
23
+
24
+ from skcapstone.doctor import (
25
+ Check,
26
+ DiagnosticReport,
27
+ FixResult,
28
+ run_diagnostics,
29
+ run_fixes,
30
+ )
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Helpers
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ def _make_report(*checks: Check) -> DiagnosticReport:
39
+ return DiagnosticReport(checks=list(checks), agent_home="/tmp/test")
40
+
41
+
42
+ def _failing(name: str, category: str = "agent") -> Check:
43
+ return Check(name=name, description=name, passed=False, category=category)
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Unit tests for run_fixes()
48
+ # ---------------------------------------------------------------------------
49
+
50
+
51
+ class TestRunFixesHomeDir:
52
+ """run_fixes creates missing home directory and subdirs."""
53
+
54
+ def test_creates_home_directory(self, tmp_path):
55
+ """home:exists fix creates the directory."""
56
+ home = tmp_path / ".skcapstone"
57
+ assert not home.exists()
58
+
59
+ report = _make_report(_failing("home:exists"))
60
+ results = run_fixes(report, home)
61
+
62
+ assert home.exists()
63
+ assert len(results) == 1
64
+ assert results[0].success
65
+ assert results[0].check_name == "home:exists"
66
+
67
+ def test_creates_missing_subdirectory(self, tmp_path):
68
+ """home:{dirname} fix creates each expected subdirectory."""
69
+ home = tmp_path / ".skcapstone"
70
+ home.mkdir()
71
+
72
+ for dirname in ["identity", "memory", "trust", "security", "sync", "config"]:
73
+ assert not (home / dirname).exists()
74
+ report = _make_report(_failing(f"home:{dirname}"))
75
+ results = run_fixes(report, home)
76
+
77
+ assert (home / dirname).exists(), f"{dirname} not created"
78
+ assert results[0].success
79
+ assert results[0].check_name == f"home:{dirname}"
80
+
81
+ def test_subdir_fix_is_idempotent(self, tmp_path):
82
+ """Re-running a subdir fix on an existing dir does not fail."""
83
+ home = tmp_path / ".skcapstone"
84
+ home.mkdir()
85
+ (home / "memory").mkdir()
86
+
87
+ report = _make_report(_failing("home:memory"))
88
+ results = run_fixes(report, home)
89
+
90
+ assert results[0].success
91
+
92
+
93
+ class TestRunFixesManifest:
94
+ """run_fixes writes a default manifest.json."""
95
+
96
+ def test_writes_default_manifest(self, tmp_path):
97
+ """manifest fix creates a valid JSON file with name + version."""
98
+ home = tmp_path / "myagent"
99
+ home.mkdir()
100
+
101
+ report = _make_report(_failing("home:manifest"))
102
+ results = run_fixes(report, home)
103
+
104
+ manifest = home / "manifest.json"
105
+ assert manifest.exists()
106
+ data = json.loads(manifest.read_text())
107
+ assert "name" in data
108
+ assert "version" in data
109
+ assert results[0].success
110
+
111
+ def test_manifest_fix_skipped_when_corrupt(self, tmp_path):
112
+ """If manifest.json already exists, fix raises and returns failure."""
113
+ home = tmp_path / "agent"
114
+ home.mkdir()
115
+ (home / "manifest.json").write_text("CORRUPT{{{")
116
+
117
+ report = _make_report(_failing("home:manifest"))
118
+ results = run_fixes(report, home)
119
+
120
+ assert not results[0].success
121
+ assert results[0].error
122
+
123
+
124
+ class TestRunFixesMemory:
125
+ """run_fixes creates memory store and rebuilds index."""
126
+
127
+ def test_creates_memory_layer_dirs(self, tmp_path):
128
+ """memory:store fix creates short-term, mid-term, long-term dirs."""
129
+ home = tmp_path / ".skcapstone"
130
+ home.mkdir()
131
+
132
+ report = _make_report(_failing("memory:store", "memory"))
133
+ results = run_fixes(report, home)
134
+
135
+ for layer in ["short-term", "mid-term", "long-term"]:
136
+ assert (home / "memory" / layer).exists(), f"{layer} not created"
137
+ assert results[0].success
138
+
139
+ def test_rebuilds_empty_index(self, tmp_path):
140
+ """memory:index fix writes an empty index.json when no memories exist."""
141
+ home = tmp_path / ".skcapstone"
142
+ memory_dir = home / "memory"
143
+ for layer in ["short-term", "mid-term", "long-term"]:
144
+ (memory_dir / layer).mkdir(parents=True)
145
+
146
+ report = _make_report(_failing("memory:index", "memory"))
147
+ results = run_fixes(report, home)
148
+
149
+ index_path = memory_dir / "index.json"
150
+ assert index_path.exists()
151
+ data = json.loads(index_path.read_text())
152
+ assert isinstance(data, dict)
153
+ assert results[0].success
154
+
155
+ def test_rebuilds_index_from_existing_memories(self, tmp_path):
156
+ """memory:index fix populates index.json from existing memory files."""
157
+ home = tmp_path / ".skcapstone"
158
+ short_term = home / "memory" / "short-term"
159
+ short_term.mkdir(parents=True)
160
+ for layer in ["mid-term", "long-term"]:
161
+ (home / "memory" / layer).mkdir(parents=True)
162
+
163
+ # Write two memory files
164
+ for mem_id in ["abc123", "def456"]:
165
+ (short_term / f"{mem_id}.json").write_text(json.dumps({
166
+ "memory_id": mem_id,
167
+ "content": f"Memory content for {mem_id}",
168
+ "tags": ["test"],
169
+ "importance": 0.8,
170
+ "created_at": "2026-01-01T00:00:00+00:00",
171
+ }))
172
+
173
+ report = _make_report(_failing("memory:index", "memory"))
174
+ results = run_fixes(report, home)
175
+
176
+ index_path = home / "memory" / "index.json"
177
+ data = json.loads(index_path.read_text())
178
+ assert "abc123" in data
179
+ assert "def456" in data
180
+ assert data["abc123"]["layer"] == "short-term"
181
+ assert results[0].success
182
+
183
+
184
+ class TestRunFixesSyncDir:
185
+ """run_fixes creates the sync directory structure."""
186
+
187
+ def test_creates_sync_dir_with_queues(self, tmp_path):
188
+ """sync:dir fix creates sync/, outbox/, and inbox/."""
189
+ home = tmp_path / ".skcapstone"
190
+ home.mkdir()
191
+
192
+ report = _make_report(_failing("sync:dir", "sync"))
193
+ results = run_fixes(report, home)
194
+
195
+ assert (home / "sync").exists()
196
+ assert (home / "sync" / "outbox").exists()
197
+ assert (home / "sync" / "inbox").exists()
198
+ assert results[0].success
199
+
200
+
201
+ class TestRunFixesSkipsUnfixable:
202
+ """run_fixes silently skips checks with no registered handler."""
203
+
204
+ def test_skips_package_checks(self, tmp_path):
205
+ """Package installation checks are not auto-fixable."""
206
+ home = tmp_path / ".skcapstone"
207
+ home.mkdir()
208
+
209
+ report = _make_report(_failing("pkg:skcapstone", "packages"))
210
+ results = run_fixes(report, home)
211
+
212
+ # No fix attempted — empty results
213
+ assert results == []
214
+
215
+ def test_skips_identity_key_check(self, tmp_path):
216
+ """PGP key generation is not auto-fixable."""
217
+ home = tmp_path / ".skcapstone"
218
+ home.mkdir()
219
+
220
+ report = _make_report(_failing("identity:pgp_key", "identity"))
221
+ results = run_fixes(report, home)
222
+
223
+ assert results == []
224
+
225
+ def test_skips_system_tool_check(self, tmp_path):
226
+ """System tool installs are not auto-fixable."""
227
+ home = tmp_path / ".skcapstone"
228
+ home.mkdir()
229
+
230
+ report = _make_report(_failing("tool:gpg", "system"))
231
+ results = run_fixes(report, home)
232
+
233
+ assert results == []
234
+
235
+ def test_passed_checks_not_attempted(self, tmp_path):
236
+ """Passing checks generate no fix results."""
237
+ home = tmp_path / ".skcapstone"
238
+ home.mkdir()
239
+
240
+ passing = Check(name="home:exists", description="home", passed=True)
241
+ report = _make_report(passing)
242
+ results = run_fixes(report, home)
243
+
244
+ assert results == []
245
+
246
+
247
+ class TestRunFixesMultiple:
248
+ """run_fixes handles multiple failing checks in one pass."""
249
+
250
+ def test_fixes_multiple_subdirs(self, tmp_path):
251
+ """Multiple missing subdirs are all created in a single run."""
252
+ home = tmp_path / ".skcapstone"
253
+ home.mkdir()
254
+
255
+ checks = [_failing(f"home:{d}") for d in ["identity", "trust", "config"]]
256
+ report = _make_report(*checks)
257
+ results = run_fixes(report, home)
258
+
259
+ assert len(results) == 3
260
+ assert all(r.success for r in results)
261
+ for d in ["identity", "trust", "config"]:
262
+ assert (home / d).exists()
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Integration: full diagnostics cycle
267
+ # ---------------------------------------------------------------------------
268
+
269
+
270
+ class TestRunDiagnosticsAfterFix:
271
+ """After running fixes, re-running diagnostics improves the report."""
272
+
273
+ def test_fix_reduces_failure_count(self, tmp_path):
274
+ """Directories created by fixes pass on the next diagnostic run."""
275
+ home = tmp_path / ".skcapstone"
276
+ home.mkdir()
277
+
278
+ before = run_diagnostics(home)
279
+ before_failed = before.failed_count
280
+
281
+ run_fixes(before, home)
282
+ after = run_diagnostics(home)
283
+
284
+ assert after.failed_count < before_failed
285
+
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # CLI integration tests
289
+ # ---------------------------------------------------------------------------
290
+
291
+
292
+ class TestCLIDoctorFix:
293
+ """CLI doctor --fix flag."""
294
+
295
+ def test_fix_flag_in_help(self):
296
+ """--fix appears in doctor --help output."""
297
+ from skcapstone.cli import main
298
+
299
+ runner = CliRunner()
300
+ result = runner.invoke(main, ["doctor", "--help"])
301
+ assert result.exit_code == 0
302
+ assert "--fix" in result.output
303
+
304
+ def test_fix_creates_dirs_and_reports(self, tmp_path):
305
+ """doctor --fix on a bare home creates dirs and prints fix results."""
306
+ from skcapstone.cli import main
307
+
308
+ home = tmp_path / ".skcapstone"
309
+ home.mkdir()
310
+
311
+ runner = CliRunner()
312
+ result = runner.invoke(main, ["doctor", "--home", str(home), "--fix"])
313
+ assert result.exit_code == 0
314
+ # At least one successful fix should be reported
315
+ assert "\u2713" in result.output or "Auto-fix" in result.output
316
+
317
+ def test_fix_json_output_includes_fixes_key(self, tmp_path):
318
+ """doctor --fix --json-out includes a 'fixes' list in JSON output."""
319
+ from skcapstone.cli import main
320
+
321
+ home = tmp_path / ".skcapstone"
322
+ home.mkdir()
323
+
324
+ runner = CliRunner()
325
+ result = runner.invoke(
326
+ main, ["doctor", "--home", str(home), "--fix", "--json-out"]
327
+ )
328
+ assert result.exit_code == 0
329
+ data = json.loads(result.output)
330
+ assert "fixes" in data
331
+ assert isinstance(data["fixes"], list)
332
+
333
+ def test_fix_on_already_healthy_home_is_noop(self, tmp_path):
334
+ """doctor --fix on a passing home makes no changes and exits cleanly."""
335
+ from skcapstone.cli import main
336
+
337
+ # Build a fully populated home
338
+ home = tmp_path / ".skcapstone"
339
+ for d in ["identity", "memory", "trust", "security", "sync", "config",
340
+ "memory/short-term", "memory/mid-term", "memory/long-term",
341
+ "sync/outbox", "sync/inbox"]:
342
+ (home / d).mkdir(parents=True, exist_ok=True)
343
+ (home / "manifest.json").write_text(json.dumps({"name": "T", "version": "0.1.0"}))
344
+ (home / "identity" / "identity.json").write_text(json.dumps({
345
+ "name": "T", "fingerprint": "AABB1122", "capauth_managed": True,
346
+ }))
347
+ (home / "memory" / "index.json").write_text("{}")
348
+
349
+ runner = CliRunner()
350
+ result = runner.invoke(main, ["doctor", "--home", str(home), "--fix"])
351
+ assert result.exit_code == 0