codexa 0.4.0__py3-none-any.whl
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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
"""Phase 24 — Self-Improving Development Loop.
|
|
2
|
+
|
|
3
|
+
Tests verify:
|
|
4
|
+
1. BudgetGuard — start, can_continue, record_tokens/iteration, stop_reason, summary
|
|
5
|
+
2. TestRunner — parse_summary, TestResult
|
|
6
|
+
3. CommitManager — git operations with mocked subprocess
|
|
7
|
+
4. TaskSelector — priority selection, task builders
|
|
8
|
+
5. ContextBuilder — system prompt, build sections, truncation, estimate_tokens
|
|
9
|
+
6. PatchGenerator — diff extraction, diff parsing, safety limits
|
|
10
|
+
7. EvolutionEngine — orchestrated loop with mocked components
|
|
11
|
+
8. CLI command — evolve command exists, help text, options
|
|
12
|
+
9. Module imports and version
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import textwrap
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from unittest.mock import MagicMock, patch
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Imports under test
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
from semantic_code_intelligence.evolution.budget_guard import BudgetGuard
|
|
30
|
+
from semantic_code_intelligence.evolution.test_runner import TestResult, TestRunner, _parse_summary
|
|
31
|
+
from semantic_code_intelligence.evolution.commit_manager import CommitManager
|
|
32
|
+
from semantic_code_intelligence.evolution.task_selector import (
|
|
33
|
+
TASK_ERROR_HANDLING,
|
|
34
|
+
TASK_FIX_TESTS,
|
|
35
|
+
TASK_REDUCE_DUPLICATION,
|
|
36
|
+
TASK_SMALL_OPTIMISATION,
|
|
37
|
+
TASK_TYPE_HINTS,
|
|
38
|
+
EvolutionTask,
|
|
39
|
+
TaskSelector,
|
|
40
|
+
)
|
|
41
|
+
from semantic_code_intelligence.evolution.context_builder import (
|
|
42
|
+
SYSTEM_PROMPT,
|
|
43
|
+
ContextBuilder,
|
|
44
|
+
)
|
|
45
|
+
from semantic_code_intelligence.evolution.patch_generator import (
|
|
46
|
+
PatchGenerator,
|
|
47
|
+
PatchResult,
|
|
48
|
+
_diff_files,
|
|
49
|
+
_diff_line_count,
|
|
50
|
+
_extract_diff,
|
|
51
|
+
)
|
|
52
|
+
from semantic_code_intelligence.evolution.engine import (
|
|
53
|
+
EvolutionEngine,
|
|
54
|
+
EvolutionResult,
|
|
55
|
+
IterationRecord,
|
|
56
|
+
)
|
|
57
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
58
|
+
from semantic_code_intelligence.llm.provider import LLMMessage, LLMResponse, MessageRole
|
|
59
|
+
|
|
60
|
+
_PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
61
|
+
_SRC = _PROJECT_ROOT / "semantic_code_intelligence"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
65
|
+
# 1 — BudgetGuard
|
|
66
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestBudgetGuard:
|
|
70
|
+
"""Tests for the BudgetGuard resource tracker."""
|
|
71
|
+
|
|
72
|
+
def test_defaults(self):
|
|
73
|
+
g = BudgetGuard()
|
|
74
|
+
assert g.max_tokens == 20_000
|
|
75
|
+
assert g.max_iterations == 5
|
|
76
|
+
assert g.max_seconds == 600.0
|
|
77
|
+
assert g.tokens_used == 0
|
|
78
|
+
assert g.iterations_done == 0
|
|
79
|
+
|
|
80
|
+
def test_can_continue_fresh(self):
|
|
81
|
+
g = BudgetGuard()
|
|
82
|
+
assert g.can_continue() is True
|
|
83
|
+
|
|
84
|
+
def test_record_tokens(self):
|
|
85
|
+
g = BudgetGuard(max_tokens=100)
|
|
86
|
+
g.record_tokens(40)
|
|
87
|
+
assert g.tokens_used == 40
|
|
88
|
+
assert g.tokens_remaining == 60
|
|
89
|
+
|
|
90
|
+
def test_record_iteration(self):
|
|
91
|
+
g = BudgetGuard(max_iterations=3)
|
|
92
|
+
g.record_iteration()
|
|
93
|
+
g.record_iteration()
|
|
94
|
+
assert g.iterations_done == 2
|
|
95
|
+
assert g.iterations_remaining == 1
|
|
96
|
+
|
|
97
|
+
def test_stop_on_token_limit(self):
|
|
98
|
+
g = BudgetGuard(max_tokens=50)
|
|
99
|
+
g.record_tokens(50)
|
|
100
|
+
assert g.can_continue() is False
|
|
101
|
+
assert "token" in g.stop_reason().lower()
|
|
102
|
+
|
|
103
|
+
def test_stop_on_iteration_limit(self):
|
|
104
|
+
g = BudgetGuard(max_iterations=2)
|
|
105
|
+
g.record_iteration()
|
|
106
|
+
g.record_iteration()
|
|
107
|
+
assert g.can_continue() is False
|
|
108
|
+
assert "iteration" in g.stop_reason().lower()
|
|
109
|
+
|
|
110
|
+
def test_stop_on_time_limit(self):
|
|
111
|
+
g = BudgetGuard(max_seconds=0.01)
|
|
112
|
+
g.start()
|
|
113
|
+
time.sleep(0.02)
|
|
114
|
+
assert g.can_continue() is False
|
|
115
|
+
assert "time" in g.stop_reason().lower()
|
|
116
|
+
|
|
117
|
+
def test_stop_reason_none_when_ok(self):
|
|
118
|
+
g = BudgetGuard()
|
|
119
|
+
assert g.stop_reason() is None
|
|
120
|
+
|
|
121
|
+
def test_elapsed_without_start(self):
|
|
122
|
+
g = BudgetGuard()
|
|
123
|
+
assert g.elapsed_seconds == 0.0
|
|
124
|
+
|
|
125
|
+
def test_summary(self):
|
|
126
|
+
g = BudgetGuard(max_tokens=1000, max_iterations=3, max_seconds=300)
|
|
127
|
+
g.start()
|
|
128
|
+
g.record_tokens(150)
|
|
129
|
+
g.record_iteration()
|
|
130
|
+
s = g.summary()
|
|
131
|
+
assert s["tokens_used"] == 150
|
|
132
|
+
assert s["tokens_max"] == 1000
|
|
133
|
+
assert s["iterations_done"] == 1
|
|
134
|
+
assert s["iterations_max"] == 3
|
|
135
|
+
assert isinstance(s["elapsed_seconds"], float)
|
|
136
|
+
|
|
137
|
+
def test_tokens_remaining_never_negative(self):
|
|
138
|
+
g = BudgetGuard(max_tokens=10)
|
|
139
|
+
g.record_tokens(100)
|
|
140
|
+
assert g.tokens_remaining == 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
144
|
+
# 2 — TestRunner
|
|
145
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestTestResult:
|
|
149
|
+
"""Tests for the TestResult dataclass."""
|
|
150
|
+
|
|
151
|
+
def test_summary_line_pass(self):
|
|
152
|
+
r = TestResult(passed=True, total=10, failures=0, errors=0)
|
|
153
|
+
line = r.summary_line()
|
|
154
|
+
assert "PASS" in line
|
|
155
|
+
assert "10 tests" in line
|
|
156
|
+
|
|
157
|
+
def test_summary_line_fail(self):
|
|
158
|
+
r = TestResult(passed=False, total=10, failures=2, errors=0)
|
|
159
|
+
line = r.summary_line()
|
|
160
|
+
assert "FAIL" in line
|
|
161
|
+
assert "2 failures" in line
|
|
162
|
+
|
|
163
|
+
def test_summary_line_errors(self):
|
|
164
|
+
r = TestResult(passed=False, total=10, failures=1, errors=2)
|
|
165
|
+
line = r.summary_line()
|
|
166
|
+
assert "2 errors" in line
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TestTestRunnerParsing:
|
|
170
|
+
"""Tests for TestRunner._parse_summary regex parsing."""
|
|
171
|
+
|
|
172
|
+
def test_parse_all_passed(self):
|
|
173
|
+
total, failures, errors = _parse_summary("10 passed in 2.34s")
|
|
174
|
+
assert total == 10
|
|
175
|
+
assert failures == 0
|
|
176
|
+
assert errors == 0
|
|
177
|
+
|
|
178
|
+
def test_parse_mixed(self):
|
|
179
|
+
total, failures, errors = _parse_summary("8 passed, 2 failed in 5.67s")
|
|
180
|
+
assert total == 10
|
|
181
|
+
assert failures == 2
|
|
182
|
+
assert errors == 0
|
|
183
|
+
|
|
184
|
+
def test_parse_errors(self):
|
|
185
|
+
total, failures, errors = _parse_summary("5 passed, 1 failed, 2 errors in 3.0s")
|
|
186
|
+
assert total == 8
|
|
187
|
+
assert failures == 1
|
|
188
|
+
assert errors == 2
|
|
189
|
+
|
|
190
|
+
def test_parse_empty(self):
|
|
191
|
+
total, failures, errors = _parse_summary("")
|
|
192
|
+
assert total == 0
|
|
193
|
+
assert failures == 0
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
# 3 — CommitManager
|
|
198
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestCommitManager:
|
|
202
|
+
"""Tests for CommitManager git operations (mocked subprocess)."""
|
|
203
|
+
|
|
204
|
+
def test_has_changes_true(self):
|
|
205
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
206
|
+
with patch("subprocess.run") as mock_run:
|
|
207
|
+
mock_run.return_value = MagicMock(
|
|
208
|
+
stdout="M foo.py\n", returncode=0
|
|
209
|
+
)
|
|
210
|
+
assert cm.has_changes() is True
|
|
211
|
+
|
|
212
|
+
def test_has_changes_false(self):
|
|
213
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
214
|
+
with patch("subprocess.run") as mock_run:
|
|
215
|
+
mock_run.return_value = MagicMock(stdout="", returncode=0)
|
|
216
|
+
assert cm.has_changes() is False
|
|
217
|
+
|
|
218
|
+
def test_git_diff(self):
|
|
219
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
220
|
+
with patch("subprocess.run") as mock_run:
|
|
221
|
+
mock_run.return_value = MagicMock(
|
|
222
|
+
stdout="+added line\n-removed line\n", returncode=0
|
|
223
|
+
)
|
|
224
|
+
diff = cm.git_diff()
|
|
225
|
+
assert "+added line" in diff
|
|
226
|
+
|
|
227
|
+
def test_commit_returns_sha(self):
|
|
228
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
229
|
+
with patch("subprocess.run") as mock_run:
|
|
230
|
+
mock_run.return_value = MagicMock(
|
|
231
|
+
stdout="abc1234\n", returncode=0
|
|
232
|
+
)
|
|
233
|
+
sha = cm.commit("test message")
|
|
234
|
+
assert sha == "abc1234"
|
|
235
|
+
|
|
236
|
+
def test_stage_files(self):
|
|
237
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
238
|
+
with patch("subprocess.run") as mock_run:
|
|
239
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
240
|
+
cm.stage_files(["a.py", "b.py"])
|
|
241
|
+
args = mock_run.call_args[0][0]
|
|
242
|
+
assert "add" in args
|
|
243
|
+
assert "a.py" in args
|
|
244
|
+
assert "b.py" in args
|
|
245
|
+
|
|
246
|
+
def test_revert_files(self):
|
|
247
|
+
cm = CommitManager(project_root=_PROJECT_ROOT)
|
|
248
|
+
with patch("subprocess.run") as mock_run:
|
|
249
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
250
|
+
cm.revert_files(["c.py"])
|
|
251
|
+
args = mock_run.call_args[0][0]
|
|
252
|
+
assert "checkout" in args
|
|
253
|
+
assert "c.py" in args
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
257
|
+
# 4 — TaskSelector
|
|
258
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class TestEvolutionTask:
|
|
262
|
+
"""Tests for the EvolutionTask dataclass."""
|
|
263
|
+
|
|
264
|
+
def test_basic_construction(self):
|
|
265
|
+
t = EvolutionTask(
|
|
266
|
+
category=TASK_TYPE_HINTS,
|
|
267
|
+
description="Add return types to foo.py",
|
|
268
|
+
target_files=["foo.py"],
|
|
269
|
+
)
|
|
270
|
+
assert t.category == TASK_TYPE_HINTS
|
|
271
|
+
assert len(t.target_files) == 1
|
|
272
|
+
assert t.context_hint == ""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestTaskSelector:
|
|
276
|
+
"""Tests for priority-based task selection."""
|
|
277
|
+
|
|
278
|
+
def test_fix_tests_has_highest_priority(self):
|
|
279
|
+
runner = MagicMock()
|
|
280
|
+
cm = MagicMock()
|
|
281
|
+
sel = TaskSelector(
|
|
282
|
+
project_root=_PROJECT_ROOT,
|
|
283
|
+
test_runner=runner,
|
|
284
|
+
commit_manager=cm,
|
|
285
|
+
)
|
|
286
|
+
failed_result = TestResult(
|
|
287
|
+
passed=False, total=7, failures=2, errors=0,
|
|
288
|
+
output="FAILED tests/test_foo.py::test_bar - AssertionError",
|
|
289
|
+
return_code=1,
|
|
290
|
+
)
|
|
291
|
+
task = sel.select(failed_result)
|
|
292
|
+
assert task.category == TASK_FIX_TESTS
|
|
293
|
+
|
|
294
|
+
def test_fallback_to_generic(self):
|
|
295
|
+
"""With no test failures and no code issues -> small optimisation."""
|
|
296
|
+
runner = MagicMock()
|
|
297
|
+
cm = MagicMock()
|
|
298
|
+
sel = TaskSelector(
|
|
299
|
+
project_root=_PROJECT_ROOT,
|
|
300
|
+
test_runner=runner,
|
|
301
|
+
commit_manager=cm,
|
|
302
|
+
)
|
|
303
|
+
passing = TestResult(passed=10, total=10, failures=0, errors=0, return_code=0)
|
|
304
|
+
with patch.object(sel, "_find_type_hint_task", return_value=None), \
|
|
305
|
+
patch.object(sel, "_find_error_handling_task", return_value=None), \
|
|
306
|
+
patch.object(sel, "_find_duplication_task", return_value=None):
|
|
307
|
+
task = sel.select(passing)
|
|
308
|
+
assert task.category == TASK_SMALL_OPTIMISATION
|
|
309
|
+
|
|
310
|
+
def test_task_categories_are_strings(self):
|
|
311
|
+
assert isinstance(TASK_FIX_TESTS, str)
|
|
312
|
+
assert isinstance(TASK_TYPE_HINTS, str)
|
|
313
|
+
assert isinstance(TASK_ERROR_HANDLING, str)
|
|
314
|
+
assert isinstance(TASK_REDUCE_DUPLICATION, str)
|
|
315
|
+
assert isinstance(TASK_SMALL_OPTIMISATION, str)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
319
|
+
# 5 — ContextBuilder
|
|
320
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class TestContextBuilder:
|
|
324
|
+
"""Tests for context assembly."""
|
|
325
|
+
|
|
326
|
+
def test_system_prompt_exists(self):
|
|
327
|
+
assert len(SYSTEM_PROMPT) > 100
|
|
328
|
+
assert "diff" in SYSTEM_PROMPT.lower()
|
|
329
|
+
|
|
330
|
+
def test_estimate_tokens(self):
|
|
331
|
+
cm = MagicMock()
|
|
332
|
+
cb = ContextBuilder(project_root=_PROJECT_ROOT, commit_manager=cm)
|
|
333
|
+
# 11 chars // 4 = 2 (floor division), min 1
|
|
334
|
+
assert cb.estimate_tokens("hello world") == 2
|
|
335
|
+
assert cb.estimate_tokens("hi") == 1 # min clamp
|
|
336
|
+
|
|
337
|
+
def test_build_includes_task(self):
|
|
338
|
+
cm = MagicMock()
|
|
339
|
+
cm.git_diff.return_value = ""
|
|
340
|
+
cb = ContextBuilder(project_root=_PROJECT_ROOT, commit_manager=cm)
|
|
341
|
+
task = EvolutionTask(
|
|
342
|
+
category="test_cat",
|
|
343
|
+
description="Do something helpful",
|
|
344
|
+
target_files=[],
|
|
345
|
+
)
|
|
346
|
+
ctx = cb.build(task)
|
|
347
|
+
assert "Do something helpful" in ctx
|
|
348
|
+
assert "test_cat" in ctx
|
|
349
|
+
|
|
350
|
+
def test_build_truncates_large_content(self):
|
|
351
|
+
cm = MagicMock()
|
|
352
|
+
cm.git_diff.return_value = ""
|
|
353
|
+
cb = ContextBuilder(
|
|
354
|
+
project_root=_PROJECT_ROOT,
|
|
355
|
+
commit_manager=cm,
|
|
356
|
+
max_context_tokens=50,
|
|
357
|
+
)
|
|
358
|
+
task = EvolutionTask(
|
|
359
|
+
category="demo",
|
|
360
|
+
description="truncation test",
|
|
361
|
+
target_files=[],
|
|
362
|
+
context_hint="A" * 10000,
|
|
363
|
+
)
|
|
364
|
+
ctx = cb.build(task)
|
|
365
|
+
assert len(ctx) < 10000
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
369
|
+
# 6 — PatchGenerator — diff helpers
|
|
370
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class TestDiffExtraction:
|
|
374
|
+
"""Tests for _extract_diff from LLM output."""
|
|
375
|
+
|
|
376
|
+
def test_fenced_diff_block(self):
|
|
377
|
+
text = textwrap.dedent("""\
|
|
378
|
+
Here is the patch:
|
|
379
|
+
|
|
380
|
+
```diff
|
|
381
|
+
--- a/foo.py
|
|
382
|
+
+++ b/foo.py
|
|
383
|
+
@@ -1,3 +1,3 @@
|
|
384
|
+
-old line
|
|
385
|
+
+new line
|
|
386
|
+
context
|
|
387
|
+
```
|
|
388
|
+
""")
|
|
389
|
+
diff = _extract_diff(text)
|
|
390
|
+
assert "--- a/foo.py" in diff
|
|
391
|
+
assert "+new line" in diff
|
|
392
|
+
|
|
393
|
+
def test_raw_diff_without_fence(self):
|
|
394
|
+
text = textwrap.dedent("""\
|
|
395
|
+
--- a/bar.py
|
|
396
|
+
+++ b/bar.py
|
|
397
|
+
@@ -10,4 +10,5 @@
|
|
398
|
+
context
|
|
399
|
+
-removed
|
|
400
|
+
+added
|
|
401
|
+
+another
|
|
402
|
+
""")
|
|
403
|
+
diff = _extract_diff(text)
|
|
404
|
+
assert "--- a/bar.py" in diff
|
|
405
|
+
assert "+added" in diff
|
|
406
|
+
|
|
407
|
+
def test_no_diff_returns_empty(self):
|
|
408
|
+
text = "Just some regular text with no diff."
|
|
409
|
+
diff = _extract_diff(text)
|
|
410
|
+
assert diff == ""
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class TestDiffFiles:
|
|
414
|
+
"""Tests for _diff_files extraction."""
|
|
415
|
+
|
|
416
|
+
def test_single_file(self):
|
|
417
|
+
diff = "--- a/foo.py\n+++ b/foo.py\n@@ -1 +1 @@\n-x\n+y"
|
|
418
|
+
files = _diff_files(diff)
|
|
419
|
+
assert files == ["foo.py"]
|
|
420
|
+
|
|
421
|
+
def test_multiple_files(self):
|
|
422
|
+
diff = (
|
|
423
|
+
"--- a/a.py\n+++ b/a.py\n@@ -1 +1 @@\n-x\n+y\n"
|
|
424
|
+
"--- a/b.py\n+++ b/b.py\n@@ -1 +1 @@\n-x\n+y"
|
|
425
|
+
)
|
|
426
|
+
files = _diff_files(diff)
|
|
427
|
+
assert "a.py" in files
|
|
428
|
+
assert "b.py" in files
|
|
429
|
+
|
|
430
|
+
def test_dev_null_excluded(self):
|
|
431
|
+
diff = "--- /dev/null\n+++ b/new.py\n@@ -0,0 +1 @@\n+new"
|
|
432
|
+
files = _diff_files(diff)
|
|
433
|
+
assert files == ["new.py"]
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class TestDiffLineCount:
|
|
437
|
+
"""Tests for _diff_line_count."""
|
|
438
|
+
|
|
439
|
+
def test_counts_adds_and_removes(self):
|
|
440
|
+
diff = (
|
|
441
|
+
"--- a/foo.py\n+++ b/foo.py\n@@ -1,3 +1,3 @@\n"
|
|
442
|
+
" ctx\n-removed\n+added\n ctx\n"
|
|
443
|
+
)
|
|
444
|
+
assert _diff_line_count(diff) == 2
|
|
445
|
+
|
|
446
|
+
def test_ignores_header_lines(self):
|
|
447
|
+
diff = "--- a/foo.py\n+++ b/foo.py\n@@ -1 +1 @@\n-x\n+y"
|
|
448
|
+
assert _diff_line_count(diff) == 2
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class TestPatchResult:
|
|
452
|
+
"""Tests for the PatchResult dataclass."""
|
|
453
|
+
|
|
454
|
+
def test_default_is_failure(self):
|
|
455
|
+
r = PatchResult()
|
|
456
|
+
assert r.success is False
|
|
457
|
+
assert r.files_changed == []
|
|
458
|
+
|
|
459
|
+
def test_to_dict(self):
|
|
460
|
+
r = PatchResult(success=True, files_changed=["a.py"], lines_changed=5)
|
|
461
|
+
d = r.to_dict()
|
|
462
|
+
assert d["success"] is True
|
|
463
|
+
assert d["files_changed"] == ["a.py"]
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class TestPatchGeneratorSafety:
|
|
467
|
+
"""Tests for PatchGenerator safety limits with mock LLM."""
|
|
468
|
+
|
|
469
|
+
def test_rejects_too_many_files(self):
|
|
470
|
+
provider = MockProvider()
|
|
471
|
+
lines = []
|
|
472
|
+
for i in range(5):
|
|
473
|
+
lines.append(f"--- a/f{i}.py")
|
|
474
|
+
lines.append(f"+++ b/f{i}.py")
|
|
475
|
+
lines.append("@@ -1 +1 @@")
|
|
476
|
+
lines.append(f"-old{i}")
|
|
477
|
+
lines.append(f"+new{i}")
|
|
478
|
+
diff_text = "\n".join(lines)
|
|
479
|
+
provider.enqueue_response(f"```diff\n{diff_text}\n```")
|
|
480
|
+
|
|
481
|
+
budget = BudgetGuard(max_tokens=50000)
|
|
482
|
+
cm = MagicMock()
|
|
483
|
+
cm.git_diff.return_value = ""
|
|
484
|
+
cb = ContextBuilder(project_root=_PROJECT_ROOT, commit_manager=cm)
|
|
485
|
+
pg = PatchGenerator(
|
|
486
|
+
project_root=_PROJECT_ROOT,
|
|
487
|
+
provider=provider,
|
|
488
|
+
context_builder=cb,
|
|
489
|
+
budget=budget,
|
|
490
|
+
)
|
|
491
|
+
task = EvolutionTask(category="test", description="test", target_files=[])
|
|
492
|
+
result = pg.generate_and_apply(task)
|
|
493
|
+
assert result.success is False
|
|
494
|
+
assert "files" in result.error.lower()
|
|
495
|
+
|
|
496
|
+
def test_rejects_when_budget_exceeded(self):
|
|
497
|
+
provider = MockProvider()
|
|
498
|
+
budget = BudgetGuard(max_tokens=10)
|
|
499
|
+
budget.record_tokens(10)
|
|
500
|
+
|
|
501
|
+
cm = MagicMock()
|
|
502
|
+
cm.git_diff.return_value = ""
|
|
503
|
+
cb = ContextBuilder(project_root=_PROJECT_ROOT, commit_manager=cm)
|
|
504
|
+
pg = PatchGenerator(
|
|
505
|
+
project_root=_PROJECT_ROOT,
|
|
506
|
+
provider=provider,
|
|
507
|
+
context_builder=cb,
|
|
508
|
+
budget=budget,
|
|
509
|
+
)
|
|
510
|
+
task = EvolutionTask(category="test", description="test", target_files=[])
|
|
511
|
+
result = pg.generate_and_apply(task)
|
|
512
|
+
assert result.success is False
|
|
513
|
+
assert "budget" in result.error.lower()
|
|
514
|
+
|
|
515
|
+
def test_handles_llm_returning_no_diff(self):
|
|
516
|
+
provider = MockProvider()
|
|
517
|
+
provider.enqueue_response("Sorry, I cannot generate a diff for this.")
|
|
518
|
+
|
|
519
|
+
budget = BudgetGuard(max_tokens=50000)
|
|
520
|
+
cm = MagicMock()
|
|
521
|
+
cm.git_diff.return_value = ""
|
|
522
|
+
cb = ContextBuilder(project_root=_PROJECT_ROOT, commit_manager=cm)
|
|
523
|
+
pg = PatchGenerator(
|
|
524
|
+
project_root=_PROJECT_ROOT,
|
|
525
|
+
provider=provider,
|
|
526
|
+
context_builder=cb,
|
|
527
|
+
budget=budget,
|
|
528
|
+
)
|
|
529
|
+
task = EvolutionTask(category="test", description="test", target_files=[])
|
|
530
|
+
result = pg.generate_and_apply(task)
|
|
531
|
+
assert result.success is False
|
|
532
|
+
assert "valid" in result.error.lower() or "diff" in result.error.lower()
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
536
|
+
# 7 — Engine — IterationRecord and EvolutionResult
|
|
537
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class TestIterationRecord:
|
|
541
|
+
"""Tests for the IterationRecord dataclass."""
|
|
542
|
+
|
|
543
|
+
def test_to_dict(self):
|
|
544
|
+
r = IterationRecord(
|
|
545
|
+
iteration=1,
|
|
546
|
+
task_category="type_hints",
|
|
547
|
+
task_description="Add types",
|
|
548
|
+
committed=True,
|
|
549
|
+
commit_sha="abc123",
|
|
550
|
+
)
|
|
551
|
+
d = r.to_dict()
|
|
552
|
+
assert d["iteration"] == 1
|
|
553
|
+
assert d["committed"] is True
|
|
554
|
+
assert d["commit_sha"] == "abc123"
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class TestEvolutionResult:
|
|
558
|
+
"""Tests for the EvolutionResult aggregate."""
|
|
559
|
+
|
|
560
|
+
def test_to_dict(self):
|
|
561
|
+
r = EvolutionResult(
|
|
562
|
+
iterations_completed=2,
|
|
563
|
+
commits=["abc", "def"],
|
|
564
|
+
reverts=1,
|
|
565
|
+
stop_reason="iteration limit reached (2)",
|
|
566
|
+
)
|
|
567
|
+
d = r.to_dict()
|
|
568
|
+
assert d["iterations_completed"] == 2
|
|
569
|
+
assert len(d["commits"]) == 2
|
|
570
|
+
assert d["reverts"] == 1
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
class TestEvolutionEngineOrchestration:
|
|
574
|
+
"""Tests for the EvolutionEngine loop with mocked components."""
|
|
575
|
+
|
|
576
|
+
def test_engine_runs_iterations(self, tmp_path):
|
|
577
|
+
"""Engine should run iterations and respect budget."""
|
|
578
|
+
provider = MockProvider()
|
|
579
|
+
provider.enqueue_response("no diff here")
|
|
580
|
+
provider.enqueue_response("no diff here")
|
|
581
|
+
|
|
582
|
+
budget = BudgetGuard(max_tokens=50000, max_iterations=2, max_seconds=60)
|
|
583
|
+
|
|
584
|
+
with patch.object(TestRunner, "run") as mock_test_run, \
|
|
585
|
+
patch.object(CommitManager, "has_changes", return_value=False):
|
|
586
|
+
mock_test_run.return_value = TestResult(
|
|
587
|
+
passed=10, total=10, failures=0, errors=0, return_code=0,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
engine = EvolutionEngine(
|
|
591
|
+
project_root=tmp_path,
|
|
592
|
+
provider=provider,
|
|
593
|
+
budget=budget,
|
|
594
|
+
)
|
|
595
|
+
result = engine.run()
|
|
596
|
+
|
|
597
|
+
assert result.iterations_completed == 2
|
|
598
|
+
assert result.stop_reason is not None
|
|
599
|
+
assert len(result.history) == 2
|
|
600
|
+
|
|
601
|
+
def test_engine_writes_history(self, tmp_path):
|
|
602
|
+
"""Engine should persist evolution_history.json."""
|
|
603
|
+
provider = MockProvider()
|
|
604
|
+
provider.enqueue_response("no diff")
|
|
605
|
+
budget = BudgetGuard(max_tokens=50000, max_iterations=1, max_seconds=60)
|
|
606
|
+
|
|
607
|
+
with patch.object(TestRunner, "run") as mock_test_run, \
|
|
608
|
+
patch.object(CommitManager, "has_changes", return_value=False):
|
|
609
|
+
mock_test_run.return_value = TestResult(
|
|
610
|
+
passed=5, total=5, failures=0, errors=0, return_code=0,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
engine = EvolutionEngine(
|
|
614
|
+
project_root=tmp_path,
|
|
615
|
+
provider=provider,
|
|
616
|
+
budget=budget,
|
|
617
|
+
)
|
|
618
|
+
engine.run()
|
|
619
|
+
|
|
620
|
+
history_file = tmp_path / ".codexa" / "evolution_history.json"
|
|
621
|
+
assert history_file.exists()
|
|
622
|
+
data = json.loads(history_file.read_text(encoding="utf-8"))
|
|
623
|
+
assert isinstance(data, list)
|
|
624
|
+
assert len(data) == 1
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
628
|
+
# 8 — CLI command
|
|
629
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class TestEvolveCLI:
|
|
633
|
+
"""Tests for the evolve CLI command."""
|
|
634
|
+
|
|
635
|
+
def test_command_exists(self):
|
|
636
|
+
from semantic_code_intelligence.cli.commands.evolve_cmd import evolve_cmd
|
|
637
|
+
assert evolve_cmd.name == "evolve"
|
|
638
|
+
|
|
639
|
+
def test_command_has_options(self):
|
|
640
|
+
from semantic_code_intelligence.cli.commands.evolve_cmd import evolve_cmd
|
|
641
|
+
param_names = [p.name for p in evolve_cmd.params]
|
|
642
|
+
assert "iterations" in param_names
|
|
643
|
+
assert "budget" in param_names
|
|
644
|
+
assert "timeout" in param_names
|
|
645
|
+
assert "path" in param_names
|
|
646
|
+
|
|
647
|
+
def test_command_registered_in_router(self):
|
|
648
|
+
from semantic_code_intelligence.cli.router import register_commands
|
|
649
|
+
group = MagicMock(spec=["add_command"])
|
|
650
|
+
register_commands(group)
|
|
651
|
+
added_names = [call.args[0].name for call in group.add_command.call_args_list]
|
|
652
|
+
assert "evolve" in added_names
|
|
653
|
+
|
|
654
|
+
def test_help_mentions_evolve(self):
|
|
655
|
+
from click.testing import CliRunner
|
|
656
|
+
from semantic_code_intelligence.cli.commands.evolve_cmd import evolve_cmd
|
|
657
|
+
runner = CliRunner()
|
|
658
|
+
result = runner.invoke(evolve_cmd, ["--help"])
|
|
659
|
+
assert result.exit_code == 0
|
|
660
|
+
assert "self-improving" in result.output.lower() or "evolve" in result.output.lower()
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
664
|
+
# 9 — Module imports and version
|
|
665
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class TestModuleImports:
|
|
669
|
+
"""Verify that all Phase 24 modules are importable."""
|
|
670
|
+
|
|
671
|
+
def test_import_budget_guard(self):
|
|
672
|
+
from semantic_code_intelligence.evolution import budget_guard
|
|
673
|
+
assert hasattr(budget_guard, "BudgetGuard")
|
|
674
|
+
|
|
675
|
+
def test_import_test_runner(self):
|
|
676
|
+
from semantic_code_intelligence.evolution import test_runner
|
|
677
|
+
assert hasattr(test_runner, "TestRunner")
|
|
678
|
+
assert hasattr(test_runner, "TestResult")
|
|
679
|
+
|
|
680
|
+
def test_import_commit_manager(self):
|
|
681
|
+
from semantic_code_intelligence.evolution import commit_manager
|
|
682
|
+
assert hasattr(commit_manager, "CommitManager")
|
|
683
|
+
|
|
684
|
+
def test_import_task_selector(self):
|
|
685
|
+
from semantic_code_intelligence.evolution import task_selector
|
|
686
|
+
assert hasattr(task_selector, "TaskSelector")
|
|
687
|
+
assert hasattr(task_selector, "EvolutionTask")
|
|
688
|
+
|
|
689
|
+
def test_import_context_builder(self):
|
|
690
|
+
from semantic_code_intelligence.evolution import context_builder
|
|
691
|
+
assert hasattr(context_builder, "ContextBuilder")
|
|
692
|
+
assert hasattr(context_builder, "SYSTEM_PROMPT")
|
|
693
|
+
|
|
694
|
+
def test_import_patch_generator(self):
|
|
695
|
+
from semantic_code_intelligence.evolution import patch_generator
|
|
696
|
+
assert hasattr(patch_generator, "PatchGenerator")
|
|
697
|
+
assert hasattr(patch_generator, "PatchResult")
|
|
698
|
+
|
|
699
|
+
def test_import_engine(self):
|
|
700
|
+
from semantic_code_intelligence.evolution import engine
|
|
701
|
+
assert hasattr(engine, "EvolutionEngine")
|
|
702
|
+
assert hasattr(engine, "EvolutionResult")
|
|
703
|
+
assert hasattr(engine, "IterationRecord")
|
|
704
|
+
|
|
705
|
+
def test_import_evolve_cmd(self):
|
|
706
|
+
from semantic_code_intelligence.cli.commands import evolve_cmd
|
|
707
|
+
assert hasattr(evolve_cmd, "evolve_cmd")
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
class TestVersion:
|
|
711
|
+
"""Verify the project version reflects Phase 24."""
|
|
712
|
+
|
|
713
|
+
def test_version_is_0_24_0(self):
|
|
714
|
+
from semantic_code_intelligence import __version__
|
|
715
|
+
assert __version__ == "0.4.0"
|