swarph-cli 0.70.0__tar.gz → 0.71.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. {swarph_cli-0.70.0/src/swarph_cli.egg-info → swarph_cli-0.71.0}/PKG-INFO +1 -1
  2. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/pyproject.toml +4 -1
  3. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/backends.py +9 -0
  5. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/runner.py +23 -2
  6. swarph_cli-0.71.0/src/swarph_cli/bench/signal_score.py +110 -0
  7. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/validate.py +63 -0
  8. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/paths.py +11 -0
  9. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/bench.py +30 -0
  10. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/board.py +68 -1
  11. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/codegraph_hook.py +12 -1
  12. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/hooks.py +50 -13
  13. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_wake_hook.py +8 -7
  14. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/mesh.py +138 -1
  15. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/monitor.py +90 -1
  16. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/wake_hook_output.py +64 -5
  17. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/guide/GUIDE.md +3 -2
  18. swarph_cli-0.71.0/src/swarph_cli/operator/__init__.py +11 -0
  19. swarph_cli-0.71.0/src/swarph_cli/operator/__main__.py +4 -0
  20. swarph_cli-0.71.0/src/swarph_cli/operator/api.py +40 -0
  21. swarph_cli-0.71.0/src/swarph_cli/operator/cli.py +509 -0
  22. swarph_cli-0.71.0/src/swarph_cli/operator/config.py +90 -0
  23. swarph_cli-0.71.0/src/swarph_cli/operator/fmt.py +131 -0
  24. {swarph_cli-0.70.0 → swarph_cli-0.71.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  25. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli.egg-info/SOURCES.txt +12 -0
  26. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli.egg-info/entry_points.txt +1 -0
  27. swarph_cli-0.71.0/tests/test_1006_board_publisher.py +311 -0
  28. swarph_cli-0.71.0/tests/test_538_signal_bench.py +126 -0
  29. swarph_cli-0.71.0/tests/test_729_arm_check_delivery_gap.py +136 -0
  30. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_830_hook_binding_version.py +39 -1
  31. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_board_command.py +61 -1
  32. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_content_composition.py +6 -0
  33. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_install_wake_hook.py +107 -53
  34. swarph_cli-0.71.0/tests/test_mesh_sidecar_muse_sink.py +193 -0
  35. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_opencode_plugin.py +1 -2
  36. swarph_cli-0.71.0/tests/test_operator_me.py +218 -0
  37. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/LICENSE +0 -0
  38. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/README.md +0 -0
  39. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/setup.cfg +0 -0
  40. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/__main__.py +0 -0
  41. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/__init__.py +0 -0
  42. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/claude_cli.py +0 -0
  43. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/pack.py +0 -0
  44. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/prices.py +0 -0
  45. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/providers.py +0 -0
  46. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/quality.py +0 -0
  47. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
  48. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/bench/rules.py +0 -0
  49. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/caller.py +0 -0
  50. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/__init__.py +0 -0
  51. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/harden.py +0 -0
  52. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/lineage.py +0 -0
  53. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/liveness.py +0 -0
  54. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/manifest.py +0 -0
  55. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/capture/verify.py +0 -0
  56. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/cell.py +0 -0
  57. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/chain_token.py +0 -0
  58. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/channel.py +0 -0
  59. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/__init__.py +0 -0
  60. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/_content.py +0 -0
  61. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/_display.py +0 -0
  62. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  63. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/add.py +0 -0
  64. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
  65. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/brain.py +0 -0
  66. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  67. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/cell.py +0 -0
  68. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/cell_probe.py +0 -0
  69. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  70. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/channel.py +0 -0
  71. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/chat.py +0 -0
  72. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/codegraph.py +0 -0
  73. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  74. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/codex_waker.py +0 -0
  75. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/compress.py +0 -0
  76. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/daemon.py +0 -0
  77. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/dreaming.py +0 -0
  78. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/event.py +0 -0
  79. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/gateway.py +0 -0
  80. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/gh_route.py +0 -0
  81. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/group.py +0 -0
  82. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/guide.py +0 -0
  83. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/highlight.py +0 -0
  84. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  85. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/hook_output.py +0 -0
  86. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/import_session.py +0 -0
  87. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/init.py +0 -0
  88. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  89. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_hook.py +0 -0
  90. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  91. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  92. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  93. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/lane.py +0 -0
  94. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  95. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/memory.py +0 -0
  96. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  97. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  98. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/okf_links.py +0 -0
  99. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/onboard.py +0 -0
  100. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  101. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  102. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  103. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/ratify.py +0 -0
  104. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/schedule.py +0 -0
  105. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/security.py +0 -0
  106. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/service.py +0 -0
  107. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/spawn.py +0 -0
  108. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/timeline.py +0 -0
  109. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/version.py +0 -0
  110. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/commands/watchdog.py +0 -0
  111. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/compress/__init__.py +0 -0
  112. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/compress/levers.py +0 -0
  113. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/compress/marker.py +0 -0
  114. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/compress/verify.py +0 -0
  115. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/console_safe.py +0 -0
  116. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/delivery_queue.py +0 -0
  117. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dm_frame.py +0 -0
  118. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/__init__.py +0 -0
  119. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/candidates.py +0 -0
  120. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/clone.py +0 -0
  121. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/enrich.py +0 -0
  122. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/index_check.py +0 -0
  123. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/notify.py +0 -0
  124. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/organize.py +0 -0
  125. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/probes.py +0 -0
  126. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/report.py +0 -0
  127. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/run.py +0 -0
  128. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/slm.py +0 -0
  129. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
  130. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/dreaming/verify.py +0 -0
  131. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/__init__.py +0 -0
  132. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  133. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  134. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/schema.sql +0 -0
  135. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/server.py +0 -0
  136. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway/services_control.py +0 -0
  137. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gateway_default.py +0 -0
  138. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/gh_identity.py +0 -0
  139. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/identity.py +0 -0
  140. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/main.py +0 -0
  141. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/multiplexer.py +0 -0
  142. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/orphan_daemons.py +0 -0
  143. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/pane_probe.py +0 -0
  144. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/parsers/__init__.py +0 -0
  145. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/parsers/claude.py +0 -0
  146. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  147. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  148. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  149. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  150. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  151. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  152. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  153. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  154. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  155. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  156. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  157. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  158. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_executor.py +0 -0
  159. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_executor_windows.py +0 -0
  160. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_reconciliation.py +0 -0
  161. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
  162. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_service_host.py +0 -0
  163. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_service_payload.py +0 -0
  164. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_service_reply.py +0 -0
  165. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/peer_staging.py +0 -0
  166. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/query_filters.py +0 -0
  167. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/__init__.py +0 -0
  168. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  169. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  170. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  171. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  172. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  173. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/service/__init__.py +0 -0
  174. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/service/app.py +0 -0
  175. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/service/providers.py +0 -0
  176. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/session_bridge.py +0 -0
  177. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  178. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  179. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/source_text.py +0 -0
  180. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/stall_alert.py +0 -0
  181. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
  182. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  183. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  184. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  185. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  186. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  187. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  188. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  189. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  190. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  191. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  192. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  193. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  194. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  195. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/timeline_paths.py +0 -0
  196. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli/tokens.py +0 -0
  197. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  198. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  199. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  200. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_1006_session_board.py +0 -0
  201. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_124_register_merge.py +0 -0
  202. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_126_pending_from_complete.py +0 -0
  203. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  204. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_138_memory_navigate.py +0 -0
  205. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_139_tool_probe.py +0 -0
  206. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_183_card_anchor.py +0 -0
  207. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_184_notify_honesty.py +0 -0
  208. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_201_win32_bash_refusal.py +0 -0
  209. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_243_onboard_per_peer_token.py +0 -0
  210. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_307_task4_ask_reply.py +0 -0
  211. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  212. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_319_refusal_exit_codes.py +0 -0
  213. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_323_mistral_backend.py +0 -0
  214. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_332_report_organize_findings.py +0 -0
  215. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_356_query_filters.py +0 -0
  216. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_397_gh_identity_router.py +0 -0
  217. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_397_router_preflight.py +0 -0
  218. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_402_one_identity_resolver.py +0 -0
  219. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_467b_onboard_operator_credential.py +0 -0
  220. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_472_register_caller_binding.py +0 -0
  221. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_523_guide.py +0 -0
  222. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_524_conftest_hermeticity.py +0 -0
  223. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_532_accept_flag.py +0 -0
  224. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_535_version_capability.py +0 -0
  225. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_546_default_gateway.py +0 -0
  226. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_547_verbs_from_registry.py +0 -0
  227. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_548_gateway_remainder.py +0 -0
  228. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_552_codegraph_gateway.py +0 -0
  229. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_555_relay_row_shape.py +0 -0
  230. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_556_relay_freshness.py +0 -0
  231. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_559_edges_truncated.py +0 -0
  232. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_564_bootstrap_first_peer.py +0 -0
  233. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  234. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_579_register_bootstrap_message.py +0 -0
  235. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_591_cli_step_graph.py +0 -0
  236. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_644_monitor_supervision.py +0 -0
  237. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_648_install_unit.py +0 -0
  238. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_654_reexec.py +0 -0
  239. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_656_owned_slm.py +0 -0
  240. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_684_dreaming_verb.py +0 -0
  241. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_689_pkg_version_order.py +0 -0
  242. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_715_grok_wake.py +0 -0
  243. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_716_timeline_one_path.py +0 -0
  244. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_723_grok_0251.py +0 -0
  245. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  246. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_725_stdio_reconfigure.py +0 -0
  247. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_726_grok_limit_screen.py +0 -0
  248. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_729_dm_once.py +0 -0
  249. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_729_wake_watchdog.py +0 -0
  250. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  251. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_735_dm_mcp_tools.py +0 -0
  252. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_739_wake_writer.py +0 -0
  253. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_740_edit_project.py +0 -0
  254. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_762_cards_edit_priority.py +0 -0
  255. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_764_enrich_collapse.py +0 -0
  256. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_801_confirm_flow_cli.py +0 -0
  257. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_802_stage_history.py +0 -0
  258. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_825_codegraph_hook_noise.py +0 -0
  259. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_825_install_posttooluse_only.py +0 -0
  260. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  261. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_829_counterfactual_column.py +0 -0
  262. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_84_cli_labels.py +0 -0
  263. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_866_doctrine_guide.py +0 -0
  264. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
  265. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_874_source_text.py +0 -0
  266. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  267. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_937_dreaming_notify.py +0 -0
  268. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_938_organize_line_limit.py +0 -0
  269. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_941_bench_backends.py +0 -0
  270. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_941_harness_342.py +0 -0
  271. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_945_same_line_pairing.py +0 -0
  272. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_955_prompt_audit.py +0 -0
  273. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_957_auth_http.py +0 -0
  274. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_959_venv_interpreter.py +0 -0
  275. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_960_channel.py +0 -0
  276. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  277. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_961_cursor_grok_footer.py +0 -0
  278. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_963_plugin_manifest.py +0 -0
  279. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_964_channel_cell.py +0 -0
  280. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_965_mesh_wait.py +0 -0
  281. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_975_channel_hop.py +0 -0
  282. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_add_security_gate.py +0 -0
  283. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_add.py +0 -0
  284. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_hash.py +0 -0
  285. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_lib.py +0 -0
  286. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_mcp.py +0 -0
  287. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_skill.py +0 -0
  288. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_tool.py +0 -0
  289. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_artifact_uri.py +0 -0
  290. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_backends.py +0 -0
  291. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_claude_cli.py +0 -0
  292. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_command.py +0 -0
  293. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_pack.py +0 -0
  294. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_prices.py +0 -0
  295. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_quality.py +0 -0
  296. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_runner.py +0 -0
  297. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_bench_validate.py +0 -0
  298. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_board_card_thread.py +0 -0
  299. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_board_merge_check.py +0 -0
  300. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_board_obligation_close.py +0 -0
  301. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_brain_ask_command.py +0 -0
  302. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
  303. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
  304. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_brain_command.py +0 -0
  305. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_caller_meta_guard.py +0 -0
  306. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_capture_lineage.py +0 -0
  307. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_capture_liveness.py +0 -0
  308. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_capture_manifest.py +0 -0
  309. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_capture_paths.py +0 -0
  310. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_capture_security.py +0 -0
  311. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cell_command_dispatch.py +0 -0
  312. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cell_harden.py +0 -0
  313. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cell_loader.py +0 -0
  314. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cell_selfcheck.py +0 -0
  315. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cell_verify.py +0 -0
  316. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_chain_token.py +0 -0
  317. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_channel_command.py +0 -0
  318. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  319. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_channel_post.py +0 -0
  320. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_channel_wake_policy_filter.py +0 -0
  321. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_chat_command.py +0 -0
  322. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_claude_parser.py +0 -0
  323. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_claude_tmux_template.py +0 -0
  324. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_codegraph_caller_scope.py +0 -0
  325. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_codegraph_command.py +0 -0
  326. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_codegraph_hook.py +0 -0
  327. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_codegraph_mcp.py +0 -0
  328. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_codex_waker.py +0 -0
  329. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_compress_caller_convention.py +0 -0
  330. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_compress_command.py +0 -0
  331. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_compress_levers.py +0 -0
  332. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_compress_marker.py +0 -0
  333. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_compress_verify.py +0 -0
  334. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_console_encoding_e2e.py +0 -0
  335. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_cursor_membrane.py +0 -0
  336. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_daemon_command.py +0 -0
  337. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_daemon_console_encoding.py +0 -0
  338. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_daemon_delivery.py +0 -0
  339. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_delivery_queue.py +0 -0
  340. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_display_sanitize.py +0 -0
  341. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  342. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_event_emit.py +0 -0
  343. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_feature_to_uri.py +0 -0
  344. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_gateway_brain.py +0 -0
  345. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_gateway_client.py +0 -0
  346. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_gateway_command.py +0 -0
  347. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_gateway_message_idempotency.py +0 -0
  348. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  349. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_group_command.py +0 -0
  350. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_guide_commands_parse.py +0 -0
  351. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_guide_selfcheck.py +0 -0
  352. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_heartbeat_check.py +0 -0
  353. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_highlight_command.py +0 -0
  354. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hook_output.py +0 -0
  355. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_activity_marker.py +0 -0
  356. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_add.py +0 -0
  357. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_bundle.py +0 -0
  358. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_init.py +0 -0
  359. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
  360. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_lifecycle.py +0 -0
  361. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_merge.py +0 -0
  362. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_verify.py +0 -0
  363. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  364. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_hooks_win32_command_path.py +0 -0
  365. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_import_command.py +0 -0
  366. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_inbox_identity_provenance.py +0 -0
  367. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_init_command.py +0 -0
  368. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_init_provider_menu.py +0 -0
  369. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_install_codex_hooks.py +0 -0
  370. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_install_hook.py +0 -0
  371. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_install_multiplexer_command.py +0 -0
  372. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_install_postcompact_hook.py +0 -0
  373. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_lane_command.py +0 -0
  374. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_main.py +0 -0
  375. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_main_unknown_verb_guard.py +0 -0
  376. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mcp_missing_hint.py +0 -0
  377. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mcp_server.py +0 -0
  378. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_memory_command.py +0 -0
  379. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_memory_emit_snippet.py +0 -0
  380. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_memory_gateway.py +0 -0
  381. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_memory_sync.py +0 -0
  382. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_memory_sync_membrane.py +0 -0
  383. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_command.py +0 -0
  384. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  385. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  386. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_originate_thread_623.py +0 -0
  387. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_send_unknown_peer.py +0 -0
  388. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_sidecar.py +0 -0
  389. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  390. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  391. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_meta_edge_identity.py +0 -0
  392. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_monitor_channel_poll.py +0 -0
  393. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_monitor_cli.py +0 -0
  394. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_monitor_daemon_collision.py +0 -0
  395. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_monitor_self_name_verification.py +0 -0
  396. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_monitor_state_split.py +0 -0
  397. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_multiplexer.py +0 -0
  398. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_muse_membrane.py +0 -0
  399. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_muse_own_cli.py +0 -0
  400. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_obligation_close_is_surfaced.py +0 -0
  401. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_okf_links.py +0 -0
  402. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_onboard_command.py +0 -0
  403. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_opencode_membrane.py +0 -0
  404. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_orphan_daemons.py +0 -0
  405. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_packaged_artifacts_exist.py +0 -0
  406. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_packaged_skills.py +0 -0
  407. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_executor.py +0 -0
  408. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_executor_windows.py +0 -0
  409. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_reply_drain_command.py +0 -0
  410. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
  411. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_reply_outbox.py +0 -0
  412. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_reply_transport.py +0 -0
  413. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_service_e2e.py +0 -0
  414. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_service_host.py +0 -0
  415. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_service_payload.py +0 -0
  416. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_peer_service_reply.py +0 -0
  417. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_postcompact_recall.py +0 -0
  418. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_process_liveness.py +0 -0
  419. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_protocol_handler.py +0 -0
  420. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_providers_isolation.py +0 -0
  421. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_ratify_command.py +0 -0
  422. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
  423. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_repl_caller_convention.py +0 -0
  424. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_schedule_command.py +0 -0
  425. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_schedule_create_contract.py +0 -0
  426. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_security.py +0 -0
  427. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_service_app.py +0 -0
  428. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_service_command.py +0 -0
  429. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_service_providers.py +0 -0
  430. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_session_bridge_inject.py +0 -0
  431. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_session_bridge_probe.py +0 -0
  432. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_session_bridge_resolve.py +0 -0
  433. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_session_path_role_gate.py +0 -0
  434. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_shipped_units_match_the_cli.py +0 -0
  435. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_smoke_chat.py +0 -0
  436. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_smoke_one_shot.py +0 -0
  437. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_smoke_phase_5_5.py +0 -0
  438. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_base_hoist.py +0 -0
  439. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_command.py +0 -0
  440. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_identity_stamp.py +0 -0
  441. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_live_pin.py +0 -0
  442. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  443. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_tmux_session.py +0 -0
  444. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_spawn_windows_relaunch.py +0 -0
  445. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_stall_alert.py +0 -0
  446. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_subprocess_decode_encoding.py +0 -0
  447. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_timeline_command.py +0 -0
  448. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
  449. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
  450. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_tmux_wake_stale.py +0 -0
  451. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_tmux_wake_submit_verify.py +0 -0
  452. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_token_file_one_parser.py +0 -0
  453. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_token_precedence_explicit_first.py +0 -0
  454. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_unified_token_resolver.py +0 -0
  455. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_version_command.py +0 -0
  456. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_version_consistency.py +0 -0
  457. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_vibe_membrane.py +0 -0
  458. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog.py +0 -0
  459. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_dm_wake.py +0 -0
  460. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  461. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  462. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_emit_health.py +0 -0
  463. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_install_flags.py +0 -0
  464. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_model_rung.py +0 -0
  465. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_watchdog_stale_peers.py +0 -0
  466. {swarph_cli-0.70.0 → swarph_cli-0.71.0}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.70.0
3
+ Version: 0.71.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.70.0"
7
+ version = "0.71.0"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -130,6 +130,9 @@ service = [
130
130
 
131
131
  [project.scripts]
132
132
  swarph = "swarph_cli.main:main"
133
+ # #660: operator surface — separate entrypoint so identity cannot inherit
134
+ # $SWARPH_SELF from the cell CLI helpers (see swarph_cli.operator + its tests).
135
+ swarph-me = "swarph_cli.operator:main"
133
136
 
134
137
  [tool.setuptools.packages.find]
135
138
  where = ["src"]
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.70.0"
19
+ __version__ = "0.71.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -475,6 +475,15 @@ class RuleBackend:
475
475
  )
476
476
 
477
477
 
478
+ class SignalBackend(RuleBackend):
479
+ """Deterministic signal arm (#213). ``model_id`` is ``module:callable``.
480
+
481
+ Same call as :class:`RuleBackend`: no credentials, no network, the
482
+ callable's return value is the categorical label. The runner scores a
483
+ ``signal:`` arm with the robust date-grouped scorer, not mean distance.
484
+ """
485
+
486
+
478
487
  class TypedHttpBackend:
479
488
  KIND = "typed"
480
489
  """POST a bench item to a Jev-compatible ``/v1/systemone`` server.
@@ -21,6 +21,7 @@ from .backends import Backend, BackendResult, _redact
21
21
  from .prices import cost_usd, is_known, lookup
22
22
  from .providers import egress_of_url
23
23
  from .quality import score
24
+ from .signal_score import robust_report
24
25
 
25
26
 
26
27
  @dataclass
@@ -55,7 +56,10 @@ def parse_models(arg: str) -> list[ModelSpec]:
55
56
  id=name, backend="provider", provider=name,
56
57
  label=label or f"provider:{name}"))
57
58
  continue
58
- for prefix, backend in (("typed-http:", "typed-http"), ("rule:", "rule")):
59
+ # signal:<module:callable> is the deterministic signal-backtest arm
60
+ # (#213). It carries colons in the payload, same as rule:.
61
+ for prefix, backend in (
62
+ ("typed-http:", "typed-http"), ("rule:", "rule"), ("signal:", "signal")):
59
63
  if tok.startswith(prefix):
60
64
  specs.append(ModelSpec(id=tok[len(prefix):], backend=backend, label=tok))
61
65
  break
@@ -120,6 +124,7 @@ class ModelBoardRow:
120
124
  base_url: str = ""
121
125
  path: str = ""
122
126
  kind: str = ""
127
+ robust: Optional[dict] = None
123
128
 
124
129
 
125
130
  def _dispatch(backend: Backend, model_id: str, prompt: str, system: str) -> BackendResult:
@@ -319,6 +324,7 @@ def run_pack(
319
324
  resumed[item["task_id"]] = _taskrow_from_ledger(item)
320
325
 
321
326
  rows: list[TaskRow] = []
327
+ signal_obs: list[dict] = []
322
328
  arm_spent = 0.0
323
329
  arm_stopped = False
324
330
  for task in tasks:
@@ -389,6 +395,17 @@ def run_pack(
389
395
  estimated=result.estimated or unknown,
390
396
  text=result.text,
391
397
  ))
398
+ if spec.backend == "signal":
399
+ meta = task.get("meta") or {}
400
+ fwd = meta.get("fwd")
401
+ predicted = sc.get("parsed")
402
+ signal_obs.append({
403
+ "cls": class_of(task),
404
+ "date": str(meta.get("date") or ""),
405
+ "fwd": float(fwd) if isinstance(fwd, (int, float)) and not isinstance(fwd, bool) else None,
406
+ "expected": str(task.get("expected") or ""),
407
+ "predicted": str(predicted).strip().upper() if predicted is not None else "",
408
+ })
392
409
  detail[spec.label] = rows
393
410
 
394
411
  done = [r for r in rows if r.error is None and not r.not_run]
@@ -429,6 +446,7 @@ def run_pack(
429
446
  base_url=getattr(backend, "base_url", ""),
430
447
  path=getattr(backend, "path", ""),
431
448
  kind=getattr(backend, "KIND", ""),
449
+ robust=robust_report(signal_obs) if spec.backend == "signal" else None,
432
450
  ))
433
451
 
434
452
  board.sort(key=lambda b: (b.mean_distance, b.cost_usd))
@@ -439,7 +457,10 @@ def run_pack(
439
457
  "aborted": partial == "aborted",
440
458
  "exit_code": 1 if partial else 0,
441
459
  "egress_overrides": overrides,
442
- "board": [vars(b) for b in board],
460
+ "board": [
461
+ {k: v for k, v in vars(b).items() if k != "robust" or v is not None}
462
+ for b in board
463
+ ],
443
464
  "detail": {label: [vars(r) for r in rows] for label, rows in detail.items()},
444
465
  }
445
466
  text = _redact(json.dumps(payload), secrets)
@@ -0,0 +1,110 @@
1
+ """Robust scorer for a signal pack (#213).
2
+
3
+ Mean distance is the wrong lens for a categorical UP/FLAT/DOWN call: an arm
4
+ that always says FLAT can look precise while never separating the tails.
5
+ This scorer reports, per cell and only then pooled:
6
+
7
+ - n
8
+ - balanced accuracy (mean recall over the outcome labels present in the cell)
9
+ - the median forward return of the rows the arm called UP, and of the rows
10
+ it called DOWN (the separation)
11
+ - a bootstrap confidence interval of that separation, resampling whole
12
+ decision dates so rows that share a date stay together
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import random
17
+
18
+ LABELS = ("UP", "FLAT", "DOWN")
19
+ N_BOOT = 1000
20
+ SEED = 20260927
21
+
22
+
23
+ def _median(xs: list[float]) -> float | None:
24
+ if not xs:
25
+ return None
26
+ s = sorted(xs)
27
+ n = len(s)
28
+ mid = n // 2
29
+ if n % 2:
30
+ return s[mid]
31
+ return (s[mid - 1] + s[mid]) / 2.0
32
+
33
+
34
+ def balanced_accuracy(expected: list[str], predicted: list[str]) -> float | None:
35
+ recalls = []
36
+ for lab in LABELS:
37
+ idx = [i for i, e in enumerate(expected) if e == lab]
38
+ if not idx:
39
+ continue
40
+ recalls.append(sum(1 for i in idx if predicted[i] == lab) / len(idx))
41
+ if not recalls:
42
+ return None
43
+ return round(sum(recalls) / len(recalls), 4)
44
+
45
+
46
+ def cell_block(rows: list[dict]) -> dict:
47
+ expected = [str(r.get("expected") or "") for r in rows]
48
+ predicted = [str(r.get("predicted") or "") for r in rows]
49
+ up = [float(r["fwd"]) for r in rows if r.get("predicted") == "UP" and isinstance(r.get("fwd"), (int, float))]
50
+ down = [float(r["fwd"]) for r in rows if r.get("predicted") == "DOWN" and isinstance(r.get("fwd"), (int, float))]
51
+ med_up = _median(up)
52
+ med_down = _median(down)
53
+ sep = None if med_up is None or med_down is None else round(med_up - med_down, 6)
54
+ return {
55
+ "n": len(rows),
56
+ "balanced_accuracy": balanced_accuracy(expected, predicted),
57
+ "n_called_up": len(up),
58
+ "n_called_down": len(down),
59
+ "median_fwd_called_up": None if med_up is None else round(med_up, 6),
60
+ "median_fwd_called_down": None if med_down is None else round(med_down, 6),
61
+ "separation": sep,
62
+ }
63
+
64
+
65
+ def bootstrap_ci(rows: list[dict], *, n_boot: int = N_BOOT, seed: int = SEED) -> dict:
66
+ """95% interval of separation, resampling decision dates with replacement."""
67
+ by_date: dict[str, list[dict]] = {}
68
+ for row in rows:
69
+ by_date.setdefault(str(row.get("date") or ""), []).append(row)
70
+ dates = sorted(by_date)
71
+ out = {
72
+ "stat": "separation",
73
+ "n_dates": len(dates),
74
+ "n_boot": n_boot,
75
+ "seed": seed,
76
+ "lo": None,
77
+ "hi": None,
78
+ }
79
+ if len(dates) < 2:
80
+ return out
81
+ rng = random.Random(seed)
82
+ stats: list[float] = []
83
+ n_dates = len(dates)
84
+ for _ in range(n_boot):
85
+ drawn: list[dict] = []
86
+ for _i in range(n_dates):
87
+ drawn.extend(by_date[dates[rng.randrange(n_dates)]])
88
+ sep = cell_block(drawn)["separation"]
89
+ if sep is not None:
90
+ stats.append(sep)
91
+ if not stats:
92
+ return out
93
+ stats.sort()
94
+ last = len(stats) - 1
95
+ out["lo"] = round(stats[int(0.025 * last)], 6)
96
+ out["hi"] = round(stats[int(0.975 * last)], 6)
97
+ return out
98
+
99
+
100
+ def robust_report(obs: list[dict]) -> dict:
101
+ """Per-cell table first. Pooled is present only as a sibling of that table."""
102
+ per_cell: dict[str, dict] = {}
103
+ for cls in sorted({str(o.get("cls") or "") for o in obs}):
104
+ rows = [o for o in obs if str(o.get("cls") or "") == cls]
105
+ block = cell_block(rows)
106
+ block["bootstrap_ci"] = bootstrap_ci(rows)
107
+ per_cell[cls] = block
108
+ pooled = cell_block(obs)
109
+ pooled["bootstrap_ci"] = bootstrap_ci(obs)
110
+ return {"per_cell": per_cell, "pooled": pooled}
@@ -16,6 +16,9 @@ from __future__ import annotations
16
16
  import re
17
17
  from typing import Optional
18
18
 
19
+ import hashlib
20
+ import json
21
+
19
22
  from .backends import Backend
20
23
  from .pack import validate_schema
21
24
  from .quality import d_text
@@ -280,4 +283,64 @@ def validate_pack(
280
283
  report.warnings.extend(disc_warnings)
281
284
 
282
285
  report.warnings.extend(gate_calibration(pack, backends))
286
+ report.errors.extend(gate_signal_pack(pack))
283
287
  return report
288
+
289
+
290
+ def tasks_sha256(tasks: list) -> str:
291
+ """sha256 of the task list. Frozen canon: json.dumps with sort_keys and
292
+ compact separators, UTF-8. The pack builder must use this exact canon."""
293
+ blob = json.dumps(tasks, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
294
+ return hashlib.sha256(blob).hexdigest()
295
+
296
+
297
+ def gate_signal_pack(pack: dict) -> list[str]:
298
+ """A signal pack is valid only with a matching task-list sha256, a frozen
299
+ 5-bar outcome rule, and cardinality > 1 on both conditioning axes
300
+ (regime and sector). A constant axis passes a schema check and still
301
+ measures nothing."""
302
+ header = pack.get("header")
303
+ if not isinstance(header, dict) or header.get("pack_kind") != "signal":
304
+ return []
305
+ errors: list[str] = []
306
+ tasks = pack.get("tasks") or []
307
+ digest = tasks_sha256(tasks)
308
+ if header.get("sha256_tasks") != digest:
309
+ errors.append(
310
+ f"header.sha256_tasks does not match the task list (computed {digest})"
311
+ )
312
+ if header.get("row_count") != len(tasks):
313
+ errors.append(
314
+ f"header.row_count={header.get('row_count')!r} does not match len(tasks)={len(tasks)}"
315
+ )
316
+ outcome = header.get("outcome") if isinstance(header.get("outcome"), dict) else {}
317
+ if outcome.get("horizon_bars") != 5 or outcome.get("up_gt") is None or outcome.get("down_lt") is None:
318
+ errors.append("header.outcome must freeze horizon_bars=5, up_gt, and down_lt")
319
+ for key in ("data_window", "universe_source_query", "price_history_snapshot_date"):
320
+ if not header.get(key):
321
+ errors.append(f"header.{key} is required on a signal pack")
322
+ regimes: set[str] = set()
323
+ sectors: set[str] = set()
324
+ for task in tasks:
325
+ if not isinstance(task, dict):
326
+ continue
327
+ cls = str((task.get("meta") or {}).get("class") or "")
328
+ if "|" not in cls:
329
+ errors.append(f"task {task.get('id')!r} meta.class must be '<regime>|<sector>'")
330
+ continue
331
+ regime, sector = cls.split("|", 1)
332
+ if regime:
333
+ regimes.add(regime)
334
+ if sector:
335
+ sectors.add(sector)
336
+ if len(regimes) < 2:
337
+ errors.append(
338
+ f"conditioning axis regime has cardinality {len(regimes)}; "
339
+ "a constant axis is not a valid pack"
340
+ )
341
+ if len(sectors) < 2:
342
+ errors.append(
343
+ f"conditioning axis sector has cardinality {len(sectors)}; "
344
+ "a constant axis is not a valid pack"
345
+ )
346
+ return errors
@@ -77,3 +77,14 @@ def manifest_path(role: str) -> Path:
77
77
  validate_role(role)
78
78
  base = captures_dir()
79
79
  return _assert_contained(base / f"{role}.json", base)
80
+
81
+
82
+ def card_preimage_path() -> Path:
83
+ """Append-only log of card text REPLACED by ``board cards edit`` on this box.
84
+
85
+ Not role-scoped (no ``validate_role``): the writer is the CLI itself, not a
86
+ capture role, and the file is one per machine on purpose — it answers "what
87
+ did an edit issued FROM HERE destroy", which is the only question a local
88
+ file can honestly answer. See ``_write_card_preimage`` in commands/board.py.
89
+ """
90
+ return _swarph_state() / "board-card-preimages.jsonl"
@@ -28,6 +28,7 @@ from swarph_cli.bench.backends import (
28
28
  MeteredGeminiBackend,
29
29
  MeteredMistralBackend,
30
30
  RuleBackend,
31
+ SignalBackend,
31
32
  SubscriptionBackend,
32
33
  TypedHttpBackend,
33
34
  HttpBackend,
@@ -65,6 +66,7 @@ def _default_backends() -> dict[str, Backend]:
65
66
  "mistral": MeteredMistralBackend(),
66
67
  "subscription": subscription,
67
68
  "rule": RuleBackend(),
69
+ "signal": SignalBackend(),
68
70
  "typed-http": TypedHttpBackend(),
69
71
  # Selectable class for the wiring lock. A real call is provider:<name>,
70
72
  # which replaces this with the registry arm. An unresolved name is
@@ -108,6 +110,34 @@ def _format_run_table(result: dict) -> str:
108
110
  f" {_s(cls):20} n={c['n']:<4} hits={c['hits']:<4} "
109
111
  f"hit_rate={c['hit_rate']:<6} mean_distance={c['mean_distance']}"
110
112
  )
113
+ robust = row.get("robust")
114
+ if robust:
115
+ lines.append(" per-cell (robust):")
116
+ per_cell = robust.get("per_cell") or {}
117
+ for cls, c in sorted(per_cell.items()):
118
+ ci = c.get("bootstrap_ci") or {}
119
+ lines.append(
120
+ f" {_s(cls):24} n={c['n']:<5} "
121
+ f"bal_acc={c['balanced_accuracy']} "
122
+ f"med_up={c['median_fwd_called_up']} "
123
+ f"med_down={c['median_fwd_called_down']} "
124
+ f"sep={c['separation']} "
125
+ f"ci=[{ci.get('lo')}, {ci.get('hi')}]"
126
+ )
127
+ # Pooled sits beside the per-cell table. It is not printed when
128
+ # the table is empty — a pooled-only line is the failure mode
129
+ # this scorer exists to prevent.
130
+ if per_cell and robust.get("pooled"):
131
+ p = robust["pooled"]
132
+ ci = p.get("bootstrap_ci") or {}
133
+ lines.append(
134
+ " pooled (beside the per-cell table): "
135
+ f"n={p['n']} bal_acc={p['balanced_accuracy']} "
136
+ f"med_up={p['median_fwd_called_up']} "
137
+ f"med_down={p['median_fwd_called_down']} "
138
+ f"sep={p['separation']} "
139
+ f"ci=[{ci.get('lo')}, {ci.get('hi')}]"
140
+ )
111
141
  return "\n".join(lines)
112
142
 
113
143
 
@@ -12,11 +12,14 @@ from __future__ import annotations
12
12
 
13
13
  import argparse
14
14
  import json
15
+ import os
15
16
  import re
16
17
  import sys
17
18
  import urllib.error
18
19
  import urllib.parse
19
20
  import urllib.request
21
+ from datetime import datetime, timezone
22
+ from pathlib import Path
20
23
  from typing import Optional
21
24
 
22
25
  # #532 — negative-branch marker detection. THREE COPIES exist and no single
@@ -30,6 +33,7 @@ from typing import Optional
30
33
  # substring test can know a falsifier exists.
31
34
  _FAIL_MARKER_RE = re.compile(r"(?<![\w-])fail(?:s|ed|ing|ures?)?(?![\w-])", re.IGNORECASE)
32
35
 
36
+ from swarph_cli.capture.paths import card_preimage_path
33
37
  from swarph_cli.commands._content import ContentError, add_content_args, resolve_content
34
38
  from swarph_cli.commands._display import sanitize_terminal as _s
35
39
  from swarph_cli.commands.mesh import (
@@ -178,6 +182,49 @@ def _card_edit_payload(actor, title, body, *, due_at=_SENTINEL, project_id=None,
178
182
  return patch
179
183
 
180
184
 
185
+ def _write_card_preimage(actor, gateway, card_id, card, patch) -> Path:
186
+ """Append the card's CURRENT title and body to a local log BEFORE an edit
187
+ replaces them. Returns the path written.
188
+
189
+ >>> WHY THE CLIENT AND NOT THE SERVER. <<< #596 is fixed for TITLE only: the
190
+ gateway retains the replaced title in ``body_history`` (mesh-gateway 989c83d,
191
+ live since 2026-09-17 13:22Z) and for a BODY edit appends {by, at, chars} —
192
+ who, when, and the length of the NEW text. The replaced body is gone, and
193
+ #596's side table (the system-wide fix) is a gateway change behind a deploy
194
+ gate. This is the footprint the EDITING MACHINE can keep on its own.
195
+
196
+ ponytail: one file, one box. It covers edits issued FROM HERE and nothing
197
+ else — a body replaced from another cell is still unrecoverable, and
198
+ "a transcript is a copy of the board nobody indexes" (drop-on-meta-edge,
199
+ #596) applies to this file too. The side table remains the real fix; this
200
+ only makes the window survivable.
201
+ """
202
+ rec = {
203
+ "at": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"),
204
+ "by": actor,
205
+ "gateway": gateway,
206
+ "card": card_id,
207
+ "body_version": card.get("body_version"),
208
+ "prev_title": card.get("title"),
209
+ "prev_body": card.get("body"),
210
+ "new_title": patch.get("title"),
211
+ "new_body": patch.get("body"),
212
+ }
213
+ path = card_preimage_path()
214
+ path.parent.mkdir(parents=True, exist_ok=True)
215
+ # A previous line with no trailing newline would MERGE with this one and
216
+ # destroy both records; the reader is json-per-line.
217
+ prefix = ""
218
+ if path.exists() and path.stat().st_size:
219
+ with path.open("rb") as fh:
220
+ fh.seek(-1, os.SEEK_END)
221
+ if fh.read(1) != b"\n":
222
+ prefix = "\n"
223
+ with path.open("a", encoding="utf-8") as fh:
224
+ fh.write(prefix + json.dumps(rec, ensure_ascii=False) + "\n")
225
+ return path
226
+
227
+
181
228
  def _apply_label(current: list, action: str, label: str) -> list:
182
229
  """Compute the NEW label set. Pure, so the read-modify-write is testable
183
230
  without a gateway.
@@ -1241,12 +1288,32 @@ def run_board(argv: list[str]) -> int:
1241
1288
  except ValueError as exc:
1242
1289
  print(f"swarph board cards edit: {exc}", file=sys.stderr)
1243
1290
  return 2
1291
+ # READ BEFORE WRITE, and REFUSE if either the read or the capture
1292
+ # fails. This mirrors `label`'s read-modify-write guard for a
1293
+ # different reason: there the failed read would clobber the other
1294
+ # labels; here it would destroy text nothing can recover (#596).
1295
+ st_cur, cur = _http_get_json(f"{gw}/board/cards/{args.id}", token)
1296
+ if st_cur != 200 or not isinstance(cur, dict):
1297
+ print(f"swarph board cards edit: cannot read card #{args.id} "
1298
+ f"before editing (HTTP {st_cur}) — refusing to write. The "
1299
+ f"gateway keeps NO recoverable copy of a replaced body "
1300
+ f"(#596), so an edit whose pre-image was not captured is "
1301
+ f"an unrecoverable one", file=sys.stderr)
1302
+ return 1
1303
+ try:
1304
+ pre = _write_card_preimage(self_name, gw, args.id, cur, patch)
1305
+ except OSError as exc:
1306
+ print(f"swarph board cards edit: cannot write the pre-image log "
1307
+ f"({exc}) — refusing to write, for the same reason: the "
1308
+ f"replaced text would exist nowhere (#596)", file=sys.stderr)
1309
+ return 1
1244
1310
  st, d = _patch_json(f"{gw}/board/cards/{args.id}", patch, token)
1245
1311
  # Surface body_version: it is the latch every verdict stamp keys on
1246
1312
  # (#199), so an edit that re-opens reviewed work is visible in the
1247
1313
  # success line, not only in a later audit.
1248
1314
  return _out(st, d, lambda x: f"card #{x.get('id')} edited "
1249
- f"(body_version={x.get('body_version')})", aj)
1315
+ f"(body_version={x.get('body_version')}; "
1316
+ f"pre-image {pre})", aj)
1250
1317
  if args.command == "thread":
1251
1318
  st, d = _http_get_json(_thread_url(gw, args.id, limit=args.limit), token)
1252
1319
  # NO special-casing of 403/409. `_out` already returns 1 and prints the
@@ -476,8 +476,19 @@ def supported_hook_events() -> frozenset:
476
476
 
477
477
 
478
478
  def run_codegraph_hook(argv: Optional[list] = None) -> int:
479
- """ALWAYS exits 0 — must never fail a turn."""
479
+ """ALWAYS exits 0 on hook stdin — must never fail a turn.
480
+
481
+ ``--supported-events`` is the out-of-process advertise path for
482
+ ``swarph hooks add`` (#830): prints one event name per line and exits 0
483
+ without reading stdin. Installers MUST invoke this via PATH ``swarph``,
484
+ not an in-process import — same late binding as the generated hook script.
485
+ """
480
486
  argv = list(argv or [])
487
+ if "--supported-events" in argv:
488
+ for name in sorted(supported_hook_events()):
489
+ print(name)
490
+ return 0
491
+
481
492
  self_name = identity.declared(env=identity.SELF_ONLY)[0] or ""
482
493
  gateway = env_gateway()
483
494
  for i, a in enumerate(argv):
@@ -42,6 +42,7 @@ import json
42
42
  import os
43
43
  import re
44
44
  import shutil
45
+ import subprocess
45
46
  import sys
46
47
  import tempfile
47
48
  from dataclasses import dataclass
@@ -618,16 +619,50 @@ def _read_script_bundle_version(script_path: Path) -> str | None:
618
619
  return m.group(1) if m else None
619
620
 
620
621
 
621
- def _handler_missing_events(required: tuple) -> tuple[frozenset, frozenset]:
622
- """Return (advertised, missing) for the late-bound codegraph handler (#830).
622
+ def _probe_path_handler_events() -> frozenset | None:
623
+ """Ask PATH's ``swarph codegraph-hook --supported-events`` (#830).
624
+
625
+ Same late-bound name the generated script ``exec``s. An in-process import
626
+ cannot see install-vs-PATH skew — measured on lab-ovh after #408. Returns
627
+ ``None`` when the probe cannot run; empty frozenset when the handler
628
+ answers with no lines (old binary that ignores the flag and prints nothing).
623
629
 
624
- Bundles with empty ``handler_events`` skip this gate. Today only
625
- ``codegraph-on-grep`` advertises; the import is the running package on
626
- PATH's peer — i.e. the same interpreter that will execute the hook.
630
+ Empty is NOT "handler was asked and supports nothing" as distinct evidence:
631
+ an old handler silently ignores the unknown flag, reads stdin, finds no
632
+ tool_input, and exits 0 with blank stdout — #830's own silent-success defect
633
+ answering this probe. That empty, a genuine empty advertise, and a failed
634
+ probe (``None`` → treated as empty below) all fail the gate closed; the
635
+ operator message names the likely cause. Do not read empty as a successful
636
+ negotiation.
627
637
  """
628
- from swarph_cli.commands.codegraph_hook import supported_hook_events
638
+ try:
639
+ proc = subprocess.run(
640
+ ["swarph", "codegraph-hook", "--supported-events"],
641
+ capture_output=True,
642
+ text=True,
643
+ encoding="utf-8",
644
+ errors="replace",
645
+ timeout=30,
646
+ stdin=subprocess.DEVNULL,
647
+ )
648
+ except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
649
+ return None
650
+ if proc.returncode != 0:
651
+ return None
652
+ return frozenset(
653
+ ln.strip() for ln in (proc.stdout or "").splitlines() if ln.strip()
654
+ )
655
+
629
656
 
630
- advertised = supported_hook_events()
657
+ def _handler_missing_events(required: tuple) -> tuple[frozenset, frozenset]:
658
+ """Return (advertised, missing) via PATH probe — not an in-process import.
659
+
660
+ Bundles with empty ``handler_events`` skip this gate. A failed probe is
661
+ treated as advertising nothing so install fails closed.
662
+ """
663
+ advertised = _probe_path_handler_events()
664
+ if advertised is None:
665
+ return frozenset(), frozenset(required)
631
666
  return advertised, frozenset(required) - advertised
632
667
 
633
668
 
@@ -685,16 +720,18 @@ def install_hook(
685
720
  out(" Install Git for Windows, then re-run — nothing was written.")
686
721
  return 1
687
722
 
688
- # #830: refuse bindings the RUNNING handler cannot advertise. Code must
689
- # land before or with bindings — never after.
723
+ # #830: refuse bindings PATH's handler cannot advertise. Probe out of
724
+ # process (same ``swarph`` name the script will exec) — an in-process
725
+ # import is inert against install/PATH skew. Code must land before or
726
+ # with bindings, never after.
690
727
  if bundle.handler_events:
691
728
  advertised, missing = _handler_missing_events(bundle.handler_events)
692
729
  if missing:
693
- out(f"cannot install {bundle.name}: running handler does not "
694
- f"advertise events {sorted(missing)}.")
730
+ out(f"cannot install {bundle.name}: PATH's swarph codegraph-hook "
731
+ f"does not advertise events {sorted(missing)}.")
695
732
  out(f" handler advertises: {sorted(advertised) or '(none)'}")
696
- out(" Upgrade swarph-cli so the handler supports these events, "
697
- "then re-run — nothing was written (#830).")
733
+ out(" Upgrade the swarph on PATH so the handler supports these "
734
+ "events, then re-run — nothing was written (#830).")
698
735
  return 1
699
736
 
700
737
  command = _hook_command_path(script_dst)
@@ -7,9 +7,9 @@ design), not by which harness happens to be detected:
7
7
  * ``claude`` / ``codex`` → ARM-INSTRUCTION: the session-start hook emits
8
8
  the watch pipeline (``tail -F inbox.log | dm_notify_filter``) as session
9
9
  context so the agent arms it as a background watch.
10
- * ``cursor`` → VERIFY-AND-REPORT: the wake already lives in swarph (the
11
- monitor's push sink); the hook verifies it at every session start and
12
- says loudly when the cell has no wake path.
10
+ * ``cursor`` → ARM-INSTRUCTION, same tail pipeline as claude/codex, written
11
+ to ``~/.cursor/hooks.json``. The wake is the session-armed tail. A
12
+ missing or stale sidecar inbox.log is said loudly in the session context.
13
13
  * unknown / undetectable harness → LOUD REFUSAL: nonzero exit, nothing
14
14
  written. A silent no-op here would manufacture exactly the
15
15
  armed-looking-but-deaf cell this card exists to eliminate.
@@ -271,9 +271,10 @@ Usage:
271
271
 
272
272
 
273
273
  Installs the silent-wake session-start hook (board card #482). The product
274
- depends on where the wake lives: claude/codex get an arm-instruction (the
275
- tail -F inbox.log | dm_notify_filter watch), cursor gets verify-and-report
276
- of the swarph monitor's push sink. Unknown harnesses are refused loudly.
274
+ depends on where the wake lives: claude, codex, and cursor get an
275
+ arm-instruction (the tail -F inbox.log | dm_notify_filter watch). Cursor
276
+ emits that pipeline as session context and says loudly when the sidecar
277
+ inbox.log is missing or stale. Unknown harnesses are refused loudly.
277
278
  --cell is refused with --scope user (card #527): a baked cell name in a
278
279
  box-global file arms one cell's wake for every session on the box and
279
280
  silently evicts the last installed cell's. Omit --cell and the hook
@@ -444,7 +445,7 @@ def run_install_wake_hook(argv: Optional[list[str]] = None) -> int:
444
445
  product = (
445
446
  "arm-instruction (the session-start hook emits the "
446
447
  "tail -F inbox.log | dm_notify_filter watch as session context)"
447
- if harness in ("claude", "codex", "muse")
448
+ if harness in ("claude", "codex", "muse", "cursor")
448
449
  else "verify-and-report (the session-start hook checks the "
449
450
  "swarph monitor's push sink and says loudly if none exists)"
450
451
  )