cluxion-agentplugin-supercoder 0.2.11__tar.gz → 0.2.13__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 (64) hide show
  1. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/plugin.yaml +1 -1
  3. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/pyproject.toml +1 -1
  4. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/hash_patch.py +18 -2
  5. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_hash_patch.py +129 -1
  6. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/uv.lock +1 -1
  7. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/.github/workflows/ci.yml +0 -0
  8. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/.github/workflows/publish.yml +0 -0
  9. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/.gitignore +0 -0
  10. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/ARCHITECTURE.md +0 -0
  11. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/README.md +0 -0
  12. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/agent-surfaces.md +0 -0
  13. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/architecture.md +0 -0
  14. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/capabilities.md +0 -0
  15. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/design.md +0 -0
  16. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/installation.md +0 -0
  17. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/rust-architecture.md +0 -0
  18. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/Docs/tools.md +0 -0
  19. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/LICENSE +0 -0
  20. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/README.md +0 -0
  21. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/__init__.py +0 -0
  22. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/adapters/claude/.claude-plugin/plugin.json +0 -0
  23. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/adapters/claude/skills/supercoder/SKILL.md +0 -0
  24. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/adapters/codex/config-snippet.toml +0 -0
  25. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/adapters/hermes/README.md +0 -0
  26. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/Cargo.lock +0 -0
  27. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/Cargo.toml +0 -0
  28. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/pyproject.toml +0 -0
  29. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/src/lib.rs +0 -0
  30. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/src/main.rs +0 -0
  31. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/src/outline.rs +0 -0
  32. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/rust/supercoder_index/src/syntax.rs +0 -0
  33. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/scripts/repack_native_wheel.py +0 -0
  34. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/__init__.py +0 -0
  35. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/cli.py +0 -0
  36. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/cursor.py +0 -0
  37. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/line_budget.py +0 -0
  38. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/lint_gate.py +0 -0
  39. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/queue.py +0 -0
  40. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/repo_map.py +0 -0
  41. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/retry_loop.py +0 -0
  42. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/safety.py +0 -0
  43. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/syntax_gate.py +0 -0
  44. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/core/test_gate.py +0 -0
  45. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/doctor/__init__.py +0 -0
  46. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/doctor/catalog.json +0 -0
  47. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/doctor/framework.py +0 -0
  48. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/doctor/probes.py +0 -0
  49. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/plugin.py +0 -0
  50. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/runner.py +0 -0
  51. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/rust_bridge.py +0 -0
  52. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/src/cluxion_agentplugin_supercoder/schemas.py +0 -0
  53. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_cursor.py +0 -0
  54. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_doctor.py +0 -0
  55. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_line_budget.py +0 -0
  56. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_lint_gate.py +0 -0
  57. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_plugin.py +0 -0
  58. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_queue.py +0 -0
  59. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_repo_map.py +0 -0
  60. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_retry_loop.py +0 -0
  61. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_rust_bridge.py +0 -0
  62. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_safety.py +0 -0
  63. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_syntax_gate.py +0 -0
  64. {cluxion_agentplugin_supercoder-0.2.11 → cluxion_agentplugin_supercoder-0.2.13}/tests/test_test_gate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cluxion-agentplugin-supercoder
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Summary: Universal agent coding harness plugin: cursor logic, safe patch, line budget, Rust index, test gates.
5
5
  Project-URL: Homepage, https://github.com/cluxion/cluxion-Agentplugin-supercoder
6
6
  Project-URL: Repository, https://github.com/cluxion/cluxion-Agentplugin-supercoder
@@ -1,5 +1,5 @@
1
1
  name: cluxion-agentplugin-supercoder
2
- version: 0.2.11
2
+ version: 0.2.12
3
3
  description: "Universal agent coding harness: cursor, safe patch, line budget, Rust index."
4
4
  author: cluxion
5
5
  kind: standalone
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cluxion-agentplugin-supercoder"
7
- version = "0.2.11"
7
+ version = "0.2.13"
8
8
  description = "Universal agent coding harness plugin: cursor logic, safe patch, line budget, Rust index, test gates."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -17,6 +17,7 @@ except ImportError:
17
17
  fcntl = None # type: ignore[assignment]
18
18
 
19
19
  DEFAULT_FUZZY_THRESHOLD = 0.86
20
+ AMBIGUITY_MARGIN = 0.015
20
21
  MAX_CONTEXT_SCAN = 8
21
22
  MAX_LINE_DRIFT = 2
22
23
 
@@ -76,6 +77,8 @@ def apply_patch(
76
77
  expected_file_hash: str = "",
77
78
  fuzzy_threshold: float = DEFAULT_FUZZY_THRESHOLD,
78
79
  ) -> PatchResult:
80
+ if not old_text:
81
+ return _failed(str(path), "empty_old_text", expected_file_hash, "old_text must be non-empty")
79
82
  if not path.exists():
80
83
  return _failed(str(path), "missing_file", expected_file_hash, "file not found")
