codeframe-ai 0.9.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.
- codeframe/__init__.py +11 -0
- codeframe/__main__.py +20 -0
- codeframe/adapters/__init__.py +5 -0
- codeframe/adapters/e2b/__init__.py +13 -0
- codeframe/adapters/e2b/adapter.py +342 -0
- codeframe/adapters/e2b/budget.py +71 -0
- codeframe/adapters/e2b/credential_scanner.py +134 -0
- codeframe/adapters/llm/__init__.py +92 -0
- codeframe/adapters/llm/anthropic.py +414 -0
- codeframe/adapters/llm/base.py +444 -0
- codeframe/adapters/llm/mock.py +281 -0
- codeframe/adapters/llm/openai.py +483 -0
- codeframe/agents/__init__.py +8 -0
- codeframe/agents/dependency_resolver.py +714 -0
- codeframe/auth/__init__.py +16 -0
- codeframe/auth/api_key_router.py +238 -0
- codeframe/auth/api_keys.py +156 -0
- codeframe/auth/dependencies.py +358 -0
- codeframe/auth/manager.py +178 -0
- codeframe/auth/models.py +30 -0
- codeframe/auth/router.py +93 -0
- codeframe/auth/schemas.py +15 -0
- codeframe/auth/scopes.py +53 -0
- codeframe/cli/__init__.py +12 -0
- codeframe/cli/__main__.py +20 -0
- codeframe/cli/api_client.py +275 -0
- codeframe/cli/app.py +5688 -0
- codeframe/cli/auth.py +122 -0
- codeframe/cli/auth_commands.py +958 -0
- codeframe/cli/commands/__init__.py +5 -0
- codeframe/cli/config_commands.py +79 -0
- codeframe/cli/dashboard_commands.py +67 -0
- codeframe/cli/engines_commands.py +205 -0
- codeframe/cli/env_commands.py +409 -0
- codeframe/cli/helpers.py +56 -0
- codeframe/cli/hooks_commands.py +208 -0
- codeframe/cli/import_commands.py +129 -0
- codeframe/cli/pr_commands.py +549 -0
- codeframe/cli/proof_commands.py +415 -0
- codeframe/cli/stats_commands.py +311 -0
- codeframe/cli/telemetry_runtime.py +153 -0
- codeframe/cli/validators.py +123 -0
- codeframe/config/rate_limits.py +165 -0
- codeframe/core/__init__.py +15 -0
- codeframe/core/adapters/__init__.py +43 -0
- codeframe/core/adapters/agent_adapter.py +114 -0
- codeframe/core/adapters/builtin.py +326 -0
- codeframe/core/adapters/claude_code.py +62 -0
- codeframe/core/adapters/codex.py +393 -0
- codeframe/core/adapters/git_utils.py +40 -0
- codeframe/core/adapters/kilocode.py +126 -0
- codeframe/core/adapters/opencode.py +48 -0
- codeframe/core/adapters/streaming_chat.py +483 -0
- codeframe/core/adapters/subprocess_adapter.py +213 -0
- codeframe/core/adapters/verification_wrapper.py +269 -0
- codeframe/core/agent.py +2183 -0
- codeframe/core/agents_config.py +569 -0
- codeframe/core/api_key_service.py +211 -0
- codeframe/core/artifacts.py +428 -0
- codeframe/core/blocker_detection.py +218 -0
- codeframe/core/blockers.py +433 -0
- codeframe/core/checkpoints.py +481 -0
- codeframe/core/conductor.py +2255 -0
- codeframe/core/config.py +827 -0
- codeframe/core/config_watcher.py +268 -0
- codeframe/core/context.py +542 -0
- codeframe/core/context_packager.py +234 -0
- codeframe/core/credentials.py +735 -0
- codeframe/core/dependency_analyzer.py +229 -0
- codeframe/core/dependency_graph.py +290 -0
- codeframe/core/diagnostic_agent.py +712 -0
- codeframe/core/diagnostics.py +616 -0
- codeframe/core/editor.py +556 -0
- codeframe/core/engine_registry.py +256 -0
- codeframe/core/engine_stats.py +231 -0
- codeframe/core/environment.py +697 -0
- codeframe/core/events.py +375 -0
- codeframe/core/executor.py +1005 -0
- codeframe/core/fix_tracker.py +480 -0
- codeframe/core/gates.py +1322 -0
- codeframe/core/git.py +477 -0
- codeframe/core/github_connect_service.py +178 -0
- codeframe/core/github_integration_config.py +118 -0
- codeframe/core/github_issues_service.py +449 -0
- codeframe/core/hooks.py +184 -0
- codeframe/core/importers/__init__.py +1 -0
- codeframe/core/importers/ralph.py +540 -0
- codeframe/core/installer.py +650 -0
- codeframe/core/models.py +1026 -0
- codeframe/core/notifications_config.py +183 -0
- codeframe/core/planner.py +437 -0
- codeframe/core/prd.py +670 -0
- codeframe/core/prd_discovery.py +1118 -0
- codeframe/core/prd_stress_test.py +499 -0
- codeframe/core/progress.py +126 -0
- codeframe/core/proof/__init__.py +34 -0
- codeframe/core/proof/capture.py +79 -0
- codeframe/core/proof/evidence.py +56 -0
- codeframe/core/proof/ledger.py +574 -0
- codeframe/core/proof/models.py +162 -0
- codeframe/core/proof/obligations.py +103 -0
- codeframe/core/proof/runner.py +233 -0
- codeframe/core/proof/scope.py +81 -0
- codeframe/core/proof/stubs.py +156 -0
- codeframe/core/quick_fixes.py +558 -0
- codeframe/core/react_agent.py +1650 -0
- codeframe/core/reconciliation.py +183 -0
- codeframe/core/replay.py +788 -0
- codeframe/core/review.py +285 -0
- codeframe/core/runtime.py +1134 -0
- codeframe/core/sandbox/__init__.py +27 -0
- codeframe/core/sandbox/context.py +98 -0
- codeframe/core/sandbox/worktree.py +20 -0
- codeframe/core/schedule.py +396 -0
- codeframe/core/stall_detector.py +71 -0
- codeframe/core/stall_monitor.py +134 -0
- codeframe/core/state_machine.py +121 -0
- codeframe/core/streaming.py +502 -0
- codeframe/core/task_tree.py +400 -0
- codeframe/core/tasks.py +1022 -0
- codeframe/core/telemetry.py +232 -0
- codeframe/core/templates.py +221 -0
- codeframe/core/tools.py +942 -0
- codeframe/core/workspace.py +887 -0
- codeframe/core/worktrees.py +276 -0
- codeframe/git/__init__.py +5 -0
- codeframe/git/github_integration.py +505 -0
- codeframe/lib/__init__.py +0 -0
- codeframe/lib/audit_logger.py +248 -0
- codeframe/lib/metrics_tracker.py +800 -0
- codeframe/lib/quality/__init__.py +7 -0
- codeframe/lib/quality/complexity_analyzer.py +316 -0
- codeframe/lib/quality/owasp_patterns.py +284 -0
- codeframe/lib/quality/security_scanner.py +250 -0
- codeframe/lib/rate_limiter.py +312 -0
- codeframe/notifications/__init__.py +0 -0
- codeframe/notifications/webhook.py +380 -0
- codeframe/planning/__init__.py +30 -0
- codeframe/planning/issue_generator.py +219 -0
- codeframe/planning/prd_template_functions.py +137 -0
- codeframe/planning/prd_templates.py +975 -0
- codeframe/planning/task_scheduler.py +511 -0
- codeframe/planning/task_templates.py +533 -0
- codeframe/platform_store/__init__.py +5 -0
- codeframe/platform_store/database.py +277 -0
- codeframe/platform_store/repositories/__init__.py +24 -0
- codeframe/platform_store/repositories/api_key_repository.py +245 -0
- codeframe/platform_store/repositories/audit_repository.py +67 -0
- codeframe/platform_store/repositories/base.py +295 -0
- codeframe/platform_store/repositories/interactive_sessions.py +165 -0
- codeframe/platform_store/repositories/token_repository.py +598 -0
- codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
- codeframe/platform_store/schema_manager.py +321 -0
- codeframe/templates/AGENTS.md.default +94 -0
- codeframe/tui/__init__.py +5 -0
- codeframe/tui/app.py +256 -0
- codeframe/tui/data_service.py +103 -0
- codeframe/ui/__init__.py +0 -0
- codeframe/ui/dependencies.py +103 -0
- codeframe/ui/models.py +999 -0
- codeframe/ui/response_models.py +201 -0
- codeframe/ui/routers/__init__.py +5 -0
- codeframe/ui/routers/_helpers.py +29 -0
- codeframe/ui/routers/batches_v2.py +315 -0
- codeframe/ui/routers/blockers_v2.py +320 -0
- codeframe/ui/routers/checkpoints_v2.py +310 -0
- codeframe/ui/routers/costs_v2.py +322 -0
- codeframe/ui/routers/diagnose_v2.py +225 -0
- codeframe/ui/routers/discovery_v2.py +417 -0
- codeframe/ui/routers/environment_v2.py +284 -0
- codeframe/ui/routers/events_v2.py +75 -0
- codeframe/ui/routers/gates_v2.py +166 -0
- codeframe/ui/routers/git_v2.py +284 -0
- codeframe/ui/routers/github_integrations_v2.py +532 -0
- codeframe/ui/routers/interactive_sessions_v2.py +238 -0
- codeframe/ui/routers/pr_v2.py +709 -0
- codeframe/ui/routers/prd_v2.py +695 -0
- codeframe/ui/routers/proof_v2.py +755 -0
- codeframe/ui/routers/review_v2.py +360 -0
- codeframe/ui/routers/schedule_v2.py +214 -0
- codeframe/ui/routers/session_chat_ws.py +354 -0
- codeframe/ui/routers/settings_v2.py +562 -0
- codeframe/ui/routers/streaming_v2.py +155 -0
- codeframe/ui/routers/tasks_v2.py +1098 -0
- codeframe/ui/routers/templates_v2.py +232 -0
- codeframe/ui/routers/terminal_ws.py +267 -0
- codeframe/ui/routers/workspace_v2.py +527 -0
- codeframe/ui/server.py +568 -0
- codeframe/ui/shared.py +241 -0
- codeframe/workspace/__init__.py +5 -0
- codeframe/workspace/manager.py +249 -0
- codeframe_ai-0.9.0.dist-info/METADATA +517 -0
- codeframe_ai-0.9.0.dist-info/RECORD +197 -0
- codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
- codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
- codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
- codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""`cf config` — machine-wide CodeFRAME configuration (issue #616)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from codeframe.core import telemetry as telemetry_core
|
|
9
|
+
|
|
10
|
+
config_app = typer.Typer(
|
|
11
|
+
name="config",
|
|
12
|
+
help="Manage machine-wide CodeFRAME configuration",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@config_app.command()
|
|
20
|
+
def telemetry(
|
|
21
|
+
action: str = typer.Argument(..., help="on | off | status"),
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Enable, disable, or inspect anonymous telemetry (default: off).
|
|
24
|
+
|
|
25
|
+
See PRIVACY.md for exactly what is collected. Env overrides:
|
|
26
|
+
CODEFRAME_TELEMETRY=on|off always wins; DO_NOT_TRACK disables.
|
|
27
|
+
"""
|
|
28
|
+
action = action.strip().lower()
|
|
29
|
+
if action not in ("on", "off", "status"):
|
|
30
|
+
console.print(f"[red]Error:[/red] unknown action '{action}' (expected on|off|status)")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
if action == "status":
|
|
34
|
+
_print_status()
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
config = telemetry_core.load_config()
|
|
38
|
+
config.enabled = action == "on"
|
|
39
|
+
config.prompted = True
|
|
40
|
+
telemetry_core.save_config(config)
|
|
41
|
+
|
|
42
|
+
if config.enabled:
|
|
43
|
+
console.print(
|
|
44
|
+
"[green]Telemetry enabled.[/green] Anonymous usage events and crash "
|
|
45
|
+
"reports will be sent — see PRIVACY.md for exactly what is collected."
|
|
46
|
+
)
|
|
47
|
+
console.print(f"Anonymous id: [dim]{config.anonymous_id}[/dim]")
|
|
48
|
+
else:
|
|
49
|
+
console.print("[yellow]Telemetry disabled.[/yellow] Nothing will be sent.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _print_status() -> None:
|
|
53
|
+
# Resolve the effective state from this single config read (rather than
|
|
54
|
+
# is_enabled(), which re-reads) so the displayed state and anonymous id
|
|
55
|
+
# can't disagree if the file changes between two loads.
|
|
56
|
+
config = telemetry_core.load_config()
|
|
57
|
+
override = telemetry_core.env_override()
|
|
58
|
+
dnt_active = os.environ.get("DO_NOT_TRACK", "").strip().lower() not in ("", "0", "false")
|
|
59
|
+
if override is not None:
|
|
60
|
+
effective = override
|
|
61
|
+
elif dnt_active:
|
|
62
|
+
effective = False
|
|
63
|
+
else:
|
|
64
|
+
effective = config.enabled
|
|
65
|
+
state = "[green]enabled[/green]" if effective else "[yellow]disabled[/yellow]"
|
|
66
|
+
console.print(f"Telemetry: {state}")
|
|
67
|
+
|
|
68
|
+
if override is not None:
|
|
69
|
+
console.print(
|
|
70
|
+
f" (overridden by CODEFRAME_TELEMETRY={os.environ['CODEFRAME_TELEMETRY']})"
|
|
71
|
+
)
|
|
72
|
+
elif dnt_active:
|
|
73
|
+
console.print(" (disabled by DO_NOT_TRACK)")
|
|
74
|
+
|
|
75
|
+
console.print(f" Config file: {telemetry_core.config_path()}")
|
|
76
|
+
console.print(f" Endpoint: {telemetry_core.resolve_endpoint()}")
|
|
77
|
+
if telemetry_core.config_path().exists():
|
|
78
|
+
console.print(f" Anonymous id: {config.anonymous_id}")
|
|
79
|
+
console.print(" Details: PRIVACY.md | Change: cf config telemetry on|off")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""CLI command for launching the TUI dashboard.
|
|
2
|
+
|
|
3
|
+
Provides `cf dashboard` to launch the Textual-based terminal dashboard.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
dashboard_app = typer.Typer(
|
|
15
|
+
name="dashboard",
|
|
16
|
+
help="Terminal dashboard for monitoring workspace state",
|
|
17
|
+
invoke_without_command=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dashboard_app.callback(invoke_without_command=True)
|
|
22
|
+
def dashboard(
|
|
23
|
+
repo_path: Optional[Path] = typer.Option(
|
|
24
|
+
None,
|
|
25
|
+
"--workspace", "-w",
|
|
26
|
+
help="Workspace path (defaults to current directory)",
|
|
27
|
+
),
|
|
28
|
+
refresh_interval: int = typer.Option(
|
|
29
|
+
2,
|
|
30
|
+
"--refresh-interval",
|
|
31
|
+
min=1,
|
|
32
|
+
max=60,
|
|
33
|
+
help="Seconds between data refreshes (default: 2)",
|
|
34
|
+
),
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Launch the TUI dashboard.
|
|
37
|
+
|
|
38
|
+
Shows a live terminal dashboard with task board, event log,
|
|
39
|
+
and blocker notifications. Updates automatically.
|
|
40
|
+
|
|
41
|
+
Keyboard shortcuts:
|
|
42
|
+
q Quit
|
|
43
|
+
r Force refresh
|
|
44
|
+
Tab Switch panels
|
|
45
|
+
Up/Down Navigate rows
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
codeframe dashboard
|
|
49
|
+
codeframe dashboard --refresh-interval 5
|
|
50
|
+
"""
|
|
51
|
+
from codeframe.core.workspace import get_workspace
|
|
52
|
+
from codeframe.tui.app import DashboardApp
|
|
53
|
+
|
|
54
|
+
workspace_path = repo_path or Path.cwd()
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
workspace = get_workspace(workspace_path)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
60
|
+
console.print("[dim]Run 'codeframe init' to create a workspace first.[/dim]")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
app = DashboardApp(
|
|
64
|
+
workspace=workspace,
|
|
65
|
+
refresh_interval=refresh_interval,
|
|
66
|
+
)
|
|
67
|
+
app.run()
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""CLI engine management commands.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
codeframe engines list # Show available engines
|
|
5
|
+
codeframe engines check <name> # Check engine requirements
|
|
6
|
+
codeframe engines stats # Show engine performance stats
|
|
7
|
+
codeframe engines compare # Compare engine performance
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json as _json
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from codeframe.core import engine_stats
|
|
20
|
+
from codeframe.core.workspace import get_workspace
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
engines_app = typer.Typer(
|
|
27
|
+
name="engines",
|
|
28
|
+
help="Engine management commands",
|
|
29
|
+
no_args_is_help=True,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@engines_app.command("list")
|
|
34
|
+
def engines_list() -> None:
|
|
35
|
+
"""List all available execution engines and their requirement status."""
|
|
36
|
+
from codeframe.core.engine_registry import VALID_ENGINES, EXTERNAL_ENGINES, check_requirements
|
|
37
|
+
|
|
38
|
+
table = Table(title="Available Engines")
|
|
39
|
+
table.add_column("Engine", style="cyan")
|
|
40
|
+
table.add_column("Type", style="dim")
|
|
41
|
+
table.add_column("Requirements", style="green")
|
|
42
|
+
|
|
43
|
+
for engine in sorted(VALID_ENGINES):
|
|
44
|
+
engine_type = "external" if engine in EXTERNAL_ENGINES else "builtin"
|
|
45
|
+
if engine == "built-in":
|
|
46
|
+
engine_type = "alias → react"
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
reqs = check_requirements(engine)
|
|
50
|
+
except ValueError:
|
|
51
|
+
reqs = {}
|
|
52
|
+
|
|
53
|
+
if not reqs:
|
|
54
|
+
req_display = "[green]Ready[/green]"
|
|
55
|
+
else:
|
|
56
|
+
parts = []
|
|
57
|
+
for key, satisfied in reqs.items():
|
|
58
|
+
mark = "[green]✓[/green]" if satisfied else "[red]✗[/red]"
|
|
59
|
+
parts.append(f"{mark} {key}")
|
|
60
|
+
req_display = ", ".join(parts)
|
|
61
|
+
|
|
62
|
+
table.add_row(engine, engine_type, req_display)
|
|
63
|
+
|
|
64
|
+
console.print(table)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@engines_app.command("check")
|
|
68
|
+
def engines_check(
|
|
69
|
+
name: str = typer.Argument(..., help="Engine name to check"),
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Check if an engine's requirements are satisfied."""
|
|
72
|
+
from codeframe.core.engine_registry import check_requirements
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
reqs = check_requirements(name)
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
78
|
+
raise typer.Exit(1)
|
|
79
|
+
|
|
80
|
+
if not reqs:
|
|
81
|
+
console.print(f"[green]Engine '{name}' has no additional requirements.[/green]")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
all_satisfied = True
|
|
85
|
+
for key, satisfied in reqs.items():
|
|
86
|
+
if satisfied:
|
|
87
|
+
console.print(f" [green]✓[/green] {key}")
|
|
88
|
+
else:
|
|
89
|
+
console.print(f" [red]✗[/red] {key} — not set")
|
|
90
|
+
all_satisfied = False
|
|
91
|
+
|
|
92
|
+
if all_satisfied:
|
|
93
|
+
console.print(f"\n[green]Engine '{name}' is ready.[/green]")
|
|
94
|
+
else:
|
|
95
|
+
console.print(f"\n[red]Engine '{name}' has unmet requirements.[/red]")
|
|
96
|
+
raise typer.Exit(1)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_current_workspace():
|
|
100
|
+
"""Get workspace from current working directory.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Workspace object.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
typer.Exit: If no workspace is found.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
return get_workspace(Path.cwd())
|
|
110
|
+
except (FileNotFoundError, ValueError):
|
|
111
|
+
console.print("[red]Error:[/red] No workspace found. Run 'cf init' first.")
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _compute_success_rate(metrics: dict[str, float]) -> float:
|
|
116
|
+
"""Compute success rate from engine metrics."""
|
|
117
|
+
attempted = metrics.get("tasks_attempted", 0)
|
|
118
|
+
completed = metrics.get("tasks_completed", 0)
|
|
119
|
+
if attempted == 0:
|
|
120
|
+
return 0.0
|
|
121
|
+
return round(100.0 * completed / attempted, 1)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _format_duration(ms: float) -> str:
|
|
125
|
+
"""Format duration in human-readable form."""
|
|
126
|
+
if ms < 1000:
|
|
127
|
+
return f"{ms:.0f}ms"
|
|
128
|
+
return f"{ms / 1000:.1f}s"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_stats_table(stats: dict[str, dict[str, float]], title: str = "Engine Stats") -> Table:
|
|
132
|
+
"""Build a Rich table from engine stats."""
|
|
133
|
+
table = Table(title=title)
|
|
134
|
+
table.add_column("Engine", style="cyan")
|
|
135
|
+
table.add_column("Tasks", justify="right")
|
|
136
|
+
table.add_column("Success %", justify="right", style="green")
|
|
137
|
+
table.add_column("Gate Pass %", justify="right")
|
|
138
|
+
table.add_column("Avg Duration", justify="right")
|
|
139
|
+
table.add_column("Total Tokens", justify="right")
|
|
140
|
+
table.add_column("Avg Tokens/Task", justify="right")
|
|
141
|
+
|
|
142
|
+
# Sort by success rate descending
|
|
143
|
+
sorted_engines = sorted(
|
|
144
|
+
stats.items(),
|
|
145
|
+
key=lambda item: _compute_success_rate(item[1]),
|
|
146
|
+
reverse=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
for eng, metrics in sorted_engines:
|
|
150
|
+
success_rate = _compute_success_rate(metrics)
|
|
151
|
+
gate_rate = metrics.get("gate_pass_rate", 0.0)
|
|
152
|
+
avg_dur = metrics.get("avg_duration_ms", 0.0)
|
|
153
|
+
total_tok = metrics.get("total_tokens", 0.0)
|
|
154
|
+
avg_tok = metrics.get("avg_tokens_per_task", 0.0)
|
|
155
|
+
attempted = int(metrics.get("tasks_attempted", 0))
|
|
156
|
+
|
|
157
|
+
table.add_row(
|
|
158
|
+
eng,
|
|
159
|
+
str(attempted),
|
|
160
|
+
f"{success_rate}%",
|
|
161
|
+
f"{gate_rate}%",
|
|
162
|
+
_format_duration(avg_dur),
|
|
163
|
+
f"{int(total_tok):,}",
|
|
164
|
+
f"{int(avg_tok):,}",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return table
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@engines_app.command("stats")
|
|
171
|
+
def stats(
|
|
172
|
+
engine: Optional[str] = typer.Option(None, "--engine", "-e", help="Filter by engine name"),
|
|
173
|
+
output_format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Show engine performance statistics."""
|
|
176
|
+
workspace = _get_current_workspace()
|
|
177
|
+
data = engine_stats.get_engine_stats(workspace, engine=engine)
|
|
178
|
+
|
|
179
|
+
if not data:
|
|
180
|
+
console.print("[yellow]No engine stats recorded yet.[/yellow]")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
if output_format == "json":
|
|
184
|
+
console.print(_json.dumps(data, indent=2))
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
table = _build_stats_table(data, title="Engine Performance Stats")
|
|
188
|
+
console.print(table)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@engines_app.command("compare")
|
|
192
|
+
def compare() -> None:
|
|
193
|
+
"""Compare performance across all engines."""
|
|
194
|
+
workspace = _get_current_workspace()
|
|
195
|
+
data = engine_stats.get_engine_stats(workspace)
|
|
196
|
+
|
|
197
|
+
if not data:
|
|
198
|
+
console.print(
|
|
199
|
+
"[yellow]No engine stats recorded yet. "
|
|
200
|
+
"Run tasks with different engines to see comparison.[/yellow]"
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
table = _build_stats_table(data, title="Engine Comparison (sorted by success rate)")
|
|
205
|
+
console.print(table)
|