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.
Files changed (197) hide show
  1. codeframe/__init__.py +11 -0
  2. codeframe/__main__.py +20 -0
  3. codeframe/adapters/__init__.py +5 -0
  4. codeframe/adapters/e2b/__init__.py +13 -0
  5. codeframe/adapters/e2b/adapter.py +342 -0
  6. codeframe/adapters/e2b/budget.py +71 -0
  7. codeframe/adapters/e2b/credential_scanner.py +134 -0
  8. codeframe/adapters/llm/__init__.py +92 -0
  9. codeframe/adapters/llm/anthropic.py +414 -0
  10. codeframe/adapters/llm/base.py +444 -0
  11. codeframe/adapters/llm/mock.py +281 -0
  12. codeframe/adapters/llm/openai.py +483 -0
  13. codeframe/agents/__init__.py +8 -0
  14. codeframe/agents/dependency_resolver.py +714 -0
  15. codeframe/auth/__init__.py +16 -0
  16. codeframe/auth/api_key_router.py +238 -0
  17. codeframe/auth/api_keys.py +156 -0
  18. codeframe/auth/dependencies.py +358 -0
  19. codeframe/auth/manager.py +178 -0
  20. codeframe/auth/models.py +30 -0
  21. codeframe/auth/router.py +93 -0
  22. codeframe/auth/schemas.py +15 -0
  23. codeframe/auth/scopes.py +53 -0
  24. codeframe/cli/__init__.py +12 -0
  25. codeframe/cli/__main__.py +20 -0
  26. codeframe/cli/api_client.py +275 -0
  27. codeframe/cli/app.py +5688 -0
  28. codeframe/cli/auth.py +122 -0
  29. codeframe/cli/auth_commands.py +958 -0
  30. codeframe/cli/commands/__init__.py +5 -0
  31. codeframe/cli/config_commands.py +79 -0
  32. codeframe/cli/dashboard_commands.py +67 -0
  33. codeframe/cli/engines_commands.py +205 -0
  34. codeframe/cli/env_commands.py +409 -0
  35. codeframe/cli/helpers.py +56 -0
  36. codeframe/cli/hooks_commands.py +208 -0
  37. codeframe/cli/import_commands.py +129 -0
  38. codeframe/cli/pr_commands.py +549 -0
  39. codeframe/cli/proof_commands.py +415 -0
  40. codeframe/cli/stats_commands.py +311 -0
  41. codeframe/cli/telemetry_runtime.py +153 -0
  42. codeframe/cli/validators.py +123 -0
  43. codeframe/config/rate_limits.py +165 -0
  44. codeframe/core/__init__.py +15 -0
  45. codeframe/core/adapters/__init__.py +43 -0
  46. codeframe/core/adapters/agent_adapter.py +114 -0
  47. codeframe/core/adapters/builtin.py +326 -0
  48. codeframe/core/adapters/claude_code.py +62 -0
  49. codeframe/core/adapters/codex.py +393 -0
  50. codeframe/core/adapters/git_utils.py +40 -0
  51. codeframe/core/adapters/kilocode.py +126 -0
  52. codeframe/core/adapters/opencode.py +48 -0
  53. codeframe/core/adapters/streaming_chat.py +483 -0
  54. codeframe/core/adapters/subprocess_adapter.py +213 -0
  55. codeframe/core/adapters/verification_wrapper.py +269 -0
  56. codeframe/core/agent.py +2183 -0
  57. codeframe/core/agents_config.py +569 -0
  58. codeframe/core/api_key_service.py +211 -0
  59. codeframe/core/artifacts.py +428 -0
  60. codeframe/core/blocker_detection.py +218 -0
  61. codeframe/core/blockers.py +433 -0
  62. codeframe/core/checkpoints.py +481 -0
  63. codeframe/core/conductor.py +2255 -0
  64. codeframe/core/config.py +827 -0
  65. codeframe/core/config_watcher.py +268 -0
  66. codeframe/core/context.py +542 -0
  67. codeframe/core/context_packager.py +234 -0
  68. codeframe/core/credentials.py +735 -0
  69. codeframe/core/dependency_analyzer.py +229 -0
  70. codeframe/core/dependency_graph.py +290 -0
  71. codeframe/core/diagnostic_agent.py +712 -0
  72. codeframe/core/diagnostics.py +616 -0
  73. codeframe/core/editor.py +556 -0
  74. codeframe/core/engine_registry.py +256 -0
  75. codeframe/core/engine_stats.py +231 -0
  76. codeframe/core/environment.py +697 -0
  77. codeframe/core/events.py +375 -0
  78. codeframe/core/executor.py +1005 -0
  79. codeframe/core/fix_tracker.py +480 -0
  80. codeframe/core/gates.py +1322 -0
  81. codeframe/core/git.py +477 -0
  82. codeframe/core/github_connect_service.py +178 -0
  83. codeframe/core/github_integration_config.py +118 -0
  84. codeframe/core/github_issues_service.py +449 -0
  85. codeframe/core/hooks.py +184 -0
  86. codeframe/core/importers/__init__.py +1 -0
  87. codeframe/core/importers/ralph.py +540 -0
  88. codeframe/core/installer.py +650 -0
  89. codeframe/core/models.py +1026 -0
  90. codeframe/core/notifications_config.py +183 -0
  91. codeframe/core/planner.py +437 -0
  92. codeframe/core/prd.py +670 -0
  93. codeframe/core/prd_discovery.py +1118 -0
  94. codeframe/core/prd_stress_test.py +499 -0
  95. codeframe/core/progress.py +126 -0
  96. codeframe/core/proof/__init__.py +34 -0
  97. codeframe/core/proof/capture.py +79 -0
  98. codeframe/core/proof/evidence.py +56 -0
  99. codeframe/core/proof/ledger.py +574 -0
  100. codeframe/core/proof/models.py +162 -0
  101. codeframe/core/proof/obligations.py +103 -0
  102. codeframe/core/proof/runner.py +233 -0
  103. codeframe/core/proof/scope.py +81 -0
  104. codeframe/core/proof/stubs.py +156 -0
  105. codeframe/core/quick_fixes.py +558 -0
  106. codeframe/core/react_agent.py +1650 -0
  107. codeframe/core/reconciliation.py +183 -0
  108. codeframe/core/replay.py +788 -0
  109. codeframe/core/review.py +285 -0
  110. codeframe/core/runtime.py +1134 -0
  111. codeframe/core/sandbox/__init__.py +27 -0
  112. codeframe/core/sandbox/context.py +98 -0
  113. codeframe/core/sandbox/worktree.py +20 -0
  114. codeframe/core/schedule.py +396 -0
  115. codeframe/core/stall_detector.py +71 -0
  116. codeframe/core/stall_monitor.py +134 -0
  117. codeframe/core/state_machine.py +121 -0
  118. codeframe/core/streaming.py +502 -0
  119. codeframe/core/task_tree.py +400 -0
  120. codeframe/core/tasks.py +1022 -0
  121. codeframe/core/telemetry.py +232 -0
  122. codeframe/core/templates.py +221 -0
  123. codeframe/core/tools.py +942 -0
  124. codeframe/core/workspace.py +887 -0
  125. codeframe/core/worktrees.py +276 -0
  126. codeframe/git/__init__.py +5 -0
  127. codeframe/git/github_integration.py +505 -0
  128. codeframe/lib/__init__.py +0 -0
  129. codeframe/lib/audit_logger.py +248 -0
  130. codeframe/lib/metrics_tracker.py +800 -0
  131. codeframe/lib/quality/__init__.py +7 -0
  132. codeframe/lib/quality/complexity_analyzer.py +316 -0
  133. codeframe/lib/quality/owasp_patterns.py +284 -0
  134. codeframe/lib/quality/security_scanner.py +250 -0
  135. codeframe/lib/rate_limiter.py +312 -0
  136. codeframe/notifications/__init__.py +0 -0
  137. codeframe/notifications/webhook.py +380 -0
  138. codeframe/planning/__init__.py +30 -0
  139. codeframe/planning/issue_generator.py +219 -0
  140. codeframe/planning/prd_template_functions.py +137 -0
  141. codeframe/planning/prd_templates.py +975 -0
  142. codeframe/planning/task_scheduler.py +511 -0
  143. codeframe/planning/task_templates.py +533 -0
  144. codeframe/platform_store/__init__.py +5 -0
  145. codeframe/platform_store/database.py +277 -0
  146. codeframe/platform_store/repositories/__init__.py +24 -0
  147. codeframe/platform_store/repositories/api_key_repository.py +245 -0
  148. codeframe/platform_store/repositories/audit_repository.py +67 -0
  149. codeframe/platform_store/repositories/base.py +295 -0
  150. codeframe/platform_store/repositories/interactive_sessions.py +165 -0
  151. codeframe/platform_store/repositories/token_repository.py +598 -0
  152. codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
  153. codeframe/platform_store/schema_manager.py +321 -0
  154. codeframe/templates/AGENTS.md.default +94 -0
  155. codeframe/tui/__init__.py +5 -0
  156. codeframe/tui/app.py +256 -0
  157. codeframe/tui/data_service.py +103 -0
  158. codeframe/ui/__init__.py +0 -0
  159. codeframe/ui/dependencies.py +103 -0
  160. codeframe/ui/models.py +999 -0
  161. codeframe/ui/response_models.py +201 -0
  162. codeframe/ui/routers/__init__.py +5 -0
  163. codeframe/ui/routers/_helpers.py +29 -0
  164. codeframe/ui/routers/batches_v2.py +315 -0
  165. codeframe/ui/routers/blockers_v2.py +320 -0
  166. codeframe/ui/routers/checkpoints_v2.py +310 -0
  167. codeframe/ui/routers/costs_v2.py +322 -0
  168. codeframe/ui/routers/diagnose_v2.py +225 -0
  169. codeframe/ui/routers/discovery_v2.py +417 -0
  170. codeframe/ui/routers/environment_v2.py +284 -0
  171. codeframe/ui/routers/events_v2.py +75 -0
  172. codeframe/ui/routers/gates_v2.py +166 -0
  173. codeframe/ui/routers/git_v2.py +284 -0
  174. codeframe/ui/routers/github_integrations_v2.py +532 -0
  175. codeframe/ui/routers/interactive_sessions_v2.py +238 -0
  176. codeframe/ui/routers/pr_v2.py +709 -0
  177. codeframe/ui/routers/prd_v2.py +695 -0
  178. codeframe/ui/routers/proof_v2.py +755 -0
  179. codeframe/ui/routers/review_v2.py +360 -0
  180. codeframe/ui/routers/schedule_v2.py +214 -0
  181. codeframe/ui/routers/session_chat_ws.py +354 -0
  182. codeframe/ui/routers/settings_v2.py +562 -0
  183. codeframe/ui/routers/streaming_v2.py +155 -0
  184. codeframe/ui/routers/tasks_v2.py +1098 -0
  185. codeframe/ui/routers/templates_v2.py +232 -0
  186. codeframe/ui/routers/terminal_ws.py +267 -0
  187. codeframe/ui/routers/workspace_v2.py +527 -0
  188. codeframe/ui/server.py +568 -0
  189. codeframe/ui/shared.py +241 -0
  190. codeframe/workspace/__init__.py +5 -0
  191. codeframe/workspace/manager.py +249 -0
  192. codeframe_ai-0.9.0.dist-info/METADATA +517 -0
  193. codeframe_ai-0.9.0.dist-info/RECORD +197 -0
  194. codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
  195. codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
  196. codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
  197. codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,268 @@
