@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,208 @@
|
|
|
1
|
+
"""Tests for the skcapstone memory CLI commands.
|
|
2
|
+
|
|
3
|
+
Tests argument parsing, option defaults, and error paths using
|
|
4
|
+
click.testing.CliRunner. External I/O (disk, memory engine) is mocked.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from unittest.mock import MagicMock, patch
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from click.testing import CliRunner
|
|
14
|
+
|
|
15
|
+
from skcapstone._cli_monolith import main
|
|
16
|
+
from skcapstone.models import MemoryEntry, MemoryLayer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def runner() -> CliRunner:
|
|
21
|
+
return CliRunner()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def fake_entry() -> MemoryEntry:
|
|
26
|
+
"""A minimal MemoryEntry returned by mocked mem_store."""
|
|
27
|
+
return MemoryEntry(
|
|
28
|
+
memory_id="abc12345",
|
|
29
|
+
content="test memory content",
|
|
30
|
+
tags=["test"],
|
|
31
|
+
source="cli",
|
|
32
|
+
importance=0.5,
|
|
33
|
+
layer=MemoryLayer.SHORT_TERM,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestMemoryStoreArgs:
|
|
38
|
+
"""Tests for `skcapstone memory store` argument parsing."""
|
|
39
|
+
|
|
40
|
+
def test_store_requires_content(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
41
|
+
"""Expected: missing content argument produces a usage error."""
|
|
42
|
+
result = runner.invoke(main, ["memory", "store"])
|
|
43
|
+
assert result.exit_code != 0
|
|
44
|
+
assert "Missing argument" in result.output or result.exit_code == 2
|
|
45
|
+
|
|
46
|
+
def test_store_exits_when_no_agent_home(
|
|
47
|
+
self, runner: CliRunner, tmp_path: Path
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Expected: exits with error when agent home does not exist."""
|
|
50
|
+
nonexistent = str(tmp_path / "no_such_agent")
|
|
51
|
+
result = runner.invoke(
|
|
52
|
+
main, ["memory", "store", "--home", nonexistent, "some memory"]
|
|
53
|
+
)
|
|
54
|
+
assert result.exit_code != 0
|
|
55
|
+
assert "No agent found" in result.output
|
|
56
|
+
|
|
57
|
+
def test_store_default_importance(
|
|
58
|
+
self, runner: CliRunner, tmp_path: Path, fake_entry: MemoryEntry
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Expected: importance defaults to 0.5 when not provided."""
|
|
61
|
+
agent_home = tmp_path / ".skcapstone"
|
|
62
|
+
agent_home.mkdir()
|
|
63
|
+
|
|
64
|
+
with patch("skcapstone._cli_monolith.mem_store", create=True), patch(
|
|
65
|
+
"skcapstone.memory_engine.store", return_value=fake_entry
|
|
66
|
+
) as mock_store, patch("skcapstone.pillars.security.audit_event"):
|
|
67
|
+
result = runner.invoke(
|
|
68
|
+
main,
|
|
69
|
+
["memory", "store", "--home", str(agent_home), "hello world"],
|
|
70
|
+
)
|
|
71
|
+
# Verify importance was 0.5 (default) if store was called
|
|
72
|
+
if mock_store.called:
|
|
73
|
+
_, kwargs = mock_store.call_args
|
|
74
|
+
assert kwargs.get("importance", 0.5) == 0.5
|
|
75
|
+
|
|
76
|
+
def test_store_custom_tags(
|
|
77
|
+
self, runner: CliRunner, tmp_path: Path, fake_entry: MemoryEntry
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Expected: multiple --tag options are collected into a list."""
|
|
80
|
+
agent_home = tmp_path / ".skcapstone"
|
|
81
|
+
agent_home.mkdir()
|
|
82
|
+
|
|
83
|
+
captured_tags: list[list[str]] = []
|
|
84
|
+
|
|
85
|
+
def fake_store(**kwargs):
|
|
86
|
+
captured_tags.append(kwargs.get("tags", []))
|
|
87
|
+
return fake_entry
|
|
88
|
+
|
|
89
|
+
with patch("skcapstone.memory_engine.store", side_effect=fake_store), patch(
|
|
90
|
+
"skcapstone.pillars.security.audit_event"
|
|
91
|
+
):
|
|
92
|
+
runner.invoke(
|
|
93
|
+
main,
|
|
94
|
+
[
|
|
95
|
+
"memory",
|
|
96
|
+
"store",
|
|
97
|
+
"--home",
|
|
98
|
+
str(agent_home),
|
|
99
|
+
"-t",
|
|
100
|
+
"alpha",
|
|
101
|
+
"-t",
|
|
102
|
+
"beta",
|
|
103
|
+
"tagged memory",
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
if captured_tags:
|
|
107
|
+
assert "alpha" in captured_tags[0]
|
|
108
|
+
assert "beta" in captured_tags[0]
|
|
109
|
+
|
|
110
|
+
def test_store_layer_choice_validation(
|
|
111
|
+
self, runner: CliRunner, tmp_path: Path
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Edge case: invalid layer value is rejected by click."""
|
|
114
|
+
agent_home = tmp_path / ".skcapstone"
|
|
115
|
+
agent_home.mkdir()
|
|
116
|
+
result = runner.invoke(
|
|
117
|
+
main,
|
|
118
|
+
[
|
|
119
|
+
"memory",
|
|
120
|
+
"store",
|
|
121
|
+
"--home",
|
|
122
|
+
str(agent_home),
|
|
123
|
+
"--layer",
|
|
124
|
+
"invalid-layer",
|
|
125
|
+
"content",
|
|
126
|
+
],
|
|
127
|
+
)
|
|
128
|
+
assert result.exit_code != 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestMemorySearchArgs:
|
|
132
|
+
"""Tests for `skcapstone memory search` argument parsing."""
|
|
133
|
+
|
|
134
|
+
def test_search_requires_query(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
135
|
+
"""Expected: missing query argument produces a usage error."""
|
|
136
|
+
result = runner.invoke(main, ["memory", "search"])
|
|
137
|
+
assert result.exit_code != 0
|
|
138
|
+
|
|
139
|
+
def test_search_exits_when_no_agent_home(
|
|
140
|
+
self, runner: CliRunner, tmp_path: Path
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Expected: exits with error when agent home does not exist."""
|
|
143
|
+
nonexistent = str(tmp_path / "no_such_agent")
|
|
144
|
+
result = runner.invoke(
|
|
145
|
+
main, ["memory", "search", "--home", nonexistent, "my query"]
|
|
146
|
+
)
|
|
147
|
+
assert result.exit_code != 0
|
|
148
|
+
assert "No agent found" in result.output
|
|
149
|
+
|
|
150
|
+
def test_search_no_results(
|
|
151
|
+
self, runner: CliRunner, tmp_path: Path
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Expected: prints 'no memories' message when search returns empty."""
|
|
154
|
+
agent_home = tmp_path / ".skcapstone"
|
|
155
|
+
agent_home.mkdir()
|
|
156
|
+
|
|
157
|
+
with patch("skcapstone.memory_engine.search", return_value=[]):
|
|
158
|
+
result = runner.invoke(
|
|
159
|
+
main,
|
|
160
|
+
["memory", "search", "--home", str(agent_home), "nonexistent"],
|
|
161
|
+
)
|
|
162
|
+
# Should succeed and report no results
|
|
163
|
+
assert result.exit_code == 0
|
|
164
|
+
assert "No memories" in result.output or "no memories" in result.output.lower()
|
|
165
|
+
|
|
166
|
+
def test_search_default_limit(
|
|
167
|
+
self, runner: CliRunner, tmp_path: Path
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Expected: limit defaults to 20 when not specified."""
|
|
170
|
+
agent_home = tmp_path / ".skcapstone"
|
|
171
|
+
agent_home.mkdir()
|
|
172
|
+
|
|
173
|
+
captured: list[dict] = []
|
|
174
|
+
|
|
175
|
+
def fake_search(**kwargs):
|
|
176
|
+
captured.append(kwargs)
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
with patch("skcapstone.memory_engine.search", side_effect=fake_search):
|
|
180
|
+
runner.invoke(
|
|
181
|
+
main,
|
|
182
|
+
["memory", "search", "--home", str(agent_home), "query"],
|
|
183
|
+
)
|
|
184
|
+
if captured:
|
|
185
|
+
assert captured[0].get("limit", 20) == 20
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TestMemorySubcommandHelp:
|
|
189
|
+
"""Tests that memory subcommands expose help text."""
|
|
190
|
+
|
|
191
|
+
def test_memory_group_help(self, runner: CliRunner) -> None:
|
|
192
|
+
"""Expected: `memory --help` exits 0 and shows subcommands."""
|
|
193
|
+
result = runner.invoke(main, ["memory", "--help"])
|
|
194
|
+
assert result.exit_code == 0
|
|
195
|
+
assert "store" in result.output
|
|
196
|
+
|
|
197
|
+
def test_memory_store_help(self, runner: CliRunner) -> None:
|
|
198
|
+
"""Expected: `memory store --help` exits 0 and documents options."""
|
|
199
|
+
result = runner.invoke(main, ["memory", "store", "--help"])
|
|
200
|
+
assert result.exit_code == 0
|
|
201
|
+
assert "--tag" in result.output or "-t" in result.output
|
|
202
|
+
assert "--importance" in result.output or "-i" in result.output
|
|
203
|
+
|
|
204
|
+
def test_memory_search_help(self, runner: CliRunner) -> None:
|
|
205
|
+
"""Expected: `memory search --help` exits 0 and documents options."""
|
|
206
|
+
result = runner.invoke(main, ["memory", "search", "--help"])
|
|
207
|
+
assert result.exit_code == 0
|
|
208
|
+
assert "QUERY" in result.output or "query" in result.output.lower()
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Tests for skcapstone profile CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import date, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import MagicMock, patch
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
import yaml
|
|
12
|
+
from click.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
from skcapstone.cli import main
|
|
15
|
+
from skcapstone.prompt_adapter import ModelProfile
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Helpers
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _make_profile(**kwargs) -> ModelProfile:
|
|
24
|
+
"""Return a minimal ModelProfile with optional overrides."""
|
|
25
|
+
defaults = {
|
|
26
|
+
"model_pattern": "test-.*",
|
|
27
|
+
"family": "test",
|
|
28
|
+
"last_updated": "2026-03-02",
|
|
29
|
+
}
|
|
30
|
+
defaults.update(kwargs)
|
|
31
|
+
return ModelProfile(**defaults)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _make_adapter(profiles: list[ModelProfile]) -> MagicMock:
|
|
35
|
+
"""Return a mock PromptAdapter pre-loaded with *profiles*."""
|
|
36
|
+
adapter = MagicMock()
|
|
37
|
+
adapter.profiles = profiles
|
|
38
|
+
adapter.resolve_profile.side_effect = lambda model: next(
|
|
39
|
+
(p for p in profiles if p.family in model or p.model_pattern in model),
|
|
40
|
+
_make_profile(model_pattern=".*", family="generic"),
|
|
41
|
+
)
|
|
42
|
+
return adapter
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# skcapstone profile list
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestProfileList:
|
|
51
|
+
"""Tests for 'skcapstone profile list'."""
|
|
52
|
+
|
|
53
|
+
def test_list_shows_families(self):
|
|
54
|
+
"""Happy path: table includes all profile families."""
|
|
55
|
+
runner = CliRunner()
|
|
56
|
+
profiles = [
|
|
57
|
+
_make_profile(model_pattern="claude-.*", family="claude"),
|
|
58
|
+
_make_profile(model_pattern="grok-.*", family="grok"),
|
|
59
|
+
]
|
|
60
|
+
adapter = _make_adapter(profiles)
|
|
61
|
+
|
|
62
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
63
|
+
result = runner.invoke(main, ["profile", "list"])
|
|
64
|
+
|
|
65
|
+
assert result.exit_code == 0
|
|
66
|
+
assert "claude" in result.output
|
|
67
|
+
assert "grok" in result.output
|
|
68
|
+
|
|
69
|
+
def test_list_json_output(self):
|
|
70
|
+
"""--json flag emits a valid JSON array."""
|
|
71
|
+
runner = CliRunner()
|
|
72
|
+
profiles = [
|
|
73
|
+
_make_profile(model_pattern="claude-.*", family="claude"),
|
|
74
|
+
]
|
|
75
|
+
adapter = _make_adapter(profiles)
|
|
76
|
+
|
|
77
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
78
|
+
result = runner.invoke(main, ["profile", "list", "--json"])
|
|
79
|
+
|
|
80
|
+
assert result.exit_code == 0
|
|
81
|
+
parsed = json.loads(result.output)
|
|
82
|
+
assert isinstance(parsed, list)
|
|
83
|
+
assert parsed[0]["family"] == "claude"
|
|
84
|
+
|
|
85
|
+
def test_list_empty_exits_1(self):
|
|
86
|
+
"""When no profiles are loaded the command exits 1."""
|
|
87
|
+
runner = CliRunner()
|
|
88
|
+
adapter = _make_adapter([])
|
|
89
|
+
|
|
90
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
91
|
+
result = runner.invoke(main, ["profile", "list"])
|
|
92
|
+
|
|
93
|
+
assert result.exit_code == 1
|
|
94
|
+
assert "No profiles" in result.output
|
|
95
|
+
|
|
96
|
+
def test_list_uses_bundled_profiles_by_default(self):
|
|
97
|
+
"""Without mocking, the command loads real bundled profiles."""
|
|
98
|
+
runner = CliRunner()
|
|
99
|
+
result = runner.invoke(main, ["profile", "list"])
|
|
100
|
+
|
|
101
|
+
assert result.exit_code == 0
|
|
102
|
+
# Bundled profiles include at least claude and grok
|
|
103
|
+
assert "claude" in result.output
|
|
104
|
+
assert "grok" in result.output
|
|
105
|
+
|
|
106
|
+
def test_list_json_includes_all_fields(self):
|
|
107
|
+
"""JSON output contains expected ModelProfile fields."""
|
|
108
|
+
runner = CliRunner()
|
|
109
|
+
profiles = [_make_profile(family="deepseek-r1", model_pattern="deepseek-r1.*")]
|
|
110
|
+
adapter = _make_adapter(profiles)
|
|
111
|
+
|
|
112
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
113
|
+
result = runner.invoke(main, ["profile", "list", "--json"])
|
|
114
|
+
|
|
115
|
+
parsed = json.loads(result.output)
|
|
116
|
+
record = parsed[0]
|
|
117
|
+
assert "family" in record
|
|
118
|
+
assert "model_pattern" in record
|
|
119
|
+
assert "system_prompt_mode" in record
|
|
120
|
+
assert "last_updated" in record
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# skcapstone profile show
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestProfileShow:
|
|
129
|
+
"""Tests for 'skcapstone profile show MODEL'."""
|
|
130
|
+
|
|
131
|
+
def test_show_known_model(self):
|
|
132
|
+
"""show resolves a known model and prints its family."""
|
|
133
|
+
runner = CliRunner()
|
|
134
|
+
result = runner.invoke(main, ["profile", "show", "claude-opus-4-5"])
|
|
135
|
+
|
|
136
|
+
assert result.exit_code == 0
|
|
137
|
+
assert "claude" in result.output
|
|
138
|
+
|
|
139
|
+
def test_show_prints_fields(self):
|
|
140
|
+
"""show prints key profile fields (system_prompt_mode, structure_format, etc.)."""
|
|
141
|
+
runner = CliRunner()
|
|
142
|
+
result = runner.invoke(main, ["profile", "show", "claude-opus-4-5"])
|
|
143
|
+
|
|
144
|
+
assert result.exit_code == 0
|
|
145
|
+
assert "system_prompt_mode" in result.output
|
|
146
|
+
assert "structure_format" in result.output
|
|
147
|
+
assert "last_updated" in result.output
|
|
148
|
+
|
|
149
|
+
def test_show_json_output(self):
|
|
150
|
+
"""--json flag emits a JSON object with all profile fields."""
|
|
151
|
+
runner = CliRunner()
|
|
152
|
+
result = runner.invoke(main, ["profile", "show", "grok-3", "--json"])
|
|
153
|
+
|
|
154
|
+
assert result.exit_code == 0
|
|
155
|
+
parsed = json.loads(result.output)
|
|
156
|
+
assert isinstance(parsed, dict)
|
|
157
|
+
assert parsed["family"] == "grok"
|
|
158
|
+
assert "model_pattern" in parsed
|
|
159
|
+
|
|
160
|
+
def test_show_unknown_model_falls_back_to_generic(self):
|
|
161
|
+
"""Unknown model falls back to generic profile gracefully."""
|
|
162
|
+
runner = CliRunner()
|
|
163
|
+
result = runner.invoke(main, ["profile", "show", "totally-unknown-xyz"])
|
|
164
|
+
|
|
165
|
+
assert result.exit_code == 0
|
|
166
|
+
assert "generic" in result.output
|
|
167
|
+
|
|
168
|
+
def test_show_deepseek_r1_omits_system(self):
|
|
169
|
+
"""DeepSeek R1 profile shows system_prompt_mode = omit."""
|
|
170
|
+
runner = CliRunner()
|
|
171
|
+
result = runner.invoke(main, ["profile", "show", "deepseek-r1-70b"])
|
|
172
|
+
|
|
173
|
+
assert result.exit_code == 0
|
|
174
|
+
assert "omit" in result.output
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
# skcapstone profile stale
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class TestProfileStale:
|
|
183
|
+
"""Tests for 'skcapstone profile stale'."""
|
|
184
|
+
|
|
185
|
+
def test_stale_shows_old_profiles(self):
|
|
186
|
+
"""Profiles with old last_updated dates appear in stale output."""
|
|
187
|
+
runner = CliRunner()
|
|
188
|
+
old_date = (date.today() - timedelta(days=200)).isoformat()
|
|
189
|
+
profiles = [
|
|
190
|
+
_make_profile(family="old-model", last_updated=old_date),
|
|
191
|
+
_make_profile(family="new-model", last_updated=date.today().isoformat()),
|
|
192
|
+
]
|
|
193
|
+
adapter = _make_adapter(profiles)
|
|
194
|
+
|
|
195
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
196
|
+
result = runner.invoke(main, ["profile", "stale"])
|
|
197
|
+
|
|
198
|
+
assert result.exit_code == 0
|
|
199
|
+
assert "old-model" in result.output
|
|
200
|
+
assert "new-model" not in result.output
|
|
201
|
+
|
|
202
|
+
def test_stale_all_fresh_prints_ok(self):
|
|
203
|
+
"""When all profiles are recent, a green OK message is shown."""
|
|
204
|
+
runner = CliRunner()
|
|
205
|
+
fresh = date.today().isoformat()
|
|
206
|
+
profiles = [
|
|
207
|
+
_make_profile(family="claude", last_updated=fresh),
|
|
208
|
+
_make_profile(family="grok", last_updated=fresh),
|
|
209
|
+
]
|
|
210
|
+
adapter = _make_adapter(profiles)
|
|
211
|
+
|
|
212
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
213
|
+
result = runner.invoke(main, ["profile", "stale"])
|
|
214
|
+
|
|
215
|
+
assert result.exit_code == 0
|
|
216
|
+
assert "All profiles updated" in result.output
|
|
217
|
+
|
|
218
|
+
def test_stale_missing_last_updated_flagged(self):
|
|
219
|
+
"""Profile with missing last_updated is always considered stale."""
|
|
220
|
+
runner = CliRunner()
|
|
221
|
+
profiles = [
|
|
222
|
+
_make_profile(family="no-date-model", last_updated=""),
|
|
223
|
+
]
|
|
224
|
+
adapter = _make_adapter(profiles)
|
|
225
|
+
|
|
226
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
227
|
+
result = runner.invoke(main, ["profile", "stale"])
|
|
228
|
+
|
|
229
|
+
assert result.exit_code == 0
|
|
230
|
+
assert "no-date-model" in result.output
|
|
231
|
+
|
|
232
|
+
def test_stale_custom_days_flag(self):
|
|
233
|
+
"""--days flag changes the staleness threshold."""
|
|
234
|
+
runner = CliRunner()
|
|
235
|
+
# 45 days old — stale at 30 days, fresh at 90 days
|
|
236
|
+
mid_date = (date.today() - timedelta(days=45)).isoformat()
|
|
237
|
+
profiles = [
|
|
238
|
+
_make_profile(family="mid-model", last_updated=mid_date),
|
|
239
|
+
]
|
|
240
|
+
adapter = _make_adapter(profiles)
|
|
241
|
+
|
|
242
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
243
|
+
# Should be stale at 30-day threshold
|
|
244
|
+
r30 = runner.invoke(main, ["profile", "stale", "--days", "30"])
|
|
245
|
+
# Should be fresh at 90-day threshold
|
|
246
|
+
r90 = runner.invoke(main, ["profile", "stale", "--days", "90"])
|
|
247
|
+
|
|
248
|
+
assert r30.exit_code == 0
|
|
249
|
+
assert "mid-model" in r30.output
|
|
250
|
+
|
|
251
|
+
assert r90.exit_code == 0
|
|
252
|
+
assert "All profiles updated" in r90.output
|
|
253
|
+
|
|
254
|
+
def test_stale_json_output(self):
|
|
255
|
+
"""--json flag emits a JSON array of stale profiles."""
|
|
256
|
+
runner = CliRunner()
|
|
257
|
+
old_date = (date.today() - timedelta(days=200)).isoformat()
|
|
258
|
+
profiles = [
|
|
259
|
+
_make_profile(family="stale-json-model", last_updated=old_date),
|
|
260
|
+
]
|
|
261
|
+
adapter = _make_adapter(profiles)
|
|
262
|
+
|
|
263
|
+
with patch("skcapstone.cli.profile_cmd._get_adapter", return_value=adapter):
|
|
264
|
+
result = runner.invoke(main, ["profile", "stale", "--json"])
|
|
265
|
+
|
|
266
|
+
assert result.exit_code == 0
|
|
267
|
+
parsed = json.loads(result.output)
|
|
268
|
+
assert isinstance(parsed, list)
|
|
269
|
+
assert len(parsed) == 1
|
|
270
|
+
assert parsed[0]["family"] == "stale-json-model"
|
|
271
|
+
assert "_days_old" in parsed[0]
|
|
272
|
+
assert parsed[0]["_days_old"] >= 200
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
276
|
+
# _parse_last_updated helper
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class TestParseLastUpdated:
|
|
281
|
+
"""Unit tests for _parse_last_updated."""
|
|
282
|
+
|
|
283
|
+
def test_valid_date(self):
|
|
284
|
+
from skcapstone.cli.profile_cmd import _parse_last_updated
|
|
285
|
+
result = _parse_last_updated("2026-03-02")
|
|
286
|
+
assert result == date(2026, 3, 2)
|
|
287
|
+
|
|
288
|
+
def test_empty_string_returns_none(self):
|
|
289
|
+
from skcapstone.cli.profile_cmd import _parse_last_updated
|
|
290
|
+
assert _parse_last_updated("") is None
|
|
291
|
+
|
|
292
|
+
def test_invalid_format_returns_none(self):
|
|
293
|
+
from skcapstone.cli.profile_cmd import _parse_last_updated
|
|
294
|
+
assert _parse_last_updated("not-a-date") is None
|