@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,529 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for skcapstone.config_validator.
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
- validate_consciousness_yaml: valid, type errors, syntax error, unknown key
|
|
6
|
+
- validate_model_profiles_yaml: valid, missing key, bad regex, enum error
|
|
7
|
+
- validate_identity_json: valid, missing field, JSON parse error, bad fingerprint
|
|
8
|
+
- validate_soul_blueprint_json: valid, missing required field, bad type
|
|
9
|
+
- validate_all: integration — correct files collected, missing files are warnings
|
|
10
|
+
- CLI smoke: skcapstone config validate --json-out
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
import yaml
|
|
20
|
+
from click.testing import CliRunner
|
|
21
|
+
|
|
22
|
+
from skcapstone.config_validator import (
|
|
23
|
+
ConfigValidationReport,
|
|
24
|
+
FileValidationResult,
|
|
25
|
+
ValidationIssue,
|
|
26
|
+
validate_all,
|
|
27
|
+
validate_consciousness_yaml,
|
|
28
|
+
validate_identity_json,
|
|
29
|
+
validate_model_profiles_yaml,
|
|
30
|
+
validate_soul_blueprint_json,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Fixtures
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def agent_home(tmp_path: Path) -> Path:
|
|
41
|
+
"""Create a minimal agent home directory structure."""
|
|
42
|
+
home = tmp_path / ".skcapstone"
|
|
43
|
+
for d in ("config", "identity", "soul", "soul/installed"):
|
|
44
|
+
(home / d).mkdir(parents=True, exist_ok=True)
|
|
45
|
+
return home
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _write(path: Path, content: str) -> Path:
|
|
49
|
+
path.write_text(content, encoding="utf-8")
|
|
50
|
+
return path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# consciousness.yaml tests
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestConsciousnessYaml:
|
|
59
|
+
"""Tests for validate_consciousness_yaml()."""
|
|
60
|
+
|
|
61
|
+
def test_valid_config_passes(self, tmp_path: Path) -> None:
|
|
62
|
+
"""Happy path: well-formed consciousness.yaml produces no errors."""
|
|
63
|
+
path = tmp_path / "consciousness.yaml"
|
|
64
|
+
_write(path, """
|
|
65
|
+
enabled: true
|
|
66
|
+
use_inotify: true
|
|
67
|
+
inotify_debounce_ms: 200
|
|
68
|
+
response_timeout: 120
|
|
69
|
+
max_context_tokens: 8000
|
|
70
|
+
max_history_messages: 10
|
|
71
|
+
auto_memory: true
|
|
72
|
+
auto_ack: true
|
|
73
|
+
privacy_default: false
|
|
74
|
+
max_concurrent_requests: 3
|
|
75
|
+
fallback_chain:
|
|
76
|
+
- ollama
|
|
77
|
+
- anthropic
|
|
78
|
+
- passthrough
|
|
79
|
+
desktop_notifications: true
|
|
80
|
+
""")
|
|
81
|
+
result = validate_consciousness_yaml(path)
|
|
82
|
+
assert result.is_valid, f"Unexpected errors: {result.errors}"
|
|
83
|
+
assert result.errors == []
|
|
84
|
+
|
|
85
|
+
def test_missing_file_is_warning_not_error(self, tmp_path: Path) -> None:
|
|
86
|
+
"""Missing file produces a warning, not an error."""
|
|
87
|
+
path = tmp_path / "consciousness.yaml"
|
|
88
|
+
result = validate_consciousness_yaml(path)
|
|
89
|
+
assert not result.found
|
|
90
|
+
assert result.is_valid # missing → valid (uses defaults)
|
|
91
|
+
assert len(result.warnings) == 1
|
|
92
|
+
assert result.errors == []
|
|
93
|
+
|
|
94
|
+
def test_bool_field_wrong_type_is_error(self, tmp_path: Path) -> None:
|
|
95
|
+
"""Passing a string for a bool field reports an error with the field name."""
|
|
96
|
+
path = tmp_path / "consciousness.yaml"
|
|
97
|
+
_write(path, "enabled: yes_please\n")
|
|
98
|
+
result = validate_consciousness_yaml(path)
|
|
99
|
+
err_fields = [e.field for e in result.errors]
|
|
100
|
+
assert "enabled" in err_fields
|
|
101
|
+
|
|
102
|
+
def test_int_field_wrong_type_is_error(self, tmp_path: Path) -> None:
|
|
103
|
+
"""Passing a string for an int field reports an error."""
|
|
104
|
+
path = tmp_path / "consciousness.yaml"
|
|
105
|
+
_write(path, "response_timeout: fast\n")
|
|
106
|
+
result = validate_consciousness_yaml(path)
|
|
107
|
+
err_fields = [e.field for e in result.errors]
|
|
108
|
+
assert "response_timeout" in err_fields
|
|
109
|
+
|
|
110
|
+
def test_int_field_zero_is_error(self, tmp_path: Path) -> None:
|
|
111
|
+
"""response_timeout: 0 must report an error (must be > 0)."""
|
|
112
|
+
path = tmp_path / "consciousness.yaml"
|
|
113
|
+
_write(path, "response_timeout: 0\n")
|
|
114
|
+
result = validate_consciousness_yaml(path)
|
|
115
|
+
assert any(e.field == "response_timeout" for e in result.errors)
|
|
116
|
+
|
|
117
|
+
def test_yaml_syntax_error_reports_line(self, tmp_path: Path) -> None:
|
|
118
|
+
"""A YAML syntax error is an error and the line number is set."""
|
|
119
|
+
path = tmp_path / "consciousness.yaml"
|
|
120
|
+
_write(path, "enabled: true\nbroken: [\n")
|
|
121
|
+
result = validate_consciousness_yaml(path)
|
|
122
|
+
assert len(result.errors) == 1
|
|
123
|
+
assert result.errors[0].line is not None
|
|
124
|
+
assert result.errors[0].line >= 1
|
|
125
|
+
|
|
126
|
+
def test_unknown_key_is_warning(self, tmp_path: Path) -> None:
|
|
127
|
+
"""An unknown top-level key generates a warning, not an error."""
|
|
128
|
+
path = tmp_path / "consciousness.yaml"
|
|
129
|
+
_write(path, "enabled: true\nmy_custom_setting: 42\n")
|
|
130
|
+
result = validate_consciousness_yaml(path)
|
|
131
|
+
assert result.is_valid # no errors
|
|
132
|
+
warn_fields = [w.field for w in result.warnings]
|
|
133
|
+
assert "my_custom_setting" in warn_fields
|
|
134
|
+
|
|
135
|
+
def test_fallback_chain_unknown_backend_is_warning(self, tmp_path: Path) -> None:
|
|
136
|
+
"""An unknown backend in fallback_chain is a warning, not an error."""
|
|
137
|
+
path = tmp_path / "consciousness.yaml"
|
|
138
|
+
_write(path, "fallback_chain:\n - my_custom_backend\n")
|
|
139
|
+
result = validate_consciousness_yaml(path)
|
|
140
|
+
assert result.is_valid
|
|
141
|
+
assert any("my_custom_backend" in w.message for w in result.warnings)
|
|
142
|
+
|
|
143
|
+
def test_fallback_chain_not_list_is_error(self, tmp_path: Path) -> None:
|
|
144
|
+
"""A non-list fallback_chain reports an error."""
|
|
145
|
+
path = tmp_path / "consciousness.yaml"
|
|
146
|
+
_write(path, "fallback_chain: ollama\n")
|
|
147
|
+
result = validate_consciousness_yaml(path)
|
|
148
|
+
assert any(e.field == "fallback_chain" for e in result.errors)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# model_profiles.yaml tests
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class TestModelProfilesYaml:
|
|
157
|
+
"""Tests for validate_model_profiles_yaml()."""
|
|
158
|
+
|
|
159
|
+
def test_valid_profiles_pass(self, tmp_path: Path) -> None:
|
|
160
|
+
"""Happy path: valid profiles list produces no errors."""
|
|
161
|
+
path = tmp_path / "model_profiles.yaml"
|
|
162
|
+
_write(path, """
|
|
163
|
+
profiles:
|
|
164
|
+
- model_pattern: "claude-.*"
|
|
165
|
+
family: claude
|
|
166
|
+
system_prompt_mode: separate_param
|
|
167
|
+
structure_format: xml
|
|
168
|
+
thinking_enabled: true
|
|
169
|
+
thinking_mode: budget
|
|
170
|
+
tool_format: anthropic
|
|
171
|
+
""")
|
|
172
|
+
result = validate_model_profiles_yaml(path)
|
|
173
|
+
assert result.is_valid, f"Unexpected errors: {result.errors}"
|
|
174
|
+
|
|
175
|
+
def test_missing_profiles_key_is_error(self, tmp_path: Path) -> None:
|
|
176
|
+
"""YAML without a top-level 'profiles' key is an error."""
|
|
177
|
+
path = tmp_path / "model_profiles.yaml"
|
|
178
|
+
_write(path, "family: openai\n")
|
|
179
|
+
result = validate_model_profiles_yaml(path)
|
|
180
|
+
err_fields = [e.field for e in result.errors]
|
|
181
|
+
assert "profiles" in err_fields
|
|
182
|
+
|
|
183
|
+
def test_missing_required_field_in_profile(self, tmp_path: Path) -> None:
|
|
184
|
+
"""A profile missing 'model_pattern' or 'family' reports an error."""
|
|
185
|
+
path = tmp_path / "model_profiles.yaml"
|
|
186
|
+
_write(path, """
|
|
187
|
+
profiles:
|
|
188
|
+
- family: openai
|
|
189
|
+
""")
|
|
190
|
+
result = validate_model_profiles_yaml(path)
|
|
191
|
+
assert any("model_pattern" in (e.field or "") for e in result.errors)
|
|
192
|
+
|
|
193
|
+
def test_invalid_regex_in_model_pattern(self, tmp_path: Path) -> None:
|
|
194
|
+
"""An invalid regex in model_pattern reports an error."""
|
|
195
|
+
path = tmp_path / "model_profiles.yaml"
|
|
196
|
+
_write(path, """
|
|
197
|
+
profiles:
|
|
198
|
+
- model_pattern: "[invalid("
|
|
199
|
+
family: broken
|
|
200
|
+
""")
|
|
201
|
+
result = validate_model_profiles_yaml(path)
|
|
202
|
+
assert any("model_pattern" in (e.field or "") for e in result.errors)
|
|
203
|
+
assert any("regex" in e.message.lower() for e in result.errors)
|
|
204
|
+
|
|
205
|
+
def test_invalid_enum_value_is_error(self, tmp_path: Path) -> None:
|
|
206
|
+
"""An out-of-range enum value (e.g. structure_format) is an error."""
|
|
207
|
+
path = tmp_path / "model_profiles.yaml"
|
|
208
|
+
_write(path, """
|
|
209
|
+
profiles:
|
|
210
|
+
- model_pattern: ".*"
|
|
211
|
+
family: generic
|
|
212
|
+
structure_format: html
|
|
213
|
+
""")
|
|
214
|
+
result = validate_model_profiles_yaml(path)
|
|
215
|
+
assert any("structure_format" in (e.field or "") for e in result.errors)
|
|
216
|
+
|
|
217
|
+
def test_line_numbers_reported_for_profile_error(self, tmp_path: Path) -> None:
|
|
218
|
+
"""Error on a known profile field includes a non-None line number."""
|
|
219
|
+
path = tmp_path / "model_profiles.yaml"
|
|
220
|
+
content = (
|
|
221
|
+
"profiles:\n"
|
|
222
|
+
" - model_pattern: \"[bad(\"\n"
|
|
223
|
+
" family: x\n"
|
|
224
|
+
)
|
|
225
|
+
_write(path, content)
|
|
226
|
+
result = validate_model_profiles_yaml(path)
|
|
227
|
+
regex_errors = [e for e in result.errors if "model_pattern" in (e.field or "")]
|
|
228
|
+
assert regex_errors
|
|
229
|
+
assert regex_errors[0].line is not None
|
|
230
|
+
|
|
231
|
+
def test_missing_file_is_warning(self, tmp_path: Path) -> None:
|
|
232
|
+
"""Missing model_profiles.yaml is a warning (bundled defaults used)."""
|
|
233
|
+
path = tmp_path / "model_profiles.yaml"
|
|
234
|
+
result = validate_model_profiles_yaml(path)
|
|
235
|
+
assert not result.found
|
|
236
|
+
assert result.is_valid
|
|
237
|
+
assert len(result.warnings) == 1
|
|
238
|
+
|
|
239
|
+
def test_yaml_syntax_error_is_error(self, tmp_path: Path) -> None:
|
|
240
|
+
"""A YAML syntax error in model_profiles.yaml is reported as an error."""
|
|
241
|
+
path = tmp_path / "model_profiles.yaml"
|
|
242
|
+
_write(path, "profiles:\n - broken: [\n")
|
|
243
|
+
result = validate_model_profiles_yaml(path)
|
|
244
|
+
assert len(result.errors) == 1
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
# identity.json tests
|
|
249
|
+
# ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class TestIdentityJson:
|
|
253
|
+
"""Tests for validate_identity_json()."""
|
|
254
|
+
|
|
255
|
+
def test_valid_identity_passes(self, tmp_path: Path) -> None:
|
|
256
|
+
"""Happy path: identity.json with all required fields passes."""
|
|
257
|
+
path = tmp_path / "identity.json"
|
|
258
|
+
_write(path, json.dumps({
|
|
259
|
+
"name": "Opus",
|
|
260
|
+
"fingerprint": "A" * 40,
|
|
261
|
+
"email": "opus@skworld.io",
|
|
262
|
+
"capauth_managed": True,
|
|
263
|
+
}))
|
|
264
|
+
result = validate_identity_json(path)
|
|
265
|
+
assert result.is_valid, f"Unexpected errors: {result.errors}"
|
|
266
|
+
|
|
267
|
+
def test_missing_name_is_error(self, tmp_path: Path) -> None:
|
|
268
|
+
"""identity.json without 'name' is an error."""
|
|
269
|
+
path = tmp_path / "identity.json"
|
|
270
|
+
_write(path, json.dumps({"fingerprint": "A" * 40}))
|
|
271
|
+
result = validate_identity_json(path)
|
|
272
|
+
assert any(e.field == "name" for e in result.errors)
|
|
273
|
+
|
|
274
|
+
def test_missing_fingerprint_is_error(self, tmp_path: Path) -> None:
|
|
275
|
+
"""identity.json without 'fingerprint' is an error."""
|
|
276
|
+
path = tmp_path / "identity.json"
|
|
277
|
+
_write(path, json.dumps({"name": "Opus"}))
|
|
278
|
+
result = validate_identity_json(path)
|
|
279
|
+
assert any(e.field == "fingerprint" for e in result.errors)
|
|
280
|
+
|
|
281
|
+
def test_short_fingerprint_is_warning(self, tmp_path: Path) -> None:
|
|
282
|
+
"""A fingerprint that doesn't match 40-hex-char format is a warning."""
|
|
283
|
+
path = tmp_path / "identity.json"
|
|
284
|
+
_write(path, json.dumps({"name": "Opus", "fingerprint": "DEADBEEF"}))
|
|
285
|
+
result = validate_identity_json(path)
|
|
286
|
+
assert result.is_valid # warning only
|
|
287
|
+
assert any(e.field == "fingerprint" for e in result.warnings)
|
|
288
|
+
|
|
289
|
+
def test_json_parse_error_reports_line(self, tmp_path: Path) -> None:
|
|
290
|
+
"""A JSON syntax error is an error and the line number is set."""
|
|
291
|
+
path = tmp_path / "identity.json"
|
|
292
|
+
_write(path, '{"name": "Opus"\n broken\n}')
|
|
293
|
+
result = validate_identity_json(path)
|
|
294
|
+
assert len(result.errors) == 1
|
|
295
|
+
assert result.errors[0].line is not None
|
|
296
|
+
assert result.errors[0].line >= 1
|
|
297
|
+
|
|
298
|
+
def test_missing_file_is_warning(self, tmp_path: Path) -> None:
|
|
299
|
+
"""Missing identity.json is a warning (not yet initialised)."""
|
|
300
|
+
path = tmp_path / "identity.json"
|
|
301
|
+
result = validate_identity_json(path)
|
|
302
|
+
assert not result.found
|
|
303
|
+
assert result.is_valid
|
|
304
|
+
assert len(result.warnings) == 1
|
|
305
|
+
|
|
306
|
+
def test_empty_name_is_error(self, tmp_path: Path) -> None:
|
|
307
|
+
"""An empty string for 'name' is an error."""
|
|
308
|
+
path = tmp_path / "identity.json"
|
|
309
|
+
_write(path, json.dumps({"name": " ", "fingerprint": "A" * 40}))
|
|
310
|
+
result = validate_identity_json(path)
|
|
311
|
+
assert any(e.field == "name" for e in result.errors)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
# Soul blueprint JSON tests
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class TestSoulBlueprintJson:
|
|
320
|
+
"""Tests for validate_soul_blueprint_json()."""
|
|
321
|
+
|
|
322
|
+
def test_valid_blueprint_passes(self, tmp_path: Path) -> None:
|
|
323
|
+
"""Happy path: blueprint with all required fields passes."""
|
|
324
|
+
path = tmp_path / "lumina.json"
|
|
325
|
+
_write(path, json.dumps({
|
|
326
|
+
"name": "lumina",
|
|
327
|
+
"display_name": "Lumina",
|
|
328
|
+
"category": "sovereign",
|
|
329
|
+
"vibe": "warmth and clarity",
|
|
330
|
+
"core_traits": ["empathy", "precision"],
|
|
331
|
+
"emotional_topology": {"warmth": 0.9, "precision": 0.8},
|
|
332
|
+
}))
|
|
333
|
+
result = validate_soul_blueprint_json(path)
|
|
334
|
+
assert result.is_valid, f"Unexpected errors: {result.errors}"
|
|
335
|
+
|
|
336
|
+
def test_missing_name_is_error(self, tmp_path: Path) -> None:
|
|
337
|
+
"""Blueprint without 'name' is an error."""
|
|
338
|
+
path = tmp_path / "missing_name.json"
|
|
339
|
+
_write(path, json.dumps({"display_name": "Lumina"}))
|
|
340
|
+
result = validate_soul_blueprint_json(path)
|
|
341
|
+
assert any(e.field == "name" for e in result.errors)
|
|
342
|
+
|
|
343
|
+
def test_missing_display_name_is_error(self, tmp_path: Path) -> None:
|
|
344
|
+
"""Blueprint without 'display_name' is an error."""
|
|
345
|
+
path = tmp_path / "missing_dn.json"
|
|
346
|
+
_write(path, json.dumps({"name": "lumina"}))
|
|
347
|
+
result = validate_soul_blueprint_json(path)
|
|
348
|
+
assert any(e.field == "display_name" for e in result.errors)
|
|
349
|
+
|
|
350
|
+
def test_core_traits_wrong_type_is_error(self, tmp_path: Path) -> None:
|
|
351
|
+
"""core_traits must be a list."""
|
|
352
|
+
path = tmp_path / "bad_traits.json"
|
|
353
|
+
_write(path, json.dumps({
|
|
354
|
+
"name": "lumina", "display_name": "Lumina",
|
|
355
|
+
"core_traits": "empathy, precision",
|
|
356
|
+
}))
|
|
357
|
+
result = validate_soul_blueprint_json(path)
|
|
358
|
+
assert any(e.field == "core_traits" for e in result.errors)
|
|
359
|
+
|
|
360
|
+
def test_emotional_topology_non_numeric_is_error(self, tmp_path: Path) -> None:
|
|
361
|
+
"""emotional_topology values must be numeric."""
|
|
362
|
+
path = tmp_path / "bad_topo.json"
|
|
363
|
+
_write(path, json.dumps({
|
|
364
|
+
"name": "lumina", "display_name": "Lumina",
|
|
365
|
+
"emotional_topology": {"warmth": "high"},
|
|
366
|
+
}))
|
|
367
|
+
result = validate_soul_blueprint_json(path)
|
|
368
|
+
assert any("emotional_topology" in (e.field or "") for e in result.errors)
|
|
369
|
+
|
|
370
|
+
def test_json_parse_error_is_error(self, tmp_path: Path) -> None:
|
|
371
|
+
"""Invalid JSON in a soul file is reported as an error with a line number."""
|
|
372
|
+
path = tmp_path / "broken.json"
|
|
373
|
+
_write(path, '{"name": "x"\n oops\n}')
|
|
374
|
+
result = validate_soul_blueprint_json(path)
|
|
375
|
+
assert len(result.errors) == 1
|
|
376
|
+
assert result.errors[0].line is not None
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
# validate_all integration tests
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class TestValidateAll:
|
|
385
|
+
"""Integration tests for validate_all()."""
|
|
386
|
+
|
|
387
|
+
def test_empty_home_produces_warnings_not_errors(self, agent_home: Path) -> None:
|
|
388
|
+
"""validate_all on a home with no config files produces warnings only."""
|
|
389
|
+
report = validate_all(agent_home)
|
|
390
|
+
assert report.total_errors == 0
|
|
391
|
+
# At least consciousness.yaml + model_profiles.yaml + identity.json are checked
|
|
392
|
+
assert len(report.results) >= 3
|
|
393
|
+
# All missing files produce warnings
|
|
394
|
+
assert report.total_warnings >= 3
|
|
395
|
+
|
|
396
|
+
def test_valid_configs_produce_clean_report(self, agent_home: Path) -> None:
|
|
397
|
+
"""A home with all valid configs reports no errors or warnings (except soul)."""
|
|
398
|
+
# consciousness.yaml
|
|
399
|
+
_write(agent_home / "config" / "consciousness.yaml",
|
|
400
|
+
"enabled: true\nfallback_chain:\n - ollama\n")
|
|
401
|
+
# identity.json
|
|
402
|
+
_write(agent_home / "identity" / "identity.json", json.dumps({
|
|
403
|
+
"name": "Opus",
|
|
404
|
+
"fingerprint": "A" * 40,
|
|
405
|
+
}))
|
|
406
|
+
|
|
407
|
+
report = validate_all(agent_home)
|
|
408
|
+
assert report.total_errors == 0
|
|
409
|
+
|
|
410
|
+
def test_identity_error_is_counted(self, agent_home: Path) -> None:
|
|
411
|
+
"""An error in identity.json is reflected in the report totals."""
|
|
412
|
+
_write(agent_home / "identity" / "identity.json",
|
|
413
|
+
json.dumps({"fingerprint": "A" * 40})) # missing 'name'
|
|
414
|
+
report = validate_all(agent_home)
|
|
415
|
+
assert report.total_errors >= 1
|
|
416
|
+
|
|
417
|
+
def test_soul_installed_blueprints_are_validated(self, agent_home: Path) -> None:
|
|
418
|
+
"""Installed soul blueprints are included in validate_all."""
|
|
419
|
+
soul_dir = agent_home / "soul" / "installed"
|
|
420
|
+
_write(soul_dir / "lumina.json", json.dumps({
|
|
421
|
+
"name": "lumina", "display_name": "Lumina",
|
|
422
|
+
}))
|
|
423
|
+
_write(soul_dir / "broken.json", '{"name": "x"}') # missing display_name
|
|
424
|
+
|
|
425
|
+
report = validate_all(agent_home)
|
|
426
|
+
config_names = [r.config_name for r in report.results]
|
|
427
|
+
assert any("lumina.json" in n for n in config_names)
|
|
428
|
+
assert any("broken.json" in n for n in config_names)
|
|
429
|
+
# broken.json is missing display_name → error
|
|
430
|
+
broken = next(r for r in report.results if "broken.json" in r.config_name)
|
|
431
|
+
assert not broken.is_valid
|
|
432
|
+
|
|
433
|
+
def test_report_is_valid_iff_no_errors(self, agent_home: Path) -> None:
|
|
434
|
+
"""ConfigValidationReport.is_valid is False when any result has errors."""
|
|
435
|
+
_write(agent_home / "identity" / "identity.json",
|
|
436
|
+
json.dumps({"fingerprint": "tooshort"})) # missing 'name'
|
|
437
|
+
report = validate_all(agent_home)
|
|
438
|
+
assert not report.is_valid
|
|
439
|
+
|
|
440
|
+
def test_validate_all_soul_dir_absent(self, tmp_path: Path) -> None:
|
|
441
|
+
"""When soul/ directory doesn't exist, validate_all still runs."""
|
|
442
|
+
home = tmp_path / ".skcapstone"
|
|
443
|
+
(home / "config").mkdir(parents=True)
|
|
444
|
+
(home / "identity").mkdir()
|
|
445
|
+
# soul/ intentionally absent
|
|
446
|
+
report = validate_all(home)
|
|
447
|
+
config_names = [r.config_name for r in report.results]
|
|
448
|
+
assert not any("soul/" in n for n in config_names)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ---------------------------------------------------------------------------
|
|
452
|
+
# CLI smoke tests
|
|
453
|
+
# ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class TestConfigValidateCli:
|
|
457
|
+
"""CLI smoke tests for `skcapstone config validate`."""
|
|
458
|
+
|
|
459
|
+
def test_cli_json_output_structure(self, agent_home: Path) -> None:
|
|
460
|
+
"""--json-out emits a JSON object with 'valid', 'files', etc."""
|
|
461
|
+
from skcapstone.cli import main
|
|
462
|
+
|
|
463
|
+
runner = CliRunner()
|
|
464
|
+
result = runner.invoke(
|
|
465
|
+
main,
|
|
466
|
+
["--agent", "", "config", "validate",
|
|
467
|
+
"--home", str(agent_home), "--json-out"],
|
|
468
|
+
)
|
|
469
|
+
assert result.exit_code in (0, 1), result.output
|
|
470
|
+
data = json.loads(result.output)
|
|
471
|
+
assert "valid" in data
|
|
472
|
+
assert "total_errors" in data
|
|
473
|
+
assert "total_warnings" in data
|
|
474
|
+
assert "files" in data
|
|
475
|
+
assert isinstance(data["files"], list)
|
|
476
|
+
|
|
477
|
+
def test_cli_exits_zero_when_valid(self, agent_home: Path) -> None:
|
|
478
|
+
"""CLI exits 0 when all configs are valid (missing files are warnings)."""
|
|
479
|
+
from skcapstone.cli import main
|
|
480
|
+
|
|
481
|
+
# Write a valid identity so the only issues are warnings (missing files)
|
|
482
|
+
_write(agent_home / "identity" / "identity.json", json.dumps({
|
|
483
|
+
"name": "TestAgent",
|
|
484
|
+
"fingerprint": "A" * 40,
|
|
485
|
+
}))
|
|
486
|
+
# A fully valid consciousness config
|
|
487
|
+
_write(agent_home / "config" / "consciousness.yaml",
|
|
488
|
+
"enabled: true\n")
|
|
489
|
+
|
|
490
|
+
runner = CliRunner()
|
|
491
|
+
result = runner.invoke(
|
|
492
|
+
main,
|
|
493
|
+
["config", "validate", "--home", str(agent_home)],
|
|
494
|
+
)
|
|
495
|
+
# Warnings are present (missing model_profiles, soul) but exit 0
|
|
496
|
+
assert result.exit_code == 0
|
|
497
|
+
|
|
498
|
+
def test_cli_exits_one_on_error(self, agent_home: Path) -> None:
|
|
499
|
+
"""CLI exits 1 when identity.json has a schema error."""
|
|
500
|
+
from skcapstone.cli import main
|
|
501
|
+
|
|
502
|
+
_write(agent_home / "identity" / "identity.json",
|
|
503
|
+
json.dumps({"fingerprint": "BADFINGERPRINT"})) # missing 'name'
|
|
504
|
+
|
|
505
|
+
runner = CliRunner()
|
|
506
|
+
result = runner.invoke(
|
|
507
|
+
main,
|
|
508
|
+
["config", "validate", "--home", str(agent_home)],
|
|
509
|
+
)
|
|
510
|
+
assert result.exit_code == 1
|
|
511
|
+
|
|
512
|
+
def test_cli_strict_exits_one_on_warnings(self, agent_home: Path) -> None:
|
|
513
|
+
"""--strict causes exit 1 when only warnings are present."""
|
|
514
|
+
from skcapstone.cli import main
|
|
515
|
+
|
|
516
|
+
# Valid identity but missing other configs → warnings
|
|
517
|
+
_write(agent_home / "identity" / "identity.json", json.dumps({
|
|
518
|
+
"name": "TestAgent",
|
|
519
|
+
"fingerprint": "A" * 40,
|
|
520
|
+
}))
|
|
521
|
+
|
|
522
|
+
runner = CliRunner()
|
|
523
|
+
result = runner.invoke(
|
|
524
|
+
main,
|
|
525
|
+
["config", "validate", "--home", str(agent_home), "--strict"],
|
|
526
|
+
)
|
|
527
|
+
# model_profiles.yaml and consciousness.yaml are missing → warnings
|
|
528
|
+
# --strict turns those into a failure
|
|
529
|
+
assert result.exit_code == 1
|