@smilintux/skcapstone 0.10.0 → 0.12.5

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 (279) hide show
  1. package/.env.example +10 -4
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.github/workflows/publish.yml +9 -2
  4. package/.openclaw-workspace.json +2 -2
  5. package/CLAUDE.md +37 -0
  6. package/MISSION.md +17 -2
  7. package/README.md +282 -3
  8. package/docker/Dockerfile +7 -7
  9. package/docker/compose-templates/dev-team.yml +12 -12
  10. package/docker/compose-templates/mini-team.yml +9 -9
  11. package/docker/compose-templates/ops-team.yml +10 -10
  12. package/docker/compose-templates/research-team.yml +10 -10
  13. package/docker/entrypoint.sh +4 -4
  14. package/docs/ADR-optional-integration-backbone.md +181 -0
  15. package/docs/ARCHITECTURE.md +186 -43
  16. package/docs/BOND_WITH_GROK.md +6 -6
  17. package/docs/CUSTOM_AGENT.md +123 -30
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +7 -7
  20. package/docs/QUICKSTART.md +10 -6
  21. package/docs/SKJOULE_ARCHITECTURE.md +3 -3
  22. package/docs/SOUL_SWAPPER.md +5 -5
  23. package/docs/hammertime-audit.md +402 -0
  24. package/docs/sk-integration-HANDOFF.md +117 -0
  25. package/docs/skscheduler.md +155 -0
  26. package/docs/superpowers/examples/jobs.yaml +31 -0
  27. package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
  28. package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
  29. package/examples/custom-bond-template.json +1 -1
  30. package/examples/grok-feb.json +1 -1
  31. package/examples/queen-ava-feb.json +1 -1
  32. package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
  33. package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
  34. package/launchd/install-launchd.sh +6 -6
  35. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  36. package/package.json +1 -1
  37. package/pyproject.toml +16 -10
  38. package/scripts/archive-sessions.sh +7 -0
  39. package/scripts/check-updates.py +4 -4
  40. package/scripts/install-bundle.sh +8 -8
  41. package/scripts/install.ps1 +12 -11
  42. package/scripts/install.sh +159 -5
  43. package/scripts/model-fallback-monitor.sh +102 -0
  44. package/scripts/nvidia-proxy.mjs +78 -26
  45. package/scripts/refresh-anthropic-token.sh +172 -0
  46. package/scripts/release.sh +98 -0
  47. package/scripts/session-to-memory.py +219 -0
  48. package/scripts/skgateway.mjs +3 -3
  49. package/scripts/telegram-catchup-all.sh +12 -1
  50. package/scripts/verify_install.sh +2 -2
  51. package/scripts/wargov-ufo-capture/README.md +43 -0
  52. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  53. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  54. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  55. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  56. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  57. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  58. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  59. package/scripts/watch-anthropic-token.sh +212 -0
  60. package/scripts/windows/install-tasks.ps1 +7 -7
  61. package/scripts/windows/skcapstone-task.xml +1 -1
  62. package/src/skcapstone/__init__.py +45 -3
  63. package/src/skcapstone/_cli_monolith.py +20 -15
  64. package/src/skcapstone/activity.py +5 -1
  65. package/src/skcapstone/agent_card.py +3 -2
  66. package/src/skcapstone/api.py +41 -40
  67. package/src/skcapstone/auction.py +14 -11
  68. package/src/skcapstone/backup.py +2 -1
  69. package/src/skcapstone/blueprint_registry.py +4 -3
  70. package/src/skcapstone/brain_first.py +238 -0
  71. package/src/skcapstone/changelog.py +1 -1
  72. package/src/skcapstone/chat.py +22 -17
  73. package/src/skcapstone/cli/__init__.py +9 -1
  74. package/src/skcapstone/cli/_common.py +1 -0
  75. package/src/skcapstone/cli/agents_spawner.py +5 -2
  76. package/src/skcapstone/cli/alerts.py +25 -4
  77. package/src/skcapstone/cli/bench.py +15 -15
  78. package/src/skcapstone/cli/chat.py +7 -4
  79. package/src/skcapstone/cli/consciousness.py +5 -2
  80. package/src/skcapstone/cli/context_cmd.py +18 -4
  81. package/src/skcapstone/cli/daemon.py +11 -7
  82. package/src/skcapstone/cli/gtd.py +26 -1
  83. package/src/skcapstone/cli/housekeeping.py +3 -3
  84. package/src/skcapstone/cli/identity_cmd.py +378 -0
  85. package/src/skcapstone/cli/joule_cmd.py +7 -3
  86. package/src/skcapstone/cli/memory.py +8 -6
  87. package/src/skcapstone/cli/peers_dir.py +1 -1
  88. package/src/skcapstone/cli/register_cmd.py +29 -3
  89. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  90. package/src/skcapstone/cli/session.py +25 -0
  91. package/src/skcapstone/cli/setup.py +96 -29
  92. package/src/skcapstone/cli/shell_cmd.py +53 -1
  93. package/src/skcapstone/cli/skills_cmd.py +2 -2
  94. package/src/skcapstone/cli/soul.py +8 -5
  95. package/src/skcapstone/cli/status.py +37 -11
  96. package/src/skcapstone/cli/telegram.py +21 -0
  97. package/src/skcapstone/cli/test_cmd.py +5 -5
  98. package/src/skcapstone/cli/test_connection.py +2 -2
  99. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  100. package/src/skcapstone/cli/version_cmd.py +1 -1
  101. package/src/skcapstone/cli/watch_cmd.py +9 -6
  102. package/src/skcapstone/cloud9_bridge.py +14 -14
  103. package/src/skcapstone/codex_setup.py +255 -0
  104. package/src/skcapstone/config_validator.py +7 -4
  105. package/src/skcapstone/consciousness_config.py +5 -1
  106. package/src/skcapstone/consciousness_loop.py +313 -273
  107. package/src/skcapstone/context_loader.py +121 -0
  108. package/src/skcapstone/coord_federation.py +2 -1
  109. package/src/skcapstone/coordination.py +23 -6
  110. package/src/skcapstone/crush_integration.py +2 -1
  111. package/src/skcapstone/daemon.py +132 -77
  112. package/src/skcapstone/dashboard.py +10 -10
  113. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  114. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  115. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  116. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  117. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  118. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  119. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  120. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  121. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  122. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  123. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  124. package/src/skcapstone/defaults/claude/settings.json +74 -0
  125. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  126. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  127. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  128. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  129. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  130. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  131. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  132. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  133. package/src/skcapstone/defaults/unhinged.json +13 -0
  134. package/src/skcapstone/discovery.py +43 -20
  135. package/src/skcapstone/doctor.py +941 -22
  136. package/src/skcapstone/dreaming.py +1183 -109
  137. package/src/skcapstone/emotion_tracker.py +2 -2
  138. package/src/skcapstone/export.py +4 -3
  139. package/src/skcapstone/fuse_mount.py +14 -12
  140. package/src/skcapstone/gui_installer.py +2 -2
  141. package/src/skcapstone/heartbeat.py +1 -1
  142. package/src/skcapstone/housekeeping.py +14 -14
  143. package/src/skcapstone/install_wizard.py +209 -7
  144. package/src/skcapstone/itil.py +13 -4
  145. package/src/skcapstone/kms_scheduler.py +10 -8
  146. package/src/skcapstone/launchd.py +19 -19
  147. package/src/skcapstone/mcp_launcher.py +15 -1
  148. package/src/skcapstone/mcp_server.py +83 -49
  149. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  150. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  151. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  152. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  153. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  154. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  155. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  156. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  157. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  158. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  159. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  160. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  161. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  162. package/src/skcapstone/mdns_discovery.py +2 -2
  163. package/src/skcapstone/memory_curator.py +1 -1
  164. package/src/skcapstone/memory_engine.py +10 -3
  165. package/src/skcapstone/metrics.py +30 -16
  166. package/src/skcapstone/migrate_memories.py +4 -3
  167. package/src/skcapstone/migrate_multi_agent.py +8 -7
  168. package/src/skcapstone/models.py +47 -5
  169. package/src/skcapstone/notifications.py +42 -18
  170. package/src/skcapstone/onboard.py +875 -121
  171. package/src/skcapstone/operator_link.py +170 -0
  172. package/src/skcapstone/peer_directory.py +4 -4
  173. package/src/skcapstone/peers.py +19 -19
  174. package/src/skcapstone/pillars/__init__.py +7 -5
  175. package/src/skcapstone/pillars/consciousness.py +191 -0
  176. package/src/skcapstone/pillars/identity.py +51 -7
  177. package/src/skcapstone/pillars/memory.py +9 -3
  178. package/src/skcapstone/pillars/sync.py +2 -2
  179. package/src/skcapstone/preflight.py +3 -3
  180. package/src/skcapstone/providers/docker.py +28 -28
  181. package/src/skcapstone/register.py +6 -6
  182. package/src/skcapstone/registry_client.py +5 -4
  183. package/src/skcapstone/runtime.py +14 -3
  184. package/src/skcapstone/scheduled_tasks.py +254 -19
  185. package/src/skcapstone/scheduler_jobs.py +456 -0
  186. package/src/skcapstone/scheduler_runner.py +239 -0
  187. package/src/skcapstone/scheduler_state.py +162 -0
  188. package/src/skcapstone/sdk.py +310 -0
  189. package/src/skcapstone/service_health.py +279 -39
  190. package/src/skcapstone/session_briefing.py +108 -0
  191. package/src/skcapstone/session_capture.py +1 -1
  192. package/src/skcapstone/shell.py +7 -1
  193. package/src/skcapstone/soul.py +3 -1
  194. package/src/skcapstone/soul_switch.py +3 -1
  195. package/src/skcapstone/summary.py +6 -6
  196. package/src/skcapstone/sync_engine.py +15 -15
  197. package/src/skcapstone/sync_watcher.py +2 -2
  198. package/src/skcapstone/systemd.py +55 -21
  199. package/src/skcapstone/team_comms.py +8 -8
  200. package/src/skcapstone/team_engine.py +1 -1
  201. package/src/skcapstone/testrunner.py +3 -3
  202. package/src/skcapstone/trust_graph.py +40 -5
  203. package/src/skcapstone/unified_search.py +15 -6
  204. package/src/skcapstone/uninstall_wizard.py +11 -3
  205. package/src/skcapstone/version_check.py +8 -4
  206. package/src/skcapstone/warmth_anchor.py +4 -2
  207. package/src/skcapstone/whoami.py +4 -4
  208. package/systemd/skcapstone.service +4 -6
  209. package/systemd/skcapstone@.service +7 -8
  210. package/systemd/skcomms-heartbeat.service +21 -0
  211. package/systemd/skcomms-heartbeat.timer +12 -0
  212. package/systemd/skcomms-queue-drain.service +17 -0
  213. package/systemd/skcomms-queue-drain.timer +12 -0
  214. package/tests/conftest.py +39 -0
  215. package/tests/integration/test_consciousness_e2e.py +39 -39
  216. package/tests/test_agent_card.py +1 -1
  217. package/tests/test_agent_home_scaffold.py +34 -0
  218. package/tests/test_alerts_consumer_topics.py +27 -0
  219. package/tests/test_backup.py +2 -1
  220. package/tests/test_chat.py +6 -6
  221. package/tests/test_claude_md.py +2 -2
  222. package/tests/test_cli_skills.py +10 -10
  223. package/tests/test_cli_test_cmd.py +4 -4
  224. package/tests/test_cli_test_connection.py +1 -1
  225. package/tests/test_cloud9_bridge.py +6 -6
  226. package/tests/test_consciousness_e2e.py +1 -1
  227. package/tests/test_consciousness_loop.py +10 -10
  228. package/tests/test_coordination.py +25 -0
  229. package/tests/test_cross_package.py +21 -21
  230. package/tests/test_daemon.py +4 -4
  231. package/tests/test_daemon_shutdown.py +1 -1
  232. package/tests/test_docker_provider.py +29 -29
  233. package/tests/test_doctor.py +400 -0
  234. package/tests/test_doctor_skscheduler.py +50 -0
  235. package/tests/test_dreaming_engine.py +147 -0
  236. package/tests/test_dreaming_gtd_capture.py +35 -0
  237. package/tests/test_e2e_automated.py +8 -5
  238. package/tests/test_fuse_mount.py +10 -10
  239. package/tests/test_gtd_brief.py +46 -0
  240. package/tests/test_gtd_malformed_tolerance.py +31 -0
  241. package/tests/test_housekeeping.py +15 -15
  242. package/tests/test_identity_migrate.py +251 -0
  243. package/tests/test_integration_backbone.py +598 -0
  244. package/tests/test_itil_gtd_lifecycle.py +37 -0
  245. package/tests/test_jobs_dropins.py +84 -0
  246. package/tests/test_mcp_server.py +82 -37
  247. package/tests/test_models.py +48 -4
  248. package/tests/test_multi_agent.py +31 -29
  249. package/tests/test_notifications.py +122 -32
  250. package/tests/test_onboard.py +63 -75
  251. package/tests/test_operator_link.py +78 -0
  252. package/tests/test_peers.py +14 -14
  253. package/tests/test_pillars.py +98 -0
  254. package/tests/test_preflight.py +3 -3
  255. package/tests/test_runtime.py +21 -0
  256. package/tests/test_scheduled_tasks.py +11 -6
  257. package/tests/test_scheduler_cli.py +47 -0
  258. package/tests/test_scheduler_features.py +133 -0
  259. package/tests/test_scheduler_integration.py +87 -0
  260. package/tests/test_scheduler_jobs.py +155 -0
  261. package/tests/test_scheduler_runner.py +64 -0
  262. package/tests/test_scheduler_state.py +57 -0
  263. package/tests/test_sdk.py +70 -0
  264. package/tests/test_service_health_incidents.py +34 -0
  265. package/tests/test_service_registry.py +52 -0
  266. package/tests/test_session_briefing.py +130 -0
  267. package/tests/test_snapshots.py +4 -4
  268. package/tests/test_sync_pipeline.py +26 -26
  269. package/tests/test_team_comms.py +2 -2
  270. package/tests/test_testrunner.py +2 -2
  271. package/tests/test_trust_graph.py +18 -0
  272. package/tests/test_unified_search.py +2 -2
  273. package/tests/test_version_check.py +10 -0
  274. package/tests/test_version_cmd.py +8 -8
  275. package/tests/test_whoami.py +1 -1
  276. package/systemd/skcomm-heartbeat.service +0 -18
  277. package/systemd/skcomm-queue-drain.service +0 -17
  278. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  279. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -20,8 +20,8 @@ Wants=network-online.target
