swarph-cli 0.63.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.63.0/src/swarph_cli.egg-info → swarph_cli-0.65.0}/PKG-INFO +5 -2
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/README.md +4 -1
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/pyproject.toml +1 -1
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph_hook.py +24 -1
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/dreaming.py +8 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hooks.py +59 -34
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/enrich.py +74 -4
- {swarph_cli-0.63.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.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/organize.py +21 -8
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/report.py +20 -3
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/run.py +14 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/verify.py +16 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0/src/swarph_cli.egg-info}/PKG-INFO +5 -2
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/SOURCES.txt +6 -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_825_remaining_cap_shred_bind.py +165 -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.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_hook.py +1 -1
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/LICENSE +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/setup.cfg +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_main.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_security.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_version_command.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.63.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
|
|
@@ -292,9 +292,12 @@ Between-sessions memory pass: **VERIFY · ORGANIZE · ENRICH** against a **CLONE
|
|
|
292
292
|
```
|
|
293
293
|
$ swarph dreaming run --corpus ~/.claude/projects/.../memory --out /tmp/dreaming-out
|
|
294
294
|
$ swarph dreaming run --corpus <dir> --out <dir> --verify-only
|
|
295
|
-
$
|
|
295
|
+
$ swarph dreaming run --corpus <dir> --out <dir> --notify <cell>
|
|
296
|
+
$ echo $? # 0 clean-and-adjudicated | 1 findings | 2 refused | 3 ran-but-adjudicated-nothing | 4 notify send failed
|
|
296
297
|
```
|
|
297
298
|
|
|
299
|
+
`--notify <cell>` (#937) sends one mesh DM to `<cell>` **only when the finding set changes** (new and cleared findings named, with counts); an unchanged set sends nothing. State lives in `.last-findings.json` beside the `--out` dir and advances only after a successful send, so a failed send (exit 4) retries on the next run.
|
|
300
|
+
|
|
298
301
|
**Scope is single-cell:** your corpus and your local transcripts. This is not the cross-agent multi-transcript dreaming from the Anthropic talk — that needs a shared transcript surface this release does not claim.
|
|
299
302
|
|
|
300
303
|
#### The AI Router (why a tool, not a hook)
|
|
@@ -244,9 +244,12 @@ Between-sessions memory pass: **VERIFY · ORGANIZE · ENRICH** against a **CLONE
|
|
|
244
244
|
```
|
|
245
245
|
$ swarph dreaming run --corpus ~/.claude/projects/.../memory --out /tmp/dreaming-out
|
|
246
246
|
$ swarph dreaming run --corpus <dir> --out <dir> --verify-only
|
|
247
|
-
$
|
|
247
|
+
$ swarph dreaming run --corpus <dir> --out <dir> --notify <cell>
|
|
248
|
+
$ echo $? # 0 clean-and-adjudicated | 1 findings | 2 refused | 3 ran-but-adjudicated-nothing | 4 notify send failed
|
|
248
249
|
```
|
|
249
250
|
|
|
251
|
+
`--notify <cell>` (#937) sends one mesh DM to `<cell>` **only when the finding set changes** (new and cleared findings named, with counts); an unchanged set sends nothing. State lives in `.last-findings.json` beside the `--out` dir and advances only after a successful send, so a failed send (exit 4) retries on the next run.
|
|
252
|
+
|
|
250
253
|
**Scope is single-cell:** your corpus and your local transcripts. This is not the cross-agent multi-transcript dreaming from the Anthropic talk — that needs a shared transcript surface this release does not claim.
|
|
251
254
|
|
|
252
255
|
#### The AI Router (why a tool, not a hook)
|
|
@@ -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" }
|
|
@@ -37,6 +37,10 @@ from swarph_cli.gateway_default import env_gateway
|
|
|
37
37
|
|
|
38
38
|
TIMEOUT_S = 6
|
|
39
39
|
MAX_ROWS = 6
|
|
40
|
+
# Probe one past the display cap so audit can tell "exactly 6" from "hit the
|
|
41
|
+
# clamp" (#825 / lab 48145: 90.5% of rows sat on match_count==6 because the
|
|
42
|
+
# query limit WAS the count).
|
|
43
|
+
_PROBE_LIMIT = MAX_ROWS + 1
|
|
40
44
|
|
|
41
45
|
# Identifier token — used for prompt terms and for the Bash shred skip (no
|
|
42
46
|
# useful symbol-shaped token left after cleaning).
|
|
@@ -89,6 +93,12 @@ def extract_raw_and_cleaned(cmd: str) -> Optional[tuple[str, str]]:
|
|
|
89
93
|
if tok.startswith("-"):
|
|
90
94
|
continue
|
|
91
95
|
raw = tok.strip("'\"")
|
|
96
|
+
# Prefer the longest identifier STILL VISIBLE in the raw pattern
|
|
97
|
+
# before the shredding re.sub (#825). `foo_bar.*[0-9]+` must yield
|
|
98
|
+
# `foo_bar`, not skip-as-shredded or query regex debris.
|
|
99
|
+
idents = [t for t in _IDENT_TOKEN.findall(raw) if len(t) >= 3]
|
|
100
|
+
if idents:
|
|
101
|
+
return (raw, max(idents, key=len))
|
|
92
102
|
cleaned = re.sub(r"[^\w\s.]", " ", raw).strip()
|
|
93
103
|
if not cleaned:
|
|
94
104
|
return None
|
|
@@ -308,7 +318,8 @@ def resolve_pending_outcome(self_name: str, subsequent: str,
|
|
|
308
318
|
|
|
309
319
|
# ── gateway query ─────────────────────────────────────────────────────────
|
|
310
320
|
|
|
311
|
-
def query_gateway(term: str, gateway: str, token: str,
|
|
321
|
+
def query_gateway(term: str, gateway: str, token: str,
|
|
322
|
+
limit: int = _PROBE_LIMIT) -> dict:
|
|
312
323
|
if not (gateway or "").strip():
|
|
313
324
|
return {"error": "MESH_GATEWAY_URL is not set and swarph ships no default "
|
|
314
325
|
"gateway host (#578) — the graph was never asked"}
|
|
@@ -411,6 +422,9 @@ def _query_and_emit(self_name: str, gateway: str, term: str, *,
|
|
|
411
422
|
return
|
|
412
423
|
env = query_gateway(term, gateway, token)
|
|
413
424
|
rows = env.get("results") or [] if "error" not in env else []
|
|
425
|
+
capped = len(rows) > MAX_ROWS
|
|
426
|
+
# Audit sees the probe length (MAX_ROWS+1 ⇒ at-least / capped). Display
|
|
427
|
+
# still truncates via render()'s rows[:MAX_ROWS].
|
|
414
428
|
name_hit = any_symbol_name_contains_term(term, rows) if rows else False
|
|
415
429
|
fid = firing_id or str(uuid.uuid4())
|
|
416
430
|
write_audit(self_name, {
|
|
@@ -424,13 +438,22 @@ def _query_and_emit(self_name: str, gateway: str, term: str, *,
|
|
|
424
438
|
"raw_term": raw_term or term,
|
|
425
439
|
"skipped": False,
|
|
426
440
|
"match_count": len(rows),
|
|
441
|
+
"match_count_capped": capped,
|
|
427
442
|
"any_name_contains_term": name_hit,
|
|
428
443
|
"error": env.get("error"),
|
|
429
444
|
})
|
|
430
445
|
if trigger == "prompt":
|
|
431
446
|
register_pending_firing(self_name, fid, session_id)
|
|
447
|
+
# Render only the display window so the prompt is not flooded.
|
|
448
|
+
if capped and "error" not in env:
|
|
449
|
+
env = {**env, "results": rows[:MAX_ROWS], "match_count_capped": True}
|
|
432
450
|
text = render(term, env)
|
|
433
451
|
if text:
|
|
452
|
+
if capped:
|
|
453
|
+
text = text.rstrip() + (
|
|
454
|
+
f"\n (display capped at {MAX_ROWS}; audit match_count="
|
|
455
|
+
f"{len(rows)} match_count_capped=true)"
|
|
456
|
+
)
|
|
434
457
|
_emit(text, event_name)
|
|
435
458
|
|
|
436
459
|
|
|
@@ -67,6 +67,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
67
67
|
help="require enrich; refuse (rc=2) if SLM client unavailable (#734)",
|
|
68
68
|
)
|
|
69
69
|
run.add_argument("--verify-only", action="store_true", help="verify only; skip organize/enrich")
|
|
70
|
+
run.add_argument(
|
|
71
|
+
"--notify",
|
|
72
|
+
default=None,
|
|
73
|
+
metavar="CELL",
|
|
74
|
+
help="DM CELL when the finding set changes; no DM when it is unchanged (#937)",
|
|
75
|
+
)
|
|
70
76
|
return p
|
|
71
77
|
|
|
72
78
|
|
|
@@ -95,4 +101,6 @@ def run_dreaming(argv: list[str] | None = None) -> int:
|
|
|
95
101
|
forwarded.append("--enrich")
|
|
96
102
|
if args.verify_only:
|
|
97
103
|
forwarded.append("--verify-only")
|
|
104
|
+
if getattr(args, "notify", None):
|
|
105
|
+
forwarded.extend(["--notify", args.notify])
|
|
98
106
|
return dreaming_run.main(forwarded)
|
|
@@ -1005,36 +1005,47 @@ def uninstall_hook(
|
|
|
1005
1005
|
return 0
|
|
1006
1006
|
|
|
1007
1007
|
|
|
1008
|
-
def
|
|
1009
|
-
|
|
1008
|
+
def _event_binding_present(
|
|
1009
|
+
settings: dict, command: str, binding: HookBinding,
|
|
1010
|
+
) -> bool:
|
|
1011
|
+
"""True when one specific event/matcher carries our command."""
|
|
1010
1012
|
hooks = settings.get("hooks")
|
|
1011
1013
|
if not isinstance(hooks, dict):
|
|
1012
1014
|
return False
|
|
1015
|
+
event_list = hooks.get(binding.event)
|
|
1016
|
+
if not isinstance(event_list, list):
|
|
1017
|
+
return False
|
|
1018
|
+
_known = _command_variants_for(command)
|
|
1019
|
+
for entry in event_list:
|
|
1020
|
+
if entry.get("matcher", "") != binding.matcher:
|
|
1021
|
+
continue
|
|
1022
|
+
return any(a.get("command") in _known for a in entry.get("hooks", []))
|
|
1023
|
+
return False
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def binding_coverage(
|
|
1027
|
+
settings: dict, command: str, bundle: HookBundle,
|
|
1028
|
+
) -> tuple[list[str], list[str]]:
|
|
1029
|
+
"""Return (present_events, missing_events) for ``bundle``'s bindings (#825).
|
|
1030
|
+
|
|
1031
|
+
A PostToolUse-only install of codegraph-on-grep used to report as plain
|
|
1032
|
+
``available`` (not installed) while still firing 400 times — the steering
|
|
1033
|
+
half unbound and invisible. Coverage makes PARTIAL legible.
|
|
1034
|
+
"""
|
|
1035
|
+
present, missing = [], []
|
|
1013
1036
|
for b in bundle.bindings:
|
|
1014
|
-
|
|
1015
|
-
if
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
# were THREE. A rule applied at N-1 sites is the defect this
|
|
1027
|
-
# class keeps producing, and it produced it again inside the
|
|
1028
|
-
# fix for it.
|
|
1029
|
-
_known = _command_variants_for(command)
|
|
1030
|
-
if any(
|
|
1031
|
-
a.get("command") in _known for a in entry.get("hooks", [])
|
|
1032
|
-
):
|
|
1033
|
-
found = True
|
|
1034
|
-
break
|
|
1035
|
-
if not found:
|
|
1036
|
-
return False
|
|
1037
|
-
return True
|
|
1037
|
+
label = b.event if not b.matcher else f"{b.event}:{b.matcher}"
|
|
1038
|
+
if _event_binding_present(settings, command, b):
|
|
1039
|
+
present.append(label)
|
|
1040
|
+
else:
|
|
1041
|
+
missing.append(label)
|
|
1042
|
+
return present, missing
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def _is_installed(settings: dict, command: str, bundle: HookBundle) -> bool:
|
|
1046
|
+
"""True when ALL of ``bundle``'s bindings' commands are present in settings."""
|
|
1047
|
+
present, missing = binding_coverage(settings, command, bundle)
|
|
1048
|
+
return bool(present) and not missing
|
|
1038
1049
|
|
|
1039
1050
|
|
|
1040
1051
|
def list_hooks(
|
|
@@ -1045,8 +1056,10 @@ def list_hooks(
|
|
|
1045
1056
|
) -> int:
|
|
1046
1057
|
"""List builtin hooks with install status. One greppable line per builtin::
|
|
1047
1058
|
|
|
1048
|
-
name [installed|available] trust=builtin — description
|
|
1059
|
+
name [installed|partial|available] trust=builtin — description
|
|
1049
1060
|
|
|
1061
|
+
``partial`` (#825): at least one binding present but not the full set —
|
|
1062
|
+
the lab-ovh codegraph case (PostToolUse only, UserPromptSubmit/Stop absent).
|
|
1050
1063
|
When installed and the script carries a ``swarph-cli-bundle-version`` stamp
|
|
1051
1064
|
(#830), a second line reports match / mismatch vs the running package.
|
|
1052
1065
|
"""
|
|
@@ -1054,14 +1067,26 @@ def list_hooks(
|
|
|
1054
1067
|
hooks_home_p = Path(hooks_home).expanduser()
|
|
1055
1068
|
for name in sorted(BUILTIN_HOOKS):
|
|
1056
1069
|
bundle = BUILTIN_HOOKS[name]
|
|
1057
|
-
|
|
1058
|
-
#
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1070
|
+
variants = _installed_command_variants(bundle, hooks_home)
|
|
1071
|
+
# Pick the first variant that has ANY binding coverage for reporting.
|
|
1072
|
+
present: list[str] = []
|
|
1073
|
+
missing: list[str] = [b.event for b in bundle.bindings]
|
|
1074
|
+
for c in variants:
|
|
1075
|
+
p, m = binding_coverage(settings, c, bundle)
|
|
1076
|
+
if len(p) > len(present):
|
|
1077
|
+
present, missing = p, m
|
|
1078
|
+
if present and not missing:
|
|
1079
|
+
status = "installed"
|
|
1080
|
+
elif present and missing:
|
|
1081
|
+
status = "partial"
|
|
1082
|
+
else:
|
|
1083
|
+
status = "available"
|
|
1064
1084
|
out(f"{name} [{status}] trust=builtin — {bundle.description}")
|
|
1085
|
+
if status == "partial":
|
|
1086
|
+
out(f" bindings present: {', '.join(present) or '(none)'}")
|
|
1087
|
+
out(f" bindings MISSING: {', '.join(missing)} "
|
|
1088
|
+
f"— re-run: swarph hooks add {name} (#825 fleet bind)")
|
|
1089
|
+
continue
|
|
1065
1090
|
if status != "installed":
|
|
1066
1091
|
continue
|
|
1067
1092
|
script = (hooks_home_p / bundle.script_name).expanduser()
|
|
@@ -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
|
|