swarph-cli 0.45.1__tar.gz → 0.46.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swarph_cli-0.45.1/src/swarph_cli.egg-info → swarph_cli-0.46.0}/PKG-INFO +2 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/README.md +1 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/pyproject.toml +1 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/board.py +123 -3
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/cell_selfcheck.py +4 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/codegraph.py +181 -6
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/codegraph_hook.py +5 -2
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/compress.py +4 -2
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/daemon.py +1 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/gh_route.py +2 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/hooks.py +201 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/init.py +20 -3
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_wake_hook.py +39 -14
- swarph_cli-0.46.0/src/swarph_cli/commands/memory_emit_hook.py +157 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/mesh.py +271 -13
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/monitor.py +411 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/onboard.py +45 -9
- swarph_cli-0.46.0/src/swarph_cli/commands/postcompact_hook_output.py +114 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/ratify.py +1 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/spawn.py +63 -172
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/wake_hook_output.py +37 -4
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/watchdog.py +12 -2
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/server.py +15 -2
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/guide/GUIDE.md +7 -3
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/main.py +8 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0/src/swarph_cli.egg-info}/PKG-INFO +2 -1
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/SOURCES.txt +15 -0
- swarph_cli-0.46.0/tests/test_124_register_merge.py +112 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_307_task4_ask_reply.py +129 -0
- swarph_cli-0.46.0/tests/test_319_refusal_exit_codes.py +147 -0
- swarph_cli-0.46.0/tests/test_532_accept_flag.py +109 -0
- swarph_cli-0.46.0/tests/test_535_version_capability.py +113 -0
- swarph_cli-0.46.0/tests/test_546_default_gateway.py +164 -0
- swarph_cli-0.46.0/tests/test_552_codegraph_gateway.py +231 -0
- swarph_cli-0.46.0/tests/test_555_relay_row_shape.py +70 -0
- swarph_cli-0.46.0/tests/test_556_relay_freshness.py +90 -0
- swarph_cli-0.46.0/tests/test_board_obligation_close.py +77 -0
- swarph_cli-0.46.0/tests/test_heartbeat_check.py +220 -0
- swarph_cli-0.46.0/tests/test_hooks_verify.py +163 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_init_command.py +21 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_init_provider_menu.py +75 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_install_wake_hook.py +95 -8
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_command.py +9 -3
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_meta_edge_identity.py +28 -0
- swarph_cli-0.46.0/tests/test_obligation_close_is_surfaced.py +57 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_onboard_command.py +87 -0
- swarph_cli-0.46.0/tests/test_postcompact_recall.py +285 -0
- swarph_cli-0.46.0/tests/test_shipped_units_match_the_cli.py +368 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_base_hoist.py +39 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_command.py +3 -1
- swarph_cli-0.45.1/tests/test_shipped_units_match_the_cli.py +0 -124
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/LICENSE +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/setup.cfg +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_main.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_security.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.45.1 → swarph_cli-0.46.0}/tests/test_windows_waker_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.46.0
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -186,6 +186,7 @@ swarph board cards thread <id> [--limit N] # the card's conversation (a card
|
|
|
186
186
|
swarph board cards say <id> --content "…" [--to <peer>] [--kind fyi|question|answer|status|unblock]
|
|
187
187
|
swarph board cards ask <id> <holder> "<what>" [--timeout-hours N] # MINT an obligation:
|
|
188
188
|
# name who owes what as a ROW, not a sentence (#307)
|
|
189
|
+
swarph board obligations close <id> --outcome <pass|fail|cannot_evaluate> --evidence "what you observed"
|
|
189
190
|
```
|
|
190
191
|
|
|
191
192
|
`--project` accepts a numeric id **or** a slug (resolved via `projects list`). Note: card creation always starts at `proposed` (the gateway has no stage-on-create) — use `cards move` to advance.
|
|
@@ -138,6 +138,7 @@ swarph board cards thread <id> [--limit N] # the card's conversation (a card
|
|
|
138
138
|
swarph board cards say <id> --content "…" [--to <peer>] [--kind fyi|question|answer|status|unblock]
|
|
139
139
|
swarph board cards ask <id> <holder> "<what>" [--timeout-hours N] # MINT an obligation:
|
|
140
140
|
# name who owes what as a ROW, not a sentence (#307)
|
|
141
|
+
swarph board obligations close <id> --outcome <pass|fail|cannot_evaluate> --evidence "what you observed"
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
`--project` accepts a numeric id **or** a slug (resolved via `projects list`). Note: card creation always starts at `proposed` (the gateway has no stage-on-create) — use `cards move` to advance.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.46.0"
|
|
8
8
|
description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -12,12 +12,24 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import argparse
|
|
14
14
|
import json
|
|
15
|
+
import re
|
|
15
16
|
import sys
|
|
16
17
|
import urllib.error
|
|
17
18
|
import urllib.parse
|
|
18
19
|
import urllib.request
|
|
19
20
|
from typing import Optional
|
|
20
21
|
|
|
22
|
+
# #532 — negative-branch marker detection. THREE COPIES exist and no single
|
|
23
|
+
# suite pins all three: here, mesh-gateway server.py (_accept_state), and
|
|
24
|
+
# mesh-gateway scripts/obligation_sweep.py. Unpinned-by-design is not the same
|
|
25
|
+
# decision as standalone-by-design — each repo's tests pin the same seven
|
|
26
|
+
# phrases. The detector is TWO-SIDED (drop-on-meta-edge, PR #124): it
|
|
27
|
+
# over-reads wishes ("must not fail") and under-reads real checks without the
|
|
28
|
+
# token ("Otherwise reject"); word boundaries remove the incidental compounds
|
|
29
|
+
# (failover, fail-safe, failsafe). Detection is named, never concluded — no
|
|
30
|
+
# substring test can know a falsifier exists.
|
|
31
|
+
_FAIL_MARKER_RE = re.compile(r"(?<![\w-])fail(?:s|ed|ing|ures?)?(?![\w-])", re.IGNORECASE)
|
|
32
|
+
|
|
21
33
|
from swarph_cli.commands._content import ContentError, add_content_args, resolve_content
|
|
22
34
|
from swarph_cli.commands._display import sanitize_terminal as _s
|
|
23
35
|
from swarph_cli.commands.mesh import (
|
|
@@ -196,6 +208,41 @@ def _thread_recipient(card: dict, explicit_to: Optional[str]) -> str:
|
|
|
196
208
|
)
|
|
197
209
|
|
|
198
210
|
|
|
211
|
+
def _say_line(resp: dict, card_id, to_node: str) -> str:
|
|
212
|
+
"""The post confirmation — AND any obligation the post just DISCHARGED.
|
|
213
|
+
|
|
214
|
+
>>> THE GATEWAY ALREADY SAID SO AND THIS CLI THREW IT AWAY. <<< POST
|
|
215
|
+
/messages returns `closed_obligations: [ids]` (server.py:4077-4099): any
|
|
216
|
+
message a HOLDER posts to a thread closes their oldest open obligation on
|
|
217
|
+
it, unconditionally, without consulting the `accept` falsifier #532 added.
|
|
218
|
+
The old formatter read `id` only, so the terminal printed "posted id=NNN"
|
|
219
|
+
and nothing else.
|
|
220
|
+
|
|
221
|
+
MEASURED CONSEQUENCE (science-claude, card #562): obligation #22 on card
|
|
222
|
+
#544 closed at 06:59:15 on a status post whose literal content was that the
|
|
223
|
+
work was NOT done — one membrane passing, four CANNOT_EVALUATE, two
|
|
224
|
+
proposals unstarted. IT STAYED WRONGLY CLOSED FOR SIX HOURS while its holder
|
|
225
|
+
said in five further messages that it was unmet, and nothing compared the
|
|
226
|
+
two. `obligation_sweep.py` could not catch it either: that sweep selects
|
|
227
|
+
`status = 'open'`, so a wrongly-closed row falls out of the set forever.
|
|
228
|
+
|
|
229
|
+
The signal existed, was correct, and travelled the wire. It died at the last
|
|
230
|
+
hop, in the formatter. A one-line print is not the fix for the auto-close
|
|
231
|
+
POLICY (that is #562's own question) -- it is the fix for the holder not
|
|
232
|
+
being told, which is what made six hours of divergence possible.
|
|
233
|
+
"""
|
|
234
|
+
line = f"posted id={resp.get('id')} onto card #{card_id} (to {to_node})"
|
|
235
|
+
closed = resp.get("closed_obligations") or []
|
|
236
|
+
if closed:
|
|
237
|
+
ids = ", ".join(f"#{i}" for i in closed)
|
|
238
|
+
line += (f"\n >>> THIS POST CLOSED OBLIGATION {ids}. <<< Posting to a "
|
|
239
|
+
f"thread discharges your oldest open obligation on it — the "
|
|
240
|
+
f"accept check was NOT evaluated. If the work is not actually "
|
|
241
|
+
f"done, say so now: a closed obligation leaves the sweep set "
|
|
242
|
+
f"and nothing will chase it again.")
|
|
243
|
+
return line
|
|
244
|
+
|
|
245
|
+
|
|
199
246
|
def _card_say_payload(from_node: str, to_node: str, kind: str, content: str,
|
|
200
247
|
thread_uuid: str) -> dict:
|
|
201
248
|
return {
|
|
@@ -215,13 +262,32 @@ def _format_ask(d) -> str:
|
|
|
215
262
|
An obligation with no timeout never goes red on its own, and the operator has to
|
|
216
263
|
be told that AT THE MOMENT THEY CREATE IT — afterwards it looks identical to one
|
|
217
264
|
that simply is not late yet.
|
|
265
|
+
|
|
266
|
+
#532 applies the same rule to the falsifier: an obligation with no accept
|
|
267
|
+
check reads RED in the sweep, and one whose check has no FAIL branch is
|
|
268
|
+
marked NO-FAIL-BRANCH — both are said AT MINT TIME, because afterwards the
|
|
269
|
+
rows look identical to sound ones.
|
|
218
270
|
"""
|
|
219
271
|
when = d.get("timeout_at")
|
|
220
272
|
deadline = f"overdue after {when}" if when else (
|
|
221
273
|
"NO TIMEOUT — this never goes red on its own; pass --timeout-hours if it should")
|
|
274
|
+
accept = d.get("accept")
|
|
275
|
+
if not accept:
|
|
276
|
+
falsifier = ("NO ACCEPT CHECK — reads RED in the sweep; pass --accept "
|
|
277
|
+
"\"PASS = ... | FAIL = ...\" naming an observable and a way "
|
|
278
|
+
"it comes out negative")
|
|
279
|
+
elif not _FAIL_MARKER_RE.search(str(accept)):
|
|
280
|
+
falsifier = ("accept check has NO FAIL BRANCH — marked NO-FAIL-BRANCH in "
|
|
281
|
+
"the sweep; a check that cannot come out negative is not one")
|
|
282
|
+
else:
|
|
283
|
+
falsifier = f"accept: {accept}"
|
|
222
284
|
return (f"obligation #{d.get('id')} on card #{d.get('card_id')}: "
|
|
223
285
|
f"{d.get('holder')} owes it, status={d.get('status')}, {deadline}\n"
|
|
224
|
-
f"
|
|
286
|
+
f" {falsifier}\n"
|
|
287
|
+
f" thread {d.get('thread_uuid')} — work lands IN that thread; CLOSE "
|
|
288
|
+
f"it with `swarph board obligations close {d.get('id')}` plus an "
|
|
289
|
+
f"outcome and evidence; a plain `mesh send` cannot close it "
|
|
290
|
+
f"(#509/#562 — the VERB is the mechanism, the thread is where it lands)")
|
|
225
291
|
|
|
226
292
|
|
|
227
293
|
def _format_thread(data) -> str:
|
|
@@ -301,12 +367,36 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
301
367
|
ck.add_argument("--timeout-hours", type=int, default=None,
|
|
302
368
|
help="hours until this obligation reads OVERDUE (default: none, "
|
|
303
369
|
"which means it never goes red on its own)")
|
|
370
|
+
ck.add_argument("--accept", default=None, metavar='"PASS = ... | FAIL = ..."',
|
|
371
|
+
help="the falsifiable check that closes this obligation "
|
|
372
|
+
"(#532). Omitting it mints an obligation that reads RED "
|
|
373
|
+
"in the sweep; a check with no FAIL branch is marked "
|
|
374
|
+
"NO-FAIL-BRANCH — 'verify it works' is not a check")
|
|
304
375
|
ck.add_argument("--kind", default="action", help="obligation kind (default: action)")
|
|
305
376
|
ck.add_argument("--json", action="store_true"); _add_common(ck)
|
|
306
377
|
|
|
307
378
|
cr = cards.add_parser("ready", help="flag a card ready-to-advance (move_ready) for the orchestrator")
|
|
308
379
|
cr.add_argument("id", type=int); cr.add_argument("--clear", action="store_true", help="unset move_ready")
|
|
309
380
|
cr.add_argument("--json", action="store_true"); _add_common(cr)
|
|
381
|
+
|
|
382
|
+
obl = top.add_parser(
|
|
383
|
+
"obligations",
|
|
384
|
+
help="card obligations — the close act (#562)").add_subparsers(
|
|
385
|
+
dest="command", required=True)
|
|
386
|
+
oc = obl.add_parser(
|
|
387
|
+
"close",
|
|
388
|
+
help="CLOSE an obligation: the DISTINCT ACT that records the check's "
|
|
389
|
+
"outcome plus the evidence observed. A thread reply does NOT "
|
|
390
|
+
"close an obligation (#562) — this does")
|
|
391
|
+
oc.add_argument("id", type=int, help="the obligation id")
|
|
392
|
+
oc.add_argument("--outcome", required=True,
|
|
393
|
+
choices=("pass", "fail", "cannot_evaluate"),
|
|
394
|
+
help="the accept check's OUTCOME")
|
|
395
|
+
oc.add_argument("--evidence", required=True,
|
|
396
|
+
help="what you OBSERVED running the check — whitespace is "
|
|
397
|
+
"refused: whitespace evidence is the vibe-close this "
|
|
398
|
+
"endpoint exists to kill")
|
|
399
|
+
oc.add_argument("--json", action="store_true"); _add_common(oc)
|
|
310
400
|
return p
|
|
311
401
|
|
|
312
402
|
|
|
@@ -355,6 +445,35 @@ def run_board(argv: list[str]) -> int:
|
|
|
355
445
|
st, d = _post_json(f"{gw}/board/projects", _project_add_payload(self_name, args.slug, args.title, goal=args.goal), token)
|
|
356
446
|
return _out(st, d, lambda x: f"created project #{x.get('id')} ({x.get('slug')})", aj)
|
|
357
447
|
|
|
448
|
+
if args.group == "obligations":
|
|
449
|
+
if args.command == "close":
|
|
450
|
+
# #562's CLI half: the explicit close act. The gateway guards the
|
|
451
|
+
# identity (holder, creator, or board orchestrator) and refuses a
|
|
452
|
+
# second close; the CLI's job is to refuse what it can see —
|
|
453
|
+
# whitespace evidence — and to propagate the gateway's refusals
|
|
454
|
+
# with their detail intact.
|
|
455
|
+
evidence = args.evidence
|
|
456
|
+
if not evidence.strip():
|
|
457
|
+
print("swarph board obligations close: --evidence is "
|
|
458
|
+
"whitespace-only — the close act records what you "
|
|
459
|
+
"OBSERVED, and nothing was observed", file=sys.stderr)
|
|
460
|
+
return 2
|
|
461
|
+
st, d = _post_json(
|
|
462
|
+
f"{gw}/board/obligations/{args.id}/close",
|
|
463
|
+
{"outcome": args.outcome, "evidence": evidence}, token)
|
|
464
|
+
if st < 200 or st >= 300:
|
|
465
|
+
detail = d.get("detail", d) if isinstance(d, dict) else d
|
|
466
|
+
print(f"swarph board obligations close: gateway {st}: {_s(detail)}",
|
|
467
|
+
file=sys.stderr)
|
|
468
|
+
return 1
|
|
469
|
+
if aj:
|
|
470
|
+
print(json.dumps(d, indent=2))
|
|
471
|
+
else:
|
|
472
|
+
print(f"obligation #{_s(d.get('id', args.id))} CLOSED by "
|
|
473
|
+
f"{_s(d.get('closed_by', self_name))} — outcome: "
|
|
474
|
+
f"{_s(d.get('close_outcome', args.outcome))}")
|
|
475
|
+
return 0
|
|
476
|
+
|
|
358
477
|
if args.group == "cards":
|
|
359
478
|
if args.command == "list":
|
|
360
479
|
pid, err = _resolve_project(gw, token, args.project)
|
|
@@ -426,6 +545,8 @@ def run_board(argv: list[str]) -> int:
|
|
|
426
545
|
"kind": args.kind}
|
|
427
546
|
if args.timeout_hours is not None:
|
|
428
547
|
body["timeout_hours"] = args.timeout_hours
|
|
548
|
+
if args.accept is not None:
|
|
549
|
+
body["accept"] = args.accept
|
|
429
550
|
st, d = _post_json(f"{gw}/board/cards/{args.id}/ask", body, token)
|
|
430
551
|
return _out(st, d, _format_ask, args.json)
|
|
431
552
|
if args.command == "say":
|
|
@@ -461,8 +582,7 @@ def run_board(argv: list[str]) -> int:
|
|
|
461
582
|
# explicit `propose` grant. `_out` passes `detail` through whole; it does
|
|
462
583
|
# not need help, and a second copy of that logic is a second thing to
|
|
463
584
|
# keep in sync.
|
|
464
|
-
return _out(st, d, lambda x:
|
|
465
|
-
f"#{args.id} (to {to_node})", aj)
|
|
585
|
+
return _out(st, d, lambda x: _say_line(x, args.id, to_node), aj)
|
|
466
586
|
if args.command == "ready":
|
|
467
587
|
st, d = _patch_json(f"{gw}/board/cards/{args.id}", {"actor": self_name, "move_ready": not args.clear}, token)
|
|
468
588
|
return _out(st, d, lambda x: f"card #{x.get('id')} move_ready -> {x.get('move_ready')}", aj)
|
|
@@ -289,7 +289,10 @@ def unit_is_relevant(name: str, text: str) -> bool:
|
|
|
289
289
|
# If the canonical chain grows a leg, this one must too — that is the known
|
|
290
290
|
# cost of the circularity break, stated here rather than discovered.
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
# TAILNET IP, NOT localhost and NOT a MagicDNS name (commander, 2026-08-21).
|
|
293
|
+
# The mesh-gateway binds HOST=100.107.222.72 ONLY; localhost has never been bound.
|
|
294
|
+
# MESH_GATEWAY_URL overrides — the escape hatch for anyone outside this mesh.
|
|
295
|
+
_RESOLVER_DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
293
296
|
|
|
294
297
|
|
|
295
298
|
def resolver_gateway(env: dict) -> str:
|
|
@@ -32,6 +32,7 @@ import json
|
|
|
32
32
|
import os
|
|
33
33
|
import re
|
|
34
34
|
import sqlite3
|
|
35
|
+
import sys
|
|
35
36
|
|
|
36
37
|
DEFAULT_INDEX = os.path.expanduser("~/.swarph/codegraph/index.db")
|
|
37
38
|
# NOTE: named DEFAULT_CALLER_CELL, not "*_CALLER" — this is an A8 caller-CELL
|
|
@@ -41,6 +42,13 @@ DEFAULT_INDEX = os.path.expanduser("~/.swarph/codegraph/index.db")
|
|
|
41
42
|
# against that unrelated dotted-slug convention.
|
|
42
43
|
DEFAULT_CALLER_CELL = "lab-ovh"
|
|
43
44
|
|
|
45
|
+
# NOT 1 and NOT 2. A distinct code so "nothing was searched" can never be confused
|
|
46
|
+
# with a crash or a usage error, and so a caller can branch on it specifically.
|
|
47
|
+
# This is the AVAILABILITY axis, the same thing the gateway answers with a 503 --
|
|
48
|
+
# separate from the VISIBILITY axis stdout carries. rc is the one channel the frozen
|
|
49
|
+
# stdout contract left free.
|
|
50
|
+
RC_NO_INDEX = 3
|
|
51
|
+
|
|
44
52
|
# --- textsearch (ported verbatim from codegraph_index/textsearch.py) ------
|
|
45
53
|
|
|
46
54
|
_STOP = {"the", "a", "an", "is", "are", "was", "how", "does", "do", "of", "to", "in", "on", "for", "and", "or",
|
|
@@ -212,24 +220,191 @@ def format_human(rows, term) -> str:
|
|
|
212
220
|
lines.append(f"{i}. {r['name']} [{r['kind']}] {r['repo']} {loc}{callers}")
|
|
213
221
|
if r["signature"]:
|
|
214
222
|
lines.append(f" {r['signature']}")
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
# >>> .get, AND ONLY HERE. <<< The RELAYED rows (#552) carry seven keys --
|
|
224
|
+
# repo, name, kind, file_path, start_line, callers, signature -- and NOT
|
|
225
|
+
# docstring, which the endpoint does not project. Every other key is common
|
|
226
|
+
# to both producers, so they stay subscripted: a missing `name` or
|
|
227
|
+
# `file_path` is a BROKEN PAYLOAD and must raise rather than format as blank.
|
|
228
|
+
# Loosening all of them would turn a real fault into a quiet half-answer.
|
|
229
|
+
doc = (r.get("docstring") or "").strip()
|
|
230
|
+
if doc:
|
|
231
|
+
first = doc.splitlines()[0][:100]
|
|
217
232
|
if first:
|
|
218
233
|
lines.append(f" {first}")
|
|
219
234
|
return "\n".join(lines)
|
|
220
235
|
|
|
221
236
|
|
|
237
|
+
def _resolve_gateway(arg) -> str:
|
|
238
|
+
"""Gateway base URL for the SEARCH-RELAY path (#552).
|
|
239
|
+
|
|
240
|
+
>>> THE CANONICAL VARIABLE IS MESH_GATEWAY_URL, AND SWARPH_BRAIN_GATEWAY IS
|
|
241
|
+
DELIBERATELY *NOT* IN THIS CHAIN. <<<
|
|
242
|
+
|
|
243
|
+
The first draft copied `highlight`'s chain, which ends in SWARPH_BRAIN_GATEWAY.
|
|
244
|
+
drop-on-meta-edge measured what that actually resolves to on this box:
|
|
245
|
+
|
|
246
|
+
SWARPH_CODEGRAPH_GATEWAY <unset>
|
|
247
|
+
SWARPH_GATEWAY <unset>
|
|
248
|
+
SWARPH_BRAIN_GATEWAY http://100.107.222.72:8788 <- brain-NAMED, MESH value
|
|
249
|
+
MESH_GATEWAY_URL http://100.107.222.72:8788 <- correct, and NOT consulted
|
|
250
|
+
|
|
251
|
+
The relay worked ONLY because a brain-named variable was mis-set to the mesh
|
|
252
|
+
gateway's address. Every link that resolved was unset or wrong-by-name, and the
|
|
253
|
+
one that was right was the one not read -- while #546 canonicalised
|
|
254
|
+
MESH_GATEWAY_URL across four modules the same morning.
|
|
255
|
+
|
|
256
|
+
>>> AND THE FAILURE IS TRIGGERED BY SOMEONE DOING THE RIGHT THING. <<<
|
|
257
|
+
SWARPH_BRAIN_GATEWAY names the brain (:8792, mint :8793) -- a different service on
|
|
258
|
+
different ports. The day anyone points it where its NAME says, this POSTs to
|
|
259
|
+
:8792/codegraph: a mesh peer token handed to a service it was not minted for, a
|
|
260
|
+
non-2xx, the fallback, and droplet's original symptom returns ON THE DAY SOMEONE
|
|
261
|
+
FIXED A VARIABLE. A fallback to a differently-named service is not a safety net;
|
|
262
|
+
it re-arms the collision on the day it fires.
|
|
263
|
+
|
|
264
|
+
`--gateway` / `--local` override.
|
|
265
|
+
"""
|
|
266
|
+
return (arg
|
|
267
|
+
or os.environ.get("SWARPH_CODEGRAPH_GATEWAY")
|
|
268
|
+
or os.environ.get("SWARPH_GATEWAY")
|
|
269
|
+
or os.environ.get("MESH_GATEWAY_URL")
|
|
270
|
+
or "").strip()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _query_via_gateway(gateway: str, query: str, limit: int, token_file):
|
|
274
|
+
"""POST to the gateway's /codegraph — THE SERVER SEARCHES, WE GET THE ANSWER.
|
|
275
|
+
|
|
276
|
+
>>> WHY A RELAY AND NOT A SHIPPED INDEX (commander, 2026-08-21): one index,
|
|
277
|
+
one truth. Copying the db to every cell makes N stores that drift, which is
|
|
278
|
+
the divergence this fleet hit four separate ways the same week. <<<
|
|
279
|
+
|
|
280
|
+
The endpoint takes NO caller field on purpose -- the caller comes from the
|
|
281
|
+
bearer token and cannot be self-asserted the way `--caller-cell` can here.
|
|
282
|
+
So the relayed path is strictly *less* spoofable than the local one.
|
|
283
|
+
|
|
284
|
+
Returns (rows, index_present, freshness).
|
|
285
|
+
|
|
286
|
+
index_present is False when the SERVER says 503 -- the availability axis, kept
|
|
287
|
+
distinct from an empty result.
|
|
288
|
+
|
|
289
|
+
>>> WE DELIBERATELY IGNORE THE BODY'S `index_present` FIELD. <<< (drop, review of
|
|
290
|
+
PR #280, measured against the live endpoint.) The endpoint hardcodes it True on
|
|
291
|
+
its only 200 path and raises 503 otherwise, so on the wire the field carries no
|
|
292
|
+
information. The 503 IS the signal. A later "improvement" that reads the body
|
|
293
|
+
field instead would be branching on a constant and would silently stop detecting
|
|
294
|
+
the very thing it appears to check.
|
|
295
|
+
|
|
296
|
+
`freshness` is a list of per-repo dicts -- repo, indexed_at, index_age_hours,
|
|
297
|
+
stale, basis. The first cut of this relay DROPPED it, which meant the automatic
|
|
298
|
+
grep hook warned about a stale index while the HAND-TYPED verb did not -- and the
|
|
299
|
+
hand-typed verb is the consumer this relay was built for, because that is how
|
|
300
|
+
droplet hit the original problem. Card #193 exists because this index has run
|
|
301
|
+
BACKWARD before, so a stale index answering confidently is an OBSERVED failure,
|
|
302
|
+
not a hypothetical.
|
|
303
|
+
"""
|
|
304
|
+
from swarph_cli.commands.mesh import _post_json, _resolve_token # local import: keeps
|
|
305
|
+
# the local-only path free of any mesh dependency, so an offline cell still works.
|
|
306
|
+
|
|
307
|
+
self_name = os.environ.get("SWARPH_SELF") or DEFAULT_CALLER_CELL
|
|
308
|
+
token = _resolve_token(self_name, token_file)
|
|
309
|
+
body = {"query": query, "limit": max(1, min(int(limit), 25))}
|
|
310
|
+
status, payload = _post_json(gateway.rstrip("/") + "/codegraph", body, token)
|
|
311
|
+
if status == 503:
|
|
312
|
+
return [], False, []
|
|
313
|
+
if status >= 400:
|
|
314
|
+
raise RuntimeError(f"gateway returned {status}: {str(payload)[:160]}")
|
|
315
|
+
payload = payload or {}
|
|
316
|
+
return payload.get("results", []), True, (payload.get("freshness") or [])
|
|
317
|
+
|
|
318
|
+
|
|
222
319
|
def run_codegraph(argv) -> int:
|
|
223
320
|
ap = argparse.ArgumentParser(
|
|
224
321
|
prog="swarph codegraph",
|
|
225
322
|
description="On-demand structural code search: find where a symbol is defined or what calls it, "
|
|
226
|
-
"across the indexed repos. Natural-language query."
|
|
323
|
+
"across the indexed repos. Natural-language query. Queries the mesh gateway when one "
|
|
324
|
+
"is configured (the server searches and returns the answer); falls back to a local index.")
|
|
227
325
|
ap.add_argument("query", help="natural-language query, e.g. 'which function escapes HTML'")
|
|
228
326
|
ap.add_argument("--index", default=DEFAULT_INDEX, help=f"index db (default {DEFAULT_INDEX})")
|
|
229
327
|
ap.add_argument("--caller-cell", default=DEFAULT_CALLER_CELL, help="A8 caller identity (default lab-ovh)")
|
|
230
328
|
ap.add_argument("--limit", type=int, default=8)
|
|
231
329
|
ap.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
330
|
+
ap.add_argument("--gateway", default=None,
|
|
331
|
+
help="mesh-gateway base URL for relayed search (else $SWARPH_CODEGRAPH_GATEWAY / "
|
|
332
|
+
"$SWARPH_GATEWAY / $SWARPH_BRAIN_GATEWAY). The server holds the index.")
|
|
333
|
+
ap.add_argument("--local", action="store_true",
|
|
334
|
+
help="force the local index even when a gateway is configured")
|
|
335
|
+
ap.add_argument("--token-file", default=None, help="explicit bearer token file")
|
|
232
336
|
a = ap.parse_args(argv)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
337
|
+
|
|
338
|
+
gateway = "" if a.local else _resolve_gateway(a.gateway)
|
|
339
|
+
source = "local"
|
|
340
|
+
# The LOCAL path computes no freshness -- the index file is right there and the
|
|
341
|
+
# hook that used to state its age reads the mtime itself. Only the relayed path
|
|
342
|
+
# receives it, and only the relayed path can be silently answering from a store
|
|
343
|
+
# the user cannot see.
|
|
344
|
+
freshness = []
|
|
345
|
+
if gateway:
|
|
346
|
+
try:
|
|
347
|
+
rows, index_present, freshness = _query_via_gateway(
|
|
348
|
+
gateway, a.query, a.limit, a.token_file)
|
|
349
|
+
source = "gateway"
|
|
350
|
+
except Exception as exc: # noqa: BLE001
|
|
351
|
+
# >>> LOUD, NEVER A SILENT FALL BACK TO LOCAL. <<< A relay failure that
|
|
352
|
+
# degrades quietly to an empty local index is indistinguishable from
|
|
353
|
+
# "no matches" -- the exact confusion this change exists to remove.
|
|
354
|
+
print(f"swarph codegraph: gateway {gateway} unreachable ({type(exc).__name__}: {exc}); "
|
|
355
|
+
f"falling back to the local index at {a.index}", file=sys.stderr)
|
|
356
|
+
rows = structural_query(a.query, index_path=a.index,
|
|
357
|
+
caller_cell=a.caller_cell, limit=a.limit)
|
|
358
|
+
index_present = os.path.exists(os.path.expanduser(a.index))
|
|
359
|
+
else:
|
|
360
|
+
rows = structural_query(a.query, index_path=a.index,
|
|
361
|
+
caller_cell=a.caller_cell, limit=a.limit)
|
|
362
|
+
index_present = os.path.exists(os.path.expanduser(a.index))
|
|
363
|
+
|
|
364
|
+
stale = [f for f in freshness if f.get("stale")]
|
|
365
|
+
if stale:
|
|
366
|
+
# >>> A STALE INDEX ANSWERING CONFIDENTLY IS THE #193 FAILURE, AND IT LOOKS
|
|
367
|
+
# EXACTLY LIKE A GOOD ANSWER. <<< stderr, not stdout: the rows are real and a
|
|
368
|
+
# machine consumer should still get them unmodified. The user needs to know
|
|
369
|
+
# the line numbers may have moved; the parser does not.
|
|
370
|
+
for f in stale:
|
|
371
|
+
print(f"swarph codegraph: STALE INDEX for {f.get('repo', '?')} — "
|
|
372
|
+
f"{f.get('index_age_hours', 0):.1f}h old ({f.get('basis', 'unknown basis')}); "
|
|
373
|
+
f"verify line numbers against the file before acting on them.",
|
|
374
|
+
file=sys.stderr)
|
|
375
|
+
|
|
376
|
+
if not index_present:
|
|
377
|
+
# >>> STDERR, AND --json STAYS A BARE LIST. <<< .claude/hooks/codegraph-on-grep.sh
|
|
378
|
+
# does `rows=json.loads(...)` and iterates, so wrapping stdout in an envelope
|
|
379
|
+
# would break the hook that fires on every grep in the fleet. The availability
|
|
380
|
+
# signal therefore goes to stderr, where it costs no consumer anything.
|
|
381
|
+
#
|
|
382
|
+
# >>> THE EXIT CODE IS THE CHANNEL THAT WAS FREE ALL ALONG (drop, review of
|
|
383
|
+
# PR #279). <<< The stdout contract is frozen -- codegraph-on-grep.sh does
|
|
384
|
+
# json.loads() and iterates, so an envelope breaks the caller that fires on
|
|
385
|
+
# every grep in the fleet. But rc costs that contract NOTHING: a consumer
|
|
386
|
+
# that ignores rc sees exactly what it saw before, and one that reads it gets
|
|
387
|
+
# the availability axis the gateway already expresses as a 503.
|
|
388
|
+
#
|
|
389
|
+
# The hook itself already branches on `[ $rc -ne 0 ]` and reports the query as
|
|
390
|
+
# UNCORROBORATED, so this lands in a handler that exists.
|
|
391
|
+
#
|
|
392
|
+
# It also closes the ambiguity my previous commit MOVED rather than removed:
|
|
393
|
+
# dropping the false "No structural matches" line left the human path at NO
|
|
394
|
+
# STDOUT, NO STDERR (the hook redirects it), EXIT 0 -- and silence plus success
|
|
395
|
+
# reads as "ran fine, nothing found". Same rc closes both halves.
|
|
396
|
+
print(f"swarph codegraph: NO INDEX AVAILABLE (source={source}) — this is NOT a "
|
|
397
|
+
f"negative result; nothing was searched.", file=sys.stderr)
|
|
398
|
+
|
|
399
|
+
if a.json:
|
|
400
|
+
print(json.dumps(rows, indent=2))
|
|
401
|
+
elif not index_present:
|
|
402
|
+
# >>> A WARNING IS NOT ENOUGH IF THE CLAIM IS STILL PRINTED UNDER IT. <<<
|
|
403
|
+
# `format_human` says "No structural matches for X" -- a claim about the CODE,
|
|
404
|
+
# made when nothing was searched. The human path owns both streams, so it does
|
|
405
|
+
# not make the claim; a warning above a contradicting result just asks the
|
|
406
|
+
# reader to pick, and they pick the one that looks like a result.
|
|
407
|
+
pass
|
|
408
|
+
else:
|
|
409
|
+
print(format_human(rows, a.query))
|
|
410
|
+
return 0 if index_present else RC_NO_INDEX
|
|
@@ -37,7 +37,7 @@ import urllib.request
|
|
|
37
37
|
from pathlib import Path
|
|
38
38
|
from typing import Optional
|
|
39
39
|
|
|
40
|
-
DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://
|
|
40
|
+
DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
|
|
41
41
|
TIMEOUT_S = 6
|
|
42
42
|
MAX_ROWS = 6
|
|
43
43
|
|
|
@@ -252,7 +252,10 @@ def run_codegraph_hook(argv: Optional[list] = None) -> int:
|
|
|
252
252
|
return 0
|
|
253
253
|
tp = _token_path(self_name)
|
|
254
254
|
try:
|
|
255
|
-
|
|
255
|
+
# encoding="utf-8": on Windows a bare read_text() uses the locale codec, and a
|
|
256
|
+
# non-ASCII byte in the token file crashes the hook with a charmap traceback
|
|
257
|
+
# that names a credential path.
|
|
258
|
+
token = tp.read_text(encoding="utf-8").strip()
|
|
256
259
|
except OSError as e:
|
|
257
260
|
_emit(f"CODEGRAPH UNAVAILABLE for '{term}' — peer token unreadable at {tp}: "
|
|
258
261
|
f"{e}. Not a negative result: the graph was never asked.")
|
|
@@ -31,7 +31,9 @@ def run_compress(argv: list[str]) -> int:
|
|
|
31
31
|
if not path.exists():
|
|
32
32
|
print(f"[compress] no such file: {path}", file=sys.stderr)
|
|
33
33
|
return 2
|
|
34
|
-
|
|
34
|
+
# utf-8 explicitly: a bare read_text() uses the locale codec, and on Windows
|
|
35
|
+
# (charmap) a non-ASCII byte in a marked file crashes the verb (#550).
|
|
36
|
+
source = path.read_text(encoding="utf-8")
|
|
35
37
|
marker = parse_marker(source)
|
|
36
38
|
if marker is None:
|
|
37
39
|
print(f"[compress] REFUSE: {path} carries no swarph:compress marker — leaving untouched", file=sys.stderr)
|
|
@@ -47,7 +49,7 @@ def run_compress(argv: list[str]) -> int:
|
|
|
47
49
|
print(f"[compress] archival: {path.name} {len(source)}B → {len(res.live)}B live "
|
|
48
50
|
f"(+{len(res.archive)}B → {archive_path.name}); saved {saved}B always-loaded")
|
|
49
51
|
if args.apply:
|
|
50
|
-
archive_path.write_text(res.archive)
|
|
52
|
+
archive_path.write_text(res.archive, encoding="utf-8")
|
|
51
53
|
_atomic_write(path, res.live)
|
|
52
54
|
print(f"[compress] applied (.bak left, archive {archive_path.name})")
|
|
53
55
|
return 0
|
|
@@ -85,7 +85,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
85
85
|
)
|
|
86
86
|
p.add_argument(
|
|
87
87
|
"--gateway",
|
|
88
|
-
default=os.environ.get("MESH_GATEWAY_URL", "http://
|
|
88
|
+
default=os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788"),
|
|
89
89
|
help="mesh-gateway base URL.",
|
|
90
90
|
)
|
|
91
91
|
p.add_argument(
|
|
@@ -51,6 +51,8 @@ def _allow_unchanged() -> int:
|
|
|
51
51
|
|
|
52
52
|
def run_hook(stdin_text: Optional[str] = None) -> int:
|
|
53
53
|
raw = stdin_text if stdin_text is not None else sys.stdin.read()
|
|
54
|
+
# PS 5.1 native pipes prefix a (double) UTF-8 BOM; strip before parse.
|
|
55
|
+
raw = (raw or "").lstrip("\ufeff")
|
|
54
56
|
try:
|
|
55
57
|
payload = json.loads(raw or "{}")
|
|
56
58
|
except Exception:
|