swarph-cli 0.64.0__tar.gz → 0.65.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swarph_cli-0.64.0/src/swarph_cli.egg-info → swarph_cli-0.65.0}/PKG-INFO +1 -1
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/pyproject.toml +1 -1
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/enrich.py +74 -4
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/index_check.py +106 -6
- swarph_cli-0.65.0/src/swarph_cli/dreaming/notify.py +162 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/organize.py +21 -8
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/report.py +20 -3
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/verify.py +16 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- swarph_cli-0.65.0/tests/test_332_report_organize_findings.py +39 -0
- swarph_cli-0.65.0/tests/test_764_enrich_collapse.py +117 -0
- swarph_cli-0.65.0/tests/test_937_dreaming_notify.py +232 -0
- swarph_cli-0.65.0/tests/test_938_organize_line_limit.py +175 -0
- swarph_cli-0.64.0/src/swarph_cli/dreaming/notify.py +0 -96
- swarph_cli-0.64.0/tests/test_937_dreaming_notify.py +0 -120
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/LICENSE +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/README.md +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/setup.cfg +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_main.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_security.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_version_command.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_windows_waker_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.65.0
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.65.0"
|
|
8
8
|
description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -56,6 +56,72 @@ def _client():
|
|
|
56
56
|
return OwnedSLMClient()
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def _manifest_sha(manifest: dict, file_key: str) -> str | None:
|
|
60
|
+
"""Resolve sha256; model may omit .md while the manifest keys include it."""
|
|
61
|
+
if not file_key:
|
|
62
|
+
return None
|
|
63
|
+
candidates = [file_key]
|
|
64
|
+
if file_key.endswith(".md"):
|
|
65
|
+
candidates.append(file_key[:-3])
|
|
66
|
+
else:
|
|
67
|
+
candidates.append(file_key + ".md")
|
|
68
|
+
for key in candidates:
|
|
69
|
+
hit = manifest.get(key) or {}
|
|
70
|
+
sha = hit.get("sha256")
|
|
71
|
+
if sha:
|
|
72
|
+
return sha
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _norm_key_part(s: str | None) -> str:
|
|
77
|
+
"""Strip + casefold — drop's #333: 'b.md' and 'b.md ' must be one row."""
|
|
78
|
+
return (s or "").strip().casefold()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _collapse_proposals(proposals: list[dict]) -> list[dict]:
|
|
82
|
+
"""One row per normalised (file, proposed_link); supported_by = distinct sessions (#764/#333).
|
|
83
|
+
|
|
84
|
+
Exact-match dedup would discard agreement evidence — N sessions converging
|
|
85
|
+
on one link is the strongest signal. Collapse and COUNT; keep session ids.
|
|
86
|
+
supported_by is NEVER the proposal count: one session emitting the same
|
|
87
|
+
link twice still counts as 1 (#333).
|
|
88
|
+
"""
|
|
89
|
+
groups: dict[tuple, dict] = {}
|
|
90
|
+
order: list[tuple] = []
|
|
91
|
+
for p in proposals:
|
|
92
|
+
file_raw = (p.get("file") or "").strip()
|
|
93
|
+
link_raw = (p.get("proposed_link") or "").strip()
|
|
94
|
+
if not file_raw or not link_raw:
|
|
95
|
+
continue
|
|
96
|
+
# Self-link: a file proposing itself is not enrichment (#333).
|
|
97
|
+
if _norm_key_part(file_raw) == _norm_key_part(link_raw):
|
|
98
|
+
continue
|
|
99
|
+
key = (_norm_key_part(file_raw), _norm_key_part(link_raw))
|
|
100
|
+
if key not in groups:
|
|
101
|
+
row = {
|
|
102
|
+
"file": file_raw,
|
|
103
|
+
"proposed_link": link_raw,
|
|
104
|
+
"rationale": p.get("rationale", ""),
|
|
105
|
+
"derived": True,
|
|
106
|
+
"supported_by": 0,
|
|
107
|
+
"sessions": [],
|
|
108
|
+
}
|
|
109
|
+
if p.get("source_sha256"):
|
|
110
|
+
row["source_sha256"] = p["source_sha256"]
|
|
111
|
+
groups[key] = row
|
|
112
|
+
order.append(key)
|
|
113
|
+
g = groups[key]
|
|
114
|
+
sid = p.get("session_id")
|
|
115
|
+
if sid and sid not in g["sessions"]:
|
|
116
|
+
g["sessions"].append(sid)
|
|
117
|
+
g["supported_by"] = len(g["sessions"])
|
|
118
|
+
if "source_sha256" not in g and p.get("source_sha256"):
|
|
119
|
+
g["source_sha256"] = p["source_sha256"]
|
|
120
|
+
if not g.get("rationale") and p.get("rationale"):
|
|
121
|
+
g["rationale"] = p["rationale"]
|
|
122
|
+
return [groups[k] for k in order]
|
|
123
|
+
|
|
124
|
+
|
|
59
125
|
def enrich(clone: Path, records: list[dict], client=None) -> list[dict]:
|
|
60
126
|
# `generate`, NOT `generate_json`. Both exist on SLMClient (lines 78 and 118,
|
|
61
127
|
# read 2026-09-03). generate_json coerces its result to a DICT: given a JSON
|
|
@@ -101,18 +167,22 @@ def enrich(clone: Path, records: list[dict], client=None) -> list[dict]:
|
|
|
101
167
|
for i in items:
|
|
102
168
|
if not (isinstance(i, dict) and "file" in i and "link" in i):
|
|
103
169
|
continue
|
|
104
|
-
|
|
170
|
+
row = {
|
|
105
171
|
"file": i["file"], "proposed_link": i["link"],
|
|
106
172
|
"rationale": i.get("why", ""), "derived": True,
|
|
107
173
|
# TWO COORDINATES, per GC2 and R2: source_sha256 says which FILE
|
|
108
174
|
# the proposal is against; session_id says which CONTEXT it came
|
|
109
175
|
# from. The hash alone answers only the first, and the talk's
|
|
110
176
|
# VERSIONING guardrail asks for both.
|
|
111
|
-
"source_sha256": manifest.get(i["file"], {}).get("sha256"),
|
|
112
177
|
"session_id": sid,
|
|
113
178
|
"transcript_range": s["range"],
|
|
114
|
-
}
|
|
115
|
-
|
|
179
|
+
}
|
|
180
|
+
sha = _manifest_sha(manifest, i["file"])
|
|
181
|
+
if sha:
|
|
182
|
+
row["source_sha256"] = sha
|
|
183
|
+
# null source_sha256 is worse than absent (#764) — omit the key.
|
|
184
|
+
proposals.append(row)
|
|
185
|
+
return _collapse_proposals(proposals)
|
|
116
186
|
|
|
117
187
|
|
|
118
188
|
def _sessions_from(records: list[dict]) -> dict:
|
|
@@ -15,7 +15,7 @@ The index's own header warns "over budget = SILENT partial load (18 lost
|
|
|
15
15
|
was missing is the only thing that catches it: something that reads the index and
|
|
16
16
|
asks whether it still points at what exists.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
FIVE CHECKS, each for a failure that produced real loss:
|
|
19
19
|
|
|
20
20
|
1. ORPHANS a memory file no index points at — invisible when gbrain is down
|
|
21
21
|
and dependent on semantic luck when it is up
|
|
@@ -24,6 +24,9 @@ FOUR CHECKS, each for a failure that produced real loss:
|
|
|
24
24
|
4. BUDGET MEMORY.md over 24985 BYTES (wc -c, NOT len(str) — ⭐/— are
|
|
25
25
|
multi-byte, so a character count reads ~600 low and says
|
|
26
26
|
"under" for a file that is over)
|
|
27
|
+
5. LINES MEMORY.md over the harness's 200-line load (#938). Bytes and
|
|
28
|
+
lines are independent limits — long lines hit bytes first;
|
|
29
|
+
short ones hit lines first (droplet counter-specimen).
|
|
27
30
|
|
|
28
31
|
Exit 0 = clean, 1 = findings. Read-only; it never edits the index.
|
|
29
32
|
Run: python3 ~/tools/memory-index-check.py [--quiet]
|
|
@@ -49,12 +52,53 @@ BUDGET = 24985 # bytes, MEMORY.md only — stated in the file's own header comm
|
|
|
49
52
|
# Write|Edit|Bash across several cells, and turning a healthy-but-tight file
|
|
50
53
|
# into a non-zero exit would be a fleet change, not a check.
|
|
51
54
|
TARGET = int(BUDGET * 0.94) # 23485 bytes; the band is TARGET..BUDGET
|
|
55
|
+
# Claude Code truncates MEMORY.md after line 200 at session start (#938).
|
|
56
|
+
LINE_LIMIT = 200
|
|
57
|
+
LINE_TARGET = int(LINE_LIMIT * 0.94) # 188
|
|
52
58
|
LINK = re.compile(r"\(([A-Za-z0-9_./-]+\.md)\)")
|
|
59
|
+
TITLE_LINK = re.compile(r"\[([^\]]+)\]\(([A-Za-z0-9_./-]+\.md)\)")
|
|
53
60
|
# A line is suspect when it opens a link it never closes. An explicit "…" means
|
|
54
61
|
# the author elided on purpose, which is a different thing from a cut.
|
|
55
62
|
TRUNC = re.compile(r"\([A-Za-z0-9_./-]*$")
|
|
56
63
|
|
|
57
64
|
|
|
65
|
+
def cut_point(text: str) -> int:
|
|
66
|
+
"""First 1-based line the harness drops: byte ceiling or line 201, whichever first.
|
|
67
|
+
|
|
68
|
+
cut_line = min(first line where cumulative bytes > BUDGET, LINE_LIMIT + 1).
|
|
69
|
+
Naming that line (and the pointers at/after it) is the finding payload —
|
|
70
|
+
"600 bytes over" does not say which memories went invisible (#938 droplet).
|
|
71
|
+
"""
|
|
72
|
+
cum = 0
|
|
73
|
+
lines = text.split("\n")
|
|
74
|
+
for n, line in enumerate(lines, 1):
|
|
75
|
+
if n > LINE_LIMIT:
|
|
76
|
+
return LINE_LIMIT + 1
|
|
77
|
+
# recreate file bytes: join with \n except we count each line's UTF-8
|
|
78
|
+
# plus the newline that follows it (except possibly the last). Mirror
|
|
79
|
+
# how the file is stored: "\n".join(lines).encode — so newlines between
|
|
80
|
+
# lines, none after the final line if the file had no trailing \n.
|
|
81
|
+
piece = line if n == 1 else "\n" + line
|
|
82
|
+
cum += len(piece.encode("utf-8"))
|
|
83
|
+
if cum > BUDGET:
|
|
84
|
+
return n
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def pointer_titles_at_or_after(text: str, cut_line: int) -> list[str]:
|
|
89
|
+
"""Pointer entries at or after cut_line — the names that go invisible."""
|
|
90
|
+
out: list[str] = []
|
|
91
|
+
for n, line in enumerate(text.split("\n"), 1):
|
|
92
|
+
if n < cut_line:
|
|
93
|
+
continue
|
|
94
|
+
m = TITLE_LINK.search(line)
|
|
95
|
+
if m:
|
|
96
|
+
out.append("%s -> %s" % (m.group(1), m.group(2)))
|
|
97
|
+
elif LINK.search(line):
|
|
98
|
+
out.append(LINK.search(line).group(1))
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
|
|
58
102
|
def links_in(name: str) -> set[str]:
|
|
59
103
|
p = MEM / name
|
|
60
104
|
return set(LINK.findall(p.read_text(encoding="utf-8"))) if p.exists() else set()
|
|
@@ -174,19 +218,72 @@ def main() -> int:
|
|
|
174
218
|
findings.append(f"DANGLING: {len(dangling)} pointer(s) to a file that does not exist")
|
|
175
219
|
findings += [f" {d}" for d in dangling[:20]]
|
|
176
220
|
|
|
221
|
+
# Duplicate-target pointers under different titles: allowed, but listed (#938).
|
|
177
222
|
mm = MEM / "MEMORY.md"
|
|
223
|
+
if mm.exists():
|
|
224
|
+
by_target: dict[str, set[str]] = {}
|
|
225
|
+
for line in mm.read_text(encoding="utf-8").split("\n"):
|
|
226
|
+
for title, target in TITLE_LINK.findall(line):
|
|
227
|
+
by_target.setdefault(target, set()).add(title)
|
|
228
|
+
dupes = sorted((t, sorted(titles)) for t, titles in by_target.items()
|
|
229
|
+
if len(titles) > 1)
|
|
230
|
+
if dupes:
|
|
231
|
+
findings.append(
|
|
232
|
+
"DUP_TARGET: %d file(s) pointed at under different titles (allowed, listed)"
|
|
233
|
+
% len(dupes))
|
|
234
|
+
for target, titles in dupes[:20]:
|
|
235
|
+
findings.append(" %s <- %s" % (target, " | ".join(titles)))
|
|
236
|
+
|
|
178
237
|
warn = None
|
|
179
238
|
if mm.exists():
|
|
239
|
+
text = mm.read_text(encoding="utf-8")
|
|
180
240
|
size = len(mm.read_bytes())
|
|
181
|
-
|
|
241
|
+
nlines = len(text.split("\n"))
|
|
242
|
+
# Bytes keep the WARN band (TARGET..BUDGET, exit 0). Lines do not:
|
|
243
|
+
# over LINE_TARGET is a FINDING — lab specimen was 197/200 still "clean"
|
|
244
|
+
# on bytes while 3 more pointers would be cut (#938 accept).
|
|
245
|
+
if BUDGET >= size > TARGET and nlines <= LINE_TARGET:
|
|
182
246
|
warn = ("WARN: MEMORY.md is %d BYTES — under the %d ceiling but inside the last %d "
|
|
183
247
|
"bytes of it. Not broken, not fine: %d bytes of headroom, so the next pointer "
|
|
184
248
|
"line breaches. Trim to <= %d." % (size, BUDGET, BUDGET - TARGET,
|
|
185
249
|
BUDGET - size, TARGET))
|
|
186
|
-
|
|
250
|
+
|
|
251
|
+
# Over byte ceiling, line ceiling, or line target: name the cut line.
|
|
252
|
+
over_bytes = size > BUDGET
|
|
253
|
+
over_lines = nlines > LINE_LIMIT
|
|
254
|
+
over_line_target = nlines > LINE_TARGET
|
|
255
|
+
if over_bytes or over_lines or over_line_target:
|
|
256
|
+
cut = cut_point(text)
|
|
257
|
+
if not cut and over_line_target:
|
|
258
|
+
cut = LINE_LIMIT + 1 if nlines > LINE_LIMIT else (LINE_TARGET + 1)
|
|
259
|
+
# Which limit binds first: byte cut before line 201, else line.
|
|
260
|
+
if over_bytes and (not over_lines or cut <= LINE_LIMIT):
|
|
261
|
+
binder = "BYTES"
|
|
262
|
+
elif over_lines or over_line_target:
|
|
263
|
+
binder = "LINES"
|
|
264
|
+
else:
|
|
265
|
+
binder = "BYTES"
|
|
187
266
|
findings.append(
|
|
188
|
-
|
|
189
|
-
|
|
267
|
+
"CUT: MEMORY.md %d bytes / %d lines — %s binds first; "
|
|
268
|
+
"cut_line=%d (harness drops from here; byte ceiling %d, line ceiling %d, "
|
|
269
|
+
"line target %d)"
|
|
270
|
+
% (size, nlines, binder, cut or LINE_LIMIT + 1, BUDGET, LINE_LIMIT,
|
|
271
|
+
LINE_TARGET))
|
|
272
|
+
dropped = pointer_titles_at_or_after(text, cut or (LINE_LIMIT + 1))
|
|
273
|
+
if dropped:
|
|
274
|
+
findings.append(" first lost pointers:")
|
|
275
|
+
findings += [" %s" % d for d in dropped[:20]]
|
|
276
|
+
if len(dropped) > 20:
|
|
277
|
+
findings.append(" … and %d more" % (len(dropped) - 20))
|
|
278
|
+
if over_bytes:
|
|
279
|
+
findings.append(
|
|
280
|
+
f"BUDGET: MEMORY.md is {size} BYTES, over {BUDGET} by {size - BUDGET} "
|
|
281
|
+
f"— over budget loads SILENTLY PARTIAL (its own header records 18 lost 2026-08-26)")
|
|
282
|
+
if over_lines or over_line_target:
|
|
283
|
+
findings.append(
|
|
284
|
+
"LINES: MEMORY.md is %d lines (harness truncates after %d; "
|
|
285
|
+
"target <= %d) — over target by %d"
|
|
286
|
+
% (nlines, LINE_LIMIT, LINE_TARGET, max(0, nlines - LINE_TARGET)))
|
|
190
287
|
|
|
191
288
|
if warn:
|
|
192
289
|
print(warn)
|
|
@@ -198,8 +295,11 @@ def main() -> int:
|
|
|
198
295
|
return 1
|
|
199
296
|
if not quiet:
|
|
200
297
|
mmsize = len((MEM / "MEMORY.md").read_bytes()) if mm.exists() else 0
|
|
298
|
+
mmlines = (len((MEM / "MEMORY.md").read_text(encoding="utf-8").split("\n"))
|
|
299
|
+
if mm.exists() else 0)
|
|
201
300
|
print("memory-index-check: clean — %d files, all indexed; MEMORY.md %d/%d bytes "
|
|
202
|
-
"(target %d)
|
|
301
|
+
"(target %d), %d/%d lines (target %d)"
|
|
302
|
+
% (len(files), mmsize, BUDGET, TARGET, mmlines, LINE_LIMIT, LINE_TARGET))
|
|
203
303
|
return 0
|
|
204
304
|
|
|
205
305
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Notify the owning cell when the dreaming finding set changes (#937).
|
|
2
|
+
|
|
3
|
+
The finding set is disagree + surface_disagreement, keyed file:line:kind, plus
|
|
4
|
+
each organize finding keyed by its category token and, where the line names
|
|
5
|
+
one, the file. A clean status line is not a finding. Organize keys contain
|
|
6
|
+
no digits: counts stay in the DM body, not in the key. State lives at
|
|
7
|
+
<out-parent>/.last-findings.json and is written only after a successful send.
|
|
8
|
+
An unchanged set sends nothing. A missing state file is a first run: send
|
|
9
|
+
the full set once.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# "memory-index-check: clean — ..." is a status line. "not clean" is not.
|
|
18
|
+
_CLEAN = re.compile(r"(?:^|:)\s*clean\b", re.IGNORECASE)
|
|
19
|
+
# Longest first so "MISSING INDEX" wins over a shorter prefix.
|
|
20
|
+
_CATEGORY = (
|
|
21
|
+
"MISSING INDEX",
|
|
22
|
+
"DUP_TARGET",
|
|
23
|
+
"LINES",
|
|
24
|
+
"CUT",
|
|
25
|
+
"DANGLING",
|
|
26
|
+
"ORPHANS",
|
|
27
|
+
"TRUNCATED",
|
|
28
|
+
"BUDGET",
|
|
29
|
+
"WARN",
|
|
30
|
+
)
|
|
31
|
+
_FILE = re.compile(r"[\w.-]+\.md\b")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NotifySendError(Exception):
|
|
35
|
+
"""The mesh send failed. Distinct from a findings exit."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _category(text: str) -> str | None:
|
|
39
|
+
for name in _CATEGORY:
|
|
40
|
+
if text.startswith(name + ":") or text.startswith(name + " "):
|
|
41
|
+
return name
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def organize_keys(lines) -> list[str]:
|
|
46
|
+
"""One key per category header. Member lines are not keys.
|
|
47
|
+
|
|
48
|
+
A header that names a file keys ``organize:CATEGORY:file``. Indented
|
|
49
|
+
children (CUT's lost pointers, ORPHANS' name window, a DUP_TARGET block)
|
|
50
|
+
stay out of the key, so they cannot attach to the previous category and
|
|
51
|
+
a count or a member list cannot change the set. Digits that are part of
|
|
52
|
+
a filename stay. Counts do not.
|
|
53
|
+
"""
|
|
54
|
+
keys: set[str] = set()
|
|
55
|
+
for raw in lines or []:
|
|
56
|
+
original = str(raw)
|
|
57
|
+
text = original.strip()
|
|
58
|
+
if not text or _CLEAN.search(text) or text.startswith("memory-index-check:"):
|
|
59
|
+
continue
|
|
60
|
+
# A child line is not a finding of its own, and it must not inherit
|
|
61
|
+
# the category above it.
|
|
62
|
+
if original.startswith((" ", "\t")) or _FILE.fullmatch(text):
|
|
63
|
+
continue
|
|
64
|
+
cat = _category(text)
|
|
65
|
+
if not cat:
|
|
66
|
+
continue
|
|
67
|
+
files = _FILE.findall(text)
|
|
68
|
+
if files:
|
|
69
|
+
keys.add(f"organize:{cat}:{files[0]}")
|
|
70
|
+
else:
|
|
71
|
+
keys.add(f"organize:{cat}")
|
|
72
|
+
return sorted(keys)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def finding_keys(verdicts, organized) -> list[str]:
|
|
76
|
+
keys: set[str] = set()
|
|
77
|
+
for v in verdicts or []:
|
|
78
|
+
if v.get("verdict") in ("disagree", "surface_disagreement"):
|
|
79
|
+
keys.add(f"{v.get('file')}:{v.get('line')}:{v.get('kind')}")
|
|
80
|
+
keys.update(organize_keys((organized or {}).get("findings") or []))
|
|
81
|
+
return sorted(keys)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _load_previous(path: Path) -> list[str] | None:
|
|
85
|
+
if not path.exists():
|
|
86
|
+
return None
|
|
87
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
88
|
+
return list(data.get("keys") or [])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _details(organized) -> list[str]:
|
|
92
|
+
"""Original organize lines, counts included. Clean status is not a finding."""
|
|
93
|
+
out = []
|
|
94
|
+
for raw in (organized or {}).get("findings") or []:
|
|
95
|
+
text = str(raw).strip()
|
|
96
|
+
if text and not _CLEAN.search(text) and not text.startswith("memory-index-check:"):
|
|
97
|
+
out.append(text)
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _body(keys: list[str], previous: list[str] | None, details: list[str]) -> str:
|
|
102
|
+
if previous is None:
|
|
103
|
+
lines = ["dreaming findings first run", f"counts: {len(keys)}"]
|
|
104
|
+
lines.extend(keys)
|
|
105
|
+
else:
|
|
106
|
+
prev = set(previous)
|
|
107
|
+
cur = set(keys)
|
|
108
|
+
new = sorted(cur - prev)
|
|
109
|
+
cleared = sorted(prev - cur)
|
|
110
|
+
lines = [
|
|
111
|
+
"dreaming findings changed",
|
|
112
|
+
"new: " + (", ".join(new) if new else "(none)"),
|
|
113
|
+
"cleared: " + (", ".join(cleared) if cleared else "(none)"),
|
|
114
|
+
f"counts: {len(keys)} was {len(previous)}",
|
|
115
|
+
]
|
|
116
|
+
if details:
|
|
117
|
+
lines.append("details:")
|
|
118
|
+
lines.extend(details)
|
|
119
|
+
return "\n".join(lines)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def apply(out: Path, verdicts, organized, cell: str, sender) -> bool:
|
|
123
|
+
"""Send one DM when the set changes. Return True if a DM was sent.
|
|
124
|
+
|
|
125
|
+
``sender(cell, body)`` raises on failure. State is not written then.
|
|
126
|
+
"""
|
|
127
|
+
out = Path(out)
|
|
128
|
+
keys = finding_keys(verdicts, organized)
|
|
129
|
+
state = out.parent / ".last-findings.json"
|
|
130
|
+
previous = _load_previous(state)
|
|
131
|
+
if previous is not None and sorted(previous) == keys:
|
|
132
|
+
return False
|
|
133
|
+
body = _body(keys, previous, _details(organized))
|
|
134
|
+
try:
|
|
135
|
+
sender(cell, body)
|
|
136
|
+
except NotifySendError:
|
|
137
|
+
raise
|
|
138
|
+
except Exception as exc:
|
|
139
|
+
raise NotifySendError(str(exc)) from exc
|
|
140
|
+
state.write_text(json.dumps({"keys": keys}, indent=2) + "\n", encoding="utf-8")
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def mesh_sender(cell: str, content: str) -> None:
|
|
145
|
+
"""Same path as ``swarph mesh send``: one status DM, no second transport."""
|
|
146
|
+
import argparse
|
|
147
|
+
|
|
148
|
+
from swarph_cli.commands.mesh import _run_send
|
|
149
|
+
from swarph_cli.gateway_default import env_gateway
|
|
150
|
+
|
|
151
|
+
ns = argparse.Namespace(
|
|
152
|
+
to=cell,
|
|
153
|
+
kind="status",
|
|
154
|
+
content=content,
|
|
155
|
+
content_file=None,
|
|
156
|
+
self_name=None,
|
|
157
|
+
token_file=None,
|
|
158
|
+
gateway=env_gateway(),
|
|
159
|
+
)
|
|
160
|
+
rc = _run_send(ns)
|
|
161
|
+
if rc != 0:
|
|
162
|
+
raise NotifySendError(f"mesh send exited {rc}")
|
|
@@ -9,6 +9,11 @@ BUDGET = 24985 # BYTES. GC6 -- never len(str).
|
|
|
9
9
|
# organised anything; it has queued the same breach for tomorrow.
|
|
10
10
|
TARGET = int(BUDGET * 0.94) # ~1,500 bytes of headroom
|
|
11
11
|
|
|
12
|
+
# Claude Code loads MEMORY.md at session start and TRUNCATES after line 200
|
|
13
|
+
# (#938). Same headroom rule as bytes: land under the target, not the ceiling.
|
|
14
|
+
LINE_LIMIT = 200 # harness truncation line
|
|
15
|
+
LINE_TARGET = int(LINE_LIMIT * 0.94) # 188
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
def organize(clone: Path) -> dict:
|
|
14
19
|
import subprocess, sys
|
|
@@ -21,17 +26,25 @@ def organize(clone: Path) -> dict:
|
|
|
21
26
|
idx = clone / "MEMORY.md"
|
|
22
27
|
if not idx.exists():
|
|
23
28
|
return {"findings": findings, "index_bytes_before": 0,
|
|
24
|
-
"index_bytes_after": 0, "trimmed": []
|
|
25
|
-
|
|
29
|
+
"index_bytes_after": 0, "trimmed": [],
|
|
30
|
+
"index_lines_before": 0, "index_lines_after": 0}
|
|
31
|
+
raw = idx.read_bytes()
|
|
32
|
+
before = len(raw)
|
|
33
|
+
lines = idx.read_text(encoding="utf-8").split("\n")
|
|
34
|
+
lines_before = len(lines)
|
|
26
35
|
trimmed = []
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
# Two independent limits (#938 / droplet plan-review): bytes OR lines may
|
|
37
|
+
# bind first depending on line length. Trim when either ceiling is breached;
|
|
38
|
+
# land under BOTH targets.
|
|
39
|
+
if before > BUDGET or lines_before > LINE_LIMIT:
|
|
40
|
+
while lines and (
|
|
41
|
+
len("\n".join(lines).encode("utf-8")) > TARGET
|
|
42
|
+
or len(lines) > LINE_TARGET):
|
|
33
43
|
trimmed.append(lines.pop())
|
|
34
44
|
idx.write_text("\n".join(lines), encoding="utf-8")
|
|
45
|
+
after_text = idx.read_text(encoding="utf-8") if idx.exists() else ""
|
|
35
46
|
return {"findings": findings, "index_bytes_before": before,
|
|
36
47
|
"index_bytes_after": len(idx.read_bytes()),
|
|
48
|
+
"index_lines_before": lines_before,
|
|
49
|
+
"index_lines_after": len(after_text.split("\n")) if after_text or idx.exists() else 0,
|
|
37
50
|
"trimmed": [t for t in trimmed if t.strip()]}
|
|
@@ -48,10 +48,27 @@ def render(verdicts, organized, proposals, corpus, clone, enrich_note: str | Non
|
|
|
48
48
|
"| verdict | n |", "|---|---|"]
|
|
49
49
|
for k in ("disagree", "surface_disagreement", "mention", "unprobeable", "agree"):
|
|
50
50
|
L.append("| %s | %d |" % (k, counts.get(k, 0)))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
# Bytes AND lines (#938 / #332): the harness truncates on both; a byte-only
|
|
52
|
+
# line hid the line-budget findings that organize already collected.
|
|
53
|
+
lines_before = organized.get("index_lines_before")
|
|
54
|
+
lines_after = organized.get("index_lines_after")
|
|
55
|
+
if lines_before is None or lines_after is None:
|
|
56
|
+
L += ["", "MEMORY.md: %d bytes before, %d bytes after (budget 24985)" % (
|
|
57
|
+
organized["index_bytes_before"], organized["index_bytes_after"])]
|
|
58
|
+
else:
|
|
59
|
+
L += ["", "MEMORY.md: %d bytes / %d lines before, %d bytes / %d lines after "
|
|
60
|
+
"(budget 24985 bytes, harness line limit 200)" % (
|
|
61
|
+
organized["index_bytes_before"], lines_before,
|
|
62
|
+
organized["index_bytes_after"], lines_after)]
|
|
63
|
+
if organized.get("trimmed"):
|
|
54
64
|
L.append("trimmed %d index line(s) to stay under budget" % len(organized["trimmed"]))
|
|
65
|
+
org_findings = [f for f in (organized.get("findings") or []) if f.strip()]
|
|
66
|
+
if org_findings:
|
|
67
|
+
# Verbatim — LINES, CUT, DANGLING, ORPHANS, DUP_TARGET, BUDGET, WARN…
|
|
68
|
+
# Drop's surface gap (#332): organize collected these and the report
|
|
69
|
+
# never printed them, so #REACH had nothing to deliver.
|
|
70
|
+
L += ["", "organize findings (%d):" % len(org_findings)]
|
|
71
|
+
L.extend(org_findings)
|
|
55
72
|
L += ["", "enrichment proposals (all marked derived, none applied): %d" % len(proposals)]
|
|
56
73
|
if enrich_note:
|
|
57
74
|
L.append(enrich_note)
|
|
@@ -202,12 +202,28 @@ def _is_dated_record(context: str, asserted=None) -> bool:
|
|
|
202
202
|
for ds, de in hits)
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
def _is_superseded(context: str) -> bool:
|
|
206
|
+
"""Same-line SUPERSEDED note: claim is a MENTION, not a live assertion (#938 addendum / #939).
|
|
207
|
+
|
|
208
|
+
grep -i supersed over dreaming/ was 0; corrected corpus lines still carried the
|
|
209
|
+
old address and re-flagged every night. An inline note must silence the probe.
|
|
210
|
+
"""
|
|
211
|
+
return bool(re.search(r"SUPERSEDED", context or "", re.I))
|
|
212
|
+
|
|
213
|
+
|
|
205
214
|
def verify(corpus: Path, manifest: dict) -> list[dict]:
|
|
206
215
|
verdicts = []
|
|
207
216
|
for c in extract_candidates(corpus):
|
|
208
217
|
results = probe(c)
|
|
209
218
|
base = {k: c[k] for k in ("file", "line", "kind", "ref", "asserted", "context")}
|
|
210
219
|
base["source_sha256"] = manifest["files"].get(c["file"], {}).get("sha256")
|
|
220
|
+
# #938/#939: SUPERSEDED on the claim's line -> mention, never disagreement.
|
|
221
|
+
if _is_superseded(c.get("context", "")):
|
|
222
|
+
r = results[0] if results else {}
|
|
223
|
+
verdicts.append({**base, "observed": r.get("observed"),
|
|
224
|
+
"surface": r.get("surface"),
|
|
225
|
+
"verdict": "mention", "reason": "superseded"})
|
|
226
|
+
continue
|
|
211
227
|
# GC4d: board_stage emits surface_disagreement ONLY, never disagree.
|
|
212
228
|
# A card can sit at spec while the work is done; the corpus may be
|
|
213
229
|
# the accurate side. Locked before any compare, including empty probe.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.65.0
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -181,6 +181,7 @@ tests/test_307_task4_ask_reply.py
|
|
|
181
181
|
tests/test_312_codegraph_fuzzy_verb.py
|
|
182
182
|
tests/test_319_refusal_exit_codes.py
|
|
183
183
|
tests/test_323_mistral_backend.py
|
|
184
|
+
tests/test_332_report_organize_findings.py
|
|
184
185
|
tests/test_356_query_filters.py
|
|
185
186
|
tests/test_397_gh_identity_router.py
|
|
186
187
|
tests/test_397_router_preflight.py
|
|
@@ -211,6 +212,7 @@ tests/test_725_stdio_reconfigure.py
|
|
|
211
212
|
tests/test_734_dreaming_enrich_degrade.py
|
|
212
213
|
tests/test_740_edit_project.py
|
|
213
214
|
tests/test_762_cards_edit_priority.py
|
|
215
|
+
tests/test_764_enrich_collapse.py
|
|
214
216
|
tests/test_801_confirm_flow_cli.py
|
|
215
217
|
tests/test_802_stage_history.py
|
|
216
218
|
tests/test_825_codegraph_hook_noise.py
|
|
@@ -223,6 +225,7 @@ tests/test_872_identity_refuses_not_warns.py
|
|
|
223
225
|
tests/test_874_source_text.py
|
|
224
226
|
tests/test_888_opencode_data_dir_can_fail.py
|
|
225
227
|
tests/test_937_dreaming_notify.py
|
|
228
|
+
tests/test_938_organize_line_limit.py
|
|
226
229
|
tests/test_add_security_gate.py
|
|
227
230
|
tests/test_artifact_add.py
|
|
228
231
|
tests/test_artifact_hash.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""#332: dreaming-report.md must surface organize findings + line counts."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from swarph_cli.dreaming.report import render
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_332_report_renders_lines_finding_and_line_counts(tmp_path):
|
|
10
|
+
"""CAN-FAIL: LINES finding in organized['findings'] appears in report text."""
|
|
11
|
+
corpus = tmp_path / "mem"
|
|
12
|
+
clone = tmp_path / "clone"
|
|
13
|
+
corpus.mkdir()
|
|
14
|
+
clone.mkdir()
|
|
15
|
+
lines_finding = (
|
|
16
|
+
"LINES: MEMORY.md is 197 lines (harness truncates after 200; "
|
|
17
|
+
"target <= 188) — over target by 9"
|
|
18
|
+
)
|
|
19
|
+
cut_finding = (
|
|
20
|
+
"CUT: MEMORY.md 24068 bytes / 197 lines — LINES binds first; "
|
|
21
|
+
"cut_line=189 (harness drops from here; byte ceiling 24985, line ceiling 200, "
|
|
22
|
+
"line target 188)"
|
|
23
|
+
)
|
|
24
|
+
dangling = "DANGLING: 1 pointer(s) to a file that does not exist"
|
|
25
|
+
organized = {
|
|
26
|
+
"index_bytes_before": 24068,
|
|
27
|
+
"index_bytes_after": 24068,
|
|
28
|
+
"index_lines_before": 197,
|
|
29
|
+
"index_lines_after": 197,
|
|
30
|
+
"trimmed": [],
|
|
31
|
+
"findings": [cut_finding, " first lost pointers:", dangling, lines_finding],
|
|
32
|
+
}
|
|
33
|
+
text = render([], organized, [], corpus, clone)
|
|
34
|
+
assert "197 lines" in text
|
|
35
|
+
assert "24068 bytes" in text
|
|
36
|
+
assert lines_finding in text, "LINES finding must appear verbatim"
|
|
37
|
+
assert cut_finding in text
|
|
38
|
+
assert dangling in text
|
|
39
|
+
assert "organize findings" in text
|