@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,257 @@
|
|
|
1
|
+
"""Tests for agent capability advertisement.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- AgentConfig defaults
|
|
5
|
+
- HeartbeatBeacon reading capabilities from config
|
|
6
|
+
- HeartbeatBeacon falling back to defaults when config absent
|
|
7
|
+
- Heartbeat pulse includes config capabilities
|
|
8
|
+
- CLI capabilities command (list, add, remove)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
import yaml
|
|
18
|
+
from click.testing import CliRunner
|
|
19
|
+
|
|
20
|
+
from skcapstone.heartbeat import AgentCapability, HeartbeatBeacon
|
|
21
|
+
from skcapstone.models import AgentConfig
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# AgentConfig defaults
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestAgentConfigCapabilities:
|
|
30
|
+
"""AgentConfig capabilities field."""
|
|
31
|
+
|
|
32
|
+
def test_default_capabilities(self) -> None:
|
|
33
|
+
"""AgentConfig has the four default capabilities."""
|
|
34
|
+
cfg = AgentConfig()
|
|
35
|
+
assert cfg.capabilities == ["consciousness", "code", "chat", "memory"]
|
|
36
|
+
|
|
37
|
+
def test_custom_capabilities(self) -> None:
|
|
38
|
+
"""AgentConfig accepts custom capabilities."""
|
|
39
|
+
cfg = AgentConfig(capabilities=["vector-search", "reasoning"])
|
|
40
|
+
assert cfg.capabilities == ["vector-search", "reasoning"]
|
|
41
|
+
|
|
42
|
+
def test_capabilities_preserved_in_yaml_roundtrip(self, tmp_path: Path) -> None:
|
|
43
|
+
"""Capabilities survive a YAML serialise/deserialise round-trip."""
|
|
44
|
+
cfg = AgentConfig(capabilities=["consciousness", "code"])
|
|
45
|
+
config_path = tmp_path / "config.yaml"
|
|
46
|
+
config_path.write_text(
|
|
47
|
+
yaml.dump({"capabilities": cfg.capabilities}, default_flow_style=False),
|
|
48
|
+
encoding="utf-8",
|
|
49
|
+
)
|
|
50
|
+
loaded = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
51
|
+
restored = AgentConfig(**loaded)
|
|
52
|
+
assert restored.capabilities == ["consciousness", "code"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# HeartbeatBeacon — capability loading
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def home(tmp_path: Path) -> Path:
|
|
62
|
+
"""Minimal agent home."""
|
|
63
|
+
(tmp_path / "identity").mkdir()
|
|
64
|
+
(tmp_path / "identity" / "identity.json").write_text(
|
|
65
|
+
json.dumps({"name": "opus", "fingerprint": "ABCD1234567890AB"}),
|
|
66
|
+
encoding="utf-8",
|
|
67
|
+
)
|
|
68
|
+
return tmp_path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def beacon(home: Path) -> HeartbeatBeacon:
|
|
73
|
+
b = HeartbeatBeacon(home, agent_name="opus")
|
|
74
|
+
b.initialize()
|
|
75
|
+
return b
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestHeartbeatCapabilityLoading:
|
|
79
|
+
"""HeartbeatBeacon reads capabilities from config."""
|
|
80
|
+
|
|
81
|
+
def test_defaults_when_no_config(self, beacon: HeartbeatBeacon) -> None:
|
|
82
|
+
"""Falls back to AgentConfig defaults when config.yaml is absent."""
|
|
83
|
+
caps = beacon._load_config_capabilities()
|
|
84
|
+
assert caps == ["consciousness", "code", "chat", "memory"]
|
|
85
|
+
|
|
86
|
+
def test_reads_capabilities_from_config(self, beacon: HeartbeatBeacon, home: Path) -> None:
|
|
87
|
+
"""Reads custom capabilities list from config.yaml."""
|
|
88
|
+
config_dir = home / "config"
|
|
89
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
(config_dir / "config.yaml").write_text(
|
|
91
|
+
yaml.dump({"capabilities": ["consciousness", "code", "vector-search"]}),
|
|
92
|
+
encoding="utf-8",
|
|
93
|
+
)
|
|
94
|
+
caps = beacon._load_config_capabilities()
|
|
95
|
+
assert caps == ["consciousness", "code", "vector-search"]
|
|
96
|
+
|
|
97
|
+
def test_detect_capabilities_includes_config_caps(
|
|
98
|
+
self, beacon: HeartbeatBeacon, home: Path
|
|
99
|
+
) -> None:
|
|
100
|
+
"""_detect_capabilities() includes all config-listed capabilities."""
|
|
101
|
+
config_dir = home / "config"
|
|
102
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
(config_dir / "config.yaml").write_text(
|
|
104
|
+
yaml.dump({"capabilities": ["consciousness", "code", "chat", "memory"]}),
|
|
105
|
+
encoding="utf-8",
|
|
106
|
+
)
|
|
107
|
+
caps = beacon._detect_capabilities()
|
|
108
|
+
names = [c.name for c in caps]
|
|
109
|
+
assert "consciousness" in names
|
|
110
|
+
assert "code" in names
|
|
111
|
+
assert "chat" in names
|
|
112
|
+
assert "memory" in names
|
|
113
|
+
|
|
114
|
+
def test_detect_capabilities_no_duplicates_from_packages(
|
|
115
|
+
self, beacon: HeartbeatBeacon, home: Path
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Packages already in config list are not duplicated."""
|
|
118
|
+
config_dir = home / "config"
|
|
119
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
(config_dir / "config.yaml").write_text(
|
|
121
|
+
yaml.dump({"capabilities": ["skcapstone"]}),
|
|
122
|
+
encoding="utf-8",
|
|
123
|
+
)
|
|
124
|
+
caps = beacon._detect_capabilities()
|
|
125
|
+
names = [c.name for c in caps]
|
|
126
|
+
assert names.count("skcapstone") == 1
|
|
127
|
+
|
|
128
|
+
def test_pulse_heartbeat_has_config_capabilities(
|
|
129
|
+
self, beacon: HeartbeatBeacon, home: Path
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Pulse embeds config capabilities in the published heartbeat."""
|
|
132
|
+
config_dir = home / "config"
|
|
133
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
(config_dir / "config.yaml").write_text(
|
|
135
|
+
yaml.dump({"capabilities": ["consciousness", "code", "chat", "memory"]}),
|
|
136
|
+
encoding="utf-8",
|
|
137
|
+
)
|
|
138
|
+
hb = beacon.pulse()
|
|
139
|
+
cap_names = [c.name for c in hb.capabilities]
|
|
140
|
+
assert "consciousness" in cap_names
|
|
141
|
+
assert "code" in cap_names
|
|
142
|
+
assert "chat" in cap_names
|
|
143
|
+
assert "memory" in cap_names
|
|
144
|
+
|
|
145
|
+
def test_pulse_heartbeat_persisted_capabilities(
|
|
146
|
+
self, beacon: HeartbeatBeacon, home: Path
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Capabilities written to disk match what was pulsed."""
|
|
149
|
+
config_dir = home / "config"
|
|
150
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
(config_dir / "config.yaml").write_text(
|
|
152
|
+
yaml.dump({"capabilities": ["consciousness", "chat"]}),
|
|
153
|
+
encoding="utf-8",
|
|
154
|
+
)
|
|
155
|
+
beacon.pulse()
|
|
156
|
+
hb_file = home / "heartbeats" / "opus.json"
|
|
157
|
+
data = json.loads(hb_file.read_text(encoding="utf-8"))
|
|
158
|
+
persisted_names = [c["name"] for c in data["capabilities"]]
|
|
159
|
+
assert "consciousness" in persisted_names
|
|
160
|
+
assert "chat" in persisted_names
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# CLI capabilities command
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.fixture
|
|
169
|
+
def runner() -> CliRunner:
|
|
170
|
+
return CliRunner()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class TestCapabilitiesCLI:
|
|
174
|
+
"""CLI: skcapstone capabilities."""
|
|
175
|
+
|
|
176
|
+
def test_capabilities_list_defaults(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
177
|
+
"""capabilities list shows defaults when no config present."""
|
|
178
|
+
from skcapstone.cli import main
|
|
179
|
+
|
|
180
|
+
result = runner.invoke(main, ["capabilities", "list", "--home", str(tmp_path)])
|
|
181
|
+
assert result.exit_code == 0
|
|
182
|
+
assert "consciousness" in result.output
|
|
183
|
+
assert "code" in result.output
|
|
184
|
+
assert "chat" in result.output
|
|
185
|
+
assert "memory" in result.output
|
|
186
|
+
|
|
187
|
+
def test_capabilities_list_json(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
188
|
+
"""capabilities list --json returns a JSON array."""
|
|
189
|
+
from skcapstone.cli import main
|
|
190
|
+
|
|
191
|
+
result = runner.invoke(
|
|
192
|
+
main, ["capabilities", "list", "--json", "--home", str(tmp_path)]
|
|
193
|
+
)
|
|
194
|
+
assert result.exit_code == 0
|
|
195
|
+
caps = json.loads(result.output)
|
|
196
|
+
assert isinstance(caps, list)
|
|
197
|
+
assert "consciousness" in caps
|
|
198
|
+
|
|
199
|
+
def test_capabilities_add(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
200
|
+
"""capabilities add appends a new capability to config."""
|
|
201
|
+
from skcapstone.cli import main
|
|
202
|
+
|
|
203
|
+
result = runner.invoke(
|
|
204
|
+
main, ["capabilities", "add", "vector-search", "--home", str(tmp_path)]
|
|
205
|
+
)
|
|
206
|
+
assert result.exit_code == 0
|
|
207
|
+
assert "vector-search" in result.output
|
|
208
|
+
|
|
209
|
+
# Verify it persisted
|
|
210
|
+
config_path = tmp_path / "config" / "config.yaml"
|
|
211
|
+
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
212
|
+
assert "vector-search" in data["capabilities"]
|
|
213
|
+
|
|
214
|
+
def test_capabilities_add_no_duplicate(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
215
|
+
"""capabilities add skips silently when capability already present."""
|
|
216
|
+
from skcapstone.cli import main
|
|
217
|
+
|
|
218
|
+
# Add a new capability first (ensures config file is created)
|
|
219
|
+
runner.invoke(main, ["capabilities", "add", "vector-search", "--home", str(tmp_path)])
|
|
220
|
+
config_path = tmp_path / "config" / "config.yaml"
|
|
221
|
+
data1 = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
222
|
+
count_before = data1["capabilities"].count("vector-search")
|
|
223
|
+
|
|
224
|
+
# Add the same capability again — should NOT duplicate
|
|
225
|
+
result = runner.invoke(
|
|
226
|
+
main, ["capabilities", "add", "vector-search", "--home", str(tmp_path)]
|
|
227
|
+
)
|
|
228
|
+
assert result.exit_code == 0
|
|
229
|
+
data2 = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
230
|
+
assert data2["capabilities"].count("vector-search") == count_before
|
|
231
|
+
|
|
232
|
+
def test_capabilities_remove(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
233
|
+
"""capabilities remove drops a capability from config."""
|
|
234
|
+
from skcapstone.cli import main
|
|
235
|
+
|
|
236
|
+
# First add so it's persisted
|
|
237
|
+
runner.invoke(main, ["capabilities", "add", "chat", "--home", str(tmp_path)])
|
|
238
|
+
|
|
239
|
+
result = runner.invoke(
|
|
240
|
+
main, ["capabilities", "remove", "chat", "--home", str(tmp_path)]
|
|
241
|
+
)
|
|
242
|
+
assert result.exit_code == 0
|
|
243
|
+
assert "chat" in result.output
|
|
244
|
+
|
|
245
|
+
config_path = tmp_path / "config" / "config.yaml"
|
|
246
|
+
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
247
|
+
assert "chat" not in data["capabilities"]
|
|
248
|
+
|
|
249
|
+
def test_capabilities_root_command_shows_table(
|
|
250
|
+
self, runner: CliRunner, tmp_path: Path
|
|
251
|
+
) -> None:
|
|
252
|
+
"""skcapstone capabilities (no subcommand) prints capability table."""
|
|
253
|
+
from skcapstone.cli import main
|
|
254
|
+
|
|
255
|
+
result = runner.invoke(main, ["capabilities", "--home", str(tmp_path)])
|
|
256
|
+
assert result.exit_code == 0
|
|
257
|
+
assert "consciousness" in result.output
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Tests for the Changelog Generator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from skcapstone.changelog import (
|
|
11
|
+
TAG_CATEGORIES,
|
|
12
|
+
_categorize,
|
|
13
|
+
_parse_date,
|
|
14
|
+
generate_changelog,
|
|
15
|
+
write_changelog,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Categorization
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestCategorize:
|
|
25
|
+
"""Tests for tag-based categorization."""
|
|
26
|
+
|
|
27
|
+
def test_feature_tags(self) -> None:
|
|
28
|
+
"""Feature tags categorize correctly."""
|
|
29
|
+
assert _categorize(["skcapstone", "memory"]) == "feature"
|
|
30
|
+
|
|
31
|
+
def test_security_tags(self) -> None:
|
|
32
|
+
"""Security tags categorize correctly."""
|
|
33
|
+
assert _categorize(["encryption", "security"]) == "security"
|
|
34
|
+
|
|
35
|
+
def test_infrastructure_tags(self) -> None:
|
|
36
|
+
"""Infrastructure tags categorize correctly."""
|
|
37
|
+
assert _categorize(["docker", "ci"]) == "infrastructure"
|
|
38
|
+
|
|
39
|
+
def test_documentation_tags(self) -> None:
|
|
40
|
+
"""Documentation tags categorize correctly."""
|
|
41
|
+
assert _categorize(["docs", "readme"]) == "documentation"
|
|
42
|
+
|
|
43
|
+
def test_empty_tags(self) -> None:
|
|
44
|
+
"""Empty tags return 'other'."""
|
|
45
|
+
assert _categorize([]) == "other"
|
|
46
|
+
|
|
47
|
+
def test_unknown_tags(self) -> None:
|
|
48
|
+
"""Unrecognized tags return 'other'."""
|
|
49
|
+
assert _categorize(["random", "unknown"]) == "other"
|
|
50
|
+
|
|
51
|
+
def test_best_match_wins(self) -> None:
|
|
52
|
+
"""When multiple categories match, the one with most matches wins."""
|
|
53
|
+
result = _categorize(["skcapstone", "skchat", "security"])
|
|
54
|
+
assert result == "feature" # 2 feature matches vs 1 security
|
|
55
|
+
|
|
56
|
+
def test_case_insensitive(self) -> None:
|
|
57
|
+
"""Tags are case-insensitive."""
|
|
58
|
+
assert _categorize(["SKCAPSTONE"]) == "feature"
|
|
59
|
+
|
|
60
|
+
def test_emotional_tags(self) -> None:
|
|
61
|
+
"""Emotional/soul tags categorize correctly."""
|
|
62
|
+
assert _categorize(["cloud9", "soul", "trust"]) == "emotional"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Date parsing
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestParseDate:
|
|
71
|
+
"""Tests for ISO date extraction."""
|
|
72
|
+
|
|
73
|
+
def test_iso_datetime(self) -> None:
|
|
74
|
+
"""Parses full ISO datetime."""
|
|
75
|
+
assert _parse_date("2026-02-24T12:00:00+00:00") == "2026-02-24"
|
|
76
|
+
|
|
77
|
+
def test_date_only(self) -> None:
|
|
78
|
+
"""Parses date-only string."""
|
|
79
|
+
assert _parse_date("2026-01-15") == "2026-01-15"
|
|
80
|
+
|
|
81
|
+
def test_invalid_string(self) -> None:
|
|
82
|
+
"""Invalid string returns today's date."""
|
|
83
|
+
result = _parse_date("not-a-date")
|
|
84
|
+
# Should be YYYY-MM-DD format
|
|
85
|
+
assert len(result) == 10
|
|
86
|
+
assert result[4] == "-"
|
|
87
|
+
|
|
88
|
+
def test_none_input(self) -> None:
|
|
89
|
+
"""None input returns today's date."""
|
|
90
|
+
result = _parse_date(None)
|
|
91
|
+
assert len(result) == 10
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Changelog generation
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _setup_board(home: Path, tasks: list[dict]) -> None:
|
|
100
|
+
"""Create a minimal coordination board with tasks and agent files.
|
|
101
|
+
|
|
102
|
+
Status is derived from agent files, not the task JSON. Tasks whose
|
|
103
|
+
``status`` field is ``"done"`` are added to a test agent's
|
|
104
|
+
``completed_tasks`` list so the Board marks them as done.
|
|
105
|
+
"""
|
|
106
|
+
coord_dir = home / "coordination" / "tasks"
|
|
107
|
+
coord_dir.mkdir(parents=True)
|
|
108
|
+
agents_dir = home / "coordination" / "agents"
|
|
109
|
+
agents_dir.mkdir(parents=True)
|
|
110
|
+
|
|
111
|
+
completed_ids: list[str] = []
|
|
112
|
+
for task in tasks:
|
|
113
|
+
# Strip the non-schema "status" key before writing the task file.
|
|
114
|
+
task_data = {k: v for k, v in task.items() if k != "status"}
|
|
115
|
+
task_file = coord_dir / f"{task['id']}-test.json"
|
|
116
|
+
task_file.write_text(json.dumps(task_data), encoding="utf-8")
|
|
117
|
+
|
|
118
|
+
if task.get("status") == "done":
|
|
119
|
+
completed_ids.append(task["id"])
|
|
120
|
+
|
|
121
|
+
# Create an agent file so the Board can derive "done" status.
|
|
122
|
+
if completed_ids:
|
|
123
|
+
agent_file = agents_dir / "test-agent.json"
|
|
124
|
+
agent_file.write_text(
|
|
125
|
+
json.dumps({
|
|
126
|
+
"agent": "test-agent",
|
|
127
|
+
"completed_tasks": completed_ids,
|
|
128
|
+
"claimed_tasks": completed_ids,
|
|
129
|
+
}),
|
|
130
|
+
encoding="utf-8",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TestGenerateChangelog:
|
|
135
|
+
"""Tests for changelog generation."""
|
|
136
|
+
|
|
137
|
+
def test_empty_board(self, tmp_path: Path) -> None:
|
|
138
|
+
"""Empty board generates minimal changelog."""
|
|
139
|
+
(tmp_path / "coordination" / "tasks").mkdir(parents=True)
|
|
140
|
+
(tmp_path / "coordination" / "agents").mkdir(parents=True)
|
|
141
|
+
|
|
142
|
+
result = generate_changelog(tmp_path)
|
|
143
|
+
assert "# SKCapstone Changelog" in result
|
|
144
|
+
assert "Total completed: 0" in result
|
|
145
|
+
|
|
146
|
+
def test_completed_tasks_appear(self, tmp_path: Path) -> None:
|
|
147
|
+
"""Completed tasks appear in the changelog."""
|
|
148
|
+
_setup_board(tmp_path, [
|
|
149
|
+
{
|
|
150
|
+
"id": "abc123",
|
|
151
|
+
"title": "Add encrypted messaging",
|
|
152
|
+
"status": "done",
|
|
153
|
+
"priority": "high",
|
|
154
|
+
"tags": ["skchat", "encryption"],
|
|
155
|
+
"created_at": "2026-02-24T12:00:00+00:00",
|
|
156
|
+
},
|
|
157
|
+
])
|
|
158
|
+
result = generate_changelog(tmp_path)
|
|
159
|
+
assert "Add encrypted messaging" in result
|
|
160
|
+
|
|
161
|
+
def test_open_tasks_excluded(self, tmp_path: Path) -> None:
|
|
162
|
+
"""Open tasks do not appear in the changelog."""
|
|
163
|
+
_setup_board(tmp_path, [
|
|
164
|
+
{
|
|
165
|
+
"id": "abc123",
|
|
166
|
+
"title": "Pending feature",
|
|
167
|
+
"status": "open",
|
|
168
|
+
"priority": "medium",
|
|
169
|
+
"tags": ["skcapstone"],
|
|
170
|
+
"created_at": "2026-02-24T12:00:00+00:00",
|
|
171
|
+
},
|
|
172
|
+
])
|
|
173
|
+
result = generate_changelog(tmp_path)
|
|
174
|
+
assert "Pending feature" not in result
|
|
175
|
+
|
|
176
|
+
def test_grouped_by_date(self, tmp_path: Path) -> None:
|
|
177
|
+
"""Tasks are grouped by date."""
|
|
178
|
+
_setup_board(tmp_path, [
|
|
179
|
+
{
|
|
180
|
+
"id": "t1",
|
|
181
|
+
"title": "Task One",
|
|
182
|
+
"status": "done",
|
|
183
|
+
"priority": "medium",
|
|
184
|
+
"tags": ["skcapstone"],
|
|
185
|
+
"created_at": "2026-02-24T12:00:00+00:00",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "t2",
|
|
189
|
+
"title": "Task Two",
|
|
190
|
+
"status": "done",
|
|
191
|
+
"priority": "medium",
|
|
192
|
+
"tags": ["skcapstone"],
|
|
193
|
+
"created_at": "2026-02-25T12:00:00+00:00",
|
|
194
|
+
},
|
|
195
|
+
])
|
|
196
|
+
result = generate_changelog(tmp_path)
|
|
197
|
+
assert "## 2026-02-24" in result
|
|
198
|
+
assert "## 2026-02-25" in result
|
|
199
|
+
|
|
200
|
+
def test_custom_title(self, tmp_path: Path) -> None:
|
|
201
|
+
"""Custom title is used."""
|
|
202
|
+
(tmp_path / "coordination" / "tasks").mkdir(parents=True)
|
|
203
|
+
(tmp_path / "coordination" / "agents").mkdir(parents=True)
|
|
204
|
+
|
|
205
|
+
result = generate_changelog(tmp_path, title="My Custom Changelog")
|
|
206
|
+
assert "# My Custom Changelog" in result
|
|
207
|
+
|
|
208
|
+
def test_without_agents(self, tmp_path: Path) -> None:
|
|
209
|
+
"""Agent attribution can be disabled."""
|
|
210
|
+
_setup_board(tmp_path, [
|
|
211
|
+
{
|
|
212
|
+
"id": "t1",
|
|
213
|
+
"title": "Some Task",
|
|
214
|
+
"status": "done",
|
|
215
|
+
"priority": "medium",
|
|
216
|
+
"tags": ["skcapstone"],
|
|
217
|
+
"created_at": "2026-02-24T12:00:00+00:00",
|
|
218
|
+
},
|
|
219
|
+
])
|
|
220
|
+
result = generate_changelog(tmp_path, include_agents=False)
|
|
221
|
+
assert "Some Task" in result
|
|
222
|
+
assert "(@" not in result.split("Some Task")[1].split("\n")[0]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# Write changelog
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestWriteChangelog:
|
|
231
|
+
"""Tests for writing changelog to file."""
|
|
232
|
+
|
|
233
|
+
def test_writes_to_default_path(self, tmp_path: Path, monkeypatch) -> None:
|
|
234
|
+
"""write_changelog creates CHANGELOG.md."""
|
|
235
|
+
(tmp_path / "coordination" / "tasks").mkdir(parents=True)
|
|
236
|
+
(tmp_path / "coordination" / "agents").mkdir(parents=True)
|
|
237
|
+
|
|
238
|
+
output = tmp_path / "CHANGELOG.md"
|
|
239
|
+
result = write_changelog(tmp_path, output=output)
|
|
240
|
+
assert result == output
|
|
241
|
+
assert output.exists()
|
|
242
|
+
content = output.read_text(encoding="utf-8")
|
|
243
|
+
assert "SKCapstone Changelog" in content
|
|
244
|
+
|
|
245
|
+
def test_writes_to_custom_path(self, tmp_path: Path) -> None:
|
|
246
|
+
"""write_changelog respects custom output path."""
|
|
247
|
+
(tmp_path / "coordination" / "tasks").mkdir(parents=True)
|
|
248
|
+
(tmp_path / "coordination" / "agents").mkdir(parents=True)
|
|
249
|
+
|
|
250
|
+
output = tmp_path / "custom" / "changes.md"
|
|
251
|
+
output.parent.mkdir(parents=True)
|
|
252
|
+
result = write_changelog(tmp_path, output=output)
|
|
253
|
+
assert result == output
|
|
254
|
+
assert output.exists()
|