codegraph-cli 2.1.0__py3-none-any.whl → 2.1.2__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.
- codegraph_cli/__init__.py +1 -1
- codegraph_cli/agents.py +59 -3
- codegraph_cli/chat_agent.py +58 -11
- codegraph_cli/cli.py +569 -54
- codegraph_cli/cli_chat.py +204 -94
- codegraph_cli/cli_diagnose.py +13 -2
- codegraph_cli/cli_docs.py +207 -0
- codegraph_cli/cli_explore.py +1053 -0
- codegraph_cli/cli_export.py +941 -0
- codegraph_cli/cli_groups.py +33 -0
- codegraph_cli/cli_health.py +316 -0
- codegraph_cli/cli_history.py +213 -0
- codegraph_cli/cli_onboard.py +380 -0
- codegraph_cli/cli_quickstart.py +256 -0
- codegraph_cli/cli_refactor.py +17 -3
- codegraph_cli/cli_setup.py +12 -12
- codegraph_cli/cli_suggestions.py +90 -0
- codegraph_cli/cli_test.py +17 -3
- codegraph_cli/cli_tui.py +210 -0
- codegraph_cli/cli_v2.py +24 -4
- codegraph_cli/cli_watch.py +158 -0
- codegraph_cli/cli_workflows.py +255 -0
- codegraph_cli/codegen_agent.py +15 -1
- codegraph_cli/config.py +18 -5
- codegraph_cli/context_manager.py +117 -15
- codegraph_cli/crew_agents.py +32 -8
- codegraph_cli/crew_chat.py +146 -13
- codegraph_cli/crew_tools.py +30 -2
- codegraph_cli/embeddings.py +95 -5
- codegraph_cli/llm.py +42 -55
- codegraph_cli/project_context.py +64 -1
- codegraph_cli/rag.py +282 -19
- codegraph_cli/storage.py +310 -14
- codegraph_cli/vector_store.py +110 -8
- {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/METADATA +75 -21
- codegraph_cli-2.1.2.dist-info/RECORD +55 -0
- codegraph_cli-2.1.2.dist-info/entry_points.txt +2 -0
- codegraph_cli-2.1.0.dist-info/RECORD +0 -43
- codegraph_cli-2.1.0.dist-info/entry_points.txt +0 -2
- {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/WHEEL +0 -0
- {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/licenses/LICENSE +0 -0
- {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/top_level.txt +0 -0
codegraph_cli/cli_setup.py
CHANGED
|
@@ -305,10 +305,10 @@ def set_llm(
|
|
|
305
305
|
"""Quickly switch LLM provider without full setup wizard.
|
|
306
306
|
|
|
307
307
|
Examples:
|
|
308
|
-
cg set-llm groq -k YOUR_API_KEY
|
|
309
|
-
cg set-llm gemini -k YOUR_API_KEY -m gemini-2.0-flash
|
|
310
|
-
cg set-llm openrouter -k YOUR_API_KEY -m google/gemini-2.0-flash-exp:free
|
|
311
|
-
cg set-llm ollama -m qwen2.5-coder:7b
|
|
308
|
+
cg config set-llm groq -k YOUR_API_KEY
|
|
309
|
+
cg config set-llm gemini -k YOUR_API_KEY -m gemini-2.0-flash
|
|
310
|
+
cg config set-llm openrouter -k YOUR_API_KEY -m google/gemini-2.0-flash-exp:free
|
|
311
|
+
cg config set-llm ollama -m qwen2.5-coder:7b
|
|
312
312
|
"""
|
|
313
313
|
provider = provider.lower().strip()
|
|
314
314
|
|
|
@@ -467,9 +467,9 @@ def show_llm():
|
|
|
467
467
|
typer.echo("")
|
|
468
468
|
typer.echo(typer.style(" Quick Commands", bold=True))
|
|
469
469
|
typer.echo(typer.style(" ─────────────────────────────────────────", dim=True))
|
|
470
|
-
typer.echo(f" {typer.style('cg setup', fg=typer.colors.YELLOW)} Full interactive wizard")
|
|
471
|
-
typer.echo(f" {typer.style('cg set-llm <name>', fg=typer.colors.YELLOW)} Quick switch provider")
|
|
472
|
-
typer.echo(f" {typer.style('cg unset-llm', fg=typer.colors.YELLOW)} Reset / clear config")
|
|
470
|
+
typer.echo(f" {typer.style('cg config setup', fg=typer.colors.YELLOW)} Full interactive wizard")
|
|
471
|
+
typer.echo(f" {typer.style('cg config set-llm <name>', fg=typer.colors.YELLOW)} Quick switch provider")
|
|
472
|
+
typer.echo(f" {typer.style('cg config unset-llm', fg=typer.colors.YELLOW)} Reset / clear config")
|
|
473
473
|
typer.echo("")
|
|
474
474
|
|
|
475
475
|
|
|
@@ -550,9 +550,9 @@ def set_embedding(
|
|
|
550
550
|
qodo-1.5b ~6.2 GB Best quality, code-optimized
|
|
551
551
|
|
|
552
552
|
Examples:
|
|
553
|
-
cg set-embedding minilm
|
|
554
|
-
cg set-embedding jina-code
|
|
555
|
-
cg set-embedding hash
|
|
553
|
+
cg config set-embedding minilm
|
|
554
|
+
cg config set-embedding jina-code
|
|
555
|
+
cg config set-embedding hash
|
|
556
556
|
"""
|
|
557
557
|
model = model.lower().strip()
|
|
558
558
|
|
|
@@ -619,8 +619,8 @@ def show_embedding():
|
|
|
619
619
|
typer.echo("")
|
|
620
620
|
typer.echo(typer.style(" Quick Commands", bold=True))
|
|
621
621
|
typer.echo(typer.style(" ─────────────────────────────────────────", dim=True))
|
|
622
|
-
typer.echo(f" {typer.style('cg set-embedding <model>', fg=typer.colors.YELLOW)} Switch model")
|
|
623
|
-
typer.echo(f" {typer.style('cg unset-embedding', fg=typer.colors.YELLOW)} Reset to hash")
|
|
622
|
+
typer.echo(f" {typer.style('cg config set-embedding <model>', fg=typer.colors.YELLOW)} Switch model")
|
|
623
|
+
typer.echo(f" {typer.style('cg config unset-embedding', fg=typer.colors.YELLOW)} Reset to hash")
|
|
624
624
|
typer.echo("")
|
|
625
625
|
|
|
626
626
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Contextual next-step suggestions after command completion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
NEXT_STEPS: dict[str, list[str]] = {
|
|
11
|
+
"index": [
|
|
12
|
+
"[cyan]cg search[/cyan] 'your query' Search code semantically",
|
|
13
|
+
"[cyan]cg chat start[/cyan] Ask questions about your code",
|
|
14
|
+
"[cyan]cg impact[/cyan] 'symbol' See what depends on a function",
|
|
15
|
+
],
|
|
16
|
+
"search": [
|
|
17
|
+
"[cyan]cg chat start[/cyan] Discuss results with AI",
|
|
18
|
+
"[cyan]cg v2 review[/cyan] <file> Get AI code review",
|
|
19
|
+
"[cyan]cg graph[/cyan] 'symbol' Visualize dependencies",
|
|
20
|
+
],
|
|
21
|
+
"generate": [
|
|
22
|
+
"[cyan]cg v2 review[/cyan] <file> Review generated code",
|
|
23
|
+
"[cyan]cg v2 test unit[/cyan] <symbol> Generate tests for new code",
|
|
24
|
+
"[cyan]cg export-graph[/cyan] Visualize changes",
|
|
25
|
+
],
|
|
26
|
+
"diagnose": [
|
|
27
|
+
"[cyan]cg v2 test unit[/cyan] <symbol> Add tests for fixed code",
|
|
28
|
+
"[cyan]cg v2 review[/cyan] <file> Review the fixes",
|
|
29
|
+
],
|
|
30
|
+
"chat": [
|
|
31
|
+
"[cyan]cg search[/cyan] 'query' Find specific code",
|
|
32
|
+
"[cyan]cg v2 generate[/cyan] 'description' Generate new code",
|
|
33
|
+
],
|
|
34
|
+
"review": [
|
|
35
|
+
"[cyan]cg v2 diagnose fix[/cyan] <path> Auto-fix detected issues",
|
|
36
|
+
"[cyan]cg v2 refactor rename[/cyan] <old> <new> Rename symbols safely",
|
|
37
|
+
"[cyan]cg v2 test unit[/cyan] <symbol> Generate tests",
|
|
38
|
+
],
|
|
39
|
+
"refactor": [
|
|
40
|
+
"[cyan]cg v2 review[/cyan] <file> Review refactored code",
|
|
41
|
+
"[cyan]cg v2 test unit[/cyan] <symbol> Add tests after refactoring",
|
|
42
|
+
],
|
|
43
|
+
"test": [
|
|
44
|
+
"[cyan]cg v2 review[/cyan] <file> Review generated tests",
|
|
45
|
+
"[cyan]cg chat start[/cyan] Discuss test strategy with AI",
|
|
46
|
+
],
|
|
47
|
+
"impact": [
|
|
48
|
+
"[cyan]cg graph[/cyan] 'symbol' Visualize dependency graph",
|
|
49
|
+
"[cyan]cg search[/cyan] 'related query' Find related code",
|
|
50
|
+
"[cyan]cg chat start[/cyan] Deep-dive with AI",
|
|
51
|
+
],
|
|
52
|
+
"quickstart": [
|
|
53
|
+
"[cyan]cg search[/cyan] 'your query' Search code semantically",
|
|
54
|
+
"[cyan]cg chat start[/cyan] Chat with AI about your code",
|
|
55
|
+
"[cyan]cg v2 generate[/cyan] 'description' Generate new code",
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def show_next_steps(command_name: str) -> None:
|
|
61
|
+
"""Show contextual next steps after command completion.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
command_name: The name of the command that just completed.
|
|
65
|
+
"""
|
|
66
|
+
steps = NEXT_STEPS.get(command_name)
|
|
67
|
+
if not steps:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
console.print(
|
|
71
|
+
"\n"
|
|
72
|
+
+ Panel.fit(
|
|
73
|
+
"\n".join([f" {step}" for step in steps]),
|
|
74
|
+
title="[bold cyan]💡 Next steps:[/bold cyan]",
|
|
75
|
+
border_style="cyan",
|
|
76
|
+
padding=(0, 1),
|
|
77
|
+
).markup # type: ignore[attr-defined]
|
|
78
|
+
if False
|
|
79
|
+
else ""
|
|
80
|
+
)
|
|
81
|
+
# Use direct Panel printing to avoid markup issues
|
|
82
|
+
console.print()
|
|
83
|
+
console.print(
|
|
84
|
+
Panel(
|
|
85
|
+
"\n".join([f" {step}" for step in steps]),
|
|
86
|
+
title="[bold cyan]💡 Next steps:[/bold cyan]",
|
|
87
|
+
border_style="cyan",
|
|
88
|
+
padding=(0, 1),
|
|
89
|
+
)
|
|
90
|
+
)
|
codegraph_cli/cli_test.py
CHANGED
|
@@ -45,7 +45,12 @@ def generate_unit_tests(
|
|
|
45
45
|
symbol: str = typer.Argument(..., help="Function name to generate tests for"),
|
|
46
46
|
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output test file path"),
|
|
47
47
|
):
|
|
48
|
-
"""Generate unit tests for a function.
|
|
48
|
+
"""🧪 Generate unit tests for a function.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
cg v2 test unit "calculate_total"
|
|
52
|
+
cg v2 test unit "UserService.authenticate" --output tests/test_auth.py
|
|
53
|
+
"""
|
|
49
54
|
pm = ProjectManager()
|
|
50
55
|
agent = _get_testgen_agent(pm)
|
|
51
56
|
|
|
@@ -106,7 +111,12 @@ def generate_integration_tests(
|
|
|
106
111
|
flow: str = typer.Argument(..., help="User flow description"),
|
|
107
112
|
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output test file path"),
|
|
108
113
|
):
|
|
109
|
-
"""Generate integration tests for a user flow.
|
|
114
|
+
"""🧪 Generate integration tests for a user flow.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
cg v2 test integration "user login and token refresh"
|
|
118
|
+
cg v2 test integration "payment processing" --output tests/test_payments.py
|
|
119
|
+
"""
|
|
110
120
|
pm = ProjectManager()
|
|
111
121
|
agent = _get_testgen_agent(pm)
|
|
112
122
|
|
|
@@ -155,7 +165,11 @@ def generate_integration_tests(
|
|
|
155
165
|
def show_coverage_prediction(
|
|
156
166
|
symbol: str = typer.Argument(..., help="Function to analyze"),
|
|
157
167
|
):
|
|
158
|
-
"""Show predicted coverage impact of generating tests.
|
|
168
|
+
"""📊 Show predicted coverage impact of generating tests.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
cg v2 test coverage "process_payment"
|
|
172
|
+
"""
|
|
159
173
|
pm = ProjectManager()
|
|
160
174
|
agent = _get_testgen_agent(pm)
|
|
161
175
|
|
codegraph_cli/cli_tui.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Interactive TUI menu for CodeGraph CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.prompt import Prompt
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def show_interactive_menu() -> None:
|
|
14
|
+
"""Display the interactive TUI menu when no command is provided."""
|
|
15
|
+
console.print()
|
|
16
|
+
console.print(
|
|
17
|
+
Panel.fit(
|
|
18
|
+
"[bold cyan]🧠 CodeGraph CLI[/bold cyan]\n"
|
|
19
|
+
"[dim]AI-powered code intelligence & multi-agent assistant[/dim]",
|
|
20
|
+
border_style="cyan",
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
while True:
|
|
25
|
+
console.print("\n[bold]What would you like to do?[/bold]\n")
|
|
26
|
+
|
|
27
|
+
choices = [
|
|
28
|
+
"1. 🔍 Search my codebase",
|
|
29
|
+
"2. 💬 Chat with AI about my code",
|
|
30
|
+
"3. ✨ Generate new code",
|
|
31
|
+
"4. 📊 Analyze code impact",
|
|
32
|
+
"5. 🔧 Review and improve code",
|
|
33
|
+
"6. ⚙️ Configure settings",
|
|
34
|
+
"7. 📚 Learn more / tutorial",
|
|
35
|
+
"8. 🏥 Project health dashboard",
|
|
36
|
+
"0. Exit",
|
|
37
|
+
]
|
|
38
|
+
for choice in choices:
|
|
39
|
+
console.print(f" {choice}")
|
|
40
|
+
|
|
41
|
+
selection = Prompt.ask(
|
|
42
|
+
"\nChoice",
|
|
43
|
+
choices=["0", "1", "2", "3", "4", "5", "6", "7", "8"],
|
|
44
|
+
default="0",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if selection == "0":
|
|
48
|
+
console.print("[cyan]Goodbye![/cyan]")
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
elif selection == "1":
|
|
52
|
+
query = Prompt.ask("Search query")
|
|
53
|
+
if query.strip():
|
|
54
|
+
_run_search(query.strip())
|
|
55
|
+
|
|
56
|
+
elif selection == "2":
|
|
57
|
+
_run_chat()
|
|
58
|
+
|
|
59
|
+
elif selection == "3":
|
|
60
|
+
desc = Prompt.ask("What code to generate")
|
|
61
|
+
if desc.strip():
|
|
62
|
+
_run_generate(desc.strip())
|
|
63
|
+
|
|
64
|
+
elif selection == "4":
|
|
65
|
+
symbol = Prompt.ask("Symbol to analyze")
|
|
66
|
+
if symbol.strip():
|
|
67
|
+
_run_impact(symbol.strip())
|
|
68
|
+
|
|
69
|
+
elif selection == "5":
|
|
70
|
+
path = Prompt.ask("File path to review")
|
|
71
|
+
if path.strip():
|
|
72
|
+
_run_review(path.strip())
|
|
73
|
+
|
|
74
|
+
elif selection == "6":
|
|
75
|
+
_run_setup()
|
|
76
|
+
|
|
77
|
+
elif selection == "7":
|
|
78
|
+
_show_tutorial()
|
|
79
|
+
|
|
80
|
+
elif selection == "8":
|
|
81
|
+
_run_health()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _run_search(query: str) -> None:
|
|
85
|
+
"""Run search from TUI."""
|
|
86
|
+
try:
|
|
87
|
+
from .storage import ProjectManager, GraphStore
|
|
88
|
+
from .orchestrator import MCPOrchestrator
|
|
89
|
+
|
|
90
|
+
pm = ProjectManager()
|
|
91
|
+
project = pm.get_current_project()
|
|
92
|
+
if not project:
|
|
93
|
+
console.print("[red]✗[/red] No project loaded. Run 'cg index <path>' first.")
|
|
94
|
+
return
|
|
95
|
+
store = GraphStore(pm.project_dir(project))
|
|
96
|
+
orchestrator = MCPOrchestrator(store)
|
|
97
|
+
results = orchestrator.search(query, top_k=5)
|
|
98
|
+
if not results:
|
|
99
|
+
console.print("[yellow]No results found.[/yellow]")
|
|
100
|
+
else:
|
|
101
|
+
for item in results:
|
|
102
|
+
console.print(
|
|
103
|
+
f" [{item.node_type}] {item.qualname} "
|
|
104
|
+
f"[dim]score={item.score:.3f}[/dim]"
|
|
105
|
+
)
|
|
106
|
+
console.print(f" [dim]{item.file_path}:{item.start_line}-{item.end_line}[/dim]")
|
|
107
|
+
store.close()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _run_chat() -> None:
|
|
113
|
+
"""Start chat from TUI."""
|
|
114
|
+
try:
|
|
115
|
+
# Import and invoke via typer to handle all the setup
|
|
116
|
+
console.print("[cyan]Starting chat... (use /exit to return)[/cyan]")
|
|
117
|
+
from .cli_chat import start_chat
|
|
118
|
+
start_chat()
|
|
119
|
+
except (typer.Exit, SystemExit):
|
|
120
|
+
pass
|
|
121
|
+
except Exception as e:
|
|
122
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _run_generate(description: str) -> None:
|
|
126
|
+
"""Run code generation from TUI."""
|
|
127
|
+
try:
|
|
128
|
+
from .cli_v2 import generate_code
|
|
129
|
+
generate_code(description)
|
|
130
|
+
except (typer.Exit, SystemExit):
|
|
131
|
+
pass
|
|
132
|
+
except Exception as e:
|
|
133
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _run_impact(symbol: str) -> None:
|
|
137
|
+
"""Run impact analysis from TUI."""
|
|
138
|
+
try:
|
|
139
|
+
from .storage import ProjectManager, GraphStore
|
|
140
|
+
from .orchestrator import MCPOrchestrator
|
|
141
|
+
|
|
142
|
+
pm = ProjectManager()
|
|
143
|
+
project = pm.get_current_project()
|
|
144
|
+
if not project:
|
|
145
|
+
console.print("[red]✗[/red] No project loaded.")
|
|
146
|
+
return
|
|
147
|
+
store = GraphStore(pm.project_dir(project))
|
|
148
|
+
orchestrator = MCPOrchestrator(store)
|
|
149
|
+
report = orchestrator.impact(symbol, hops=2)
|
|
150
|
+
console.print(f"Root: {report.root}")
|
|
151
|
+
if report.impacted:
|
|
152
|
+
console.print("Impacted symbols:")
|
|
153
|
+
for imp in report.impacted:
|
|
154
|
+
console.print(f" • {imp}")
|
|
155
|
+
console.print(f"\n{report.explanation}")
|
|
156
|
+
store.close()
|
|
157
|
+
except Exception as e:
|
|
158
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _run_review(path: str) -> None:
|
|
162
|
+
"""Run code review from TUI."""
|
|
163
|
+
try:
|
|
164
|
+
from .cli_v2 import review_code
|
|
165
|
+
review_code(path)
|
|
166
|
+
except (typer.Exit, SystemExit):
|
|
167
|
+
pass
|
|
168
|
+
except Exception as e:
|
|
169
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _run_setup() -> None:
|
|
173
|
+
"""Run setup wizard from TUI."""
|
|
174
|
+
try:
|
|
175
|
+
from .cli_setup import setup
|
|
176
|
+
setup()
|
|
177
|
+
except (typer.Exit, SystemExit):
|
|
178
|
+
pass
|
|
179
|
+
except Exception as e:
|
|
180
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _run_health() -> None:
|
|
184
|
+
"""Run health dashboard from TUI."""
|
|
185
|
+
try:
|
|
186
|
+
from .cli_health import health_dashboard
|
|
187
|
+
health_dashboard()
|
|
188
|
+
except (typer.Exit, SystemExit):
|
|
189
|
+
pass
|
|
190
|
+
except Exception as e:
|
|
191
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _show_tutorial() -> None:
|
|
195
|
+
"""Show interactive tutorial."""
|
|
196
|
+
console.print(
|
|
197
|
+
Panel(
|
|
198
|
+
"📚 [bold]Quick Tutorial[/bold]\n\n"
|
|
199
|
+
"1. [cyan]Index your project[/cyan]: cg index ./my-project\n"
|
|
200
|
+
"2. [cyan]Search code[/cyan]: cg search 'authentication'\n"
|
|
201
|
+
"3. [cyan]Chat with AI[/cyan]: cg chat start\n"
|
|
202
|
+
"4. [cyan]Generate code[/cyan]: cg v2 generate 'add API endpoint'\n"
|
|
203
|
+
"5. [cyan]Impact analysis[/cyan]: cg impact 'symbol_name'\n"
|
|
204
|
+
"6. [cyan]Code review[/cyan]: cg v2 review path/to/file.py\n"
|
|
205
|
+
"7. [cyan]Project health[/cyan]: cg health dashboard\n\n"
|
|
206
|
+
"For full reference: cg cheatsheet\n"
|
|
207
|
+
"For documentation: cg learn",
|
|
208
|
+
border_style="blue",
|
|
209
|
+
)
|
|
210
|
+
)
|
codegraph_cli/cli_v2.py
CHANGED
|
@@ -56,7 +56,13 @@ def generate_code(
|
|
|
56
56
|
llm_api_key: Optional[str] = typer.Option(config.LLM_API_KEY, help="API key for LLM"),
|
|
57
57
|
llm_model: str = typer.Option(config.LLM_MODEL, help="LLM model"),
|
|
58
58
|
):
|
|
59
|
-
"""Generate code from natural language description (v2.0 experimental).
|
|
59
|
+
"""✨ Generate code from natural language description (v2.0 experimental).
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
cg v2 generate 'add REST API endpoint for users'
|
|
63
|
+
cg v2 generate 'add login function' --file auth.py
|
|
64
|
+
cg v2 generate 'create data model' --output models/ --auto-apply
|
|
65
|
+
"""
|
|
60
66
|
pm = ProjectManager()
|
|
61
67
|
agent = _get_codegen_agent(pm)
|
|
62
68
|
|
|
@@ -116,7 +122,11 @@ def generate_code(
|
|
|
116
122
|
def rollback_changes(
|
|
117
123
|
backup_id: str = typer.Argument(..., help="Backup ID to rollback to"),
|
|
118
124
|
):
|
|
119
|
-
"""Rollback to a previous backup.
|
|
125
|
+
"""⏪ Rollback to a previous backup.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
cg v2 rollback backup_20240101_120000
|
|
129
|
+
"""
|
|
120
130
|
diff_engine = DiffEngine()
|
|
121
131
|
|
|
122
132
|
typer.echo(f"🔄 Rolling back to backup: {backup_id}")
|
|
@@ -130,7 +140,11 @@ def rollback_changes(
|
|
|
130
140
|
|
|
131
141
|
@v2_app.command("list-backups")
|
|
132
142
|
def list_backups():
|
|
133
|
-
"""List all available backups.
|
|
143
|
+
"""📦 List all available backups.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
cg v2 list-backups
|
|
147
|
+
"""
|
|
134
148
|
diff_engine = DiffEngine()
|
|
135
149
|
backups = diff_engine.list_backups()
|
|
136
150
|
|
|
@@ -156,7 +170,13 @@ def review_code(
|
|
|
156
170
|
use_llm: bool = typer.Option(False, "--llm", help="Use LLM for deeper analysis"),
|
|
157
171
|
show_fixes: bool = typer.Option(False, "--fix", help="Show auto-fix suggestions"),
|
|
158
172
|
):
|
|
159
|
-
"""Run AI-powered code review on a file.
|
|
173
|
+
"""🔍 Run AI-powered code review on a file.
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
cg v2 review src/auth.py
|
|
177
|
+
cg v2 review src/models.py --check security --llm
|
|
178
|
+
cg v2 review src/api.py --fix --verbose
|
|
179
|
+
"""
|
|
160
180
|
from .bug_detector import BugDetector
|
|
161
181
|
from .security_scanner import SecurityScanner
|
|
162
182
|
from .performance_analyzer import PerformanceAnalyzer
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Watch mode for auto-reindexing on file changes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
watch_app = typer.Typer(help="👀 Watch mode for auto-reindexing")
|
|
14
|
+
|
|
15
|
+
# Supported code file extensions
|
|
16
|
+
WATCHED_EXTENSIONS = {
|
|
17
|
+
".py", ".js", ".ts", ".tsx", ".jsx",
|
|
18
|
+
".go", ".rs", ".java", ".kt", ".kts",
|
|
19
|
+
".rb", ".php", ".ex", ".exs",
|
|
20
|
+
".c", ".cpp", ".h", ".hpp", ".cs",
|
|
21
|
+
".swift", ".dart",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CodeChangeHandler:
|
|
26
|
+
"""Handle file system events and trigger re-indexing."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, reindex_callback, debounce_seconds: float = 2.0):
|
|
29
|
+
from watchdog.events import FileSystemEventHandler
|
|
30
|
+
self._base_class = FileSystemEventHandler
|
|
31
|
+
self.reindex_callback = reindex_callback
|
|
32
|
+
self.last_reindex = 0.0
|
|
33
|
+
self.debounce_seconds = debounce_seconds
|
|
34
|
+
self._pending_files: set[str] = set()
|
|
35
|
+
|
|
36
|
+
def dispatch(self, event):
|
|
37
|
+
"""Route events to handler methods."""
|
|
38
|
+
if event.is_directory:
|
|
39
|
+
return
|
|
40
|
+
if hasattr(event, "src_path"):
|
|
41
|
+
self._handle_change(event.src_path)
|
|
42
|
+
|
|
43
|
+
def _handle_change(self, src_path: str):
|
|
44
|
+
file_path = Path(src_path)
|
|
45
|
+
if file_path.suffix not in WATCHED_EXTENSIONS:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Skip hidden/temp files
|
|
49
|
+
if any(part.startswith(".") for part in file_path.parts):
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
now = time.time()
|
|
53
|
+
self._pending_files.add(str(file_path))
|
|
54
|
+
|
|
55
|
+
if now - self.last_reindex < self.debounce_seconds:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Flush pending
|
|
59
|
+
files = list(self._pending_files)
|
|
60
|
+
self._pending_files.clear()
|
|
61
|
+
self.last_reindex = now
|
|
62
|
+
|
|
63
|
+
for f in files:
|
|
64
|
+
self.reindex_callback(Path(f))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@watch_app.command("start")
|
|
68
|
+
def watch(
|
|
69
|
+
path: str = typer.Argument(".", help="Path to watch for changes."),
|
|
70
|
+
interval: float = typer.Option(2.0, "--interval", "-i", help="Debounce interval in seconds."),
|
|
71
|
+
full_reindex: bool = typer.Option(False, "--full", help="Re-index entire project on each change."),
|
|
72
|
+
):
|
|
73
|
+
"""👀 Watch mode — auto-reindex on file changes.
|
|
74
|
+
|
|
75
|
+
Monitors your project directory for file changes and automatically
|
|
76
|
+
re-indexes modified files to keep the code graph up to date.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
cg watch
|
|
80
|
+
cg watch ./src --interval 5
|
|
81
|
+
cg watch --full
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
from watchdog.observers import Observer
|
|
85
|
+
from watchdog.events import FileSystemEventHandler
|
|
86
|
+
except ImportError:
|
|
87
|
+
console.print("[red]✗[/red] watchdog is not installed.")
|
|
88
|
+
console.print("[dim]Install with: pip install watchdog[/dim]")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
watch_path = Path(path).resolve()
|
|
92
|
+
if not watch_path.exists():
|
|
93
|
+
console.print(f"[red]✗[/red] Path not found: {path}")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
|
|
96
|
+
from .storage import ProjectManager, GraphStore
|
|
97
|
+
from .orchestrator import MCPOrchestrator
|
|
98
|
+
|
|
99
|
+
pm = ProjectManager()
|
|
100
|
+
project = pm.get_current_project()
|
|
101
|
+
if not project:
|
|
102
|
+
console.print("[red]✗[/red] No project loaded. Run 'cg index <path>' first.")
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
|
|
105
|
+
project_dir = pm.project_dir(project)
|
|
106
|
+
reindex_count = 0
|
|
107
|
+
|
|
108
|
+
def reindex_file(file_path: Path):
|
|
109
|
+
nonlocal reindex_count
|
|
110
|
+
try:
|
|
111
|
+
if full_reindex:
|
|
112
|
+
store = GraphStore(project_dir)
|
|
113
|
+
orchestrator = MCPOrchestrator(store)
|
|
114
|
+
stats = orchestrator.index(watch_path)
|
|
115
|
+
store.close()
|
|
116
|
+
console.print(
|
|
117
|
+
f" [green]✓[/green] Full re-index: {stats['nodes']} nodes, {stats['edges']} edges"
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
# Incremental: re-index the single changed file
|
|
121
|
+
store = GraphStore(project_dir)
|
|
122
|
+
orchestrator = MCPOrchestrator(store)
|
|
123
|
+
# For now, do a full re-index (single-file not yet supported in orchestrator)
|
|
124
|
+
stats = orchestrator.index(watch_path)
|
|
125
|
+
store.close()
|
|
126
|
+
console.print(f" [green]✓[/green] Re-indexed ({file_path.name} changed)")
|
|
127
|
+
reindex_count += 1
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(f" [red]✗[/red] Re-index failed: {e}")
|
|
130
|
+
|
|
131
|
+
console.print(f"\n[bold green]👀 Watching[/bold green] [cyan]{watch_path}[/cyan] for changes...")
|
|
132
|
+
console.print(f"[dim] Debounce: {interval}s")
|
|
133
|
+
console.print(f" Mode: {'full re-index' if full_reindex else 'incremental'}")
|
|
134
|
+
console.print(f" Project: {project}")
|
|
135
|
+
console.print(f" Press Ctrl+C to stop[/dim]\n")
|
|
136
|
+
|
|
137
|
+
handler = CodeChangeHandler(reindex_file, debounce_seconds=interval)
|
|
138
|
+
|
|
139
|
+
# Wrap as proper watchdog handler
|
|
140
|
+
class WatchdogAdapter(FileSystemEventHandler):
|
|
141
|
+
def on_modified(self, event):
|
|
142
|
+
handler.dispatch(event)
|
|
143
|
+
|
|
144
|
+
def on_created(self, event):
|
|
145
|
+
handler.dispatch(event)
|
|
146
|
+
|
|
147
|
+
observer = Observer()
|
|
148
|
+
observer.schedule(WatchdogAdapter(), str(watch_path), recursive=True)
|
|
149
|
+
observer.start()
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
while True:
|
|
153
|
+
time.sleep(1)
|
|
154
|
+
except KeyboardInterrupt:
|
|
155
|
+
observer.stop()
|
|
156
|
+
console.print(f"\n[yellow]Stopped watching.[/yellow] Re-indexed {reindex_count} time(s).")
|
|
157
|
+
|
|
158
|
+
observer.join()
|