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,496 @@
|
|
|
1
|
+
"""Phase 25 — Incremental Indexing & Quality Refactors.
|
|
2
|
+
|
|
3
|
+
Tests verify:
|
|
4
|
+
1. VectorStore.remove_by_file — correct removal and index rebuild
|
|
5
|
+
2. Incremental indexing — stale vector removal, deleted file cleanup
|
|
6
|
+
3. HF_TOKEN configuration — env var propagation
|
|
7
|
+
4. CallGraph regex matching — word boundary accuracy
|
|
8
|
+
5. Web viz data format — edges key in callgraph response
|
|
9
|
+
6. Silent exception logging — debug messages instead of bare pass
|
|
10
|
+
7. Refactored CLI output helpers — quality_cmd, metrics_cmd
|
|
11
|
+
8. Module imports and version
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from unittest.mock import MagicMock, patch
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
from semantic_code_intelligence.storage.vector_store import ChunkMetadata, VectorStore
|
|
25
|
+
|
|
26
|
+
_PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ===================================================================
|
|
30
|
+
# 1. VectorStore.remove_by_file
|
|
31
|
+
# ===================================================================
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestVectorStoreRemoveByFile:
|
|
35
|
+
"""Tests for VectorStore.remove_by_file()."""
|
|
36
|
+
|
|
37
|
+
def _make_store(self) -> VectorStore:
|
|
38
|
+
store = VectorStore(dimension=4)
|
|
39
|
+
vecs = np.array([
|
|
40
|
+
[1.0, 0.0, 0.0, 0.0],
|
|
41
|
+
[0.0, 1.0, 0.0, 0.0],
|
|
42
|
+
[0.0, 0.0, 1.0, 0.0],
|
|
43
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
44
|
+
], dtype=np.float32)
|
|
45
|
+
meta = [
|
|
46
|
+
ChunkMetadata(file_path="/a.py", start_line=1, end_line=10,
|
|
47
|
+
chunk_index=0, language="python", content="a1"),
|
|
48
|
+
ChunkMetadata(file_path="/b.py", start_line=1, end_line=5,
|
|
49
|
+
chunk_index=0, language="python", content="b1"),
|
|
50
|
+
ChunkMetadata(file_path="/a.py", start_line=11, end_line=20,
|
|
51
|
+
chunk_index=1, language="python", content="a2"),
|
|
52
|
+
ChunkMetadata(file_path="/c.py", start_line=1, end_line=15,
|
|
53
|
+
chunk_index=0, language="python", content="c1"),
|
|
54
|
+
]
|
|
55
|
+
store.add(vecs, meta)
|
|
56
|
+
return store
|
|
57
|
+
|
|
58
|
+
def test_remove_existing_file(self) -> None:
|
|
59
|
+
store = self._make_store()
|
|
60
|
+
assert store.size == 4
|
|
61
|
+
removed = store.remove_by_file("/a.py")
|
|
62
|
+
assert removed == 2
|
|
63
|
+
assert store.size == 2
|
|
64
|
+
assert all(m.file_path != "/a.py" for m in store.metadata)
|
|
65
|
+
|
|
66
|
+
def test_remove_nonexistent_file(self) -> None:
|
|
67
|
+
store = self._make_store()
|
|
68
|
+
removed = store.remove_by_file("/nonexistent.py")
|
|
69
|
+
assert removed == 0
|
|
70
|
+
assert store.size == 4
|
|
71
|
+
|
|
72
|
+
def test_remove_all_files(self) -> None:
|
|
73
|
+
store = self._make_store()
|
|
74
|
+
store.remove_by_file("/a.py")
|
|
75
|
+
store.remove_by_file("/b.py")
|
|
76
|
+
store.remove_by_file("/c.py")
|
|
77
|
+
assert store.size == 0
|
|
78
|
+
assert store.metadata == []
|
|
79
|
+
|
|
80
|
+
def test_search_after_removal(self) -> None:
|
|
81
|
+
store = self._make_store()
|
|
82
|
+
store.remove_by_file("/a.py")
|
|
83
|
+
query = np.array([0.0, 1.0, 0.0, 0.0], dtype=np.float32)
|
|
84
|
+
results = store.search(query, top_k=5)
|
|
85
|
+
assert len(results) == 2
|
|
86
|
+
assert results[0][0].file_path == "/b.py"
|
|
87
|
+
|
|
88
|
+
def test_add_after_removal(self) -> None:
|
|
89
|
+
store = self._make_store()
|
|
90
|
+
store.remove_by_file("/b.py")
|
|
91
|
+
new_vec = np.array([[0.5, 0.5, 0.0, 0.0]], dtype=np.float32)
|
|
92
|
+
new_meta = [ChunkMetadata(file_path="/d.py", start_line=1, end_line=3,
|
|
93
|
+
chunk_index=0, language="python", content="d1")]
|
|
94
|
+
store.add(new_vec, new_meta)
|
|
95
|
+
assert store.size == 4
|
|
96
|
+
paths = {m.file_path for m in store.metadata}
|
|
97
|
+
assert "/d.py" in paths
|
|
98
|
+
assert "/b.py" not in paths
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ===================================================================
|
|
102
|
+
# 2. Incremental indexing — stale removal
|
|
103
|
+
# ===================================================================
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TestIncrementalIndexing:
|
|
107
|
+
"""Tests for indexing_service stale vector and deleted file handling."""
|
|
108
|
+
|
|
109
|
+
def test_deleted_paths_detected(self) -> None:
|
|
110
|
+
"""HashStore tracks should be cleaned up for deleted files."""
|
|
111
|
+
from semantic_code_intelligence.storage.hash_store import HashStore
|
|
112
|
+
|
|
113
|
+
hs = HashStore()
|
|
114
|
+
hs.set("src/old.py", "abc123")
|
|
115
|
+
hs.set("src/keep.py", "def456")
|
|
116
|
+
|
|
117
|
+
scanned_paths = {"src/keep.py"}
|
|
118
|
+
deleted = [k for k in list(hs._hashes.keys()) if k not in scanned_paths]
|
|
119
|
+
assert deleted == ["src/old.py"]
|
|
120
|
+
|
|
121
|
+
def test_hash_store_remove(self) -> None:
|
|
122
|
+
from semantic_code_intelligence.storage.hash_store import HashStore
|
|
123
|
+
|
|
124
|
+
hs = HashStore()
|
|
125
|
+
hs.set("a.py", "h1")
|
|
126
|
+
hs.set("b.py", "h2")
|
|
127
|
+
hs.remove("a.py")
|
|
128
|
+
assert hs.get("a.py") is None
|
|
129
|
+
assert hs.get("b.py") == "h2"
|
|
130
|
+
|
|
131
|
+
def test_remove_by_file_preserves_other_vectors(self) -> None:
|
|
132
|
+
"""Incremental re-index removes stale vectors but keeps others."""
|
|
133
|
+
store = VectorStore(dimension=2)
|
|
134
|
+
vecs = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=np.float32)
|
|
135
|
+
meta = [
|
|
136
|
+
ChunkMetadata(file_path="/changed.py", start_line=1, end_line=5,
|
|
137
|
+
chunk_index=0, language="python", content="old"),
|
|
138
|
+
ChunkMetadata(file_path="/unchanged.py", start_line=1, end_line=5,
|
|
139
|
+
chunk_index=0, language="python", content="keep"),
|
|
140
|
+
]
|
|
141
|
+
store.add(vecs, meta)
|
|
142
|
+
store.remove_by_file("/changed.py")
|
|
143
|
+
|
|
144
|
+
assert store.size == 1
|
|
145
|
+
assert store.metadata[0].file_path == "/unchanged.py"
|
|
146
|
+
|
|
147
|
+
# Add updated file
|
|
148
|
+
new_vec = np.array([[0.5, 0.5]], dtype=np.float32)
|
|
149
|
+
new_meta = [ChunkMetadata(file_path="/changed.py", start_line=1, end_line=8,
|
|
150
|
+
chunk_index=0, language="python", content="new")]
|
|
151
|
+
store.add(new_vec, new_meta)
|
|
152
|
+
assert store.size == 2
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ===================================================================
|
|
156
|
+
# 3. HF_TOKEN configuration
|
|
157
|
+
# ===================================================================
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestHFTokenConfig:
|
|
161
|
+
"""Tests for _configure_hf_token() in embeddings/generator.py."""
|
|
162
|
+
|
|
163
|
+
def test_hf_token_already_set(self) -> None:
|
|
164
|
+
from semantic_code_intelligence.embeddings.generator import _configure_hf_token
|
|
165
|
+
|
|
166
|
+
with patch.dict(os.environ, {"HF_TOKEN": "existing"}, clear=False):
|
|
167
|
+
_configure_hf_token()
|
|
168
|
+
assert os.environ["HF_TOKEN"] == "existing"
|
|
169
|
+
|
|
170
|
+
def test_hugging_face_hub_token_propagated(self) -> None:
|
|
171
|
+
from semantic_code_intelligence.embeddings.generator import _configure_hf_token
|
|
172
|
+
|
|
173
|
+
env = {"HUGGING_FACE_HUB_TOKEN": "hub_tok"}
|
|
174
|
+
with patch.dict(os.environ, env, clear=False):
|
|
175
|
+
os.environ.pop("HF_TOKEN", None)
|
|
176
|
+
_configure_hf_token()
|
|
177
|
+
assert os.environ.get("HF_TOKEN") == "hub_tok"
|
|
178
|
+
|
|
179
|
+
def test_huggingface_token_propagated(self) -> None:
|
|
180
|
+
from semantic_code_intelligence.embeddings.generator import _configure_hf_token
|
|
181
|
+
|
|
182
|
+
env = {"HUGGINGFACE_TOKEN": "hf_tok"}
|
|
183
|
+
with patch.dict(os.environ, env, clear=False):
|
|
184
|
+
os.environ.pop("HF_TOKEN", None)
|
|
185
|
+
os.environ.pop("HUGGING_FACE_HUB_TOKEN", None)
|
|
186
|
+
_configure_hf_token()
|
|
187
|
+
assert os.environ.get("HF_TOKEN") == "hf_tok"
|
|
188
|
+
|
|
189
|
+
def test_no_token_set(self) -> None:
|
|
190
|
+
from semantic_code_intelligence.embeddings.generator import _configure_hf_token
|
|
191
|
+
|
|
192
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
193
|
+
os.environ.pop("HF_TOKEN", None)
|
|
194
|
+
os.environ.pop("HUGGING_FACE_HUB_TOKEN", None)
|
|
195
|
+
os.environ.pop("HUGGINGFACE_TOKEN", None)
|
|
196
|
+
_configure_hf_token()
|
|
197
|
+
assert "HF_TOKEN" not in os.environ
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ===================================================================
|
|
201
|
+
# 4. CallGraph regex matching
|
|
202
|
+
# ===================================================================
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class TestCallGraphRegex:
|
|
206
|
+
"""Tests for word-boundary call detection in context/engine.py."""
|
|
207
|
+
|
|
208
|
+
def test_exact_function_call_detected(self) -> None:
|
|
209
|
+
pattern = re.compile(r"\bprocess_data\s*[\(\.]")
|
|
210
|
+
body = "result = process_data(items)"
|
|
211
|
+
assert pattern.search(body) is not None
|
|
212
|
+
|
|
213
|
+
def test_method_call_detected(self) -> None:
|
|
214
|
+
pattern = re.compile(r"\bprocess_data\s*[\(\.]")
|
|
215
|
+
body = "obj.process_data(x)"
|
|
216
|
+
assert pattern.search(body) is not None
|
|
217
|
+
|
|
218
|
+
def test_substring_not_matched(self) -> None:
|
|
219
|
+
pattern = re.compile(r"\bprocess_data\s*[\(\.]")
|
|
220
|
+
body = "unprocess_data_handler = 1"
|
|
221
|
+
assert pattern.search(body) is None
|
|
222
|
+
|
|
223
|
+
def test_partial_name_not_matched(self) -> None:
|
|
224
|
+
pattern = re.compile(r"\bget\s*[\(\.]")
|
|
225
|
+
body = "get_user()"
|
|
226
|
+
# "get" at word boundary followed by "_" should not match \(|\.)
|
|
227
|
+
assert pattern.search(body) is None
|
|
228
|
+
|
|
229
|
+
def test_call_with_space(self) -> None:
|
|
230
|
+
pattern = re.compile(r"\bfoo\s*[\(\.]")
|
|
231
|
+
body = "foo (x)"
|
|
232
|
+
assert pattern.search(body) is not None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ===================================================================
|
|
236
|
+
# 5. Web viz data format
|
|
237
|
+
# ===================================================================
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestVizDataFormat:
|
|
241
|
+
"""Tests for viz endpoint data format."""
|
|
242
|
+
|
|
243
|
+
def test_callgraph_has_edges_key(self) -> None:
|
|
244
|
+
from semantic_code_intelligence.bridge.context_provider import ContextProvider
|
|
245
|
+
|
|
246
|
+
provider = ContextProvider(_PROJECT_ROOT)
|
|
247
|
+
with patch.object(provider, "_ensure_indexed") as mock_idx:
|
|
248
|
+
mock_builder = MagicMock()
|
|
249
|
+
mock_idx.return_value = mock_builder
|
|
250
|
+
mock_builder.get_call_graph.return_value = MagicMock(
|
|
251
|
+
callers={"a": ["b"]},
|
|
252
|
+
callees={"a": ["c"]},
|
|
253
|
+
)
|
|
254
|
+
data = provider.get_call_graph(symbol_name="a")
|
|
255
|
+
assert "edges" in data
|
|
256
|
+
assert isinstance(data["edges"], list)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ===================================================================
|
|
260
|
+
# 6. Silent exception logging
|
|
261
|
+
# ===================================================================
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class TestSilentExceptionLogging:
|
|
265
|
+
"""Verify that previously silent catches now log debug messages."""
|
|
266
|
+
|
|
267
|
+
@pytest.mark.parametrize("module_path", [
|
|
268
|
+
"semantic_code_intelligence.cli.commands.explain_cmd",
|
|
269
|
+
"semantic_code_intelligence.ci.hooks",
|
|
270
|
+
"semantic_code_intelligence.cli.commands.quality_cmd",
|
|
271
|
+
"semantic_code_intelligence.cli.commands.pr_summary_cmd",
|
|
272
|
+
"semantic_code_intelligence.llm.investigation",
|
|
273
|
+
"semantic_code_intelligence.llm.cross_refactor",
|
|
274
|
+
"semantic_code_intelligence.cli.commands.chat_cmd",
|
|
275
|
+
"semantic_code_intelligence.cli.commands.cross_refactor_cmd",
|
|
276
|
+
"semantic_code_intelligence.ci.metrics",
|
|
277
|
+
"semantic_code_intelligence.cli.commands.hotspots_cmd",
|
|
278
|
+
"semantic_code_intelligence.cli.commands.impact_cmd",
|
|
279
|
+
"semantic_code_intelligence.cli.commands.trace_cmd",
|
|
280
|
+
"semantic_code_intelligence.docs",
|
|
281
|
+
"semantic_code_intelligence.llm.streaming",
|
|
282
|
+
])
|
|
283
|
+
def test_module_has_no_bare_pass_in_except(self, module_path: str) -> None:
|
|
284
|
+
"""Ensure no bare 'except Exception: pass' remains in the source.
|
|
285
|
+
|
|
286
|
+
JSON/TypeError fallback patterns (e.g. ``except (json.JSONDecodeError, TypeError): pass``)
|
|
287
|
+
are allowed because they represent intentional parse-fallback, not swallowed errors.
|
|
288
|
+
"""
|
|
289
|
+
import importlib
|
|
290
|
+
mod = importlib.import_module(module_path)
|
|
291
|
+
src_path = Path(mod.__file__)
|
|
292
|
+
source = src_path.read_text(encoding="utf-8")
|
|
293
|
+
lines = source.splitlines()
|
|
294
|
+
for i, line in enumerate(lines):
|
|
295
|
+
stripped = line.strip()
|
|
296
|
+
if stripped == "pass" and i > 0:
|
|
297
|
+
prev = lines[i - 1].strip()
|
|
298
|
+
if prev.startswith("except"):
|
|
299
|
+
# Allow JSON/type-error parse fallbacks
|
|
300
|
+
if "JSONDecodeError" in prev or "TypeError" in prev:
|
|
301
|
+
continue
|
|
302
|
+
if "Exception" in prev or prev == "except:":
|
|
303
|
+
pytest.fail(
|
|
304
|
+
f"{module_path}:{i + 1} has bare 'except: pass' — "
|
|
305
|
+
f"should use logger.debug()"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ===================================================================
|
|
310
|
+
# 7. Refactored CLI output helpers
|
|
311
|
+
# ===================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class TestQualityCmdHelpers:
|
|
315
|
+
"""Tests for extracted quality_cmd output helpers."""
|
|
316
|
+
|
|
317
|
+
def test_output_safety_json(self) -> None:
|
|
318
|
+
from semantic_code_intelligence.cli.commands.quality_cmd import _output_safety
|
|
319
|
+
|
|
320
|
+
safety = MagicMock()
|
|
321
|
+
safety.safe = True
|
|
322
|
+
safety.to_dict.return_value = {"safe": True, "issues": []}
|
|
323
|
+
|
|
324
|
+
from io import StringIO
|
|
325
|
+
from unittest.mock import patch as _patch
|
|
326
|
+
from click.testing import CliRunner
|
|
327
|
+
|
|
328
|
+
import click
|
|
329
|
+
|
|
330
|
+
@click.command()
|
|
331
|
+
def _test_cmd() -> None:
|
|
332
|
+
_output_safety(safety, 10, json_mode=True, pipe=False)
|
|
333
|
+
|
|
334
|
+
runner = CliRunner()
|
|
335
|
+
result = runner.invoke(_test_cmd)
|
|
336
|
+
assert '"safe": true' in result.output
|
|
337
|
+
|
|
338
|
+
def test_output_safety_pipe(self) -> None:
|
|
339
|
+
from semantic_code_intelligence.cli.commands.quality_cmd import _output_safety
|
|
340
|
+
|
|
341
|
+
safety = MagicMock()
|
|
342
|
+
safety.safe = True
|
|
343
|
+
|
|
344
|
+
import click
|
|
345
|
+
from click.testing import CliRunner
|
|
346
|
+
|
|
347
|
+
@click.command()
|
|
348
|
+
def _test_cmd() -> None:
|
|
349
|
+
_output_safety(safety, 5, json_mode=False, pipe=True)
|
|
350
|
+
|
|
351
|
+
runner = CliRunner()
|
|
352
|
+
result = runner.invoke(_test_cmd)
|
|
353
|
+
assert "PASS" in result.output
|
|
354
|
+
assert "5 files" in result.output
|
|
355
|
+
|
|
356
|
+
def test_output_report_pipe(self) -> None:
|
|
357
|
+
from semantic_code_intelligence.cli.commands.quality_cmd import _output_report_pipe
|
|
358
|
+
|
|
359
|
+
report = MagicMock()
|
|
360
|
+
report.files_analyzed = 10
|
|
361
|
+
report.symbol_count = 50
|
|
362
|
+
report.issue_count = 3
|
|
363
|
+
report.complexity_issues = []
|
|
364
|
+
report.dead_code = []
|
|
365
|
+
report.duplicates = []
|
|
366
|
+
report.bandit_issues = []
|
|
367
|
+
report.safety = None
|
|
368
|
+
report.maintainability_index = None
|
|
369
|
+
|
|
370
|
+
import click
|
|
371
|
+
from click.testing import CliRunner
|
|
372
|
+
|
|
373
|
+
@click.command()
|
|
374
|
+
def _test_cmd() -> None:
|
|
375
|
+
_output_report_pipe(report)
|
|
376
|
+
|
|
377
|
+
runner = CliRunner()
|
|
378
|
+
result = runner.invoke(_test_cmd)
|
|
379
|
+
assert "Files: 10" in result.output
|
|
380
|
+
assert "Issues: 3" in result.output
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class TestMetricsCmdHelpers:
|
|
384
|
+
"""Tests for extracted metrics_cmd output helpers."""
|
|
385
|
+
|
|
386
|
+
def test_output_history_json(self) -> None:
|
|
387
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import _output_history
|
|
388
|
+
|
|
389
|
+
snap = MagicMock()
|
|
390
|
+
snap.to_dict.return_value = {"timestamp": 1000, "mi": 65.0}
|
|
391
|
+
|
|
392
|
+
import click
|
|
393
|
+
from click.testing import CliRunner
|
|
394
|
+
|
|
395
|
+
@click.command()
|
|
396
|
+
def _test_cmd() -> None:
|
|
397
|
+
_output_history([snap], 1, json_mode=True, pipe=False)
|
|
398
|
+
|
|
399
|
+
runner = CliRunner()
|
|
400
|
+
result = runner.invoke(_test_cmd)
|
|
401
|
+
assert '"snapshots"' in result.output
|
|
402
|
+
|
|
403
|
+
def test_output_trend_pipe(self) -> None:
|
|
404
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import _output_trend
|
|
405
|
+
|
|
406
|
+
trend = MagicMock()
|
|
407
|
+
trend.metric_name = "maintainability_index"
|
|
408
|
+
trend.direction = "improving"
|
|
409
|
+
trend.oldest_value = 60.0
|
|
410
|
+
trend.newest_value = 70.0
|
|
411
|
+
trend.delta = 10.0
|
|
412
|
+
|
|
413
|
+
import click
|
|
414
|
+
from click.testing import CliRunner
|
|
415
|
+
|
|
416
|
+
@click.command()
|
|
417
|
+
def _test_cmd() -> None:
|
|
418
|
+
_output_trend([trend], 5, json_mode=False, pipe=True)
|
|
419
|
+
|
|
420
|
+
runner = CliRunner()
|
|
421
|
+
result = runner.invoke(_test_cmd)
|
|
422
|
+
assert "TREND" in result.output
|
|
423
|
+
assert "maintainability_index" in result.output
|
|
424
|
+
|
|
425
|
+
def test_output_current_metrics_json(self) -> None:
|
|
426
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import _output_current_metrics
|
|
427
|
+
|
|
428
|
+
pm = MagicMock()
|
|
429
|
+
pm.to_dict.return_value = {"files_analyzed": 10, "mi": 65.0}
|
|
430
|
+
|
|
431
|
+
import click
|
|
432
|
+
from click.testing import CliRunner
|
|
433
|
+
|
|
434
|
+
@click.command()
|
|
435
|
+
def _test_cmd() -> None:
|
|
436
|
+
_output_current_metrics(pm, Path("."), None, json_mode=True, pipe=False)
|
|
437
|
+
|
|
438
|
+
runner = CliRunner()
|
|
439
|
+
result = runner.invoke(_test_cmd)
|
|
440
|
+
assert '"files_analyzed"' in result.output
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
# ===================================================================
|
|
444
|
+
# 8. Module imports and version
|
|
445
|
+
# ===================================================================
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class TestImportsAndVersion:
|
|
449
|
+
"""Verify that all Phase 25 modules import correctly."""
|
|
450
|
+
|
|
451
|
+
def test_version_0_25(self) -> None:
|
|
452
|
+
import semantic_code_intelligence
|
|
453
|
+
assert semantic_code_intelligence.__version__ == "0.4.0"
|
|
454
|
+
|
|
455
|
+
def test_vector_store_has_remove_by_file(self) -> None:
|
|
456
|
+
assert hasattr(VectorStore, "remove_by_file")
|
|
457
|
+
|
|
458
|
+
def test_hf_token_function_importable(self) -> None:
|
|
459
|
+
from semantic_code_intelligence.embeddings.generator import _configure_hf_token
|
|
460
|
+
assert callable(_configure_hf_token)
|
|
461
|
+
|
|
462
|
+
def test_quality_helpers_importable(self) -> None:
|
|
463
|
+
from semantic_code_intelligence.cli.commands.quality_cmd import (
|
|
464
|
+
_output_safety,
|
|
465
|
+
_output_report_pipe,
|
|
466
|
+
_output_report_rich,
|
|
467
|
+
)
|
|
468
|
+
assert callable(_output_safety)
|
|
469
|
+
assert callable(_output_report_pipe)
|
|
470
|
+
assert callable(_output_report_rich)
|
|
471
|
+
|
|
472
|
+
def test_metrics_helpers_importable(self) -> None:
|
|
473
|
+
from semantic_code_intelligence.cli.commands.metrics_cmd import (
|
|
474
|
+
_output_history,
|
|
475
|
+
_output_trend,
|
|
476
|
+
_output_current_metrics,
|
|
477
|
+
)
|
|
478
|
+
assert callable(_output_history)
|
|
479
|
+
assert callable(_output_trend)
|
|
480
|
+
assert callable(_output_current_metrics)
|
|
481
|
+
|
|
482
|
+
def test_indexing_helpers_importable(self) -> None:
|
|
483
|
+
from semantic_code_intelligence.services.indexing_service import (
|
|
484
|
+
_extract_symbols,
|
|
485
|
+
_compute_index_stats,
|
|
486
|
+
)
|
|
487
|
+
assert callable(_extract_symbols)
|
|
488
|
+
assert callable(_compute_index_stats)
|
|
489
|
+
|
|
490
|
+
def test_visualize_module_importable(self) -> None:
|
|
491
|
+
from semantic_code_intelligence.web.visualize import (
|
|
492
|
+
render_call_graph,
|
|
493
|
+
render_dependency_graph,
|
|
494
|
+
)
|
|
495
|
+
assert callable(render_call_graph)
|
|
496
|
+
assert callable(render_dependency_graph)
|