cluxion-agentplugin-supercoder 0.2.10__tar.gz → 0.2.12__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.10 → cluxion_agentplugin_supercoder-0.2.12}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/plugin.yaml +1 -1
  3. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/pyproject.toml +1 -1
  4. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/hash_patch.py +23 -6
  5. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_hash_patch.py +120 -1
  6. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/uv.lock +1 -1
  7. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/.github/workflows/ci.yml +0 -0
  8. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/.github/workflows/publish.yml +0 -0
  9. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/.gitignore +0 -0
  10. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/ARCHITECTURE.md +0 -0
  11. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/README.md +0 -0
  12. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/agent-surfaces.md +0 -0
  13. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/architecture.md +0 -0
  14. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/capabilities.md +0 -0
  15. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/design.md +0 -0
  16. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/installation.md +0 -0
  17. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/rust-architecture.md +0 -0
  18. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/Docs/tools.md +0 -0
  19. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/LICENSE +0 -0
  20. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/README.md +0 -0
  21. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/__init__.py +0 -0
  22. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/adapters/claude/.claude-plugin/plugin.json +0 -0
  23. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/adapters/claude/skills/supercoder/SKILL.md +0 -0
  24. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/adapters/codex/config-snippet.toml +0 -0
  25. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/adapters/hermes/README.md +0 -0
  26. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/Cargo.lock +0 -0
  27. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/Cargo.toml +0 -0
  28. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/pyproject.toml +0 -0
  29. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/src/lib.rs +0 -0
  30. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/src/main.rs +0 -0
  31. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/src/outline.rs +0 -0
  32. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/rust/supercoder_index/src/syntax.rs +0 -0
  33. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/scripts/repack_native_wheel.py +0 -0
  34. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/__init__.py +0 -0
  35. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/cli.py +0 -0
  36. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/cursor.py +0 -0
  37. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/line_budget.py +0 -0
  38. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/lint_gate.py +0 -0
  39. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/queue.py +0 -0
  40. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/repo_map.py +0 -0
  41. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/retry_loop.py +0 -0
  42. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/safety.py +0 -0
  43. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/syntax_gate.py +0 -0
  44. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/core/test_gate.py +0 -0
  45. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/doctor/__init__.py +0 -0
  46. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/doctor/catalog.json +0 -0
  47. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/doctor/framework.py +0 -0
  48. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/doctor/probes.py +0 -0
  49. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/plugin.py +0 -0
  50. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/runner.py +0 -0
  51. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/rust_bridge.py +0 -0
  52. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/src/cluxion_agentplugin_supercoder/schemas.py +0 -0
  53. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_cursor.py +0 -0
  54. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_doctor.py +0 -0
  55. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_line_budget.py +0 -0
  56. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_lint_gate.py +0 -0
  57. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_plugin.py +0 -0
  58. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_queue.py +0 -0
  59. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_repo_map.py +0 -0
  60. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_retry_loop.py +0 -0
  61. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_rust_bridge.py +0 -0
  62. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_safety.py +0 -0
  63. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/tests/test_syntax_gate.py +0 -0
  64. {cluxion_agentplugin_supercoder-0.2.10 → cluxion_agentplugin_supercoder-0.2.12}/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.10
3
+ Version: 0.2.12
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.2
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.10"
7
+ version = "0.2.12"
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"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import hashlib
6
6
  import os
7
7
  import tempfile
8
+ import threading
8
9
  from contextlib import contextmanager
9
10
  from dataclasses import dataclass
10
11
  from difflib import SequenceMatcher
@@ -16,17 +17,26 @@ except ImportError:
16
17
  fcntl = None # type: ignore[assignment]
17
18
 
18
19
  DEFAULT_FUZZY_THRESHOLD = 0.86
20
+ AMBIGUITY_MARGIN = 0.015
19
21
  MAX_CONTEXT_SCAN = 8
20
22
  MAX_LINE_DRIFT = 2
21
23
 
24
+ _thread_fallback_lock = threading.Lock()
25
+
26
+
27
+ def _lock_path(path: Path) -> Path:
28
+ return path.parent / f".{path.name}.cluxion-lock"
29
+
22
30
 
23
31
  @contextmanager
24
32
  def _exclusive_lock(path: Path):
