swarph-cli 0.61.0__tar.gz → 0.63.0__tar.gz

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 (411) hide show
  1. {swarph_cli-0.61.0/src/swarph_cli.egg-info → swarph_cli-0.63.0}/PKG-INFO +2 -1
  2. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/README.md +1 -0
  3. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/pyproject.toml +2 -1
  4. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/__init__.py +1 -1
  5. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/cell.py +1 -1
  6. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/board.py +59 -12
  7. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codegraph.py +63 -8
  8. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/guide.py +55 -2
  9. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/init.py +1 -0
  10. swarph_cli-0.63.0/src/swarph_cli/commands/install_opencode_plugin.py +173 -0
  11. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/mcp_server.py +9 -2
  12. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory_emit_hook.py +29 -7
  13. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/mesh.py +24 -7
  14. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/monitor.py +4 -2
  15. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/onboard.py +8 -0
  16. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/spawn.py +344 -1
  17. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/wake_hook_output.py +1 -1
  18. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/watchdog.py +33 -7
  19. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/enrich.py +28 -6
  20. swarph_cli-0.63.0/src/swarph_cli/dreaming/slm.py +120 -0
  21. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/guide/GUIDE.md +151 -0
  22. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/main.py +19 -0
  23. swarph_cli-0.63.0/src/swarph_cli/payloads/opencode/plugin.js +82 -0
  24. swarph_cli-0.63.0/src/swarph_cli/source_text.py +129 -0
  25. {swarph_cli-0.61.0 → swarph_cli-0.63.0/src/swarph_cli.egg-info}/PKG-INFO +2 -1
  26. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/SOURCES.txt +14 -0
  27. swarph_cli-0.63.0/tests/test_126_pending_from_complete.py +116 -0
  28. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_397_gh_identity_router.py +4 -1
  29. swarph_cli-0.63.0/tests/test_547_verbs_from_registry.py +59 -0
  30. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_552_codegraph_gateway.py +15 -1
  31. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_556_relay_freshness.py +15 -1
  32. swarph_cli-0.63.0/tests/test_656_owned_slm.py +184 -0
  33. swarph_cli-0.63.0/tests/test_866_doctrine_guide.py +71 -0
  34. swarph_cli-0.63.0/tests/test_872_identity_refuses_not_warns.py +129 -0
  35. swarph_cli-0.63.0/tests/test_874_source_text.py +116 -0
  36. swarph_cli-0.63.0/tests/test_888_opencode_data_dir_can_fail.py +71 -0
  37. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_board_card_thread.py +58 -2
  38. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_codegraph_mcp.py +15 -1
  39. swarph_cli-0.63.0/tests/test_memory_emit_snippet.py +43 -0
  40. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_monitor_state_split.py +2 -1
  41. swarph_cli-0.63.0/tests/test_opencode_membrane.py +540 -0
  42. swarph_cli-0.63.0/tests/test_opencode_plugin.py +144 -0
  43. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/LICENSE +0 -0
  44. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/setup.cfg +0 -0
  45. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/__main__.py +0 -0
  46. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/__init__.py +0 -0
  47. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/backends.py +0 -0
  48. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/pack.py +0 -0
  49. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/prices.py +0 -0
  50. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/quality.py +0 -0
  51. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
  52. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/runner.py +0 -0
  53. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/validate.py +0 -0
  54. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/caller.py +0 -0
  55. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/__init__.py +0 -0
  56. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/harden.py +0 -0
  57. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/lineage.py +0 -0
  58. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/liveness.py +0 -0
  59. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/manifest.py +0 -0
  60. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/paths.py +0 -0
  61. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/verify.py +0 -0
  62. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/chain_token.py +0 -0
  63. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/__init__.py +0 -0
  64. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_content.py +0 -0
  65. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_display.py +0 -0
  66. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  67. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/add.py +0 -0
  68. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/bench.py +0 -0
  69. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
  70. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/brain.py +0 -0
  71. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  72. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell.py +0 -0
  73. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell_probe.py +0 -0
  74. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  75. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/channel.py +0 -0
  76. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/chat.py +0 -0
  77. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  78. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  79. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codex_waker.py +0 -0
  80. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/compress.py +0 -0
  81. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/daemon.py +0 -0
  82. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/dreaming.py +0 -0
  83. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/event.py +0 -0
  84. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/gateway.py +0 -0
  85. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/gh_route.py +0 -0
  86. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/group.py +0 -0
  87. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/highlight.py +0 -0
  88. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/hook_output.py +0 -0
  89. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/hooks.py +0 -0
  90. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/import_session.py +0 -0
  91. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  92. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_hook.py +0 -0
  93. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  94. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  95. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  96. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/lane.py +0 -0
  97. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory.py +0 -0
  98. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  99. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/okf_links.py +0 -0
  100. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  101. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  102. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  103. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/ratify.py +0 -0
  104. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/schedule.py +0 -0
  105. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/security.py +0 -0
  106. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/service.py +0 -0
  107. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/timeline.py +0 -0
  108. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/version.py +0 -0
  109. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/__init__.py +0 -0
  110. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/levers.py +0 -0
  111. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/marker.py +0 -0
  112. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/verify.py +0 -0
  113. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/console_safe.py +0 -0
  114. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/delivery_queue.py +0 -0
  115. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/__init__.py +0 -0
  116. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/candidates.py +0 -0
  117. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/clone.py +0 -0
  118. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/index_check.py +0 -0
  119. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/organize.py +0 -0
  120. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/probes.py +0 -0
  121. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/report.py +0 -0
  122. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/run.py +0 -0
  123. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
  124. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/verify.py +0 -0
  125. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/__init__.py +0 -0
  126. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  127. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  128. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/schema.sql +0 -0
  129. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/server.py +0 -0
  130. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/services_control.py +0 -0
  131. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway_default.py +0 -0
  132. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/gh_identity.py +0 -0
  133. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/multiplexer.py +0 -0
  134. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/orphan_daemons.py +0 -0
  135. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/pane_probe.py +0 -0
  136. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/parsers/__init__.py +0 -0
  137. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/parsers/claude.py +0 -0
  138. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  139. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  140. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  141. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  142. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  143. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  144. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  145. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  146. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  147. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  148. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  149. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_executor.py +0 -0
  150. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_executor_windows.py +0 -0
  151. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_reconciliation.py +0 -0
  152. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
  153. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_host.py +0 -0
  154. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_payload.py +0 -0
  155. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_reply.py +0 -0
  156. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_staging.py +0 -0
  157. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/query_filters.py +0 -0
  158. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/__init__.py +0 -0
  159. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  160. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  161. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  162. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  163. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/service/__init__.py +0 -0
  164. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/service/app.py +0 -0
  165. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/service/providers.py +0 -0
  166. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/session_bridge.py +0 -0
  167. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  168. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  169. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/stall_alert.py +0 -0
  170. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
  171. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  172. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  173. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  174. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  175. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  176. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  177. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  178. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  179. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  180. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  181. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  182. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  183. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  184. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/timeline_paths.py +0 -0
  185. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli/tokens.py +0 -0
  186. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  187. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  188. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  189. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  190. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_124_register_merge.py +0 -0
  191. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  192. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_139_tool_probe.py +0 -0
  193. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_183_card_anchor.py +0 -0
  194. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_201_win32_bash_refusal.py +0 -0
  195. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_243_onboard_per_peer_token.py +0 -0
  196. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_307_task4_ask_reply.py +0 -0
  197. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  198. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_319_refusal_exit_codes.py +0 -0
  199. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_323_mistral_backend.py +0 -0
  200. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_356_query_filters.py +0 -0
  201. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_397_router_preflight.py +0 -0
  202. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_467b_onboard_operator_credential.py +0 -0
  203. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_523_guide.py +0 -0
  204. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_524_conftest_hermeticity.py +0 -0
  205. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_532_accept_flag.py +0 -0
  206. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_535_version_capability.py +0 -0
  207. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_546_default_gateway.py +0 -0
  208. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_548_gateway_remainder.py +0 -0
  209. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_555_relay_row_shape.py +0 -0
  210. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_564_bootstrap_first_peer.py +0 -0
  211. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  212. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_579_register_bootstrap_message.py +0 -0
  213. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_591_cli_step_graph.py +0 -0
  214. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_644_monitor_supervision.py +0 -0
  215. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_648_install_unit.py +0 -0
  216. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_654_reexec.py +0 -0
  217. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_684_dreaming_verb.py +0 -0
  218. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_689_pkg_version_order.py +0 -0
  219. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_716_timeline_one_path.py +0 -0
  220. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  221. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_725_stdio_reconfigure.py +0 -0
  222. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  223. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_740_edit_project.py +0 -0
  224. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_762_cards_edit_priority.py +0 -0
  225. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_801_confirm_flow_cli.py +0 -0
  226. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_802_stage_history.py +0 -0
  227. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_825_codegraph_hook_noise.py +0 -0
  228. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_829_counterfactual_column.py +0 -0
  229. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_830_hook_binding_version.py +0 -0
  230. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_84_cli_labels.py +0 -0
  231. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_add_security_gate.py +0 -0
  232. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_add.py +0 -0
  233. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_hash.py +0 -0
  234. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_lib.py +0 -0
  235. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_mcp.py +0 -0
  236. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_skill.py +0 -0
  237. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_tool.py +0 -0
  238. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_artifact_uri.py +0 -0
  239. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_backends.py +0 -0
  240. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_command.py +0 -0
  241. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_pack.py +0 -0
  242. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_prices.py +0 -0
  243. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_quality.py +0 -0
  244. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_runner.py +0 -0
  245. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_bench_validate.py +0 -0
  246. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_board_command.py +0 -0
  247. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_board_merge_check.py +0 -0
  248. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_board_obligation_close.py +0 -0
  249. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_brain_ask_command.py +0 -0
  250. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
  251. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
  252. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_brain_command.py +0 -0
  253. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_caller_meta_guard.py +0 -0
  254. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_capture_lineage.py +0 -0
  255. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_capture_liveness.py +0 -0
  256. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_capture_manifest.py +0 -0
  257. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_capture_paths.py +0 -0
  258. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_capture_security.py +0 -0
  259. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cell_command_dispatch.py +0 -0
  260. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cell_harden.py +0 -0
  261. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cell_loader.py +0 -0
  262. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cell_selfcheck.py +0 -0
  263. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cell_verify.py +0 -0
  264. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_chain_token.py +0 -0
  265. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_channel_command.py +0 -0
  266. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  267. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_channel_post.py +0 -0
  268. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_channel_wake_policy_filter.py +0 -0
  269. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_chat_command.py +0 -0
  270. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_claude_parser.py +0 -0
  271. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_claude_tmux_template.py +0 -0
  272. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_codegraph_caller_scope.py +0 -0
  273. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_codegraph_command.py +0 -0
  274. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_codegraph_hook.py +0 -0
  275. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_codex_waker.py +0 -0
  276. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_compress_caller_convention.py +0 -0
  277. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_compress_command.py +0 -0
  278. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_compress_levers.py +0 -0
  279. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_compress_marker.py +0 -0
  280. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_compress_verify.py +0 -0
  281. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_console_encoding_e2e.py +0 -0
  282. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_content_composition.py +0 -0
  283. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_cursor_membrane.py +0 -0
  284. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_daemon_command.py +0 -0
  285. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_daemon_console_encoding.py +0 -0
  286. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_daemon_delivery.py +0 -0
  287. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_delivery_queue.py +0 -0
  288. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_display_sanitize.py +0 -0
  289. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  290. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_event_emit.py +0 -0
  291. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_feature_to_uri.py +0 -0
  292. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_gateway_brain.py +0 -0
  293. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_gateway_client.py +0 -0
  294. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_gateway_command.py +0 -0
  295. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_gateway_message_idempotency.py +0 -0
  296. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  297. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_group_command.py +0 -0
  298. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_guide_commands_parse.py +0 -0
  299. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_guide_selfcheck.py +0 -0
  300. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_heartbeat_check.py +0 -0
  301. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_highlight_command.py +0 -0
  302. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hook_output.py +0 -0
  303. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_activity_marker.py +0 -0
  304. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_add.py +0 -0
  305. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_bundle.py +0 -0
  306. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_init.py +0 -0
  307. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
  308. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_lifecycle.py +0 -0
  309. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_merge.py +0 -0
  310. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_verify.py +0 -0
  311. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  312. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_hooks_win32_command_path.py +0 -0
  313. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_import_command.py +0 -0
  314. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_inbox_identity_provenance.py +0 -0
  315. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_init_command.py +0 -0
  316. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_init_provider_menu.py +0 -0
  317. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_install_codex_hooks.py +0 -0
  318. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_install_hook.py +0 -0
  319. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_install_multiplexer_command.py +0 -0
  320. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_install_postcompact_hook.py +0 -0
  321. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_install_wake_hook.py +0 -0
  322. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_lane_command.py +0 -0
  323. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_main.py +0 -0
  324. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_main_unknown_verb_guard.py +0 -0
  325. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mcp_missing_hint.py +0 -0
  326. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mcp_server.py +0 -0
  327. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_memory_command.py +0 -0
  328. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_memory_gateway.py +0 -0
  329. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_memory_sync.py +0 -0
  330. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_memory_sync_membrane.py +0 -0
  331. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_command.py +0 -0
  332. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  333. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  334. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_send_unknown_peer.py +0 -0
  335. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar.py +0 -0
  336. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  337. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  338. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_meta_edge_identity.py +0 -0
  339. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_monitor_channel_poll.py +0 -0
  340. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_monitor_cli.py +0 -0
  341. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_monitor_daemon_collision.py +0 -0
  342. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_monitor_self_name_verification.py +0 -0
  343. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_multiplexer.py +0 -0
  344. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_muse_membrane.py +0 -0
  345. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_muse_own_cli.py +0 -0
  346. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_obligation_close_is_surfaced.py +0 -0
  347. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_okf_links.py +0 -0
  348. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_onboard_command.py +0 -0
  349. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_orphan_daemons.py +0 -0
  350. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_packaged_artifacts_exist.py +0 -0
  351. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_packaged_skills.py +0 -0
  352. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_executor.py +0 -0
  353. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_executor_windows.py +0 -0
  354. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_reply_drain_command.py +0 -0
  355. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
  356. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_reply_outbox.py +0 -0
  357. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_reply_transport.py +0 -0
  358. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_service_e2e.py +0 -0
  359. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_service_host.py +0 -0
  360. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_service_payload.py +0 -0
  361. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_peer_service_reply.py +0 -0
  362. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_postcompact_recall.py +0 -0
  363. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_process_liveness.py +0 -0
  364. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_protocol_handler.py +0 -0
  365. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_providers_isolation.py +0 -0
  366. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_ratify_command.py +0 -0
  367. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
  368. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_repl_caller_convention.py +0 -0
  369. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_schedule_command.py +0 -0
  370. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_schedule_create_contract.py +0 -0
  371. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_security.py +0 -0
  372. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_service_app.py +0 -0
  373. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_service_command.py +0 -0
  374. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_service_providers.py +0 -0
  375. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_session_bridge_inject.py +0 -0
  376. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_session_bridge_probe.py +0 -0
  377. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_session_bridge_resolve.py +0 -0
  378. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_session_path_role_gate.py +0 -0
  379. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_shipped_units_match_the_cli.py +0 -0
  380. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_smoke_chat.py +0 -0
  381. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_smoke_one_shot.py +0 -0
  382. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_smoke_phase_5_5.py +0 -0
  383. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_base_hoist.py +0 -0
  384. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_command.py +0 -0
  385. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_identity_stamp.py +0 -0
  386. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_live_pin.py +0 -0
  387. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  388. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_tmux_session.py +0 -0
  389. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_spawn_windows_relaunch.py +0 -0
  390. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_stall_alert.py +0 -0
  391. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_subprocess_decode_encoding.py +0 -0
  392. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_timeline_command.py +0 -0
  393. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
  394. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
  395. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_stale.py +0 -0
  396. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_submit_verify.py +0 -0
  397. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_token_file_one_parser.py +0 -0
  398. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_token_precedence_explicit_first.py +0 -0
  399. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_unified_token_resolver.py +0 -0
  400. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_version_command.py +0 -0
  401. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_version_consistency.py +0 -0
  402. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_vibe_membrane.py +0 -0
  403. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog.py +0 -0
  404. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake.py +0 -0
  405. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  406. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  407. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_emit_health.py +0 -0
  408. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_install_flags.py +0 -0
  409. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_model_rung.py +0 -0
  410. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_watchdog_stale_peers.py +0 -0
  411. {swarph_cli-0.61.0 → swarph_cli-0.63.0}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.61.0
