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,815 @@
|
|
|
1
|
+
"""Tests for Phase 17 — Code Quality Metrics & Trends.
|
|
2
|
+
|
|
3
|
+
Covers: file metrics, project metrics, maintainability index, quality snapshots,
|
|
4
|
+
trend analysis, quality policies, gate enforcement, CLI commands,
|
|
5
|
+
router, version, docs, and module structure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from unittest.mock import patch
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from click.testing import CliRunner
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =========================================================================
|
|
20
|
+
# Helper: create sample Python files
|
|
21
|
+
# =========================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _write_sample_project(root: Path) -> None:
|
|
25
|
+
"""Write a small multi-file project for metrics testing."""
|
|
26
|
+
src = root / "src"
|
|
27
|
+
src.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
(src / "simple.py").write_text(
|
|
30
|
+
'# Simple module\n'
|
|
31
|
+
'def greet(name: str) -> str:\n'
|
|
32
|
+
' """Return a greeting."""\n'
|
|
33
|
+
' return f"Hello, {name}!"\n'
|
|
34
|
+
'\n'
|
|
35
|
+
'def add(a: int, b: int) -> int:\n'
|
|
36
|
+
' return a + b\n',
|
|
37
|
+
encoding="utf-8",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
(src / "complex.py").write_text(
|
|
41
|
+
'def process(data):\n'
|
|
42
|
+
' if not data:\n'
|
|
43
|
+
' return None\n'
|
|
44
|
+
' result = []\n'
|
|
45
|
+
' for item in data:\n'
|
|
46
|
+
' if isinstance(item, dict):\n'
|
|
47
|
+
' if "key" in item:\n'
|
|
48
|
+
' if item["key"] > 0:\n'
|
|
49
|
+
' result.append(item)\n'
|
|
50
|
+
' else:\n'
|
|
51
|
+
' result.append(None)\n'
|
|
52
|
+
' elif "alt" in item:\n'
|
|
53
|
+
' result.append(item["alt"])\n'
|
|
54
|
+
' elif isinstance(item, list):\n'
|
|
55
|
+
' result.extend(item)\n'
|
|
56
|
+
' else:\n'
|
|
57
|
+
' result.append(item)\n'
|
|
58
|
+
' return result\n',
|
|
59
|
+
encoding="utf-8",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
(src / "empty.py").write_text("", encoding="utf-8")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# =========================================================================
|
|
66
|
+
# FileMetrics tests
|
|
67
|
+
# =========================================================================
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestFileMetrics:
|
|
71
|
+
"""Tests for per-file metric computation."""
|
|
72
|
+
|
|
73
|
+
def test_basic_metrics(self, tmp_path):
|
|
74
|
+
from semantic_code_intelligence.ci.metrics import compute_file_metrics
|
|
75
|
+
|
|
76
|
+
_write_sample_project(tmp_path)
|
|
77
|
+
fm = compute_file_metrics(tmp_path / "src" / "simple.py")
|
|
78
|
+
|
|
79
|
+
assert fm.file_path.endswith("simple.py")
|
|
80
|
+
assert fm.lines_of_code > 0
|
|
81
|
+
assert fm.comment_lines >= 1 # '# Simple module'
|
|
82
|
+
assert fm.symbol_count >= 2 # greet, add
|
|
83
|
+
assert 0 <= fm.maintainability_index <= 100
|
|
84
|
+
|
|
85
|
+
def test_empty_file(self, tmp_path):
|
|
86
|
+
from semantic_code_intelligence.ci.metrics import compute_file_metrics
|
|
87
|
+
|
|
88
|
+
_write_sample_project(tmp_path)
|
|
89
|
+
fm = compute_file_metrics(tmp_path / "src" / "empty.py")
|
|
90
|
+
|
|
91
|
+
assert fm.lines_of_code == 0
|
|
92
|
+
assert fm.maintainability_index >= 0
|
|
93
|
+
|
|
94
|
+
def test_nonexistent_file(self, tmp_path):
|
|
95
|
+
from semantic_code_intelligence.ci.metrics import compute_file_metrics
|
|
96
|
+
|
|
97
|
+
fm = compute_file_metrics(tmp_path / "nope.py")
|
|
98
|
+
assert fm.lines_of_code == 0
|
|
99
|
+
|
|
100
|
+
def test_comment_ratio_property(self, tmp_path):
|
|
101
|
+
from semantic_code_intelligence.ci.metrics import compute_file_metrics
|
|
102
|
+
|
|
103
|
+
_write_sample_project(tmp_path)
|
|
104
|
+
fm = compute_file_metrics(tmp_path / "src" / "simple.py")
|
|
105
|
+
assert 0.0 <= fm.comment_ratio <= 1.0
|
|
106
|
+
|
|
107
|
+
def test_to_dict(self, tmp_path):
|
|
108
|
+
from semantic_code_intelligence.ci.metrics import compute_file_metrics
|
|
109
|
+
|
|
110
|
+
_write_sample_project(tmp_path)
|
|
111
|
+
fm = compute_file_metrics(tmp_path / "src" / "simple.py")
|
|
112
|
+
d = fm.to_dict()
|
|
113
|
+
|
|
114
|
+
assert "file_path" in d
|
|
115
|
+
assert "lines_of_code" in d
|
|
116
|
+
assert "maintainability_index" in d
|
|
117
|
+
assert "comment_ratio" in d
|
|
118
|
+
assert isinstance(d["maintainability_index"], float)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# =========================================================================
|
|
122
|
+
# ProjectMetrics tests
|
|
123
|
+
# =========================================================================
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestProjectMetrics:
|
|
127
|
+
"""Tests for project-wide metric aggregation."""
|
|
128
|
+
|
|
129
|
+
def test_project_metrics(self, tmp_path):
|
|
130
|
+
from semantic_code_intelligence.ci.metrics import compute_project_metrics
|
|
131
|
+
|
|
132
|
+
_write_sample_project(tmp_path)
|
|
133
|
+
pm = compute_project_metrics(tmp_path)
|
|
134
|
+
|
|
135
|
+
assert pm.files_analyzed >= 2 # simple.py, complex.py (empty.py may be counted too)
|
|
136
|
+
assert pm.total_loc > 0
|
|
137
|
+
assert pm.total_symbols > 0
|
|
138
|
+
assert 0 <= pm.maintainability_index <= 100
|
|
139
|
+
|
|
140
|
+
def test_project_metrics_with_file_paths(self, tmp_path):
|
|
141
|
+
from semantic_code_intelligence.ci.metrics import compute_project_metrics
|
|
142
|
+
|
|
143
|
+
_write_sample_project(tmp_path)
|
|
144
|
+
files = [str(tmp_path / "src" / "simple.py")]
|
|
145
|
+
pm = compute_project_metrics(tmp_path, file_paths=files)
|
|
146
|
+
|
|
147
|
+
assert pm.files_analyzed == 1
|
|
148
|
+
|
|
149
|
+
def test_empty_project(self, tmp_path):
|
|
150
|
+
from semantic_code_intelligence.ci.metrics import compute_project_metrics
|
|
151
|
+
|
|
152
|
+
pm = compute_project_metrics(tmp_path)
|
|
153
|
+
assert pm.files_analyzed == 0
|
|
154
|
+
assert pm.maintainability_index >= 0
|
|
155
|
+
|
|
156
|
+
def test_to_dict(self, tmp_path):
|
|
157
|
+
from semantic_code_intelligence.ci.metrics import compute_project_metrics
|
|
158
|
+
|
|
159
|
+
_write_sample_project(tmp_path)
|
|
160
|
+
pm = compute_project_metrics(tmp_path)
|
|
161
|
+
d = pm.to_dict()
|
|
162
|
+
|
|
163
|
+
assert "files_analyzed" in d
|
|
164
|
+
assert "total_loc" in d
|
|
165
|
+
assert "maintainability_index" in d
|
|
166
|
+
assert "file_metrics" in d
|
|
167
|
+
assert isinstance(d["file_metrics"], list)
|
|
168
|
+
|
|
169
|
+
def test_comment_ratio(self, tmp_path):
|
|
170
|
+
from semantic_code_intelligence.ci.metrics import compute_project_metrics
|
|
171
|
+
|
|
172
|
+
_write_sample_project(tmp_path)
|
|
173
|
+
pm = compute_project_metrics(tmp_path)
|
|
174
|
+
assert 0.0 <= pm.comment_ratio <= 1.0
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# =========================================================================
|
|
178
|
+
# Maintainability index computation
|
|
179
|
+
# =========================================================================
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class TestMaintainabilityIndex:
|
|
183
|
+
"""Tests for the MI formula."""
|
|
184
|
+
|
|
185
|
+
def test_mi_range(self):
|
|
186
|
+
from semantic_code_intelligence.ci.metrics import _compute_mi
|
|
187
|
+
|
|
188
|
+
# Simple code should have high MI
|
|
189
|
+
mi_simple = _compute_mi(10.0, 1.0, 0.3)
|
|
190
|
+
assert 0 <= mi_simple <= 100
|
|
191
|
+
|
|
192
|
+
# Complex code should have lower MI
|
|
193
|
+
mi_complex = _compute_mi(500.0, 20.0, 0.0)
|
|
194
|
+
assert 0 <= mi_complex <= 100
|
|
195
|
+
assert mi_complex < mi_simple
|
|
196
|
+
|
|
197
|
+
def test_mi_zero_loc(self):
|
|
198
|
+
from semantic_code_intelligence.ci.metrics import _compute_mi
|
|
199
|
+
|
|
200
|
+
mi = _compute_mi(0.0, 0.0, 0.0)
|
|
201
|
+
assert 0 <= mi <= 100
|
|
202
|
+
|
|
203
|
+
def test_mi_high_comments_helps(self):
|
|
204
|
+
from semantic_code_intelligence.ci.metrics import _compute_mi
|
|
205
|
+
|
|
206
|
+
mi_no_cm = _compute_mi(100.0, 5.0, 0.0)
|
|
207
|
+
mi_with_cm = _compute_mi(100.0, 5.0, 0.3)
|
|
208
|
+
assert mi_with_cm >= mi_no_cm
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# =========================================================================
|
|
212
|
+
# Line counting helpers
|
|
213
|
+
# =========================================================================
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class TestLineCounting:
|
|
217
|
+
"""Tests for _count_lines helper."""
|
|
218
|
+
|
|
219
|
+
def test_count_python_lines(self):
|
|
220
|
+
from semantic_code_intelligence.ci.metrics import _count_lines
|
|
221
|
+
|
|
222
|
+
code = "# comment\ndef foo():\n pass\n\n"
|
|
223
|
+
loc, comments, blanks = _count_lines(code)
|
|
224
|
+
assert comments >= 1
|
|
225
|
+
assert loc >= 2 # def foo, pass
|
|
226
|
+
assert blanks >= 1
|
|
227
|
+
|
|
228
|
+
def test_count_js_comments(self):
|
|
229
|
+
from semantic_code_intelligence.ci.metrics import _count_lines
|
|
230
|
+
|
|
231
|
+
code = "// comment\nfunction foo() {\n}\n"
|
|
232
|
+
loc, comments, blanks = _count_lines(code)
|
|
233
|
+
assert comments >= 1
|
|
234
|
+
|
|
235
|
+
def test_empty_content(self):
|
|
236
|
+
from semantic_code_intelligence.ci.metrics import _count_lines
|
|
237
|
+
|
|
238
|
+
loc, comments, blanks = _count_lines("")
|
|
239
|
+
assert loc == 0
|
|
240
|
+
assert comments == 0
|
|
241
|
+
assert blanks == 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# =========================================================================
|
|
245
|
+
# Quality snapshots
|
|
246
|
+
# =========================================================================
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class TestQualitySnapshots:
|
|
250
|
+
"""Tests for snapshot save/load via WorkspaceMemory."""
|
|
251
|
+
|
|
252
|
+
def test_save_snapshot(self, tmp_path):
|
|
253
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
254
|
+
ProjectMetrics,
|
|
255
|
+
save_snapshot,
|
|
256
|
+
)
|
|
257
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
258
|
+
|
|
259
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
260
|
+
pm = ProjectMetrics(
|
|
261
|
+
files_analyzed=5,
|
|
262
|
+
total_loc=200,
|
|
263
|
+
avg_complexity=3.5,
|
|
264
|
+
max_complexity=8,
|
|
265
|
+
total_symbols=20,
|
|
266
|
+
maintainability_index=72.5,
|
|
267
|
+
)
|
|
268
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
269
|
+
|
|
270
|
+
snap = save_snapshot(tmp_path, pm, qr, metadata={"branch": "main"})
|
|
271
|
+
assert snap.timestamp > 0
|
|
272
|
+
assert snap.maintainability_index == 72.5
|
|
273
|
+
assert snap.metadata["branch"] == "main"
|
|
274
|
+
|
|
275
|
+
def test_save_and_load(self, tmp_path):
|
|
276
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
277
|
+
ProjectMetrics,
|
|
278
|
+
load_snapshots,
|
|
279
|
+
save_snapshot,
|
|
280
|
+
)
|
|
281
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
282
|
+
|
|
283
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
284
|
+
pm = ProjectMetrics(
|
|
285
|
+
files_analyzed=3,
|
|
286
|
+
total_loc=100,
|
|
287
|
+
avg_complexity=2.0,
|
|
288
|
+
max_complexity=5,
|
|
289
|
+
total_symbols=10,
|
|
290
|
+
maintainability_index=80.0,
|
|
291
|
+
)
|
|
292
|
+
qr = QualityReport(files_analyzed=3, symbol_count=10)
|
|
293
|
+
|
|
294
|
+
save_snapshot(tmp_path, pm, qr)
|
|
295
|
+
time.sleep(0.01)
|
|
296
|
+
save_snapshot(tmp_path, pm, qr)
|
|
297
|
+
|
|
298
|
+
snaps = load_snapshots(tmp_path, limit=10)
|
|
299
|
+
assert len(snaps) >= 2
|
|
300
|
+
# Newest first
|
|
301
|
+
assert snaps[0].timestamp >= snaps[1].timestamp
|
|
302
|
+
|
|
303
|
+
def test_load_empty(self, tmp_path):
|
|
304
|
+
from semantic_code_intelligence.ci.metrics import load_snapshots
|
|
305
|
+
|
|
306
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
307
|
+
snaps = load_snapshots(tmp_path)
|
|
308
|
+
assert snaps == []
|
|
309
|
+
|
|
310
|
+
def test_snapshot_to_dict_roundtrip(self):
|
|
311
|
+
from semantic_code_intelligence.ci.metrics import QualitySnapshot
|
|
312
|
+
|
|
313
|
+
snap = QualitySnapshot(
|
|
314
|
+
timestamp=1234567890.0,
|
|
315
|
+
maintainability_index=65.0,
|
|
316
|
+
total_loc=500,
|
|
317
|
+
total_symbols=50,
|
|
318
|
+
issue_count=3,
|
|
319
|
+
files_analyzed=10,
|
|
320
|
+
avg_complexity=4.2,
|
|
321
|
+
comment_ratio=0.15,
|
|
322
|
+
metadata={"test": True},
|
|
323
|
+
)
|
|
324
|
+
d = snap.to_dict()
|
|
325
|
+
restored = QualitySnapshot.from_dict(d)
|
|
326
|
+
assert restored.timestamp == snap.timestamp
|
|
327
|
+
assert restored.maintainability_index == snap.maintainability_index
|
|
328
|
+
assert restored.metadata["test"] is True
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# =========================================================================
|
|
332
|
+
# Trend analysis
|
|
333
|
+
# =========================================================================
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class TestTrendAnalysis:
|
|
337
|
+
"""Tests for trend computation over snapshots."""
|
|
338
|
+
|
|
339
|
+
def _make_snapshot(self, ts, mi, issues=0, cc=1.0, loc=100):
|
|
340
|
+
from semantic_code_intelligence.ci.metrics import QualitySnapshot
|
|
341
|
+
|
|
342
|
+
return QualitySnapshot(
|
|
343
|
+
timestamp=ts,
|
|
344
|
+
maintainability_index=mi,
|
|
345
|
+
total_loc=loc,
|
|
346
|
+
total_symbols=10,
|
|
347
|
+
issue_count=issues,
|
|
348
|
+
files_analyzed=5,
|
|
349
|
+
avg_complexity=cc,
|
|
350
|
+
comment_ratio=0.1,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def test_improving_trend(self):
|
|
354
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
355
|
+
|
|
356
|
+
# MI getting better over time (newest first)
|
|
357
|
+
snaps = [
|
|
358
|
+
self._make_snapshot(3.0, 80.0),
|
|
359
|
+
self._make_snapshot(2.0, 60.0),
|
|
360
|
+
self._make_snapshot(1.0, 40.0),
|
|
361
|
+
]
|
|
362
|
+
t = compute_trend(snaps, "maintainability_index", higher_is_better=True)
|
|
363
|
+
assert t.direction == "improving"
|
|
364
|
+
assert t.delta > 0
|
|
365
|
+
|
|
366
|
+
def test_degrading_trend(self):
|
|
367
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
368
|
+
|
|
369
|
+
# MI getting worse
|
|
370
|
+
snaps = [
|
|
371
|
+
self._make_snapshot(3.0, 30.0),
|
|
372
|
+
self._make_snapshot(2.0, 50.0),
|
|
373
|
+
self._make_snapshot(1.0, 80.0),
|
|
374
|
+
]
|
|
375
|
+
t = compute_trend(snaps, "maintainability_index", higher_is_better=True)
|
|
376
|
+
assert t.direction == "degrading"
|
|
377
|
+
assert t.delta < 0
|
|
378
|
+
|
|
379
|
+
def test_stable_trend(self):
|
|
380
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
381
|
+
|
|
382
|
+
snaps = [
|
|
383
|
+
self._make_snapshot(3.0, 50.0),
|
|
384
|
+
self._make_snapshot(2.0, 50.0),
|
|
385
|
+
self._make_snapshot(1.0, 50.0),
|
|
386
|
+
]
|
|
387
|
+
t = compute_trend(snaps, "maintainability_index")
|
|
388
|
+
assert t.direction == "stable"
|
|
389
|
+
|
|
390
|
+
def test_empty_snapshots(self):
|
|
391
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
392
|
+
|
|
393
|
+
t = compute_trend([], "maintainability_index")
|
|
394
|
+
assert t.snapshot_count == 0
|
|
395
|
+
assert t.direction == "stable"
|
|
396
|
+
|
|
397
|
+
def test_single_snapshot(self):
|
|
398
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
399
|
+
|
|
400
|
+
snaps = [self._make_snapshot(1.0, 50.0)]
|
|
401
|
+
t = compute_trend(snaps, "maintainability_index")
|
|
402
|
+
assert t.direction == "stable"
|
|
403
|
+
assert t.snapshot_count == 1
|
|
404
|
+
|
|
405
|
+
def test_trend_to_dict(self):
|
|
406
|
+
from semantic_code_intelligence.ci.metrics import compute_trend
|
|
407
|
+
|
|
408
|
+
snaps = [
|
|
409
|
+
self._make_snapshot(2.0, 70.0),
|
|
410
|
+
self._make_snapshot(1.0, 50.0),
|
|
411
|
+
]
|
|
412
|
+
t = compute_trend(snaps, "maintainability_index")
|
|
413
|
+
d = t.to_dict()
|
|
414
|
+
assert "metric_name" in d
|
|
415
|
+
assert "direction" in d
|
|
416
|
+
assert "delta" in d
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# =========================================================================
|
|
420
|
+
# Quality policy & gate enforcement
|
|
421
|
+
# =========================================================================
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class TestQualityPolicy:
|
|
425
|
+
"""Tests for QualityPolicy dataclass."""
|
|
426
|
+
|
|
427
|
+
def test_defaults(self):
|
|
428
|
+
from semantic_code_intelligence.ci.metrics import QualityPolicy
|
|
429
|
+
|
|
430
|
+
p = QualityPolicy()
|
|
431
|
+
assert p.min_maintainability == 40.0
|
|
432
|
+
assert p.max_complexity == 25
|
|
433
|
+
assert p.max_issues == 20
|
|
434
|
+
|
|
435
|
+
def test_to_dict_roundtrip(self):
|
|
436
|
+
from semantic_code_intelligence.ci.metrics import QualityPolicy
|
|
437
|
+
|
|
438
|
+
p = QualityPolicy(min_maintainability=50.0, max_complexity=15)
|
|
439
|
+
d = p.to_dict()
|
|
440
|
+
restored = QualityPolicy.from_dict(d)
|
|
441
|
+
assert restored.min_maintainability == 50.0
|
|
442
|
+
assert restored.max_complexity == 15
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class TestGateEnforcement:
|
|
446
|
+
"""Tests for enforce_quality_gate."""
|
|
447
|
+
|
|
448
|
+
def test_gate_pass(self):
|
|
449
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
450
|
+
ProjectMetrics,
|
|
451
|
+
QualityPolicy,
|
|
452
|
+
enforce_quality_gate,
|
|
453
|
+
)
|
|
454
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
455
|
+
|
|
456
|
+
pm = ProjectMetrics(
|
|
457
|
+
maintainability_index=80.0,
|
|
458
|
+
max_complexity=5,
|
|
459
|
+
)
|
|
460
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
461
|
+
|
|
462
|
+
result = enforce_quality_gate(pm, qr, QualityPolicy())
|
|
463
|
+
assert result.passed is True
|
|
464
|
+
assert len(result.violations) == 0
|
|
465
|
+
|
|
466
|
+
def test_gate_fail_maintainability(self):
|
|
467
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
468
|
+
ProjectMetrics,
|
|
469
|
+
QualityPolicy,
|
|
470
|
+
enforce_quality_gate,
|
|
471
|
+
)
|
|
472
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
473
|
+
|
|
474
|
+
pm = ProjectMetrics(maintainability_index=20.0, max_complexity=5)
|
|
475
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
476
|
+
|
|
477
|
+
result = enforce_quality_gate(pm, qr, QualityPolicy(min_maintainability=40.0))
|
|
478
|
+
assert result.passed is False
|
|
479
|
+
assert any(v.rule == "min_maintainability" for v in result.violations)
|
|
480
|
+
|
|
481
|
+
def test_gate_fail_complexity(self):
|
|
482
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
483
|
+
ProjectMetrics,
|
|
484
|
+
QualityPolicy,
|
|
485
|
+
enforce_quality_gate,
|
|
486
|
+
)
|
|
487
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
488
|
+
|
|
489
|
+
pm = ProjectMetrics(maintainability_index=80.0, max_complexity=30)
|
|
490
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
491
|
+
|
|
492
|
+
result = enforce_quality_gate(pm, qr, QualityPolicy(max_complexity=25))
|
|
493
|
+
assert result.passed is False
|
|
494
|
+
assert any(v.rule == "max_complexity" for v in result.violations)
|
|
495
|
+
|
|
496
|
+
def test_gate_multiple_violations(self):
|
|
497
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
498
|
+
ProjectMetrics,
|
|
499
|
+
QualityPolicy,
|
|
500
|
+
enforce_quality_gate,
|
|
501
|
+
)
|
|
502
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
503
|
+
|
|
504
|
+
pm = ProjectMetrics(maintainability_index=10.0, max_complexity=50)
|
|
505
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
506
|
+
|
|
507
|
+
result = enforce_quality_gate(pm, qr, QualityPolicy())
|
|
508
|
+
assert result.passed is False
|
|
509
|
+
assert len(result.violations) >= 2
|
|
510
|
+
|
|
511
|
+
def test_gate_result_to_dict(self):
|
|
512
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
513
|
+
ProjectMetrics,
|
|
514
|
+
enforce_quality_gate,
|
|
515
|
+
)
|
|
516
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
517
|
+
|
|
518
|
+
pm = ProjectMetrics(maintainability_index=80.0, max_complexity=5)
|
|
519
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
520
|
+
|
|
521
|
+
result = enforce_quality_gate(pm, qr)
|
|
522
|
+
d = result.to_dict()
|
|
523
|
+
assert "passed" in d
|
|
524
|
+
assert "violations" in d
|
|
525
|
+
assert "policy" in d
|
|
526
|
+
|
|
527
|
+
def test_gate_default_policy(self):
|
|
528
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
529
|
+
ProjectMetrics,
|
|
530
|
+
enforce_quality_gate,
|
|
531
|
+
)
|
|
532
|
+
from semantic_code_intelligence.ci.quality import QualityReport
|
|
533
|
+
|
|
534
|
+
pm = ProjectMetrics(maintainability_index=80.0, max_complexity=5)
|
|
535
|
+
qr = QualityReport(files_analyzed=5, symbol_count=20)
|
|
536
|
+
|
|
537
|
+
# Should use default policy
|
|
538
|
+
result = enforce_quality_gate(pm, qr)
|
|
539
|
+
assert result.passed is True
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
# =========================================================================
|
|
543
|
+
# CLI: metrics command
|
|
544
|
+
# =========================================================================
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class TestMetricsCLI:
|
|
548
|
+
"""Tests for the `codexa metrics` CLI command."""
|
|
549
|
+
|
|
550
|
+
@pytest.fixture
|
|
551
|
+
def runner(self):
|
|
552
|
+
return CliRunner()
|
|
553
|
+
|
|
554
|
+
def test_basic_metrics_json(self, runner, tmp_path):
|
|
555
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
556
|
+
|
|
557
|
+
_write_sample_project(tmp_path)
|
|
558
|
+
result = runner.invoke(metrics_cmd, [
|
|
559
|
+
"--path", str(tmp_path), "--json",
|
|
560
|
+
], obj={"pipe": False})
|
|
561
|
+
assert result.exit_code == 0
|
|
562
|
+
data = json.loads(result.output)
|
|
563
|
+
assert "files_analyzed" in data
|
|
564
|
+
assert "maintainability_index" in data
|
|
565
|
+
|
|
566
|
+
def test_pipe_mode(self, runner, tmp_path):
|
|
567
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
568
|
+
|
|
569
|
+
_write_sample_project(tmp_path)
|
|
570
|
+
result = runner.invoke(metrics_cmd, [
|
|
571
|
+
"--path", str(tmp_path), "--pipe",
|
|
572
|
+
], obj={"pipe": False})
|
|
573
|
+
assert result.exit_code == 0
|
|
574
|
+
assert "MI:" in result.output
|
|
575
|
+
|
|
576
|
+
def test_rich_output(self, runner, tmp_path):
|
|
577
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
578
|
+
|
|
579
|
+
_write_sample_project(tmp_path)
|
|
580
|
+
result = runner.invoke(metrics_cmd, [
|
|
581
|
+
"--path", str(tmp_path),
|
|
582
|
+
], obj={"pipe": False})
|
|
583
|
+
assert result.exit_code == 0
|
|
584
|
+
assert "Quality Metrics" in result.output
|
|
585
|
+
|
|
586
|
+
def test_snapshot_json(self, runner, tmp_path):
|
|
587
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
588
|
+
|
|
589
|
+
_write_sample_project(tmp_path)
|
|
590
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
591
|
+
result = runner.invoke(metrics_cmd, [
|
|
592
|
+
"--path", str(tmp_path), "--json", "--snapshot",
|
|
593
|
+
], obj={"pipe": False})
|
|
594
|
+
assert result.exit_code == 0
|
|
595
|
+
data = json.loads(result.output)
|
|
596
|
+
assert "snapshot" in data
|
|
597
|
+
|
|
598
|
+
def test_history_empty(self, runner, tmp_path):
|
|
599
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
600
|
+
|
|
601
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
602
|
+
result = runner.invoke(metrics_cmd, [
|
|
603
|
+
"--path", str(tmp_path), "--history", "5",
|
|
604
|
+
], obj={"pipe": False})
|
|
605
|
+
assert result.exit_code == 0
|
|
606
|
+
|
|
607
|
+
def test_trend_needs_data(self, runner, tmp_path):
|
|
608
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
609
|
+
|
|
610
|
+
(tmp_path / ".codexa").mkdir(exist_ok=True)
|
|
611
|
+
result = runner.invoke(metrics_cmd, [
|
|
612
|
+
"--path", str(tmp_path), "--trend",
|
|
613
|
+
], obj={"pipe": False})
|
|
614
|
+
assert result.exit_code == 0
|
|
615
|
+
# Should tell user they need more snapshots
|
|
616
|
+
assert "2 snapshots" in result.output.lower() or "need" in result.output.lower()
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
# =========================================================================
|
|
620
|
+
# CLI: gate command
|
|
621
|
+
# =========================================================================
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class TestGateCLI:
|
|
625
|
+
"""Tests for the `codexa gate` CLI command."""
|
|
626
|
+
|
|
627
|
+
@pytest.fixture
|
|
628
|
+
def runner(self):
|
|
629
|
+
return CliRunner()
|
|
630
|
+
|
|
631
|
+
def test_gate_json(self, runner, tmp_path):
|
|
632
|
+
from semantic_code_intelligence.cli.commands.gate_cmd import gate_cmd
|
|
633
|
+
|
|
634
|
+
_write_sample_project(tmp_path)
|
|
635
|
+
result = runner.invoke(gate_cmd, [
|
|
636
|
+
"--path", str(tmp_path), "--json",
|
|
637
|
+
], obj={"pipe": False})
|
|
638
|
+
assert result.exit_code == 0
|
|
639
|
+
data = json.loads(result.output)
|
|
640
|
+
assert "passed" in data
|
|
641
|
+
assert "violations" in data
|
|
642
|
+
|
|
643
|
+
def test_gate_pipe_pass(self, runner, tmp_path):
|
|
644
|
+
from semantic_code_intelligence.cli.commands.gate_cmd import gate_cmd
|
|
645
|
+
|
|
646
|
+
_write_sample_project(tmp_path)
|
|
647
|
+
result = runner.invoke(gate_cmd, [
|
|
648
|
+
"--path", str(tmp_path), "--pipe",
|
|
649
|
+
], obj={"pipe": False})
|
|
650
|
+
assert result.exit_code == 0
|
|
651
|
+
assert "MI=" in result.output
|
|
652
|
+
|
|
653
|
+
def test_gate_rich_output(self, runner, tmp_path):
|
|
654
|
+
from semantic_code_intelligence.cli.commands.gate_cmd import gate_cmd
|
|
655
|
+
|
|
656
|
+
_write_sample_project(tmp_path)
|
|
657
|
+
result = runner.invoke(gate_cmd, [
|
|
658
|
+
"--path", str(tmp_path),
|
|
659
|
+
], obj={"pipe": False})
|
|
660
|
+
assert result.exit_code == 0
|
|
661
|
+
|
|
662
|
+
def test_gate_custom_thresholds(self, runner, tmp_path):
|
|
663
|
+
from semantic_code_intelligence.cli.commands.gate_cmd import gate_cmd
|
|
664
|
+
|
|
665
|
+
_write_sample_project(tmp_path)
|
|
666
|
+
result = runner.invoke(gate_cmd, [
|
|
667
|
+
"--path", str(tmp_path), "--json",
|
|
668
|
+
"--min-maintainability", "99",
|
|
669
|
+
], obj={"pipe": False})
|
|
670
|
+
assert result.exit_code == 0
|
|
671
|
+
data = json.loads(result.output)
|
|
672
|
+
# With MI threshold of 99, should likely fail
|
|
673
|
+
assert "passed" in data
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
# =========================================================================
|
|
677
|
+
# Configuration extension
|
|
678
|
+
# =========================================================================
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
class TestQualityConfigExtension:
|
|
682
|
+
"""Tests for QualityConfig in AppConfig."""
|
|
683
|
+
|
|
684
|
+
def test_default_quality_config(self):
|
|
685
|
+
from semantic_code_intelligence.config.settings import AppConfig
|
|
686
|
+
|
|
687
|
+
cfg = AppConfig()
|
|
688
|
+
assert hasattr(cfg, "quality")
|
|
689
|
+
assert cfg.quality.complexity_threshold == 10
|
|
690
|
+
assert cfg.quality.min_maintainability == 40.0
|
|
691
|
+
assert cfg.quality.snapshot_on_index is False
|
|
692
|
+
|
|
693
|
+
def test_quality_config_in_json(self):
|
|
694
|
+
from semantic_code_intelligence.config.settings import AppConfig
|
|
695
|
+
|
|
696
|
+
cfg = AppConfig()
|
|
697
|
+
d = json.loads(cfg.model_dump_json())
|
|
698
|
+
assert "quality" in d
|
|
699
|
+
assert d["quality"]["complexity_threshold"] == 10
|
|
700
|
+
|
|
701
|
+
def test_load_config_with_quality(self, tmp_path):
|
|
702
|
+
from semantic_code_intelligence.config.settings import (
|
|
703
|
+
AppConfig,
|
|
704
|
+
save_config,
|
|
705
|
+
load_config,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
cfg = AppConfig(project_root=str(tmp_path))
|
|
709
|
+
cfg.quality.min_maintainability = 60.0
|
|
710
|
+
save_config(cfg, tmp_path)
|
|
711
|
+
|
|
712
|
+
loaded = load_config(tmp_path)
|
|
713
|
+
assert loaded.quality.min_maintainability == 60.0
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
# =========================================================================
|
|
717
|
+
# Documentation generation
|
|
718
|
+
# =========================================================================
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
class TestDocsPhase17:
|
|
722
|
+
"""Tests for QUALITY_METRICS.md documentation generation."""
|
|
723
|
+
|
|
724
|
+
def test_quality_metrics_reference(self):
|
|
725
|
+
from semantic_code_intelligence.docs import generate_quality_metrics_reference
|
|
726
|
+
|
|
727
|
+
md = generate_quality_metrics_reference()
|
|
728
|
+
assert "Maintainability Index" in md
|
|
729
|
+
assert "Quality Gates" in md
|
|
730
|
+
assert "codexa gate" in md
|
|
731
|
+
assert "codexa metrics" in md
|
|
732
|
+
|
|
733
|
+
def test_generate_all_docs_includes_quality(self, tmp_path):
|
|
734
|
+
from semantic_code_intelligence.docs import generate_all_docs
|
|
735
|
+
|
|
736
|
+
generated = generate_all_docs(tmp_path)
|
|
737
|
+
assert "QUALITY_METRICS.md" in generated
|
|
738
|
+
|
|
739
|
+
def test_ci_reference_updated(self):
|
|
740
|
+
from semantic_code_intelligence.docs import generate_ci_reference
|
|
741
|
+
|
|
742
|
+
md = generate_ci_reference()
|
|
743
|
+
assert "codexa metrics" in md
|
|
744
|
+
assert "codexa gate" in md
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
# =========================================================================
|
|
748
|
+
# Router, version, and module structure tests
|
|
749
|
+
# =========================================================================
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
class TestRouterPhase17:
|
|
753
|
+
"""Tests for CLI router registration."""
|
|
754
|
+
|
|
755
|
+
def test_register_commands_count(self):
|
|
756
|
+
import click
|
|
757
|
+
from semantic_code_intelligence.cli.router import register_commands
|
|
758
|
+
|
|
759
|
+
group = click.Group("test")
|
|
760
|
+
register_commands(group)
|
|
761
|
+
assert len(group.commands) == 39
|
|
762
|
+
|
|
763
|
+
def test_metrics_command_registered(self):
|
|
764
|
+
from semantic_code_intelligence.cli.main import cli
|
|
765
|
+
|
|
766
|
+
assert "metrics" in cli.commands
|
|
767
|
+
|
|
768
|
+
def test_gate_command_registered(self):
|
|
769
|
+
from semantic_code_intelligence.cli.main import cli
|
|
770
|
+
|
|
771
|
+
assert "gate" in cli.commands
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
class TestVersionBump17:
|
|
775
|
+
"""Test version is 0.19.0."""
|
|
776
|
+
|
|
777
|
+
def test_version_is_017(self):
|
|
778
|
+
from semantic_code_intelligence import __version__
|
|
779
|
+
|
|
780
|
+
assert __version__ == "0.4.0"
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
class TestPhase17ModuleStructure:
|
|
784
|
+
"""Tests for module import structure."""
|
|
785
|
+
|
|
786
|
+
def test_import_metrics(self):
|
|
787
|
+
from semantic_code_intelligence.ci.metrics import (
|
|
788
|
+
FileMetrics,
|
|
789
|
+
ProjectMetrics,
|
|
790
|
+
QualitySnapshot,
|
|
791
|
+
TrendResult,
|
|
792
|
+
QualityPolicy,
|
|
793
|
+
GateResult,
|
|
794
|
+
GateViolation,
|
|
795
|
+
compute_file_metrics,
|
|
796
|
+
compute_project_metrics,
|
|
797
|
+
save_snapshot,
|
|
798
|
+
load_snapshots,
|
|
799
|
+
compute_trend,
|
|
800
|
+
enforce_quality_gate,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
def test_import_quality_config(self):
|
|
804
|
+
from semantic_code_intelligence.config.settings import QualityConfig
|
|
805
|
+
|
|
806
|
+
def test_import_metrics_cmd(self):
|
|
807
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import metrics_cmd
|
|
808
|
+
|
|
809
|
+
def test_import_gate_cmd(self):
|
|
810
|
+
from semantic_code_intelligence.cli.commands.gate_cmd import gate_cmd
|
|
811
|
+
|
|
812
|
+
def test_ci_module_docstring_updated(self):
|
|
813
|
+
import semantic_code_intelligence.ci as ci_mod
|
|
814
|
+
|
|
815
|
+
assert "metrics" in ci_mod.__doc__.lower()
|