@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,208 @@
1
+ """Tests for the skcapstone memory CLI commands.
2
+
3
+ Tests argument parsing, option defaults, and error paths using
4
+ click.testing.CliRunner. External I/O (disk, memory engine) is mocked.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ import pytest
13
+ from click.testing import CliRunner
14
+
15
+ from skcapstone._cli_monolith import main
16
+ from skcapstone.models import MemoryEntry, MemoryLayer
17
+
18
+
19
+ @pytest.fixture
20
+ def runner() -> CliRunner:
21
+ return CliRunner()
22
+
23
+
24
+ @pytest.fixture
25
+ def fake_entry() -> MemoryEntry:
26
+ """A minimal MemoryEntry returned by mocked mem_store."""
27
+ return MemoryEntry(
28
+ memory_id="abc12345",
29
+ content="test memory content",
30
+ tags=["test"],
31
+ source="cli",
32
+ importance=0.5,
33
+ layer=MemoryLayer.SHORT_TERM,
34
+ )
35
+
36
+
37
+ class TestMemoryStoreArgs:
38
+ """Tests for `skcapstone memory store` argument parsing."""
39
+
40
+ def test_store_requires_content(self, runner: CliRunner, tmp_path: Path) -> None:
41
+ """Expected: missing content argument produces a usage error."""
42
+ result = runner.invoke(main, ["memory", "store"])
43
+ assert result.exit_code != 0
44
+ assert "Missing argument" in result.output or result.exit_code == 2
45
+
46
+ def test_store_exits_when_no_agent_home(
47
+ self, runner: CliRunner, tmp_path: Path
48
+ ) -> None:
49
+ """Expected: exits with error when agent home does not exist."""
50
+ nonexistent = str(tmp_path / "no_such_agent")
51
+ result = runner.invoke(
52
+ main, ["memory", "store", "--home", nonexistent, "some memory"]
53
+ )
54
+ assert result.exit_code != 0
55
+ assert "No agent found" in result.output
56
+
57
+ def test_store_default_importance(
58
+ self, runner: CliRunner, tmp_path: Path, fake_entry: MemoryEntry
59
+ ) -> None:
60
+ """Expected: importance defaults to 0.5 when not provided."""
61
+ agent_home = tmp_path / ".skcapstone"
62
+ agent_home.mkdir()
63
+
64
+ with patch("skcapstone._cli_monolith.mem_store", create=True), patch(
65
+ "skcapstone.memory_engine.store", return_value=fake_entry
66
+ ) as mock_store, patch("skcapstone.pillars.security.audit_event"):
67
+ result = runner.invoke(
68
+ main,
69
+ ["memory", "store", "--home", str(agent_home), "hello world"],
70
+ )
71
+ # Verify importance was 0.5 (default) if store was called
72
+ if mock_store.called:
73
+ _, kwargs = mock_store.call_args
74
+ assert kwargs.get("importance", 0.5) == 0.5
75
+
76
+ def test_store_custom_tags(
77
+ self, runner: CliRunner, tmp_path: Path, fake_entry: MemoryEntry
78
+ ) -> None:
79
+ """Expected: multiple --tag options are collected into a list."""
80
+ agent_home = tmp_path / ".skcapstone"
81
+ agent_home.mkdir()
82
+
83
+ captured_tags: list[list[str]] = []
84
+
85
+ def fake_store(**kwargs):
86
+ captured_tags.append(kwargs.get("tags", []))
87
+ return fake_entry
88
+
89
+ with patch("skcapstone.memory_engine.store", side_effect=fake_store), patch(
90
+ "skcapstone.pillars.security.audit_event"
91
+ ):
92
+ runner.invoke(
93
+ main,
94
+ [
95
+ "memory",
96
+ "store",
97
+ "--home",
98
+ str(agent_home),
99
+ "-t",
100
+ "alpha",
101
+ "-t",
102
+ "beta",
103
+ "tagged memory",
104
+ ],
105
+ )
106
+ if captured_tags:
107
+ assert "alpha" in captured_tags[0]
108
+ assert "beta" in captured_tags[0]
109
+
110
+ def test_store_layer_choice_validation(
111
+ self, runner: CliRunner, tmp_path: Path
112
+ ) -> None:
113
+ """Edge case: invalid layer value is rejected by click."""
114
+ agent_home = tmp_path / ".skcapstone"
115
+ agent_home.mkdir()
116
+ result = runner.invoke(
117
+ main,
118
+ [
119
+ "memory",
120
+ "store",
121
+ "--home",
122
+ str(agent_home),
123
+ "--layer",
124
+ "invalid-layer",
125
+ "content",
126
+ ],
127
+ )
128
+ assert result.exit_code != 0
129
+
130
+
131
+ class TestMemorySearchArgs:
132
+ """Tests for `skcapstone memory search` argument parsing."""
133
+
134
+ def test_search_requires_query(self, runner: CliRunner, tmp_path: Path) -> None:
135
+ """Expected: missing query argument produces a usage error."""
136
+ result = runner.invoke(main, ["memory", "search"])
137
+ assert result.exit_code != 0
138
+
139
+ def test_search_exits_when_no_agent_home(
140
+ self, runner: CliRunner, tmp_path: Path
141
+ ) -> None:
142
+ """Expected: exits with error when agent home does not exist."""
143
+ nonexistent = str(tmp_path / "no_such_agent")
144
+ result = runner.invoke(
145
+ main, ["memory", "search", "--home", nonexistent, "my query"]
146
+ )
147
+ assert result.exit_code != 0
148
+ assert "No agent found" in result.output
149
+
150
+ def test_search_no_results(
151
+ self, runner: CliRunner, tmp_path: Path
152
+ ) -> None:
153
+ """Expected: prints 'no memories' message when search returns empty."""
154
+ agent_home = tmp_path / ".skcapstone"
155
+ agent_home.mkdir()
156
+
157
+ with patch("skcapstone.memory_engine.search", return_value=[]):
158
+ result = runner.invoke(
159
+ main,
160
+ ["memory", "search", "--home", str(agent_home), "nonexistent"],
161
+ )
162
+ # Should succeed and report no results
163
+ assert result.exit_code == 0
164
+ assert "No memories" in result.output or "no memories" in result.output.lower()
165
+
166
+ def test_search_default_limit(
167
+ self, runner: CliRunner, tmp_path: Path
168
+ ) -> None:
169
+ """Expected: limit defaults to 20 when not specified."""
170
+ agent_home = tmp_path / ".skcapstone"
171
+ agent_home.mkdir()
172
+
173
+ captured: list[dict] = []
174
+
175
+ def fake_search(**kwargs):
176
+ captured.append(kwargs)
177
+ return []
178
+
179
+ with patch("skcapstone.memory_engine.search", side_effect=fake_search):
180
+ runner.invoke(
181
+ main,
182
+ ["memory", "search", "--home", str(agent_home), "query"],
183
+ )
184
+ if captured:
185
+ assert captured[0].get("limit", 20) == 20
186
+
187
+
188
+ class TestMemorySubcommandHelp:
189
+ """Tests that memory subcommands expose help text."""
190
+
191
+ def test_memory_group_help(self, runner: CliRunner) -> None:
192
+ """Expected: `memory --help` exits 0 and shows subcommands."""
193
+ result = runner.invoke(main, ["memory", "--help"])
194
+ assert result.exit_code == 0
195
+ assert "store" in result.output
196
+
197
+ def test_memory_store_help(self, runner: CliRunner) -> None:
198
+ """Expected: `memory store --help` exits 0 and documents options."""
199
+ result = runner.invoke(main, ["memory", "store", "--help"])
200
+ assert result.exit_code == 0
201
+ assert "--tag" in result.output or "-t" in result.output
202
+ assert "--importance" in result.output or "-i" in result.output
203
+
204
+ def test_memory_search_help(self, runner: CliRunner) -> None:
205
+ """Expected: `memory search --help` exits 0 and documents options."""
206
+ result = runner.invoke(main, ["memory", "search", "--help"])
207
+ assert result.exit_code == 0
208
+ assert "QUERY" in result.output or "query" in result.output.lower()
@@ -0,0 +1,294 @@
1
+ """Tests for skcapstone profile CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import date, timedelta
7
+ from pathlib import Path
8
+ from unittest.mock import MagicMock, patch
9
+
10
+ import pytest
11
+ import yaml
12
+ from click.testing import CliRunner
13
+
14
+ from skcapstone.cli import main
15
+ from skcapstone.prompt_adapter import ModelProfile
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Helpers
20
+ # ---------------------------------------------------------------------------
21
+
22
+
23
+ def _make_profile(**kwargs) -> ModelProfile:
24
+ """Return a minimal ModelProfile with optional overrides."""
25
+ defaults = {
26
+ "model_pattern": "test-.*",
27
+ "family": "test",
28
+ "last_updated": "2026-03-02",
29
+ }
30
+ defaults.update(kwargs)
31
+ return ModelProfile(**defaults)
32
+
33
+
34
+ def _make_adapter(profiles: list[ModelProfile]) -> MagicMock:
35
+ """Return a mock PromptAdapter pre-loaded with *profiles*."""
36
+ adapter = MagicMock()
37
+ adapter.profiles = profiles
38
+ adapter.resolve_profile.side_effect = lambda model: next(
39
+ (p for p in profiles if p.family in model or p.model_pattern in model),
40
+ _make_profile(model_pattern=".*", family="generic"),
41
+ )
42
+ return adapter
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # skcapstone profile list
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ class TestProfileList:
51
+ """Tests for 'skcapstone profile list'."""
52
+
53
+ def test_list_shows_families(self):
54
+ """Happy path: table includes all profile families."""
55
+ runner = CliRunner()
56
+ profiles = [
57
+ _make_profile(model_pattern="claude-.*", family="claude"),
58
+ _make_profile(model_pattern="grok-.*", family="grok"),
59
+ ]
60
+ adapter = _make_adapter(profiles)
61
+
62
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
63
+ result = runner.invoke(main, ["profile", "list"])
64
+
65
+ assert result.exit_code == 0
66
+ assert "claude" in result.output
67
+ assert "grok" in result.output
68
+
69
+ def test_list_json_output(self):
70
+ """--json flag emits a valid JSON array."""
71
+ runner = CliRunner()
72
+ profiles = [
73
+ _make_profile(model_pattern="claude-.*", family="claude"),
74
+ ]
75
+ adapter = _make_adapter(profiles)
76
+
77
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
78
+ result = runner.invoke(main, ["profile", "list", "--json"])
79
+
80
+ assert result.exit_code == 0
81
+ parsed = json.loads(result.output)
82
+ assert isinstance(parsed, list)
83
+ assert parsed[0]["family"] == "claude"
84
+
85
+ def test_list_empty_exits_1(self):
86
+ """When no profiles are loaded the command exits 1."""
87
+ runner = CliRunner()
88
+ adapter = _make_adapter([])
89
+
90
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
91
+ result = runner.invoke(main, ["profile", "list"])
92
+
93
+ assert result.exit_code == 1
94
+ assert "No profiles" in result.output
95
+
96
+ def test_list_uses_bundled_profiles_by_default(self):
97
+ """Without mocking, the command loads real bundled profiles."""
98
+ runner = CliRunner()
99
+ result = runner.invoke(main, ["profile", "list"])
100
+
101
+ assert result.exit_code == 0
102
+ # Bundled profiles include at least claude and grok
103
+ assert "claude" in result.output
104
+ assert "grok" in result.output
105
+
106
+ def test_list_json_includes_all_fields(self):
107
+ """JSON output contains expected ModelProfile fields."""
108
+ runner = CliRunner()
109
+ profiles = [_make_profile(family="deepseek-r1", model_pattern="deepseek-r1.*")]
110
+ adapter = _make_adapter(profiles)
111
+
112
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
113
+ result = runner.invoke(main, ["profile", "list", "--json"])
114
+
115
+ parsed = json.loads(result.output)
116
+ record = parsed[0]
117
+ assert "family" in record
118
+ assert "model_pattern" in record
119
+ assert "system_prompt_mode" in record
120
+ assert "last_updated" in record
121
+
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # skcapstone profile show
125
+ # ---------------------------------------------------------------------------
126
+
127
+
128
+ class TestProfileShow:
129
+ """Tests for 'skcapstone profile show MODEL'."""
130
+
131
+ def test_show_known_model(self):
132
+ """show resolves a known model and prints its family."""
133
+ runner = CliRunner()
134
+ result = runner.invoke(main, ["profile", "show", "claude-opus-4-5"])
135
+
136
+ assert result.exit_code == 0
137
+ assert "claude" in result.output
138
+
139
+ def test_show_prints_fields(self):
140
+ """show prints key profile fields (system_prompt_mode, structure_format, etc.)."""
141
+ runner = CliRunner()
142
+ result = runner.invoke(main, ["profile", "show", "claude-opus-4-5"])
143
+
144
+ assert result.exit_code == 0
145
+ assert "system_prompt_mode" in result.output
146
+ assert "structure_format" in result.output
147
+ assert "last_updated" in result.output
148
+
149
+ def test_show_json_output(self):
150
+ """--json flag emits a JSON object with all profile fields."""
151
+ runner = CliRunner()
152
+ result = runner.invoke(main, ["profile", "show", "grok-3", "--json"])
153
+
154
+ assert result.exit_code == 0
155
+ parsed = json.loads(result.output)
156
+ assert isinstance(parsed, dict)
157
+ assert parsed["family"] == "grok"
158
+ assert "model_pattern" in parsed
159
+
160
+ def test_show_unknown_model_falls_back_to_generic(self):
161
+ """Unknown model falls back to generic profile gracefully."""
162
+ runner = CliRunner()
163
+ result = runner.invoke(main, ["profile", "show", "totally-unknown-xyz"])
164
+
165
+ assert result.exit_code == 0
166
+ assert "generic" in result.output
167
+
168
+ def test_show_deepseek_r1_omits_system(self):
169
+ """DeepSeek R1 profile shows system_prompt_mode = omit."""
170
+ runner = CliRunner()
171
+ result = runner.invoke(main, ["profile", "show", "deepseek-r1-70b"])
172
+
173
+ assert result.exit_code == 0
174
+ assert "omit" in result.output
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # skcapstone profile stale
179
+ # ---------------------------------------------------------------------------
180
+
181
+
182
+ class TestProfileStale:
183
+ """Tests for 'skcapstone profile stale'."""
184
+
185
+ def test_stale_shows_old_profiles(self):
186
+ """Profiles with old last_updated dates appear in stale output."""
187
+ runner = CliRunner()
188
+ old_date = (date.today() - timedelta(days=200)).isoformat()
189
+ profiles = [
190
+ _make_profile(family="old-model", last_updated=old_date),
191
+ _make_profile(family="new-model", last_updated=date.today().isoformat()),
192
+ ]
193
+ adapter = _make_adapter(profiles)
194
+
195
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
196
+ result = runner.invoke(main, ["profile", "stale"])
197
+
198
+ assert result.exit_code == 0
199
+ assert "old-model" in result.output
200
+ assert "new-model" not in result.output
201
+
202
+ def test_stale_all_fresh_prints_ok(self):
203
+ """When all profiles are recent, a green OK message is shown."""
204
+ runner = CliRunner()
205
+ fresh = date.today().isoformat()
206
+ profiles = [
207
+ _make_profile(family="claude", last_updated=fresh),
208
+ _make_profile(family="grok", last_updated=fresh),
209
+ ]
210
+ adapter = _make_adapter(profiles)
211
+
212
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
213
+ result = runner.invoke(main, ["profile", "stale"])
214
+
215
+ assert result.exit_code == 0
216
+ assert "All profiles updated" in result.output
217
+
218
+ def test_stale_missing_last_updated_flagged(self):
219
+ """Profile with missing last_updated is always considered stale."""
220
+ runner = CliRunner()
221
+ profiles = [
222
+ _make_profile(family="no-date-model", last_updated=""),
223
+ ]
224
+ adapter = _make_adapter(profiles)
225
+
226
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
227
+ result = runner.invoke(main, ["profile", "stale"])
228
+
229
+ assert result.exit_code == 0
230
+ assert "no-date-model" in result.output
231
+
232
+ def test_stale_custom_days_flag(self):
233
+ """--days flag changes the staleness threshold."""
234
+ runner = CliRunner()
235
+ # 45 days old — stale at 30 days, fresh at 90 days
236
+ mid_date = (date.today() - timedelta(days=45)).isoformat()
237
+ profiles = [
238
+ _make_profile(family="mid-model", last_updated=mid_date),
239
+ ]
240
+ adapter = _make_adapter(profiles)
241
+
242
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
243
+ # Should be stale at 30-day threshold
244
+ r30 = runner.invoke(main, ["profile", "stale", "--days", "30"])
245
+ # Should be fresh at 90-day threshold
246
+ r90 = runner.invoke(main, ["profile", "stale", "--days", "90"])
247
+
248
+ assert r30.exit_code == 0
249
+ assert "mid-model" in r30.output
250
+
251
+ assert r90.exit_code == 0
252
+ assert "All profiles updated" in r90.output
253
+
254
+ def test_stale_json_output(self):
255
+ """--json flag emits a JSON array of stale profiles."""
256
+ runner = CliRunner()
257
+ old_date = (date.today() - timedelta(days=200)).isoformat()
258
+ profiles = [
259
+ _make_profile(family="stale-json-model", last_updated=old_date),
260
+ ]
261
+ adapter = _make_adapter(profiles)
262
+
263
+ with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
264
+ result = runner.invoke(main, ["profile", "stale", "--json"])
265
+
266
+ assert result.exit_code == 0
267
+ parsed = json.loads(result.output)
268
+ assert isinstance(parsed, list)
269
+ assert len(parsed) == 1
270
+ assert parsed[0]["family"] == "stale-json-model"
271
+ assert "_days_old" in parsed[0]
272
+ assert parsed[0]["_days_old"] >= 200
273
+
274
+
275
+ # ---------------------------------------------------------------------------
276
+ # _parse_last_updated helper
277
+ # ---------------------------------------------------------------------------
278
+
279
+
280
+ class TestParseLastUpdated:
281
+ """Unit tests for _parse_last_updated."""
282
+
283
+ def test_valid_date(self):
284
+ from skcapstone.cli.profile_cmd import _parse_last_updated
285
+ result = _parse_last_updated("2026-03-02")
286
+ assert result == date(2026, 3, 2)
287
+
288
+ def test_empty_string_returns_none(self):
289
+ from skcapstone.cli.profile_cmd import _parse_last_updated
290
+ assert _parse_last_updated("") is None
291
+
292
+ def test_invalid_format_returns_none(self):
293
+ from skcapstone.cli.profile_cmd import _parse_last_updated
294
+ assert _parse_last_updated("not-a-date") is None