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,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]")
|