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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- 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()
|