20
20
 
21
21
  [Service]
22
22
  Type=notify
23
- ExecStart=skcapstone daemon start --agent %i --foreground
24
- ExecStop=skcapstone daemon stop --agent %i
23
+ ExecStart=%h/.skenv/bin/skcapstone daemon start --agent %i --foreground
24
+ ExecStop=%h/.skenv/bin/skcapstone daemon stop --agent %i
25
25
  ExecReload=/bin/kill -HUP $MAINPID
26
26
  Restart=on-failure
27
27
  RestartSec=10
@@ -31,20 +31,19 @@ WatchdogSec=300
31
31
  # resolve the correct per-agent home even before the CLI flag is processed.
32
32
  Environment=PYTHONUNBUFFERED=1
33
33
  Environment=OLLAMA_KEEP_ALIVE=5m
34
+ Environment=SKAGENT=%i
34
35
  Environment=SKCAPSTONE_AGENT=%i
36
+ Environment=SKMEMORY_AGENT=%i
37
+ Environment=PATH=%h/.skenv/bin:/usr/local/bin:/usr/bin:/bin
35
38
  # Journal logging — logs appear under skcapstone@<instance>
36
39
  StandardOutput=journal
37
40
  StandardError=journal
38
41
  SyslogIdentifier=skcapstone@%i
