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.
Files changed (159) hide show
  1. forgecli/__init__.py +4 -0
  2. forgecli/build/__init__.py +135 -0
  3. forgecli/build/apply.py +218 -0
  4. forgecli/build/caveman_optimize.py +37 -0
  5. forgecli/build/diff_extract.py +218 -0
  6. forgecli/build/llm.py +167 -0
  7. forgecli/build/optimize.py +37 -0
  8. forgecli/build/pipeline.py +76 -0
  9. forgecli/build/retrieval.py +157 -0
  10. forgecli/build/summarize.py +76 -0
  11. forgecli/build/test_run.py +75 -0
  12. forgecli/builder/__init__.py +11 -0
  13. forgecli/builder/builder.py +53 -0
  14. forgecli/builder/editor.py +36 -0
  15. forgecli/builder/formatter.py +31 -0
  16. forgecli/cli/__init__.py +5 -0
  17. forgecli/cli/bootstrap.py +281 -0
  18. forgecli/cli/commands_graph.py +149 -0
  19. forgecli/cli/commands_wrappers.py +80 -0
  20. forgecli/cli/daemon.py +744 -0
  21. forgecli/cli/main.py +234 -0
  22. forgecli/cli/ui.py +56 -0
  23. forgecli/config/__init__.py +28 -0
  24. forgecli/config/loader.py +85 -0
  25. forgecli/config/settings.py +206 -0
  26. forgecli/config/writer.py +90 -0
  27. forgecli/core/__init__.py +35 -0
  28. forgecli/core/container.py +68 -0
  29. forgecli/core/context.py +54 -0
  30. forgecli/core/credentials.py +114 -0
  31. forgecli/core/errors.py +27 -0
  32. forgecli/core/events.py +67 -0
  33. forgecli/core/logging.py +47 -0
  34. forgecli/core/models.py +214 -0
  35. forgecli/core/plugins.py +54 -0
  36. forgecli/core/service.py +22 -0
  37. forgecli/docs/__init__.py +5 -0
  38. forgecli/docs/generator.py +115 -0
  39. forgecli/engine/__init__.py +105 -0
  40. forgecli/engine/context.py +163 -0
  41. forgecli/engine/defaults.py +89 -0
  42. forgecli/engine/events.py +185 -0
  43. forgecli/engine/execution.py +519 -0
  44. forgecli/engine/plugins.py +145 -0
  45. forgecli/engine/runner.py +158 -0
  46. forgecli/engine/stages/__init__.py +33 -0
  47. forgecli/engine/stages/caveman_optimizer.py +47 -0
  48. forgecli/engine/stages/context_optimizer.py +44 -0
  49. forgecli/engine/stages/execution_engine_stage.py +102 -0
  50. forgecli/engine/stages/git_engine.py +30 -0
  51. forgecli/engine/stages/intent_analyzer.py +38 -0
  52. forgecli/engine/stages/model_router.py +65 -0
  53. forgecli/engine/stages/planning_engine.py +45 -0
  54. forgecli/engine/stages/repository_analyzer.py +54 -0
  55. forgecli/engine/stages/validation_engine.py +87 -0
  56. forgecli/git/__init__.py +9 -0
  57. forgecli/git/repo.py +55 -0
  58. forgecli/git/service.py +45 -0
  59. forgecli/graph/__init__.py +56 -0
  60. forgecli/graph/backend_graphify.py +448 -0
  61. forgecli/graph/edge.py +19 -0
  62. forgecli/graph/graph.py +85 -0
  63. forgecli/graph/graphify.py +405 -0
  64. forgecli/graph/indexer.py +82 -0
  65. forgecli/graph/node.py +47 -0
  66. forgecli/graph/repository.py +164 -0
  67. forgecli/memory/__init__.py +12 -0
  68. forgecli/memory/cache.py +61 -0
  69. forgecli/memory/history.py +136 -0
  70. forgecli/memory/store.py +85 -0
  71. forgecli/optimizer/__init__.py +13 -0
  72. forgecli/optimizer/caveman/__init__.py +169 -0
  73. forgecli/optimizer/caveman/cli.py +62 -0
  74. forgecli/optimizer/caveman/decorator.py +67 -0
  75. forgecli/optimizer/caveman/factory.py +51 -0
  76. forgecli/optimizer/caveman/ruleset.py +156 -0
  77. forgecli/optimizer/caveman/state.py +50 -0
  78. forgecli/optimizer/chunker.py +85 -0
  79. forgecli/optimizer/optimizer.py +81 -0
  80. forgecli/optimizer/ponytail/__init__.py +181 -0
  81. forgecli/optimizer/ponytail/cli.py +159 -0
  82. forgecli/optimizer/ponytail/decorator.py +70 -0
  83. forgecli/optimizer/ponytail/factory.py +51 -0
  84. forgecli/optimizer/ponytail/ruleset.py +168 -0
  85. forgecli/optimizer/ponytail/state.py +51 -0
  86. forgecli/optimizer/ranker.py +37 -0
  87. forgecli/optimizer/summarizer.py +44 -0
  88. forgecli/orchestrator/__init__.py +706 -0
  89. forgecli/planner/__init__.py +56 -0
  90. forgecli/planner/agent.py +61 -0
  91. forgecli/planner/plan.py +65 -0
  92. forgecli/planner/planner.py +17 -0
  93. forgecli/planner/render.py +267 -0
  94. forgecli/planner/serialize.py +104 -0
  95. forgecli/planner/software.py +832 -0
  96. forgecli/platform/__init__.py +91 -0
  97. forgecli/platform/core.py +201 -0
  98. forgecli/platform/deps.py +361 -0
  99. forgecli/platform/paths.py +234 -0
  100. forgecli/platform/shell.py +176 -0
  101. forgecli/platform/update.py +253 -0
  102. forgecli/plugins/__init__.py +249 -0
  103. forgecli/prompts/__init__.py +11 -0
  104. forgecli/prompts/loader.py +28 -0
  105. forgecli/prompts/registry.py +32 -0
  106. forgecli/prompts/renderer.py +23 -0
  107. forgecli/providers/__init__.py +39 -0
  108. forgecli/providers/anthropic.py +207 -0
  109. forgecli/providers/base.py +204 -0
  110. forgecli/providers/builtin.py +26 -0
  111. forgecli/providers/conversation.py +74 -0
  112. forgecli/providers/google.py +290 -0
  113. forgecli/providers/http_base.py +207 -0
  114. forgecli/providers/mock.py +111 -0
  115. forgecli/providers/openai.py +206 -0
  116. forgecli/providers/openai_compatible.py +787 -0
  117. forgecli/providers/router.py +340 -0
  118. forgecli/providers/router_state.py +89 -0
  119. forgecli/review/__init__.py +45 -0
  120. forgecli/review/analyzer.py +124 -0
  121. forgecli/review/analyzers/__init__.py +1 -0
  122. forgecli/review/analyzers/architecture.py +255 -0
  123. forgecli/review/analyzers/complexity.py +162 -0
  124. forgecli/review/analyzers/dead_code.py +255 -0
  125. forgecli/review/analyzers/duplicates.py +161 -0
  126. forgecli/review/analyzers/performance.py +161 -0
  127. forgecli/review/analyzers/security.py +244 -0
  128. forgecli/review/finding.py +68 -0
  129. forgecli/review/report.py +321 -0
  130. forgecli/review/repository.py +130 -0
  131. forgecli/review/suggestions.py +98 -0
  132. forgecli/runtime/__init__.py +6 -0
  133. forgecli/runtime/cache_store.py +75 -0
  134. forgecli/runtime/mcp_config.py +118 -0
  135. forgecli/runtime/prepare.py +203 -0
  136. forgecli/runtime/wrappers.py +150 -0
  137. forgecli/sdk/__init__.py +132 -0
  138. forgecli/sdk/events.py +206 -0
  139. forgecli/sdk/interfaces.py +282 -0
  140. forgecli/sdk/loader.py +239 -0
  141. forgecli/sdk/manager.py +678 -0
  142. forgecli/sdk/manifest.py +395 -0
  143. forgecli/sdk/sandbox.py +247 -0
  144. forgecli/sdk/version.py +313 -0
  145. forgecli/templates/__init__.py +9 -0
  146. forgecli/templates/engine.py +37 -0
  147. forgecli/templates/registry.py +32 -0
  148. forgecli/utils/__init__.py +19 -0
  149. forgecli/utils/fs.py +121 -0
  150. forgecli/utils/ids.py +11 -0
  151. forgecli/utils/io.py +27 -0
  152. forgecli/utils/paths.py +78 -0
  153. forgecli/utils/stats.py +179 -0
  154. forgecli/utils/timing.py +25 -0
  155. forgeoptimizer-0.1.0.dist-info/METADATA +177 -0
  156. forgeoptimizer-0.1.0.dist-info/RECORD +159 -0
  157. forgeoptimizer-0.1.0.dist-info/WHEEL +4 -0
  158. forgeoptimizer-0.1.0.dist-info/entry_points.txt +2 -0
  159. 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
+ ]