1
+ """Dynamic configuration reload for batch execution.
2
+
3
+ Watches CODEFRAME.md, AGENTS.md, and CLAUDE.md for changes during
4
+ long-running batch executions and hot-reloads configuration without
5
+ restarting. Inspired by Symphony's dynamic WORKFLOW.md reload pattern.
6
+
7
+ Components:
8
+ - ConfigReloadState: Thread-safe shared state holding current config
9
+ - ConfigFileWatcher: Daemon thread polling file mtimes for changes
10
+
11
+ What reloads dynamically:
12
+ - Agent prompt / system prompt supplement (always_do, ask_first, never_do, raw_content)
13
+ - Tool preferences (tooling, commands)
14
+ - Code style preferences (code_style)
15
+
16
+ What requires restart (not reloaded):
17
+ - Engine selection (react/plan)
18
+ - Workspace path
19
+ - Tech stack
20
+
21
+ This module is headless - no FastAPI or HTTP dependencies.
22
+ """
23
+
24
+ import logging
25
+ import threading
26
+ from datetime import datetime, timezone
27
+ from pathlib import Path
28
+ from typing import Optional
29
+
30
+ from codeframe.core.agents_config import AgentPreferences, load_preferences
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def _utc_now() -> datetime:
36
+ return datetime.now(timezone.utc)
37
+
38
+
39
+ # Config files to watch, in priority order
40
+ _CONFIG_FILES = ("CODEFRAME.md", "AGENTS.md", "CLAUDE.md")
41
+
42
+
43
+ class ConfigReloadState:
44
+ """Thread-safe shared state for dynamic config reload.
45
+
46
+ Holds the last-known-good configuration and tracks reload history.
47
+ Accessed by the watcher thread (writes) and the conductor (reads).
48
+ """
49
+
50
+ def __init__(self, initial_prefs: AgentPreferences) -> None:
51
+ self._prefs = initial_prefs
52
+ self._lock = threading.Lock()
53
+ self.last_reload_at: Optional[datetime] = None
54
+ self.last_error: Optional[str] = None
55
+ self.reload_timestamps: list[str] = []
56
+
57
+ def get_prefs(self) -> AgentPreferences:
58
+ with self._lock:
59
+ return self._prefs
60
+
61
+ def apply_reload(self, new_prefs: AgentPreferences, timestamp: datetime) -> None:
62
+ with self._lock:
63
+ self._prefs = new_prefs
64
+ self.last_reload_at = timestamp
65
+ self.last_error = None
66
+ self.reload_timestamps.append(timestamp.isoformat())
67
+ if len(self.reload_timestamps) > 100:
68
+ self.reload_timestamps = self.reload_timestamps[-100:]
69
+
70
+ def record_error(self, msg: str) -> None:
71
+ with self._lock:
72
+ self.last_error = msg
73
+
74
+ def has_reloaded_since(self, since: datetime) -> bool:
75
+ with self._lock:
76
+ return self.last_reload_at is not None and self.last_reload_at > since
77
+
78
+
79
+ class ConfigFileWatcher:
80
+ """Polling-based file watcher for config hot-reload.
81
+
82
+ Monitors CODEFRAME.md, AGENTS.md, and CLAUDE.md for mtime changes.
83
+ On change, re-parses and validates the config, updating shared state.
84
+
85
+ Follows the same daemon-thread pattern as StallMonitor.
86
+
87
+ Usage::
88
+
89
+ watcher = ConfigFileWatcher(workspace_path, poll_interval_s=2.0)
90
+ state = watcher.start(initial_prefs)
91
+ try:
92
+ # ... batch execution ...
93
+ # Check state.get_prefs() between tasks
94
+ finally:
95
+ watcher.stop()
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ workspace_path: Path,
101
+ poll_interval_s: float = 2.0,
102
+ ) -> None:
103
+ self._workspace_path = workspace_path
104
+ self._poll_interval_s = poll_interval_s
105
+ self._stop_event = threading.Event()
106
+ self._thread: Optional[threading.Thread] = None
107
+ self._state: Optional[ConfigReloadState] = None
108
+ self._watched_mtimes: dict[Path, float] = {}
109
+ self._workspace_ref: Optional[object] = None # Cached workspace for event emission
110
+
111
+ def start(self, initial_prefs: AgentPreferences) -> ConfigReloadState:
112
+ """Begin watching config files.
113
+
114
+ Args:
115
+ initial_prefs: The current agent preferences to use as baseline.
116
+
117
+ Returns:
118
+ Shared ConfigReloadState that the conductor can query.
119
+ """
120
+ self.stop()
121
+
122
+ self._state = ConfigReloadState(initial_prefs)
123
+ self._stop_event.clear()
124
+
125
+ # Snapshot current mtimes
126
+ self._watched_mtimes = {}
127
+ for name in _CONFIG_FILES:
128
+ path = self._workspace_path / name
129
+ if path.is_file():
130
+ self._watched_mtimes[path] = path.stat().st_mtime
131
+
132
+ self._thread = threading.Thread(
133
+ target=self._poll_loop,
134
+ name="config-file-watcher",
135
+ daemon=True,
136
+ )
137
+ self._thread.start()
138
+ return self._state
139
+
140
+ def stop(self) -> None:
141
+ """Stop the watcher thread."""
142
+ self._stop_event.set()
143
+ if self._thread is not None:
144
+ self._thread.join(timeout=2.0)
145
+ self._thread = None
146
+
147
+ def _poll_loop(self) -> None:
148
+ """Daemon thread: check file mtimes at regular intervals."""
149
+ while not self._stop_event.wait(timeout=self._poll_interval_s):
150
+ self._check_for_changes()
151
+
152
+ def _check_for_changes(self) -> None:
153
+ """Compare current mtimes against snapshots."""
154
+ changed = False
155
+
156
+ for name in _CONFIG_FILES:
157
+ path = self._workspace_path / name
158
+ if not path.is_file():
159
+ # File might have been created since start
160
+ if path not in self._watched_mtimes:
161
+ continue
162
+ # File was deleted — skip
163
+ continue
164
+
165
+ try:
166
+ current_mtime = path.stat().st_mtime
167
+ except OSError:
168
+ continue
169
+
170
+ prev_mtime = self._watched_mtimes.get(path, 0.0)
171
+ if current_mtime > prev_mtime:
172
+ self._watched_mtimes[path] = current_mtime
173
+ changed = True
174
+
175
+ # Also detect newly created config files
176
+ for name in _CONFIG_FILES:
177
+ path = self._workspace_path / name
178
+ try:
179
+ if path.is_file() and path not in self._watched_mtimes:
180
+ self._watched_mtimes[path] = path.stat().st_mtime
181
+ changed = True
182
+ except OSError:
183
+ continue
184
+
185
+ if changed:
186
+ self._reload()
187
+
188
+ def _reload(self) -> None:
189
+ """Re-parse config files and update shared state."""
190
+ try:
191
+ new_prefs = load_preferences(self._workspace_path)
192
+
193
+ if not new_prefs.has_preferences() and not new_prefs.raw_content:
194
+ msg = "Reloaded config is empty — retaining previous config"
195
+ logger.warning(msg)
196
+ if self._state:
197
+ self._state.record_error(msg)
198
+ self._emit_reload_failed(msg)
199
+ return
200
+
201
+ now = _utc_now()
202
+ if self._state:
203
+ old_prefs = self._state.get_prefs()
204
+ self._state.apply_reload(new_prefs, now)
205
+ diff = self._build_diff_summary(old_prefs, new_prefs)
206
+ logger.info("Config reloaded: %s", diff)
207
+ self._emit_reload_success(diff)
208
+
209
+ except Exception as e:
210
+ msg = f"Config reload failed: {e}"
211
+ logger.warning(msg)
212
+ if self._state:
213
+ self._state.record_error(msg)
214
+ self._emit_reload_failed(msg)
215
+
216
+ def _build_diff_summary(
217
+ self, old: AgentPreferences, new: AgentPreferences
218
+ ) -> str:
219
+ """Build a human-readable summary of what changed."""
220
+ changes = []
221
+ if old.always_do != new.always_do:
222
+ changes.append(f"always_do: {len(old.always_do)} -> {len(new.always_do)} items")
223
+ if old.never_do != new.never_do:
224
+ changes.append(f"never_do: {len(old.never_do)} -> {len(new.never_do)} items")
225
+ if old.ask_first != new.ask_first:
226
+ changes.append(f"ask_first: {len(old.ask_first)} -> {len(new.ask_first)} items")
227
+ if old.tooling != new.tooling:
228
+ changes.append("tooling changed")
229
+ if old.commands != new.commands:
230
+ changes.append("commands changed")
231
+ if old.code_style != new.code_style:
232
+ changes.append("code_style changed")
233
+ return "; ".join(changes) if changes else "content changed"
234
+
235
+ def _get_workspace(self):
236
+ """Get cached workspace reference for event emission."""
237
+ if self._workspace_ref is None:
238
+ from codeframe.core.workspace import get_workspace
239
+ self._workspace_ref = get_workspace(self._workspace_path)
240
+ return self._workspace_ref
241
+
242
+ def _emit_reload_success(self, diff_summary: str) -> None:
243
+ """Emit CONFIG_RELOADED event (best-effort)."""
244
+ try:
245
+ from codeframe.core import events
246
+
247
+ events.emit_for_workspace(
248
+ self._get_workspace(),
249
+ events.EventType.CONFIG_RELOADED,
250
+ {"diff_summary": diff_summary},
251
+ print_event=True,
252
+ )
253
+ except Exception:
254
+ logger.debug("Failed to emit CONFIG_RELOADED event", exc_info=True)
255
+
256
+ def _emit_reload_failed(self, error_msg: str) -> None:
257
+ """Emit CONFIG_RELOAD_FAILED event (best-effort)."""
258
+ try:
259
+ from codeframe.core import events
260
+
261
+ events.emit_for_workspace(
262
+ self._get_workspace(),
263
+ events.EventType.CONFIG_RELOAD_FAILED,
264
+ {"error": error_msg},
265
+ print_event=True,
266
+ )
267
+ except Exception:
268
+ logger.debug("Failed to emit CONFIG_RELOAD_FAILED event", exc_info=True)