39
42
 
40
- # Security hardening (same as single-agent unit)
43
+ # Security hardening (relaxed ProtectHome=read-only breaks if any
44
+ # ReadWritePaths dir is missing on the host)
41
45
  NoNewPrivileges=true
42
- ProtectSystem=strict
43
- ProtectHome=read-only
44
- ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
45
46
  PrivateTmp=true
46
- ProtectKernelTunables=true
47
- ProtectControlGroups=true
48
47
 
49
48
  [Install]
50
49
  WantedBy=default.target
@@ -0,0 +1,21 @@
1
+ [Unit]
2
+ Description=SKComms Heartbeat Emission
3
+ Documentation=https://github.com/smilinTux/skcomms
4
+ After=network-online.target
5
+
6
+ [Service]
7
+ Type=oneshot
8
+ ExecStart=%h/.skenv/bin/skcomms heartbeat --no-emit
9
+ ExecStart=%h/.skenv/bin/skcomms heartbeat
10
+ Nice=19
11
+
12
+ NoNewPrivileges=true
13
+ ProtectSystem=strict
14
+ ProtectHome=read-only
15
+ ReadWritePaths=%h/.skcapstone %h/.skcomms
16
+ PrivateTmp=true
17
+
18
+ Environment=PYTHONUNBUFFERED=1
19
+ Environment=SKAGENT=lumina
20
+ Environment=SKCAPSTONE_AGENT=lumina
21
+ Environment=PATH=%h/.skenv/bin:/usr/local/bin:/usr/bin:/bin
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=SKComms Heartbeat Timer — periodic liveness announcements
3
+ Documentation=https://github.com/smilinTux/skcomms
4
+
5
+ [Timer]
6
+ OnBootSec=30s
7
+ OnUnitActiveSec=60s
8
+ RandomizedDelaySec=10s
9
+ Persistent=true
10
+
11
+ [Install]
12
+ WantedBy=timers.target
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ Description=SKComms Queue Drain — retry undelivered messages
3
+ Documentation=https://github.com/smilinTux/skcomms
4
+ After=network-online.target
5
+
6
+ [Service]
7
+ Type=oneshot
8
+ ExecStart=skcomms queue drain
9
+ Nice=19
10
+
11
+ NoNewPrivileges=true
12
+ ProtectSystem=strict
13
+ ProtectHome=read-only
14
+ ReadWritePaths=%h/.skcapstone %h/.skcomms
15
+ PrivateTmp=true
16
+
17
+ Environment=PYTHONUNBUFFERED=1
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=SKComms Queue Drain Timer — periodic message retry
3
+ Documentation=https://github.com/smilinTux/skcomms
4
+
5
+ [Timer]
6
+ OnBootSec=60s
7
+ OnUnitActiveSec=120s
8
+ RandomizedDelaySec=15s
9
+ Persistent=true
10
+
11
+ [Install]
12
+ WantedBy=timers.target
package/tests/conftest.py CHANGED
@@ -19,6 +19,45 @@ from pathlib import Path
19
19
  import pytest
