cluxion-agentplugin-preprocessing 0.3.18__tar.gz → 0.3.20__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 (97) hide show
  1. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/pyproject.toml +1 -1
  3. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/plugin.yaml +1 -1
  4. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/hybrid_forget.py +13 -2
  5. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/llm_compress.py +14 -2
  6. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/preprocess.py +2 -2
  7. cluxion_agentplugin_preprocessing-0.3.20/tests/runtime/test_bin_resolution.py +103 -0
  8. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_context_compress_llm_forget.py +12 -2
  9. cluxion_agentplugin_preprocessing-0.3.20/tests/runtime/test_estimate_tokens.py +52 -0
  10. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/.github/profile/README.md +0 -0
  11. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/.gitignore +0 -0
  12. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/Docs/README.md +0 -0
  13. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/LICENSE +0 -0
  14. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/README.md +0 -0
  15. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/adapters/claude/.claude-plugin/plugin.json +0 -0
  16. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/adapters/claude/skills/preprocess/SKILL.md +0 -0
  17. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/adapters/codex/config-snippet.toml +0 -0
  18. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/README.md +0 -0
  19. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/architecture.md +0 -0
  20. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/harness-logic.md +0 -0
  21. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/honesty-preprocessing.md +0 -0
  22. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/install-and-operations.md +0 -0
  23. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/cluxion-Docs/security.md +0 -0
  24. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/Cargo.lock +0 -0
  25. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/Cargo.toml +0 -0
  26. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/pyproject.toml +0 -0
  27. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/context.rs +0 -0
  28. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/dispatch.rs +0 -0
  29. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/guard.rs +0 -0
  30. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/lib.rs +0 -0
  31. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/main.rs +0 -0
  32. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/queue.rs +0 -0
  33. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/rust/cluxion_queue/src/types.rs +0 -0
  34. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/__init__.py +0 -0
  35. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
  36. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/doctor/__init__.py +0 -0
  37. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/doctor/catalog.json +0 -0
  38. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/doctor/framework.py +0 -0
  39. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/doctor/probes.py +0 -0
  40. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/guard_watch.py +0 -0
  41. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
  42. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/plugin.py +0 -0
  43. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/runner.py +0 -0
  44. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
  45. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/__init__.py +0 -0
  46. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/__main__.py +0 -0
  47. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/adapters/__init__.py +0 -0
  48. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/adapters/contract.py +0 -0
  49. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/adapters/grok_build.py +0 -0
  50. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/adapters/hermes.py +0 -0
  51. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/adapters/spec.py +0 -0
  52. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/bootstrap.py +0 -0
  53. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/cli.py +0 -0
  54. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/__init__.py +0 -0
  55. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/clarification.py +0 -0
  56. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/context_compress.py +0 -0
  57. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/dispatch_store.py +0 -0
  58. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/harness.py +0 -0
  59. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/intent.py +0 -0
  60. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/ledger.py +0 -0
  61. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/ledger_codec.py +0 -0
  62. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/plan_codec.py +0 -0
  63. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/types.py +0 -0
  64. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/core/work_queue.py +0 -0
  65. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/guard_daemon_host.py +0 -0
  66. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/models/__init__.py +0 -0
  67. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/models/supervisor.py +0 -0
  68. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
  69. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/resources/__init__.py +0 -0
  70. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
  71. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/resources/py_queue.py +0 -0
  72. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/resources/queue_bridge.py +0 -0
  73. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
  74. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/web/__init__.py +0 -0
  75. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/src/cluxion_runtime/web/browser_bridge.py +0 -0
  76. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_auto_compress_middleware.py +0 -0
  77. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_browser_bridge.py +0 -0
  78. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_clarification.py +0 -0
  79. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
  80. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_context_compress.py +0 -0
  81. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_contract.py +0 -0
  82. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_dispatch_store.py +0 -0
  83. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_guard.py +0 -0
  84. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_guard_daemon_host.py +0 -0
  85. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_ledger.py +0 -0
  86. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_py_queue_concurrency.py +0 -0
  87. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_queue_backends.py +0 -0
  88. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_runtime_adapter_cli.py +0 -0
  89. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_rust_queue.py +0 -0
  90. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/runtime/test_supervisor.py +0 -0
  91. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_bootstrap.py +0 -0
  92. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_doctor.py +0 -0
  93. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_guard_watch.py +0 -0
  94. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_hermes_config.py +0 -0
  95. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_packaging_policy.py +0 -0
  96. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_plugin.py +0 -0
  97. {cluxion_agentplugin_preprocessing-0.3.18 → cluxion_agentplugin_preprocessing-0.3.20}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cluxion-agentplugin-preprocessing
