@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,385 @@
1
+ """Tests for the skcapstone agent-to-agent chat module.
2
+
3
+ Covers:
4
+ - AgentChat.send() with and without transport
5
+ - AgentChat.receive() and inbox retrieval
6
+ - Payload serialization round-trip
7
+ - CLI commands (send, inbox) via CliRunner
8
+ - Graceful degradation when dependencies are missing
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+ from unittest.mock import MagicMock, patch
16
+
17
+ import pytest
18
+ from click.testing import CliRunner
19
+
20
+ from skcapstone.chat import (
21
+ AgentChat,
22
+ _pack_chat_payload,
23
+ _short_timestamp,
24
+ _unpack_chat_payload,
25
+ )
26
+
27
+
28
+ @pytest.fixture
29
+ def tmp_home(tmp_path):
30
+ """Create a minimal agent home directory."""
31
+ home = tmp_path / ".skcapstone"
32
+ (home / "config").mkdir(parents=True)
33
+ (home / "identity").mkdir(parents=True)
34
+ identity_data = {"name": "TestAgent", "fingerprint": "AABB1234", "capauth_managed": False}
35
+ (home / "identity" / "identity.json").write_text(json.dumps(identity_data))
36
+ (home / "manifest.json").write_text(json.dumps({"name": "TestAgent", "version": "0.1.0"}))
37
+ import yaml
38
+ (home / "config" / "config.yaml").write_text(yaml.dump({"agent_name": "TestAgent"}))
39
+ return home
40
+
41
+
42
+ class TestPayloadRoundTrip:
43
+ """Verify pack/unpack preserves message data."""
44
+
45
+ def test_pack_unpack_basic(self):
46
+ """Basic message survives serialize/deserialize."""
47
+ mock_msg = MagicMock()
48
+ mock_msg.id = "msg-123"
49
+ mock_msg.sender = "opus"
50
+ mock_msg.recipient = "lumina"
51
+ mock_msg.content = "Hello sovereign world"
52
+ mock_msg.thread_id = None
53
+ mock_msg.timestamp.isoformat.return_value = "2026-02-24T05:00:00Z"
54
+
55
+ packed = _pack_chat_payload(mock_msg)
56
+ unpacked = _unpack_chat_payload(packed, sender="fallback", recipient="fallback")
57
+
58
+ assert unpacked["sender"] == "opus"
59
+ assert unpacked["recipient"] == "lumina"
60
+ assert unpacked["content"] == "Hello sovereign world"
61
+
62
+ def test_pack_unpack_with_thread(self):
63
+ """Thread ID survives round-trip."""
64
+ mock_msg = MagicMock()
65
+ mock_msg.id = "msg-456"
66
+ mock_msg.sender = "jarvis"
67
+ mock_msg.recipient = "opus"
68
+ mock_msg.content = "Build update"
69
+ mock_msg.thread_id = "deploy-01"
70
+ mock_msg.timestamp.isoformat.return_value = "2026-02-24T05:00:00Z"
71
+
72
+ packed = _pack_chat_payload(mock_msg)
73
+ unpacked = _unpack_chat_payload(packed, sender="x", recipient="y")
74
+
75
+ assert unpacked["thread_id"] == "deploy-01"
76
+
77
+ def test_unpack_plain_text_fallback(self):
78
+ """Non-JSON payloads treated as plain text."""
79
+ result = _unpack_chat_payload("just plain text", sender="opus", recipient="lumina")
80
+
81
+ assert result["content"] == "just plain text"
82
+ assert result["sender"] == "opus"
83
+ assert result["recipient"] == "lumina"
84
+
85
+ def test_unpack_non_skchat_json(self):
86
+ """JSON without skchat_version falls back to plain text."""
87
+ payload = json.dumps({"type": "other", "data": "test"})
88
+ result = _unpack_chat_payload(payload, sender="a", recipient="b")
89
+
90
+ assert result["sender"] == "a"
91
+ assert "other" in result["content"] or result["content"] == payload
92
+
93
+
94
+ class TestShortTimestamp:
95
+ """Verify timestamp formatting."""
96
+
97
+ def test_format(self):
98
+ """Timestamp is HH:MM:SS format."""
99
+ ts = _short_timestamp()
100
+ assert len(ts) == 8
101
+ assert ts[2] == ":"
102
+ assert ts[5] == ":"
103
+
104
+
105
+ class TestAgentChatSend:
106
+ """Tests for AgentChat.send() without real transport."""
107
+
108
+ def test_send_stores_locally_without_skcomm(self, tmp_home):
109
+ """Message is stored in history even without SKComm."""
110
+ agent = AgentChat(home=tmp_home, identity="opus")
111
+
112
+ mock_history = MagicMock()
113
+ mock_history.store_message.return_value = "mem-abc"
114
+ agent._history = mock_history
115
+ agent._ensure_comm = lambda: False
116
+
117
+ result = agent.send("lumina", "Hello!")
118
+
119
+ assert result["stored"] is True
120
+ assert result["delivered"] is False
121
+ mock_history.store_message.assert_called_once()
122
+
123
+ def test_send_delivers_with_skcomm(self, tmp_home):
124
+ """Message is delivered when SKComm has transports."""
125
+ agent = AgentChat(home=tmp_home, identity="opus")
126
+
127
+ mock_comm = MagicMock()
128
+ mock_report = MagicMock()
129
+ mock_report.delivered = True
130
+ mock_report.successful_transport = "syncthing"
131
+ mock_comm.send.return_value = mock_report
132
+ mock_comm.router.transports = [MagicMock()]
133
+ agent._comm = mock_comm
134
+
135
+ mock_history = MagicMock()
136
+ mock_history.store_message.return_value = "mem-xyz"
137
+ agent._history = mock_history
138
+
139
+ result = agent.send("lumina", "Delivered message")
140
+
141
+ assert result["stored"] is True
142
+ assert result["delivered"] is True
143
+ assert result["transport"] == "syncthing"
144
+
145
+
146
+ class TestAgentChatReceive:
147
+ """Tests for AgentChat.receive()."""
148
+
149
+ def test_receive_empty_inbox(self, tmp_home):
150
+ """Empty inbox returns empty list."""
151
+ agent = AgentChat(home=tmp_home, identity="opus")
152
+
153
+ mock_comm = MagicMock()
154
+ mock_comm.receive.return_value = []
155
+ mock_comm.router.transports = [MagicMock()]
156
+ agent._comm = mock_comm
157
+
158
+ messages = agent.receive()
159
+ assert messages == []
160
+
161
+ def test_receive_with_messages(self, tmp_home):
162
+ """Incoming envelopes are parsed into message dicts."""
163
+ agent = AgentChat(home=tmp_home, identity="opus")
164
+
165
+ payload = json.dumps({
166
+ "skchat_version": "1.0.0",
167
+ "message_id": "msg-1",
168
+ "sender": "lumina",
169
+ "recipient": "opus",
170
+ "content": "Hello from Lumina",
171
+ "thread_id": None,
172
+ "timestamp": "2026-02-24T05:00:00Z",
173
+ })
174
+
175
+ envelope = MagicMock()
176
+ envelope.sender = "lumina"
177
+ envelope.recipient = "opus"
178
+ envelope.payload.content = payload
179
+
180
+ mock_comm = MagicMock()
181
+ mock_comm.receive.return_value = [envelope]
182
+ mock_comm.router.transports = [MagicMock()]
183
+ agent._comm = mock_comm
184
+
185
+ mock_history = MagicMock()
186
+ agent._history = mock_history
187
+
188
+ messages = agent.receive()
189
+
190
+ assert len(messages) == 1
191
+ assert messages[0]["sender"] == "lumina"
192
+ assert messages[0]["content"] == "Hello from Lumina"
193
+
194
+
195
+ class TestAgentChatForward:
196
+ """Tests for AgentChat.forward()."""
197
+
198
+ def test_forward_preserves_original_sender_and_timestamp(self, tmp_home):
199
+ """Forward envelope preserves forwarded_from and forwarded_at."""
200
+ agent = AgentChat(home=tmp_home, identity="opus")
201
+ agent._ensure_comm = lambda: False
202
+
203
+ mock_history = MagicMock()
204
+ agent._history = mock_history
205
+
206
+ original = {
207
+ "message_id": "orig-001",
208
+ "sender": "lumina",
209
+ "recipient": "opus",
210
+ "content": "Deploy the fleet",
211
+ "timestamp": "2026-03-01T12:00:00+00:00",
212
+ "thread_id": None,
213
+ }
214
+
215
+ result = agent.forward(original, "jarvis")
216
+
217
+ assert result["stored"] is True
218
+ assert result["forwarded_id"] is not None
219
+
220
+ stored_msg = mock_history.store_message.call_args[0][0]
221
+ payload = json.loads(stored_msg.content)
222
+ assert payload["skchat_forward"] is True
223
+ assert payload["forwarded_from"] == "lumina"
224
+ assert payload["forwarded_at"] == "2026-03-01T12:00:00+00:00"
225
+ assert payload["original_message_id"] == "orig-001"
226
+ assert payload["sender"] == "opus"
227
+ assert payload["recipient"] == "jarvis"
228
+ assert payload["content"] == "Deploy the fleet"
229
+
230
+ def test_forward_delivers_via_comm(self, tmp_home):
231
+ """Forward delivers via SKComm when transports are available."""
232
+ agent = AgentChat(home=tmp_home, identity="opus")
233
+
234
+ mock_comm = MagicMock()
235
+ mock_report = MagicMock()
236
+ mock_report.delivered = True
237
+ mock_report.successful_transport = "syncthing"
238
+ mock_comm.send.return_value = mock_report
239
+ mock_comm.router.transports = [MagicMock()]
240
+ agent._comm = mock_comm
241
+
242
+ mock_history = MagicMock()
243
+ agent._history = mock_history
244
+
245
+ original = {
246
+ "message_id": "orig-002",
247
+ "sender": "jarvis",
248
+ "content": "Status update",
249
+ "timestamp": "2026-03-01T13:00:00+00:00",
250
+ }
251
+
252
+ result = agent.forward(original, "lumina")
253
+
254
+ assert result["delivered"] is True
255
+ assert result["transport"] == "syncthing"
256
+ assert result["stored"] is True
257
+ assert result["forwarded_id"] is not None
258
+
259
+ call_kwargs = mock_comm.send.call_args[1]
260
+ assert call_kwargs["recipient"] == "lumina"
261
+ fwd_payload = json.loads(call_kwargs["message"])
262
+ assert fwd_payload["skchat_forward"] is True
263
+ assert fwd_payload["forwarded_from"] == "jarvis"
264
+
265
+ def test_forward_without_comm_stores_locally(self, tmp_home):
266
+ """Forward stores locally and returns stored=True when no comm."""
267
+ agent = AgentChat(home=tmp_home, identity="lumina")
268
+ agent._ensure_comm = lambda: False
269
+
270
+ mock_history = MagicMock()
271
+ agent._history = mock_history
272
+
273
+ original = {
274
+ "message_id": "orig-003",
275
+ "sender": "ava",
276
+ "content": "Check in",
277
+ "timestamp": "2026-03-01T14:00:00+00:00",
278
+ }
279
+
280
+ result = agent.forward(original, "opus")
281
+
282
+ assert result["stored"] is True
283
+ assert result["delivered"] is False
284
+ assert result["transport"] is None
285
+ assert result["forwarded_id"] is not None
286
+
287
+ def test_forward_unique_ids_per_call(self, tmp_home):
288
+ """Each forward call generates a distinct forwarded_id."""
289
+ agent = AgentChat(home=tmp_home, identity="opus")
290
+ agent._ensure_comm = lambda: False
291
+
292
+ mock_history = MagicMock()
293
+ agent._history = mock_history
294
+
295
+ original = {"message_id": "m1", "sender": "x", "content": "hi", "timestamp": ""}
296
+
297
+ r1 = agent.forward(original, "peer-a")
298
+ r2 = agent.forward(original, "peer-b")
299
+
300
+ assert r1["forwarded_id"] != r2["forwarded_id"]
301
+
302
+
303
+ class TestCLIChatCommands:
304
+ """Integration tests for the CLI chat commands."""
305
+
306
+ def test_chat_send_help(self):
307
+ """chat send --help works."""
308
+ from skcapstone.cli import main
309
+
310
+ runner = CliRunner()
311
+ result = runner.invoke(main, ["chat", "send", "--help"])
312
+ assert result.exit_code == 0
313
+ assert "PEER" in result.output
314
+ assert "MESSAGE" in result.output
315
+
316
+ def test_chat_inbox_help(self):
317
+ """chat inbox --help works."""
318
+ from skcapstone.cli import main
319
+
320
+ runner = CliRunner()
321
+ result = runner.invoke(main, ["chat", "inbox", "--help"])
322
+ assert result.exit_code == 0
323
+ assert "--poll" in result.output
324
+ assert "--limit" in result.output
325
+
326
+ def test_chat_live_help(self):
327
+ """chat live --help works."""
328
+ from skcapstone.cli import main
329
+
330
+ runner = CliRunner()
331
+ result = runner.invoke(main, ["chat", "live", "--help"])
332
+ assert result.exit_code == 0
333
+ assert "PEER" in result.output
334
+ assert "--poll-interval" in result.output
335
+
336
+ def test_chat_forward_help(self):
337
+ """chat forward --help works and shows PEER and MSG_ID."""
338
+ from skcapstone.cli import main
339
+
340
+ runner = CliRunner()
341
+ result = runner.invoke(main, ["chat", "forward", "--help"])
342
+ assert result.exit_code == 0
343
+ assert "PEER" in result.output
344
+ assert "MSG_ID" in result.output
345
+
346
+ def test_chat_forward_message_not_found(self, tmp_home):
347
+ """chat forward exits non-zero when MSG_ID is not in inbox."""
348
+ from skcapstone.cli import main
349
+ from skcapstone.cli._common import AGENT_HOME
350
+
351
+ runner = CliRunner()
352
+ with patch("skcapstone.cli._common.get_runtime") as mock_rt, \
353
+ patch("skcapstone.chat.AgentChat.get_inbox", return_value=[]):
354
+ mock_rt.return_value.manifest.name = "opus"
355
+ result = runner.invoke(
356
+ main,
357
+ ["chat", "forward", "lumina", "no-such-id", "--home", str(tmp_home)],
358
+ )
359
+ assert result.exit_code != 0
360
+ assert "not found" in result.output.lower() or result.exit_code == 1
361
+
362
+ def test_chat_forward_stored_locally(self, tmp_home):
363
+ """chat forward stores message when SKComm unavailable."""
364
+ from skcapstone.cli import main
365
+
366
+ original = {
367
+ "message_id": "msg-fwd-001",
368
+ "sender": "jarvis",
369
+ "content": "Forward this",
370
+ "timestamp": "2026-03-01T10:00:00+00:00",
371
+ "thread_id": None,
372
+ }
373
+
374
+ runner = CliRunner()
375
+ with patch("skcapstone.cli._common.get_runtime") as mock_rt, \
376
+ patch("skcapstone.chat.AgentChat.get_inbox", return_value=[original]), \
377
+ patch("skcapstone.chat.AgentChat._ensure_comm", return_value=False), \
378
+ patch("skcapstone.chat.AgentChat._ensure_history", return_value=None):
379
+ mock_rt.return_value.manifest.name = "opus"
380
+ result = runner.invoke(
381
+ main,
382
+ ["chat", "forward", "lumina", "msg-fwd-001", "--home", str(tmp_home)],
383
+ )
384
+ # stored=False (no history) and delivered=False → "Failed" or graceful output
385
+ assert result.exit_code == 0 or "failed" in result.output.lower() or "stored" in result.output.lower()
@@ -0,0 +1,271 @@
1
+ """Tests for skcapstone.claude_md — CLAUDE.md auto-regeneration.
2
+
3
+ Covers:
4
+ - generate_claude_md() produces correct markdown structure
5
+ - generate_claude_md() is graceful with an uninitialized home
6
+ - generate_claude_md() embeds recent memories when present
7
+ - write_claude_md() persists content to disk
8
+ - write_claude_md() --backup renames the existing file
9
+ - refresh-context CLI command writes CLAUDE.md
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.claude_md import generate_claude_md, write_claude_md
23
+ from skcapstone.cli import main
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Helpers
28
+ # ---------------------------------------------------------------------------
29
+
30
+ def _init_agent(home: Path, name: str = "md-test") -> None:
31
+ """Minimal agent init needed for context gathering."""
32
+ from skcapstone.pillars.identity import generate_identity
33
+ from skcapstone.pillars.memory import initialize_memory
34
+ from skcapstone.pillars.security import initialize_security
35
+ from skcapstone.pillars.sync import initialize_sync
36
+ from skcapstone.pillars.trust import initialize_trust
37
+
38
+ generate_identity(home, name)
39
+ initialize_memory(home)
40
+ initialize_trust(home)
41
+ initialize_security(home)
42
+ initialize_sync(home)
43
+
44
+ manifest = {
45
+ "name": name,
46
+ "version": "0.1.0",
47
+ "created_at": "2026-01-01T00:00:00Z",
48
+ "connectors": [],
49
+ }
50
+ (home / "manifest.json").write_text(json.dumps(manifest, indent=2))
51
+ (home / "config").mkdir(exist_ok=True)
52
+ (home / "config" / "config.yaml").write_text(yaml.dump({"agent_name": name}))
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # generate_claude_md()
57
+ # ---------------------------------------------------------------------------
58
+
59
+ class TestGenerateClaudeMd:
60
+ """Tests for generate_claude_md()."""
61
+
62
+ def test_returns_string(self, tmp_agent_home: Path):
63
+ """generate_claude_md() returns a non-empty string."""
64
+ result = generate_claude_md(tmp_agent_home)
65
+ assert isinstance(result, str)
66
+ assert len(result) > 0
67
+
68
+ def test_markdown_h1_header(self, tmp_agent_home: Path):
69
+ """Output begins with the expected H1 heading."""
70
+ result = generate_claude_md(tmp_agent_home)
71
+ assert "# SKCapstone Agent Context" in result
72
+
73
+ def test_agent_identity_section(self, tmp_agent_home: Path):
74
+ """Output contains the Agent Identity section."""
75
+ _init_agent(tmp_agent_home, "regen-test")
76
+ result = generate_claude_md(tmp_agent_home)
77
+
78
+ assert "## Agent Identity" in result
79
+ assert "regen-test" in result
80
+
81
+ def test_pillar_status_table(self, tmp_agent_home: Path):
82
+ """Output contains a pillar status table."""
83
+ _init_agent(tmp_agent_home)
84
+ result = generate_claude_md(tmp_agent_home)
85
+
86
+ assert "## Pillar Status" in result
87
+ assert "| Pillar | Status |" in result
88
+ assert "identity" in result
89
+
90
+ def test_coordination_board_section(self, tmp_agent_home: Path):
91
+ """Output contains the Coordination Board section."""
92
+ result = generate_claude_md(tmp_agent_home)
93
+ assert "## Coordination Board" in result
94
+
95
+ def test_cli_reference_section(self, tmp_agent_home: Path):
96
+ """Output contains the CLI Reference section with key commands."""
97
+ result = generate_claude_md(tmp_agent_home)
98
+
99
+ assert "## CLI Reference" in result
100
+ assert "skcapstone status" in result
101
+ assert "skcapstone memory" in result
102
+ assert "skcapstone coord" in result
103
+
104
+ def test_graceful_with_uninitialized_home(self, tmp_agent_home: Path):
105
+ """generate_claude_md() does not raise on an empty home directory."""
106
+ result = generate_claude_md(tmp_agent_home)
107
+ # Must still return valid markdown even without initialized pillars
108
+ assert "# SKCapstone Agent Context" in result
109
+
110
+ def test_embeds_recent_memories(self, tmp_agent_home: Path):
111
+ """Memories stored before generation appear in the output."""
112
+ _init_agent(tmp_agent_home)
113
+ from skcapstone.memory_engine import store
114
+ store(tmp_agent_home, "Penguin test memory for claude-md", tags=["pengu"])
115
+
116
+ result = generate_claude_md(tmp_agent_home, memory_limit=5)
117
+
118
+ assert "Penguin test memory for claude-md" in result
119
+
120
+ def test_memory_limit_applied(self, tmp_agent_home: Path):
121
+ """memory_limit=1 produces output shorter than memory_limit=10."""
122
+ _init_agent(tmp_agent_home)
123
+ from skcapstone.memory_engine import store
124
+ for i in range(8):
125
+ store(tmp_agent_home, f"Memory item {i}", tags=["bulk"])
126
+
127
+ short = generate_claude_md(tmp_agent_home, memory_limit=1)
128
+ long = generate_claude_md(tmp_agent_home, memory_limit=8)
129
+
130
+ assert len(short) <= len(long)
131
+
132
+ def test_consciousness_section_absent_when_disabled(self, tmp_agent_home: Path):
133
+ """When consciousness is disabled, status shows INACTIVE."""
134
+ import urllib.error
135
+ with patch(
136
+ "urllib.request.urlopen",
137
+ side_effect=urllib.error.URLError("connection refused"),
138
+ ):
139
+ result = generate_claude_md(tmp_agent_home)
140
+ # Section may be omitted or show INACTIVE — either is fine
141
+ if "## Consciousness" in result:
142
+ assert "INACTIVE" in result
143
+
144
+ def test_soul_overlay_included(self, tmp_agent_home: Path):
145
+ """Active soul name appears in the output when an overlay is set."""
146
+ _init_agent(tmp_agent_home)
147
+ soul_dir = tmp_agent_home / "soul"
148
+ soul_dir.mkdir(exist_ok=True)
149
+ import json as _json
150
+ (soul_dir / "active.json").write_text(
151
+ _json.dumps({"active_soul": "lumina", "base_soul": "default"}),
152
+ encoding="utf-8",
153
+ )
154
+
155
+ result = generate_claude_md(tmp_agent_home)
156
+ assert "lumina" in result
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # write_claude_md()
161
+ # ---------------------------------------------------------------------------
162
+
163
+ class TestWriteClaudeMd:
164
+ """Tests for write_claude_md()."""
165
+
166
+ def test_creates_file(self, tmp_agent_home: Path, tmp_path: Path):
167
+ """write_claude_md() creates the destination file."""
168
+ dest = tmp_path / "CLAUDE.md"
169
+ write_claude_md(tmp_agent_home, dest)
170
+
171
+ assert dest.exists()
172
+
173
+ def test_file_content_is_markdown(self, tmp_agent_home: Path, tmp_path: Path):
174
+ """Written file has CLAUDE.md markdown structure."""
175
+ dest = tmp_path / "CLAUDE.md"
176
+ write_claude_md(tmp_agent_home, dest)
177
+
178
+ content = dest.read_text(encoding="utf-8")
179
+ assert "# SKCapstone Agent Context" in content
180
+
181
+ def test_backup_renames_existing(self, tmp_agent_home: Path, tmp_path: Path):
182
+ """backup=True renames an existing CLAUDE.md to .bak before writing."""
183
+ dest = tmp_path / "CLAUDE.md"
184
+ dest.write_text("old content", encoding="utf-8")
185
+
186
+ write_claude_md(tmp_agent_home, dest, backup=True)
187
+
188
+ bak = tmp_path / "CLAUDE.md.bak"
189
+ assert bak.exists(), "Expected .bak file to be created"
190
+ assert bak.read_text(encoding="utf-8") == "old content"
191
+ assert dest.exists()
192
+ assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
193
+
194
+ def test_no_backup_overwrites_existing(self, tmp_agent_home: Path, tmp_path: Path):
195
+ """Without backup=True the existing file is silently overwritten."""
196
+ dest = tmp_path / "CLAUDE.md"
197
+ dest.write_text("old content", encoding="utf-8")
198
+
199
+ write_claude_md(tmp_agent_home, dest, backup=False)
200
+
201
+ bak = tmp_path / "CLAUDE.md.bak"
202
+ assert not bak.exists(), "No .bak file expected when backup=False"
203
+ assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
204
+
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # refresh-context CLI
208
+ # ---------------------------------------------------------------------------
209
+
210
+ class TestRefreshContextCli:
211
+ """Tests for `skcapstone refresh-context` CLI command."""
212
+
213
+ def _run(self, *args, home: str | None = None) -> "click.testing.Result":
214
+ runner = CliRunner(mix_stderr=False)
215
+ cmd: list[str] = ["refresh-context"]
216
+ if home:
217
+ cmd += ["--home", home]
218
+ cmd += list(args)
219
+ return runner.invoke(main, cmd, catch_exceptions=False)
220
+
221
+ def test_writes_claude_md_to_dest(self, tmp_agent_home: Path, tmp_path: Path):
222
+ """--dest writes CLAUDE.md to the specified path."""
223
+ dest = tmp_path / "CLAUDE.md"
224
+ result = self._run("--dest", str(dest), home=str(tmp_agent_home))
225
+
226
+ assert result.exit_code == 0, result.output
227
+ assert dest.exists()
228
+ assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
229
+
230
+ def test_output_confirms_written_path(self, tmp_agent_home: Path, tmp_path: Path):
231
+ """CLI prints the path of the written file."""
232
+ dest = tmp_path / "CLAUDE.md"
233
+ result = self._run("--dest", str(dest), home=str(tmp_agent_home))
234
+
235
+ assert "Written" in result.output or str(dest) in result.output
236
+
237
+ def test_backup_flag_creates_bak(self, tmp_agent_home: Path, tmp_path: Path):
238
+ """--backup renames the existing file before writing."""
239
+ dest = tmp_path / "CLAUDE.md"
240
+ dest.write_text("old content", encoding="utf-8")
241
+
242
+ result = self._run("--backup", "--dest", str(dest), home=str(tmp_agent_home))
243
+
244
+ assert result.exit_code == 0, result.output
245
+ bak = tmp_path / "CLAUDE.md.bak"
246
+ assert bak.exists()
247
+ assert bak.read_text(encoding="utf-8") == "old content"
248
+
249
+ def test_dest_dir_writes_claude_md_inside(self, tmp_agent_home: Path, tmp_path: Path):
250
+ """Passing a directory as --dest writes CLAUDE.md inside it."""
251
+ result = self._run("--dest", str(tmp_path), home=str(tmp_agent_home))
252
+
253
+ assert result.exit_code == 0, result.output
254
+ assert (tmp_path / "CLAUDE.md").exists()
255
+
256
+ def test_falls_back_to_cwd_without_git(self, tmp_agent_home: Path, tmp_path: Path):
257
+ """Without --dest and outside a git repo, writes to cwd/CLAUDE.md."""
258
+ runner = CliRunner(mix_stderr=False)
259
+ written: list[Path] = []
260
+
261
+ with runner.isolated_filesystem(temp_dir=tmp_path) as iso_dir:
262
+ with patch("subprocess.run", side_effect=FileNotFoundError("git not found")):
263
+ result = runner.invoke(
264
+ main,
265
+ ["refresh-context", "--home", str(tmp_agent_home)],
266
+ catch_exceptions=False,
267
+ )
268
+ written.append(Path(iso_dir) / "CLAUDE.md")
269
+
270
+ assert result.exit_code == 0, result.output
271
+ assert written[0].exists(), f"Expected CLAUDE.md at {written[0]}"