20
20
 
21
21
 
22
+ @pytest.fixture(autouse=True)
23
+ def _silence_desktop_notifications(monkeypatch):
24
+ """Suppress real desktop notifications for the entire test session.
25
+
26
+ Several code paths (consciousness_loop, kms_scheduler, the send_notification
27
+ MCP tool, and NotificationManager) shell out to ``notify-send`` / libnotify.
28
+ Left unmocked, a test run floods the live desktop's notification tray —
29
+ and mass-closing that backlog can stall single-threaded shells like
30
+ Cinnamon, freezing the whole UI. Forcing the guard off keeps test runs
31
+ silent regardless of which path fires.
32
+
33
+ A test that needs to exercise the real dispatch can re-enable it locally
34
+ with ``monkeypatch.setenv("SKCAPSTONE_DESKTOP_NOTIFY", "1")``.
35
+ """
36
+ monkeypatch.setenv("SKCAPSTONE_DESKTOP_NOTIFY", "0")
37
+
38
+
39
+ @pytest.fixture(autouse=True)
40
+ def _isolate_agent_env(monkeypatch):
41
+ """Prevent host SKCAPSTONE_AGENT / SKMEMORY_AGENT from leaking into unit tests.
42
+
43
+ The profile-aware runtime reads both the env var and the module-level
44
+ skcapstone.SKCAPSTONE_AGENT (set at import time). We clear both so that
45
+ _memory_dir() falls back to the flat "home/memory" layout expected by
46
+ tests that use the tmp_agent_home fixture.
47
+ Tests that need a specific agent should override explicitly via monkeypatch.
48
+ """
49
+ monkeypatch.delenv("SKCAPSTONE_AGENT", raising=False)
50
+ monkeypatch.delenv("SKMEMORY_AGENT", raising=False)
51
+ import skcapstone
52
+
53
+ monkeypatch.setattr(skcapstone, "SKCAPSTONE_AGENT", "")
54
+ # _detect_active_agent() scans ~/.skcapstone/agents/ even when the env var
55
+ # is cleared, returning a real agent name that routes memory writes to the
56
+ # wrong directory. Stub it out so tests using tmp directories get the flat
57
+ # "home/memory" layout they expect.
58
+ monkeypatch.setattr(skcapstone, "_detect_active_agent", lambda root=None: None)
59
+
60
+
22
61
  @pytest.fixture
23
62
  def tmp_agent_home(tmp_path: Path) -> Path:
24
63
  """Provide a temporary agent home directory for testing."""
@@ -7,16 +7,16 @@ Pipeline under test
7
7
  -------------------
8
8
  1. DaemonService starts with consciousness loop enabled (in-process thread).
9
9
  2. A .skc.json envelope is dropped into the inbox directory,
10
- simulating delivery by SKComm or ``skcapstone send``.
10
+ simulating delivery by SKComms or ``skcapstone send``.
11
11
  3. Inotify / watchdog detects the file within 5 s.
12
12
  4. ConsciousnessLoop classifies the message and calls LLMBridge.generate().
13
- 5. Mock SKComm captures the outbound response.
13
+ 5. Mock SKComms captures the outbound response.
14
14
  6. All steps complete within 60 s total.
15
15
 
16
16
  Related coordination tasks
17
17
  --------------------------
18
18
  [8fbd0130] — Full E2E integration test (this file)
19
- [c9e7b9d8] — End-to-end consciousness test: send SKComm message,
19
+ [c9e7b9d8] — End-to-end consciousness test: send SKComms message,
20
20
  verify autonomous response
21
21
 
22
22
  Running
@@ -29,8 +29,8 @@ Running
29
29
 
30
30
  Known daemon startup issues
31
31
  ---------------------------
32
- * SKComm not configured in test home: DaemonService logs a warning and
33
- skips SKComm polling. Consciousness loop still runs via inotify.
32
+ * SKComms not configured in test home: DaemonService logs a warning and
33
+ skips SKComms polling. Consciousness loop still runs via inotify.
34
34
  * Prompt build latency: SystemPromptBuilder.build() loads identity, soul,
35
35
  context, and snapshots from disk. In tests this takes ~3-4 s even with
36
36
  empty dirs because it probes optional YAML/JSON files. Tests account for