3
- Version: 0.3.18
3
+ Version: 0.3.20
4
4
  Summary: Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff.
5
5
  Project-URL: Homepage, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
6
6
  Project-URL: Repository, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cluxion-agentplugin-preprocessing"
7
- version = "0.3.18"
7
+ version = "0.3.20"
8
8
  description = "Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,5 @@
1
1
  name: cluxion-agentplugin-preprocessing
2
- version: 0.3.18
2
+ version: 0.3.20
3
3
  description: "Universal agent preprocessing plugin: honesty contracts, clarification, Rust work queue, resource-aware harness handoff. Connected AI calls cluxion tools; plugin does not own models."
4
4
  author: cluxion
5
5
  kind: standalone
@@ -6,9 +6,11 @@ is removed permanently. Stage 3 is Python-only; the Rust mirror does not run thi
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import os
9
10
  import re
10
11
  import shutil
11
12
  import subprocess
13
+ import sys
12
14
  import uuid
13
15
  from dataclasses import dataclass
14
16
  from typing import TYPE_CHECKING, Protocol
@@ -18,7 +20,14 @@ from cluxion_runtime.core.preprocess import estimate_tokens
18
20
  if TYPE_CHECKING:
19
21
  from collections.abc import Sequence
20
22
 
21
- _FORGETFORGE_BIN = "forgetforge"
23
+ def _resolve_bin(name: str) -> str:
24
+ candidate = os.path.join(os.path.dirname(sys.executable), name)
25
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
26
+ return candidate
27
+ return shutil.which(name) or name
28
+
29
+
30
+ _FORGETFORGE_BIN = _resolve_bin("forgetforge")
22
31
  _JUNK_MARKERS = ("[cluxion: duplicate", "[cluxion digest]")
