deepstrike 0.2.41__tar.gz → 0.2.43__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 (190) hide show
  1. {deepstrike-0.2.41 → deepstrike-0.2.43}/Cargo.lock +6 -6
  2. {deepstrike-0.2.41 → deepstrike-0.2.43}/Cargo.toml +2 -2
  3. {deepstrike-0.2.41 → deepstrike-0.2.43}/PKG-INFO +2 -2
  4. {deepstrike-0.2.41 → deepstrike-0.2.43}/README.md +1 -1
  5. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/manager.rs +3 -3
  6. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/renderer.rs +11 -6
  7. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/summarizer.rs +57 -7
  8. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/task_state.rs +55 -3
  9. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/utility.rs +36 -29
  10. deepstrike-0.2.43/crates/deepstrike-core/src/lexical.rs +178 -0
  11. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/lib.rs +1 -0
  12. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/memory/curator.rs +16 -12
  13. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/mm/handle.rs +19 -10
  14. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/kernel/protocol.rs +10 -0
  15. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/kernel/tests.rs +103 -32
  16. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/rollback.rs +11 -11
  17. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +21 -1
  18. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +89 -60
  19. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +1 -2
  20. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +102 -74
  21. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +11 -19
  22. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +506 -17
  23. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +69 -58
  24. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/milestone.rs +2 -1
  25. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/__init__.py +2 -2
  26. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/harness/harness.py +7 -0
  27. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/runner.py +265 -75
  28. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/types/agent.py +9 -0
  29. {deepstrike-0.2.41 → deepstrike-0.2.43}/pyproject.toml +1 -1
  30. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/Cargo.toml +0 -0
  31. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/benches/kernel_baseline.rs +0 -0
  32. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/benchmark/mod.rs +0 -0
  33. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/compression.rs +0 -0
  34. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/config.rs +0 -0
  35. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/fault.rs +0 -0
  36. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/mod.rs +0 -0
  37. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/partitions.rs +0 -0
  38. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/policy.rs +0 -0
  39. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/pressure.rs +0 -0
  40. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/renewal.rs +0 -0
  41. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
  42. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/text.rs +0 -0
  43. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
  44. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/context/units.rs +0 -0
  45. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
  46. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/mod.rs +0 -0
  47. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/permission.rs +0 -0
  48. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
  49. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/quota.rs +0 -0
  50. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
  51. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/repeat_fuse.rs +0 -0
  52. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/governance/veto.rs +0 -0
  53. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/harness/eval.rs +0 -0
  54. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/harness/mod.rs +0 -0
  55. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/memory/durable.rs +0 -0
  56. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/memory/mod.rs +0 -0
  57. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/mm/memory.rs +0 -0
  58. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/mm/mod.rs +0 -0
  59. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/mm/value.rs +0 -0
  60. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
  61. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
  62. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
  63. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
  64. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
  65. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/proc/mod.rs +0 -0
  66. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
  67. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/kernel/runtime.rs +0 -0
  68. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/kernel.rs +0 -0
  69. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
  70. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
  71. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
  72. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/runtime/session.rs +0 -0
  73. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/entropy.rs +0 -0
  74. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
  75. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
  76. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
  77. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/cancellation.rs +0 -0
  78. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
  79. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
  80. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
  81. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/signals/attention.rs +0 -0
  82. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/signals/mod.rs +0 -0
  83. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/signals/queue.rs +0 -0
  84. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/signals/router.rs +0 -0
  85. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
  86. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/agent.rs +0 -0
  87. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/capability.rs +0 -0
  88. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/contract.rs +0 -0
  89. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/error.rs +0 -0
  90. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/message.rs +0 -0
  91. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/mod.rs +0 -0
  92. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/policy.rs +0 -0
  93. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/result.rs +0 -0
  94. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/signal.rs +0 -0
  95. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/skill.rs +0 -0
  96. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-core/src/types/task.rs +0 -0
  97. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-py/Cargo.toml +0 -0
  98. {deepstrike-0.2.41 → deepstrike-0.2.43}/crates/deepstrike-py/src/lib.rs +0 -0
  99. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/__init__.py +0 -0
  100. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/contract.py +0 -0
  101. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/handoff.py +0 -0
  102. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/harness.py +0 -0
  103. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/modes.py +0 -0
  104. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/collaboration/pool.py +0 -0
  105. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/governance.py +0 -0
  106. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/harness/__init__.py +0 -0
  107. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/harness/judge.py +0 -0
  108. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/kernel/__init__.py +0 -0
  109. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/knowledge/__init__.py +0 -0
  110. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/knowledge/source.py +0 -0
  111. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/__init__.py +0 -0
  112. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/agent.py +0 -0
  113. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/extraction.py +0 -0
  114. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/in_memory_store.py +0 -0
  115. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/protocols.py +0 -0
  116. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/ranking.py +0 -0
  117. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/retention.py +0 -0
  118. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/memory/working.py +0 -0
  119. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/__init__.py +0 -0
  120. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/anthropic.py +0 -0
  121. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/anthropic_compatible.py +0 -0
  122. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/base.py +0 -0
  123. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/deepseek.py +0 -0
  124. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/factories.py +0 -0
  125. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/gemini.py +0 -0
  126. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/glm.py +0 -0
  127. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/kimi.py +0 -0
  128. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/minimax.py +0 -0
  129. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/ollama.py +0 -0
  130. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/openai.py +0 -0
  131. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/openai_responses.py +0 -0
  132. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/qwen.py +0 -0
  133. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/replay.py +0 -0
  134. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/replay_validator.py +0 -0
  135. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/stream.py +0 -0
  136. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/providers/vendor_profiles.py +0 -0
  137. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/__init__.py +0 -0
  138. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/archive.py +0 -0
  139. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/context_policy.py +0 -0
  140. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/credential_vault.py +0 -0
  141. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/eval.py +0 -0
  142. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/event_stream.py +0 -0
  143. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/execution_plane.py +0 -0
  144. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/facade.py +0 -0
  145. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/filtered_plane.py +0 -0
  146. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/kernel_event_log.py +0 -0
  147. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/kernel_rebuild.py +0 -0
  148. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/kernel_step.py +0 -0
  149. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/kernel_transaction_log.py +0 -0
  150. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/large_result_spool.py +0 -0
  151. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/loop_driver.py +0 -0
  152. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
  153. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/os_profile.py +0 -0
  154. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/os_snapshot.py +0 -0
  155. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/output_schema.py +0 -0
  156. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/process_sandbox_plane.py +0 -0
  157. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/provider_replay.py +0 -0
  158. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/reaction_checkpoint.py +0 -0
  159. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/reactive_session.py +0 -0
  160. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/reducers.py +0 -0
  161. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/reliability.py +0 -0
  162. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/remote_vpc_plane.py +0 -0
  163. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/replay_fixture.py +0 -0
  164. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/replay_provider.py +0 -0
  165. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/replay_sanitize.py +0 -0
  166. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/run_group.py +0 -0
  167. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/session_log.py +0 -0
  168. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/session_repair.py +0 -0
  169. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
  170. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/turn_policy.py +0 -0
  171. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/workflow_control_flow.py +0 -0
  172. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/workflow_store.py +0 -0
  173. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/runtime/worktree_plane.py +0 -0
  174. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/safety/__init__.py +0 -0
  175. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/safety/permissions.py +0 -0
  176. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/signals/__init__.py +0 -0
  177. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/signals/gateway.py +0 -0
  178. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/signals/scheduled.py +0 -0
  179. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/signals/types.py +0 -0
  180. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/skills/__init__.py +0 -0
  181. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/skills/loader.py +0 -0
  182. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/skills/registry.py +0 -0
  183. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/skills/watcher.py +0 -0
  184. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/__init__.py +0 -0
  185. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/builtin/__init__.py +0 -0
  186. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/builtin/read_file.py +0 -0
  187. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/errors.py +0 -0
  188. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/execution.py +0 -0
  189. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/tools/registry.py +0 -0
  190. {deepstrike-0.2.41 → deepstrike-0.2.43}/deepstrike/types/__init__.py +0 -0