25
- """Exclusive advisory lock on target file (fcntl.flock). Graceful degrade on non-POSIX."""
26
- if fcntl is None or not path.exists():
27
- yield
33
+ """Exclusive advisory lock on stable sidecar file (fcntl.flock). Graceful degrade on non-POSIX."""
34
+ if fcntl is None:
35
+ with _thread_fallback_lock:
36
+ yield
28
37
  return
29
- fd = os.open(str(path), os.O_RDWR)
38
+ lock_path = _lock_path(path)
39
+ fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR, 0o600)
30
40
  try:
31
41
  fcntl.flock(fd, fcntl.LOCK_EX)
32
42
  yield
@@ -148,6 +158,8 @@ def _best_fuzzy_span(text: str, reference: str) -> tuple[int, int, str, float, b
148
158
  offsets = [0]
149
159
  for line in lines:
150
160
  offsets.append(offsets[-1] + len(line))
161
+ sm = SequenceMatcher(autojunk=False)
162
+ sm.set_seq2(reference)
151
163
  for start, end, block in _candidate_spans(text, reference, MAX_LINE_DRIFT):
152
164
  # compute line range [start_line, end_line) for overlap test
153
165
  start_line = 0
@@ -156,12 +168,17 @@ def _best_fuzzy_span(text: str, reference: str) -> tuple[int, int, str, float, b
156
168
  end_line = start_line
157
169
  while end_line < len(offsets) - 1 and offsets[end_line] < end:
158
170
  end_line += 1
159
- score = SequenceMatcher(None, block, reference, autojunk=False).ratio()
171
+ sm.set_seq1(block)
172
+ if best is not None:
173
+ prune_below = best[3] - AMBIGUITY_MARGIN
174
+ if sm.real_quick_ratio() < prune_below or sm.quick_ratio() < prune_below:
175
+ continue
176
+ score = sm.ratio()
160
177
  if best is None or score > best[3]:
161
178
  best = (start, end, block, score)
162
179
  best_lines = (start_line, end_line)
163
180
  ambiguous = False
164
- elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < 0.015:
181
+ elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < AMBIGUITY_MARGIN:
165
182
  # only treat as ambiguous if a genuinely different (non-overlapping) location matches closely
166
183
  if best_lines is not None and not (end_line <= best_lines[0] or start_line >= best_lines[1]):
167
184
  continue # overlapping window on same location -> not real ambiguity
@@ -4,11 +4,112 @@ 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
+ apply_patch,
20
+ file_hash,
21
+ )
22
+
23
+
24
+ def _best_fuzzy_span_legacy(text: str, reference: str) -> tuple[int, int, str, float, bool] | None:
25
+ """Pre-optimization reference implementation for byte-identical regression checks."""
26
+ best: tuple[int, int, str, float] | None = None
27
+ best_lines: tuple[int, int] | None = None
28
+ ambiguous = False
29
+ lines = text.splitlines(keepends=True)
30
+ offsets = [0]
31
+ for line in lines:
32
+ offsets.append(offsets[-1] + len(line))
33
+ for start, end, block in _candidate_spans(text, reference, MAX_LINE_DRIFT):
34
+ start_line = 0
35
+ while start_line < len(offsets) - 1 and offsets[start_line + 1] <= start:
36
+ start_line += 1
37
+ end_line = start_line
38
+ while end_line < len(offsets) - 1 and offsets[end_line] < end:
39
+ end_line += 1
40
+ score = SequenceMatcher(None, block, reference, autojunk=False).ratio()
41
+ if best is None or score > best[3]:
42
+ best = (start, end, block, score)
43
+ best_lines = (start_line, end_line)
44
+ ambiguous = False
45
+ elif score >= DEFAULT_FUZZY_THRESHOLD and best and abs(score - best[3]) < AMBIGUITY_MARGIN:
46
+ if best_lines is not None and not (end_line <= best_lines[0] or start_line >= best_lines[1]):
47
+ continue
48
+ ambiguous = True
49
+ if best is None:
50
+ return None
51
+ return best[0], best[1], best[2], best[3], ambiguous
52
+
53
+
54
+ def _fuzzy_result_key(result: tuple[int, int, str, float, bool] | None) -> tuple | None:
55
+ if result is None:
56
+ return None
57
+ start, end, _block, score, ambiguous = result
58
+ return (start, end, score, ambiguous)
59
+
60
+
61
+ @pytest.mark.parametrize(
62
+ ("text", "reference"),
63
+ [
64
+ pytest.param(
65
+ "\n".join(f"line_{i} = {i}" for i in range(20))
66
+ + "\n"
67
+ + "def target():\n return 42\n"
68
+ + "\n".join(f"tail_{i} = {i}" for i in range(20))
69
+ + "\n",
70
+ "def target():\n return 43\n",
71
+ id="exact-best-in-middle",
72
+ ),
73
+ pytest.param(
74
+ "def f():\n return 1\n\n" + "def f():\n return 1\n",
75
+ "def f():\n return 2\n",
76
+ id="ambiguous-near-ties",
77
+ ),
78
+ pytest.param(
79
+ "alpha\nbeta\ngamma\ndelta\n",
80
+ "alpha\nBETA\ngamma\n",
81
+ id="clear-single-best",
82
+ ),
83
+ pytest.param(
84
+ "alpha\nbeta\ngamma\n",
85
+ "completely different content\n",
86
+ id="no-match",
87
+ ),
88
+ ],
89
+ )
90
+ def test_best_fuzzy_span_matches_legacy(text: str, reference: str) -> None:
91
+ legacy = _fuzzy_result_key(_best_fuzzy_span_legacy(text, reference))
92
+ optimized = _fuzzy_result_key(_best_fuzzy_span(text, reference))
93
+ assert optimized == legacy
94
+
95
+
96
+ def test_best_fuzzy_span_large_file_benchmark() -> None:
97
+ """Optimized path should be materially faster on a ~500-line fuzzy search."""
98
+ filler = "\n".join(f"# filler line {i:03d} with some padding text" for i in range(480)) + "\n"
99
+ target = "def handler(request):\n value = compute(request)\n return value\n"
100
+ text = filler + target + filler
101
+ reference = "def handler(request):\n value = compute(request) # cached\n return value\n"
102
+
103
+ legacy_start = time.perf_counter()
104
+ legacy = _best_fuzzy_span_legacy(text, reference)
105
+ legacy_elapsed = time.perf_counter() - legacy_start
106
+
107
+ optimized_start = time.perf_counter()
108
+ optimized = _best_fuzzy_span(text, reference)
109
+ optimized_elapsed = time.perf_counter() - optimized_start
110
+
111
+ assert _fuzzy_result_key(optimized) == _fuzzy_result_key(legacy)
112
+ assert optimized_elapsed < legacy_elapsed * 0.6
12
113
 
13
114
 
14
115
  def test_exact_patch(tmp_path: Path) -> None:
@@ -158,6 +259,24 @@ def test_concurrent_patches_no_lost_update(tmp_path: Path) -> None:
158
259
  assert new_hash != file_hash(initial)
159
260
 
160
261
 
262
+ def test_concurrent_patches_no_lost_update_stress(tmp_path: Path) -> None:
263
+ """50 iterations of 8 concurrent workers: zero lost updates across all runs."""
264
+ for _ in range(50):
265
+ path = tmp_path / "concurrent_stress.py"
266
+ initial = "\n".join(f"# UNIQUE_PATCH_{i}_START" for i in range(8)) + "\n"
267
+ path.write_text(initial, encoding="utf-8")
268
+
269
+ with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
270
+ futures = [executor.submit(_worker_apply, i, path) for i in range(8)]
271
+ successes = [f.result() for f in concurrent.futures.as_completed(futures)]
272
+
273
+ assert all(successes), "Some patches lost due to race"
274
+ final = path.read_text(encoding="utf-8")
275
+ for i in range(8):
276
+ assert f"# UNIQUE_PATCH_{i}_DONE" in final
277
+ assert f"# UNIQUE_PATCH_{i}_START" not in final
278
+
279
+
161
280
  def test_atomic_write_interruption_leaves_original_intact(tmp_path: Path) -> None:
162
281
  """Simulated mid-write crash leaves ORIGINAL file intact (temp may remain, no truncate)."""
163
282
  path = tmp_path / "atomic_test.txt"
@@ -160,7 +160,7 @@ wheels = [
160
160
 
161
161
  [[package]]
162
162
  name = "cluxion-agentplugin-supercoder"
163
- version = "0.2.10"
163
+ version = "0.2.12"
164
164
  source = { editable = "." }
165
165
  dependencies = [
166
166
  { name = "psutil" },