context-gen-cli 1.2.5__tar.gz → 1.3.3__tar.gz
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.
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/PKG-INFO +1 -1
- context_gen_cli-1.3.3/context_gen/cli.py +242 -0
- context_gen_cli-1.3.3/context_gen/distill.py +107 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/PKG-INFO +1 -1
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/SOURCES.txt +1 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/pyproject.toml +1 -1
- context_gen_cli-1.2.5/context_gen/cli.py +0 -507
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/README.md +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/__init__.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/extractor.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/generator.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/graph.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/mcp/__init__.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/mcp/advanced_tools.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen/scanner.py +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/dependency_links.txt +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/entry_points.txt +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/requires.txt +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/top_level.txt +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/setup.cfg +0 -0
- {context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/tests/test_advanced_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: context-gen-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: A modern CLI tool that scans any project, builds an AST-based dependency graph, and generates structured LLM context (agent.md / agent.json).
|
|
5
5
|
Author: yakkshit
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
context_gen.cli: Typer-based CLI entry point for context-gen-cli (Python Edition).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
16
|
+
|
|
17
|
+
from context_gen.scanner import scan_project
|
|
18
|
+
from context_gen.extractor import extract_metadata
|
|
19
|
+
from context_gen.graph import build_dependency_graph
|
|
20
|
+
from context_gen.generator import generate_agent_md, generate_agent_json, generate_agent_yaml
|
|
21
|
+
from context_gen.distill import distill_query
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
name="context-gen",
|
|
25
|
+
help="⚡ Generate graph-based project context & distill minimal AI prompts (ctx).",
|
|
26
|
+
add_completion=False,
|
|
27
|
+
no_args_is_help=False,
|
|
28
|
+
)
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OutputFormat(str, Enum):
|
|
33
|
+
ALL = "all"
|
|
34
|
+
MD = "md"
|
|
35
|
+
JSON = "json"
|
|
36
|
+
YAML = "yaml"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_python_banner() -> None:
|
|
40
|
+
banner = r"""
|
|
41
|
+
___ _ _ __
|
|
42
|
+
/ __| |_ \ \/ /
|
|
43
|
+
| (__| _| > <
|
|
44
|
+
\___|\__|/_/\_\
|
|
45
|
+
"""
|
|
46
|
+
console.print(f"[bold magenta]{banner}[/bold magenta]")
|
|
47
|
+
console.print(
|
|
48
|
+
Panel.fit(
|
|
49
|
+
"[bold cyan]⚡ context-gen-cli (ctx)[/bold cyan] — [magenta]Senior Engineer Context & Knowledge Graph Engine[/magenta]",
|
|
50
|
+
border_style="cyan",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
console.print("")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def execute_generation_with_progress(
|
|
57
|
+
target_dir: Path,
|
|
58
|
+
format_choice: OutputFormat,
|
|
59
|
+
output_override: Optional[Path] = None,
|
|
60
|
+
max_depth: int = 3,
|
|
61
|
+
) -> dict:
|
|
62
|
+
with Progress(
|
|
63
|
+
SpinnerColumn("dots", style="cyan"),
|
|
64
|
+
TextColumn("[bold blue]{task.description}"),
|
|
65
|
+
BarColumn(bar_width=35, complete_style="magenta", finished_style="green"),
|
|
66
|
+
TaskProgressColumn(),
|
|
67
|
+
TimeElapsedColumn(),
|
|
68
|
+
console=console,
|
|
69
|
+
) as progress:
|
|
70
|
+
t1 = progress.add_task("🔍 Scanning workspace files & security guards...", total=100)
|
|
71
|
+
scan_result = scan_project(str(target_dir), max_depth=max_depth)
|
|
72
|
+
progress.update(t1, completed=100)
|
|
73
|
+
|
|
74
|
+
t2 = progress.add_task("📦 Extracting metadata & tech stack signals...", total=100)
|
|
75
|
+
files = scan_result["files"]
|
|
76
|
+
tree = scan_result["tree"]
|
|
77
|
+
metadata = extract_metadata(str(target_dir), files)
|
|
78
|
+
progress.update(t2, completed=100)
|
|
79
|
+
|
|
80
|
+
t3 = progress.add_task("🕸️ Building AST module dependency graph...", total=100)
|
|
81
|
+
graph = build_dependency_graph(files, str(target_dir))
|
|
82
|
+
progress.update(t3, completed=100)
|
|
83
|
+
|
|
84
|
+
t4 = progress.add_task(f"✍️ Generating context artifacts ({format_choice.value})...", total=100)
|
|
85
|
+
|
|
86
|
+
output_dir_name = "context"
|
|
87
|
+
config_file = target_dir / "context" / ".context-gen.json"
|
|
88
|
+
if not config_file.is_file():
|
|
89
|
+
config_file = target_dir / ".context-gen.json"
|
|
90
|
+
|
|
91
|
+
if config_file.is_file():
|
|
92
|
+
try:
|
|
93
|
+
cfg = json.loads(config_file.read_text(encoding="utf-8"))
|
|
94
|
+
output_dir_name = cfg.get("outputDir", "context")
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
dest_dir = target_dir / output_dir_name
|
|
99
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
generated_files: List[Path] = []
|
|
101
|
+
|
|
102
|
+
if output_override:
|
|
103
|
+
out_file = output_override.resolve()
|
|
104
|
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
if format_choice == OutputFormat.JSON:
|
|
106
|
+
out_file.write_text(json.dumps(generate_agent_json(metadata, graph, tree, str(target_dir)), indent=2), encoding="utf-8")
|
|
107
|
+
elif format_choice == OutputFormat.YAML:
|
|
108
|
+
out_file.write_text(generate_agent_yaml(metadata, graph, tree, str(target_dir)), encoding="utf-8")
|
|
109
|
+
else:
|
|
110
|
+
out_file.write_text(generate_agent_md(metadata, graph, tree), encoding="utf-8")
|
|
111
|
+
generated_files.append(out_file)
|
|
112
|
+
else:
|
|
113
|
+
formats_to_build = ["md", "json", "yaml"] if format_choice == OutputFormat.ALL else [format_choice.value]
|
|
114
|
+
if "md" in formats_to_build:
|
|
115
|
+
f = dest_dir / "agent.md"
|
|
116
|
+
f.write_text(generate_agent_md(metadata, graph, tree), encoding="utf-8")
|
|
117
|
+
generated_files.append(f)
|
|
118
|
+
if "json" in formats_to_build:
|
|
119
|
+
f = dest_dir / "agent.json"
|
|
120
|
+
f.write_text(json.dumps(generate_agent_json(metadata, graph, tree, str(target_dir)), indent=2), encoding="utf-8")
|
|
121
|
+
generated_files.append(f)
|
|
122
|
+
if "yaml" in formats_to_build:
|
|
123
|
+
f = dest_dir / "agent.yaml"
|
|
124
|
+
f.write_text(generate_agent_yaml(metadata, graph, tree, str(target_dir)), encoding="utf-8")
|
|
125
|
+
generated_files.append(f)
|
|
126
|
+
|
|
127
|
+
progress.update(t4, completed=100)
|
|
128
|
+
|
|
129
|
+
console.print("")
|
|
130
|
+
return {
|
|
131
|
+
"files": files,
|
|
132
|
+
"graph": graph,
|
|
133
|
+
"metadata": metadata,
|
|
134
|
+
"generated_files": generated_files,
|
|
135
|
+
"output_dir": dest_dir,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def run_watch_loop(target_dir: Path, format_choice: OutputFormat, max_depth: int = 3) -> None:
|
|
140
|
+
console.print(
|
|
141
|
+
Panel(
|
|
142
|
+
f"[bold cyan]👀 Watch Mode Active[/bold cyan]\n"
|
|
143
|
+
f"[dim]Monitoring:[/dim] [green]{target_dir}[/green]\n"
|
|
144
|
+
f"[dim]Auto-updating context on save. Press [bold]Ctrl+C[/bold] to stop.[/dim]",
|
|
145
|
+
border_style="cyan",
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def get_snapshot():
|
|
150
|
+
snap = {}
|
|
151
|
+
for p in target_dir.rglob("*"):
|
|
152
|
+
if p.is_file():
|
|
153
|
+
rel = p.relative_to(target_dir).as_posix()
|
|
154
|
+
if not any(t in rel for t in ["context/", "node_modules/", ".git/", "__pycache__/", ".venv/", "agent."]):
|
|
155
|
+
try:
|
|
156
|
+
snap[rel] = p.stat().st_mtime
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
return snap
|
|
160
|
+
|
|
161
|
+
last_snapshot = get_snapshot()
|
|
162
|
+
try:
|
|
163
|
+
while True:
|
|
164
|
+
time.sleep(1.0)
|
|
165
|
+
current = get_snapshot()
|
|
166
|
+
if current != last_snapshot:
|
|
167
|
+
last_snapshot = current
|
|
168
|
+
ts = datetime.now().strftime("%H:%M:%S")
|
|
169
|
+
console.print(f"[dim][{ts}][/dim] [bold green]🔄 Change detected. Syncing context...[/bold green]")
|
|
170
|
+
execute_generation_with_progress(target_dir, format_choice, max_depth=max_depth)
|
|
171
|
+
except KeyboardInterrupt:
|
|
172
|
+
console.print("\n[bold yellow]👋 Stopping watch mode...[/bold yellow]")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.callback(invoke_without_command=True)
|
|
176
|
+
def main(
|
|
177
|
+
ctx: typer.Context,
|
|
178
|
+
path: Path = typer.Argument(default=Path("."), help="Target project directory."),
|
|
179
|
+
project: Optional[Path] = typer.Option(None, "--project", "-p", help="Target project directory."),
|
|
180
|
+
default_mode: bool = typer.Option(False, "--default", "-d", help="⚡ Generate all formats + live watch mode."),
|
|
181
|
+
format: OutputFormat = typer.Option(OutputFormat.ALL, "--format", "-f", help="Output format: all, md, json, yaml."),
|
|
182
|
+
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Custom output path."),
|
|
183
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Watch directory for changes."),
|
|
184
|
+
max_depth: int = typer.Option(3, "--max-depth", min=1, max=10, help="Max scan depth."),
|
|
185
|
+
) -> None:
|
|
186
|
+
if ctx.invoked_subcommand is not None:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
target_dir = (project or path).resolve()
|
|
190
|
+
print_python_banner()
|
|
191
|
+
res = execute_generation_with_progress(target_dir, format, output_override=output, max_depth=max_depth)
|
|
192
|
+
|
|
193
|
+
table = Table(show_header=False, box=None)
|
|
194
|
+
table.add_row("[bold]📂 Scanned Files:[/bold]", f"[cyan]{len(res['files'])}[/cyan]")
|
|
195
|
+
table.add_row("[bold]🕸️ Graph Edges:[/bold]", f"[magenta]{len(res['graph']['edges'])}[/magenta]")
|
|
196
|
+
table.add_row("[bold]⚡ Tech Stack:[/bold]", f"[green]{', '.join(res['metadata']['techStack'])}[/green]")
|
|
197
|
+
table.add_row("[bold]📁 Destination:[/bold]", f"[yellow]{res['output_dir']}[/yellow]")
|
|
198
|
+
table.add_row("[bold]�� Artifacts:[/bold]", f"[bold cyan]{', '.join(f.name for f in res['generated_files'])}[/bold cyan]")
|
|
199
|
+
|
|
200
|
+
console.print(Panel(table, title="[bold green]✅ Context Generated Successfully[/bold green]", border_style="green"))
|
|
201
|
+
console.print(
|
|
202
|
+
Panel(
|
|
203
|
+
"[bold cyan]ctx distill \"add feature\"[/bold cyan] → 🧠 Get minimal Senior Engineer context plan\n"
|
|
204
|
+
"[bold cyan]ctx -d[/bold cyan] → ⚡ All-in-one generate + live watcher\n"
|
|
205
|
+
"[bold cyan]ctx update -f json[/bold cyan] → 🔄 Quick format refresh",
|
|
206
|
+
title="[bold]💡 Quick Commands[/bold]",
|
|
207
|
+
border_style="blue",
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if default_mode or watch:
|
|
212
|
+
run_watch_loop(target_dir, format, max_depth=max_depth)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@app.command()
|
|
216
|
+
def distill(
|
|
217
|
+
prompt: str = typer.Argument(..., help="Task or feature request to distill context for."),
|
|
218
|
+
budget: int = typer.Option(250, "--budget", "-b", help="Max token budget."),
|
|
219
|
+
project: Path = typer.Option(Path("."), "--project", "-p", help="Target project directory."),
|
|
220
|
+
) -> None:
|
|
221
|
+
"""🧠 Distill minimal high-signal context using Entropy + Knowledge Graph."""
|
|
222
|
+
res = distill_query(str(project.resolve()), prompt, budget=budget)
|
|
223
|
+
if res["status"] == "needs_clarification":
|
|
224
|
+
console.print(Panel(res["output"], title="[bold yellow]⚠️ Clarification Needed[/bold yellow]", border_style="yellow"))
|
|
225
|
+
else:
|
|
226
|
+
console.print(Panel(res["output"], title="[bold cyan]🧠 Senior Engineer Context Plan[/bold cyan]", border_style="cyan"))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@app.command()
|
|
230
|
+
def update(
|
|
231
|
+
path: Path = typer.Argument(default=Path("."), help="Target project directory."),
|
|
232
|
+
format: OutputFormat = typer.Option(OutputFormat.ALL, "--format", "-f", help="Output format."),
|
|
233
|
+
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Custom output path."),
|
|
234
|
+
) -> None:
|
|
235
|
+
"""🔄 Regenerate and update project context files."""
|
|
236
|
+
target_dir = path.resolve()
|
|
237
|
+
res = execute_generation_with_progress(target_dir, format, output_override=output)
|
|
238
|
+
console.print(f"[bold green]✓ Context updated in {res['output_dir']}![/bold green]")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
app()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import math
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
IGNORE_DIRS = {
|
|
7
|
+
"node_modules", ".git", ".next", "dist", "build", ".graviton",
|
|
8
|
+
"target", "__pycache__", ".venv", "venv", ".dart_tool", "bin", "obj",
|
|
9
|
+
"test", "tests", "context", "public",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
SUPPORTED_EXT = {
|
|
13
|
+
".ts": "ts", ".tsx": "ts", ".js": "js", ".jsx": "js",
|
|
14
|
+
".py": "python", ".rs": "rust", ".go": "go", ".dart": "dart", ".cs": "csharp",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def get_hardware_budget(file_count: int) -> dict:
|
|
18
|
+
total_cores = os.cpu_count() or 2
|
|
19
|
+
allocated = 2 if (total_cores >= 6 and file_count >= 80) else 1
|
|
20
|
+
return {"total_cores": total_cores, "allocated_threads": allocated}
|
|
21
|
+
|
|
22
|
+
def scan_signatures(root_dir: Path) -> list[dict]:
|
|
23
|
+
sigs = []
|
|
24
|
+
for dirpath, dirnames, filenames in os.walk(root_dir):
|
|
25
|
+
dirnames[:] = [d for d in dirnames if d not in IGNORE_DIRS and not d.startswith(".") and not d.endswith(".egg-info")]
|
|
26
|
+
for fname in filenames:
|
|
27
|
+
ext = Path(fname).suffix
|
|
28
|
+
if ext not in SUPPORTED_EXT:
|
|
29
|
+
continue
|
|
30
|
+
full_path = Path(dirpath) / fname
|
|
31
|
+
rel_path = str(full_path.relative_to(root_dir))
|
|
32
|
+
try:
|
|
33
|
+
code = full_path.read_text(encoding="utf-8", errors="ignore")
|
|
34
|
+
except Exception:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
imports = re.findall(r"(?:import|from|use|using)\s+['\"]?([\w./:-]+)", code)[:6]
|
|
38
|
+
exports = re.findall(r"(?:export\s+(?:async\s+)?(?:function|const|class)|def|class|pub\s+fn|func)\s+([A-Za-z_]\w*)", code)[:6]
|
|
39
|
+
tokens = max(10, len(f"{rel_path} {' '.join(imports)} {' '.join(exports)}") // 4)
|
|
40
|
+
sigs.append({
|
|
41
|
+
"path": rel_path,
|
|
42
|
+
"lang": SUPPORTED_EXT[ext],
|
|
43
|
+
"imports": list(dict.fromkeys(imports)),
|
|
44
|
+
"exports": list(dict.fromkeys(exports)),
|
|
45
|
+
"tokens": tokens,
|
|
46
|
+
})
|
|
47
|
+
return sigs
|
|
48
|
+
|
|
49
|
+
def distill_query(root_dir: str, prompt: str, budget: int = 250) -> dict:
|
|
50
|
+
root = Path(root_dir).resolve()
|
|
51
|
+
sigs = scan_signatures(root)
|
|
52
|
+
hw = get_hardware_budget(len(sigs))
|
|
53
|
+
|
|
54
|
+
if not sigs:
|
|
55
|
+
return {"status": "needs_clarification", "output": "No source files found. Which directory contains your code?"}
|
|
56
|
+
|
|
57
|
+
words = [w for w in re.split(r"\W+", prompt.lower()) if len(w) > 2]
|
|
58
|
+
scored = []
|
|
59
|
+
for s in sigs:
|
|
60
|
+
haystack = f"{s['path']} {' '.join(s['imports'])} {' '.join(s['exports'])}".lower()
|
|
61
|
+
hits = sum(1 for w in words if w in haystack)
|
|
62
|
+
scored.append((s, 0.1 + hits * 0.45))
|
|
63
|
+
|
|
64
|
+
max_s = max(sc for _, sc in scored)
|
|
65
|
+
exps = [math.exp((sc - max_s) / 0.35) for _, sc in scored]
|
|
66
|
+
total_exp = sum(exps)
|
|
67
|
+
probs = [e / total_exp for e in exps]
|
|
68
|
+
initial_h = -sum(p * math.log2(p) for p in probs if p > 1e-6)
|
|
69
|
+
|
|
70
|
+
ranked = sorted(
|
|
71
|
+
[(s, p, (p * initial_h * 1.6), (p * initial_h * 1.6) / s["tokens"]) for (s, _), p in zip(scored, probs)],
|
|
72
|
+
key=lambda x: x[3],
|
|
73
|
+
reverse=True,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
entropy = initial_h
|
|
77
|
+
picked = []
|
|
78
|
+
used_tokens = 0
|
|
79
|
+
for sig, prob, delta_h, _ in ranked:
|
|
80
|
+
if used_tokens + sig["tokens"] > budget or entropy < 1.5:
|
|
81
|
+
break
|
|
82
|
+
if prob < 0.05 and picked:
|
|
83
|
+
break
|
|
84
|
+
picked.append(sig)
|
|
85
|
+
used_tokens += sig["tokens"]
|
|
86
|
+
entropy = max(0.35, entropy - delta_h)
|
|
87
|
+
|
|
88
|
+
if entropy >= 1.5 or (len(words) <= 3 and ranked[0][1] < 0.22):
|
|
89
|
+
sample = ranked[0][0]["path"] if ranked else "src/main.py"
|
|
90
|
+
output = (
|
|
91
|
+
f"CLARIFICATION NEEDED (Before Writing Code):\n"
|
|
92
|
+
f"1. **Implementation Pattern:** Should I extend an existing module? (e.g., *Follow `{sample}`* or *Create a standalone file*)\n"
|
|
93
|
+
f"2. **Side Effects:** Should this persist state/files or run read-only?"
|
|
94
|
+
)
|
|
95
|
+
return {"status": "needs_clarification", "output": output}
|
|
96
|
+
|
|
97
|
+
refs = "\n".join(
|
|
98
|
+
f" • {p['path']} [{p['lang']}] — exports: [{', '.join(p['exports'][:4])}], imports: [{', '.join(p['imports'][:4])}]"
|
|
99
|
+
for p in picked
|
|
100
|
+
)
|
|
101
|
+
output = (
|
|
102
|
+
f"### Senior Engineer Context Plan ({used_tokens} tokens | CPU: {hw['allocated_threads']} core)\n"
|
|
103
|
+
f"- **Task:** {prompt}\n"
|
|
104
|
+
f"- **Primary Files to Reference:**\n{refs}\n"
|
|
105
|
+
f"- **Architecture Guardrail:** Match existing imports, function signatures, and module boundaries."
|
|
106
|
+
)
|
|
107
|
+
return {"status": "ready", "output": output}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: context-gen-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: A modern CLI tool that scans any project, builds an AST-based dependency graph, and generates structured LLM context (agent.md / agent.json).
|
|
5
5
|
Author: yakkshit
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "context-gen-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.3"
|
|
8
8
|
description = "A modern CLI tool that scans any project, builds an AST-based dependency graph, and generates structured LLM context (agent.md / agent.json)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "yakkshit" }]
|
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
context_gen.cli: Typer-based CLI entry point for context-gen-cli (Python Edition).
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import time
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
from enum import Enum
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import List, Optional
|
|
11
|
-
import typer
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
from rich.panel import Panel
|
|
14
|
-
from rich.table import Table
|
|
15
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
16
|
-
from rich.text import Text
|
|
17
|
-
|
|
18
|
-
from context_gen.scanner import scan_project
|
|
19
|
-
from context_gen.extractor import extract_metadata
|
|
20
|
-
from context_gen.graph import build_dependency_graph
|
|
21
|
-
from context_gen.generator import generate_agent_md, generate_agent_json, generate_agent_yaml
|
|
22
|
-
|
|
23
|
-
app = typer.Typer(
|
|
24
|
-
name="context-gen",
|
|
25
|
-
help="⚡ Generate graph-based project context (agent.md, agent.json, agent.yaml) for LLMs.",
|
|
26
|
-
add_completion=False,
|
|
27
|
-
no_args_is_help=False
|
|
28
|
-
)
|
|
29
|
-
console = Console()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class OutputFormat(str, Enum):
|
|
33
|
-
"""Supported output formats."""
|
|
34
|
-
ALL = "all"
|
|
35
|
-
MD = "md"
|
|
36
|
-
JSON = "json"
|
|
37
|
-
YAML = "yaml"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def execute_generation(
|
|
41
|
-
target_dir: Path,
|
|
42
|
-
format_choice: OutputFormat,
|
|
43
|
-
output_override: Optional[Path] = None,
|
|
44
|
-
max_depth: int = 3,
|
|
45
|
-
verbose: bool = True
|
|
46
|
-
) -> dict:
|
|
47
|
-
"""Scans and generates the requested context format files."""
|
|
48
|
-
scan_result = scan_project(str(target_dir), max_depth=max_depth)
|
|
49
|
-
files = scan_result["files"]
|
|
50
|
-
tree = scan_result["tree"]
|
|
51
|
-
|
|
52
|
-
metadata = extract_metadata(str(target_dir), files)
|
|
53
|
-
graph = build_dependency_graph(files, str(target_dir))
|
|
54
|
-
|
|
55
|
-
# Read output directory from context/.context-gen.json or legacy .context-gen.json
|
|
56
|
-
output_dir_name = "context"
|
|
57
|
-
config_file = target_dir / "context" / ".context-gen.json"
|
|
58
|
-
if not config_file.is_file():
|
|
59
|
-
config_file = target_dir / ".context-gen.json"
|
|
60
|
-
|
|
61
|
-
if config_file.is_file():
|
|
62
|
-
try:
|
|
63
|
-
with open(config_file, "r", encoding="utf-8") as f:
|
|
64
|
-
cfg = json.load(f)
|
|
65
|
-
output_dir_name = cfg.get("outputDir", "context")
|
|
66
|
-
except Exception:
|
|
67
|
-
pass
|
|
68
|
-
|
|
69
|
-
dest_dir = target_dir / output_dir_name
|
|
70
|
-
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
-
|
|
72
|
-
generated_files: List[Path] = []
|
|
73
|
-
|
|
74
|
-
if output_override:
|
|
75
|
-
out_file = output_override.resolve()
|
|
76
|
-
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
-
if format_choice == OutputFormat.JSON:
|
|
78
|
-
json_data = generate_agent_json(metadata, graph, tree, str(target_dir))
|
|
79
|
-
out_file.write_text(json.dumps(json_data, indent=2), encoding="utf-8")
|
|
80
|
-
elif format_choice == OutputFormat.YAML:
|
|
81
|
-
yaml_text = generate_agent_yaml(metadata, graph, tree, str(target_dir))
|
|
82
|
-
out_file.write_text(yaml_text, encoding="utf-8")
|
|
83
|
-
else:
|
|
84
|
-
md_text = generate_agent_md(metadata, graph, tree)
|
|
85
|
-
out_file.write_text(md_text, encoding="utf-8")
|
|
86
|
-
generated_files.append(out_file)
|
|
87
|
-
else:
|
|
88
|
-
# Determine formats to generate
|
|
89
|
-
formats_to_build = ["md", "json", "yaml"] if format_choice == OutputFormat.ALL else [format_choice.value]
|
|
90
|
-
|
|
91
|
-
if "md" in formats_to_build:
|
|
92
|
-
md_file = dest_dir / "agent.md"
|
|
93
|
-
md_text = generate_agent_md(metadata, graph, tree)
|
|
94
|
-
md_file.write_text(md_text, encoding="utf-8")
|
|
95
|
-
generated_files.append(md_file)
|
|
96
|
-
|
|
97
|
-
if "json" in formats_to_build:
|
|
98
|
-
json_file = dest_dir / "agent.json"
|
|
99
|
-
json_data = generate_agent_json(metadata, graph, tree, str(target_dir))
|
|
100
|
-
json_file.write_text(json.dumps(json_data, indent=2), encoding="utf-8")
|
|
101
|
-
generated_files.append(json_file)
|
|
102
|
-
|
|
103
|
-
if "yaml" in formats_to_build:
|
|
104
|
-
yaml_file = dest_dir / "agent.yaml"
|
|
105
|
-
yaml_text = generate_agent_yaml(metadata, graph, tree, str(target_dir))
|
|
106
|
-
yaml_file.write_text(yaml_text, encoding="utf-8")
|
|
107
|
-
generated_files.append(yaml_file)
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
"files": files,
|
|
111
|
-
"graph": graph,
|
|
112
|
-
"metadata": metadata,
|
|
113
|
-
"generated_files": generated_files,
|
|
114
|
-
"output_dir": dest_dir
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def run_watch_loop(target_dir: Path, format_choice: OutputFormat, max_depth: int = 3) -> None:
|
|
119
|
-
"""Runs a live background file watch loop that regenerates context on changes."""
|
|
120
|
-
console.print(
|
|
121
|
-
Panel(
|
|
122
|
-
f"[bold cyan]👀 Watch Mode Active[/bold cyan]\n"
|
|
123
|
-
f"[dim]Monitoring workspace:[/dim] [green]{target_dir}[/green]\n"
|
|
124
|
-
f"[dim]Auto-updating context files on save. Press [bold]Ctrl+C[/bold] to exit.[/dim]",
|
|
125
|
-
border_style="cyan"
|
|
126
|
-
)
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
last_snapshot = {}
|
|
130
|
-
|
|
131
|
-
def get_snapshot():
|
|
132
|
-
snap = {}
|
|
133
|
-
for p in target_dir.rglob("*"):
|
|
134
|
-
if p.is_file():
|
|
135
|
-
rel = p.relative_to(target_dir).as_posix()
|
|
136
|
-
if not any(token in rel for token in ["context/", "node_modules/", ".git/", "__pycache__/", ".venv/", "agent."]):
|
|
137
|
-
try:
|
|
138
|
-
snap[rel] = p.stat().st_mtime
|
|
139
|
-
except Exception:
|
|
140
|
-
pass
|
|
141
|
-
return snap
|
|
142
|
-
|
|
143
|
-
last_snapshot = get_snapshot()
|
|
144
|
-
|
|
145
|
-
try:
|
|
146
|
-
while True:
|
|
147
|
-
time.sleep(1.0)
|
|
148
|
-
current_snapshot = get_snapshot()
|
|
149
|
-
|
|
150
|
-
changed = False
|
|
151
|
-
for f, mtime in current_snapshot.items():
|
|
152
|
-
if f not in last_snapshot or last_snapshot[f] != mtime:
|
|
153
|
-
changed = True
|
|
154
|
-
break
|
|
155
|
-
|
|
156
|
-
if not changed and set(last_snapshot.keys()) != set(current_snapshot.keys()):
|
|
157
|
-
changed = True
|
|
158
|
-
|
|
159
|
-
if changed:
|
|
160
|
-
last_snapshot = current_snapshot
|
|
161
|
-
time_str = datetime.now().strftime("%H:%M:%S")
|
|
162
|
-
console.print(f"[dim][{time_str}][/dim] [bold green]🔄 Project changed. Regenerating context files...[/bold green]")
|
|
163
|
-
execute_generation(target_dir, format_choice, max_depth=max_depth, verbose=False)
|
|
164
|
-
console.print(f"[dim][{time_str}][/dim] [bold cyan]✓ Context files updated & synced in context/![/bold cyan]")
|
|
165
|
-
except KeyboardInterrupt:
|
|
166
|
-
console.print("\n[bold yellow]👋 Stopping watch mode...[/bold yellow]")
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@app.command()
|
|
170
|
-
def main(
|
|
171
|
-
path: Path = typer.Argument(
|
|
172
|
-
default=Path("."),
|
|
173
|
-
help="Target project directory to analyze.",
|
|
174
|
-
file_okay=False,
|
|
175
|
-
dir_okay=True,
|
|
176
|
-
readable=True,
|
|
177
|
-
resolve_path=True,
|
|
178
|
-
),
|
|
179
|
-
project: Optional[Path] = typer.Option(
|
|
180
|
-
None,
|
|
181
|
-
"--project",
|
|
182
|
-
"-p",
|
|
183
|
-
help="Target project directory to analyze.",
|
|
184
|
-
file_okay=False,
|
|
185
|
-
dir_okay=True,
|
|
186
|
-
readable=True,
|
|
187
|
-
resolve_path=True,
|
|
188
|
-
),
|
|
189
|
-
default_mode: bool = typer.Option(
|
|
190
|
-
False,
|
|
191
|
-
"--default",
|
|
192
|
-
"-d",
|
|
193
|
-
help="⚡ Zero-config default mode: generate all formats in context/ and run live auto-sync watcher.",
|
|
194
|
-
),
|
|
195
|
-
format: OutputFormat = typer.Option(
|
|
196
|
-
OutputFormat.ALL,
|
|
197
|
-
"--format",
|
|
198
|
-
"-f",
|
|
199
|
-
help="Output format: 'all', 'md', 'json', or 'yaml'.",
|
|
200
|
-
case_sensitive=False,
|
|
201
|
-
),
|
|
202
|
-
mcp: bool = typer.Option(
|
|
203
|
-
False,
|
|
204
|
-
"--mcp",
|
|
205
|
-
"-m",
|
|
206
|
-
help="Start MCP server mode.",
|
|
207
|
-
),
|
|
208
|
-
interactive: bool = typer.Option(
|
|
209
|
-
False,
|
|
210
|
-
"--interactive",
|
|
211
|
-
"-i",
|
|
212
|
-
help="Run in interactive mode.",
|
|
213
|
-
),
|
|
214
|
-
output: Optional[Path] = typer.Option(
|
|
215
|
-
None,
|
|
216
|
-
"--output",
|
|
217
|
-
"-o",
|
|
218
|
-
help="Custom output file path (e.g. context/agent.md or custom.json).",
|
|
219
|
-
),
|
|
220
|
-
watch: bool = typer.Option(
|
|
221
|
-
False,
|
|
222
|
-
"--watch",
|
|
223
|
-
"-w",
|
|
224
|
-
help="Watch directory and auto-regenerate context on file changes.",
|
|
225
|
-
),
|
|
226
|
-
max_depth: int = typer.Option(
|
|
227
|
-
3,
|
|
228
|
-
"--max-depth",
|
|
229
|
-
help="Maximum directory depth to scan.",
|
|
230
|
-
min=1,
|
|
231
|
-
max=10,
|
|
232
|
-
),
|
|
233
|
-
) -> None:
|
|
234
|
-
"""
|
|
235
|
-
Scan a project, build an AST-powered dependency graph, and generate LLM context.
|
|
236
|
-
"""
|
|
237
|
-
target_dir = (project or path).resolve()
|
|
238
|
-
|
|
239
|
-
# If -d / --default is supplied, generate all formats and keep watch running
|
|
240
|
-
def print_python_banner():
|
|
241
|
-
"""Renders a stylish ASCII gradient banner in Python CLI."""
|
|
242
|
-
banner = """
|
|
243
|
-
___ _ _ __
|
|
244
|
-
/ __| |_ \ \/ /
|
|
245
|
-
| (__| _| > <
|
|
246
|
-
\___|\__|/_/\_\
|
|
247
|
-
"""
|
|
248
|
-
console.print(f"[bold magenta]{banner}[/bold magenta]")
|
|
249
|
-
console.print(Panel.fit("[bold cyan]⚡ context-gen-cli (ctx)[/bold cyan] — [magenta]AST AI Project Context Generator[/magenta]", border_style="cyan"))
|
|
250
|
-
console.print("")
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def execute_generation_with_progress(
|
|
254
|
-
target_dir: Path,
|
|
255
|
-
format_choice: OutputFormat,
|
|
256
|
-
output_override: Optional[Path] = None,
|
|
257
|
-
max_depth: int = 3
|
|
258
|
-
) -> dict:
|
|
259
|
-
"""Executes the pipeline with rich progress bar animation."""
|
|
260
|
-
with Progress(
|
|
261
|
-
SpinnerColumn("dots", style="cyan"),
|
|
262
|
-
TextColumn("[bold blue]{task.description}"),
|
|
263
|
-
BarColumn(bar_width=35, complete_style="magenta", finished_style="green"),
|
|
264
|
-
TaskProgressColumn(),
|
|
265
|
-
TimeElapsedColumn(),
|
|
266
|
-
console=console
|
|
267
|
-
) as progress:
|
|
268
|
-
t1 = progress.add_task("🔍 Scanning workspace files & security ignores...", total=100)
|
|
269
|
-
scan_result = scan_project(str(target_dir), max_depth=max_depth)
|
|
270
|
-
time.sleep(0.08)
|
|
271
|
-
progress.update(t1, completed=100)
|
|
272
|
-
|
|
273
|
-
t2 = progress.add_task("📦 Extracting metadata & tech stack signals...", total=100)
|
|
274
|
-
files = scan_result["files"]
|
|
275
|
-
tree = scan_result["tree"]
|
|
276
|
-
metadata = extract_metadata(str(target_dir), files)
|
|
277
|
-
time.sleep(0.08)
|
|
278
|
-
progress.update(t2, completed=100)
|
|
279
|
-
|
|
280
|
-
t3 = progress.add_task("🕸️ Parsing AST module dependency graph...", total=100)
|
|
281
|
-
graph = build_dependency_graph(files, str(target_dir))
|
|
282
|
-
time.sleep(0.08)
|
|
283
|
-
progress.update(t3, completed=100)
|
|
284
|
-
|
|
285
|
-
t4 = progress.add_task(f"✍️ Generating context artifacts ({format_choice.value})...", total=100)
|
|
286
|
-
|
|
287
|
-
output_dir_name = "context"
|
|
288
|
-
config_file = target_dir / "context" / ".context-gen.json"
|
|
289
|
-
if not config_file.is_file():
|
|
290
|
-
config_file = target_dir / ".context-gen.json"
|
|
291
|
-
|
|
292
|
-
if config_file.is_file():
|
|
293
|
-
try:
|
|
294
|
-
with open(config_file, "r", encoding="utf-8") as f:
|
|
295
|
-
cfg = json.load(f)
|
|
296
|
-
output_dir_name = cfg.get("outputDir", "context")
|
|
297
|
-
except Exception:
|
|
298
|
-
pass
|
|
299
|
-
|
|
300
|
-
dest_dir = target_dir / output_dir_name
|
|
301
|
-
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
302
|
-
generated_files: List[Path] = []
|
|
303
|
-
|
|
304
|
-
if output_override:
|
|
305
|
-
out_file = output_override.resolve()
|
|
306
|
-
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
307
|
-
if format_choice == OutputFormat.JSON:
|
|
308
|
-
json_data = generate_agent_json(metadata, graph, tree, str(target_dir))
|
|
309
|
-
out_file.write_text(json.dumps(json_data, indent=2), encoding="utf-8")
|
|
310
|
-
elif format_choice == OutputFormat.YAML:
|
|
311
|
-
yaml_text = generate_agent_yaml(metadata, graph, tree, str(target_dir))
|
|
312
|
-
out_file.write_text(yaml_text, encoding="utf-8")
|
|
313
|
-
else:
|
|
314
|
-
md_text = generate_agent_md(metadata, graph, tree)
|
|
315
|
-
out_file.write_text(md_text, encoding="utf-8")
|
|
316
|
-
generated_files.append(out_file)
|
|
317
|
-
else:
|
|
318
|
-
formats_to_build = ["md", "json", "yaml"] if format_choice == OutputFormat.ALL else [format_choice.value]
|
|
319
|
-
|
|
320
|
-
if "md" in formats_to_build:
|
|
321
|
-
md_file = dest_dir / "agent.md"
|
|
322
|
-
md_text = generate_agent_md(metadata, graph, tree)
|
|
323
|
-
md_file.write_text(md_text, encoding="utf-8")
|
|
324
|
-
generated_files.append(md_file)
|
|
325
|
-
|
|
326
|
-
if "json" in formats_to_build:
|
|
327
|
-
json_file = dest_dir / "agent.json"
|
|
328
|
-
json_data = generate_agent_json(metadata, graph, tree, str(target_dir))
|
|
329
|
-
json_file.write_text(json.dumps(json_data, indent=2), encoding="utf-8")
|
|
330
|
-
generated_files.append(json_file)
|
|
331
|
-
|
|
332
|
-
if "yaml" in formats_to_build:
|
|
333
|
-
yaml_file = dest_dir / "agent.yaml"
|
|
334
|
-
yaml_text = generate_agent_yaml(metadata, graph, tree, str(target_dir))
|
|
335
|
-
yaml_file.write_text(yaml_text, encoding="utf-8")
|
|
336
|
-
generated_files.append(yaml_file)
|
|
337
|
-
|
|
338
|
-
time.sleep(0.08)
|
|
339
|
-
progress.update(t4, completed=100)
|
|
340
|
-
|
|
341
|
-
console.print("")
|
|
342
|
-
return {
|
|
343
|
-
"files": files,
|
|
344
|
-
"graph": graph,
|
|
345
|
-
"metadata": metadata,
|
|
346
|
-
"generated_files": generated_files,
|
|
347
|
-
"output_dir": dest_dir
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
@app.command()
|
|
352
|
-
def main(
|
|
353
|
-
path: Path = typer.Argument(
|
|
354
|
-
default=Path("."),
|
|
355
|
-
help="Target project directory to analyze.",
|
|
356
|
-
file_okay=False,
|
|
357
|
-
dir_okay=True,
|
|
358
|
-
readable=True,
|
|
359
|
-
resolve_path=True,
|
|
360
|
-
),
|
|
361
|
-
project: Optional[Path] = typer.Option(
|
|
362
|
-
None,
|
|
363
|
-
"--project",
|
|
364
|
-
"-p",
|
|
365
|
-
help="Target project directory to analyze.",
|
|
366
|
-
file_okay=False,
|
|
367
|
-
dir_okay=True,
|
|
368
|
-
readable=True,
|
|
369
|
-
resolve_path=True,
|
|
370
|
-
),
|
|
371
|
-
default_mode: bool = typer.Option(
|
|
372
|
-
False,
|
|
373
|
-
"--default",
|
|
374
|
-
"-d",
|
|
375
|
-
help="⚡ Zero-config default mode: generate all formats in context/ and run live auto-sync watcher.",
|
|
376
|
-
),
|
|
377
|
-
format: OutputFormat = typer.Option(
|
|
378
|
-
OutputFormat.ALL,
|
|
379
|
-
"--format",
|
|
380
|
-
"-f",
|
|
381
|
-
help="Output format: 'all', 'md', 'json', or 'yaml'.",
|
|
382
|
-
case_sensitive=False,
|
|
383
|
-
),
|
|
384
|
-
mcp: bool = typer.Option(
|
|
385
|
-
False,
|
|
386
|
-
"--mcp",
|
|
387
|
-
"-m",
|
|
388
|
-
help="Start MCP server mode.",
|
|
389
|
-
),
|
|
390
|
-
interactive: bool = typer.Option(
|
|
391
|
-
False,
|
|
392
|
-
"--interactive",
|
|
393
|
-
"-i",
|
|
394
|
-
help="Run in interactive mode.",
|
|
395
|
-
),
|
|
396
|
-
output: Optional[Path] = typer.Option(
|
|
397
|
-
None,
|
|
398
|
-
"--output",
|
|
399
|
-
"-o",
|
|
400
|
-
help="Custom output file path (e.g. context/agent.md or custom.json).",
|
|
401
|
-
),
|
|
402
|
-
watch: bool = typer.Option(
|
|
403
|
-
False,
|
|
404
|
-
"--watch",
|
|
405
|
-
"-w",
|
|
406
|
-
help="Watch directory and auto-regenerate context on file changes.",
|
|
407
|
-
),
|
|
408
|
-
max_depth: int = typer.Option(
|
|
409
|
-
3,
|
|
410
|
-
"--max-depth",
|
|
411
|
-
help="Maximum directory depth to scan.",
|
|
412
|
-
min=1,
|
|
413
|
-
max=10,
|
|
414
|
-
),
|
|
415
|
-
) -> None:
|
|
416
|
-
"""
|
|
417
|
-
Scan a project, build an AST-powered dependency graph, and generate LLM context.
|
|
418
|
-
"""
|
|
419
|
-
target_dir = (project or path).resolve()
|
|
420
|
-
print_python_banner()
|
|
421
|
-
|
|
422
|
-
# If -d / --default is supplied, generate all formats and keep watch running
|
|
423
|
-
if default_mode:
|
|
424
|
-
console.print(
|
|
425
|
-
Panel.fit(
|
|
426
|
-
f"[bold cyan]⚡ context-gen-cli (Python All-In-One Mode -d)[/bold cyan]\n"
|
|
427
|
-
f"[dim]Target:[/dim] [green]{target_dir}[/green]\n"
|
|
428
|
-
f"[dim]Mode:[/dim] [yellow]Auto-Generate All Formats + Live Background Watcher[/yellow]",
|
|
429
|
-
border_style="cyan"
|
|
430
|
-
)
|
|
431
|
-
)
|
|
432
|
-
res = execute_generation_with_progress(target_dir, OutputFormat.ALL, max_depth=max_depth)
|
|
433
|
-
table = Table(show_header=False, box=None)
|
|
434
|
-
table.add_row("[bold]Scanned Files:[/bold]", f"[cyan]{len(res['files'])}[/cyan]")
|
|
435
|
-
table.add_row("[bold]AST Graph Edges:[/bold]", f"[cyan]{len(res['graph']['edges'])}[/cyan]")
|
|
436
|
-
table.add_row("[bold]Tech Stack:[/bold]", f"[green]{', '.join(res['metadata']['techStack'])}[/green]")
|
|
437
|
-
table.add_row("[bold]Generated Files:[/bold]", f"[yellow]{', '.join(f.name for f in res['generated_files'])}[/yellow]")
|
|
438
|
-
console.print(Panel(table, title="[bold green]✅ Context Generated in context/[/bold green]", border_style="green"))
|
|
439
|
-
run_watch_loop(target_dir, OutputFormat.ALL, max_depth=max_depth)
|
|
440
|
-
return
|
|
441
|
-
|
|
442
|
-
res = execute_generation_with_progress(target_dir, format, output_override=output, max_depth=max_depth)
|
|
443
|
-
|
|
444
|
-
# Display completion summary
|
|
445
|
-
table = Table(show_header=False, box=None)
|
|
446
|
-
table.add_row("[bold]Scanned Files:[/bold]", f"[cyan]{len(res['files'])}[/cyan]")
|
|
447
|
-
table.add_row("[bold]AST Graph Edges:[/bold]", f"[cyan]{len(res['graph']['edges'])}[/cyan]")
|
|
448
|
-
table.add_row("[bold]Tech Stack:[/bold]", f"[green]{', '.join(res['metadata']['techStack'])}[/green]")
|
|
449
|
-
table.add_row("[bold]Output Destination:[/bold]", f"[yellow]{res['output_dir']}[/yellow]")
|
|
450
|
-
table.add_row("[bold]Generated Files:[/bold]", f"[green]{', '.join(f.name for f in res['generated_files'])}[/green]")
|
|
451
|
-
|
|
452
|
-
console.print(
|
|
453
|
-
Panel(
|
|
454
|
-
table,
|
|
455
|
-
title="[bold green]✅ Context Generated Successfully[/bold green]",
|
|
456
|
-
border_style="green"
|
|
457
|
-
)
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
tips = (
|
|
461
|
-
"[bold cyan]ctx -w[/bold cyan] (or [cyan]ctx --watch[/cyan]) → Keep context fresh (auto-syncs on save)\n"
|
|
462
|
-
"[bold cyan]ctx -d[/bold cyan] (or [cyan]ctx --default[/cyan]) → ⚡ All-in-one (generate + live watcher)\n"
|
|
463
|
-
"[bold cyan]ctx update -f json[/bold cyan] → Quick format change (md, json, yaml, all)\n"
|
|
464
|
-
"[bold cyan]ctx -o <dir>[/bold cyan] → Custom output folder\n"
|
|
465
|
-
f"[dim]Config saved in:[/dim] [yellow]{res['output_dir'] / '.context-gen.json'}[/yellow]"
|
|
466
|
-
)
|
|
467
|
-
console.print(Panel(tips, title="[bold]💡 Pro-Tips & Next Steps[/bold]", border_style="blue"))
|
|
468
|
-
|
|
469
|
-
if watch:
|
|
470
|
-
run_watch_loop(target_dir, format, max_depth=max_depth)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
@app.command()
|
|
474
|
-
def update(
|
|
475
|
-
path: Path = typer.Argument(
|
|
476
|
-
default=Path("."),
|
|
477
|
-
help="Target project directory to analyze.",
|
|
478
|
-
file_okay=False,
|
|
479
|
-
dir_okay=True,
|
|
480
|
-
readable=True,
|
|
481
|
-
resolve_path=True,
|
|
482
|
-
),
|
|
483
|
-
format: OutputFormat = typer.Option(
|
|
484
|
-
OutputFormat.ALL,
|
|
485
|
-
"--format",
|
|
486
|
-
"-f",
|
|
487
|
-
help="Output format: 'all', 'md', 'json', or 'yaml'.",
|
|
488
|
-
case_sensitive=False,
|
|
489
|
-
),
|
|
490
|
-
output: Optional[Path] = typer.Option(
|
|
491
|
-
None,
|
|
492
|
-
"--output",
|
|
493
|
-
"-o",
|
|
494
|
-
help="Custom output file path.",
|
|
495
|
-
),
|
|
496
|
-
) -> None:
|
|
497
|
-
"""
|
|
498
|
-
Regenerate and update project context files.
|
|
499
|
-
"""
|
|
500
|
-
target_dir = path.resolve()
|
|
501
|
-
with console.status("[bold green]Updating project context files...[/bold green]"):
|
|
502
|
-
res = execute_generation(target_dir, format, output_override=output)
|
|
503
|
-
console.print(f"[bold green]✓ Context updated in {res['output_dir']}![/bold green]")
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if __name__ == "__main__":
|
|
507
|
-
app()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_gen_cli-1.2.5 → context_gen_cli-1.3.3}/context_gen_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|