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,293 @@
|
|
|
1
|
+
"""Tests for the plugin architecture SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from semantic_code_intelligence.plugins import (
|
|
11
|
+
PluginBase,
|
|
12
|
+
PluginHook,
|
|
13
|
+
PluginManager,
|
|
14
|
+
PluginMetadata,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Test Plugin Implementation
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
class SamplePlugin(PluginBase):
|
|
23
|
+
"""A simple plugin for testing."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, name: str = "test-plugin") -> None:
|
|
26
|
+
self._name = name
|
|
27
|
+
self.activated = False
|
|
28
|
+
self.deactivated = False
|
|
29
|
+
self.hooks_received: list[tuple[PluginHook, dict]] = []
|
|
30
|
+
|
|
31
|
+
def metadata(self) -> PluginMetadata:
|
|
32
|
+
return PluginMetadata(
|
|
33
|
+
name=self._name,
|
|
34
|
+
version="1.0.0",
|
|
35
|
+
description="Test plugin",
|
|
36
|
+
hooks=[PluginHook.PRE_INDEX, PluginHook.POST_INDEX],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def activate(self, context: dict[str, Any]) -> None:
|
|
40
|
+
self.activated = True
|
|
41
|
+
|
|
42
|
+
def deactivate(self) -> None:
|
|
43
|
+
self.deactivated = True
|
|
44
|
+
|
|
45
|
+
def on_hook(self, hook: PluginHook, data: dict[str, Any]) -> dict[str, Any]:
|
|
46
|
+
self.hooks_received.append((hook, dict(data)))
|
|
47
|
+
data["processed_by"] = self._name
|
|
48
|
+
return data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FailingPlugin(PluginBase):
|
|
52
|
+
"""A plugin that raises during hook dispatch."""
|
|
53
|
+
|
|
54
|
+
def metadata(self) -> PluginMetadata:
|
|
55
|
+
return PluginMetadata(
|
|
56
|
+
name="failing-plugin",
|
|
57
|
+
hooks=[PluginHook.PRE_INDEX],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def on_hook(self, hook: PluginHook, data: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
+
raise RuntimeError("Plugin error!")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# PluginMetadata
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
class TestPluginMetadata:
|
|
69
|
+
def test_to_dict(self):
|
|
70
|
+
meta = PluginMetadata(
|
|
71
|
+
name="my-plugin",
|
|
72
|
+
version="2.0",
|
|
73
|
+
description="Test",
|
|
74
|
+
hooks=[PluginHook.PRE_SEARCH],
|
|
75
|
+
)
|
|
76
|
+
d = meta.to_dict()
|
|
77
|
+
assert d["name"] == "my-plugin"
|
|
78
|
+
assert d["version"] == "2.0"
|
|
79
|
+
assert "pre_search" in d["hooks"]
|
|
80
|
+
|
|
81
|
+
def test_defaults(self):
|
|
82
|
+
meta = PluginMetadata(name="minimal")
|
|
83
|
+
assert meta.version == "0.1.0"
|
|
84
|
+
assert meta.hooks == []
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# PluginHook Enum
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
class TestPluginHook:
|
|
92
|
+
def test_values(self):
|
|
93
|
+
assert PluginHook.PRE_INDEX.value == "pre_index"
|
|
94
|
+
assert PluginHook.POST_SEARCH.value == "post_search"
|
|
95
|
+
assert PluginHook.ON_FILE_CHANGE.value == "on_file_change"
|
|
96
|
+
|
|
97
|
+
def test_all_hooks_are_strings(self):
|
|
98
|
+
for hook in PluginHook:
|
|
99
|
+
assert isinstance(hook.value, str)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# PluginManager — Registration
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
class TestPluginManagerRegistration:
|
|
107
|
+
def test_register(self):
|
|
108
|
+
mgr = PluginManager()
|
|
109
|
+
plugin = SamplePlugin()
|
|
110
|
+
mgr.register(plugin)
|
|
111
|
+
assert "test-plugin" in mgr.registered_plugins
|
|
112
|
+
|
|
113
|
+
def test_register_duplicate_replaces(self):
|
|
114
|
+
mgr = PluginManager()
|
|
115
|
+
p1 = SamplePlugin("dup")
|
|
116
|
+
p2 = SamplePlugin("dup")
|
|
117
|
+
mgr.register(p1)
|
|
118
|
+
mgr.register(p2)
|
|
119
|
+
assert mgr.registered_plugins.count("dup") == 1
|
|
120
|
+
|
|
121
|
+
def test_unregister(self):
|
|
122
|
+
mgr = PluginManager()
|
|
123
|
+
plugin = SamplePlugin()
|
|
124
|
+
mgr.register(plugin)
|
|
125
|
+
mgr.unregister("test-plugin")
|
|
126
|
+
assert "test-plugin" not in mgr.registered_plugins
|
|
127
|
+
|
|
128
|
+
def test_unregister_nonexistent(self):
|
|
129
|
+
mgr = PluginManager()
|
|
130
|
+
mgr.unregister("nope") # should not raise
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# PluginManager — Activation
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
class TestPluginManagerActivation:
|
|
138
|
+
def test_activate(self):
|
|
139
|
+
mgr = PluginManager()
|
|
140
|
+
plugin = SamplePlugin()
|
|
141
|
+
mgr.register(plugin)
|
|
142
|
+
mgr.activate("test-plugin")
|
|
143
|
+
assert "test-plugin" in mgr.active_plugins
|
|
144
|
+
assert plugin.activated
|
|
145
|
+
|
|
146
|
+
def test_deactivate(self):
|
|
147
|
+
mgr = PluginManager()
|
|
148
|
+
plugin = SamplePlugin()
|
|
149
|
+
mgr.register(plugin)
|
|
150
|
+
mgr.activate("test-plugin")
|
|
151
|
+
mgr.deactivate("test-plugin")
|
|
152
|
+
assert "test-plugin" not in mgr.active_plugins
|
|
153
|
+
assert plugin.deactivated
|
|
154
|
+
|
|
155
|
+
def test_activate_unregistered(self):
|
|
156
|
+
mgr = PluginManager()
|
|
157
|
+
with pytest.raises(ValueError):
|
|
158
|
+
mgr.activate("nope")
|
|
159
|
+
|
|
160
|
+
def test_unregister_active_deactivates(self):
|
|
161
|
+
mgr = PluginManager()
|
|
162
|
+
plugin = SamplePlugin()
|
|
163
|
+
mgr.register(plugin)
|
|
164
|
+
mgr.activate("test-plugin")
|
|
165
|
+
mgr.unregister("test-plugin")
|
|
166
|
+
assert plugin.deactivated
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# PluginManager — Hook Dispatch
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
class TestPluginManagerDispatch:
|
|
174
|
+
def test_dispatch_basic(self):
|
|
175
|
+
mgr = PluginManager()
|
|
176
|
+
plugin = SamplePlugin()
|
|
177
|
+
mgr.register(plugin)
|
|
178
|
+
mgr.activate("test-plugin")
|
|
179
|
+
|
|
180
|
+
result = mgr.dispatch(PluginHook.PRE_INDEX, {"file": "test.py"})
|
|
181
|
+
assert result["processed_by"] == "test-plugin"
|
|
182
|
+
assert len(plugin.hooks_received) == 1
|
|
183
|
+
|
|
184
|
+
def test_dispatch_skips_inactive(self):
|
|
185
|
+
mgr = PluginManager()
|
|
186
|
+
plugin = SamplePlugin()
|
|
187
|
+
mgr.register(plugin)
|
|
188
|
+
# Not activated
|
|
189
|
+
result = mgr.dispatch(PluginHook.PRE_INDEX, {"file": "test.py"})
|
|
190
|
+
assert "processed_by" not in result
|
|
191
|
+
assert len(plugin.hooks_received) == 0
|
|
192
|
+
|
|
193
|
+
def test_dispatch_unregistered_hook(self):
|
|
194
|
+
mgr = PluginManager()
|
|
195
|
+
plugin = SamplePlugin() # only PRE_INDEX and POST_INDEX
|
|
196
|
+
mgr.register(plugin)
|
|
197
|
+
mgr.activate("test-plugin")
|
|
198
|
+
|
|
199
|
+
result = mgr.dispatch(PluginHook.PRE_SEARCH, {"query": "test"})
|
|
200
|
+
assert "processed_by" not in result # plugin not registered for this hook
|
|
201
|
+
|
|
202
|
+
def test_dispatch_chain(self):
|
|
203
|
+
mgr = PluginManager()
|
|
204
|
+
p1 = SamplePlugin("plugin-a")
|
|
205
|
+
p2 = SamplePlugin("plugin-b")
|
|
206
|
+
mgr.register(p1)
|
|
207
|
+
mgr.register(p2)
|
|
208
|
+
mgr.activate("plugin-a")
|
|
209
|
+
mgr.activate("plugin-b")
|
|
210
|
+
|
|
211
|
+
result = mgr.dispatch(PluginHook.PRE_INDEX, {"count": 0})
|
|
212
|
+
# Last plugin wins for processed_by
|
|
213
|
+
assert result["processed_by"] == "plugin-b"
|
|
214
|
+
|
|
215
|
+
def test_dispatch_failing_plugin_continues(self):
|
|
216
|
+
mgr = PluginManager()
|
|
217
|
+
failing = FailingPlugin()
|
|
218
|
+
good = SamplePlugin()
|
|
219
|
+
mgr.register(failing)
|
|
220
|
+
mgr.register(good)
|
|
221
|
+
mgr.activate("failing-plugin")
|
|
222
|
+
mgr.activate("test-plugin")
|
|
223
|
+
|
|
224
|
+
# Should not raise; failing plugin error is caught
|
|
225
|
+
result = mgr.dispatch(PluginHook.PRE_INDEX, {"x": 1})
|
|
226
|
+
assert result["processed_by"] == "test-plugin"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
# PluginManager — Info
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
class TestPluginManagerInfo:
|
|
234
|
+
def test_get_plugin_info(self):
|
|
235
|
+
mgr = PluginManager()
|
|
236
|
+
plugin = SamplePlugin()
|
|
237
|
+
mgr.register(plugin)
|
|
238
|
+
info = mgr.get_plugin_info("test-plugin")
|
|
239
|
+
assert info is not None
|
|
240
|
+
assert info["name"] == "test-plugin"
|
|
241
|
+
assert info["active"] is False
|
|
242
|
+
|
|
243
|
+
def test_get_plugin_info_active(self):
|
|
244
|
+
mgr = PluginManager()
|
|
245
|
+
plugin = SamplePlugin()
|
|
246
|
+
mgr.register(plugin)
|
|
247
|
+
mgr.activate("test-plugin")
|
|
248
|
+
info = mgr.get_plugin_info("test-plugin")
|
|
249
|
+
assert info["active"] is True
|
|
250
|
+
|
|
251
|
+
def test_get_plugin_info_missing(self):
|
|
252
|
+
mgr = PluginManager()
|
|
253
|
+
assert mgr.get_plugin_info("nope") is None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
# Plugin Discovery
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
class TestPluginDiscovery:
|
|
261
|
+
def test_discover_empty_dir(self, tmp_path):
|
|
262
|
+
mgr = PluginManager()
|
|
263
|
+
count = mgr.discover_from_directory(tmp_path)
|
|
264
|
+
assert count == 0
|
|
265
|
+
|
|
266
|
+
def test_discover_nonexistent_dir(self, tmp_path):
|
|
267
|
+
mgr = PluginManager()
|
|
268
|
+
count = mgr.discover_from_directory(tmp_path / "nope")
|
|
269
|
+
assert count == 0
|
|
270
|
+
|
|
271
|
+
def test_discover_valid_plugin(self, tmp_path):
|
|
272
|
+
plugin_code = '''\
|
|
273
|
+
from semantic_code_intelligence.plugins import PluginBase, PluginMetadata, PluginHook
|
|
274
|
+
|
|
275
|
+
class MyPlugin(PluginBase):
|
|
276
|
+
def metadata(self):
|
|
277
|
+
return PluginMetadata(name="discovered", hooks=[PluginHook.PRE_INDEX])
|
|
278
|
+
|
|
279
|
+
def create_plugin():
|
|
280
|
+
return MyPlugin()
|
|
281
|
+
'''
|
|
282
|
+
(tmp_path / "my_plugin.py").write_text(plugin_code, encoding="utf-8")
|
|
283
|
+
|
|
284
|
+
mgr = PluginManager()
|
|
285
|
+
count = mgr.discover_from_directory(tmp_path)
|
|
286
|
+
assert count == 1
|
|
287
|
+
assert "discovered" in mgr.registered_plugins
|
|
288
|
+
|
|
289
|
+
def test_discover_skips_underscore_files(self, tmp_path):
|
|
290
|
+
(tmp_path / "_private.py").write_text("x = 1", encoding="utf-8")
|
|
291
|
+
mgr = PluginManager()
|
|
292
|
+
count = mgr.discover_from_directory(tmp_path)
|
|
293
|
+
assert count == 0
|