@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,385 @@
|
|
|
1
|
+
"""Tests for the skcapstone agent-to-agent chat module.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- AgentChat.send() with and without transport
|
|
5
|
+
- AgentChat.receive() and inbox retrieval
|
|
6
|
+
- Payload serialization round-trip
|
|
7
|
+
- CLI commands (send, inbox) via CliRunner
|
|
8
|
+
- Graceful degradation when dependencies are missing
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from unittest.mock import MagicMock, patch
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
from click.testing import CliRunner
|
|
19
|
+
|
|
20
|
+
from skcapstone.chat import (
|
|
21
|
+
AgentChat,
|
|
22
|
+
_pack_chat_payload,
|
|
23
|
+
_short_timestamp,
|
|
24
|
+
_unpack_chat_payload,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def tmp_home(tmp_path):
|
|
30
|
+
"""Create a minimal agent home directory."""
|
|
31
|
+
home = tmp_path / ".skcapstone"
|
|
32
|
+
(home / "config").mkdir(parents=True)
|
|
33
|
+
(home / "identity").mkdir(parents=True)
|
|
34
|
+
identity_data = {"name": "TestAgent", "fingerprint": "AABB1234", "capauth_managed": False}
|
|
35
|
+
(home / "identity" / "identity.json").write_text(json.dumps(identity_data))
|
|
36
|
+
(home / "manifest.json").write_text(json.dumps({"name": "TestAgent", "version": "0.1.0"}))
|
|
37
|
+
import yaml
|
|
38
|
+
(home / "config" / "config.yaml").write_text(yaml.dump({"agent_name": "TestAgent"}))
|
|
39
|
+
return home
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestPayloadRoundTrip:
|
|
43
|
+
"""Verify pack/unpack preserves message data."""
|
|
44
|
+
|
|
45
|
+
def test_pack_unpack_basic(self):
|
|
46
|
+
"""Basic message survives serialize/deserialize."""
|
|
47
|
+
mock_msg = MagicMock()
|
|
48
|
+
mock_msg.id = "msg-123"
|
|
49
|
+
mock_msg.sender = "opus"
|
|
50
|
+
mock_msg.recipient = "lumina"
|
|
51
|
+
mock_msg.content = "Hello sovereign world"
|
|
52
|
+
mock_msg.thread_id = None
|
|
53
|
+
mock_msg.timestamp.isoformat.return_value = "2026-02-24T05:00:00Z"
|
|
54
|
+
|
|
55
|
+
packed = _pack_chat_payload(mock_msg)
|
|
56
|
+
unpacked = _unpack_chat_payload(packed, sender="fallback", recipient="fallback")
|
|
57
|
+
|
|
58
|
+
assert unpacked["sender"] == "opus"
|
|
59
|
+
assert unpacked["recipient"] == "lumina"
|
|
60
|
+
assert unpacked["content"] == "Hello sovereign world"
|
|
61
|
+
|
|
62
|
+
def test_pack_unpack_with_thread(self):
|
|
63
|
+
"""Thread ID survives round-trip."""
|
|
64
|
+
mock_msg = MagicMock()
|
|
65
|
+
mock_msg.id = "msg-456"
|
|
66
|
+
mock_msg.sender = "jarvis"
|
|
67
|
+
mock_msg.recipient = "opus"
|
|
68
|
+
mock_msg.content = "Build update"
|
|
69
|
+
mock_msg.thread_id = "deploy-01"
|
|
70
|
+
mock_msg.timestamp.isoformat.return_value = "2026-02-24T05:00:00Z"
|
|
71
|
+
|
|
72
|
+
packed = _pack_chat_payload(mock_msg)
|
|
73
|
+
unpacked = _unpack_chat_payload(packed, sender="x", recipient="y")
|
|
74
|
+
|
|
75
|
+
assert unpacked["thread_id"] == "deploy-01"
|
|
76
|
+
|
|
77
|
+
def test_unpack_plain_text_fallback(self):
|
|
78
|
+
"""Non-JSON payloads treated as plain text."""
|
|
79
|
+
result = _unpack_chat_payload("just plain text", sender="opus", recipient="lumina")
|
|
80
|
+
|
|
81
|
+
assert result["content"] == "just plain text"
|
|
82
|
+
assert result["sender"] == "opus"
|
|
83
|
+
assert result["recipient"] == "lumina"
|
|
84
|
+
|
|
85
|
+
def test_unpack_non_skchat_json(self):
|
|
86
|
+
"""JSON without skchat_version falls back to plain text."""
|
|
87
|
+
payload = json.dumps({"type": "other", "data": "test"})
|
|
88
|
+
result = _unpack_chat_payload(payload, sender="a", recipient="b")
|
|
89
|
+
|
|
90
|
+
assert result["sender"] == "a"
|
|
91
|
+
assert "other" in result["content"] or result["content"] == payload
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestShortTimestamp:
|
|
95
|
+
"""Verify timestamp formatting."""
|
|
96
|
+
|
|
97
|
+
def test_format(self):
|
|
98
|
+
"""Timestamp is HH:MM:SS format."""
|
|
99
|
+
ts = _short_timestamp()
|
|
100
|
+
assert len(ts) == 8
|
|
101
|
+
assert ts[2] == ":"
|
|
102
|
+
assert ts[5] == ":"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestAgentChatSend:
|
|
106
|
+
"""Tests for AgentChat.send() without real transport."""
|
|
107
|
+
|
|
108
|
+
def test_send_stores_locally_without_skcomm(self, tmp_home):
|
|
109
|
+
"""Message is stored in history even without SKComm."""
|
|
110
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
111
|
+
|
|
112
|
+
mock_history = MagicMock()
|
|
113
|
+
mock_history.store_message.return_value = "mem-abc"
|
|
114
|
+
agent._history = mock_history
|
|
115
|
+
agent._ensure_comm = lambda: False
|
|
116
|
+
|
|
117
|
+
result = agent.send("lumina", "Hello!")
|
|
118
|
+
|
|
119
|
+
assert result["stored"] is True
|
|
120
|
+
assert result["delivered"] is False
|
|
121
|
+
mock_history.store_message.assert_called_once()
|
|
122
|
+
|
|
123
|
+
def test_send_delivers_with_skcomm(self, tmp_home):
|
|
124
|
+
"""Message is delivered when SKComm has transports."""
|
|
125
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
126
|
+
|
|
127
|
+
mock_comm = MagicMock()
|
|
128
|
+
mock_report = MagicMock()
|
|
129
|
+
mock_report.delivered = True
|
|
130
|
+
mock_report.successful_transport = "syncthing"
|
|
131
|
+
mock_comm.send.return_value = mock_report
|
|
132
|
+
mock_comm.router.transports = [MagicMock()]
|
|
133
|
+
agent._comm = mock_comm
|
|
134
|
+
|
|
135
|
+
mock_history = MagicMock()
|
|
136
|
+
mock_history.store_message.return_value = "mem-xyz"
|
|
137
|
+
agent._history = mock_history
|
|
138
|
+
|
|
139
|
+
result = agent.send("lumina", "Delivered message")
|
|
140
|
+
|
|
141
|
+
assert result["stored"] is True
|
|
142
|
+
assert result["delivered"] is True
|
|
143
|
+
assert result["transport"] == "syncthing"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestAgentChatReceive:
|
|
147
|
+
"""Tests for AgentChat.receive()."""
|
|
148
|
+
|
|
149
|
+
def test_receive_empty_inbox(self, tmp_home):
|
|
150
|
+
"""Empty inbox returns empty list."""
|
|
151
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
152
|
+
|
|
153
|
+
mock_comm = MagicMock()
|
|
154
|
+
mock_comm.receive.return_value = []
|
|
155
|
+
mock_comm.router.transports = [MagicMock()]
|
|
156
|
+
agent._comm = mock_comm
|
|
157
|
+
|
|
158
|
+
messages = agent.receive()
|
|
159
|
+
assert messages == []
|
|
160
|
+
|
|
161
|
+
def test_receive_with_messages(self, tmp_home):
|
|
162
|
+
"""Incoming envelopes are parsed into message dicts."""
|
|
163
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
164
|
+
|
|
165
|
+
payload = json.dumps({
|
|
166
|
+
"skchat_version": "1.0.0",
|
|
167
|
+
"message_id": "msg-1",
|
|
168
|
+
"sender": "lumina",
|
|
169
|
+
"recipient": "opus",
|
|
170
|
+
"content": "Hello from Lumina",
|
|
171
|
+
"thread_id": None,
|
|
172
|
+
"timestamp": "2026-02-24T05:00:00Z",
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
envelope = MagicMock()
|
|
176
|
+
envelope.sender = "lumina"
|
|
177
|
+
envelope.recipient = "opus"
|
|
178
|
+
envelope.payload.content = payload
|
|
179
|
+
|
|
180
|
+
mock_comm = MagicMock()
|
|
181
|
+
mock_comm.receive.return_value = [envelope]
|
|
182
|
+
mock_comm.router.transports = [MagicMock()]
|
|
183
|
+
agent._comm = mock_comm
|
|
184
|
+
|
|
185
|
+
mock_history = MagicMock()
|
|
186
|
+
agent._history = mock_history
|
|
187
|
+
|
|
188
|
+
messages = agent.receive()
|
|
189
|
+
|
|
190
|
+
assert len(messages) == 1
|
|
191
|
+
assert messages[0]["sender"] == "lumina"
|
|
192
|
+
assert messages[0]["content"] == "Hello from Lumina"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestAgentChatForward:
|
|
196
|
+
"""Tests for AgentChat.forward()."""
|
|
197
|
+
|
|
198
|
+
def test_forward_preserves_original_sender_and_timestamp(self, tmp_home):
|
|
199
|
+
"""Forward envelope preserves forwarded_from and forwarded_at."""
|
|
200
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
201
|
+
agent._ensure_comm = lambda: False
|
|
202
|
+
|
|
203
|
+
mock_history = MagicMock()
|
|
204
|
+
agent._history = mock_history
|
|
205
|
+
|
|
206
|
+
original = {
|
|
207
|
+
"message_id": "orig-001",
|
|
208
|
+
"sender": "lumina",
|
|
209
|
+
"recipient": "opus",
|
|
210
|
+
"content": "Deploy the fleet",
|
|
211
|
+
"timestamp": "2026-03-01T12:00:00+00:00",
|
|
212
|
+
"thread_id": None,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
result = agent.forward(original, "jarvis")
|
|
216
|
+
|
|
217
|
+
assert result["stored"] is True
|
|
218
|
+
assert result["forwarded_id"] is not None
|
|
219
|
+
|
|
220
|
+
stored_msg = mock_history.store_message.call_args[0][0]
|
|
221
|
+
payload = json.loads(stored_msg.content)
|
|
222
|
+
assert payload["skchat_forward"] is True
|
|
223
|
+
assert payload["forwarded_from"] == "lumina"
|
|
224
|
+
assert payload["forwarded_at"] == "2026-03-01T12:00:00+00:00"
|
|
225
|
+
assert payload["original_message_id"] == "orig-001"
|
|
226
|
+
assert payload["sender"] == "opus"
|
|
227
|
+
assert payload["recipient"] == "jarvis"
|
|
228
|
+
assert payload["content"] == "Deploy the fleet"
|
|
229
|
+
|
|
230
|
+
def test_forward_delivers_via_comm(self, tmp_home):
|
|
231
|
+
"""Forward delivers via SKComm when transports are available."""
|
|
232
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
233
|
+
|
|
234
|
+
mock_comm = MagicMock()
|
|
235
|
+
mock_report = MagicMock()
|
|
236
|
+
mock_report.delivered = True
|
|
237
|
+
mock_report.successful_transport = "syncthing"
|
|
238
|
+
mock_comm.send.return_value = mock_report
|
|
239
|
+
mock_comm.router.transports = [MagicMock()]
|
|
240
|
+
agent._comm = mock_comm
|
|
241
|
+
|
|
242
|
+
mock_history = MagicMock()
|
|
243
|
+
agent._history = mock_history
|
|
244
|
+
|
|
245
|
+
original = {
|
|
246
|
+
"message_id": "orig-002",
|
|
247
|
+
"sender": "jarvis",
|
|
248
|
+
"content": "Status update",
|
|
249
|
+
"timestamp": "2026-03-01T13:00:00+00:00",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
result = agent.forward(original, "lumina")
|
|
253
|
+
|
|
254
|
+
assert result["delivered"] is True
|
|
255
|
+
assert result["transport"] == "syncthing"
|
|
256
|
+
assert result["stored"] is True
|
|
257
|
+
assert result["forwarded_id"] is not None
|
|
258
|
+
|
|
259
|
+
call_kwargs = mock_comm.send.call_args[1]
|
|
260
|
+
assert call_kwargs["recipient"] == "lumina"
|
|
261
|
+
fwd_payload = json.loads(call_kwargs["message"])
|
|
262
|
+
assert fwd_payload["skchat_forward"] is True
|
|
263
|
+
assert fwd_payload["forwarded_from"] == "jarvis"
|
|
264
|
+
|
|
265
|
+
def test_forward_without_comm_stores_locally(self, tmp_home):
|
|
266
|
+
"""Forward stores locally and returns stored=True when no comm."""
|
|
267
|
+
agent = AgentChat(home=tmp_home, identity="lumina")
|
|
268
|
+
agent._ensure_comm = lambda: False
|
|
269
|
+
|
|
270
|
+
mock_history = MagicMock()
|
|
271
|
+
agent._history = mock_history
|
|
272
|
+
|
|
273
|
+
original = {
|
|
274
|
+
"message_id": "orig-003",
|
|
275
|
+
"sender": "ava",
|
|
276
|
+
"content": "Check in",
|
|
277
|
+
"timestamp": "2026-03-01T14:00:00+00:00",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
result = agent.forward(original, "opus")
|
|
281
|
+
|
|
282
|
+
assert result["stored"] is True
|
|
283
|
+
assert result["delivered"] is False
|
|
284
|
+
assert result["transport"] is None
|
|
285
|
+
assert result["forwarded_id"] is not None
|
|
286
|
+
|
|
287
|
+
def test_forward_unique_ids_per_call(self, tmp_home):
|
|
288
|
+
"""Each forward call generates a distinct forwarded_id."""
|
|
289
|
+
agent = AgentChat(home=tmp_home, identity="opus")
|
|
290
|
+
agent._ensure_comm = lambda: False
|
|
291
|
+
|
|
292
|
+
mock_history = MagicMock()
|
|
293
|
+
agent._history = mock_history
|
|
294
|
+
|
|
295
|
+
original = {"message_id": "m1", "sender": "x", "content": "hi", "timestamp": ""}
|
|
296
|
+
|
|
297
|
+
r1 = agent.forward(original, "peer-a")
|
|
298
|
+
r2 = agent.forward(original, "peer-b")
|
|
299
|
+
|
|
300
|
+
assert r1["forwarded_id"] != r2["forwarded_id"]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class TestCLIChatCommands:
|
|
304
|
+
"""Integration tests for the CLI chat commands."""
|
|
305
|
+
|
|
306
|
+
def test_chat_send_help(self):
|
|
307
|
+
"""chat send --help works."""
|
|
308
|
+
from skcapstone.cli import main
|
|
309
|
+
|
|
310
|
+
runner = CliRunner()
|
|
311
|
+
result = runner.invoke(main, ["chat", "send", "--help"])
|
|
312
|
+
assert result.exit_code == 0
|
|
313
|
+
assert "PEER" in result.output
|
|
314
|
+
assert "MESSAGE" in result.output
|
|
315
|
+
|
|
316
|
+
def test_chat_inbox_help(self):
|
|
317
|
+
"""chat inbox --help works."""
|
|
318
|
+
from skcapstone.cli import main
|
|
319
|
+
|
|
320
|
+
runner = CliRunner()
|
|
321
|
+
result = runner.invoke(main, ["chat", "inbox", "--help"])
|
|
322
|
+
assert result.exit_code == 0
|
|
323
|
+
assert "--poll" in result.output
|
|
324
|
+
assert "--limit" in result.output
|
|
325
|
+
|
|
326
|
+
def test_chat_live_help(self):
|
|
327
|
+
"""chat live --help works."""
|
|
328
|
+
from skcapstone.cli import main
|
|
329
|
+
|
|
330
|
+
runner = CliRunner()
|
|
331
|
+
result = runner.invoke(main, ["chat", "live", "--help"])
|
|
332
|
+
assert result.exit_code == 0
|
|
333
|
+
assert "PEER" in result.output
|
|
334
|
+
assert "--poll-interval" in result.output
|
|
335
|
+
|
|
336
|
+
def test_chat_forward_help(self):
|
|
337
|
+
"""chat forward --help works and shows PEER and MSG_ID."""
|
|
338
|
+
from skcapstone.cli import main
|
|
339
|
+
|
|
340
|
+
runner = CliRunner()
|
|
341
|
+
result = runner.invoke(main, ["chat", "forward", "--help"])
|
|
342
|
+
assert result.exit_code == 0
|
|
343
|
+
assert "PEER" in result.output
|
|
344
|
+
assert "MSG_ID" in result.output
|
|
345
|
+
|
|
346
|
+
def test_chat_forward_message_not_found(self, tmp_home):
|
|
347
|
+
"""chat forward exits non-zero when MSG_ID is not in inbox."""
|
|
348
|
+
from skcapstone.cli import main
|
|
349
|
+
from skcapstone.cli._common import AGENT_HOME
|
|
350
|
+
|
|
351
|
+
runner = CliRunner()
|
|
352
|
+
with patch("skcapstone.cli._common.get_runtime") as mock_rt, \
|
|
353
|
+
patch("skcapstone.chat.AgentChat.get_inbox", return_value=[]):
|
|
354
|
+
mock_rt.return_value.manifest.name = "opus"
|
|
355
|
+
result = runner.invoke(
|
|
356
|
+
main,
|
|
357
|
+
["chat", "forward", "lumina", "no-such-id", "--home", str(tmp_home)],
|
|
358
|
+
)
|
|
359
|
+
assert result.exit_code != 0
|
|
360
|
+
assert "not found" in result.output.lower() or result.exit_code == 1
|
|
361
|
+
|
|
362
|
+
def test_chat_forward_stored_locally(self, tmp_home):
|
|
363
|
+
"""chat forward stores message when SKComm unavailable."""
|
|
364
|
+
from skcapstone.cli import main
|
|
365
|
+
|
|
366
|
+
original = {
|
|
367
|
+
"message_id": "msg-fwd-001",
|
|
368
|
+
"sender": "jarvis",
|
|
369
|
+
"content": "Forward this",
|
|
370
|
+
"timestamp": "2026-03-01T10:00:00+00:00",
|
|
371
|
+
"thread_id": None,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
runner = CliRunner()
|
|
375
|
+
with patch("skcapstone.cli._common.get_runtime") as mock_rt, \
|
|
376
|
+
patch("skcapstone.chat.AgentChat.get_inbox", return_value=[original]), \
|
|
377
|
+
patch("skcapstone.chat.AgentChat._ensure_comm", return_value=False), \
|
|
378
|
+
patch("skcapstone.chat.AgentChat._ensure_history", return_value=None):
|
|
379
|
+
mock_rt.return_value.manifest.name = "opus"
|
|
380
|
+
result = runner.invoke(
|
|
381
|
+
main,
|
|
382
|
+
["chat", "forward", "lumina", "msg-fwd-001", "--home", str(tmp_home)],
|
|
383
|
+
)
|
|
384
|
+
# stored=False (no history) and delivered=False → "Failed" or graceful output
|
|
385
|
+
assert result.exit_code == 0 or "failed" in result.output.lower() or "stored" in result.output.lower()
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Tests for skcapstone.claude_md — CLAUDE.md auto-regeneration.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- generate_claude_md() produces correct markdown structure
|
|
5
|
+
- generate_claude_md() is graceful with an uninitialized home
|
|
6
|
+
- generate_claude_md() embeds recent memories when present
|
|
7
|
+
- write_claude_md() persists content to disk
|
|
8
|
+
- write_claude_md() --backup renames the existing file
|
|
9
|
+
- refresh-context CLI command writes CLAUDE.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from unittest.mock import patch
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
import yaml
|
|
20
|
+
from click.testing import CliRunner
|
|
21
|
+
|
|
22
|
+
from skcapstone.claude_md import generate_claude_md, write_claude_md
|
|
23
|
+
from skcapstone.cli import main
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Helpers
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
def _init_agent(home: Path, name: str = "md-test") -> None:
|
|
31
|
+
"""Minimal agent init needed for context gathering."""
|
|
32
|
+
from skcapstone.pillars.identity import generate_identity
|
|
33
|
+
from skcapstone.pillars.memory import initialize_memory
|
|
34
|
+
from skcapstone.pillars.security import initialize_security
|
|
35
|
+
from skcapstone.pillars.sync import initialize_sync
|
|
36
|
+
from skcapstone.pillars.trust import initialize_trust
|
|
37
|
+
|
|
38
|
+
generate_identity(home, name)
|
|
39
|
+
initialize_memory(home)
|
|
40
|
+
initialize_trust(home)
|
|
41
|
+
initialize_security(home)
|
|
42
|
+
initialize_sync(home)
|
|
43
|
+
|
|
44
|
+
manifest = {
|
|
45
|
+
"name": name,
|
|
46
|
+
"version": "0.1.0",
|
|
47
|
+
"created_at": "2026-01-01T00:00:00Z",
|
|
48
|
+
"connectors": [],
|
|
49
|
+
}
|
|
50
|
+
(home / "manifest.json").write_text(json.dumps(manifest, indent=2))
|
|
51
|
+
(home / "config").mkdir(exist_ok=True)
|
|
52
|
+
(home / "config" / "config.yaml").write_text(yaml.dump({"agent_name": name}))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# generate_claude_md()
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
class TestGenerateClaudeMd:
|
|
60
|
+
"""Tests for generate_claude_md()."""
|
|
61
|
+
|
|
62
|
+
def test_returns_string(self, tmp_agent_home: Path):
|
|
63
|
+
"""generate_claude_md() returns a non-empty string."""
|
|
64
|
+
result = generate_claude_md(tmp_agent_home)
|
|
65
|
+
assert isinstance(result, str)
|
|
66
|
+
assert len(result) > 0
|
|
67
|
+
|
|
68
|
+
def test_markdown_h1_header(self, tmp_agent_home: Path):
|
|
69
|
+
"""Output begins with the expected H1 heading."""
|
|
70
|
+
result = generate_claude_md(tmp_agent_home)
|
|
71
|
+
assert "# SKCapstone Agent Context" in result
|
|
72
|
+
|
|
73
|
+
def test_agent_identity_section(self, tmp_agent_home: Path):
|
|
74
|
+
"""Output contains the Agent Identity section."""
|
|
75
|
+
_init_agent(tmp_agent_home, "regen-test")
|
|
76
|
+
result = generate_claude_md(tmp_agent_home)
|
|
77
|
+
|
|
78
|
+
assert "## Agent Identity" in result
|
|
79
|
+
assert "regen-test" in result
|
|
80
|
+
|
|
81
|
+
def test_pillar_status_table(self, tmp_agent_home: Path):
|
|
82
|
+
"""Output contains a pillar status table."""
|
|
83
|
+
_init_agent(tmp_agent_home)
|
|
84
|
+
result = generate_claude_md(tmp_agent_home)
|
|
85
|
+
|
|
86
|
+
assert "## Pillar Status" in result
|
|
87
|
+
assert "| Pillar | Status |" in result
|
|
88
|
+
assert "identity" in result
|
|
89
|
+
|
|
90
|
+
def test_coordination_board_section(self, tmp_agent_home: Path):
|
|
91
|
+
"""Output contains the Coordination Board section."""
|
|
92
|
+
result = generate_claude_md(tmp_agent_home)
|
|
93
|
+
assert "## Coordination Board" in result
|
|
94
|
+
|
|
95
|
+
def test_cli_reference_section(self, tmp_agent_home: Path):
|
|
96
|
+
"""Output contains the CLI Reference section with key commands."""
|
|
97
|
+
result = generate_claude_md(tmp_agent_home)
|
|
98
|
+
|
|
99
|
+
assert "## CLI Reference" in result
|
|
100
|
+
assert "skcapstone status" in result
|
|
101
|
+
assert "skcapstone memory" in result
|
|
102
|
+
assert "skcapstone coord" in result
|
|
103
|
+
|
|
104
|
+
def test_graceful_with_uninitialized_home(self, tmp_agent_home: Path):
|
|
105
|
+
"""generate_claude_md() does not raise on an empty home directory."""
|
|
106
|
+
result = generate_claude_md(tmp_agent_home)
|
|
107
|
+
# Must still return valid markdown even without initialized pillars
|
|
108
|
+
assert "# SKCapstone Agent Context" in result
|
|
109
|
+
|
|
110
|
+
def test_embeds_recent_memories(self, tmp_agent_home: Path):
|
|
111
|
+
"""Memories stored before generation appear in the output."""
|
|
112
|
+
_init_agent(tmp_agent_home)
|
|
113
|
+
from skcapstone.memory_engine import store
|
|
114
|
+
store(tmp_agent_home, "Penguin test memory for claude-md", tags=["pengu"])
|
|
115
|
+
|
|
116
|
+
result = generate_claude_md(tmp_agent_home, memory_limit=5)
|
|
117
|
+
|
|
118
|
+
assert "Penguin test memory for claude-md" in result
|
|
119
|
+
|
|
120
|
+
def test_memory_limit_applied(self, tmp_agent_home: Path):
|
|
121
|
+
"""memory_limit=1 produces output shorter than memory_limit=10."""
|
|
122
|
+
_init_agent(tmp_agent_home)
|
|
123
|
+
from skcapstone.memory_engine import store
|
|
124
|
+
for i in range(8):
|
|
125
|
+
store(tmp_agent_home, f"Memory item {i}", tags=["bulk"])
|
|
126
|
+
|
|
127
|
+
short = generate_claude_md(tmp_agent_home, memory_limit=1)
|
|
128
|
+
long = generate_claude_md(tmp_agent_home, memory_limit=8)
|
|
129
|
+
|
|
130
|
+
assert len(short) <= len(long)
|
|
131
|
+
|
|
132
|
+
def test_consciousness_section_absent_when_disabled(self, tmp_agent_home: Path):
|
|
133
|
+
"""When consciousness is disabled, status shows INACTIVE."""
|
|
134
|
+
import urllib.error
|
|
135
|
+
with patch(
|
|
136
|
+
"urllib.request.urlopen",
|
|
137
|
+
side_effect=urllib.error.URLError("connection refused"),
|
|
138
|
+
):
|
|
139
|
+
result = generate_claude_md(tmp_agent_home)
|
|
140
|
+
# Section may be omitted or show INACTIVE — either is fine
|
|
141
|
+
if "## Consciousness" in result:
|
|
142
|
+
assert "INACTIVE" in result
|
|
143
|
+
|
|
144
|
+
def test_soul_overlay_included(self, tmp_agent_home: Path):
|
|
145
|
+
"""Active soul name appears in the output when an overlay is set."""
|
|
146
|
+
_init_agent(tmp_agent_home)
|
|
147
|
+
soul_dir = tmp_agent_home / "soul"
|
|
148
|
+
soul_dir.mkdir(exist_ok=True)
|
|
149
|
+
import json as _json
|
|
150
|
+
(soul_dir / "active.json").write_text(
|
|
151
|
+
_json.dumps({"active_soul": "lumina", "base_soul": "default"}),
|
|
152
|
+
encoding="utf-8",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
result = generate_claude_md(tmp_agent_home)
|
|
156
|
+
assert "lumina" in result
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# write_claude_md()
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
class TestWriteClaudeMd:
|
|
164
|
+
"""Tests for write_claude_md()."""
|
|
165
|
+
|
|
166
|
+
def test_creates_file(self, tmp_agent_home: Path, tmp_path: Path):
|
|
167
|
+
"""write_claude_md() creates the destination file."""
|
|
168
|
+
dest = tmp_path / "CLAUDE.md"
|
|
169
|
+
write_claude_md(tmp_agent_home, dest)
|
|
170
|
+
|
|
171
|
+
assert dest.exists()
|
|
172
|
+
|
|
173
|
+
def test_file_content_is_markdown(self, tmp_agent_home: Path, tmp_path: Path):
|
|
174
|
+
"""Written file has CLAUDE.md markdown structure."""
|
|
175
|
+
dest = tmp_path / "CLAUDE.md"
|
|
176
|
+
write_claude_md(tmp_agent_home, dest)
|
|
177
|
+
|
|
178
|
+
content = dest.read_text(encoding="utf-8")
|
|
179
|
+
assert "# SKCapstone Agent Context" in content
|
|
180
|
+
|
|
181
|
+
def test_backup_renames_existing(self, tmp_agent_home: Path, tmp_path: Path):
|
|
182
|
+
"""backup=True renames an existing CLAUDE.md to .bak before writing."""
|
|
183
|
+
dest = tmp_path / "CLAUDE.md"
|
|
184
|
+
dest.write_text("old content", encoding="utf-8")
|
|
185
|
+
|
|
186
|
+
write_claude_md(tmp_agent_home, dest, backup=True)
|
|
187
|
+
|
|
188
|
+
bak = tmp_path / "CLAUDE.md.bak"
|
|
189
|
+
assert bak.exists(), "Expected .bak file to be created"
|
|
190
|
+
assert bak.read_text(encoding="utf-8") == "old content"
|
|
191
|
+
assert dest.exists()
|
|
192
|
+
assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
|
|
193
|
+
|
|
194
|
+
def test_no_backup_overwrites_existing(self, tmp_agent_home: Path, tmp_path: Path):
|
|
195
|
+
"""Without backup=True the existing file is silently overwritten."""
|
|
196
|
+
dest = tmp_path / "CLAUDE.md"
|
|
197
|
+
dest.write_text("old content", encoding="utf-8")
|
|
198
|
+
|
|
199
|
+
write_claude_md(tmp_agent_home, dest, backup=False)
|
|
200
|
+
|
|
201
|
+
bak = tmp_path / "CLAUDE.md.bak"
|
|
202
|
+
assert not bak.exists(), "No .bak file expected when backup=False"
|
|
203
|
+
assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# refresh-context CLI
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
class TestRefreshContextCli:
|
|
211
|
+
"""Tests for `skcapstone refresh-context` CLI command."""
|
|
212
|
+
|
|
213
|
+
def _run(self, *args, home: str | None = None) -> "click.testing.Result":
|
|
214
|
+
runner = CliRunner(mix_stderr=False)
|
|
215
|
+
cmd: list[str] = ["refresh-context"]
|
|
216
|
+
if home:
|
|
217
|
+
cmd += ["--home", home]
|
|
218
|
+
cmd += list(args)
|
|
219
|
+
return runner.invoke(main, cmd, catch_exceptions=False)
|
|
220
|
+
|
|
221
|
+
def test_writes_claude_md_to_dest(self, tmp_agent_home: Path, tmp_path: Path):
|
|
222
|
+
"""--dest writes CLAUDE.md to the specified path."""
|
|
223
|
+
dest = tmp_path / "CLAUDE.md"
|
|
224
|
+
result = self._run("--dest", str(dest), home=str(tmp_agent_home))
|
|
225
|
+
|
|
226
|
+
assert result.exit_code == 0, result.output
|
|
227
|
+
assert dest.exists()
|
|
228
|
+
assert "# SKCapstone Agent Context" in dest.read_text(encoding="utf-8")
|
|
229
|
+
|
|
230
|
+
def test_output_confirms_written_path(self, tmp_agent_home: Path, tmp_path: Path):
|
|
231
|
+
"""CLI prints the path of the written file."""
|
|
232
|
+
dest = tmp_path / "CLAUDE.md"
|
|
233
|
+
result = self._run("--dest", str(dest), home=str(tmp_agent_home))
|
|
234
|
+
|
|
235
|
+
assert "Written" in result.output or str(dest) in result.output
|
|
236
|
+
|
|
237
|
+
def test_backup_flag_creates_bak(self, tmp_agent_home: Path, tmp_path: Path):
|
|
238
|
+
"""--backup renames the existing file before writing."""
|
|
239
|
+
dest = tmp_path / "CLAUDE.md"
|
|
240
|
+
dest.write_text("old content", encoding="utf-8")
|
|
241
|
+
|
|
242
|
+
result = self._run("--backup", "--dest", str(dest), home=str(tmp_agent_home))
|
|
243
|
+
|
|
244
|
+
assert result.exit_code == 0, result.output
|
|
245
|
+
bak = tmp_path / "CLAUDE.md.bak"
|
|
246
|
+
assert bak.exists()
|
|
247
|
+
assert bak.read_text(encoding="utf-8") == "old content"
|
|
248
|
+
|
|
249
|
+
def test_dest_dir_writes_claude_md_inside(self, tmp_agent_home: Path, tmp_path: Path):
|
|
250
|
+
"""Passing a directory as --dest writes CLAUDE.md inside it."""
|
|
251
|
+
result = self._run("--dest", str(tmp_path), home=str(tmp_agent_home))
|
|
252
|
+
|
|
253
|
+
assert result.exit_code == 0, result.output
|
|
254
|
+
assert (tmp_path / "CLAUDE.md").exists()
|
|
255
|
+
|
|
256
|
+
def test_falls_back_to_cwd_without_git(self, tmp_agent_home: Path, tmp_path: Path):
|
|
257
|
+
"""Without --dest and outside a git repo, writes to cwd/CLAUDE.md."""
|
|
258
|
+
runner = CliRunner(mix_stderr=False)
|
|
259
|
+
written: list[Path] = []
|
|
260
|
+
|
|
261
|
+
with runner.isolated_filesystem(temp_dir=tmp_path) as iso_dir:
|
|
262
|
+
with patch("subprocess.run", side_effect=FileNotFoundError("git not found")):
|
|
263
|
+
result = runner.invoke(
|
|
264
|
+
main,
|
|
265
|
+
["refresh-context", "--home", str(tmp_agent_home)],
|
|
266
|
+
catch_exceptions=False,
|
|
267
|
+
)
|
|
268
|
+
written.append(Path(iso_dir) / "CLAUDE.md")
|
|
269
|
+
|
|
270
|
+
assert result.exit_code == 0, result.output
|
|
271
|
+
assert written[0].exists(), f"Expected CLAUDE.md at {written[0]}"
|