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,2058 @@
1
+ """Phase 20b — Extended deep coverage tests (targeting 2000+ total).
2
+
3
+ Covers configuration, services, protocol, reasoning, investigation,
4
+ analysis, context engine, memory, conversation, streaming, storage,
5
+ chunking, scanning, parsing, and CLI helpers.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import tempfile
12
+ from dataclasses import fields, is_dataclass
13
+ from pathlib import Path
14
+ from typing import Any
15
+ from unittest.mock import MagicMock, patch
16
+
17
+
18
+ # ==========================================================================
19
+ # Config / Settings
20
+ # ==========================================================================
21
+
22
+ from semantic_code_intelligence.config.settings import (
23
+ AppConfig,
24
+ EmbeddingConfig,
25
+ IndexConfig,
26
+ LLMConfig,
27
+ QualityConfig,
28
+ SearchConfig,
29
+ DEFAULT_EXTENSIONS,
30
+ DEFAULT_IGNORE_DIRS,
31
+ load_config,
32
+ save_config,
33
+ init_project,
34
+ )
35
+
36
+
37
+ class TestEmbeddingConfig:
38
+ def test_defaults(self):
39
+ ec = EmbeddingConfig()
40
+ assert ec.model_name == "all-MiniLM-L6-v2"
41
+ assert ec.chunk_size == 512
42
+ assert ec.chunk_overlap == 64
43
+
44
+ def test_custom(self):
45
+ ec = EmbeddingConfig(model_name="custom", chunk_size=256, chunk_overlap=32)
46
+ assert ec.model_name == "custom"
47
+ assert ec.chunk_size == 256
48
+
49
+ def test_model_dump(self):
50
+ ec = EmbeddingConfig()
51
+ d = ec.model_dump()
52
+ assert "model_name" in d
53
+ assert "chunk_size" in d
54
+
55
+
56
+ class TestSearchConfig:
57
+ def test_defaults(self):
58
+ sc = SearchConfig()
59
+ assert sc.top_k == 10
60
+ assert sc.similarity_threshold == 0.3
61
+
62
+ def test_custom(self):
63
+ sc = SearchConfig(top_k=5, similarity_threshold=0.5)
64
+ assert sc.top_k == 5
65
+ assert sc.similarity_threshold == 0.5
66
+
67
+ def test_model_dump(self):
68
+ d = SearchConfig().model_dump()
69
+ assert "top_k" in d
70
+ assert "similarity_threshold" in d
71
+
72
+
73
+ class TestIndexConfig:
74
+ def test_defaults(self):
75
+ ic = IndexConfig()
76
+ assert ".git" in ic.ignore_dirs
77
+ assert ".py" in ic.extensions
78
+ assert ic.use_incremental is True
79
+
80
+ def test_custom_ignore(self):
81
+ ic = IndexConfig(ignore_dirs={"custom_dir"})
82
+ assert "custom_dir" in ic.ignore_dirs
83
+
84
+ def test_custom_extensions(self):
85
+ ic = IndexConfig(extensions={".xyz"})
86
+ assert ".xyz" in ic.extensions
87
+
88
+ def test_model_dump(self):
89
+ d = IndexConfig().model_dump()
90
+ assert "ignore_dirs" in d
91
+ assert "extensions" in d
92
+ assert "use_incremental" in d
93
+
94
+
95
+ class TestLLMConfig:
96
+ def test_defaults(self):
97
+ lc = LLMConfig()
98
+ assert lc.provider == "mock"
99
+ assert lc.model == "gpt-3.5-turbo"
100
+ assert lc.temperature == 0.2
101
+ assert lc.max_tokens == 2048
102
+
103
+ def test_custom(self):
104
+ lc = LLMConfig(provider="openai", api_key="key123", temperature=0.8)
105
+ assert lc.provider == "openai"
106
+ assert lc.api_key == "key123"
107
+ assert lc.temperature == 0.8
108
+
109
+ def test_model_dump(self):
110
+ d = LLMConfig().model_dump()
111
+ assert "provider" in d
112
+ assert "model" in d
113
+ assert "api_key" in d
114
+
115
+
116
+ class TestQualityConfig:
117
+ def test_defaults(self):
118
+ qc = QualityConfig()
119
+ assert qc.complexity_threshold == 10
120
+ assert qc.min_maintainability == 40.0
121
+ assert qc.max_issues == 20
122
+ assert qc.snapshot_on_index is False
123
+ assert qc.history_limit == 50
124
+
125
+ def test_custom(self):
126
+ qc = QualityConfig(complexity_threshold=15, min_maintainability=50.0)
127
+ assert qc.complexity_threshold == 15
128
+
129
+ def test_model_dump(self):
130
+ d = QualityConfig().model_dump()
131
+ assert "complexity_threshold" in d
132
+ assert "history_limit" in d
133
+
134
+
135
+ class TestAppConfig:
136
+ def test_defaults(self):
137
+ ac = AppConfig()
138
+ assert ac.project_root == "."
139
+ assert ac.verbose is False
140
+ assert isinstance(ac.embedding, EmbeddingConfig)
141
+ assert isinstance(ac.search, SearchConfig)
142
+ assert isinstance(ac.index, IndexConfig)
143
+ assert isinstance(ac.llm, LLMConfig)
144
+ assert isinstance(ac.quality, QualityConfig)
145
+
146
+ def test_config_dir(self):
147
+ with tempfile.TemporaryDirectory() as tmp:
148
+ cd = AppConfig.config_dir(tmp)
149
+ assert cd.name == ".codexa"
150
+ assert cd.parent == Path(tmp).resolve()
151
+
152
+ def test_config_path(self):
153
+ with tempfile.TemporaryDirectory() as tmp:
154
+ cp = AppConfig.config_path(tmp)
155
+ assert cp.name == "config.json"
156
+
157
+ def test_index_dir(self):
158
+ with tempfile.TemporaryDirectory() as tmp:
159
+ idx = AppConfig.index_dir(tmp)
160
+ assert idx.name == "index"
161
+
162
+ def test_model_dump_roundtrip(self):
163
+ ac = AppConfig()
164
+ d = ac.model_dump()
165
+ ac2 = AppConfig.model_validate(d)
166
+ assert ac2.project_root == ac.project_root
167
+
168
+ def test_nested_configs(self):
169
+ ac = AppConfig()
170
+ d = ac.model_dump()
171
+ assert "embedding" in d
172
+ assert "search" in d
173
+ assert "index" in d
174
+ assert "llm" in d
175
+ assert "quality" in d
176
+
177
+
178
+ class TestLoadConfig:
179
+ def test_load_default(self):
180
+ with tempfile.TemporaryDirectory() as tmp:
181
+ cfg = load_config(tmp)
182
+ assert isinstance(cfg, AppConfig)
183
+
184
+ def test_load_from_file(self):
185
+ with tempfile.TemporaryDirectory() as tmp:
186
+ cfg, path = init_project(tmp)
187
+ loaded = load_config(tmp)
188
+ assert loaded.project_root == cfg.project_root
189
+
190
+
191
+ class TestSaveConfig:
192
+ def test_save_creates_file(self):
193
+ with tempfile.TemporaryDirectory() as tmp:
194
+ cfg = AppConfig(project_root=str(Path(tmp).resolve()))
195
+ path = save_config(cfg, tmp)
196
+ assert path.exists()
197
+
198
+ def test_save_json_parseable(self):
199
+ with tempfile.TemporaryDirectory() as tmp:
200
+ cfg = AppConfig(project_root=str(Path(tmp).resolve()))
201
+ path = save_config(cfg, tmp)
202
+ data = json.loads(path.read_text(encoding="utf-8"))
203
+ assert "project_root" in data
204
+
205
+
206
+ class TestInitProject:
207
+ def test_creates_dirs(self):
208
+ with tempfile.TemporaryDirectory() as tmp:
209
+ cfg, path = init_project(tmp)
210
+ assert path.exists()
211
+ assert AppConfig.config_dir(tmp).exists()
212
+ assert AppConfig.index_dir(tmp).exists()
213
+
214
+ def test_returns_config(self):
215
+ with tempfile.TemporaryDirectory() as tmp:
216
+ cfg, _ = init_project(tmp)
217
+ assert isinstance(cfg, AppConfig)
218
+
219
+
220
+ class TestDefaultSets:
221
+ def test_default_ignore_dirs(self):
222
+ assert ".git" in DEFAULT_IGNORE_DIRS
223
+ assert "__pycache__" in DEFAULT_IGNORE_DIRS
224
+ assert "node_modules" in DEFAULT_IGNORE_DIRS
225
+
226
+ def test_default_extensions(self):
227
+ assert ".py" in DEFAULT_EXTENSIONS
228
+ assert ".js" in DEFAULT_EXTENSIONS
229
+ assert ".ts" in DEFAULT_EXTENSIONS
230
+ assert ".java" in DEFAULT_EXTENSIONS
231
+
232
+
233
+ # ==========================================================================
234
+ # Bridge Protocol
235
+ # ==========================================================================
236
+
237
+ from semantic_code_intelligence.bridge.protocol import (
238
+ RequestKind,
239
+ AgentRequest,
240
+ AgentResponse,
241
+ )
242
+
243
+
244
+ class TestRequestKind:
245
+ def test_has_semantic_search(self):
246
+ assert RequestKind.SEMANTIC_SEARCH.value == "semantic_search"
247
+
248
+ def test_has_explain_symbol(self):
249
+ assert RequestKind.EXPLAIN_SYMBOL.value == "explain_symbol"
250
+
251
+ def test_has_explain_file(self):
252
+ assert RequestKind.EXPLAIN_FILE.value == "explain_file"
253
+
254
+ def test_has_get_context(self):
255
+ assert RequestKind.GET_CONTEXT.value == "get_context"
256
+
257
+ def test_has_get_dependencies(self):
258
+ assert RequestKind.GET_DEPENDENCIES.value == "get_dependencies"
259
+
260
+ def test_has_get_call_graph(self):
261
+ assert RequestKind.GET_CALL_GRAPH.value == "get_call_graph"
262
+
263
+ def test_has_summarize_repo(self):
264
+ assert RequestKind.SUMMARIZE_REPO.value == "summarize_repo"
265
+
266
+ def test_has_find_references(self):
267
+ assert RequestKind.FIND_REFERENCES.value == "find_references"
268
+
269
+ def test_has_validate_code(self):
270
+ assert RequestKind.VALIDATE_CODE.value == "validate_code"
271
+
272
+ def test_has_list_capabilities(self):
273
+ assert RequestKind.LIST_CAPABILITIES.value == "list_capabilities"
274
+
275
+ def test_has_invoke_tool(self):
276
+ assert RequestKind.INVOKE_TOOL.value == "invoke_tool"
277
+
278
+ def test_has_list_tools(self):
279
+ assert RequestKind.LIST_TOOLS.value == "list_tools"
280
+
281
+ def test_count(self):
282
+ assert len(RequestKind) == 12
283
+
284
+
285
+ class TestAgentRequestProtocol:
286
+ def test_create_minimal(self):
287
+ r = AgentRequest(kind="semantic_search")
288
+ assert r.kind == "semantic_search"
289
+ assert r.params == {}
290
+
291
+ def test_create_full(self):
292
+ r = AgentRequest(kind="explain_symbol", params={"name": "foo"},
293
+ request_id="r1", source="copilot")
294
+ assert r.params["name"] == "foo"
295
+ assert r.request_id == "r1"
296
+ assert r.source == "copilot"
297
+
298
+ def test_to_dict(self):
299
+ r = AgentRequest(kind="test", params={"a": 1})
300
+ d = r.to_dict()
301
+ assert d["kind"] == "test"
302
+ assert d["params"]["a"] == 1
303
+ assert "request_id" in d
304
+ assert "source" in d
305
+
306
+ def test_to_json(self):
307
+ r = AgentRequest(kind="test")
308
+ j = r.to_json()
309
+ parsed = json.loads(j)
310
+ assert parsed["kind"] == "test"
311
+
312
+ def test_from_dict(self):
313
+ data = {"kind": "semantic_search", "params": {"q": "hello"}, "request_id": "x"}
314
+ r = AgentRequest.from_dict(data)
315
+ assert r.kind == "semantic_search"
316
+ assert r.params["q"] == "hello"
317
+
318
+ def test_from_json(self):
319
+ j = '{"kind":"explain_symbol","params":{"name":"foo"}}'
320
+ r = AgentRequest.from_json(j)
321
+ assert r.kind == "explain_symbol"
322
+ assert r.params["name"] == "foo"
323
+
324
+ def test_roundtrip(self):
325
+ r = AgentRequest(kind="test", params={"x": 42}, request_id="abc")
326
+ r2 = AgentRequest.from_json(r.to_json())
327
+ assert r2.kind == r.kind
328
+ assert r2.params == r.params
329
+ assert r2.request_id == r.request_id
330
+
331
+ def test_from_dict_missing_fields(self):
332
+ r = AgentRequest.from_dict({})
333
+ assert r.kind == ""
334
+ assert r.params == {}
335
+
336
+
337
+ class TestAgentResponseProtocol:
338
+ def test_success(self):
339
+ r = AgentResponse(success=True, data={"result": "ok"})
340
+ assert r.success is True
341
+ assert r.data["result"] == "ok"
342
+
343
+ def test_error(self):
344
+ r = AgentResponse(success=False, error="not found")
345
+ assert r.success is False
346
+ assert r.error == "not found"
347
+
348
+ def test_to_dict_success(self):
349
+ r = AgentResponse(success=True, data={"a": 1}, request_id="r1")
350
+ d = r.to_dict()
351
+ assert d["success"] is True
352
+ assert "data" in d
353
+ assert d["request_id"] == "r1"
354
+
355
+ def test_to_dict_error(self):
356
+ r = AgentResponse(success=False, error="boom")
357
+ d = r.to_dict()
358
+ assert "error" in d
359
+ assert d["error"] == "boom"
360
+
361
+ def test_to_json(self):
362
+ r = AgentResponse(success=True, data={})
363
+ j = r.to_json()
364
+ parsed = json.loads(j)
365
+ assert parsed["success"] is True
366
+
367
+ def test_elapsed_ms(self):
368
+ r = AgentResponse(success=True, data={}, elapsed_ms=12.345)
369
+ d = r.to_dict()
370
+ assert d["elapsed_ms"] == 12.35 # rounded to 2 decimal places
371
+
372
+ def test_to_json_with_indent(self):
373
+ r = AgentResponse(success=True, data={"x": 1})
374
+ j = r.to_json(indent=2)
375
+ assert "\n" in j
376
+
377
+
378
+ # ==========================================================================
379
+ # LLM Provider Base & Message
380
+ # ==========================================================================
381
+
382
+ from semantic_code_intelligence.llm.provider import (
383
+ LLMMessage,
384
+ LLMResponse,
385
+ MessageRole,
386
+ )
387
+
388
+
389
+ class TestMessageRole:
390
+ def test_system(self):
391
+ assert MessageRole.SYSTEM.value == "system"
392
+
393
+ def test_user(self):
394
+ assert MessageRole.USER.value == "user"
395
+
396
+ def test_assistant(self):
397
+ assert MessageRole.ASSISTANT.value == "assistant"
398
+
399
+ def test_count(self):
400
+ assert len(MessageRole) == 3
401
+
402
+
403
+ class TestLLMMessage:
404
+ def test_create(self):
405
+ m = LLMMessage(role=MessageRole.USER, content="hello")
406
+ assert m.role == MessageRole.USER
407
+ assert m.content == "hello"
408
+
409
+ def test_to_dict(self):
410
+ m = LLMMessage(role=MessageRole.SYSTEM, content="prompt")
411
+ d = m.to_dict()
412
+ assert d["role"] == "system"
413
+ assert d["content"] == "prompt"
414
+
415
+ def test_is_dataclass(self):
416
+ assert is_dataclass(LLMMessage)
417
+
418
+ def test_equality(self):
419
+ m1 = LLMMessage(role=MessageRole.USER, content="hi")
420
+ m2 = LLMMessage(role=MessageRole.USER, content="hi")
421
+ assert m1 == m2
422
+
423
+ def test_different(self):
424
+ m1 = LLMMessage(role=MessageRole.USER, content="hi")
425
+ m2 = LLMMessage(role=MessageRole.ASSISTANT, content="hi")
426
+ assert m1 != m2
427
+
428
+
429
+ class TestLLMResponse:
430
+ def test_create(self):
431
+ r = LLMResponse(content="answer")
432
+ assert r.content == "answer"
433
+ assert r.model == ""
434
+ assert r.provider == ""
435
+
436
+ def test_to_dict(self):
437
+ r = LLMResponse(content="ans", model="gpt-4", provider="openai",
438
+ usage={"prompt_tokens": 10, "completion_tokens": 20})
439
+ d = r.to_dict()
440
+ assert d["content"] == "ans"
441
+ assert d["model"] == "gpt-4"
442
+ assert d["provider"] == "openai"
443
+ assert d["usage"]["prompt_tokens"] == 10
444
+
445
+ def test_defaults(self):
446
+ r = LLMResponse(content="x")
447
+ assert r.usage == {}
448
+ assert r.raw == {}
449
+
450
+
451
+ # ==========================================================================
452
+ # LLM Safety
453
+ # ==========================================================================
454
+
455
+ from semantic_code_intelligence.llm.safety import SafetyIssue, SafetyReport, SafetyValidator
456
+
457
+
458
+ class TestSafetyIssueDeep:
459
+ def test_create(self):
460
+ si = SafetyIssue(pattern="eval", description="Dangerous eval", line_number=5)
461
+ assert si.pattern == "eval"
462
+ assert si.line_number == 5
463
+
464
+ def test_to_dict(self):
465
+ si = SafetyIssue(pattern="exec", description="exec call", severity="error")
466
+ d = si.to_dict()
467
+ assert d["pattern"] == "exec"
468
+ assert d["severity"] == "error"
469
+
470
+ def test_default_severity(self):
471
+ si = SafetyIssue(pattern="x", description="y")
472
+ assert si.severity == "warning"
473
+
474
+
475
+ class TestSafetyReportDeep:
476
+ def test_safe(self):
477
+ sr = SafetyReport()
478
+ assert sr.safe is True
479
+ assert sr.issues == []
480
+
481
+ def test_unsafe(self):
482
+ sr = SafetyReport(safe=False, issues=[
483
+ SafetyIssue(pattern="eval", description="eval call")
484
+ ])
485
+ assert sr.safe is False
486
+ assert len(sr.issues) == 1
487
+
488
+ def test_to_dict(self):
489
+ sr = SafetyReport(safe=True)
490
+ d = sr.to_dict()
491
+ assert d["safe"] is True
492
+ assert d["issues"] == []
493
+
494
+
495
+ class TestSafetyValidatorDeep:
496
+ def test_safe_code(self):
497
+ sv = SafetyValidator()
498
+ report = sv.validate("x = 1 + 2")
499
+ assert report.safe is True
500
+
501
+ def test_unsafe_eval(self):
502
+ sv = SafetyValidator()
503
+ report = sv.validate("result = eval(user_input)")
504
+ assert report.safe is False
505
+
506
+ def test_unsafe_exec(self):
507
+ sv = SafetyValidator()
508
+ report = sv.validate("exec(code)")
509
+ assert report.safe is False
510
+
511
+ def test_is_safe_shorthand(self):
512
+ sv = SafetyValidator()
513
+ assert sv.is_safe("x = 1") is True
514
+
515
+ def test_is_safe_unsafe(self):
516
+ sv = SafetyValidator()
517
+ assert sv.is_safe("eval('code')") is False
518
+
519
+ def test_extra_patterns(self):
520
+ sv = SafetyValidator(extra_patterns=[("DANGER", "custom danger")])
521
+ report = sv.validate("DANGER this is bad")
522
+ assert report.safe is False
523
+ assert any("DANGER" in i.pattern for i in report.issues)
524
+
525
+
526
+ # ==========================================================================
527
+ # LLM Reasoning Data Types
528
+ # ==========================================================================
529
+
530
+ from semantic_code_intelligence.llm.reasoning import (
531
+ AskResult,
532
+ ReviewResult,
533
+ RefactorResult,
534
+ SuggestResult,
535
+ )
536
+
537
+
538
+ class TestAskResult:
539
+ def test_create(self):
540
+ ar = AskResult(question="what?", answer="this")
541
+ assert ar.question == "what?"
542
+ assert ar.answer == "this"
543
+
544
+ def test_to_dict(self):
545
+ ar = AskResult(question="q", answer="a")
546
+ d = ar.to_dict()
547
+ assert d["question"] == "q"
548
+ assert d["answer"] == "a"
549
+ assert d["context_snippets"] == []
550
+ assert d["usage"] == {}
551
+
552
+ def test_with_context(self):
553
+ ar = AskResult(question="q", answer="a",
554
+ context_snippets=[{"file": "x.py", "content": "code"}])
555
+ d = ar.to_dict()
556
+ assert len(d["context_snippets"]) == 1
557
+
558
+ def test_with_llm_response(self):
559
+ resp = LLMResponse(content="ans", usage={"tokens": 100})
560
+ ar = AskResult(question="q", answer="a", llm_response=resp)
561
+ d = ar.to_dict()
562
+ assert d["usage"]["tokens"] == 100
563
+
564
+
565
+ class TestReviewResult:
566
+ def test_create(self):
567
+ rr = ReviewResult(file_path="test.py")
568
+ assert rr.file_path == "test.py"
569
+ assert rr.issues == []
570
+ assert rr.summary == ""
571
+
572
+ def test_to_dict(self):
573
+ rr = ReviewResult(file_path="a.py", summary="looks good",
574
+ issues=[{"type": "style", "msg": "long line"}])
575
+ d = rr.to_dict()
576
+ assert d["file_path"] == "a.py"
577
+ assert len(d["issues"]) == 1
578
+ assert d["summary"] == "looks good"
579
+
580
+ def test_empty_usage(self):
581
+ d = ReviewResult(file_path="x").to_dict()
582
+ assert d["usage"] == {}
583
+
584
+
585
+ class TestRefactorResult:
586
+ def test_create(self):
587
+ rr = RefactorResult(file_path="a.py", original_code="x=1",
588
+ refactored_code="x = 1", explanation="add spacing")
589
+ assert rr.original_code == "x=1"
590
+ assert rr.refactored_code == "x = 1"
591
+
592
+ def test_to_dict(self):
593
+ rr = RefactorResult(file_path="a.py")
594
+ d = rr.to_dict()
595
+ assert "file_path" in d
596
+ assert "original_code" in d
597
+ assert "refactored_code" in d
598
+ assert "explanation" in d
599
+
600
+ def test_defaults(self):
601
+ rr = RefactorResult(file_path="b.py")
602
+ assert rr.original_code == ""
603
+ assert rr.refactored_code == ""
604
+ assert rr.explanation == ""
605
+
606
+
607
+ class TestSuggestResult:
608
+ def test_create(self):
609
+ sr = SuggestResult(target="function_name")
610
+ assert sr.target == "function_name"
611
+ assert sr.suggestions == []
612
+
613
+ def test_to_dict(self):
614
+ sr = SuggestResult(target="x", suggestions=[{"text": "use typing"}])
615
+ d = sr.to_dict()
616
+ assert d["target"] == "x"
617
+ assert len(d["suggestions"]) == 1
618
+
619
+ def test_defaults(self):
620
+ sr = SuggestResult(target="t")
621
+ assert sr.llm_response is None
622
+ assert sr.explainability == {}
623
+
624
+
625
+ # ==========================================================================
626
+ # LLM Investigation Data Types
627
+ # ==========================================================================
628
+
629
+ from semantic_code_intelligence.llm.investigation import InvestigationResult
630
+
631
+
632
+ class TestInvestigationResult:
633
+ def test_create(self):
634
+ ir = InvestigationResult(question="why?", conclusion="because")
635
+ assert ir.question == "why?"
636
+ assert ir.conclusion == "because"
637
+
638
+ def test_to_dict(self):
639
+ ir = InvestigationResult(question="q", conclusion="c",
640
+ chain_id="ch1", total_steps=3,
641
+ steps=[{"action": "search", "result": "found"}])
642
+ d = ir.to_dict()
643
+ assert d["question"] == "q"
644
+ assert d["conclusion"] == "c"
645
+ assert d["chain_id"] == "ch1"
646
+ assert d["total_steps"] == 3
647
+ assert len(d["steps"]) == 1
648
+
649
+ def test_defaults(self):
650
+ ir = InvestigationResult(question="q", conclusion="c")
651
+ assert ir.steps == []
652
+ assert ir.chain_id == ""
653
+ assert ir.total_steps == 0
654
+
655
+
656
+ # ==========================================================================
657
+ # LLM Streaming
658
+ # ==========================================================================
659
+
660
+ from semantic_code_intelligence.llm.streaming import StreamEvent
661
+
662
+
663
+ class TestStreamEventDeep:
664
+ def test_to_dict(self):
665
+ se = StreamEvent(kind="token", content="hello")
666
+ d = se.to_dict()
667
+ assert d["kind"] == "token"
668
+ assert d["content"] == "hello"
669
+
670
+ def test_to_sse(self):
671
+ se = StreamEvent(kind="token", content="hi")
672
+ sse = se.to_sse()
673
+ assert isinstance(sse, str)
674
+
675
+ def test_metadata(self):
676
+ se = StreamEvent(kind="done", metadata={"model": "gpt-4"})
677
+ d = se.to_dict()
678
+ assert d["metadata"]["model"] == "gpt-4"
679
+
680
+ def test_default_content(self):
681
+ se = StreamEvent(kind="start")
682
+ assert se.content == ""
683
+
684
+ def test_default_metadata(self):
685
+ se = StreamEvent(kind="end")
686
+ assert se.metadata == {}
687
+
688
+
689
+ # ==========================================================================
690
+ # LLM Conversation
691
+ # ==========================================================================
692
+
693
+ from semantic_code_intelligence.llm.conversation import ConversationSession
694
+
695
+
696
+ class TestConversationSessionDeep:
697
+ def test_create(self):
698
+ cs = ConversationSession()
699
+ assert cs.session_id != ""
700
+ assert cs.messages == []
701
+
702
+ def test_add_user(self):
703
+ cs = ConversationSession()
704
+ cs.add_user("hello")
705
+ assert len(cs.messages) == 1
706
+ assert cs.messages[0].role == MessageRole.USER
707
+
708
+ def test_add_assistant(self):
709
+ cs = ConversationSession()
710
+ cs.add_assistant("response")
711
+ assert cs.messages[0].role == MessageRole.ASSISTANT
712
+
713
+ def test_add_system(self):
714
+ cs = ConversationSession()
715
+ cs.add_system("system prompt")
716
+ assert cs.messages[0].role == MessageRole.SYSTEM
717
+
718
+ def test_turn_count(self):
719
+ cs = ConversationSession()
720
+ assert cs.turn_count == 0
721
+ cs.add_user("q1")
722
+ cs.add_assistant("a1")
723
+ assert cs.turn_count == 2 # counts individual user+assistant messages
724
+
725
+ def test_last_message(self):
726
+ cs = ConversationSession()
727
+ cs.add_user("first")
728
+ cs.add_assistant("second")
729
+ assert cs.last_message.content == "second"
730
+
731
+ def test_last_message_none(self):
732
+ cs = ConversationSession()
733
+ assert cs.last_message is None
734
+
735
+ def test_get_messages_for_llm(self):
736
+ cs = ConversationSession()
737
+ cs.add_user("q1")
738
+ cs.add_assistant("a1")
739
+ cs.add_user("q2")
740
+ msgs = cs.get_messages_for_llm()
741
+ assert len(msgs) == 3
742
+
743
+ def test_get_messages_for_llm_max_turns(self):
744
+ cs = ConversationSession()
745
+ for i in range(10):
746
+ cs.add_user(f"q{i}")
747
+ cs.add_assistant(f"a{i}")
748
+ msgs = cs.get_messages_for_llm(max_turns=2)
749
+ assert len(msgs) <= 4
750
+
751
+ def test_to_dict(self):
752
+ cs = ConversationSession()
753
+ cs.add_user("test")
754
+ d = cs.to_dict()
755
+ assert "session_id" in d
756
+ assert "messages" in d
757
+ assert len(d["messages"]) == 1
758
+
759
+ def test_from_dict_roundtrip(self):
760
+ cs = ConversationSession(title="test session")
761
+ cs.add_user("hello")
762
+ cs.add_assistant("world")
763
+ d = cs.to_dict()
764
+ cs2 = ConversationSession.from_dict(d)
765
+ assert cs2.title == "test session"
766
+ assert len(cs2.messages) == 2
767
+
768
+
769
+ # ==========================================================================
770
+ # Context Engine
771
+ # ==========================================================================
772
+
773
+ from semantic_code_intelligence.context.engine import (
774
+ ContextBuilder,
775
+ ContextWindow,
776
+ )
777
+ from semantic_code_intelligence.parsing.parser import Symbol
778
+
779
+
780
+ def _sym(name="test_fn", kind="function", file_path="test.py",
781
+ start_line=1, end_line=5, body="def test_fn(): pass"):
782
+ return Symbol(
783
+ name=name, kind=kind, file_path=file_path,
784
+ start_line=start_line, end_line=end_line,
785
+ start_col=0, end_col=0, body=body,
786
+ )
787
+
788
+
789
+ class TestContextWindow:
790
+ def test_create(self):
791
+ s = _sym()
792
+ cw = ContextWindow(focal_symbol=s)
793
+ assert cw.focal_symbol.name == "test_fn"
794
+
795
+ def test_to_dict(self):
796
+ cw = ContextWindow(focal_symbol=_sym(), related_symbols=[_sym("helper")])
797
+ d = cw.to_dict()
798
+ assert "focal_symbol" in d
799
+
800
+ def test_render(self):
801
+ cw = ContextWindow(focal_symbol=_sym(), file_content="def test_fn(): pass")
802
+ text = cw.render()
803
+ assert isinstance(text, str)
804
+
805
+ def test_defaults(self):
806
+ cw = ContextWindow(focal_symbol=_sym())
807
+ assert cw.related_symbols == []
808
+ assert cw.imports == []
809
+ assert cw.file_content == ""
810
+
811
+
812
+ class TestContextBuilder:
813
+ def test_create(self):
814
+ cb = ContextBuilder()
815
+ assert cb is not None
816
+
817
+ def test_get_symbols_empty(self):
818
+ cb = ContextBuilder()
819
+ syms = cb.get_symbols("nonexistent.py")
820
+ assert syms == []
821
+
822
+ def test_get_all_symbols_empty(self):
823
+ cb = ContextBuilder()
824
+ syms = cb.get_all_symbols()
825
+ assert syms == []
826
+
827
+ def test_find_symbol_empty(self):
828
+ cb = ContextBuilder()
829
+ matches = cb.find_symbol("nonexistent")
830
+ assert matches == []
831
+
832
+ def test_index_file(self):
833
+ cb = ContextBuilder()
834
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
835
+ delete=False, encoding="utf-8") as f:
836
+ f.write("def hello():\n pass\n\nclass World:\n pass\n")
837
+ f.flush()
838
+ syms = cb.index_file(f.name)
839
+ assert isinstance(syms, list)
840
+
841
+ def test_find_after_index(self):
842
+ cb = ContextBuilder()
843
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
844
+ delete=False, encoding="utf-8") as f:
845
+ f.write("def my_unique_fn():\n return 42\n")
846
+ f.flush()
847
+ cb.index_file(f.name)
848
+ matches = cb.find_symbol("my_unique_fn")
849
+ assert len(matches) >= 1
850
+ assert matches[0].name == "my_unique_fn"
851
+
852
+
853
+ # ==========================================================================
854
+ # Context Memory
855
+ # ==========================================================================
856
+
857
+ from semantic_code_intelligence.context.memory import (
858
+ MemoryEntry,
859
+ ReasoningStep,
860
+ SessionMemory,
861
+ )
862
+
863
+
864
+ class TestMemoryEntry:
865
+ def test_create(self):
866
+ me = MemoryEntry(key="k1", content="stuff")
867
+ assert me.key == "k1"
868
+ assert me.content == "stuff"
869
+
870
+ def test_to_dict(self):
871
+ me = MemoryEntry(key="k", content="v", kind="fact")
872
+ d = me.to_dict()
873
+ assert d["key"] == "k"
874
+ assert d["kind"] == "fact"
875
+
876
+ def test_from_dict(self):
877
+ d = {"key": "a", "content": "b", "kind": "general", "timestamp": 0.0, "metadata": {}}
878
+ me = MemoryEntry.from_dict(d)
879
+ assert me.key == "a"
880
+
881
+ def test_default_kind(self):
882
+ me = MemoryEntry(key="x", content="y")
883
+ assert me.kind == "general"
884
+
885
+
886
+ class TestReasoningStep:
887
+ def test_create(self):
888
+ rs = ReasoningStep(step_id=1, action="search", input_text="query",
889
+ output_text="results")
890
+ assert rs.step_id == 1
891
+ assert rs.action == "search"
892
+
893
+ def test_to_dict(self):
894
+ rs = ReasoningStep(step_id=0, action="analyze", input_text="i",
895
+ output_text="o")
896
+ d = rs.to_dict()
897
+ assert d["step_id"] == 0
898
+ assert d["action"] == "analyze"
899
+
900
+
901
+ class TestSessionMemory:
902
+ def test_create(self):
903
+ sm = SessionMemory()
904
+ assert len(sm.entries) == 0
905
+
906
+ def test_add(self):
907
+ sm = SessionMemory()
908
+ entry = sm.add("k1", "content1")
909
+ assert isinstance(entry, MemoryEntry)
910
+ assert len(sm.entries) == 1
911
+
912
+ def test_search(self):
913
+ sm = SessionMemory()
914
+ sm.add("python", "Python is a programming language")
915
+ sm.add("java", "Java is a compiled language")
916
+ results = sm.search("python", limit=1)
917
+ assert isinstance(results, list)
918
+
919
+ def test_get_recent(self):
920
+ sm = SessionMemory()
921
+ for i in range(5):
922
+ sm.add(f"k{i}", f"content{i}")
923
+ recent = sm.get_recent(limit=3)
924
+ assert len(recent) == 3
925
+
926
+ def test_clear(self):
927
+ sm = SessionMemory()
928
+ sm.add("k", "v")
929
+ sm.clear()
930
+ assert len(sm.entries) == 0
931
+
932
+ def test_start_chain(self):
933
+ sm = SessionMemory()
934
+ sm.start_chain("ch1")
935
+ # No error means success
936
+
937
+ def test_add_step(self):
938
+ sm = SessionMemory()
939
+ sm.start_chain("ch1")
940
+ step = sm.add_step("ch1", "search", "query", "results")
941
+ assert isinstance(step, ReasoningStep)
942
+
943
+ def test_get_chain(self):
944
+ sm = SessionMemory()
945
+ sm.start_chain("ch1")
946
+ sm.add_step("ch1", "search", "q", "r")
947
+ sm.add_step("ch1", "analyze", "sym", "ctx")
948
+ chain = sm.get_chain("ch1")
949
+ assert len(chain) == 2
950
+
951
+ def test_to_dict(self):
952
+ sm = SessionMemory()
953
+ sm.add("k", "v")
954
+ d = sm.to_dict()
955
+ assert isinstance(d, dict)
956
+
957
+ def test_max_entries(self):
958
+ sm = SessionMemory(max_entries=3)
959
+ for i in range(5):
960
+ sm.add(f"k{i}", f"v{i}")
961
+ assert len(sm.entries) <= 3
962
+
963
+
964
+ # ==========================================================================
965
+ # Analysis / AI Features
966
+ # ==========================================================================
967
+
968
+ from semantic_code_intelligence.analysis.ai_features import (
969
+ LanguageStats,
970
+ RepoSummary,
971
+ )
972
+
973
+
974
+ class TestLanguageStats:
975
+ def test_create(self):
976
+ ls = LanguageStats(language="python")
977
+ assert ls.language == "python"
978
+ assert ls.file_count == 0
979
+
980
+ def test_to_dict(self):
981
+ ls = LanguageStats(language="javascript", file_count=10, function_count=50)
982
+ d = ls.to_dict()
983
+ assert d["language"] == "javascript"
984
+ assert d["file_count"] == 10
985
+ assert d["function_count"] == 50
986
+
987
+ def test_defaults(self):
988
+ ls = LanguageStats(language="go")
989
+ assert ls.class_count == 0
990
+ assert ls.method_count == 0
991
+ assert ls.import_count == 0
992
+ assert ls.total_lines == 0
993
+
994
+
995
+ class TestRepoSummary:
996
+ def test_create(self):
997
+ rs = RepoSummary()
998
+ assert rs.total_files == 0
999
+ assert rs.total_symbols == 0
1000
+
1001
+ def test_to_dict(self):
1002
+ rs = RepoSummary(total_files=5, total_functions=20)
1003
+ d = rs.to_dict()
1004
+ assert d["total_files"] == 5
1005
+ assert d["total_functions"] == 20
1006
+
1007
+ def test_to_json(self):
1008
+ rs = RepoSummary()
1009
+ j = rs.to_json()
1010
+ parsed = json.loads(j)
1011
+ assert "total_files" in parsed
1012
+
1013
+ def test_render(self):
1014
+ rs = RepoSummary(total_files=3, total_functions=10)
1015
+ text = rs.render()
1016
+ assert "3" in text
1017
+ assert "Repository Summary" in text
1018
+
1019
+ def test_render_with_languages(self):
1020
+ ls = LanguageStats(language="python", file_count=5, function_count=20, class_count=3)
1021
+ rs = RepoSummary(total_files=5, languages=[ls])
1022
+ text = rs.render()
1023
+ assert "python" in text
1024
+
1025
+ def test_defaults(self):
1026
+ rs = RepoSummary()
1027
+ assert rs.languages == []
1028
+ assert rs.top_functions == []
1029
+ assert rs.top_classes == []
1030
+
1031
+
1032
+ # ==========================================================================
1033
+ # Indexing - Chunker
1034
+ # ==========================================================================
1035
+
1036
+ from semantic_code_intelligence.indexing.chunker import (
1037
+ CodeChunk,
1038
+ chunk_code,
1039
+ detect_language as chunker_detect_language,
1040
+ )
1041
+
1042
+
1043
+ class TestCodeChunk:
1044
+ def test_create(self):
1045
+ cc = CodeChunk(file_path="a.py", content="code", start_line=1,
1046
+ end_line=10, chunk_index=0, language="python")
1047
+ assert cc.file_path == "a.py"
1048
+ assert cc.language == "python"
1049
+
1050
+ def test_is_dataclass(self):
1051
+ assert is_dataclass(CodeChunk)
1052
+
1053
+ def test_fields(self):
1054
+ names = {f.name for f in fields(CodeChunk)}
1055
+ assert "file_path" in names
1056
+ assert "content" in names
1057
+ assert "start_line" in names
1058
+ assert "end_line" in names
1059
+ assert "chunk_index" in names
1060
+ assert "language" in names
1061
+
1062
+
1063
+ class TestChunkCode:
1064
+ def test_basic(self):
1065
+ code = "line1\nline2\nline3\nline4\nline5\n" * 50
1066
+ chunks = chunk_code(code, "test.py", chunk_size=100, chunk_overlap=10)
1067
+ assert len(chunks) >= 1
1068
+ assert all(isinstance(c, CodeChunk) for c in chunks)
1069
+
1070
+ def test_empty(self):
1071
+ chunks = chunk_code("", "empty.py")
1072
+ assert isinstance(chunks, list)
1073
+
1074
+ def test_small_file(self):
1075
+ chunks = chunk_code("x = 1\n", "small.py")
1076
+ assert len(chunks) >= 1
1077
+
1078
+ def test_chunk_index_sequential(self):
1079
+ code = "x = 1\n" * 200
1080
+ chunks = chunk_code(code, "test.py", chunk_size=50)
1081
+ if len(chunks) > 1:
1082
+ for i, c in enumerate(chunks):
1083
+ assert c.chunk_index == i
1084
+
1085
+ def test_language_detection(self):
1086
+ chunks = chunk_code("def foo(): pass", "test.py")
1087
+ if chunks:
1088
+ assert chunks[0].language == "python"
1089
+
1090
+
1091
+ class TestChunkerDetectLanguage:
1092
+ def test_python(self):
1093
+ assert chunker_detect_language("test.py") == "python"
1094
+
1095
+ def test_javascript(self):
1096
+ assert chunker_detect_language("app.js") == "javascript"
1097
+
1098
+ def test_typescript(self):
1099
+ assert chunker_detect_language("main.ts") == "typescript"
1100
+
1101
+ def test_java(self):
1102
+ assert chunker_detect_language("Main.java") == "java"
1103
+
1104
+ def test_go(self):
1105
+ assert chunker_detect_language("main.go") == "go"
1106
+
1107
+ def test_rust(self):
1108
+ assert chunker_detect_language("lib.rs") == "rust"
1109
+
1110
+ def test_c(self):
1111
+ assert chunker_detect_language("main.c") == "c"
1112
+
1113
+ def test_cpp(self):
1114
+ assert chunker_detect_language("main.cpp") == "cpp"
1115
+
1116
+ def test_ruby(self):
1117
+ assert chunker_detect_language("app.rb") == "ruby"
1118
+
1119
+ def test_php(self):
1120
+ assert chunker_detect_language("index.php") == "php"
1121
+
1122
+ def test_csharp(self):
1123
+ assert chunker_detect_language("Program.cs") == "csharp"
1124
+
1125
+ def test_swift(self):
1126
+ assert chunker_detect_language("vc.swift") == "swift"
1127
+
1128
+ def test_kotlin(self):
1129
+ assert chunker_detect_language("Main.kt") == "kotlin"
1130
+
1131
+ def test_scala(self):
1132
+ assert chunker_detect_language("App.scala") == "scala"
1133
+
1134
+ def test_unknown(self):
1135
+ result = chunker_detect_language("readme.md")
1136
+ assert result == "unknown" or result is None
1137
+
1138
+
1139
+ # ==========================================================================
1140
+ # Indexing - Scanner
1141
+ # ==========================================================================
1142
+
1143
+ from semantic_code_intelligence.indexing.scanner import (
1144
+ ScannedFile,
1145
+ compute_file_hash,
1146
+ should_ignore,
1147
+ )
1148
+
1149
+
1150
+ class TestComputeFileHash:
1151
+ def test_basic(self):
1152
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
1153
+ delete=False, encoding="utf-8") as f:
1154
+ f.write("x = 1\n")
1155
+ f.flush()
1156
+ h = compute_file_hash(Path(f.name))
1157
+ assert isinstance(h, str)
1158
+ assert len(h) > 0
1159
+
1160
+ def test_deterministic(self):
1161
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
1162
+ delete=False, encoding="utf-8") as f:
1163
+ f.write("deterministic content\n")
1164
+ f.flush()
1165
+ h1 = compute_file_hash(Path(f.name))
1166
+ h2 = compute_file_hash(Path(f.name))
1167
+ assert h1 == h2
1168
+
1169
+
1170
+ class TestShouldIgnoreDeep:
1171
+ def test_git_dir(self):
1172
+ with tempfile.TemporaryDirectory() as tmp:
1173
+ root = Path(tmp)
1174
+ git_file = root / ".git" / "config"
1175
+ git_file.parent.mkdir()
1176
+ git_file.touch()
1177
+ assert should_ignore(git_file, root, {".git"}) is True
1178
+
1179
+ def test_normal_file(self):
1180
+ with tempfile.TemporaryDirectory() as tmp:
1181
+ root = Path(tmp)
1182
+ f = root / "src" / "main.py"
1183
+ f.parent.mkdir()
1184
+ f.touch()
1185
+ assert should_ignore(f, root, {".git"}) is False
1186
+
1187
+ def test_node_modules(self):
1188
+ with tempfile.TemporaryDirectory() as tmp:
1189
+ root = Path(tmp)
1190
+ f = root / "node_modules" / "pkg" / "index.js"
1191
+ f.parent.mkdir(parents=True)
1192
+ f.touch()
1193
+ assert should_ignore(f, root, {"node_modules"}) is True
1194
+
1195
+ def test_pycache(self):
1196
+ with tempfile.TemporaryDirectory() as tmp:
1197
+ root = Path(tmp)
1198
+ f = root / "__pycache__" / "mod.pyc"
1199
+ f.parent.mkdir()
1200
+ f.touch()
1201
+ assert should_ignore(f, root, {"__pycache__"}) is True
1202
+
1203
+
1204
+ class TestScannedFileDeep:
1205
+ def test_create(self):
1206
+ sf = ScannedFile(path=Path("a.py"), relative_path="a.py",
1207
+ extension=".py", size_bytes=100, content_hash="abc")
1208
+ assert sf.relative_path == "a.py"
1209
+ assert sf.extension == ".py"
1210
+ assert sf.size_bytes == 100
1211
+
1212
+ def test_is_dataclass(self):
1213
+ assert is_dataclass(ScannedFile)
1214
+
1215
+ def test_fields(self):
1216
+ names = {f.name for f in fields(ScannedFile)}
1217
+ assert "path" in names
1218
+ assert "relative_path" in names
1219
+ assert "extension" in names
1220
+ assert "size_bytes" in names
1221
+ assert "content_hash" in names
1222
+
1223
+
1224
+ # ==========================================================================
1225
+ # Parsing - Symbol
1226
+ # ==========================================================================
1227
+
1228
+ from semantic_code_intelligence.parsing.parser import (
1229
+ Symbol as ParserSymbol,
1230
+ parse_file,
1231
+ detect_language as parser_detect_language,
1232
+ )
1233
+
1234
+
1235
+ class TestParserSymbol:
1236
+ def test_create(self):
1237
+ s = _sym()
1238
+ assert s.name == "test_fn"
1239
+
1240
+ def test_to_dict(self):
1241
+ s = _sym(name="foo", kind="class")
1242
+ d = s.to_dict()
1243
+ assert d["name"] == "foo"
1244
+ assert d["kind"] == "class"
1245
+
1246
+ def test_parent(self):
1247
+ s = _sym()
1248
+ assert s.parent is None
1249
+
1250
+ def test_with_parent(self):
1251
+ s = Symbol(name="method", kind="method", file_path="a.py",
1252
+ start_line=5, end_line=10, start_col=4, end_col=0,
1253
+ body="def method(): pass", parent="MyClass")
1254
+ assert s.parent == "MyClass"
1255
+
1256
+ def test_decorators(self):
1257
+ s = Symbol(name="fn", kind="function", file_path="a.py",
1258
+ start_line=1, end_line=3, start_col=0, end_col=0,
1259
+ body="@staticmethod\ndef fn(): pass",
1260
+ decorators=["staticmethod"])
1261
+ assert "staticmethod" in s.decorators
1262
+
1263
+ def test_parameters(self):
1264
+ s = Symbol(name="fn", kind="function", file_path="a.py",
1265
+ start_line=1, end_line=2, start_col=0, end_col=0,
1266
+ body="def fn(x, y): pass",
1267
+ parameters=["x", "y"])
1268
+ assert s.parameters == ["x", "y"]
1269
+
1270
+
1271
+ class TestParseFile:
1272
+ def test_python_file(self):
1273
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
1274
+ delete=False, encoding="utf-8") as f:
1275
+ f.write("def greet(name):\n print(f'Hello {name}')\n\nclass Greeter:\n pass\n")
1276
+ f.flush()
1277
+ symbols = parse_file(f.name)
1278
+ assert len(symbols) >= 2
1279
+ names = [s.name for s in symbols]
1280
+ assert "greet" in names
1281
+ assert "Greeter" in names
1282
+
1283
+ def test_empty_file(self):
1284
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py",
1285
+ delete=False, encoding="utf-8") as f:
1286
+ f.write("")
1287
+ f.flush()
1288
+ symbols = parse_file(f.name)
1289
+ assert isinstance(symbols, list)
1290
+
1291
+ def test_with_content(self):
1292
+ symbols = parse_file("virtual.py", content="class Foo:\n pass\n")
1293
+ assert any(s.name == "Foo" for s in symbols)
1294
+
1295
+
1296
+ class TestParserDetectLanguage:
1297
+ def test_python(self):
1298
+ assert parser_detect_language("test.py") == "python"
1299
+
1300
+ def test_javascript(self):
1301
+ assert parser_detect_language("app.js") == "javascript"
1302
+
1303
+ def test_none_for_unknown(self):
1304
+ result = parser_detect_language("file.xyz123")
1305
+ assert result is None or result == "unknown"
1306
+
1307
+
1308
+ # ==========================================================================
1309
+ # Storage - Hash Store
1310
+ # ==========================================================================
1311
+
1312
+ from semantic_code_intelligence.storage.hash_store import HashStore
1313
+
1314
+
1315
+ class TestHashStoreExtended:
1316
+ def test_get_existing(self):
1317
+ hs = HashStore()
1318
+ hs.set("a.py", "hash1")
1319
+ assert hs.get("a.py") == "hash1"
1320
+
1321
+ def test_get_missing(self):
1322
+ hs = HashStore()
1323
+ assert hs.get("nonexistent.py") is None
1324
+
1325
+ def test_remove(self):
1326
+ hs = HashStore()
1327
+ hs.set("a.py", "h1")
1328
+ hs.remove("a.py")
1329
+ assert hs.get("a.py") is None
1330
+ assert hs.count == 0
1331
+
1332
+ def test_remove_nonexistent(self):
1333
+ hs = HashStore()
1334
+ hs.remove("nope") # Should not raise
1335
+
1336
+ def test_overwrite(self):
1337
+ hs = HashStore()
1338
+ hs.set("a.py", "h1")
1339
+ hs.set("a.py", "h2")
1340
+ assert hs.get("a.py") == "h2"
1341
+ assert hs.count == 1
1342
+
1343
+ def test_count(self):
1344
+ hs = HashStore()
1345
+ assert hs.count == 0
1346
+ hs.set("a.py", "h1")
1347
+ hs.set("b.py", "h2")
1348
+ assert hs.count == 2
1349
+
1350
+ def test_has_changed_new_file(self):
1351
+ hs = HashStore()
1352
+ assert hs.has_changed("new.py", "anyhash") is True
1353
+
1354
+ def test_has_changed_same_hash(self):
1355
+ hs = HashStore()
1356
+ hs.set("a.py", "h1")
1357
+ assert hs.has_changed("a.py", "h1") is False
1358
+
1359
+ def test_has_changed_different_hash(self):
1360
+ hs = HashStore()
1361
+ hs.set("a.py", "h1")
1362
+ assert hs.has_changed("a.py", "h2") is True
1363
+
1364
+ def test_save_load_roundtrip(self):
1365
+ with tempfile.TemporaryDirectory() as tmp:
1366
+ hs = HashStore()
1367
+ hs.set("a.py", "h1")
1368
+ hs.set("b.py", "h2")
1369
+ hs.save(Path(tmp))
1370
+
1371
+ hs2 = HashStore.load(Path(tmp))
1372
+ assert hs2.get("a.py") == "h1"
1373
+ assert hs2.get("b.py") == "h2"
1374
+ assert hs2.count == 2
1375
+
1376
+
1377
+ # ==========================================================================
1378
+ # Storage - Vector Store
1379
+ # ==========================================================================
1380
+
1381
+ from semantic_code_intelligence.storage.vector_store import VectorStore
1382
+ from semantic_code_intelligence.storage.vector_store import ChunkMetadata
1383
+
1384
+
1385
+ class TestVectorStoreExtended:
1386
+ def test_create(self):
1387
+ vs = VectorStore(dimension=8)
1388
+ assert vs.size == 0
1389
+ assert vs.dimension == 8
1390
+
1391
+ def test_add_and_size(self):
1392
+ import numpy as np
1393
+ vs = VectorStore(dimension=4)
1394
+ emb = np.array([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
1395
+ meta = ChunkMetadata(file_path="a.py", start_line=1, end_line=2,
1396
+ chunk_index=0, content="test", language="python")
1397
+ vs.add(emb, [meta])
1398
+ assert vs.size == 1
1399
+
1400
+ def test_search_returns_list(self):
1401
+ import numpy as np
1402
+ vs = VectorStore(dimension=4)
1403
+ emb = np.array([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
1404
+ meta = ChunkMetadata(file_path="a.py", start_line=1, end_line=2,
1405
+ chunk_index=0, content="code", language="python")
1406
+ vs.add(emb, [meta])
1407
+ query = np.array([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
1408
+ results = vs.search(query, top_k=1)
1409
+ assert isinstance(results, list)
1410
+ assert len(results) >= 1
1411
+
1412
+ def test_search_empty(self):
1413
+ import numpy as np
1414
+ vs = VectorStore(dimension=4)
1415
+ query = np.array([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
1416
+ results = vs.search(query, top_k=5)
1417
+ assert results == []
1418
+
1419
+ def test_save_load(self):
1420
+ import numpy as np
1421
+ with tempfile.TemporaryDirectory() as tmp:
1422
+ vs = VectorStore(dimension=4)
1423
+ emb = np.array([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
1424
+ meta = ChunkMetadata(file_path="x.py", start_line=1, end_line=5,
1425
+ chunk_index=0, content="code", language="python")
1426
+ vs.add(emb, [meta])
1427
+ vs.save(Path(tmp))
1428
+
1429
+ vs2 = VectorStore.load(Path(tmp))
1430
+ assert vs2.size == 1
1431
+ assert vs2.dimension == 4
1432
+
1433
+
1434
+ # ==========================================================================
1435
+ # Workspace
1436
+ # ==========================================================================
1437
+
1438
+ from semantic_code_intelligence.workspace import (
1439
+ RepoEntry,
1440
+ WorkspaceManifest,
1441
+ Workspace,
1442
+ )
1443
+
1444
+
1445
+ class TestRepoEntryExtended:
1446
+ def test_from_dict(self):
1447
+ d = {"name": "myrepo", "path": "/path"}
1448
+ re = RepoEntry.from_dict(d)
1449
+ assert re.name == "myrepo"
1450
+ assert re.path == "/path"
1451
+
1452
+ def test_from_dict_with_extras(self):
1453
+ d = {"name": "r", "path": "/r", "last_indexed": 1.0, "file_count": 5, "vector_count": 10}
1454
+ re = RepoEntry.from_dict(d)
1455
+ assert re.last_indexed == 1.0
1456
+ assert re.file_count == 5
1457
+ assert re.vector_count == 10
1458
+
1459
+ def test_roundtrip(self):
1460
+ re = RepoEntry(name="test", path="/test", file_count=3)
1461
+ d = re.to_dict()
1462
+ re2 = RepoEntry.from_dict(d)
1463
+ assert re2.name == re.name
1464
+ assert re2.file_count == re.file_count
1465
+
1466
+
1467
+ class TestWorkspaceManifestExtended:
1468
+ def test_from_dict(self):
1469
+ d = {"version": "2.0.0", "repos": [{"name": "r1", "path": "/r1"}]}
1470
+ wm = WorkspaceManifest.from_dict(d)
1471
+ assert wm.version == "2.0.0"
1472
+ assert len(wm.repos) == 1
1473
+
1474
+ def test_roundtrip(self):
1475
+ wm = WorkspaceManifest(repos=[RepoEntry(name="a", path="/a")])
1476
+ d = wm.to_dict()
1477
+ wm2 = WorkspaceManifest.from_dict(d)
1478
+ assert len(wm2.repos) == 1
1479
+ assert wm2.repos[0].name == "a"
1480
+
1481
+
1482
+ class TestWorkspaceExtended:
1483
+ def test_properties(self):
1484
+ with tempfile.TemporaryDirectory() as tmp:
1485
+ ws = Workspace(Path(tmp))
1486
+ assert ws.root == Path(tmp).resolve()
1487
+ assert ws.config_dir.name == ".codexa"
1488
+ assert ws.repos_dir.name == "repos"
1489
+
1490
+ def test_add_repo(self):
1491
+ with tempfile.TemporaryDirectory() as tmp:
1492
+ ws = Workspace(Path(tmp))
1493
+ entry = ws.add_repo("myrepo", Path(tmp))
1494
+ assert entry.name == "myrepo"
1495
+ assert len(ws.repos) == 1
1496
+
1497
+ def test_get_repo(self):
1498
+ with tempfile.TemporaryDirectory() as tmp:
1499
+ ws = Workspace(Path(tmp))
1500
+ ws.add_repo("r1", Path(tmp))
1501
+ found = ws.get_repo("r1")
1502
+ assert found is not None
1503
+ assert found.name == "r1"
1504
+
1505
+ def test_get_repo_missing(self):
1506
+ with tempfile.TemporaryDirectory() as tmp:
1507
+ ws = Workspace(Path(tmp))
1508
+ assert ws.get_repo("nonexistent") is None
1509
+
1510
+ def test_add_duplicate_raises(self):
1511
+ import pytest
1512
+ with tempfile.TemporaryDirectory() as tmp:
1513
+ ws = Workspace(Path(tmp))
1514
+ ws.add_repo("r1", Path(tmp))
1515
+ with pytest.raises(ValueError):
1516
+ ws.add_repo("r1", Path(tmp))
1517
+
1518
+ def test_save_and_load(self):
1519
+ with tempfile.TemporaryDirectory() as tmp:
1520
+ ws = Workspace(Path(tmp))
1521
+ ws.add_repo("myrepo", Path(tmp))
1522
+ ws.save()
1523
+
1524
+ ws2 = Workspace.load(Path(tmp))
1525
+ assert len(ws2.repos) == 1
1526
+ assert ws2.repos[0].name == "myrepo"
1527
+
1528
+ def test_load_or_create(self):
1529
+ with tempfile.TemporaryDirectory() as tmp:
1530
+ ws = Workspace.load_or_create(Path(tmp))
1531
+ assert ws is not None
1532
+ assert ws.root == Path(tmp).resolve()
1533
+
1534
+
1535
+ # ==========================================================================
1536
+ # Daemon / Watcher
1537
+ # ==========================================================================
1538
+
1539
+ from semantic_code_intelligence.daemon.watcher import FileChangeEvent, FileWatcher
1540
+
1541
+
1542
+ class TestFileChangeEventExtended:
1543
+ def test_default_timestamp(self):
1544
+ ev = FileChangeEvent(path=Path("a.py"), relative_path="a.py",
1545
+ change_type="created")
1546
+ assert ev.timestamp == 0.0
1547
+
1548
+ def test_custom_timestamp(self):
1549
+ ev = FileChangeEvent(path=Path("b.py"), relative_path="b.py",
1550
+ change_type="modified", timestamp=123.456)
1551
+ assert ev.timestamp == 123.456
1552
+
1553
+ def test_change_types(self):
1554
+ for ct in ("created", "modified", "deleted"):
1555
+ ev = FileChangeEvent(path=Path("x"), relative_path="x", change_type=ct)
1556
+ assert ev.change_type == ct
1557
+
1558
+
1559
+ class TestFileWatcherExtended:
1560
+ def test_not_running(self):
1561
+ with tempfile.TemporaryDirectory() as tmp:
1562
+ fw = FileWatcher(Path(tmp))
1563
+ assert fw.is_running is False
1564
+
1565
+ def test_on_change_callback(self):
1566
+ with tempfile.TemporaryDirectory() as tmp:
1567
+ fw = FileWatcher(Path(tmp))
1568
+ callback = MagicMock()
1569
+ fw.on_change(callback)
1570
+ # Callback registered, no error
1571
+
1572
+
1573
+ # ==========================================================================
1574
+ # Semantic Chunker
1575
+ # ==========================================================================
1576
+
1577
+ from semantic_code_intelligence.indexing.semantic_chunker import SemanticChunk
1578
+
1579
+
1580
+ class TestSemanticChunk:
1581
+ def test_create(self):
1582
+ sc = SemanticChunk(file_path="a.py", content="def foo(): pass",
1583
+ start_line=1, end_line=1, chunk_index=0,
1584
+ language="python", symbol_name="foo",
1585
+ symbol_kind="function", semantic_label="function foo")
1586
+ assert sc.symbol_name == "foo"
1587
+ assert sc.symbol_kind == "function"
1588
+ assert sc.semantic_label == "function foo"
1589
+
1590
+ def test_to_dict(self):
1591
+ sc = SemanticChunk(file_path="b.py", content="class Bar: pass",
1592
+ start_line=1, end_line=1, chunk_index=0,
1593
+ language="python", symbol_name="Bar",
1594
+ symbol_kind="class", semantic_label="class Bar")
1595
+ d = sc.to_dict()
1596
+ assert d["symbol_name"] == "Bar"
1597
+ assert d["symbol_kind"] == "class"
1598
+ assert d["semantic_label"] == "class Bar"
1599
+
1600
+ def test_inherits_from_code_chunk(self):
1601
+ assert issubclass(SemanticChunk, CodeChunk)
1602
+
1603
+ def test_defaults(self):
1604
+ sc = SemanticChunk(file_path="c.py", content="x", start_line=1,
1605
+ end_line=1, chunk_index=0, language="python")
1606
+ assert sc.symbol_name == ""
1607
+ assert sc.symbol_kind == ""
1608
+ assert sc.parent_symbol == ""
1609
+ assert sc.parameters == []
1610
+ assert sc.semantic_label == ""
1611
+
1612
+
1613
+ # ==========================================================================
1614
+ # Quality Module Deep Tests
1615
+ # ==========================================================================
1616
+
1617
+ from semantic_code_intelligence.ci.quality import (
1618
+ ComplexityResult,
1619
+ compute_complexity,
1620
+ )
1621
+
1622
+
1623
+ class TestComplexityResultDeep:
1624
+ def test_create(self):
1625
+ cr = ComplexityResult(symbol_name="fn", file_path="a.py",
1626
+ start_line=1, end_line=5, complexity=3, rating="A")
1627
+ assert cr.symbol_name == "fn"
1628
+ assert cr.complexity == 3
1629
+ assert cr.rating == "A"
1630
+
1631
+ def test_to_dict(self):
1632
+ cr = ComplexityResult(symbol_name="fn", file_path="a.py",
1633
+ start_line=1, end_line=5, complexity=15, rating="C")
1634
+ d = cr.to_dict()
1635
+ assert d["symbol_name"] == "fn"
1636
+ assert d["complexity"] == 15
1637
+ assert d["rating"] == "C"
1638
+
1639
+
1640
+ class TestComputeComplexity:
1641
+ def test_simple_function(self):
1642
+ s = _sym(name="simple", body="def simple():\n return 1\n")
1643
+ cr = compute_complexity(s)
1644
+ assert isinstance(cr, ComplexityResult)
1645
+ assert cr.complexity >= 1
1646
+
1647
+ def test_complex_function(self):
1648
+ body = "def complex():\n"
1649
+ body += " if True:\n pass\n"
1650
+ body += " for i in range(10):\n pass\n"
1651
+ body += " while True:\n break\n"
1652
+ s = _sym(name="complex", body=body)
1653
+ cr = compute_complexity(s)
1654
+ assert cr.complexity >= 3
1655
+
1656
+
1657
+ # ==========================================================================
1658
+ # CI Metrics
1659
+ # ==========================================================================
1660
+
1661
+ from semantic_code_intelligence.ci.metrics import QualitySnapshot
1662
+
1663
+
1664
+ class TestQualitySnapshotExtended:
1665
+ def test_to_dict(self):
1666
+ qs = QualitySnapshot(
1667
+ timestamp=1000.0,
1668
+ maintainability_index=75.0,
1669
+ total_loc=500,
1670
+ total_symbols=50,
1671
+ issue_count=0,
1672
+ files_analyzed=10,
1673
+ avg_complexity=5.0,
1674
+ comment_ratio=0.2,
1675
+ )
1676
+ d = qs.to_dict()
1677
+ assert d["maintainability_index"] == 75.0
1678
+ assert d["avg_complexity"] == 5.0
1679
+ assert d["total_loc"] == 500
1680
+
1681
+ def test_is_dataclass(self):
1682
+ assert is_dataclass(QualitySnapshot)
1683
+
1684
+
1685
+ # ==========================================================================
1686
+ # Mock Provider
1687
+ # ==========================================================================
1688
+
1689
+ from semantic_code_intelligence.llm.mock_provider import MockProvider
1690
+
1691
+
1692
+ class TestMockProviderExtended:
1693
+ def test_name(self):
1694
+ p = MockProvider()
1695
+ assert p.name == "mock"
1696
+
1697
+ def test_is_available(self):
1698
+ p = MockProvider()
1699
+ assert p.is_available() is True
1700
+
1701
+ def test_complete(self):
1702
+ p = MockProvider()
1703
+ resp = p.complete("Hello")
1704
+ assert isinstance(resp, LLMResponse)
1705
+ assert len(resp.content) > 0
1706
+
1707
+ def test_chat(self):
1708
+ p = MockProvider()
1709
+ messages = [LLMMessage(role=MessageRole.USER, content="Hi")]
1710
+ resp = p.chat(messages)
1711
+ assert isinstance(resp, LLMResponse)
1712
+ assert len(resp.content) > 0
1713
+
1714
+ def test_response_has_provider(self):
1715
+ p = MockProvider()
1716
+ resp = p.complete("test")
1717
+ assert resp.provider == "mock"
1718
+
1719
+
1720
+ # ==========================================================================
1721
+ # Scalability
1722
+ # ==========================================================================
1723
+
1724
+ from semantic_code_intelligence.scalability import BatchProcessor, ParallelScanner
1725
+
1726
+
1727
+ class TestBatchProcessorExtended:
1728
+ def test_batch_counting(self):
1729
+ bp = BatchProcessor(batch_size=3)
1730
+ items = list(range(10))
1731
+ _, stats = bp.process(items, lambda batch: batch)
1732
+ assert stats.batches_processed >= 4 # ceil(10/3) = 4
1733
+
1734
+ def test_callback(self):
1735
+ bp = BatchProcessor(batch_size=2)
1736
+ callbacks = []
1737
+ items = list(range(5))
1738
+ _, stats = bp.process(items, lambda b: b,
1739
+ on_batch=lambda cur, tot: callbacks.append((cur, tot)))
1740
+ assert len(callbacks) >= 1
1741
+
1742
+ def test_processor_failure(self):
1743
+ bp = BatchProcessor(batch_size=2)
1744
+ def failing(batch):
1745
+ raise ValueError("boom")
1746
+ results, stats = bp.process([1, 2, 3], failing)
1747
+ assert stats.items_failed >= 1
1748
+
1749
+
1750
+ class TestParallelScannerExtended:
1751
+ def test_process_files(self):
1752
+ with tempfile.TemporaryDirectory() as tmp:
1753
+ for i in range(3):
1754
+ (Path(tmp) / f"file{i}.txt").write_text(f"content{i}")
1755
+ files = list(Path(tmp).glob("*.txt"))
1756
+ ps = ParallelScanner(max_workers=2)
1757
+ results, errors = ps.scan_and_process(files, lambda fp: fp.name)
1758
+ assert len(results) == 3
1759
+ assert errors == []
1760
+
1761
+ def test_error_handling(self):
1762
+ ps = ParallelScanner(max_workers=1)
1763
+ def fail(fp):
1764
+ raise ValueError("error")
1765
+ results, errors = ps.scan_and_process([Path("fake.txt")], fail)
1766
+ assert len(errors) >= 1
1767
+
1768
+
1769
+ # ==========================================================================
1770
+ # Plugins
1771
+ # ==========================================================================
1772
+
1773
+ from semantic_code_intelligence.plugins import PluginHook
1774
+
1775
+
1776
+ class TestPluginHookValues:
1777
+ def test_pre_index(self):
1778
+ assert PluginHook.PRE_INDEX.value == "pre_index"
1779
+
1780
+ def test_post_index(self):
1781
+ assert PluginHook.POST_INDEX.value == "post_index"
1782
+
1783
+ def test_pre_search(self):
1784
+ assert PluginHook.PRE_SEARCH.value == "pre_search"
1785
+
1786
+ def test_post_search(self):
1787
+ assert PluginHook.POST_SEARCH.value == "post_search"
1788
+
1789
+ def test_on_chunk(self):
1790
+ assert PluginHook.ON_CHUNK.value == "on_chunk"
1791
+
1792
+ def test_count(self):
1793
+ assert len(PluginHook) >= 20
1794
+
1795
+
1796
+ # ==========================================================================
1797
+ # Tools
1798
+ # ==========================================================================
1799
+
1800
+ from semantic_code_intelligence.tools import (
1801
+ TOOL_DEFINITIONS,
1802
+ ToolResult,
1803
+ ToolRegistry,
1804
+ )
1805
+
1806
+
1807
+ class TestToolDefinitionsExtended:
1808
+ _names = [t["name"] for t in TOOL_DEFINITIONS]
1809
+
1810
+ def test_explain_symbol(self):
1811
+ assert "explain_symbol" in self._names
1812
+
1813
+ def test_get_call_graph(self):
1814
+ assert "get_call_graph" in self._names
1815
+
1816
+ def test_get_dependencies(self):
1817
+ assert "get_dependencies" in self._names
1818
+
1819
+ def test_find_references(self):
1820
+ assert "find_references" in self._names
1821
+
1822
+ def test_get_context(self):
1823
+ assert "get_context" in self._names
1824
+
1825
+ def test_summarize_repo(self):
1826
+ assert "summarize_repo" in self._names
1827
+
1828
+ def test_explain_file(self):
1829
+ assert "explain_file" in self._names
1830
+
1831
+ def test_search(self):
1832
+ assert "semantic_search" in self._names
1833
+
1834
+ def test_each_has_description(self):
1835
+ for defn in TOOL_DEFINITIONS:
1836
+ assert "description" in defn, f"Tool {defn['name']} missing description"
1837
+
1838
+ def test_each_has_parameters(self):
1839
+ for defn in TOOL_DEFINITIONS:
1840
+ assert "parameters" in defn, f"Tool {defn['name']} missing parameters"
1841
+
1842
+
1843
+ class TestToolResultExtended:
1844
+ def test_success(self):
1845
+ tr = ToolResult(tool_name="search", success=True, data={"results": []})
1846
+ assert tr.tool_name == "search"
1847
+ assert tr.success is True
1848
+
1849
+ def test_failure(self):
1850
+ tr = ToolResult(tool_name="explain", success=False, error="not found")
1851
+ assert tr.success is False
1852
+ assert tr.error == "not found"
1853
+
1854
+ def test_to_dict(self):
1855
+ tr = ToolResult(tool_name="test", success=True, data={"x": 1})
1856
+ d = tr.to_dict()
1857
+ assert d["tool"] == "test"
1858
+ assert d["success"] is True
1859
+
1860
+
1861
+ class TestToolRegistryExtended:
1862
+ def test_create(self):
1863
+ with tempfile.TemporaryDirectory() as tmp:
1864
+ tr = ToolRegistry(Path(tmp))
1865
+ assert tr is not None
1866
+
1867
+ def test_tool_definitions(self):
1868
+ with tempfile.TemporaryDirectory() as tmp:
1869
+ tr = ToolRegistry(Path(tmp))
1870
+ defns = tr.tool_definitions
1871
+ assert isinstance(defns, list)
1872
+ assert len(defns) >= 8
1873
+
1874
+
1875
+ # ==========================================================================
1876
+ # Version
1877
+ # ==========================================================================
1878
+
1879
+ from semantic_code_intelligence import __version__, __app_name__
1880
+
1881
+
1882
+ class TestVersionExtended:
1883
+ def test_semver(self):
1884
+ parts = __version__.split(".")
1885
+ assert len(parts) == 3
1886
+ for p in parts:
1887
+ assert p.isdigit()
1888
+
1889
+ def test_app_name_value(self):
1890
+ assert __app_name__ == "codexa"
1891
+
1892
+ def test_version_not_empty(self):
1893
+ assert len(__version__) >= 5
1894
+
1895
+
1896
+ # ==========================================================================
1897
+ # Bridge Context Provider
1898
+ # ==========================================================================
1899
+
1900
+ from semantic_code_intelligence.bridge.context_provider import ContextProvider
1901
+
1902
+
1903
+ class TestContextProvider:
1904
+ def test_create(self):
1905
+ with tempfile.TemporaryDirectory() as tmp:
1906
+ cp = ContextProvider(Path(tmp))
1907
+ assert cp is not None
1908
+
1909
+ def test_repo_summary(self):
1910
+ with tempfile.TemporaryDirectory() as tmp:
1911
+ cp = ContextProvider(Path(tmp))
1912
+ summary = cp.context_for_repo()
1913
+ assert isinstance(summary, dict)
1914
+
1915
+
1916
+ # ==========================================================================
1917
+ # Services - Indexing
1918
+ # ==========================================================================
1919
+
1920
+ from semantic_code_intelligence.services.indexing_service import IndexingResult
1921
+
1922
+
1923
+ class TestIndexingResult:
1924
+ def test_create(self):
1925
+ ir = IndexingResult()
1926
+ assert ir is not None
1927
+
1928
+ def test_repr(self):
1929
+ ir = IndexingResult()
1930
+ r = repr(ir)
1931
+ assert isinstance(r, str)
1932
+
1933
+
1934
+ # ==========================================================================
1935
+ # Services - Search
1936
+ # ==========================================================================
1937
+
1938
+ from semantic_code_intelligence.services.search_service import SearchResult
1939
+
1940
+
1941
+ class TestSearchResultExtended:
1942
+ def test_create(self):
1943
+ sr = SearchResult(file_path="a.py", start_line=1, end_line=5,
1944
+ language="python", content="code", score=0.95,
1945
+ chunk_index=0)
1946
+ assert sr.file_path == "a.py"
1947
+ assert sr.score == 0.95
1948
+
1949
+ def test_to_dict(self):
1950
+ sr = SearchResult(file_path="b.py", start_line=10, end_line=20,
1951
+ language="js", content="function()", score=0.8,
1952
+ chunk_index=1)
1953
+ d = sr.to_dict()
1954
+ assert d["file_path"] == "b.py"
1955
+ assert d["start_line"] == 10
1956
+ assert d["score"] == 0.8
1957
+
1958
+ def test_is_dataclass(self):
1959
+ assert is_dataclass(SearchResult)
1960
+
1961
+
1962
+ # ==========================================================================
1963
+ # CI Hooks
1964
+ # ==========================================================================
1965
+
1966
+ from semantic_code_intelligence.ci.hooks import run_precommit_check
1967
+
1968
+
1969
+ class TestPrecommitCheck:
1970
+ def test_no_git_dir(self):
1971
+ with tempfile.TemporaryDirectory() as tmp:
1972
+ # No .git dir — just verify it's callable
1973
+ assert callable(run_precommit_check)
1974
+
1975
+
1976
+ # ==========================================================================
1977
+ # CI Templates
1978
+ # ==========================================================================
1979
+
1980
+ from semantic_code_intelligence.ci.templates import get_template, generate_precommit_config
1981
+
1982
+
1983
+ class TestCITemplates:
1984
+ def test_get_analysis_template(self):
1985
+ tmpl = get_template("analysis")
1986
+ assert isinstance(tmpl, str)
1987
+ assert len(tmpl) > 0
1988
+
1989
+ def test_get_safety_template(self):
1990
+ tmpl = get_template("safety")
1991
+ assert isinstance(tmpl, str)
1992
+ assert len(tmpl) > 0
1993
+
1994
+ def test_get_precommit_template(self):
1995
+ tmpl = get_template("precommit")
1996
+ assert isinstance(tmpl, str)
1997
+
1998
+ def test_get_template_invalid(self):
1999
+ import pytest
2000
+ with pytest.raises(KeyError):
2001
+ get_template("nonexistent_template_xyz")
2002
+
2003
+ def test_generate_precommit_config(self):
2004
+ config = generate_precommit_config()
2005
+ assert isinstance(config, str)
2006
+
2007
+
2008
+ # ==========================================================================
2009
+ # CI PR Review
2010
+ # ==========================================================================
2011
+
2012
+ from semantic_code_intelligence.ci.pr import FileChange, ChangeSummary, RiskScore, PRReport
2013
+
2014
+
2015
+ class TestFileChange:
2016
+ def test_is_dataclass(self):
2017
+ assert is_dataclass(FileChange)
2018
+
2019
+
2020
+ class TestChangeSummary:
2021
+ def test_is_dataclass(self):
2022
+ assert is_dataclass(ChangeSummary)
2023
+
2024
+
2025
+ class TestRiskScore:
2026
+ def test_is_dataclass(self):
2027
+ assert is_dataclass(RiskScore)
2028
+
2029
+
2030
+ class TestPRReport:
2031
+ def test_is_dataclass(self):
2032
+ assert is_dataclass(PRReport)
2033
+
2034
+
2035
+ # ==========================================================================
2036
+ # Docs generators
2037
+ # ==========================================================================
2038
+
2039
+ from semantic_code_intelligence.docs import generate_all_docs
2040
+
2041
+
2042
+ class TestDocsGenerationExtended:
2043
+ def test_returns_list(self):
2044
+ with tempfile.TemporaryDirectory() as tmp:
2045
+ result = generate_all_docs(Path(tmp))
2046
+ assert isinstance(result, list)
2047
+
2048
+ def test_files_created(self):
2049
+ with tempfile.TemporaryDirectory() as tmp:
2050
+ result = generate_all_docs(Path(tmp))
2051
+ for name in result:
2052
+ assert (Path(tmp) / name).exists()
2053
+
2054
+ def test_files_are_markdown(self):
2055
+ with tempfile.TemporaryDirectory() as tmp:
2056
+ result = generate_all_docs(Path(tmp))
2057
+ for name in result:
2058
+ assert name.endswith(".md")