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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. 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)