@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,213 @@
|
|
|
1
|
+
"""Unit tests for the security pillar module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.models import PillarStatus
|
|
11
|
+
from skcapstone.pillars.security import (
|
|
12
|
+
AUDIT_LOG_NAME,
|
|
13
|
+
AuditEntry,
|
|
14
|
+
audit_event,
|
|
15
|
+
initialize_security,
|
|
16
|
+
read_audit_log,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestInitializeSecurity:
|
|
21
|
+
"""Tests for initialize_security()."""
|
|
22
|
+
|
|
23
|
+
def test_creates_security_directory(self, tmp_agent_home: Path):
|
|
24
|
+
initialize_security(tmp_agent_home)
|
|
25
|
+
assert (tmp_agent_home / "security").is_dir()
|
|
26
|
+
|
|
27
|
+
def test_creates_audit_log(self, tmp_agent_home: Path):
|
|
28
|
+
initialize_security(tmp_agent_home)
|
|
29
|
+
assert (tmp_agent_home / "security" / AUDIT_LOG_NAME).exists()
|
|
30
|
+
|
|
31
|
+
def test_audit_log_has_init_entry(self, tmp_agent_home: Path):
|
|
32
|
+
initialize_security(tmp_agent_home)
|
|
33
|
+
log_path = tmp_agent_home / "security" / AUDIT_LOG_NAME
|
|
34
|
+
first_line = log_path.read_text(encoding="utf-8").strip().splitlines()[0]
|
|
35
|
+
entry = json.loads(first_line)
|
|
36
|
+
assert entry["event_type"] == "INIT"
|
|
37
|
+
|
|
38
|
+
def test_audit_log_init_entry_has_timestamp(self, tmp_agent_home: Path):
|
|
39
|
+
initialize_security(tmp_agent_home)
|
|
40
|
+
log_path = tmp_agent_home / "security" / AUDIT_LOG_NAME
|
|
41
|
+
entry = json.loads(log_path.read_text().strip().splitlines()[0])
|
|
42
|
+
assert "timestamp" in entry and entry["timestamp"]
|
|
43
|
+
|
|
44
|
+
def test_audit_log_init_entry_has_host(self, tmp_agent_home: Path):
|
|
45
|
+
initialize_security(tmp_agent_home)
|
|
46
|
+
log_path = tmp_agent_home / "security" / AUDIT_LOG_NAME
|
|
47
|
+
entry = json.loads(log_path.read_text().strip().splitlines()[0])
|
|
48
|
+
assert "host" in entry and entry["host"]
|
|
49
|
+
|
|
50
|
+
def test_returns_security_state(self, tmp_agent_home: Path):
|
|
51
|
+
state = initialize_security(tmp_agent_home)
|
|
52
|
+
assert state is not None
|
|
53
|
+
|
|
54
|
+
def test_idempotent_does_not_duplicate_init_entry(self, tmp_agent_home: Path):
|
|
55
|
+
"""Calling initialize_security twice must not write a second INIT entry."""
|
|
56
|
+
initialize_security(tmp_agent_home)
|
|
57
|
+
initialize_security(tmp_agent_home)
|
|
58
|
+
log_path = tmp_agent_home / "security" / AUDIT_LOG_NAME
|
|
59
|
+
lines = [l for l in log_path.read_text().splitlines() if l.strip()]
|
|
60
|
+
assert len(lines) == 1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestAuditEvent:
|
|
64
|
+
"""Tests for audit_event()."""
|
|
65
|
+
|
|
66
|
+
def test_returns_audit_entry(self, tmp_agent_home: Path):
|
|
67
|
+
initialize_security(tmp_agent_home)
|
|
68
|
+
entry = audit_event(tmp_agent_home, "TEST", "a test event")
|
|
69
|
+
assert isinstance(entry, AuditEntry)
|
|
70
|
+
|
|
71
|
+
def test_event_type_stored(self, tmp_agent_home: Path):
|
|
72
|
+
initialize_security(tmp_agent_home)
|
|
73
|
+
entry = audit_event(tmp_agent_home, "AUTH", "key verified")
|
|
74
|
+
assert entry.event_type == "AUTH"
|
|
75
|
+
|
|
76
|
+
def test_detail_stored(self, tmp_agent_home: Path):
|
|
77
|
+
initialize_security(tmp_agent_home)
|
|
78
|
+
entry = audit_event(tmp_agent_home, "AUTH", "key verified")
|
|
79
|
+
assert entry.detail == "key verified"
|
|
80
|
+
|
|
81
|
+
def test_entry_appended_to_log(self, tmp_agent_home: Path):
|
|
82
|
+
initialize_security(tmp_agent_home)
|
|
83
|
+
audit_event(tmp_agent_home, "SYNC_PUSH", "seed pushed")
|
|
84
|
+
lines = (tmp_agent_home / "security" / AUDIT_LOG_NAME).read_text().splitlines()
|
|
85
|
+
assert len(lines) == 2
|
|
86
|
+
second = json.loads(lines[1])
|
|
87
|
+
assert second["event_type"] == "SYNC_PUSH"
|
|
88
|
+
|
|
89
|
+
def test_agent_field_stored(self, tmp_agent_home: Path):
|
|
90
|
+
initialize_security(tmp_agent_home)
|
|
91
|
+
entry = audit_event(tmp_agent_home, "BOOT", "agent started", agent="opus")
|
|
92
|
+
assert entry.agent == "opus"
|
|
93
|
+
lines = (tmp_agent_home / "security" / AUDIT_LOG_NAME).read_text().splitlines()
|
|
94
|
+
assert json.loads(lines[-1])["agent"] == "opus"
|
|
95
|
+
|
|
96
|
+
def test_metadata_field_stored(self, tmp_agent_home: Path):
|
|
97
|
+
initialize_security(tmp_agent_home)
|
|
98
|
+
meta = {"token_id": "tok123", "caps": ["read"]}
|
|
99
|
+
entry = audit_event(tmp_agent_home, "TOKEN_ISSUE", "token issued", metadata=meta)
|
|
100
|
+
assert entry.metadata["token_id"] == "tok123"
|
|
101
|
+
last = json.loads(
|
|
102
|
+
(tmp_agent_home / "security" / AUDIT_LOG_NAME).read_text().splitlines()[-1]
|
|
103
|
+
)
|
|
104
|
+
assert last["metadata"]["token_id"] == "tok123"
|
|
105
|
+
|
|
106
|
+
def test_creates_security_dir_if_missing(self, tmp_path: Path):
|
|
107
|
+
fresh_home = tmp_path / "fresh"
|
|
108
|
+
fresh_home.mkdir()
|
|
109
|
+
audit_event(fresh_home, "BOOT", "first event")
|
|
110
|
+
assert (fresh_home / "security" / AUDIT_LOG_NAME).exists()
|
|
111
|
+
|
|
112
|
+
def test_multiple_events_accumulate(self, tmp_agent_home: Path):
|
|
113
|
+
initialize_security(tmp_agent_home)
|
|
114
|
+
for i in range(3):
|
|
115
|
+
audit_event(tmp_agent_home, "EVENT", f"entry {i}")
|
|
116
|
+
lines = (tmp_agent_home / "security" / AUDIT_LOG_NAME).read_text().splitlines()
|
|
117
|
+
assert len(lines) == 4 # 1 INIT + 3 events
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestReadAuditLog:
|
|
121
|
+
"""Tests for read_audit_log()."""
|
|
122
|
+
|
|
123
|
+
def test_empty_when_log_missing(self, tmp_path: Path):
|
|
124
|
+
entries = read_audit_log(tmp_path / "no-home")
|
|
125
|
+
assert entries == []
|
|
126
|
+
|
|
127
|
+
def test_returns_list_of_audit_entries(self, tmp_agent_home: Path):
|
|
128
|
+
initialize_security(tmp_agent_home)
|
|
129
|
+
entries = read_audit_log(tmp_agent_home)
|
|
130
|
+
assert isinstance(entries, list)
|
|
131
|
+
assert all(isinstance(e, AuditEntry) for e in entries)
|
|
132
|
+
|
|
133
|
+
def test_parses_init_entry(self, tmp_agent_home: Path):
|
|
134
|
+
initialize_security(tmp_agent_home)
|
|
135
|
+
entries = read_audit_log(tmp_agent_home)
|
|
136
|
+
assert len(entries) >= 1
|
|
137
|
+
assert entries[0].event_type == "INIT"
|
|
138
|
+
|
|
139
|
+
def test_reads_all_events_in_order(self, tmp_agent_home: Path):
|
|
140
|
+
initialize_security(tmp_agent_home)
|
|
141
|
+
audit_event(tmp_agent_home, "AUTH", "key check")
|
|
142
|
+
audit_event(tmp_agent_home, "SYNC_PUSH", "seed sent")
|
|
143
|
+
entries = read_audit_log(tmp_agent_home)
|
|
144
|
+
assert len(entries) == 3
|
|
145
|
+
assert entries[0].event_type == "INIT"
|
|
146
|
+
assert entries[1].event_type == "AUTH"
|
|
147
|
+
assert entries[2].event_type == "SYNC_PUSH"
|
|
148
|
+
|
|
149
|
+
def test_limit_returns_newest_n(self, tmp_agent_home: Path):
|
|
150
|
+
initialize_security(tmp_agent_home)
|
|
151
|
+
for i in range(5):
|
|
152
|
+
audit_event(tmp_agent_home, "EVENT", f"entry {i}")
|
|
153
|
+
entries = read_audit_log(tmp_agent_home, limit=2)
|
|
154
|
+
assert len(entries) == 2
|
|
155
|
+
assert "entry 3" in entries[0].detail
|
|
156
|
+
assert "entry 4" in entries[1].detail
|
|
157
|
+
|
|
158
|
+
def test_handles_legacy_plaintext_lines(self, tmp_agent_home: Path):
|
|
159
|
+
security_dir = tmp_agent_home / "security"
|
|
160
|
+
security_dir.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
(security_dir / AUDIT_LOG_NAME).write_text(
|
|
162
|
+
"[2026-01-01T00:00:00] INIT — legacy format\n"
|
|
163
|
+
"[2026-01-01T00:01:00] AUTH — old auth event\n",
|
|
164
|
+
encoding="utf-8",
|
|
165
|
+
)
|
|
166
|
+
entries = read_audit_log(tmp_agent_home)
|
|
167
|
+
assert len(entries) == 2
|
|
168
|
+
assert all(e.event_type == "LEGACY" for e in entries)
|
|
169
|
+
|
|
170
|
+
def test_mixed_jsonl_and_legacy(self, tmp_agent_home: Path):
|
|
171
|
+
"""Log may contain a mix of old plain-text and new JSONL entries."""
|
|
172
|
+
initialize_security(tmp_agent_home)
|
|
173
|
+
log_path = tmp_agent_home / "security" / AUDIT_LOG_NAME
|
|
174
|
+
# Append a legacy line after the JSONL INIT entry
|
|
175
|
+
with log_path.open("a") as f:
|
|
176
|
+
f.write("[legacy] some old plain text event\n")
|
|
177
|
+
entries = read_audit_log(tmp_agent_home)
|
|
178
|
+
assert len(entries) == 2
|
|
179
|
+
assert entries[0].event_type == "INIT"
|
|
180
|
+
assert entries[1].event_type == "LEGACY"
|
|
181
|
+
|
|
182
|
+
def test_limit_zero_returns_all(self, tmp_agent_home: Path):
|
|
183
|
+
initialize_security(tmp_agent_home)
|
|
184
|
+
audit_event(tmp_agent_home, "A", "a")
|
|
185
|
+
audit_event(tmp_agent_home, "B", "b")
|
|
186
|
+
entries = read_audit_log(tmp_agent_home, limit=0)
|
|
187
|
+
assert len(entries) == 3
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class TestAuditEntryModel:
|
|
191
|
+
"""Tests for the AuditEntry model."""
|
|
192
|
+
|
|
193
|
+
def test_default_timestamp_is_set(self):
|
|
194
|
+
entry = AuditEntry(event_type="TEST", detail="x")
|
|
195
|
+
assert entry.timestamp
|
|
196
|
+
|
|
197
|
+
def test_default_host_is_set(self):
|
|
198
|
+
entry = AuditEntry(event_type="TEST", detail="x")
|
|
199
|
+
assert entry.host
|
|
200
|
+
|
|
201
|
+
def test_optional_agent_is_none_by_default(self):
|
|
202
|
+
entry = AuditEntry(event_type="TEST", detail="x")
|
|
203
|
+
assert entry.agent is None
|
|
204
|
+
|
|
205
|
+
def test_optional_metadata_is_none_by_default(self):
|
|
206
|
+
entry = AuditEntry(event_type="TEST", detail="x")
|
|
207
|
+
assert entry.metadata is None
|
|
208
|
+
|
|
209
|
+
def test_model_dump_json_round_trip(self):
|
|
210
|
+
entry = AuditEntry(event_type="SYNC", detail="pushed seed", agent="opus")
|
|
211
|
+
reloaded = AuditEntry.model_validate(json.loads(entry.model_dump_json()))
|
|
212
|
+
assert reloaded.event_type == "SYNC"
|
|
213
|
+
assert reloaded.agent == "opus"
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Tests for the self-healing doctor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.self_healing import SelfHealingDoctor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestHomeDirs:
|
|
14
|
+
"""Home directory auto-fix tests."""
|
|
15
|
+
|
|
16
|
+
def test_creates_missing_dirs(self, tmp_path):
|
|
17
|
+
"""Missing home subdirs are auto-created."""
|
|
18
|
+
home = tmp_path / ".skcapstone"
|
|
19
|
+
home.mkdir()
|
|
20
|
+
# Only create 'identity' — rest should be auto-fixed
|
|
21
|
+
(home / "identity").mkdir()
|
|
22
|
+
|
|
23
|
+
doctor = SelfHealingDoctor(home)
|
|
24
|
+
result = doctor._check_home_dirs()
|
|
25
|
+
|
|
26
|
+
assert result["status"] == "fixed"
|
|
27
|
+
assert (home / "memory").exists()
|
|
28
|
+
assert (home / "trust").exists()
|
|
29
|
+
assert (home / "config").exists()
|
|
30
|
+
|
|
31
|
+
def test_ok_when_all_present(self, tmp_path):
|
|
32
|
+
"""Returns ok when all dirs exist."""
|
|
33
|
+
home = tmp_path / ".skcapstone"
|
|
34
|
+
for subdir in ("identity", "memory", "trust", "security", "sync", "config", "soul", "logs"):
|
|
35
|
+
(home / subdir).mkdir(parents=True)
|
|
36
|
+
|
|
37
|
+
doctor = SelfHealingDoctor(home)
|
|
38
|
+
result = doctor._check_home_dirs()
|
|
39
|
+
assert result["status"] == "ok"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestMemoryIndex:
|
|
43
|
+
"""Memory index rebuild tests."""
|
|
44
|
+
|
|
45
|
+
def test_rebuilds_missing_index(self, tmp_path):
|
|
46
|
+
"""Missing index.json is rebuilt from memory files."""
|
|
47
|
+
home = tmp_path / ".skcapstone"
|
|
48
|
+
memory_dir = home / "memory" / "short-term"
|
|
49
|
+
memory_dir.mkdir(parents=True)
|
|
50
|
+
|
|
51
|
+
# Write a test memory file
|
|
52
|
+
memory = {
|
|
53
|
+
"memory_id": "test-123",
|
|
54
|
+
"content": "Test memory",
|
|
55
|
+
"tags": ["test"],
|
|
56
|
+
}
|
|
57
|
+
(memory_dir / "test-123.json").write_text(json.dumps(memory))
|
|
58
|
+
|
|
59
|
+
doctor = SelfHealingDoctor(home)
|
|
60
|
+
result = doctor._check_memory_index()
|
|
61
|
+
|
|
62
|
+
assert result["status"] == "fixed"
|
|
63
|
+
index = json.loads((home / "memory" / "index.json").read_text())
|
|
64
|
+
assert len(index) == 1
|
|
65
|
+
assert index[0]["memory_id"] == "test-123"
|
|
66
|
+
|
|
67
|
+
def test_ok_when_valid(self, tmp_path):
|
|
68
|
+
"""Returns ok when index exists and is valid."""
|
|
69
|
+
home = tmp_path / ".skcapstone"
|
|
70
|
+
memory_dir = home / "memory"
|
|
71
|
+
memory_dir.mkdir(parents=True)
|
|
72
|
+
(memory_dir / "index.json").write_text("[]")
|
|
73
|
+
|
|
74
|
+
doctor = SelfHealingDoctor(home)
|
|
75
|
+
result = doctor._check_memory_index()
|
|
76
|
+
assert result["status"] == "ok"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestSyncManifest:
|
|
80
|
+
"""Sync manifest auto-fix tests."""
|
|
81
|
+
|
|
82
|
+
def test_creates_default_manifest(self, tmp_path):
|
|
83
|
+
"""Missing sync-manifest.json is auto-created."""
|
|
84
|
+
home = tmp_path / ".skcapstone"
|
|
85
|
+
sync_dir = home / "sync"
|
|
86
|
+
sync_dir.mkdir(parents=True)
|
|
87
|
+
|
|
88
|
+
doctor = SelfHealingDoctor(home)
|
|
89
|
+
result = doctor._check_sync_manifest()
|
|
90
|
+
|
|
91
|
+
assert result["status"] == "fixed"
|
|
92
|
+
manifest = json.loads((sync_dir / "sync-manifest.json").read_text())
|
|
93
|
+
assert manifest["version"] == 1
|
|
94
|
+
assert "syncthing" in manifest["backends"]
|
|
95
|
+
|
|
96
|
+
def test_ok_when_present(self, tmp_path):
|
|
97
|
+
"""Returns ok when manifest exists."""
|
|
98
|
+
home = tmp_path / ".skcapstone"
|
|
99
|
+
sync_dir = home / "sync"
|
|
100
|
+
sync_dir.mkdir(parents=True)
|
|
101
|
+
(sync_dir / "sync-manifest.json").write_text("{}")
|
|
102
|
+
|
|
103
|
+
doctor = SelfHealingDoctor(home)
|
|
104
|
+
result = doctor._check_sync_manifest()
|
|
105
|
+
assert result["status"] == "ok"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestConsciousnessHealth:
|
|
109
|
+
"""Consciousness health check tests."""
|
|
110
|
+
|
|
111
|
+
def test_ok_when_not_loaded(self, tmp_path):
|
|
112
|
+
"""Returns ok when consciousness is not loaded (disabled)."""
|
|
113
|
+
home = tmp_path / ".skcapstone"
|
|
114
|
+
home.mkdir()
|
|
115
|
+
doctor = SelfHealingDoctor(home, consciousness_loop=None)
|
|
116
|
+
result = doctor._check_consciousness_health()
|
|
117
|
+
assert result["status"] == "ok"
|
|
118
|
+
assert "not loaded" in result["message"]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestProfileFreshness:
|
|
122
|
+
"""Profile freshness check tests."""
|
|
123
|
+
|
|
124
|
+
def test_checks_profile_dates(self, tmp_path):
|
|
125
|
+
"""Profile freshness check doesn't crash."""
|
|
126
|
+
home = tmp_path / ".skcapstone"
|
|
127
|
+
home.mkdir()
|
|
128
|
+
doctor = SelfHealingDoctor(home)
|
|
129
|
+
result = doctor._check_profile_freshness()
|
|
130
|
+
assert result["status"] == "ok"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TestDiagnoseAndHeal:
|
|
134
|
+
"""Full diagnose-and-heal pipeline tests."""
|
|
135
|
+
|
|
136
|
+
def test_full_pipeline(self, tmp_path):
|
|
137
|
+
"""Full pipeline runs all checks and returns report."""
|
|
138
|
+
home = tmp_path / ".skcapstone"
|
|
139
|
+
home.mkdir()
|
|
140
|
+
|
|
141
|
+
doctor = SelfHealingDoctor(home)
|
|
142
|
+
report = doctor.diagnose_and_heal()
|
|
143
|
+
|
|
144
|
+
assert "checks_run" in report
|
|
145
|
+
assert "checks_passed" in report
|
|
146
|
+
assert "auto_fixed" in report
|
|
147
|
+
assert "still_broken" in report
|
|
148
|
+
assert report["checks_run"] > 0
|
|
149
|
+
assert isinstance(report["details"], list)
|
|
150
|
+
|
|
151
|
+
def test_all_ok_when_healthy(self, tmp_path):
|
|
152
|
+
"""Healthy home returns all checks passed."""
|
|
153
|
+
home = tmp_path / ".skcapstone"
|
|
154
|
+
for subdir in ("identity", "memory", "trust", "security", "sync", "config", "soul", "logs"):
|
|
155
|
+
(home / subdir).mkdir(parents=True)
|
|
156
|
+
(home / "memory" / "index.json").write_text("[]")
|
|
157
|
+
(home / "sync" / "sync-manifest.json").write_text("{}")
|
|
158
|
+
|
|
159
|
+
doctor = SelfHealingDoctor(home)
|
|
160
|
+
report = doctor.diagnose_and_heal()
|
|
161
|
+
|
|
162
|
+
assert report["still_broken"] == 0
|
|
163
|
+
assert report["checks_passed"] == report["checks_run"]
|
|
164
|
+
|
|
165
|
+
def test_last_report_saved(self, tmp_path):
|
|
166
|
+
"""Last report is accessible after running."""
|
|
167
|
+
home = tmp_path / ".skcapstone"
|
|
168
|
+
home.mkdir()
|
|
169
|
+
doctor = SelfHealingDoctor(home)
|
|
170
|
+
doctor.diagnose_and_heal()
|
|
171
|
+
assert doctor.last_report["checks_run"] > 0
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Tests for the session auto-capture module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.memory_engine import search
|
|
11
|
+
from skcapstone.pillars.memory import initialize_memory
|
|
12
|
+
from skcapstone.session_capture import (
|
|
13
|
+
CapturedMoment,
|
|
14
|
+
SessionCapture,
|
|
15
|
+
_text_overlap,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def capture_home(tmp_agent_home: Path) -> Path:
|
|
21
|
+
"""Provide an agent home with memory initialized."""
|
|
22
|
+
initialize_memory(tmp_agent_home)
|
|
23
|
+
return tmp_agent_home
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestMomentExtraction:
|
|
27
|
+
"""Tests for extract_moments()."""
|
|
28
|
+
|
|
29
|
+
def test_splits_paragraphs(self, capture_home: Path):
|
|
30
|
+
"""Multiple paragraphs become separate moments."""
|
|
31
|
+
cap = SessionCapture(capture_home)
|
|
32
|
+
text = (
|
|
33
|
+
"We decided to use Ed25519 for all agent keys. "
|
|
34
|
+
"This gives us small keys and fast verification.\n\n"
|
|
35
|
+
"The deployment pipeline will use GitHub Actions. "
|
|
36
|
+
"Each package gets its own workflow file."
|
|
37
|
+
)
|
|
38
|
+
moments = cap.extract_moments(text)
|
|
39
|
+
assert len(moments) >= 2
|
|
40
|
+
|
|
41
|
+
def test_short_fragments_merged(self, capture_home: Path):
|
|
42
|
+
"""Very short segments get merged with neighbors."""
|
|
43
|
+
cap = SessionCapture(capture_home)
|
|
44
|
+
moments = cap.extract_moments("Yes. No. OK. Sure thing. Got it.")
|
|
45
|
+
# Reason: these are too short individually, should merge
|
|
46
|
+
assert len(moments) <= 2
|
|
47
|
+
|
|
48
|
+
def test_empty_input(self, capture_home: Path):
|
|
49
|
+
"""Empty input produces no moments."""
|
|
50
|
+
cap = SessionCapture(capture_home)
|
|
51
|
+
assert cap.extract_moments("") == []
|
|
52
|
+
assert cap.extract_moments(" ") == []
|
|
53
|
+
|
|
54
|
+
def test_single_long_sentence(self, capture_home: Path):
|
|
55
|
+
"""A single long sentence becomes one moment."""
|
|
56
|
+
cap = SessionCapture(capture_home)
|
|
57
|
+
text = (
|
|
58
|
+
"The sovereign agent framework uses CapAuth PGP keys for identity, "
|
|
59
|
+
"SKMemory for persistent memory across sessions, and Cloud 9 FEB files "
|
|
60
|
+
"for trust state rehydration after session resets."
|
|
61
|
+
)
|
|
62
|
+
moments = cap.extract_moments(text)
|
|
63
|
+
assert len(moments) >= 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestMomentScoring:
|
|
67
|
+
"""Tests for score_moment()."""
|
|
68
|
+
|
|
69
|
+
def test_decision_scores_higher(self, capture_home: Path):
|
|
70
|
+
"""Moments with decision keywords score above baseline."""
|
|
71
|
+
cap = SessionCapture(capture_home)
|
|
72
|
+
decision = cap.score_moment("We decided to use PostgreSQL instead of MySQL for the database.")
|
|
73
|
+
generic = cap.score_moment("The sky is blue and the weather is nice today in the park.")
|
|
74
|
+
|
|
75
|
+
assert decision.importance > generic.importance
|
|
76
|
+
|
|
77
|
+
def test_architecture_tagged(self, capture_home: Path):
|
|
78
|
+
"""Architecture mentions get tagged and boosted."""
|
|
79
|
+
cap = SessionCapture(capture_home)
|
|
80
|
+
moment = cap.score_moment("The architecture uses a plugin-based design pattern for transports.")
|
|
81
|
+
assert moment.importance > 0.3
|
|
82
|
+
assert "architecture" in moment.reason
|
|
83
|
+
|
|
84
|
+
def test_security_boosted(self, capture_home: Path):
|
|
85
|
+
"""Security-related content gets a boost."""
|
|
86
|
+
cap = SessionCapture(capture_home)
|
|
87
|
+
moment = cap.score_moment("PGP encryption protects all messages with GPG keys.")
|
|
88
|
+
assert moment.importance > 0.4
|
|
89
|
+
assert "pgp" in moment.tags
|
|
90
|
+
|
|
91
|
+
def test_package_auto_tagged(self, capture_home: Path):
|
|
92
|
+
"""Package names are auto-detected as tags."""
|
|
93
|
+
cap = SessionCapture(capture_home)
|
|
94
|
+
moment = cap.score_moment("The skcapstone MCP server exposes memory tools.")
|
|
95
|
+
assert "skcapstone" in moment.tags
|
|
96
|
+
assert "mcp" in moment.tags
|
|
97
|
+
|
|
98
|
+
def test_importance_capped_at_one(self, capture_home: Path):
|
|
99
|
+
"""Importance never exceeds 1.0 even with many signals."""
|
|
100
|
+
cap = SessionCapture(capture_home)
|
|
101
|
+
text = (
|
|
102
|
+
"We decided on a critical security architecture pattern for the "
|
|
103
|
+
"encrypted PGP GPG API endpoint deployment requiring always-on "
|
|
104
|
+
"convention-based important rules."
|
|
105
|
+
)
|
|
106
|
+
moment = cap.score_moment(text)
|
|
107
|
+
assert moment.importance <= 1.0
|
|
108
|
+
|
|
109
|
+
def test_baseline_score(self, capture_home: Path):
|
|
110
|
+
"""Generic text gets the baseline score."""
|
|
111
|
+
cap = SessionCapture(capture_home)
|
|
112
|
+
moment = cap.score_moment("Nothing special about this particular sentence at all here.")
|
|
113
|
+
assert 0.25 <= moment.importance <= 0.5
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TestCapture:
|
|
117
|
+
"""Tests for the full capture() pipeline."""
|
|
118
|
+
|
|
119
|
+
def test_stores_memories(self, capture_home: Path):
|
|
120
|
+
"""Captured moments are stored as retrievable memories."""
|
|
121
|
+
cap = SessionCapture(capture_home)
|
|
122
|
+
entries = cap.capture(
|
|
123
|
+
"We decided to use Ed25519 for all sovereign agent identity keys. "
|
|
124
|
+
"This is a critical architectural decision for the CapAuth system."
|
|
125
|
+
)
|
|
126
|
+
assert len(entries) >= 1
|
|
127
|
+
assert all(e.memory_id for e in entries)
|
|
128
|
+
|
|
129
|
+
results = search(capture_home, "Ed25519")
|
|
130
|
+
assert len(results) >= 1
|
|
131
|
+
|
|
132
|
+
def test_tags_applied(self, capture_home: Path):
|
|
133
|
+
"""Extra tags are applied to all captured memories."""
|
|
134
|
+
cap = SessionCapture(capture_home)
|
|
135
|
+
entries = cap.capture(
|
|
136
|
+
"The skcapstone architecture uses five sovereign pillars for agent consciousness.",
|
|
137
|
+
tags=["meeting", "2026-02-24"],
|
|
138
|
+
)
|
|
139
|
+
assert len(entries) >= 1
|
|
140
|
+
for e in entries:
|
|
141
|
+
assert "session-capture" in e.tags
|
|
142
|
+
assert "meeting" in e.tags
|
|
143
|
+
|
|
144
|
+
def test_min_importance_filters(self, capture_home: Path):
|
|
145
|
+
"""Moments below min_importance are not stored."""
|
|
146
|
+
cap = SessionCapture(capture_home)
|
|
147
|
+
entries = cap.capture(
|
|
148
|
+
"Nothing important here. Just chatting. The weather is nice.",
|
|
149
|
+
min_importance=0.9,
|
|
150
|
+
)
|
|
151
|
+
assert len(entries) == 0
|
|
152
|
+
|
|
153
|
+
def test_source_recorded(self, capture_home: Path):
|
|
154
|
+
"""The source field is set correctly."""
|
|
155
|
+
cap = SessionCapture(capture_home)
|
|
156
|
+
entries = cap.capture(
|
|
157
|
+
"We decided to switch from REST to GraphQL for the agent API.",
|
|
158
|
+
source="claude-code",
|
|
159
|
+
)
|
|
160
|
+
assert len(entries) >= 1
|
|
161
|
+
assert entries[0].source == "claude-code"
|
|
162
|
+
|
|
163
|
+
def test_deduplication(self, capture_home: Path):
|
|
164
|
+
"""Identical content is not captured twice."""
|
|
165
|
+
cap = SessionCapture(capture_home)
|
|
166
|
+
text = "The sovereign agent framework requires PGP identity for all operations."
|
|
167
|
+
|
|
168
|
+
first = cap.capture(text)
|
|
169
|
+
second = cap.capture(text)
|
|
170
|
+
|
|
171
|
+
assert len(first) >= 1
|
|
172
|
+
assert len(second) == 0
|
|
173
|
+
|
|
174
|
+
def test_empty_content_no_crash(self, capture_home: Path):
|
|
175
|
+
"""Empty content produces no memories and doesn't crash."""
|
|
176
|
+
cap = SessionCapture(capture_home)
|
|
177
|
+
assert cap.capture("") == []
|
|
178
|
+
assert cap.capture(" \n\n ") == []
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestTextOverlap:
|
|
182
|
+
"""Tests for the Jaccard overlap helper."""
|
|
183
|
+
|
|
184
|
+
def test_identical_strings(self):
|
|
185
|
+
"""Identical strings have overlap of 1.0."""
|
|
186
|
+
assert _text_overlap("hello world", "hello world") == 1.0
|
|
187
|
+
|
|
188
|
+
def test_no_overlap(self):
|
|
189
|
+
"""Completely different strings have overlap of 0.0."""
|
|
190
|
+
assert _text_overlap("alpha beta", "gamma delta") == 0.0
|
|
191
|
+
|
|
192
|
+
def test_partial_overlap(self):
|
|
193
|
+
"""Partial overlap returns a value between 0 and 1."""
|
|
194
|
+
overlap = _text_overlap("the quick brown fox", "the slow brown cat")
|
|
195
|
+
assert 0.0 < overlap < 1.0
|
|
196
|
+
|
|
197
|
+
def test_empty_strings(self):
|
|
198
|
+
"""Empty strings return 0.0."""
|
|
199
|
+
assert _text_overlap("", "") == 0.0
|
|
200
|
+
assert _text_overlap("hello", "") == 0.0
|