@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,390 @@
1
+ """Tests for skcapstone shell completion.
2
+
3
+ Covers:
4
+ - Dynamic completion callbacks (complete_memory_tags, complete_agent_names,
5
+ complete_task_ids) — unit tests with tmp_path fixtures.
6
+ - CLI commands: skcapstone install-completion --help, completions --help,
7
+ completions show, completions install (mocked), completions uninstall.
8
+ - Graceful degradation: callbacks return [] when dirs are missing or files
9
+ are malformed JSON.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from pathlib import Path
16
+ from unittest.mock import patch, MagicMock
17
+
18
+ import pytest
19
+ from click.testing import CliRunner
20
+
21
+ from skcapstone.cli import main
22
+ from skcapstone.completions import (
23
+ complete_memory_tags,
24
+ complete_agent_names,
25
+ complete_task_ids,
26
+ generate_script,
27
+ detect_shell,
28
+ SUPPORTED_SHELLS,
29
+ )
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Helpers
34
+ # ---------------------------------------------------------------------------
35
+
36
+
37
+ def _make_ctx_param():
38
+ """Return stub (ctx, param) suitable for shell_complete callbacks."""
39
+ return MagicMock(), MagicMock()
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # complete_memory_tags
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ class TestCompleteMemoryTags:
48
+ def test_returns_matching_tags(self, tmp_path: Path, monkeypatch):
49
+ """Tags that start with the incomplete prefix are returned."""
50
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
51
+ layer_dir = tmp_path / "memory" / "short-term"
52
+ layer_dir.mkdir(parents=True)
53
+ (layer_dir / "mem1.json").write_text(
54
+ json.dumps({"tags": ["kubernetes", "python", "infra"]}), encoding="utf-8"
55
+ )
56
+ (layer_dir / "mem2.json").write_text(
57
+ json.dumps({"tags": ["kubernetes", "docker"]}), encoding="utf-8"
58
+ )
59
+
60
+ ctx, param = _make_ctx_param()
61
+ results = complete_memory_tags(ctx, param, "ku")
62
+
63
+ values = {r.value for r in results}
64
+ assert "kubernetes" in values
65
+ assert "python" not in values
66
+
67
+ def test_returns_all_tags_on_empty_prefix(self, tmp_path: Path, monkeypatch):
68
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
69
+ layer_dir = tmp_path / "memory" / "mid-term"
70
+ layer_dir.mkdir(parents=True)
71
+ (layer_dir / "m.json").write_text(
72
+ json.dumps({"tags": ["alpha", "beta"]}), encoding="utf-8"
73
+ )
74
+
75
+ ctx, param = _make_ctx_param()
76
+ values = {r.value for r in complete_memory_tags(ctx, param, "")}
77
+ assert "alpha" in values
78
+ assert "beta" in values
79
+
80
+ def test_deduplicates_tags_across_files(self, tmp_path: Path, monkeypatch):
81
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
82
+ layer_dir = tmp_path / "memory" / "long-term"
83
+ layer_dir.mkdir(parents=True)
84
+ for i in range(3):
85
+ (layer_dir / f"m{i}.json").write_text(
86
+ json.dumps({"tags": ["shared-tag"]}), encoding="utf-8"
87
+ )
88
+
89
+ ctx, param = _make_ctx_param()
90
+ results = complete_memory_tags(ctx, param, "shared")
91
+ assert len(results) == 1
92
+ assert results[0].value == "shared-tag"
93
+
94
+ def test_empty_when_no_memory_dir(self, tmp_path: Path, monkeypatch):
95
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
96
+ ctx, param = _make_ctx_param()
97
+ assert complete_memory_tags(ctx, param, "") == []
98
+
99
+ def test_skips_malformed_json(self, tmp_path: Path, monkeypatch):
100
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
101
+ layer_dir = tmp_path / "memory" / "short-term"
102
+ layer_dir.mkdir(parents=True)
103
+ (layer_dir / "bad.json").write_text("not json{{", encoding="utf-8")
104
+ (layer_dir / "good.json").write_text(
105
+ json.dumps({"tags": ["valid"]}), encoding="utf-8"
106
+ )
107
+
108
+ ctx, param = _make_ctx_param()
109
+ values = {r.value for r in complete_memory_tags(ctx, param, "")}
110
+ assert "valid" in values
111
+
112
+ def test_covers_all_three_layers(self, tmp_path: Path, monkeypatch):
113
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
114
+ for layer in ("short-term", "mid-term", "long-term"):
115
+ d = tmp_path / "memory" / layer
116
+ d.mkdir(parents=True)
117
+ (d / "m.json").write_text(
118
+ json.dumps({"tags": [f"tag-{layer}"]}), encoding="utf-8"
119
+ )
120
+
121
+ ctx, param = _make_ctx_param()
122
+ values = {r.value for r in complete_memory_tags(ctx, param, "")}
123
+ assert {"tag-short-term", "tag-mid-term", "tag-long-term"}.issubset(values)
124
+
125
+
126
+ # ---------------------------------------------------------------------------
127
+ # complete_agent_names
128
+ # ---------------------------------------------------------------------------
129
+
130
+
131
+ class TestCompleteAgentNames:
132
+ def test_returns_matching_agents(self, tmp_path: Path, monkeypatch):
133
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
134
+ hb_dir = tmp_path / "heartbeats"
135
+ hb_dir.mkdir()
136
+ for name in ("opus", "lumina", "grok"):
137
+ (hb_dir / f"{name}.json").write_text("{}", encoding="utf-8")
138
+
139
+ ctx, param = _make_ctx_param()
140
+ results = complete_agent_names(ctx, param, "o")
141
+ assert any(r.value == "opus" for r in results)
142
+ assert not any(r.value == "lumina" for r in results)
143
+
144
+ def test_returns_all_on_empty_prefix(self, tmp_path: Path, monkeypatch):
145
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
146
+ hb_dir = tmp_path / "heartbeats"
147
+ hb_dir.mkdir()
148
+ for name in ("alpha", "beta"):
149
+ (hb_dir / f"{name}.json").write_text("{}", encoding="utf-8")
150
+
151
+ ctx, param = _make_ctx_param()
152
+ values = {r.value for r in complete_agent_names(ctx, param, "")}
153
+ assert {"alpha", "beta"}.issubset(values)
154
+
155
+ def test_empty_when_no_heartbeat_dir(self, tmp_path: Path, monkeypatch):
156
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
157
+ ctx, param = _make_ctx_param()
158
+ assert complete_agent_names(ctx, param, "") == []
159
+
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # complete_task_ids
163
+ # ---------------------------------------------------------------------------
164
+
165
+
166
+ class TestCompleteTaskIds:
167
+ def _write_task(self, tasks_dir: Path, task_id: str, title: str) -> None:
168
+ (tasks_dir / f"{task_id}-{title.lower().replace(' ', '-')}.json").write_text(
169
+ json.dumps({"id": task_id, "title": title}), encoding="utf-8"
170
+ )
171
+
172
+ def test_returns_matching_task_ids(self, tmp_path: Path, monkeypatch):
173
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
174
+ tasks_dir = tmp_path / "coordination" / "tasks"
175
+ tasks_dir.mkdir(parents=True)
176
+ self._write_task(tasks_dir, "abc12345", "Add shell completion")
177
+ self._write_task(tasks_dir, "def67890", "Fix routing bug")
178
+
179
+ ctx, param = _make_ctx_param()
180
+ results = complete_task_ids(ctx, param, "abc")
181
+ assert len(results) == 1
182
+ assert results[0].value == "abc12345"
183
+
184
+ def test_help_text_is_task_title(self, tmp_path: Path, monkeypatch):
185
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
186
+ tasks_dir = tmp_path / "coordination" / "tasks"
187
+ tasks_dir.mkdir(parents=True)
188
+ self._write_task(tasks_dir, "aaa11111", "My Important Task")
189
+
190
+ ctx, param = _make_ctx_param()
191
+ results = complete_task_ids(ctx, param, "aaa")
192
+ assert len(results) == 1
193
+ assert "My Important Task" in results[0].help
194
+
195
+ def test_empty_when_no_tasks_dir(self, tmp_path: Path, monkeypatch):
196
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
197
+ ctx, param = _make_ctx_param()
198
+ assert complete_task_ids(ctx, param, "") == []
199
+
200
+ def test_skips_malformed_task_files(self, tmp_path: Path, monkeypatch):
201
+ monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_path))
202
+ tasks_dir = tmp_path / "coordination" / "tasks"
203
+ tasks_dir.mkdir(parents=True)
204
+ (tasks_dir / "bad.json").write_text("{{bad", encoding="utf-8")
205
+ self._write_task(tasks_dir, "fff99999", "Valid task")
206
+
207
+ ctx, param = _make_ctx_param()
208
+ results = complete_task_ids(ctx, param, "fff")
209
+ assert len(results) == 1
210
+
211
+
212
+ # ---------------------------------------------------------------------------
213
+ # generate_script / detect_shell
214
+ # ---------------------------------------------------------------------------
215
+
216
+
217
+ class TestGenerateScript:
218
+ def test_bash_script_uses_env_var(self):
219
+ script = generate_script("bash")
220
+ assert "_SKCAPSTONE_COMPLETE=bash_source" in script
221
+
222
+ def test_zsh_script_uses_env_var(self):
223
+ script = generate_script("zsh")
224
+ assert "_SKCAPSTONE_COMPLETE=zsh_source" in script
225
+
226
+ def test_fish_script_uses_env_var(self):
227
+ script = generate_script("fish")
228
+ assert "_SKCAPSTONE_COMPLETE=fish_source" in script
229
+
230
+ def test_invalid_shell_raises(self):
231
+ with pytest.raises(ValueError, match="Unsupported shell"):
232
+ generate_script("powershell")
233
+
234
+ def test_all_supported_shells_generate(self):
235
+ for shell in SUPPORTED_SHELLS:
236
+ assert generate_script(shell)
237
+
238
+
239
+ class TestDetectShell:
240
+ def test_detects_bash(self, monkeypatch):
241
+ monkeypatch.setenv("SHELL", "/bin/bash")
242
+ assert detect_shell() == "bash"
243
+
244
+ def test_detects_zsh(self, monkeypatch):
245
+ monkeypatch.setenv("SHELL", "/usr/bin/zsh")
246
+ assert detect_shell() == "zsh"
247
+
248
+ def test_detects_fish(self, monkeypatch):
249
+ monkeypatch.setenv("SHELL", "/usr/bin/fish")
250
+ assert detect_shell() == "fish"
251
+
252
+ def test_returns_none_for_unknown(self, monkeypatch):
253
+ monkeypatch.setenv("SHELL", "/bin/dash")
254
+ assert detect_shell() is None
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # CLI — install-completion command
259
+ # ---------------------------------------------------------------------------
260
+
261
+
262
+ class TestInstallCompletionCommand:
263
+ def setup_method(self):
264
+ self.runner = CliRunner()
265
+
266
+ def test_help_shows_description(self):
267
+ result = self.runner.invoke(main, ["install-completion", "--help"])
268
+ assert result.exit_code == 0
269
+ assert "completion" in result.output.lower()
270
+
271
+ def test_install_with_explicit_shell(self):
272
+ with patch("skcapstone.completions.install_completions") as mock_install:
273
+ mock_install.return_value = {
274
+ "success": True,
275
+ "shell": "bash",
276
+ "script_path": "/home/user/.bash_completion.d/skcapstone.bash-completion",
277
+ "rc_updated": False,
278
+ }
279
+ result = self.runner.invoke(main, ["install-completion", "bash"])
280
+ assert result.exit_code == 0
281
+ mock_install.assert_called_once_with(shell="bash")
282
+
283
+ def test_install_auto_detects_shell(self):
284
+ with patch("skcapstone.completions.install_completions") as mock_install:
285
+ mock_install.return_value = {
286
+ "success": True,
287
+ "shell": "zsh",
288
+ "script_path": "/home/user/.zfunc/_skcapstone",
289
+ "rc_updated": True,
290
+ "rc_path": "/home/user/.zshrc",
291
+ }
292
+ result = self.runner.invoke(main, ["install-completion"])
293
+ assert result.exit_code == 0
294
+
295
+ def test_install_prints_rc_path_when_updated(self):
296
+ with patch("skcapstone.completions.install_completions") as mock_install:
297
+ mock_install.return_value = {
298
+ "success": True,
299
+ "shell": "zsh",
300
+ "script_path": "/home/user/.zfunc/_skcapstone",
301
+ "rc_updated": True,
302
+ "rc_path": "/home/user/.zshrc",
303
+ }
304
+ result = self.runner.invoke(main, ["install-completion", "zsh"])
305
+ assert result.exit_code == 0
306
+ assert ".zshrc" in result.output
307
+
308
+ def test_invalid_shell_rejected(self):
309
+ result = self.runner.invoke(main, ["install-completion", "powershell"])
310
+ assert result.exit_code != 0
311
+
312
+
313
+ # ---------------------------------------------------------------------------
314
+ # CLI — completions group
315
+ # ---------------------------------------------------------------------------
316
+
317
+
318
+ class TestCompletionsGroup:
319
+ def setup_method(self):
320
+ self.runner = CliRunner()
321
+
322
+ def test_completions_help(self):
323
+ result = self.runner.invoke(main, ["completions", "--help"])
324
+ assert result.exit_code == 0
325
+ assert "install" in result.output
326
+ assert "show" in result.output
327
+ assert "uninstall" in result.output
328
+
329
+ def test_completions_show_bash(self):
330
+ result = self.runner.invoke(main, ["completions", "show", "--shell", "bash"])
331
+ assert result.exit_code == 0
332
+ assert "_SKCAPSTONE_COMPLETE=bash_source" in result.output
333
+
334
+ def test_completions_show_zsh(self):
335
+ result = self.runner.invoke(main, ["completions", "show", "--shell", "zsh"])
336
+ assert result.exit_code == 0
337
+ assert "_SKCAPSTONE_COMPLETE=zsh_source" in result.output
338
+
339
+ def test_completions_show_fish(self):
340
+ result = self.runner.invoke(main, ["completions", "show", "--shell", "fish"])
341
+ assert result.exit_code == 0
342
+ assert "_SKCAPSTONE_COMPLETE=fish_source" in result.output
343
+
344
+ def test_completions_install(self):
345
+ with patch("skcapstone.completions.install_completions") as mock_install:
346
+ mock_install.return_value = {
347
+ "success": True,
348
+ "shell": "bash",
349
+ "script_path": "/tmp/skcapstone.bash-completion",
350
+ "rc_updated": False,
351
+ }
352
+ result = self.runner.invoke(
353
+ main, ["completions", "install", "--shell", "bash"]
354
+ )
355
+ assert result.exit_code == 0
356
+ mock_install.assert_called_once_with(shell="bash")
357
+
358
+ def test_completions_install_failure(self):
359
+ with patch("skcapstone.completions.install_completions") as mock_install:
360
+ mock_install.return_value = {
361
+ "success": False,
362
+ "error": "Could not detect shell. Use --shell bash/zsh/fish.",
363
+ }
364
+ result = self.runner.invoke(main, ["completions", "install"])
365
+ assert result.exit_code != 0
366
+ assert "detect" in result.output.lower() or "shell" in result.output.lower()
367
+
368
+ def test_completions_uninstall_no_scripts(self):
369
+ with patch("skcapstone.completions.uninstall_completions") as mock_uninstall:
370
+ mock_uninstall.return_value = {
371
+ "success": True,
372
+ "removed": [],
373
+ "note": "Source lines in RC files were not removed.",
374
+ }
375
+ result = self.runner.invoke(main, ["completions", "uninstall"])
376
+ assert result.exit_code == 0
377
+ assert "No completion scripts found" in result.output
378
+
379
+ def test_completions_uninstall_removes_scripts(self):
380
+ with patch("skcapstone.completions.uninstall_completions") as mock_uninstall:
381
+ mock_uninstall.return_value = {
382
+ "success": True,
383
+ "removed": ["/home/user/.zfunc/_skcapstone"],
384
+ "note": "Source lines in RC files were not removed.",
385
+ }
386
+ result = self.runner.invoke(
387
+ main, ["completions", "uninstall", "--shell", "zsh"]
388
+ )
389
+ assert result.exit_code == 0
390
+ assert "_skcapstone" in result.output
@@ -0,0 +1,164 @@
1
+ """Tests for skcapstone init (onboard alias) and skcapstone reset commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+ from click.testing import CliRunner
10
+
11
+ from skcapstone.cli import main
12
+
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # skcapstone init — alias for onboard
16
+ # ---------------------------------------------------------------------------
17
+
18
+
19
+ class TestInitAlias:
20
+ """Verify that `skcapstone init` delegates to run_onboard."""
21
+
22
+ def test_init_calls_run_onboard(self, tmp_path: Path) -> None:
23
+ """init command invokes run_onboard with the home path."""
24
+ runner = CliRunner()
25
+ with patch("skcapstone.onboard.run_onboard") as mock_onboard:
26
+ result = runner.invoke(main, ["init", "--home", str(tmp_path)])
27
+ mock_onboard.assert_called_once_with(str(tmp_path))
28
+ assert result.exit_code == 0
29
+
30
+ def test_init_registered_in_cli(self) -> None:
31
+ """init is a registered top-level command."""
32
+ assert "init" in main.commands
33
+
34
+ def test_init_and_onboard_both_registered(self) -> None:
35
+ """Both init and onboard exist as distinct commands."""
36
+ assert "init" in main.commands
37
+ assert "onboard" in main.commands
38
+
39
+ def test_init_default_home(self) -> None:
40
+ """init uses AGENT_HOME when --home is not supplied."""
41
+ from skcapstone.cli._common import AGENT_HOME
42
+
43
+ runner = CliRunner()
44
+ with patch("skcapstone.onboard.run_onboard") as mock_onboard:
45
+ result = runner.invoke(main, ["init"])
46
+ # run_onboard should have been called with the default home string
47
+ mock_onboard.assert_called_once_with(AGENT_HOME)
48
+ assert result.exit_code == 0
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # skcapstone reset
53
+ # ---------------------------------------------------------------------------
54
+
55
+
56
+ class TestReset:
57
+ """Tests for `skcapstone reset` factory-reset command."""
58
+
59
+ def _make_home(self, tmp_path: Path) -> Path:
60
+ """Build a minimal agent home tree with an identity dir."""
61
+ home = tmp_path / ".skcapstone"
62
+ (home / "identity").mkdir(parents=True)
63
+ (home / "identity" / "key.gpg").write_text("fake-key-data")
64
+ (home / "memory").mkdir()
65
+ (home / "config").mkdir()
66
+ return home
67
+
68
+ def test_reset_aborted_on_wrong_confirmation(self, tmp_path: Path) -> None:
69
+ """Reset is aborted when the user types anything other than YES."""
70
+ home = self._make_home(tmp_path)
71
+ runner = CliRunner()
72
+ result = runner.invoke(
73
+ main,
74
+ ["reset", "--home", str(home)],
75
+ input="no\n",
76
+ )
77
+ assert result.exit_code == 0
78
+ assert "aborted" in result.output.lower()
79
+ assert home.exists(), "Home should NOT be deleted when aborted"
80
+
81
+ def test_reset_aborted_on_empty_confirmation(self, tmp_path: Path) -> None:
82
+ """Reset is aborted when the user presses Enter (empty input)."""
83
+ home = self._make_home(tmp_path)
84
+ runner = CliRunner()
85
+ result = runner.invoke(
86
+ main,
87
+ ["reset", "--home", str(home)],
88
+ input="\n",
89
+ )
90
+ assert result.exit_code == 0
91
+ assert "aborted" in result.output.lower()
92
+ assert home.exists()
93
+
94
+ def test_reset_wipes_home_on_yes_confirmation(self, tmp_path: Path) -> None:
95
+ """Typing YES wipes the agent home directory."""
96
+ home = self._make_home(tmp_path)
97
+ runner = CliRunner()
98
+ result = runner.invoke(
99
+ main,
100
+ ["reset", "--home", str(home)],
101
+ input="YES\n",
102
+ )
103
+ assert result.exit_code == 0
104
+ assert not home.exists(), "Home should be deleted after YES"
105
+ assert "reset complete" in result.output.lower()
106
+
107
+ def test_reset_backs_up_identity_before_wipe(self, tmp_path: Path) -> None:
108
+ """identity/ is copied to a timestamped backup before deletion."""
109
+ home = self._make_home(tmp_path)
110
+ runner = CliRunner()
111
+ result = runner.invoke(
112
+ main,
113
+ ["reset", "--home", str(home)],
114
+ input="YES\n",
115
+ )
116
+ assert result.exit_code == 0
117
+
118
+ # Find backup dir(s) created under tmp_path
119
+ backups = list(tmp_path.glob(".skcapstone-backup-*"))
120
+ assert len(backups) == 1, f"Expected one backup dir, found: {backups}"
121
+ backup = backups[0]
122
+ assert (backup / "identity" / "key.gpg").exists(), (
123
+ "identity/key.gpg must be present in the backup"
124
+ )
125
+
126
+ def test_reset_force_skips_prompt(self, tmp_path: Path) -> None:
127
+ """--force bypasses the confirmation prompt."""
128
+ home = self._make_home(tmp_path)
129
+ runner = CliRunner()
130
+ result = runner.invoke(
131
+ main,
132
+ ["reset", "--home", str(home), "--force"],
133
+ )
134
+ assert result.exit_code == 0
135
+ assert not home.exists(), "Home should be wiped with --force"
136
+
137
+ def test_reset_nonexistent_home_is_graceful(self, tmp_path: Path) -> None:
138
+ """reset on a non-existent home prints a warning and exits cleanly."""
139
+ missing = tmp_path / ".skcapstone-does-not-exist"
140
+ runner = CliRunner()
141
+ result = runner.invoke(
142
+ main,
143
+ ["reset", "--home", str(missing)],
144
+ )
145
+ assert result.exit_code == 0
146
+ assert "nothing to reset" in result.output.lower()
147
+
148
+ def test_reset_registered_in_cli(self) -> None:
149
+ """reset is a registered top-level command."""
150
+ assert "reset" in main.commands
151
+
152
+ def test_reset_backup_path_in_output(self, tmp_path: Path) -> None:
153
+ """Output mentions the backup path when identity/ exists."""
154
+ home = self._make_home(tmp_path)
155
+ runner = CliRunner()
156
+ result = runner.invoke(
157
+ main,
158
+ ["reset", "--home", str(home), "--force"],
159
+ )
160
+ assert result.exit_code == 0
161
+ assert "backup" in result.output.lower()
162
+ # Rich may wrap long paths — join lines before checking
163
+ joined = result.output.replace("\n", "")
164
+ assert ".skcapstone-backup-" in joined