@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,401 @@
|
|
|
1
|
+
"""
|
|
2
|
+
State Diff — show what changed since the last sync/snapshot.
|
|
3
|
+
|
|
4
|
+
Compares the current agent state to the most recent sync seed
|
|
5
|
+
or saved snapshot, producing a clear diff of what's new, changed,
|
|
6
|
+
or removed across memories, trust, coordination, and pillars.
|
|
7
|
+
|
|
8
|
+
Tool-agnostic: works from any terminal, MCP, or the REPL shell.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
skcapstone diff # text diff to terminal
|
|
12
|
+
skcapstone diff --format json # machine-readable
|
|
13
|
+
skcapstone diff --save # save current state as baseline
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("skcapstone.state_diff")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class StateDiff:
|
|
30
|
+
"""Diff between two agent state snapshots.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
new_memories: Memory IDs added since last snapshot.
|
|
34
|
+
removed_memories: Memory IDs no longer present.
|
|
35
|
+
trust_changes: Dict of changed trust fields (old -> new).
|
|
36
|
+
new_tasks: Task IDs created since last snapshot.
|
|
37
|
+
completed_tasks: Task IDs completed since last snapshot.
|
|
38
|
+
pillar_changes: Dict of pillar status changes.
|
|
39
|
+
memory_count_before: Total memories at last snapshot.
|
|
40
|
+
memory_count_now: Total memories now.
|
|
41
|
+
snapshot_time: When the baseline snapshot was taken.
|
|
42
|
+
has_changes: Whether any changes were detected.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
new_memories: list[dict[str, Any]] = field(default_factory=list)
|
|
46
|
+
removed_memories: list[str] = field(default_factory=list)
|
|
47
|
+
trust_changes: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
48
|
+
new_tasks: list[dict[str, Any]] = field(default_factory=list)
|
|
49
|
+
completed_tasks: list[dict[str, Any]] = field(default_factory=list)
|
|
50
|
+
pillar_changes: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
51
|
+
memory_count_before: int = 0
|
|
52
|
+
memory_count_now: int = 0
|
|
53
|
+
snapshot_time: str = ""
|
|
54
|
+
has_changes: bool = False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
SNAPSHOT_FILENAME = "state_snapshot.json"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def take_snapshot(home: Path) -> dict[str, Any]:
|
|
61
|
+
"""Capture the current agent state as a snapshot.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
home: Agent home directory (~/.skcapstone).
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dict representing the full state at this moment.
|
|
68
|
+
"""
|
|
69
|
+
snapshot: dict[str, Any] = {
|
|
70
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
71
|
+
"version": "1.0",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
snapshot["memories"] = _snapshot_memories(home)
|
|
75
|
+
snapshot["trust"] = _snapshot_trust(home)
|
|
76
|
+
snapshot["tasks"] = _snapshot_tasks(home)
|
|
77
|
+
snapshot["pillars"] = _snapshot_pillars(home)
|
|
78
|
+
|
|
79
|
+
return snapshot
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def save_snapshot(home: Path) -> Path:
|
|
83
|
+
"""Save the current state as the diff baseline.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
home: Agent home directory.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Path to the saved snapshot file.
|
|
90
|
+
"""
|
|
91
|
+
snapshot = take_snapshot(home)
|
|
92
|
+
snap_path = home / SNAPSHOT_FILENAME
|
|
93
|
+
snap_path.write_text(json.dumps(snapshot, indent=2, default=str), encoding="utf-8")
|
|
94
|
+
return snap_path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def load_snapshot(home: Path) -> dict[str, Any] | None:
|
|
98
|
+
"""Load the most recent saved snapshot.
|
|
99
|
+
|
|
100
|
+
Falls back to the most recent sync seed if no snapshot exists.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
home: Agent home directory.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Snapshot dict, or None if no baseline exists.
|
|
107
|
+
"""
|
|
108
|
+
snap_path = home / SNAPSHOT_FILENAME
|
|
109
|
+
if snap_path.exists():
|
|
110
|
+
try:
|
|
111
|
+
return json.loads(snap_path.read_text(encoding="utf-8"))
|
|
112
|
+
except (json.JSONDecodeError, OSError):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
return _find_latest_seed(home)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def compute_diff(home: Path) -> StateDiff:
|
|
119
|
+
"""Compute the diff between current state and last snapshot.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
home: Agent home directory.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
StateDiff with all detected changes.
|
|
126
|
+
"""
|
|
127
|
+
baseline = load_snapshot(home)
|
|
128
|
+
current = take_snapshot(home)
|
|
129
|
+
diff = StateDiff()
|
|
130
|
+
|
|
131
|
+
if baseline is None:
|
|
132
|
+
diff.memory_count_now = len(current.get("memories", {}).get("ids", []))
|
|
133
|
+
diff.has_changes = True
|
|
134
|
+
diff.new_memories = [
|
|
135
|
+
{"id": m["id"], "content": m["content"][:80]}
|
|
136
|
+
for m in current.get("memories", {}).get("entries", [])
|
|
137
|
+
]
|
|
138
|
+
return diff
|
|
139
|
+
|
|
140
|
+
diff.snapshot_time = baseline.get("timestamp", "unknown")
|
|
141
|
+
|
|
142
|
+
_diff_memories(baseline, current, diff)
|
|
143
|
+
_diff_trust(baseline, current, diff)
|
|
144
|
+
_diff_tasks(baseline, current, diff)
|
|
145
|
+
_diff_pillars(baseline, current, diff)
|
|
146
|
+
|
|
147
|
+
diff.has_changes = bool(
|
|
148
|
+
diff.new_memories or diff.removed_memories or diff.trust_changes
|
|
149
|
+
or diff.new_tasks or diff.completed_tasks or diff.pillar_changes
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return diff
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def format_text(diff: StateDiff) -> str:
|
|
156
|
+
"""Format the diff as plain text.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
diff: The computed state diff.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Human-readable diff text.
|
|
163
|
+
"""
|
|
164
|
+
lines = ["# Agent State Diff", ""]
|
|
165
|
+
|
|
166
|
+
if diff.snapshot_time:
|
|
167
|
+
lines.append(f"Since: {diff.snapshot_time}")
|
|
168
|
+
lines.append(
|
|
169
|
+
f"Memories: {diff.memory_count_before} -> {diff.memory_count_now}"
|
|
170
|
+
)
|
|
171
|
+
lines.append("")
|
|
172
|
+
|
|
173
|
+
if not diff.has_changes:
|
|
174
|
+
lines.append("No changes detected.")
|
|
175
|
+
return "\n".join(lines)
|
|
176
|
+
|
|
177
|
+
if diff.new_memories:
|
|
178
|
+
lines.append(f"+ {len(diff.new_memories)} new memor{'y' if len(diff.new_memories) == 1 else 'ies'}:")
|
|
179
|
+
for m in diff.new_memories[:10]:
|
|
180
|
+
lines.append(f" + {m['content'][:70]}")
|
|
181
|
+
|
|
182
|
+
if diff.removed_memories:
|
|
183
|
+
lines.append(f"- {len(diff.removed_memories)} removed memor{'y' if len(diff.removed_memories) == 1 else 'ies'}")
|
|
184
|
+
|
|
185
|
+
if diff.trust_changes:
|
|
186
|
+
lines.append("")
|
|
187
|
+
lines.append("Trust changes:")
|
|
188
|
+
for key, change in diff.trust_changes.items():
|
|
189
|
+
lines.append(f" {key}: {change.get('old')} -> {change.get('new')}")
|
|
190
|
+
|
|
191
|
+
if diff.completed_tasks:
|
|
192
|
+
lines.append("")
|
|
193
|
+
lines.append(f"Completed {len(diff.completed_tasks)} task(s):")
|
|
194
|
+
for t in diff.completed_tasks:
|
|
195
|
+
lines.append(f" [done] {t.get('title', t.get('id', '?'))}")
|
|
196
|
+
|
|
197
|
+
if diff.new_tasks:
|
|
198
|
+
lines.append("")
|
|
199
|
+
lines.append(f"Created {len(diff.new_tasks)} task(s):")
|
|
200
|
+
for t in diff.new_tasks:
|
|
201
|
+
lines.append(f" [new] {t.get('title', t.get('id', '?'))}")
|
|
202
|
+
|
|
203
|
+
if diff.pillar_changes:
|
|
204
|
+
lines.append("")
|
|
205
|
+
lines.append("Pillar changes:")
|
|
206
|
+
for name, change in diff.pillar_changes.items():
|
|
207
|
+
lines.append(f" {name}: {change.get('old')} -> {change.get('new')}")
|
|
208
|
+
|
|
209
|
+
lines.append("")
|
|
210
|
+
return "\n".join(lines)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def format_json(diff: StateDiff) -> str:
|
|
214
|
+
"""Format the diff as JSON.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
diff: The computed state diff.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
JSON string.
|
|
221
|
+
"""
|
|
222
|
+
return json.dumps({
|
|
223
|
+
"has_changes": diff.has_changes,
|
|
224
|
+
"snapshot_time": diff.snapshot_time,
|
|
225
|
+
"memories": {
|
|
226
|
+
"before": diff.memory_count_before,
|
|
227
|
+
"now": diff.memory_count_now,
|
|
228
|
+
"new": len(diff.new_memories),
|
|
229
|
+
"removed": len(diff.removed_memories),
|
|
230
|
+
"new_entries": diff.new_memories[:20],
|
|
231
|
+
},
|
|
232
|
+
"trust_changes": diff.trust_changes,
|
|
233
|
+
"tasks": {
|
|
234
|
+
"new": diff.new_tasks,
|
|
235
|
+
"completed": diff.completed_tasks,
|
|
236
|
+
},
|
|
237
|
+
"pillar_changes": diff.pillar_changes,
|
|
238
|
+
}, indent=2, default=str)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
FORMATTERS = {"text": format_text, "json": format_json}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
245
|
+
# Snapshot helpers
|
|
246
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _snapshot_memories(home: Path) -> dict[str, Any]:
|
|
250
|
+
"""Capture memory state."""
|
|
251
|
+
from .memory_engine import list_memories
|
|
252
|
+
try:
|
|
253
|
+
entries = list_memories(home, limit=10000)
|
|
254
|
+
return {
|
|
255
|
+
"count": len(entries),
|
|
256
|
+
"ids": [e.memory_id for e in entries],
|
|
257
|
+
"entries": [
|
|
258
|
+
{"id": e.memory_id, "content": e.content[:100], "layer": e.layer.value}
|
|
259
|
+
for e in entries
|
|
260
|
+
],
|
|
261
|
+
}
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
logger.warning("Failed to snapshot memories: %s", exc)
|
|
264
|
+
return {"count": 0, "ids": [], "entries": []}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _snapshot_trust(home: Path) -> dict[str, Any]:
|
|
268
|
+
"""Capture trust state."""
|
|
269
|
+
trust_file = home / "trust" / "trust.json"
|
|
270
|
+
if not trust_file.exists():
|
|
271
|
+
return {}
|
|
272
|
+
try:
|
|
273
|
+
return json.loads(trust_file.read_text(encoding="utf-8"))
|
|
274
|
+
except (json.JSONDecodeError, OSError):
|
|
275
|
+
return {}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _snapshot_tasks(home: Path) -> dict[str, Any]:
|
|
279
|
+
"""Capture coordination board state."""
|
|
280
|
+
from .coordination import Board
|
|
281
|
+
try:
|
|
282
|
+
board = Board(home)
|
|
283
|
+
views = board.get_task_views()
|
|
284
|
+
return {
|
|
285
|
+
"total": len(views),
|
|
286
|
+
"done_ids": [v.task.id for v in views if v.status.value == "done"],
|
|
287
|
+
"all_ids": [v.task.id for v in views],
|
|
288
|
+
"tasks": [
|
|
289
|
+
{"id": v.task.id, "title": v.task.title, "status": v.status.value}
|
|
290
|
+
for v in views
|
|
291
|
+
],
|
|
292
|
+
}
|
|
293
|
+
except Exception as exc:
|
|
294
|
+
logger.warning("Failed to snapshot tasks: %s", exc)
|
|
295
|
+
return {"total": 0, "done_ids": [], "all_ids": [], "tasks": []}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _snapshot_pillars(home: Path) -> dict[str, str]:
|
|
299
|
+
"""Capture pillar statuses."""
|
|
300
|
+
from .discovery import discover_all
|
|
301
|
+
try:
|
|
302
|
+
states = discover_all(home)
|
|
303
|
+
return {name: state.status.value for name, state in states.items()}
|
|
304
|
+
except Exception as exc:
|
|
305
|
+
logger.warning("Failed to snapshot pillars: %s", exc)
|
|
306
|
+
return {}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _find_latest_seed(home: Path) -> dict[str, Any] | None:
|
|
310
|
+
"""Find the most recent sync seed as a fallback baseline."""
|
|
311
|
+
for subdir in ("outbox", "archive"):
|
|
312
|
+
seed_dir = home / "sync" / subdir
|
|
313
|
+
if not seed_dir.exists():
|
|
314
|
+
continue
|
|
315
|
+
seeds = sorted(seed_dir.glob("*.seed.json*"), reverse=True)
|
|
316
|
+
if seeds:
|
|
317
|
+
try:
|
|
318
|
+
data = json.loads(seeds[0].read_text(encoding="utf-8"))
|
|
319
|
+
memory_data = data.get("memory", {})
|
|
320
|
+
return {
|
|
321
|
+
"timestamp": data.get("created_at", "unknown"),
|
|
322
|
+
"memories": {
|
|
323
|
+
"count": memory_data.get("total", 0),
|
|
324
|
+
"ids": [],
|
|
325
|
+
"entries": [],
|
|
326
|
+
},
|
|
327
|
+
"trust": data.get("trust", {}),
|
|
328
|
+
"tasks": {"total": 0, "done_ids": [], "all_ids": [], "tasks": []},
|
|
329
|
+
"pillars": {},
|
|
330
|
+
}
|
|
331
|
+
except (json.JSONDecodeError, OSError):
|
|
332
|
+
continue
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
337
|
+
# Diff computation
|
|
338
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _diff_memories(baseline: dict, current: dict, diff: StateDiff) -> None:
|
|
342
|
+
"""Compute memory differences."""
|
|
343
|
+
old_mem = baseline.get("memories", {})
|
|
344
|
+
new_mem = current.get("memories", {})
|
|
345
|
+
|
|
346
|
+
old_ids = set(old_mem.get("ids", []))
|
|
347
|
+
new_ids = set(new_mem.get("ids", []))
|
|
348
|
+
|
|
349
|
+
diff.memory_count_before = old_mem.get("count", len(old_ids))
|
|
350
|
+
diff.memory_count_now = new_mem.get("count", len(new_ids))
|
|
351
|
+
|
|
352
|
+
added_ids = new_ids - old_ids
|
|
353
|
+
diff.removed_memories = list(old_ids - new_ids)
|
|
354
|
+
|
|
355
|
+
entries_by_id = {e["id"]: e for e in new_mem.get("entries", [])}
|
|
356
|
+
diff.new_memories = [
|
|
357
|
+
entries_by_id.get(mid, {"id": mid, "content": ""})
|
|
358
|
+
for mid in added_ids
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _diff_trust(baseline: dict, current: dict, diff: StateDiff) -> None:
|
|
363
|
+
"""Compute trust state differences."""
|
|
364
|
+
old_trust = baseline.get("trust", {})
|
|
365
|
+
new_trust = current.get("trust", {})
|
|
366
|
+
|
|
367
|
+
for key in ("depth", "trust_level", "love_intensity", "entangled"):
|
|
368
|
+
old_val = old_trust.get(key)
|
|
369
|
+
new_val = new_trust.get(key)
|
|
370
|
+
if old_val != new_val and new_val is not None:
|
|
371
|
+
diff.trust_changes[key] = {"old": old_val, "new": new_val}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _diff_tasks(baseline: dict, current: dict, diff: StateDiff) -> None:
|
|
375
|
+
"""Compute coordination board differences."""
|
|
376
|
+
old_tasks = baseline.get("tasks", {})
|
|
377
|
+
new_tasks = current.get("tasks", {})
|
|
378
|
+
|
|
379
|
+
old_ids = set(old_tasks.get("all_ids", []))
|
|
380
|
+
new_ids = set(new_tasks.get("all_ids", []))
|
|
381
|
+
old_done = set(old_tasks.get("done_ids", []))
|
|
382
|
+
new_done = set(new_tasks.get("done_ids", []))
|
|
383
|
+
|
|
384
|
+
created_ids = new_ids - old_ids
|
|
385
|
+
newly_done = new_done - old_done
|
|
386
|
+
|
|
387
|
+
tasks_by_id = {t["id"]: t for t in new_tasks.get("tasks", [])}
|
|
388
|
+
diff.new_tasks = [tasks_by_id.get(tid, {"id": tid}) for tid in created_ids]
|
|
389
|
+
diff.completed_tasks = [tasks_by_id.get(tid, {"id": tid}) for tid in newly_done]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _diff_pillars(baseline: dict, current: dict, diff: StateDiff) -> None:
|
|
393
|
+
"""Compute pillar status changes."""
|
|
394
|
+
old_p = baseline.get("pillars", {})
|
|
395
|
+
new_p = current.get("pillars", {})
|
|
396
|
+
|
|
397
|
+
for name in set(list(old_p.keys()) + list(new_p.keys())):
|
|
398
|
+
old_val = old_p.get(name, "unknown")
|
|
399
|
+
new_val = new_p.get(name, "unknown")
|
|
400
|
+
if old_val != new_val:
|
|
401
|
+
diff.pillar_changes[name] = {"old": old_val, "new": new_val}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sovereign agent morning briefing.
|
|
3
|
+
|
|
4
|
+
One screen. Everything you need to know. The first command of
|
|
5
|
+
every session. Gathers data from every pillar and presents a
|
|
6
|
+
compact, information-dense overview.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
skcapstone summary
|
|
10
|
+
skcapstone summary --json-out
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("skcapstone.summary")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def gather_briefing(home: Path) -> dict:
|
|
25
|
+
"""Gather all data for the morning briefing.
|
|
26
|
+
|
|
27
|
+
Pulls from runtime, memory, coordination board, peers,
|
|
28
|
+
backups, and doctor diagnostics to build a single dict.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
home: Agent home directory (~/.skcapstone).
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
dict: Complete briefing data.
|
|
35
|
+
"""
|
|
36
|
+
briefing = {
|
|
37
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
38
|
+
"agent": _agent_info(home),
|
|
39
|
+
"pillars": _pillar_summary(home),
|
|
40
|
+
"memory": _memory_summary(home),
|
|
41
|
+
"board": _board_summary(home),
|
|
42
|
+
"peers": _peer_summary(home),
|
|
43
|
+
"inbox": _inbox_summary(home),
|
|
44
|
+
"sync": _sync_summary(home),
|
|
45
|
+
"backups": _backup_summary(home),
|
|
46
|
+
"health": _health_summary(home),
|
|
47
|
+
"journal": _journal_summary(),
|
|
48
|
+
}
|
|
49
|
+
return briefing
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _agent_info(home: Path) -> dict:
|
|
53
|
+
"""Load basic agent identity and consciousness."""
|
|
54
|
+
try:
|
|
55
|
+
from .runtime import get_runtime
|
|
56
|
+
|
|
57
|
+
runtime = get_runtime(home)
|
|
58
|
+
m = runtime.manifest
|
|
59
|
+
|
|
60
|
+
if m.is_singular:
|
|
61
|
+
consciousness = "SINGULAR"
|
|
62
|
+
elif m.is_conscious:
|
|
63
|
+
consciousness = "CONSCIOUS"
|
|
64
|
+
else:
|
|
65
|
+
consciousness = "AWAKENING"
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"name": m.name,
|
|
69
|
+
"version": m.version,
|
|
70
|
+
"consciousness": consciousness,
|
|
71
|
+
"home": str(m.home),
|
|
72
|
+
}
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
logger.warning("Failed to load agent info: %s", exc)
|
|
75
|
+
return {"name": "unknown", "consciousness": "UNKNOWN", "home": str(home)}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _pillar_summary(home: Path) -> dict:
|
|
79
|
+
"""Get one-line status for each pillar."""
|
|
80
|
+
try:
|
|
81
|
+
from .runtime import get_runtime
|
|
82
|
+
|
|
83
|
+
runtime = get_runtime(home)
|
|
84
|
+
m = runtime.manifest
|
|
85
|
+
return {k: v.value for k, v in m.pillar_summary.items()}
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
logger.warning("Failed to load pillar summary: %s", exc)
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _memory_summary(home: Path) -> dict:
|
|
92
|
+
"""Get memory counts and recent entries."""
|
|
93
|
+
try:
|
|
94
|
+
from .memory_engine import get_stats, list_memories
|
|
95
|
+
from .models import MemoryLayer
|
|
96
|
+
|
|
97
|
+
stats = get_stats(home)
|
|
98
|
+
recent = list_memories(home, limit=3)
|
|
99
|
+
recent_titles = []
|
|
100
|
+
for entry in recent:
|
|
101
|
+
preview = entry.content[:60] + "..." if len(entry.content) > 60 else entry.content
|
|
102
|
+
recent_titles.append(preview)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"total": stats.total_memories,
|
|
106
|
+
"short_term": stats.short_term,
|
|
107
|
+
"mid_term": stats.mid_term,
|
|
108
|
+
"long_term": stats.long_term,
|
|
109
|
+
"recent": recent_titles,
|
|
110
|
+
}
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
logger.warning("Failed to load memory summary: %s", exc)
|
|
113
|
+
return {"total": 0, "recent": []}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _board_summary(home: Path) -> dict:
|
|
117
|
+
"""Get coordination board stats and active tasks."""
|
|
118
|
+
try:
|
|
119
|
+
from .coordination import Board
|
|
120
|
+
|
|
121
|
+
board = Board(home)
|
|
122
|
+
views = board.get_task_views()
|
|
123
|
+
|
|
124
|
+
done = sum(1 for v in views if v.status.value == "done")
|
|
125
|
+
open_count = sum(1 for v in views if v.status.value == "open")
|
|
126
|
+
in_progress = sum(1 for v in views if v.status.value in ("in_progress", "claimed"))
|
|
127
|
+
|
|
128
|
+
active_tasks = [
|
|
129
|
+
{"title": v.task.title[:50], "assignee": v.claimed_by or "unassigned"}
|
|
130
|
+
for v in views
|
|
131
|
+
if v.status.value in ("in_progress", "claimed", "open")
|
|
132
|
+
][:5]
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"total": len(views),
|
|
136
|
+
"done": done,
|
|
137
|
+
"open": open_count,
|
|
138
|
+
"in_progress": in_progress,
|
|
139
|
+
"active_tasks": active_tasks,
|
|
140
|
+
}
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
logger.warning("Failed to load board summary: %s", exc)
|
|
143
|
+
return {"total": 0, "done": 0, "open": 0, "in_progress": 0, "active_tasks": []}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _peer_summary(home: Path) -> dict:
|
|
147
|
+
"""Count known peers."""
|
|
148
|
+
try:
|
|
149
|
+
from .peers import list_peers
|
|
150
|
+
|
|
151
|
+
peers = list_peers(skcapstone_home=home)
|
|
152
|
+
names = [p.name for p in peers[:5]]
|
|
153
|
+
return {"count": len(peers), "names": names}
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
logger.debug("Peer discovery unavailable: %s", exc)
|
|
156
|
+
peers_dir = home / "peers"
|
|
157
|
+
if peers_dir.exists():
|
|
158
|
+
count = sum(1 for f in peers_dir.glob("*.json"))
|
|
159
|
+
return {"count": count, "names": []}
|
|
160
|
+
return {"count": 0, "names": []}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _backup_summary(home: Path) -> dict:
|
|
164
|
+
"""Get last backup info."""
|
|
165
|
+
try:
|
|
166
|
+
from .backup import list_backups
|
|
167
|
+
|
|
168
|
+
backups = list_backups(home)
|
|
169
|
+
if backups:
|
|
170
|
+
latest = backups[0]
|
|
171
|
+
return {
|
|
172
|
+
"count": len(backups),
|
|
173
|
+
"latest": latest.get("created_at", "")[:19],
|
|
174
|
+
"encrypted": latest.get("encrypted", False),
|
|
175
|
+
}
|
|
176
|
+
return {"count": 0, "latest": None}
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
logger.debug("Backup info unavailable: %s", exc)
|
|
179
|
+
return {"count": 0, "latest": None}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _health_summary(home: Path) -> dict:
|
|
183
|
+
"""Get doctor pass/fail counts."""
|
|
184
|
+
try:
|
|
185
|
+
from .doctor import run_diagnostics
|
|
186
|
+
|
|
187
|
+
report = run_diagnostics(home)
|
|
188
|
+
return {
|
|
189
|
+
"passed": report.passed_count,
|
|
190
|
+
"failed": report.failed_count,
|
|
191
|
+
"total": report.total_count,
|
|
192
|
+
"all_passed": report.all_passed,
|
|
193
|
+
}
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
logger.debug("Doctor diagnostics unavailable: %s", exc)
|
|
196
|
+
return {"passed": 0, "failed": 0, "total": 0, "all_passed": False}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _inbox_summary(home: Path) -> dict:
|
|
200
|
+
"""Count unread messages in the SKComm and SKChat inboxes."""
|
|
201
|
+
total = 0
|
|
202
|
+
sources: list[str] = []
|
|
203
|
+
|
|
204
|
+
# SKComm file transport inbox
|
|
205
|
+
skcomm_inbox = home / "comms" / "inbox"
|
|
206
|
+
if skcomm_inbox.exists():
|
|
207
|
+
count = sum(1 for f in skcomm_inbox.iterdir() if f.is_file())
|
|
208
|
+
if count:
|
|
209
|
+
total += count
|
|
210
|
+
sources.append(f"skcomm:{count}")
|
|
211
|
+
|
|
212
|
+
# SKChat local inbox (skchat daemon stores messages here)
|
|
213
|
+
skchat_inbox = home / "skchat" / "inbox"
|
|
214
|
+
if skchat_inbox.exists():
|
|
215
|
+
count = sum(1 for f in skchat_inbox.iterdir() if f.is_file())
|
|
216
|
+
if count:
|
|
217
|
+
total += count
|
|
218
|
+
sources.append(f"skchat:{count}")
|
|
219
|
+
|
|
220
|
+
return {"count": total, "sources": sources}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _sync_summary(home: Path) -> dict:
|
|
224
|
+
"""Get sync pillar status: transport, seed count, last push."""
|
|
225
|
+
try:
|
|
226
|
+
from .runtime import get_runtime
|
|
227
|
+
|
|
228
|
+
runtime = get_runtime(home)
|
|
229
|
+
sy = runtime.manifest.sync
|
|
230
|
+
result: dict = {
|
|
231
|
+
"status": sy.status.value,
|
|
232
|
+
"seed_count": sy.seed_count,
|
|
233
|
+
"transport": sy.transport.value if sy.transport else None,
|
|
234
|
+
"gpg": bool(sy.gpg_fingerprint),
|
|
235
|
+
"last_push": sy.last_push.isoformat() if sy.last_push else None,
|
|
236
|
+
}
|
|
237
|
+
return result
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
logger.debug("Sync summary from runtime unavailable: %s", exc)
|
|
240
|
+
# Fallback: inspect sync directory directly
|
|
241
|
+
sync_dir = home / "sync"
|
|
242
|
+
seeds = list(sync_dir.glob("*.tar.gz*")) if sync_dir.exists() else []
|
|
243
|
+
return {
|
|
244
|
+
"status": "active" if sync_dir.exists() else "missing",
|
|
245
|
+
"seed_count": len(seeds),
|
|
246
|
+
"transport": None,
|
|
247
|
+
"gpg": False,
|
|
248
|
+
"last_push": None,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _journal_summary() -> dict:
|
|
253
|
+
"""Get journal entry count and latest title."""
|
|
254
|
+
try:
|
|
255
|
+
from skmemory.journal import Journal
|
|
256
|
+
|
|
257
|
+
journal = Journal()
|
|
258
|
+
count = journal.count_entries()
|
|
259
|
+
latest = ""
|
|
260
|
+
if count > 0:
|
|
261
|
+
text = journal.read_latest(1)
|
|
262
|
+
for line in text.split("\n"):
|
|
263
|
+
if line.startswith("## "):
|
|
264
|
+
latest = line[3:].strip()
|
|
265
|
+
break
|
|
266
|
+
|
|
267
|
+
return {"entries": count, "latest_title": latest}
|
|
268
|
+
except Exception as exc:
|
|
269
|
+
logger.debug("Journal unavailable: %s", exc)
|
|
270
|
+
return {"entries": 0, "latest_title": ""}
|