swarph-cli 0.64.0__tar.gz → 0.65.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. {swarph_cli-0.64.0/src/swarph_cli.egg-info → swarph_cli-0.65.0}/PKG-INFO +1 -1
  2. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/pyproject.toml +1 -1
  3. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/enrich.py +74 -4
  5. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/index_check.py +106 -6
  6. swarph_cli-0.65.0/src/swarph_cli/dreaming/notify.py +162 -0
  7. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/organize.py +21 -8
  8. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/report.py +20 -3
  9. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/verify.py +16 -0
  10. {swarph_cli-0.64.0 → swarph_cli-0.65.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  11. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  12. swarph_cli-0.65.0/tests/test_332_report_organize_findings.py +39 -0
  13. swarph_cli-0.65.0/tests/test_764_enrich_collapse.py +117 -0
  14. swarph_cli-0.65.0/tests/test_937_dreaming_notify.py +232 -0
  15. swarph_cli-0.65.0/tests/test_938_organize_line_limit.py +175 -0
  16. swarph_cli-0.64.0/src/swarph_cli/dreaming/notify.py +0 -96
  17. swarph_cli-0.64.0/tests/test_937_dreaming_notify.py +0 -120
  18. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/LICENSE +0 -0
  19. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/README.md +0 -0
  20. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/setup.cfg +0 -0
  21. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/__main__.py +0 -0
  22. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/__init__.py +0 -0
  23. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/backends.py +0 -0
  24. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/pack.py +0 -0
  25. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/prices.py +0 -0
  26. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/quality.py +0 -0
  27. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
  28. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/runner.py +0 -0
  29. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/bench/validate.py +0 -0
  30. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/caller.py +0 -0
  31. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/__init__.py +0 -0
  32. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/harden.py +0 -0
  33. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/lineage.py +0 -0
  34. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/liveness.py +0 -0
  35. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/manifest.py +0 -0
  36. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/paths.py +0 -0
  37. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/capture/verify.py +0 -0
  38. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/cell.py +0 -0
  39. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/chain_token.py +0 -0
  40. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/__init__.py +0 -0
  41. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_content.py +0 -0
  42. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_display.py +0 -0
  43. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  44. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/add.py +0 -0
  45. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/bench.py +0 -0
  46. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board.py +0 -0
  47. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
  48. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain.py +0 -0
  49. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  50. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell.py +0 -0
  51. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_probe.py +0 -0
  52. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  53. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/channel.py +0 -0
  54. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/chat.py +0 -0
  55. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph.py +0 -0
  56. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  57. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  58. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/codex_waker.py +0 -0
  59. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/compress.py +0 -0
  60. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/daemon.py +0 -0
  61. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/dreaming.py +0 -0
  62. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/event.py +0 -0
  63. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gateway.py +0 -0
  64. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/gh_route.py +0 -0
  65. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/group.py +0 -0
  66. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/guide.py +0 -0
  67. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/highlight.py +0 -0
  68. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hook_output.py +0 -0
  69. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/hooks.py +0 -0
  70. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/import_session.py +0 -0
  71. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/init.py +0 -0
  72. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  73. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_hook.py +0 -0
  74. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  75. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  76. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  77. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  78. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/lane.py +0 -0
  79. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  80. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory.py +0 -0
  81. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  82. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  83. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/mesh.py +0 -0
  84. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/monitor.py +0 -0
  85. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/okf_links.py +0 -0
  86. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/onboard.py +0 -0
  87. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  88. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  89. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  90. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/ratify.py +0 -0
  91. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/schedule.py +0 -0
  92. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/security.py +0 -0
  93. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/service.py +0 -0
  94. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/spawn.py +0 -0
  95. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/timeline.py +0 -0
  96. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/version.py +0 -0
  97. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  98. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/commands/watchdog.py +0 -0
  99. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/__init__.py +0 -0
  100. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/levers.py +0 -0
  101. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/marker.py +0 -0
  102. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/compress/verify.py +0 -0
  103. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/console_safe.py +0 -0
  104. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/delivery_queue.py +0 -0
  105. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/__init__.py +0 -0
  106. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/candidates.py +0 -0
  107. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/clone.py +0 -0
  108. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/probes.py +0 -0
  109. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/run.py +0 -0
  110. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/slm.py +0 -0
  111. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
  112. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/__init__.py +0 -0
  113. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  114. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  115. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/schema.sql +0 -0
  116. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/server.py +0 -0
  117. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway/services_control.py +0 -0
  118. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gateway_default.py +0 -0
  119. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/gh_identity.py +0 -0
  120. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/guide/GUIDE.md +0 -0
  121. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/main.py +0 -0
  122. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/multiplexer.py +0 -0
  123. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/orphan_daemons.py +0 -0
  124. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/pane_probe.py +0 -0
  125. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/__init__.py +0 -0
  126. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/parsers/claude.py +0 -0
  127. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  128. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  129. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  130. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  131. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  132. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  133. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  134. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  135. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  136. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  137. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  138. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  139. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor.py +0 -0
  140. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_executor_windows.py +0 -0
  141. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reconciliation.py +0 -0
  142. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
  143. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_host.py +0 -0
  144. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_payload.py +0 -0
  145. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_service_reply.py +0 -0
  146. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/peer_staging.py +0 -0
  147. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/query_filters.py +0 -0
  148. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/__init__.py +0 -0
  149. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  150. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  151. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  152. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  153. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/__init__.py +0 -0
  154. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/app.py +0 -0
  155. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/service/providers.py +0 -0
  156. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/session_bridge.py +0 -0
  157. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  158. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  159. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/source_text.py +0 -0
  160. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/stall_alert.py +0 -0
  161. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
  162. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  163. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  164. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  165. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  166. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  167. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  168. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  169. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  170. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  171. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  172. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  173. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  174. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  175. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/timeline_paths.py +0 -0
  176. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli/tokens.py +0 -0
  177. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  178. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  179. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  180. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  181. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_124_register_merge.py +0 -0
  182. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_126_pending_from_complete.py +0 -0
  183. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  184. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_139_tool_probe.py +0 -0
  185. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_183_card_anchor.py +0 -0
  186. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_201_win32_bash_refusal.py +0 -0
  187. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_243_onboard_per_peer_token.py +0 -0
  188. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_307_task4_ask_reply.py +0 -0
  189. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  190. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_319_refusal_exit_codes.py +0 -0
  191. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_323_mistral_backend.py +0 -0
  192. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_356_query_filters.py +0 -0
  193. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_397_gh_identity_router.py +0 -0
  194. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_397_router_preflight.py +0 -0
  195. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_467b_onboard_operator_credential.py +0 -0
  196. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_523_guide.py +0 -0
  197. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_524_conftest_hermeticity.py +0 -0
  198. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_532_accept_flag.py +0 -0
  199. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_535_version_capability.py +0 -0
  200. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_546_default_gateway.py +0 -0
  201. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_547_verbs_from_registry.py +0 -0
  202. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_548_gateway_remainder.py +0 -0
  203. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_552_codegraph_gateway.py +0 -0
  204. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_555_relay_row_shape.py +0 -0
  205. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_556_relay_freshness.py +0 -0
  206. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_564_bootstrap_first_peer.py +0 -0
  207. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  208. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_579_register_bootstrap_message.py +0 -0
  209. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_591_cli_step_graph.py +0 -0
  210. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_644_monitor_supervision.py +0 -0
  211. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_648_install_unit.py +0 -0
  212. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_654_reexec.py +0 -0
  213. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_656_owned_slm.py +0 -0
  214. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_684_dreaming_verb.py +0 -0
  215. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_689_pkg_version_order.py +0 -0
  216. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_716_timeline_one_path.py +0 -0
  217. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  218. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_725_stdio_reconfigure.py +0 -0
  219. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  220. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_740_edit_project.py +0 -0
  221. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_762_cards_edit_priority.py +0 -0
  222. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_801_confirm_flow_cli.py +0 -0
  223. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_802_stage_history.py +0 -0
  224. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_825_codegraph_hook_noise.py +0 -0
  225. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  226. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_829_counterfactual_column.py +0 -0
  227. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_830_hook_binding_version.py +0 -0
  228. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_84_cli_labels.py +0 -0
  229. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_866_doctrine_guide.py +0 -0
  230. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
  231. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_874_source_text.py +0 -0
  232. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  233. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_add_security_gate.py +0 -0
  234. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_add.py +0 -0
  235. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_hash.py +0 -0
  236. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_lib.py +0 -0
  237. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_mcp.py +0 -0
  238. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_skill.py +0 -0
  239. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_tool.py +0 -0
  240. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_artifact_uri.py +0 -0
  241. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_backends.py +0 -0
  242. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_command.py +0 -0
  243. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_pack.py +0 -0
  244. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_prices.py +0 -0
  245. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_quality.py +0 -0
  246. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_runner.py +0 -0
  247. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_bench_validate.py +0 -0
  248. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_card_thread.py +0 -0
  249. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_command.py +0 -0
  250. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_merge_check.py +0 -0
  251. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_board_obligation_close.py +0 -0
  252. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_command.py +0 -0
  253. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
  254. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
  255. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_brain_command.py +0 -0
  256. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_caller_meta_guard.py +0 -0
  257. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_lineage.py +0 -0
  258. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_liveness.py +0 -0
  259. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_manifest.py +0 -0
  260. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_paths.py +0 -0
  261. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_capture_security.py +0 -0
  262. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_command_dispatch.py +0 -0
  263. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_harden.py +0 -0
  264. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_loader.py +0 -0
  265. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_selfcheck.py +0 -0
  266. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cell_verify.py +0 -0
  267. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_chain_token.py +0 -0
  268. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_command.py +0 -0
  269. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  270. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_post.py +0 -0
  271. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_channel_wake_policy_filter.py +0 -0
  272. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_chat_command.py +0 -0
  273. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_claude_parser.py +0 -0
  274. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_claude_tmux_template.py +0 -0
  275. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_caller_scope.py +0 -0
  276. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_command.py +0 -0
  277. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_hook.py +0 -0
  278. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codegraph_mcp.py +0 -0
  279. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_codex_waker.py +0 -0
  280. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_caller_convention.py +0 -0
  281. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_command.py +0 -0
  282. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_levers.py +0 -0
  283. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_marker.py +0 -0
  284. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_compress_verify.py +0 -0
  285. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_console_encoding_e2e.py +0 -0
  286. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_content_composition.py +0 -0
  287. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_cursor_membrane.py +0 -0
  288. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_command.py +0 -0
  289. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_console_encoding.py +0 -0
  290. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_daemon_delivery.py +0 -0
  291. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_delivery_queue.py +0 -0
  292. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_display_sanitize.py +0 -0
  293. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  294. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_event_emit.py +0 -0
  295. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_feature_to_uri.py +0 -0
  296. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_brain.py +0 -0
  297. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_client.py +0 -0
  298. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_command.py +0 -0
  299. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gateway_message_idempotency.py +0 -0
  300. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  301. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_group_command.py +0 -0
  302. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_guide_commands_parse.py +0 -0
  303. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_guide_selfcheck.py +0 -0
  304. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_heartbeat_check.py +0 -0
  305. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_highlight_command.py +0 -0
  306. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hook_output.py +0 -0
  307. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_activity_marker.py +0 -0
  308. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_add.py +0 -0
  309. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_bundle.py +0 -0
  310. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_init.py +0 -0
  311. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
  312. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_lifecycle.py +0 -0
  313. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_merge.py +0 -0
  314. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_verify.py +0 -0
  315. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  316. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_hooks_win32_command_path.py +0 -0
  317. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_import_command.py +0 -0
  318. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_inbox_identity_provenance.py +0 -0
  319. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_init_command.py +0 -0
  320. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_init_provider_menu.py +0 -0
  321. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_codex_hooks.py +0 -0
  322. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_hook.py +0 -0
  323. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_multiplexer_command.py +0 -0
  324. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_postcompact_hook.py +0 -0
  325. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_install_wake_hook.py +0 -0
  326. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_lane_command.py +0 -0
  327. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_main.py +0 -0
  328. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_main_unknown_verb_guard.py +0 -0
  329. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mcp_missing_hint.py +0 -0
  330. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mcp_server.py +0 -0
  331. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_command.py +0 -0
  332. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_emit_snippet.py +0 -0
  333. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_gateway.py +0 -0
  334. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_sync.py +0 -0
  335. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_memory_sync_membrane.py +0 -0
  336. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_command.py +0 -0
  337. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  338. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  339. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_send_unknown_peer.py +0 -0
  340. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar.py +0 -0
  341. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  342. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  343. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_meta_edge_identity.py +0 -0
  344. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_channel_poll.py +0 -0
  345. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_cli.py +0 -0
  346. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_daemon_collision.py +0 -0
  347. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_self_name_verification.py +0 -0
  348. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_monitor_state_split.py +0 -0
  349. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_multiplexer.py +0 -0
  350. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_muse_membrane.py +0 -0
  351. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_muse_own_cli.py +0 -0
  352. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_obligation_close_is_surfaced.py +0 -0
  353. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_okf_links.py +0 -0
  354. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_onboard_command.py +0 -0
  355. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_opencode_membrane.py +0 -0
  356. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_opencode_plugin.py +0 -0
  357. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_orphan_daemons.py +0 -0
  358. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_packaged_artifacts_exist.py +0 -0
  359. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_packaged_skills.py +0 -0
  360. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_executor.py +0 -0
  361. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_executor_windows.py +0 -0
  362. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_command.py +0 -0
  363. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
  364. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_outbox.py +0 -0
  365. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_reply_transport.py +0 -0
  366. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_e2e.py +0 -0
  367. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_host.py +0 -0
  368. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_payload.py +0 -0
  369. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_peer_service_reply.py +0 -0
  370. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_postcompact_recall.py +0 -0
  371. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_process_liveness.py +0 -0
  372. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_protocol_handler.py +0 -0
  373. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_providers_isolation.py +0 -0
  374. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_ratify_command.py +0 -0
  375. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
  376. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_repl_caller_convention.py +0 -0
  377. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_schedule_command.py +0 -0
  378. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_schedule_create_contract.py +0 -0
  379. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_security.py +0 -0
  380. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_app.py +0 -0
  381. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_command.py +0 -0
  382. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_service_providers.py +0 -0
  383. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_inject.py +0 -0
  384. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_probe.py +0 -0
  385. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_bridge_resolve.py +0 -0
  386. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_session_path_role_gate.py +0 -0
  387. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_shipped_units_match_the_cli.py +0 -0
  388. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_chat.py +0 -0
  389. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_one_shot.py +0 -0
  390. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_smoke_phase_5_5.py +0 -0
  391. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_base_hoist.py +0 -0
  392. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_command.py +0 -0
  393. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_identity_stamp.py +0 -0
  394. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_live_pin.py +0 -0
  395. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  396. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_tmux_session.py +0 -0
  397. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_spawn_windows_relaunch.py +0 -0
  398. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_stall_alert.py +0 -0
  399. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_subprocess_decode_encoding.py +0 -0
  400. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_timeline_command.py +0 -0
  401. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
  402. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
  403. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_stale.py +0 -0
  404. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_tmux_wake_submit_verify.py +0 -0
  405. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_token_file_one_parser.py +0 -0
  406. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_token_precedence_explicit_first.py +0 -0
  407. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_unified_token_resolver.py +0 -0
  408. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_version_command.py +0 -0
  409. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_version_consistency.py +0 -0
  410. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_vibe_membrane.py +0 -0
  411. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog.py +0 -0
  412. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake.py +0 -0
  413. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  414. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  415. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_emit_health.py +0 -0
  416. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_install_flags.py +0 -0
  417. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_model_rung.py +0 -0
  418. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_watchdog_stale_peers.py +0 -0
  419. {swarph_cli-0.64.0 → swarph_cli-0.65.0}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.64.0
3
+ Version: 0.65.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.64.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.64.0"
19
+ __version__ = "0.65.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -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
 
@@ -0,0 +1,162 @@
1
+ """Notify the owning cell when the dreaming finding set changes (#937).
2
+
3
+ The finding set is disagree + surface_disagreement, keyed file:line:kind, plus
4
+ each organize finding keyed by its category token and, where the line names
5
+ one, the file. A clean status line is not a finding. Organize keys contain
6
+ no digits: counts stay in the DM body, not in the key. State lives at
7
+ <out-parent>/.last-findings.json and is written only after a successful send.
8
+ An unchanged set sends nothing. A missing state file is a first run: send
9
+ the full set once.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import re
15
+ from pathlib import Path
16
+
17
+ # "memory-index-check: clean — ..." is a status line. "not clean" is not.
18
+ _CLEAN = re.compile(r"(?:^|:)\s*clean\b", re.IGNORECASE)
19
+ # Longest first so "MISSING INDEX" wins over a shorter prefix.
20
+ _CATEGORY = (
21
+ "MISSING INDEX",
22
+ "DUP_TARGET",
23
+ "LINES",
24
+ "CUT",
25
+ "DANGLING",
26
+ "ORPHANS",
27
+ "TRUNCATED",
28
+ "BUDGET",
29
+ "WARN",
30
+ )
31
+ _FILE = re.compile(r"[\w.-]+\.md\b")
32
+
33
+
34
+ class NotifySendError(Exception):
35
+ """The mesh send failed. Distinct from a findings exit."""
36
+
37
+
38
+ def _category(text: str) -> str | None:
39
+ for name in _CATEGORY:
40
+ if text.startswith(name + ":") or text.startswith(name + " "):
41
+ return name
42
+ return None
43
+
44
+
45
+ def organize_keys(lines) -> list[str]:
46
+ """One key per category header. Member lines are not keys.
47
+
48
+ A header that names a file keys ``organize:CATEGORY:file``. Indented
49
+ children (CUT's lost pointers, ORPHANS' name window, a DUP_TARGET block)
50
+ stay out of the key, so they cannot attach to the previous category and
51
+ a count or a member list cannot change the set. Digits that are part of
52
+ a filename stay. Counts do not.
53
+ """
54
+ keys: set[str] = set()
55
+ for raw in lines or []:
56
+ original = str(raw)
57
+ text = original.strip()
58
+ if not text or _CLEAN.search(text) or text.startswith("memory-index-check:"):
59
+ continue
60
+ # A child line is not a finding of its own, and it must not inherit
61
+ # the category above it.
62
+ if original.startswith((" ", "\t")) or _FILE.fullmatch(text):
63
+ continue
64
+ cat = _category(text)
65
+ if not cat:
66
+ continue
67
+ files = _FILE.findall(text)
68
+ if files:
69
+ keys.add(f"organize:{cat}:{files[0]}")
70
+ else:
71
+ keys.add(f"organize:{cat}")
72
+ return sorted(keys)
73
+
74
+
75
+ def finding_keys(verdicts, organized) -> list[str]:
76
+ keys: set[str] = set()
77
+ for v in verdicts or []:
78
+ if v.get("verdict") in ("disagree", "surface_disagreement"):
79
+ keys.add(f"{v.get('file')}:{v.get('line')}:{v.get('kind')}")
80
+ keys.update(organize_keys((organized or {}).get("findings") or []))
81
+ return sorted(keys)
82
+
83
+
84
+ def _load_previous(path: Path) -> list[str] | None:
85
+ if not path.exists():
86
+ return None
87
+ data = json.loads(path.read_text(encoding="utf-8"))
88
+ return list(data.get("keys") or [])
89
+
90
+
91
+ def _details(organized) -> list[str]:
92
+ """Original organize lines, counts included. Clean status is not a finding."""
93
+ out = []
94
+ for raw in (organized or {}).get("findings") or []:
95
+ text = str(raw).strip()
96
+ if text and not _CLEAN.search(text) and not text.startswith("memory-index-check:"):
97
+ out.append(text)
98
+ return out
99
+
100
+
101
+ def _body(keys: list[str], previous: list[str] | None, details: list[str]) -> str:
102
+ if previous is None:
103
+ lines = ["dreaming findings first run", f"counts: {len(keys)}"]
104
+ lines.extend(keys)
105
+ else:
106
+ prev = set(previous)
107
+ cur = set(keys)
108
+ new = sorted(cur - prev)
109
+ cleared = sorted(prev - cur)
110
+ lines = [
111
+ "dreaming findings changed",
112
+ "new: " + (", ".join(new) if new else "(none)"),
113
+ "cleared: " + (", ".join(cleared) if cleared else "(none)"),
114
+ f"counts: {len(keys)} was {len(previous)}",
115
+ ]
116
+ if details:
117
+ lines.append("details:")
118
+ lines.extend(details)
119
+ return "\n".join(lines)
120
+
121
+
122
+ def apply(out: Path, verdicts, organized, cell: str, sender) -> bool:
123
+ """Send one DM when the set changes. Return True if a DM was sent.
124
+
125
+ ``sender(cell, body)`` raises on failure. State is not written then.
126
+ """
127
+ out = Path(out)
128
+ keys = finding_keys(verdicts, organized)
129
+ state = out.parent / ".last-findings.json"
130
+ previous = _load_previous(state)
131
+ if previous is not None and sorted(previous) == keys:
132
+ return False
133
+ body = _body(keys, previous, _details(organized))
134
+ try:
135
+ sender(cell, body)
136
+ except NotifySendError:
137
+ raise
138
+ except Exception as exc:
139
+ raise NotifySendError(str(exc)) from exc
140
+ state.write_text(json.dumps({"keys": keys}, indent=2) + "\n", encoding="utf-8")
141
+ return True
142
+
143
+
144
+ def mesh_sender(cell: str, content: str) -> None:
145
+ """Same path as ``swarph mesh send``: one status DM, no second transport."""
146
+ import argparse
147
+
148
+ from swarph_cli.commands.mesh import _run_send
149
+ from swarph_cli.gateway_default import env_gateway
150
+
151
+ ns = argparse.Namespace(
152
+ to=cell,
153
+ kind="status",
154
+ content=content,
155
+ content_file=None,
156
+ self_name=None,
157
+ token_file=None,
158
+ gateway=env_gateway(),
159
+ )
160
+ rc = _run_send(ns)
161
+ if rc != 0:
162
+ raise NotifySendError(f"mesh send exited {rc}")
@@ -9,6 +9,11 @@ BUDGET = 24985 # BYTES. GC6 -- never len(str).
9
9
  # organised anything; it has queued the same breach for tomorrow.
10
10
  TARGET = int(BUDGET * 0.94) # ~1,500 bytes of headroom
11
11
 
12
+ # Claude Code loads MEMORY.md at session start and TRUNCATES after line 200
13
+ # (#938). Same headroom rule as bytes: land under the target, not the ceiling.
14
+ LINE_LIMIT = 200 # harness truncation line
15
+ LINE_TARGET = int(LINE_LIMIT * 0.94) # 188
16
+
12
17
 
13
18
  def organize(clone: Path) -> dict:
14
19
  import subprocess, sys
@@ -21,17 +26,25 @@ def organize(clone: Path) -> dict:
21
26
  idx = clone / "MEMORY.md"
22
27
  if not idx.exists():
23
28
  return {"findings": findings, "index_bytes_before": 0,
24
- "index_bytes_after": 0, "trimmed": []}
25
- before = len(idx.read_bytes())
29
+ "index_bytes_after": 0, "trimmed": [],
30
+ "index_lines_before": 0, "index_lines_after": 0}
31
+ raw = idx.read_bytes()
32
+ before = len(raw)
33
+ lines = idx.read_text(encoding="utf-8").split("\n")
34
+ lines_before = len(lines)
26
35
  trimmed = []
27
- if before > BUDGET:
28
- lines = idx.read_text(encoding="utf-8").split("\n")
29
- # Trim from the END: the index is roughly oldest-first, and the head
30
- # carries the starred entries. ponytail: crude ordering, replace with a
31
- # recency/star rank if trims start dropping things that matter.
32
- while len("\n".join(lines).encode("utf-8")) > TARGET and lines:
36
+ # Two independent limits (#938 / droplet plan-review): bytes OR lines may
37
+ # bind first depending on line length. Trim when either ceiling is breached;
38
+ # land under BOTH targets.
39
+ if before > BUDGET or lines_before > LINE_LIMIT:
40
+ while lines and (
41
+ len("\n".join(lines).encode("utf-8")) > TARGET
42
+ or len(lines) > LINE_TARGET):
33
43
  trimmed.append(lines.pop())
34
44
  idx.write_text("\n".join(lines), encoding="utf-8")
45
+ after_text = idx.read_text(encoding="utf-8") if idx.exists() else ""
35
46
  return {"findings": findings, "index_bytes_before": before,
36
47
  "index_bytes_after": len(idx.read_bytes()),
48
+ "index_lines_before": lines_before,
49
+ "index_lines_after": len(after_text.split("\n")) if after_text or idx.exists() else 0,
37
50
  "trimmed": [t for t in trimmed if t.strip()]}
@@ -48,10 +48,27 @@ def render(verdicts, organized, proposals, corpus, clone, enrich_note: str | Non
48
48
  "| verdict | n |", "|---|---|"]
49
49
  for k in ("disagree", "surface_disagreement", "mention", "unprobeable", "agree"):
50
50
  L.append("| %s | %d |" % (k, counts.get(k, 0)))
51
- L += ["", "MEMORY.md: %d bytes before, %d bytes after (budget 24985)" % (
52
- organized["index_bytes_before"], organized["index_bytes_after"])]
53
- if organized["trimmed"]:
51
+ # Bytes AND lines (#938 / #332): the harness truncates on both; a byte-only
52
+ # line hid the line-budget findings that organize already collected.
53
+ lines_before = organized.get("index_lines_before")
54
+ lines_after = organized.get("index_lines_after")
55
+ if lines_before is None or lines_after is None:
56
+ L += ["", "MEMORY.md: %d bytes before, %d bytes after (budget 24985)" % (
57
+ organized["index_bytes_before"], organized["index_bytes_after"])]
58
+ else:
59
+ L += ["", "MEMORY.md: %d bytes / %d lines before, %d bytes / %d lines after "
60
+ "(budget 24985 bytes, harness line limit 200)" % (
61
+ organized["index_bytes_before"], lines_before,
62
+ organized["index_bytes_after"], lines_after)]
63
+ if organized.get("trimmed"):
54
64
  L.append("trimmed %d index line(s) to stay under budget" % len(organized["trimmed"]))
65
+ org_findings = [f for f in (organized.get("findings") or []) if f.strip()]
66
+ if org_findings:
67
+ # Verbatim — LINES, CUT, DANGLING, ORPHANS, DUP_TARGET, BUDGET, WARN…
68
+ # Drop's surface gap (#332): organize collected these and the report
69
+ # never printed them, so #REACH had nothing to deliver.
70
+ L += ["", "organize findings (%d):" % len(org_findings)]
71
+ L.extend(org_findings)
55
72
  L += ["", "enrichment proposals (all marked derived, none applied): %d" % len(proposals)]
56
73
  if enrich_note:
57
74
  L.append(enrich_note)
@@ -202,12 +202,28 @@ def _is_dated_record(context: str, asserted=None) -> bool:
202
202
  for ds, de in hits)
203
203
 
204
204
 
205
+ def _is_superseded(context: str) -> bool:
206
+ """Same-line SUPERSEDED note: claim is a MENTION, not a live assertion (#938 addendum / #939).
207
+
208
+ grep -i supersed over dreaming/ was 0; corrected corpus lines still carried the
209
+ old address and re-flagged every night. An inline note must silence the probe.
210
+ """
211
+ return bool(re.search(r"SUPERSEDED", context or "", re.I))
212
+
213
+
205
214
  def verify(corpus: Path, manifest: dict) -> list[dict]:
206
215
  verdicts = []
207
216
  for c in extract_candidates(corpus):
208
217
  results = probe(c)
209
218
  base = {k: c[k] for k in ("file", "line", "kind", "ref", "asserted", "context")}
210
219
  base["source_sha256"] = manifest["files"].get(c["file"], {}).get("sha256")
220
+ # #938/#939: SUPERSEDED on the claim's line -> mention, never disagreement.
221
+ if _is_superseded(c.get("context", "")):
222
+ r = results[0] if results else {}
223
+ verdicts.append({**base, "observed": r.get("observed"),
224
+ "surface": r.get("surface"),
225
+ "verdict": "mention", "reason": "superseded"})
226
+ continue
211
227
  # GC4d: board_stage emits surface_disagreement ONLY, never disagree.
212
228
  # A card can sit at spec while the work is done; the corpus may be
213
229
  # the accurate side. Locked before any compare, including empty probe.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.64.0
3
+ Version: 0.65.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -181,6 +181,7 @@ tests/test_307_task4_ask_reply.py
181
181
  tests/test_312_codegraph_fuzzy_verb.py
182
182
  tests/test_319_refusal_exit_codes.py
183
183
  tests/test_323_mistral_backend.py
184
+ tests/test_332_report_organize_findings.py
184
185
  tests/test_356_query_filters.py
185
186
  tests/test_397_gh_identity_router.py
186
187
  tests/test_397_router_preflight.py
@@ -211,6 +212,7 @@ tests/test_725_stdio_reconfigure.py
211
212
  tests/test_734_dreaming_enrich_degrade.py
212
213
  tests/test_740_edit_project.py
213
214
  tests/test_762_cards_edit_priority.py
215
+ tests/test_764_enrich_collapse.py
214
216
  tests/test_801_confirm_flow_cli.py
215
217
  tests/test_802_stage_history.py
216
218
  tests/test_825_codegraph_hook_noise.py
@@ -223,6 +225,7 @@ tests/test_872_identity_refuses_not_warns.py
223
225
  tests/test_874_source_text.py
224
226
  tests/test_888_opencode_data_dir_can_fail.py
225
227
  tests/test_937_dreaming_notify.py
228
+ tests/test_938_organize_line_limit.py
226
229
  tests/test_add_security_gate.py
227
230
  tests/test_artifact_add.py
228
231
  tests/test_artifact_hash.py
@@ -0,0 +1,39 @@
1
+ """#332: dreaming-report.md must surface organize findings + line counts."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ from swarph_cli.dreaming.report import render
7
+
8
+
9
+ def test_332_report_renders_lines_finding_and_line_counts(tmp_path):
10
+ """CAN-FAIL: LINES finding in organized['findings'] appears in report text."""
11
+ corpus = tmp_path / "mem"
12
+ clone = tmp_path / "clone"
13
+ corpus.mkdir()
14
+ clone.mkdir()
15
+ lines_finding = (
16
+ "LINES: MEMORY.md is 197 lines (harness truncates after 200; "
17
+ "target <= 188) — over target by 9"
18
+ )
19
+ cut_finding = (
20
+ "CUT: MEMORY.md 24068 bytes / 197 lines — LINES binds first; "
21
+ "cut_line=189 (harness drops from here; byte ceiling 24985, line ceiling 200, "
22
+ "line target 188)"
23
+ )
24
+ dangling = "DANGLING: 1 pointer(s) to a file that does not exist"
25
+ organized = {
26
+ "index_bytes_before": 24068,
27
+ "index_bytes_after": 24068,
28
+ "index_lines_before": 197,
29
+ "index_lines_after": 197,
30
+ "trimmed": [],
31
+ "findings": [cut_finding, " first lost pointers:", dangling, lines_finding],
32
+ }
33
+ text = render([], organized, [], corpus, clone)
34
+ assert "197 lines" in text
35
+ assert "24068 bytes" in text
36
+ assert lines_finding in text, "LINES finding must appear verbatim"
37
+ assert cut_finding in text
38
+ assert dangling in text
39
+ assert "organize findings" in text