@@ -194,7 +194,7 @@ dependencies = [
194
194
 
195
195
  [[package]]
196
196
  name = "deepstrike-core"
197
- version = "0.2.41"
197
+ version = "0.2.43"
198
198
  dependencies = [
199
199
  "compact_str",
200
200
  "pretty_assertions",
@@ -206,7 +206,7 @@ dependencies = [
206
206
 
207
207
  [[package]]
208
208
  name = "deepstrike-lab"
209
- version = "0.2.41"
209
+ version = "0.2.43"
210
210
  dependencies = [
211
211
  "deepstrike-core",
212
212
  "serde",
@@ -216,7 +216,7 @@ dependencies = [
216
216
 
217
217
  [[package]]
218
218
  name = "deepstrike-node"
219
- version = "0.2.41"
219
+ version = "0.2.43"
220
220
  dependencies = [
221
221
  "compact_str",
222
222
  "deepstrike-core",
@@ -228,7 +228,7 @@ dependencies = [
228
228
 
229
229
  [[package]]
230
230
  name = "deepstrike-py"
231
- version = "0.2.41"
231
+ version = "0.2.43"
232
232
  dependencies = [
233
233
  "compact_str",
234
234
  "deepstrike-core",
@@ -239,7 +239,7 @@ dependencies = [
239
239
 
240
240
  [[package]]
241
241
  name = "deepstrike-sdk"
242
- version = "0.2.41"
242
+ version = "0.2.43"
243
243
  dependencies = [
244
244
  "async-stream",
245
245
  "async-trait",
@@ -273,7 +273,7 @@ dependencies = [
273
273
 
274
274
  [[package]]
275
275
  name = "deepstrike-wasm"
276
- version = "0.2.41"
276
+ version = "0.2.43"
277
277
  dependencies = [
278
278
  "compact_str",
279
279
  "deepstrike-core",
@@ -3,13 +3,13 @@ resolver = "2"
3
3
  members = ["crates/deepstrike-core", "crates/deepstrike-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.41"
6
+ version = "0.2.43"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/kongusen/deepstrike"
10
10
 
11
11
  [workspace.dependencies]
12
- deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.41" }
12
+ deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.43" }
13
13
  notify = "6"
14
14
  serde = { version = "1", features = ["derive"] }
15
15
  serde_json = "1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepstrike
3
- Version: 0.2.41
3
+ Version: 0.2.43
4
4
  Requires-Dist: httpx>=0.27
5
5
  Requires-Dist: pyyaml>=6.0
6
6
  Requires-Dist: anyio>=4.0
@@ -206,7 +206,7 @@ A node's `kind` selects the control-flow shape; the same executor drives them al
206
206
 
207
207
  ### 0.2.11 capabilities
208
208
 
209
- - **Runtime fan-out** — give a node the `submit_workflow_nodes_tool` and its agent can append nodes to the live DAG mid-run (true loop-until-done; one verifier per claim it discovers). Recorded and replayed on `resume_workflow`.
209
+ - **Runtime fan-out** — give a node the `submit_workflow_nodes_tool` and its agent can append nodes to the live DAG mid-run (true loop-until-done; one verifier per claim it discovers). Recorded and replayed on `resume_workflow`. Governance rejection fails the submitting node instead of acknowledging work that was never appended.
210
210
  - **Quarantine, no escape** — set `trust="quarantined"` on a node that reads untrusted content; it's denied write-capable isolation in-kernel, and any nodes it submits are coerced to quarantined too (no privilege escalation).
211
211
  - **Structured output** — set `output_schema` on a node; the runner instructs the agent, validates the result against the JSON-Schema subset, and re-runs once with the errors on mismatch. A node that never conforms fails (its dependents starve).
212
212
  - **Budget as signal** — with a `max_workflow_nodes` / `max_concurrent_subagents` quota installed, each spawned node's goal carries its remaining headroom so a coordinator can size its fan-out to fit.
@@ -193,7 +193,7 @@ A node's `kind` selects the control-flow shape; the same executor drives them al
193
193
 
194
194
  ### 0.2.11 capabilities
195
195
 
196
- - **Runtime fan-out** — give a node the `submit_workflow_nodes_tool` and its agent can append nodes to the live DAG mid-run (true loop-until-done; one verifier per claim it discovers). Recorded and replayed on `resume_workflow`.
196
+ - **Runtime fan-out** — give a node the `submit_workflow_nodes_tool` and its agent can append nodes to the live DAG mid-run (true loop-until-done; one verifier per claim it discovers). Recorded and replayed on `resume_workflow`. Governance rejection fails the submitting node instead of acknowledging work that was never appended.
197
197
  - **Quarantine, no escape** — set `trust="quarantined"` on a node that reads untrusted content; it's denied write-capable isolation in-kernel, and any nodes it submits are coerced to quarantined too (no privilege escalation).
198
198
  - **Structured output** — set `output_schema` on a node; the runner instructs the agent, validates the result against the JSON-Schema subset, and re-runs once with the errors on mismatch. A node that never conforms fails (its dependents starve).
199
199
  - **Budget as signal** — with a `max_workflow_nodes` / `max_concurrent_subagents` quota installed, each spawned node's goal carries its remaining headroom so a coordinator can size its fan-out to fit.
@@ -798,9 +798,9 @@ impl ContextManager {
798
798
  }
799
799
  Some(ToolSchema {
800
800
  name: CompactString::new(READ_RESULT_TOOL_NAME),
801
- description: "Re-read a tool result that was evicted from context (you see a \
802
- placeholder like '[…tool result spooled]' or a collapsed entry). \
803
- Pass the tool call's call_id; use offset/max_bytes to page through \
801
+ description: "Re-read the full content of a tool result that was truncated to save \
802
+ context (marked '[Output truncated:call the read_result tool …]'). \
803
+ Pass that marker's call_id; use offset/max_bytes to page through \
804
804
  large content."
805
805
  .to_string(),
806
806
  parameters: serde_json::json!({
@@ -256,7 +256,10 @@ fn normalize_turn_prefix(turns: &mut Vec<Message>) {
256
256
  /// preview, leaving a marker. Non-destructive — the full output stays in `partitions.history`;
257
257
  /// only the rendered copy shrinks. Un-collapse is boundary-only (P0-C): handles re-evaluate
258
258
  /// from Resident at the next compaction/renewal, never mid-generation (cache-safe monotonic).
259
- fn collapse_preview(output: &str) -> String {
259
+ /// Trained-convention truncation marker: says what was cut and how to get it back (the
260
+ /// `read_result` tool + this result's `call_id`), instead of kernel-internal vocabulary
261
+ /// ("projected out of view") the model has never seen in training.
262
+ fn collapse_preview(output: &str, call_id: &str) -> String {
260
263
  const PREVIEW_BYTES: usize = 160;
261
264
  let mut end = PREVIEW_BYTES.min(output.len());
262
265
  while end > 0 && !output.is_char_boundary(end) {
@@ -264,7 +267,7 @@ fn collapse_preview(output: &str) -> String {
264
267
  }
265
268
  let dropped = output.len().saturating_sub(end);
266
269
  format!(
267
- "{}…\n[collapsed: {dropped} chars projected out of view; full result retained in history]",
270
+ "{}…\n[Output truncated: {dropped} bytes omitted. Call the read_result tool with call_id \"{call_id}\" to re-read the full result.]",
268
271
  &output[..end]
269
272
  )
270
273
  }
@@ -322,7 +325,7 @@ fn project_message(msg: &Message, handles: &HandleTable) -> Option<Message> {
322
325
  changed = true;
323
326
  ContentPart::ToolResult {
324
327
  call_id: call_id.clone(),
325
- output: collapse_preview(output),
328
+ output: collapse_preview(output, call_id.as_str()),
326
329
  is_error: *is_error,
327
330
  }
328
331
  }
@@ -656,8 +659,10 @@ mod tests {
656
659
  _ => None,
657
660
  })
658
661
  .expect("tool result rendered");
659
- // Rendered copy is a preview; original full output is retained in history.
660
- assert!(rendered.contains("[collapsed:"));
662
+ // Rendered copy is a preview with the trained truncation phrasing + retrieval instruction.
663
+ assert!(rendered.contains("[Output truncated:"));
664
+ assert!(rendered.contains("read_result"));
665
+ assert!(rendered.contains("\"c1\""));
661
666
  assert!(rendered.len() < long.len());
662
667
  let stored = match &c.history.messages[0].content {
663
668
  Content::Parts(parts) => match &parts[0] {
@@ -700,7 +705,7 @@ mod tests {
700
705
  })
701
706
  .expect("tool result rendered");
702
707
  assert_eq!(rendered, body);
703
- assert!(!rendered.contains("[collapsed:"));
708
+ assert!(!rendered.contains("[Output truncated:"));
704
709
  }
705
710
 
706
711
  // ── P1-F: state-turn recency footer ───────────────────────────────────
@@ -5,6 +5,11 @@ use crate::types::message::{Content, ContentPart, Message};
5
5
  /// Deterministic six-slot summariser used before archived units page out.
6
6
  pub struct RuleSummarizer;
7
7
 
8
+ /// Items rendered per slot before an honest `(+N more)` line. Bounds each digest so the
9
+ /// task-state compression history can afford to keep MANY digests visible instead of a few
10
+ /// bloated ones.
11
+ const SLOT_ITEM_CAP: usize = 6;
12
+
8
13
  impl RuleSummarizer {
9
14
  /// Produce a structured summary whose char-approx token count never exceeds
10
15
  /// `max_tokens`. Slot order is the deterministic truncation priority.
@@ -95,10 +100,18 @@ impl RuleSummarizer {
95
100
  if values.is_empty() {
96
101
  push_line(&mut output, "- none", max_tokens, &engine);
97
102
  } else {
98
- for value in values {
103
+ for value in values.iter().take(SLOT_ITEM_CAP) {
99
104
  push_line(
100
105
  &mut output,
101
- &format!("- {}", compact(&value, 240)),
106
+ &format!("- {}", compact(value, 240)),
107
+ max_tokens,
108
+ &engine,
109
+ );
110
+ }
111
+ if values.len() > SLOT_ITEM_CAP {
112
+ push_line(
113
+ &mut output,
114
+ &format!("- (+{} more)", values.len() - SLOT_ITEM_CAP),
102
115
  max_tokens,
103
116
  &engine,
104
117
  );
@@ -126,6 +139,9 @@ struct SummarySlots {
126
139
 
127
140
  fn classify_text(text: &str, slots: &mut SummarySlots) {
128
141
  for statement in statements(text) {
142
+ if is_diff_noise(&statement) {
143
+ continue;
144
+ }
129
145
  let folded = statement.to_lowercase();
130
146
  if contains_any(
131
147
  &folded,
@@ -199,11 +215,45 @@ fn classify_text(text: &str, slots: &mut SummarySlots) {
199
215
  }
200
216
 
201
217
  fn statements(text: &str) -> Vec<String> {
202
- text.split(['\n', '.', '!', '?', ';', '。', '!', '?', ';'])
203
- .map(str::trim)
204
- .filter(|statement| !statement.is_empty())
205
- .map(|statement| compact(statement, 240))
206
- .collect()
218
+ let mut output = Vec::new();
219
+ let mut current = String::new();
220
+ let mut chars = text.chars().peekable();
221
+ while let Some(character) = chars.next() {
222
+ let boundary = match character {
223
+ '\n' | '!' | '?' | ';' | '。' | '!' | '?' | ';' => true,
224
+ // A '.' splits only at a sentence boundary (followed by whitespace or end of
225
+ // text). Splitting inside paths/versions shredded `src/auth.js` into useless
226
+ // `js b/src/auth` fragments that polluted every digest.
227
+ '.' => chars.peek().is_none_or(|next| next.is_whitespace()),
228
+ _ => false,
229
+ };
230
+ if boundary {
231
+ flush_statement(&mut current, &mut output);
232
+ } else {
233
+ current.push(character);
234
+ }
235
+ }
236
+ flush_statement(&mut current, &mut output);
237
+ output
238
+ }
239
+
240
+ fn flush_statement(current: &mut String, output: &mut Vec<String>) {
241
+ let statement = current.trim();
242
+ if !statement.is_empty() {
243
+ output.push(compact(statement, 240));
244
+ }
245
+ current.clear();
246
+ }
247
+
248
+ /// Structural diff/patch header lines carry no summarizable content — dropping them keeps
249
+ /// digests dense so more real process state survives a given summary budget.
250
+ fn is_diff_noise(statement: &str) -> bool {
251
+ let trimmed = statement.trim_start();
252
+ trimmed.starts_with("diff --git")
253
+ || trimmed.starts_with("+++")
254
+ || trimmed.starts_with("---")
255
+ || trimmed.starts_with("@@")
256
+ || trimmed.starts_with("index ")
207
257
  }
208
258
 
209
259
  fn contains_any(text: &str, markers: &[&str]) -> bool {
@@ -46,10 +46,19 @@ pub struct TaskState {
46
46
  /// so updating it never churns the prompt cache. Bounded + recency-ordered; newest last.
47
47
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
48
48
  pub recent_actions: Vec<String>,
49
- /// Append-only log of all compression events. Never overwritten.
49
+ /// Rolling log of compression events, newest last. Bounded at [`MAX_COMPRESSION_LOG`]
50
+ /// (oldest dropped past the cap, counted in `compression_log_dropped`).
50
51
  /// Rendered into systemVolatile so the model always sees compression history.
51
52
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
52
53
  pub compression_log: Vec<CompressionEntry>,
54
+ /// Entries dropped from `compression_log` past its cap — kept so the render stays honest
55
+ /// about how much history is no longer visible.
56
+ #[serde(default, skip_serializing_if = "u64_is_zero")]
57
+ pub compression_log_dropped: u64,
58
+ }
59
+
60
+ fn u64_is_zero(value: &u64) -> bool {
61
+ *value == 0
53
62
  }
54
63
 
55
64
  #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -73,6 +82,9 @@ pub const MAX_DIRECTIVES: usize = 8;
73
82
  /// Maximum recent action-turns retained for the recency footer (bounded ring).
74
83
  pub const MAX_RECENT_ACTIONS: usize = 6;
75
84
 
85
+ /// Compression-log entries retained in state (rolling window; render shows the last 3).
86
+ pub const MAX_COMPRESSION_LOG: usize = 64;
87
+
76
88
  /// Partial update applied by the SDK or via `update_plan` meta-tool.
77
89
  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
78
90
  pub struct TaskUpdate {
@@ -136,7 +148,14 @@ impl TaskState {
136
148
  lines.push(format!("scratchpad: {}", self.scratchpad));
137
149
  }
138
150
 
139
- // Render the most recent compression events (cap at 3 to limit token cost).
151
+ // Render only the most recent compression events (fixed cap of 3). A wider budgeted
152
+ // window was tried and withdrawn: live A/B at n≤4 (12-PR review @ 2048, DeepSeek)
153
+ // could not distinguish it from provider drift — a control run on the unmodified
154
+ // kernel showed the same failures — and a window full of old `tool X args` digest
155
+ // lines is plausible re-execution bait. Absent replay-lab evidence FOR widening, the
156
+ // proven shape stays. Older digests remain in `compression_log` (bounded at
157
+ // [`MAX_COMPRESSION_LOG`]); making their *content* durably useful to the model is
158
+ // semantic-summary (P2) territory, not a bigger raw window.
140
159
  if !self.compression_log.is_empty() {
141
160
  lines.push("compression_history:".to_string());
142
161
  let start = self.compression_log.len().saturating_sub(3);
@@ -184,12 +203,18 @@ impl TaskState {
184
203
  }
185
204
  }
186
205
 
187
- /// Append a compression event to the log. Never overwrites existing entries.
206
+ /// Append a compression event to the log (bounded at [`MAX_COMPRESSION_LOG`]; the oldest
207
+ /// entries are dropped past the cap and counted so the render can report them).
188
208
  pub fn log_compression(&mut self, action: &str, summary: String) {
189
209
  self.compression_log.push(CompressionEntry {
190
210
  action: action.to_string(),
191
211
  summary,
192
212
  });
213
+ if self.compression_log.len() > MAX_COMPRESSION_LOG {
214
+ let overflow = self.compression_log.len() - MAX_COMPRESSION_LOG;
215
+ self.compression_log.drain(0..overflow);
216
+ self.compression_log_dropped += overflow as u64;
217
+ }
193
218
  }
194
219
 
195
220
  pub fn apply(&mut self, update: TaskUpdate) {
@@ -349,6 +374,33 @@ mod tests {
349
374
  assert_eq!(ts.directives.len(), MAX_DIRECTIVES);
350
375
  }
351
376
 
377
+ #[test]
378
+ fn compression_render_shows_exactly_the_last_three_digests() {
379
+ // Pin the 3-entry window (see format_compact for why widening was withdrawn) so a
380
+ // future widening must re-justify itself with replay-lab evidence.
381
+ let mut ts = TaskState::default();
382
+ ts.goal = "review PRs".into();
383
+ for n in 1..=10 {
384
+ ts.log_compression("auto_compact", format!("digest {n}"));
385
+ }
386
+ let rendered = ts.format_compact();
387
+ assert!(!rendered.contains("digest 7\n"));
388
+ assert!(rendered.contains("digest 8"));
389
+ assert!(rendered.contains("digest 9"));
390
+ assert!(rendered.contains("digest 10"));
391
+ }
392
+
393
+ #[test]
394
+ fn compression_log_is_bounded_and_counts_drops() {
395
+ let mut ts = TaskState::default();
396
+ for n in 0..(MAX_COMPRESSION_LOG + 5) {
397
+ ts.log_compression("micro_compact", format!("d{n}"));
398
+ }
399
+ assert_eq!(ts.compression_log.len(), MAX_COMPRESSION_LOG);
400
+ assert_eq!(ts.compression_log_dropped, 5);
401
+ assert_eq!(ts.compression_log[0].summary, "d5");
402
+ }
403
+
352
404
  #[test]
353
405
  fn apply_updates_fields() {
354
406
  let mut ts = TaskState::default();
@@ -6,6 +6,7 @@ use std::ops::Range;
6
6
 
7
7
  use super::token_engine::ContextTokenEngine;
8
8
  use super::units::unit_boundaries;
9
+ use crate::lexical::{overlap_count, terms};
9
10
  use crate::types::message::{Content, ContentPart, Message};
10
11
 
11
12
  pub struct UtilitySelectionContext<'a> {
@@ -215,35 +216,6 @@ fn unit_text(messages: &[Message]) -> String {
215
216
  parts.join("\n")
216
217
  }
217
218
 
218
- fn terms(text: &str) -> BTreeSet<String> {
219
- let mut output = BTreeSet::new();
220
- let mut ascii = String::new();
221
- for character in text.chars().flat_map(char::to_lowercase) {
222
- if character.is_ascii_alphanumeric() || matches!(character, '_' | '-' | '/' | '.' | ':') {
223
- ascii.push(character);
224
- continue;
225
- }
226
- if !ascii.is_empty() {
227
- if ascii.chars().count() > 1 {
228
- output.insert(std::mem::take(&mut ascii));
229
- } else {
230
- ascii.clear();
231
- }
232
- }
233
- if !character.is_whitespace() && !character.is_ascii_punctuation() {
234
- output.insert(character.to_string());
235
- }
236
- }
237
- if ascii.chars().count() > 1 {
238
- output.insert(ascii);
239
- }
240
- output
241
- }
242
-
243
- fn overlap_count(left: &BTreeSet<String>, right: &BTreeSet<String>) -> u32 {
244
- left.intersection(right).count() as u32
245
- }
246
-
247
219
  fn contains_folded(text: &str, pattern: &str) -> bool {
248
220
  !pattern.trim().is_empty() && text.to_lowercase().contains(&pattern.to_lowercase())
249
221
  }
@@ -367,6 +339,41 @@ mod tests {
367
339
  assert_eq!(plan.retained_tokens, 40);
368
340
  }
369
341
 
342
+ #[test]
343
+ fn chinese_directive_dependency_requires_bigram_overlap_not_shared_characters() {
344
+ // Under the old per-character CJK vocabulary, any Chinese unit sharing two
345
+ // common characters (中/文/回…) with an active directive was marked mandatory,
346
+ // so compression could never archive unrelated Chinese history.
347
+ let mut unrelated = Message::assistant("我们在文中回顾了天气");
348
+ unrelated.token_count = Some(30);
349
+ let mut on_topic = Message::user("已按要求保持中文回答");
350
+ on_topic.token_count = Some(30);
351
+ let mut recent = Message::user("recent");
352
+ recent.token_count = Some(10);
353
+ let messages = vec![unrelated, on_topic, recent];
354
+ let plan = plan_utility_archive(
355
+ &messages,
356
+ 70,
357
+ 10,
358
+ 1,
359
+ &ContextTokenEngine::char_approx(),
360
+ &UtilitySelectionContext {
361
+ goal: "",
362
+ criteria: &[],
363
+ preserved_refs: &[],
364
+ active_directives: &["必须用中文回答".into()],
365
+ },
366
+ );
367
+ assert!(
368
+ !plan.scores[0].mandatory,
369
+ "unrelated Chinese text must not bind to the directive"
370
+ );
371
+ assert!(
372
+ plan.scores[1].mandatory,
373
+ "text restating the directive must stay mandatory"
374
+ );
375
+ }
376
+
370
377
  #[test]
371
378
  fn preserved_ref_keeps_complete_tool_unit() {
372
379
  let mut call = Message::assistant("read artifact");
@@ -0,0 +1,178 @@
1
+ //! Shared lexical vocabulary for deterministic in-kernel value scoring.
2
+ //!
3
+ //! Compression utility (goal overlap, directive dependency) and memory curation
4
+ //! (fuzzy dedupe) must speak ONE term vocabulary — and it must discriminate for
5
+ //! CJK text, where there is no whitespace between words: per-character unigrams
6
+ //! make every Chinese unit overlap every Chinese goal, and whitespace tokens
7
+ //! make a whole Chinese sentence a single token. Terms here are lowercased word
8
+ //! runs plus Han character bigrams, matching the host-side recall rankers
9
+ //! (node `memory/ranking.ts`, python `memory/ranking.py`) so knowledge
10
+ //! residency, compression, and memory recall share one notion of "relevant".
11
+
12
+ use std::collections::BTreeSet;
13
+
14
+ /// Deterministic term set of `text`.
15
+ ///
16
+ /// - ASCII word runs (`[a-z0-9_\-/.:]`, lowercased) of more than one char are
17
+ /// one term each — call ids, paths, and URLs stay whole.
18
+ /// - Non-ASCII alphanumeric runs of more than one char are one term each; a
19
+ /// lone non-ASCII char is kept as its own term.
20
+ /// - Runs containing Han chars additionally emit every adjacent-char bigram,
21
+ /// the standard whitespace-free segmentation proxy.
22
+ ///
23
+ /// Punctuation (ASCII and fullwidth alike) and whitespace never become terms.
24
+ pub fn terms(text: &str) -> BTreeSet<String> {
25
+ let mut output = BTreeSet::new();
26
+ let mut ascii_run = String::new();
27
+ let mut wide_run: Vec<char> = Vec::new();
28
+ for character in text.chars().flat_map(char::to_lowercase) {
29
+ if character.is_ascii_alphanumeric() || matches!(character, '_' | '-' | '/' | '.' | ':') {
30
+ flush_wide(&mut wide_run, &mut output);
31
+ ascii_run.push(character);
32
+ } else if !character.is_ascii() && character.is_alphanumeric() {
33
+ flush_ascii(&mut ascii_run, &mut output);
34
+ wide_run.push(character);
35
+ } else {
36
+ flush_ascii(&mut ascii_run, &mut output);
37
+ flush_wide(&mut wide_run, &mut output);
38
+ }
39
+ }
40
+ flush_ascii(&mut ascii_run, &mut output);
41
+ flush_wide(&mut wide_run, &mut output);
42
+ output
43
+ }
44
+
45
+ /// Number of terms shared by both sets.
46
+ pub fn overlap_count(left: &BTreeSet<String>, right: &BTreeSet<String>) -> u32 {
47
+ left.intersection(right).count() as u32
48
+ }
49
+
50
+ /// Term-set Jaccard similarity over the shared vocabulary.
51
+ ///
52
+ /// Inputs whose combined vocabulary is empty score 0.0 — no lexical evidence is
53
+ /// treated as "not a duplicate", so degenerate content fails open (both records
54
+ /// are kept) rather than merging on nothing.
55
+ pub fn jaccard(left: &str, right: &str) -> f64 {
56
+ let left = terms(left);
57
+ let right = terms(right);
58
+ let union = left.union(&right).count();
59
+ if union == 0 {
60
+ return 0.0;
61
+ }
62
+ left.intersection(&right).count() as f64 / union as f64
63
+ }
64
+
65
+ fn flush_ascii(run: &mut String, output: &mut BTreeSet<String>) {
66
+ // All-ASCII, so byte length equals char count.
67
+ if run.len() > 1 {
68
+ output.insert(std::mem::take(run));
69
+ } else {
70
+ run.clear();
71
+ }
72
+ }
73
+
74
+ fn flush_wide(run: &mut Vec<char>, output: &mut BTreeSet<String>) {
75
+ match run.len() {
76
+ 0 => return,
77
+ 1 => {
78
+ output.insert(run[0].to_string());
79
+ }
80
+ _ => {
81
+ output.insert(run.iter().collect());
82
+ if run.iter().copied().any(is_han) {
83
+ for pair in run.windows(2) {
84
+ output.insert(pair.iter().collect());
85
+ }
86
+ }
87
+ }
88
+ }
89
+ run.clear();
90
+ }
91
+
92
+ /// Han ranges mirrored from the host rankers (BMP unified + ext A, compat, ext B+).
93
+ fn is_han(character: char) -> bool {
94
+ matches!(
95
+ u32::from(character),
96
+ 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xF900..=0xFAFF | 0x20000..=0x3134F
97
+ )
98
+ }
99
+
100
+ #[cfg(test)]
101
+ mod tests {
102
+ use super::*;
103
+
104
+ #[test]
105
+ fn ascii_words_paths_and_ids_stay_whole() {
106
+ let output = terms("Read src/main.rs via call_abc-123, then STOP.");
107
+ assert!(output.contains("read"));
108
+ assert!(output.contains("src/main.rs"));
109
+ assert!(output.contains("call_abc-123"));
110
+ // Single ASCII chars never become terms; punctuation is stripped.
111
+ assert!(!output.contains("a"));
112
+ assert!(!output.contains(","));
113
+ }
114
+
115
+ #[test]
116
+ fn han_runs_emit_bigrams_not_unigrams() {
117
+ let output = terms("实现用户登录");
118
+ assert!(output.contains("实现"));
119
+ assert!(output.contains("用户"));
120
+ assert!(output.contains("户登"));
121
+ assert!(output.contains("实现用户登录"));
122
+ assert!(!output.contains("实"), "unigram noise must be gone");
123
+ }
124
+
125
+ #[test]
126
+ fn chinese_goal_overlap_discriminates() {
127
+ let goal = terms("实现用户登录功能");
128
+ let on_topic = terms("已完成用户登录表单");
129
+ let off_topic = terms("今天天气很好我们去公园");
130
+ assert!(overlap_count(&goal, &on_topic) >= 2);
131
+ assert_eq!(overlap_count(&goal, &off_topic), 0);
132
+ }
133
+
134
+ #[test]
135
+ fn fullwidth_punctuation_is_not_a_term() {
136
+ let output = terms("完成了。下一步:测试!");
137
+ assert!(!output.contains("。"));
138
+ assert!(!output.contains(":"));
139
+ assert!(output.contains("完成"));
140
+ }
141
+
142
+ #[test]
143
+ fn lone_wide_char_is_kept() {
144
+ assert!(terms("改 a").contains("改"));
145
+ }
146
+
147
+ #[test]
148
+ fn mixed_ascii_and_han_split_into_both_vocabularies() {
149
+ let output = terms("部署v2服务到prod环境");
150
+ assert!(output.contains("v2"));
151
+ assert!(output.contains("prod"));
152
+ assert!(output.contains("部署"));
153
+ assert!(output.contains("服务"));
154
+ assert!(output.contains("环境"));
155
+ }
156
+
157
+ #[test]
158
+ fn jaccard_near_duplicate_chinese_scores_high() {
159
+ let near = jaccard("用户偏好深色模式界面", "用户偏好浅色模式界面");
160
+ let unrelated = jaccard("用户偏好深色模式界面", "周五之前完成部署上线");
161
+ assert!(near > 0.5, "near-duplicates must be detectable: {near}");
162
+ assert!(unrelated < 0.2, "unrelated must stay low: {unrelated}");
163
+ }
164
+
165
+ #[test]
166
+ fn jaccard_identical_english_is_one_and_empty_vocabulary_is_zero() {
167
+ assert_eq!(jaccard("prefer cargo nextest", "prefer cargo nextest"), 1.0);
168
+ assert_eq!(jaccard("", ""), 0.0);
169
+ }
170
+
171
+ #[test]
172
+ fn non_han_scripts_keep_whole_words_without_bigrams() {
173
+ let output = terms("привет мир");
174
+ assert!(output.contains("привет"));
175
+ assert!(output.contains("мир"));
176
+ assert!(!output.contains("пр"));
177
+ }
178
+ }
@@ -28,6 +28,7 @@ pub mod benchmark;
28
28
  pub mod context;
29
29
  pub mod governance;
30
30
  pub mod harness;
31
+ pub mod lexical;
31
32
  pub mod memory;
32
33
  pub mod mm;
33
34
  pub mod orchestration;