23
32
  _IMPORTANCE_KEYWORDS = (
24
33
  "decide",
@@ -75,7 +84,9 @@ class ForgetResult:
75
84
 
76
85
 
77
86
  def forgetforge_available() -> bool:
78
- return shutil.which(_FORGETFORGE_BIN) is not None
87
+ return (os.path.isfile(_FORGETFORGE_BIN) and os.access(_FORGETFORGE_BIN, os.X_OK)) or (
88
+ shutil.which(_FORGETFORGE_BIN) is not None
89
+ )
79
90
 
80
91
 
81
92
  def apply_hybrid_forget(
@@ -13,13 +13,23 @@ import os
13
13
  import re
14
14
  import shutil
15
15
  import subprocess
16
+ import sys
16
17
  from typing import TYPE_CHECKING, Protocol
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from collections.abc import Mapping, Sequence
20
21
 
21
22
  DEFAULT_TIMEOUT_S = 120.0
22
- _HERMES_BIN = "hermes"
23
+
24
+
25
+ def _resolve_bin(name: str) -> str:
26
+ candidate = os.path.join(os.path.dirname(sys.executable), name)
27
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
28
+ return candidate
29
+ return shutil.which(name) or name
30
+
31
+
32
+ _HERMES_BIN = _resolve_bin("hermes")
23
33
  logger = logging.getLogger(__name__)
24
34
 
25
35
  _SUMMARY_INSTRUCTIONS = (
@@ -54,7 +64,9 @@ class _MessageLike(Protocol):
54
64
 
55
65
 
56
66
  def hermes_available() -> bool:
57
- return shutil.which(_HERMES_BIN) is not None
67
+ return (os.path.isfile(_HERMES_BIN) and os.access(_HERMES_BIN, os.X_OK)) or (
68
+ shutil.which(_HERMES_BIN) is not None
69
+ )
58
70
 
59
71
 
60
72
  def summarize_messages(
@@ -172,8 +172,8 @@ def preprocess_work(
172
172
 
173
173
  def estimate_tokens(text: str) -> int:
174
174
  """Estimate tokens conservatively without depending on the old Cluxion OS package."""
175
- cjk = sum(1 for ch in text if ord(ch) > 127)
176
- ascii_chars = max(0, len(text) - cjk)
175
+ ascii_chars = len(text.encode("ascii", "ignore"))
176
+ cjk = len(text) - ascii_chars
177
177
  return max(1, cjk + ascii_chars // 4)
178
178
 
179
179
 
@@ -0,0 +1,103 @@
1
+ """PATH-independent resolution for forgetforge and hermes console scripts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import stat
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from cluxion_runtime.core import hybrid_forget, llm_compress
13
+
14
+
15
+ def _write_executable(path: Path) -> None:
16
+ path.write_text("#!/bin/sh\necho ok\n")
17
+ path.chmod(path.stat().st_mode | stat.S_IXUSR)
18
+
19
+
20
+ @pytest.fixture
21
+ def isolated_bin_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
22
+ """Temp venv bin dir; PATH excludes it so only sys.executable dir can resolve bins."""
23
+ bin_dir = tmp_path / "bin"
24
+ bin_dir.mkdir()
25
+ monkeypatch.setenv("PATH", str(tmp_path / "empty"))
26
+ monkeypatch.setattr(sys, "executable", str(bin_dir / "python"))
27
+ return bin_dir
28
+
29
+
30
+ def test_forgetforge_available_via_venv_bin_without_path(
31
+ isolated_bin_dir: Path,
32
+ monkeypatch: pytest.MonkeyPatch,
33
+ ) -> None:
34
+ forgetforge = isolated_bin_dir / "forgetforge"
35
+ _write_executable(forgetforge)
36
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
37
+
38
+ assert hybrid_forget.forgetforge_available() is True
39
+ assert os.path.isabs(hybrid_forget._FORGETFORGE_BIN)
40
+ assert str(forgetforge) == hybrid_forget._FORGETFORGE_BIN
41
+
42
+
43
+ def test_forgetforge_unavailable_when_missing_everywhere(
44
+ isolated_bin_dir: Path,
45
+ monkeypatch: pytest.MonkeyPatch,
46
+ ) -> None:
47
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
48
+ assert hybrid_forget.forgetforge_available() is False
49
+
50
+
51
+ def test_forgetforge_available_via_path_fallback(
52
+ tmp_path: Path,
53
+ monkeypatch: pytest.MonkeyPatch,
54
+ ) -> None:
55
+ bin_dir = tmp_path / "on-path"
56
+ bin_dir.mkdir()
57
+ forgetforge = bin_dir / "forgetforge"
58
+ _write_executable(forgetforge)
59
+
60
+ monkeypatch.setenv("PATH", str(bin_dir))
61
+ monkeypatch.setattr(sys, "executable", str(tmp_path / "venv" / "bin" / "python"))
62
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
63
+
64
+ assert hybrid_forget.forgetforge_available() is True
65
+ assert str(forgetforge) == hybrid_forget._FORGETFORGE_BIN
66
+
67
+
68
+ def test_hermes_available_via_venv_bin_without_path(
69
+ isolated_bin_dir: Path,
70
+ monkeypatch: pytest.MonkeyPatch,
71
+ ) -> None:
72
+ hermes = isolated_bin_dir / "hermes"
73
+ _write_executable(hermes)
74
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
75
+
76
+ assert llm_compress.hermes_available() is True
77
+ assert os.path.isabs(llm_compress._HERMES_BIN)
78
+ assert str(hermes) == llm_compress._HERMES_BIN
79
+
80
+
81
+ def test_hermes_unavailable_when_missing_everywhere(
82
+ isolated_bin_dir: Path,
83
+ monkeypatch: pytest.MonkeyPatch,
84
+ ) -> None:
85
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
86
+ assert llm_compress.hermes_available() is False
87
+
88
+
89
+ def test_hermes_available_via_path_fallback(
90
+ tmp_path: Path,
91
+ monkeypatch: pytest.MonkeyPatch,
92
+ ) -> None:
93
+ bin_dir = tmp_path / "on-path"
94
+ bin_dir.mkdir()
95
+ hermes = bin_dir / "hermes"
96
+ _write_executable(hermes)
97
+
98
+ monkeypatch.setenv("PATH", str(bin_dir))
99
+ monkeypatch.setattr(sys, "executable", str(tmp_path / "venv" / "bin" / "python"))
100
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
101
+
102
+ assert llm_compress.hermes_available() is True
103
+ assert str(hermes) == llm_compress._HERMES_BIN
@@ -9,7 +9,11 @@ import pytest
9
9
 
10
10
  from cluxion_runtime.core import context_compress
11
11
  from cluxion_runtime.core.context_compress import _Msg
12
- from cluxion_runtime.core.hybrid_forget import _cold_demote, apply_hybrid_forget
12
+ from cluxion_runtime.core.hybrid_forget import (
13
+ _FORGETFORGE_BIN,
14
+ _cold_demote,
15
+ apply_hybrid_forget,
16
+ )
13
17
  from cluxion_runtime.core.preprocess import estimate_tokens
14
18
 
15
19
 
@@ -95,7 +99,13 @@ def test_cold_demote_returns_true_on_successful_store_only(monkeypatch) -> None:
95
99
 
96
100
  assert _cold_demote("recoverable content", "sess-abc123") is True
97
101
  assert len(calls) == 1
98
- assert calls[0] == ["forgetforge", "store", "sess-abc123", "--content", "recoverable content"]
102
+ assert calls[0] == [
103
+ _FORGETFORGE_BIN,
104
+ "store",
105
+ "sess-abc123",
106
+ "--content",
107
+ "recoverable content",
108
+ ]
99
109
 
100
110
 
101
111
  def test_cold_demote_returns_false_when_store_fails(monkeypatch) -> None:
@@ -0,0 +1,52 @@
1
+ """Byte-identical parity tests for estimate_tokens optimization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random
6
+
7
+ from cluxion_runtime.core.preprocess import estimate_tokens
8
+
9
+
10
+ def _estimate_tokens_legacy(text: str) -> int:
11
+ cjk = sum(1 for ch in text if ord(ch) > 127)
12
+ ascii_chars = max(0, len(text) - cjk)
13
+ return max(1, cjk + ascii_chars // 4)
14
+
15
+
16
+ def test_estimate_tokens_matches_legacy_fixed_cases() -> None:
17
+ cases = [
18
+ ("", 1),
19
+ ("abcd", 1),
20
+ ("한글텍스트", 5),
21
+ ("ab한", 1),
22
+ ]
23
+ for text, expected in cases:
24
+ assert estimate_tokens(text) == expected
25
+ assert estimate_tokens(text) == _estimate_tokens_legacy(text)
26
+
27
+
28
+ def test_estimate_tokens_matches_legacy_random_unicode() -> None:
29
+ rng = random.Random(0)
30
+ chars: list[str] = []
31
+ for _ in range(10_000):
32
+ roll = rng.random()
33
+ if roll < 0.4:
34
+ chars.append(chr(rng.randint(0, 127)))
35
+ elif roll < 0.7:
36
+ chars.append(chr(rng.randint(128, 0xFFFF)))
37
+ else:
38
+ chars.append(chr(rng.randint(0x10000, 0x10FFFF)))
39
+ text = "".join(chars)
40
+ assert estimate_tokens(text) == _estimate_tokens_legacy(text)
41
+
42
+
43
+ def test_estimate_tokens_lone_surrogate_does_not_raise() -> None:
44
+ text = "prefix\ud800suffix"
45
+ assert estimate_tokens(text) == _estimate_tokens_legacy(text)
46
+
47
+
48
+ def test_estimate_tokens_legacy_reference_cases() -> None:
49
+ assert _estimate_tokens_legacy("") == 1
50
+ assert _estimate_tokens_legacy("abcd") == 1
51
+ assert _estimate_tokens_legacy("한글텍스트") == 5
52
+ assert _estimate_tokens_legacy("ab한") == 1