up-cli 0.2.0__py3-none-any.whl → 0.5.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.
- up/__init__.py +1 -1
- up/ai_cli.py +229 -0
- up/cli.py +54 -9
- up/commands/agent.py +521 -0
- up/commands/bisect.py +343 -0
- up/commands/branch.py +350 -0
- up/commands/init.py +195 -6
- up/commands/learn.py +1392 -32
- up/commands/memory.py +545 -0
- up/commands/provenance.py +267 -0
- up/commands/review.py +239 -0
- up/commands/start.py +752 -42
- up/commands/status.py +173 -18
- up/commands/sync.py +317 -0
- up/commands/vibe.py +304 -0
- up/context.py +64 -10
- up/core/__init__.py +69 -0
- up/core/checkpoint.py +479 -0
- up/core/provenance.py +364 -0
- up/core/state.py +678 -0
- up/events.py +512 -0
- up/git/__init__.py +37 -0
- up/git/utils.py +270 -0
- up/git/worktree.py +331 -0
- up/learn/__init__.py +155 -0
- up/learn/analyzer.py +227 -0
- up/learn/plan.py +374 -0
- up/learn/research.py +511 -0
- up/learn/utils.py +117 -0
- up/memory.py +1096 -0
- up/parallel.py +551 -0
- up/templates/config/__init__.py +1 -1
- up/templates/docs/SKILL.md +28 -0
- up/templates/docs/__init__.py +341 -0
- up/templates/docs/standards/HEADERS.md +24 -0
- up/templates/docs/standards/STRUCTURE.md +18 -0
- up/templates/docs/standards/TEMPLATES.md +19 -0
- up/templates/loop/__init__.py +92 -32
- up/ui/__init__.py +14 -0
- up/ui/loop_display.py +650 -0
- up/ui/theme.py +137 -0
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/METADATA +160 -15
- up_cli-0.5.0.dist-info/RECORD +55 -0
- up_cli-0.2.0.dist-info/RECORD +0 -23
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""up provenance - View AI operation history and lineage."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from up.core.provenance import get_provenance_manager, ProvenanceEntry
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def provenance():
|
|
18
|
+
"""View AI operation history and lineage.
|
|
19
|
+
|
|
20
|
+
Track the provenance of AI-generated code changes,
|
|
21
|
+
including models used, prompts, and verification results.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@provenance.command("show")
|
|
27
|
+
@click.argument("entry_id", required=False)
|
|
28
|
+
@click.option("--task", "-t", help="Show by task ID")
|
|
29
|
+
def show_cmd(entry_id: str, task: str):
|
|
30
|
+
"""Show details of a provenance entry.
|
|
31
|
+
|
|
32
|
+
\b
|
|
33
|
+
Examples:
|
|
34
|
+
up provenance show abc123def456
|
|
35
|
+
up provenance show --task US-007
|
|
36
|
+
"""
|
|
37
|
+
cwd = Path.cwd()
|
|
38
|
+
manager = get_provenance_manager(cwd)
|
|
39
|
+
|
|
40
|
+
entry = None
|
|
41
|
+
if entry_id:
|
|
42
|
+
entry = manager.get_entry(entry_id)
|
|
43
|
+
elif task:
|
|
44
|
+
entry = manager.get_entry_for_task(task)
|
|
45
|
+
else:
|
|
46
|
+
console.print("[yellow]Provide an entry ID or --task[/]")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if not entry:
|
|
50
|
+
console.print("[red]Entry not found[/]")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
_display_entry(entry)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@provenance.command("list")
|
|
57
|
+
@click.option("--limit", "-n", default=20, help="Number of entries to show")
|
|
58
|
+
@click.option("--status", type=click.Choice(["pending", "accepted", "rejected"]), help="Filter by status")
|
|
59
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
60
|
+
def list_cmd(limit: int, status: str, as_json: bool):
|
|
61
|
+
"""List recent provenance entries.
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
Examples:
|
|
65
|
+
up provenance list
|
|
66
|
+
up provenance list -n 50
|
|
67
|
+
up provenance list --status accepted
|
|
68
|
+
"""
|
|
69
|
+
cwd = Path.cwd()
|
|
70
|
+
manager = get_provenance_manager(cwd)
|
|
71
|
+
|
|
72
|
+
entries = manager.list_entries(limit=limit, status=status)
|
|
73
|
+
|
|
74
|
+
if not entries:
|
|
75
|
+
console.print("[dim]No provenance entries found[/]")
|
|
76
|
+
console.print("Entries are created when running [cyan]up start[/]")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
if as_json:
|
|
80
|
+
output = [e.to_dict() for e in entries]
|
|
81
|
+
console.print(json.dumps(output, indent=2))
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
table = Table(title=f"Provenance History (last {len(entries)})")
|
|
85
|
+
table.add_column("ID", style="cyan")
|
|
86
|
+
table.add_column("Task")
|
|
87
|
+
table.add_column("Model")
|
|
88
|
+
table.add_column("Status")
|
|
89
|
+
table.add_column("Files")
|
|
90
|
+
table.add_column("Tests")
|
|
91
|
+
table.add_column("Time")
|
|
92
|
+
|
|
93
|
+
for entry in entries:
|
|
94
|
+
status_icon = {
|
|
95
|
+
"accepted": "🟢",
|
|
96
|
+
"rejected": "🔴",
|
|
97
|
+
"pending": "🟡",
|
|
98
|
+
}.get(entry.status, "⚪")
|
|
99
|
+
|
|
100
|
+
tests_icon = "✓" if entry.tests_passed else "✗" if entry.tests_passed is False else "-"
|
|
101
|
+
|
|
102
|
+
table.add_row(
|
|
103
|
+
entry.id,
|
|
104
|
+
entry.task_id[:10] if entry.task_id else "-",
|
|
105
|
+
entry.ai_model[:10],
|
|
106
|
+
f"{status_icon} {entry.status}",
|
|
107
|
+
str(len(entry.files_modified)),
|
|
108
|
+
tests_icon,
|
|
109
|
+
entry.created_at[:16]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
console.print(table)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@provenance.command("stats")
|
|
116
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
117
|
+
def stats_cmd(as_json: bool):
|
|
118
|
+
"""Show provenance statistics.
|
|
119
|
+
|
|
120
|
+
Displays aggregated stats about AI operations,
|
|
121
|
+
including acceptance rates and test results.
|
|
122
|
+
"""
|
|
123
|
+
cwd = Path.cwd()
|
|
124
|
+
manager = get_provenance_manager(cwd)
|
|
125
|
+
|
|
126
|
+
stats = manager.get_stats()
|
|
127
|
+
|
|
128
|
+
if as_json:
|
|
129
|
+
console.print(json.dumps(stats, indent=2))
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
console.print(Panel.fit(
|
|
133
|
+
"[bold]AI Operation Statistics[/]",
|
|
134
|
+
border_style="blue"
|
|
135
|
+
))
|
|
136
|
+
|
|
137
|
+
# Overview
|
|
138
|
+
console.print("\n[bold]Overview[/]")
|
|
139
|
+
console.print(f" Total Operations: {stats['total_operations']}")
|
|
140
|
+
console.print(f" Accepted: [green]{stats['accepted']}[/]")
|
|
141
|
+
console.print(f" Rejected: [red]{stats['rejected']}[/]")
|
|
142
|
+
console.print(f" Pending: [yellow]{stats['pending']}[/]")
|
|
143
|
+
|
|
144
|
+
if stats['total_operations'] > 0:
|
|
145
|
+
console.print(f" Acceptance Rate: {stats['acceptance_rate']*100:.1f}%")
|
|
146
|
+
|
|
147
|
+
# Code changes
|
|
148
|
+
console.print("\n[bold]Code Changes[/]")
|
|
149
|
+
console.print(f" Lines Added: [green]+{stats['total_lines_added']}[/]")
|
|
150
|
+
console.print(f" Lines Removed: [red]-{stats['total_lines_removed']}[/]")
|
|
151
|
+
|
|
152
|
+
# Testing
|
|
153
|
+
if stats['tests_run'] > 0:
|
|
154
|
+
console.print("\n[bold]Testing[/]")
|
|
155
|
+
console.print(f" Tests Run: {stats['tests_run']}")
|
|
156
|
+
console.print(f" Tests Passed: {stats['tests_passed']}")
|
|
157
|
+
console.print(f" Pass Rate: {stats['test_pass_rate']*100:.1f}%")
|
|
158
|
+
|
|
159
|
+
# Models
|
|
160
|
+
if stats['models_used']:
|
|
161
|
+
console.print("\n[bold]AI Models Used[/]")
|
|
162
|
+
for model, count in sorted(stats['models_used'].items(), key=lambda x: -x[1]):
|
|
163
|
+
console.print(f" {model}: {count}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@provenance.command("verify")
|
|
167
|
+
@click.argument("entry_id")
|
|
168
|
+
@click.option("--accept", "-a", is_flag=True, help="Mark as accepted")
|
|
169
|
+
@click.option("--reject", "-r", is_flag=True, help="Mark as rejected")
|
|
170
|
+
@click.option("--reason", help="Rejection reason")
|
|
171
|
+
def verify_cmd(entry_id: str, accept: bool, reject: bool, reason: str):
|
|
172
|
+
"""Verify a provenance entry.
|
|
173
|
+
|
|
174
|
+
Mark an entry as accepted or rejected after review.
|
|
175
|
+
"""
|
|
176
|
+
cwd = Path.cwd()
|
|
177
|
+
manager = get_provenance_manager(cwd)
|
|
178
|
+
|
|
179
|
+
if not accept and not reject:
|
|
180
|
+
console.print("[yellow]Specify --accept or --reject[/]")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
entry = manager.get_entry(entry_id)
|
|
184
|
+
if not entry:
|
|
185
|
+
console.print(f"[red]Entry not found: {entry_id}[/]")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if accept:
|
|
189
|
+
entry = manager.complete_operation(entry_id, status="accepted")
|
|
190
|
+
console.print(f"[green]✓[/] Entry {entry_id} marked as accepted")
|
|
191
|
+
elif reject:
|
|
192
|
+
entry = manager.reject_operation(entry_id, reason=reason or "Manual rejection")
|
|
193
|
+
console.print(f"[red]✗[/] Entry {entry_id} marked as rejected")
|
|
194
|
+
if reason:
|
|
195
|
+
console.print(f" Reason: {reason}")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _display_entry(entry: ProvenanceEntry) -> None:
|
|
199
|
+
"""Display a single provenance entry in detail."""
|
|
200
|
+
status_color = {
|
|
201
|
+
"accepted": "green",
|
|
202
|
+
"rejected": "red",
|
|
203
|
+
"pending": "yellow",
|
|
204
|
+
}.get(entry.status, "white")
|
|
205
|
+
|
|
206
|
+
console.print(Panel.fit(
|
|
207
|
+
f"[bold]Provenance Entry[/] [{status_color}]{entry.status.upper()}[/]",
|
|
208
|
+
border_style=status_color
|
|
209
|
+
))
|
|
210
|
+
|
|
211
|
+
# Basic info
|
|
212
|
+
table = Table(show_header=False, box=None)
|
|
213
|
+
table.add_column("Key", style="dim")
|
|
214
|
+
table.add_column("Value")
|
|
215
|
+
|
|
216
|
+
table.add_row("ID", f"[cyan]{entry.id}[/]")
|
|
217
|
+
table.add_row("Task", f"{entry.task_id} - {entry.task_title}")
|
|
218
|
+
table.add_row("AI Model", entry.ai_model)
|
|
219
|
+
table.add_row("Branch", entry.branch)
|
|
220
|
+
table.add_row("Commit", entry.commit_sha or "-")
|
|
221
|
+
table.add_row("Created", entry.created_at)
|
|
222
|
+
if entry.completed_at:
|
|
223
|
+
table.add_row("Completed", entry.completed_at)
|
|
224
|
+
|
|
225
|
+
console.print(table)
|
|
226
|
+
|
|
227
|
+
# Prompt preview
|
|
228
|
+
if entry.prompt_preview:
|
|
229
|
+
console.print("\n[bold]Prompt Preview[/]")
|
|
230
|
+
console.print(f"[dim]{entry.prompt_preview}[/]")
|
|
231
|
+
|
|
232
|
+
# Files
|
|
233
|
+
if entry.files_modified:
|
|
234
|
+
console.print("\n[bold]Files Modified[/]")
|
|
235
|
+
for f in entry.files_modified[:10]:
|
|
236
|
+
console.print(f" • {f}")
|
|
237
|
+
if len(entry.files_modified) > 10:
|
|
238
|
+
console.print(f" ... and {len(entry.files_modified) - 10} more")
|
|
239
|
+
|
|
240
|
+
# Context files
|
|
241
|
+
if entry.context_files:
|
|
242
|
+
console.print("\n[bold]Context Files[/]")
|
|
243
|
+
for f in entry.context_files[:5]:
|
|
244
|
+
console.print(f" • {f}")
|
|
245
|
+
|
|
246
|
+
# Verification
|
|
247
|
+
console.print("\n[bold]Verification[/]")
|
|
248
|
+
|
|
249
|
+
def status_icon(val):
|
|
250
|
+
if val is True:
|
|
251
|
+
return "[green]✓[/]"
|
|
252
|
+
elif val is False:
|
|
253
|
+
return "[red]✗[/]"
|
|
254
|
+
return "[dim]-[/]"
|
|
255
|
+
|
|
256
|
+
console.print(f" Tests: {status_icon(entry.tests_passed)}")
|
|
257
|
+
console.print(f" Lint: {status_icon(entry.lint_passed)}")
|
|
258
|
+
console.print(f" Type Check: {status_icon(entry.type_check_passed)}")
|
|
259
|
+
|
|
260
|
+
if entry.verification_notes:
|
|
261
|
+
console.print(f"\n[bold]Notes[/]")
|
|
262
|
+
console.print(f" {entry.verification_notes}")
|
|
263
|
+
|
|
264
|
+
# Hashes
|
|
265
|
+
console.print("\n[bold]Content Hashes[/]")
|
|
266
|
+
console.print(f" Prompt: {entry.prompt_hash}")
|
|
267
|
+
console.print(f" Context: {entry.context_hash or '-'}")
|
up/commands/review.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""up review - AI-powered code review.
|
|
2
|
+
|
|
3
|
+
Provides adversarial review of AI-generated code to catch:
|
|
4
|
+
- Logic errors
|
|
5
|
+
- Security issues
|
|
6
|
+
- Performance problems
|
|
7
|
+
- Best practice violations
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.markdown import Markdown
|
|
19
|
+
|
|
20
|
+
from up.ai_cli import check_ai_cli, run_ai_prompt
|
|
21
|
+
from up.core.checkpoint import get_checkpoint_manager
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command("review")
|
|
27
|
+
@click.option("--checkpoint", "-c", help="Review changes since checkpoint")
|
|
28
|
+
@click.option("--files", "-f", multiple=True, help="Specific files to review")
|
|
29
|
+
@click.option("--focus", type=click.Choice(["security", "performance", "logic", "style", "all"]),
|
|
30
|
+
default="all", help="Focus area for review")
|
|
31
|
+
@click.option("--strict", is_flag=True, help="Strict mode - fail on any issue")
|
|
32
|
+
@click.option("--model", "-m", help="AI model to use (claude, cursor)")
|
|
33
|
+
def review_cmd(checkpoint: str, files: tuple, focus: str, strict: bool, model: str):
|
|
34
|
+
"""AI-powered adversarial code review.
|
|
35
|
+
|
|
36
|
+
Reviews recent AI-generated changes for potential issues.
|
|
37
|
+
Use after 'up start' to verify AI work before committing.
|
|
38
|
+
|
|
39
|
+
\b
|
|
40
|
+
Examples:
|
|
41
|
+
up review # Review all changes
|
|
42
|
+
up review --checkpoint cp-1 # Review since checkpoint
|
|
43
|
+
up review --focus security # Security-focused review
|
|
44
|
+
up review --files src/auth.py --strict
|
|
45
|
+
"""
|
|
46
|
+
cwd = Path.cwd()
|
|
47
|
+
|
|
48
|
+
# Get diff to review
|
|
49
|
+
if files:
|
|
50
|
+
# Review specific files
|
|
51
|
+
diff_content = _get_files_diff(cwd, list(files))
|
|
52
|
+
files_reviewed = list(files)
|
|
53
|
+
elif checkpoint:
|
|
54
|
+
# Review since checkpoint
|
|
55
|
+
diff_content = _get_checkpoint_diff(cwd, checkpoint)
|
|
56
|
+
files_reviewed = _get_changed_files(cwd, checkpoint)
|
|
57
|
+
else:
|
|
58
|
+
# Review all uncommitted changes
|
|
59
|
+
diff_content = _get_uncommitted_diff(cwd)
|
|
60
|
+
files_reviewed = _get_uncommitted_files(cwd)
|
|
61
|
+
|
|
62
|
+
if not diff_content or diff_content.strip() == "":
|
|
63
|
+
console.print("[dim]No changes to review[/]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
console.print(Panel.fit(
|
|
67
|
+
"[bold blue]AI Code Review[/]",
|
|
68
|
+
border_style="blue"
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
console.print(f"Files to review: [cyan]{len(files_reviewed)}[/]")
|
|
72
|
+
for f in files_reviewed[:5]:
|
|
73
|
+
console.print(f" • {f}")
|
|
74
|
+
if len(files_reviewed) > 5:
|
|
75
|
+
console.print(f" ... and {len(files_reviewed) - 5} more")
|
|
76
|
+
|
|
77
|
+
# Check AI availability
|
|
78
|
+
cli_name = model
|
|
79
|
+
if not cli_name:
|
|
80
|
+
cli_name, available = check_ai_cli()
|
|
81
|
+
if not available:
|
|
82
|
+
console.print("\n[red]Error:[/] No AI CLI available")
|
|
83
|
+
console.print("Install Claude CLI or Cursor Agent for AI review")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
console.print(f"\n[yellow]Running review with {cli_name}...[/]")
|
|
87
|
+
|
|
88
|
+
# Build review prompt
|
|
89
|
+
prompt = _build_review_prompt(diff_content, focus, strict)
|
|
90
|
+
|
|
91
|
+
# Run AI review
|
|
92
|
+
result = run_ai_prompt(cwd, prompt, cli_name, timeout=180)
|
|
93
|
+
|
|
94
|
+
if not result:
|
|
95
|
+
console.print("[red]Review failed - no response from AI[/]")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Display results
|
|
99
|
+
console.print("\n")
|
|
100
|
+
console.print(Panel(
|
|
101
|
+
Markdown(result),
|
|
102
|
+
title="Review Results",
|
|
103
|
+
border_style="yellow" if "issue" in result.lower() or "problem" in result.lower() else "green"
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
# Check for issues in strict mode
|
|
107
|
+
if strict:
|
|
108
|
+
issue_indicators = ["issue", "problem", "bug", "error", "vulnerability", "critical", "warning"]
|
|
109
|
+
has_issues = any(indicator in result.lower() for indicator in issue_indicators)
|
|
110
|
+
|
|
111
|
+
if has_issues:
|
|
112
|
+
console.print("\n[red]⚠ Issues found in strict mode[/]")
|
|
113
|
+
console.print("Consider fixing issues before committing.")
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
else:
|
|
116
|
+
console.print("\n[green]✓ No issues found[/]")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_uncommitted_diff(cwd: Path) -> str:
|
|
120
|
+
"""Get diff of uncommitted changes."""
|
|
121
|
+
result = subprocess.run(
|
|
122
|
+
["git", "diff", "HEAD"],
|
|
123
|
+
cwd=cwd,
|
|
124
|
+
capture_output=True,
|
|
125
|
+
text=True
|
|
126
|
+
)
|
|
127
|
+
return result.stdout if result.returncode == 0 else ""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _get_uncommitted_files(cwd: Path) -> list:
|
|
131
|
+
"""Get list of uncommitted changed files."""
|
|
132
|
+
result = subprocess.run(
|
|
133
|
+
["git", "diff", "--name-only", "HEAD"],
|
|
134
|
+
cwd=cwd,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True
|
|
137
|
+
)
|
|
138
|
+
if result.returncode == 0:
|
|
139
|
+
return [f for f in result.stdout.strip().split("\n") if f]
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _get_checkpoint_diff(cwd: Path, checkpoint: str) -> str:
|
|
144
|
+
"""Get diff since checkpoint."""
|
|
145
|
+
manager = get_checkpoint_manager(cwd)
|
|
146
|
+
return manager.diff_from_checkpoint(checkpoint)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _get_changed_files(cwd: Path, checkpoint: str) -> list:
|
|
150
|
+
"""Get files changed since checkpoint."""
|
|
151
|
+
tag_name = f"up-checkpoint/{checkpoint}"
|
|
152
|
+
result = subprocess.run(
|
|
153
|
+
["git", "diff", "--name-only", tag_name, "HEAD"],
|
|
154
|
+
cwd=cwd,
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True
|
|
157
|
+
)
|
|
158
|
+
if result.returncode == 0:
|
|
159
|
+
return [f for f in result.stdout.strip().split("\n") if f]
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _get_files_diff(cwd: Path, files: list) -> str:
|
|
164
|
+
"""Get diff for specific files."""
|
|
165
|
+
result = subprocess.run(
|
|
166
|
+
["git", "diff", "HEAD", "--"] + files,
|
|
167
|
+
cwd=cwd,
|
|
168
|
+
capture_output=True,
|
|
169
|
+
text=True
|
|
170
|
+
)
|
|
171
|
+
return result.stdout if result.returncode == 0 else ""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _build_review_prompt(diff: str, focus: str, strict: bool) -> str:
|
|
175
|
+
"""Build the review prompt."""
|
|
176
|
+
# Truncate large diffs
|
|
177
|
+
max_chars = 15000
|
|
178
|
+
if len(diff) > max_chars:
|
|
179
|
+
diff = diff[:max_chars] + "\n\n[... diff truncated ...]"
|
|
180
|
+
|
|
181
|
+
focus_instructions = {
|
|
182
|
+
"security": """Focus on security issues:
|
|
183
|
+
- SQL injection, XSS, CSRF vulnerabilities
|
|
184
|
+
- Hardcoded secrets or credentials
|
|
185
|
+
- Insecure data handling
|
|
186
|
+
- Authentication/authorization flaws
|
|
187
|
+
- Input validation issues""",
|
|
188
|
+
|
|
189
|
+
"performance": """Focus on performance issues:
|
|
190
|
+
- O(n²) or worse algorithms
|
|
191
|
+
- Memory leaks or inefficient memory use
|
|
192
|
+
- Missing caching opportunities
|
|
193
|
+
- Unnecessary database queries
|
|
194
|
+
- Blocking operations in async code""",
|
|
195
|
+
|
|
196
|
+
"logic": """Focus on logic issues:
|
|
197
|
+
- Off-by-one errors
|
|
198
|
+
- Race conditions
|
|
199
|
+
- Null/undefined handling
|
|
200
|
+
- Edge cases not covered
|
|
201
|
+
- Incorrect state management""",
|
|
202
|
+
|
|
203
|
+
"style": """Focus on code style:
|
|
204
|
+
- Naming conventions
|
|
205
|
+
- Function/class organization
|
|
206
|
+
- Code duplication
|
|
207
|
+
- Missing documentation
|
|
208
|
+
- Type annotations""",
|
|
209
|
+
|
|
210
|
+
"all": """Review comprehensively for:
|
|
211
|
+
1. Security vulnerabilities
|
|
212
|
+
2. Logic errors and bugs
|
|
213
|
+
3. Performance issues
|
|
214
|
+
4. Code style and best practices"""
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
severity_note = ""
|
|
218
|
+
if strict:
|
|
219
|
+
severity_note = "\n\nIMPORTANT: This is a strict review. Flag ALL potential issues, even minor ones."
|
|
220
|
+
|
|
221
|
+
return f"""You are a senior code reviewer conducting an adversarial review of AI-generated code.
|
|
222
|
+
|
|
223
|
+
{focus_instructions.get(focus, focus_instructions["all"])}
|
|
224
|
+
{severity_note}
|
|
225
|
+
|
|
226
|
+
Review the following diff and provide:
|
|
227
|
+
|
|
228
|
+
1. **Critical Issues** - Must fix before merging (security, data loss, crashes)
|
|
229
|
+
2. **Warnings** - Should fix (bugs, performance, logic errors)
|
|
230
|
+
3. **Suggestions** - Nice to have (style, refactoring)
|
|
231
|
+
4. **Summary** - Overall assessment (APPROVE / NEEDS CHANGES / REJECT)
|
|
232
|
+
|
|
233
|
+
Be specific. Reference line numbers. Suggest fixes.
|
|
234
|
+
|
|
235
|
+
```diff
|
|
236
|
+
{diff}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Provide your review:"""
|