swarph-cli 0.41.0__tar.gz → 0.41.2__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 (228) hide show
  1. {swarph_cli-0.41.0/src/swarph_cli.egg-info → swarph_cli-0.41.2}/PKG-INFO +34 -7
  2. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/README.md +33 -5
  3. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/pyproject.toml +8 -2
  4. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/__init__.py +1 -1
  5. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/board_merge_check.py +75 -1
  6. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/codegraph_hook.py +40 -1
  7. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/server.py +15 -1
  8. {swarph_cli-0.41.0 → swarph_cli-0.41.2/src/swarph_cli.egg-info}/PKG-INFO +34 -7
  9. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_board_merge_check.py +129 -1
  10. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_ask_command.py +1 -1
  11. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_hook.py +48 -0
  12. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_brain.py +33 -0
  13. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog.py +1 -1
  14. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/LICENSE +0 -0
  15. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/setup.cfg +0 -0
  16. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/__init__.py +0 -0
  17. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/backends.py +0 -0
  18. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/pack.py +0 -0
  19. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/prices.py +0 -0
  20. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/quality.py +0 -0
  21. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
  22. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/runner.py +0 -0
  23. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/validate.py +0 -0
  24. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/caller.py +0 -0
  25. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/__init__.py +0 -0
  26. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/harden.py +0 -0
  27. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/lineage.py +0 -0
  28. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/liveness.py +0 -0
  29. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/manifest.py +0 -0
  30. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/paths.py +0 -0
  31. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/verify.py +0 -0
  32. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/cell.py +0 -0
  33. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/chain_token.py +0 -0
  34. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/__init__.py +0 -0
  35. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/_display.py +0 -0
  36. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
  37. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/add.py +0 -0
  38. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/bench.py +0 -0
  39. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/board.py +0 -0
  40. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/brain.py +0 -0
  41. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/brain_ask.py +0 -0
  42. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/cell.py +0 -0
  43. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  44. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/channel.py +0 -0
  45. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/chat.py +0 -0
  46. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/codegraph.py +0 -0
  47. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/compress.py +0 -0
  48. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/daemon.py +0 -0
  49. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/event.py +0 -0
  50. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/gateway.py +0 -0
  51. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/group.py +0 -0
  52. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/highlight.py +0 -0
  53. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/hook_output.py +0 -0
  54. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/hooks.py +0 -0
  55. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/import_session.py +0 -0
  56. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/init.py +0 -0
  57. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/install_hook.py +0 -0
  58. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  59. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/lane.py +0 -0
  60. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/mcp_server.py +0 -0
  61. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/memory.py +0 -0
  62. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/memory_sync.py +0 -0
  63. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/mesh.py +0 -0
  64. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/monitor.py +0 -0
  65. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/okf_links.py +0 -0
  66. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/onboard.py +0 -0
  67. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
  68. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/ratify.py +0 -0
  69. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/schedule.py +0 -0
  70. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/security.py +0 -0
  71. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/service.py +0 -0
  72. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/spawn.py +0 -0
  73. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/timeline.py +0 -0
  74. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/watchdog.py +0 -0
  75. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/__init__.py +0 -0
  76. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/levers.py +0 -0
  77. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/marker.py +0 -0
  78. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/verify.py +0 -0
  79. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/console_safe.py +0 -0
  80. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/delivery_queue.py +0 -0
  81. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/__init__.py +0 -0
  82. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
  83. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
  84. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/schema.sql +0 -0
  85. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/services_control.py +0 -0
  86. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/main.py +0 -0
  87. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/multiplexer.py +0 -0
  88. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/parsers/__init__.py +0 -0
  89. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/parsers/claude.py +0 -0
  90. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  91. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/__init__.py +0 -0
  92. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/app.py +0 -0
  93. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/providers.py +0 -0
  94. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/session_bridge.py +0 -0
  95. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/stall_alert.py +0 -0
  96. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  97. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  98. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  99. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  100. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  101. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  102. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/requires.txt +0 -0
  103. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
  104. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_add_security_gate.py +0 -0
  105. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_add.py +0 -0
  106. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_hash.py +0 -0
  107. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_lib.py +0 -0
  108. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_mcp.py +0 -0
  109. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_skill.py +0 -0
  110. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_tool.py +0 -0
  111. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_uri.py +0 -0
  112. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_backends.py +0 -0
  113. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_command.py +0 -0
  114. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_pack.py +0 -0
  115. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_prices.py +0 -0
  116. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_quality.py +0 -0
  117. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_runner.py +0 -0
  118. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_validate.py +0 -0
  119. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_board_command.py +0 -0
  120. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
  121. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_command.py +0 -0
  122. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_caller_meta_guard.py +0 -0
  123. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_lineage.py +0 -0
  124. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_liveness.py +0 -0
  125. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_manifest.py +0 -0
  126. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_paths.py +0 -0
  127. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_security.py +0 -0
  128. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_command_dispatch.py +0 -0
  129. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_harden.py +0 -0
  130. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_loader.py +0 -0
  131. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_selfcheck.py +0 -0
  132. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_verify.py +0 -0
  133. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_chain_token.py +0 -0
  134. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_channel_command.py +0 -0
  135. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_channel_post.py +0 -0
  136. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_chat_command.py +0 -0
  137. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_claude_parser.py +0 -0
  138. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_claude_tmux_template.py +0 -0
  139. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_caller_scope.py +0 -0
  140. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_command.py +0 -0
  141. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_mcp.py +0 -0
  142. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_caller_convention.py +0 -0
  143. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_command.py +0 -0
  144. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_levers.py +0 -0
  145. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_marker.py +0 -0
  146. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_verify.py +0 -0
  147. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_console_encoding_e2e.py +0 -0
  148. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_command.py +0 -0
  149. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_console_encoding.py +0 -0
  150. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_delivery.py +0 -0
  151. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_delivery_queue.py +0 -0
  152. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_display_sanitize.py +0 -0
  153. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_event_emit.py +0 -0
  154. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_feature_to_uri.py +0 -0
  155. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_client.py +0 -0
  156. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_command.py +0 -0
  157. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_group_command.py +0 -0
  158. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_highlight_command.py +0 -0
  159. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hook_output.py +0 -0
  160. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_activity_marker.py +0 -0
  161. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_add.py +0 -0
  162. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_bundle.py +0 -0
  163. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_init.py +0 -0
  164. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_lifecycle.py +0 -0
  165. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_merge.py +0 -0
  166. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_import_command.py +0 -0
  167. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_init_command.py +0 -0
  168. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_install_hook.py +0 -0
  169. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_install_multiplexer_command.py +0 -0
  170. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_lane_command.py +0 -0
  171. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_main.py +0 -0
  172. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_main_unknown_verb_guard.py +0 -0
  173. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mcp_server.py +0 -0
  174. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_command.py +0 -0
  175. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_gateway.py +0 -0
  176. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_sync.py +0 -0
  177. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_sync_membrane.py +0 -0
  178. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_command.py +0 -0
  179. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_inbox_mark_read.py +0 -0
  180. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_inbox_sanitize.py +0 -0
  181. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar.py +0 -0
  182. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  183. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  184. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_meta_edge_identity.py +0 -0
  185. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_cli.py +0 -0
  186. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_daemon_collision.py +0 -0
  187. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_self_name_verification.py +0 -0
  188. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_state_split.py +0 -0
  189. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_multiplexer.py +0 -0
  190. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_okf_links.py +0 -0
  191. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_onboard_command.py +0 -0
  192. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_packaged_artifacts_exist.py +0 -0
  193. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_process_liveness.py +0 -0
  194. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_protocol_handler.py +0 -0
  195. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_providers_isolation.py +0 -0
  196. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_ratify_command.py +0 -0
  197. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_repl_caller_convention.py +0 -0
  198. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_schedule_command.py +0 -0
  199. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_schedule_create_contract.py +0 -0
  200. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_security.py +0 -0
  201. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_app.py +0 -0
  202. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_command.py +0 -0
  203. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_providers.py +0 -0
  204. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_inject.py +0 -0
  205. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_probe.py +0 -0
  206. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_resolve.py +0 -0
  207. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_path_role_gate.py +0 -0
  208. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_shipped_units_match_the_cli.py +0 -0
  209. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_chat.py +0 -0
  210. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_one_shot.py +0 -0
  211. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_phase_5_5.py +0 -0
  212. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_base_hoist.py +0 -0
  213. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_command.py +0 -0
  214. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_live_pin.py +0 -0
  215. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_mitosis_lineage.py +0 -0
  216. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_tmux_session.py +0 -0
  217. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_windows_relaunch.py +0 -0
  218. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_stall_alert.py +0 -0
  219. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_timeline_command.py +0 -0
  220. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_token_file_one_parser.py +0 -0
  221. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_version_consistency.py +0 -0
  222. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake.py +0 -0
  223. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  224. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  225. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_emit_health.py +0 -0
  226. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_install_flags.py +0 -0
  227. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_model_rung.py +0 -0
  228. {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_stale_peers.py +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.41.0
3
+ Version: 0.41.2
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`, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/BrainSurfing-tech/swarph-cli
8
8
  Project-URL: Source, https://github.com/BrainSurfing-tech/swarph-cli
9
9
  Project-URL: Substrate, https://github.com/BrainSurfing-tech/swarph-mesh
10
- Project-URL: Spec, https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
11
10
  Keywords: swarph,llm,cli,mesh,gemini,claude,deepseek
12
11
  Classifier: Development Status :: 3 - Alpha
13
12
  Classifier: Environment :: Console
@@ -467,13 +466,41 @@ swarph watchdog --check --peer researcher --gateway http://localhost:8788 --dm-w
467
466
  Installs Claude Code hooks as **content** wired into `~/.claude/settings.json` — a hook becomes an installable artifact (a script + its event/matcher bindings merged into your settings) with no swarph-cli version bump per hook, the same way `watchdog --install-service` ships systemd units as bundled data.
468
467
 
469
468
  ```bash
470
- swarph hooks init # install the recommended bundled set (cell-resilience)
471
- swarph hooks add cell-resilience # install one builtin by name
472
- swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
473
- swarph hooks list # builtins + install status (installed|available)
469
+ swarph hooks init # install the recommended bundled set
470
+ swarph hooks add codegraph-on-grep # structural search alongside grep (see below)
471
+ swarph hooks add cell-resilience # install one builtin by name
472
+ swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
473
+ swarph hooks list # builtins + install status (installed|available)
474
474
  swarph hooks remove cell-resilience
475
475
  ```
476
476
 
477
+ **Bundled `codegraph-on-grep`** *(0.41.0)*. Binds `PostToolUse`/`Bash`. When a `grep`/`rg` searches
478
+ **code**, it also queries the mesh gateway's structural codegraph and hands back the symbol-level
479
+ answer — definitions, `file:line`, and **caller counts, which grep structurally cannot see**:
480
+
481
+ ```
482
+ CODEGRAPH (structural, index 2.1h old) for 'def _board_grant' — grep found text; this is
483
+ the symbol graph, incl. CALLER COUNTS grep cannot see:
484
+ mesh-gateway/server.py:1352 function _board_grant callers=11
485
+ ```
486
+
487
+ The point is **context cost**: a grep returning 200 matching lines is far more expensive than the
488
+ handful of symbols that answer *"where is this defined and who calls it"*.
489
+
490
+ - **It supplements grep; it never blocks it.** grep stays correct for config, logs and string
491
+ literals — the codegraph indexes *symbols*.
492
+ - **An unavailable index is reported loudly, never as "no matches."** A failed lookup rendered as a
493
+ real negative teaches exactly the wrong thing, so the hook says so and marks the answer
494
+ unverified. A genuinely empty result from a *working* index says `a REAL negative` explicitly, and
495
+ a stale index reports its age.
496
+ - **No local index required.** It asks the gateway, which scopes both rows and caller counts to your
497
+ visibility — resolved from your **bearer token**, never from a field you send, so it cannot be
498
+ self-asserted.
499
+
500
+ Needs `SWARPH_SELF` (or `--as`), your peer token at `~/.config/swarph/<cell>.peer_token`, and a
501
+ reachable gateway. Do not run a hand-rolled copy alongside it: two hooks per `Bash` call means two
502
+ lookups, doubling the cost the hook exists to cut.
503
+
477
504
  **Trust model.** Three tiers: `builtin` (trusted, bundled with swarph-cli — installs without a prompt), `local` (a bundle dir you point at — shown then confirmed before any write), and `published`/`@cell/name` (**fails closed in v1** — never installs another cell's unreviewed code). Signed-publisher identity plus a publish-time security gate is the v2 model.
478
505
 
479
506
  **Bundled `cell-resilience`.** Binds `StopFailure`/`rate_limit` + `Stop`/`(all)` to a script that writes `$XDG_STATE_HOME/swarph/idle_since.json` (`{"session","reason","hook_event","ts"}`, `reason=throttle|normal`) — the push-side throttle detector the watchdog's `--dm-wake` can read instead of polling. Observational only: it never blocks the session and always exits 0 (jq if present, printf/sed fallback otherwise).
@@ -655,7 +682,7 @@ Dry-run is the default — nothing mutates without `--apply`. Cron-friendly exit
655
682
  | `8` | refused — not idempotent (second pass kept cutting; signal-eating alarm) |
656
683
 
657
684
  Design spec: `docs/superpowers/specs/2026-06-11-swarph-context-compressor-design.md`
658
- (in the hedge-fund-mcp repo).
685
+ (in a private design repo; not published).
659
686
 
660
687
  ### `--json` mode semantics
661
688
 
@@ -420,13 +420,41 @@ swarph watchdog --check --peer researcher --gateway http://localhost:8788 --dm-w
420
420
  Installs Claude Code hooks as **content** wired into `~/.claude/settings.json` — a hook becomes an installable artifact (a script + its event/matcher bindings merged into your settings) with no swarph-cli version bump per hook, the same way `watchdog --install-service` ships systemd units as bundled data.
421
421
 
422
422
  ```bash
423
- swarph hooks init # install the recommended bundled set (cell-resilience)
424
- swarph hooks add cell-resilience # install one builtin by name
425
- swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
426
- swarph hooks list # builtins + install status (installed|available)
423
+ swarph hooks init # install the recommended bundled set
424
+ swarph hooks add codegraph-on-grep # structural search alongside grep (see below)
425
+ swarph hooks add cell-resilience # install one builtin by name
426
+ swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
427
+ swarph hooks list # builtins + install status (installed|available)
427
428
  swarph hooks remove cell-resilience
428
429
  ```
429
430
 
431
+ **Bundled `codegraph-on-grep`** *(0.41.0)*. Binds `PostToolUse`/`Bash`. When a `grep`/`rg` searches
432
+ **code**, it also queries the mesh gateway's structural codegraph and hands back the symbol-level
433
+ answer — definitions, `file:line`, and **caller counts, which grep structurally cannot see**:
434
+
435
+ ```
436
+ CODEGRAPH (structural, index 2.1h old) for 'def _board_grant' — grep found text; this is
437
+ the symbol graph, incl. CALLER COUNTS grep cannot see:
438
+ mesh-gateway/server.py:1352 function _board_grant callers=11
439
+ ```
440
+
441
+ The point is **context cost**: a grep returning 200 matching lines is far more expensive than the
442
+ handful of symbols that answer *"where is this defined and who calls it"*.
443
+
444
+ - **It supplements grep; it never blocks it.** grep stays correct for config, logs and string
445
+ literals — the codegraph indexes *symbols*.
446
+ - **An unavailable index is reported loudly, never as "no matches."** A failed lookup rendered as a
447
+ real negative teaches exactly the wrong thing, so the hook says so and marks the answer
448
+ unverified. A genuinely empty result from a *working* index says `a REAL negative` explicitly, and
449
+ a stale index reports its age.
450
+ - **No local index required.** It asks the gateway, which scopes both rows and caller counts to your
451
+ visibility — resolved from your **bearer token**, never from a field you send, so it cannot be
452
+ self-asserted.
453
+
454
+ Needs `SWARPH_SELF` (or `--as`), your peer token at `~/.config/swarph/<cell>.peer_token`, and a
455
+ reachable gateway. Do not run a hand-rolled copy alongside it: two hooks per `Bash` call means two
456
+ lookups, doubling the cost the hook exists to cut.
457
+
430
458
  **Trust model.** Three tiers: `builtin` (trusted, bundled with swarph-cli — installs without a prompt), `local` (a bundle dir you point at — shown then confirmed before any write), and `published`/`@cell/name` (**fails closed in v1** — never installs another cell's unreviewed code). Signed-publisher identity plus a publish-time security gate is the v2 model.
431
459
 
432
460
  **Bundled `cell-resilience`.** Binds `StopFailure`/`rate_limit` + `Stop`/`(all)` to a script that writes `$XDG_STATE_HOME/swarph/idle_since.json` (`{"session","reason","hook_event","ts"}`, `reason=throttle|normal`) — the push-side throttle detector the watchdog's `--dm-wake` can read instead of polling. Observational only: it never blocks the session and always exits 0 (jq if present, printf/sed fallback otherwise).
@@ -608,7 +636,7 @@ Dry-run is the default — nothing mutates without `--apply`. Cron-friendly exit
608
636
  | `8` | refused — not idempotent (second pass kept cutting; signal-eating alarm) |
609
637
 
610
638
  Design spec: `docs/superpowers/specs/2026-06-11-swarph-context-compressor-design.md`
611
- (in the hedge-fund-mcp repo).
639
+ (in a private design repo; not published).
612
640
 
613
641
  ### `--json` mode semantics
614
642
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.41.0"
7
+ version = "0.41.2"
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`, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -51,7 +51,6 @@ dependencies = [
51
51
  Homepage = "https://github.com/BrainSurfing-tech/swarph-cli"
52
52
  Source = "https://github.com/BrainSurfing-tech/swarph-cli"
53
53
  Substrate = "https://github.com/BrainSurfing-tech/swarph-mesh"
54
- Spec = "https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md"
55
54
 
56
55
  [project.optional-dependencies]
57
56
  dev = ["pytest>=7.0"]
@@ -111,4 +110,11 @@ swarph_cli = ["systemd/*.service", "systemd/*.timer", "systemd/*.default",
111
110
 
112
111
  [tool.pytest.ini_options]
113
112
  testpaths = ["tests"]
113
+ # The tree must win over any installed copy. Without this, `pip install swarph-cli`
114
+ # on the same box shadows src/ and the suite silently tests the PUBLISHED wheel
115
+ # instead of your edits — every local change reads green whether or not it works.
116
+ # Measured 2026-08-01: 1689 passed against site-packages vs 1690 against the tree;
117
+ # the only test that noticed was a brand-new one, because the tree and 0.41.0 were
118
+ # nearly identical. That agreement is a polluted baseline, not isolation.
119
+ pythonpath = ["src"]
114
120
  addopts = "-v --tb=short"
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.41.0"
19
+ __version__ = "0.41.2"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -126,11 +126,51 @@ def decide(card: dict, pr_state: dict) -> Decision:
126
126
  "" if verdict else "missing or unparseable — expected '<cell> APPROVED <sha>'")
127
127
 
128
128
  author = card.get("created_by")
129
+ assignee = card.get("assignee")
129
130
  if verdict:
130
131
  # Author identity comes from the CARD. Git says the same account for every
131
132
  # cell, so it cannot answer "was this reviewed by someone else".
132
133
  add("reviewer is not the author", verdict["cell"] != author,
133
134
  f"reviewer={verdict['cell']} author={author}")
135
+ # >>> LEG 2 (#144): READ THE FROZEN, BOUND self_authored FLAG — NOT A
136
+ # GATE-TIME COMPARISON OF AN ASSERTED NAME. <<<
137
+ #
138
+ # The first version of this leg compared verdict["cell"] against
139
+ # card["assignee"]. drop-on-meta-edge showed it was DOUBLY WEAK, verified
140
+ # against the merged commit:
141
+ #
142
+ # (i) ASSERTED — verdict["cell"] is parsed out of the peer_verdict VALUE,
143
+ # which the writer controls. The assignee simply writes
144
+ # "not-me APPROVED <sha>": cell(not-me) != assignee(me) PASSES, and the
145
+ # created_by leg passes too. The self-review leg was defeated by
146
+ # asserting a non-self name, so it meant nothing until the value's cell
147
+ # was forced to equal the bound writer.
148
+ #
149
+ # (ii) GATE-TIME — it read card["assignee"] AT MERGE. Self-verdict, then the
150
+ # work reassigns (the normal flow: 9% of live cards already have
151
+ # created_by != assignee), and the gate sees cell != assignee(new) so a
152
+ # STALE SELF-APPROVAL PASSES. Frozen-vs-mutable, on the fix itself.
153
+ #
154
+ # `self_authored` closes both in one read. The gateway computes it
155
+ # SERVER-SIDE from row["assignee"] == the BOUND writer and FREEZES it in
156
+ # link_stamps at write time: BOUND (so no asserted name can dodge it — no
157
+ # dependency on leg (a)) and FROZEN (so reassignment cannot launder it).
158
+ #
159
+ # ABSENCE IS NOT INNOCENCE. A verdict written before the flag existed has no
160
+ # stamp, and treating that as "not self-authored" would make this leg
161
+ # vacuous for exactly the backlog it must cover. ok=None => COULD NOT
162
+ # EVALUATE => REFUSE, which is this file's existing doctrine for the
163
+ # unreachable-GitHub case and the reason it has a tri-state at all.
164
+ _vstamp = (card.get("link_stamps") or {}).get("peer_verdict")
165
+ if _vstamp is None:
166
+ add("verdict is not self-authored", None,
167
+ "no link_stamp for peer_verdict — cannot tell who wrote it "
168
+ "(pre-#199 verdict, or a producer that dropped link_stamps); "
169
+ "COULD NOT EVALUATE, never a pass")
170
+ else:
171
+ add("verdict is not self-authored", not _vstamp.get("self_authored"),
172
+ f"self_authored={_vstamp.get('self_authored')!r} by={_vstamp.get('by')!r} "
173
+ f"— the person being reviewed cannot supply the review")
134
174
  add("verdict is APPROVED", verdict["verdict"] == "APPROVED",
135
175
  f"verdict={verdict['verdict']}")
136
176
  head = pr_state.get("head_sha") or ""
@@ -220,7 +260,41 @@ def fetch_pr_state(repo: str, number: str) -> dict:
220
260
 
221
261
 
222
262
  def format_decision(d: Decision) -> str:
223
- lines = [f"card #{d.card_id}: {d.verdict}"]
263
+ """Render a decision, and >>> DISTINGUISH *FAILED* FROM *COULD NOT EVALUATE* IN
264
+ THE SUMMARY, NOT ONLY PER-LINE. <<<
265
+
266
+ Condition 1 of the date-holder's ruling on #144 (droplet, 2026-08-01):
267
+
268
+ "A gate that graduates and then refuses everything is INDISTINGUISHABLE
269
+ from a gate that never graduated — from the outside, and from the board.
270
+ The abstention rate must be REPORTED, not silent."
271
+
272
+ The register exists because "a recording phase that never graduates is a
273
+ control that does not exist, and it LOOKS LIKE PROGRESS ON A BOARD."
274
+ Abstention is that same failure wearing the opposite mask: near-total
275
+ abstention is acceptable as a TRANSIENT and is a dead control as a steady
276
+ state, and the only thing that tells them apart is whether anyone can SEE it
277
+ without going to look.
278
+
279
+ SCOPE, stated so it is not mistaken for more: this tool evaluates ONE card per
280
+ run (--card-json). It cannot compute a RATE. What it can do — and now does — is
281
+ make every run self-describing, naming which checks abstained and why, so the
282
+ caller that iterates cards can aggregate without parsing prose.
283
+ """
284
+ ok = sum(1 for c in d.checks if c.ok is True)
285
+ failed = [c for c in d.checks if c.ok is False]
286
+ blind = [c for c in d.checks if c.ok is None]
287
+ head = f"card #{d.card_id}: {d.verdict}"
288
+ if failed or blind:
289
+ head += (f" — {ok} ok · {len(failed)} FAILED · "
290
+ f"{len(blind)} COULD-NOT-EVALUATE")
291
+ if blind:
292
+ # Name them. A count alone cannot tell "abstained on self-authorship
293
+ # because the stamp predates the flag" from "abstained because GitHub
294
+ # was unreachable", and those decay differently: one shrinks to zero as
295
+ # verdicts are rewritten, the other is an outage.
296
+ head += f" [blind: {', '.join(c.name for c in blind)}]"
297
+ lines = [head]
224
298
  for c in d.checks:
225
299
  mark = "ok " if c.ok is True else ("FAIL" if c.ok is False else "????")
226
300
  lines.append(f" [{mark}] {c.name}" + (f" — {c.detail}" if c.detail else ""))
@@ -152,6 +152,35 @@ def query_gateway(term: str, gateway: str, token: str, limit: int = MAX_ROWS) ->
152
152
  return {"error": f"{type(e).__name__}: {e}"}
153
153
 
154
154
 
155
+ def _match_quality(term: str, rows: list) -> tuple:
156
+ """Did the results actually match what was ASKED, or only a common token?
157
+
158
+ >>> THE EMPTY ANSWER WAS MADE HONEST; THE NON-EMPTY ONE WAS NOT. <<<
159
+ (Reported first-hand by a peer, 2026-08-01.) The index's query sanitiser
160
+ OR-JOINS tokens, so `def command_beta_executor` matches anything containing
161
+ "command". He grepped a private-repo file and got six confident-looking
162
+ swarph-cli symbols WITH CALLER COUNTS — plausible structure from the wrong
163
+ repository, and caller counts make it read authoritative. His words: "the
164
+ failure mode you engineered out of the empty case walked back in through the
165
+ non-empty one."
166
+
167
+ So: find the query's DISTINCTIVE token — the longest content token, which is
168
+ the one carrying the intent (`command_beta_executor`, not `def`) — and check
169
+ whether ANY returned symbol name actually contains it. If none does, every
170
+ row is a common-token coincidence and must be labelled as such rather than
171
+ served as an answer.
172
+
173
+ Returns (distinctive_token, n_rows_matching_it).
174
+ """
175
+ toks = [t for t in re.findall(r"[A-Za-z0-9_]+", (term or "").lower())
176
+ if len(t) > 2 and t not in {"def", "class", "async", "the", "for"}]
177
+ if not toks:
178
+ return ("", len(rows))
179
+ key = max(toks, key=len)
180
+ hits = sum(1 for r in rows if key in str(r.get("name", "")).lower())
181
+ return (key, hits)
182
+
183
+
155
184
  def render(term: str, env: dict) -> str:
156
185
  """Format the envelope for injection. Errors are LOUD and named."""
157
186
  if "error" in env:
@@ -179,8 +208,18 @@ def render(term: str, env: dict) -> str:
179
208
  f"{r.get('kind')} {r.get('name')} callers={r.get('callers')}"
180
209
  for r in rows[:MAX_ROWS]
181
210
  )
211
+ warn = ""
212
+ if rows:
213
+ key, hits = _match_quality(term, rows)
214
+ if key and hits == 0:
215
+ repos = sorted({str(r.get("repo")) for r in rows})
216
+ warn = (f"\n >>> FUZZY MATCH — NOT AN ANSWER TO YOUR QUERY. No returned symbol's "
217
+ f"name contains {key!r}. These matched a COMMON TOKEN only"
218
+ + (f", and all are from: {', '.join(repos)}" if repos else "")
219
+ + f". Treat them as unrelated: the symbol you grepped for is NOT in "
220
+ f"what this index can see. <<<")
182
221
  return (f"CODEGRAPH (structural{age}) for '{term}' — grep found text; this is "
183
- f"the symbol graph, incl. CALLER COUNTS grep cannot see:\n{body}\n\n"
222
+ f"the symbol graph, incl. CALLER COUNTS grep cannot see:\n{body}{warn}\n\n"
184
223
  f"Use this for definitions/callers/blast-radius. grep remains correct "
185
224
  f"for config, logs and string literals the codegraph does not index.")
186
225
 
@@ -76,7 +76,12 @@ PORT = int(os.environ.get("PORT", "8788"))
76
76
  # Gateway-held gbrain proxy token (POST /brain/query). Cells authenticate to
77
77
  # THIS gateway with their mesh token; the gateway then presents ITS OWN
78
78
  # gbrain token upstream so cells never need a separate gbrain_ credential.
79
- GATEWAY_GBRAIN_URL = os.environ.get("GATEWAY_GBRAIN_URL", "http://100.107.222.72:8792/mcp")
79
+ # No default host. This shipped for 79 releases with a hardcoded PRIVATE TAILNET address as
80
+ # its fallback, so every public install pointed at one operator's box unless the deployer
81
+ # happened to set the variable. Unroutable from outside, so never an access hole — but it
82
+ # disclosed topology, and a default that silently targets someone else's host is wrong even
83
+ # when it cannot be reached. Unset now means UNCONFIGURED, which is the honest state.
84
+ GATEWAY_GBRAIN_URL = os.environ.get("GATEWAY_GBRAIN_URL", "")
80
85
  GATEWAY_GBRAIN_TOKEN = os.environ.get("GATEWAY_GBRAIN_TOKEN", "")
81
86
 
82
87
  # B1 Meta-Edge identity (META_EDGE_IDENTITY_CONTRACT.md). Meta-Edge SSO ISSUES
@@ -4219,6 +4224,15 @@ def _brain_query_upstream(question: str, limit: int) -> list:
4219
4224
  """Proxy a READ-ONLY gbrain query. Builds the MCP `query` call itself (never
4220
4225
  write/admin), POSTs to GATEWAY_GBRAIN_URL with the gateway-held token, returns
4221
4226
  the chunk array. Raises on any upstream/parse failure (caller maps to 502)."""
4227
+ if not GATEWAY_GBRAIN_URL:
4228
+ # Named, not incidental. Removing the hardcoded default turned "unconfigured"
4229
+ # into a reachable state for the first time, and an empty URL would otherwise
4230
+ # surface as a urllib ValueError — a transport error for a CONFIGURATION fault,
4231
+ # which sends the reader looking at the network instead of their env.
4232
+ raise RuntimeError(
4233
+ "GATEWAY_GBRAIN_URL is not set — the brain proxy is UNCONFIGURED, not "
4234
+ "unreachable. Set it to your gbrain MCP endpoint. (Releases up to 0.41.0 "
4235
+ "silently defaulted to one operator's private host; that default is gone.)")
4222
4236
  body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
4223
4237
  "params": {"name": "query",
4224
4238
  "arguments": {"query": question, "limit": limit, "expand": False}}}
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.41.0
3
+ Version: 0.41.2
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`, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/BrainSurfing-tech/swarph-cli
8
8
  Project-URL: Source, https://github.com/BrainSurfing-tech/swarph-cli
9
9
  Project-URL: Substrate, https://github.com/BrainSurfing-tech/swarph-mesh
10
- Project-URL: Spec, https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
11
10
  Keywords: swarph,llm,cli,mesh,gemini,claude,deepseek
12
11
  Classifier: Development Status :: 3 - Alpha
13
12
  Classifier: Environment :: Console
@@ -467,13 +466,41 @@ swarph watchdog --check --peer researcher --gateway http://localhost:8788 --dm-w
467
466
  Installs Claude Code hooks as **content** wired into `~/.claude/settings.json` — a hook becomes an installable artifact (a script + its event/matcher bindings merged into your settings) with no swarph-cli version bump per hook, the same way `watchdog --install-service` ships systemd units as bundled data.
468
467
 
469
468
  ```bash
