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,428 @@
|
|
|
1
|
+
"""Phase 21 — Mypy Strict Typing & Pytest Coverage Gate.
|
|
2
|
+
|
|
3
|
+
Tests verify:
|
|
4
|
+
1. pyproject.toml mypy strict config exists and is correct
|
|
5
|
+
2. pyproject.toml coverage config exists with fail_under gate
|
|
6
|
+
3. All 49 original mypy errors stay fixed (regression guard)
|
|
7
|
+
4. Type annotations are present on key functions
|
|
8
|
+
5. No bare ``dict`` return types remain in source
|
|
9
|
+
6. Critical bug fixes (SafetyReport import, FileDependency attr, etc.)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import ast
|
|
15
|
+
import configparser
|
|
16
|
+
import importlib
|
|
17
|
+
import inspect
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
from unittest.mock import MagicMock, patch
|
|
24
|
+
|
|
25
|
+
import pytest
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Helpers
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
_PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
32
|
+
_PYPROJECT = _PROJECT_ROOT / "pyproject.toml"
|
|
33
|
+
_SRC = _PROJECT_ROOT / "semantic_code_intelligence"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read_pyproject() -> str:
|
|
37
|
+
return _PYPROJECT.read_text(encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
# 1 — pyproject.toml: [tool.mypy] section
|
|
42
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
class TestMypyConfig:
|
|
45
|
+
"""[tool.mypy] section validations."""
|
|
46
|
+
|
|
47
|
+
def test_mypy_section_exists(self) -> None:
|
|
48
|
+
text = _read_pyproject()
|
|
49
|
+
assert "[tool.mypy]" in text
|
|
50
|
+
|
|
51
|
+
def test_strict_enabled(self) -> None:
|
|
52
|
+
text = _read_pyproject()
|
|
53
|
+
assert "strict = true" in text
|
|
54
|
+
|
|
55
|
+
def test_warn_return_any(self) -> None:
|
|
56
|
+
text = _read_pyproject()
|
|
57
|
+
assert "warn_return_any = true" in text
|
|
58
|
+
|
|
59
|
+
def test_warn_unused_ignores(self) -> None:
|
|
60
|
+
text = _read_pyproject()
|
|
61
|
+
assert "warn_unused_ignores = true" in text
|
|
62
|
+
|
|
63
|
+
def test_ignore_missing_imports(self) -> None:
|
|
64
|
+
text = _read_pyproject()
|
|
65
|
+
assert "ignore_missing_imports = true" in text
|
|
66
|
+
|
|
67
|
+
def test_tests_excluded(self) -> None:
|
|
68
|
+
text = _read_pyproject()
|
|
69
|
+
assert 'exclude = ["tests/"]' in text
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
73
|
+
# 2 — pyproject.toml: coverage config
|
|
74
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
75
|
+
|
|
76
|
+
class TestCoverageConfig:
|
|
77
|
+
"""[tool.coverage.*] section validations."""
|
|
78
|
+
|
|
79
|
+
def test_coverage_run_section_exists(self) -> None:
|
|
80
|
+
text = _read_pyproject()
|
|
81
|
+
assert "[tool.coverage.run]" in text
|
|
82
|
+
|
|
83
|
+
def test_coverage_source(self) -> None:
|
|
84
|
+
text = _read_pyproject()
|
|
85
|
+
assert 'source = ["semantic_code_intelligence"]' in text
|
|
86
|
+
|
|
87
|
+
def test_coverage_omit_tests(self) -> None:
|
|
88
|
+
text = _read_pyproject()
|
|
89
|
+
assert "semantic_code_intelligence/tests/*" in text
|
|
90
|
+
|
|
91
|
+
def test_coverage_report_section_exists(self) -> None:
|
|
92
|
+
text = _read_pyproject()
|
|
93
|
+
assert "[tool.coverage.report]" in text
|
|
94
|
+
|
|
95
|
+
def test_fail_under_gate(self) -> None:
|
|
96
|
+
text = _read_pyproject()
|
|
97
|
+
# Expect fail_under = 70 (or any integer >= 70)
|
|
98
|
+
match = re.search(r"fail_under\s*=\s*(\d+)", text)
|
|
99
|
+
assert match is not None, "fail_under not found in pyproject.toml"
|
|
100
|
+
assert int(match.group(1)) >= 70
|
|
101
|
+
|
|
102
|
+
def test_show_missing(self) -> None:
|
|
103
|
+
text = _read_pyproject()
|
|
104
|
+
assert "show_missing = true" in text
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
108
|
+
# 3 — Bug-fix regression guards (the 49 original errors)
|
|
109
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
110
|
+
|
|
111
|
+
class TestSafetyReportImport:
|
|
112
|
+
"""ci/pr.py must import SafetyReport."""
|
|
113
|
+
|
|
114
|
+
def test_import_exists(self) -> None:
|
|
115
|
+
from semantic_code_intelligence.ci import pr
|
|
116
|
+
assert hasattr(pr, "SafetyReport")
|
|
117
|
+
|
|
118
|
+
def test_safety_report_in_source(self) -> None:
|
|
119
|
+
src = (_SRC / "ci" / "pr.py").read_text(encoding="utf-8")
|
|
120
|
+
assert "SafetyReport" in src
|
|
121
|
+
assert "from semantic_code_intelligence.llm.safety import" in src
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestFileDependencyAttr:
|
|
125
|
+
"""investigation.py must use .import_text not .module."""
|
|
126
|
+
|
|
127
|
+
def test_no_dot_module_usage(self) -> None:
|
|
128
|
+
src = (_SRC / "llm" / "investigation.py").read_text(encoding="utf-8")
|
|
129
|
+
# Should NOT contain d.module
|
|
130
|
+
assert ".module" not in src or "import_text" in src
|
|
131
|
+
|
|
132
|
+
def test_import_text_usage(self) -> None:
|
|
133
|
+
src = (_SRC / "llm" / "investigation.py").read_text(encoding="utf-8")
|
|
134
|
+
assert "import_text" in src
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TestVizCmdReposList:
|
|
138
|
+
"""viz_cmd.py must iterate list, not call .values()."""
|
|
139
|
+
|
|
140
|
+
def test_no_values_call(self) -> None:
|
|
141
|
+
src = (_SRC / "cli" / "commands" / "viz_cmd.py").read_text(encoding="utf-8")
|
|
142
|
+
assert ".repos.values()" not in src
|
|
143
|
+
|
|
144
|
+
def test_direct_iteration(self) -> None:
|
|
145
|
+
src = (_SRC / "cli" / "commands" / "viz_cmd.py").read_text(encoding="utf-8")
|
|
146
|
+
assert "ws.repos]" in src or "r.to_dict() for r in ws.repos" in src
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestQualityCmdDuplicateVar:
|
|
150
|
+
"""quality_cmd.py should not reuse dead_code var name for duplicates."""
|
|
151
|
+
|
|
152
|
+
def test_duplicate_loop_uses_dup_var(self) -> None:
|
|
153
|
+
src = (_SRC / "cli" / "commands" / "quality_cmd.py").read_text(encoding="utf-8")
|
|
154
|
+
# The duplicates loop should use 'dup' not 'd'
|
|
155
|
+
assert "for dup in report.duplicates:" in src
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestCrossRefactorTuple:
|
|
159
|
+
"""cross_refactor.py pair_key must be tuple[str, str]."""
|
|
160
|
+
|
|
161
|
+
def test_no_bare_tuple_sorted(self) -> None:
|
|
162
|
+
src = (_SRC / "llm" / "cross_refactor.py").read_text(encoding="utf-8")
|
|
163
|
+
# Should NOT have tuple(sorted([...]))
|
|
164
|
+
assert "tuple(sorted(" not in src
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TestImpactAffectedSymbolVar:
|
|
168
|
+
"""impact.py loop vars for AffectedSymbol lists renamed."""
|
|
169
|
+
|
|
170
|
+
def test_direct_loop_uses_af(self) -> None:
|
|
171
|
+
src = (_SRC / "ci" / "impact.py").read_text(encoding="utf-8")
|
|
172
|
+
assert "for af in direct:" in src or "for af in direct[" in src
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
# 4 — Type annotation presence checks
|
|
177
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
+
|
|
179
|
+
class TestGetProviderAnnotated:
|
|
180
|
+
"""_get_provider functions must have type annotations."""
|
|
181
|
+
|
|
182
|
+
@pytest.mark.parametrize("mod_path", [
|
|
183
|
+
"semantic_code_intelligence.cli.commands.chat_cmd",
|
|
184
|
+
"semantic_code_intelligence.cli.commands.ask_cmd",
|
|
185
|
+
"semantic_code_intelligence.cli.commands.investigate_cmd",
|
|
186
|
+
])
|
|
187
|
+
def test_get_provider_has_return_annotation(self, mod_path: str) -> None:
|
|
188
|
+
mod = importlib.import_module(mod_path)
|
|
189
|
+
fn = getattr(mod, "_get_provider")
|
|
190
|
+
hints = fn.__annotations__
|
|
191
|
+
assert "return" in hints, f"{mod_path}._get_provider missing return annotation"
|
|
192
|
+
|
|
193
|
+
@pytest.mark.parametrize("mod_path", [
|
|
194
|
+
"semantic_code_intelligence.cli.commands.chat_cmd",
|
|
195
|
+
"semantic_code_intelligence.cli.commands.ask_cmd",
|
|
196
|
+
"semantic_code_intelligence.cli.commands.investigate_cmd",
|
|
197
|
+
])
|
|
198
|
+
def test_get_provider_has_config_annotation(self, mod_path: str) -> None:
|
|
199
|
+
mod = importlib.import_module(mod_path)
|
|
200
|
+
fn = getattr(mod, "_get_provider")
|
|
201
|
+
hints = fn.__annotations__
|
|
202
|
+
assert "config" in hints, f"{mod_path}._get_provider missing config annotation"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class TestDoctorCmdTypedDicts:
|
|
206
|
+
"""doctor_cmd.py functions must return dict[str, Any], not bare dict."""
|
|
207
|
+
|
|
208
|
+
def test_check_python_return_annotation(self) -> None:
|
|
209
|
+
from semantic_code_intelligence.cli.commands import doctor_cmd
|
|
210
|
+
hints = doctor_cmd._check_python.__annotations__
|
|
211
|
+
assert "return" in hints
|
|
212
|
+
|
|
213
|
+
def test_check_package_return_annotation(self) -> None:
|
|
214
|
+
from semantic_code_intelligence.cli.commands import doctor_cmd
|
|
215
|
+
hints = doctor_cmd._check_package.__annotations__
|
|
216
|
+
assert "return" in hints
|
|
217
|
+
|
|
218
|
+
def test_check_project_return_annotation(self) -> None:
|
|
219
|
+
from semantic_code_intelligence.cli.commands import doctor_cmd
|
|
220
|
+
hints = doctor_cmd._check_project.__annotations__
|
|
221
|
+
assert "return" in hints
|
|
222
|
+
|
|
223
|
+
def test_run_checks_return_annotation(self) -> None:
|
|
224
|
+
from semantic_code_intelligence.cli.commands import doctor_cmd
|
|
225
|
+
hints = doctor_cmd.run_checks.__annotations__
|
|
226
|
+
assert "return" in hints
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestSearchServiceTypedDict:
|
|
230
|
+
"""search_service.SearchResult.to_dict must return dict[str, Any]."""
|
|
231
|
+
|
|
232
|
+
def test_to_dict_return_type(self) -> None:
|
|
233
|
+
from semantic_code_intelligence.services.search_service import SearchResult
|
|
234
|
+
hints = SearchResult.to_dict.__annotations__
|
|
235
|
+
assert "return" in hints
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
239
|
+
# 5 — No bare dict returns in source (AST scan)
|
|
240
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
241
|
+
|
|
242
|
+
class TestNoBareDictReturns:
|
|
243
|
+
"""Source files should not have bare 'dict' return annotations."""
|
|
244
|
+
|
|
245
|
+
@pytest.mark.parametrize("rel_path", [
|
|
246
|
+
"cli/commands/doctor_cmd.py",
|
|
247
|
+
"services/search_service.py",
|
|
248
|
+
"tools/__init__.py",
|
|
249
|
+
"bridge/context_provider.py",
|
|
250
|
+
])
|
|
251
|
+
def test_no_bare_dict_in_file(self, rel_path: str) -> None:
|
|
252
|
+
src = (_SRC / rel_path).read_text(encoding="utf-8")
|
|
253
|
+
tree = ast.parse(src)
|
|
254
|
+
for node in ast.walk(tree):
|
|
255
|
+
if isinstance(node, ast.FunctionDef):
|
|
256
|
+
ret = node.returns
|
|
257
|
+
if ret is not None:
|
|
258
|
+
# Check for bare Name("dict") without subscript
|
|
259
|
+
if isinstance(ret, ast.Name) and ret.id == "dict":
|
|
260
|
+
pytest.fail(
|
|
261
|
+
f"{rel_path}:{node.lineno} — "
|
|
262
|
+
f"function '{node.name}' has bare 'dict' return"
|
|
263
|
+
)
|
|
264
|
+
# Check for list[dict] without subscript
|
|
265
|
+
if isinstance(ret, ast.Subscript):
|
|
266
|
+
if isinstance(ret.slice, ast.Name) and ret.slice.id == "dict":
|
|
267
|
+
pytest.fail(
|
|
268
|
+
f"{rel_path}:{node.lineno} — "
|
|
269
|
+
f"function '{node.name}' has list[dict] return"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
274
|
+
# 6 — No-any-return fixes
|
|
275
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
276
|
+
|
|
277
|
+
class TestNoAnyReturnFixes:
|
|
278
|
+
"""Key functions should cast/annotate to avoid returning Any."""
|
|
279
|
+
|
|
280
|
+
def test_ollama_api_call_returns_typed(self) -> None:
|
|
281
|
+
src = (_SRC / "llm" / "ollama_provider.py").read_text(encoding="utf-8")
|
|
282
|
+
assert "result: dict[str, Any]" in src
|
|
283
|
+
|
|
284
|
+
def test_vector_store_size_int_cast(self) -> None:
|
|
285
|
+
src = (_SRC / "storage" / "vector_store.py").read_text(encoding="utf-8")
|
|
286
|
+
assert "int(self.index.ntotal)" in src
|
|
287
|
+
|
|
288
|
+
def test_embedding_dim_none_guard(self) -> None:
|
|
289
|
+
src = (_SRC / "embeddings" / "generator.py").read_text(encoding="utf-8")
|
|
290
|
+
assert "if dim is None:" in src
|
|
291
|
+
|
|
292
|
+
def test_templates_typed_generators(self) -> None:
|
|
293
|
+
src = (_SRC / "ci" / "templates.py").read_text(encoding="utf-8")
|
|
294
|
+
assert "Callable[..., str]" in src
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
298
|
+
# 7 — Stale type:ignore removal
|
|
299
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
300
|
+
|
|
301
|
+
class TestNoStaleTypeIgnore:
|
|
302
|
+
"""Unused type:ignore comments should be removed."""
|
|
303
|
+
|
|
304
|
+
def test_plugins_no_unused_ignore(self) -> None:
|
|
305
|
+
src = (_SRC / "plugins" / "__init__.py").read_text(encoding="utf-8")
|
|
306
|
+
# The exec_module line should NOT have type: ignore
|
|
307
|
+
for line in src.splitlines():
|
|
308
|
+
if "exec_module" in line:
|
|
309
|
+
assert "type: ignore" not in line, f"Stale type:ignore: {line}"
|
|
310
|
+
|
|
311
|
+
def test_openai_no_unused_ignore(self) -> None:
|
|
312
|
+
src = (_SRC / "llm" / "openai_provider.py").read_text(encoding="utf-8")
|
|
313
|
+
for line in src.splitlines():
|
|
314
|
+
if "import openai" in line and "ignore" in line.lower():
|
|
315
|
+
pytest.fail(f"Stale type:ignore on openai import: {line}")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
319
|
+
# 8 — docs/__init__.py click type fix
|
|
320
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
321
|
+
|
|
322
|
+
class TestDocsClickType:
|
|
323
|
+
"""docs/__init__.py should not use click.BaseCommand as type hint."""
|
|
324
|
+
|
|
325
|
+
def test_no_base_command_annotation(self) -> None:
|
|
326
|
+
src = (_SRC / "docs" / "__init__.py").read_text(encoding="utf-8")
|
|
327
|
+
assert "group: click.BaseCommand" not in src
|
|
328
|
+
|
|
329
|
+
def test_uses_proper_type(self) -> None:
|
|
330
|
+
src = (_SRC / "docs" / "__init__.py").read_text(encoding="utf-8")
|
|
331
|
+
# Should use click.Group | click.Command or similar
|
|
332
|
+
assert "click.Group" in src or "click.Command" in src
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
336
|
+
# 9 — Functional regression: fixed modules still work
|
|
337
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
338
|
+
|
|
339
|
+
class TestFixedModulesImport:
|
|
340
|
+
"""All fixed modules import without error."""
|
|
341
|
+
|
|
342
|
+
@pytest.mark.parametrize("mod_path", [
|
|
343
|
+
"semantic_code_intelligence.ci.pr",
|
|
344
|
+
"semantic_code_intelligence.ci.impact",
|
|
345
|
+
"semantic_code_intelligence.ci.templates",
|
|
346
|
+
"semantic_code_intelligence.cli.commands.doctor_cmd",
|
|
347
|
+
"semantic_code_intelligence.cli.commands.quality_cmd",
|
|
348
|
+
"semantic_code_intelligence.cli.commands.chat_cmd",
|
|
349
|
+
"semantic_code_intelligence.cli.commands.ask_cmd",
|
|
350
|
+
"semantic_code_intelligence.cli.commands.investigate_cmd",
|
|
351
|
+
"semantic_code_intelligence.cli.commands.viz_cmd",
|
|
352
|
+
"semantic_code_intelligence.cli.commands.cross_refactor_cmd",
|
|
353
|
+
"semantic_code_intelligence.llm.ollama_provider",
|
|
354
|
+
"semantic_code_intelligence.llm.openai_provider",
|
|
355
|
+
"semantic_code_intelligence.llm.investigation",
|
|
356
|
+
"semantic_code_intelligence.llm.cross_refactor",
|
|
357
|
+
"semantic_code_intelligence.storage.vector_store",
|
|
358
|
+
"semantic_code_intelligence.embeddings.generator",
|
|
359
|
+
"semantic_code_intelligence.services.search_service",
|
|
360
|
+
"semantic_code_intelligence.tools",
|
|
361
|
+
"semantic_code_intelligence.search.formatter",
|
|
362
|
+
"semantic_code_intelligence.bridge.context_provider",
|
|
363
|
+
"semantic_code_intelligence.web.api",
|
|
364
|
+
"semantic_code_intelligence.docs",
|
|
365
|
+
"semantic_code_intelligence.plugins",
|
|
366
|
+
])
|
|
367
|
+
def test_module_imports(self, mod_path: str) -> None:
|
|
368
|
+
importlib.import_module(mod_path)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
372
|
+
# 10 — Coverage gate configuration is respected
|
|
373
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
374
|
+
|
|
375
|
+
class TestCoverageGateIntegrity:
|
|
376
|
+
"""Coverage gate integrates properly with pytest-cov."""
|
|
377
|
+
|
|
378
|
+
def test_pytest_cov_installed(self) -> None:
|
|
379
|
+
import pytest_cov # noqa: F401
|
|
380
|
+
|
|
381
|
+
def test_coverage_source_matches_package(self) -> None:
|
|
382
|
+
text = _read_pyproject()
|
|
383
|
+
assert "semantic_code_intelligence" in text
|
|
384
|
+
# Coverage source points to the right package
|
|
385
|
+
match = re.search(r'source\s*=\s*\["([^"]+)"\]', text)
|
|
386
|
+
assert match is not None
|
|
387
|
+
assert match.group(1) == "semantic_code_intelligence"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
391
|
+
# 11 — TYPE_CHECKING guard pattern
|
|
392
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
393
|
+
|
|
394
|
+
class TestTypeCheckingGuard:
|
|
395
|
+
"""Files using TYPE_CHECKING should guard runtime-only imports."""
|
|
396
|
+
|
|
397
|
+
@pytest.mark.parametrize("rel_path", [
|
|
398
|
+
"cli/commands/chat_cmd.py",
|
|
399
|
+
"cli/commands/ask_cmd.py",
|
|
400
|
+
"cli/commands/investigate_cmd.py",
|
|
401
|
+
])
|
|
402
|
+
def test_type_checking_import(self, rel_path: str) -> None:
|
|
403
|
+
src = (_SRC / rel_path).read_text(encoding="utf-8")
|
|
404
|
+
assert "TYPE_CHECKING" in src
|
|
405
|
+
assert "if TYPE_CHECKING:" in src
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
409
|
+
# 12 — Version tagging
|
|
410
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
411
|
+
|
|
412
|
+
class TestVersion:
|
|
413
|
+
"""Version must be ≥ 0.4.0 for stable release."""
|
|
414
|
+
|
|
415
|
+
def test_version_in_init(self) -> None:
|
|
416
|
+
from semantic_code_intelligence import __version__
|
|
417
|
+
parts = __version__.split(".")
|
|
418
|
+
assert len(parts) >= 3
|
|
419
|
+
major, minor = int(parts[0]), int(parts[1])
|
|
420
|
+
assert (major, minor) >= (0, 4)
|
|
421
|
+
|
|
422
|
+
def test_version_in_pyproject(self) -> None:
|
|
423
|
+
text = _read_pyproject()
|
|
424
|
+
match = re.search(r'version\s*=\s*"(\d+\.\d+\.\d+)"', text)
|
|
425
|
+
assert match is not None
|
|
426
|
+
parts = match.group(1).split(".")
|
|
427
|
+
major, minor = int(parts[0]), int(parts[1])
|
|
428
|
+
assert (major, minor) >= (0, 4)
|