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
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""CLI command for AI-generated project onboarding README."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
|
+
|
|
13
|
+
from . import config
|
|
14
|
+
from .llm import LocalLLM
|
|
15
|
+
from .storage import GraphStore, ProjectManager
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _collect_project_intel(store: GraphStore, source_path: Optional[Path]) -> dict:
|
|
21
|
+
"""Gather all graph data needed to generate the README.
|
|
22
|
+
|
|
23
|
+
Returns a dict with keys:
|
|
24
|
+
files, functions, classes, modules, edges, entry_points,
|
|
25
|
+
top_callers, dependency_tree, docstrings, file_tree
|
|
26
|
+
"""
|
|
27
|
+
nodes = store.get_nodes()
|
|
28
|
+
edges = store.get_edges()
|
|
29
|
+
|
|
30
|
+
files: set[str] = set()
|
|
31
|
+
functions: list[dict] = []
|
|
32
|
+
classes: list[dict] = []
|
|
33
|
+
modules: list[dict] = []
|
|
34
|
+
docstrings: dict[str, str] = {} # qualname → docstring
|
|
35
|
+
|
|
36
|
+
for n in nodes:
|
|
37
|
+
files.add(n["file_path"])
|
|
38
|
+
entry = {
|
|
39
|
+
"name": n["name"],
|
|
40
|
+
"qualname": n["qualname"],
|
|
41
|
+
"file": n["file_path"],
|
|
42
|
+
"lines": f"L{n['start_line']}-{n['end_line']}",
|
|
43
|
+
}
|
|
44
|
+
if n["node_type"] == "function":
|
|
45
|
+
functions.append(entry)
|
|
46
|
+
elif n["node_type"] == "class":
|
|
47
|
+
classes.append(entry)
|
|
48
|
+
elif n["node_type"] == "module":
|
|
49
|
+
modules.append(entry)
|
|
50
|
+
if n["docstring"] and n["docstring"].strip():
|
|
51
|
+
docstrings[n["qualname"]] = n["docstring"].strip()
|
|
52
|
+
|
|
53
|
+
# Build call count per function (who gets called the most?)
|
|
54
|
+
call_count: dict[str, int] = {}
|
|
55
|
+
callers_of: dict[str, list[str]] = {}
|
|
56
|
+
imports: list[tuple[str, str]] = []
|
|
57
|
+
|
|
58
|
+
for e in edges:
|
|
59
|
+
if e["edge_type"] == "calls":
|
|
60
|
+
call_count[e["dst"]] = call_count.get(e["dst"], 0) + 1
|
|
61
|
+
callers_of.setdefault(e["dst"], []).append(e["src"])
|
|
62
|
+
elif e["edge_type"] == "depends_on":
|
|
63
|
+
imports.append((e["src"], e["dst"]))
|
|
64
|
+
|
|
65
|
+
# Top-called functions (likely core logic)
|
|
66
|
+
top_called = sorted(call_count.items(), key=lambda x: x[1], reverse=True)[:15]
|
|
67
|
+
|
|
68
|
+
# Entry points: functions that are called by nothing (zero incoming call edges)
|
|
69
|
+
all_dst = {e["dst"] for e in edges if e["edge_type"] == "calls"}
|
|
70
|
+
all_func_ids = {f["qualname"] for f in functions}
|
|
71
|
+
entry_points = sorted(all_func_ids - all_dst)[:20]
|
|
72
|
+
|
|
73
|
+
# File tree (compact)
|
|
74
|
+
sorted_files = sorted(files)
|
|
75
|
+
file_tree = "\n".join(f" {f}" for f in sorted_files[:60])
|
|
76
|
+
if len(sorted_files) > 60:
|
|
77
|
+
file_tree += f"\n ... and {len(sorted_files) - 60} more files"
|
|
78
|
+
|
|
79
|
+
# Detect likely config / entry files
|
|
80
|
+
notable_files = []
|
|
81
|
+
for f in sorted_files:
|
|
82
|
+
name = Path(f).name.lower()
|
|
83
|
+
if name in (
|
|
84
|
+
"main.py", "app.py", "cli.py", "__main__.py", "server.py",
|
|
85
|
+
"wsgi.py", "asgi.py", "manage.py", "setup.py", "conftest.py",
|
|
86
|
+
"settings.py", "config.py", "urls.py", "routes.py",
|
|
87
|
+
"index.js", "index.ts", "app.js", "app.ts", "server.js",
|
|
88
|
+
):
|
|
89
|
+
notable_files.append(f)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"file_count": len(files),
|
|
93
|
+
"function_count": len(functions),
|
|
94
|
+
"class_count": len(classes),
|
|
95
|
+
"module_count": len(modules),
|
|
96
|
+
"edge_count": len(edges),
|
|
97
|
+
"files": sorted_files,
|
|
98
|
+
"notable_files": notable_files,
|
|
99
|
+
"functions": functions,
|
|
100
|
+
"classes": classes,
|
|
101
|
+
"entry_points": entry_points,
|
|
102
|
+
"top_called": top_called,
|
|
103
|
+
"imports": imports[:30],
|
|
104
|
+
"docstrings": docstrings,
|
|
105
|
+
"file_tree": file_tree,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _build_onboard_prompt(project_name: str, intel: dict, source_path: Optional[str]) -> str:
|
|
110
|
+
"""Build the LLM prompt from collected intelligence."""
|
|
111
|
+
# Top-called functions with callers
|
|
112
|
+
top_called_text = ""
|
|
113
|
+
for qualname, count in intel["top_called"]:
|
|
114
|
+
top_called_text += f" - {qualname} (called {count} times)\n"
|
|
115
|
+
|
|
116
|
+
# Entry points
|
|
117
|
+
entry_text = "\n".join(f" - {ep}" for ep in intel["entry_points"][:15])
|
|
118
|
+
|
|
119
|
+
# Classes
|
|
120
|
+
class_text = "\n".join(
|
|
121
|
+
f" - {c['qualname']} ({c['file']})" for c in intel["classes"][:20]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Notable files
|
|
125
|
+
notable_text = "\n".join(f" - {f}" for f in intel["notable_files"])
|
|
126
|
+
|
|
127
|
+
# Key docstrings (first 12)
|
|
128
|
+
doc_text = ""
|
|
129
|
+
for qualname, doc in list(intel["docstrings"].items())[:12]:
|
|
130
|
+
first_line = doc.split("\n")[0][:120]
|
|
131
|
+
doc_text += f" - {qualname}: {first_line}\n"
|
|
132
|
+
|
|
133
|
+
# Import relationships (module dependencies)
|
|
134
|
+
import_text = "\n".join(
|
|
135
|
+
f" - {src} → {dst}" for src, dst in intel["imports"][:20]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
prompt = f"""You are a senior developer writing a README.md for a project you just analyzed.
|
|
139
|
+
Generate a complete, professional README.md based on the code graph analysis below.
|
|
140
|
+
|
|
141
|
+
PROJECT: {project_name}
|
|
142
|
+
SOURCE: {source_path or 'unknown'}
|
|
143
|
+
|
|
144
|
+
STATISTICS:
|
|
145
|
+
- {intel['file_count']} files
|
|
146
|
+
- {intel['function_count']} functions
|
|
147
|
+
- {intel['class_count']} classes
|
|
148
|
+
- {intel['edge_count']} dependency edges
|
|
149
|
+
|
|
150
|
+
FILE STRUCTURE:
|
|
151
|
+
{intel['file_tree']}
|
|
152
|
+
|
|
153
|
+
NOTABLE FILES (likely entry points / config):
|
|
154
|
+
{notable_text or ' (none detected)'}
|
|
155
|
+
|
|
156
|
+
TOP ENTRY POINTS (functions not called by anything else — likely CLI/API handlers):
|
|
157
|
+
{entry_text or ' (none detected)'}
|
|
158
|
+
|
|
159
|
+
MOST-CALLED FUNCTIONS (core logic):
|
|
160
|
+
{top_called_text or ' (none detected)'}
|
|
161
|
+
|
|
162
|
+
CLASSES:
|
|
163
|
+
{class_text or ' (none)'}
|
|
164
|
+
|
|
165
|
+
KEY DOCSTRINGS:
|
|
166
|
+
{doc_text or ' (none found)'}
|
|
167
|
+
|
|
168
|
+
MODULE DEPENDENCIES:
|
|
169
|
+
{import_text or ' (none)'}
|
|
170
|
+
|
|
171
|
+
INSTRUCTIONS:
|
|
172
|
+
Write a complete README.md with these sections:
|
|
173
|
+
1. **Project title and one-line description** — infer the purpose from the code structure, docstrings, and function names.
|
|
174
|
+
2. **Overview** — 2-3 paragraphs explaining what this project does, its architecture, and key design decisions.
|
|
175
|
+
3. **Project Structure** — a clean tree view of the major directories/files with brief descriptions.
|
|
176
|
+
4. **Key Modules** — describe the 5-8 most important modules and what they do.
|
|
177
|
+
5. **Getting Started** — installation steps (infer from file structure: requirements.txt, pyproject.toml, package.json, etc.)
|
|
178
|
+
6. **Usage** — example commands or API usage (infer from entry points and CLI handlers).
|
|
179
|
+
7. **Architecture** — how the components connect. Use the dependency graph data.
|
|
180
|
+
8. **Contributing** — brief contributing guidelines.
|
|
181
|
+
|
|
182
|
+
RULES:
|
|
183
|
+
- Output ONLY the markdown content, no preamble or explanation.
|
|
184
|
+
- Be specific — use real function names, file paths, and class names from the analysis.
|
|
185
|
+
- Do NOT invent features not evidenced by the code graph.
|
|
186
|
+
- Keep it concise but comprehensive. Target ~200-400 lines.
|
|
187
|
+
- Use proper markdown formatting with headers, code blocks, and tables where appropriate.
|
|
188
|
+
"""
|
|
189
|
+
return prompt
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _generate_fallback_readme(project_name: str, intel: dict, source_path: Optional[str]) -> str:
|
|
193
|
+
"""Generate a README without LLM — pure template from graph data."""
|
|
194
|
+
lines = [
|
|
195
|
+
f"# {project_name}",
|
|
196
|
+
"",
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# One-liner from first docstring or generic
|
|
200
|
+
first_doc = next(iter(intel["docstrings"].values()), None)
|
|
201
|
+
if first_doc:
|
|
202
|
+
lines.append(f"> {first_doc.split(chr(10))[0]}")
|
|
203
|
+
else:
|
|
204
|
+
lines.append(f"> Auto-generated documentation for **{project_name}**.")
|
|
205
|
+
lines.append("")
|
|
206
|
+
|
|
207
|
+
# Stats
|
|
208
|
+
lines.extend([
|
|
209
|
+
"## Overview",
|
|
210
|
+
"",
|
|
211
|
+
f"| Metric | Count |",
|
|
212
|
+
f"|--------|-------|",
|
|
213
|
+
f"| Files | {intel['file_count']} |",
|
|
214
|
+
f"| Functions | {intel['function_count']} |",
|
|
215
|
+
f"| Classes | {intel['class_count']} |",
|
|
216
|
+
f"| Dependencies | {intel['edge_count']} |",
|
|
217
|
+
"",
|
|
218
|
+
])
|
|
219
|
+
|
|
220
|
+
# Project structure
|
|
221
|
+
lines.extend(["## Project Structure", "", "```"])
|
|
222
|
+
for f in intel["files"][:40]:
|
|
223
|
+
lines.append(f)
|
|
224
|
+
if len(intel["files"]) > 40:
|
|
225
|
+
lines.append(f"... and {len(intel['files']) - 40} more files")
|
|
226
|
+
lines.extend(["```", ""])
|
|
227
|
+
|
|
228
|
+
# Key classes
|
|
229
|
+
if intel["classes"]:
|
|
230
|
+
lines.extend(["## Key Classes", ""])
|
|
231
|
+
for c in intel["classes"][:15]:
|
|
232
|
+
doc = intel["docstrings"].get(c["qualname"], "")
|
|
233
|
+
desc = f" — {doc.split(chr(10))[0]}" if doc else ""
|
|
234
|
+
lines.append(f"- **`{c['qualname']}`** ({c['file']}){desc}")
|
|
235
|
+
lines.append("")
|
|
236
|
+
|
|
237
|
+
# Entry points
|
|
238
|
+
if intel["entry_points"]:
|
|
239
|
+
lines.extend(["## Entry Points", "", "Functions that are not called by any other indexed code:", ""])
|
|
240
|
+
for ep in intel["entry_points"][:15]:
|
|
241
|
+
lines.append(f"- `{ep}`")
|
|
242
|
+
lines.append("")
|
|
243
|
+
|
|
244
|
+
# Most-called functions
|
|
245
|
+
if intel["top_called"]:
|
|
246
|
+
lines.extend(["## Core Functions", "", "Most frequently called functions in the codebase:", ""])
|
|
247
|
+
lines.append("| Function | Call Count |")
|
|
248
|
+
lines.append("|----------|-----------|")
|
|
249
|
+
for qualname, count in intel["top_called"]:
|
|
250
|
+
lines.append(f"| `{qualname}` | {count} |")
|
|
251
|
+
lines.append("")
|
|
252
|
+
|
|
253
|
+
# Notable files
|
|
254
|
+
if intel["notable_files"]:
|
|
255
|
+
lines.extend(["## Notable Files", ""])
|
|
256
|
+
for f in intel["notable_files"]:
|
|
257
|
+
lines.append(f"- `{f}`")
|
|
258
|
+
lines.append("")
|
|
259
|
+
|
|
260
|
+
lines.extend([
|
|
261
|
+
"---",
|
|
262
|
+
"",
|
|
263
|
+
f"*Generated by [CodeGraph CLI](https://github.com/al1-nasir/codegraph-cli) from code graph analysis.*",
|
|
264
|
+
])
|
|
265
|
+
|
|
266
|
+
return "\n".join(lines)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def onboard(
|
|
270
|
+
output: Optional[Path] = typer.Option(
|
|
271
|
+
None, "--output", "-o",
|
|
272
|
+
help="Output file path (default: prints to stdout).",
|
|
273
|
+
),
|
|
274
|
+
save: bool = typer.Option(
|
|
275
|
+
False, "--save", "-s",
|
|
276
|
+
help="Save as ONBOARD.md in the project source directory.",
|
|
277
|
+
),
|
|
278
|
+
no_llm: bool = typer.Option(
|
|
279
|
+
False, "--no-llm",
|
|
280
|
+
help="Skip LLM and generate from template only.",
|
|
281
|
+
),
|
|
282
|
+
llm_provider: str = typer.Option(config.LLM_PROVIDER, help="LLM provider."),
|
|
283
|
+
llm_model: str = typer.Option(config.LLM_MODEL, help="LLM model."),
|
|
284
|
+
llm_api_key: Optional[str] = typer.Option(config.LLM_API_KEY, help="API key."),
|
|
285
|
+
):
|
|
286
|
+
"""🚀 Auto-generate a project README from the code graph.
|
|
287
|
+
|
|
288
|
+
Analyzes the indexed project's structure, dependencies, entry points,
|
|
289
|
+
and docstrings to produce a comprehensive README.md — either via LLM
|
|
290
|
+
or from a pure template.
|
|
291
|
+
|
|
292
|
+
Examples:
|
|
293
|
+
cg onboard # print to stdout
|
|
294
|
+
cg onboard --save # save as ONBOARD.md in project dir
|
|
295
|
+
cg onboard -o README.md # save to specific file
|
|
296
|
+
cg onboard --no-llm # template only, no LLM call
|
|
297
|
+
"""
|
|
298
|
+
pm = ProjectManager()
|
|
299
|
+
project = pm.get_current_project()
|
|
300
|
+
if not project:
|
|
301
|
+
console.print("[red]❌ No project loaded.[/red]")
|
|
302
|
+
console.print("[dim]Use: cg project index <path> or cg project load <name>[/dim]")
|
|
303
|
+
raise typer.Exit(1)
|
|
304
|
+
|
|
305
|
+
project_dir = pm.project_dir(project)
|
|
306
|
+
if not project_dir.exists():
|
|
307
|
+
console.print(f"[red]❌ Project '{project}' not found in memory.[/red]")
|
|
308
|
+
raise typer.Exit(1)
|
|
309
|
+
|
|
310
|
+
store = GraphStore(project_dir)
|
|
311
|
+
metadata = store.get_metadata()
|
|
312
|
+
source_path = metadata.get("source_path") or metadata.get("project_root")
|
|
313
|
+
|
|
314
|
+
# ── Step 1: Collect intelligence from the graph ──────────────
|
|
315
|
+
with Progress(
|
|
316
|
+
SpinnerColumn(), TextColumn("[cyan]{task.description}[/cyan]"), console=console,
|
|
317
|
+
) as progress:
|
|
318
|
+
task = progress.add_task("Analyzing project graph...", total=None)
|
|
319
|
+
intel = _collect_project_intel(store, Path(source_path) if source_path else None)
|
|
320
|
+
progress.update(task, description="[green]Graph analysis complete")
|
|
321
|
+
|
|
322
|
+
console.print(
|
|
323
|
+
f" [dim]Analyzed[/dim] [white]{intel['file_count']} files, "
|
|
324
|
+
f"{intel['function_count']} functions, "
|
|
325
|
+
f"{intel['class_count']} classes[/white]"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# ── Step 2: Generate README ──────────────────────────────────
|
|
329
|
+
if no_llm:
|
|
330
|
+
console.print(" [dim]Generating template-based README (--no-llm)...[/dim]")
|
|
331
|
+
readme_content = _generate_fallback_readme(project, intel, source_path)
|
|
332
|
+
else:
|
|
333
|
+
llm = LocalLLM(
|
|
334
|
+
model=llm_model,
|
|
335
|
+
provider=llm_provider,
|
|
336
|
+
api_key=llm_api_key,
|
|
337
|
+
)
|
|
338
|
+
prompt = _build_onboard_prompt(project, intel, source_path)
|
|
339
|
+
|
|
340
|
+
with Progress(
|
|
341
|
+
SpinnerColumn(), TextColumn("[cyan]{task.description}[/cyan]"), console=console,
|
|
342
|
+
) as progress:
|
|
343
|
+
task = progress.add_task("Generating README with LLM...", total=None)
|
|
344
|
+
result = llm.explain(prompt)
|
|
345
|
+
progress.update(task, description="[green]README generated")
|
|
346
|
+
|
|
347
|
+
# If LLM returned something useful, use it; otherwise fall back
|
|
348
|
+
if result and len(result) > 200 and not result.startswith("LLM provider"):
|
|
349
|
+
readme_content = result
|
|
350
|
+
else:
|
|
351
|
+
console.print(" [yellow]⚠ LLM unavailable, using template fallback.[/yellow]")
|
|
352
|
+
readme_content = _generate_fallback_readme(project, intel, source_path)
|
|
353
|
+
|
|
354
|
+
store.close()
|
|
355
|
+
|
|
356
|
+
# ── Step 3: Output ───────────────────────────────────────────
|
|
357
|
+
if output:
|
|
358
|
+
output.write_text(readme_content, encoding="utf-8")
|
|
359
|
+
console.print(f"\n[green]✅ Saved to {output}[/green]")
|
|
360
|
+
elif save:
|
|
361
|
+
if source_path:
|
|
362
|
+
dest = Path(source_path) / "ONBOARD.md"
|
|
363
|
+
else:
|
|
364
|
+
dest = Path.cwd() / "ONBOARD.md"
|
|
365
|
+
dest.write_text(readme_content, encoding="utf-8")
|
|
366
|
+
console.print(f"\n[green]✅ Saved to {dest}[/green]")
|
|
367
|
+
else:
|
|
368
|
+
# Print to stdout with a panel
|
|
369
|
+
console.print()
|
|
370
|
+
console.print(Panel(
|
|
371
|
+
readme_content,
|
|
372
|
+
title=f"📄 Generated README for {project}",
|
|
373
|
+
title_align="left",
|
|
374
|
+
border_style="cyan",
|
|
375
|
+
expand=False,
|
|
376
|
+
))
|
|
377
|
+
|
|
378
|
+
console.print(
|
|
379
|
+
f"\n[dim]Tip: Review and edit the output — AI-generated docs are a starting point, not final.[/dim]"
|
|
380
|
+
)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Quick-start wizard for CodeGraph CLI — get started in 30 seconds."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from . import config
|
|
14
|
+
from .config import BASE_DIR
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
quickstart_app = typer.Typer(help="🚀 Quick setup and demo")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def detect_project_type() -> str:
|
|
22
|
+
"""Auto-detect project type from common files in the current directory."""
|
|
23
|
+
cwd = Path.cwd()
|
|
24
|
+
detectors = [
|
|
25
|
+
("package.json", "JavaScript/TypeScript"),
|
|
26
|
+
("tsconfig.json", "TypeScript"),
|
|
27
|
+
("setup.py", "Python"),
|
|
28
|
+
("pyproject.toml", "Python"),
|
|
29
|
+
("setup.cfg", "Python"),
|
|
30
|
+
("requirements.txt", "Python"),
|
|
31
|
+
("go.mod", "Go"),
|
|
32
|
+
("Cargo.toml", "Rust"),
|
|
33
|
+
("pom.xml", "Java"),
|
|
34
|
+
("build.gradle", "Java/Kotlin"),
|
|
35
|
+
("build.gradle.kts", "Kotlin"),
|
|
36
|
+
("Gemfile", "Ruby"),
|
|
37
|
+
("composer.json", "PHP"),
|
|
38
|
+
("mix.exs", "Elixir"),
|
|
39
|
+
("CMakeLists.txt", "C/C++"),
|
|
40
|
+
("Makefile", "C/C++"),
|
|
41
|
+
(".csproj", "C#/.NET"),
|
|
42
|
+
("pubspec.yaml", "Dart/Flutter"),
|
|
43
|
+
("Package.swift", "Swift"),
|
|
44
|
+
]
|
|
45
|
+
for filename, project_type in detectors:
|
|
46
|
+
if (cwd / filename).exists():
|
|
47
|
+
return project_type
|
|
48
|
+
# Also check for glob patterns (e.g. *.csproj)
|
|
49
|
+
if filename.startswith(".") and list(cwd.glob(f"*{filename}")):
|
|
50
|
+
return project_type
|
|
51
|
+
return "Unknown"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def config_exists() -> bool:
|
|
55
|
+
"""Check if CodeGraph configuration already exists."""
|
|
56
|
+
config_file = BASE_DIR / "config.toml"
|
|
57
|
+
return config_file.exists()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def has_active_project() -> bool:
|
|
61
|
+
"""Check if a project is currently loaded."""
|
|
62
|
+
try:
|
|
63
|
+
from .storage import ProjectManager
|
|
64
|
+
pm = ProjectManager()
|
|
65
|
+
return pm.get_current_project() is not None
|
|
66
|
+
except Exception:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def run_setup() -> None:
|
|
71
|
+
"""Run the interactive setup wizard."""
|
|
72
|
+
try:
|
|
73
|
+
from .cli_setup import setup
|
|
74
|
+
setup()
|
|
75
|
+
except (typer.Exit, SystemExit):
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_index(path: str = ".") -> Optional[dict]:
|
|
80
|
+
"""Index the current directory and return stats."""
|
|
81
|
+
from .orchestrator import MCPOrchestrator
|
|
82
|
+
from .storage import GraphStore, ProjectManager
|
|
83
|
+
|
|
84
|
+
project_path = Path(path).resolve()
|
|
85
|
+
pm = ProjectManager()
|
|
86
|
+
name = project_path.name.replace(" ", "_")
|
|
87
|
+
project_dir = pm.create_or_get_project(name)
|
|
88
|
+
|
|
89
|
+
store = GraphStore(project_dir)
|
|
90
|
+
orchestrator = MCPOrchestrator(store)
|
|
91
|
+
stats = orchestrator.index(project_path)
|
|
92
|
+
|
|
93
|
+
from datetime import datetime
|
|
94
|
+
store.set_metadata({
|
|
95
|
+
**store.get_metadata(),
|
|
96
|
+
"project_name": name,
|
|
97
|
+
"source_path": str(project_path),
|
|
98
|
+
"indexed_at": datetime.now().isoformat(),
|
|
99
|
+
})
|
|
100
|
+
pm.set_current_project(name)
|
|
101
|
+
store.close()
|
|
102
|
+
return stats
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def demo_search_based_on_project_type(project_type: str) -> None:
|
|
106
|
+
"""Run a demo search relevant to the detected project type."""
|
|
107
|
+
demo_queries = {
|
|
108
|
+
"Python": "main entry point or application startup",
|
|
109
|
+
"JavaScript/TypeScript": "main entry point or app initialization",
|
|
110
|
+
"TypeScript": "main entry point or app initialization",
|
|
111
|
+
"Go": "main function or server startup",
|
|
112
|
+
"Rust": "main function or entry point",
|
|
113
|
+
"Java": "main class or application entry",
|
|
114
|
+
"Java/Kotlin": "main class or application entry",
|
|
115
|
+
"Kotlin": "main class or application entry",
|
|
116
|
+
"Ruby": "application controller or main module",
|
|
117
|
+
"PHP": "index or main controller",
|
|
118
|
+
"C/C++": "main function",
|
|
119
|
+
"C#/.NET": "program entry point",
|
|
120
|
+
}
|
|
121
|
+
query = demo_queries.get(project_type, "main entry point")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
from .storage import ProjectManager, GraphStore
|
|
125
|
+
from .orchestrator import MCPOrchestrator
|
|
126
|
+
|
|
127
|
+
pm = ProjectManager()
|
|
128
|
+
project = pm.get_current_project()
|
|
129
|
+
if not project:
|
|
130
|
+
return
|
|
131
|
+
project_dir = pm.project_dir(project)
|
|
132
|
+
store = GraphStore(project_dir)
|
|
133
|
+
orchestrator = MCPOrchestrator(store)
|
|
134
|
+
results = orchestrator.search(query, top_k=3)
|
|
135
|
+
|
|
136
|
+
if results:
|
|
137
|
+
console.print(f" [green]✓[/green] Found {len(results)} results for [cyan]'{query}'[/cyan]")
|
|
138
|
+
for item in results[:3]:
|
|
139
|
+
console.print(
|
|
140
|
+
f" [dim]•[/dim] [{item.node_type}] {item.qualname} "
|
|
141
|
+
f"[dim]score={item.score:.3f}[/dim]"
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
console.print(" [yellow]No results yet — try a specific search after indexing[/yellow]")
|
|
145
|
+
store.close()
|
|
146
|
+
except Exception:
|
|
147
|
+
console.print(" [yellow]Demo search skipped (index may still be building)[/yellow]")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def show_quickstart_next_steps(project_type: str) -> None:
|
|
151
|
+
"""Show contextual next-step suggestions after quickstart."""
|
|
152
|
+
suggestions = {
|
|
153
|
+
"Python": [
|
|
154
|
+
"[cyan]cg search[/cyan] 'database models' — Find code semantically",
|
|
155
|
+
"[cyan]cg chat start[/cyan] — Chat with AI about your code",
|
|
156
|
+
"[cyan]cg v2 generate[/cyan] 'add API endpoint' — Generate new code",
|
|
157
|
+
],
|
|
158
|
+
"JavaScript/TypeScript": [
|
|
159
|
+
"[cyan]cg search[/cyan] 'API routes' — Find code semantically",
|
|
160
|
+
"[cyan]cg chat start[/cyan] — Chat with AI about your code",
|
|
161
|
+
"[cyan]cg v2 generate[/cyan] 'add REST endpoint' — Generate new code",
|
|
162
|
+
],
|
|
163
|
+
"Go": [
|
|
164
|
+
"[cyan]cg search[/cyan] 'HTTP handler' — Find code semantically",
|
|
165
|
+
"[cyan]cg chat start[/cyan] — Chat with AI about your code",
|
|
166
|
+
"[cyan]cg v2 generate[/cyan] 'add gRPC service' — Generate new code",
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
defaults = [
|
|
170
|
+
"[cyan]cg search[/cyan] 'your query' — Find code semantically",
|
|
171
|
+
"[cyan]cg chat start[/cyan] — Chat with AI about your code",
|
|
172
|
+
"[cyan]cg v2 generate[/cyan] 'description' — Generate new code",
|
|
173
|
+
]
|
|
174
|
+
steps = suggestions.get(project_type, defaults)
|
|
175
|
+
|
|
176
|
+
console.print()
|
|
177
|
+
console.print(
|
|
178
|
+
Panel.fit(
|
|
179
|
+
"\n".join([f" {step}" for step in steps]),
|
|
180
|
+
title="[bold green]🎉 Try these commands next:[/bold green]",
|
|
181
|
+
border_style="green",
|
|
182
|
+
padding=(0, 1),
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
console.print()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@quickstart_app.command("run")
|
|
189
|
+
def quickstart(
|
|
190
|
+
path: str = typer.Argument(".", help="Path to the project to index."),
|
|
191
|
+
skip_setup: bool = typer.Option(False, "--skip-setup", help="Skip LLM setup even if not configured."),
|
|
192
|
+
skip_index: bool = typer.Option(False, "--skip-index", help="Skip auto-indexing."),
|
|
193
|
+
):
|
|
194
|
+
"""🚀 Quick setup wizard — get started in 30 seconds.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
cg quickstart
|
|
198
|
+
cg quickstart ./my-project
|
|
199
|
+
cg quickstart --skip-setup
|
|
200
|
+
"""
|
|
201
|
+
console.print()
|
|
202
|
+
console.print(
|
|
203
|
+
Panel.fit(
|
|
204
|
+
"[bold cyan]🚀 CodeGraph Quick Start[/bold cyan]\n"
|
|
205
|
+
"[dim]AI-powered code intelligence in 30 seconds[/dim]",
|
|
206
|
+
border_style="cyan",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
console.print()
|
|
210
|
+
|
|
211
|
+
# 1. Detect project type
|
|
212
|
+
project_type = detect_project_type()
|
|
213
|
+
if project_type != "Unknown":
|
|
214
|
+
console.print(f" [green]✓[/green] Detected [bold]{project_type}[/bold] project")
|
|
215
|
+
else:
|
|
216
|
+
console.print(" [yellow]⚠[/yellow] Could not detect project type — will index all supported files")
|
|
217
|
+
|
|
218
|
+
# 2. Check if setup needed
|
|
219
|
+
if not skip_setup and not config_exists():
|
|
220
|
+
console.print()
|
|
221
|
+
console.print(" [yellow]⚙️ No LLM configured yet[/yellow]")
|
|
222
|
+
do_setup = typer.confirm(" Run interactive setup?", default=True)
|
|
223
|
+
if do_setup:
|
|
224
|
+
run_setup()
|
|
225
|
+
else:
|
|
226
|
+
console.print(" [dim]Skipped — using defaults (ollama/hash). Run 'cg config setup' later.[/dim]")
|
|
227
|
+
else:
|
|
228
|
+
console.print(f" [green]✓[/green] Configuration found")
|
|
229
|
+
|
|
230
|
+
# 3. Index current directory
|
|
231
|
+
if not skip_index:
|
|
232
|
+
console.print()
|
|
233
|
+
console.print(" [cyan]📊 Indexing your project...[/cyan]")
|
|
234
|
+
try:
|
|
235
|
+
stats = run_index(path)
|
|
236
|
+
if stats:
|
|
237
|
+
console.print(
|
|
238
|
+
f" [green]✓[/green] Indexed [bold]{stats['nodes']}[/bold] nodes "
|
|
239
|
+
f"and [bold]{stats['edges']}[/bold] edges"
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
console.print(" [green]✓[/green] Indexing complete")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
console.print(f" [red]✗[/red] Indexing failed: {e}")
|
|
245
|
+
console.print(" [dim]You can index manually later with: cg index <path>[/dim]")
|
|
246
|
+
else:
|
|
247
|
+
console.print(" [dim]Skipped indexing[/dim]")
|
|
248
|
+
|
|
249
|
+
# 4. Run demo search
|
|
250
|
+
if not skip_index:
|
|
251
|
+
console.print()
|
|
252
|
+
console.print(" [magenta]🔍 Running sample search...[/magenta]")
|
|
253
|
+
demo_search_based_on_project_type(project_type)
|
|
254
|
+
|
|
255
|
+
# 5. Show next steps
|
|
256
|
+
show_quickstart_next_steps(project_type)
|
codegraph_cli/cli_refactor.py
CHANGED
|
@@ -37,7 +37,12 @@ def rename_symbol(
|
|
|
37
37
|
preview_only: bool = typer.Option(False, "--preview", "-p", help="Preview changes without applying"),
|
|
38
38
|
auto_apply: bool = typer.Option(False, "--auto-apply", "-y", help="Apply changes without confirmation"),
|
|
39
39
|
):
|
|
40
|
-
"""Rename a symbol and update all references.
|
|
40
|
+
"""✏️ Rename a symbol and update all references.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
cg v2 refactor rename "old_function" "new_function"
|
|
44
|
+
cg v2 refactor rename "UserModel" "Account" --preview
|
|
45
|
+
"""
|
|
41
46
|
pm = ProjectManager()
|
|
42
47
|
agent = _get_refactor_agent(pm)
|
|
43
48
|
|
|
@@ -106,7 +111,12 @@ def extract_function(
|
|
|
106
111
|
preview_only: bool = typer.Option(False, "--preview", "-p", help="Preview changes without applying"),
|
|
107
112
|
auto_apply: bool = typer.Option(False, "--auto-apply", "-y", help="Apply changes without confirmation"),
|
|
108
113
|
):
|
|
109
|
-
"""Extract code range into a new function.
|
|
114
|
+
"""📤 Extract code range into a new function.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
cg v2 refactor extract-function src/handler.py 10 25 process_request
|
|
118
|
+
cg v2 refactor extract-function src/utils.py 5 15 validate_input --preview
|
|
119
|
+
"""
|
|
110
120
|
pm = ProjectManager()
|
|
111
121
|
agent = _get_refactor_agent(pm)
|
|
112
122
|
|
|
@@ -169,7 +179,11 @@ def extract_service(
|
|
|
169
179
|
preview_only: bool = typer.Option(False, "--preview", "-p", help="Preview changes without applying"),
|
|
170
180
|
auto_apply: bool = typer.Option(False, "--auto-apply", "-y", help="Apply changes without confirmation"),
|
|
171
181
|
):
|
|
172
|
-
"""Extract multiple functions to a new service file.
|
|
182
|
+
"""📤 Extract multiple functions to a new service file.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
cg v2 refactor extract-service send_email notify_user --target src/notifications.py
|
|
186
|
+
"""
|
|
173
187
|
pm = ProjectManager()
|
|
174
188
|
agent = _get_refactor_agent(pm)
|
|
175
189
|
|