@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,312 @@
|
|
|
1
|
+
"""Tests for ResponseCache — TTL, cache hit/miss, skip_cache wiring."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.blueprints.schema import ModelTier
|
|
11
|
+
from skcapstone.response_cache import ResponseCache, _TTL_CODE, _TTL_FAST, _ttl_for_tier, hash_prompt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# hash_prompt
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestHashPrompt:
|
|
20
|
+
"""hash_prompt produces stable, distinct digests."""
|
|
21
|
+
|
|
22
|
+
def test_deterministic(self):
|
|
23
|
+
"""Same inputs always produce the same hash."""
|
|
24
|
+
h1 = hash_prompt("system", "hello")
|
|
25
|
+
h2 = hash_prompt("system", "hello")
|
|
26
|
+
assert h1 == h2
|
|
27
|
+
|
|
28
|
+
def test_distinct_messages(self):
|
|
29
|
+
"""Different user messages produce different hashes."""
|
|
30
|
+
h1 = hash_prompt("system", "hello")
|
|
31
|
+
h2 = hash_prompt("system", "goodbye")
|
|
32
|
+
assert h1 != h2
|
|
33
|
+
|
|
34
|
+
def test_distinct_systems(self):
|
|
35
|
+
"""Different system prompts produce different hashes even with same user message."""
|
|
36
|
+
h1 = hash_prompt("system-A", "hello")
|
|
37
|
+
h2 = hash_prompt("system-B", "hello")
|
|
38
|
+
assert h1 != h2
|
|
39
|
+
|
|
40
|
+
def test_returns_hex_string(self):
|
|
41
|
+
"""Result is a 64-char lowercase hex string (SHA-256)."""
|
|
42
|
+
h = hash_prompt("s", "u")
|
|
43
|
+
assert len(h) == 64
|
|
44
|
+
assert h == h.lower()
|
|
45
|
+
int(h, 16) # must parse as hex
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# TTL helper
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestTtlForTier:
|
|
54
|
+
"""_ttl_for_tier maps tiers to correct TTLs."""
|
|
55
|
+
|
|
56
|
+
def test_fast_tier_ttl(self):
|
|
57
|
+
assert _ttl_for_tier(ModelTier.FAST) == _TTL_FAST
|
|
58
|
+
|
|
59
|
+
def test_code_tier_ttl(self):
|
|
60
|
+
assert _ttl_for_tier(ModelTier.CODE) == _TTL_CODE
|
|
61
|
+
|
|
62
|
+
def test_other_tiers_default_to_fast(self):
|
|
63
|
+
"""REASON, NUANCE, LOCAL, CUSTOM all get the FAST TTL (1 h)."""
|
|
64
|
+
for tier in (ModelTier.REASON, ModelTier.NUANCE, ModelTier.LOCAL, ModelTier.CUSTOM):
|
|
65
|
+
assert _ttl_for_tier(tier) == _TTL_FAST
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# ResponseCache — basic put/get
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestResponseCacheBasic:
|
|
74
|
+
"""Basic put/get, miss, empty-response guard."""
|
|
75
|
+
|
|
76
|
+
def test_miss_on_empty_cache(self):
|
|
77
|
+
"""get() returns None for a key that was never stored."""
|
|
78
|
+
cache = ResponseCache()
|
|
79
|
+
assert cache.get("deadbeef" * 8, "gpt-4") is None
|
|
80
|
+
|
|
81
|
+
def test_hit_after_put(self):
|
|
82
|
+
"""A stored entry is returned on subsequent get()."""
|
|
83
|
+
cache = ResponseCache()
|
|
84
|
+
ph = hash_prompt("sys", "hello")
|
|
85
|
+
cache.put(ph, "llama3.2", ModelTier.FAST, "Hi there!")
|
|
86
|
+
assert cache.get(ph, "llama3.2") == "Hi there!"
|
|
87
|
+
|
|
88
|
+
def test_miss_different_model(self):
|
|
89
|
+
"""Cache is keyed by model name; wrong model → miss."""
|
|
90
|
+
cache = ResponseCache()
|
|
91
|
+
ph = hash_prompt("sys", "hello")
|
|
92
|
+
cache.put(ph, "llama3.2", ModelTier.FAST, "Hi there!")
|
|
93
|
+
assert cache.get(ph, "gpt-4") is None
|
|
94
|
+
|
|
95
|
+
def test_miss_different_prompt(self):
|
|
96
|
+
"""Cache is keyed by prompt hash; different prompt → miss."""
|
|
97
|
+
cache = ResponseCache()
|
|
98
|
+
ph1 = hash_prompt("sys", "hello")
|
|
99
|
+
ph2 = hash_prompt("sys", "goodbye")
|
|
100
|
+
cache.put(ph1, "llama3.2", ModelTier.FAST, "Hi there!")
|
|
101
|
+
assert cache.get(ph2, "llama3.2") is None
|
|
102
|
+
|
|
103
|
+
def test_empty_response_not_stored(self):
|
|
104
|
+
"""put() silently ignores empty responses."""
|
|
105
|
+
cache = ResponseCache()
|
|
106
|
+
ph = hash_prompt("sys", "q")
|
|
107
|
+
cache.put(ph, "llama3.2", ModelTier.FAST, "")
|
|
108
|
+
assert cache.get(ph, "llama3.2") is None
|
|
109
|
+
|
|
110
|
+
def test_size_tracks_entries(self):
|
|
111
|
+
cache = ResponseCache()
|
|
112
|
+
assert cache.size == 0
|
|
113
|
+
ph = hash_prompt("sys", "x")
|
|
114
|
+
cache.put(ph, "m1", ModelTier.FAST, "resp")
|
|
115
|
+
assert cache.size == 1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# TTL expiry
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestResponseCacheTTL:
|
|
124
|
+
"""Entries expire after their TTL elapses."""
|
|
125
|
+
|
|
126
|
+
def test_entry_expires(self, monkeypatch):
|
|
127
|
+
"""An entry is evicted once monotonic time exceeds its expiry."""
|
|
128
|
+
cache = ResponseCache()
|
|
129
|
+
ph = hash_prompt("s", "u")
|
|
130
|
+
|
|
131
|
+
# Freeze time at T=0 for put
|
|
132
|
+
fake_time = [0.0]
|
|
133
|
+
monkeypatch.setattr("skcapstone.response_cache.time.monotonic", lambda: fake_time[0])
|
|
134
|
+
|
|
135
|
+
cache.put(ph, "llama3.2", ModelTier.FAST, "answer")
|
|
136
|
+
assert cache.get(ph, "llama3.2") == "answer" # alive at T=0
|
|
137
|
+
|
|
138
|
+
# Advance past TTL
|
|
139
|
+
fake_time[0] = _TTL_FAST + 1.0
|
|
140
|
+
result = cache.get(ph, "llama3.2")
|
|
141
|
+
assert result is None # expired
|
|
142
|
+
|
|
143
|
+
def test_code_tier_longer_ttl(self, monkeypatch):
|
|
144
|
+
"""CODE tier entries survive for 24 h but expire after."""
|
|
145
|
+
cache = ResponseCache()
|
|
146
|
+
ph = hash_prompt("s", "u")
|
|
147
|
+
|
|
148
|
+
fake_time = [0.0]
|
|
149
|
+
monkeypatch.setattr("skcapstone.response_cache.time.monotonic", lambda: fake_time[0])
|
|
150
|
+
|
|
151
|
+
cache.put(ph, "claude-code", ModelTier.CODE, "code answer")
|
|
152
|
+
|
|
153
|
+
# Still alive after 23 h
|
|
154
|
+
fake_time[0] = 3600 * 23
|
|
155
|
+
assert cache.get(ph, "claude-code") == "code answer"
|
|
156
|
+
|
|
157
|
+
# Expired after 24 h + 1 s
|
|
158
|
+
fake_time[0] = _TTL_CODE + 1.0
|
|
159
|
+
assert cache.get(ph, "claude-code") is None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Eviction
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TestResponseCacheEviction:
|
|
168
|
+
"""evict() removes expired entries; max_size cap is enforced."""
|
|
169
|
+
|
|
170
|
+
def test_evict_removes_expired(self, monkeypatch):
|
|
171
|
+
cache = ResponseCache()
|
|
172
|
+
fake_time = [0.0]
|
|
173
|
+
monkeypatch.setattr("skcapstone.response_cache.time.monotonic", lambda: fake_time[0])
|
|
174
|
+
|
|
175
|
+
ph = hash_prompt("s", "u")
|
|
176
|
+
cache.put(ph, "m1", ModelTier.FAST, "r1")
|
|
177
|
+
assert cache.size == 1
|
|
178
|
+
|
|
179
|
+
fake_time[0] = _TTL_FAST + 1.0
|
|
180
|
+
removed = cache.evict()
|
|
181
|
+
assert removed == 1
|
|
182
|
+
assert cache.size == 0
|
|
183
|
+
|
|
184
|
+
def test_max_size_enforced(self):
|
|
185
|
+
"""When max_size is exceeded, oldest entries are dropped."""
|
|
186
|
+
cache = ResponseCache(max_size=3)
|
|
187
|
+
for i in range(5):
|
|
188
|
+
ph = hash_prompt("s", str(i))
|
|
189
|
+
cache.put(ph, "m", ModelTier.FAST, f"resp-{i}")
|
|
190
|
+
assert cache.size <= 3
|
|
191
|
+
|
|
192
|
+
def test_clear_empties_cache(self):
|
|
193
|
+
cache = ResponseCache()
|
|
194
|
+
for i in range(5):
|
|
195
|
+
ph = hash_prompt("s", str(i))
|
|
196
|
+
cache.put(ph, "m", ModelTier.FAST, "resp")
|
|
197
|
+
cache.clear()
|
|
198
|
+
assert cache.size == 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# Stats
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TestResponseCacheStats:
|
|
207
|
+
"""hits and misses are tracked correctly."""
|
|
208
|
+
|
|
209
|
+
def test_hit_increments_hits(self):
|
|
210
|
+
cache = ResponseCache()
|
|
211
|
+
ph = hash_prompt("s", "u")
|
|
212
|
+
cache.put(ph, "m", ModelTier.FAST, "r")
|
|
213
|
+
cache.get(ph, "m")
|
|
214
|
+
assert cache.stats["hits"] == 1
|
|
215
|
+
assert cache.stats["misses"] == 0
|
|
216
|
+
|
|
217
|
+
def test_miss_increments_misses(self):
|
|
218
|
+
cache = ResponseCache()
|
|
219
|
+
cache.get("no-such-hash", "model")
|
|
220
|
+
assert cache.stats["misses"] == 1
|
|
221
|
+
assert cache.stats["hits"] == 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
# LLMBridge integration — cache wired into generate()
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestLLMBridgeCacheIntegration:
|
|
230
|
+
"""LLMBridge.generate() uses the cache and respects skip_cache."""
|
|
231
|
+
|
|
232
|
+
def _make_bridge(self, cache):
|
|
233
|
+
from skcapstone.consciousness_loop import ConsciousnessConfig, LLMBridge
|
|
234
|
+
|
|
235
|
+
config = ConsciousnessConfig()
|
|
236
|
+
with patch.object(LLMBridge, "_probe_available_backends"):
|
|
237
|
+
bridge = LLMBridge(config, cache=cache)
|
|
238
|
+
bridge._available = {"passthrough": True}
|
|
239
|
+
return bridge
|
|
240
|
+
|
|
241
|
+
def test_cache_hit_skips_llm(self):
|
|
242
|
+
"""When cache has a matching entry, the LLM callback is never called."""
|
|
243
|
+
from skcapstone.model_router import TaskSignal
|
|
244
|
+
|
|
245
|
+
cache = ResponseCache()
|
|
246
|
+
bridge = self._make_bridge(cache)
|
|
247
|
+
|
|
248
|
+
# Pre-populate the cache
|
|
249
|
+
ph = hash_prompt("system", "hello")
|
|
250
|
+
model = bridge._router.route(
|
|
251
|
+
TaskSignal(description="hello", tags=["simple"])
|
|
252
|
+
).model_name
|
|
253
|
+
cache.put(ph, model, ModelTier.FAST, "cached answer")
|
|
254
|
+
|
|
255
|
+
with patch.object(bridge, "_resolve_callback") as mock_cb:
|
|
256
|
+
result = bridge.generate("system", "hello", TaskSignal(description="hello", tags=["simple"]))
|
|
257
|
+
|
|
258
|
+
mock_cb.assert_not_called()
|
|
259
|
+
assert result == "cached answer"
|
|
260
|
+
|
|
261
|
+
def test_skip_cache_bypasses_lookup_and_store(self):
|
|
262
|
+
"""When skip_cache=True the cache is never consulted or written."""
|
|
263
|
+
from skcapstone.model_router import TaskSignal
|
|
264
|
+
|
|
265
|
+
cache = ResponseCache()
|
|
266
|
+
bridge = self._make_bridge(cache)
|
|
267
|
+
|
|
268
|
+
fake_callback = MagicMock(return_value="live answer")
|
|
269
|
+
with patch.object(bridge, "_resolve_callback", return_value=fake_callback):
|
|
270
|
+
with patch.object(bridge, "_timed_call", return_value="live answer"):
|
|
271
|
+
result = bridge.generate(
|
|
272
|
+
"system", "hello",
|
|
273
|
+
TaskSignal(description="hello", tags=["simple"]),
|
|
274
|
+
skip_cache=True,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
assert result == "live answer"
|
|
278
|
+
assert cache.size == 0 # nothing was stored
|
|
279
|
+
|
|
280
|
+
def test_successful_call_populates_cache(self):
|
|
281
|
+
"""A successful LLM call populates the cache for next time."""
|
|
282
|
+
from skcapstone.model_router import TaskSignal
|
|
283
|
+
|
|
284
|
+
cache = ResponseCache()
|
|
285
|
+
bridge = self._make_bridge(cache)
|
|
286
|
+
|
|
287
|
+
with patch.object(bridge, "_timed_call", return_value="fresh answer"):
|
|
288
|
+
with patch.object(bridge, "_resolve_callback", return_value=MagicMock()):
|
|
289
|
+
bridge.generate(
|
|
290
|
+
"system", "hello",
|
|
291
|
+
TaskSignal(description="hello", tags=["simple"]),
|
|
292
|
+
skip_cache=False,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
assert cache.size == 1
|
|
296
|
+
|
|
297
|
+
def test_out_info_backend_is_cache_on_hit(self):
|
|
298
|
+
"""_out_info['backend'] is set to 'cache' on a cache hit."""
|
|
299
|
+
from skcapstone.model_router import TaskSignal
|
|
300
|
+
|
|
301
|
+
cache = ResponseCache()
|
|
302
|
+
bridge = self._make_bridge(cache)
|
|
303
|
+
|
|
304
|
+
signal = TaskSignal(description="hello", tags=["simple"])
|
|
305
|
+
decision = bridge._router.route(signal)
|
|
306
|
+
ph = hash_prompt("system", "hello")
|
|
307
|
+
cache.put(ph, decision.model_name, decision.tier, "cached")
|
|
308
|
+
|
|
309
|
+
out: dict = {}
|
|
310
|
+
result = bridge.generate("system", "hello", signal, _out_info=out)
|
|
311
|
+
assert result == "cached"
|
|
312
|
+
assert out.get("backend") == "cache"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Tests for skcapstone.response_scorer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.response_scorer import (
|
|
11
|
+
ResponseScore,
|
|
12
|
+
_score_coherence,
|
|
13
|
+
_score_latency,
|
|
14
|
+
_score_length,
|
|
15
|
+
score_response,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# ResponseScore dataclass
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestResponseScore:
|
|
25
|
+
"""Tests for ResponseScore construction and overall computation."""
|
|
26
|
+
|
|
27
|
+
def test_overall_is_weighted_average(self) -> None:
|
|
28
|
+
"""overall = coherence*0.4 + length*0.3 + latency*0.3."""
|
|
29
|
+
s = ResponseScore(length_score=1.0, coherence_score=1.0, latency_score=1.0)
|
|
30
|
+
assert s.overall == 1.0
|
|
31
|
+
|
|
32
|
+
def test_overall_zero_all_zeros(self) -> None:
|
|
33
|
+
"""All-zero dimensions → overall 0.0."""
|
|
34
|
+
s = ResponseScore(length_score=0.0, coherence_score=0.0, latency_score=0.0)
|
|
35
|
+
assert s.overall == 0.0
|
|
36
|
+
|
|
37
|
+
def test_overall_mixed(self) -> None:
|
|
38
|
+
"""Verify weighted formula with asymmetric scores."""
|
|
39
|
+
s = ResponseScore(length_score=0.0, coherence_score=1.0, latency_score=0.0)
|
|
40
|
+
# overall = 1.0*0.4 + 0.0*0.3 + 0.0*0.3 = 0.4
|
|
41
|
+
assert abs(s.overall - 0.4) < 1e-4
|
|
42
|
+
|
|
43
|
+
def test_to_dict_has_all_keys(self) -> None:
|
|
44
|
+
"""to_dict contains length, coherence, latency, overall."""
|
|
45
|
+
s = ResponseScore(length_score=0.8, coherence_score=0.7, latency_score=0.9)
|
|
46
|
+
d = s.to_dict()
|
|
47
|
+
assert set(d.keys()) == {"length", "coherence", "latency", "overall"}
|
|
48
|
+
|
|
49
|
+
def test_to_dict_values_match(self) -> None:
|
|
50
|
+
"""to_dict values match the dataclass attributes."""
|
|
51
|
+
s = ResponseScore(length_score=0.5, coherence_score=0.6, latency_score=0.7)
|
|
52
|
+
d = s.to_dict()
|
|
53
|
+
assert d["length"] == s.length_score
|
|
54
|
+
assert d["coherence"] == s.coherence_score
|
|
55
|
+
assert d["latency"] == s.latency_score
|
|
56
|
+
assert d["overall"] == s.overall
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Length scoring
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestScoreLength:
|
|
65
|
+
"""Tests for _score_length."""
|
|
66
|
+
|
|
67
|
+
def test_empty_response_is_zero(self) -> None:
|
|
68
|
+
"""Empty response → 0.0."""
|
|
69
|
+
assert _score_length("Tell me about Python", "") == 0.0
|
|
70
|
+
|
|
71
|
+
def test_response_in_ideal_range_is_one(self) -> None:
|
|
72
|
+
"""Response in [lo, hi] word range → 1.0."""
|
|
73
|
+
question = "What is Python?"
|
|
74
|
+
# question has 3 words → lo=max(10,3)=10, hi=max(50,30)=50
|
|
75
|
+
response = " ".join(["word"] * 20)
|
|
76
|
+
assert _score_length(question, response) == 1.0
|
|
77
|
+
|
|
78
|
+
def test_very_short_response_penalised(self) -> None:
|
|
79
|
+
"""One-word response for a normal question → score < 0.5."""
|
|
80
|
+
question = "Explain the history of artificial intelligence in detail."
|
|
81
|
+
score = _score_length(question, "ok")
|
|
82
|
+
assert score < 0.5
|
|
83
|
+
|
|
84
|
+
def test_very_long_response_penalised_gently(self) -> None:
|
|
85
|
+
"""Extremely long response → score 0.3 (verbose floor)."""
|
|
86
|
+
question = "Hi"
|
|
87
|
+
response = " ".join(["word"] * 5000)
|
|
88
|
+
score = _score_length(question, response)
|
|
89
|
+
assert score <= 0.5
|
|
90
|
+
|
|
91
|
+
def test_long_response_floor_is_0_3(self) -> None:
|
|
92
|
+
"""Length score never drops below 0.3 even for very long responses."""
|
|
93
|
+
score = _score_length("hello", " ".join(["x"] * 10_000))
|
|
94
|
+
assert score >= 0.3
|
|
95
|
+
|
|
96
|
+
def test_single_word_question(self) -> None:
|
|
97
|
+
"""Single-word question — lo=10; response of 15 words → 1.0."""
|
|
98
|
+
score = _score_length("Hi", " ".join(["word"] * 15))
|
|
99
|
+
assert score == 1.0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Coherence scoring
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestScoreCoherence:
|
|
108
|
+
"""Tests for _score_coherence."""
|
|
109
|
+
|
|
110
|
+
def test_empty_response_is_zero(self) -> None:
|
|
111
|
+
"""Empty response → 0.0."""
|
|
112
|
+
assert _score_coherence("What is Python?", "") == 0.0
|
|
113
|
+
|
|
114
|
+
def test_whitespace_response_is_zero(self) -> None:
|
|
115
|
+
"""Whitespace-only response → 0.0."""
|
|
116
|
+
assert _score_coherence("What is Python?", " ") == 0.0
|
|
117
|
+
|
|
118
|
+
def test_all_keywords_present_is_one(self) -> None:
|
|
119
|
+
"""Response contains all question content words → 1.0."""
|
|
120
|
+
question = "What is Python?"
|
|
121
|
+
# 'python' and 'what'? 'what' is a stopword, 'is' is a stopword.
|
|
122
|
+
# Only 'python' is a content word → 1 keyword
|
|
123
|
+
response = "Python is a high-level programming language."
|
|
124
|
+
assert _score_coherence(question, response) == 1.0
|
|
125
|
+
|
|
126
|
+
def test_no_keywords_present_is_zero(self) -> None:
|
|
127
|
+
"""Response unrelated to question → low coherence."""
|
|
128
|
+
question = "Explain neural networks"
|
|
129
|
+
response = "The weather today is sunny and warm in Paris."
|
|
130
|
+
score = _score_coherence(question, response)
|
|
131
|
+
assert score < 0.5
|
|
132
|
+
|
|
133
|
+
def test_partial_overlap(self) -> None:
|
|
134
|
+
"""Partial keyword overlap → intermediate score."""
|
|
135
|
+
question = "Compare Python and JavaScript performance"
|
|
136
|
+
# Content words: compare, python, javascript, performance
|
|
137
|
+
response = "Python is great for scripting."
|
|
138
|
+
# 'python' matches — 1/4 = 0.25
|
|
139
|
+
score = _score_coherence(question, response)
|
|
140
|
+
assert 0.0 < score < 1.0
|
|
141
|
+
|
|
142
|
+
def test_question_all_stopwords_returns_neutral(self) -> None:
|
|
143
|
+
"""Question with only stopwords → neutral 0.5."""
|
|
144
|
+
assert _score_coherence("is it the", "yes it is") == 0.5
|
|
145
|
+
|
|
146
|
+
def test_case_insensitive(self) -> None:
|
|
147
|
+
"""Matching is case-insensitive."""
|
|
148
|
+
score = _score_coherence("What is PYTHON?", "python is a language")
|
|
149
|
+
assert score == 1.0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Latency scoring
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestScoreLatency:
|
|
158
|
+
"""Tests for _score_latency."""
|
|
159
|
+
|
|
160
|
+
def test_zero_latency_is_one(self) -> None:
|
|
161
|
+
"""Zero (or negative) latency → 1.0."""
|
|
162
|
+
assert _score_latency(0.0) == 1.0
|
|
163
|
+
assert _score_latency(-100.0) == 1.0
|
|
164
|
+
|
|
165
|
+
def test_under_500ms_is_one(self) -> None:
|
|
166
|
+
"""Under 500 ms → 1.0."""
|
|
167
|
+
assert _score_latency(499.9) == 1.0
|
|
168
|
+
assert _score_latency(500.0) == 1.0
|
|
169
|
+
|
|
170
|
+
def test_1000ms_is_0_9(self) -> None:
|
|
171
|
+
"""1000 ms → 0.9."""
|
|
172
|
+
assert _score_latency(1000.0) == 0.9
|
|
173
|
+
|
|
174
|
+
def test_3000ms_is_0_7(self) -> None:
|
|
175
|
+
"""3000 ms → 0.7."""
|
|
176
|
+
assert _score_latency(3000.0) == 0.7
|
|
177
|
+
|
|
178
|
+
def test_10000ms_is_0_4(self) -> None:
|
|
179
|
+
"""10 s → 0.4."""
|
|
180
|
+
assert _score_latency(10_000.0) == 0.4
|
|
181
|
+
|
|
182
|
+
def test_20000ms_is_0_2(self) -> None:
|
|
183
|
+
"""20 s → 0.2."""
|
|
184
|
+
assert _score_latency(20_000.0) == 0.2
|
|
185
|
+
|
|
186
|
+
def test_over_30s_is_0_1(self) -> None:
|
|
187
|
+
"""Over 30 s → 0.1 (floor)."""
|
|
188
|
+
assert _score_latency(60_000.0) == 0.1
|
|
189
|
+
assert _score_latency(30_001.0) == 0.1
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# score_response (integration)
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TestScoreResponse:
|
|
198
|
+
"""Tests for the public score_response function."""
|
|
199
|
+
|
|
200
|
+
def test_returns_response_score(self) -> None:
|
|
201
|
+
"""score_response returns a ResponseScore instance."""
|
|
202
|
+
result = score_response("What is Python?", "Python is a language.", 300.0)
|
|
203
|
+
assert isinstance(result, ResponseScore)
|
|
204
|
+
|
|
205
|
+
def test_overall_in_range(self) -> None:
|
|
206
|
+
"""overall is always in [0, 1]."""
|
|
207
|
+
result = score_response("", "", 99999.0)
|
|
208
|
+
assert 0.0 <= result.overall <= 1.0
|
|
209
|
+
|
|
210
|
+
def test_good_response_high_score(self) -> None:
|
|
211
|
+
"""Coherent, appropriately-length, fast response scores >= 0.6."""
|
|
212
|
+
question = "Explain what Python programming language is used for"
|
|
213
|
+
response = (
|
|
214
|
+
"Python is a versatile programming language used for web development, "
|
|
215
|
+
"data science, automation, and artificial intelligence. It is known for "
|
|
216
|
+
"its readable syntax and large ecosystem of libraries."
|
|
217
|
+
)
|
|
218
|
+
result = score_response(question, response, 800.0)
|
|
219
|
+
assert result.overall >= 0.6
|
|
220
|
+
|
|
221
|
+
def test_empty_response_low_score(self) -> None:
|
|
222
|
+
"""Empty response → low overall score."""
|
|
223
|
+
result = score_response("Explain the universe", "", 200.0)
|
|
224
|
+
assert result.overall < 0.4
|
|
225
|
+
|
|
226
|
+
def test_slow_response_penalises_latency(self) -> None:
|
|
227
|
+
"""Latency score is low for slow responses."""
|
|
228
|
+
result = score_response("Hi", "Hello!", 60_000.0)
|
|
229
|
+
assert result.latency_score == 0.1
|
|
230
|
+
|
|
231
|
+
def test_to_dict_is_json_serializable(self) -> None:
|
|
232
|
+
"""score_response().to_dict() can be serialized to JSON."""
|
|
233
|
+
result = score_response("test question", "test answer", 500.0)
|
|
234
|
+
serialized = json.dumps(result.to_dict())
|
|
235
|
+
assert isinstance(serialized, str)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# ConsciousnessMetrics integration
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TestConsciousnessMetricsQuality:
|
|
244
|
+
"""Tests for quality scoring integration in ConsciousnessMetrics."""
|
|
245
|
+
|
|
246
|
+
@pytest.fixture
|
|
247
|
+
def cm(self, tmp_path: Path):
|
|
248
|
+
"""ConsciousnessMetrics with no background thread."""
|
|
249
|
+
from skcapstone.metrics import ConsciousnessMetrics
|
|
250
|
+
return ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
251
|
+
|
|
252
|
+
def test_initial_quality_avg_zeros(self, cm) -> None:
|
|
253
|
+
"""quality_avg returns zeros when nothing recorded."""
|
|
254
|
+
q = cm.quality_avg()
|
|
255
|
+
assert q["count"] == 0
|
|
256
|
+
assert q["overall"] == 0.0
|
|
257
|
+
|
|
258
|
+
def test_record_quality_increments_count(self, cm) -> None:
|
|
259
|
+
"""record_quality increments the count."""
|
|
260
|
+
score = score_response("What is 2+2?", "2+2 equals 4.", 100.0)
|
|
261
|
+
cm.record_quality(score)
|
|
262
|
+
cm.record_quality(score)
|
|
263
|
+
assert cm.quality_avg()["count"] == 2
|
|
264
|
+
|
|
265
|
+
def test_quality_avg_computes_mean(self, cm) -> None:
|
|
266
|
+
"""quality_avg computes the average across recorded scores."""
|
|
267
|
+
s1 = ResponseScore(length_score=1.0, coherence_score=1.0, latency_score=1.0)
|
|
268
|
+
s2 = ResponseScore(length_score=0.0, coherence_score=0.0, latency_score=0.0)
|
|
269
|
+
cm.record_quality(s1)
|
|
270
|
+
cm.record_quality(s2)
|
|
271
|
+
avg = cm.quality_avg()
|
|
272
|
+
assert avg["length"] == pytest.approx(0.5, abs=1e-4)
|
|
273
|
+
assert avg["coherence"] == pytest.approx(0.5, abs=1e-4)
|
|
274
|
+
assert avg["latency"] == pytest.approx(0.5, abs=1e-4)
|
|
275
|
+
|
|
276
|
+
def test_to_dict_includes_quality_avg(self, cm) -> None:
|
|
277
|
+
"""to_dict includes quality_avg key."""
|
|
278
|
+
d = cm.to_dict()
|
|
279
|
+
assert "quality_avg" in d
|
|
280
|
+
assert "count" in d["quality_avg"]
|
|
281
|
+
|
|
282
|
+
def test_quality_persists_and_reloads(self, tmp_path: Path) -> None:
|
|
283
|
+
"""Quality sums persist to disk and reload correctly."""
|
|
284
|
+
from skcapstone.metrics import ConsciousnessMetrics
|
|
285
|
+
|
|
286
|
+
cm1 = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
287
|
+
score = score_response("capital of France?", "Paris is the capital.", 400.0)
|
|
288
|
+
cm1.record_quality(score)
|
|
289
|
+
cm1.save()
|
|
290
|
+
|
|
291
|
+
cm2 = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
292
|
+
avg = cm2.quality_avg()
|
|
293
|
+
assert avg["count"] == 1
|
|
294
|
+
assert abs(avg["overall"] - score.overall) < 1e-4
|
package/tests/test_runtime.py
CHANGED
|
@@ -58,3 +58,62 @@ class TestAgentRuntime:
|
|
|
58
58
|
runtime2.awaken()
|
|
59
59
|
assert len(runtime2.manifest.connectors) == 1
|
|
60
60
|
assert runtime2.manifest.connectors[0].platform == "terminal"
|
|
61
|
+
|
|
62
|
+
def test_awaken_populates_skills_state(self, initialized_agent_home: Path):
|
|
63
|
+
"""Awaken should populate manifest.skills from SKSkills discovery."""
|
|
64
|
+
from skcapstone.models import SkillsState
|
|
65
|
+
|
|
66
|
+
runtime = AgentRuntime(home=initialized_agent_home)
|
|
67
|
+
manifest = runtime.awaken()
|
|
68
|
+
assert isinstance(manifest.skills, SkillsState)
|
|
69
|
+
|
|
70
|
+
def test_pillar_summary_includes_skills(self, initialized_agent_home: Path):
|
|
71
|
+
"""pillar_summary property should include the skills pillar."""
|
|
72
|
+
runtime = AgentRuntime(home=initialized_agent_home)
|
|
73
|
+
manifest = runtime.awaken()
|
|
74
|
+
summary = manifest.pillar_summary
|
|
75
|
+
assert "skills" in summary
|
|
76
|
+
|
|
77
|
+
def test_load_skills_handles_missing_skskills(self, initialized_agent_home: Path):
|
|
78
|
+
"""load_skills() should return None gracefully when skskills is unavailable."""
|
|
79
|
+
import sys
|
|
80
|
+
from unittest.mock import patch
|
|
81
|
+
|
|
82
|
+
runtime = AgentRuntime(home=initialized_agent_home)
|
|
83
|
+
runtime.awaken()
|
|
84
|
+
|
|
85
|
+
# Patch ImportError to simulate skskills not installed
|
|
86
|
+
with patch.dict(sys.modules, {"skskills.loader": None, "skskills.registry": None}):
|
|
87
|
+
result = runtime.load_skills()
|
|
88
|
+
|
|
89
|
+
# Returns None when not installed, or a loader when installed
|
|
90
|
+
assert result is None or hasattr(result, "all_tools")
|
|
91
|
+
|
|
92
|
+
def test_load_skills_with_installed_skills(self, initialized_agent_home: Path, tmp_path: Path):
|
|
93
|
+
"""load_skills() should load skills from SKSkills registry."""
|
|
94
|
+
import os
|
|
95
|
+
from textwrap import dedent
|
|
96
|
+
from unittest.mock import patch
|
|
97
|
+
|
|
98
|
+
# Create a minimal SKSkills installation
|
|
99
|
+
skskills_home = tmp_path / "skskills"
|
|
100
|
+
installed_dir = skskills_home / "installed"
|
|
101
|
+
skill_dir = installed_dir / "test-skill"
|
|
102
|
+
skill_dir.mkdir(parents=True)
|
|
103
|
+
(skill_dir / "skill.yaml").write_text(dedent("""\
|
|
104
|
+
name: test-skill
|
|
105
|
+
version: "0.1.0"
|
|
106
|
+
description: A test skill
|
|
107
|
+
author:
|
|
108
|
+
name: tester
|
|
109
|
+
"""))
|
|
110
|
+
|
|
111
|
+
runtime = AgentRuntime(home=initialized_agent_home)
|
|
112
|
+
runtime.awaken()
|
|
113
|
+
|
|
114
|
+
with patch.dict(os.environ, {"SKSKILLS_HOME": str(skskills_home)}):
|
|
115
|
+
loader = runtime.load_skills(agent="global")
|
|
116
|
+
|
|
117
|
+
if loader is not None:
|
|
118
|
+
# SKSkills is installed — verify the skill was loaded
|
|
119
|
+
assert runtime.manifest.skills.loaded >= 0
|