@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.
- package/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /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
|
|
24
|
-
ExecStop
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
33
|
-
skips
|
|
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,
|
|
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
|
|
166
|
-
|
|
167
|
-
loop.
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
402
|
+
# Verify SKComms.send was called with the response
|
|
403
403
|
response_calls = [
|
|
404
|
-
call for call in
|
|
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"
|
|
409
|
-
f"All calls: {
|
|
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
|
|
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
|
|
431
|
-
"""Loop processes correctly even when no
|
|
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
|
|
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
|
|
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 →
|
|
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
|
|
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,
|
|
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
|
-
|
|
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"
|
|
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,
|
|
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
|
|
779
|
+
processes the file → response captured on mock SKComms.
|
|
780
780
|
|
|
781
|
-
This covers task [c9e7b9d8]: send
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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, (
|
package/tests/test_agent_card.py
CHANGED
|
@@ -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/
|
|
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"
|
package/tests/test_backup.py
CHANGED
|
@@ -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
|
-
|
|
240
|
+
import skcapstone
|
|
241
|
+
assert m.version == skcapstone.__version__
|
|
241
242
|
assert m.files == {}
|
|
242
243
|
assert m.total_size == 0
|
|
243
244
|
|
package/tests/test_chat.py
CHANGED
|
@@ -105,8 +105,8 @@ class TestShortTimestamp:
|
|
|
105
105
|
class TestAgentChatSend:
|
|
106
106
|
"""Tests for AgentChat.send() without real transport."""
|
|
107
107
|
|
|
108
|
-
def
|
|
109
|
-
"""Message is stored in history even without
|
|
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
|
|
124
|
-
"""Message is delivered when
|
|
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
|
|
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
|
|
363
|
+
"""chat forward stores message when SKComms unavailable."""
|
|
364
364
|
from skcapstone.cli import main
|
|
365
365
|
|
|
366
366
|
original = {
|
package/tests/test_claude_md.py
CHANGED
|
@@ -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(
|
|
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(
|
|
258
|
+
runner = CliRunner()
|
|
259
259
|
written: list[Path] = []
|
|
260
260
|
|
|
261
261
|
with runner.isolated_filesystem(temp_dir=tmp_path) as iso_dir:
|
package/tests/test_cli_skills.py
CHANGED
|
@@ -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
|
-
|
|
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 ==
|
|
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 ==
|
|
119
|
-
assert "
|
|
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(
|
|
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="
|
|
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="
|
|
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", "
|
|
119
|
+
result = runner.invoke(main, ["test", "--package", "skcomms"])
|
|
120
120
|
assert result.exit_code == 0
|
|
121
121
|
|
|
122
122
|
|