swarph-cli 0.72.9__tar.gz → 0.72.11__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 (487) hide show
  1. {swarph_cli-0.72.9/src/swarph_cli.egg-info → swarph_cli-0.72.11}/PKG-INFO +1 -1
  2. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/pyproject.toml +1 -1
  3. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/channel.py +64 -4
  5. swarph_cli-0.72.11/src/swarph_cli/commands/sweep.py +413 -0
  6. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/guide/GUIDE.md +1 -0
  7. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/main.py +1 -0
  8. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/dm_notify_filter.py +112 -8
  9. swarph_cli-0.72.11/src/swarph_cli/systemd/swarph-stuck-sweep.service +15 -0
  10. swarph_cli-0.72.11/src/swarph_cli/systemd/swarph-stuck-sweep.timer +14 -0
  11. {swarph_cli-0.72.9 → swarph_cli-0.72.11/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  12. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
  13. swarph_cli-0.72.11/tests/test_1064_stuck_sweep.py +332 -0
  14. swarph_cli-0.72.11/tests/test_1065_release.py +396 -0
  15. swarph_cli-0.72.11/tests/test_1066_quiet_wakes.py +306 -0
  16. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/LICENSE +0 -0
  17. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/README.md +0 -0
  18. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/setup.cfg +0 -0
  19. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/__main__.py +0 -0
  20. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/__init__.py +0 -0
  21. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/backends.py +0 -0
  22. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/claude_cli.py +0 -0
  23. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/codex_cli.py +0 -0
  24. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/pack.py +0 -0
  25. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/prices.py +0 -0
  26. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/providers.py +0 -0
  27. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/quality.py +0 -0
  28. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/refresh_prices.py +0 -0
  29. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/rules.py +0 -0
  30. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/runner.py +0 -0
  31. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/signal_score.py +0 -0
  32. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/validate.py +0 -0
  33. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/caller.py +0 -0
  34. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/__init__.py +0 -0
  35. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/harden.py +0 -0
  36. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/lineage.py +0 -0
  37. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/liveness.py +0 -0
  38. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/manifest.py +0 -0
  39. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/paths.py +0 -0
  40. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/verify.py +0 -0
  41. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/cell.py +0 -0
  42. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/chain_token.py +0 -0
  43. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/__init__.py +0 -0
  44. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_content.py +0 -0
  45. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_display.py +0 -0
  46. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_gateway_client.py +0 -0
  47. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/add.py +0 -0
  48. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/bench.py +0 -0
  49. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/board.py +0 -0
  50. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/board_merge_check.py +0 -0
  51. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/brain.py +0 -0
  52. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/brain_ask.py +0 -0
  53. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell.py +0 -0
  54. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell_probe.py +0 -0
  55. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  56. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/channel.py +0 -0
  57. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/chat.py +0 -0
  58. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codegraph.py +0 -0
  59. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  60. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  61. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codex_waker.py +0 -0
  62. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/compress.py +0 -0
  63. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/daemon.py +0 -0
  64. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/dreaming.py +0 -0
  65. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/event.py +0 -0
  66. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/gateway.py +0 -0
  67. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/gh_route.py +0 -0
  68. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/group.py +0 -0
  69. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/guide.py +0 -0
  70. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/highlight.py +0 -0
  71. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  72. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hook_output.py +0 -0
  73. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hooks.py +0 -0
  74. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/import_session.py +0 -0
  75. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/init.py +0 -0
  76. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  77. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_hook.py +0 -0
  78. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  79. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  80. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  81. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  82. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/lane.py +0 -0
  83. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/mcp_server.py +0 -0
  84. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory.py +0 -0
  85. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  86. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory_sync.py +0 -0
  87. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/mesh.py +0 -0
  88. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/monitor.py +0 -0
  89. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/okf_links.py +0 -0
  90. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/onboard.py +0 -0
  91. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  92. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  93. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/protocol_handler.py +0 -0
  94. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/ratify.py +0 -0
  95. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/roster.py +0 -0
  96. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/rowclear_hook.py +0 -0
  97. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/schedule.py +0 -0
  98. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/security.py +0 -0
  99. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/service.py +0 -0
  100. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/spawn.py +0 -0
  101. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/timeline.py +0 -0
  102. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/version.py +0 -0
  103. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  104. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/watchdog.py +0 -0
  105. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/__init__.py +0 -0
  106. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/levers.py +0 -0
  107. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/marker.py +0 -0
  108. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/verify.py +0 -0
  109. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/console_safe.py +0 -0
  110. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/delivery_queue.py +0 -0
  111. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dm_frame.py +0 -0
  112. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/__init__.py +0 -0
  113. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/candidates.py +0 -0
  114. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/clone.py +0 -0
  115. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/enrich.py +0 -0
  116. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/index_check.py +0 -0
  117. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/notify.py +0 -0
  118. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/organize.py +0 -0
  119. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/probes.py +0 -0
  120. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/report.py +0 -0
  121. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/run.py +0 -0
  122. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/slm.py +0 -0
  123. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/transcripts.py +0 -0
  124. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/verify.py +0 -0
  125. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/__init__.py +0 -0
  126. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/feature_registry.py +0 -0
  127. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/lanes_control.py +0 -0
  128. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/schema.sql +0 -0
  129. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/server.py +0 -0
  130. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/services_control.py +0 -0
  131. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway_default.py +0 -0
  132. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gh_identity.py +0 -0
  133. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/identity.py +0 -0
  134. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/multiplexer.py +0 -0
  135. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/__init__.py +0 -0
  136. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/__main__.py +0 -0
  137. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/api.py +0 -0
  138. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/cli.py +0 -0
  139. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/config.py +0 -0
  140. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/fmt.py +0 -0
  141. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/orphan_daemons.py +0 -0
  142. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/pane_probe.py +0 -0
  143. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/parsers/__init__.py +0 -0
  144. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/parsers/claude.py +0 -0
  145. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  146. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  147. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  148. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  149. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  150. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  151. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  152. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  153. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  154. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  155. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  156. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  157. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_executor.py +0 -0
  158. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_executor_windows.py +0 -0
  159. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_reconciliation.py +0 -0
  160. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_reply_outbox.py +0 -0
  161. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_host.py +0 -0
  162. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_payload.py +0 -0
  163. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_reply.py +0 -0
  164. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_staging.py +0 -0
  165. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/query_filters.py +0 -0
  166. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/rowclear.py +0 -0
  167. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/__init__.py +0 -0
  168. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  169. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  170. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  171. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  172. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/__init__.py +0 -0
  173. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/app.py +0 -0
  174. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/providers.py +0 -0
  175. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/session_bridge.py +0 -0
  176. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  177. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  178. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/source_text.py +0 -0
  179. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/stall_alert.py +0 -0
  180. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/codex-waker.default +0 -0
  181. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  182. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  183. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  184. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  185. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  186. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  187. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  188. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  189. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  190. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  191. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  192. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  193. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  194. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/timeline_paths.py +0 -0
  195. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/tokens.py +0 -0
  196. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  197. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  198. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/requires.txt +0 -0
  199. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/top_level.txt +0 -0
  200. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1006_board_publisher.py +0 -0
  201. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1006_session_board.py +0 -0
  202. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1010_strand_only.py +0 -0
  203. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1013_spawn_channel.py +0 -0
  204. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1017_muse_tui.py +0 -0
  205. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1050_surrogate_wake.py +0 -0
  206. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1052_opencode_deaf.py +0 -0
  207. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1059_watchdog_unread_env.py +0 -0
  208. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1061_roster_cli.py +0 -0
  209. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_124_register_merge.py +0 -0
  210. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_126_pending_from_complete.py +0 -0
  211. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  212. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_138_memory_navigate.py +0 -0
  213. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_139_tool_probe.py +0 -0
  214. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_183_card_anchor.py +0 -0
  215. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_184_notify_honesty.py +0 -0
  216. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_201_win32_bash_refusal.py +0 -0
  217. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_243_onboard_per_peer_token.py +0 -0
  218. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_307_task4_ask_reply.py +0 -0
  219. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  220. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_319_refusal_exit_codes.py +0 -0
  221. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_323_mistral_backend.py +0 -0
  222. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_332_report_organize_findings.py +0 -0
  223. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_356_query_filters.py +0 -0
  224. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_397_gh_identity_router.py +0 -0
  225. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_397_router_preflight.py +0 -0
  226. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_402_one_identity_resolver.py +0 -0
  227. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_432_rowclear.py +0 -0
  228. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_467b_onboard_operator_credential.py +0 -0
  229. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_472_register_caller_binding.py +0 -0
  230. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_484_codex_queue_sink.py +0 -0
  231. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_523_guide.py +0 -0
  232. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_524_conftest_hermeticity.py +0 -0
  233. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_532_accept_flag.py +0 -0
  234. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_535_version_capability.py +0 -0
  235. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_538_signal_bench.py +0 -0
  236. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_546_default_gateway.py +0 -0
  237. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_547_verbs_from_registry.py +0 -0
  238. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_548_gateway_remainder.py +0 -0
  239. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_552_codegraph_gateway.py +0 -0
  240. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_555_relay_row_shape.py +0 -0
  241. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_556_relay_freshness.py +0 -0
  242. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_559_edges_truncated.py +0 -0
  243. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_564_bootstrap_first_peer.py +0 -0
  244. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  245. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_579_register_bootstrap_message.py +0 -0
  246. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_591_cli_step_graph.py +0 -0
  247. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_644_monitor_supervision.py +0 -0
  248. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_648_install_unit.py +0 -0
  249. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_654_reexec.py +0 -0
  250. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_656_owned_slm.py +0 -0
  251. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_684_dreaming_verb.py +0 -0
  252. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_689_pkg_version_order.py +0 -0
  253. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_715_grok_wake.py +0 -0
  254. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_716_timeline_one_path.py +0 -0
  255. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_723_grok_0251.py +0 -0
  256. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  257. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_725_stdio_reconfigure.py +0 -0
  258. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_726_grok_limit_screen.py +0 -0
  259. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_arm_check_delivery_gap.py +0 -0
  260. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_dm_once.py +0 -0
  261. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_wake_watchdog.py +0 -0
  262. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  263. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_735_dm_mcp_tools.py +0 -0
  264. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_739_wake_writer.py +0 -0
  265. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_740_edit_project.py +0 -0
  266. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_762_cards_edit_priority.py +0 -0
  267. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_764_enrich_collapse.py +0 -0
  268. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_801_confirm_flow_cli.py +0 -0
  269. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_802_stage_history.py +0 -0
  270. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_codegraph_hook_noise.py +0 -0
  271. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_install_posttooluse_only.py +0 -0
  272. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  273. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_829_counterfactual_column.py +0 -0
  274. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_830_hook_binding_version.py +0 -0
  275. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_84_cli_labels.py +0 -0
  276. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_866_doctrine_guide.py +0 -0
  277. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_872_identity_refuses_not_warns.py +0 -0
  278. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_874_source_text.py +0 -0
  279. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  280. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_937_dreaming_notify.py +0 -0
  281. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_938_organize_line_limit.py +0 -0
  282. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_941_bench_backends.py +0 -0
  283. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_941_harness_342.py +0 -0
  284. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_945_same_line_pairing.py +0 -0
  285. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_955_prompt_audit.py +0 -0
  286. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_957_auth_http.py +0 -0
  287. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_959_venv_interpreter.py +0 -0
  288. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_960_channel.py +0 -0
  289. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  290. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_961_cursor_grok_footer.py +0 -0
  291. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_961_loud_stop.py +0 -0
  292. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_963_plugin_manifest.py +0 -0
  293. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_964_channel_cell.py +0 -0
  294. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_965_mesh_wait.py +0 -0
  295. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_975_channel_hop.py +0 -0
  296. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_add_security_gate.py +0 -0
  297. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_add.py +0 -0
  298. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_hash.py +0 -0
  299. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_lib.py +0 -0
  300. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_mcp.py +0 -0
  301. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_skill.py +0 -0
  302. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_tool.py +0 -0
  303. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_uri.py +0 -0
  304. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_backends.py +0 -0
  305. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_claude_cli.py +0 -0
  306. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_codex_cli.py +0 -0
  307. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_command.py +0 -0
  308. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_pack.py +0 -0
  309. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_prices.py +0 -0
  310. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_quality.py +0 -0
  311. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_runner.py +0 -0
  312. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_validate.py +0 -0
  313. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_card_thread.py +0 -0
  314. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_command.py +0 -0
  315. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_merge_check.py +0 -0
  316. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_obligation_close.py +0 -0
  317. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_command.py +0 -0
  318. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_env_diagnosis.py +0 -0
  319. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_identity_explicitness.py +0 -0
  320. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_command.py +0 -0
  321. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_caller_meta_guard.py +0 -0
  322. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_lineage.py +0 -0
  323. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_liveness.py +0 -0
  324. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_manifest.py +0 -0
  325. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_paths.py +0 -0
  326. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_security.py +0 -0
  327. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_command_dispatch.py +0 -0
  328. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_harden.py +0 -0
  329. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_loader.py +0 -0
  330. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_selfcheck.py +0 -0
  331. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_verify.py +0 -0
  332. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_chain_token.py +0 -0
  333. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_command.py +0 -0
  334. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  335. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_post.py +0 -0
  336. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_wake_policy_filter.py +0 -0
  337. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_chat_command.py +0 -0
  338. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_claude_parser.py +0 -0
  339. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_claude_tmux_template.py +0 -0
  340. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_caller_scope.py +0 -0
  341. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_command.py +0 -0
  342. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_hook.py +0 -0
  343. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_mcp.py +0 -0
  344. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codex_waker.py +0 -0
  345. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_caller_convention.py +0 -0
  346. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_command.py +0 -0
  347. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_levers.py +0 -0
  348. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_marker.py +0 -0
  349. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_verify.py +0 -0
  350. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_console_encoding_e2e.py +0 -0
  351. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_content_composition.py +0 -0
  352. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cursor_membrane.py +0 -0
  353. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_command.py +0 -0
  354. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_console_encoding.py +0 -0
  355. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_delivery.py +0 -0
  356. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_delivery_queue.py +0 -0
  357. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_display_sanitize.py +0 -0
  358. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  359. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_event_emit.py +0 -0
  360. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_feature_to_uri.py +0 -0
  361. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_brain.py +0 -0
  362. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_client.py +0 -0
  363. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_command.py +0 -0
  364. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_message_idempotency.py +0 -0
  365. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  366. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_group_command.py +0 -0
  367. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_guide_commands_parse.py +0 -0
  368. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_guide_selfcheck.py +0 -0
  369. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_heartbeat_check.py +0 -0
  370. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_highlight_command.py +0 -0
  371. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hook_output.py +0 -0
  372. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_activity_marker.py +0 -0
  373. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_add.py +0 -0
  374. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_bundle.py +0 -0
  375. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_init.py +0 -0
  376. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_legacy_generation_removal.py +0 -0
  377. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_lifecycle.py +0 -0
  378. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_merge.py +0 -0
  379. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_verify.py +0 -0
  380. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  381. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_win32_command_path.py +0 -0
  382. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_import_command.py +0 -0
  383. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_inbox_identity_provenance.py +0 -0
  384. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_init_command.py +0 -0
  385. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_init_provider_menu.py +0 -0
  386. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_codex_hooks.py +0 -0
  387. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_hook.py +0 -0
  388. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_multiplexer_command.py +0 -0
  389. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_postcompact_hook.py +0 -0
  390. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_wake_hook.py +0 -0
  391. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_lane_command.py +0 -0
  392. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_main.py +0 -0
  393. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_main_unknown_verb_guard.py +0 -0
  394. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mcp_missing_hint.py +0 -0
  395. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mcp_server.py +0 -0
  396. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_command.py +0 -0
  397. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_emit_snippet.py +0 -0
  398. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_gateway.py +0 -0
  399. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_sync.py +0 -0
  400. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_sync_membrane.py +0 -0
  401. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_command.py +0 -0
  402. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_inbox_mark_read.py +0 -0
  403. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_inbox_sanitize.py +0 -0
  404. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_originate_thread_623.py +0 -0
  405. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_send_unknown_peer.py +0 -0
  406. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar.py +0 -0
  407. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  408. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  409. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  410. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_meta_edge_identity.py +0 -0
  411. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_channel_poll.py +0 -0
  412. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_cli.py +0 -0
  413. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_daemon_collision.py +0 -0
  414. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_self_name_verification.py +0 -0
  415. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_state_split.py +0 -0
  416. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_multiplexer.py +0 -0
  417. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_muse_membrane.py +0 -0
  418. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_muse_own_cli.py +0 -0
  419. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_obligation_close_is_surfaced.py +0 -0
  420. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_okf_links.py +0 -0
  421. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_onboard_command.py +0 -0
  422. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_opencode_membrane.py +0 -0
  423. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_opencode_plugin.py +0 -0
  424. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_operator_me.py +0 -0
  425. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_orphan_daemons.py +0 -0
  426. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_packaged_artifacts_exist.py +0 -0
  427. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_packaged_skills.py +0 -0
  428. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_executor.py +0 -0
  429. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_executor_windows.py +0 -0
  430. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_drain_command.py +0 -0
  431. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_drain_supervisor.py +0 -0
  432. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_outbox.py +0 -0
  433. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_transport.py +0 -0
  434. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_e2e.py +0 -0
  435. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_host.py +0 -0
  436. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_payload.py +0 -0
  437. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_reply.py +0 -0
  438. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_postcompact_recall.py +0 -0
  439. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_process_liveness.py +0 -0
  440. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_protocol_handler.py +0 -0
  441. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_providers_isolation.py +0 -0
  442. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_ratify_command.py +0 -0
  443. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_readme_claims_no_gateway_default.py +0 -0
  444. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_repl_caller_convention.py +0 -0
  445. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_schedule_command.py +0 -0
  446. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_schedule_create_contract.py +0 -0
  447. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_security.py +0 -0
  448. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_app.py +0 -0
  449. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_command.py +0 -0
  450. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_providers.py +0 -0
  451. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_inject.py +0 -0
  452. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_probe.py +0 -0
  453. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_resolve.py +0 -0
  454. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_path_role_gate.py +0 -0
  455. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_shipped_units_match_the_cli.py +0 -0
  456. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_chat.py +0 -0
  457. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_one_shot.py +0 -0
  458. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_phase_5_5.py +0 -0
  459. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_base_hoist.py +0 -0
  460. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_command.py +0 -0
  461. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_identity_stamp.py +0 -0
  462. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_live_pin.py +0 -0
  463. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_mitosis_lineage.py +0 -0
  464. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_tmux_session.py +0 -0
  465. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_windows_relaunch.py +0 -0
  466. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_stall_alert.py +0 -0
  467. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_subprocess_decode_encoding.py +0 -0
  468. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_timeline_command.py +0 -0
  469. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_runstate_defer.py +0 -0
  470. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_session_reanchor.py +0 -0
  471. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_stale.py +0 -0
  472. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_submit_verify.py +0 -0
  473. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_token_file_one_parser.py +0 -0
  474. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_token_precedence_explicit_first.py +0 -0
  475. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_unified_token_resolver.py +0 -0
  476. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_version_command.py +0 -0
  477. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_version_consistency.py +0 -0
  478. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_vibe_membrane.py +0 -0
  479. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog.py +0 -0
  480. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake.py +0 -0
  481. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  482. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  483. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_emit_health.py +0 -0
  484. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_install_flags.py +0 -0
  485. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_model_rung.py +0 -0
  486. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_stale_peers.py +0 -0
  487. {swarph_cli-0.72.9 → swarph_cli-0.72.11}/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.9
3
+ Version: 0.72.11
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.9"
7
+ version = "0.72.11"
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.9"
19
+ __version__ = "0.72.11"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -15,7 +15,12 @@ from pathlib import Path
15
15
 
16
16
  from swarph_cli import identity
17
17
  from swarph_cli.dm_frame import FIRST_START_GRACE_S, created_within, frame_text, rows
18
- from swarph_cli.scripts.dm_notify_filter import is_real_dm
18
+ from swarph_cli.scripts.dm_notify_filter import (
19
+ _QUIET_CAP_S,
20
+ is_quiet_dm,
21
+ is_real_dm,
22
+ quiet_enabled,
23
+ )
19
24
 
20
25
  PROTOCOL_VERSION = "2025-06-18"
21
26
  METHOD = "notifications/claude/channel"
@@ -127,8 +132,15 @@ class Channel:
127
132
  First start keeps a present row when created_at is within 30s before
128
133
  this poll or any time after, via dm_frame.created_within. Older rows
129
134
  set the cursor and are not pushed.
135
+
136
+ Card #1066: with quiet wakes enabled, TAKEN receipts and repo.merged
137
+ events are HELD, not pushed — carried with the next real push or
138
+ once each passes the cap. The hold is durable: held ids persist in
139
+ the cursor file, so a restart neither loses nor double-pushes them.
140
+ A held id missing from the inbox (rotated away) is dropped.
130
141
  """
131
142
  now = time.time() if now is None else now
143
+ quiet_on = quiet_enabled()
132
144
  notes: list[dict] = []
133
145
  if not self.inbox.is_file():
134
146
  self._heartbeat(now, None)
@@ -153,6 +165,8 @@ class Channel:
153
165
  "offset": 0,
154
166
  "anomaly_sent": False,
155
167
  }
168
+ if quiet_on:
169
+ self.cursor["held_quiet"] = []
156
170
  self._save()
157
171
  notes.append(_note(self.armed_line()))
158
172
  if self.cursor.get("inode") != st.st_ino:
@@ -162,6 +176,7 @@ class Channel:
162
176
  fresh = _rows(text[offset:])
163
177
  last = self.cursor.get("last_pushed_id")
164
178
  last_i = int(last) if last is not None else -1
179
+ held = self._held_entries()
165
180
  pending = []
166
181
  for row in fresh:
167
182
  if row.get("to_node") != self.cell:
@@ -175,30 +190,75 @@ class Channel:
175
190
  continue
176
191
  if rid <= last_i:
177
192
  continue
193
+ if quiet_on and is_quiet_dm(row):
194
+ if all(h["id"] != rid for h in held):
195
+ held.append({"id": rid, "at": now})
196
+ continue
178
197
  pending.append(row)
179
198
  if self.foreign and not self.anomaly_sent:
180
199
  notes.append(_note(
181
200
  f"{self.foreign} inbox rows dropped: to_node was not {self.cell}"))
182
201
  self.anomaly_sent = True
183
202
  self.cursor["anomaly_sent"] = True
203
+ # Card #1066 carry + cap: held rows flush with the first real push
204
+ # (held first — they arrived earlier) or once each passes the cap.
205
+ # Held rows are NEVER backlog-truncated: dropping a waited-on row
206
+ # would lose it, the exact FAIL this card names.
207
+ by_id = {}
208
+ for row in rows:
209
+ try:
210
+ by_id[int(row["id"])] = row
211
+ except (KeyError, TypeError, ValueError):
212
+ continue
213
+ carried, kept = [], []
214
+ for h in held:
215
+ if h["id"] not in by_id:
216
+ continue # rotated out of the inbox: undeliverable, drop
217
+ if pending or now - h["at"] >= _QUIET_CAP_S:
218
+ carried.append(by_id[h["id"]])
219
+ else:
220
+ kept.append(h)
184
221
  if len(pending) > BACKLOG:
185
222
  skipped = len(pending) - BACKLOG
186
223
  pending = pending[-BACKLOG:]
187
224
  notes.append(_note(f"{skipped} older DMs not pushed, read the inbox"))
188
- for row in pending:
225
+ for row in carried + pending:
189
226
  notes.append(_frame(row))
190
227
  self.cursor["last_pushed_id"] = int(row["id"])
228
+ # Byte-identical cursors when quiet is off: never introduce the key
229
+ # into a cursor that never had it.
230
+ if quiet_on or "held_quiet" in self.cursor:
231
+ self.cursor["held_quiet"] = kept
191
232
  self.cursor["offset"] = st.st_size
192
233
  self.cursor["inode"] = st.st_ino
193
234
  self._save()
194
- if newest is not None and self.cursor.get("last_pushed_id") is not None:
195
- if int(newest) > int(self.cursor["last_pushed_id"]):
235
+ covered = self.cursor.get("last_pushed_id")
236
+ for h in kept:
237
+ if covered is None or h["id"] > covered:
238
+ covered = h["id"]
239
+ if newest is not None and covered is not None:
240
+ if int(newest) > int(covered):
196
241
  self.stall_since = self.stall_since or now
197
242
  else:
198
243
  self.stall_since = None
199
244
  self._heartbeat(now, newest)
200
245
  return notes
201
246
 
247
+ def _held_entries(self) -> list:
248
+ """Durable hold, validated: a corrupt held_quiet resets, never kills
249
+ the poll (a dead channel from a bad cursor is worse than a re-push —
250
+ re-read rows re-hold by id)."""
251
+ raw = (self.cursor or {}).get("held_quiet") or []
252
+ out = []
253
+ for h in raw:
254
+ try:
255
+ if not isinstance(h, dict):
256
+ continue
257
+ out.append({"id": int(h["id"]), "at": float(h["at"])})
258
+ except (KeyError, TypeError, ValueError):
259
+ continue
260
+ return out
261
+
202
262
  def _save(self) -> None:
203
263
  self.cursor_path.parent.mkdir(parents=True, exist_ok=True)
204
264
  self.cursor_path.write_text(json.dumps(self.cursor), encoding="utf-8")
@@ -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
@@ -549,6 +549,7 @@ answers `--help`; hook-plumbing verbs are the callbacks hooks invoke, not comman
549
549
  - `swarph schedule` -- the gateway's scheduled events: create, list, get, enable, disable, delete, fire-now.
550
550
  - `swarph service` -- stand up a $0 subscription-LLM HTTP lane.
551
551
  - `swarph spawn` -- run a cell from its role name or yaml path.
552
+ - `swarph sweep` -- the card-#1064 stuck-work sweep: blocked rows and unreviewed PRs, DMs the blocker, `swarph sweep --dry-run` reads only.
552
553
  - `swarph timeline` -- deterministic temporal lookup over the timeline: around, since, range (see [Code and history](#code-and-history)).
553
554
  - `swarph version` -- per-module versions and install origin (PEP 610).
554
555
  - `swarph wake-hook-output` -- hook plumbing: the silent-wake SessionStart callback.
@@ -111,6 +111,7 @@ _VERB_HANDLERS: dict[str, str] = {
111
111
  "group": "swarph_cli.commands.group.run_group",
112
112
  "rights": "swarph_cli.commands.group.run_rights",
113
113
  "roster": "swarph_cli.commands.roster.run_roster",
114
+ "sweep": "swarph_cli.commands.sweep.run_sweep_cmd",
114
115
  "mcp-server": "swarph_cli.commands.mcp_server.run_mcp_server",
115
116
  "compress": "swarph_cli.commands.compress.run_compress",
116
117
  "cell": "swarph_cli.commands.cell.run_cell",