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
up/learn/__init__.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Learning system CLI commands.
|
|
2
|
+
|
|
3
|
+
This module provides the main CLI entry points for the learning system.
|
|
4
|
+
The implementation is split across several submodules:
|
|
5
|
+
- utils: Shared utilities
|
|
6
|
+
- analyzer: Project analysis
|
|
7
|
+
- research: Topic and file learning
|
|
8
|
+
- plan: PRD generation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
|
|
17
|
+
from up.learn.utils import check_vision_map_exists, is_valid_path, find_skill_dir, display_profile, save_profile
|
|
18
|
+
from up.learn.analyzer import analyze_project, learn_self_improvement
|
|
19
|
+
from up.learn.research import learn_from_topic, learn_from_file, learn_from_project
|
|
20
|
+
from up.learn.plan import learn_analyze, learn_plan, learn_status
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group(invoke_without_command=True)
|
|
26
|
+
@click.argument("topic_or_path", required=False)
|
|
27
|
+
@click.option("--workspace", "-w", type=click.Path(exists=True), help="Workspace path")
|
|
28
|
+
@click.option("--no-ai", is_flag=True, help="Disable AI analysis")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def learn_cmd(ctx, topic_or_path: str, workspace: str, no_ai: bool):
|
|
31
|
+
"""Learning system - analyze, research, and improve.
|
|
32
|
+
|
|
33
|
+
\b
|
|
34
|
+
Usage:
|
|
35
|
+
up learn Auto-analyze and improve
|
|
36
|
+
up learn "topic" Learn about a specific topic
|
|
37
|
+
up learn "file.md" Analyze file with AI
|
|
38
|
+
up learn "project/path" Compare and learn from another project
|
|
39
|
+
|
|
40
|
+
\b
|
|
41
|
+
Subcommands:
|
|
42
|
+
up learn auto Analyze project (no vision check)
|
|
43
|
+
up learn analyze Analyze all research files
|
|
44
|
+
up learn plan Generate improvement PRD
|
|
45
|
+
up learn status Show learning system status
|
|
46
|
+
"""
|
|
47
|
+
if ctx.invoked_subcommand is not None:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
ctx.ensure_object(dict)
|
|
51
|
+
ctx.obj['workspace'] = workspace
|
|
52
|
+
ctx.obj['no_ai'] = no_ai
|
|
53
|
+
|
|
54
|
+
# Check if topic_or_path is a subcommand
|
|
55
|
+
subcommands = ["auto", "analyze", "plan", "status"]
|
|
56
|
+
if topic_or_path in subcommands:
|
|
57
|
+
subcmd = ctx.command.commands[topic_or_path]
|
|
58
|
+
ctx.invoke(subcmd, workspace=workspace)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
ws = Path(workspace) if workspace else Path.cwd()
|
|
62
|
+
use_ai = not no_ai
|
|
63
|
+
|
|
64
|
+
# No argument: self-improvement mode
|
|
65
|
+
if not topic_or_path:
|
|
66
|
+
vision_exists, vision_path = check_vision_map_exists(ws)
|
|
67
|
+
|
|
68
|
+
if not vision_exists:
|
|
69
|
+
console.print(Panel.fit(
|
|
70
|
+
"[yellow]Vision Map Not Configured[/]",
|
|
71
|
+
border_style="yellow"
|
|
72
|
+
))
|
|
73
|
+
console.print("\nThe learning system requires a configured vision map.")
|
|
74
|
+
console.print(f"\nPlease configure: [cyan]{vision_path}[/]")
|
|
75
|
+
console.print("\n[bold]Alternatives:[/]")
|
|
76
|
+
console.print(" • [cyan]up learn auto[/] - Analyze without vision map")
|
|
77
|
+
console.print(" • [cyan]up learn \"topic\"[/] - Learn about specific topic")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
learn_self_improvement(ws, use_ai=use_ai)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# Has argument: determine if topic or path
|
|
84
|
+
if is_valid_path(topic_or_path):
|
|
85
|
+
learn_from_project(ws, topic_or_path, use_ai=use_ai)
|
|
86
|
+
else:
|
|
87
|
+
learn_from_topic(ws, topic_or_path, use_ai=use_ai)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@learn_cmd.command("auto")
|
|
91
|
+
@click.option("--workspace", "-w", type=click.Path(exists=True), help="Workspace path")
|
|
92
|
+
def auto_cmd(workspace: str):
|
|
93
|
+
"""Auto-analyze project and identify improvements."""
|
|
94
|
+
ws = Path(workspace) if workspace else Path.cwd()
|
|
95
|
+
|
|
96
|
+
console.print(Panel.fit(
|
|
97
|
+
"[bold blue]Learning System[/] - Auto Analysis",
|
|
98
|
+
border_style="blue"
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
profile = analyze_project(ws)
|
|
102
|
+
|
|
103
|
+
if profile is None:
|
|
104
|
+
console.print("[red]Error: Could not analyze project[/]")
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
display_profile(profile)
|
|
108
|
+
|
|
109
|
+
save_path = save_profile(ws, profile)
|
|
110
|
+
console.print(f"\n[green]✓[/] Profile saved to: [cyan]{save_path}[/]")
|
|
111
|
+
|
|
112
|
+
console.print("\n[bold]Next Steps:[/]")
|
|
113
|
+
if profile.get("research_topics"):
|
|
114
|
+
console.print(" 1. Research topics with: [cyan]up learn \"topic\"[/]")
|
|
115
|
+
console.print(" 2. Generate PRD with: [cyan]up learn plan[/]")
|
|
116
|
+
console.print(" 3. Start development with: [cyan]up start[/]")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@learn_cmd.command("analyze")
|
|
120
|
+
@click.option("--workspace", "-w", type=click.Path(exists=True), help="Workspace path")
|
|
121
|
+
def analyze_cmd(workspace: str):
|
|
122
|
+
"""Analyze all research files with AI."""
|
|
123
|
+
ws = Path(workspace) if workspace else Path.cwd()
|
|
124
|
+
learn_analyze(ws)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@learn_cmd.command("plan")
|
|
128
|
+
@click.option("--workspace", "-w", type=click.Path(exists=True), help="Workspace path")
|
|
129
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
130
|
+
def plan_cmd(workspace: str, output: str):
|
|
131
|
+
"""Generate improvement PRD from analysis."""
|
|
132
|
+
ws = Path(workspace) if workspace else Path.cwd()
|
|
133
|
+
learn_plan(ws, output)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@learn_cmd.command("status")
|
|
137
|
+
@click.option("--workspace", "-w", type=click.Path(exists=True), help="Workspace path")
|
|
138
|
+
def status_cmd(workspace: str):
|
|
139
|
+
"""Show learning system status."""
|
|
140
|
+
ws = Path(workspace) if workspace else Path.cwd()
|
|
141
|
+
learn_status(ws)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Export for external use
|
|
145
|
+
__all__ = [
|
|
146
|
+
"learn_cmd",
|
|
147
|
+
"analyze_project",
|
|
148
|
+
"learn_self_improvement",
|
|
149
|
+
"learn_from_topic",
|
|
150
|
+
"learn_from_file",
|
|
151
|
+
"learn_from_project",
|
|
152
|
+
"learn_analyze",
|
|
153
|
+
"learn_plan",
|
|
154
|
+
"learn_status",
|
|
155
|
+
]
|
up/learn/analyzer.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Project analysis for the learning system."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from up.learn.utils import find_skill_dir, display_profile, save_profile, load_profile, record_to_memory
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def analyze_project(workspace: Path) -> dict:
|
|
16
|
+
"""Analyze project and return profile."""
|
|
17
|
+
profile = {
|
|
18
|
+
"name": workspace.name,
|
|
19
|
+
"languages": [],
|
|
20
|
+
"frameworks": [],
|
|
21
|
+
"patterns_detected": [],
|
|
22
|
+
"improvement_areas": [],
|
|
23
|
+
"research_topics": [],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Extension to language mapping
|
|
27
|
+
extensions = {
|
|
28
|
+
".py": "Python",
|
|
29
|
+
".js": "JavaScript",
|
|
30
|
+
".ts": "TypeScript",
|
|
31
|
+
".tsx": "TypeScript",
|
|
32
|
+
".go": "Go",
|
|
33
|
+
".rs": "Rust",
|
|
34
|
+
".java": "Java",
|
|
35
|
+
".rb": "Ruby",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Framework indicators
|
|
39
|
+
framework_indicators = {
|
|
40
|
+
"fastapi": "FastAPI",
|
|
41
|
+
"django": "Django",
|
|
42
|
+
"flask": "Flask",
|
|
43
|
+
"react": "React",
|
|
44
|
+
"next": "Next.js",
|
|
45
|
+
"vue": "Vue.js",
|
|
46
|
+
"langchain": "LangChain",
|
|
47
|
+
"langgraph": "LangGraph",
|
|
48
|
+
"express": "Express",
|
|
49
|
+
"pytest": "pytest",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Detect languages
|
|
53
|
+
skip_dirs = {".git", "node_modules", "__pycache__", ".venv", "venv", "build", "dist"}
|
|
54
|
+
found_languages = set()
|
|
55
|
+
|
|
56
|
+
for root, dirs, files in os.walk(workspace):
|
|
57
|
+
dirs[:] = [d for d in dirs if d not in skip_dirs]
|
|
58
|
+
for f in files:
|
|
59
|
+
ext = Path(f).suffix.lower()
|
|
60
|
+
if ext in extensions:
|
|
61
|
+
found_languages.add(extensions[ext])
|
|
62
|
+
|
|
63
|
+
profile["languages"] = sorted(found_languages)
|
|
64
|
+
|
|
65
|
+
# Detect frameworks
|
|
66
|
+
config_files = [
|
|
67
|
+
workspace / "pyproject.toml",
|
|
68
|
+
workspace / "requirements.txt",
|
|
69
|
+
workspace / "package.json",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
found_frameworks = set()
|
|
73
|
+
for config in config_files:
|
|
74
|
+
if config.exists():
|
|
75
|
+
try:
|
|
76
|
+
content = config.read_text().lower()
|
|
77
|
+
for key, name in framework_indicators.items():
|
|
78
|
+
if key in content:
|
|
79
|
+
found_frameworks.add(name)
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
profile["frameworks"] = sorted(found_frameworks)
|
|
84
|
+
|
|
85
|
+
# Detect patterns
|
|
86
|
+
pattern_indicators = {
|
|
87
|
+
r"class.*Repository": "Repository Pattern",
|
|
88
|
+
r"class.*Service": "Service Layer",
|
|
89
|
+
r"@dataclass": "Dataclasses",
|
|
90
|
+
r"async def": "Async/Await",
|
|
91
|
+
r"def test_": "Unit Tests",
|
|
92
|
+
r"Protocol\)": "Protocol Pattern",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
src_dir = workspace / "src"
|
|
96
|
+
if not src_dir.exists():
|
|
97
|
+
src_dir = workspace
|
|
98
|
+
|
|
99
|
+
found_patterns = set()
|
|
100
|
+
for py_file in src_dir.rglob("*.py"):
|
|
101
|
+
try:
|
|
102
|
+
content = py_file.read_text()
|
|
103
|
+
for pattern, name in pattern_indicators.items():
|
|
104
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
105
|
+
found_patterns.add(name)
|
|
106
|
+
except Exception:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
profile["patterns_detected"] = sorted(found_patterns)
|
|
110
|
+
|
|
111
|
+
# Identify improvements
|
|
112
|
+
improvements = []
|
|
113
|
+
if "Python" in profile["languages"]:
|
|
114
|
+
if "Unit Tests" not in profile["patterns_detected"]:
|
|
115
|
+
improvements.append("add-unit-tests")
|
|
116
|
+
if "Protocol Pattern" not in profile["patterns_detected"]:
|
|
117
|
+
improvements.append("add-interfaces")
|
|
118
|
+
|
|
119
|
+
if any(f in profile["frameworks"] for f in ["FastAPI", "Django", "Flask"]):
|
|
120
|
+
improvements.append("add-caching")
|
|
121
|
+
|
|
122
|
+
profile["improvement_areas"] = improvements
|
|
123
|
+
|
|
124
|
+
# Generate research topics
|
|
125
|
+
topic_map = {
|
|
126
|
+
"add-unit-tests": "testing best practices",
|
|
127
|
+
"add-interfaces": "Python Protocol patterns",
|
|
128
|
+
"add-caching": "caching strategies",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
topics = [topic_map[i] for i in improvements if i in topic_map]
|
|
132
|
+
for fw in profile["frameworks"][:2]:
|
|
133
|
+
topics.append(f"{fw} best practices")
|
|
134
|
+
|
|
135
|
+
profile["research_topics"] = topics[:5]
|
|
136
|
+
|
|
137
|
+
return profile
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def analyze_project_structure(project_path: Path) -> list:
|
|
141
|
+
"""Analyze project directory structure for insights."""
|
|
142
|
+
insights = []
|
|
143
|
+
|
|
144
|
+
good_patterns = {
|
|
145
|
+
"src": "Source code organization in src/ directory",
|
|
146
|
+
"tests": "Dedicated tests/ directory",
|
|
147
|
+
"docs": "Documentation directory present",
|
|
148
|
+
".github": "GitHub workflows/CI present",
|
|
149
|
+
"scripts": "Automation scripts directory",
|
|
150
|
+
"__init__.py": "Proper Python package structure",
|
|
151
|
+
"pyproject.toml": "Modern Python packaging (PEP 517)",
|
|
152
|
+
"Makefile": "Make-based automation",
|
|
153
|
+
"docker-compose": "Docker containerization",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for pattern, description in good_patterns.items():
|
|
157
|
+
if (project_path / pattern).exists() or any(project_path.glob(f"**/{pattern}")):
|
|
158
|
+
insights.append(description)
|
|
159
|
+
|
|
160
|
+
return insights[:5]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def learn_self_improvement(workspace: Path, use_ai: bool = True) -> dict:
|
|
164
|
+
"""Analyze current project for self-improvement opportunities."""
|
|
165
|
+
from rich.panel import Panel
|
|
166
|
+
|
|
167
|
+
console.print(Panel.fit(
|
|
168
|
+
"[bold blue]Learning System[/] - Self-Improvement Analysis",
|
|
169
|
+
border_style="blue"
|
|
170
|
+
))
|
|
171
|
+
|
|
172
|
+
profile = analyze_project(workspace)
|
|
173
|
+
if not profile:
|
|
174
|
+
return {}
|
|
175
|
+
|
|
176
|
+
# Load existing profile to track improvements
|
|
177
|
+
old_profile = load_profile(workspace)
|
|
178
|
+
|
|
179
|
+
# Identify what changed
|
|
180
|
+
improvements = {
|
|
181
|
+
"new_patterns": [],
|
|
182
|
+
"new_frameworks": [],
|
|
183
|
+
"addressed_improvements": [],
|
|
184
|
+
"remaining_improvements": [],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
old_patterns = set(old_profile.get("patterns_detected", []))
|
|
188
|
+
new_patterns = set(profile.get("patterns_detected", []))
|
|
189
|
+
improvements["new_patterns"] = list(new_patterns - old_patterns)
|
|
190
|
+
|
|
191
|
+
old_frameworks = set(old_profile.get("frameworks", []))
|
|
192
|
+
new_frameworks = set(profile.get("frameworks", []))
|
|
193
|
+
improvements["new_frameworks"] = list(new_frameworks - old_frameworks)
|
|
194
|
+
|
|
195
|
+
old_areas = set(old_profile.get("improvement_areas", []))
|
|
196
|
+
new_areas = set(profile.get("improvement_areas", []))
|
|
197
|
+
improvements["addressed_improvements"] = list(old_areas - new_areas)
|
|
198
|
+
improvements["remaining_improvements"] = list(new_areas)
|
|
199
|
+
|
|
200
|
+
# Display results
|
|
201
|
+
display_profile(profile)
|
|
202
|
+
|
|
203
|
+
if improvements["new_patterns"]:
|
|
204
|
+
console.print("\n[green]✓ New Patterns Adopted:[/]")
|
|
205
|
+
for p in improvements["new_patterns"]:
|
|
206
|
+
console.print(f" • {p}")
|
|
207
|
+
|
|
208
|
+
if improvements["addressed_improvements"]:
|
|
209
|
+
console.print("\n[green]✓ Improvements Addressed:[/]")
|
|
210
|
+
for a in improvements["addressed_improvements"]:
|
|
211
|
+
console.print(f" • {a}")
|
|
212
|
+
|
|
213
|
+
if improvements["remaining_improvements"]:
|
|
214
|
+
console.print("\n[yellow]○ Areas for Improvement:[/]")
|
|
215
|
+
for r in improvements["remaining_improvements"]:
|
|
216
|
+
console.print(f" • {r}")
|
|
217
|
+
|
|
218
|
+
# Save updated profile
|
|
219
|
+
save_path = save_profile(workspace, profile)
|
|
220
|
+
console.print(f"\n[green]✓[/] Profile updated: [cyan]{save_path}[/]")
|
|
221
|
+
|
|
222
|
+
# Record to memory
|
|
223
|
+
content = f"Self-improvement analysis: Found {len(profile.get('patterns_detected', []))} patterns, "
|
|
224
|
+
content += f"{len(improvements.get('new_patterns', []))} new patterns adopted"
|
|
225
|
+
record_to_memory(workspace, content)
|
|
226
|
+
|
|
227
|
+
return improvements
|