@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.
Files changed (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,164 @@
1
+ """
2
+ Blueprint Registry — discovers, validates, and loads team blueprints.
3
+
4
+ Searches three locations in priority order:
5
+ 1. User blueprints: ~/.skcapstone/blueprints/teams/
6
+ 2. Vault blueprints: ~/.skcapstone/vaults/blueprints/teams/ (synced via skref)
7
+ 3. Built-in blueprints: shipped with the skcapstone package
8
+
9
+ This follows the filesystem-context skill pattern: use the filesystem
10
+ as the source of truth, keep it simple, add sophistication later.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from pathlib import Path
17
+ from typing import Dict, List, Optional
18
+
19
+ import yaml
20
+
21
+ from .schema import BlueprintManifest
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Built-in blueprints ship alongside this module
26
+ _BUILTIN_DIR = Path(__file__).parent / "builtins"
27
+
28
+
29
+ class BlueprintRegistry:
30
+ """Discovers and manages agent team blueprints.
31
+
32
+ Args:
33
+ home: Agent home directory (default ~/.skcapstone).
34
+ """
35
+
36
+ def __init__(self, home: Optional[Path] = None) -> None:
37
+ self._home = (home or Path("~/.skcapstone")).expanduser()
38
+ self._cache: Dict[str, BlueprintManifest] = {}
39
+
40
+ # ------------------------------------------------------------------
41
+ # Discovery paths
42
+ # ------------------------------------------------------------------
43
+
44
+ @property
45
+ def _search_paths(self) -> List[Path]:
46
+ """Return blueprint directories in priority order."""
47
+ return [
48
+ self._home / "blueprints" / "teams",
49
+ self._home / "vaults" / "blueprints" / "teams",
50
+ _BUILTIN_DIR,
51
+ ]
52
+
53
+ # ------------------------------------------------------------------
54
+ # Public API
55
+ # ------------------------------------------------------------------
56
+
57
+ def scan(self) -> Dict[str, BlueprintManifest]:
58
+ """Scan all search paths and return discovered blueprints.
59
+
60
+ Later paths lose to earlier paths when slugs collide (user
61
+ overrides take priority over built-ins).
62
+
63
+ Returns:
64
+ Dict mapping slug to validated BlueprintManifest.
65
+ """
66
+ found: Dict[str, BlueprintManifest] = {}
67
+
68
+ # Reverse so built-ins load first, then get overridden
69
+ for search_dir in reversed(self._search_paths):
70
+ if not search_dir.is_dir():
71
+ continue
72
+ for yaml_file in sorted(search_dir.glob("*.yaml")):
73
+ try:
74
+ bp = self._load_file(yaml_file)
75
+ found[bp.slug] = bp
76
+ except Exception as exc:
77
+ logger.warning("Skipping %s: %s", yaml_file, exc)
78
+
79
+ for yml_file in sorted(search_dir.glob("*.yml")):
80
+ try:
81
+ bp = self._load_file(yml_file)
82
+ found[bp.slug] = bp
83
+ except Exception as exc:
84
+ logger.warning("Skipping %s: %s", yml_file, exc)
85
+
86
+ self._cache = found
87
+ return found
88
+
89
+ def list_blueprints(self) -> List[BlueprintManifest]:
90
+ """Return all discovered blueprints as a sorted list.
91
+
92
+ Returns:
93
+ List of BlueprintManifest objects sorted by name.
94
+ """
95
+ if not self._cache:
96
+ self.scan()
97
+ return sorted(self._cache.values(), key=lambda b: b.name)
98
+
99
+ def get(self, slug: str) -> Optional[BlueprintManifest]:
100
+ """Get a specific blueprint by slug.
101
+
102
+ Args:
103
+ slug: The blueprint identifier.
104
+
105
+ Returns:
106
+ BlueprintManifest or None if not found.
107
+ """
108
+ if not self._cache:
109
+ self.scan()
110
+ return self._cache.get(slug)
111
+
112
+ def save_blueprint(
113
+ self,
114
+ blueprint: BlueprintManifest,
115
+ location: str = "user",
116
+ ) -> Path:
117
+ """Save a blueprint to the user or vault directory.
118
+
119
+ Args:
120
+ blueprint: The validated blueprint to save.
121
+ location: 'user' for ~/.skcapstone/blueprints/teams/,
122
+ 'vault' for ~/.skcapstone/vaults/blueprints/teams/.
123
+
124
+ Returns:
125
+ Path to the written file.
126
+ """
127
+ if location == "vault":
128
+ target_dir = self._home / "vaults" / "blueprints" / "teams"
129
+ else:
130
+ target_dir = self._home / "blueprints" / "teams"
131
+
132
+ target_dir.mkdir(parents=True, exist_ok=True)
133
+ target_file = target_dir / f"{blueprint.slug}.yaml"
134
+
135
+ data = blueprint.model_dump(mode="json", exclude_none=True)
136
+ target_file.write_text(
137
+ yaml.dump(data, default_flow_style=False, sort_keys=False),
138
+ encoding="utf-8",
139
+ )
140
+
141
+ self._cache[blueprint.slug] = blueprint
142
+ return target_file
143
+
144
+ # ------------------------------------------------------------------
145
+ # Internal
146
+ # ------------------------------------------------------------------
147
+
148
+ @staticmethod
149
+ def _load_file(path: Path) -> BlueprintManifest:
150
+ """Parse and validate a single blueprint YAML file.
151
+
152
+ Args:
153
+ path: Path to the YAML file.
154
+
155
+ Returns:
156
+ Validated BlueprintManifest.
157
+
158
+ Raises:
159
+ ValueError: If the YAML is invalid or fails validation.
160
+ """
161
+ raw = yaml.safe_load(path.read_text(encoding="utf-8"))
162
+ if not isinstance(raw, dict):
163
+ raise ValueError(f"Expected a YAML mapping, got {type(raw).__name__}")
164
+ return BlueprintManifest(**raw)
@@ -0,0 +1,229 @@
1
+ """
2
+ Pydantic models for Agent Team Blueprint definitions.
3
+
4
+ A BlueprintManifest defines a complete deployable team of AI agents,
5
+ including their roles, models, resource requirements, networking,
6
+ memory, and coordination settings. Provider-agnostic by design —
7
+ the same blueprint deploys to local processes, Proxmox LXCs,
8
+ Hetzner, AWS, GCP, or any future provider.
9
+
10
+ Architecture references:
11
+ - Context isolation per agent (multi-agent-patterns best practice)
12
+ - Tiered model strategy (fast/code/reason/nuance)
13
+ - Memory-systems: file-system -> vector -> graph progression
14
+ - Hosted-agents: pre-built images, warm pools, snapshot/restore
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from enum import Enum
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ from pydantic import BaseModel, Field, field_validator
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Enums
27
+ # ---------------------------------------------------------------------------
28
+
29
+ class ModelTier(str, Enum):
30
+ """Model selection tiers aligned with agent framework best practices."""
31
+
32
+ FAST = "fast"
33
+ CODE = "code"
34
+ REASON = "reason"
35
+ NUANCE = "nuance"
36
+ LOCAL = "local"
37
+ CUSTOM = "custom"
38
+
39
+
40
+ class ProviderType(str, Enum):
41
+ """Infrastructure providers for agent deployment."""
42
+
43
+ LOCAL = "local"
44
+ PROXMOX = "proxmox"
45
+ HETZNER = "hetzner"
46
+ AWS = "aws"
47
+ GCP = "gcp"
48
+ DOCKER = "docker"
49
+
50
+
51
+ class VMType(str, Enum):
52
+ """VM/container type for cloud/proxmox deployments."""
53
+
54
+ LXC = "lxc"
55
+ VM = "vm"
56
+ CONTAINER = "container"
57
+ PROCESS = "process"
58
+
59
+
60
+ class AgentRole(str, Enum):
61
+ """Standard agent roles within a team."""
62
+
63
+ MANAGER = "manager"
64
+ WORKER = "worker"
65
+ RESEARCHER = "researcher"
66
+ CODER = "coder"
67
+ REVIEWER = "reviewer"
68
+ DOCUMENTARIAN = "documentarian"
69
+ SECURITY = "security"
70
+ OPS = "ops"
71
+ CUSTOM = "custom"
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Sub-models
76
+ # ---------------------------------------------------------------------------
77
+
78
+ class ResourceSpec(BaseModel):
79
+ """Compute resources allocated to an agent."""
80
+
81
+ memory: str = Field(default="2g", description="RAM allocation (e.g. '2g', '512m')")
82
+ cores: int = Field(default=1, ge=1, le=128, description="CPU cores")
83
+ disk: str = Field(default="10g", description="Disk allocation")
84
+ gpu: Optional[str] = Field(default=None, description="GPU type if needed")
85
+
86
+
87
+ class AgentSpec(BaseModel):
88
+ """Specification for a single agent within a team blueprint.
89
+
90
+ Each agent gets its own context window (context isolation principle),
91
+ its own model tier, its own soul blueprint, and a defined skill set.
92
+ """
93
+
94
+ role: AgentRole = Field(default=AgentRole.WORKER, description="Functional role")
95
+ model: ModelTier = Field(default=ModelTier.FAST, description="Model tier")
96
+ model_name: Optional[str] = Field(
97
+ default=None,
98
+ description="Specific model override (e.g. 'kimi-k2.5', 'minimax-m2.1')",
99
+ )
100
+ vm_type: VMType = Field(default=VMType.PROCESS, description="Execution environment")
101
+ resources: ResourceSpec = Field(default_factory=ResourceSpec)
102
+ soul_blueprint: Optional[str] = Field(
103
+ default=None,
104
+ description="Path to soul blueprint YAML (e.g. 'souls/sentinel.yaml')",
105
+ )
106
+ skills: List[str] = Field(default_factory=list, description="Skill names")
107
+ depends_on: List[str] = Field(
108
+ default_factory=list,
109
+ description="Other agents this one depends on being ready first",
110
+ )
111
+ env: Dict[str, str] = Field(
112
+ default_factory=dict,
113
+ description="Extra environment variables for this agent",
114
+ )
115
+ description: Optional[str] = Field(default=None, description="What this agent does")
116
+ count: int = Field(
117
+ default=1, ge=1, le=50,
118
+ description="Number of instances of this agent to spawn",
119
+ )
120
+
121
+
122
+ class NetworkConfig(BaseModel):
123
+ """Networking configuration for the team."""
124
+
125
+ mesh_vpn: str = Field(default="tailscale", description="VPN mesh provider")
126
+ discovery: str = Field(
127
+ default="skref_registry",
128
+ description="How agents discover each other",
129
+ )
130
+
131
+
132
+ class StorageConfig(BaseModel):
133
+ """Storage and memory configuration for the team."""
134
+
135
+ skref_vault: Optional[str] = Field(
136
+ default=None,
137
+ description="Shared vault name for the team",
138
+ )
139
+ # Supported backends: "filesystem" (default, no deps) and "skvector" (via skmemory).
140
+ # "mem0" and "zep" are not yet implemented — no adapter classes exist.
141
+ memory_backend: str = Field(
142
+ default="filesystem",
143
+ description="Memory backend: filesystem, skvector",
144
+ )
145
+ memory_sync: bool = Field(
146
+ default=True,
147
+ description="Auto-sync agent memories to shared storage",
148
+ )
149
+
150
+
151
+ class CoordinationConfig(BaseModel):
152
+ """How the team coordinates internally."""
153
+
154
+ queen: Optional[str] = Field(
155
+ default=None,
156
+ description="Managing agent (e.g. 'lumina')",
157
+ )
158
+ pattern: str = Field(
159
+ default="supervisor",
160
+ description="Architecture: supervisor, peer-to-peer, hierarchical",
161
+ )
162
+ heartbeat: str = Field(default="30m", description="Health check interval")
163
+ escalation: Optional[str] = Field(
164
+ default=None,
165
+ description="Who to escalate critical issues to",
166
+ )
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Top-level Blueprint
171
+ # ---------------------------------------------------------------------------
172
+
173
+ class BlueprintManifest(BaseModel):
174
+ """Complete definition of a deployable agent team.
175
+
176
+ This is the YAML schema that users select from the blueprint store.
177
+ It contains everything needed to spin up a coordinated team of AI
178
+ agents on any supported infrastructure provider.
179
+ """
180
+
181
+ name: str = Field(description="Human-readable team name")
182
+ slug: str = Field(description="URL/filesystem-safe identifier")
183
+ version: str = Field(default="1.0.0")
184
+ description: str = Field(description="What this team does")
185
+ icon: str = Field(default="🤖", description="Display emoji")
186
+ author: str = Field(default="smilinTux")
187
+
188
+ agents: Dict[str, AgentSpec] = Field(
189
+ description="Named agents in this team (key = agent name)",
190
+ )
191
+
192
+ default_provider: ProviderType = Field(
193
+ default=ProviderType.LOCAL,
194
+ description="Default infrastructure provider",
195
+ )
196
+ network: NetworkConfig = Field(default_factory=NetworkConfig)
197
+ storage: StorageConfig = Field(default_factory=StorageConfig)
198
+ coordination: CoordinationConfig = Field(default_factory=CoordinationConfig)
199
+
200
+ tags: List[str] = Field(default_factory=list)
201
+ estimated_cost: Optional[str] = Field(
202
+ default=None,
203
+ description="Estimated monthly cost (e.g. '$12/mo compute')",
204
+ )
205
+
206
+ @field_validator("slug")
207
+ @classmethod
208
+ def slug_must_be_clean(cls, v: str) -> str:
209
+ """Ensure slug is filesystem/URL safe."""
210
+ import re
211
+ if not re.match(r"^[a-z0-9][a-z0-9\-]*[a-z0-9]$", v):
212
+ raise ValueError(
213
+ f"slug must be lowercase alphanumeric with hyphens: got '{v}'"
214
+ )
215
+ return v
216
+
217
+ @property
218
+ def agent_count(self) -> int:
219
+ """Total number of agent instances in this blueprint."""
220
+ return sum(spec.count for spec in self.agents.values())
221
+
222
+ @property
223
+ def model_summary(self) -> str:
224
+ """Comma-separated list of model tiers used."""
225
+ tiers = sorted({
226
+ spec.model_name or spec.model.value
227
+ for spec in self.agents.values()
228
+ })
229
+ return ", ".join(tiers)
@@ -0,0 +1,180 @@
1
+ """
2
+ Changelog Generator — auto-document the Kingdom's progress.
3
+
4
+ Reads the coordination board's completed tasks and generates
5
+ a structured CHANGELOG.md grouped by date and category. Every
6
+ completed task becomes a line item in the project's history.
7
+
8
+ The board IS the changelog. No separate tracking needed.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import re
15
+ from collections import defaultdict
16
+ from datetime import datetime, timezone
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ from .coordination import Board, TaskStatus
21
+
22
+
23
+ TAG_CATEGORIES = {
24
+ "feature": ["skcapstone", "skchat", "skcomm", "skmemory", "capauth", "cloud9", "skworld"],
25
+ "security": ["security", "encryption", "capauth", "integrity", "quantum-resistant", "pgp"],
26
+ "infrastructure": ["ci", "docker", "systemd", "daemon", "syncthing", "pypi", "packaging"],
27
+ "documentation": ["documentation", "docs", "readme", "quickstart", "api"],
28
+ "testing": ["testing", "integration", "e2e", "pytest"],
29
+ "ux": ["cli", "repl", "dashboard", "interactive", "rich"],
30
+ "p2p": ["p2p", "nostr", "mesh", "discovery", "transport"],
31
+ "emotional": ["cloud9", "soul", "trust", "feb", "emotional", "seeds", "anchor"],
32
+ }
33
+
34
+
35
+ def _categorize(tags: list[str]) -> str:
36
+ """Determine the changelog category from task tags.
37
+
38
+ Args:
39
+ tags: Task tags list.
40
+
41
+ Returns:
42
+ Category string (e.g., 'feature', 'security', 'infrastructure').
43
+ """
44
+ tag_set = set(t.lower() for t in tags)
45
+ best_category = "other"
46
+ best_score = 0
47
+
48
+ for category, keywords in TAG_CATEGORIES.items():
49
+ score = len(tag_set.intersection(keywords))
50
+ if score > best_score:
51
+ best_score = score
52
+ best_category = category
53
+
54
+ return best_category
55
+
56
+
57
+ def _parse_date(iso_str: str) -> str:
58
+ """Extract YYYY-MM-DD from an ISO timestamp.
59
+
60
+ Args:
61
+ iso_str: ISO 8601 datetime string.
62
+
63
+ Returns:
64
+ Date string like '2026-02-24'.
65
+ """
66
+ try:
67
+ match = re.match(r"(\d{4}-\d{2}-\d{2})", iso_str)
68
+ if match:
69
+ return match.group(1)
70
+ except (TypeError, ValueError):
71
+ pass
72
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d")
73
+
74
+
75
+ def generate_changelog(
76
+ home: Path,
77
+ title: str = "SKCapstone Changelog",
78
+ include_agents: bool = True,
79
+ ) -> str:
80
+ """Generate a CHANGELOG.md from completed board tasks.
81
+
82
+ Groups tasks by date and category, with agent attribution.
83
+
84
+ Args:
85
+ home: Path to ~/.skcapstone.
86
+ title: Changelog title.
87
+ include_agents: Whether to show who completed each task.
88
+
89
+ Returns:
90
+ Markdown string for the changelog.
91
+ """
92
+ board = Board(home)
93
+ views = board.get_task_views()
94
+ agents = board.load_agents()
95
+
96
+ completed = [v for v in views if v.status == TaskStatus.DONE]
97
+
98
+ completed_by_agent: dict[str, list[str]] = defaultdict(list)
99
+ for a in agents:
100
+ for tid in a.completed_tasks:
101
+ completed_by_agent[tid].append(a.agent)
102
+
103
+ by_date: dict[str, list[dict]] = defaultdict(list)
104
+ for v in completed:
105
+ t = v.task
106
+ date = _parse_date(t.created_at)
107
+ category = _categorize(t.tags)
108
+ who = completed_by_agent.get(t.id, [v.claimed_by] if v.claimed_by else ["unknown"])
109
+
110
+ by_date[date].append({
111
+ "id": t.id,
112
+ "title": t.title,
113
+ "category": category,
114
+ "tags": t.tags,
115
+ "agents": who,
116
+ "priority": t.priority.value,
117
+ })
118
+
119
+ lines = [
120
+ f"# {title}",
121
+ "",
122
+ f"*Auto-generated from the coordination board — {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}*",
123
+ "",
124
+ f"**Total completed: {len(completed)}** across {len(set(a.agent for a in agents))} agents",
125
+ "",
126
+ ]
127
+
128
+ category_icons = {
129
+ "feature": "NEW",
130
+ "security": "SEC",
131
+ "infrastructure": "OPS",
132
+ "documentation": "DOC",
133
+ "testing": "TST",
134
+ "ux": "UX",
135
+ "p2p": "P2P",
136
+ "emotional": "SOUL",
137
+ "other": "---",
138
+ }
139
+
140
+ for date in sorted(by_date.keys(), reverse=True):
141
+ tasks = by_date[date]
142
+ lines.append(f"## {date}")
143
+ lines.append("")
144
+
145
+ by_cat: dict[str, list[dict]] = defaultdict(list)
146
+ for task in tasks:
147
+ by_cat[task["category"]].append(task)
148
+
149
+ for cat in ["feature", "security", "p2p", "emotional", "ux", "infrastructure", "testing", "documentation", "other"]:
150
+ if cat not in by_cat:
151
+ continue
152
+ icon = category_icons.get(cat, "---")
153
+ lines.append(f"### [{icon}] {cat.title()}")
154
+ lines.append("")
155
+ for task in by_cat[cat]:
156
+ agent_str = f" (@{', @'.join(task['agents'])})" if include_agents and task["agents"] else ""
157
+ lines.append(f"- **{task['title']}**{agent_str}")
158
+ lines.append("")
159
+
160
+ lines.append("---")
161
+ lines.append("")
162
+ lines.append("*Built by the Pengu Nation — staycuriousANDkeepsmilin*")
163
+
164
+ return "\n".join(lines)
165
+
166
+
167
+ def write_changelog(home: Path, output: Optional[Path] = None) -> Path:
168
+ """Generate and write CHANGELOG.md.
169
+
170
+ Args:
171
+ home: Path to ~/.skcapstone.
172
+ output: Output file path. Defaults to project root CHANGELOG.md.
173
+
174
+ Returns:
175
+ Path to the written file.
176
+ """
177
+ content = generate_changelog(home)
178
+ out_path = output or Path.cwd() / "CHANGELOG.md"
179
+ out_path.write_text(content, encoding="utf-8")
180
+ return out_path