470
- swarph hooks init # install the recommended bundled set (cell-resilience)
471
- swarph hooks add cell-resilience # install one builtin by name
472
- swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
473
- swarph hooks list # builtins + install status (installed|available)
469
+ swarph hooks init # install the recommended bundled set
470
+ swarph hooks add codegraph-on-grep # structural search alongside grep (see below)
471
+ swarph hooks add cell-resilience # install one builtin by name
472
+ swarph hooks add ./my-hook # install a local bundle dir (hook.json + script)
473
+ swarph hooks list # builtins + install status (installed|available)
474
474
  swarph hooks remove cell-resilience
475
475
  ```
476
476
 
477
+ **Bundled `codegraph-on-grep`** *(0.41.0)*. Binds `PostToolUse`/`Bash`. When a `grep`/`rg` searches
478
+ **code**, it also queries the mesh gateway's structural codegraph and hands back the symbol-level
479
+ answer — definitions, `file:line`, and **caller counts, which grep structurally cannot see**:
480
+
481
+ ```
482
+ CODEGRAPH (structural, index 2.1h old) for 'def _board_grant' — grep found text; this is
483
+ the symbol graph, incl. CALLER COUNTS grep cannot see:
484
+ mesh-gateway/server.py:1352 function _board_grant callers=11
485
+ ```
486
+
487
+ The point is **context cost**: a grep returning 200 matching lines is far more expensive than the
488
+ handful of symbols that answer *"where is this defined and who calls it"*.
489
+
490
+ - **It supplements grep; it never blocks it.** grep stays correct for config, logs and string
491
+ literals — the codegraph indexes *symbols*.
492
+ - **An unavailable index is reported loudly, never as "no matches."** A failed lookup rendered as a
493
+ real negative teaches exactly the wrong thing, so the hook says so and marks the answer
494
+ unverified. A genuinely empty result from a *working* index says `a REAL negative` explicitly, and
495
+ a stale index reports its age.
496
+ - **No local index required.** It asks the gateway, which scopes both rows and caller counts to your
497
+ visibility — resolved from your **bearer token**, never from a field you send, so it cannot be
498
+ self-asserted.
499
+
500
+ Needs `SWARPH_SELF` (or `--as`), your peer token at `~/.config/swarph/<cell>.peer_token`, and a
501
+ reachable gateway. Do not run a hand-rolled copy alongside it: two hooks per `Bash` call means two
502
+ lookups, doubling the cost the hook exists to cut.
503
+
477
504
  **Trust model.** Three tiers: `builtin` (trusted, bundled with swarph-cli — installs without a prompt), `local` (a bundle dir you point at — shown then confirmed before any write), and `published`/`@cell/name` (**fails closed in v1** — never installs another cell's unreviewed code). Signed-publisher identity plus a publish-time security gate is the v2 model.
478
505
 
479
506
  **Bundled `cell-resilience`.** Binds `StopFailure`/`rate_limit` + `Stop`/`(all)` to a script that writes `$XDG_STATE_HOME/swarph/idle_since.json` (`{"session","reason","hook_event","ts"}`, `reason=throttle|normal`) — the push-side throttle detector the watchdog's `--dm-wake` can read instead of polling. Observational only: it never blocks the session and always exits 0 (jq if present, printf/sed fallback otherwise).
@@ -655,7 +682,7 @@ Dry-run is the default — nothing mutates without `--apply`. Cron-friendly exit
655
682
  | `8` | refused — not idempotent (second pass kept cutting; signal-eating alarm) |
656
683
 
657
684
  Design spec: `docs/superpowers/specs/2026-06-11-swarph-context-compressor-design.md`
658
- (in the hedge-fund-mcp repo).
685
+ (in a private design repo; not published).
659
686
 
660
687
  ### `--json` mode semantics
661
688
 
@@ -28,7 +28,15 @@ SHA = "abc1234def5678"
28
28
  def _card(**over):
29
29
  card = {"id": 152, "stage": "build", "move_ready": True, "created_by": "lab-ovh",
30
30
  "links": {"pr": "https://github.com/o/r/pull/152",
31
- "peer_verdict": f"droplet APPROVED {SHA}"}}
31
+ "peer_verdict": f"droplet APPROVED {SHA}"},
32
+ # #144 leg 2: the DEFAULT fixture is a NORMAL card — a bound
33
+ # third-party verdict. Without a stamp the self-authorship leg reports
34
+ # COULD-NOT-EVALUATE (never a pass), which is correct behaviour and
35
+ # would otherwise make every unrelated test refuse for the wrong
36
+ # reason. Tests that care about the flag override it explicitly.
37
+ "link_stamps": {"peer_verdict": {"by": "droplet", "self_authored": False,
38
+ "caller_bound": True,
39
+ "at": "2026-08-01T00:00:00Z"}}}
32
40
  card.update(over)
33
41
  return card
34
42
 
@@ -219,3 +227,123 @@ def test_cli_reports_dry_run(tmp_path, capsys):
219
227
  out = capsys.readouterr().out
220
228
  assert "DRY RUN" in out
221
229
  assert rc == 2, "offline means COULD NOT EVALUATE (2), never a measured refusal (1)"
230
+
231
+
232
+ # ── #144 leg 2: read the FROZEN, BOUND self_authored flag ────────────────────
233
+ # The first version compared verdict["cell"] against card["assignee"].
234
+ # drop-on-meta-edge showed it was doubly weak: (i) ASSERTED — the writer controls
235
+ # the cell string, so an assignee writing "not-me APPROVED <sha>" passed it; and
236
+ # (ii) GATE-TIME — it read the assignee at merge, so a self-verdict laundered by a
237
+ # later reassignment passed. Both close by reading the server-computed flag.
238
+
239
+ def _stamped(self_authored, by="droplet", **over):
240
+ """A card whose peer_verdict carries a link_stamp."""
241
+ return _card(link_stamps={"peer_verdict": {"by": by, "self_authored": self_authored,
242
+ "caller_bound": True, "at": "2026-08-01T00:00:00Z"}},
243
+ **over)
244
+
245
+
246
+ def test_a_SELF_AUTHORED_verdict_is_refused():
247
+ """>>> THE HOLE. <<< `is_exec_assignee` permits the assignee to write
248
+ peer_verdict on their own card, so the person being reviewed supplies the
249
+ review — no forgery, no privilege escalation. 20 of 213 live cards (9%) have
250
+ created_by != assignee, so the created_by leg passes on the normal split."""
251
+ d = mc.decide(_stamped(True, created_by="lab-ovh"), _pr())
252
+ assert not _fail(d, "reviewer is not the author"), (
253
+ "the created_by leg fired — this would pass for the wrong reason")
254
+ assert _fail(d, "verdict is not self-authored")
255
+ assert d.verdict != "WOULD_MERGE"
256
+
257
+
258
+ def test_an_ASSERTED_non_self_name_CANNOT_dodge_it():
259
+ """WEAKNESS (i) OF THE FIRST VERSION, pinned. The assignee writes a verdict
260
+ naming someone else. The old leg compared that ASSERTED string and passed;
261
+ the flag is computed server-side from the BOUND writer, so the string is
262
+ irrelevant."""
263
+ card = _stamped(True, created_by="lab-ovh",
264
+ links={"pr": "https://github.com/o/r/pull/152",
265
+ "peer_verdict": f"not-me APPROVED {SHA}"})
266
+ d = mc.decide(card, _pr())
267
+ assert _fail(d, "verdict is not self-authored"), (
268
+ "an asserted non-self name defeated the self-review leg")
269
+
270
+
271
+ def test_a_LATER_REASSIGNMENT_cannot_launder_a_self_verdict():
272
+ """WEAKNESS (ii), pinned. droplet self-verdicts, then the work is reassigned.
273
+ A gate-time comparison sees cell != assignee(new) and passes; the FROZEN flag
274
+ still says the verdict was self-authored when written."""
275
+ card = _stamped(True, created_by="lab-ovh", assignee="gridiron") # reassigned away
276
+ d = mc.decide(card, _pr())
277
+ assert _fail(d, "verdict is not self-authored")
278
+
279
+
280
+ def test_a_genuine_third_party_review_still_passes():
281
+ """NON-VACUITY. If the leg fired on real reviews it would be a blocker, not a
282
+ gate — and third-party review is the entire feature."""
283
+ card = _stamped(False, by="grok-researcher", created_by="lab-ovh",
284
+ links={"pr": "https://github.com/o/r/pull/152",
285
+ "peer_verdict": f"grok-researcher APPROVED {SHA}"})
286
+ d = mc.decide(card, _pr())
287
+ assert not _fail(d, "verdict is not self-authored")
288
+ assert d.verdict == "WOULD_MERGE", [(c.name, c.detail) for c in d.blockers]
289
+
290
+
291
+ def test_a_MISSING_stamp_is_COULD_NOT_EVALUATE_never_a_pass():
292
+ """>>> ABSENCE IS NOT INNOCENCE. <<< A verdict written before the flag existed
293
+ has no stamp. Treating that as "not self-authored" would make this leg vacuous
294
+ for exactly the backlog it must cover — the empty-subject defect, in the fix
295
+ for a self-review defect. ok=None is this file's existing doctrine."""
296
+ for absent in ({}, None, {"some_other_key": {"by": "x"}}):
297
+ d = mc.decide(_card(created_by="lab-ovh", link_stamps=absent), _pr())
298
+ assert _fail(d, "verdict is not self-authored"), (
299
+ f"a verdict with NO peer_verdict stamp passed (link_stamps={absent!r}) "
300
+ "— absence read as innocence")
301
+ check = [c for c in d.blockers if c.name == "verdict is not self-authored"][0]
302
+ assert check.ok is None, "must be COULD-NOT-EVALUATE, not a hard False"
303
+
304
+
305
+ def test_the_summary_DISTINGUISHES_failed_from_could_not_evaluate():
306
+ """>>> CONDITION 1 OF THE DATE-HOLDER'S RULING. <<< (droplet, #144.)
307
+
308
+ "A gate that graduates and then refuses everything is INDISTINGUISHABLE from a
309
+ gate that never graduated — from the outside, and from the board."
310
+
311
+ A REFUSE that fails a check and a REFUSE that could not see its subject are
312
+ different facts with different decay: the first is the gate working, the
313
+ second shrinks to zero as verdicts are rewritten — or doesn't, which is the
314
+ dead-control signal. They must not print the same.
315
+ """
316
+ d = mc.decide(_card(created_by="lab-ovh", link_stamps={}), _pr())
317
+ out = mc.format_decision(d)
318
+ assert "COULD-NOT-EVALUATE" in out
319
+ assert "blind:" in out
320
+ assert "verdict is not self-authored" in out.split("\n")[0], (
321
+ "the abstaining check must be NAMED on the summary line, not only "
322
+ "buried in the per-check list — a count cannot tell a stamp-less backlog "
323
+ "from a GitHub outage")
324
+
325
+
326
+ def test_a_clean_pass_does_not_print_an_abstention_summary():
327
+ """NON-VACUITY. If the summary always printed the tri-state it would be noise
328
+ on every healthy run, and noise is how a gate stops being read."""
329
+ out = mc.format_decision(mc.decide(_card(), _pr()))
330
+ assert "COULD-NOT-EVALUATE" not in out
331
+ assert out.split("\n")[0] == "card #152: WOULD_MERGE"
332
+
333
+
334
+ def test_a_READY_card_with_NO_peer_verdict_REFUSES():
335
+ """>>> drop-on-meta-edge's pin, and it guards a live emptiness. <<< MEASURED
336
+ 2026-08-01: 13 cards are in build/test and ZERO carry a peer_verdict link. So
337
+ at arming EVERY ready card hits "peer verdict recorded on the card" = False →
338
+ REFUSE → human gate. The gate is safe-BY-EMPTINESS today.
339
+
340
+ That is the same empty-subject shape found all night (group_grants, the verdict
341
+ store) except here it fails SAFE — refuse, not pass — which is the one
342
+ direction that makes an empty subject acceptable rather than a defect.
343
+
344
+ Pinned so the day someone "helpfully" defaults a missing verdict to pass, it
345
+ fails loudly instead of silently auto-merging an unreviewed card.
346
+ """
347
+ d = mc.decide(_card(links={"pr": "https://github.com/o/r/pull/152"}), _pr())
348
+ assert _fail(d, "peer verdict recorded on the card")
349
+ assert d.verdict != "WOULD_MERGE"
@@ -17,7 +17,7 @@ from swarph_cli.commands import brain_ask as ba
17
17
 
18
18
  def test_version_is_0_33_0():
19
19
  import swarph_cli
20
- assert swarph_cli.__version__ == "0.41.0"
20
+ assert swarph_cli.__version__ == "0.41.2"
21
21
 
22
22
 
23
23
  # --- request / response plumbing -------------------------------------------
@@ -143,3 +143,51 @@ def test_the_hook_is_a_registered_builtin_bundle():
143
143
  def test_the_verb_is_registered():
144
144
  from swarph_cli.main import _VERB_HANDLERS # noqa: PLC0415
145
145
  assert "codegraph-hook" in _VERB_HANDLERS
146
+
147
+
148
+ # ── the non-empty answer must be as honest as the empty one ───────────────
149
+
150
+ def test_a_FUZZY_match_is_labelled_not_served_as_an_answer():
151
+ """>>> a peer's first-hand receipt, 2026-08-01: the defect this closes. <<<
152
+
153
+ He grepped a private-repo file for `def command_beta_executor` and got six
154
+ swarph-cli symbols WITH CALLER COUNTS — the sanitiser OR-joins tokens, so it
155
+ matched "command". His diagnosis: "the failure mode you engineered out of the
156
+ empty case walked back in through the non-empty one." An empty answer said
157
+ "REAL negative"; a wrong answer said nothing at all.
158
+ """
159
+ env = {"results": [
160
+ {"repo": "swarph-cli", "file_path": "src/x.py", "start_line": 46,
161
+ "kind": "variable", "name": "_HOOK_COMMAND", "callers": 0},
162
+ {"repo": "swarph-cli", "file_path": "src/y.py", "start_line": 62,
163
+ "kind": "function", "name": "provider_command", "callers": 1},
164
+ ], "freshness": [{"index_age_hours": 1.6}]}
165
+ out = ch.render("def command_beta_executor", env)
166
+ assert "FUZZY MATCH" in out
167
+ assert "NOT AN ANSWER TO YOUR QUERY" in out
168
+ assert "command_beta_executor" in out
169
+ assert "swarph-cli" in out # names the repo the strays came from
170
+
171
+
172
+ def test_a_GENUINE_match_carries_no_fuzzy_warning():
173
+ """Non-vacuity pair. If the warning fired on real hits it would be noise, and
174
+ a warning that always fires is one nobody reads."""
175
+ env = {"results": [
176
+ {"repo": "service-repo", "file_path": "server.py", "start_line": 1352,
177
+ "kind": "function", "name": "_board_grant", "callers": 11},
178
+ ], "freshness": [{"index_age_hours": 2.0}]}
179
+ out = ch.render("_board_grant", env)
180
+ assert "FUZZY MATCH" not in out
181
+ assert "_board_grant" in out
182
+
183
+
184
+ def test_a_PARTIAL_match_is_treated_as_genuine():
185
+ """One real hit among strays still answers the question — warn only when NOTHING
186
+ matches, or the signal degrades into noise."""
187
+ env = {"results": [
188
+ {"repo": "swarph-cli", "file_path": "a.py", "start_line": 1,
189
+ "kind": "variable", "name": "_HOOK_COMMAND", "callers": 0},
190
+ {"repo": "private-repo", "file_path": "workers/worker_beta.py",
191
+ "start_line": 900, "kind": "function", "name": "command_beta_executor", "callers": 3},
192
+ ], "freshness": [{"index_age_hours": 1.0}]}
193
+ assert "FUZZY MATCH" not in ch.render("def command_beta_executor", env)