81
84
  with _exclusive_lock(path):
@@ -119,6 +122,8 @@ def _normalize_hash(value: str) -> str:
119
122
 
120
123
 
121
124
  def _exact_spans(text: str, needle: str) -> list[tuple[int, int]]:
125
+ if not needle:
126
+ return []
122
127
  spans: list[tuple[int, int]] = []
123
128
  offset = 0
124
129
  while True:
@@ -130,6 +135,8 @@ def _exact_spans(text: str, needle: str) -> list[tuple[int, int]]:
130
135
 
131
136
 
132
137
  def _candidate_spans(text: str, reference: str, line_drift: int) -> list[tuple[int, int, str]]:
138
+ if not reference:
139
+ return []
133
140
  lines = text.splitlines(keepends=True)
134
141
  if not lines:
135
142
  return []
@@ -150,6 +157,8 @@ def _candidate_spans(text: str, reference: str, line_drift: int) -> list[tuple[i
150
157
 
151
158
 
152
159
  def _best_fuzzy_span(text: str, reference: str) -> tuple[int, int, str, float, bool] | None:
160
+ if not reference:
161
+ return None
153
162
  best: tuple[int, int, str, float] | None = None
154
163
  best_lines: tuple[int, int] | None = None
155
164
  ambiguous = False
@@ -157,6 +166,8 @@ def _best_fuzzy_span(text: str, reference: str) -> tuple[int, int, str, float, b
157
166
  offsets = [0]
158
167
  for line in lines:
159
168
  offsets.append(offsets[-1] + len(line))
169
+ sm = SequenceMatcher(autojunk=False)
170
+ sm.set_seq2(reference)
160
171
  for start, end, block in _candidate_spans(text, reference, MAX_LINE_DRIFT):
161
172
  # compute line range [start_line, end_line) for overlap test
162
173
  start_line = 0
@@ -165,12 +176,17 @@ def _best_fuzzy_span(text: str, reference: str) -> tuple[int, int, str, float, b
165
176
  end_line = start_line
166
177
  while end_line < len(offsets) - 1 and offsets[end_line] < end:
167
178
  end_line += 1
168
- score = SequenceMatcher(None, block, reference, autojunk=False).ratio()
179
+ sm.set_seq1(block)
180
+ if best is not None:
181
+ prune_below = best[3] - AMBIGUITY_MARGIN
182
+ if sm.real_quick_ratio() < prune_below or sm.quick_ratio() < prune_below:
183
+ continue
184
+ score = sm.ratio()
169
185
  if best is None or score > best[3]:
170
186
  best = (start, end, block, score)
171
187
  best_lines = (start_line, end_line)
172
188
  ambiguous = False
173
- elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < 0.015:
189
+ elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < AMBIGUITY_MARGIN:
174
190
  # only treat as ambiguous if a genuinely different (non-overlapping) location matches closely
175
191
  if best_lines is not None and not (end_line <= best_lines[0] or start_line >= best_lines[1]):
176
192
  continue # overlapping window on same location -> not real ambiguity
@@ -4,11 +4,139 @@ import concurrent.futures
4
4
  import contextlib
5
5
  import os
6
6
  import tempfile
7
+ import time
8
+ from difflib import SequenceMatcher
7
9
  from pathlib import Path
8
10
 
9
11
  import pytest
10
12
 
11
- from cluxion_agentplugin_supercoder.core.hash_patch import apply_patch, file_hash
13
+ from cluxion_agentplugin_supercoder.core.hash_patch import (
14
+ AMBIGUITY_MARGIN,
15
+ DEFAULT_FUZZY_THRESHOLD,
16
+ MAX_LINE_DRIFT,
17
+ _best_fuzzy_span,
18
+ _candidate_spans,
19
+ _exact_spans,
20
+ apply_patch,
21
+ file_hash,
22
+ )
23
+
24
+
25
+ def _best_fuzzy_span_legacy(text: str, reference: str) -> tuple[int, int, str, float, bool] | None:
26
+ """Pre-optimization reference implementation for byte-identical regression checks."""
27
+ best: tuple[int, int, str, float] | None = None
28
+ best_lines: tuple[int, int] | None = None
29
+ ambiguous = False
30
+ lines = text.splitlines(keepends=True)
31
+ offsets = [0]
32
+ for line in lines:
33
+ offsets.append(offsets[-1] + len(line))
34
+ for start, end, block in _candidate_spans(text, reference, MAX_LINE_DRIFT):
35
+ start_line = 0
36
+ while start_line < len(offsets) - 1 and offsets[start_line + 1] <= start:
37
+ start_line += 1
38
+ end_line = start_line
39
+ while end_line < len(offsets) - 1 and offsets[end_line] < end:
40
+ end_line += 1
41
+ score = SequenceMatcher(None, block, reference, autojunk=False).ratio()
42
+ if best is None or score > best[3]:
43
+ best = (start, end, block, score)
44
+ best_lines = (start_line, end_line)
45
+ ambiguous = False
46
+ elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < AMBIGUITY_MARGIN:
47
+ if best_lines is not None and not (end_line <= best_lines[0] or start_line >= best_lines[1]):
48
+ continue
49
+ ambiguous = True
50
+ if best is None:
51
+ return None
52
+ return best[0], best[1], best[2], best[3], ambiguous
53
+
54
+
55
+ def _fuzzy_result_key(result: tuple[int, int, str, float, bool] | None) -> tuple | None:
56
+ if result is None:
57
+ return None
58
+ start, end, _block, score, ambiguous = result
59
+ return (start, end, score, ambiguous)
60
+
61
+
62
+ @pytest.mark.parametrize(
63
+ ("text", "reference"),
64
+ [
65
+ pytest.param(
66
+ "\n".join(f"line_{i} = {i}" for i in range(20))
67
+ + "\n"
68
+ + "def target():\n return 42\n"
69
+ + "\n".join(f"tail_{i} = {i}" for i in range(20))
70
+ + "\n",
71
+ "def target():\n return 43\n",
72
+ id="exact-best-in-middle",
73
+ ),
74
+ pytest.param(
75
+ "def f():\n return 1\n\n" + "def f():\n return 1\n",
76
+ "def f():\n return 2\n",
77
+ id="ambiguous-near-ties",
78
+ ),
79
+ pytest.param(
80
+ "alpha\nbeta\ngamma\ndelta\n",
81
+ "alpha\nBETA\ngamma\n",
82
+ id="clear-single-best",
83
+ ),
84
+ pytest.param(
85
+ "alpha\nbeta\ngamma\n",
86
+ "completely different content\n",
87
+ id="no-match",
88
+ ),
89
+ ],
90
+ )
91
+ def test_best_fuzzy_span_matches_legacy(text: str, reference: str) -> None:
92
+ legacy = _fuzzy_result_key(_best_fuzzy_span_legacy(text, reference))
93
+ optimized = _fuzzy_result_key(_best_fuzzy_span(text, reference))
94
+ assert optimized == legacy
95
+
96
+
97
+ def test_best_fuzzy_span_large_file_benchmark() -> None:
98
+ """Optimized path should be materially faster on a ~500-line fuzzy search."""
99
+ filler = "\n".join(f"# filler line {i:03d} with some padding text" for i in range(480)) + "\n"
100
+ target = "def handler(request):\n value = compute(request)\n return value\n"
101
+ text = filler + target + filler
102
+ reference = "def handler(request):\n value = compute(request) # cached\n return value\n"
103
+
104
+ legacy_start = time.perf_counter()
105
+ legacy = _best_fuzzy_span_legacy(text, reference)
106
+ legacy_elapsed = time.perf_counter() - legacy_start
107
+
108
+ optimized_start = time.perf_counter()
109
+ optimized = _best_fuzzy_span(text, reference)
110
+ optimized_elapsed = time.perf_counter() - optimized_start
111
+
112
+ assert _fuzzy_result_key(optimized) == _fuzzy_result_key(legacy)
113
+ assert optimized_elapsed < legacy_elapsed * 0.6
114
+
115
+
116
+ def test_exact_spans_empty_needle_returns_immediately() -> None:
117
+ assert _exact_spans("hello", "") == []
118
+
119
+
120
+ def test_apply_patch_empty_old_text_fails_fast(tmp_path: Path) -> None:
121
+ path = tmp_path / "a.py"
122
+ path.write_text("alpha\nbeta\ngamma\n", encoding="utf-8")
123
+ start = time.perf_counter()
124
+ result = apply_patch(path, old_text="", new_text="INSERT\n")
125
+ elapsed = time.perf_counter() - start
126
+ assert elapsed < 1.0
127
+ assert result.success is False
128
+ assert result.strategy == "empty_old_text"
129
+ assert "non-empty" in result.message
130
+ assert path.read_text(encoding="utf-8") == "alpha\nbeta\ngamma\n"
131
+
132
+
133
+ def test_empty_reference_does_not_hang_fuzzy_path() -> None:
134
+ text = "\n".join(f"line_{i} = {i}" for i in range(500)) + "\n"
135
+ start = time.perf_counter()
136
+ assert _candidate_spans(text, "", MAX_LINE_DRIFT) == []
137
+ assert _best_fuzzy_span(text, "") is None
138
+ elapsed = time.perf_counter() - start
139
+ assert elapsed < 1.0
12
140
 
13
141
 
14
142
  def test_exact_patch(tmp_path: Path) -> None:
@@ -160,7 +160,7 @@ wheels = [
160
160
 
161
161
  [[package]]
162
162
  name = "cluxion-agentplugin-supercoder"
163
- version = "0.2.11"
163
+ version = "0.2.13"
164
164
  source = { editable = "." }
165
165
  dependencies = [
166
166
  { name = "psutil" },