@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.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""Tests for session_recorder and session_replayer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# SessionRecorder
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestSessionRecorder:
|
|
17
|
+
def test_auto_session_file_created(self, tmp_agent_home: Path) -> None:
|
|
18
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
19
|
+
|
|
20
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
21
|
+
assert rec.auto_path is not None
|
|
22
|
+
assert rec.auto_path.exists()
|
|
23
|
+
rec.close()
|
|
24
|
+
|
|
25
|
+
def test_record_writes_jsonl_line(self, tmp_agent_home: Path) -> None:
|
|
26
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
27
|
+
|
|
28
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
29
|
+
rec.record(
|
|
30
|
+
tool="memory_store",
|
|
31
|
+
arguments={"content": "hello", "tags": ["test"]},
|
|
32
|
+
result=[{"type": "text", "text": '{"ok": true}'}],
|
|
33
|
+
duration_ms=12,
|
|
34
|
+
)
|
|
35
|
+
rec.close()
|
|
36
|
+
|
|
37
|
+
lines = rec.auto_path.read_text(encoding="utf-8").strip().splitlines()
|
|
38
|
+
assert len(lines) == 1
|
|
39
|
+
entry = json.loads(lines[0])
|
|
40
|
+
assert entry["tool"] == "memory_store"
|
|
41
|
+
assert entry["duration_ms"] == 12
|
|
42
|
+
assert entry["arguments"]["content"] == "hello"
|
|
43
|
+
assert "ts" in entry
|
|
44
|
+
|
|
45
|
+
def test_explicit_output_file(self, tmp_agent_home: Path, tmp_path: Path) -> None:
|
|
46
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
47
|
+
|
|
48
|
+
out = tmp_path / "explicit.jsonl"
|
|
49
|
+
rec = SessionRecorder.start_session(tmp_agent_home, output_path=out)
|
|
50
|
+
rec.record("coord_status", {}, [{"type": "text", "text": "ok"}], 5)
|
|
51
|
+
rec.close()
|
|
52
|
+
|
|
53
|
+
assert out.exists()
|
|
54
|
+
entry = json.loads(out.read_text(encoding="utf-8").strip())
|
|
55
|
+
assert entry["tool"] == "coord_status"
|
|
56
|
+
|
|
57
|
+
def test_env_var_output_file(
|
|
58
|
+
self, tmp_agent_home: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
59
|
+
) -> None:
|
|
60
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
61
|
+
|
|
62
|
+
out = tmp_path / "env_output.jsonl"
|
|
63
|
+
monkeypatch.setenv("SKCAPSTONE_RECORD_FILE", str(out))
|
|
64
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
65
|
+
rec.record("agent_status", {}, [], 1)
|
|
66
|
+
rec.close()
|
|
67
|
+
|
|
68
|
+
assert out.exists()
|
|
69
|
+
entry = json.loads(out.read_text(encoding="utf-8").strip())
|
|
70
|
+
assert entry["tool"] == "agent_status"
|
|
71
|
+
|
|
72
|
+
def test_auto_rotate_keeps_last_five(self, tmp_agent_home: Path) -> None:
|
|
73
|
+
from skcapstone.session_recorder import SessionRecorder, list_sessions
|
|
74
|
+
|
|
75
|
+
# Create 7 sessions
|
|
76
|
+
for _ in range(7):
|
|
77
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
78
|
+
rec.record("agent_status", {}, [], 1)
|
|
79
|
+
rec.close()
|
|
80
|
+
|
|
81
|
+
remaining = list_sessions(tmp_agent_home)
|
|
82
|
+
assert len(remaining) == 5
|
|
83
|
+
|
|
84
|
+
def test_count_tracks_records(self, tmp_agent_home: Path) -> None:
|
|
85
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
86
|
+
|
|
87
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
88
|
+
assert rec.count == 0
|
|
89
|
+
for i in range(3):
|
|
90
|
+
rec.record(f"tool_{i}", {}, [], i)
|
|
91
|
+
assert rec.count == 3
|
|
92
|
+
rec.close()
|
|
93
|
+
|
|
94
|
+
def test_multiple_records_multiple_lines(self, tmp_agent_home: Path) -> None:
|
|
95
|
+
from skcapstone.session_recorder import SessionRecorder, load_session
|
|
96
|
+
|
|
97
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
98
|
+
tools = ["memory_store", "coord_status", "agent_status"]
|
|
99
|
+
for t in tools:
|
|
100
|
+
rec.record(t, {}, [], 1)
|
|
101
|
+
rec.close()
|
|
102
|
+
|
|
103
|
+
entries = load_session(rec.auto_path)
|
|
104
|
+
assert [e["tool"] for e in entries] == tools
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# load_session / list_sessions helpers
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestSessionHelpers:
|
|
113
|
+
def test_load_session_empty_file(self, tmp_path: Path) -> None:
|
|
114
|
+
from skcapstone.session_recorder import load_session
|
|
115
|
+
|
|
116
|
+
f = tmp_path / "empty.jsonl"
|
|
117
|
+
f.write_text("")
|
|
118
|
+
assert load_session(f) == []
|
|
119
|
+
|
|
120
|
+
def test_load_session_skips_bad_lines(self, tmp_path: Path) -> None:
|
|
121
|
+
from skcapstone.session_recorder import load_session
|
|
122
|
+
|
|
123
|
+
f = tmp_path / "bad.jsonl"
|
|
124
|
+
f.write_text(
|
|
125
|
+
'{"tool": "ok", "arguments": {}, "result": [], "duration_ms": 1, "ts": "x"}\n'
|
|
126
|
+
"not-json\n"
|
|
127
|
+
'{"tool": "ok2", "arguments": {}, "result": [], "duration_ms": 2, "ts": "y"}\n'
|
|
128
|
+
)
|
|
129
|
+
entries = load_session(f)
|
|
130
|
+
assert len(entries) == 2
|
|
131
|
+
assert entries[0]["tool"] == "ok"
|
|
132
|
+
assert entries[1]["tool"] == "ok2"
|
|
133
|
+
|
|
134
|
+
def test_list_sessions_newest_first(self, tmp_agent_home: Path) -> None:
|
|
135
|
+
import time
|
|
136
|
+
from skcapstone.session_recorder import SessionRecorder, list_sessions
|
|
137
|
+
|
|
138
|
+
recs = []
|
|
139
|
+
for i in range(3):
|
|
140
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
141
|
+
rec.record("t", {}, [], i)
|
|
142
|
+
rec.close()
|
|
143
|
+
recs.append(rec.auto_path)
|
|
144
|
+
time.sleep(0.01) # ensure distinct mtime
|
|
145
|
+
|
|
146
|
+
listed = list_sessions(tmp_agent_home)
|
|
147
|
+
assert len(listed) == 3
|
|
148
|
+
# newest first
|
|
149
|
+
assert listed[0].stat().st_mtime >= listed[1].stat().st_mtime
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# SessionReplayer — dry-run
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestSessionReplayerDryRun:
|
|
158
|
+
def _write_session(self, path: Path, entries: list[dict]) -> None:
|
|
159
|
+
with path.open("w") as fh:
|
|
160
|
+
for e in entries:
|
|
161
|
+
fh.write(json.dumps(e) + "\n")
|
|
162
|
+
|
|
163
|
+
def test_dry_run_yields_results(self, tmp_path: Path) -> None:
|
|
164
|
+
from skcapstone.session_replayer import SessionReplayer
|
|
165
|
+
|
|
166
|
+
f = tmp_path / "s.jsonl"
|
|
167
|
+
self._write_session(f, [
|
|
168
|
+
{"ts": "t", "tool": "agent_status", "arguments": {}, "result": [], "duration_ms": 5},
|
|
169
|
+
{"ts": "t", "tool": "coord_status", "arguments": {}, "result": [], "duration_ms": 3},
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
replayer = SessionReplayer(f, dry_run=True)
|
|
173
|
+
results = list(replayer.replay())
|
|
174
|
+
assert len(results) == 2
|
|
175
|
+
assert results[0].tool == "agent_status"
|
|
176
|
+
assert results[1].tool == "coord_status"
|
|
177
|
+
|
|
178
|
+
def test_dry_run_no_execution(self, tmp_path: Path) -> None:
|
|
179
|
+
from skcapstone.session_replayer import SessionReplayer
|
|
180
|
+
|
|
181
|
+
f = tmp_path / "s.jsonl"
|
|
182
|
+
self._write_session(f, [
|
|
183
|
+
{"ts": "t", "tool": "agent_status", "arguments": {}, "result": [], "duration_ms": 5},
|
|
184
|
+
])
|
|
185
|
+
|
|
186
|
+
replayer = SessionReplayer(f, dry_run=True)
|
|
187
|
+
results = list(replayer.replay())
|
|
188
|
+
r = results[0]
|
|
189
|
+
assert r.replayed_result is None
|
|
190
|
+
assert r.match is None
|
|
191
|
+
assert r.duration_ms == 0
|
|
192
|
+
assert r.error is None
|
|
193
|
+
|
|
194
|
+
def test_dry_run_empty_file(self, tmp_path: Path) -> None:
|
|
195
|
+
from skcapstone.session_replayer import SessionReplayer
|
|
196
|
+
|
|
197
|
+
f = tmp_path / "empty.jsonl"
|
|
198
|
+
f.write_text("")
|
|
199
|
+
|
|
200
|
+
results = list(SessionReplayer(f, dry_run=True).replay())
|
|
201
|
+
assert results == []
|
|
202
|
+
|
|
203
|
+
def test_dry_run_arguments_preserved(self, tmp_path: Path) -> None:
|
|
204
|
+
from skcapstone.session_replayer import SessionReplayer
|
|
205
|
+
|
|
206
|
+
args = {"content": "test memory", "tags": ["debug"]}
|
|
207
|
+
f = tmp_path / "s.jsonl"
|
|
208
|
+
self._write_session(f, [
|
|
209
|
+
{"ts": "t", "tool": "memory_store", "arguments": args,
|
|
210
|
+
"result": [{"type": "text", "text": "{}"}], "duration_ms": 10},
|
|
211
|
+
])
|
|
212
|
+
|
|
213
|
+
results = list(SessionReplayer(f, dry_run=True).replay())
|
|
214
|
+
assert results[0].arguments == args
|
|
215
|
+
assert results[0].recorded_result == [{"type": "text", "text": "{}"}]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
# MockMCPServer
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestMockMCPServer:
|
|
224
|
+
def test_call_returns_matching_recorded_result(self, tmp_path: Path) -> None:
|
|
225
|
+
from skcapstone.session_replayer import MockMCPServer
|
|
226
|
+
|
|
227
|
+
f = tmp_path / "s.jsonl"
|
|
228
|
+
result_data = [{"type": "text", "text": '{"status": "ok"}'}]
|
|
229
|
+
f.write_text(
|
|
230
|
+
json.dumps({
|
|
231
|
+
"ts": "t", "tool": "agent_status", "arguments": {},
|
|
232
|
+
"result": result_data, "duration_ms": 5,
|
|
233
|
+
}) + "\n"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
mock = MockMCPServer(f)
|
|
237
|
+
result = mock.call("agent_status", {})
|
|
238
|
+
assert result == result_data
|
|
239
|
+
|
|
240
|
+
def test_call_returns_none_for_unknown_tool(self, tmp_path: Path) -> None:
|
|
241
|
+
from skcapstone.session_replayer import MockMCPServer
|
|
242
|
+
|
|
243
|
+
f = tmp_path / "s.jsonl"
|
|
244
|
+
f.write_text(
|
|
245
|
+
json.dumps({
|
|
246
|
+
"ts": "t", "tool": "memory_store", "arguments": {},
|
|
247
|
+
"result": [], "duration_ms": 1,
|
|
248
|
+
}) + "\n"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
mock = MockMCPServer(f)
|
|
252
|
+
result = mock.call("no_such_tool", {})
|
|
253
|
+
assert result is None
|
|
254
|
+
|
|
255
|
+
def test_sequential_calls_advance_index(self, tmp_path: Path) -> None:
|
|
256
|
+
from skcapstone.session_replayer import MockMCPServer
|
|
257
|
+
|
|
258
|
+
f = tmp_path / "s.jsonl"
|
|
259
|
+
lines = [
|
|
260
|
+
{"ts": "t", "tool": "tool_a", "arguments": {}, "result": [{"t": "a"}], "duration_ms": 1},
|
|
261
|
+
{"ts": "t", "tool": "tool_b", "arguments": {}, "result": [{"t": "b"}], "duration_ms": 2},
|
|
262
|
+
]
|
|
263
|
+
f.write_text("\n".join(json.dumps(l) for l in lines) + "\n")
|
|
264
|
+
|
|
265
|
+
mock = MockMCPServer(f)
|
|
266
|
+
r1 = mock.call("tool_a", {})
|
|
267
|
+
r2 = mock.call("tool_b", {})
|
|
268
|
+
assert r1 == [{"t": "a"}]
|
|
269
|
+
assert r2 == [{"t": "b"}]
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
# CLI smoke tests
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class TestCLIRecordCommands:
|
|
278
|
+
def test_sessions_list_empty(
|
|
279
|
+
self, tmp_agent_home: Path, monkeypatch: pytest.MonkeyPatch
|
|
280
|
+
) -> None:
|
|
281
|
+
from click.testing import CliRunner
|
|
282
|
+
from skcapstone.cli import main
|
|
283
|
+
|
|
284
|
+
monkeypatch.setenv("SKCAPSTONE_ROOT", str(tmp_agent_home.parent))
|
|
285
|
+
runner = CliRunner()
|
|
286
|
+
result = runner.invoke(main, ["sessions", "list", "--home", str(tmp_agent_home)])
|
|
287
|
+
assert result.exit_code == 0
|
|
288
|
+
assert "No sessions" in result.output
|
|
289
|
+
|
|
290
|
+
def test_sessions_list_with_sessions(
|
|
291
|
+
self, tmp_agent_home: Path, monkeypatch: pytest.MonkeyPatch
|
|
292
|
+
) -> None:
|
|
293
|
+
from click.testing import CliRunner
|
|
294
|
+
from skcapstone.cli import main
|
|
295
|
+
from skcapstone.session_recorder import SessionRecorder
|
|
296
|
+
|
|
297
|
+
rec = SessionRecorder.start_session(tmp_agent_home)
|
|
298
|
+
rec.record("agent_status", {}, [], 5)
|
|
299
|
+
rec.close()
|
|
300
|
+
|
|
301
|
+
runner = CliRunner()
|
|
302
|
+
result = runner.invoke(main, ["sessions", "list", "--home", str(tmp_agent_home)])
|
|
303
|
+
assert result.exit_code == 0
|
|
304
|
+
assert "session-" in result.output
|
|
305
|
+
|
|
306
|
+
def test_replay_dry_run_cli(
|
|
307
|
+
self, tmp_agent_home: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
308
|
+
) -> None:
|
|
309
|
+
import json as _json
|
|
310
|
+
from click.testing import CliRunner
|
|
311
|
+
from skcapstone.cli import main
|
|
312
|
+
|
|
313
|
+
f = tmp_path / "test_session.jsonl"
|
|
314
|
+
f.write_text(
|
|
315
|
+
_json.dumps({
|
|
316
|
+
"ts": "2026-01-01T00:00:00+00:00",
|
|
317
|
+
"tool": "agent_status",
|
|
318
|
+
"arguments": {},
|
|
319
|
+
"result": [],
|
|
320
|
+
"duration_ms": 3,
|
|
321
|
+
}) + "\n"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
runner = CliRunner()
|
|
325
|
+
result = runner.invoke(
|
|
326
|
+
main,
|
|
327
|
+
["replay", str(f), "--dry-run", "--home", str(tmp_agent_home)],
|
|
328
|
+
)
|
|
329
|
+
assert result.exit_code == 0
|
|
330
|
+
assert "agent_status" in result.output
|
|
331
|
+
assert "SKIP" in result.output or "dry" in result.output.lower()
|
|
332
|
+
|
|
333
|
+
def test_replay_json_format(
|
|
334
|
+
self, tmp_agent_home: Path, tmp_path: Path
|
|
335
|
+
) -> None:
|
|
336
|
+
import json as _json
|
|
337
|
+
from click.testing import CliRunner
|
|
338
|
+
from skcapstone.cli import main
|
|
339
|
+
|
|
340
|
+
f = tmp_path / "test_session.jsonl"
|
|
341
|
+
f.write_text(
|
|
342
|
+
_json.dumps({
|
|
343
|
+
"ts": "2026-01-01T00:00:00+00:00",
|
|
344
|
+
"tool": "coord_status",
|
|
345
|
+
"arguments": {},
|
|
346
|
+
"result": [{"type": "text", "text": "{}"}],
|
|
347
|
+
"duration_ms": 7,
|
|
348
|
+
}) + "\n"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
runner = CliRunner()
|
|
352
|
+
result = runner.invoke(
|
|
353
|
+
main,
|
|
354
|
+
["replay", str(f), "--dry-run", "--format", "json",
|
|
355
|
+
"--home", str(tmp_agent_home)],
|
|
356
|
+
)
|
|
357
|
+
assert result.exit_code == 0
|
|
358
|
+
rows = _json.loads(result.output)
|
|
359
|
+
assert len(rows) == 1
|
|
360
|
+
assert rows[0]["tool"] == "coord_status"
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Tests for the session skills bridge — wiring SKSkills into agent runtime sessions."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
from typing import Dict
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def skskills_home(tmp_path: Path) -> Path:
|
|
15
|
+
"""Set up a mock SKSkills home with installed skills."""
|
|
16
|
+
home = tmp_path / "skskills"
|
|
17
|
+
installed = home / "installed"
|
|
18
|
+
agents = home / "agents"
|
|
19
|
+
|
|
20
|
+
# Global skill
|
|
21
|
+
global_skill = installed / "syncthing-setup"
|
|
22
|
+
global_skill.mkdir(parents=True)
|
|
23
|
+
(global_skill / "skill.yaml").write_text(dedent("""\
|
|
24
|
+
name: syncthing-setup
|
|
25
|
+
version: "1.0.0"
|
|
26
|
+
description: Auto-configure Syncthing
|
|
27
|
+
author:
|
|
28
|
+
name: tester
|
|
29
|
+
"""))
|
|
30
|
+
|
|
31
|
+
# Agent-specific skill
|
|
32
|
+
agent_skill = agents / "jarvis" / "code-review"
|
|
33
|
+
agent_skill.mkdir(parents=True)
|
|
34
|
+
(agent_skill / "skill.yaml").write_text(dedent("""\
|
|
35
|
+
name: code-review
|
|
36
|
+
version: "0.1.0"
|
|
37
|
+
description: Code review skill
|
|
38
|
+
author:
|
|
39
|
+
name: tester
|
|
40
|
+
"""))
|
|
41
|
+
|
|
42
|
+
return home
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def repo_root(tmp_path: Path) -> Path:
|
|
47
|
+
"""Set up a mock repo root."""
|
|
48
|
+
root = tmp_path / "repo"
|
|
49
|
+
root.mkdir(parents=True)
|
|
50
|
+
return root
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestResolveSkillPaths:
|
|
54
|
+
"""Test skill resolution with SKSkills registry integration."""
|
|
55
|
+
|
|
56
|
+
def test_resolve_from_skskills_global(self, skskills_home: Path):
|
|
57
|
+
"""Should resolve skill from global SKSkills registry."""
|
|
58
|
+
from skcapstone.session_skills import resolve_skill_paths_with_skskills
|
|
59
|
+
|
|
60
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
61
|
+
result = resolve_skill_paths_with_skskills(["syncthing-setup"])
|
|
62
|
+
assert len(result) == 1
|
|
63
|
+
assert "syncthing-setup" in result[0]
|
|
64
|
+
assert Path(result[0]).exists()
|
|
65
|
+
|
|
66
|
+
def test_resolve_from_skskills_agent(self, skskills_home: Path):
|
|
67
|
+
"""Should resolve agent-specific skills first."""
|
|
68
|
+
from skcapstone.session_skills import resolve_skill_paths_with_skskills
|
|
69
|
+
|
|
70
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
71
|
+
result = resolve_skill_paths_with_skskills(
|
|
72
|
+
["code-review"], agent="jarvis"
|
|
73
|
+
)
|
|
74
|
+
assert len(result) == 1
|
|
75
|
+
assert "jarvis" in result[0]
|
|
76
|
+
assert "code-review" in result[0]
|
|
77
|
+
|
|
78
|
+
def test_resolve_passthrough_when_not_in_skskills(self, skskills_home: Path, repo_root: Path):
|
|
79
|
+
"""Skills not in SKSkills should pass through as-is."""
|
|
80
|
+
from skcapstone.session_skills import resolve_skill_paths_with_skskills
|
|
81
|
+
|
|
82
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
83
|
+
result = resolve_skill_paths_with_skskills(
|
|
84
|
+
["legacy"], repo_root=repo_root
|
|
85
|
+
)
|
|
86
|
+
assert result == ["legacy"]
|
|
87
|
+
|
|
88
|
+
def test_resolve_passthrough_for_unknown(self, skskills_home: Path):
|
|
89
|
+
"""Unknown skills should pass through as-is."""
|
|
90
|
+
from skcapstone.session_skills import resolve_skill_paths_with_skskills
|
|
91
|
+
|
|
92
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
93
|
+
result = resolve_skill_paths_with_skskills(["unknown-skill"])
|
|
94
|
+
assert result == ["unknown-skill"]
|
|
95
|
+
|
|
96
|
+
def test_resolve_absolute_path(self, skskills_home: Path, tmp_path: Path):
|
|
97
|
+
"""Absolute paths should be used as-is if they exist."""
|
|
98
|
+
from skcapstone.session_skills import resolve_skill_paths_with_skskills
|
|
99
|
+
|
|
100
|
+
abs_path = tmp_path / "my-skill"
|
|
101
|
+
abs_path.mkdir()
|
|
102
|
+
|
|
103
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
104
|
+
result = resolve_skill_paths_with_skskills([str(abs_path)])
|
|
105
|
+
assert result == [str(abs_path)]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestPrepareSessionSkills:
|
|
109
|
+
"""Test session skill preparation."""
|
|
110
|
+
|
|
111
|
+
def test_prepare_with_skskills_dir(self, skskills_home: Path, tmp_path: Path):
|
|
112
|
+
"""Should load skills that have skill.yaml."""
|
|
113
|
+
from skcapstone.session_skills import prepare_session_skills
|
|
114
|
+
|
|
115
|
+
work_dir = tmp_path / "agent-work"
|
|
116
|
+
work_dir.mkdir()
|
|
117
|
+
|
|
118
|
+
skill_path = str(skskills_home / "installed" / "syncthing-setup")
|
|
119
|
+
result = prepare_session_skills("test-agent", [skill_path], work_dir)
|
|
120
|
+
|
|
121
|
+
assert result["skills_loaded"] == 1
|
|
122
|
+
assert "syncthing-setup" in result["skill_names"]
|
|
123
|
+
|
|
124
|
+
def test_prepare_skips_non_skskills(self, tmp_path: Path):
|
|
125
|
+
"""Should skip paths that don't have skill.yaml."""
|
|
126
|
+
from skcapstone.session_skills import prepare_session_skills
|
|
127
|
+
|
|
128
|
+
work_dir = tmp_path / "agent-work"
|
|
129
|
+
work_dir.mkdir()
|
|
130
|
+
non_skill = tmp_path / "not-a-skill"
|
|
131
|
+
non_skill.mkdir()
|
|
132
|
+
|
|
133
|
+
result = prepare_session_skills("test-agent", [str(non_skill)], work_dir)
|
|
134
|
+
assert result["skills_loaded"] == 0
|
|
135
|
+
|
|
136
|
+
def test_prepare_writes_mcp_config(self, skskills_home: Path, tmp_path: Path):
|
|
137
|
+
"""Should write MCP config when skills are loaded."""
|
|
138
|
+
from skcapstone.session_skills import prepare_session_skills
|
|
139
|
+
|
|
140
|
+
work_dir = tmp_path / "agent-work"
|
|
141
|
+
work_dir.mkdir()
|
|
142
|
+
|
|
143
|
+
skill_path = str(skskills_home / "installed" / "syncthing-setup")
|
|
144
|
+
result = prepare_session_skills("test-agent", [skill_path], work_dir)
|
|
145
|
+
|
|
146
|
+
assert result["mcp_config_path"] is not None
|
|
147
|
+
config_path = Path(result["mcp_config_path"])
|
|
148
|
+
assert config_path.exists()
|
|
149
|
+
config = json.loads(config_path.read_text())
|
|
150
|
+
assert "mcpServers" in config
|
|
151
|
+
assert "skskills" in config["mcpServers"]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestEnrichConfigs:
|
|
155
|
+
"""Test session and crush config enrichment."""
|
|
156
|
+
|
|
157
|
+
def test_enrich_session_config(self):
|
|
158
|
+
"""Should add skskills metadata to session config."""
|
|
159
|
+
from skcapstone.session_skills import enrich_session_config
|
|
160
|
+
|
|
161
|
+
session_config: Dict = {"agent_name": "test", "skills": []}
|
|
162
|
+
skill_result = {
|
|
163
|
+
"skills_loaded": 2,
|
|
164
|
+
"skill_names": ["skill-a", "skill-b"],
|
|
165
|
+
"tools_available": ["skill-a.tool1", "skill-b.tool2"],
|
|
166
|
+
"mcp_config_path": "/tmp/test.json",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
enriched = enrich_session_config(session_config, skill_result)
|
|
170
|
+
assert "skskills" in enriched
|
|
171
|
+
assert enriched["skskills"]["loaded"] == 2
|
|
172
|
+
assert enriched["skskills"]["tools_available"] == ["skill-a.tool1", "skill-b.tool2"]
|
|
173
|
+
|
|
174
|
+
def test_enrich_session_config_no_skills(self):
|
|
175
|
+
"""Should not add skskills section when no skills loaded."""
|
|
176
|
+
from skcapstone.session_skills import enrich_session_config
|
|
177
|
+
|
|
178
|
+
session_config: Dict = {"agent_name": "test"}
|
|
179
|
+
skill_result = {"skills_loaded": 0, "skill_names": [], "tools_available": []}
|
|
180
|
+
|
|
181
|
+
enriched = enrich_session_config(session_config, skill_result)
|
|
182
|
+
assert "skskills" not in enriched
|
|
183
|
+
|
|
184
|
+
def test_enrich_crush_config(self):
|
|
185
|
+
"""Should add skskills MCP server to crush config."""
|
|
186
|
+
from skcapstone.session_skills import enrich_crush_config
|
|
187
|
+
|
|
188
|
+
crush_config: Dict = {
|
|
189
|
+
"options": {},
|
|
190
|
+
"permissions": {"allowed_tools": ["view", "edit"]},
|
|
191
|
+
}
|
|
192
|
+
skill_result = {
|
|
193
|
+
"skills_loaded": 1,
|
|
194
|
+
"tools_available": ["my-skill.deploy"],
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
enriched = enrich_crush_config(crush_config, skill_result)
|
|
198
|
+
assert "skskills" in enriched["mcpServers"]
|
|
199
|
+
assert "mcp_skskills_my_skill_deploy" in enriched["permissions"]["allowed_tools"]
|
|
200
|
+
|
|
201
|
+
def test_enrich_crush_config_no_skills(self):
|
|
202
|
+
"""Should not modify crush config when no skills loaded."""
|
|
203
|
+
from skcapstone.session_skills import enrich_crush_config
|
|
204
|
+
|
|
205
|
+
crush_config: Dict = {"options": {}}
|
|
206
|
+
skill_result = {"skills_loaded": 0, "tools_available": []}
|
|
207
|
+
|
|
208
|
+
enriched = enrich_crush_config(crush_config, skill_result)
|
|
209
|
+
assert "mcpServers" not in enriched
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TestCleanup:
|
|
213
|
+
"""Test session skills cleanup."""
|
|
214
|
+
|
|
215
|
+
def test_cleanup_removes_mcp_config(self, tmp_path: Path):
|
|
216
|
+
"""Should remove the MCP config file."""
|
|
217
|
+
from skcapstone.session_skills import cleanup_session_skills
|
|
218
|
+
|
|
219
|
+
work_dir = tmp_path / "agent-work"
|
|
220
|
+
work_dir.mkdir()
|
|
221
|
+
mcp_config = work_dir / "skskills_mcp.json"
|
|
222
|
+
mcp_config.write_text("{}")
|
|
223
|
+
|
|
224
|
+
cleanup_session_skills("test-agent", work_dir)
|
|
225
|
+
assert not mcp_config.exists()
|
|
226
|
+
|
|
227
|
+
def test_cleanup_handles_missing_config(self, tmp_path: Path):
|
|
228
|
+
"""Should not fail when MCP config doesn't exist."""
|
|
229
|
+
from skcapstone.session_skills import cleanup_session_skills
|
|
230
|
+
|
|
231
|
+
work_dir = tmp_path / "agent-work"
|
|
232
|
+
work_dir.mkdir()
|
|
233
|
+
|
|
234
|
+
# Should not raise
|
|
235
|
+
cleanup_session_skills("test-agent", work_dir)
|