3
+ Version: 0.63.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -148,6 +148,7 @@ swarph guide # the whole guide
148
148
  swarph guide channels # one topic
149
149
  swarph guide --list # every topic, with the commands it teaches
150
150
  swarph guide --search subscribe # find a topic by INTENT, not by its name
151
+ swarph guide doctrine # the standard of evidence a cell is held to (v0.63.0)
151
152
  ```
152
153
 
153
154
  Three ways in, because a new cell does not know which word it has:
@@ -100,6 +100,7 @@ swarph guide # the whole guide
100
100
  swarph guide channels # one topic
101
101
  swarph guide --list # every topic, with the commands it teaches
102
102
  swarph guide --search subscribe # find a topic by INTENT, not by its name
103
+ swarph guide doctrine # the standard of evidence a cell is held to (v0.63.0)
103
104
  ```
104
105
 
105
106
  Three ways in, because a new cell does not know which word it has:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.61.0"
7
+ version = "0.63.0"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -146,6 +146,7 @@ swarph_cli = ["systemd/*.service", "systemd/*.timer", "systemd/*.default",
146
146
  "systemd/*.path",
147
147
  "scripts/*.sh", "scripts/*.ps1", "guide/*.md",
148
148
  "payloads/postcompact/*.sh", "payloads/postcompact/*.cmd",
149
+ "payloads/opencode/*.js",
149
150
  "skills/*/SKILL.md", "skills/*/LICENSE"]