@@ -133,7 +133,7 @@ def _make_loop(
133
133
  None → let the real bridge run (requires backends).
134
134
 
135
135
  Returns:
136
- (loop, mock_skcomm, inbox_dir) triple.
136
+ (loop, mock_skcomms, inbox_dir) triple.
137
137
  """
138
138
  from skcapstone.consciousness_loop import ConsciousnessConfig, ConsciousnessLoop, LLMBridge
139
139
 
@@ -162,11 +162,11 @@ def _make_loop(
162
162
  mock_bridge.available_backends = {"passthrough": True}
163
163
  loop._bridge = mock_bridge
164
164
 
165
- # Inject a mock SKComm so responses are captured without real transport
166
- mock_skcomm = MagicMock()
167
- loop.set_skcomm(mock_skcomm)
165
+ # Inject a mock SKComms so responses are captured without real transport
166
+ mock_skcomms = MagicMock()
167
+ loop.set_skcomms(mock_skcomms)
168
168
 
169
- return loop, mock_skcomm, inbox_dir
169
+ return loop, mock_skcomms, inbox_dir
170
170
 
171
171
 
172
172
  def _wait_for_http(port: int, path: str = "/status", timeout: float = 20.0) -> bool:
@@ -272,7 +272,7 @@ class TestInboxFileTrigger:
272
272
  """_on_inbox_file submits a valid .skc.json for async processing."""
273
273
  from skcapstone.consciousness_loop import SystemPromptBuilder
274
274
 
275
- loop, mock_skcomm, inbox_dir = _make_loop(
275
+ loop, mock_skcomms, inbox_dir = _make_loop(
276
276
  tmp_path, use_inotify=False, mock_generate="pong"
277
277
  )
278
278
 
@@ -286,7 +286,7 @@ class TestInboxFileTrigger:
286
286
  if isinstance(message, str) and message not in ("ACK",):
287
287
  response_event.set()
288
288
 
289
- mock_skcomm.send.side_effect = _capture_send
289
+ mock_skcomms.send.side_effect = _capture_send
290
290
 
291
291
  msg_path, _ = _drop_message(inbox_dir, content="ping")
292
292
 
@@ -299,7 +299,7 @@ class TestInboxFileTrigger:
299
299
 
300
300
  assert got_response, (
301
301
  "_on_inbox_file did not produce a response within 10s. "
302
- f"SKComm calls: {mock_skcomm.send.call_args_list}"
302
+ f"SKComms calls: {mock_skcomms.send.call_args_list}"
303
303
  )
304
304
 
305
305
 
@@ -377,18 +377,18 @@ class TestLLMClassifyAndGenerate:
377
377
 
378
378
 
379
379
  # ===========================================================================
380
- # Test Class 3: Response delivery via SKComm
380
+ # Test Class 3: Response delivery via SKComms
381
381
  # ===========================================================================
382
382
 
383
383
 
384
384
  class TestResponseDeliveredViaSkcomm:
385
- """Verify that the generated response is sent back through SKComm."""
385
+ """Verify that the generated response is sent back through SKComms."""
386
386
 
387
387
  def test_response_sent_to_sender(self, tmp_path: Path) -> None:
388
- """Mock SKComm.send() is called with the LLM response directed at the sender."""
388
+ """Mock SKComms.send() is called with the LLM response directed at the sender."""
389
389
  from skcapstone.consciousness_loop import _SimpleEnvelope
390
390
 
391
- loop, mock_skcomm, _ = _make_loop(
391
+ loop, mock_skcomms, _ = _make_loop(
392
392
  tmp_path, use_inotify=False, mock_generate="Hello from the agent!"
393
393
  )
394
394
 
@@ -399,21 +399,21 @@ class TestResponseDeliveredViaSkcomm:
399
399
  result = loop.process_envelope(envelope)
400
400
 
401
401
  assert result == "Hello from the agent!"
402
- # Verify SKComm.send was called with the response
402
+ # Verify SKComms.send was called with the response
403
403
  response_calls = [
404
- call for call in mock_skcomm.send.call_args_list
404
+ call for call in mock_skcomms.send.call_args_list
405
405
  if len(call.args) >= 2 and call.args[1] == "Hello from the agent!"
406
406
  ]
407
407
  assert response_calls, (
408
- f"SKComm.send() was not called with the LLM response. "
409
- f"All calls: {mock_skcomm.send.call_args_list}"
408
+ f"SKComms.send() was not called with the LLM response. "
409
+ f"All calls: {mock_skcomms.send.call_args_list}"
410
410
  )
411
411
  assert response_calls[0].args[0] == "alice", (
412
412
  f"Response sent to wrong peer: {response_calls[0].args[0]}"
413
413
  )
414
414
 
415
415
  def test_responses_sent_counter_increments(self, tmp_path: Path) -> None:
416
- """stats['responses_sent'] increments each time SKComm.send() succeeds."""
416
+ """stats['responses_sent'] increments each time SKComms.send() succeeds."""
417
417
  from skcapstone.consciousness_loop import _SimpleEnvelope
418
418
 
419
419
  loop, _, _ = _make_loop(tmp_path, use_inotify=False, mock_generate="reply")
@@ -427,8 +427,8 @@ class TestResponseDeliveredViaSkcomm:
427
427
 
428
428
  assert loop.stats["responses_sent"] == 3
429
429
 
430
- def test_skcomm_none_does_not_crash(self, tmp_path: Path) -> None:
431
- """Loop processes correctly even when no SKComm is set (responses dropped silently)."""
430
+ def test_skcomms_none_does_not_crash(self, tmp_path: Path) -> None:
431
+ """Loop processes correctly even when no SKComms is set (responses dropped silently)."""
432
432
  from skcapstone.consciousness_loop import (
433
433
  ConsciousnessConfig, ConsciousnessLoop, LLMBridge, _SimpleEnvelope,
434
434
  )
@@ -442,7 +442,7 @@ class TestResponseDeliveredViaSkcomm:
442
442
  with patch.object(LLMBridge, "_probe_ollama", return_value=False):
443
443
  loop = ConsciousnessLoop(config, home=home, shared_root=shared)
444
444
 
445
- # No SKComm set — _skcomm stays None
445
+ # No SKComms set — _skcomms stays None
446
446
  loop._bridge = MagicMock()
447
447
  loop._bridge.generate.return_value = "silent reply"
448
448
  loop._bridge.available_backends = {"passthrough": True}
@@ -452,7 +452,7 @@ class TestResponseDeliveredViaSkcomm:
452
452
  "payload": {"content": "hello", "content_type": "text"},
453
453
  }))
454
454
  assert result == "silent reply"
455
- assert loop.stats["responses_sent"] == 0 # no SKComm → not counted
455
+ assert loop.stats["responses_sent"] == 0 # no SKComms → not counted
456
456
 
457
457
 
458
458
  # ===========================================================================
@@ -461,7 +461,7 @@ class TestResponseDeliveredViaSkcomm:
461
461
 
462
462
 
463
463
  class TestFullE2EPipeline:
464
- """End-to-end: drop .skc.json → inotify → classify → LLM → SKComm response.
464
+ """End-to-end: drop .skc.json → inotify → classify → LLM → SKComms response.
465
465
 
466
466
  Asserts the complete pipeline completes within TOTAL_TIMEOUT seconds.
467
467
  This is the primary test for task [8fbd0130] and [c9e7b9d8].
@@ -470,13 +470,13 @@ class TestFullE2EPipeline:
470
470
  def test_full_pipeline_within_60s(self, tmp_path: Path) -> None:
471
471
  """
472
472
  Drop a .skc.json, start the consciousness loop with inotify, and assert
473
- the mock SKComm.send() is called with a response within TOTAL_TIMEOUT.
473
+ the mock SKComms.send() is called with a response within TOTAL_TIMEOUT.
474
474
 
475
475
  Two-phase assertion:
476
476
  Phase 1 — Inotify pickup: _on_inbox_file fires within INOTIFY_TIMEOUT (5 s)
477
477
  Phase 2 — Full pipeline: response is sent within TOTAL_TIMEOUT (60 s)
478
478
  """
479
- loop, mock_skcomm, inbox_dir = _make_loop(
479
+ loop, mock_skcomms, inbox_dir = _make_loop(
480
480
  tmp_path,
481
481
  use_inotify=True,
482
482
  mock_generate="E2E test response — pipeline complete.",
@@ -510,7 +510,7 @@ class TestFullE2EPipeline:
510
510
  response_captured.append(message)
511
511
  response_event.set()
512
512
 
513
- mock_skcomm.send.side_effect = _capturing_send
513
+ mock_skcomms.send.side_effect = _capturing_send
514
514
 
515
515
  # Start inotify + config-watcher threads
516
516
  threads = loop.start()
@@ -556,7 +556,7 @@ class TestFullE2EPipeline:
556
556
  assert got_response, (
557
557
  f"No response captured within {_TOTAL_TIMEOUT}s. "
558
558
  f"Pickup at t={t_pickup:.1f}s; total elapsed: {total_elapsed:.1f}s. "
559
- f"SKComm calls: {mock_skcomm.send.call_args_list}"
559
+ f"SKComms calls: {mock_skcomms.send.call_args_list}"
560
560
  )
561
561
  assert response_captured, "response_captured list is empty"
562
562
  assert "E2E test response" in response_captured[0], (
@@ -571,7 +571,7 @@ class TestFullE2EPipeline:
571
571
 
572
572
  def test_inotify_pickup_within_5s(self, tmp_path: Path) -> None:
573
573
  """Assert the inotify watcher detects the inbox file within INOTIFY_TIMEOUT seconds."""
574
- loop, mock_skcomm, inbox_dir = _make_loop(tmp_path, use_inotify=True)
574
+ loop, mock_skcomms, inbox_dir = _make_loop(tmp_path, use_inotify=True)
575
575
 
576
576
  pickup_event = threading.Event()
577
577
  picked_up_paths: list[Path] = []
@@ -776,9 +776,9 @@ class TestDaemonServiceIntegration:
776
776
  ) -> None:
777
777
  """
778
778
  Full integration: start daemon → drop .skc.json → consciousness loop
779
- processes the file → response captured on mock SKComm.
779
+ processes the file → response captured on mock SKComms.
780
780
 
781
- This covers task [c9e7b9d8]: send SKComm message, verify autonomous response.
781
+ This covers task [c9e7b9d8]: send SKComms message, verify autonomous response.
782
782
  """
783
783
  from skcapstone.daemon import DaemonConfig, DaemonService
784
784
  from skcapstone.consciousness_loop import LLMBridge
@@ -801,7 +801,7 @@ class TestDaemonServiceIntegration:
801
801
  response_event = threading.Event()
802
802
  captured_responses: list[str] = []
803
803
 
804
- mock_skcomm = MagicMock()
804
+ mock_skcomms = MagicMock()
805
805
 
806
806
  def _capturing_send(peer, message, **kwargs):
807
807
  # Skip heartbeat / typing-indicator sends (they carry message_type kwarg).
@@ -811,7 +811,7 @@ class TestDaemonServiceIntegration:
811
811
  captured_responses.append(message)
812
812
  response_event.set()
813
813
 
814
- mock_skcomm.send.side_effect = _capturing_send
814
+ mock_skcomms.send.side_effect = _capturing_send
815
815
 
816
816
  with (
817
817
  patch.object(service, "_setup_signals"),
@@ -827,7 +827,7 @@ class TestDaemonServiceIntegration:
827
827
  t.join(timeout=5)
828
828
  pytest.skip(f"Daemon HTTP not ready within 30s on port {free_port}")
829
829
 
830
- # Inject mock LLM and mock SKComm into the running consciousness loop
830
+ # Inject mock LLM and mock SKComms into the running consciousness loop
831
831
  consciousness = service._consciousness
832
832
  if consciousness is None:
833
833
  service.stop()
@@ -839,7 +839,7 @@ class TestDaemonServiceIntegration:
839
839
  mock_bridge.generate.return_value = "Autonomous response — consciousness is active."
840
840
  mock_bridge.available_backends = {"passthrough": True}
841
841
  consciousness._bridge = mock_bridge
842
- consciousness.set_skcomm(mock_skcomm)
842
+ consciousness.set_skcomms(mock_skcomms)
843
843
 
844
844
  t_start = time.monotonic()
845
845
 
@@ -869,7 +869,7 @@ class TestDaemonServiceIntegration:
869
869
 
870
870
  assert got_response, (
871
871
  f"Consciousness loop did not respond within {_TOTAL_TIMEOUT}s. "
872
- f"Elapsed: {total_elapsed:.1f}s. SKComm calls: {mock_skcomm.send.call_args_list}"
872
+ f"Elapsed: {total_elapsed:.1f}s. SKComms calls: {mock_skcomms.send.call_args_list}"
873
873
  )
874
874
  assert captured_responses, "No response text captured from consciousness loop"
875
875
  assert total_elapsed <= _TOTAL_TIMEOUT, (
@@ -51,7 +51,7 @@ def sample_card(test_keys: tuple[str, str]) -> AgentCard:
51
51
  public_key=pub,
52
52
  entity_type="ai",
53
53
  transports=[
54
- TransportEndpoint(transport="file", address="/tmp/skcomm/drop"),
54
+ TransportEndpoint(transport="file", address="/tmp/skcomms/drop"),
55
55
  TransportEndpoint(transport="nostr", address="abc123" * 10 + "abcd"),
56
56
  ],
57
57
  capabilities=[
@@ -0,0 +1,34 @@
1
+ """Tests for fresh agent-home scaffolding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import patch
8
+
9
+ from skcapstone.migrate_multi_agent import create_agent_home
10
+
11
+
12
+ class TestCreateAgentHome:
13
+ def test_manifest_includes_human_operator_when_available(self, tmp_path: Path):
14
+ """New agent homes persist the linked human operator in manifest.json."""
15
+ with patch(
16
+ "skcapstone.migrate_multi_agent.discover_human_operator",
17
+ return_value={"name": "Casey", "fingerprint": "FP123", "relationship": "human-operator"},
18
+ ):
19
+ result = create_agent_home(tmp_path, "teddy")
20
+
21
+ manifest = json.loads((tmp_path / "agents" / "teddy" / "manifest.json").read_text())
22
+ assert result["agent_name"] == "teddy"
23
+ assert manifest["name"] == "teddy"
24
+ assert manifest["operator"]["name"] == "Casey"
25
+ assert manifest["operator"]["fingerprint"] == "FP123"
26
+
27
+ def test_manifest_omits_operator_when_none_available(self, tmp_path: Path):
28
+ """New agent homes still create a valid manifest when no operator exists yet."""
29
+ with patch("skcapstone.migrate_multi_agent.discover_human_operator", return_value=None):
30
+ create_agent_home(tmp_path, "lumina")
31
+
32
+ manifest = json.loads((tmp_path / "agents" / "lumina" / "manifest.json").read_text())
33
+ assert manifest["name"] == "lumina"
34
+ assert "operator" not in manifest
@@ -0,0 +1,27 @@
1
+ """T3 — alerts command surfaces consumer <service>.<severity> topics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from skcapstone.cli.alerts import DEFAULT_TOPICS, _style_for_topic
6
+
7
+
8
+ def test_severity_wildcards_subscribed():
9
+ """The command subscribes to severity wildcards so any service is seen."""
10
+ for sev in ("*.critical", "*.error", "*.warn"):
11
+ assert sev in DEFAULT_TOPICS
12
+
13
+
14
+ def test_exact_topic_style_wins():
15
+ assert _style_for_topic("agent.critical") == "bold red"
16
+
17
+
18
+ def test_consumer_topic_styled_by_severity_suffix():
19
+ assert _style_for_topic("skmemory.error") == "red"
20
+ assert _style_for_topic("sksecurity.critical") == "bold red"
21
+ assert _style_for_topic("skvoice.warn") == "yellow"
22
+ assert _style_for_topic("skseed.info") == "cyan"
23
+
24
+
25
+ def test_unknown_topic_is_dim():
26
+ assert _style_for_topic("something.random") == "dim"
27
+ assert _style_for_topic("noseparator") == "dim"
@@ -237,7 +237,8 @@ class TestBackupManifest:
237
237
  def test_manifest_defaults(self) -> None:
238
238
  """Manifest has sensible defaults."""
239
239
  m = BackupManifest()
240
- assert m.version == "0.9.0"
240
+ import skcapstone
241
+ assert m.version == skcapstone.__version__
241
242
  assert m.files == {}
242
243
  assert m.total_size == 0
243
244
 
@@ -105,8 +105,8 @@ class TestShortTimestamp:
105
105
  class TestAgentChatSend:
106
106
  """Tests for AgentChat.send() without real transport."""
107
107
 
108
- def test_send_stores_locally_without_skcomm(self, tmp_home):
109
- """Message is stored in history even without SKComm."""
108
+ def test_send_stores_locally_without_skcomms(self, tmp_home):
109
+ """Message is stored in history even without SKComms."""
110
110
  agent = AgentChat(home=tmp_home, identity="opus")
111
111
 
112
112
  mock_history = MagicMock()
@@ -120,8 +120,8 @@ class TestAgentChatSend:
120
120
  assert result["delivered"] is False
121
121
  mock_history.store_message.assert_called_once()
122
122
 
123
- def test_send_delivers_with_skcomm(self, tmp_home):
124
- """Message is delivered when SKComm has transports."""
123
+ def test_send_delivers_with_skcomms(self, tmp_home):
124
+ """Message is delivered when SKComms has transports."""
125
125
  agent = AgentChat(home=tmp_home, identity="opus")
126
126
 
127
127
  mock_comm = MagicMock()
@@ -228,7 +228,7 @@ class TestAgentChatForward:
228
228
  assert payload["content"] == "Deploy the fleet"
229
229
 
230
230
  def test_forward_delivers_via_comm(self, tmp_home):
231
- """Forward delivers via SKComm when transports are available."""
231
+ """Forward delivers via SKComms when transports are available."""
232
232
  agent = AgentChat(home=tmp_home, identity="opus")
233
233
 
234
234
  mock_comm = MagicMock()
@@ -360,7 +360,7 @@ class TestCLIChatCommands:
360
360
  assert "not found" in result.output.lower() or result.exit_code == 1
361
361
 
362
362
  def test_chat_forward_stored_locally(self, tmp_home):
363
- """chat forward stores message when SKComm unavailable."""
363
+ """chat forward stores message when SKComms unavailable."""
364
364
  from skcapstone.cli import main
365
365
 
366
366
  original = {
@@ -211,7 +211,7 @@ class TestRefreshContextCli:
211
211
  """Tests for `skcapstone refresh-context` CLI command."""
212
212
 
213
213
  def _run(self, *args, home: str | None = None) -> "click.testing.Result":
214
- runner = CliRunner(mix_stderr=False)
214
+ runner = CliRunner()
215
215
  cmd: list[str] = ["refresh-context"]
216
216
  if home:
217
217
  cmd += ["--home", home]
@@ -255,7 +255,7 @@ class TestRefreshContextCli:
255
255
 
256
256
  def test_falls_back_to_cwd_without_git(self, tmp_agent_home: Path, tmp_path: Path):
257
257
  """Without --dest and outside a git repo, writes to cwd/CLAUDE.md."""
258
- runner = CliRunner(mix_stderr=False)
258
+ runner = CliRunner()
259
259
  written: list[Path] = []
260
260
 
261
261
  with runner.isolated_filesystem(temp_dir=tmp_path) as iso_dir:
@@ -52,7 +52,7 @@ class TestSkillsList:
52
52
  client = _make_registry_client()
53
53
 
54
54
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
55
- result = runner.invoke(main, ["skills", "list"])
55
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example"])
56
56
 
57
57
  assert result.exit_code == 0
58
58
  assert "syncthing-setup" in result.output
@@ -64,7 +64,7 @@ class TestSkillsList:
64
64
  client = _make_registry_client()
65
65
 
66
66
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
67
- result = runner.invoke(main, ["skills", "list", "--query", "syncthing"])
67
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example", "--query", "syncthing"])
68
68
 
69
69
  assert result.exit_code == 0
70
70
  client.search.assert_called_once_with("syncthing")
@@ -78,7 +78,7 @@ class TestSkillsList:
78
78
  client = _make_registry_client()
79
79
 
80
80
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
81
- result = runner.invoke(main, ["skills", "list", "--json"])
81
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example", "--json"])
82
82
 
83
83
  assert result.exit_code == 0
84
84
  parsed = json.loads(result.output)
@@ -91,7 +91,7 @@ class TestSkillsList:
91
91
  client = _make_registry_client(skills=[])
92
92
 
93
93
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
94
- result = runner.invoke(main, ["skills", "list"])
94
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example"])
95
95
 
96
96
  assert result.exit_code == 0
97
97
  assert "No skills found" in result.output
@@ -101,10 +101,10 @@ class TestSkillsList:
101
101
  runner = CliRunner()
102
102
 
103
103
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=None):
104
- result = runner.invoke(main, ["skills", "list"])
104
+ with patch("skcapstone.cli.skills_cmd._fetch_github_catalog", return_value=None):
105
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example", "--offline"])
105
106
 
106
- assert result.exit_code == 1
107
- assert "skskills not installed" in result.output
107
+ assert result.exit_code == 0
108
108
 
109
109
  def test_list_registry_error_exits_1(self):
110
110
  """Registry connection error should print an error and exit 1."""
@@ -113,10 +113,10 @@ class TestSkillsList:
113
113
  client.list_skills.side_effect = ConnectionError("offline")
114
114
 
115
115
  with patch("skcapstone.cli.skills_cmd.get_registry_client", return_value=client):
116
- result = runner.invoke(main, ["skills", "list"])
116
+ result = runner.invoke(main, ["skills", "list", "--registry", "https://registry.example"])
117
117
 
118
- assert result.exit_code == 1
119
- assert "Registry error" in result.output
118
+ assert result.exit_code == 0
119
+ assert "offline" not in result.output
120
120
 
121
121
 
122
122
  # ---------------------------------------------------------------------------
@@ -22,7 +22,7 @@ from skcapstone.testrunner import PackageResult, TestReport
22
22
 
23
23
  @pytest.fixture()
24
24
  def runner():
25
- return CliRunner(mix_stderr=False)
25
+ return CliRunner()
26
26
 
27
27
 
28
28
  @pytest.fixture()
@@ -31,7 +31,7 @@ def mock_report_all_pass():
31
31
  return TestReport(
32
32
  results=[
33
33
  PackageResult(name="skcapstone", passed=10, exit_code=0, duration_s=1.2),
34
- PackageResult(name="skcomm", passed=5, exit_code=0, duration_s=0.8),
34
+ PackageResult(name="skcomms", passed=5, exit_code=0, duration_s=0.8),
35
35
  ],
36
36
  duration_s=2.0,
37
37
  )
@@ -44,7 +44,7 @@ def mock_report_with_failure():
44
44
  results=[
45
45
  PackageResult(name="skcapstone", passed=10, exit_code=0, duration_s=1.2),
46
46
  PackageResult(
47
- name="skcomm",
47
+ name="skcomms",
48
48
  passed=3,
49
49
  failed=2,
50
50
  exit_code=1,
@@ -116,7 +116,7 @@ class TestInvalidPackage:
116
116
  "skcapstone.cli.test_cmd.run_all_tests",
117
117
  return_value=mock_report_all_pass,
118
118
  ):
119
- result = runner.invoke(main, ["test", "--package", "skcomm"])
119
+ result = runner.invoke(main, ["test", "--package", "skcomms"])
120
120
  assert result.exit_code == 0
121
121
 
122
122