forgeoptimizer 0.1.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.
- forgecli/__init__.py +4 -0
- forgecli/build/__init__.py +135 -0
- forgecli/build/apply.py +218 -0
- forgecli/build/caveman_optimize.py +37 -0
- forgecli/build/diff_extract.py +218 -0
- forgecli/build/llm.py +167 -0
- forgecli/build/optimize.py +37 -0
- forgecli/build/pipeline.py +76 -0
- forgecli/build/retrieval.py +157 -0
- forgecli/build/summarize.py +76 -0
- forgecli/build/test_run.py +75 -0
- forgecli/builder/__init__.py +11 -0
- forgecli/builder/builder.py +53 -0
- forgecli/builder/editor.py +36 -0
- forgecli/builder/formatter.py +31 -0
- forgecli/cli/__init__.py +5 -0
- forgecli/cli/bootstrap.py +281 -0
- forgecli/cli/commands_graph.py +149 -0
- forgecli/cli/commands_wrappers.py +80 -0
- forgecli/cli/daemon.py +744 -0
- forgecli/cli/main.py +234 -0
- forgecli/cli/ui.py +56 -0
- forgecli/config/__init__.py +28 -0
- forgecli/config/loader.py +85 -0
- forgecli/config/settings.py +206 -0
- forgecli/config/writer.py +90 -0
- forgecli/core/__init__.py +35 -0
- forgecli/core/container.py +68 -0
- forgecli/core/context.py +54 -0
- forgecli/core/credentials.py +114 -0
- forgecli/core/errors.py +27 -0
- forgecli/core/events.py +67 -0
- forgecli/core/logging.py +47 -0
- forgecli/core/models.py +214 -0
- forgecli/core/plugins.py +54 -0
- forgecli/core/service.py +22 -0
- forgecli/docs/__init__.py +5 -0
- forgecli/docs/generator.py +115 -0
- forgecli/engine/__init__.py +105 -0
- forgecli/engine/context.py +163 -0
- forgecli/engine/defaults.py +89 -0
- forgecli/engine/events.py +185 -0
- forgecli/engine/execution.py +519 -0
- forgecli/engine/plugins.py +145 -0
- forgecli/engine/runner.py +158 -0
- forgecli/engine/stages/__init__.py +33 -0
- forgecli/engine/stages/caveman_optimizer.py +47 -0
- forgecli/engine/stages/context_optimizer.py +44 -0
- forgecli/engine/stages/execution_engine_stage.py +102 -0
- forgecli/engine/stages/git_engine.py +30 -0
- forgecli/engine/stages/intent_analyzer.py +38 -0
- forgecli/engine/stages/model_router.py +65 -0
- forgecli/engine/stages/planning_engine.py +45 -0
- forgecli/engine/stages/repository_analyzer.py +54 -0
- forgecli/engine/stages/validation_engine.py +87 -0
- forgecli/git/__init__.py +9 -0
- forgecli/git/repo.py +55 -0
- forgecli/git/service.py +45 -0
- forgecli/graph/__init__.py +56 -0
- forgecli/graph/backend_graphify.py +448 -0
- forgecli/graph/edge.py +19 -0
- forgecli/graph/graph.py +85 -0
- forgecli/graph/graphify.py +405 -0
- forgecli/graph/indexer.py +82 -0
- forgecli/graph/node.py +47 -0
- forgecli/graph/repository.py +164 -0
- forgecli/memory/__init__.py +12 -0
- forgecli/memory/cache.py +61 -0
- forgecli/memory/history.py +136 -0
- forgecli/memory/store.py +85 -0
- forgecli/optimizer/__init__.py +13 -0
- forgecli/optimizer/caveman/__init__.py +169 -0
- forgecli/optimizer/caveman/cli.py +62 -0
- forgecli/optimizer/caveman/decorator.py +67 -0
- forgecli/optimizer/caveman/factory.py +51 -0
- forgecli/optimizer/caveman/ruleset.py +156 -0
- forgecli/optimizer/caveman/state.py +50 -0
- forgecli/optimizer/chunker.py +85 -0
- forgecli/optimizer/optimizer.py +81 -0
- forgecli/optimizer/ponytail/__init__.py +181 -0
- forgecli/optimizer/ponytail/cli.py +159 -0
- forgecli/optimizer/ponytail/decorator.py +70 -0
- forgecli/optimizer/ponytail/factory.py +51 -0
- forgecli/optimizer/ponytail/ruleset.py +168 -0
- forgecli/optimizer/ponytail/state.py +51 -0
- forgecli/optimizer/ranker.py +37 -0
- forgecli/optimizer/summarizer.py +44 -0
- forgecli/orchestrator/__init__.py +706 -0
- forgecli/planner/__init__.py +56 -0
- forgecli/planner/agent.py +61 -0
- forgecli/planner/plan.py +65 -0
- forgecli/planner/planner.py +17 -0
- forgecli/planner/render.py +267 -0
- forgecli/planner/serialize.py +104 -0
- forgecli/planner/software.py +832 -0
- forgecli/platform/__init__.py +91 -0
- forgecli/platform/core.py +201 -0
- forgecli/platform/deps.py +361 -0
- forgecli/platform/paths.py +234 -0
- forgecli/platform/shell.py +176 -0
- forgecli/platform/update.py +253 -0
- forgecli/plugins/__init__.py +249 -0
- forgecli/prompts/__init__.py +11 -0
- forgecli/prompts/loader.py +28 -0
- forgecli/prompts/registry.py +32 -0
- forgecli/prompts/renderer.py +23 -0
- forgecli/providers/__init__.py +39 -0
- forgecli/providers/anthropic.py +207 -0
- forgecli/providers/base.py +204 -0
- forgecli/providers/builtin.py +26 -0
- forgecli/providers/conversation.py +74 -0
- forgecli/providers/google.py +290 -0
- forgecli/providers/http_base.py +207 -0
- forgecli/providers/mock.py +111 -0
- forgecli/providers/openai.py +206 -0
- forgecli/providers/openai_compatible.py +787 -0
- forgecli/providers/router.py +340 -0
- forgecli/providers/router_state.py +89 -0
- forgecli/review/__init__.py +45 -0
- forgecli/review/analyzer.py +124 -0
- forgecli/review/analyzers/__init__.py +1 -0
- forgecli/review/analyzers/architecture.py +255 -0
- forgecli/review/analyzers/complexity.py +162 -0
- forgecli/review/analyzers/dead_code.py +255 -0
- forgecli/review/analyzers/duplicates.py +161 -0
- forgecli/review/analyzers/performance.py +161 -0
- forgecli/review/analyzers/security.py +244 -0
- forgecli/review/finding.py +68 -0
- forgecli/review/report.py +321 -0
- forgecli/review/repository.py +130 -0
- forgecli/review/suggestions.py +98 -0
- forgecli/runtime/__init__.py +6 -0
- forgecli/runtime/cache_store.py +75 -0
- forgecli/runtime/mcp_config.py +118 -0
- forgecli/runtime/prepare.py +203 -0
- forgecli/runtime/wrappers.py +150 -0
- forgecli/sdk/__init__.py +132 -0
- forgecli/sdk/events.py +206 -0
- forgecli/sdk/interfaces.py +282 -0
- forgecli/sdk/loader.py +239 -0
- forgecli/sdk/manager.py +678 -0
- forgecli/sdk/manifest.py +395 -0
- forgecli/sdk/sandbox.py +247 -0
- forgecli/sdk/version.py +313 -0
- forgecli/templates/__init__.py +9 -0
- forgecli/templates/engine.py +37 -0
- forgecli/templates/registry.py +32 -0
- forgecli/utils/__init__.py +19 -0
- forgecli/utils/fs.py +121 -0
- forgecli/utils/ids.py +11 -0
- forgecli/utils/io.py +27 -0
- forgecli/utils/paths.py +78 -0
- forgecli/utils/stats.py +179 -0
- forgecli/utils/timing.py +25 -0
- forgeoptimizer-0.1.0.dist-info/METADATA +177 -0
- forgeoptimizer-0.1.0.dist-info/RECORD +159 -0
- forgeoptimizer-0.1.0.dist-info/WHEEL +4 -0
- forgeoptimizer-0.1.0.dist-info/entry_points.txt +2 -0
- forgeoptimizer-0.1.0.dist-info/licenses/LICENSE +21 -0
forgecli/sdk/events.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Plugin event bus + hook manager.
|
|
2
|
+
|
|
3
|
+
The SDK exposes:
|
|
4
|
+
|
|
5
|
+
* :class:`PluginEventBus` — a tiny pub/sub bus for plugin
|
|
6
|
+
lifecycle and runtime events. Distinct from the engine's
|
|
7
|
+
:class:`~forgecli.engine.events.EventBus` (which is for the
|
|
8
|
+
eight-stage pipeline). The two are independent so plugins can
|
|
9
|
+
listen to the engine *and* the plugin lifecycle without coupling.
|
|
10
|
+
* :class:`PluginHook` and :class:`HookManager` — synchronous
|
|
11
|
+
before/after hooks the SDK fires when a plugin is installed,
|
|
12
|
+
enabled, disabled, updated, or uninstalled.
|
|
13
|
+
|
|
14
|
+
Plugins subscribe at enable-time; the SDK guarantees that the
|
|
15
|
+
event handlers are called for every registered subscriber, even
|
|
16
|
+
if one raises (failures are logged and isolated).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import contextlib
|
|
23
|
+
import logging
|
|
24
|
+
from collections.abc import Awaitable, Callable
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from datetime import UTC, datetime
|
|
27
|
+
from enum import Enum
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
_logger = logging.getLogger("forgecli.sdk.events")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Event types
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PluginEventKind(str, Enum):
|
|
39
|
+
"""The high-level events plugins can listen to."""
|
|
40
|
+
|
|
41
|
+
INSTALLED = "installed"
|
|
42
|
+
ENABLED = "enabled"
|
|
43
|
+
DISABLED = "disabled"
|
|
44
|
+
UNINSTALLED = "uninstalled"
|
|
45
|
+
UPDATED = "updated"
|
|
46
|
+
BEFORE_COMMAND = "before_command"
|
|
47
|
+
AFTER_COMMAND = "after_command"
|
|
48
|
+
CONFIG_CHANGED = "config_changed"
|
|
49
|
+
ERROR = "error"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class PluginEvent:
|
|
54
|
+
"""A single lifecycle / runtime event."""
|
|
55
|
+
|
|
56
|
+
kind: PluginEventKind
|
|
57
|
+
plugin_name: str | None = None
|
|
58
|
+
payload: dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
EventHandler = Callable[[PluginEvent], "None | Awaitable[None]"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Event bus
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PluginEventBus:
|
|
71
|
+
"""In-process pub/sub bus for plugin events.
|
|
72
|
+
|
|
73
|
+
Async-aware: subscribers may be sync or async. The bus
|
|
74
|
+
serialises fan-out for synchronous subscribers and schedules
|
|
75
|
+
coroutines for async ones.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self) -> None:
|
|
79
|
+
self._subscribers: dict[PluginEventKind, list[EventHandler]] = {}
|
|
80
|
+
|
|
81
|
+
def subscribe(self, kind: PluginEventKind, handler: EventHandler) -> None:
|
|
82
|
+
self._subscribers.setdefault(kind, []).append(handler)
|
|
83
|
+
|
|
84
|
+
def unsubscribe(self, kind: PluginEventKind, handler: EventHandler) -> None:
|
|
85
|
+
bucket = self._subscribers.get(kind)
|
|
86
|
+
if not bucket:
|
|
87
|
+
return
|
|
88
|
+
with _suppress(ValueError):
|
|
89
|
+
bucket.remove(handler)
|
|
90
|
+
|
|
91
|
+
def publish(self, event: PluginEvent) -> None:
|
|
92
|
+
"""Publish synchronously. Async handlers are scheduled."""
|
|
93
|
+
for handler in list(self._subscribers.get(event.kind, ())):
|
|
94
|
+
try:
|
|
95
|
+
result = handler(event)
|
|
96
|
+
except Exception:
|
|
97
|
+
_logger.exception("plugin event handler raised")
|
|
98
|
+
continue
|
|
99
|
+
if asyncio.iscoroutine(result):
|
|
100
|
+
try:
|
|
101
|
+
loop = asyncio.get_running_loop()
|
|
102
|
+
task = loop.create_task(result)
|
|
103
|
+
task.add_done_callback(_discard_task)
|
|
104
|
+
except RuntimeError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
async def publish_and_drain(self, event: PluginEvent) -> None:
|
|
108
|
+
"""Publish and await any async handler results."""
|
|
109
|
+
for handler in list(self._subscribers.get(event.kind, ())):
|
|
110
|
+
try:
|
|
111
|
+
result = handler(event)
|
|
112
|
+
except Exception:
|
|
113
|
+
_logger.exception("plugin event handler raised")
|
|
114
|
+
continue
|
|
115
|
+
if asyncio.iscoroutine(result):
|
|
116
|
+
await result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Hook manager
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class PluginHook:
|
|
126
|
+
"""A single registered hook.
|
|
127
|
+
|
|
128
|
+
``callback`` may be sync or async. The SDK wraps it in
|
|
129
|
+
:func:`asyncio.iscoroutine` before calling so plugins can
|
|
130
|
+
return a coroutine for async work.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
name: str
|
|
134
|
+
callback: Callable[..., Any]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class HookManager:
|
|
138
|
+
"""Synchronous registry of before/after hooks."""
|
|
139
|
+
|
|
140
|
+
def __init__(self) -> None:
|
|
141
|
+
self._before: list[PluginHook] = []
|
|
142
|
+
self._after: list[PluginHook] = []
|
|
143
|
+
|
|
144
|
+
def before(self, hook: PluginHook) -> None:
|
|
145
|
+
self._before.append(hook)
|
|
146
|
+
|
|
147
|
+
def after(self, hook: PluginHook) -> None:
|
|
148
|
+
self._after.append(hook)
|
|
149
|
+
|
|
150
|
+
def fire_before(self, *args: Any, **kwargs: Any) -> None:
|
|
151
|
+
for hook in self._before:
|
|
152
|
+
try:
|
|
153
|
+
hook.callback(*args, **kwargs)
|
|
154
|
+
except Exception:
|
|
155
|
+
_logger.exception("hook %s failed (before)", hook.name)
|
|
156
|
+
|
|
157
|
+
def fire_after(self, *args: Any, **kwargs: Any) -> None:
|
|
158
|
+
for hook in self._after:
|
|
159
|
+
try:
|
|
160
|
+
hook.callback(*args, **kwargs)
|
|
161
|
+
except Exception:
|
|
162
|
+
_logger.exception("hook %s failed (after)", hook.name)
|
|
163
|
+
|
|
164
|
+
async def fire_before_async(self, *args: Any, **kwargs: Any) -> None:
|
|
165
|
+
for hook in self._before:
|
|
166
|
+
try:
|
|
167
|
+
result = hook.callback(*args, **kwargs)
|
|
168
|
+
if asyncio.iscoroutine(result):
|
|
169
|
+
await result
|
|
170
|
+
except Exception:
|
|
171
|
+
_logger.exception("hook %s failed (before)", hook.name)
|
|
172
|
+
|
|
173
|
+
async def fire_after_async(self, *args: Any, **kwargs: Any) -> None:
|
|
174
|
+
for hook in self._after:
|
|
175
|
+
try:
|
|
176
|
+
result = hook.callback(*args, **kwargs)
|
|
177
|
+
if asyncio.iscoroutine(result):
|
|
178
|
+
await result
|
|
179
|
+
except Exception:
|
|
180
|
+
_logger.exception("hook %s failed (after)", hook.name)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Helpers
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@contextlib.contextmanager
|
|
189
|
+
def _suppress(*exceptions: type[BaseException]):
|
|
190
|
+
with contextlib.suppress(*exceptions):
|
|
191
|
+
yield
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _discard_task(task: asyncio.Task) -> None:
|
|
195
|
+
"""Keep a strong reference to a scheduled task until it completes."""
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = [
|
|
200
|
+
"EventHandler",
|
|
201
|
+
"HookManager",
|
|
202
|
+
"PluginEvent",
|
|
203
|
+
"PluginEventBus",
|
|
204
|
+
"PluginEventKind",
|
|
205
|
+
"PluginHook",
|
|
206
|
+
]
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""The 10 canonical plugin extension points.
|
|
2
|
+
|
|
3
|
+
Every ForgeCLI plugin declares one or more of these. Each interface
|
|
4
|
+
is a :class:`typing.Protocol` with a single async or sync ``register``
|
|
5
|
+
method; the SDK calls the method when the plugin is enabled.
|
|
6
|
+
|
|
7
|
+
Adding a new plugin category is intentionally hard: the SDK exposes
|
|
8
|
+
exactly these ten categories and the engine only looks for them by
|
|
9
|
+
name. Plugin authors who want a new category should add a new
|
|
10
|
+
:class:`EntryPointKind` to :mod:`forgecli.sdk.manifest`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
# Re-export the SDK manager for forward references below. The import
|
|
18
|
+
# is intentionally non-binding (TYPE_CHECKING) because the actual
|
|
19
|
+
# PluginManager class lives in :mod:`forgecli.sdk.manager` and would
|
|
20
|
+
# create a circular import at runtime.
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING: # pragma: no cover - type hints only
|
|
24
|
+
from forgecli.sdk.manager import PluginManager
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Configuration protocol
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@runtime_checkable
|
|
32
|
+
class PluginConfigurable(Protocol):
|
|
33
|
+
"""A plugin that exposes user-configurable values.
|
|
34
|
+
|
|
35
|
+
Plugins implementing this protocol may return a dict from
|
|
36
|
+
:meth:`default_config`; the SDK merges that into the persisted
|
|
37
|
+
configuration under ``plugin.<name>``. The dict may contain any
|
|
38
|
+
JSON-serialisable values.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def default_config(self) -> dict[str, Any]: ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Health protocol
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@runtime_checkable
|
|
50
|
+
class PluginHealthCheck(Protocol):
|
|
51
|
+
"""A plugin that exposes a synchronous health probe.
|
|
52
|
+
|
|
53
|
+
The SDK calls :meth:`health` whenever ``forge plugin doctor``
|
|
54
|
+
is invoked; the returned list of :class:`HealthIssue` items is
|
|
55
|
+
surfaced in the doctor report.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def health(self) -> list[HealthIssue]: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# 1. AI Provider
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@runtime_checkable
|
|
67
|
+
class AIProviderPlugin(Protocol):
|
|
68
|
+
"""Register a custom AI provider with the router.
|
|
69
|
+
|
|
70
|
+
The plugin's :meth:`register` is called with the
|
|
71
|
+
:class:`~forgecli.plugins.PluginRegistry`; the plugin calls
|
|
72
|
+
:meth:`PluginRegistry.register_provider` to add itself.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
name: str
|
|
76
|
+
|
|
77
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# 2. Repository Analyzer
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@runtime_checkable
|
|
86
|
+
class RepositoryAnalyzerPlugin(Protocol):
|
|
87
|
+
"""Register a :class:`RepositoryAnalyzer` with the review engine.
|
|
88
|
+
|
|
89
|
+
The plugin's :meth:`register` is called with the manager; the
|
|
90
|
+
plugin calls :meth:`PluginManager.register_repository_analyzer`.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
name: str
|
|
94
|
+
|
|
95
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# 3. Context Optimizer
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@runtime_checkable
|
|
104
|
+
class ContextOptimizerPlugin(Protocol):
|
|
105
|
+
"""Register a :class:`PromptOptimizer` with the engine."""
|
|
106
|
+
|
|
107
|
+
name: str
|
|
108
|
+
|
|
109
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# 4. Code Generator
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@runtime_checkable
|
|
118
|
+
class CodeGeneratorPlugin(Protocol):
|
|
119
|
+
"""Register a code generator (e.g. a custom executor)."""
|
|
120
|
+
|
|
121
|
+
name: str
|
|
122
|
+
|
|
123
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# 5. Test Runner
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@runtime_checkable
|
|
132
|
+
class TestRunnerPlugin(Protocol):
|
|
133
|
+
"""Register a custom test runner.
|
|
134
|
+
|
|
135
|
+
The plugin's :meth:`register` is called with the manager; the
|
|
136
|
+
plugin can use ``manager.register_test_runner(name, callback)``
|
|
137
|
+
to register a callable that the engine will use to run tests
|
|
138
|
+
on a project.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
name: str
|
|
142
|
+
|
|
143
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# 6. Git Provider
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@runtime_checkable
|
|
152
|
+
class GitProviderPlugin(Protocol):
|
|
153
|
+
"""Register a custom Git provider.
|
|
154
|
+
|
|
155
|
+
The plugin's :meth:`register` is called with the manager; the
|
|
156
|
+
plugin can replace or extend ``manager.git_service`` with its
|
|
157
|
+
own implementation.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
name: str
|
|
161
|
+
|
|
162
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
# 7. Documentation Generator
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@runtime_checkable
|
|
171
|
+
class DocumentationGeneratorPlugin(Protocol):
|
|
172
|
+
"""Register a documentation generator.
|
|
173
|
+
|
|
174
|
+
The plugin's :meth:`register` is called with the manager; the
|
|
175
|
+
plugin calls :meth:`PluginManager.register_docs_generator`.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
name: str
|
|
179
|
+
|
|
180
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# 8. Deployment Provider
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@runtime_checkable
|
|
189
|
+
class DeploymentProviderPlugin(Protocol):
|
|
190
|
+
"""Register a deployment provider (CI, cloud, package manager)."""
|
|
191
|
+
|
|
192
|
+
name: str
|
|
193
|
+
|
|
194
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
# 9. Observability Provider
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@runtime_checkable
|
|
203
|
+
class ObservabilityProviderPlugin(Protocol):
|
|
204
|
+
"""Register an observability provider (metrics, tracing, logs)."""
|
|
205
|
+
|
|
206
|
+
name: str
|
|
207
|
+
|
|
208
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# 10. Notification Provider
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@runtime_checkable
|
|
217
|
+
class NotificationProviderPlugin(Protocol):
|
|
218
|
+
"""Register a notification sink (Slack, email, webhook)."""
|
|
219
|
+
|
|
220
|
+
name: str
|
|
221
|
+
|
|
222
|
+
def register(self, manager: PluginManager) -> None: ...
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# Health issue + summary types
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass(frozen=True)
|
|
231
|
+
class HealthIssue:
|
|
232
|
+
"""A single result from a plugin's health probe."""
|
|
233
|
+
|
|
234
|
+
severity: str # "info" | "warn" | "error"
|
|
235
|
+
message: str
|
|
236
|
+
suggestion: str | None = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(frozen=True)
|
|
240
|
+
class HealthReport:
|
|
241
|
+
"""The aggregated health report for a single plugin."""
|
|
242
|
+
|
|
243
|
+
plugin_name: str
|
|
244
|
+
issues: tuple[HealthIssue, ...] = ()
|
|
245
|
+
healthy: bool = True
|
|
246
|
+
|
|
247
|
+
def to_dict(self) -> dict[str, object]:
|
|
248
|
+
return {
|
|
249
|
+
"plugin": self.plugin_name,
|
|
250
|
+
"healthy": self.healthy,
|
|
251
|
+
"issues": [
|
|
252
|
+
{
|
|
253
|
+
"severity": issue.severity,
|
|
254
|
+
"message": issue.message,
|
|
255
|
+
"suggestion": issue.suggestion,
|
|
256
|
+
}
|
|
257
|
+
for issue in self.issues
|
|
258
|
+
],
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Forward import for type hints only.
|
|
263
|
+
if False: # pragma: no cover - type hints only
|
|
264
|
+
from forgecli.sdk.manager import PluginManager
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
__all__ = [
|
|
268
|
+
"AIProviderPlugin",
|
|
269
|
+
"CodeGeneratorPlugin",
|
|
270
|
+
"ContextOptimizerPlugin",
|
|
271
|
+
"DeploymentProviderPlugin",
|
|
272
|
+
"DocumentationGeneratorPlugin",
|
|
273
|
+
"GitProviderPlugin",
|
|
274
|
+
"HealthIssue",
|
|
275
|
+
"HealthReport",
|
|
276
|
+
"NotificationProviderPlugin",
|
|
277
|
+
"ObservabilityProviderPlugin",
|
|
278
|
+
"PluginConfigurable",
|
|
279
|
+
"PluginHealthCheck",
|
|
280
|
+
"RepositoryAnalyzerPlugin",
|
|
281
|
+
"TestRunnerPlugin",
|
|
282
|
+
]
|