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,986 @@
1
+ """Tests for Phase 19 — AI Agent Tooling Protocol.
2
+
3
+ Covers: tool protocol dataclasses (ToolInvocation, ToolExecutionResult,
4
+ ToolError, ToolErrorCode), ToolExecutor engine (validation, routing,
5
+ plugin tools, batch execution), bridge extensions (INVOKE_TOOL, LIST_TOOLS,
6
+ /tools/invoke, /tools/list, /tools/stream), CLI `tool` command (list, run,
7
+ schema), plugin hooks (REGISTER_TOOL, PRE_TOOL_INVOKE, POST_TOOL_INVOKE),
8
+ capability manifest (tools field), docs generation (AI_TOOL_PROTOCOL.md),
9
+ router (31 commands), version (0.19.0), and safety guardrails.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import time
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+ from click.testing import CliRunner
20
+
21
+
22
+ # =========================================================================
23
+ # Test helpers
24
+ # =========================================================================
25
+
26
+
27
+ def _write_sample_project(root: Path) -> None:
28
+ """Write a small project for tool testing."""
29
+ src = root / "src"
30
+ src.mkdir(parents=True, exist_ok=True)
31
+
32
+ (src / "core.py").write_text(
33
+ "def helper():\n"
34
+ " return 42\n"
35
+ "\n"
36
+ "def compute(x):\n"
37
+ " return helper() + x\n",
38
+ encoding="utf-8",
39
+ )
40
+
41
+ (src / "api.py").write_text(
42
+ "from src.core import compute\n"
43
+ "\n"
44
+ "def handle_request(data):\n"
45
+ " return compute(data)\n",
46
+ encoding="utf-8",
47
+ )
48
+
49
+
50
+ # =========================================================================
51
+ # ToolInvocation dataclass tests
52
+ # =========================================================================
53
+
54
+
55
+ class TestToolInvocation:
56
+ """Tests for ToolInvocation dataclass."""
57
+
58
+ def test_basic_creation(self):
59
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
60
+
61
+ inv = ToolInvocation(tool_name="semantic_search", arguments={"query": "parse"})
62
+ assert inv.tool_name == "semantic_search"
63
+ assert inv.arguments == {"query": "parse"}
64
+ assert inv.request_id # auto-generated
65
+ assert inv.timestamp > 0
66
+
67
+ def test_auto_request_id(self):
68
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
69
+
70
+ inv = ToolInvocation(tool_name="test")
71
+ assert len(inv.request_id) == 12
72
+
73
+ def test_custom_request_id(self):
74
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
75
+
76
+ inv = ToolInvocation(tool_name="test", request_id="custom-123")
77
+ assert inv.request_id == "custom-123"
78
+
79
+ def test_to_dict(self):
80
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
81
+
82
+ inv = ToolInvocation(
83
+ tool_name="explain_symbol",
84
+ arguments={"symbol_name": "Foo"},
85
+ request_id="r1",
86
+ )
87
+ d = inv.to_dict()
88
+ assert d["tool_name"] == "explain_symbol"
89
+ assert d["arguments"] == {"symbol_name": "Foo"}
90
+ assert d["request_id"] == "r1"
91
+ assert "timestamp" in d
92
+
93
+ def test_to_json(self):
94
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
95
+
96
+ inv = ToolInvocation(tool_name="test", request_id="r1")
97
+ j = inv.to_json()
98
+ parsed = json.loads(j)
99
+ assert parsed["tool_name"] == "test"
100
+
101
+ def test_from_dict(self):
102
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
103
+
104
+ data = {
105
+ "tool_name": "get_context",
106
+ "arguments": {"symbol_name": "Bar"},
107
+ "request_id": "abc",
108
+ "timestamp": 1234567890.0,
109
+ }
110
+ inv = ToolInvocation.from_dict(data)
111
+ assert inv.tool_name == "get_context"
112
+ assert inv.arguments["symbol_name"] == "Bar"
113
+ assert inv.request_id == "abc"
114
+ assert inv.timestamp == 1234567890.0
115
+
116
+ def test_from_json(self):
117
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
118
+
119
+ j = '{"tool_name": "test", "arguments": {}, "request_id": "x", "timestamp": 0}'
120
+ inv = ToolInvocation.from_json(j)
121
+ assert inv.tool_name == "test"
122
+
123
+ def test_roundtrip(self):
124
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
125
+
126
+ original = ToolInvocation(tool_name="find_references", arguments={"symbol_name": "X"})
127
+ restored = ToolInvocation.from_dict(original.to_dict())
128
+ assert restored.tool_name == original.tool_name
129
+ assert restored.arguments == original.arguments
130
+ assert restored.request_id == original.request_id
131
+
132
+ def test_empty_arguments(self):
133
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
134
+
135
+ inv = ToolInvocation(tool_name="summarize_repo")
136
+ assert inv.arguments == {}
137
+
138
+
139
+ # =========================================================================
140
+ # ToolError dataclass tests
141
+ # =========================================================================
142
+
143
+
144
+ class TestToolError:
145
+ """Tests for ToolError dataclass."""
146
+
147
+ def test_basic_creation(self):
148
+ from semantic_code_intelligence.tools.protocol import ToolError
149
+
150
+ err = ToolError(
151
+ tool_name="bad_tool",
152
+ error_code="unknown_tool",
153
+ error_message="Tool not found",
154
+ request_id="r1",
155
+ )
156
+ assert err.tool_name == "bad_tool"
157
+ assert err.error_code == "unknown_tool"
158
+ assert err.error_message == "Tool not found"
159
+
160
+ def test_to_dict(self):
161
+ from semantic_code_intelligence.tools.protocol import ToolError
162
+
163
+ err = ToolError(tool_name="t", error_code="e", error_message="m", request_id="r")
164
+ d = err.to_dict()
165
+ assert d["tool_name"] == "t"
166
+ assert d["error_code"] == "e"
167
+ assert d["error_message"] == "m"
168
+ assert d["request_id"] == "r"
169
+
170
+ def test_to_json(self):
171
+ from semantic_code_intelligence.tools.protocol import ToolError
172
+
173
+ err = ToolError(tool_name="t", error_code="e", error_message="m")
174
+ j = err.to_json()
175
+ parsed = json.loads(j)
176
+ assert parsed["error_code"] == "e"
177
+
178
+ def test_from_dict(self):
179
+ from semantic_code_intelligence.tools.protocol import ToolError
180
+
181
+ data = {"tool_name": "a", "error_code": "b", "error_message": "c", "request_id": "d"}
182
+ err = ToolError.from_dict(data)
183
+ assert err.tool_name == "a"
184
+ assert err.error_code == "b"
185
+
186
+
187
+ # =========================================================================
188
+ # ToolErrorCode enum tests
189
+ # =========================================================================
190
+
191
+
192
+ class TestToolErrorCode:
193
+ """Tests for ToolErrorCode enum."""
194
+
195
+ def test_all_codes_exist(self):
196
+ from semantic_code_intelligence.tools.protocol import ToolErrorCode
197
+
198
+ expected = {"unknown_tool", "invalid_arguments", "missing_required_arg",
199
+ "execution_error", "timeout", "permission_denied"}
200
+ actual = {c.value for c in ToolErrorCode}
201
+ assert expected == actual
202
+
203
+ def test_string_values(self):
204
+ from semantic_code_intelligence.tools.protocol import ToolErrorCode
205
+
206
+ assert ToolErrorCode.UNKNOWN_TOOL == "unknown_tool"
207
+ assert ToolErrorCode.EXECUTION_ERROR == "execution_error"
208
+
209
+ def test_is_str_enum(self):
210
+ from semantic_code_intelligence.tools.protocol import ToolErrorCode
211
+
212
+ assert isinstance(ToolErrorCode.TIMEOUT, str)
213
+
214
+
215
+ # =========================================================================
216
+ # ToolExecutionResult dataclass tests
217
+ # =========================================================================
218
+
219
+
220
+ class TestToolExecutionResult:
221
+ """Tests for ToolExecutionResult dataclass."""
222
+
223
+ def test_success_result(self):
224
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
225
+
226
+ r = ToolExecutionResult(
227
+ tool_name="semantic_search",
228
+ request_id="r1",
229
+ success=True,
230
+ result_payload={"results": [1, 2, 3]},
231
+ execution_time_ms=42.5,
232
+ )
233
+ assert r.success is True
234
+ assert r.result_payload == {"results": [1, 2, 3]}
235
+
236
+ def test_failure_result(self):
237
+ from semantic_code_intelligence.tools.protocol import ToolError, ToolExecutionResult
238
+
239
+ err = ToolError(tool_name="t", error_code="execution_error", error_message="boom")
240
+ r = ToolExecutionResult(
241
+ tool_name="t",
242
+ success=False,
243
+ error=err,
244
+ )
245
+ assert r.success is False
246
+ assert r.error is not None
247
+ assert r.error.error_message == "boom"
248
+
249
+ def test_to_dict_success(self):
250
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
251
+
252
+ r = ToolExecutionResult(
253
+ tool_name="test", request_id="r1", success=True,
254
+ result_payload={"data": 42}, execution_time_ms=10.0,
255
+ )
256
+ d = r.to_dict()
257
+ assert d["success"] is True
258
+ assert d["result_payload"]["data"] == 42
259
+ assert "error" not in d
260
+
261
+ def test_to_dict_failure(self):
262
+ from semantic_code_intelligence.tools.protocol import ToolError, ToolExecutionResult
263
+
264
+ err = ToolError(tool_name="t", error_code="x", error_message="y")
265
+ r = ToolExecutionResult(tool_name="t", success=False, error=err)
266
+ d = r.to_dict()
267
+ assert d["success"] is False
268
+ assert d["error"]["error_code"] == "x"
269
+ assert "result_payload" not in d
270
+
271
+ def test_to_json(self):
272
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
273
+
274
+ r = ToolExecutionResult(tool_name="t", success=True, result_payload={"k": "v"})
275
+ j = r.to_json(indent=2)
276
+ parsed = json.loads(j)
277
+ assert parsed["success"] is True
278
+
279
+ def test_from_dict_success(self):
280
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
281
+
282
+ data = {
283
+ "tool_name": "t",
284
+ "request_id": "r1",
285
+ "success": True,
286
+ "result_payload": {"v": 1},
287
+ "execution_time_ms": 5.0,
288
+ "timestamp": 100.0,
289
+ }
290
+ r = ToolExecutionResult.from_dict(data)
291
+ assert r.tool_name == "t"
292
+ assert r.success is True
293
+ assert r.result_payload == {"v": 1}
294
+
295
+ def test_from_dict_failure(self):
296
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
297
+
298
+ data = {
299
+ "tool_name": "t",
300
+ "success": False,
301
+ "error": {"tool_name": "t", "error_code": "c", "error_message": "m", "request_id": ""},
302
+ }
303
+ r = ToolExecutionResult.from_dict(data)
304
+ assert r.success is False
305
+ assert r.error is not None
306
+ assert r.error.error_code == "c"
307
+
308
+ def test_auto_timestamp(self):
309
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
310
+
311
+ before = time.time()
312
+ r = ToolExecutionResult(tool_name="t")
313
+ after = time.time()
314
+ assert before <= r.timestamp <= after
315
+
316
+ def test_roundtrip(self):
317
+ from semantic_code_intelligence.tools.protocol import ToolExecutionResult
318
+
319
+ original = ToolExecutionResult(
320
+ tool_name="test", request_id="abc", success=True,
321
+ result_payload={"x": [1, 2]}, execution_time_ms=3.14,
322
+ )
323
+ restored = ToolExecutionResult.from_dict(original.to_dict())
324
+ assert restored.tool_name == original.tool_name
325
+ assert restored.success == original.success
326
+ assert restored.result_payload == original.result_payload
327
+
328
+
329
+ # =========================================================================
330
+ # ToolExecutor engine tests
331
+ # =========================================================================
332
+
333
+
334
+ class TestToolExecutor:
335
+ """Tests for the ToolExecutor engine."""
336
+
337
+ def test_creation(self, tmp_path):
338
+ from semantic_code_intelligence.tools.executor import ToolExecutor
339
+
340
+ executor = ToolExecutor(tmp_path)
341
+ assert executor is not None
342
+
343
+ def test_available_tools_includes_builtins(self, tmp_path):
344
+ from semantic_code_intelligence.tools.executor import ToolExecutor
345
+
346
+ executor = ToolExecutor(tmp_path)
347
+ names = executor.list_tool_names()
348
+ assert "semantic_search" in names
349
+ assert "explain_symbol" in names
350
+ assert "summarize_repo" in names
351
+ assert len(names) >= 8
352
+
353
+ def test_get_tool_schema(self, tmp_path):
354
+ from semantic_code_intelligence.tools.executor import ToolExecutor
355
+
356
+ executor = ToolExecutor(tmp_path)
357
+ schema = executor.get_tool_schema("semantic_search")
358
+ assert schema is not None
359
+ assert schema["name"] == "semantic_search"
360
+ assert "parameters" in schema
361
+
362
+ def test_get_tool_schema_unknown(self, tmp_path):
363
+ from semantic_code_intelligence.tools.executor import ToolExecutor
364
+
365
+ executor = ToolExecutor(tmp_path)
366
+ assert executor.get_tool_schema("nonexistent") is None
367
+
368
+ def test_execute_unknown_tool(self, tmp_path):
369
+ from semantic_code_intelligence.tools.executor import ToolExecutor
370
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
371
+
372
+ executor = ToolExecutor(tmp_path)
373
+ inv = ToolInvocation(tool_name="nonexistent_tool", arguments={})
374
+ result = executor.execute(inv)
375
+ assert result.success is False
376
+ assert result.error is not None
377
+ assert result.error.error_code == "unknown_tool"
378
+
379
+ def test_execute_missing_required_arg(self, tmp_path):
380
+ from semantic_code_intelligence.tools.executor import ToolExecutor
381
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
382
+
383
+ executor = ToolExecutor(tmp_path)
384
+ inv = ToolInvocation(tool_name="semantic_search", arguments={})
385
+ result = executor.execute(inv)
386
+ assert result.success is False
387
+ assert result.error is not None
388
+ assert result.error.error_code == "missing_required_arg"
389
+
390
+ def test_execute_summarize_repo(self, tmp_path):
391
+ from semantic_code_intelligence.tools.executor import ToolExecutor
392
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
393
+
394
+ _write_sample_project(tmp_path)
395
+ executor = ToolExecutor(tmp_path)
396
+ inv = ToolInvocation(tool_name="summarize_repo", arguments={})
397
+ result = executor.execute(inv)
398
+ assert result.success is True
399
+ assert result.execution_time_ms >= 0
400
+ assert result.tool_name == "summarize_repo"
401
+
402
+ def test_execute_has_timing(self, tmp_path):
403
+ from semantic_code_intelligence.tools.executor import ToolExecutor
404
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
405
+
406
+ executor = ToolExecutor(tmp_path)
407
+ inv = ToolInvocation(tool_name="summarize_repo", arguments={})
408
+ result = executor.execute(inv)
409
+ assert result.execution_time_ms >= 0
410
+
411
+ def test_execute_preserves_request_id(self, tmp_path):
412
+ from semantic_code_intelligence.tools.executor import ToolExecutor
413
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
414
+
415
+ executor = ToolExecutor(tmp_path)
416
+ inv = ToolInvocation(tool_name="summarize_repo", arguments={}, request_id="my-id-123")
417
+ result = executor.execute(inv)
418
+ assert result.request_id == "my-id-123"
419
+
420
+ def test_execute_batch(self, tmp_path):
421
+ from semantic_code_intelligence.tools.executor import ToolExecutor
422
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
423
+
424
+ executor = ToolExecutor(tmp_path)
425
+ invocations = [
426
+ ToolInvocation(tool_name="summarize_repo", arguments={}),
427
+ ToolInvocation(tool_name="nonexistent", arguments={}),
428
+ ]
429
+ results = executor.execute_batch(invocations)
430
+ assert len(results) == 2
431
+ assert results[0].success is True
432
+ assert results[1].success is False
433
+
434
+ def test_registry_access(self, tmp_path):
435
+ from semantic_code_intelligence.tools.executor import ToolExecutor
436
+
437
+ executor = ToolExecutor(tmp_path)
438
+ assert executor.registry is not None
439
+
440
+
441
+ # =========================================================================
442
+ # Plugin tool registration tests
443
+ # =========================================================================
444
+
445
+
446
+ class TestPluginToolRegistration:
447
+ """Tests for plugin-registered tools in the ToolExecutor."""
448
+
449
+ def test_register_plugin_tool(self, tmp_path):
450
+ from semantic_code_intelligence.tools.executor import ToolExecutor
451
+
452
+ executor = ToolExecutor(tmp_path)
453
+ executor.register_plugin_tool(
454
+ name="my_custom_tool",
455
+ description="A test plugin tool",
456
+ parameters={"input": {"type": "string", "required": True}},
457
+ handler=lambda input: {"echo": input},
458
+ )
459
+ assert "my_custom_tool" in executor.list_tool_names()
460
+
461
+ def test_plugin_tool_schema(self, tmp_path):
462
+ from semantic_code_intelligence.tools.executor import ToolExecutor
463
+
464
+ executor = ToolExecutor(tmp_path)
465
+ executor.register_plugin_tool(
466
+ name="plugin_tool",
467
+ description="Plugin tool",
468
+ parameters={},
469
+ handler=lambda: {"ok": True},
470
+ )
471
+ schema = executor.get_tool_schema("plugin_tool")
472
+ assert schema is not None
473
+ assert schema["source"] == "plugin"
474
+
475
+ def test_execute_plugin_tool(self, tmp_path):
476
+ from semantic_code_intelligence.tools.executor import ToolExecutor
477
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
478
+
479
+ executor = ToolExecutor(tmp_path)
480
+ executor.register_plugin_tool(
481
+ name="echo_tool",
482
+ description="Echoes input",
483
+ parameters={"msg": {"type": "string", "required": True}},
484
+ handler=lambda msg: {"echoed": msg},
485
+ )
486
+ inv = ToolInvocation(tool_name="echo_tool", arguments={"msg": "hello"})
487
+ result = executor.execute(inv)
488
+ assert result.success is True
489
+ assert result.result_payload["echoed"] == "hello"
490
+
491
+ def test_plugin_tool_cannot_override_builtin(self, tmp_path):
492
+ from semantic_code_intelligence.tools.executor import ToolExecutor
493
+
494
+ executor = ToolExecutor(tmp_path)
495
+ with pytest.raises(ValueError, match="collides with built-in"):
496
+ executor.register_plugin_tool(
497
+ name="semantic_search",
498
+ description="override",
499
+ parameters={},
500
+ handler=lambda: {},
501
+ )
502
+
503
+ def test_unregister_plugin_tool(self, tmp_path):
504
+ from semantic_code_intelligence.tools.executor import ToolExecutor
505
+
506
+ executor = ToolExecutor(tmp_path)
507
+ executor.register_plugin_tool(
508
+ name="temp_tool",
509
+ description="Temporary",
510
+ parameters={},
511
+ handler=lambda: {},
512
+ )
513
+ assert "temp_tool" in executor.list_tool_names()
514
+ assert executor.unregister_plugin_tool("temp_tool") is True
515
+ assert "temp_tool" not in executor.list_tool_names()
516
+
517
+ def test_unregister_nonexistent(self, tmp_path):
518
+ from semantic_code_intelligence.tools.executor import ToolExecutor
519
+
520
+ executor = ToolExecutor(tmp_path)
521
+ assert executor.unregister_plugin_tool("nonexistent") is False
522
+
523
+ def test_plugin_tool_error_handling(self, tmp_path):
524
+ from semantic_code_intelligence.tools.executor import ToolExecutor
525
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
526
+
527
+ def bad_handler(**kwargs):
528
+ raise RuntimeError("Plugin tool failure")
529
+
530
+ executor = ToolExecutor(tmp_path)
531
+ executor.register_plugin_tool(
532
+ name="bad_tool",
533
+ description="Fails",
534
+ parameters={},
535
+ handler=bad_handler,
536
+ )
537
+ inv = ToolInvocation(tool_name="bad_tool", arguments={})
538
+ result = executor.execute(inv)
539
+ assert result.success is False
540
+ assert result.error is not None
541
+ assert "Plugin tool failure" in result.error.error_message
542
+
543
+
544
+ # =========================================================================
545
+ # Bridge protocol extension tests
546
+ # =========================================================================
547
+
548
+
549
+ class TestBridgeProtocolExtensions:
550
+ """Tests for Phase 19 bridge protocol additions."""
551
+
552
+ def test_request_kind_invoke_tool(self):
553
+ from semantic_code_intelligence.bridge.protocol import RequestKind
554
+
555
+ assert RequestKind.INVOKE_TOOL == "invoke_tool"
556
+
557
+ def test_request_kind_list_tools(self):
558
+ from semantic_code_intelligence.bridge.protocol import RequestKind
559
+
560
+ assert RequestKind.LIST_TOOLS == "list_tools"
561
+
562
+ def test_capabilities_include_new_kinds(self):
563
+ from semantic_code_intelligence.bridge.protocol import BridgeCapabilities
564
+
565
+ caps = BridgeCapabilities()
566
+ assert "invoke_tool" in caps.supported_requests
567
+ assert "list_tools" in caps.supported_requests
568
+
569
+ def test_capabilities_tools_field(self):
570
+ from semantic_code_intelligence.bridge.protocol import BridgeCapabilities
571
+
572
+ caps = BridgeCapabilities(tools=[{"name": "test_tool"}])
573
+ d = caps.to_dict()
574
+ assert "tools" in d
575
+ assert d["tools"] == [{"name": "test_tool"}]
576
+
577
+ def test_capabilities_no_tools_field_when_empty(self):
578
+ from semantic_code_intelligence.bridge.protocol import BridgeCapabilities
579
+
580
+ caps = BridgeCapabilities()
581
+ d = caps.to_dict()
582
+ assert "tools" not in d
583
+
584
+ def test_request_kind_count(self):
585
+ from semantic_code_intelligence.bridge.protocol import RequestKind
586
+
587
+ # 10 original + 2 new (INVOKE_TOOL, LIST_TOOLS) = 12
588
+ assert len(RequestKind) == 12
589
+
590
+
591
+ # =========================================================================
592
+ # Bridge server tool endpoint tests
593
+ # =========================================================================
594
+
595
+
596
+ class TestBridgeServerToolEndpoints:
597
+ """Tests for the bridge server's tool-related endpoints."""
598
+
599
+ def test_dispatch_list_tools(self, tmp_path):
600
+ from semantic_code_intelligence.bridge.server import BridgeServer
601
+ from semantic_code_intelligence.bridge.protocol import AgentRequest
602
+
603
+ server = BridgeServer(tmp_path)
604
+ req = AgentRequest(kind="list_tools", params={}, request_id="t1")
605
+ resp = server.dispatch(req)
606
+ assert resp.success is True
607
+ assert "tools" in resp.data
608
+ assert resp.data["count"] >= 8
609
+
610
+ def test_dispatch_invoke_tool(self, tmp_path):
611
+ from semantic_code_intelligence.bridge.server import BridgeServer
612
+ from semantic_code_intelligence.bridge.protocol import AgentRequest
613
+
614
+ server = BridgeServer(tmp_path)
615
+ req = AgentRequest(
616
+ kind="invoke_tool",
617
+ params={"tool_name": "summarize_repo", "arguments": {}},
618
+ request_id="t2",
619
+ )
620
+ resp = server.dispatch(req)
621
+ assert resp.success is True
622
+
623
+ def test_dispatch_invoke_unknown_tool(self, tmp_path):
624
+ from semantic_code_intelligence.bridge.server import BridgeServer
625
+ from semantic_code_intelligence.bridge.protocol import AgentRequest
626
+
627
+ server = BridgeServer(tmp_path)
628
+ req = AgentRequest(
629
+ kind="invoke_tool",
630
+ params={"tool_name": "fake_tool", "arguments": {}},
631
+ request_id="t3",
632
+ )
633
+ resp = server.dispatch(req)
634
+ assert resp.success is False
635
+
636
+ def test_server_has_executor(self, tmp_path):
637
+ from semantic_code_intelligence.bridge.server import BridgeServer
638
+
639
+ server = BridgeServer(tmp_path)
640
+ assert server._executor is not None
641
+
642
+ def test_capabilities_include_tools(self, tmp_path):
643
+ from semantic_code_intelligence.bridge.server import BridgeServer
644
+
645
+ server = BridgeServer(tmp_path)
646
+ caps = server._capabilities
647
+ assert len(caps.tools) >= 8
648
+
649
+
650
+ # =========================================================================
651
+ # Plugin hooks tests
652
+ # =========================================================================
653
+
654
+
655
+ class TestPluginHooksPhase19:
656
+ """Tests for Phase 19 plugin hooks."""
657
+
658
+ def test_register_tool_hook_exists(self):
659
+ from semantic_code_intelligence.plugins import PluginHook
660
+
661
+ assert hasattr(PluginHook, "REGISTER_TOOL")
662
+ assert PluginHook.REGISTER_TOOL == "register_tool"
663
+
664
+ def test_pre_tool_invoke_hook_exists(self):
665
+ from semantic_code_intelligence.plugins import PluginHook
666
+
667
+ assert hasattr(PluginHook, "PRE_TOOL_INVOKE")
668
+ assert PluginHook.PRE_TOOL_INVOKE == "pre_tool_invoke"
669
+
670
+ def test_post_tool_invoke_hook_exists(self):
671
+ from semantic_code_intelligence.plugins import PluginHook
672
+
673
+ assert hasattr(PluginHook, "POST_TOOL_INVOKE")
674
+ assert PluginHook.POST_TOOL_INVOKE == "post_tool_invoke"
675
+
676
+ def test_plugin_hook_count(self):
677
+ from semantic_code_intelligence.plugins import PluginHook
678
+
679
+ # 19 original + 3 new (REGISTER_TOOL, PRE_TOOL_INVOKE, POST_TOOL_INVOKE) = 22
680
+ assert len(PluginHook) == 22
681
+
682
+
683
+ # =========================================================================
684
+ # CLI tool command tests
685
+ # =========================================================================
686
+
687
+
688
+ class TestCLIToolCommand:
689
+ """Tests for the `codexa tool` CLI command group."""
690
+
691
+ def test_tool_group_exists(self):
692
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
693
+
694
+ assert tool_cmd.name == "tool"
695
+
696
+ def test_tool_list_subcommand(self):
697
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
698
+
699
+ runner = CliRunner()
700
+ result = runner.invoke(tool_cmd, ["list", "--json"])
701
+ assert result.exit_code == 0
702
+ data = json.loads(result.output)
703
+ assert "tools" in data
704
+ assert data["count"] >= 8
705
+
706
+ def test_tool_list_text_output(self):
707
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
708
+
709
+ runner = CliRunner()
710
+ result = runner.invoke(tool_cmd, ["list"])
711
+ assert result.exit_code == 0
712
+ assert "semantic_search" in result.output
713
+
714
+ def test_tool_run_subcommand(self, tmp_path):
715
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
716
+
717
+ runner = CliRunner()
718
+ result = runner.invoke(tool_cmd, [
719
+ "run", "summarize_repo",
720
+ "--path", str(tmp_path),
721
+ "--json",
722
+ ])
723
+ assert result.exit_code == 0
724
+ data = json.loads(result.output)
725
+ assert data["tool_name"] == "summarize_repo"
726
+
727
+ def test_tool_run_with_args(self, tmp_path):
728
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
729
+
730
+ _write_sample_project(tmp_path)
731
+ runner = CliRunner()
732
+ result = runner.invoke(tool_cmd, [
733
+ "run", "semantic_search",
734
+ "--arg", "query=helper",
735
+ "--path", str(tmp_path),
736
+ "--json",
737
+ ])
738
+ assert result.exit_code == 0
739
+
740
+ def test_tool_run_unknown_tool(self, tmp_path):
741
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
742
+
743
+ runner = CliRunner()
744
+ result = runner.invoke(tool_cmd, [
745
+ "run", "nonexistent_tool",
746
+ "--path", str(tmp_path),
747
+ "--json",
748
+ ])
749
+ assert result.exit_code == 0 # exits cleanly with error in JSON
750
+ data = json.loads(result.output)
751
+ assert data["success"] is False
752
+
753
+ def test_tool_run_invalid_arg_format(self, tmp_path):
754
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
755
+
756
+ runner = CliRunner()
757
+ result = runner.invoke(tool_cmd, [
758
+ "run", "semantic_search",
759
+ "--arg", "no_equals_sign",
760
+ "--path", str(tmp_path),
761
+ ])
762
+ # Should show error about invalid format
763
+ assert result.exit_code != 0 or "Invalid argument" in result.output
764
+
765
+ def test_tool_schema_subcommand(self):
766
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
767
+
768
+ runner = CliRunner()
769
+ result = runner.invoke(tool_cmd, ["schema", "semantic_search", "--json"])
770
+ assert result.exit_code == 0
771
+ data = json.loads(result.output)
772
+ assert data["name"] == "semantic_search"
773
+
774
+ def test_tool_schema_text_output(self):
775
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
776
+
777
+ runner = CliRunner()
778
+ result = runner.invoke(tool_cmd, ["schema", "explain_symbol"])
779
+ assert result.exit_code == 0
780
+ assert "explain_symbol" in result.output
781
+
782
+ def test_tool_schema_unknown(self):
783
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
784
+
785
+ runner = CliRunner()
786
+ result = runner.invoke(tool_cmd, ["schema", "nonexistent"])
787
+ assert result.exit_code == 0
788
+ assert "Unknown tool" in result.output
789
+
790
+ def test_tool_run_pipe_mode(self, tmp_path):
791
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
792
+
793
+ runner = CliRunner()
794
+ result = runner.invoke(tool_cmd, [
795
+ "run", "summarize_repo",
796
+ "--path", str(tmp_path),
797
+ "--pipe",
798
+ ])
799
+ assert result.exit_code == 0
800
+ data = json.loads(result.output)
801
+ assert "tool_name" in data
802
+
803
+
804
+ # =========================================================================
805
+ # Router registration tests
806
+ # =========================================================================
807
+
808
+
809
+ class TestRouterPhase19:
810
+ """Tests for router command registration."""
811
+
812
+ def test_command_count_31(self):
813
+ from semantic_code_intelligence.cli.main import cli
814
+
815
+ assert len(cli.commands) == 39
816
+
817
+ def test_tool_command_registered(self):
818
+ from semantic_code_intelligence.cli.main import cli
819
+
820
+ assert "tool" in cli.commands
821
+
822
+
823
+ # =========================================================================
824
+ # Version tests
825
+ # =========================================================================
826
+
827
+
828
+ class TestVersionPhase19:
829
+ """Tests for version 0.19.0."""
830
+
831
+ def test_version_string(self):
832
+ from semantic_code_intelligence import __version__
833
+
834
+ assert __version__ == "0.4.0"
835
+
836
+ def test_pyproject_version(self):
837
+ import tomllib
838
+ pyproject = Path(__file__).resolve().parent.parent.parent / "pyproject.toml"
839
+ if pyproject.exists():
840
+ data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
841
+ assert data["project"]["version"] == "0.4.0"
842
+
843
+
844
+ # =========================================================================
845
+ # Documentation generation tests
846
+ # =========================================================================
847
+
848
+
849
+ class TestDocGenerationPhase19:
850
+ """Tests for AI_TOOL_PROTOCOL.md documentation generation."""
851
+
852
+ def test_generate_ai_tool_protocol_reference(self):
853
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
854
+
855
+ md = generate_ai_tool_protocol_reference()
856
+ assert "# AI Tool Protocol Reference" in md
857
+ assert "ToolInvocation" in md
858
+ assert "ToolExecutionResult" in md
859
+ assert "ToolError" in md
860
+
861
+ def test_protocol_reference_lists_tools(self):
862
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
863
+
864
+ md = generate_ai_tool_protocol_reference()
865
+ assert "semantic_search" in md
866
+ assert "explain_symbol" in md
867
+
868
+ def test_protocol_reference_lists_endpoints(self):
869
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
870
+
871
+ md = generate_ai_tool_protocol_reference()
872
+ assert "/tools/invoke" in md
873
+ assert "/tools/list" in md
874
+ assert "/tools/stream" in md
875
+
876
+ def test_protocol_reference_lists_error_codes(self):
877
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
878
+
879
+ md = generate_ai_tool_protocol_reference()
880
+ assert "unknown_tool" in md
881
+ assert "missing_required_arg" in md
882
+
883
+ def test_protocol_reference_cli_usage(self):
884
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
885
+
886
+ md = generate_ai_tool_protocol_reference()
887
+ assert "codexa tool list" in md
888
+ assert "codexa tool run" in md
889
+
890
+ def test_generate_all_docs_includes_protocol(self, tmp_path):
891
+ from semantic_code_intelligence.docs import generate_all_docs
892
+
893
+ generated = generate_all_docs(tmp_path)
894
+ assert "AI_TOOL_PROTOCOL.md" in generated
895
+ content = (tmp_path / "AI_TOOL_PROTOCOL.md").read_text(encoding="utf-8")
896
+ assert "AI Tool Protocol" in content
897
+
898
+ def test_docs_count(self, tmp_path):
899
+ from semantic_code_intelligence.docs import generate_all_docs
900
+
901
+ generated = generate_all_docs(tmp_path)
902
+ # Should be 10 (9 previous + AI_TOOL_PROTOCOL.md)
903
+ assert len(generated) >= 10
904
+
905
+
906
+ # =========================================================================
907
+ # Safety guardrails tests
908
+ # =========================================================================
909
+
910
+
911
+ class TestSafetyGuardrails:
912
+ """Tests for AI safety guardrails in the tooling protocol."""
913
+
914
+ def test_tools_are_readonly(self):
915
+ """Verify no tool definition enables code execution."""
916
+ from semantic_code_intelligence.tools import TOOL_DEFINITIONS
917
+
918
+ for tool in TOOL_DEFINITIONS:
919
+ name = tool["name"]
920
+ assert "exec" not in name.lower(), f"Tool {name} may execute code"
921
+ assert "run" not in name.lower() or name == "summarize_repo", f"Tool {name} may run code"
922
+
923
+ def test_unknown_tool_rejected(self, tmp_path):
924
+ from semantic_code_intelligence.tools.executor import ToolExecutor
925
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
926
+
927
+ executor = ToolExecutor(tmp_path)
928
+ inv = ToolInvocation(tool_name="__import__", arguments={})
929
+ result = executor.execute(inv)
930
+ assert result.success is False
931
+
932
+ def test_plugin_cannot_override_builtin(self, tmp_path):
933
+ from semantic_code_intelligence.tools.executor import ToolExecutor
934
+
935
+ executor = ToolExecutor(tmp_path)
936
+ with pytest.raises(ValueError):
937
+ executor.register_plugin_tool(
938
+ name="get_context",
939
+ description="override",
940
+ parameters={},
941
+ handler=lambda: {},
942
+ )
943
+
944
+ def test_argument_validation_enforced(self, tmp_path):
945
+ from semantic_code_intelligence.tools.executor import ToolExecutor
946
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
947
+
948
+ executor = ToolExecutor(tmp_path)
949
+ # find_references requires symbol_name
950
+ inv = ToolInvocation(tool_name="find_references", arguments={})
951
+ result = executor.execute(inv)
952
+ assert result.success is False
953
+ assert result.error.error_code == "missing_required_arg"
954
+
955
+
956
+ # =========================================================================
957
+ # Module structure tests
958
+ # =========================================================================
959
+
960
+
961
+ class TestModuleStructure:
962
+ """Tests for Phase 19 module structure."""
963
+
964
+ def test_tools_protocol_importable(self):
965
+ from semantic_code_intelligence.tools.protocol import (
966
+ ToolError,
967
+ ToolErrorCode,
968
+ ToolExecutionResult,
969
+ ToolInvocation,
970
+ )
971
+ assert ToolInvocation is not None
972
+ assert ToolExecutionResult is not None
973
+ assert ToolError is not None
974
+ assert ToolErrorCode is not None
975
+
976
+ def test_tools_executor_importable(self):
977
+ from semantic_code_intelligence.tools.executor import ToolExecutor
978
+ assert ToolExecutor is not None
979
+
980
+ def test_tool_cmd_importable(self):
981
+ from semantic_code_intelligence.cli.commands.tool_cmd import tool_cmd
982
+ assert tool_cmd is not None
983
+
984
+ def test_docs_function_importable(self):
985
+ from semantic_code_intelligence.docs import generate_ai_tool_protocol_reference
986
+ assert callable(generate_ai_tool_protocol_reference)