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.
Files changed (417) hide show
  1. {swarph_cli-0.63.0/src/swarph_cli.egg-info → swarph_cli-0.65.0}/PKG-INFO +5 -2
  2. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/README.md +4 -1
  3. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/pyproject.toml +1 -1
  4. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/__init__.py +1 -1
  5. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph_hook.py +24 -1
  6. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/dreaming.py +8 -0
  7. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hooks.py +59 -34
  8. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/enrich.py +74 -4
  9. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/index_check.py +106 -6
  10. swarph_cli-0.65.0/src/swarph_cli/dreaming/notify.py +162 -0
  11. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/organize.py +21 -8
  12. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/report.py +20 -3
  13. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/run.py +14 -0
  14. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/verify.py +16 -0
  15. {swarph_cli-0.63.0 → swarph_cli-0.65.0/src/swarph_cli.egg-info}/PKG-INFO +5 -2
  16. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
  17. swarph_cli-0.65.0/tests/test_332_report_organize_findings.py +39 -0
  18. swarph_cli-0.65.0/tests/test_764_enrich_collapse.py +117 -0
  19. swarph_cli-0.65.0/tests/test_825_remaining_cap_shred_bind.py +165 -0
  20. swarph_cli-0.65.0/tests/test_937_dreaming_notify.py +232 -0
  21. swarph_cli-0.65.0/tests/test_938_organize_line_limit.py +175 -0
  22. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_hook.py +1 -1
  23. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/LICENSE +0 -0
  24. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/setup.cfg +0 -0
  25. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/__main__.py +0 -0
  26. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/__init__.py +0 -0
  27. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/backends.py +0 -0
  28. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/pack.py +0 -0
  29. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/prices.py +0 -0
  30. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/quality.py +0 -0
  31. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
  32. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/runner.py +0 -0
  33. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/validate.py +0 -0
  34. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/caller.py +0 -0
  35. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/__init__.py +0 -0
  36. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/harden.py +0 -0
  37. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/lineage.py +0 -0
  38. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/liveness.py +0 -0
  39. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/manifest.py +0 -0
  40. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/paths.py +0 -0
  41. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/verify.py +0 -0
  42. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/cell.py +0 -0
  43. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/chain_token.py +0 -0
  44. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/__init__.py +0 -0
  45. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_content.py +0 -0
  46. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_display.py +0 -0
  47. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  48. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/add.py +0 -0
  49. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/bench.py +0 -0
  50. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board.py +0 -0
  51. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
  52. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain.py +0 -0
  53. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  54. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell.py +0 -0
  55. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_probe.py +0 -0
  56. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  57. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/channel.py +0 -0
  58. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/chat.py +0 -0
  59. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph.py +0 -0
  60. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  61. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_waker.py +0 -0
  62. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/compress.py +0 -0
  63. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/daemon.py +0 -0
  64. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/event.py +0 -0
  65. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gateway.py +0 -0
  66. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gh_route.py +0 -0
  67. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/group.py +0 -0
  68. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/guide.py +0 -0
  69. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/highlight.py +0 -0
  70. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hook_output.py +0 -0
  71. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/import_session.py +0 -0
  72. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/init.py +0 -0
  73. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  74. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_hook.py +0 -0
  75. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  76. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  77. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  78. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  79. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/lane.py +0 -0
  80. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  81. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory.py +0 -0
  82. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  83. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  84. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mesh.py +0 -0
  85. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/monitor.py +0 -0
  86. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/okf_links.py +0 -0
  87. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/onboard.py +0 -0
  88. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  89. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  90. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  91. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/ratify.py +0 -0
  92. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/schedule.py +0 -0
  93. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/security.py +0 -0
  94. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/service.py +0 -0
  95. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/spawn.py +0 -0
  96. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/timeline.py +0 -0
  97. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/version.py +0 -0
  98. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  99. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/watchdog.py +0 -0
  100. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/__init__.py +0 -0
  101. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/levers.py +0 -0
  102. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/marker.py +0 -0
  103. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/verify.py +0 -0
  104. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/console_safe.py +0 -0
  105. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/delivery_queue.py +0 -0
  106. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/__init__.py +0 -0
  107. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/candidates.py +0 -0
  108. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/clone.py +0 -0
  109. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/probes.py +0 -0
  110. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/slm.py +0 -0
  111. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
  112. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/__init__.py +0 -0
  113. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  114. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  115. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/schema.sql +0 -0
  116. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/server.py +0 -0
  117. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/services_control.py +0 -0
  118. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway_default.py +0 -0
  119. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/gh_identity.py +0 -0
  120. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/guide/GUIDE.md +0 -0
  121. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/main.py +0 -0
  122. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/multiplexer.py +0 -0
  123. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/orphan_daemons.py +0 -0
  124. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/pane_probe.py +0 -0
  125. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/__init__.py +0 -0
  126. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/claude.py +0 -0
  127. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  128. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  129. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  130. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  131. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  132. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  133. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  134. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  135. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  136. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  137. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  138. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  139. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor.py +0 -0
  140. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor_windows.py +0 -0
  141. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reconciliation.py +0 -0
  142. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
  143. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_host.py +0 -0
  144. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_payload.py +0 -0
  145. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_reply.py +0 -0
  146. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_staging.py +0 -0
  147. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/query_filters.py +0 -0
  148. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/__init__.py +0 -0
  149. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  150. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  151. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  152. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  153. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/__init__.py +0 -0
  154. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/app.py +0 -0
  155. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/service/providers.py +0 -0
  156. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/session_bridge.py +0 -0
  157. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  158. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  159. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/source_text.py +0 -0
  160. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/stall_alert.py +0 -0
  161. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
  162. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  163. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  164. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  165. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  166. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  167. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  168. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  169. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  170. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  171. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  172. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  173. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  174. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  175. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/timeline_paths.py +0 -0
  176. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli/tokens.py +0 -0
  177. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  178. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  179. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  180. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  181. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_124_register_merge.py +0 -0
  182. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_126_pending_from_complete.py +0 -0
  183. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  184. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_139_tool_probe.py +0 -0
  185. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_183_card_anchor.py +0 -0
  186. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_201_win32_bash_refusal.py +0 -0
  187. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_243_onboard_per_peer_token.py +0 -0
  188. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_307_task4_ask_reply.py +0 -0
  189. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  190. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_319_refusal_exit_codes.py +0 -0
  191. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_323_mistral_backend.py +0 -0
  192. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_356_query_filters.py +0 -0
  193. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_397_gh_identity_router.py +0 -0
  194. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_397_router_preflight.py +0 -0
  195. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_467b_onboard_operator_credential.py +0 -0
  196. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_523_guide.py +0 -0
  197. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_524_conftest_hermeticity.py +0 -0
  198. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_532_accept_flag.py +0 -0
  199. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_535_version_capability.py +0 -0
  200. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_546_default_gateway.py +0 -0
  201. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_547_verbs_from_registry.py +0 -0
  202. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_548_gateway_remainder.py +0 -0
  203. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_552_codegraph_gateway.py +0 -0
  204. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_555_relay_row_shape.py +0 -0
  205. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_556_relay_freshness.py +0 -0
  206. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_564_bootstrap_first_peer.py +0 -0
  207. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  208. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_579_register_bootstrap_message.py +0 -0
  209. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_591_cli_step_graph.py +0 -0
  210. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_644_monitor_supervision.py +0 -0
  211. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_648_install_unit.py +0 -0
  212. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_654_reexec.py +0 -0
  213. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_656_owned_slm.py +0 -0
  214. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_684_dreaming_verb.py +0 -0
  215. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_689_pkg_version_order.py +0 -0
  216. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_716_timeline_one_path.py +0 -0
  217. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  218. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_725_stdio_reconfigure.py +0 -0
  219. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  220. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_740_edit_project.py +0 -0
  221. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_762_cards_edit_priority.py +0 -0
  222. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_801_confirm_flow_cli.py +0 -0
  223. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_802_stage_history.py +0 -0
  224. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_825_codegraph_hook_noise.py +0 -0
  225. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_829_counterfactual_column.py +0 -0
  226. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_830_hook_binding_version.py +0 -0
  227. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_84_cli_labels.py +0 -0
  228. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_866_doctrine_guide.py +0 -0
  229. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
  230. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_874_source_text.py +0 -0
  231. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  232. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_add_security_gate.py +0 -0
  233. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_add.py +0 -0
  234. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_hash.py +0 -0
  235. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_lib.py +0 -0
  236. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_mcp.py +0 -0
  237. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_skill.py +0 -0
  238. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_tool.py +0 -0
  239. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_artifact_uri.py +0 -0
  240. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_backends.py +0 -0
  241. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_command.py +0 -0
  242. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_pack.py +0 -0
  243. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_prices.py +0 -0
  244. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_quality.py +0 -0
  245. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_runner.py +0 -0
  246. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_bench_validate.py +0 -0
  247. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_card_thread.py +0 -0
  248. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_command.py +0 -0
  249. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_merge_check.py +0 -0
  250. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_board_obligation_close.py +0 -0
  251. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_command.py +0 -0
  252. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
  253. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
  254. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_brain_command.py +0 -0
  255. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_caller_meta_guard.py +0 -0
  256. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_lineage.py +0 -0
  257. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_liveness.py +0 -0
  258. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_manifest.py +0 -0
  259. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_paths.py +0 -0
  260. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_capture_security.py +0 -0
  261. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_command_dispatch.py +0 -0
  262. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_harden.py +0 -0
  263. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_loader.py +0 -0
  264. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_selfcheck.py +0 -0
  265. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cell_verify.py +0 -0
  266. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_chain_token.py +0 -0
  267. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_command.py +0 -0
  268. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  269. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_post.py +0 -0
  270. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_channel_wake_policy_filter.py +0 -0
  271. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_chat_command.py +0 -0
  272. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_claude_parser.py +0 -0
  273. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_claude_tmux_template.py +0 -0
  274. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_caller_scope.py +0 -0
  275. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_command.py +0 -0
  276. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codegraph_mcp.py +0 -0
  277. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_codex_waker.py +0 -0
  278. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_caller_convention.py +0 -0
  279. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_command.py +0 -0
  280. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_levers.py +0 -0
  281. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_marker.py +0 -0
  282. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_compress_verify.py +0 -0
  283. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_console_encoding_e2e.py +0 -0
  284. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_content_composition.py +0 -0
  285. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_cursor_membrane.py +0 -0
  286. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_command.py +0 -0
  287. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_console_encoding.py +0 -0
  288. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_daemon_delivery.py +0 -0
  289. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_delivery_queue.py +0 -0
  290. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_display_sanitize.py +0 -0
  291. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  292. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_event_emit.py +0 -0
  293. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_feature_to_uri.py +0 -0
  294. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_brain.py +0 -0
  295. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_client.py +0 -0
  296. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_command.py +0 -0
  297. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gateway_message_idempotency.py +0 -0
  298. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  299. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_group_command.py +0 -0
  300. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_guide_commands_parse.py +0 -0
  301. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_guide_selfcheck.py +0 -0
  302. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_heartbeat_check.py +0 -0
  303. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_highlight_command.py +0 -0
  304. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hook_output.py +0 -0
  305. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_activity_marker.py +0 -0
  306. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_add.py +0 -0
  307. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_bundle.py +0 -0
  308. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_init.py +0 -0
  309. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
  310. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_lifecycle.py +0 -0
  311. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_merge.py +0 -0
  312. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_verify.py +0 -0
  313. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  314. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_command_path.py +0 -0
  315. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_import_command.py +0 -0
  316. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_inbox_identity_provenance.py +0 -0
  317. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_init_command.py +0 -0
  318. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_init_provider_menu.py +0 -0
  319. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_codex_hooks.py +0 -0
  320. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_hook.py +0 -0
  321. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_multiplexer_command.py +0 -0
  322. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_postcompact_hook.py +0 -0
  323. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_install_wake_hook.py +0 -0
  324. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_lane_command.py +0 -0
  325. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_main.py +0 -0
  326. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_main_unknown_verb_guard.py +0 -0
  327. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mcp_missing_hint.py +0 -0
  328. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mcp_server.py +0 -0
  329. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_command.py +0 -0
  330. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_emit_snippet.py +0 -0
  331. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_gateway.py +0 -0
  332. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_sync.py +0 -0
  333. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_memory_sync_membrane.py +0 -0
  334. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_command.py +0 -0
  335. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  336. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  337. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_send_unknown_peer.py +0 -0
  338. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar.py +0 -0
  339. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  340. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  341. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_meta_edge_identity.py +0 -0
  342. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_channel_poll.py +0 -0
  343. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_cli.py +0 -0
  344. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_daemon_collision.py +0 -0
  345. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_self_name_verification.py +0 -0
  346. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_monitor_state_split.py +0 -0
  347. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_multiplexer.py +0 -0
  348. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_muse_membrane.py +0 -0
  349. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_muse_own_cli.py +0 -0
  350. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_obligation_close_is_surfaced.py +0 -0
  351. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_okf_links.py +0 -0
  352. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_onboard_command.py +0 -0
  353. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_opencode_membrane.py +0 -0
  354. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_opencode_plugin.py +0 -0
  355. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_orphan_daemons.py +0 -0
  356. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_packaged_artifacts_exist.py +0 -0
  357. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_packaged_skills.py +0 -0
  358. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_executor.py +0 -0
  359. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_executor_windows.py +0 -0
  360. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_command.py +0 -0
  361. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
  362. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_outbox.py +0 -0
  363. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_reply_transport.py +0 -0
  364. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_e2e.py +0 -0
  365. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_host.py +0 -0
  366. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_payload.py +0 -0
  367. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_peer_service_reply.py +0 -0
  368. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_postcompact_recall.py +0 -0
  369. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_process_liveness.py +0 -0
  370. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_protocol_handler.py +0 -0
  371. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_providers_isolation.py +0 -0
  372. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_ratify_command.py +0 -0
  373. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
  374. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_repl_caller_convention.py +0 -0
  375. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_schedule_command.py +0 -0
  376. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_schedule_create_contract.py +0 -0
  377. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_security.py +0 -0
  378. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_app.py +0 -0
  379. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_command.py +0 -0
  380. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_service_providers.py +0 -0
  381. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_inject.py +0 -0
  382. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_probe.py +0 -0
  383. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_bridge_resolve.py +0 -0
  384. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_session_path_role_gate.py +0 -0
  385. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_shipped_units_match_the_cli.py +0 -0
  386. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_chat.py +0 -0
  387. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_one_shot.py +0 -0
  388. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_smoke_phase_5_5.py +0 -0
  389. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_base_hoist.py +0 -0
  390. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_command.py +0 -0
  391. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_identity_stamp.py +0 -0
  392. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_live_pin.py +0 -0
  393. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  394. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_tmux_session.py +0 -0
  395. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_spawn_windows_relaunch.py +0 -0
  396. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_stall_alert.py +0 -0
  397. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_subprocess_decode_encoding.py +0 -0
  398. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_timeline_command.py +0 -0
  399. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
  400. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
  401. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_stale.py +0 -0
  402. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_submit_verify.py +0 -0
  403. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_token_file_one_parser.py +0 -0
  404. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_token_precedence_explicit_first.py +0 -0
  405. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_unified_token_resolver.py +0 -0
  406. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_version_command.py +0 -0
  407. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_version_consistency.py +0 -0
  408. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_vibe_membrane.py +0 -0
  409. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog.py +0 -0
  410. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake.py +0 -0
  411. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  412. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  413. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_emit_health.py +0 -0
  414. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_install_flags.py +0 -0
  415. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_model_rung.py +0 -0
  416. {swarph_cli-0.63.0 → swarph_cli-0.65.0}/tests/test_watchdog_stale_peers.py +0 -0
  417. {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.63.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
- $ echo $? # 0 clean-and-adjudicated | 1 findings | 2 refused | 3 ran-but-adjudicated-nothing
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
- $ echo $? # 0 clean-and-adjudicated | 1 findings | 2 refused | 3 ran-but-adjudicated-nothing
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.63.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" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.63.0"
19
+ __version__ = "0.65.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -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, limit: int = MAX_ROWS) -> dict:
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 _is_installed(settings: dict, command: str, bundle: HookBundle) -> bool:
1009
- """True when ALL of ``bundle``'s bindings' commands are present in settings."""
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
- event_list = hooks.get(b.event)
1015
- if not isinstance(event_list, list):
1016
- return False
1017
- found = False
1018
- for entry in event_list:
1019
- if entry.get("matcher", "") == b.matcher:
1020
- # #216 review (Copilot, 2nd pass): THE THIRD SITE. Exact
1021
- # matching here makes a LEGACY backslash binding read as NOT
1022
- # INSTALLED, so a Windows cell's real, working hooks list as
1023
- # "unavailable" — the operator is told nothing is installed
1024
- # while the bindings sit right there in settings.json.
1025
- # The previous commit said "one helper, BOTH callers". There
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
- command = _installed_command(bundle, hooks_home)
1058
- # Read side of the same ladder: a legacy install reported as "available"
1059
- # invites the reinstall that produces a DUPLICATE binding.
1060
- status = ("installed"
1061
- if any(_is_installed(settings, c, bundle)
1062
- for c in _installed_command_variants(bundle, hooks_home))
1063
- else "available")
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
- proposals.append({
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
- return proposals
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
- FOUR CHECKS, each for a failure that produced real loss:
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
- if BUDGET >= size > TARGET:
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
- if size > BUDGET:
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
- f"BUDGET: MEMORY.md is {size} BYTES, over {BUDGET} by {size - BUDGET} "
189
- f"— over budget loads SILENTLY PARTIAL (its own header records 18 lost 2026-08-26)")
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)" % (len(files), mmsize, BUDGET, TARGET))
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