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.
- {swarph_cli-0.41.0/src/swarph_cli.egg-info → swarph_cli-0.41.2}/PKG-INFO +34 -7
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/README.md +33 -5
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/pyproject.toml +8 -2
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/board_merge_check.py +75 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/codegraph_hook.py +40 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/server.py +15 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2/src/swarph_cli.egg-info}/PKG-INFO +34 -7
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_board_merge_check.py +129 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_ask_command.py +1 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_hook.py +48 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_brain.py +33 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog.py +1 -1
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/LICENSE +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/setup.cfg +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_board_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_group_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_import_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_init_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_main.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_security.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_app.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.41.0 → swarph_cli-0.41.2}/tests/test_watchdog_model_rung.py +0 -0
- {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.
|
|
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
|
|
471
|
-
swarph hooks add
|
|
472
|
-
swarph hooks add
|
|
473
|
-
swarph hooks
|
|
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
|
|
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
|
|
424
|
-
swarph hooks add
|
|
425
|
-
swarph hooks add
|
|
426
|
-
swarph hooks
|
|
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
|
|
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.
|
|
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"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
471
|
-
swarph hooks add
|
|
472
|
-
swarph hooks add
|
|
473
|
-
swarph hooks
|
|
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
|
|
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.
|
|
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)
|