swarph-cli 0.72.8__tar.gz → 0.72.10__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 (486) hide show
  1. {swarph_cli-0.72.8/src/swarph_cli.egg-info → swarph_cli-0.72.10}/PKG-INFO +1 -1
  2. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/pyproject.toml +1 -1
  3. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/__init__.py +1 -1
  4. swarph_cli-0.72.10/src/swarph_cli/commands/roster.py +237 -0
  5. swarph_cli-0.72.10/src/swarph_cli/commands/sweep.py +413 -0
  6. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/guide/GUIDE.md +2 -0
  7. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/main.py +2 -0
  8. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/rowclear.py +65 -4
  9. swarph_cli-0.72.10/src/swarph_cli/systemd/swarph-stuck-sweep.service +15 -0
  10. swarph_cli-0.72.10/src/swarph_cli/systemd/swarph-stuck-sweep.timer +14 -0
  11. {swarph_cli-0.72.8 → swarph_cli-0.72.10/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  12. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli.egg-info/SOURCES.txt +7 -0
  13. swarph_cli-0.72.10/tests/test_1061_roster_cli.py +137 -0
  14. swarph_cli-0.72.10/tests/test_1064_stuck_sweep.py +332 -0
  15. swarph_cli-0.72.10/tests/test_1065_release.py +295 -0
  16. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_432_rowclear.py +165 -0
  17. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_sidecar.py +40 -0
  18. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/LICENSE +0 -0
  19. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/README.md +0 -0
  20. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/setup.cfg +0 -0
  21. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/__main__.py +0 -0
  22. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/__init__.py +0 -0
  23. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/backends.py +0 -0
  24. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/claude_cli.py +0 -0
  25. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/codex_cli.py +0 -0
  26. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/pack.py +0 -0
  27. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/prices.py +0 -0
  28. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/providers.py +0 -0
  29. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/quality.py +0 -0
  30. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/refresh_prices.py +0 -0
  31. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/rules.py +0 -0
  32. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/runner.py +0 -0
  33. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/signal_score.py +0 -0
  34. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/bench/validate.py +0 -0
  35. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/caller.py +0 -0
  36. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/__init__.py +0 -0
  37. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/harden.py +0 -0
  38. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/lineage.py +0 -0
  39. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/liveness.py +0 -0
  40. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/manifest.py +0 -0
  41. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/paths.py +0 -0
  42. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/capture/verify.py +0 -0
  43. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/cell.py +0 -0
  44. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/chain_token.py +0 -0
  45. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/channel.py +0 -0
  46. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/__init__.py +0 -0
  47. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/_content.py +0 -0
  48. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/_display.py +0 -0
  49. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/_gateway_client.py +0 -0
  50. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/add.py +0 -0
  51. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/bench.py +0 -0
  52. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/board.py +0 -0
  53. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/board_merge_check.py +0 -0
  54. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/brain.py +0 -0
  55. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/brain_ask.py +0 -0
  56. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/cell.py +0 -0
  57. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/cell_probe.py +0 -0
  58. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  59. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/channel.py +0 -0
  60. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/chat.py +0 -0
  61. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/codegraph.py +0 -0
  62. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  63. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  64. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/codex_waker.py +0 -0
  65. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/compress.py +0 -0
  66. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/daemon.py +0 -0
  67. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/dreaming.py +0 -0
  68. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/event.py +0 -0
  69. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/gateway.py +0 -0
  70. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/gh_route.py +0 -0
  71. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/group.py +0 -0
  72. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/guide.py +0 -0
  73. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/highlight.py +0 -0
  74. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  75. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/hook_output.py +0 -0
  76. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/hooks.py +0 -0
  77. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/import_session.py +0 -0
  78. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/init.py +0 -0
  79. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  80. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_hook.py +0 -0
  81. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  82. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  83. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  84. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  85. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/lane.py +0 -0
  86. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/mcp_server.py +0 -0
  87. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/memory.py +0 -0
  88. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  89. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/memory_sync.py +0 -0
  90. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/mesh.py +0 -0
  91. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/monitor.py +0 -0
  92. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/okf_links.py +0 -0
  93. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/onboard.py +0 -0
  94. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  95. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  96. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/protocol_handler.py +0 -0
  97. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/ratify.py +0 -0
  98. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/rowclear_hook.py +0 -0
  99. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/schedule.py +0 -0
  100. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/security.py +0 -0
  101. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/service.py +0 -0
  102. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/spawn.py +0 -0
  103. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/timeline.py +0 -0
  104. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/version.py +0 -0
  105. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  106. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/commands/watchdog.py +0 -0
  107. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/compress/__init__.py +0 -0
  108. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/compress/levers.py +0 -0
  109. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/compress/marker.py +0 -0
  110. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/compress/verify.py +0 -0
  111. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/console_safe.py +0 -0
  112. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/delivery_queue.py +0 -0
  113. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dm_frame.py +0 -0
  114. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/__init__.py +0 -0
  115. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/candidates.py +0 -0
  116. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/clone.py +0 -0
  117. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/enrich.py +0 -0
  118. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/index_check.py +0 -0
  119. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/notify.py +0 -0
  120. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/organize.py +0 -0
  121. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/probes.py +0 -0
  122. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/report.py +0 -0
  123. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/run.py +0 -0
  124. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/slm.py +0 -0
  125. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/transcripts.py +0 -0
  126. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/dreaming/verify.py +0 -0
  127. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/__init__.py +0 -0
  128. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/feature_registry.py +0 -0
  129. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/lanes_control.py +0 -0
  130. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/schema.sql +0 -0
  131. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/server.py +0 -0
  132. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway/services_control.py +0 -0
  133. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gateway_default.py +0 -0
  134. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/gh_identity.py +0 -0
  135. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/identity.py +0 -0
  136. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/multiplexer.py +0 -0
  137. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/__init__.py +0 -0
  138. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/__main__.py +0 -0
  139. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/api.py +0 -0
  140. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/cli.py +0 -0
  141. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/config.py +0 -0
  142. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/operator/fmt.py +0 -0
  143. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/orphan_daemons.py +0 -0
  144. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/pane_probe.py +0 -0
  145. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/parsers/__init__.py +0 -0
  146. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/parsers/claude.py +0 -0
  147. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  148. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  149. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  150. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  151. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  152. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  153. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  154. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  155. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  156. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  157. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  158. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  159. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_executor.py +0 -0
  160. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_executor_windows.py +0 -0
  161. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_reconciliation.py +0 -0
  162. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_reply_outbox.py +0 -0
  163. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_service_host.py +0 -0
  164. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_service_payload.py +0 -0
  165. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_service_reply.py +0 -0
  166. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/peer_staging.py +0 -0
  167. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/query_filters.py +0 -0
  168. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/__init__.py +0 -0
  169. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  170. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  171. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  172. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  173. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  174. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/service/__init__.py +0 -0
  175. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/service/app.py +0 -0
  176. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/service/providers.py +0 -0
  177. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/session_bridge.py +0 -0
  178. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  179. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  180. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/source_text.py +0 -0
  181. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/stall_alert.py +0 -0
  182. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/codex-waker.default +0 -0
  183. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  184. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  185. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  186. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  187. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  188. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  189. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  190. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  191. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  192. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  193. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  194. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  195. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  196. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/timeline_paths.py +0 -0
  197. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli/tokens.py +0 -0
  198. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  199. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  200. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli.egg-info/requires.txt +0 -0
  201. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/src/swarph_cli.egg-info/top_level.txt +0 -0
  202. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1006_board_publisher.py +0 -0
  203. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1006_session_board.py +0 -0
  204. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1010_strand_only.py +0 -0
  205. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1013_spawn_channel.py +0 -0
  206. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1017_muse_tui.py +0 -0
  207. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1050_surrogate_wake.py +0 -0
  208. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1052_opencode_deaf.py +0 -0
  209. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_1059_watchdog_unread_env.py +0 -0
  210. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_124_register_merge.py +0 -0
  211. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_126_pending_from_complete.py +0 -0
  212. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  213. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_138_memory_navigate.py +0 -0
  214. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_139_tool_probe.py +0 -0
  215. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_183_card_anchor.py +0 -0
  216. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_184_notify_honesty.py +0 -0
  217. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_201_win32_bash_refusal.py +0 -0
  218. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_243_onboard_per_peer_token.py +0 -0
  219. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_307_task4_ask_reply.py +0 -0
  220. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  221. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_319_refusal_exit_codes.py +0 -0
  222. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_323_mistral_backend.py +0 -0
  223. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_332_report_organize_findings.py +0 -0
  224. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_356_query_filters.py +0 -0
  225. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_397_gh_identity_router.py +0 -0
  226. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_397_router_preflight.py +0 -0
  227. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_402_one_identity_resolver.py +0 -0
  228. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_467b_onboard_operator_credential.py +0 -0
  229. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_472_register_caller_binding.py +0 -0
  230. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_484_codex_queue_sink.py +0 -0
  231. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_523_guide.py +0 -0
  232. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_524_conftest_hermeticity.py +0 -0
  233. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_532_accept_flag.py +0 -0
  234. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_535_version_capability.py +0 -0
  235. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_538_signal_bench.py +0 -0
  236. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_546_default_gateway.py +0 -0
  237. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_547_verbs_from_registry.py +0 -0
  238. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_548_gateway_remainder.py +0 -0
  239. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_552_codegraph_gateway.py +0 -0
  240. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_555_relay_row_shape.py +0 -0
  241. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_556_relay_freshness.py +0 -0
  242. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_559_edges_truncated.py +0 -0
  243. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_564_bootstrap_first_peer.py +0 -0
  244. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  245. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_579_register_bootstrap_message.py +0 -0
  246. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_591_cli_step_graph.py +0 -0
  247. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_644_monitor_supervision.py +0 -0
  248. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_648_install_unit.py +0 -0
  249. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_654_reexec.py +0 -0
  250. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_656_owned_slm.py +0 -0
  251. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_684_dreaming_verb.py +0 -0
  252. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_689_pkg_version_order.py +0 -0
  253. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_715_grok_wake.py +0 -0
  254. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_716_timeline_one_path.py +0 -0
  255. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_723_grok_0251.py +0 -0
  256. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  257. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_725_stdio_reconfigure.py +0 -0
  258. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_726_grok_limit_screen.py +0 -0
  259. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_729_arm_check_delivery_gap.py +0 -0
  260. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_729_dm_once.py +0 -0
  261. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_729_wake_watchdog.py +0 -0
  262. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  263. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_735_dm_mcp_tools.py +0 -0
  264. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_739_wake_writer.py +0 -0
  265. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_740_edit_project.py +0 -0
  266. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_762_cards_edit_priority.py +0 -0
  267. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_764_enrich_collapse.py +0 -0
  268. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_801_confirm_flow_cli.py +0 -0
  269. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_802_stage_history.py +0 -0
  270. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_825_codegraph_hook_noise.py +0 -0
  271. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_825_install_posttooluse_only.py +0 -0
  272. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  273. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_829_counterfactual_column.py +0 -0
  274. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_830_hook_binding_version.py +0 -0
  275. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_84_cli_labels.py +0 -0
  276. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_866_doctrine_guide.py +0 -0
  277. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_872_identity_refuses_not_warns.py +0 -0
  278. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_874_source_text.py +0 -0
  279. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  280. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_937_dreaming_notify.py +0 -0
  281. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_938_organize_line_limit.py +0 -0
  282. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_941_bench_backends.py +0 -0
  283. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_941_harness_342.py +0 -0
  284. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_945_same_line_pairing.py +0 -0
  285. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_955_prompt_audit.py +0 -0
  286. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_957_auth_http.py +0 -0
  287. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_959_venv_interpreter.py +0 -0
  288. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_960_channel.py +0 -0
  289. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  290. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_961_cursor_grok_footer.py +0 -0
  291. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_961_loud_stop.py +0 -0
  292. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_963_plugin_manifest.py +0 -0
  293. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_964_channel_cell.py +0 -0
  294. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_965_mesh_wait.py +0 -0
  295. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_975_channel_hop.py +0 -0
  296. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_add_security_gate.py +0 -0
  297. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_add.py +0 -0
  298. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_hash.py +0 -0
  299. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_lib.py +0 -0
  300. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_mcp.py +0 -0
  301. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_skill.py +0 -0
  302. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_tool.py +0 -0
  303. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_artifact_uri.py +0 -0
  304. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_backends.py +0 -0
  305. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_claude_cli.py +0 -0
  306. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_codex_cli.py +0 -0
  307. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_command.py +0 -0
  308. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_pack.py +0 -0
  309. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_prices.py +0 -0
  310. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_quality.py +0 -0
  311. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_runner.py +0 -0
  312. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_bench_validate.py +0 -0
  313. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_board_card_thread.py +0 -0
  314. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_board_command.py +0 -0
  315. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_board_merge_check.py +0 -0
  316. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_board_obligation_close.py +0 -0
  317. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_brain_ask_command.py +0 -0
  318. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_brain_ask_env_diagnosis.py +0 -0
  319. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_brain_ask_identity_explicitness.py +0 -0
  320. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_brain_command.py +0 -0
  321. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_caller_meta_guard.py +0 -0
  322. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_capture_lineage.py +0 -0
  323. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_capture_liveness.py +0 -0
  324. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_capture_manifest.py +0 -0
  325. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_capture_paths.py +0 -0
  326. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_capture_security.py +0 -0
  327. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cell_command_dispatch.py +0 -0
  328. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cell_harden.py +0 -0
  329. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cell_loader.py +0 -0
  330. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cell_selfcheck.py +0 -0
  331. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cell_verify.py +0 -0
  332. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_chain_token.py +0 -0
  333. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_channel_command.py +0 -0
  334. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  335. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_channel_post.py +0 -0
  336. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_channel_wake_policy_filter.py +0 -0
  337. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_chat_command.py +0 -0
  338. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_claude_parser.py +0 -0
  339. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_claude_tmux_template.py +0 -0
  340. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_codegraph_caller_scope.py +0 -0
  341. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_codegraph_command.py +0 -0
  342. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_codegraph_hook.py +0 -0
  343. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_codegraph_mcp.py +0 -0
  344. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_codex_waker.py +0 -0
  345. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_compress_caller_convention.py +0 -0
  346. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_compress_command.py +0 -0
  347. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_compress_levers.py +0 -0
  348. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_compress_marker.py +0 -0
  349. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_compress_verify.py +0 -0
  350. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_console_encoding_e2e.py +0 -0
  351. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_content_composition.py +0 -0
  352. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_cursor_membrane.py +0 -0
  353. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_daemon_command.py +0 -0
  354. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_daemon_console_encoding.py +0 -0
  355. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_daemon_delivery.py +0 -0
  356. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_delivery_queue.py +0 -0
  357. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_display_sanitize.py +0 -0
  358. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  359. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_event_emit.py +0 -0
  360. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_feature_to_uri.py +0 -0
  361. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_gateway_brain.py +0 -0
  362. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_gateway_client.py +0 -0
  363. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_gateway_command.py +0 -0
  364. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_gateway_message_idempotency.py +0 -0
  365. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  366. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_group_command.py +0 -0
  367. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_guide_commands_parse.py +0 -0
  368. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_guide_selfcheck.py +0 -0
  369. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_heartbeat_check.py +0 -0
  370. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_highlight_command.py +0 -0
  371. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hook_output.py +0 -0
  372. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_activity_marker.py +0 -0
  373. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_add.py +0 -0
  374. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_bundle.py +0 -0
  375. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_init.py +0 -0
  376. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_legacy_generation_removal.py +0 -0
  377. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_lifecycle.py +0 -0
  378. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_merge.py +0 -0
  379. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_verify.py +0 -0
  380. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  381. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_hooks_win32_command_path.py +0 -0
  382. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_import_command.py +0 -0
  383. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_inbox_identity_provenance.py +0 -0
  384. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_init_command.py +0 -0
  385. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_init_provider_menu.py +0 -0
  386. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_install_codex_hooks.py +0 -0
  387. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_install_hook.py +0 -0
  388. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_install_multiplexer_command.py +0 -0
  389. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_install_postcompact_hook.py +0 -0
  390. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_install_wake_hook.py +0 -0
  391. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_lane_command.py +0 -0
  392. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_main.py +0 -0
  393. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_main_unknown_verb_guard.py +0 -0
  394. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mcp_missing_hint.py +0 -0
  395. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mcp_server.py +0 -0
  396. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_memory_command.py +0 -0
  397. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_memory_emit_snippet.py +0 -0
  398. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_memory_gateway.py +0 -0
  399. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_memory_sync.py +0 -0
  400. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_memory_sync_membrane.py +0 -0
  401. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_command.py +0 -0
  402. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_inbox_mark_read.py +0 -0
  403. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_inbox_sanitize.py +0 -0
  404. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_originate_thread_623.py +0 -0
  405. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_send_unknown_peer.py +0 -0
  406. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  407. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  408. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  409. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_meta_edge_identity.py +0 -0
  410. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_monitor_channel_poll.py +0 -0
  411. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_monitor_cli.py +0 -0
  412. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_monitor_daemon_collision.py +0 -0
  413. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_monitor_self_name_verification.py +0 -0
  414. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_monitor_state_split.py +0 -0
  415. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_multiplexer.py +0 -0
  416. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_muse_membrane.py +0 -0
  417. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_muse_own_cli.py +0 -0
  418. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_obligation_close_is_surfaced.py +0 -0
  419. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_okf_links.py +0 -0
  420. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_onboard_command.py +0 -0
  421. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_opencode_membrane.py +0 -0
  422. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_opencode_plugin.py +0 -0
  423. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_operator_me.py +0 -0
  424. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_orphan_daemons.py +0 -0
  425. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_packaged_artifacts_exist.py +0 -0
  426. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_packaged_skills.py +0 -0
  427. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_executor.py +0 -0
  428. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_executor_windows.py +0 -0
  429. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_reply_drain_command.py +0 -0
  430. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_reply_drain_supervisor.py +0 -0
  431. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_reply_outbox.py +0 -0
  432. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_reply_transport.py +0 -0
  433. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_service_e2e.py +0 -0
  434. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_service_host.py +0 -0
  435. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_service_payload.py +0 -0
  436. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_peer_service_reply.py +0 -0
  437. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_postcompact_recall.py +0 -0
  438. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_process_liveness.py +0 -0
  439. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_protocol_handler.py +0 -0
  440. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_providers_isolation.py +0 -0
  441. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_ratify_command.py +0 -0
  442. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_readme_claims_no_gateway_default.py +0 -0
  443. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_repl_caller_convention.py +0 -0
  444. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_schedule_command.py +0 -0
  445. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_schedule_create_contract.py +0 -0
  446. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_security.py +0 -0
  447. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_service_app.py +0 -0
  448. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_service_command.py +0 -0
  449. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_service_providers.py +0 -0
  450. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_session_bridge_inject.py +0 -0
  451. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_session_bridge_probe.py +0 -0
  452. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_session_bridge_resolve.py +0 -0
  453. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_session_path_role_gate.py +0 -0
  454. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_shipped_units_match_the_cli.py +0 -0
  455. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_smoke_chat.py +0 -0
  456. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_smoke_one_shot.py +0 -0
  457. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_smoke_phase_5_5.py +0 -0
  458. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_base_hoist.py +0 -0
  459. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_command.py +0 -0
  460. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_identity_stamp.py +0 -0
  461. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_live_pin.py +0 -0
  462. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_mitosis_lineage.py +0 -0
  463. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_tmux_session.py +0 -0
  464. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_spawn_windows_relaunch.py +0 -0
  465. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_stall_alert.py +0 -0
  466. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_subprocess_decode_encoding.py +0 -0
  467. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_timeline_command.py +0 -0
  468. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_tmux_wake_runstate_defer.py +0 -0
  469. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_tmux_wake_session_reanchor.py +0 -0
  470. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_tmux_wake_stale.py +0 -0
  471. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_tmux_wake_submit_verify.py +0 -0
  472. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_token_file_one_parser.py +0 -0
  473. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_token_precedence_explicit_first.py +0 -0
  474. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_unified_token_resolver.py +0 -0
  475. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_version_command.py +0 -0
  476. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_version_consistency.py +0 -0
  477. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_vibe_membrane.py +0 -0
  478. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog.py +0 -0
  479. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_dm_wake.py +0 -0
  480. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  481. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  482. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_emit_health.py +0 -0
  483. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_install_flags.py +0 -0
  484. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_model_rung.py +0 -0
  485. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/tests/test_watchdog_stale_peers.py +0 -0
  486. {swarph_cli-0.72.8 → swarph_cli-0.72.10}/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.72.8
3
+ Version: 0.72.10
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.72.8"
7
+ version = "0.72.10"
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" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.72.8"
19
+ __version__ = "0.72.10"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -0,0 +1,237 @@
1
+ """``swarph roster`` — read/write the gateway's operator-maintained capacity table.
2
+
3
+ card #1061 (ROSTER AS DATA): the roster replaces tribal knowledge about who
4
+ builds, who reviews, on which model/effort, under which quota, and who is out
5
+ until when. Reads hit GET /roster (any authenticated caller); writes hit
6
+ PUT /roster/{peer} (operators only — the gateway 403s the rest).
7
+
8
+ swarph roster [--peer PEER] [--json] list the table (or one row)
9
+ swarph roster set PEER [--model M] [--effort E] [--roles r1,r2]
10
+ [--quota-pool P] [--quota-used PCT --quota-as-of TS] [--resets DATE]
11
+ [--until TS | --clear-until] [--note TEXT]
12
+
13
+ A quota VALUE without its as_of coordinate is a rumour: --quota-used /
14
+ --resets without --quota-as-of are refused client-side (exit 2) before the
15
+ gateway's own 400. The pool NAME needs no coordinate (identifier, not a
16
+ measurement). --clear-until is the grok-returns flow (explicit null clears).
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import json
22
+ import sys
23
+ import urllib.error
24
+ import urllib.request
25
+
26
+ # card #1061 role vocabulary — mirrors the gateway's _ROSTER_ROLES. A role
27
+ # the gateway does not know is refused here (exit 2) before its 400; when the
28
+ # vocabulary grows, BOTH pins change together (gateway tests + test_1061 below).
29
+ ROSTER_ROLES = ("build", "validate", "plan-review", "orchestrate")
30
+
31
+
32
+ def _http_get_json(url, token, *, timeout=10.0):
33
+ from swarph_cli.commands.mesh import _http_get_json as _get
34
+ return _get(url, token, timeout=timeout)
35
+
36
+
37
+ def _http_put_json(url, body, token, *, timeout=10.0):
38
+ from swarph_cli.commands.mesh import _require_absolute_gateway_url
39
+ _require_absolute_gateway_url(url)
40
+ data = json.dumps(body).encode("utf-8")
41
+ req = urllib.request.Request(
42
+ url, data=data, method="PUT",
43
+ headers={"Authorization": f"Bearer {token}",
44
+ "Content-Type": "application/json"})
45
+ try:
46
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
47
+ return resp.status, json.loads(resp.read().decode("utf-8") or "{}")
48
+ except urllib.error.HTTPError as exc:
49
+ try:
50
+ err_body = json.loads(exc.read().decode("utf-8") or "{}")
51
+ except Exception:
52
+ err_body = {"detail": str(exc)}
53
+ return exc.code, err_body
54
+ except urllib.error.URLError as exc:
55
+ return 0, {"detail": str(exc)}
56
+
57
+
58
+ def _resolve(args):
59
+ from swarph_cli.tokens import resolve_token
60
+ resolution = resolve_token(args.self_name, args.token_file,
61
+ identity_is_explicit=bool(args.self_name))
62
+ if resolution is None:
63
+ print("swarph roster: no token (set MESH_GATEWAY_TOKEN, pass "
64
+ "--token-file, or --as a peer with a stored token)",
65
+ file=sys.stderr)
66
+ return None
67
+ return resolution.token
68
+
69
+
70
+ def _quota_compact(r):
71
+ if r.get("quota_used_pct") is None and not r.get("quota_pool"):
72
+ return "-"
73
+ parts = []
74
+ if r.get("quota_pool"):
75
+ parts.append(str(r["quota_pool"]))
76
+ if r.get("quota_used_pct") is not None:
77
+ parts.append(f"{r['quota_used_pct']}%")
78
+ if r.get("quota_as_of"):
79
+ parts.append(f"@{r['quota_as_of']}")
80
+ if r.get("quota_resets_at"):
81
+ parts.append(f"(resets {r['quota_resets_at']})")
82
+ return " ".join(parts)
83
+
84
+
85
+ def _line(r):
86
+ roles = ",".join(r["roles"]) if r.get("roles") else "-"
87
+ bits = [r["peer"],
88
+ f"model={r.get('model') or '-'}",
89
+ f"effort={r.get('effort') or '-'}",
90
+ f"roles={roles}",
91
+ f"quota={_quota_compact(r)}"]
92
+ if r.get("unavailable_until"):
93
+ bits.append(f"out-until={r['unavailable_until']}")
94
+ bits.append(f"updated_by={r.get('updated_by') or '?'}")
95
+ return " ".join(bits)
96
+
97
+
98
+ def _run_list(args):
99
+ token = _resolve(args)
100
+ if token is None:
101
+ return 2
102
+ gateway = args.gateway.rstrip("/")
103
+ status, payload = _http_get_json(f"{gateway}/roster", token)
104
+ if status < 200 or status >= 300:
105
+ detail = payload.get("detail", "<gateway error>") if isinstance(payload, dict) else "<gateway error>"
106
+ print(f"swarph roster: gateway {status}: {detail}", file=sys.stderr)
107
+ return 1
108
+ rows = payload.get("roster") if isinstance(payload, dict) else None
109
+ if not isinstance(rows, list):
110
+ print("swarph roster: unexpected /roster shape (no roster list)",
111
+ file=sys.stderr)
112
+ return 1
113
+ if args.peer:
114
+ rows = [r for r in rows if isinstance(r, dict) and r.get("peer") == args.peer]
115
+ if not rows:
116
+ print(f"swarph roster: no row for {args.peer!r}", file=sys.stderr)
117
+ return 1
118
+ if args.json:
119
+ print(json.dumps(rows, indent=2))
120
+ else:
121
+ for r in rows:
122
+ print(_line(r) if isinstance(r, dict) else r)
123
+ return 0
124
+
125
+
126
+ def _run_set(args):
127
+ token = _resolve(args)
128
+ if token is None:
129
+ return 2
130
+ if ((args.quota_used is not None or args.resets is not None)
131
+ and args.quota_as_of is None):
132
+ print("swarph roster set: --quota-used / --resets need --quota-as-of "
133
+ "(a quota value without its coordinate is a rumour)",
134
+ file=sys.stderr)
135
+ return 2
136
+ body = {}
137
+ if args.model is not None:
138
+ body["model"] = args.model
139
+ if args.effort is not None:
140
+ body["effort"] = args.effort
141
+ if args.roles is not None:
142
+ roles = [x.strip() for x in args.roles.split(",") if x.strip()]
143
+ bad = [x for x in roles if x not in ROSTER_ROLES]
144
+ if bad:
145
+ print(f"swarph roster set: unknown role(s) {bad} "
146
+ f"(vocabulary: {', '.join(ROSTER_ROLES)})", file=sys.stderr)
147
+ return 2
148
+ body["roles"] = roles
149
+ if args.quota_pool is not None:
150
+ body["quota_pool"] = args.quota_pool
151
+ if args.quota_used is not None:
152
+ body["quota_used_pct"] = args.quota_used
153
+ if args.resets is not None:
154
+ body["quota_resets_at"] = args.resets
155
+ if args.quota_as_of is not None:
156
+ body["quota_as_of"] = args.quota_as_of
157
+ if args.until is not None:
158
+ body["unavailable_until"] = args.until
159
+ if args.clear_until:
160
+ body["unavailable_until"] = None
161
+ if args.note is not None:
162
+ body["note"] = args.note
163
+ if not body:
164
+ print("swarph roster set: nothing to set (pass a field flag)",
165
+ file=sys.stderr)
166
+ return 2
167
+ gateway = args.gateway.rstrip("/")
168
+ status, payload = _http_put_json(
169
+ f"{gateway}/roster/{args.peer}", body, token)
170
+ if status < 200 or status >= 300:
171
+ detail = payload.get("detail", "<gateway error>") if isinstance(payload, dict) else "<gateway error>"
172
+ print(f"swarph roster set: gateway {status}: {detail}", file=sys.stderr)
173
+ return 1
174
+ if args.json:
175
+ print(json.dumps(payload, indent=2))
176
+ else:
177
+ print(f"updated {args.peer} (updated_by={payload.get('updated_by', '?')})")
178
+ return 0
179
+
180
+
181
+ def _add_global(p):
182
+ p.add_argument("--as", dest="self_name", default=None,
183
+ help="peer identity for token resolution")
184
+ from swarph_cli.gateway_default import env_gateway
185
+ p.add_argument("--gateway", default=env_gateway(),
186
+ help="mesh-gateway base URL")
187
+ p.add_argument("--token-file", default=None,
188
+ help="explicit Bearer [REDACTED] file")
189
+
190
+
191
+ def _build_parser():
192
+ p = argparse.ArgumentParser(
193
+ prog="swarph roster",
194
+ description="Read/write the gateway capacity roster (card #1061).")
195
+ _add_global(p)
196
+ p.add_argument("--peer", default=None,
197
+ help="show only this peer (list mode)")
198
+ p.add_argument("--json", action="store_true",
199
+ help="print the raw gateway payload")
200
+ sub = p.add_subparsers(dest="command")
201
+ s = sub.add_parser("set", help="upsert one roster row (operators only)")
202
+ _add_global(s)
203
+ s.add_argument("peer", help="peer the row belongs to")
204
+ s.add_argument("--json", action="store_true",
205
+ help="print the raw gateway payload")
206
+ s.add_argument("--model", default=None)
207
+ s.add_argument("--effort", default=None)
208
+ s.add_argument("--roles", default=None,
209
+ help="comma-separated subset of " + ",".join(ROSTER_ROLES))
210
+ s.add_argument("--quota-pool", default=None)
211
+ s.add_argument("--quota-used", type=float, default=None,
212
+ help="quota_used_pct (needs --quota-as-of)")
213
+ s.add_argument("--quota-as-of", default=None)
214
+ s.add_argument("--resets", default=None,
215
+ help="quota_resets_at date (needs --quota-as-of)")
216
+ group = s.add_mutually_exclusive_group()
217
+ group.add_argument("--until", default=None,
218
+ help="unavailable_until timestamp")
219
+ group.add_argument("--clear-until", action="store_true",
220
+ help="clear unavailable_until (peer is back)")
221
+ s.add_argument("--note", default=None)
222
+ return p
223
+
224
+
225
+ def run_roster(argv):
226
+ parser = _build_parser()
227
+ args = parser.parse_args(argv)
228
+ try:
229
+ if args.command == "set":
230
+ return _run_set(args)
231
+ if args.command is None:
232
+ return _run_list(args)
233
+ parser.error(f"unknown command: {args.command}")
234
+ except RuntimeError as exc:
235
+ print(f"swarph roster: {exc}", file=sys.stderr)
236
+ return 2
237
+ return 2
@@ -0,0 +1,413 @@
1
+ """``swarph sweep`` — the card-#1064 stuck-work sweep, one daily shot.
2
+
3
+ Finds (a) open/taken rows blocked >=48h on an unmoving dependency and (b)
4
+ approved-or-old open PRs with no obligation row naming them; DMs each
5
+ finding's BLOCKING holder once per 24h (never the waiter) and appends one
6
+ line per finding to the nightly watchtower report file. Never closes or
7
+ reassigns: the only board write is the DM itself (POST /messages).
8
+
9
+ swarph sweep [--dry-run] [--repos R1,R2] [--state-file P] [--report-file P]
10
+ [--as SELF] [--gateway URL] [--token-file F] [--json]
11
+
12
+ --dry-run reads the live board and GitHub, prints the findings, and writes
13
+ nothing (no DMs, no state, no report) — the rehearsal shape.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import os
20
+ import re
21
+ import subprocess
22
+ import sys
23
+ from datetime import datetime, timezone
24
+
25
+ _STUCK_H = 48
26
+ _RESEND_H = 24
27
+
28
+ _DEFAULT_REPOS = (
29
+ "BrainSurfing-tech/swarph-cli",
30
+ "BrainSurfing-tech/mesh-gateway",
31
+ "BrainSurfing-tech/swarph-codegraph-fed",
32
+ )
33
+
34
+ _PR_URL_RE = re.compile(
35
+ r"github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)/pull/(\d+)",
36
+ re.IGNORECASE)
37
+ _PR_NUM_RE = re.compile(r"\bPR\s*#?(\d+)\b", re.IGNORECASE)
38
+
39
+
40
+ def _parse_ts(v):
41
+ """Tolerant ISO parse -> epoch seconds, or None. Never raises: an
42
+ unparseable timestamp is unevaluable, not zero."""
43
+ if not v:
44
+ return None
45
+ try:
46
+ dt = datetime.fromisoformat(str(v).replace("Z", "+00:00"))
47
+ except (TypeError, ValueError):
48
+ return None
49
+ try:
50
+ return dt.timestamp()
51
+ except (TypeError, ValueError, OverflowError, OSError):
52
+ return None
53
+
54
+
55
+ def _age_days(now, ts):
56
+ return round((now - ts) / 86400)
57
+
58
+
59
+ def _row_last_change(row):
60
+ for k in ("closed_at", "taken_at", "created_at"):
61
+ ts = _parse_ts(row.get(k))
62
+ if ts is not None:
63
+ return ts
64
+ return None
65
+
66
+
67
+ def _named_prs(rows):
68
+ """(repo-or-None, number) pairs obligation rows name: full URLs match
69
+ exactly; bare PR numbers match by number across tracked repos."""
70
+ named = set()
71
+ for row in rows:
72
+ if not isinstance(row, dict):
73
+ continue
74
+ blob = " ".join(
75
+ str(row.get(k) or "") for k in ("accept", "close_evidence"))
76
+ for m in _PR_URL_RE.finditer(blob):
77
+ try:
78
+ named.add((m.group(1).lower(), int(m.group(2))))
79
+ except ValueError:
80
+ continue
81
+ for m in _PR_NUM_RE.finditer(blob):
82
+ try:
83
+ named.add((None, int(m.group(1))))
84
+ except ValueError:
85
+ continue
86
+ return named
87
+
88
+
89
+ def _graph_steps(http_get, gateway, token, card_id, cache):
90
+ if card_id in cache:
91
+ return cache[card_id]
92
+ try:
93
+ status, body = http_get(
94
+ f"{gateway}/board/cards/{card_id}/graph", token)
95
+ except Exception:
96
+ return None
97
+ if status != 200 or not isinstance(body, dict):
98
+ return None
99
+ steps = body.get("steps")
100
+ if not isinstance(steps, list):
101
+ return None
102
+ cache[card_id] = steps
103
+ return steps
104
+
105
+
106
+ def _row_findings(rows, by_id, http_get, gateway, token, now):
107
+ """Kind (a): one finding per (open taken waiter, unsatisfied old need)."""
108
+ out = []
109
+ cache = {}
110
+ for w in rows:
111
+ if w.get("status") != "open" or not w.get("taken_at"):
112
+ continue
113
+ step = w.get("step")
114
+ if not step:
115
+ continue
116
+ card_id = w.get("card_id")
117
+ if not isinstance(card_id, int) or isinstance(card_id, bool):
118
+ continue
119
+ steps = _graph_steps(http_get, gateway, token, card_id, cache)
120
+ if steps is None:
121
+ continue
122
+ entry = next((e for e in steps
123
+ if isinstance(e, dict) and e.get("step") == step), None)
124
+ if entry is None:
125
+ continue
126
+ needs = entry.get("needs")
127
+ if not isinstance(needs, list):
128
+ continue
129
+ for need in needs:
130
+ if not isinstance(need, dict):
131
+ continue
132
+ sat = need.get("satisfied")
133
+ if sat is True:
134
+ continue
135
+ if sat is not False:
136
+ continue # unevaluable need names no finding
137
+ rid = need.get("row_id")
138
+ dep = by_id.get(rid)
139
+ if not isinstance(dep, dict):
140
+ continue
141
+ if dep.get("status") == "closed":
142
+ continue # terminal: satisfied or a rework case, not unmoving
143
+ changed = _row_last_change(dep)
144
+ if changed is None or now - changed < _STUCK_H * 3600:
145
+ continue
146
+ holder = dep.get("holder")
147
+ blocker = holder if holder and dep.get("holder_known", True) else None
148
+ out.append({
149
+ "kind": "row",
150
+ "waiter_id": w.get("id"),
151
+ "waiter": w.get("holder"),
152
+ "waiter_step": step,
153
+ "card_id": card_id,
154
+ "need": need.get("step"),
155
+ "need_id": rid,
156
+ "blocker": blocker,
157
+ "age_days": _age_days(now, changed),
158
+ })
159
+ return out
160
+
161
+
162
+ def _pr_findings(named, gh_prs, repos, peers, now):
163
+ """Kind (b): approved-or-old open PRs no row names. An author with no
164
+ mesh peer is report-only — the CLI must not invent a recipient."""
165
+ out = []
166
+ for repo in repos:
167
+ try:
168
+ prs = gh_prs(repo)
169
+ except Exception:
170
+ continue
171
+ if not isinstance(prs, list):
172
+ continue
173
+ for pr in prs:
174
+ if not isinstance(pr, dict):
175
+ continue
176
+ try:
177
+ number = int(pr.get("number"))
178
+ except (TypeError, ValueError):
179
+ continue
180
+ if (repo.lower(), number) in named or (None, number) in named:
181
+ continue
182
+ approved = pr.get("reviewDecision") == "APPROVED"
183
+ created = _parse_ts(pr.get("createdAt"))
184
+ old = created is not None and now - created >= _STUCK_H * 3600
185
+ if not approved and not old:
186
+ continue
187
+ author = pr.get("author") or {}
188
+ login = author.get("login") if isinstance(author, dict) else None
189
+ # blocker names the responsible party even when they have no
190
+ # mesh peer — the SEND step DMs only peers (report-only else).
191
+ out.append({
192
+ "kind": "pr",
193
+ "repo": repo,
194
+ "number": number,
195
+ "title": pr.get("title") or "",
196
+ "url": pr.get("url") or "",
197
+ "approved": approved,
198
+ "age_days": _age_days(now, created) if created else None,
199
+ "blocker": login,
200
+ })
201
+ return out
202
+
203
+
204
+ def _finding_key(f):
205
+ if f["kind"] == "row":
206
+ return f"row:{f['waiter_id']}:{f['need']}"
207
+ return f"pr:{f['repo']}#{f['number']}"
208
+
209
+
210
+ def _report_line(now_iso, f):
211
+ if f["kind"] == "row":
212
+ return (f"{now_iso} STUCK row waiter=#{f['waiter_id']}({f['waiter']}) "
213
+ f"step={f['waiter_step']} card=#{f['card_id']} "
214
+ f"blocked-by=#{f['need_id']}({f['blocker']}) "
215
+ f"need={f['need']} need-age={f['age_days']}d")
216
+ how = "approved" if f["approved"] else f"age={f['age_days']}d"
217
+ return (f"{now_iso} STUCK pr {f['repo']}#{f['number']} "
218
+ f"'{f['title']}' ({how}, no review row) author={f['blocker']}")
219
+
220
+
221
+ def _dm_content(f):
222
+ if f["kind"] == "row":
223
+ return (f"STUCK-WORK SWEEP: row #{f['waiter_id']} ({f['waiter']}, "
224
+ f"{f['waiter_step']}, card #{f['card_id']}) has waited on "
225
+ f"your row #{f['need_id']} ({f['need']}, unchanged "
226
+ f"{f['age_days']}d). Please move it, fail it, or say what "
227
+ f"it waits on.")
228
+ state = "approved" if f["approved"] else "open %sd" % (f['age_days'],)
229
+ return (f"STUCK-WORK SWEEP: your PR {f['repo']}#{f['number']} "
230
+ f"'{f['title']}' ({f['url']}) is {state} "
231
+ f"with no review row naming it. Please land it or ask for review.")
232
+
233
+
234
+ def run_sweep(*, gateway, token, sender, repos, state_file, report_file,
235
+ now, http_get, http_post, gh_prs, dry_run=False):
236
+ """One sweep. Returns (rc, findings): rc 1 only when the board itself is
237
+ unreadable (nothing evaluable); findings are the SENT (or dry-run, would
238
+ send) findings. Only ever writes DMs + the two local files."""
239
+ gateway = gateway.rstrip("/")
240
+ try:
241
+ status, body = http_get(f"{gateway}/board/obligations", token)
242
+ except Exception:
243
+ status, body = None, None
244
+ if status != 200 or not isinstance(body, dict) or not isinstance(
245
+ body.get("obligations"), list):
246
+ print("swarph sweep: board unreadable — no findings, nothing sent",
247
+ file=sys.stderr)
248
+ return 1, []
249
+ rows = [r for r in body["obligations"] if isinstance(r, dict)]
250
+ by_id = {r.get("id"): r for r in rows
251
+ if isinstance(r.get("id"), int)}
252
+ try:
253
+ pstatus, pbody = http_get(f"{gateway}/peers", token)
254
+ peers = {p["name"] for p in pbody.get("peers", [])
255
+ if isinstance(p, dict) and p.get("name")}
256
+ if pstatus != 200:
257
+ peers = set()
258
+ except Exception:
259
+ peers = set()
260
+
261
+ findings = _row_findings(rows, by_id, http_get, gateway, token, now)
262
+ findings += _pr_findings(_named_prs(rows), gh_prs, repos, peers, now)
263
+
264
+ try:
265
+ with open(state_file, encoding="utf-8") as fp:
266
+ sent = json.load(fp)
267
+ sent = sent if isinstance(sent, dict) else {}
268
+ except (OSError, ValueError):
269
+ sent = {}
270
+ now_iso = datetime.fromtimestamp(now, timezone.utc).isoformat()
271
+ fresh, updates = [], {}
272
+ for f in findings:
273
+ key = _finding_key(f)
274
+ try:
275
+ last = _parse_ts(sent.get(key))
276
+ except Exception:
277
+ last = None
278
+ if last is not None and now - last < _RESEND_H * 3600:
279
+ continue
280
+ fresh.append(f)
281
+ updates[key] = now_iso
282
+
283
+ if dry_run:
284
+ for f in fresh:
285
+ print("DRY " + _report_line(now_iso, f))
286
+ return 0, fresh
287
+
288
+ for f in fresh:
289
+ # DM only a blocker with a mesh peer: pinging the waiter instead is
290
+ # the FAIL, and inventing a recipient for a gh login is its cousin.
291
+ if f["blocker"] is not None and f["blocker"] in peers:
292
+ status, payload = http_post(
293
+ f"{gateway}/messages",
294
+ {"from_node": sender, "to_node": f["blocker"],
295
+ "kind": "fyi", "content": _dm_content(f)},
296
+ token)
297
+ if status < 200 or status >= 300:
298
+ detail = (payload.get("detail", "<gateway error>")
299
+ if isinstance(payload, dict) else "<gateway error>")
300
+ print(f"swarph sweep: DM to {f['blocker']} failed "
301
+ f"{status}: {detail}", file=sys.stderr)
302
+ continue
303
+ line = _report_line(now_iso, f)
304
+ print(line)
305
+ try:
306
+ with open(report_file, "a", encoding="utf-8") as fp:
307
+ fp.write(line + "\n")
308
+ except OSError as exc:
309
+ print(f"swarph sweep: report append failed: {exc}",
310
+ file=sys.stderr)
311
+ if updates:
312
+ sent.update(updates)
313
+ try:
314
+ with open(state_file, "w", encoding="utf-8") as fp:
315
+ json.dump(sent, fp)
316
+ except OSError as exc:
317
+ print(f"swarph sweep: state write failed: {exc}", file=sys.stderr)
318
+ return 0, fresh
319
+
320
+
321
+ def _gh_prs(repo):
322
+ """Live gh transport: open PRs with the fields the matcher needs, or
323
+ None when gh is unusable (that repo's findings are skipped, not
324
+ invented)."""
325
+ try:
326
+ p = subprocess.run(
327
+ ["gh", "pr", "list", "--repo", repo, "--state", "open",
328
+ "--limit", "100", "--json",
329
+ "number,title,createdAt,reviewDecision,url,author"],
330
+ capture_output=True, text=True, encoding="utf-8",
331
+ errors="replace", timeout=60)
332
+ except (OSError, subprocess.SubprocessError):
333
+ return None
334
+ if p.returncode != 0:
335
+ return None
336
+ try:
337
+ data = json.loads(p.stdout or "[]")
338
+ except ValueError:
339
+ return None
340
+ return data if isinstance(data, list) else None
341
+
342
+
343
+ def _default_state_file():
344
+ xdg = os.environ.get("XDG_STATE_HOME", "").strip()
345
+ root = xdg if xdg else os.path.join(os.path.expanduser("~"), ".local", "state")
346
+ return os.path.join(root, "swarph", "stuck-sweep.json")
347
+
348
+
349
+ def _build_parser():
350
+ p = argparse.ArgumentParser(
351
+ prog="swarph sweep",
352
+ description="Daily stuck-work sweep: blocked rows + unreviewed PRs "
353
+ "(card #1064). DMs blockers, never closes/reassigns.")
354
+ p.add_argument("--dry-run", action="store_true",
355
+ help="print findings; send/record/write nothing")
356
+ p.add_argument("--repos", default=None,
357
+ help="comma-separated owner/repo list (default "
358
+ "$SWARPH_SWEEP_REPOS or the fleet three)")
359
+ p.add_argument("--state-file", default=None)
360
+ p.add_argument("--report-file", default=None,
361
+ help="nightly watchtower report path "
362
+ "(default $SWARPH_SWEEP_REPORT or state-adjacent log)")
363
+ p.add_argument("--json", action="store_true")
364
+ p.add_argument("--as", dest="self_name", default=None,
365
+ help="DM sender identity")
366
+ from swarph_cli.gateway_default import env_gateway
367
+ p.add_argument("--gateway", default=env_gateway())
368
+ p.add_argument("--token-file", default=None)
369
+ return p
370
+
371
+
372
+ def run_sweep_cmd(argv):
373
+ import time as _time
374
+ from swarph_cli.commands import mesh as _mesh
375
+ from swarph_cli.tokens import resolve_token
376
+ args = _build_parser().parse_args(argv)
377
+ try:
378
+ # #872 doctrine: an automated DM must name its sender. Dry-run sends
379
+ # nothing and needs no identity.
380
+ if not args.self_name and not args.dry_run:
381
+ print("swarph sweep: --as a sender is required (automated DMs "
382
+ "must name their sender; --dry-run needs no identity)",
383
+ file=sys.stderr)
384
+ return 2
385
+ resolution = resolve_token(
386
+ args.self_name, args.token_file,
387
+ identity_is_explicit=bool(args.self_name))
388
+ if resolution is None:
389
+ print("swarph sweep: no token (set MESH_GATEWAY_TOKEN, pass "
390
+ "--token-file, or --as a sender)", file=sys.stderr)
391
+ return 2
392
+ sender = args.self_name or "(dry-run)"
393
+ repos = ([r.strip() for r in args.repos.split(",") if r.strip()]
394
+ if args.repos else
395
+ [r.strip() for r in os.environ.get(
396
+ "SWARPH_SWEEP_REPOS", ",".join(_DEFAULT_REPOS)
397
+ ).split(",") if r.strip()])
398
+ state_file = args.state_file or os.environ.get(
399
+ "SWARPH_SWEEP_STATE", _default_state_file())
400
+ report_file = (args.report_file or os.environ.get("SWARPH_SWEEP_REPORT")
401
+ or os.path.splitext(state_file)[0] + ".log")
402
+ rc, findings = run_sweep(
403
+ gateway=args.gateway, token=resolution.token, sender=sender,
404
+ repos=repos, state_file=state_file, report_file=report_file,
405
+ now=_time.time(), http_get=_mesh._http_get_json,
406
+ http_post=_mesh._post_json, gh_prs=_gh_prs,
407
+ dry_run=args.dry_run)
408
+ if args.json:
409
+ print(json.dumps(findings, indent=2))
410
+ return rc
411
+ except RuntimeError as exc:
412
+ print(f"swarph sweep: {exc}", file=sys.stderr)
413
+ return 2