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,415 @@
1
+ """CLI commands for PROOF9 quality memory system.
2
+
3
+ Provides `cf proof` subcommands for capturing requirements,
4
+ running obligations, managing waivers, and viewing status.
5
+ """
6
+
7
+ from datetime import date
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import typer
12
+ from rich.console import Console
13
+ from rich.table import Table
14
+
15
+ console = Console()
16
+
17
+ proof_app = typer.Typer(
18
+ name="proof",
19
+ help="PROOF9 quality memory system — evidence-based verification",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+
24
+ @proof_app.command("capture")
25
+ def capture(
26
+ repo_path: Optional[Path] = typer.Option(
27
+ None, "--workspace", "-w", help="Workspace path",
28
+ ),
29
+ title: Optional[str] = typer.Option(
30
+ None, "--title", "-t", help="Short description of the glitch",
31
+ ),
32
+ description: Optional[str] = typer.Option(
33
+ None, "--description", "-d", help="What happened (expected vs actual)",
34
+ ),
35
+ where: Optional[str] = typer.Option(
36
+ None, "--where", help="Where it happened (URL, file, API route)",
37
+ ),
38
+ severity: Optional[str] = typer.Option(
39
+ None, "--severity", "-s", help="critical/high/medium/low",
40
+ ),
41
+ source: Optional[str] = typer.Option(
42
+ None, "--source", help="production/qa/dogfooding/monitoring/user_report",
43
+ ),
44
+ source_issue: Optional[str] = typer.Option(
45
+ None, "--from-issue", help="GitHub issue reference (e.g., GH-123)",
46
+ ),
47
+ ) -> None:
48
+ """Capture a glitch as a permanent proof requirement.
49
+
50
+ Creates a REQ with proof obligations and generates test stubs.
51
+ Interactive when run without arguments.
52
+
53
+ Example:
54
+ codeframe proof capture
55
+ codeframe proof capture --title "Login rejects empty password" --severity high
56
+ """
57
+ from codeframe.core.workspace import get_workspace
58
+ from codeframe.core.proof.models import Severity, Source
59
+ from codeframe.core.proof.capture import capture_requirement
60
+
61
+ workspace_path = repo_path or Path.cwd()
62
+ try:
63
+ workspace = get_workspace(workspace_path)
64
+ except Exception as e:
65
+ console.print(f"[red]Error:[/red] {e}")
66
+ raise typer.Exit(1)
67
+
68
+ # Interactive prompts for missing fields
69
+ if not title:
70
+ title = typer.prompt("What happened? (short title)")
71
+ if not description:
72
+ description = typer.prompt("Describe the issue (expected vs actual)")
73
+ if not where:
74
+ where = typer.prompt("Where? (file path, URL, API route, or component)")
75
+ if not severity:
76
+ severity = typer.prompt(
77
+ "Severity", default="medium",
78
+ type=typer.Choice(["critical", "high", "medium", "low"]),
79
+ )
80
+ if not source:
81
+ source = typer.prompt(
82
+ "Source", default="qa",
83
+ type=typer.Choice(["production", "qa", "dogfooding", "monitoring", "user_report"]),
84
+ )
85
+
86
+ try:
87
+ sev = Severity(severity)
88
+ except ValueError:
89
+ console.print(f"[red]Error:[/red] Invalid severity: {severity}")
90
+ raise typer.Exit(1)
91
+
92
+ try:
93
+ src = Source(source)
94
+ except ValueError:
95
+ console.print(f"[red]Error:[/red] Invalid source: {source}")
96
+ raise typer.Exit(1)
97
+
98
+ req, stubs = capture_requirement(
99
+ workspace,
100
+ title=title,
101
+ description=description,
102
+ where=where,
103
+ severity=sev,
104
+ source=src,
105
+ source_issue=source_issue,
106
+ )
107
+
108
+ console.print(f"\n[green]✓[/green] Created [bold]{req.id}[/bold]: {req.title}")
109
+ console.print(f" Glitch type: [cyan]{req.glitch_type.value if req.glitch_type else 'unknown'}[/cyan]")
110
+ console.print(f" Obligations: {', '.join(o.gate.value for o in req.obligations)}")
111
+ console.print(f" Scope files: {', '.join(req.scope.files) or 'none'}")
112
+ console.print(f" Scope routes: {', '.join(req.scope.routes) or 'none'}")
113
+
114
+ if stubs:
115
+ console.print("\n[bold]Generated test stubs:[/bold]")
116
+ for gate, content in stubs.items():
117
+ lines = len(content.splitlines())
118
+ console.print(f" [cyan]{gate.value}[/cyan]: {lines} lines")
119
+
120
+
121
+ @proof_app.command("run")
122
+ def run(
123
+ repo_path: Optional[Path] = typer.Option(
124
+ None, "--workspace", "-w", help="Workspace path",
125
+ ),
126
+ full: bool = typer.Option(
127
+ False, "--full", help="Run all obligations (not just changed scope)",
128
+ ),
129
+ gate: Optional[str] = typer.Option(
130
+ None, "--gate", help="Run only this gate (e.g., unit, e2e)",
131
+ ),
132
+ ) -> None:
133
+ """Run proof obligations for current changes.
134
+
135
+ Determines which requirements apply to changed files,
136
+ runs their obligations, and collects evidence.
137
+
138
+ Example:
139
+ codeframe proof run
140
+ codeframe proof run --full
141
+ codeframe proof run --gate unit
142
+ """
143
+ from codeframe.core.workspace import get_workspace
144
+ from codeframe.core.proof.models import Gate
145
+ from codeframe.core.proof.runner import run_proof
146
+
147
+ workspace_path = repo_path or Path.cwd()
148
+ try:
149
+ workspace = get_workspace(workspace_path)
150
+ except Exception as e:
151
+ console.print(f"[red]Error:[/red] {e}")
152
+ raise typer.Exit(1)
153
+
154
+ gate_filter = None
155
+ if gate:
156
+ try:
157
+ gate_filter = Gate(gate.lower())
158
+ except ValueError:
159
+ console.print(f"[red]Error:[/red] Unknown gate: {gate}")
160
+ console.print(f"Valid gates: {', '.join(g.value for g in Gate)}")
161
+ raise typer.Exit(1)
162
+
163
+ mode = "full" if full else "scope-filtered"
164
+ console.print(f"[dim]Running proof obligations ({mode})...[/dim]")
165
+
166
+ results = run_proof(workspace, full=full, gate_filter=gate_filter)
167
+
168
+ if not results:
169
+ console.print("[green]No applicable obligations found.[/green]")
170
+ return
171
+
172
+ # Display results
173
+ table = Table(title="Proof Results")
174
+ table.add_column("REQ", style="cyan")
175
+ table.add_column("Gate", style="blue")
176
+ table.add_column("Result", style="bold")
177
+
178
+ all_pass = True
179
+ for req_id, gate_results in results.items():
180
+ for g, passed in gate_results:
181
+ status = "[green]PASS[/green]" if passed else "[red]FAIL[/red]"
182
+ if not passed:
183
+ all_pass = False
184
+ table.add_row(req_id, g.value, status)
185
+
186
+ console.print(table)
187
+
188
+ if all_pass:
189
+ console.print("\n[green]All obligations satisfied.[/green]")
190
+ else:
191
+ console.print("\n[red]Some obligations failed.[/red] Fix issues and re-run.")
192
+ raise typer.Exit(1)
193
+
194
+
195
+ @proof_app.command("list")
196
+ def list_reqs(
197
+ repo_path: Optional[Path] = typer.Option(
198
+ None, "--workspace", "-w", help="Workspace path",
199
+ ),
200
+ status: Optional[str] = typer.Option(
201
+ None, "--status", help="Filter by status (open/satisfied/waived)",
202
+ ),
203
+ ) -> None:
204
+ """List all proof requirements.
205
+
206
+ Example:
207
+ codeframe proof list
208
+ codeframe proof list --status open
209
+ """
210
+ from codeframe.core.workspace import get_workspace
211
+ from codeframe.core.proof import ledger
212
+ from codeframe.core.proof.models import ReqStatus
213
+
214
+ workspace_path = repo_path or Path.cwd()
215
+ try:
216
+ workspace = get_workspace(workspace_path)
217
+ except Exception as e:
218
+ console.print(f"[red]Error:[/red] {e}")
219
+ raise typer.Exit(1)
220
+
221
+ status_filter = None
222
+ if status:
223
+ try:
224
+ status_filter = ReqStatus(status.lower())
225
+ except ValueError:
226
+ console.print(f"[red]Error:[/red] Invalid status: {status}")
227
+ raise typer.Exit(1)
228
+
229
+ reqs = ledger.list_requirements(workspace, status=status_filter)
230
+
231
+ if not reqs:
232
+ console.print("No requirements found.")
233
+ return
234
+
235
+ table = Table(title=f"Proof Requirements ({len(reqs)})")
236
+ table.add_column("ID", style="cyan")
237
+ table.add_column("Title")
238
+ table.add_column("Severity")
239
+ table.add_column("Status")
240
+ table.add_column("Gates")
241
+
242
+ for req in reqs:
243
+ sev_color = {"critical": "red", "high": "yellow", "medium": "blue", "low": "dim"}.get(
244
+ req.severity.value, "white"
245
+ )
246
+ status_color = {
247
+ "open": "yellow", "satisfied": "green", "waived": "dim"
248
+ }.get(req.status.value, "white")
249
+
250
+ table.add_row(
251
+ req.id,
252
+ req.title[:50],
253
+ f"[{sev_color}]{req.severity.value}[/{sev_color}]",
254
+ f"[{status_color}]{req.status.value}[/{status_color}]",
255
+ ", ".join(o.gate.value for o in req.obligations),
256
+ )
257
+
258
+ console.print(table)
259
+
260
+
261
+ @proof_app.command("show")
262
+ def show(
263
+ req_id: str = typer.Argument(help="Requirement ID (e.g., REQ-0001)"),
264
+ repo_path: Optional[Path] = typer.Option(
265
+ None, "--workspace", "-w", help="Workspace path",
266
+ ),
267
+ ) -> None:
268
+ """Show detailed information about a requirement.
269
+
270
+ Example:
271
+ codeframe proof show REQ-0001
272
+ """
273
+ from codeframe.core.workspace import get_workspace
274
+ from codeframe.core.proof import ledger
275
+
276
+ workspace_path = repo_path or Path.cwd()
277
+ try:
278
+ workspace = get_workspace(workspace_path)
279
+ except Exception as e:
280
+ console.print(f"[red]Error:[/red] {e}")
281
+ raise typer.Exit(1)
282
+
283
+ req = ledger.get_requirement(workspace, req_id)
284
+ if not req:
285
+ console.print(f"[red]Error:[/red] Requirement {req_id} not found.")
286
+ raise typer.Exit(1)
287
+
288
+ console.print(f"\n[bold]{req.id}[/bold]: {req.title}")
289
+ console.print(f" Status: {req.status.value}")
290
+ console.print(f" Severity: {req.severity.value}")
291
+ console.print(f" Source: {req.source.value}")
292
+ if req.glitch_type:
293
+ console.print(f" Glitch type: {req.glitch_type.value}")
294
+ console.print(f" Created: {req.created_at}")
295
+ if req.source_issue:
296
+ console.print(f" Issue: {req.source_issue}")
297
+
298
+ console.print("\n[bold]Scope:[/bold]")
299
+ for field_name in ("files", "routes", "apis", "components", "tags"):
300
+ items = getattr(req.scope, field_name)
301
+ if items:
302
+ console.print(f" {field_name}: {', '.join(items)}")
303
+
304
+ console.print("\n[bold]Obligations:[/bold]")
305
+ for obl in req.obligations:
306
+ console.print(f" {obl.gate.value}: {obl.status}")
307
+
308
+ if req.waiver:
309
+ console.print("\n[bold]Waiver:[/bold]")
310
+ console.print(f" Reason: {req.waiver.reason}")
311
+ if req.waiver.expires:
312
+ console.print(f" Expires: {req.waiver.expires}")
313
+
314
+ # Show evidence
315
+ evidence_list = ledger.list_evidence(workspace, req.id)
316
+ if evidence_list:
317
+ console.print(f"\n[bold]Evidence ({len(evidence_list)}):[/bold]")
318
+ for ev in evidence_list[:10]:
319
+ status = "[green]PASS[/green]" if ev.satisfied else "[red]FAIL[/red]"
320
+ console.print(f" {ev.gate.value} {status} — {ev.artifact_path}")
321
+
322
+
323
+ @proof_app.command("waive")
324
+ def waive(
325
+ req_id: str = typer.Argument(help="Requirement ID to waive"),
326
+ reason: str = typer.Option(..., "--reason", "-r", help="Why this is being waived"),
327
+ expires: Optional[str] = typer.Option(
328
+ None, "--expires", help="Expiry date (YYYY-MM-DD)",
329
+ ),
330
+ repo_path: Optional[Path] = typer.Option(
331
+ None, "--workspace", "-w", help="Workspace path",
332
+ ),
333
+ ) -> None:
334
+ """Waive a requirement with reason and optional expiry.
335
+
336
+ Example:
337
+ codeframe proof waive REQ-0001 --reason "No automated test yet" --expires 2026-04-01
338
+ """
339
+ from codeframe.core.workspace import get_workspace
340
+ from codeframe.core.proof import ledger
341
+ from codeframe.core.proof.models import Waiver
342
+
343
+ workspace_path = repo_path or Path.cwd()
344
+ try:
345
+ workspace = get_workspace(workspace_path)
346
+ except Exception as e:
347
+ console.print(f"[red]Error:[/red] {e}")
348
+ raise typer.Exit(1)
349
+
350
+ expiry_date = None
351
+ if expires:
352
+ try:
353
+ expiry_date = date.fromisoformat(expires)
354
+ except ValueError:
355
+ console.print(f"[red]Error:[/red] Invalid date format: {expires} (use YYYY-MM-DD)")
356
+ raise typer.Exit(1)
357
+
358
+ waiver_obj = Waiver(reason=reason, expires=expiry_date, approved_by="cli-user")
359
+ updated = ledger.waive_requirement(workspace, req_id, waiver_obj)
360
+
361
+ if updated:
362
+ console.print(f"[green]✓[/green] {req_id} waived: {reason}")
363
+ if expiry_date:
364
+ console.print(f" Expires: {expiry_date}")
365
+ else:
366
+ console.print(f"[red]Error:[/red] Requirement {req_id} not found.")
367
+ raise typer.Exit(1)
368
+
369
+
370
+ @proof_app.command("status")
371
+ def status_cmd(
372
+ repo_path: Optional[Path] = typer.Option(
373
+ None, "--workspace", "-w", help="Workspace path",
374
+ ),
375
+ ) -> None:
376
+ """Show proof system status — satisfied/failing/waived counts.
377
+
378
+ Example:
379
+ codeframe proof status
380
+ """
381
+ from codeframe.core.workspace import get_workspace
382
+ from codeframe.core.proof import ledger
383
+
384
+ workspace_path = repo_path or Path.cwd()
385
+ try:
386
+ workspace = get_workspace(workspace_path)
387
+ except Exception as e:
388
+ console.print(f"[red]Error:[/red] {e}")
389
+ raise typer.Exit(1)
390
+
391
+ # Check for expired waivers first
392
+ expired = ledger.check_expired_waivers(workspace)
393
+ if expired:
394
+ console.print(f"[yellow]Expired {len(expired)} waivers → reverted to open[/yellow]\n")
395
+
396
+ reqs = ledger.list_requirements(workspace)
397
+ if not reqs:
398
+ console.print("No proof requirements. Use 'cf proof capture' to add one.")
399
+ return
400
+
401
+ counts = {"open": 0, "satisfied": 0, "waived": 0}
402
+ for req in reqs:
403
+ counts[req.status.value] = counts.get(req.status.value, 0) + 1
404
+
405
+ total = len(reqs)
406
+ console.print(f"[bold]PROOF9 Status[/bold] ({total} requirements)\n")
407
+ console.print(f" [yellow]Open:[/yellow] {counts['open']}")
408
+ console.print(f" [green]Satisfied:[/green] {counts['satisfied']}")
409
+ console.print(f" [dim]Waived:[/dim] {counts['waived']}")
410
+
411
+ if counts["open"] > 0:
412
+ console.print(f"\n[yellow]{counts['open']} open obligations need attention.[/yellow]")
413
+ console.print("[dim]Run 'cf proof run' to execute obligations.[/dim]")
414
+ else:
415
+ console.print("\n[green]All obligations satisfied or waived.[/green]")