150
151
  # v0.15: ship the bundled mesh-gateway schema so `swarph gateway serve`
151
152
  # can apply it at startup via MESH_SCHEMA_PATH (defaults next to server.py).
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.61.0"
19
+ __version__ = "0.63.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -66,7 +66,7 @@ _validate_uuid = validate_uuid_str # historical helper name
66
66
  # ONE swarph-cli release, and entry into swarph_shared.VALID_PROVIDERS only in a
67
67
  # later one. spawn.py's guard is `VALID_PROVIDERS ⊆ MEMBRANES`, so this direction
68
68
  # is inert while the reverse raises at import for every fresh install (#247).
69
- CLI_ENABLED_PROVIDERS = VALID_PROVIDERS | {"muse", "cursor"}
69
+ CLI_ENABLED_PROVIDERS = VALID_PROVIDERS | {"muse", "cursor", "opencode"}
70
70
 
71
71
 
72
72
  def _config_root() -> Path:
@@ -390,8 +390,38 @@ def _thread_url(gateway: str, card_id: int, *, limit: Optional[int] = None) -> s
390
390
  return f"{base}?limit={limit}" if limit else base
391
391
 
392
392
 
393
- def _thread_recipient(card: dict, explicit_to: Optional[str]) -> str:
394
- """Who a card-thread post is addressed to. `--to` wins, else the assignee.
393
+ def _project_owner_orchestrator(gw: str, token: str, project_id) -> Optional[str]:
394
+ """Resolve ``projects.owner_orchestrator`` for a card's project_id.
395
+
396
+ >>> THE CARD PAYLOAD DOES NOT CARRY THE OWNER (#864). <<< Measured
397
+ 2026-09-17: GET /board/cards/{id} has assignee/project_id and no owner
398
+ field; GET /board/projects rows carry ``owner_orchestrator`` (NOT NULL on
399
+ the server). A second call is required — inventing a sentinel peer is the
400
+ #259 failure mode this helper exists to avoid.
401
+ """
402
+ if project_id is None:
403
+ return None
404
+ st, data = _http_get_json(f"{gw.rstrip('/')}/board/projects", token)
405
+ if st != 200:
406
+ return None
407
+ rows = data if isinstance(data, list) else (data or {}).get("projects", [])
408
+ for row in rows or []:
409
+ if row.get("id") == project_id or str(row.get("id")) == str(project_id):
410
+ owner = (row.get("owner_orchestrator") or "").strip()
411
+ return owner or None
412
+ return None
413
+
414
+
415
+ def _thread_recipient(
416
+ card: dict,
417
+ explicit_to: Optional[str],
418
+ *,
419
+ owner_orchestrator: Optional[str] = None,
420
+ ) -> "tuple[str, str]":
421
+ """Who a card-thread post is addressed to, and why.
422
+
423
+ Order: ``--to`` > card assignee > project ``owner_orchestrator`` (#864).
424
+ Returns ``(to_node, reason)`` so the confirmation line can name the choice.
395
425
 
396
426
  >>> RAISES RATHER THAN INVENTING A SENTINEL. <<< POST /messages requires
397
427
  exactly one of {to_node, channel}, so a card post needs a real recipient. The
@@ -401,18 +431,21 @@ def _thread_recipient(card: dict, explicit_to: Optional[str]) -> str:
401
431
  A placeholder here would manufacture that defect once per card post.
402
432
  """
403
433
  if explicit_to:
404
- return explicit_to
434
+ return explicit_to, "--to"
405
435
  assignee = card.get("assignee")
406
436
  if assignee:
407
- return assignee
437
+ return assignee, "card assignee"
438
+ if owner_orchestrator:
439
+ return owner_orchestrator, "project owner_orchestrator"
408
440
  raise RuntimeError(
409
- f"card #{card.get('id')} has no assignee, so there is no default recipient "
410
- f"for a thread post — pass --to <peer>. (A card post is still a DM on the "
411
- f"wire; it needs someone to be addressed to.)"
441
+ f"card #{card.get('id')} has no assignee and project owner_orchestrator "
442
+ f"is unresolvable, so there is no default recipient for a thread post — "
443
+ f"pass --to <peer>. (A card post is still a DM on the wire; it needs "
444
+ f"someone to be addressed to.)"
412
445
  )
413
446
 
414
447
 
415
- def _say_line(resp: dict, card_id, to_node: str) -> str:
448
+ def _say_line(resp: dict, card_id, to_node: str, *, reason: Optional[str] = None) -> str:
416
449
  """The post confirmation — AND any obligation the post just DISCHARGED.
417
450
 
418
451
  >>> THE GATEWAY ALREADY SAID SO AND THIS CLI THREW IT AWAY. <<< POST
@@ -435,7 +468,11 @@ def _say_line(resp: dict, card_id, to_node: str) -> str:
435
468
  POLICY (that is #562's own question) -- it is the fix for the holder not
436
469
  being told, which is what made six hours of divergence possible.
437
470
  """
438
- line = f"posted id={resp.get('id')} onto card #{card_id} (to {to_node})"
471
+ if reason:
472
+ line = (f"posted id={resp.get('id')} onto card #{card_id} "
473
+ f"(to {to_node}; {reason})")
474
+ else:
475
+ line = f"posted id={resp.get('id')} onto card #{card_id} (to {to_node})"
439
476
  closed = resp.get("closed_obligations") or []
440
477
  if closed:
441
478
  ids = ", ".join(f"#{i}" for i in closed)
@@ -855,7 +892,8 @@ def _build_parser() -> argparse.ArgumentParser:
855
892
  add_content_args(cy)
856
893
  cy.add_argument("--kind", default="fyi", help="status|question|answer|unblock|fyi")
857
894
  cy.add_argument("--to", dest="to_node", default=None,
858
- help="recipient peer (default: the card's assignee)")
895
+ help="recipient peer (default: card assignee, else project "
896
+ "owner_orchestrator; refused if neither resolves)")
859
897
  _add_common(cy)
860
898
 
861
899
  ck = cards.add_parser(
@@ -1278,8 +1316,15 @@ def run_board(argv: list[str]) -> int:
1278
1316
  f"(it predates #181a). Run scripts/migrate_card_threads.py on "
1279
1317
  f"the gateway host.", file=sys.stderr)
1280
1318
  return 1
1319
+ # #864: card GET has no owner field — only fetch projects when the
1320
+ # cheaper defaults (explicit --to, card assignee) are absent.
1321
+ owner = None
1322
+ if not args.to_node and not card.get("assignee"):
1323
+ owner = _project_owner_orchestrator(gw, token, card.get("project_id"))
1281
1324
  try:
1282
- to_node = _thread_recipient(card, args.to_node)
1325
+ to_node, reason = _thread_recipient(
1326
+ card, args.to_node, owner_orchestrator=owner,
1327
+ )
1283
1328
  except RuntimeError as exc:
1284
1329
  print(f"swarph board cards say: {exc}", file=sys.stderr)
1285
1330
  return 1
@@ -1293,7 +1338,9 @@ def run_board(argv: list[str]) -> int:
1293
1338
  # explicit `propose` grant. `_out` passes `detail` through whole; it does
1294
1339
  # not need help, and a second copy of that logic is a second thing to
1295
1340
  # keep in sync.
1296
- return _out(st, d, lambda x: _say_line(x, args.id, to_node), aj)
1341
+ return _out(
1342
+ st, d, lambda x: _say_line(x, args.id, to_node, reason=reason), aj,
1343
+ )
1297
1344
  if args.command == "ready":
1298
1345
  st, d = _patch_json(f"{gw}/board/cards/{args.id}", {"actor": self_name, "move_ready": not args.clear}, token)
1299
1346
  return _out(st, d, lambda x: f"card #{x.get('id')} move_ready -> {x.get('move_ready')}", aj)
@@ -37,12 +37,65 @@ import sys
37
37
  from swarph_cli.commands.codegraph_hook import _match_quality
38
38
 
39
39
  DEFAULT_INDEX = os.path.expanduser("~/.swarph/codegraph/index.db")
40
- # NOTE: named DEFAULT_CALLER_CELL, not "*_CALLER" — this is an A8 caller-CELL
41
- # identity (sensitivity-gate scoping), not a SwarphCall `role.subrole.specific`
42
- # caller tag, and must NOT trip the repo-wide caller-convention meta-guard
43
- # (tests/test_caller_meta_guard.py) which validates every `*_CALLER` constant
44
- # against that unrelated dotted-slug convention.
45
- DEFAULT_CALLER_CELL = "lab-ovh"
40
+ # >>> THERE IS NO DEFAULT IDENTITY ANY MORE. THIS CONSTANT IS GONE ON PURPOSE. <<<
41
+ #
42
+ # It used to read `DEFAULT_CALLER_CELL = "lab-ovh"`, and that one line meant every
43
+ # unconfigured caller on lab-ovh queried with the ORCHESTRATOR'S READ AUTHORITY.
44
+ #
45
+ # MEASURED 2026-09-18 (science-claude, mesh 43183/43186/43188). The gateway was never
46
+ # bypassed and nothing was ever spoofed: `_query_via_gateway` sends NO caller field, so
47
+ # the server binds identity from the bearer token, correctly. What the default did was
48
+ # pick WHICH REAL TOKEN got presented — `_resolve_token(self_name, ...)` reads the file
49
+ # that name points at. A cell asking a question on this box authenticated AS lab-ovh,
50
+ # with lab-ovh's genuine credential, and got a full, plausible, correctly-authorised
51
+ # answer belonging to somebody else.
52
+ #
53
+ # >>> A WRONG-IDENTITY WRITE ANNOUNCES ITSELF — a DM arrives signed by the wrong cell
54
+ # and someone notices. A WRONG-IDENTITY READ ANNOUNCES NOTHING. <<< It was found only
55
+ # because a repo its owner KNEW they owned was missing from their own results.
56
+ #
57
+ # WHY REFUSE RATHER THAN WARN (commander, 2026-09-18 04:44Z): a warning on a read that
58
+ # returns a correct-looking answer is a line of text above a result the caller already
59
+ # believes. And the cost of refusing was measured first, not assumed — of 8 executable
60
+ # `swarph` invocations in systemd + cron on lab-ovh, 7 already pass `--as` or
61
+ # `--token-file`, and the 8th (`monitor reexec-on-change`) cannot authenticate at all:
62
+ # no --gateway, no --token-file, no --as exists on that verb. SCHEDULED WORK BREAKS IN
63
+ # ZERO PLACES. The population that pays is interactive use, which is the population
64
+ # that was silently mis-attributed.
65
+ #
66
+ # $SWARPH_SELF STILL WORKS AND IS THE INTENDED PATH. It is a DECLARED identity. What is
67
+ # refused is the *undeclared* case, where a package-level guess stood in for one.
68
+ IDENTITY_UNSET = object() # sentinel: argparse default when --caller-cell is omitted
69
+
70
+
71
+ class IdentityNotDeclared(RuntimeError):
72
+ """No caller identity was declared and there is no default to fall back to."""
73
+
74
+
75
+ def require_caller_cell(explicit=None, *, verb: str) -> str:
76
+ """Resolve the caller cell, or REFUSE.
77
+
78
+ Order: explicit flag -> $SWARPH_SELF -> $SWARPH_CELL -> refuse.
79
+
80
+ The refusal names the verb and every way to satisfy it, because an error that
81
+ says only "identity unset" moves the work to the reader.
82
+ """
83
+ if explicit not in (None, IDENTITY_UNSET) and str(explicit).strip():
84
+ return str(explicit).strip()
85
+ for var in ("SWARPH_SELF", "SWARPH_CELL"):
86
+ v = (os.environ.get(var) or "").strip()
87
+ if v:
88
+ return v
89
+ raise IdentityNotDeclared(
90
+ f"swarph {verb}: NO CALLER IDENTITY DECLARED, and there is no default.\n"
91
+ f" This used to fall back to 'lab-ovh', which meant an undeclared caller\n"
92
+ f" queried with that cell's read authority and got somebody else's answer.\n"
93
+ f" Declare one of:\n"
94
+ f" --caller-cell <cell> for this invocation\n"
95
+ f" SWARPH_SELF=<cell> for this shell or unit\n"
96
+ f" SWARPH_CELL=<cell> (MCP hosts)\n"
97
+ f" It is NOT guessable: this box hosts several cells under one uid."
98
+ )
46
99
 
47
100
  # NOT 1 and NOT 2. A distinct code so "nothing was searched" can never be confused
48
101
  # with a crash or a usage error, and so a caller can branch on it specifically.
@@ -332,7 +385,7 @@ def _query_via_gateway(gateway: str, query: str, limit: int, token_file):
332
385
  from swarph_cli.commands.mesh import _post_json, _resolve_token # local import: keeps
333
386
  # the local-only path free of any mesh dependency, so an offline cell still works.
334
387
 
335
- self_name = os.environ.get("SWARPH_SELF") or DEFAULT_CALLER_CELL
388
+ self_name = require_caller_cell(verb="codegraph")
336
389
  token = _resolve_token(self_name, token_file)
337
390
  body = {"query": query, "limit": max(1, min(int(limit), 25))}
338
391
  status, payload = _post_json(gateway.rstrip("/") + "/codegraph", body, token)
@@ -352,7 +405,9 @@ def run_codegraph(argv) -> int:
352
405
  "is configured (the server searches and returns the answer); falls back to a local index.")
353
406
  ap.add_argument("query", help="natural-language query, e.g. 'which function escapes HTML'")
354
407
  ap.add_argument("--index", default=DEFAULT_INDEX, help=f"index db (default {DEFAULT_INDEX})")
355
- ap.add_argument("--caller-cell", default=DEFAULT_CALLER_CELL, help="A8 caller identity (default lab-ovh)")
408
+ ap.add_argument("--caller-cell", default=IDENTITY_UNSET,
409
+ help="A8 caller identity. NO DEFAULT — else $SWARPH_SELF / $SWARPH_CELL, "
410
+ "else the command REFUSES rather than guessing (it used to guess lab-ovh).")
356
411
  ap.add_argument("--limit", type=int, default=8)
357
412
  ap.add_argument("--json", action="store_true", help="machine-readable JSON output")
358
413
  ap.add_argument("--gateway", default=None,
@@ -130,6 +130,38 @@ def _commands_in(section: str) -> "list[str]":
130
130
  return out
131
131
 
132
132
 
133
+ def verbs_in_guide(topics: "dict[str, str] | None" = None) -> "set[str]":
134
+ """#547: the verbs the guide TEACHES, counted PER VERB (a topic count said 11 and
135
+ meant nothing). A verb is covered when some section's commands include it."""
136
+ topics = topics if topics is not None else _split_topics(_load_guide())
137
+ out: set[str] = set()
138
+ for body in topics.values():
139
+ for cmd in _commands_in(body):
140
+ parts = cmd.split()
141
+ if len(parts) >= 2:
142
+ out.add(parts[1])
143
+ return out
144
+
145
+
146
+ def _registry() -> "list[str]":
147
+ # Lazy: main dispatches handlers by dotted path and does not import this module,
148
+ # but keep the direction one-way anyway.
149
+ from swarph_cli.main import registered_verbs
150
+ return registered_verbs()
151
+
152
+
153
+ def _verb_entry(topics: "dict[str, str]", verb: str) -> "list[tuple[str, str]]":
154
+ """The line in each topic that teaches `swarph <verb>` -- one per topic, first hit."""
155
+ pat = re.compile(r"`swarph " + re.escape(verb) + r"(?:[ `])")
156
+ hits: list[tuple[str, str]] = []
157
+ for name, body in topics.items():
158
+ for raw in body.splitlines():
159
+ if pat.search(raw):
160
+ hits.append((name, raw.strip()))
161
+ break
162
+ return hits
163
+
164
+
133
165
  _TERM_RE = re.compile(r"\*\*(.+?)\*\*")
134
166
 
135
167
 
@@ -262,13 +294,21 @@ def run_guide(argv: "list[str]") -> int:
262
294
  # `apropos`: an LLM arrives with an INTENT ("subscribe to updates"), not the
263
295
  # topic's name ("channels"). Requiring the name is the #520 discovery defect
264
296
  # reproduced inside its own fix.
297
+ term = args.search.strip().lower()
298
+ # #547: a VERB name resolves to the verb's own entry first -- `--search highlight`
299
+ # used to answer with a topic list that did not contain it.
300
+ verb_hits = _verb_entry(topics, term) if term in _registry() else []
265
301
  hits = _search(topics, args.search)
266
- if not hits:
302
+ if not hits and not verb_hits:
267
303
  print_safe(f"swarph guide: nothing matches {args.search!r}. "
268
304
  f"Topics: {', '.join(topics)}", stream=sys.stderr)
269
305
  return 2
270
- for name, line in hits:
306
+ for name, line in verb_hits:
271
307
  print_safe(f"{name:<22} {line}")
308
+ shown = {name for name, _ in verb_hits}
309
+ for name, line in hits:
310
+ if name not in shown:
311
+ print_safe(f"{name:<22} {line}")
272
312
  return 0
273
313
 
274
314
  if not args.topic:
@@ -279,6 +319,19 @@ def run_guide(argv: "list[str]") -> int:
279
319
  if key in topics:
280
320
  print_safe(topics[key])
281
321
  return 0
322
+ if key in _registry():
323
+ # #547: every registered verb resolves, per verb (tests/test_547 pins the set):
324
+ # the lines that teach it, then -- when the name is also a topic's substring
325
+ # (`channel` -> channels) -- that one topic, as before.
326
+ for name, line in _verb_entry(topics, key):
327
+ print_safe(f"{name:<22} {line}")
328
+ near = [k for k in topics if key in k]
329
+ if len(near) == 1:
330
+ print_safe("")
331
+ print_safe(topics[near[0]])
332
+ else:
333
+ print_safe(f"\n more: swarph {key} --help")
334
+ return 0
282
335
 
283
336
  # Substring fallback before failing: `swarph guide channel` should find `channels`.
284
337
  hits = [k for k in topics if key in k]
@@ -37,6 +37,7 @@ _LLM_BLURBS = {
37
37
  "grok": "xAI Grok — grok membrane",
38
38
  "muse": "Muse — muse membrane",
39
39
  "vibe": "Vibe — vibe membrane",
40
+ "opencode": "OpenCode — opencode membrane (XDG config+data isolation)",
40
41
  }
41
42
 
42
43
 
@@ -0,0 +1,173 @@
1
+ """``swarph install-opencode-plugin`` — the OpenCode plugin that wires swarph hooks.
2
+
3
+ Part 2 of the opencode membrane. opencode has NO command-hook surface — its only
4
+ hook surface is a JavaScript plugin loaded from the config dir's ``plugins/``
5
+ tree (auto-loaded, no ``opencode.json`` ``plugin`` entry required; measured:
6
+ ``~/.config/opencode/plugin/opensling.js`` loads against a config containing only
7
+ ``$schema``). This verb renders the package payload (baking the pinned Python
8
+ interpreter, the same reason ``install_codex_hooks`` pins it: PATH presence is
9
+ not a liveness proof) and writes it as ``swarph-opencode.js``:
10
+
11
+ * ``--scope user`` → ``~/.config/opencode/plugins/swarph-opencode.js`` —
12
+ the OPERATOR's bare ``opencode`` sessions (outside a swarph cell).
13
+ * ``--scope project`` → ``<cwd>/.opencode/plugins/swarph-opencode.js`` — a
14
+ per-project wiring.
15
+
16
+ The swarph CELL does NOT read either of those locations: its membrane relocates
17
+ ``XDG_CONFIG_HOME`` to ``<cwd>/.opencode-cell/config``, so its plugin lives at
18
+ ``<cwd>/.opencode-cell/config/opencode/plugins/swarph-opencode.js`` and is
19
+ written by the membrane's ``_opencode_env`` via ``ensure_cell_plugin`` — a
20
+ spawned cell is wired without a separate onboarding step.
21
+
22
+ Identity (SWARPH_SELF / SWARPH_MEMORY_DIR) is read by the plugin from env AT
23
+ RUNTIME, not baked — the membrane already stamps SWARPH_SELF into the cell env,
24
+ and a bare-session install inherits the operator's env at hook time.
25
+
26
+ Idempotent, ``--uninstall``, ``--dry-run``, write-landed assert — the same
27
+ operator contract as every other hook installer (#527 task 3).
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import json
34
+ import sys
35
+ from importlib import resources
36
+ from pathlib import Path
37
+
38
+ from swarph_cli.cell import _atomic_write_text
39
+
40
+ _PAYLOAD = "swarph_cli.payloads.opencode"
41
+ _PLUGIN_NAME = "swarph-opencode.js"
42
+
43
+
44
+ def _payload_text() -> str:
45
+ """Read the plugin FROM THE PACKAGE — never a source-tree path, so tests
46
+ exercise the same importlib.resources lookup an installed wheel depends on
47
+ (the 0.39.3 undeclared-package-data lesson)."""
48
+ return resources.files(_PAYLOAD).joinpath("plugin.js").read_text(encoding="utf-8")
49
+
50
+
51
+ def _render_plugin(interpreter: str) -> str:
52
+ r"""Render the plugin with @PYTHON@ replaced by ``interpreter`` AS A JS STRING.
53
+
54
+ >>> shlex.quote is WRONG here, and it was the shipped defect (#423 review).
55
+ The placeholder sits on the RHS of ``const PY = @PYTHON@;`` — a JavaScript
56
+ string literal position, NOT shell text. ``shlex.quote`` wraps in single
57
+ quotes and leaves backslashes raw, so on Windows
58
+ ``C:\Program Files\Python\python.exe`` became
59
+ ``const PY = "'C:\Program Files\Python\python.exe'";`` — invalid escapes, and
60
+ the single quotes land INSIDE the double quotes, so the value is a wrong path
61
+ even where it parses. json.dumps produces a valid JS string literal (double
62
+ quoted, backslashes escaped as ``\\``), which a JS parser accepts and decodes
63
+ back to the exact path. The template leaves the quotes to the substitution
64
+ (``const PY = @PYTHON@;``) so they are never doubled. <<<
65
+ """
66
+ return _payload_text().replace("@PYTHON@", json.dumps(interpreter))
67
+
68
+
69
+ def _render() -> str:
70
+ """The rendered plugin, with @PYTHON@ pinned to THIS interpreter."""
71
+ return _render_plugin(str(Path(sys.executable).resolve()))
72
+
73
+
74
+ def _target(scope: str) -> Path:
75
+ if scope == "project":
76
+ return Path.cwd() / ".opencode" / "plugins" / _PLUGIN_NAME
77
+ return Path.home() / ".config" / "opencode" / "plugins" / _PLUGIN_NAME
78
+
79
+
80
+ def cell_plugin_path(cwd: Path) -> Path:
81
+ """Where a cell's plugin lives inside its isolated config dir.
82
+
83
+ Mirrors ``_OPENCODE_CELL_SUBDIR`` + the XDG_CONFIG_HOME layout the membrane
84
+ sets in ``_opencode_env`` — kept here (not imported from spawn) so spawn can
85
+ call back into it without a cycle.
86
+ """
87
+ return cwd / ".opencode-cell" / "config" / "opencode" / "plugins" / _PLUGIN_NAME
88
+
89
+
90
+ def ensure_cell_plugin(cwd: Path) -> None:
91
+ """Write the cell's plugin if absent or changed. Idempotent + best-effort:
92
+ a failure must not block the spawn (worst case the cell runs unhooked)."""
93
+ try:
94
+ rendered = _render()
95
+ target = cell_plugin_path(cwd)
96
+ if target.exists() and target.read_text(encoding="utf-8") == rendered:
97
+ return
98
+ target.parent.mkdir(parents=True, exist_ok=True)
99
+ _atomic_write_text(target, rendered)
100
+ except Exception:
101
+ pass
102
+
103
+
104
+ def _read_existing(target: Path) -> str:
105
+ return target.read_text(encoding="utf-8") if target.exists() else ""
106
+
107
+
108
+ def run_install_opencode_plugin(argv: list[str] | None = None) -> int:
109
+ if argv is None:
110
+ argv = sys.argv[2:]
111
+
112
+ p = argparse.ArgumentParser(prog="swarph install-opencode-plugin")
113
+ p.add_argument("--scope", choices=("user", "project"), default="user")
114
+ p.add_argument("--uninstall", action="store_true")
115
+ p.add_argument("--dry-run", action="store_true")
116
+ args = p.parse_args(argv)
117
+
118
+ target = _target(args.scope)
119
+
120
+ if args.dry_run:
121
+ print(f"# swarph install-opencode-plugin --dry-run", file=sys.stderr)
122
+ print(f"# scope: {args.scope}", file=sys.stderr)
123
+ print(f"# target: {target}", file=sys.stderr)
124
+ print(f"# action: {'uninstall' if args.uninstall else 'install'}", file=sys.stderr)
125
+ if not args.uninstall:
126
+ print(_render())
127
+ return 0
128
+
129
+ if args.uninstall:
130
+ existed = target.exists()
131
+ if existed:
132
+ target.unlink()
133
+ print(
134
+ f"swarph install-opencode-plugin: uninstalled {_PLUGIN_NAME} from "
135
+ f"{target} ({'removed' if existed else 'was absent'}).",
136
+ file=sys.stderr,
137
+ )
138
+ return 0
139
+
140
+ rendered = _render()
141
+ if _read_existing(target) == rendered:
142
+ print(
143
+ f"swarph install-opencode-plugin: no change needed at {target} "
144
+ "(plugin already in desired state).",
145
+ file=sys.stderr,
146
+ )
147
+ return 0
148
+
149
+ target.parent.mkdir(parents=True, exist_ok=True)
150
+ _atomic_write_text(target, rendered)
151
+ # The #527 task-3 assert: "installed" is a claim about the filesystem.
152
+ if target.read_text(encoding="utf-8") != rendered:
153
+ print(
154
+ f"swarph install-opencode-plugin: WRITE DID NOT LAND — {target} "
155
+ "re-reads as different content than was written. The install is NOT "
156
+ "in effect.",
157
+ file=sys.stderr,
158
+ )
159
+ return 2
160
+ print(
161
+ f"swarph install-opencode-plugin: installed {_PLUGIN_NAME} at {target}.",
162
+ file=sys.stderr,
163
+ )
164
+ print(
165
+ "NOTE: opencode loads the plugin table at session start. Already-open "
166
+ "sessions keep the pre-edit table — open a FRESH session before testing.",
167
+ file=sys.stderr,
168
+ )
169
+ return 0
170
+
171
+
172
+ if __name__ == "__main__":
173
+ raise SystemExit(run_install_opencode_plugin(sys.argv[1:]))
@@ -151,7 +151,8 @@ def _codegraph_query(query: str, *, limit: int = 8) -> list[dict]:
151
151
 
152
152
  Delegates to :func:`codegraph.structural_query` against the module-level
153
153
  ``_CODEGRAPH_INDEX`` path, using ``SWARPH_CELL`` (else
154
- ``codegraph.DEFAULT_CALLER_CELL``) as the A8 caller identity and the
154
+ ``codegraph.require_caller_cell``, which REFUSES rather than defaulting) as
155
+ the A8 caller identity and the
155
156
  default operate-what-you-own allowlist (``allowlist=None``).
156
157
 
157
158
  NEVER raises — any exception (missing index, corrupt db, bad query, or
@@ -162,7 +163,13 @@ def _codegraph_query(query: str, *, limit: int = 8) -> list[dict]:
162
163
  to keep the tool output tight for the calling agent.
163
164
  """
164
165
  try:
165
- caller_cell = os.environ.get("SWARPH_CELL", codegraph.DEFAULT_CALLER_CELL)
166
+ # NO DEFAULT IDENTITY (2026-09-18). `codegraph.DEFAULT_CALLER_CELL` is gone;
167
+ # an undeclared caller used to become lab-ovh and receive that cell's read
168
+ # authority. require_caller_cell raises IdentityNotDeclared, which the
169
+ # NEVER-RAISES contract below turns into an empty result — FAIL-CLOSED: the
170
+ # MCP host gets nothing rather than somebody else's answer.
171
+ caller_cell = codegraph.require_caller_cell(
172
+ os.environ.get("SWARPH_CELL"), verb="mcp codegraph_query")
166
173
  rows = codegraph.structural_query(
167
174
  query,
168
175
  index_path=_CODEGRAPH_INDEX,
@@ -67,15 +67,37 @@ def _memory_slug(file_path: str) -> Optional[str]:
67
67
 
68
68
 
69
69
  def _snippet(path: Path) -> str:
70
- """First non-empty line, de-headed, collapsed, truncated. Unreadable file →
71
- empty snippet (the pointer still emits — the write happened either way)."""
70
+ """One-line summary of a memory: its frontmatter `description`, else the first body
71
+ line. Unreadable file → empty snippet (the pointer still emits — the write happened).
72
+
73
+ Every memory file opens with YAML frontmatter, so "first non-empty line" returned the
74
+ literal `---` delimiter. That is a NON-EMPTY STRING, so the caller's `if snippet` guard
75
+ passed and the timeline took `memory cached: ---`. MEASURED 2026-09-15: 21 such entries
76
+ on the shared timeline between 2026-08-25 and 2026-09-15, every one of them useless.
77
+ An emptiness guard cannot catch a delimiter; the fix has to be here, not at the guard.
78
+ """
72
79
  try:
73
- for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
74
- s = line.strip().lstrip("#").strip()
75
- if s:
76
- return " ".join(s.split())[:_SNIPPET_CHARS]
80
+ lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
77
81
  except Exception:
78
- pass
82
+ return ""
83
+ body = lines
84
+ if lines and lines[0].strip() == "---":
85
+ for i, line in enumerate(lines[1:], start=1):
86
+ if line.strip() == "---":
87
+ # `description:` exists precisely to be a one-line summary — prefer it.
88
+ for fm in lines[1:i]:
89
+ if fm.strip().lower().startswith("description:"):
90
+ d = fm.split(":", 1)[1].strip().strip('"').strip("'")
91
+ if d:
92
+ return " ".join(d.split())[:_SNIPPET_CHARS]
93
+ body = lines[i + 1:]
94
+ break
95
+ else:
96
+ body = lines[1:] # unterminated frontmatter: skip the opening delimiter
97
+ for line in body:
98
+ s = line.strip().lstrip("#").strip()
99
+ if s and s != "---": # a stray delimiter is never a summary
100
+ return " ".join(s.split())[:_SNIPPET_CHARS]
79
101
  return ""
80
102
 
81
103