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,663 @@
1
+ """Tests for Phase 13 — Open Source Readiness & Developer Experience.
2
+
3
+ Covers: auto-documentation generator, CLI commands (docs, doctor, plugin),
4
+ pipeline mode, plugin templates, OSS files, and pyproject.toml metadata.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import textwrap
11
+ from pathlib import Path
12
+ from typing import Any
13
+ from unittest.mock import MagicMock, patch
14
+
15
+ import click
16
+ import pytest
17
+ from click.testing import CliRunner
18
+
19
+ # =========================================================================
20
+ # Documentation Generator Tests
21
+ # =========================================================================
22
+
23
+ from semantic_code_intelligence.docs import (
24
+ generate_bridge_reference,
25
+ generate_cli_reference,
26
+ generate_plugin_reference,
27
+ generate_tool_reference,
28
+ generate_all_docs,
29
+ )
30
+
31
+
32
+ class TestCLIReferenceGenerator:
33
+ """Tests for CLI documentation generation."""
34
+
35
+ def _make_group(self) -> click.Group:
36
+ @click.group()
37
+ def grp():
38
+ """Test group."""
39
+
40
+ @grp.command("search")
41
+ @click.argument("query")
42
+ @click.option("--top-k", "-k", default=10, type=int, help="Number of results.")
43
+ @click.option("--json", "json_mode", is_flag=True, help="JSON output.")
44
+ def search_cmd(query, top_k, json_mode):
45
+ """Search the codebase."""
46
+
47
+ @grp.command("init")
48
+ @click.option("--path", "-p", default=".", help="Project root.")
49
+ def init_cmd(path):
50
+ """Initialize project."""
51
+
52
+ return grp
53
+
54
+ def test_generates_markdown(self):
55
+ md = generate_cli_reference(self._make_group())
56
+ assert "# CLI Reference" in md
57
+
58
+ def test_includes_commands(self):
59
+ md = generate_cli_reference(self._make_group())
60
+ assert "## `codexa init`" in md
61
+ assert "## `codexa search`" in md
62
+
63
+ def test_includes_arguments(self):
64
+ md = generate_cli_reference(self._make_group())
65
+ assert "**Arguments:**" in md
66
+ assert "`query`" in md
67
+
68
+ def test_includes_options_table(self):
69
+ md = generate_cli_reference(self._make_group())
70
+ assert "**Options:**" in md
71
+ assert "| Flag |" in md
72
+ assert "`--top-k, -k`" in md
73
+
74
+ def test_includes_help_text(self):
75
+ md = generate_cli_reference(self._make_group())
76
+ assert "Search the codebase." in md
77
+ assert "Initialize project." in md
78
+
79
+ def test_nested_group(self):
80
+ @click.group()
81
+ def root():
82
+ pass
83
+
84
+ @root.group("workspace")
85
+ def ws():
86
+ """Workspace management."""
87
+
88
+ @ws.command("add")
89
+ @click.argument("name")
90
+ def ws_add(name):
91
+ """Add a repo."""
92
+
93
+ md = generate_cli_reference(root)
94
+ assert "## `codexa workspace`" in md
95
+ assert "## `codexa workspace add`" in md
96
+
97
+
98
+ class TestPluginReferenceGenerator:
99
+ """Tests for plugin documentation generation."""
100
+
101
+ def test_generates_markdown(self):
102
+ md = generate_plugin_reference()
103
+ assert "# Plugin SDK Reference" in md
104
+
105
+ def test_includes_all_hooks(self):
106
+ from semantic_code_intelligence.plugins import PluginHook
107
+ md = generate_plugin_reference()
108
+ for hook in PluginHook:
109
+ assert hook.name in md
110
+
111
+ def test_includes_hook_count(self):
112
+ from semantic_code_intelligence.plugins import PluginHook
113
+ md = generate_plugin_reference()
114
+ assert f"**{len(PluginHook)}**" in md
115
+
116
+ def test_includes_categories(self):
117
+ md = generate_plugin_reference()
118
+ assert "Indexing" in md
119
+ assert "Search" in md
120
+ assert "Streaming" in md
121
+ assert "Validation" in md
122
+
123
+ def test_includes_base_class_docs(self):
124
+ md = generate_plugin_reference()
125
+ assert "PluginBase" in md
126
+ assert "PluginMetadata" in md
127
+ assert "create_plugin()" in md
128
+
129
+ def test_includes_lifecycle(self):
130
+ md = generate_plugin_reference()
131
+ assert "Register" in md
132
+ assert "Activate" in md
133
+ assert "Dispatch" in md
134
+ assert "Deactivate" in md
135
+
136
+
137
+ class TestBridgeReferenceGenerator:
138
+ """Tests for bridge protocol documentation generation."""
139
+
140
+ def test_generates_markdown(self):
141
+ md = generate_bridge_reference()
142
+ assert "# Bridge Protocol Reference" in md
143
+
144
+ def test_includes_endpoints(self):
145
+ md = generate_bridge_reference()
146
+ assert "/health" in md
147
+ assert "/request" in md
148
+ assert "GET" in md
149
+ assert "POST" in md
150
+
151
+ def test_includes_request_kinds(self):
152
+ from semantic_code_intelligence.bridge.protocol import RequestKind
153
+ md = generate_bridge_reference()
154
+ for kind in RequestKind:
155
+ assert kind.name in md
156
+
157
+ def test_includes_request_response_schemas(self):
158
+ md = generate_bridge_reference()
159
+ assert "AgentRequest" in md
160
+ assert "AgentResponse" in md
161
+ assert "request_id" in md
162
+ assert "elapsed_ms" in md
163
+
164
+ def test_includes_example_json(self):
165
+ md = generate_bridge_reference()
166
+ assert '"kind"' in md
167
+ assert '"semantic_search"' in md
168
+
169
+
170
+ class TestToolReferenceGenerator:
171
+ """Tests for tool registry documentation generation."""
172
+
173
+ def test_generates_markdown(self):
174
+ md = generate_tool_reference()
175
+ assert "# Tool Registry Reference" in md
176
+
177
+ def test_includes_tools(self):
178
+ md = generate_tool_reference()
179
+ assert "semantic_search" in md
180
+ assert "explain_symbol" in md
181
+ assert "summarize_repo" in md
182
+
183
+ def test_includes_tool_count(self):
184
+ from semantic_code_intelligence.tools import TOOL_DEFINITIONS
185
+ md = generate_tool_reference()
186
+ assert f"**{len(TOOL_DEFINITIONS)} tools" in md
187
+
188
+ def test_includes_usage_example(self):
189
+ md = generate_tool_reference()
190
+ assert "ToolRegistry" in md
191
+ assert "to_json()" in md
192
+
193
+
194
+ class TestGenerateAllDocs:
195
+ """Tests for the combined documentation generator."""
196
+
197
+ def test_creates_output_directory(self, tmp_path):
198
+ out = tmp_path / "docs"
199
+ generated = generate_all_docs(out)
200
+ assert out.is_dir()
201
+ assert len(generated) >= 3
202
+
203
+ def test_generates_plugin_md(self, tmp_path):
204
+ out = tmp_path / "docs"
205
+ generated = generate_all_docs(out)
206
+ assert "PLUGINS.md" in generated
207
+ assert (out / "PLUGINS.md").is_file()
208
+
209
+ def test_generates_bridge_md(self, tmp_path):
210
+ out = tmp_path / "docs"
211
+ generated = generate_all_docs(out)
212
+ assert "BRIDGE.md" in generated
213
+ content = (out / "BRIDGE.md").read_text()
214
+ assert "Bridge Protocol" in content
215
+
216
+ def test_generates_tools_md(self, tmp_path):
217
+ out = tmp_path / "docs"
218
+ generated = generate_all_docs(out)
219
+ assert "TOOLS.md" in generated
220
+
221
+ def test_generates_cli_md(self, tmp_path):
222
+ out = tmp_path / "docs"
223
+ generated = generate_all_docs(out)
224
+ assert "CLI.md" in generated
225
+ content = (out / "CLI.md").read_text()
226
+ assert "CLI Reference" in content
227
+
228
+
229
+ # =========================================================================
230
+ # Doctor Command Tests
231
+ # =========================================================================
232
+
233
+ from semantic_code_intelligence.cli.commands.doctor_cmd import run_checks, doctor_cmd
234
+
235
+
236
+ class TestDoctorChecks:
237
+ """Tests for the doctor health check system."""
238
+
239
+ def test_returns_list(self, tmp_path):
240
+ checks = run_checks(tmp_path)
241
+ assert isinstance(checks, list)
242
+ assert len(checks) > 0
243
+
244
+ def test_python_check(self, tmp_path):
245
+ checks = run_checks(tmp_path)
246
+ py_check = next(c for c in checks if c["name"] == "Python")
247
+ assert py_check["ok"] is True
248
+ assert "3." in py_check["version"]
249
+
250
+ def test_codex_version_check(self, tmp_path):
251
+ from semantic_code_intelligence import __version__
252
+ checks = run_checks(tmp_path)
253
+ codex_check = next(c for c in checks if c["name"] == "CodexA")
254
+ assert codex_check["ok"] is True
255
+ assert codex_check["version"] == __version__
256
+
257
+ def test_click_check(self, tmp_path):
258
+ checks = run_checks(tmp_path)
259
+ click_check = next(c for c in checks if c["name"] == "click")
260
+ assert click_check["ok"] is True
261
+
262
+ def test_project_not_initialized(self, tmp_path):
263
+ checks = run_checks(tmp_path)
264
+ proj = next(c for c in checks if c["name"] == "Project")
265
+ assert proj["ok"] is False
266
+ assert "Not initialized" in proj["detail"]
267
+
268
+ def test_project_initialized(self, tmp_path):
269
+ (tmp_path / ".codexa").mkdir()
270
+ checks = run_checks(tmp_path)
271
+ proj = next(c for c in checks if c["name"] == "Project")
272
+ assert proj["ok"] is True
273
+
274
+ def test_project_indexed(self, tmp_path):
275
+ idx = tmp_path / ".codexa" / "index"
276
+ idx.mkdir(parents=True)
277
+ (idx / "vectors.faiss").write_text("dummy")
278
+ checks = run_checks(tmp_path)
279
+ proj = next(c for c in checks if c["name"] == "Project")
280
+ assert "indexed" in proj["detail"]
281
+
282
+
283
+ class TestDoctorCLI:
284
+ """Tests for the doctor CLI command."""
285
+
286
+ def test_runs_successfully(self):
287
+ runner = CliRunner()
288
+ result = runner.invoke(doctor_cmd, ["--path", "."])
289
+ assert result.exit_code == 0
290
+
291
+ def test_json_output(self):
292
+ runner = CliRunner()
293
+ result = runner.invoke(doctor_cmd, ["--json", "--path", "."])
294
+ assert result.exit_code == 0
295
+ data = json.loads(result.output)
296
+ assert "checks" in data
297
+ assert len(data["checks"]) > 0
298
+
299
+ def test_json_has_check_fields(self):
300
+ runner = CliRunner()
301
+ result = runner.invoke(doctor_cmd, ["--json", "--path", "."])
302
+ data = json.loads(result.output)
303
+ for check in data["checks"]:
304
+ assert "name" in check
305
+ assert "ok" in check
306
+ assert "detail" in check
307
+
308
+
309
+ # =========================================================================
310
+ # Docs Command Tests
311
+ # =========================================================================
312
+
313
+ from semantic_code_intelligence.cli.commands.docs_cmd import docs_cmd
314
+
315
+
316
+ class TestDocsCLI:
317
+ """Tests for the docs CLI command."""
318
+
319
+ def test_generates_all_docs(self, tmp_path):
320
+ runner = CliRunner()
321
+ out = str(tmp_path / "docs")
322
+ result = runner.invoke(docs_cmd, ["--output", out])
323
+ assert result.exit_code == 0
324
+
325
+ def test_generates_specific_section(self, tmp_path):
326
+ runner = CliRunner()
327
+ out = str(tmp_path / "docs")
328
+ result = runner.invoke(docs_cmd, ["--output", out, "--section", "plugins"])
329
+ assert result.exit_code == 0
330
+ assert (tmp_path / "docs" / "PLUGINS.md").is_file()
331
+
332
+ def test_json_output(self, tmp_path):
333
+ runner = CliRunner()
334
+ out = str(tmp_path / "docs")
335
+ result = runner.invoke(docs_cmd, ["--output", out, "--json"])
336
+ assert result.exit_code == 0
337
+ data = json.loads(result.output)
338
+ assert "output_dir" in data
339
+ assert "files" in data
340
+ assert len(data["files"]) > 0
341
+
342
+ def test_section_cli(self, tmp_path):
343
+ runner = CliRunner()
344
+ out = str(tmp_path / "docs")
345
+ result = runner.invoke(docs_cmd, ["--output", out, "--section", "cli"])
346
+ assert result.exit_code == 0
347
+ assert (tmp_path / "docs" / "CLI.md").is_file()
348
+
349
+ def test_section_bridge(self, tmp_path):
350
+ runner = CliRunner()
351
+ out = str(tmp_path / "docs")
352
+ result = runner.invoke(docs_cmd, ["--output", out, "--section", "bridge"])
353
+ assert result.exit_code == 0
354
+ assert (tmp_path / "docs" / "BRIDGE.md").is_file()
355
+
356
+ def test_section_tools(self, tmp_path):
357
+ runner = CliRunner()
358
+ out = str(tmp_path / "docs")
359
+ result = runner.invoke(docs_cmd, ["--output", out, "--section", "tools"])
360
+ assert result.exit_code == 0
361
+ assert (tmp_path / "docs" / "TOOLS.md").is_file()
362
+
363
+
364
+ # =========================================================================
365
+ # Plugin Command Tests
366
+ # =========================================================================
367
+
368
+ from semantic_code_intelligence.cli.commands.plugin_cmd import plugin_cmd
369
+
370
+
371
+ class TestPluginNewCLI:
372
+ """Tests for the plugin scaffold command."""
373
+
374
+ def test_creates_plugin_file(self, tmp_path):
375
+ runner = CliRunner()
376
+ result = runner.invoke(plugin_cmd, ["new", "my-test", "--output", str(tmp_path)])
377
+ assert result.exit_code == 0
378
+ assert (tmp_path / "my_test.py").is_file()
379
+
380
+ def test_plugin_file_content(self, tmp_path):
381
+ runner = CliRunner()
382
+ runner.invoke(plugin_cmd, ["new", "my-test", "--output", str(tmp_path)])
383
+ content = (tmp_path / "my_test.py").read_text()
384
+ assert "class MyTestPlugin(PluginBase)" in content
385
+ assert "def create_plugin()" in content
386
+ assert "def metadata(self)" in content
387
+
388
+ def test_custom_hooks(self, tmp_path):
389
+ runner = CliRunner()
390
+ runner.invoke(plugin_cmd, [
391
+ "new", "validator",
392
+ "--hooks", "CUSTOM_VALIDATION,POST_AI",
393
+ "--output", str(tmp_path),
394
+ ])
395
+ content = (tmp_path / "validator.py").read_text()
396
+ assert "PluginHook.CUSTOM_VALIDATION" in content
397
+ assert "PluginHook.POST_AI" in content
398
+
399
+ def test_custom_description(self, tmp_path):
400
+ runner = CliRunner()
401
+ runner.invoke(plugin_cmd, [
402
+ "new", "fmt",
403
+ "--description", "Formats code output",
404
+ "--output", str(tmp_path),
405
+ ])
406
+ content = (tmp_path / "fmt.py").read_text()
407
+ assert "Formats code output" in content
408
+
409
+ def test_custom_author(self, tmp_path):
410
+ runner = CliRunner()
411
+ runner.invoke(plugin_cmd, [
412
+ "new", "fmt",
413
+ "--author", "Test Author",
414
+ "--output", str(tmp_path),
415
+ ])
416
+ content = (tmp_path / "fmt.py").read_text()
417
+ assert "Test Author" in content
418
+
419
+ def test_rejects_invalid_hooks(self, tmp_path):
420
+ runner = CliRunner()
421
+ result = runner.invoke(plugin_cmd, [
422
+ "new", "bad",
423
+ "--hooks", "NONEXISTENT_HOOK",
424
+ "--output", str(tmp_path),
425
+ ])
426
+ assert "Unknown hook" in result.output
427
+
428
+ def test_rejects_existing_file(self, tmp_path):
429
+ # Create file first
430
+ (tmp_path / "dup.py").write_text("existing")
431
+ runner = CliRunner()
432
+ result = runner.invoke(plugin_cmd, ["new", "dup", "--output", str(tmp_path)])
433
+ assert "already exists" in result.output
434
+
435
+ def test_generated_plugin_is_importable(self, tmp_path):
436
+ """Generated plugin scaffolds should be valid Python."""
437
+ runner = CliRunner()
438
+ runner.invoke(plugin_cmd, ["new", "importable", "--output", str(tmp_path)])
439
+ filepath = tmp_path / "importable.py"
440
+ content = filepath.read_text()
441
+ # Compile to check syntax validity
442
+ compile(content, str(filepath), "exec")
443
+
444
+
445
+ class TestPluginListCLI:
446
+ """Tests for the plugin list command."""
447
+
448
+ def test_no_plugins(self, tmp_path):
449
+ runner = CliRunner()
450
+ result = runner.invoke(plugin_cmd, ["list", "--path", str(tmp_path)])
451
+ assert result.exit_code == 0
452
+ assert "No plugins found" in result.output
453
+
454
+ def test_json_no_plugins(self, tmp_path):
455
+ runner = CliRunner()
456
+ result = runner.invoke(plugin_cmd, ["list", "--path", str(tmp_path), "--json"])
457
+ assert result.exit_code == 0
458
+ data = json.loads(result.output)
459
+ assert data["count"] == 0
460
+ assert data["plugins"] == []
461
+
462
+
463
+ class TestPluginInfoCLI:
464
+ """Tests for the plugin info command."""
465
+
466
+ def test_unknown_plugin(self, tmp_path):
467
+ runner = CliRunner()
468
+ result = runner.invoke(plugin_cmd, ["info", "nonexistent", "--path", str(tmp_path)])
469
+ assert "not found" in result.output
470
+
471
+
472
+ # =========================================================================
473
+ # Pipeline Mode Tests
474
+ # =========================================================================
475
+
476
+ from semantic_code_intelligence.cli.main import cli
477
+
478
+
479
+ class TestPipelineMode:
480
+ """Tests for the --pipe global flag."""
481
+
482
+ def test_pipe_flag_exists(self):
483
+ runner = CliRunner()
484
+ result = runner.invoke(cli, ["--help"])
485
+ assert "--pipe" in result.output
486
+
487
+ def test_pipe_flag_sets_context(self):
488
+ runner = CliRunner()
489
+ result = runner.invoke(cli, ["--pipe", "--help"])
490
+ assert result.exit_code == 0
491
+
492
+ def test_version_option(self):
493
+ runner = CliRunner()
494
+ result = runner.invoke(cli, ["--version"])
495
+ assert "0.4.0" in result.output
496
+
497
+
498
+ # =========================================================================
499
+ # Router Tests
500
+ # =========================================================================
501
+
502
+ class TestRouterRegistration:
503
+ """Tests for new command registration in the router."""
504
+
505
+ def test_docs_command_registered(self):
506
+ runner = CliRunner()
507
+ result = runner.invoke(cli, ["docs", "--help"])
508
+ assert result.exit_code == 0
509
+ assert "documentation" in result.output.lower() or "docs" in result.output.lower()
510
+
511
+ def test_doctor_command_registered(self):
512
+ runner = CliRunner()
513
+ result = runner.invoke(cli, ["doctor", "--help"])
514
+ assert result.exit_code == 0
515
+ assert "health" in result.output.lower() or "environment" in result.output.lower()
516
+
517
+ def test_plugin_command_registered(self):
518
+ runner = CliRunner()
519
+ result = runner.invoke(cli, ["plugin", "--help"])
520
+ assert result.exit_code == 0
521
+ assert "new" in result.output
522
+ assert "list" in result.output
523
+
524
+ def test_total_command_count(self):
525
+ """Verify we now have 17 top-level commands."""
526
+ assert len(cli.commands) == 39
527
+
528
+
529
+ # =========================================================================
530
+ # Sample Plugin Tests
531
+ # =========================================================================
532
+
533
+ class TestSearchAnnotatorPlugin:
534
+ """Tests for the sample search annotator plugin."""
535
+
536
+ def test_plugin_imports(self):
537
+ from semantic_code_intelligence.plugins.examples.search_annotator import (
538
+ SearchAnnotatorPlugin,
539
+ create_plugin,
540
+ )
541
+ plugin = create_plugin()
542
+ assert plugin is not None
543
+
544
+ def test_metadata(self):
545
+ from semantic_code_intelligence.plugins.examples.search_annotator import create_plugin
546
+ plugin = create_plugin()
547
+ meta = plugin.metadata()
548
+ assert meta.name == "search-annotator"
549
+ assert meta.version == "0.1.0"
550
+
551
+ def test_post_search_hook(self):
552
+ from semantic_code_intelligence.plugins.examples.search_annotator import create_plugin
553
+ from semantic_code_intelligence.plugins import PluginHook
554
+ plugin = create_plugin()
555
+ plugin.activate({})
556
+ data = {"results": [{"file": "a.py"}, {"file": "b.py"}], "query": "test"}
557
+ result = plugin.on_hook(PluginHook.POST_SEARCH, data)
558
+ assert result["annotation_count"] == 2
559
+ assert result["results"][0]["annotated_by"] == "search-annotator"
560
+
561
+
562
+ class TestCodeQualityPlugin:
563
+ """Tests for the sample code quality plugin."""
564
+
565
+ def test_plugin_imports(self):
566
+ from semantic_code_intelligence.plugins.examples.code_quality import (
567
+ CodeQualityPlugin,
568
+ create_plugin,
569
+ )
570
+ plugin = create_plugin()
571
+ assert plugin is not None
572
+
573
+ def test_detects_todo(self):
574
+ from semantic_code_intelligence.plugins.examples.code_quality import create_plugin
575
+ from semantic_code_intelligence.plugins import PluginHook
576
+ plugin = create_plugin()
577
+ data = {"code": "x = 1 # TODO fix later", "issues": []}
578
+ result = plugin.on_hook(PluginHook.CUSTOM_VALIDATION, data)
579
+ assert len(result["issues"]) >= 1
580
+ assert any("TODO" in i["description"] for i in result["issues"])
581
+
582
+ def test_detects_print(self):
583
+ from semantic_code_intelligence.plugins.examples.code_quality import create_plugin
584
+ from semantic_code_intelligence.plugins import PluginHook
585
+ plugin = create_plugin()
586
+ data = {"code": 'print("hello world")', "issues": []}
587
+ result = plugin.on_hook(PluginHook.CUSTOM_VALIDATION, data)
588
+ assert any("logging" in i["description"] for i in result["issues"])
589
+
590
+ def test_clean_code(self):
591
+ from semantic_code_intelligence.plugins.examples.code_quality import create_plugin
592
+ from semantic_code_intelligence.plugins import PluginHook
593
+ plugin = create_plugin()
594
+ data = {"code": "x = 1\ny = 2\n", "issues": []}
595
+ result = plugin.on_hook(PluginHook.CUSTOM_VALIDATION, data)
596
+ assert len(result["issues"]) == 0
597
+
598
+
599
+ # =========================================================================
600
+ # OSS Files Tests
601
+ # =========================================================================
602
+
603
+ class TestOSSFiles:
604
+ """Tests that required open-source files exist and have content."""
605
+
606
+ def _project_root(self) -> Path:
607
+ return Path(__file__).resolve().parent.parent.parent
608
+
609
+ def test_license_exists(self):
610
+ assert (self._project_root() / "LICENSE").is_file()
611
+
612
+ def test_license_is_mit(self):
613
+ content = (self._project_root() / "LICENSE").read_text()
614
+ assert "MIT License" in content
615
+
616
+ def test_contributing_exists(self):
617
+ assert (self._project_root() / "CONTRIBUTING.md").is_file()
618
+
619
+ def test_contributing_has_content(self):
620
+ content = (self._project_root() / "CONTRIBUTING.md").read_text()
621
+ assert "Contributing" in content
622
+ assert "pytest" in content
623
+
624
+ def test_security_exists(self):
625
+ assert (self._project_root() / "SECURITY.md").is_file()
626
+
627
+ def test_ci_workflow_exists(self):
628
+ assert (self._project_root() / ".github" / "workflows" / "ci.yml").is_file()
629
+
630
+ def test_bug_report_template_exists(self):
631
+ assert (self._project_root() / ".github" / "ISSUE_TEMPLATE" / "bug_report.md").is_file()
632
+
633
+ def test_feature_request_template_exists(self):
634
+ assert (self._project_root() / ".github" / "ISSUE_TEMPLATE" / "feature_request.md").is_file()
635
+
636
+ def test_pr_template_exists(self):
637
+ assert (self._project_root() / ".github" / "PULL_REQUEST_TEMPLATE.md").is_file()
638
+
639
+
640
+ # =========================================================================
641
+ # Version and Metadata Tests
642
+ # =========================================================================
643
+
644
+ class TestProjectMetadata:
645
+ """Tests for project version and metadata consistency."""
646
+
647
+ def test_version_format(self):
648
+ from semantic_code_intelligence import __version__
649
+ parts = __version__.split(".")
650
+ assert len(parts) >= 2
651
+ assert all(p.isdigit() for p in parts)
652
+
653
+ def test_version_is_0_13(self):
654
+ from semantic_code_intelligence import __version__
655
+ assert __version__ == "0.4.0"
656
+
657
+ def test_app_name(self):
658
+ from semantic_code_intelligence import __app_name__
659
+ assert __app_name__ == "codexa"
660
+
661
+ def test_pyproject_exists(self):
662
+ root = Path(__file__).resolve().parent.parent.parent
663
+ assert (root / "pyproject.toml").is_file()