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,134 @@
|
|
|
1
|
+
"""Stall detection monitor for agent execution.
|
|
2
|
+
|
|
3
|
+
Detects when an agent stops making progress (no tool calls for a
|
|
4
|
+
configurable duration) and triggers a callback. Uses a daemon thread
|
|
5
|
+
that polls at regular intervals.
|
|
6
|
+
|
|
7
|
+
This module is headless - no FastAPI or HTTP dependencies.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import threading
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Callable, Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class StallEvent:
|
|
21
|
+
"""Information about a detected stall.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
task_id: The task that stalled.
|
|
25
|
+
stall_timeout_s: Configured timeout threshold.
|
|
26
|
+
elapsed_s: Seconds since last tool execution.
|
|
27
|
+
last_tool_call_at: Timestamp of the last tool execution (None if no tools ran).
|
|
28
|
+
iterations_completed: Number of loop iterations completed before stall.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
task_id: str
|
|
32
|
+
stall_timeout_s: float
|
|
33
|
+
elapsed_s: float
|
|
34
|
+
last_tool_call_at: Optional[datetime]
|
|
35
|
+
iterations_completed: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StallMonitor:
|
|
39
|
+
"""Thread-based watchdog that fires when agent activity stops.
|
|
40
|
+
|
|
41
|
+
Create one instance per agent invocation. The monitor is stateless
|
|
42
|
+
between runs.
|
|
43
|
+
|
|
44
|
+
Usage::
|
|
45
|
+
|
|
46
|
+
monitor = StallMonitor(stall_timeout_s=300, on_stall=my_callback)
|
|
47
|
+
monitor.start("task-123")
|
|
48
|
+
try:
|
|
49
|
+
for iteration in agent_loop:
|
|
50
|
+
# ... do work ...
|
|
51
|
+
monitor.notify_tool_executed("task-123", iteration)
|
|
52
|
+
finally:
|
|
53
|
+
monitor.stop()
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
stall_timeout_s: float,
|
|
59
|
+
on_stall: Callable[[StallEvent], None],
|
|
60
|
+
poll_interval_s: float = 5.0,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._stall_timeout_s = stall_timeout_s
|
|
63
|
+
self._on_stall = on_stall
|
|
64
|
+
self._poll_interval_s = poll_interval_s
|
|
65
|
+
|
|
66
|
+
self._task_id: Optional[str] = None
|
|
67
|
+
self._last_activity: Optional[datetime] = None
|
|
68
|
+
self._last_tool_call_at: Optional[datetime] = None
|
|
69
|
+
self._iterations: int = 0
|
|
70
|
+
self._lock = threading.Lock()
|
|
71
|
+
self._stop_event = threading.Event()
|
|
72
|
+
self._thread: Optional[threading.Thread] = None
|
|
73
|
+
|
|
74
|
+
def start(self, task_id: str) -> None:
|
|
75
|
+
"""Begin monitoring for the given task."""
|
|
76
|
+
if self._stall_timeout_s <= 0:
|
|
77
|
+
return # Disabled
|
|
78
|
+
|
|
79
|
+
self.stop() # Clean up any previous run
|
|
80
|
+
self._task_id = task_id
|
|
81
|
+
self._last_activity = datetime.now(timezone.utc)
|
|
82
|
+
self._iterations = 0
|
|
83
|
+
self._stop_event.clear()
|
|
84
|
+
|
|
85
|
+
self._thread = threading.Thread(
|
|
86
|
+
target=self._watch_loop,
|
|
87
|
+
name=f"stall-monitor-{task_id}",
|
|
88
|
+
daemon=True,
|
|
89
|
+
)
|
|
90
|
+
self._thread.start()
|
|
91
|
+
|
|
92
|
+
def stop(self) -> None:
|
|
93
|
+
"""Stop the watcher thread."""
|
|
94
|
+
self._stop_event.set()
|
|
95
|
+
if self._thread is not None:
|
|
96
|
+
self._thread.join(timeout=2.0)
|
|
97
|
+
self._thread = None
|
|
98
|
+
|
|
99
|
+
def notify_tool_executed(self, task_id: str, iteration: int) -> None:
|
|
100
|
+
"""Record that a tool was successfully executed.
|
|
101
|
+
|
|
102
|
+
Call this after each successful tool execution to reset the
|
|
103
|
+
inactivity timer.
|
|
104
|
+
"""
|
|
105
|
+
with self._lock:
|
|
106
|
+
now = datetime.now(timezone.utc)
|
|
107
|
+
self._last_activity = now
|
|
108
|
+
self._last_tool_call_at = now
|
|
109
|
+
self._iterations = iteration
|
|
110
|
+
|
|
111
|
+
def _watch_loop(self) -> None:
|
|
112
|
+
"""Daemon thread loop: check for stall at regular intervals."""
|
|
113
|
+
while not self._stop_event.wait(timeout=self._poll_interval_s):
|
|
114
|
+
with self._lock:
|
|
115
|
+
if self._last_activity is None:
|
|
116
|
+
continue
|
|
117
|
+
elapsed = (datetime.now(timezone.utc) - self._last_activity).total_seconds()
|
|
118
|
+
iterations = self._iterations
|
|
119
|
+
last_tool_call = self._last_tool_call_at
|
|
120
|
+
|
|
121
|
+
if elapsed >= self._stall_timeout_s:
|
|
122
|
+
event = StallEvent(
|
|
123
|
+
task_id=self._task_id or "",
|
|
124
|
+
stall_timeout_s=self._stall_timeout_s,
|
|
125
|
+
elapsed_s=elapsed,
|
|
126
|
+
last_tool_call_at=last_tool_call,
|
|
127
|
+
iterations_completed=iterations,
|
|
128
|
+
)
|
|
129
|
+
try:
|
|
130
|
+
self._on_stall(event)
|
|
131
|
+
except Exception:
|
|
132
|
+
logger.exception("Stall callback failed")
|
|
133
|
+
self._stop_event.set()
|
|
134
|
+
return
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Task state machine for CodeFRAME v2.
|
|
2
|
+
|
|
3
|
+
Defines the authoritative task statuses and allowed transitions.
|
|
4
|
+
Per GOLDEN_PATH.md, the CLI is the authority for transitions.
|
|
5
|
+
|
|
6
|
+
Statuses:
|
|
7
|
+
- BACKLOG: Task identified but not ready to work on
|
|
8
|
+
- READY: Task is ready to be started
|
|
9
|
+
- IN_PROGRESS: Task is actively being worked on
|
|
10
|
+
- BLOCKED: Task is blocked by a blocker (human-in-the-loop)
|
|
11
|
+
- DONE: Task completed successfully
|
|
12
|
+
- MERGED: Task changes merged (optional, for later)
|
|
13
|
+
|
|
14
|
+
This module is headless - no FastAPI or HTTP dependencies.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Set
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskStatus(str, Enum):
|
|
22
|
+
"""Task execution status.
|
|
23
|
+
|
|
24
|
+
Uses str mixin for easy JSON serialization and SQLite storage.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
BACKLOG = "BACKLOG"
|
|
28
|
+
READY = "READY"
|
|
29
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
30
|
+
BLOCKED = "BLOCKED"
|
|
31
|
+
FAILED = "FAILED" # Task execution failed (technical error, needs retry)
|
|
32
|
+
DONE = "DONE"
|
|
33
|
+
MERGED = "MERGED"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Allowed status transitions (from -> set of allowed targets)
|
|
37
|
+
ALLOWED_TRANSITIONS: dict[TaskStatus, Set[TaskStatus]] = {
|
|
38
|
+
TaskStatus.BACKLOG: {TaskStatus.READY},
|
|
39
|
+
TaskStatus.READY: {TaskStatus.IN_PROGRESS, TaskStatus.BACKLOG},
|
|
40
|
+
TaskStatus.IN_PROGRESS: {TaskStatus.BLOCKED, TaskStatus.DONE, TaskStatus.READY, TaskStatus.FAILED},
|
|
41
|
+
TaskStatus.BLOCKED: {TaskStatus.IN_PROGRESS, TaskStatus.READY},
|
|
42
|
+
TaskStatus.FAILED: {TaskStatus.READY, TaskStatus.IN_PROGRESS}, # Can retry from FAILED
|
|
43
|
+
TaskStatus.DONE: {TaskStatus.READY, TaskStatus.MERGED},
|
|
44
|
+
TaskStatus.MERGED: set(), # Terminal state
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InvalidTransitionError(Exception):
|
|
49
|
+
"""Raised when an invalid status transition is attempted."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, current: TaskStatus, target: TaskStatus):
|
|
52
|
+
self.current = current
|
|
53
|
+
self.target = target
|
|
54
|
+
allowed = ALLOWED_TRANSITIONS.get(current, set())
|
|
55
|
+
allowed_str = ", ".join(s.value for s in allowed) if allowed else "none"
|
|
56
|
+
super().__init__(
|
|
57
|
+
f"Invalid transition: {current.value} -> {target.value}. "
|
|
58
|
+
f"Allowed transitions from {current.value}: {allowed_str}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def can_transition(current: TaskStatus, target: TaskStatus) -> bool:
|
|
63
|
+
"""Check if a status transition is allowed.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
current: Current task status
|
|
67
|
+
target: Desired target status
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if transition is allowed, False otherwise
|
|
71
|
+
"""
|
|
72
|
+
allowed = ALLOWED_TRANSITIONS.get(current, set())
|
|
73
|
+
return target in allowed
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate_transition(current: TaskStatus, target: TaskStatus) -> None:
|
|
77
|
+
"""Validate a status transition, raising if invalid.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
current: Current task status
|
|
81
|
+
target: Desired target status
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
InvalidTransitionError: If the transition is not allowed
|
|
85
|
+
"""
|
|
86
|
+
if not can_transition(current, target):
|
|
87
|
+
raise InvalidTransitionError(current, target)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_allowed_transitions(current: TaskStatus) -> Set[TaskStatus]:
|
|
91
|
+
"""Get the set of statuses that can be transitioned to from current.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
current: Current task status
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Set of allowed target statuses
|
|
98
|
+
"""
|
|
99
|
+
return ALLOWED_TRANSITIONS.get(current, set()).copy()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def parse_status(value: str) -> TaskStatus:
|
|
103
|
+
"""Parse a string into a TaskStatus.
|
|
104
|
+
|
|
105
|
+
Accepts both uppercase and lowercase input.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
value: Status string (e.g., "READY", "ready", "in_progress")
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
TaskStatus enum value
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: If the string doesn't match any status
|
|
115
|
+
"""
|
|
116
|
+
normalized = value.upper().replace("-", "_")
|
|
117
|
+
try:
|
|
118
|
+
return TaskStatus(normalized)
|
|
119
|
+
except ValueError:
|
|
120
|
+
valid = ", ".join(s.value for s in TaskStatus)
|
|
121
|
+
raise ValueError(f"Invalid status '{value}'. Valid statuses: {valid}")
|