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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Default stage wiring for the Execution Engine.
|
|
2
|
+
|
|
3
|
+
Provides :func:`default_registry` which populates a
|
|
4
|
+
:class:`~forgecli.engine.execution.StageRegistry` with all eight
|
|
5
|
+
DEFAULT_PIPELINE stages. Callers can then build an engine with::
|
|
6
|
+
|
|
7
|
+
registry = default_registry()
|
|
8
|
+
engine = ExecutionEngine.from_registry(registry)
|
|
9
|
+
result = await engine.run(context)
|
|
10
|
+
|
|
11
|
+
Individual stages can be overridden via
|
|
12
|
+
:meth:`StageRegistry.replace` before building the engine.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from forgecli.engine.execution import Stage, StageRegistry
|
|
20
|
+
from forgecli.engine.stages import (
|
|
21
|
+
CavemanOptimizerStage,
|
|
22
|
+
ContextOptimizerStage,
|
|
23
|
+
ExecutionEngineStage,
|
|
24
|
+
GitEngineStage,
|
|
25
|
+
IntentAnalyzerStage,
|
|
26
|
+
ModelRouterStage,
|
|
27
|
+
PlanningEngineStage,
|
|
28
|
+
RepositoryAnalyzerStage,
|
|
29
|
+
ValidationEngineStage,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def default_registry(
|
|
34
|
+
*,
|
|
35
|
+
provider: Any = None,
|
|
36
|
+
optimizer: Any = None,
|
|
37
|
+
caveman_optimizer: Any = None,
|
|
38
|
+
graph: Any = None,
|
|
39
|
+
classifier: Any = None,
|
|
40
|
+
router: Any = None,
|
|
41
|
+
test_command: str | None = None,
|
|
42
|
+
plugin_registry: Any = None,
|
|
43
|
+
**stage_kwargs: Any,
|
|
44
|
+
) -> StageRegistry:
|
|
45
|
+
"""Create a :class:`StageRegistry` with all nine default pipeline stages.
|
|
46
|
+
|
|
47
|
+
Keyword arguments are forwarded to stage constructors:
|
|
48
|
+
|
|
49
|
+
* ``provider`` → :class:`ExecutionEngineStage`
|
|
50
|
+
* ``optimizer`` → :class:`ContextOptimizerStage`
|
|
51
|
+
* ``caveman_optimizer`` → :class:`CavemanOptimizerStage`
|
|
52
|
+
* ``graph`` → :class:`RepositoryAnalyzerStage`
|
|
53
|
+
* ``classifier`` → :class:`IntentAnalyzerStage`
|
|
54
|
+
* ``router`` → :class:`ModelRouterStage`
|
|
55
|
+
* ``test_command`` → :class:`ValidationEngineStage`
|
|
56
|
+
* ``plugin_registry`` → a :class:`~forgecli.plugins.PluginRegistry` whose
|
|
57
|
+
stages are bulk-loaded into the registry after default stages.
|
|
58
|
+
The plugin registry is also linked so future
|
|
59
|
+
:meth:`~forgecli.plugins.PluginRegistry.register_stage` /
|
|
60
|
+
:meth:`~forgecli.plugins.PluginRegistry.replace_stage` calls
|
|
61
|
+
are mirrored into this registry.
|
|
62
|
+
|
|
63
|
+
Remaining kwargs are passed to stages that accept them by name
|
|
64
|
+
(e.g. ``auto_commit`` for :class:`GitEngineStage`).
|
|
65
|
+
"""
|
|
66
|
+
registry = StageRegistry()
|
|
67
|
+
|
|
68
|
+
stages: list[Stage] = [
|
|
69
|
+
IntentAnalyzerStage(classifier=classifier),
|
|
70
|
+
RepositoryAnalyzerStage(graph=graph),
|
|
71
|
+
CavemanOptimizerStage(optimizer=caveman_optimizer),
|
|
72
|
+
ContextOptimizerStage(optimizer=optimizer),
|
|
73
|
+
PlanningEngineStage(**{k: v for k, v in stage_kwargs.items() if k in ("enabled",)}),
|
|
74
|
+
ModelRouterStage(router=router),
|
|
75
|
+
ExecutionEngineStage(provider=provider),
|
|
76
|
+
ValidationEngineStage(test_command=test_command),
|
|
77
|
+
GitEngineStage(),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for stage in stages:
|
|
81
|
+
registry.register(stage)
|
|
82
|
+
|
|
83
|
+
if plugin_registry is not None:
|
|
84
|
+
plugin_registry.link_engine_registry(registry)
|
|
85
|
+
|
|
86
|
+
return registry
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = ["default_registry"]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Structured event bus for the Execution Engine.
|
|
2
|
+
|
|
3
|
+
The :class:`EventBus` is a tiny pub/sub dispatcher for the four
|
|
4
|
+
event kinds the engine emits:
|
|
5
|
+
|
|
6
|
+
* :class:`StageEvent` — a stage transitioned to a new state.
|
|
7
|
+
* :class:`ProgressEvent` — fractional progress within a stage.
|
|
8
|
+
* :class:`TextLogEvent` — a free-form log line (the engine's
|
|
9
|
+
"streaming log" surface).
|
|
10
|
+
* :class:`EngineEvent` — the abstract base; every concrete event
|
|
11
|
+
inherits from it.
|
|
12
|
+
|
|
13
|
+
Subscribers can be sync or async; the engine handles fan-out
|
|
14
|
+
sequentially. A single :class:`asyncio.Event` (the cancellation
|
|
15
|
+
token) is checked between stages and during long operations so a
|
|
16
|
+
caller can abort cleanly.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import contextlib
|
|
23
|
+
from collections.abc import Awaitable, Callable
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from datetime import UTC, datetime
|
|
26
|
+
from enum import Enum
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LogLevel(str, Enum):
|
|
30
|
+
"""Severity for :class:`TextLogEvent`."""
|
|
31
|
+
|
|
32
|
+
DEBUG = "debug"
|
|
33
|
+
INFO = "info"
|
|
34
|
+
WARN = "warn"
|
|
35
|
+
ERROR = "error"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class EngineEvent:
|
|
40
|
+
"""Abstract base of every event the engine emits."""
|
|
41
|
+
|
|
42
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
43
|
+
run_id: str = ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class StageEvent(EngineEvent):
|
|
48
|
+
"""A stage transitioned to a new state."""
|
|
49
|
+
|
|
50
|
+
stage: str = ""
|
|
51
|
+
status: str = "running" # "running" | "succeeded" | "failed" | "skipped" | "retrying"
|
|
52
|
+
attempt: int = 1
|
|
53
|
+
note: str | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class ProgressEvent(EngineEvent):
|
|
58
|
+
"""Fractional progress within a stage (0.0 - 1.0)."""
|
|
59
|
+
|
|
60
|
+
stage: str = ""
|
|
61
|
+
progress: float = 0.0
|
|
62
|
+
message: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class TextLogEvent(EngineEvent):
|
|
67
|
+
"""A free-form log line emitted by a stage."""
|
|
68
|
+
|
|
69
|
+
level: LogLevel = LogLevel.INFO
|
|
70
|
+
source: str = ""
|
|
71
|
+
message: str = ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
EventHandler = Callable[[EngineEvent], "None | Awaitable[None]"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class EventBus:
|
|
78
|
+
"""In-process pub/sub bus for engine events.
|
|
79
|
+
|
|
80
|
+
Subscribers are registered per event class. Handlers may be sync
|
|
81
|
+
or async; the engine awaits any coroutines it gets back. A single
|
|
82
|
+
:class:`asyncio.Event` (the cancellation token) is checked between
|
|
83
|
+
stages so the caller can abort cleanly.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
self._subscribers: dict[type, list[EventHandler]] = {}
|
|
88
|
+
self.cancellation = asyncio.Event()
|
|
89
|
+
self.history: list[EngineEvent] = []
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
# Subscription
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def subscribe(self, event_cls: type[EngineEvent], handler: EventHandler) -> None:
|
|
96
|
+
self._subscribers.setdefault(event_cls, []).append(handler)
|
|
97
|
+
|
|
98
|
+
def unsubscribe(self, event_cls: type[EngineEvent], handler: EventHandler) -> None:
|
|
99
|
+
bucket = self._subscribers.get(event_cls)
|
|
100
|
+
if not bucket:
|
|
101
|
+
return
|
|
102
|
+
with contextlib.suppress(ValueError):
|
|
103
|
+
bucket.remove(handler)
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
# Cancellation
|
|
107
|
+
# ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def cancel(self) -> None:
|
|
110
|
+
"""Request cancellation. The engine checks ``cancellation`` between stages."""
|
|
111
|
+
self.cancellation.set()
|
|
112
|
+
|
|
113
|
+
def reset_cancellation(self) -> None:
|
|
114
|
+
self.cancellation.clear()
|
|
115
|
+
|
|
116
|
+
def is_cancelled(self) -> bool:
|
|
117
|
+
return self.cancellation.is_set()
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Publishing
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def publish(self, event: EngineEvent) -> None:
|
|
124
|
+
"""Publish synchronously; async handlers are scheduled."""
|
|
125
|
+
self.history.append(event)
|
|
126
|
+
for handler in list(self._subscribers.get(type(event), ())):
|
|
127
|
+
try:
|
|
128
|
+
result = handler(event)
|
|
129
|
+
except Exception:
|
|
130
|
+
continue
|
|
131
|
+
if asyncio.iscoroutine(result):
|
|
132
|
+
# Best-effort: schedule on the running loop if any.
|
|
133
|
+
try:
|
|
134
|
+
loop = asyncio.get_running_loop()
|
|
135
|
+
task = loop.create_task(result)
|
|
136
|
+
task.add_done_callback(self._discard_task)
|
|
137
|
+
except RuntimeError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _discard_task(task: asyncio.Task) -> None:
|
|
142
|
+
# Placeholder to keep a strong reference to the task until it
|
|
143
|
+
# completes. Without this, asyncio may garbage-collect the
|
|
144
|
+
# task mid-flight and emit "Task was destroyed but it is
|
|
145
|
+
# pending" warnings.
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
async def publish_and_drain(self, event: EngineEvent) -> None:
|
|
149
|
+
"""Publish and await any async handler results."""
|
|
150
|
+
self.history.append(event)
|
|
151
|
+
for handler in list(self._subscribers.get(type(event), ())):
|
|
152
|
+
try:
|
|
153
|
+
result = handler(event)
|
|
154
|
+
except Exception:
|
|
155
|
+
continue
|
|
156
|
+
if asyncio.iscoroutine(result):
|
|
157
|
+
await result
|
|
158
|
+
|
|
159
|
+
# ------------------------------------------------------------------
|
|
160
|
+
# Helpers
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def drain(self) -> list[EngineEvent]:
|
|
164
|
+
return list(self.history)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
# Cancellation sentinel exception
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class EngineCancelledError(Exception):
|
|
173
|
+
"""Raised by the engine when the cancellation token is set."""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
__all__ = [
|
|
177
|
+
"EngineCancelledError",
|
|
178
|
+
"EngineEvent",
|
|
179
|
+
"EventBus",
|
|
180
|
+
"EventHandler",
|
|
181
|
+
"LogLevel",
|
|
182
|
+
"ProgressEvent",
|
|
183
|
+
"StageEvent",
|
|
184
|
+
"TextLogEvent",
|
|
185
|
+
]
|