swarph-cli 0.62.0__tar.gz → 0.63.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.62.0/src/swarph_cli.egg-info → swarph_cli-0.63.0}/PKG-INFO +2 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/README.md +1 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/pyproject.toml +1 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/board.py +59 -12
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codegraph.py +63 -8
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/guide.py +55 -2
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/mcp_server.py +9 -2
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/mesh.py +24 -7
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/monitor.py +4 -2
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/onboard.py +8 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/spawn.py +67 -11
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/watchdog.py +33 -7
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/guide/GUIDE.md +151 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/main.py +18 -0
- swarph_cli-0.63.0/src/swarph_cli/source_text.py +129 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0/src/swarph_cli.egg-info}/PKG-INFO +2 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/SOURCES.txt +7 -0
- swarph_cli-0.63.0/tests/test_126_pending_from_complete.py +116 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_397_gh_identity_router.py +4 -1
- swarph_cli-0.63.0/tests/test_547_verbs_from_registry.py +59 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_552_codegraph_gateway.py +15 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_556_relay_freshness.py +15 -1
- swarph_cli-0.63.0/tests/test_866_doctrine_guide.py +71 -0
- swarph_cli-0.63.0/tests/test_872_identity_refuses_not_warns.py +129 -0
- swarph_cli-0.63.0/tests/test_874_source_text.py +116 -0
- swarph_cli-0.63.0/tests/test_888_opencode_data_dir_can_fail.py +71 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_board_card_thread.py +58 -2
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_codegraph_mcp.py +15 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_monitor_state_split.py +2 -1
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_opencode_membrane.py +45 -15
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/LICENSE +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/setup.cfg +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_main.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_security.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_version_command.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.62.0 → swarph_cli-0.63.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.63.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
|
|
@@ -148,6 +148,7 @@ swarph guide # the whole guide
|
|
|
148
148
|
swarph guide channels # one topic
|
|
149
149
|
swarph guide --list # every topic, with the commands it teaches
|
|
150
150
|
swarph guide --search subscribe # find a topic by INTENT, not by its name
|
|
151
|
+
swarph guide doctrine # the standard of evidence a cell is held to (v0.63.0)
|
|
151
152
|
```
|
|
152
153
|
|
|
153
154
|
Three ways in, because a new cell does not know which word it has:
|
|
@@ -100,6 +100,7 @@ swarph guide # the whole guide
|
|
|
100
100
|
swarph guide channels # one topic
|
|
101
101
|
swarph guide --list # every topic, with the commands it teaches
|
|
102
102
|
swarph guide --search subscribe # find a topic by INTENT, not by its name
|
|
103
|
+
swarph guide doctrine # the standard of evidence a cell is held to (v0.63.0)
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
Three ways in, because a new cell does not know which word it has:
|
|
@@ -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.63.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" }
|
|
@@ -390,8 +390,38 @@ def _thread_url(gateway: str, card_id: int, *, limit: Optional[int] = None) -> s
|
|
|
390
390
|
return f"{base}?limit={limit}" if limit else base
|
|
391
391
|
|
|
392
392
|
|
|
393
|
-
def
|
|
394
|
-
"""
|
|
393
|
+
def _project_owner_orchestrator(gw: str, token: str, project_id) -> Optional[str]:
|
|
394
|
+
"""Resolve ``projects.owner_orchestrator`` for a card's project_id.
|
|
395
|
+
|
|
396
|
+
>>> THE CARD PAYLOAD DOES NOT CARRY THE OWNER (#864). <<< Measured
|
|
397
|
+
2026-09-17: GET /board/cards/{id} has assignee/project_id and no owner
|
|
398
|
+
field; GET /board/projects rows carry ``owner_orchestrator`` (NOT NULL on
|
|
399
|
+
the server). A second call is required — inventing a sentinel peer is the
|
|
400
|
+
#259 failure mode this helper exists to avoid.
|
|
401
|
+
"""
|
|
402
|
+
if project_id is None:
|
|
403
|
+
return None
|
|
404
|
+
st, data = _http_get_json(f"{gw.rstrip('/')}/board/projects", token)
|
|
405
|
+
if st != 200:
|
|
406
|
+
return None
|
|
407
|
+
rows = data if isinstance(data, list) else (data or {}).get("projects", [])
|
|
408
|
+
for row in rows or []:
|
|
409
|
+
if row.get("id") == project_id or str(row.get("id")) == str(project_id):
|
|
410
|
+
owner = (row.get("owner_orchestrator") or "").strip()
|
|
411
|
+
return owner or None
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _thread_recipient(
|
|
416
|
+
card: dict,
|
|
417
|
+
explicit_to: Optional[str],
|
|
418
|
+
*,
|
|
419
|
+
owner_orchestrator: Optional[str] = None,
|
|
420
|
+
) -> "tuple[str, str]":
|
|
421
|
+
"""Who a card-thread post is addressed to, and why.
|
|
422
|
+
|
|
423
|
+
Order: ``--to`` > card assignee > project ``owner_orchestrator`` (#864).
|
|
424
|
+
Returns ``(to_node, reason)`` so the confirmation line can name the choice.
|
|
395
425
|
|
|
396
426
|
>>> RAISES RATHER THAN INVENTING A SENTINEL. <<< POST /messages requires
|
|
397
427
|
exactly one of {to_node, channel}, so a card post needs a real recipient. The
|
|
@@ -401,18 +431,21 @@ def _thread_recipient(card: dict, explicit_to: Optional[str]) -> str:
|
|
|
401
431
|
A placeholder here would manufacture that defect once per card post.
|
|
402
432
|
"""
|
|
403
433
|
if explicit_to:
|
|
404
|
-
return explicit_to
|
|
434
|
+
return explicit_to, "--to"
|
|
405
435
|
assignee = card.get("assignee")
|
|
406
436
|
if assignee:
|
|
407
|
-
return assignee
|
|
437
|
+
return assignee, "card assignee"
|
|
438
|
+
if owner_orchestrator:
|
|
439
|
+
return owner_orchestrator, "project owner_orchestrator"
|
|
408
440
|
raise RuntimeError(
|
|
409
|
-
f"card #{card.get('id')} has no assignee
|
|
410
|
-
f"
|
|
411
|
-
f"wire; it needs
|
|
441
|
+
f"card #{card.get('id')} has no assignee and project owner_orchestrator "
|
|
442
|
+
f"is unresolvable, so there is no default recipient for a thread post — "
|
|
443
|
+
f"pass --to <peer>. (A card post is still a DM on the wire; it needs "
|
|
444
|
+
f"someone to be addressed to.)"
|
|
412
445
|
)
|
|
413
446
|
|
|
414
447
|
|
|
415
|
-
def _say_line(resp: dict, card_id, to_node: str) -> str:
|
|
448
|
+
def _say_line(resp: dict, card_id, to_node: str, *, reason: Optional[str] = None) -> str:
|
|
416
449
|
"""The post confirmation — AND any obligation the post just DISCHARGED.
|
|
417
450
|
|
|
418
451
|
>>> THE GATEWAY ALREADY SAID SO AND THIS CLI THREW IT AWAY. <<< POST
|
|
@@ -435,7 +468,11 @@ def _say_line(resp: dict, card_id, to_node: str) -> str:
|
|
|
435
468
|
POLICY (that is #562's own question) -- it is the fix for the holder not
|
|
436
469
|
being told, which is what made six hours of divergence possible.
|
|
437
470
|
"""
|
|
438
|
-
|
|
471
|
+
if reason:
|
|
472
|
+
line = (f"posted id={resp.get('id')} onto card #{card_id} "
|
|
473
|
+
f"(to {to_node}; {reason})")
|
|
474
|
+
else:
|
|
475
|
+
line = f"posted id={resp.get('id')} onto card #{card_id} (to {to_node})"
|
|
439
476
|
closed = resp.get("closed_obligations") or []
|
|
440
477
|
if closed:
|
|
441
478
|
ids = ", ".join(f"#{i}" for i in closed)
|
|
@@ -855,7 +892,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
855
892
|
add_content_args(cy)
|
|
856
893
|
cy.add_argument("--kind", default="fyi", help="status|question|answer|unblock|fyi")
|
|
857
894
|
cy.add_argument("--to", dest="to_node", default=None,
|
|
858
|
-
help="recipient peer (default:
|
|
895
|
+
help="recipient peer (default: card assignee, else project "
|
|
896
|
+
"owner_orchestrator; refused if neither resolves)")
|
|
859
897
|
_add_common(cy)
|
|
860
898
|
|
|
861
899
|
ck = cards.add_parser(
|
|
@@ -1278,8 +1316,15 @@ def run_board(argv: list[str]) -> int:
|
|
|
1278
1316
|
f"(it predates #181a). Run scripts/migrate_card_threads.py on "
|
|
1279
1317
|
f"the gateway host.", file=sys.stderr)
|
|
1280
1318
|
return 1
|
|
1319
|
+
# #864: card GET has no owner field — only fetch projects when the
|
|
1320
|
+
# cheaper defaults (explicit --to, card assignee) are absent.
|
|
1321
|
+
owner = None
|
|
1322
|
+
if not args.to_node and not card.get("assignee"):
|
|
1323
|
+
owner = _project_owner_orchestrator(gw, token, card.get("project_id"))
|
|
1281
1324
|
try:
|
|
1282
|
-
to_node = _thread_recipient(
|
|
1325
|
+
to_node, reason = _thread_recipient(
|
|
1326
|
+
card, args.to_node, owner_orchestrator=owner,
|
|
1327
|
+
)
|
|
1283
1328
|
except RuntimeError as exc:
|
|
1284
1329
|
print(f"swarph board cards say: {exc}", file=sys.stderr)
|
|
1285
1330
|
return 1
|
|
@@ -1293,7 +1338,9 @@ def run_board(argv: list[str]) -> int:
|
|
|
1293
1338
|
# explicit `propose` grant. `_out` passes `detail` through whole; it does
|
|
1294
1339
|
# not need help, and a second copy of that logic is a second thing to
|
|
1295
1340
|
# keep in sync.
|
|
1296
|
-
return _out(
|
|
1341
|
+
return _out(
|
|
1342
|
+
st, d, lambda x: _say_line(x, args.id, to_node, reason=reason), aj,
|
|
1343
|
+
)
|
|
1297
1344
|
if args.command == "ready":
|
|
1298
1345
|
st, d = _patch_json(f"{gw}/board/cards/{args.id}", {"actor": self_name, "move_ready": not args.clear}, token)
|
|
1299
1346
|
return _out(st, d, lambda x: f"card #{x.get('id')} move_ready -> {x.get('move_ready')}", aj)
|
|
@@ -37,12 +37,65 @@ import sys
|
|
|
37
37
|
from swarph_cli.commands.codegraph_hook import _match_quality
|
|
38
38
|
|
|
39
39
|
DEFAULT_INDEX = os.path.expanduser("~/.swarph/codegraph/index.db")
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
40
|
+
# >>> THERE IS NO DEFAULT IDENTITY ANY MORE. THIS CONSTANT IS GONE ON PURPOSE. <<<
|
|
41
|
+
#
|
|
42
|
+
# It used to read `DEFAULT_CALLER_CELL = "lab-ovh"`, and that one line meant every
|
|
43
|
+
# unconfigured caller on lab-ovh queried with the ORCHESTRATOR'S READ AUTHORITY.
|
|
44
|
+
#
|
|
45
|
+
# MEASURED 2026-09-18 (science-claude, mesh 43183/43186/43188). The gateway was never
|
|
46
|
+
# bypassed and nothing was ever spoofed: `_query_via_gateway` sends NO caller field, so
|
|
47
|
+
# the server binds identity from the bearer token, correctly. What the default did was
|
|
48
|
+
# pick WHICH REAL TOKEN got presented — `_resolve_token(self_name, ...)` reads the file
|
|
49
|
+
# that name points at. A cell asking a question on this box authenticated AS lab-ovh,
|
|
50
|
+
# with lab-ovh's genuine credential, and got a full, plausible, correctly-authorised
|
|
51
|
+
# answer belonging to somebody else.
|
|
52
|
+
#
|
|
53
|
+
# >>> A WRONG-IDENTITY WRITE ANNOUNCES ITSELF — a DM arrives signed by the wrong cell
|
|
54
|
+
# and someone notices. A WRONG-IDENTITY READ ANNOUNCES NOTHING. <<< It was found only
|
|
55
|
+
# because a repo its owner KNEW they owned was missing from their own results.
|
|
56
|
+
#
|
|
57
|
+
# WHY REFUSE RATHER THAN WARN (commander, 2026-09-18 04:44Z): a warning on a read that
|
|
58
|
+
# returns a correct-looking answer is a line of text above a result the caller already
|
|
59
|
+
# believes. And the cost of refusing was measured first, not assumed — of 8 executable
|
|
60
|
+
# `swarph` invocations in systemd + cron on lab-ovh, 7 already pass `--as` or
|
|
61
|
+
# `--token-file`, and the 8th (`monitor reexec-on-change`) cannot authenticate at all:
|
|
62
|
+
# no --gateway, no --token-file, no --as exists on that verb. SCHEDULED WORK BREAKS IN
|
|
63
|
+
# ZERO PLACES. The population that pays is interactive use, which is the population
|
|
64
|
+
# that was silently mis-attributed.
|
|
65
|
+
#
|
|
66
|
+
# $SWARPH_SELF STILL WORKS AND IS THE INTENDED PATH. It is a DECLARED identity. What is
|
|
67
|
+
# refused is the *undeclared* case, where a package-level guess stood in for one.
|
|
68
|
+
IDENTITY_UNSET = object() # sentinel: argparse default when --caller-cell is omitted
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class IdentityNotDeclared(RuntimeError):
|
|
72
|
+
"""No caller identity was declared and there is no default to fall back to."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def require_caller_cell(explicit=None, *, verb: str) -> str:
|
|
76
|
+
"""Resolve the caller cell, or REFUSE.
|
|
77
|
+
|
|
78
|
+
Order: explicit flag -> $SWARPH_SELF -> $SWARPH_CELL -> refuse.
|
|
79
|
+
|
|
80
|
+
The refusal names the verb and every way to satisfy it, because an error that
|
|
81
|
+
says only "identity unset" moves the work to the reader.
|
|
82
|
+
"""
|
|
83
|
+
if explicit not in (None, IDENTITY_UNSET) and str(explicit).strip():
|
|
84
|
+
return str(explicit).strip()
|
|
85
|
+
for var in ("SWARPH_SELF", "SWARPH_CELL"):
|
|
86
|
+
v = (os.environ.get(var) or "").strip()
|
|
87
|
+
if v:
|
|
88
|
+
return v
|
|
89
|
+
raise IdentityNotDeclared(
|
|
90
|
+
f"swarph {verb}: NO CALLER IDENTITY DECLARED, and there is no default.\n"
|
|
91
|
+
f" This used to fall back to 'lab-ovh', which meant an undeclared caller\n"
|
|
92
|
+
f" queried with that cell's read authority and got somebody else's answer.\n"
|
|
93
|
+
f" Declare one of:\n"
|
|
94
|
+
f" --caller-cell <cell> for this invocation\n"
|
|
95
|
+
f" SWARPH_SELF=<cell> for this shell or unit\n"
|
|
96
|
+
f" SWARPH_CELL=<cell> (MCP hosts)\n"
|
|
97
|
+
f" It is NOT guessable: this box hosts several cells under one uid."
|
|
98
|
+
)
|
|
46
99
|
|
|
47
100
|
# NOT 1 and NOT 2. A distinct code so "nothing was searched" can never be confused
|
|
48
101
|
# with a crash or a usage error, and so a caller can branch on it specifically.
|
|
@@ -332,7 +385,7 @@ def _query_via_gateway(gateway: str, query: str, limit: int, token_file):
|
|
|
332
385
|
from swarph_cli.commands.mesh import _post_json, _resolve_token # local import: keeps
|
|
333
386
|
# the local-only path free of any mesh dependency, so an offline cell still works.
|
|
334
387
|
|
|
335
|
-
self_name =
|
|
388
|
+
self_name = require_caller_cell(verb="codegraph")
|
|
336
389
|
token = _resolve_token(self_name, token_file)
|
|
337
390
|
body = {"query": query, "limit": max(1, min(int(limit), 25))}
|
|
338
391
|
status, payload = _post_json(gateway.rstrip("/") + "/codegraph", body, token)
|
|
@@ -352,7 +405,9 @@ def run_codegraph(argv) -> int:
|
|
|
352
405
|
"is configured (the server searches and returns the answer); falls back to a local index.")
|
|
353
406
|
ap.add_argument("query", help="natural-language query, e.g. 'which function escapes HTML'")
|
|
354
407
|
ap.add_argument("--index", default=DEFAULT_INDEX, help=f"index db (default {DEFAULT_INDEX})")
|
|
355
|
-
ap.add_argument("--caller-cell", default=
|
|
408
|
+
ap.add_argument("--caller-cell", default=IDENTITY_UNSET,
|
|
409
|
+
help="A8 caller identity. NO DEFAULT — else $SWARPH_SELF / $SWARPH_CELL, "
|
|
410
|
+
"else the command REFUSES rather than guessing (it used to guess lab-ovh).")
|
|
356
411
|
ap.add_argument("--limit", type=int, default=8)
|
|
357
412
|
ap.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
358
413
|
ap.add_argument("--gateway", default=None,
|
|
@@ -130,6 +130,38 @@ def _commands_in(section: str) -> "list[str]":
|
|
|
130
130
|
return out
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
def verbs_in_guide(topics: "dict[str, str] | None" = None) -> "set[str]":
|
|
134
|
+
"""#547: the verbs the guide TEACHES, counted PER VERB (a topic count said 11 and
|
|
135
|
+
meant nothing). A verb is covered when some section's commands include it."""
|
|
136
|
+
topics = topics if topics is not None else _split_topics(_load_guide())
|
|
137
|
+
out: set[str] = set()
|
|
138
|
+
for body in topics.values():
|
|
139
|
+
for cmd in _commands_in(body):
|
|
140
|
+
parts = cmd.split()
|
|
141
|
+
if len(parts) >= 2:
|
|
142
|
+
out.add(parts[1])
|
|
143
|
+
return out
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _registry() -> "list[str]":
|
|
147
|
+
# Lazy: main dispatches handlers by dotted path and does not import this module,
|
|
148
|
+
# but keep the direction one-way anyway.
|
|
149
|
+
from swarph_cli.main import registered_verbs
|
|
150
|
+
return registered_verbs()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _verb_entry(topics: "dict[str, str]", verb: str) -> "list[tuple[str, str]]":
|
|
154
|
+
"""The line in each topic that teaches `swarph <verb>` -- one per topic, first hit."""
|
|
155
|
+
pat = re.compile(r"`swarph " + re.escape(verb) + r"(?:[ `])")
|
|
156
|
+
hits: list[tuple[str, str]] = []
|
|
157
|
+
for name, body in topics.items():
|
|
158
|
+
for raw in body.splitlines():
|
|
159
|
+
if pat.search(raw):
|
|
160
|
+
hits.append((name, raw.strip()))
|
|
161
|
+
break
|
|
162
|
+
return hits
|
|
163
|
+
|
|
164
|
+
|
|
133
165
|
_TERM_RE = re.compile(r"\*\*(.+?)\*\*")
|
|
134
166
|
|
|
135
167
|
|
|
@@ -262,13 +294,21 @@ def run_guide(argv: "list[str]") -> int:
|
|
|
262
294
|
# `apropos`: an LLM arrives with an INTENT ("subscribe to updates"), not the
|
|
263
295
|
# topic's name ("channels"). Requiring the name is the #520 discovery defect
|
|
264
296
|
# reproduced inside its own fix.
|
|
297
|
+
term = args.search.strip().lower()
|
|
298
|
+
# #547: a VERB name resolves to the verb's own entry first -- `--search highlight`
|
|
299
|
+
# used to answer with a topic list that did not contain it.
|
|
300
|
+
verb_hits = _verb_entry(topics, term) if term in _registry() else []
|
|
265
301
|
hits = _search(topics, args.search)
|
|
266
|
-
if not hits:
|
|
302
|
+
if not hits and not verb_hits:
|
|
267
303
|
print_safe(f"swarph guide: nothing matches {args.search!r}. "
|
|
268
304
|
f"Topics: {', '.join(topics)}", stream=sys.stderr)
|
|
269
305
|
return 2
|
|
270
|
-
for name, line in
|
|
306
|
+
for name, line in verb_hits:
|
|
271
307
|
print_safe(f"{name:<22} {line}")
|
|
308
|
+
shown = {name for name, _ in verb_hits}
|
|
309
|
+
for name, line in hits:
|
|
310
|
+
if name not in shown:
|
|
311
|
+
print_safe(f"{name:<22} {line}")
|
|
272
312
|
return 0
|
|
273
313
|
|
|
274
314
|
if not args.topic:
|
|
@@ -279,6 +319,19 @@ def run_guide(argv: "list[str]") -> int:
|
|
|
279
319
|
if key in topics:
|
|
280
320
|
print_safe(topics[key])
|
|
281
321
|
return 0
|
|
322
|
+
if key in _registry():
|
|
323
|
+
# #547: every registered verb resolves, per verb (tests/test_547 pins the set):
|
|
324
|
+
# the lines that teach it, then -- when the name is also a topic's substring
|
|
325
|
+
# (`channel` -> channels) -- that one topic, as before.
|
|
326
|
+
for name, line in _verb_entry(topics, key):
|
|
327
|
+
print_safe(f"{name:<22} {line}")
|
|
328
|
+
near = [k for k in topics if key in k]
|
|
329
|
+
if len(near) == 1:
|
|
330
|
+
print_safe("")
|
|
331
|
+
print_safe(topics[near[0]])
|
|
332
|
+
else:
|
|
333
|
+
print_safe(f"\n more: swarph {key} --help")
|
|
334
|
+
return 0
|
|
282
335
|
|
|
283
336
|
# Substring fallback before failing: `swarph guide channel` should find `channels`.
|
|
284
337
|
hits = [k for k in topics if key in k]
|
|
@@ -151,7 +151,8 @@ def _codegraph_query(query: str, *, limit: int = 8) -> list[dict]:
|
|
|
151
151
|
|
|
152
152
|
Delegates to :func:`codegraph.structural_query` against the module-level
|
|
153
153
|
``_CODEGRAPH_INDEX`` path, using ``SWARPH_CELL`` (else
|
|
154
|
-
``codegraph.
|
|
154
|
+
``codegraph.require_caller_cell``, which REFUSES rather than defaulting) as
|
|
155
|
+
the A8 caller identity and the
|
|
155
156
|
default operate-what-you-own allowlist (``allowlist=None``).
|
|
156
157
|
|
|
157
158
|
NEVER raises — any exception (missing index, corrupt db, bad query, or
|
|
@@ -162,7 +163,13 @@ def _codegraph_query(query: str, *, limit: int = 8) -> list[dict]:
|
|
|
162
163
|
to keep the tool output tight for the calling agent.
|
|
163
164
|
"""
|
|
164
165
|
try:
|
|
165
|
-
|
|
166
|
+
# NO DEFAULT IDENTITY (2026-09-18). `codegraph.DEFAULT_CALLER_CELL` is gone;
|
|
167
|
+
# an undeclared caller used to become lab-ovh and receive that cell's read
|
|
168
|
+
# authority. require_caller_cell raises IdentityNotDeclared, which the
|
|
169
|
+
# NEVER-RAISES contract below turns into an empty result — FAIL-CLOSED: the
|
|
170
|
+
# MCP host gets nothing rather than somebody else's answer.
|
|
171
|
+
caller_cell = codegraph.require_caller_cell(
|
|
172
|
+
os.environ.get("SWARPH_CELL"), verb="mcp codegraph_query")
|
|
166
173
|
rows = codegraph.structural_query(
|
|
167
174
|
query,
|
|
168
175
|
index_path=_CODEGRAPH_INDEX,
|
|
@@ -1172,8 +1172,14 @@ class PullSink(Sink):
|
|
|
1172
1172
|
return True
|
|
1173
1173
|
|
|
1174
1174
|
def pending_label(self, count: int) -> str:
|
|
1175
|
+
# #126: this is NOT gateway-unread. It is inbox.log entries newer than
|
|
1176
|
+
# last_delivered_id — on a frozen ledger it grows at the cell's DM rate
|
|
1177
|
+
# forever. Naming it "unread" taught cells to distrust status.
|
|
1175
1178
|
plural = "s" if count != 1 else ""
|
|
1176
|
-
return
|
|
1179
|
+
return (
|
|
1180
|
+
f"{count} DM{plural} newer than last delivery "
|
|
1181
|
+
f"(inbox.log after ledger — not gateway unread)"
|
|
1182
|
+
)
|
|
1177
1183
|
|
|
1178
1184
|
|
|
1179
1185
|
class NoneSink(Sink):
|
|
@@ -1590,17 +1596,25 @@ def _write_ledgers_atomic(path: Path, ledgers: dict) -> None:
|
|
|
1590
1596
|
|
|
1591
1597
|
def _replay_from_inbox_log(
|
|
1592
1598
|
path: Path, after_id: int, limit: int
|
|
1593
|
-
) -> tuple[list, int]:
|
|
1594
|
-
"""Owed DMs newer than `after_id`, newest `limit` kept.
|
|
1599
|
+
) -> tuple[list, int, list[str]]:
|
|
1600
|
+
"""Owed DMs newer than `after_id`, newest `limit` kept.
|
|
1601
|
+
|
|
1602
|
+
Returns ``(dms, skipped, pending_from)``.
|
|
1603
|
+
|
|
1604
|
+
``pending_from`` is the distinct-sender set over EVERY matching log entry,
|
|
1605
|
+
not over the capped ``dms`` list (#126). The deque bounds returned LINES;
|
|
1606
|
+
the sender set was taken from it by convenience and understated who was
|
|
1607
|
+
waiting (evicting the longest-waiting senders first).
|
|
1595
1608
|
|
|
1596
1609
|
This is BOTH the restart-retry path and the late-attached-sink replay path.
|
|
1597
1610
|
They are the same question — "what does this ledger still owe" — so they get
|
|
1598
1611
|
one mechanism; two would drift.
|
|
1599
1612
|
"""
|
|
1600
1613
|
if not path.exists():
|
|
1601
|
-
return [], 0
|
|
1614
|
+
return [], 0, []
|
|
1602
1615
|
keep: deque = deque(maxlen=max(0, limit))
|
|
1603
1616
|
total = 0
|
|
1617
|
+
from_nodes: set[str] = set()
|
|
1604
1618
|
try:
|
|
1605
1619
|
with path.open("r", encoding="utf-8") as fp:
|
|
1606
1620
|
for raw in fp:
|
|
@@ -1615,10 +1629,13 @@ def _replay_from_inbox_log(
|
|
|
1615
1629
|
continue
|
|
1616
1630
|
total += 1
|
|
1617
1631
|
keep.append(dm)
|
|
1632
|
+
src = dm.get("from_node")
|
|
1633
|
+
if src is not None and str(src):
|
|
1634
|
+
from_nodes.add(str(src))
|
|
1618
1635
|
except OSError as exc:
|
|
1619
1636
|
print(f"[monitor] cannot replay {path}: {exc}", file=sys.stderr, flush=True)
|
|
1620
|
-
return [], 0
|
|
1621
|
-
return list(keep), total - len(keep)
|
|
1637
|
+
return [], 0, []
|
|
1638
|
+
return list(keep), total - len(keep), sorted(from_nodes)
|
|
1622
1639
|
|
|
1623
1640
|
|
|
1624
1641
|
class MonitorState:
|
|
@@ -2198,7 +2215,7 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
2198
2215
|
f"{sink.name} (cursor advanced; delivery still pending)",
|
|
2199
2216
|
flush=True)
|
|
2200
2217
|
continue
|
|
2201
|
-
dms, skipped = _replay_from_inbox_log(
|
|
2218
|
+
dms, skipped, _pending_from = _replay_from_inbox_log(
|
|
2202
2219
|
state.inbox_log_path, delivered, state.replay_limit
|
|
2203
2220
|
)
|
|
2204
2221
|
if skipped:
|
|
@@ -678,7 +678,7 @@ def _collect(args: argparse.Namespace) -> dict:
|
|
|
678
678
|
continue
|
|
679
679
|
led = ledgers.get(sink.name)
|
|
680
680
|
delivered = int(led["last_delivered_id"]) if led else 0
|
|
681
|
-
dms, skipped = mesh._replay_from_inbox_log(
|
|
681
|
+
dms, skipped, pending_from = mesh._replay_from_inbox_log(
|
|
682
682
|
inbox_log, delivered, mesh._MONITOR_REPLAY_LIMIT
|
|
683
683
|
)
|
|
684
684
|
rows.append({
|
|
@@ -690,7 +690,9 @@ def _collect(args: argparse.Namespace) -> dict:
|
|
|
690
690
|
"consecutive_failures": int(led["consecutive_failures"]) if led else 0,
|
|
691
691
|
"ledger_missing": led is None,
|
|
692
692
|
"pending": len(dms) + skipped,
|
|
693
|
-
|
|
693
|
+
# #126: same set as `pending` — distinct senders across EVERY log
|
|
694
|
+
# entry newer than last_delivered_id, not over the 50-entry deque.
|
|
695
|
+
"pending_from": pending_from,
|
|
694
696
|
"label": sink.pending_label(len(dms) + skipped),
|
|
695
697
|
})
|
|
696
698
|
|
|
@@ -1123,4 +1123,12 @@ def run_onboard(argv: list[str]) -> int:
|
|
|
1123
1123
|
f" swarph ratify {canonical} --reason \"<short text>\"\n"
|
|
1124
1124
|
f" to flip ratified=true.\n"
|
|
1125
1125
|
)
|
|
1126
|
+
# #866: mechanics without the standard of evidence is half an onboard.
|
|
1127
|
+
# The doctrine topic is bundled; print the command, do not paste the body
|
|
1128
|
+
# here (the guide is the record -- a paraphrase in this banner would drift).
|
|
1129
|
+
print_safe(
|
|
1130
|
+
"\n[doctrine] the standard of evidence ships in the guide, not only the verbs:\n"
|
|
1131
|
+
" swarph guide doctrine\n"
|
|
1132
|
+
" Authority: proven/docs/THE_TEN.md -- approved extract: card #866 post 42503.\n"
|
|
1133
|
+
)
|
|
1126
1134
|
return 0
|
|
@@ -2704,19 +2704,70 @@ class MuseMembrane(ClaudeMembrane):
|
|
|
2704
2704
|
)
|
|
2705
2705
|
|
|
2706
2706
|
|
|
2707
|
-
#: Opencode cell subdir, created INSIDE the cell cwd, holding the
|
|
2708
|
-
#: config (XDG_CONFIG_HOME)
|
|
2709
|
-
#:
|
|
2710
|
-
#:
|
|
2711
|
-
#: `XDG_CONFIG_HOME=/tmp/x opencode --pure debug config` -> `"plugin": []`.
|
|
2707
|
+
#: Opencode cell CONFIG subdir, created INSIDE the cell cwd, holding the
|
|
2708
|
+
#: relocated config (XDG_CONFIG_HOME). Grounded against real opencode 1.18.30 by
|
|
2709
|
+
#: execution: `XDG_CONFIG_HOME=/tmp/x opencode --pure debug config` ->
|
|
2710
|
+
#: `"plugin": []`.
|
|
2712
2711
|
_OPENCODE_CELL_SUBDIR = ".opencode-cell"
|
|
2713
2712
|
|
|
2714
2713
|
|
|
2715
2714
|
def _opencode_cell_dir(cell: Cell) -> Path:
|
|
2716
|
-
"""The cell's private opencode root (``<cwd>/.opencode-cell``)."""
|
|
2715
|
+
"""The cell's private opencode CONFIG root (``<cwd>/.opencode-cell``)."""
|
|
2717
2716
|
return cell.cwd / _OPENCODE_CELL_SUBDIR
|
|
2718
2717
|
|
|
2719
2718
|
|
|
2719
|
+
def _opencode_data_dir(cell: Cell) -> Path:
|
|
2720
|
+
"""The cell's private opencode DATA dir, OUTSIDE the cell cwd.
|
|
2721
|
+
|
|
2722
|
+
opencode snapshots its work-tree with a git-dir UNDER this data dir. If the
|
|
2723
|
+
data dir sits inside the work-tree (the pre-fix shape, ``<cwd>/.opencode-cell/
|
|
2724
|
+
data``), the snapshot repo snapshots its own object store — a self-referential
|
|
2725
|
+
explosion measured 2026-09-19 at 9.1 GB of objects and a 319k-entry index,
|
|
2726
|
+
with every turn blocked at ``loop step=0`` on the resulting ``git add``. Kept
|
|
2727
|
+
under $HOME so no cell work-tree can ever contain it. Grounded:
|
|
2728
|
+
``XDG_DATA_HOME=/tmp/x opencode --pure db path`` -> ``/tmp/x/opencode/opencode.db``.
|
|
2729
|
+
"""
|
|
2730
|
+
return Path.home() / ".local" / "share" / "swarph" / "opencode" / cell.name
|
|
2731
|
+
|
|
2732
|
+
|
|
2733
|
+
def _migrate_opencode_data(cell: Cell) -> None:
|
|
2734
|
+
"""Move a pre-fix in-tree data dir to the out-of-tree root, best-effort.
|
|
2735
|
+
|
|
2736
|
+
Session continuity: the ``opencode.db`` holds the cell's session, keyed by
|
|
2737
|
+
directory, so relocating it preserves the resume. Never raises; the failure
|
|
2738
|
+
direction is a lost resume, not a refused spawn.
|
|
2739
|
+
"""
|
|
2740
|
+
old = _opencode_cell_dir(cell) / "data"
|
|
2741
|
+
new = _opencode_data_dir(cell)
|
|
2742
|
+
if not old.is_dir():
|
|
2743
|
+
return
|
|
2744
|
+
if new.exists():
|
|
2745
|
+
# Measured 2026-09-19 11:2xZ on lab-ovh: a cell process spawned BEFORE the fix
|
|
2746
|
+
# kept running with the in-tree env and wrote a second data dir there after
|
|
2747
|
+
# the first migration. It is not merged (two opencode.db files cannot be);
|
|
2748
|
+
# say so where the operator is looking instead of skipping in silence.
|
|
2749
|
+
print(f"[spawn] opencode: legacy in-tree data at {old} left in place -- {new} "
|
|
2750
|
+
f"already exists, sessions written in-tree after the first migration are "
|
|
2751
|
+
f"NOT merged; respawn the cell so no process still writes there",
|
|
2752
|
+
file=sys.stderr)
|
|
2753
|
+
return
|
|
2754
|
+
try:
|
|
2755
|
+
new.parent.mkdir(parents=True, exist_ok=True)
|
|
2756
|
+
# #888 half (2): the in-tree data dir may carry the SELF-REFERENTIAL snapshot
|
|
2757
|
+
# repo (the specimen: 9.0 GB of objects, a 319,621-entry index). It is
|
|
2758
|
+
# opencode's own work-tree snapshot cache, recreated on the next turn, so
|
|
2759
|
+
# drop it rather than carry it out of the tree. The PROJECT index is never
|
|
2760
|
+
# touched here -- on lab-ovh nothing was ever tracked (git ls-files
|
|
2761
|
+
# .opencode-cell = 0), and a blind reset of the clone that backs the
|
|
2762
|
+
# editable install would be a worse defect than this one.
|
|
2763
|
+
snap = old / "opencode" / "snapshot"
|
|
2764
|
+
if snap.is_dir():
|
|
2765
|
+
shutil.rmtree(snap, ignore_errors=True)
|
|
2766
|
+
shutil.move(str(old), str(new))
|
|
2767
|
+
except OSError:
|
|
2768
|
+
pass
|
|
2769
|
+
|
|
2770
|
+
|
|
2720
2771
|
def _scrub_opencode_namespace(env: dict[str, str]) -> None:
|
|
2721
2772
|
"""DENY-BY-DEFAULT scrub of the ``OPENCODE_*`` namespace.
|
|
2722
2773
|
|
|
@@ -2768,9 +2819,13 @@ def _opencode_env(cell: Cell) -> dict[str, str]:
|
|
|
2768
2819
|
"""Subscription env for a local ``opencode`` CELL.
|
|
2769
2820
|
|
|
2770
2821
|
On top of the canonical billing scrub, the cell relocates opencode's CONFIG
|
|
2771
|
-
(XDG_CONFIG_HOME)
|
|
2772
|
-
|
|
2773
|
-
|
|
2822
|
+
(XDG_CONFIG_HOME) to ``<cwd>/.opencode-cell/config`` and its DATA
|
|
2823
|
+
(XDG_DATA_HOME) to ``~/.local/share/swarph/opencode/<cell>`` — data OUTSIDE
|
|
2824
|
+
the work-tree, because opencode's snapshot git-dir lives under the data dir
|
|
2825
|
+
and would otherwise snapshot its own object store (the 2026-09-19 9 GB
|
|
2826
|
+
self-referential explosion). It links the operator's ``auth.json`` in, so the
|
|
2827
|
+
cell's sessions/plugins/auth never mix with the operator's. ``$HOME`` is
|
|
2828
|
+
deliberately NOT relocated — the
|
|
2774
2829
|
vibe/cursor lesson: a fake $HOME costs the cell ``~/.config/swarph/<self>.
|
|
2775
2830
|
peer_token``, ``~/.swarph/secrets.toml`` and the codegraph hook, and opencode's
|
|
2776
2831
|
XDG-honoring knobs let us pay nothing to keep it.
|
|
@@ -2788,7 +2843,8 @@ def _opencode_env(cell: Cell) -> dict[str, str]:
|
|
|
2788
2843
|
# ORDER IS LOAD-BEARING: the scrub removes an inherited OPENCODE_* var and this
|
|
2789
2844
|
# sets the cell's own AFTER, so the membrane's values are authoritative.
|
|
2790
2845
|
_scrub_opencode_namespace(env)
|
|
2791
|
-
|
|
2846
|
+
_migrate_opencode_data(cell)
|
|
2847
|
+
data_dir = _opencode_data_dir(cell)
|
|
2792
2848
|
config_dir = _opencode_cell_dir(cell) / "config"
|
|
2793
2849
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
2794
2850
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -2855,7 +2911,7 @@ def _opencode_prior_session(cell: Cell) -> Optional[str]:
|
|
|
2855
2911
|
if binary is None:
|
|
2856
2912
|
return None
|
|
2857
2913
|
env = dict(os.environ)
|
|
2858
|
-
env["XDG_DATA_HOME"] = str(
|
|
2914
|
+
env["XDG_DATA_HOME"] = str(_opencode_data_dir(cell))
|
|
2859
2915
|
env["OPENCODE_DISABLE_AUTOUPDATE"] = "1"
|
|
2860
2916
|
try:
|
|
2861
2917
|
out = subprocess.run(
|
|
@@ -641,10 +641,39 @@ def _fetch_peers(gateway: str, token: str) -> list[dict]:
|
|
|
641
641
|
return []
|
|
642
642
|
|
|
643
643
|
|
|
644
|
+
def _ppid(cur: int) -> int:
|
|
645
|
+
"""Parent of ``cur``.
|
|
646
|
+
|
|
647
|
+
PRODUCTION, not a test helper (#515 / #492). ``_process_alive`` calls
|
|
648
|
+
``_pid_under``, which calls this. Linux reads ``/proc/<pid>/stat`` —
|
|
649
|
+
``comm`` can contain spaces and parens, so PPID is the field after the
|
|
650
|
+
final ``)``. Darwin has no ``/proc``; ``ps -o ppid=`` is the same fact.
|
|
651
|
+
A missing source raises ``OSError`` and the walker treats that as
|
|
652
|
+
"not under", which is the old Linux behaviour when ``/proc`` cannot
|
|
653
|
+
be read. ``monitor.py`` and ``orphan_daemons.py`` have their own
|
|
654
|
+
``/proc`` readers and are not this function.
|
|
655
|
+
"""
|
|
656
|
+
try:
|
|
657
|
+
with open(f"/proc/{cur}/stat", encoding="utf-8") as f:
|
|
658
|
+
data = f.read()
|
|
659
|
+
except OSError:
|
|
660
|
+
if sys.platform != "darwin":
|
|
661
|
+
raise
|
|
662
|
+
r = subprocess.run(
|
|
663
|
+
["ps", "-o", "ppid=", "-p", str(cur)],
|
|
664
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
665
|
+
timeout=5,
|
|
666
|
+
)
|
|
667
|
+
if r.returncode != 0 or not r.stdout.strip():
|
|
668
|
+
raise OSError(f"ps could not read ppid of {cur}")
|
|
669
|
+
return int(r.stdout.strip())
|
|
670
|
+
after = data[data.rfind(")") + 2:].split()
|
|
671
|
+
return int(after[1]) # stat field 4 (PPID): state, ppid, ...
|
|
672
|
+
|
|
673
|
+
|
|
644
674
|
def _pid_under(pid: int, ancestors: set, _max_depth: int = 40) -> bool:
|
|
645
675
|
"""Walk the PPID chain up from ``pid``; True if any ancestor is in
|
|
646
|
-
``ancestors``.
|
|
647
|
-
contain spaces/parens, so parse PPID after the final ``)``."""
|
|
676
|
+
``ancestors``. See ``_ppid`` for the platform split."""
|
|
648
677
|
cur = pid
|
|
649
678
|
for _ in range(_max_depth):
|
|
650
679
|
if cur in ancestors:
|
|
@@ -652,11 +681,8 @@ def _pid_under(pid: int, ancestors: set, _max_depth: int = 40) -> bool:
|
|
|
652
681
|
if cur <= 1:
|
|
653
682
|
return False
|
|
654
683
|
try:
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
after = data[data.rfind(")") + 2:].split()
|
|
658
|
-
cur = int(after[1]) # stat field 4 (PPID): state, ppid, ...
|
|
659
|
-
except (OSError, ValueError, IndexError):
|
|
684
|
+
cur = _ppid(cur)
|
|
685
|
+
except (OSError, ValueError, IndexError, subprocess.TimeoutExpired):
|
|
660
686
|
return False
|
|
661
687
|
return cur in ancestors
|
|
662
688
|
|