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.
Files changed (46) hide show
  1. up/__init__.py +1 -1
  2. up/ai_cli.py +229 -0
  3. up/cli.py +54 -9
  4. up/commands/agent.py +521 -0
  5. up/commands/bisect.py +343 -0
  6. up/commands/branch.py +350 -0
  7. up/commands/init.py +195 -6
  8. up/commands/learn.py +1392 -32
  9. up/commands/memory.py +545 -0
  10. up/commands/provenance.py +267 -0
  11. up/commands/review.py +239 -0
  12. up/commands/start.py +752 -42
  13. up/commands/status.py +173 -18
  14. up/commands/sync.py +317 -0
  15. up/commands/vibe.py +304 -0
  16. up/context.py +64 -10
  17. up/core/__init__.py +69 -0
  18. up/core/checkpoint.py +479 -0
  19. up/core/provenance.py +364 -0
  20. up/core/state.py +678 -0
  21. up/events.py +512 -0
  22. up/git/__init__.py +37 -0
  23. up/git/utils.py +270 -0
  24. up/git/worktree.py +331 -0
  25. up/learn/__init__.py +155 -0
  26. up/learn/analyzer.py +227 -0
  27. up/learn/plan.py +374 -0
  28. up/learn/research.py +511 -0
  29. up/learn/utils.py +117 -0
  30. up/memory.py +1096 -0
  31. up/parallel.py +551 -0
  32. up/templates/config/__init__.py +1 -1
  33. up/templates/docs/SKILL.md +28 -0
  34. up/templates/docs/__init__.py +341 -0
  35. up/templates/docs/standards/HEADERS.md +24 -0
  36. up/templates/docs/standards/STRUCTURE.md +18 -0
  37. up/templates/docs/standards/TEMPLATES.md +19 -0
  38. up/templates/loop/__init__.py +92 -32
  39. up/ui/__init__.py +14 -0
  40. up/ui/loop_display.py +650 -0
  41. up/ui/theme.py +137 -0
  42. {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/METADATA +160 -15
  43. up_cli-0.5.0.dist-info/RECORD +55 -0
  44. up_cli-0.2.0.dist-info/RECORD +0 -23
  45. {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
  46. {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:"""