swarph-cli 0.72.3__tar.gz → 0.72.5__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.72.3/src/swarph_cli.egg-info → swarph_cli-0.72.5}/PKG-INFO +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/pyproject.toml +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/mesh.py +72 -16
- swarph_cli-0.72.5/src/swarph_cli/commands/rowclear_hook.py +42 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/guide/GUIDE.md +11 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/main.py +8 -2
- swarph_cli-0.72.5/src/swarph_cli/rowclear.py +239 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/wake_watchdog.py +4 -4
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-watchdog.service +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-watchdog.timer +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli.egg-info/SOURCES.txt +4 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_1006_board_publisher.py +74 -0
- swarph_cli-0.72.5/tests/test_1010_strand_only.py +225 -0
- swarph_cli-0.72.5/tests/test_432_rowclear.py +278 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_sidecar.py +18 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_sidecar_cursor_decoupled.py +9 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_sidecar_wake_survives_read.py +9 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_monitor_cli.py +8 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_monitor_state_split.py +9 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_tmux_wake_runstate_defer.py +7 -2
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_tmux_wake_session_reanchor.py +21 -10
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_tmux_wake_stale.py +8 -1
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_tmux_wake_submit_verify.py +15 -4
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/LICENSE +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/README.md +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/setup.cfg +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/codex_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_1013_spawn_channel.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_1017_muse_tui.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_484_codex_queue_sink.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_961_loud_stop.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_codex_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_board_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_group_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_import_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_init_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_main.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_security.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_service_app.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_service_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_version_command.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.72.3 → swarph_cli-0.72.5}/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.72.
|
|
3
|
+
Version: 0.72.5
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.72.
|
|
7
|
+
version = "0.72.5"
|
|
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" }
|
|
@@ -1483,6 +1483,10 @@ class TmuxSink(Sink):
|
|
|
1483
1483
|
def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
|
|
1484
1484
|
led = state.ledger(self.name)
|
|
1485
1485
|
self.failure_reason = None
|
|
1486
|
+
# #1010: the deferral cause, named for the monitor's DEFERRED log —
|
|
1487
|
+
# a hardcoded cause is a lie on every path that defers for another
|
|
1488
|
+
# reason (the standing-wake hold defers with a CLEAR composer).
|
|
1489
|
+
self.deferred_reason = None
|
|
1486
1490
|
# #723: a rate-limit or usage-limit screen is not deliverable. An
|
|
1487
1491
|
# empty composer beside "Retry failed: rate limit" used to inject
|
|
1488
1492
|
# again, and the 1.0.44 upgrade modal (no Grok footer) used to
|
|
@@ -1601,21 +1605,36 @@ class TmuxSink(Sink):
|
|
|
1601
1605
|
if ok is None:
|
|
1602
1606
|
return None # human adopted mid-settle: defer
|
|
1603
1607
|
return False # unreadable: loud failure
|
|
1604
|
-
#
|
|
1605
|
-
#
|
|
1606
|
-
#
|
|
1607
|
-
#
|
|
1608
|
-
#
|
|
1609
|
-
#
|
|
1610
|
-
#
|
|
1611
|
-
#
|
|
1612
|
-
#
|
|
1613
|
-
#
|
|
1614
|
-
#
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1608
|
+
# #1010 STRAND-ONLY (lab's ruling after the 5th FAIL,
|
|
1609
|
+
# science-claude #1101): this return was the ONLY
|
|
1610
|
+
# delivery report backed by no fresh keystroke, and it
|
|
1611
|
+
# STRANDED the DM batch it reported. The ledger cursor
|
|
1612
|
+
# advanced to the newest id, the monitor then owed
|
|
1613
|
+
# nothing, and this poll was the last one that would
|
|
1614
|
+
# ever look at the batch — measured by droplet on
|
|
1615
|
+
# friendly-coder: its tail was down, typing was its only
|
|
1616
|
+
# wake, and the standing-wake claim "delivered" every
|
|
1617
|
+
# DM with ZERO keystrokes while the cell idled at an
|
|
1618
|
+
# empty prompt, forever. Fix WITHOUT classification:
|
|
1619
|
+
# report the hold, not a delivery. deliver returns None
|
|
1620
|
+
# — the cursor does not advance, the batch stays OWED
|
|
1621
|
+
# and is retried on every poll — and BASE'S OWN stale
|
|
1622
|
+
# bound below (_WAKE_STALE_S, 600 s from the injection)
|
|
1623
|
+
# re-injects and types at the first idle poll past it.
|
|
1624
|
+
# Bounded, loud, never silently dropped; no pane
|
|
1625
|
+
# classifier is consulted, so every pane keeps base's
|
|
1626
|
+
# behaviour. The anti-stack property survives: zero
|
|
1627
|
+
# keystrokes while the window stands.
|
|
1628
|
+
print(f"[monitor] {self.name}: standing wake inside its "
|
|
1629
|
+
f"{_WAKE_STALE_S:.0f}s trust window (injected "
|
|
1630
|
+
f"{time.time() - injected_at:.0f}s ago) — DM held "
|
|
1631
|
+
f"owed, zero keys this poll; the stale bound "
|
|
1632
|
+
f"re-injects and types")
|
|
1633
|
+
self.deferred_reason = (
|
|
1634
|
+
f"standing wake inside the {_WAKE_STALE_S:.0f}s "
|
|
1635
|
+
"trust window — DM held owed, the stale bound "
|
|
1636
|
+
"re-injects and types")
|
|
1637
|
+
return None
|
|
1619
1638
|
if composer == "busy":
|
|
1620
1639
|
# Human text shares the composer (possibly merged into our
|
|
1621
1640
|
# wake) — an Enter here submits THEIR line (#403's shape).
|
|
@@ -3333,8 +3352,13 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
3333
3352
|
state.gateway, state.token, state.self_name, ticks, len(dms)
|
|
3334
3353
|
)
|
|
3335
3354
|
changed = True
|
|
3355
|
+
# #1010: name the deferral's real cause — a hardcoded
|
|
3356
|
+
# "composer holds human text" is a lie on the standing-wake
|
|
3357
|
+
# hold, which defers with a CLEAR composer.
|
|
3358
|
+
detail = getattr(sink, "deferred_reason", None) \
|
|
3359
|
+
or "composer holds human text"
|
|
3336
3360
|
print(f"{state.log_prefix} delivery DEFERRED for {sink.name} "
|
|
3337
|
-
f"(
|
|
3361
|
+
f"({detail}); wake stays owed, "
|
|
3338
3362
|
f"no failure counted",
|
|
3339
3363
|
flush=True)
|
|
3340
3364
|
continue
|
|
@@ -3368,6 +3392,35 @@ def _monitor_deliver(state: MonitorState) -> None:
|
|
|
3368
3392
|
_write_ledgers_atomic(state.ledgers_path, state.ledgers)
|
|
3369
3393
|
|
|
3370
3394
|
|
|
3395
|
+
def _type_slash_clear(target: str) -> bool:
|
|
3396
|
+
"""Type ``/clear`` and submit it. One command, not a wake."""
|
|
3397
|
+
for argv in (
|
|
3398
|
+
["tmux", "send-keys", "-t", target, "-l", "/clear"],
|
|
3399
|
+
["tmux", "send-keys", "-t", target, "Enter"],
|
|
3400
|
+
):
|
|
3401
|
+
try:
|
|
3402
|
+
r = subprocess.run(
|
|
3403
|
+
argv, capture_output=True, timeout=5,
|
|
3404
|
+
text=True, encoding="utf-8", errors="replace",
|
|
3405
|
+
)
|
|
3406
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
3407
|
+
return False
|
|
3408
|
+
if r.returncode != 0:
|
|
3409
|
+
return False
|
|
3410
|
+
return True
|
|
3411
|
+
|
|
3412
|
+
|
|
3413
|
+
def _maybe_rowclear(state: MonitorState) -> str:
|
|
3414
|
+
"""Card #432. Off and non-opted-in cells return before any pane read."""
|
|
3415
|
+
from swarph_cli.rowclear import maybe_rowclear
|
|
3416
|
+
return maybe_rowclear(
|
|
3417
|
+
state,
|
|
3418
|
+
agent_running=_agent_running,
|
|
3419
|
+
http_get=_http_get_json,
|
|
3420
|
+
type_clear=_type_slash_clear,
|
|
3421
|
+
)
|
|
3422
|
+
|
|
3423
|
+
|
|
3371
3424
|
def _monitor_iteration(state: MonitorState, *, poll_channels: bool = True) -> None:
|
|
3372
3425
|
state.iterations += 1
|
|
3373
3426
|
last_id = int(state.observed.get("last_msg_id", 0))
|
|
@@ -3432,6 +3485,9 @@ def _monitor_iteration(state: MonitorState, *, poll_channels: bool = True) -> No
|
|
|
3432
3485
|
# A delivery deferred by the idle guard (or failed against a dead sink) must
|
|
3433
3486
|
# still land even though no NEW mail arrived, so this runs on every poll.
|
|
3434
3487
|
_monitor_deliver(state)
|
|
3488
|
+
# Card #432: after delivery, so an owed DM (cursor still behind) blocks
|
|
3489
|
+
# the clear. off / not-opted-in return before any pane or board read.
|
|
3490
|
+
_maybe_rowclear(state)
|
|
3435
3491
|
# The observation cursor is written BEFORE delivery, while last_wake_at is
|
|
3436
3492
|
# still unknown. A wake that landed this poll has to be written again or
|
|
3437
3493
|
# cursor.json keeps the 0.0 default after a real keystroke (#405).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""``swarph rowclear-hook`` — SessionStart hook for card #432.
|
|
2
|
+
|
|
3
|
+
Claude Code runs this on SessionStart. When the payload's ``source`` is
|
|
4
|
+
``clear``, the new ``session_id`` is written into the role's
|
|
5
|
+
``<role>.session-id`` pin (the role is ``SWARPH_SELF``). Spawn then
|
|
6
|
+
resumes that id instead of the pre-clear session.
|
|
7
|
+
|
|
8
|
+
Exit 0 on every path. A hook that fails closed refuses the session.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from swarph_cli import identity
|
|
17
|
+
from swarph_cli.rowclear import apply_sessionstart_clear
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read_stdin() -> dict:
|
|
21
|
+
try:
|
|
22
|
+
if not sys.stdin.isatty():
|
|
23
|
+
raw = sys.stdin.read()
|
|
24
|
+
if raw.strip():
|
|
25
|
+
return json.loads(raw.lstrip("\ufeff"))
|
|
26
|
+
except Exception:
|
|
27
|
+
return {}
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_rowclear_hook(argv: list[str] | None = None) -> int:
|
|
32
|
+
del argv
|
|
33
|
+
role = identity.declared(env=identity.SELF_ONLY)[0] or ""
|
|
34
|
+
if not role:
|
|
35
|
+
print("rowclear-hook: SWARPH_SELF unset — pin not rewritten",
|
|
36
|
+
file=sys.stderr)
|
|
37
|
+
return 0
|
|
38
|
+
try:
|
|
39
|
+
apply_sessionstart_clear(_read_stdin(), role)
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
print(f"rowclear-hook: {exc}", file=sys.stderr)
|
|
42
|
+
return 0
|
|
@@ -23,6 +23,7 @@ Read the topic you need. You do not need to read this top to bottom.
|
|
|
23
23
|
| [Check your own setup](#check-your-own-setup) | commands with their expected answers |
|
|
24
24
|
| [How to](#how-to) | tasks, phrased the way you'd ask for them |
|
|
25
25
|
| [Glossary](#glossary) | the words this mesh uses |
|
|
26
|
+
| [Architecture overview](#architecture-overview) | the five layers, the six rules, and how a change ships |
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -55,6 +56,15 @@ want, run the command.
|
|
|
55
56
|
| see what a wake hook would print | `swarph wake-hook-output` |
|
|
56
57
|
| search this guide | `swarph guide --search <word>` |
|
|
57
58
|
| learn the standard of evidence | `swarph guide doctrine` |
|
|
59
|
+
| see how the stack fits together | see [Architecture overview](#architecture-overview) |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Architecture overview
|
|
64
|
+
|
|
65
|
+
The five layers, the six rules, the delivery pipeline, shadow-to-graduate,
|
|
66
|
+
the security posture, and the stack-versus-off-the-shelf table are in
|
|
67
|
+
[docs/architecture-overview.md](../../../docs/architecture-overview.md).
|
|
58
68
|
|
|
59
69
|
---
|
|
60
70
|
|
|
@@ -533,6 +543,7 @@ answers `--help`; hook-plumbing verbs are the callbacks hooks invoke, not comman
|
|
|
533
543
|
- `swarph protocol-handler` -- register `swarph://` as an OS URL-scheme handler.
|
|
534
544
|
- `swarph ratify` -- witness flip that admits an onboarded peer; takes the peer name and `--reason`.
|
|
535
545
|
- `swarph rights` -- RBAC rights over the gateway (companion of `swarph group`).
|
|
546
|
+
- `swarph rowclear-hook` -- SessionStart hook (card #432): when `source` is `clear`, write the new session id into the role's `<role>.session-id` pin so the next spawn resumes the cleared session.
|
|
536
547
|
- `swarph scan` -- statically scan an artifact for dangerous patterns before publish.
|
|
537
548
|
- `swarph schedule` -- the gateway's scheduled events: create, list, get, enable, disable, delete, fire-now.
|
|
538
549
|
- `swarph service` -- stand up a $0 subscription-LLM HTTP lane.
|
|
@@ -92,6 +92,7 @@ _VERB_HANDLERS: dict[str, str] = {
|
|
|
92
92
|
"install-wake-hook": "swarph_cli.commands.install_wake_hook.run_install_wake_hook",
|
|
93
93
|
"wake-hook-output": "swarph_cli.commands.wake_hook_output.run_wake_hook_output",
|
|
94
94
|
"postcompact-hook-output": "swarph_cli.commands.postcompact_hook_output.run_postcompact_hook_output",
|
|
95
|
+
"rowclear-hook": "swarph_cli.commands.rowclear_hook.run_rowclear_hook",
|
|
95
96
|
"install-postcompact-hook": "swarph_cli.commands.install_postcompact_hook.run_install_postcompact_hook",
|
|
96
97
|
"install-opencode-plugin": "swarph_cli.commands.install_opencode_plugin.run_install_opencode_plugin",
|
|
97
98
|
"memory-emit-hook": "swarph_cli.commands.memory_emit_hook.run_memory_emit_hook",
|
|
@@ -147,8 +148,13 @@ def _verbs_block(width: int = 78) -> str:
|
|
|
147
148
|
import textwrap
|
|
148
149
|
verbs = registered_verbs()
|
|
149
150
|
head = f"Verbs ({len(verbs)}) -- each has --help and a guide entry (swarph guide <verb>):"
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
# break_on_hyphens would split a verb at a wrap (wake-hook-output
|
|
152
|
+
# became "wake-hook-" / "output" once the list grew past the width).
|
|
153
|
+
return head + "\n" + textwrap.fill(
|
|
154
|
+
", ".join(verbs), width=width,
|
|
155
|
+
initial_indent=" ", subsequent_indent=" ",
|
|
156
|
+
break_on_hyphens=False, break_long_words=False,
|
|
157
|
+
) + "\n"
|
|
152
158
|
|
|
153
159
|
|
|
154
160
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Card #432: type ``/clear`` at a row boundary, and follow the new session.
|
|
2
|
+
|
|
3
|
+
The monitor types ``/clear`` into an opted-in role cell's tmux pane only
|
|
4
|
+
when the cell holds no live row, the pane reads idle, and no DM is owed.
|
|
5
|
+
``SWARPH_ROWCLEAR`` is ``off`` (default), ``shadow`` (log a would-clear,
|
|
6
|
+
type nothing), or ``live``. v1 opt-in is drop-on-meta-edge only: a cell
|
|
7
|
+
that owns a project keeps its context (design_1007 / optin_owners).
|
|
8
|
+
|
|
9
|
+
A ``/clear`` mints a new Claude session id. The SessionStart hook with
|
|
10
|
+
``source=clear`` writes that id into the role's ``<role>.session-id``
|
|
11
|
+
pin, so the next spawn resumes the cleared session instead of the long
|
|
12
|
+
one the pin still named.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Any, Callable, Optional
|
|
22
|
+
|
|
23
|
+
from swarph_cli.cell import (
|
|
24
|
+
CellError,
|
|
25
|
+
_read_session_sidecar,
|
|
26
|
+
_write_session_sidecar,
|
|
27
|
+
session_state_path,
|
|
28
|
+
validate_uuid_str,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# v1. Further cells join only by their own or the commander's opt-in.
|
|
32
|
+
OPT_IN = frozenset({"drop-on-meta-edge"})
|
|
33
|
+
|
|
34
|
+
# Gateway statuses are open, closed, and fallback_fired. ``open`` is a
|
|
35
|
+
# live row (taken or offered). ``closed`` and ``fallback_fired`` are
|
|
36
|
+
# finished and do not block: drop-on-meta-edge holds a fallback_fired
|
|
37
|
+
# row that would otherwise make the only opted-in cell never clear.
|
|
38
|
+
# Any other status fails closed — a status this code does not know must
|
|
39
|
+
# not look like an empty cell.
|
|
40
|
+
_LIVE = "open"
|
|
41
|
+
_FINISHED = frozenset({"closed", "fallback_fired"})
|
|
42
|
+
|
|
43
|
+
_MODES = frozenset({"off", "shadow", "live"})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def mode_from_env() -> str:
|
|
47
|
+
raw = os.environ.get("SWARPH_ROWCLEAR", "off").strip().lower()
|
|
48
|
+
return raw if raw in _MODES else "off"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _latch_path(state) -> "os.PathLike[str]":
|
|
52
|
+
return state.state_dir / "rowclear.json"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _log_path(state) -> "os.PathLike[str]":
|
|
56
|
+
return state.state_dir / "rowclear.log"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _read_latch(state) -> Optional[str]:
|
|
60
|
+
path = _latch_path(state)
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, ValueError):
|
|
64
|
+
return None
|
|
65
|
+
mode = data.get("latched_mode") if isinstance(data, dict) else None
|
|
66
|
+
return mode if isinstance(mode, str) else None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _write_latch(state, mode: Optional[str]) -> None:
|
|
70
|
+
path = _latch_path(state)
|
|
71
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
path.write_text(
|
|
73
|
+
json.dumps({"latched_mode": mode}) + "\n", encoding="utf-8")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _tmux_target(state) -> Optional[str]:
|
|
77
|
+
for sink in state.sinks:
|
|
78
|
+
if type(sink).__name__ == "TmuxSink":
|
|
79
|
+
target = getattr(sink, "target", None)
|
|
80
|
+
if target:
|
|
81
|
+
return target
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _dm_owed(state) -> bool:
|
|
86
|
+
observed = int(state.observed.get("last_msg_id", 0))
|
|
87
|
+
for sink in state.sinks:
|
|
88
|
+
if not getattr(sink, "keeps_ledger", False):
|
|
89
|
+
continue
|
|
90
|
+
led = state.ledger(sink.name)
|
|
91
|
+
if int(led.get("last_delivered_id", 0)) < observed:
|
|
92
|
+
return True
|
|
93
|
+
if led.get("wake_outstanding"):
|
|
94
|
+
return True
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _live_row_count(state, http_get: Callable) -> Optional[int]:
|
|
99
|
+
"""How many unfinished rows this cell holds, or None if unread.
|
|
100
|
+
|
|
101
|
+
None fails closed: an unreadable board must not look like an empty
|
|
102
|
+
one, or a live row we failed to see gets a ``/clear``.
|
|
103
|
+
"""
|
|
104
|
+
url = f"{state.gateway}/board/obligations?holder={state.self_name}"
|
|
105
|
+
try:
|
|
106
|
+
status, body = http_get(url, state.token)
|
|
107
|
+
except Exception:
|
|
108
|
+
return None
|
|
109
|
+
if status != 200 or not isinstance(body, dict):
|
|
110
|
+
return None
|
|
111
|
+
rows = body.get("obligations")
|
|
112
|
+
if not isinstance(rows, list):
|
|
113
|
+
return None
|
|
114
|
+
live = 0
|
|
115
|
+
for row in rows:
|
|
116
|
+
if not isinstance(row, dict):
|
|
117
|
+
return None
|
|
118
|
+
status = str(row.get("status") or "")
|
|
119
|
+
if status == _LIVE:
|
|
120
|
+
live += 1
|
|
121
|
+
elif status not in _FINISHED:
|
|
122
|
+
return None
|
|
123
|
+
return live
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _append_would_clear(state, *, open_rows: int, pane_state: str) -> None:
|
|
127
|
+
path = _log_path(state)
|
|
128
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
record = {
|
|
130
|
+
"cell": state.self_name,
|
|
131
|
+
"time": datetime.now(timezone.utc).isoformat(),
|
|
132
|
+
"open_rows": open_rows,
|
|
133
|
+
"pane_state": pane_state,
|
|
134
|
+
"action": "would-clear",
|
|
135
|
+
}
|
|
136
|
+
with path.open("a", encoding="utf-8") as fp:
|
|
137
|
+
fp.write(json.dumps(record) + "\n")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def maybe_rowclear(
|
|
141
|
+
state,
|
|
142
|
+
*,
|
|
143
|
+
agent_running: Callable[[str], Optional[bool]],
|
|
144
|
+
http_get: Callable,
|
|
145
|
+
type_clear: Callable[[str], bool],
|
|
146
|
+
) -> str:
|
|
147
|
+
"""One poll. Returns the decision, so a test can name why no key went.
|
|
148
|
+
|
|
149
|
+
``off`` and a cell not on the opt-in list return before any read or
|
|
150
|
+
write: they are unchanged. A live row, a busy pane, or an owed DM
|
|
151
|
+
unlatches the idle boundary and types nothing. Unknown rows or an
|
|
152
|
+
unknown pane type nothing and leave the latch alone.
|
|
153
|
+
"""
|
|
154
|
+
mode = mode_from_env()
|
|
155
|
+
if mode == "off":
|
|
156
|
+
return "off"
|
|
157
|
+
if state.self_name not in OPT_IN:
|
|
158
|
+
return "not-opted-in"
|
|
159
|
+
|
|
160
|
+
live = _live_row_count(state, http_get)
|
|
161
|
+
if live is None:
|
|
162
|
+
return "blocked-rows-unknown"
|
|
163
|
+
if live > 0:
|
|
164
|
+
_write_latch(state, None)
|
|
165
|
+
return "blocked-live-row"
|
|
166
|
+
|
|
167
|
+
target = _tmux_target(state)
|
|
168
|
+
if target is None:
|
|
169
|
+
return "blocked-no-pane"
|
|
170
|
+
pane = agent_running(target)
|
|
171
|
+
if pane is True:
|
|
172
|
+
_write_latch(state, None)
|
|
173
|
+
return "blocked-busy"
|
|
174
|
+
if pane is None:
|
|
175
|
+
return "blocked-unknown-pane"
|
|
176
|
+
if _dm_owed(state):
|
|
177
|
+
_write_latch(state, None)
|
|
178
|
+
return "blocked-owed-dm"
|
|
179
|
+
|
|
180
|
+
if _read_latch(state) == mode:
|
|
181
|
+
return "latched"
|
|
182
|
+
|
|
183
|
+
if mode == "shadow":
|
|
184
|
+
_append_would_clear(state, open_rows=live, pane_state="idle")
|
|
185
|
+
_write_latch(state, mode)
|
|
186
|
+
print(
|
|
187
|
+
f"[rowclear] {state.self_name}: would-clear "
|
|
188
|
+
f"(open_rows={live}, pane=idle) — shadow, zero keys",
|
|
189
|
+
flush=True,
|
|
190
|
+
)
|
|
191
|
+
return "shadow"
|
|
192
|
+
|
|
193
|
+
if not type_clear(target):
|
|
194
|
+
print(
|
|
195
|
+
f"[rowclear] {state.self_name}: /clear send failed — "
|
|
196
|
+
"not latched, retried next poll",
|
|
197
|
+
flush=True,
|
|
198
|
+
)
|
|
199
|
+
return "type-failed"
|
|
200
|
+
_write_latch(state, mode)
|
|
201
|
+
print(
|
|
202
|
+
f"[rowclear] {state.self_name}: typed /clear "
|
|
203
|
+
f"(open_rows={live}, pane=idle, no DM owed)",
|
|
204
|
+
flush=True,
|
|
205
|
+
)
|
|
206
|
+
return "live"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def apply_sessionstart_clear(payload: dict[str, Any], role: str) -> bool:
|
|
210
|
+
"""Write ``session_id`` into the role pin when SessionStart source is clear.
|
|
211
|
+
|
|
212
|
+
Any other source leaves the pin alone: a resume must not clobber the
|
|
213
|
+
id the spawn will attach to. Returns True only when the pin was rewritten.
|
|
214
|
+
"""
|
|
215
|
+
if not isinstance(payload, dict) or payload.get("source") != "clear":
|
|
216
|
+
return False
|
|
217
|
+
raw = payload.get("session_id") or payload.get("sessionId") or ""
|
|
218
|
+
try:
|
|
219
|
+
session_id = validate_uuid_str(str(raw))
|
|
220
|
+
except (CellError, TypeError, ValueError):
|
|
221
|
+
return False
|
|
222
|
+
path = session_state_path(role)
|
|
223
|
+
_uuid, recorded_cwd = _read_session_sidecar(path)
|
|
224
|
+
payload_cwd = payload.get("cwd") or ""
|
|
225
|
+
# ruling_1236: a clear from a different project must not rewrite
|
|
226
|
+
# another role's pin (shared HOME, card #964). Refuse only when
|
|
227
|
+
# both sides name a cwd and they differ. A missing side keeps the
|
|
228
|
+
# old fallback.
|
|
229
|
+
if recorded_cwd and str(payload_cwd).strip():
|
|
230
|
+
if os.path.normpath(recorded_cwd) != os.path.normpath(str(payload_cwd)):
|
|
231
|
+
print(
|
|
232
|
+
f"rowclear: payload cwd {payload_cwd} differs from the "
|
|
233
|
+
f"pin's recorded cwd {recorded_cwd} — pin not rewritten",
|
|
234
|
+
file=sys.stderr,
|
|
235
|
+
)
|
|
236
|
+
return False
|
|
237
|
+
cwd = payload_cwd or recorded_cwd or ""
|
|
238
|
+
_write_session_sidecar(path, session_id, cwd)
|
|
239
|
+
return True
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""IMMUNE (cell watchdog), card #729. Every pass, a cell with nobody reading its inbox is an outage.
|
|
2
2
|
|
|
3
3
|
One DM per outage. A live watcher clears it, so the next gap can alert again.
|
|
4
4
|
"""
|
|
@@ -360,17 +360,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
360
360
|
for name in alert:
|
|
361
361
|
print(f"outage {name}", flush=True)
|
|
362
362
|
dm_rc = mesh_send(
|
|
363
|
-
swarph, name, sender, "your DM wake is dead, re-arm", token_file)
|
|
363
|
+
swarph, name, sender, "IMMUNE (cell watchdog): your DM wake is dead, re-arm", token_file)
|
|
364
364
|
dm_ok = dm_rc == 0
|
|
365
365
|
card_argv = [swarph, "board", "cards", "say", "729", "--as", sender,
|
|
366
|
-
"--to", name, "--content", f"{name}: DM wake is dead, re-arm"]
|
|
366
|
+
"--to", name, "--content", f"IMMUNE (cell watchdog) — {name}: DM wake is dead, re-arm"]
|
|
367
367
|
if token_file:
|
|
368
368
|
card_argv.extend(["--token-file", token_file])
|
|
369
369
|
card = subprocess.run(card_argv, check=False)
|
|
370
370
|
esc_ok = True
|
|
371
371
|
if escalate and escalate != name:
|
|
372
372
|
esc_rc = mesh_send(
|
|
373
|
-
swarph, escalate, sender, f"{name}: DM wake is dead, re-arm",
|
|
373
|
+
swarph, escalate, sender, f"IMMUNE (cell watchdog) — {name}: DM wake is dead, re-arm",
|
|
374
374
|
token_file)
|
|
375
375
|
esc_ok = esc_rc == 0
|
|
376
376
|
if dm_ok and card.returncode == 0 and esc_ok:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[Unit]
|
|
2
|
-
Description=Swarph watchdog one-shot check (stranded-session recovery)
|
|
2
|
+
Description=IMMUNE (cell watchdog): Swarph watchdog one-shot check (stranded-session recovery)
|
|
3
3
|
Documentation=https://github.com/BrainSurfing-tech/swarph-cli
|
|
4
4
|
After=network-online.target
|
|
5
5
|
Wants=network-online.target
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[Unit]
|
|
2
|
-
Description=Run swarph watchdog every 5 minutes (stranded-session recovery)
|
|
2
|
+
Description=Run IMMUNE (cell watchdog): swarph watchdog every 5 minutes (stranded-session recovery)
|
|
3
3
|
Documentation=https://github.com/BrainSurfing-tech/swarph-cli
|
|
4
4
|
Requires=swarph-watchdog.service
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.72.
|
|
3
|
+
Version: 0.72.5
|
|
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
|
|
@@ -26,6 +26,7 @@ src/swarph_cli/peer_service_payload.py
|
|
|
26
26
|
src/swarph_cli/peer_service_reply.py
|
|
27
27
|
src/swarph_cli/peer_staging.py
|
|
28
28
|
src/swarph_cli/query_filters.py
|
|
29
|
+
src/swarph_cli/rowclear.py
|
|
29
30
|
src/swarph_cli/session_bridge.py
|
|
30
31
|
src/swarph_cli/source_text.py
|
|
31
32
|
src/swarph_cli/stall_alert.py
|
|
@@ -109,6 +110,7 @@ src/swarph_cli/commands/peer_reply_drain.py
|
|
|
109
110
|
src/swarph_cli/commands/postcompact_hook_output.py
|
|
110
111
|
src/swarph_cli/commands/protocol_handler.py
|
|
111
112
|
src/swarph_cli/commands/ratify.py
|
|
113
|
+
src/swarph_cli/commands/rowclear_hook.py
|
|
112
114
|
src/swarph_cli/commands/schedule.py
|
|
113
115
|
src/swarph_cli/commands/security.py
|
|
114
116
|
src/swarph_cli/commands/service.py
|
|
@@ -188,6 +190,7 @@ src/swarph_cli/systemd/swarph-watchdog.service
|
|
|
188
190
|
src/swarph_cli/systemd/swarph-watchdog.timer
|
|
189
191
|
tests/test_1006_board_publisher.py
|
|
190
192
|
tests/test_1006_session_board.py
|
|
193
|
+
tests/test_1010_strand_only.py
|
|
191
194
|
tests/test_1013_spawn_channel.py
|
|
192
195
|
tests/test_1017_muse_tui.py
|
|
193
196
|
tests/test_124_register_merge.py
|
|
@@ -208,6 +211,7 @@ tests/test_356_query_filters.py
|
|
|
208
211
|
tests/test_397_gh_identity_router.py
|
|
209
212
|
tests/test_397_router_preflight.py
|
|
210
213
|
tests/test_402_one_identity_resolver.py
|
|
214
|
+
tests/test_432_rowclear.py
|
|
211
215
|
tests/test_467b_onboard_operator_credential.py
|
|
212
216
|
tests/test_472_register_caller_binding.py
|
|
213
217
|
tests/test_484_codex_queue_sink.py
|
|
@@ -346,3 +346,77 @@ def test_in_session_marker_sets_the_flag_and_strips_the_title():
|
|
|
346
346
|
assert "[in-session]" not in marked["title"]
|
|
347
347
|
assert "[commander]" not in marked["title"]
|
|
348
348
|
assert plain["in_session"] is False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _commander_obligation(oid, holder="commander", step="build", accept="PASS=x | FAIL=y", title="Do it"):
|
|
352
|
+
return {
|
|
353
|
+
"id": oid,
|
|
354
|
+
"holder": holder,
|
|
355
|
+
"card_id": 291,
|
|
356
|
+
"state": "open",
|
|
357
|
+
"step": step,
|
|
358
|
+
"accept": f"[commander] {title} | {accept}",
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_tap_closable_commander_rows_render_yes_no():
|
|
363
|
+
"""card #291 ruling_1157 (1): a tap-closable commander row (build or
|
|
364
|
+
step-less) renders as yes/no with the fixed mapping yes=pass, no=fail —
|
|
365
|
+
even when the row carries its own prose options."""
|
|
366
|
+
pub = _load()
|
|
367
|
+
rows = pub.commander_rows([
|
|
368
|
+
_commander_obligation(1, step="build"),
|
|
369
|
+
_commander_obligation(2, step=None),
|
|
370
|
+
])
|
|
371
|
+
assert [r["step"] for r in rows] == ["build", None]
|
|
372
|
+
by_cell = {}
|
|
373
|
+
for row in rows:
|
|
374
|
+
row["title"] = "Do it"
|
|
375
|
+
row["options"] = [{"label": "Prose", "text": "discuss first", "rec": True}]
|
|
376
|
+
by_cell.setdefault(row["cell"], []).append(row)
|
|
377
|
+
questions = pub.build_board._questions(by_cell["commander"])
|
|
378
|
+
assert len(questions) == 2
|
|
379
|
+
for q in questions:
|
|
380
|
+
assert [(o["label"], o["text"], o["outcome"]) for o in q["options"]] == [
|
|
381
|
+
("Yes", "yes", "pass"),
|
|
382
|
+
("No", "no", "fail"),
|
|
383
|
+
], "RED: tap-closable rows render yes/no, never row prose"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def test_validate_plan_review_rows_keep_display_only_options():
|
|
387
|
+
"""card #291 ruling_1157 (1): validate/plan-review commander rows are
|
|
388
|
+
NOT taps — their options stay prose and every option carries
|
|
389
|
+
outcome None (display-only, closes nothing)."""
|
|
390
|
+
pub = _load()
|
|
391
|
+
rows = pub.commander_rows([
|
|
392
|
+
_commander_obligation(3, step="validate"),
|
|
393
|
+
_commander_obligation(4, step="plan-review"),
|
|
394
|
+
])
|
|
395
|
+
by_cell = {}
|
|
396
|
+
for row in rows:
|
|
397
|
+
row["title"] = "Check it"
|
|
398
|
+
row["options"] = [{"label": "Prose", "text": "discuss first", "rec": True}]
|
|
399
|
+
by_cell.setdefault(row["cell"], []).append(row)
|
|
400
|
+
questions = pub.build_board._questions(by_cell["commander"])
|
|
401
|
+
assert len(questions) == 2
|
|
402
|
+
for q in questions:
|
|
403
|
+
assert all(o.get("outcome") is None for o in q["options"]), \
|
|
404
|
+
"RED: no prose option closes PASS by default"
|
|
405
|
+
assert [o["label"] for o in q["options"]] == ["Prose"]
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_default_options_are_display_only():
|
|
409
|
+
"""The three fallback prose options carry outcome None on every row."""
|
|
410
|
+
pub = _load()
|
|
411
|
+
rows = pub.commander_rows([_commander_obligation(5, step="build")])
|
|
412
|
+
row = dict(rows[0])
|
|
413
|
+
row["title"] = "Do it"
|
|
414
|
+
row.pop("options", None)
|
|
415
|
+
other = dict(row, cell="drop-on-meta-edge", holder="drop-on-meta-edge")
|
|
416
|
+
for q in pub.build_board._questions([row, other]):
|
|
417
|
+
if q["to_node"] == "commander":
|
|
418
|
+
assert [o["outcome"] for o in q["options"]] == ["pass", "fail"]
|
|
419
|
+
else:
|
|
420
|
+
assert [o["label"] for o in q["options"]] == [
|
|
421
|
+
"Answer on the card", "Ask for a narrower question", "Park it"]
|
|
422
|
+
assert all(o["outcome"] is None for o in q["options"])
|