swarph-cli 0.72.9__tar.gz → 0.72.11__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.9/src/swarph_cli.egg-info → swarph_cli-0.72.11}/PKG-INFO +1 -1
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/pyproject.toml +1 -1
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/channel.py +64 -4
- swarph_cli-0.72.11/src/swarph_cli/commands/sweep.py +413 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/guide/GUIDE.md +1 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/main.py +1 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/dm_notify_filter.py +112 -8
- swarph_cli-0.72.11/src/swarph_cli/systemd/swarph-stuck-sweep.service +15 -0
- swarph_cli-0.72.11/src/swarph_cli/systemd/swarph-stuck-sweep.timer +14 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
- swarph_cli-0.72.11/tests/test_1064_stuck_sweep.py +332 -0
- swarph_cli-0.72.11/tests/test_1065_release.py +396 -0
- swarph_cli-0.72.11/tests/test_1066_quiet_wakes.py +306 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/LICENSE +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/README.md +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/setup.cfg +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/codex_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/roster.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/rowclear_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/rowclear.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1006_board_publisher.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1010_strand_only.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1013_spawn_channel.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1017_muse_tui.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1050_surrogate_wake.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1052_opencode_deaf.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1059_watchdog_unread_env.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_1061_roster_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_432_rowclear.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_484_codex_queue_sink.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_961_loud_stop.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_codex_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_group_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_import_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_init_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_main.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_operator_me.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_security.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_app.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_version_command.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.72.9 → swarph_cli-0.72.11}/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.11
|
|
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.11"
|
|
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" }
|
|
@@ -15,7 +15,12 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
from swarph_cli import identity
|
|
17
17
|
from swarph_cli.dm_frame import FIRST_START_GRACE_S, created_within, frame_text, rows
|
|
18
|
-
from swarph_cli.scripts.dm_notify_filter import
|
|
18
|
+
from swarph_cli.scripts.dm_notify_filter import (
|
|
19
|
+
_QUIET_CAP_S,
|
|
20
|
+
is_quiet_dm,
|
|
21
|
+
is_real_dm,
|
|
22
|
+
quiet_enabled,
|
|
23
|
+
)
|
|
19
24
|
|
|
20
25
|
PROTOCOL_VERSION = "2025-06-18"
|
|
21
26
|
METHOD = "notifications/claude/channel"
|
|
@@ -127,8 +132,15 @@ class Channel:
|
|
|
127
132
|
First start keeps a present row when created_at is within 30s before
|
|
128
133
|
this poll or any time after, via dm_frame.created_within. Older rows
|
|
129
134
|
set the cursor and are not pushed.
|
|
135
|
+
|
|
136
|
+
Card #1066: with quiet wakes enabled, TAKEN receipts and repo.merged
|
|
137
|
+
events are HELD, not pushed — carried with the next real push or
|
|
138
|
+
once each passes the cap. The hold is durable: held ids persist in
|
|
139
|
+
the cursor file, so a restart neither loses nor double-pushes them.
|
|
140
|
+
A held id missing from the inbox (rotated away) is dropped.
|
|
130
141
|
"""
|
|
131
142
|
now = time.time() if now is None else now
|
|
143
|
+
quiet_on = quiet_enabled()
|
|
132
144
|
notes: list[dict] = []
|
|
133
145
|
if not self.inbox.is_file():
|
|
134
146
|
self._heartbeat(now, None)
|
|
@@ -153,6 +165,8 @@ class Channel:
|
|
|
153
165
|
"offset": 0,
|
|
154
166
|
"anomaly_sent": False,
|
|
155
167
|
}
|
|
168
|
+
if quiet_on:
|
|
169
|
+
self.cursor["held_quiet"] = []
|
|
156
170
|
self._save()
|
|
157
171
|
notes.append(_note(self.armed_line()))
|
|
158
172
|
if self.cursor.get("inode") != st.st_ino:
|
|
@@ -162,6 +176,7 @@ class Channel:
|
|
|
162
176
|
fresh = _rows(text[offset:])
|
|
163
177
|
last = self.cursor.get("last_pushed_id")
|
|
164
178
|
last_i = int(last) if last is not None else -1
|
|
179
|
+
held = self._held_entries()
|
|
165
180
|
pending = []
|
|
166
181
|
for row in fresh:
|
|
167
182
|
if row.get("to_node") != self.cell:
|
|
@@ -175,30 +190,75 @@ class Channel:
|
|
|
175
190
|
continue
|
|
176
191
|
if rid <= last_i:
|
|
177
192
|
continue
|
|
193
|
+
if quiet_on and is_quiet_dm(row):
|
|
194
|
+
if all(h["id"] != rid for h in held):
|
|
195
|
+
held.append({"id": rid, "at": now})
|
|
196
|
+
continue
|
|
178
197
|
pending.append(row)
|
|
179
198
|
if self.foreign and not self.anomaly_sent:
|
|
180
199
|
notes.append(_note(
|
|
181
200
|
f"{self.foreign} inbox rows dropped: to_node was not {self.cell}"))
|
|
182
201
|
self.anomaly_sent = True
|
|
183
202
|
self.cursor["anomaly_sent"] = True
|
|
203
|
+
# Card #1066 carry + cap: held rows flush with the first real push
|
|
204
|
+
# (held first — they arrived earlier) or once each passes the cap.
|
|
205
|
+
# Held rows are NEVER backlog-truncated: dropping a waited-on row
|
|
206
|
+
# would lose it, the exact FAIL this card names.
|
|
207
|
+
by_id = {}
|
|
208
|
+
for row in rows:
|
|
209
|
+
try:
|
|
210
|
+
by_id[int(row["id"])] = row
|
|
211
|
+
except (KeyError, TypeError, ValueError):
|
|
212
|
+
continue
|
|
213
|
+
carried, kept = [], []
|
|
214
|
+
for h in held:
|
|
215
|
+
if h["id"] not in by_id:
|
|
216
|
+
continue # rotated out of the inbox: undeliverable, drop
|
|
217
|
+
if pending or now - h["at"] >= _QUIET_CAP_S:
|
|
218
|
+
carried.append(by_id[h["id"]])
|
|
219
|
+
else:
|
|
220
|
+
kept.append(h)
|
|
184
221
|
if len(pending) > BACKLOG:
|
|
185
222
|
skipped = len(pending) - BACKLOG
|
|
186
223
|
pending = pending[-BACKLOG:]
|
|
187
224
|
notes.append(_note(f"{skipped} older DMs not pushed, read the inbox"))
|
|
188
|
-
for row in pending:
|
|
225
|
+
for row in carried + pending:
|
|
189
226
|
notes.append(_frame(row))
|
|
190
227
|
self.cursor["last_pushed_id"] = int(row["id"])
|
|
228
|
+
# Byte-identical cursors when quiet is off: never introduce the key
|
|
229
|
+
# into a cursor that never had it.
|
|
230
|
+
if quiet_on or "held_quiet" in self.cursor:
|
|
231
|
+
self.cursor["held_quiet"] = kept
|
|
191
232
|
self.cursor["offset"] = st.st_size
|
|
192
233
|
self.cursor["inode"] = st.st_ino
|
|
193
234
|
self._save()
|
|
194
|
-
|
|
195
|
-
|
|
235
|
+
covered = self.cursor.get("last_pushed_id")
|
|
236
|
+
for h in kept:
|
|
237
|
+
if covered is None or h["id"] > covered:
|
|
238
|
+
covered = h["id"]
|
|
239
|
+
if newest is not None and covered is not None:
|
|
240
|
+
if int(newest) > int(covered):
|
|
196
241
|
self.stall_since = self.stall_since or now
|
|
197
242
|
else:
|
|
198
243
|
self.stall_since = None
|
|
199
244
|
self._heartbeat(now, newest)
|
|
200
245
|
return notes
|
|
201
246
|
|
|
247
|
+
def _held_entries(self) -> list:
|
|
248
|
+
"""Durable hold, validated: a corrupt held_quiet resets, never kills
|
|
249
|
+
the poll (a dead channel from a bad cursor is worse than a re-push —
|
|
250
|
+
re-read rows re-hold by id)."""
|
|
251
|
+
raw = (self.cursor or {}).get("held_quiet") or []
|
|
252
|
+
out = []
|
|
253
|
+
for h in raw:
|
|
254
|
+
try:
|
|
255
|
+
if not isinstance(h, dict):
|
|
256
|
+
continue
|
|
257
|
+
out.append({"id": int(h["id"]), "at": float(h["at"])})
|
|
258
|
+
except (KeyError, TypeError, ValueError):
|
|
259
|
+
continue
|
|
260
|
+
return out
|
|
261
|
+
|
|
202
262
|
def _save(self) -> None:
|
|
203
263
|
self.cursor_path.parent.mkdir(parents=True, exist_ok=True)
|
|
204
264
|
self.cursor_path.write_text(json.dumps(self.cursor), encoding="utf-8")
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""``swarph sweep`` — the card-#1064 stuck-work sweep, one daily shot.
|
|
2
|
+
|
|
3
|
+
Finds (a) open/taken rows blocked >=48h on an unmoving dependency and (b)
|
|
4
|
+
approved-or-old open PRs with no obligation row naming them; DMs each
|
|
5
|
+
finding's BLOCKING holder once per 24h (never the waiter) and appends one
|
|
6
|
+
line per finding to the nightly watchtower report file. Never closes or
|
|
7
|
+
reassigns: the only board write is the DM itself (POST /messages).
|
|
8
|
+
|
|
9
|
+
swarph sweep [--dry-run] [--repos R1,R2] [--state-file P] [--report-file P]
|
|
10
|
+
[--as SELF] [--gateway URL] [--token-file F] [--json]
|
|
11
|
+
|
|
12
|
+
--dry-run reads the live board and GitHub, prints the findings, and writes
|
|
13
|
+
nothing (no DMs, no state, no report) — the rehearsal shape.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
|
|
25
|
+
_STUCK_H = 48
|
|
26
|
+
_RESEND_H = 24
|
|
27
|
+
|
|
28
|
+
_DEFAULT_REPOS = (
|
|
29
|
+
"BrainSurfing-tech/swarph-cli",
|
|
30
|
+
"BrainSurfing-tech/mesh-gateway",
|
|
31
|
+
"BrainSurfing-tech/swarph-codegraph-fed",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
_PR_URL_RE = re.compile(
|
|
35
|
+
r"github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)/pull/(\d+)",
|
|
36
|
+
re.IGNORECASE)
|
|
37
|
+
_PR_NUM_RE = re.compile(r"\bPR\s*#?(\d+)\b", re.IGNORECASE)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parse_ts(v):
|
|
41
|
+
"""Tolerant ISO parse -> epoch seconds, or None. Never raises: an
|
|
42
|
+
unparseable timestamp is unevaluable, not zero."""
|
|
43
|
+
if not v:
|
|
44
|
+
return None
|
|
45
|
+
try:
|
|
46
|
+
dt = datetime.fromisoformat(str(v).replace("Z", "+00:00"))
|
|
47
|
+
except (TypeError, ValueError):
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
return dt.timestamp()
|
|
51
|
+
except (TypeError, ValueError, OverflowError, OSError):
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _age_days(now, ts):
|
|
56
|
+
return round((now - ts) / 86400)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _row_last_change(row):
|
|
60
|
+
for k in ("closed_at", "taken_at", "created_at"):
|
|
61
|
+
ts = _parse_ts(row.get(k))
|
|
62
|
+
if ts is not None:
|
|
63
|
+
return ts
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _named_prs(rows):
|
|
68
|
+
"""(repo-or-None, number) pairs obligation rows name: full URLs match
|
|
69
|
+
exactly; bare PR numbers match by number across tracked repos."""
|
|
70
|
+
named = set()
|
|
71
|
+
for row in rows:
|
|
72
|
+
if not isinstance(row, dict):
|
|
73
|
+
continue
|
|
74
|
+
blob = " ".join(
|
|
75
|
+
str(row.get(k) or "") for k in ("accept", "close_evidence"))
|
|
76
|
+
for m in _PR_URL_RE.finditer(blob):
|
|
77
|
+
try:
|
|
78
|
+
named.add((m.group(1).lower(), int(m.group(2))))
|
|
79
|
+
except ValueError:
|
|
80
|
+
continue
|
|
81
|
+
for m in _PR_NUM_RE.finditer(blob):
|
|
82
|
+
try:
|
|
83
|
+
named.add((None, int(m.group(1))))
|
|
84
|
+
except ValueError:
|
|
85
|
+
continue
|
|
86
|
+
return named
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _graph_steps(http_get, gateway, token, card_id, cache):
|
|
90
|
+
if card_id in cache:
|
|
91
|
+
return cache[card_id]
|
|
92
|
+
try:
|
|
93
|
+
status, body = http_get(
|
|
94
|
+
f"{gateway}/board/cards/{card_id}/graph", token)
|
|
95
|
+
except Exception:
|
|
96
|
+
return None
|
|
97
|
+
if status != 200 or not isinstance(body, dict):
|
|
98
|
+
return None
|
|
99
|
+
steps = body.get("steps")
|
|
100
|
+
if not isinstance(steps, list):
|
|
101
|
+
return None
|
|
102
|
+
cache[card_id] = steps
|
|
103
|
+
return steps
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _row_findings(rows, by_id, http_get, gateway, token, now):
|
|
107
|
+
"""Kind (a): one finding per (open taken waiter, unsatisfied old need)."""
|
|
108
|
+
out = []
|
|
109
|
+
cache = {}
|
|
110
|
+
for w in rows:
|
|
111
|
+
if w.get("status") != "open" or not w.get("taken_at"):
|
|
112
|
+
continue
|
|
113
|
+
step = w.get("step")
|
|
114
|
+
if not step:
|
|
115
|
+
continue
|
|
116
|
+
card_id = w.get("card_id")
|
|
117
|
+
if not isinstance(card_id, int) or isinstance(card_id, bool):
|
|
118
|
+
continue
|
|
119
|
+
steps = _graph_steps(http_get, gateway, token, card_id, cache)
|
|
120
|
+
if steps is None:
|
|
121
|
+
continue
|
|
122
|
+
entry = next((e for e in steps
|
|
123
|
+
if isinstance(e, dict) and e.get("step") == step), None)
|
|
124
|
+
if entry is None:
|
|
125
|
+
continue
|
|
126
|
+
needs = entry.get("needs")
|
|
127
|
+
if not isinstance(needs, list):
|
|
128
|
+
continue
|
|
129
|
+
for need in needs:
|
|
130
|
+
if not isinstance(need, dict):
|
|
131
|
+
continue
|
|
132
|
+
sat = need.get("satisfied")
|
|
133
|
+
if sat is True:
|
|
134
|
+
continue
|
|
135
|
+
if sat is not False:
|
|
136
|
+
continue # unevaluable need names no finding
|
|
137
|
+
rid = need.get("row_id")
|
|
138
|
+
dep = by_id.get(rid)
|
|
139
|
+
if not isinstance(dep, dict):
|
|
140
|
+
continue
|
|
141
|
+
if dep.get("status") == "closed":
|
|
142
|
+
continue # terminal: satisfied or a rework case, not unmoving
|
|
143
|
+
changed = _row_last_change(dep)
|
|
144
|
+
if changed is None or now - changed < _STUCK_H * 3600:
|
|
145
|
+
continue
|
|
146
|
+
holder = dep.get("holder")
|
|
147
|
+
blocker = holder if holder and dep.get("holder_known", True) else None
|
|
148
|
+
out.append({
|
|
149
|
+
"kind": "row",
|
|
150
|
+
"waiter_id": w.get("id"),
|
|
151
|
+
"waiter": w.get("holder"),
|
|
152
|
+
"waiter_step": step,
|
|
153
|
+
"card_id": card_id,
|
|
154
|
+
"need": need.get("step"),
|
|
155
|
+
"need_id": rid,
|
|
156
|
+
"blocker": blocker,
|
|
157
|
+
"age_days": _age_days(now, changed),
|
|
158
|
+
})
|
|
159
|
+
return out
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _pr_findings(named, gh_prs, repos, peers, now):
|
|
163
|
+
"""Kind (b): approved-or-old open PRs no row names. An author with no
|
|
164
|
+
mesh peer is report-only — the CLI must not invent a recipient."""
|
|
165
|
+
out = []
|
|
166
|
+
for repo in repos:
|
|
167
|
+
try:
|
|
168
|
+
prs = gh_prs(repo)
|
|
169
|
+
except Exception:
|
|
170
|
+
continue
|
|
171
|
+
if not isinstance(prs, list):
|
|
172
|
+
continue
|
|
173
|
+
for pr in prs:
|
|
174
|
+
if not isinstance(pr, dict):
|
|
175
|
+
continue
|
|
176
|
+
try:
|
|
177
|
+
number = int(pr.get("number"))
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
continue
|
|
180
|
+
if (repo.lower(), number) in named or (None, number) in named:
|
|
181
|
+
continue
|
|
182
|
+
approved = pr.get("reviewDecision") == "APPROVED"
|
|
183
|
+
created = _parse_ts(pr.get("createdAt"))
|
|
184
|
+
old = created is not None and now - created >= _STUCK_H * 3600
|
|
185
|
+
if not approved and not old:
|
|
186
|
+
continue
|
|
187
|
+
author = pr.get("author") or {}
|
|
188
|
+
login = author.get("login") if isinstance(author, dict) else None
|
|
189
|
+
# blocker names the responsible party even when they have no
|
|
190
|
+
# mesh peer — the SEND step DMs only peers (report-only else).
|
|
191
|
+
out.append({
|
|
192
|
+
"kind": "pr",
|
|
193
|
+
"repo": repo,
|
|
194
|
+
"number": number,
|
|
195
|
+
"title": pr.get("title") or "",
|
|
196
|
+
"url": pr.get("url") or "",
|
|
197
|
+
"approved": approved,
|
|
198
|
+
"age_days": _age_days(now, created) if created else None,
|
|
199
|
+
"blocker": login,
|
|
200
|
+
})
|
|
201
|
+
return out
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _finding_key(f):
|
|
205
|
+
if f["kind"] == "row":
|
|
206
|
+
return f"row:{f['waiter_id']}:{f['need']}"
|
|
207
|
+
return f"pr:{f['repo']}#{f['number']}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _report_line(now_iso, f):
|
|
211
|
+
if f["kind"] == "row":
|
|
212
|
+
return (f"{now_iso} STUCK row waiter=#{f['waiter_id']}({f['waiter']}) "
|
|
213
|
+
f"step={f['waiter_step']} card=#{f['card_id']} "
|
|
214
|
+
f"blocked-by=#{f['need_id']}({f['blocker']}) "
|
|
215
|
+
f"need={f['need']} need-age={f['age_days']}d")
|
|
216
|
+
how = "approved" if f["approved"] else f"age={f['age_days']}d"
|
|
217
|
+
return (f"{now_iso} STUCK pr {f['repo']}#{f['number']} "
|
|
218
|
+
f"'{f['title']}' ({how}, no review row) author={f['blocker']}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _dm_content(f):
|
|
222
|
+
if f["kind"] == "row":
|
|
223
|
+
return (f"STUCK-WORK SWEEP: row #{f['waiter_id']} ({f['waiter']}, "
|
|
224
|
+
f"{f['waiter_step']}, card #{f['card_id']}) has waited on "
|
|
225
|
+
f"your row #{f['need_id']} ({f['need']}, unchanged "
|
|
226
|
+
f"{f['age_days']}d). Please move it, fail it, or say what "
|
|
227
|
+
f"it waits on.")
|
|
228
|
+
state = "approved" if f["approved"] else "open %sd" % (f['age_days'],)
|
|
229
|
+
return (f"STUCK-WORK SWEEP: your PR {f['repo']}#{f['number']} "
|
|
230
|
+
f"'{f['title']}' ({f['url']}) is {state} "
|
|
231
|
+
f"with no review row naming it. Please land it or ask for review.")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def run_sweep(*, gateway, token, sender, repos, state_file, report_file,
|
|
235
|
+
now, http_get, http_post, gh_prs, dry_run=False):
|
|
236
|
+
"""One sweep. Returns (rc, findings): rc 1 only when the board itself is
|
|
237
|
+
unreadable (nothing evaluable); findings are the SENT (or dry-run, would
|
|
238
|
+
send) findings. Only ever writes DMs + the two local files."""
|
|
239
|
+
gateway = gateway.rstrip("/")
|
|
240
|
+
try:
|
|
241
|
+
status, body = http_get(f"{gateway}/board/obligations", token)
|
|
242
|
+
except Exception:
|
|
243
|
+
status, body = None, None
|
|
244
|
+
if status != 200 or not isinstance(body, dict) or not isinstance(
|
|
245
|
+
body.get("obligations"), list):
|
|
246
|
+
print("swarph sweep: board unreadable — no findings, nothing sent",
|
|
247
|
+
file=sys.stderr)
|
|
248
|
+
return 1, []
|
|
249
|
+
rows = [r for r in body["obligations"] if isinstance(r, dict)]
|
|
250
|
+
by_id = {r.get("id"): r for r in rows
|
|
251
|
+
if isinstance(r.get("id"), int)}
|
|
252
|
+
try:
|
|
253
|
+
pstatus, pbody = http_get(f"{gateway}/peers", token)
|
|
254
|
+
peers = {p["name"] for p in pbody.get("peers", [])
|
|
255
|
+
if isinstance(p, dict) and p.get("name")}
|
|
256
|
+
if pstatus != 200:
|
|
257
|
+
peers = set()
|
|
258
|
+
except Exception:
|
|
259
|
+
peers = set()
|
|
260
|
+
|
|
261
|
+
findings = _row_findings(rows, by_id, http_get, gateway, token, now)
|
|
262
|
+
findings += _pr_findings(_named_prs(rows), gh_prs, repos, peers, now)
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
with open(state_file, encoding="utf-8") as fp:
|
|
266
|
+
sent = json.load(fp)
|
|
267
|
+
sent = sent if isinstance(sent, dict) else {}
|
|
268
|
+
except (OSError, ValueError):
|
|
269
|
+
sent = {}
|
|
270
|
+
now_iso = datetime.fromtimestamp(now, timezone.utc).isoformat()
|
|
271
|
+
fresh, updates = [], {}
|
|
272
|
+
for f in findings:
|
|
273
|
+
key = _finding_key(f)
|
|
274
|
+
try:
|
|
275
|
+
last = _parse_ts(sent.get(key))
|
|
276
|
+
except Exception:
|
|
277
|
+
last = None
|
|
278
|
+
if last is not None and now - last < _RESEND_H * 3600:
|
|
279
|
+
continue
|
|
280
|
+
fresh.append(f)
|
|
281
|
+
updates[key] = now_iso
|
|
282
|
+
|
|
283
|
+
if dry_run:
|
|
284
|
+
for f in fresh:
|
|
285
|
+
print("DRY " + _report_line(now_iso, f))
|
|
286
|
+
return 0, fresh
|
|
287
|
+
|
|
288
|
+
for f in fresh:
|
|
289
|
+
# DM only a blocker with a mesh peer: pinging the waiter instead is
|
|
290
|
+
# the FAIL, and inventing a recipient for a gh login is its cousin.
|
|
291
|
+
if f["blocker"] is not None and f["blocker"] in peers:
|
|
292
|
+
status, payload = http_post(
|
|
293
|
+
f"{gateway}/messages",
|
|
294
|
+
{"from_node": sender, "to_node": f["blocker"],
|
|
295
|
+
"kind": "fyi", "content": _dm_content(f)},
|
|
296
|
+
token)
|
|
297
|
+
if status < 200 or status >= 300:
|
|
298
|
+
detail = (payload.get("detail", "<gateway error>")
|
|
299
|
+
if isinstance(payload, dict) else "<gateway error>")
|
|
300
|
+
print(f"swarph sweep: DM to {f['blocker']} failed "
|
|
301
|
+
f"{status}: {detail}", file=sys.stderr)
|
|
302
|
+
continue
|
|
303
|
+
line = _report_line(now_iso, f)
|
|
304
|
+
print(line)
|
|
305
|
+
try:
|
|
306
|
+
with open(report_file, "a", encoding="utf-8") as fp:
|
|
307
|
+
fp.write(line + "\n")
|
|
308
|
+
except OSError as exc:
|
|
309
|
+
print(f"swarph sweep: report append failed: {exc}",
|
|
310
|
+
file=sys.stderr)
|
|
311
|
+
if updates:
|
|
312
|
+
sent.update(updates)
|
|
313
|
+
try:
|
|
314
|
+
with open(state_file, "w", encoding="utf-8") as fp:
|
|
315
|
+
json.dump(sent, fp)
|
|
316
|
+
except OSError as exc:
|
|
317
|
+
print(f"swarph sweep: state write failed: {exc}", file=sys.stderr)
|
|
318
|
+
return 0, fresh
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _gh_prs(repo):
|
|
322
|
+
"""Live gh transport: open PRs with the fields the matcher needs, or
|
|
323
|
+
None when gh is unusable (that repo's findings are skipped, not
|
|
324
|
+
invented)."""
|
|
325
|
+
try:
|
|
326
|
+
p = subprocess.run(
|
|
327
|
+
["gh", "pr", "list", "--repo", repo, "--state", "open",
|
|
328
|
+
"--limit", "100", "--json",
|
|
329
|
+
"number,title,createdAt,reviewDecision,url,author"],
|
|
330
|
+
capture_output=True, text=True, encoding="utf-8",
|
|
331
|
+
errors="replace", timeout=60)
|
|
332
|
+
except (OSError, subprocess.SubprocessError):
|
|
333
|
+
return None
|
|
334
|
+
if p.returncode != 0:
|
|
335
|
+
return None
|
|
336
|
+
try:
|
|
337
|
+
data = json.loads(p.stdout or "[]")
|
|
338
|
+
except ValueError:
|
|
339
|
+
return None
|
|
340
|
+
return data if isinstance(data, list) else None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _default_state_file():
|
|
344
|
+
xdg = os.environ.get("XDG_STATE_HOME", "").strip()
|
|
345
|
+
root = xdg if xdg else os.path.join(os.path.expanduser("~"), ".local", "state")
|
|
346
|
+
return os.path.join(root, "swarph", "stuck-sweep.json")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _build_parser():
|
|
350
|
+
p = argparse.ArgumentParser(
|
|
351
|
+
prog="swarph sweep",
|
|
352
|
+
description="Daily stuck-work sweep: blocked rows + unreviewed PRs "
|
|
353
|
+
"(card #1064). DMs blockers, never closes/reassigns.")
|
|
354
|
+
p.add_argument("--dry-run", action="store_true",
|
|
355
|
+
help="print findings; send/record/write nothing")
|
|
356
|
+
p.add_argument("--repos", default=None,
|
|
357
|
+
help="comma-separated owner/repo list (default "
|
|
358
|
+
"$SWARPH_SWEEP_REPOS or the fleet three)")
|
|
359
|
+
p.add_argument("--state-file", default=None)
|
|
360
|
+
p.add_argument("--report-file", default=None,
|
|
361
|
+
help="nightly watchtower report path "
|
|
362
|
+
"(default $SWARPH_SWEEP_REPORT or state-adjacent log)")
|
|
363
|
+
p.add_argument("--json", action="store_true")
|
|
364
|
+
p.add_argument("--as", dest="self_name", default=None,
|
|
365
|
+
help="DM sender identity")
|
|
366
|
+
from swarph_cli.gateway_default import env_gateway
|
|
367
|
+
p.add_argument("--gateway", default=env_gateway())
|
|
368
|
+
p.add_argument("--token-file", default=None)
|
|
369
|
+
return p
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def run_sweep_cmd(argv):
|
|
373
|
+
import time as _time
|
|
374
|
+
from swarph_cli.commands import mesh as _mesh
|
|
375
|
+
from swarph_cli.tokens import resolve_token
|
|
376
|
+
args = _build_parser().parse_args(argv)
|
|
377
|
+
try:
|
|
378
|
+
# #872 doctrine: an automated DM must name its sender. Dry-run sends
|
|
379
|
+
# nothing and needs no identity.
|
|
380
|
+
if not args.self_name and not args.dry_run:
|
|
381
|
+
print("swarph sweep: --as a sender is required (automated DMs "
|
|
382
|
+
"must name their sender; --dry-run needs no identity)",
|
|
383
|
+
file=sys.stderr)
|
|
384
|
+
return 2
|
|
385
|
+
resolution = resolve_token(
|
|
386
|
+
args.self_name, args.token_file,
|
|
387
|
+
identity_is_explicit=bool(args.self_name))
|
|
388
|
+
if resolution is None:
|
|
389
|
+
print("swarph sweep: no token (set MESH_GATEWAY_TOKEN, pass "
|
|
390
|
+
"--token-file, or --as a sender)", file=sys.stderr)
|
|
391
|
+
return 2
|
|
392
|
+
sender = args.self_name or "(dry-run)"
|
|
393
|
+
repos = ([r.strip() for r in args.repos.split(",") if r.strip()]
|
|
394
|
+
if args.repos else
|
|
395
|
+
[r.strip() for r in os.environ.get(
|
|
396
|
+
"SWARPH_SWEEP_REPOS", ",".join(_DEFAULT_REPOS)
|
|
397
|
+
).split(",") if r.strip()])
|
|
398
|
+
state_file = args.state_file or os.environ.get(
|
|
399
|
+
"SWARPH_SWEEP_STATE", _default_state_file())
|
|
400
|
+
report_file = (args.report_file or os.environ.get("SWARPH_SWEEP_REPORT")
|
|
401
|
+
or os.path.splitext(state_file)[0] + ".log")
|
|
402
|
+
rc, findings = run_sweep(
|
|
403
|
+
gateway=args.gateway, token=resolution.token, sender=sender,
|
|
404
|
+
repos=repos, state_file=state_file, report_file=report_file,
|
|
405
|
+
now=_time.time(), http_get=_mesh._http_get_json,
|
|
406
|
+
http_post=_mesh._post_json, gh_prs=_gh_prs,
|
|
407
|
+
dry_run=args.dry_run)
|
|
408
|
+
if args.json:
|
|
409
|
+
print(json.dumps(findings, indent=2))
|
|
410
|
+
return rc
|
|
411
|
+
except RuntimeError as exc:
|
|
412
|
+
print(f"swarph sweep: {exc}", file=sys.stderr)
|
|
413
|
+
return 2
|
|
@@ -549,6 +549,7 @@ answers `--help`; hook-plumbing verbs are the callbacks hooks invoke, not comman
|
|
|
549
549
|
- `swarph schedule` -- the gateway's scheduled events: create, list, get, enable, disable, delete, fire-now.
|
|
550
550
|
- `swarph service` -- stand up a $0 subscription-LLM HTTP lane.
|
|
551
551
|
- `swarph spawn` -- run a cell from its role name or yaml path.
|
|
552
|
+
- `swarph sweep` -- the card-#1064 stuck-work sweep: blocked rows and unreviewed PRs, DMs the blocker, `swarph sweep --dry-run` reads only.
|
|
552
553
|
- `swarph timeline` -- deterministic temporal lookup over the timeline: around, since, range (see [Code and history](#code-and-history)).
|
|
553
554
|
- `swarph version` -- per-module versions and install origin (PEP 610).
|
|
554
555
|
- `swarph wake-hook-output` -- hook plumbing: the silent-wake SessionStart callback.
|
|
@@ -111,6 +111,7 @@ _VERB_HANDLERS: dict[str, str] = {
|
|
|
111
111
|
"group": "swarph_cli.commands.group.run_group",
|
|
112
112
|
"rights": "swarph_cli.commands.group.run_rights",
|
|
113
113
|
"roster": "swarph_cli.commands.roster.run_roster",
|
|
114
|
+
"sweep": "swarph_cli.commands.sweep.run_sweep_cmd",
|
|
114
115
|
"mcp-server": "swarph_cli.commands.mcp_server.run_mcp_server",
|
|
115
116
|
"compress": "swarph_cli.commands.compress.run_compress",
|
|
116
117
|
"cell": "swarph_cli.commands.cell.run_cell",
|