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,962 @@
1
+ """Phase 20c – Reach 2 000 tests.
2
+
3
+ Covers: visualize, formatter, context/engine, context/memory,
4
+ analysis/ai_features, llm/reasoning results, llm/investigation results,
5
+ bridge/context_provider edge cases, services, search formatter.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import tempfile
12
+ import time
13
+ from dataclasses import fields
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import pytest
18
+
19
+ # =========================================================================
20
+ # Visualization
21
+ # =========================================================================
22
+
23
+ from semantic_code_intelligence.web.visualize import (
24
+ render_call_graph,
25
+ render_dependency_graph,
26
+ render_workspace_graph,
27
+ render_symbol_map,
28
+ )
29
+
30
+
31
+ class TestRenderCallGraph:
32
+ """render_call_graph() – 10 tests."""
33
+
34
+ def test_empty_edges(self):
35
+ html = render_call_graph([])
36
+ assert isinstance(html, str)
37
+ assert "mermaid" in html.lower() or "graph" in html.lower() or "flowchart" in html.lower()
38
+
39
+ def test_single_edge(self):
40
+ edges = [{"caller": "a", "callee": "b", "file_path": "f.py", "line": 1}]
41
+ html = render_call_graph(edges)
42
+ assert "a" in html
43
+ assert "b" in html
44
+
45
+ def test_multiple_edges(self):
46
+ edges = [
47
+ {"caller": "a", "callee": "b", "file_path": "f.py", "line": 1},
48
+ {"caller": "b", "callee": "c", "file_path": "f.py", "line": 5},
49
+ ]
50
+ html = render_call_graph(edges)
51
+ assert "c" in html
52
+
53
+ def test_custom_title(self):
54
+ html = render_call_graph([], title="My Graph")
55
+ assert "My Graph" in html
56
+
57
+ def test_direction_lr(self):
58
+ html = render_call_graph([], direction="LR")
59
+ assert isinstance(html, str)
60
+
61
+ def test_direction_td(self):
62
+ html = render_call_graph([], direction="TD")
63
+ assert isinstance(html, str)
64
+
65
+ def test_direction_rl(self):
66
+ html = render_call_graph([], direction="RL")
67
+ assert isinstance(html, str)
68
+
69
+ def test_direction_tb(self):
70
+ html = render_call_graph([], direction="TB")
71
+ assert isinstance(html, str)
72
+
73
+ def test_self_loop(self):
74
+ edges = [{"caller": "f", "callee": "f", "file_path": "f.py", "line": 1}]
75
+ html = render_call_graph(edges)
76
+ assert "f" in html
77
+
78
+ def test_returns_str(self):
79
+ assert isinstance(render_call_graph([]), str)
80
+
81
+
82
+ class TestRenderDependencyGraph:
83
+ """render_dependency_graph() – 8 tests."""
84
+
85
+ def test_empty_deps(self):
86
+ html = render_dependency_graph({})
87
+ assert isinstance(html, str)
88
+
89
+ def test_flat_deps(self):
90
+ deps = {"file.py": [{"import_text": "os", "line": 1, "source_file": "file.py"}]}
91
+ html = render_dependency_graph(deps)
92
+ assert isinstance(html, str)
93
+
94
+ def test_custom_title(self):
95
+ html = render_dependency_graph({}, title="Deps")
96
+ assert "Deps" in html
97
+
98
+ def test_direction_td(self):
99
+ html = render_dependency_graph({}, direction="TD")
100
+ assert isinstance(html, str)
101
+
102
+ def test_direction_lr(self):
103
+ html = render_dependency_graph({}, direction="LR")
104
+ assert isinstance(html, str)
105
+
106
+ def test_multiple_files(self):
107
+ deps = {
108
+ "a.py": [{"import_text": "os", "line": 1, "source_file": "a.py"}],
109
+ "b.py": [{"import_text": "sys", "line": 2, "source_file": "b.py"}],
110
+ }
111
+ html = render_dependency_graph(deps)
112
+ assert isinstance(html, str)
113
+
114
+ def test_nested_import(self):
115
+ deps = {"c.py": [{"import_text": "a.b.c", "line": 3, "source_file": "c.py"}]}
116
+ html = render_dependency_graph(deps)
117
+ assert isinstance(html, str)
118
+
119
+ def test_returns_str(self):
120
+ assert isinstance(render_dependency_graph({}), str)
121
+
122
+
123
+ class TestRenderWorkspaceGraph:
124
+ """render_workspace_graph() – 5 tests."""
125
+
126
+ def test_empty(self):
127
+ html = render_workspace_graph([])
128
+ assert isinstance(html, str)
129
+
130
+ def test_single_repo(self):
131
+ repos = [{"name": "repo1", "path": "/tmp/r", "languages": ["python"]}]
132
+ html = render_workspace_graph(repos)
133
+ assert "repo1" in html
134
+
135
+ def test_custom_title(self):
136
+ html = render_workspace_graph([], title="WS")
137
+ assert "WS" in html
138
+
139
+ def test_multiple_repos(self):
140
+ repos = [
141
+ {"name": "r1", "path": "/a", "languages": ["python"]},
142
+ {"name": "r2", "path": "/b", "languages": ["javascript"]},
143
+ ]
144
+ html = render_workspace_graph(repos)
145
+ assert isinstance(html, str)
146
+
147
+ def test_returns_str(self):
148
+ assert isinstance(render_workspace_graph([]), str)
149
+
150
+
151
+ class TestRenderSymbolMap:
152
+ """render_symbol_map() – 7 tests."""
153
+
154
+ def test_empty(self):
155
+ html = render_symbol_map([])
156
+ assert isinstance(html, str)
157
+
158
+ def test_single_function(self):
159
+ syms = [{"name": "foo", "kind": "function", "line": 1}]
160
+ html = render_symbol_map(syms)
161
+ assert "foo" in html
162
+
163
+ def test_class_and_method(self):
164
+ syms = [
165
+ {"name": "MyClass", "kind": "class", "line": 1},
166
+ {"name": "my_method", "kind": "method", "line": 5, "parent": "MyClass"},
167
+ ]
168
+ html = render_symbol_map(syms)
169
+ assert "MyClass" in html
170
+
171
+ def test_custom_title(self):
172
+ html = render_symbol_map([], title="Symbols")
173
+ assert "Symbols" in html
174
+
175
+ def test_file_path(self):
176
+ html = render_symbol_map([], file_path="test.py")
177
+ assert isinstance(html, str)
178
+
179
+ def test_large_list(self):
180
+ syms = [{"name": f"func_{i}", "kind": "function", "line": i} for i in range(50)]
181
+ html = render_symbol_map(syms)
182
+ assert isinstance(html, str)
183
+
184
+ def test_returns_str(self):
185
+ assert isinstance(render_symbol_map([]), str)
186
+
187
+
188
+ # =========================================================================
189
+ # Search formatter
190
+ # =========================================================================
191
+
192
+ from semantic_code_intelligence.services.search_service import SearchResult as SvcSearchResult
193
+ from semantic_code_intelligence.search.formatter import format_results_json
194
+
195
+
196
+ class TestFormatResultsJson:
197
+ """format_results_json – 6 tests."""
198
+
199
+ def _sr(self, **kw) -> SvcSearchResult:
200
+ defaults = dict(file_path="a.py", start_line=1, end_line=5,
201
+ language="python", content="pass", score=0.9,
202
+ chunk_index=0)
203
+ defaults.update(kw)
204
+ return SvcSearchResult(**defaults)
205
+
206
+ def test_empty_results(self):
207
+ out = format_results_json("hello", [], 5)
208
+ parsed = json.loads(out)
209
+ assert parsed["query"] == "hello"
210
+ assert len(parsed["results"]) == 0
211
+
212
+ def test_single_result(self):
213
+ out = format_results_json("q", [self._sr()], 5)
214
+ parsed = json.loads(out)
215
+ assert len(parsed["results"]) == 1
216
+
217
+ def test_top_k_included(self):
218
+ out = format_results_json("q", [], 10)
219
+ parsed = json.loads(out)
220
+ assert parsed["top_k"] == 10
221
+
222
+ def test_valid_json(self):
223
+ out = format_results_json("x", [self._sr(), self._sr(file_path="b.py")], 5)
224
+ assert json.loads(out) # doesn't raise
225
+
226
+ def test_scores_preserved(self):
227
+ out = format_results_json("q", [self._sr(score=0.42)], 5)
228
+ parsed = json.loads(out)
229
+ assert parsed["results"][0]["score"] == pytest.approx(0.42, abs=0.01)
230
+
231
+ def test_returns_str(self):
232
+ assert isinstance(format_results_json("q", [], 1), str)
233
+
234
+
235
+ # =========================================================================
236
+ # Context – Engine extras
237
+ # =========================================================================
238
+
239
+ from semantic_code_intelligence.context.engine import (
240
+ ContextWindow,
241
+ ContextBuilder,
242
+ CallEdge,
243
+ CallGraph,
244
+ FileDependency,
245
+ DependencyMap,
246
+ )
247
+ from semantic_code_intelligence.parsing.parser import Symbol
248
+
249
+
250
+ def _sym(name: str = "foo", kind: str = "function", **kw) -> Symbol:
251
+ defaults = dict(name=name, kind=kind, file_path="test.py",
252
+ start_line=1, end_line=5, start_col=0, end_col=0,
253
+ body="pass")
254
+ defaults.update(kw)
255
+ return Symbol(**defaults)
256
+
257
+
258
+ class TestCallEdge:
259
+ """CallEdge dataclass – 4 tests."""
260
+
261
+ def test_create(self):
262
+ e = CallEdge(caller="a", callee="b", file_path="f.py", line=10)
263
+ assert e.caller == "a"
264
+
265
+ def test_to_dict(self):
266
+ e = CallEdge(caller="a", callee="b", file_path="f.py", line=3)
267
+ d = e.to_dict()
268
+ assert "caller" in d and "callee" in d
269
+
270
+ def test_line(self):
271
+ e = CallEdge(caller="x", callee="y", file_path="z.py", line=99)
272
+ assert e.line == 99
273
+
274
+ def test_fields_count(self):
275
+ assert len(fields(CallEdge)) == 4
276
+
277
+
278
+ class TestFileDependency:
279
+ """FileDependency dataclass – 4 tests."""
280
+
281
+ def test_create(self):
282
+ fd = FileDependency(source_file="a.py", import_text="os", line=1)
283
+ assert fd.source_file == "a.py"
284
+
285
+ def test_to_dict(self):
286
+ fd = FileDependency(source_file="a.py", import_text="os", line=1)
287
+ d = fd.to_dict()
288
+ assert d["import_text"] == "os"
289
+
290
+ def test_line(self):
291
+ fd = FileDependency(source_file="b.py", import_text="sys", line=42)
292
+ assert fd.line == 42
293
+
294
+ def test_fields_count(self):
295
+ assert len(fields(FileDependency)) == 3
296
+
297
+
298
+ class TestCallGraphExtended:
299
+ """CallGraph – 6 tests."""
300
+
301
+ def test_create(self):
302
+ cg = CallGraph()
303
+ assert cg is not None
304
+
305
+ def test_empty_edges(self):
306
+ cg = CallGraph()
307
+ assert cg.edges == []
308
+
309
+ def test_build_empty(self):
310
+ cg = CallGraph()
311
+ cg.build([])
312
+ assert cg.edges == []
313
+
314
+ def test_callers_of_unknown(self):
315
+ cg = CallGraph()
316
+ assert cg.callers_of("nonexistent") == []
317
+
318
+ def test_callees_of_unknown(self):
319
+ cg = CallGraph()
320
+ assert cg.callees_of("nonexistent") == []
321
+
322
+ def test_to_dict(self):
323
+ cg = CallGraph()
324
+ d = cg.to_dict()
325
+ assert isinstance(d, dict)
326
+
327
+
328
+ class TestDependencyMapExtended:
329
+ """DependencyMap – 6 tests."""
330
+
331
+ def test_create(self):
332
+ dm = DependencyMap()
333
+ assert dm is not None
334
+
335
+ def test_empty_all_files(self):
336
+ dm = DependencyMap()
337
+ assert dm.get_all_files() == []
338
+
339
+ def test_get_deps_unknown(self):
340
+ dm = DependencyMap()
341
+ assert dm.get_dependencies("nonexistent.py") == []
342
+
343
+ def test_get_dependents_empty(self):
344
+ dm = DependencyMap()
345
+ assert dm.get_dependents("os") == []
346
+
347
+ def test_to_dict(self):
348
+ dm = DependencyMap()
349
+ assert isinstance(dm.to_dict(), dict)
350
+
351
+ def test_add_file_inline(self):
352
+ dm = DependencyMap()
353
+ deps = dm.add_file("test.py", "import os\nimport sys\n")
354
+ assert isinstance(deps, list)
355
+
356
+
357
+ class TestContextWindowExtended:
358
+ """ContextWindow – 5 tests."""
359
+
360
+ def test_create(self):
361
+ s = _sym()
362
+ cw = ContextWindow(focal_symbol=s)
363
+ assert cw.focal_symbol.name == "foo"
364
+
365
+ def test_to_dict(self):
366
+ cw = ContextWindow(focal_symbol=_sym())
367
+ d = cw.to_dict()
368
+ assert "focal_symbol" in d
369
+
370
+ def test_render(self):
371
+ cw = ContextWindow(focal_symbol=_sym(), file_content="x = 1\n")
372
+ text = cw.render()
373
+ assert isinstance(text, str)
374
+
375
+ def test_related_default(self):
376
+ cw = ContextWindow(focal_symbol=_sym())
377
+ assert cw.related_symbols == []
378
+
379
+ def test_imports_default(self):
380
+ cw = ContextWindow(focal_symbol=_sym())
381
+ assert cw.imports == []
382
+
383
+
384
+ class TestContextBuilderExtended:
385
+ """ContextBuilder – 8 tests."""
386
+
387
+ def test_create(self):
388
+ cb = ContextBuilder()
389
+ assert cb is not None
390
+
391
+ def test_get_all_symbols_empty(self):
392
+ cb = ContextBuilder()
393
+ assert cb.get_all_symbols() == []
394
+
395
+ def test_get_symbols_unknown(self):
396
+ cb = ContextBuilder()
397
+ assert cb.get_symbols("nonexistent.py") == []
398
+
399
+ def test_find_symbol_empty(self):
400
+ cb = ContextBuilder()
401
+ assert cb.find_symbol("nonexistent") == []
402
+
403
+ def test_index_inline_content(self):
404
+ cb = ContextBuilder()
405
+ syms = cb.index_file("test.py", "def hello():\n pass\n")
406
+ assert isinstance(syms, list)
407
+
408
+ def test_find_after_index(self):
409
+ cb = ContextBuilder()
410
+ cb.index_file("test.py", "def hello():\n pass\n")
411
+ found = cb.find_symbol("hello")
412
+ assert len(found) >= 1
413
+
414
+ def test_build_context_for_name(self):
415
+ cb = ContextBuilder()
416
+ cb.index_file("test.py", "def greet():\n pass\n")
417
+ windows = cb.build_context_for_name("greet")
418
+ assert isinstance(windows, list)
419
+
420
+ def test_build_context(self):
421
+ cb = ContextBuilder()
422
+ cb.index_file("test.py", "def myfunc():\n pass\n")
423
+ found = cb.find_symbol("myfunc")
424
+ if found:
425
+ cw = cb.build_context(found[0])
426
+ assert isinstance(cw, ContextWindow)
427
+
428
+
429
+ # =========================================================================
430
+ # Context – Memory extras
431
+ # =========================================================================
432
+
433
+ from semantic_code_intelligence.context.memory import (
434
+ MemoryEntry,
435
+ ReasoningStep,
436
+ SessionMemory,
437
+ WorkspaceMemory,
438
+ )
439
+
440
+
441
+ class TestMemoryEntryExtended:
442
+ """MemoryEntry – 6 tests."""
443
+
444
+ def test_create(self):
445
+ me = MemoryEntry(key="k", content="c")
446
+ assert me.kind == "general"
447
+
448
+ def test_custom_kind(self):
449
+ me = MemoryEntry(key="k", content="c", kind="search")
450
+ assert me.kind == "search"
451
+
452
+ def test_to_dict(self):
453
+ me = MemoryEntry(key="k", content="c")
454
+ d = me.to_dict()
455
+ assert d["key"] == "k"
456
+
457
+ def test_from_dict(self):
458
+ me = MemoryEntry(key="k", content="c", kind="general", timestamp=1.0)
459
+ d = me.to_dict()
460
+ me2 = MemoryEntry.from_dict(d)
461
+ assert me2.key == "k"
462
+
463
+ def test_roundtrip(self):
464
+ me = MemoryEntry(key="x", content="y", kind="note", metadata={"a": 1})
465
+ d = me.to_dict()
466
+ me2 = MemoryEntry.from_dict(d)
467
+ assert me2.metadata.get("a") == 1
468
+
469
+ def test_timestamp_auto(self):
470
+ before = time.time()
471
+ me = MemoryEntry(key="k", content="c")
472
+ assert me.timestamp >= before
473
+
474
+
475
+ class TestReasoningStepExtended:
476
+ """ReasoningStep – 4 tests."""
477
+
478
+ def test_create(self):
479
+ rs = ReasoningStep(step_id=1, action="search", input_text="q", output_text="r")
480
+ assert rs.step_id == 1
481
+
482
+ def test_to_dict(self):
483
+ rs = ReasoningStep(step_id=0, action="a", input_text="i", output_text="o")
484
+ d = rs.to_dict()
485
+ assert "action" in d
486
+
487
+ def test_timestamp_auto(self):
488
+ rs = ReasoningStep(step_id=1, action="a", input_text="", output_text="")
489
+ assert rs.timestamp > 0
490
+
491
+ def test_fields(self):
492
+ assert len(fields(ReasoningStep)) >= 5
493
+
494
+
495
+ class TestSessionMemoryExtended:
496
+ """SessionMemory – 10 tests."""
497
+
498
+ def test_create(self):
499
+ sm = SessionMemory()
500
+ assert sm is not None
501
+
502
+ def test_add_entry(self):
503
+ sm = SessionMemory()
504
+ me = sm.add("k1", "content1")
505
+ assert me.key == "k1"
506
+
507
+ def test_entries_property(self):
508
+ sm = SessionMemory()
509
+ sm.add("k1", "c1")
510
+ assert len(sm.entries) == 1
511
+
512
+ def test_search_basic(self):
513
+ sm = SessionMemory()
514
+ sm.add("python", "python info")
515
+ results = sm.search("python")
516
+ assert len(results) >= 1
517
+
518
+ def test_search_no_match(self):
519
+ sm = SessionMemory()
520
+ sm.add("java", "java info")
521
+ results = sm.search("zzz_nonexistent_zzz")
522
+ assert isinstance(results, list)
523
+
524
+ def test_get_recent(self):
525
+ sm = SessionMemory()
526
+ sm.add("a", "aa")
527
+ sm.add("b", "bb")
528
+ recent = sm.get_recent(1)
529
+ assert len(recent) == 1
530
+
531
+ def test_clear(self):
532
+ sm = SessionMemory()
533
+ sm.add("k", "v")
534
+ sm.clear()
535
+ assert len(sm.entries) == 0
536
+
537
+ def test_chain_lifecycle(self):
538
+ sm = SessionMemory()
539
+ sm.start_chain("c1")
540
+ sm.add_step("c1", "search", "q", "r")
541
+ steps = sm.get_chain("c1")
542
+ assert len(steps) == 1
543
+
544
+ def test_chain_multiple_steps(self):
545
+ sm = SessionMemory()
546
+ sm.start_chain("c2")
547
+ sm.add_step("c2", "search", "q1", "r1")
548
+ sm.add_step("c2", "analyze", "q2", "r2")
549
+ steps = sm.get_chain("c2")
550
+ assert len(steps) == 2
551
+
552
+ def test_to_dict(self):
553
+ sm = SessionMemory()
554
+ sm.add("k", "v")
555
+ d = sm.to_dict()
556
+ assert isinstance(d, dict)
557
+
558
+
559
+ class TestWorkspaceMemory:
560
+ """WorkspaceMemory – 8 tests."""
561
+
562
+ def test_create(self):
563
+ with tempfile.TemporaryDirectory() as tmp:
564
+ wm = WorkspaceMemory(Path(tmp))
565
+ assert wm is not None
566
+
567
+ def test_add_entry(self):
568
+ with tempfile.TemporaryDirectory() as tmp:
569
+ wm = WorkspaceMemory(Path(tmp))
570
+ me = wm.add("k1", "c1")
571
+ assert me.key == "k1"
572
+
573
+ def test_get_entry(self):
574
+ with tempfile.TemporaryDirectory() as tmp:
575
+ wm = WorkspaceMemory(Path(tmp))
576
+ wm.add("k1", "c1")
577
+ entry = wm.get("k1")
578
+ assert entry is not None
579
+
580
+ def test_get_missing(self):
581
+ with tempfile.TemporaryDirectory() as tmp:
582
+ wm = WorkspaceMemory(Path(tmp))
583
+ assert wm.get("nokey") is None
584
+
585
+ def test_search(self):
586
+ with tempfile.TemporaryDirectory() as tmp:
587
+ wm = WorkspaceMemory(Path(tmp))
588
+ wm.add("python", "python stuff")
589
+ results = wm.search("python")
590
+ assert len(results) >= 1
591
+
592
+ def test_remove(self):
593
+ with tempfile.TemporaryDirectory() as tmp:
594
+ wm = WorkspaceMemory(Path(tmp))
595
+ wm.add("k1", "c1")
596
+ assert wm.remove("k1") is True
597
+ assert wm.get("k1") is None
598
+
599
+ def test_clear(self):
600
+ with tempfile.TemporaryDirectory() as tmp:
601
+ wm = WorkspaceMemory(Path(tmp))
602
+ wm.add("a", "b")
603
+ wm.clear()
604
+ assert len(wm.entries) == 0
605
+
606
+ def test_to_dict(self):
607
+ with tempfile.TemporaryDirectory() as tmp:
608
+ wm = WorkspaceMemory(Path(tmp))
609
+ d = wm.to_dict()
610
+ assert isinstance(d, dict)
611
+
612
+
613
+ # =========================================================================
614
+ # Analysis – AI features
615
+ # =========================================================================
616
+
617
+ from semantic_code_intelligence.analysis.ai_features import (
618
+ LanguageStats,
619
+ RepoSummary,
620
+ CodeExplanation,
621
+ summarize_repository,
622
+ explain_symbol,
623
+ explain_file,
624
+ )
625
+
626
+
627
+ class TestLanguageStatsExtended:
628
+ """LanguageStats – 5 tests."""
629
+
630
+ def test_create(self):
631
+ ls = LanguageStats(language="python")
632
+ assert ls.language == "python"
633
+
634
+ def test_defaults(self):
635
+ ls = LanguageStats(language="js")
636
+ assert ls.file_count == 0
637
+ assert ls.function_count == 0
638
+
639
+ def test_to_dict(self):
640
+ ls = LanguageStats(language="py", file_count=3, function_count=10)
641
+ d = ls.to_dict()
642
+ assert d["language"] == "py"
643
+
644
+ def test_all_fields(self):
645
+ ls = LanguageStats(language="go", file_count=1, function_count=2,
646
+ class_count=3, method_count=4, import_count=5,
647
+ total_lines=100)
648
+ assert ls.total_lines == 100
649
+
650
+ def test_fields_count(self):
651
+ assert len(fields(LanguageStats)) >= 7
652
+
653
+
654
+ class TestRepoSummaryExtended:
655
+ """RepoSummary – 7 tests."""
656
+
657
+ def test_create(self):
658
+ rs = RepoSummary()
659
+ assert rs.total_files == 0
660
+
661
+ def test_to_dict(self):
662
+ rs = RepoSummary(total_files=5, total_symbols=20)
663
+ d = rs.to_dict()
664
+ assert d["total_files"] == 5
665
+
666
+ def test_to_json(self):
667
+ rs = RepoSummary(total_files=1)
668
+ j = rs.to_json()
669
+ parsed = json.loads(j)
670
+ assert parsed["total_files"] == 1
671
+
672
+ def test_render(self):
673
+ rs = RepoSummary(total_files=2, total_classes=1,
674
+ languages=[LanguageStats(language="python")])
675
+ text = rs.render()
676
+ assert isinstance(text, str)
677
+
678
+ def test_languages(self):
679
+ rs = RepoSummary(languages=[LanguageStats(language="python"),
680
+ LanguageStats(language="javascript")])
681
+ assert len(rs.languages) == 2
682
+
683
+ def test_top_functions(self):
684
+ rs = RepoSummary(top_functions=[{"name": "main"}])
685
+ assert len(rs.top_functions) == 1
686
+
687
+ def test_fields_count(self):
688
+ assert len(fields(RepoSummary)) >= 9
689
+
690
+
691
+ class TestCodeExplanation:
692
+ """CodeExplanation – 5 tests."""
693
+
694
+ def test_create(self):
695
+ ce = CodeExplanation(symbol_name="foo", symbol_kind="function",
696
+ file_path="a.py", summary="Does stuff")
697
+ assert ce.symbol_name == "foo"
698
+
699
+ def test_to_dict(self):
700
+ ce = CodeExplanation(symbol_name="bar", symbol_kind="class",
701
+ file_path="b.py", summary="A class")
702
+ d = ce.to_dict()
703
+ assert d["symbol_kind"] == "class"
704
+
705
+ def test_render(self):
706
+ ce = CodeExplanation(symbol_name="baz", symbol_kind="function",
707
+ file_path="c.py", summary="baz summary")
708
+ text = ce.render()
709
+ assert isinstance(text, str) and len(text) > 0
710
+
711
+ def test_details_default(self):
712
+ ce = CodeExplanation(symbol_name="f", symbol_kind="function",
713
+ file_path="d.py", summary="s")
714
+ assert ce.details == {}
715
+
716
+ def test_details_custom(self):
717
+ ce = CodeExplanation(symbol_name="f", symbol_kind="function",
718
+ file_path="d.py", summary="s",
719
+ details={"params": ["a", "b"]})
720
+ assert ce.details["params"] == ["a", "b"]
721
+
722
+
723
+ class TestSummarizeRepository:
724
+ """summarize_repository – 3 tests."""
725
+
726
+ def test_empty_builder(self):
727
+ cb = ContextBuilder()
728
+ summary = summarize_repository(cb)
729
+ assert isinstance(summary, RepoSummary)
730
+
731
+ def test_with_file(self):
732
+ cb = ContextBuilder()
733
+ cb.index_file("test.py", "def hello():\n pass\n")
734
+ summary = summarize_repository(cb)
735
+ assert summary.total_files >= 1
736
+
737
+ def test_returns_repo_summary(self):
738
+ cb = ContextBuilder()
739
+ assert isinstance(summarize_repository(cb), RepoSummary)
740
+
741
+
742
+ class TestExplainSymbol:
743
+ """explain_symbol – 3 tests."""
744
+
745
+ def test_basic(self):
746
+ s = _sym(name="myfunc", kind="function")
747
+ ce = explain_symbol(s)
748
+ assert isinstance(ce, CodeExplanation)
749
+ assert ce.symbol_name == "myfunc"
750
+
751
+ def test_class(self):
752
+ s = _sym(name="MyClass", kind="class")
753
+ ce = explain_symbol(s)
754
+ assert ce.symbol_kind == "class"
755
+
756
+ def test_with_builder(self):
757
+ cb = ContextBuilder()
758
+ cb.index_file("test.py", "def foo():\n pass\n")
759
+ found = cb.find_symbol("foo")
760
+ if found:
761
+ ce = explain_symbol(found[0], cb)
762
+ assert isinstance(ce, CodeExplanation)
763
+
764
+
765
+ class TestExplainFile:
766
+ """explain_file – 3 tests."""
767
+
768
+ def test_inline(self):
769
+ result = explain_file("test.py", "def f():\n pass\n")
770
+ assert isinstance(result, list)
771
+
772
+ def test_empty_content(self):
773
+ result = explain_file("empty.py", "")
774
+ assert isinstance(result, list)
775
+
776
+ def test_multiple_symbols(self):
777
+ code = "def a():\n pass\ndef b():\n pass\n"
778
+ result = explain_file("multi.py", code)
779
+ assert len(result) >= 2
780
+
781
+
782
+ # =========================================================================
783
+ # LLM – Reasoning results
784
+ # =========================================================================
785
+
786
+ from semantic_code_intelligence.llm.reasoning import (
787
+ AskResult,
788
+ ReviewResult,
789
+ RefactorResult,
790
+ SuggestResult,
791
+ )
792
+
793
+
794
+ class TestAskResultExtended:
795
+ """AskResult – 4 tests."""
796
+
797
+ def test_create(self):
798
+ ar = AskResult(question="what?", answer="this")
799
+ assert ar.question == "what?"
800
+
801
+ def test_to_dict(self):
802
+ ar = AskResult(question="q", answer="a")
803
+ d = ar.to_dict()
804
+ assert d["question"] == "q"
805
+
806
+ def test_context_snippets_default(self):
807
+ ar = AskResult(question="q", answer="a")
808
+ assert ar.context_snippets == []
809
+
810
+ def test_explainability_default(self):
811
+ ar = AskResult(question="q", answer="a")
812
+ assert ar.explainability == {}
813
+
814
+
815
+ class TestReviewResultExtended:
816
+ """ReviewResult – 4 tests."""
817
+
818
+ def test_create(self):
819
+ rr = ReviewResult(file_path="a.py")
820
+ assert rr.file_path == "a.py"
821
+
822
+ def test_to_dict(self):
823
+ rr = ReviewResult(file_path="b.py", summary="ok")
824
+ d = rr.to_dict()
825
+ assert d["file_path"] == "b.py"
826
+
827
+ def test_issues_default(self):
828
+ rr = ReviewResult(file_path="c.py")
829
+ assert rr.issues == []
830
+
831
+ def test_summary_default(self):
832
+ rr = ReviewResult(file_path="d.py")
833
+ assert rr.summary == ""
834
+
835
+
836
+ class TestRefactorResultExtended:
837
+ """RefactorResult – 4 tests."""
838
+
839
+ def test_create(self):
840
+ rf = RefactorResult(file_path="a.py")
841
+ assert rf.file_path == "a.py"
842
+
843
+ def test_to_dict(self):
844
+ rf = RefactorResult(file_path="b.py", explanation="cleaned up")
845
+ d = rf.to_dict()
846
+ assert "file_path" in d
847
+
848
+ def test_defaults(self):
849
+ rf = RefactorResult(file_path="c.py")
850
+ assert rf.original_code == "" and rf.refactored_code == ""
851
+
852
+ def test_explainability(self):
853
+ rf = RefactorResult(file_path="d.py", explainability={"model": "gpt-4"})
854
+ assert rf.explainability["model"] == "gpt-4"
855
+
856
+
857
+ class TestSuggestResultExtended:
858
+ """SuggestResult – 3 tests."""
859
+
860
+ def test_create(self):
861
+ sr = SuggestResult(target="func")
862
+ assert sr.target == "func"
863
+
864
+ def test_to_dict(self):
865
+ sr = SuggestResult(target="cls")
866
+ d = sr.to_dict()
867
+ assert "target" in d
868
+
869
+ def test_suggestions_default(self):
870
+ sr = SuggestResult(target="x")
871
+ assert sr.suggestions == []
872
+
873
+
874
+ # =========================================================================
875
+ # LLM – Investigation results
876
+ # =========================================================================
877
+
878
+ from semantic_code_intelligence.llm.investigation import InvestigationResult
879
+
880
+
881
+ class TestInvestigationResultExtended:
882
+ """InvestigationResult – 4 tests."""
883
+
884
+ def test_create(self):
885
+ ir = InvestigationResult(question="why?", conclusion="because")
886
+ assert ir.question == "why?"
887
+
888
+ def test_to_dict(self):
889
+ ir = InvestigationResult(question="q", conclusion="c")
890
+ d = ir.to_dict()
891
+ assert d["conclusion"] == "c"
892
+
893
+ def test_steps_default(self):
894
+ ir = InvestigationResult(question="q", conclusion="c")
895
+ assert ir.steps == []
896
+
897
+ def test_total_steps(self):
898
+ ir = InvestigationResult(question="q", conclusion="c", total_steps=3)
899
+ assert ir.total_steps == 3
900
+
901
+
902
+ # =========================================================================
903
+ # Services – SearchResult extras
904
+ # =========================================================================
905
+
906
+
907
+ class TestSearchResultToDict:
908
+ """SearchResult.to_dict – 4 tests."""
909
+
910
+ def _sr(self, **kw) -> SvcSearchResult:
911
+ defaults = dict(file_path="a.py", start_line=1, end_line=5,
912
+ language="python", content="code", score=0.9,
913
+ chunk_index=0)
914
+ defaults.update(kw)
915
+ return SvcSearchResult(**defaults)
916
+
917
+ def test_keys(self):
918
+ d = self._sr().to_dict()
919
+ assert "file_path" in d and "score" in d
920
+
921
+ def test_score(self):
922
+ d = self._sr(score=0.75).to_dict()
923
+ assert d["score"] == pytest.approx(0.75)
924
+
925
+ def test_language(self):
926
+ d = self._sr(language="javascript").to_dict()
927
+ assert d["language"] == "javascript"
928
+
929
+ def test_roundtrip_fields(self):
930
+ sr = self._sr(file_path="z.py", start_line=10, end_line=20)
931
+ d = sr.to_dict()
932
+ assert d["start_line"] == 10 and d["end_line"] == 20
933
+
934
+
935
+ # =========================================================================
936
+ # Services – IndexingResult extras
937
+ # =========================================================================
938
+
939
+ from semantic_code_intelligence.services.indexing_service import IndexingResult
940
+
941
+
942
+ class TestIndexingResultExtended:
943
+ """IndexingResult – 4 tests."""
944
+
945
+ def test_defaults(self):
946
+ ir = IndexingResult()
947
+ assert ir.files_scanned == 0
948
+ assert ir.files_indexed == 0
949
+
950
+ def test_repr_str(self):
951
+ ir = IndexingResult()
952
+ assert isinstance(repr(ir), str)
953
+
954
+ def test_mutate(self):
955
+ ir = IndexingResult()
956
+ ir.files_scanned = 42
957
+ assert ir.files_scanned == 42
958
+
959
+ def test_chunks_created(self):
960
+ ir = IndexingResult()
961
+ ir.chunks_created = 100
962
+ assert ir.chunks_created == 100