ctxgraph-code 0.1.0__tar.gz → 0.1.2__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.
Files changed (30) hide show
  1. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/PKG-INFO +37 -10
  2. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/README.md +36 -9
  3. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/pyproject.toml +1 -1
  4. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/cli.py +104 -10
  5. ctxgraph_code-0.1.2/src/ctxgraph_code/config/init.py +19 -0
  6. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/config/settings.py +41 -6
  7. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/graph/builder.py +4 -2
  8. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/render.py +101 -0
  9. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/PKG-INFO +37 -10
  10. ctxgraph_code-0.1.0/src/ctxgraph_code/config/init.py +0 -14
  11. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/setup.cfg +0 -0
  12. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/__init__.py +0 -0
  13. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/__main__.py +0 -0
  14. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/analyzers/__init__.py +0 -0
  15. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/analyzers/python/__init__.py +0 -0
  16. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/analyzers/python/importer.py +0 -0
  17. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/analyzers/python/semantic.py +0 -0
  18. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/analyzers/python/symbols.py +0 -0
  19. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/config/__init__.py +0 -0
  20. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/exclude/__init__.py +0 -0
  21. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/exclude/patterns.py +0 -0
  22. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/graph/__init__.py +0 -0
  23. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/graph/models.py +0 -0
  24. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/graph/query.py +0 -0
  25. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code/graph/storage.py +0 -0
  26. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/SOURCES.txt +0 -0
  27. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/dependency_links.txt +0 -0
  28. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/entry_points.txt +0 -0
  29. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/requires.txt +0 -0
  30. {ctxgraph_code-0.1.0 → ctxgraph_code-0.1.2}/src/ctxgraph_code.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph-code
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions.
5
5
  Author: ctxgraph-code contributors
6
6
  License: MIT
@@ -70,11 +70,21 @@ ctxgraph-code setup
70
70
  ctxgraph-code setup
71
71
  ```
72
72
 
73
+ Interactive walkthrough — prompts for:
74
+ - **File extensions** to scan (`.py`, `.js`, `.ts`, etc.)
75
+ - **Exclude patterns** (folders like `tests/`, globs like `*.generated.py`)
76
+
73
77
  Does everything in one step:
74
- 1. Creates `.ctxgraph/config.toml` with defaults
75
- 2. Builds the knowledge graph from all Python files
78
+ 1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
79
+ 2. Builds the knowledge graph from all matching files
76
80
  3. Creates `.claude/commands/ctxgraph-code.md` with instructions for Claude Code
77
81
 
82
+ Non-interactive mode (skip prompts):
83
+ ```bash
84
+ ctxgraph-code setup --extensions .py,.js,.ts --exclude tests/,examples/
85
+ ctxgraph-code setup -y # all defaults
86
+ ```
87
+
78
88
  ### `init`
79
89
 
80
90
  ```bash
@@ -87,10 +97,12 @@ Creates the `.ctxgraph/` directory with a default `config.toml`.
87
97
 
88
98
  ```bash
89
99
  ctxgraph-code build
90
- ctxgraph-code build --exclude "tests/" --exclude "*.generated.py"
100
+ ctxgraph-code build --extensions .py,.js,.ts
101
+ ctxgraph-code build --exclude tests/ --exclude *.generated.py
91
102
  ```
92
103
 
93
- Scans all `*.py` files in the project, runs AST analysis:
104
+ Scans all matching files in the project, runs AST analysis. Extensions are read from config (`.py` by default, or whatever was set in `setup`).
105
+
94
106
  - **Imports**: which files import other files
95
107
  - **Class definitions**: class names, base classes, methods
96
108
  - **Function definitions**: function names, arguments
@@ -99,6 +111,8 @@ Scans all `*.py` files in the project, runs AST analysis:
99
111
 
100
112
  Stores the result in `.ctxgraph/graph.db`.
101
113
 
114
+ > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Claude Code will also rebuild when it detects the graph is stale.
115
+
102
116
  ### `query`
103
117
 
104
118
  ```bash
@@ -148,6 +162,17 @@ ctxgraph-code context "add pagination to the users endpoint"
148
162
 
149
163
  Generates a focused context summary: relevant files, their symbols, and dependency/call edges between them. This is the closest equivalent to `ctxgraph`'s capsule format.
150
164
 
165
+ ### `view`
166
+
167
+ ```bash
168
+ ctxgraph-code view
169
+ ctxgraph-code view --output tree.txt
170
+ ```
171
+
172
+ Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
173
+
174
+ Useful for a quick visual scan of the project structure without reading every file.
175
+
151
176
  ### `info`
152
177
 
153
178
  ```bash
@@ -234,11 +259,13 @@ Claude then uses these commands as needed during the conversation.
234
259
 
235
260
  ## Configuration
236
261
 
237
- Configure via `.ctxgraph/config.toml` (created by `init` or `setup`):
262
+ Configure via `.ctxgraph/config.toml` (created interactively by `setup` or manually):
238
263
 
239
264
  ```toml
240
265
  [graph]
241
- # Additional exclude patterns beyond defaults
266
+ # File extensions to scan
267
+ extensions = [".py", ".js", ".ts"]
268
+ # Exclude patterns beyond built-in defaults
242
269
  exclude = ["tests/", "examples/"]
243
270
  # Follow symlinks when scanning
244
271
  follow_symlinks = false
@@ -246,7 +273,7 @@ follow_symlinks = false
246
273
  max_file_size_mb = 5
247
274
  ```
248
275
 
249
- Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
276
+ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
250
277
 
251
278
  ---
252
279
 
@@ -256,10 +283,10 @@ Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `ven
256
283
 
257
284
  | Feature | ctxgraph | ctxgraph-code |
258
285
  |---------|----------|---------------|
259
- | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 8 (init, build, query, deps, usedby, overview, symbols, context, setup, info) |
286
+ | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
260
287
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
261
288
  | Chat sessions | Yes | No |
262
- | Visualizer | D3.js HTML + SVG | No |
289
+ | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
263
290
  | Skills system | Yes (customizable skill TOML files) | No |
264
291
  | MCP server | Yes | No |
265
292
  | Token savings | Yes (capsule DSL compression) | No |
@@ -47,11 +47,21 @@ ctxgraph-code setup
47
47
  ctxgraph-code setup
48
48
  ```
49
49
 
50
+ Interactive walkthrough — prompts for:
51
+ - **File extensions** to scan (`.py`, `.js`, `.ts`, etc.)
52
+ - **Exclude patterns** (folders like `tests/`, globs like `*.generated.py`)
53
+
50
54
  Does everything in one step:
51
- 1. Creates `.ctxgraph/config.toml` with defaults
52
- 2. Builds the knowledge graph from all Python files
55
+ 1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
56
+ 2. Builds the knowledge graph from all matching files
53
57
  3. Creates `.claude/commands/ctxgraph-code.md` with instructions for Claude Code
54
58
 
59
+ Non-interactive mode (skip prompts):
60
+ ```bash
61
+ ctxgraph-code setup --extensions .py,.js,.ts --exclude tests/,examples/
62
+ ctxgraph-code setup -y # all defaults
63
+ ```
64
+
55
65
  ### `init`
56
66
 
57
67
  ```bash
@@ -64,10 +74,12 @@ Creates the `.ctxgraph/` directory with a default `config.toml`.
64
74
 
65
75
  ```bash
66
76
  ctxgraph-code build
67
- ctxgraph-code build --exclude "tests/" --exclude "*.generated.py"
77
+ ctxgraph-code build --extensions .py,.js,.ts
78
+ ctxgraph-code build --exclude tests/ --exclude *.generated.py
68
79
  ```
69
80
 
70
- Scans all `*.py` files in the project, runs AST analysis:
81
+ Scans all matching files in the project, runs AST analysis. Extensions are read from config (`.py` by default, or whatever was set in `setup`).
82
+
71
83
  - **Imports**: which files import other files
72
84
  - **Class definitions**: class names, base classes, methods
73
85
  - **Function definitions**: function names, arguments
@@ -76,6 +88,8 @@ Scans all `*.py` files in the project, runs AST analysis:
76
88
 
77
89
  Stores the result in `.ctxgraph/graph.db`.
78
90
 
91
+ > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Claude Code will also rebuild when it detects the graph is stale.
92
+
79
93
  ### `query`
80
94
 
81
95
  ```bash
@@ -125,6 +139,17 @@ ctxgraph-code context "add pagination to the users endpoint"
125
139
 
126
140
  Generates a focused context summary: relevant files, their symbols, and dependency/call edges between them. This is the closest equivalent to `ctxgraph`'s capsule format.
127
141
 
142
+ ### `view`
143
+
144
+ ```bash
145
+ ctxgraph-code view
146
+ ctxgraph-code view --output tree.txt
147
+ ```
148
+
149
+ Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
150
+
151
+ Useful for a quick visual scan of the project structure without reading every file.
152
+
128
153
  ### `info`
129
154
 
130
155
  ```bash
@@ -211,11 +236,13 @@ Claude then uses these commands as needed during the conversation.
211
236
 
212
237
  ## Configuration
213
238
 
214
- Configure via `.ctxgraph/config.toml` (created by `init` or `setup`):
239
+ Configure via `.ctxgraph/config.toml` (created interactively by `setup` or manually):
215
240
 
216
241
  ```toml
217
242
  [graph]
218
- # Additional exclude patterns beyond defaults
243
+ # File extensions to scan
244
+ extensions = [".py", ".js", ".ts"]
245
+ # Exclude patterns beyond built-in defaults
219
246
  exclude = ["tests/", "examples/"]
220
247
  # Follow symlinks when scanning
221
248
  follow_symlinks = false
@@ -223,7 +250,7 @@ follow_symlinks = false
223
250
  max_file_size_mb = 5
224
251
  ```
225
252
 
226
- Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
253
+ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
227
254
 
228
255
  ---
229
256
 
@@ -233,10 +260,10 @@ Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `ven
233
260
 
234
261
  | Feature | ctxgraph | ctxgraph-code |
235
262
  |---------|----------|---------------|
236
- | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 8 (init, build, query, deps, usedby, overview, symbols, context, setup, info) |
263
+ | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
237
264
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
238
265
  | Chat sessions | Yes | No |
239
- | Visualizer | D3.js HTML + SVG | No |
266
+ | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
240
267
  | Skills system | Yes (customizable skill TOML files) | No |
241
268
  | MCP server | Yes | No |
242
269
  | Token savings | Yes (capsule DSL compression) | No |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxgraph-code"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -17,6 +17,7 @@ from ctxgraph_code.render import (
17
17
  render_deps,
18
18
  render_overview,
19
19
  render_symbols,
20
+ render_treeview,
20
21
  render_usedby,
21
22
  )
22
23
 
@@ -24,11 +25,25 @@ app = typer.Typer(name="ctxgraph-code", help="Code knowledge graph for Claude Co
24
25
  console = Console()
25
26
 
26
27
 
28
+ def _build_time_label(storage) -> str:
29
+ ts = storage.get_metadata("build_time")
30
+ if ts:
31
+ try:
32
+ from datetime import datetime
33
+ dt = datetime.fromtimestamp(float(ts))
34
+ return dt.strftime("%Y-%m-%d %H:%M")
35
+ except Exception:
36
+ return ts
37
+ return "unknown"
38
+
39
+
27
40
  SLASH_COMMAND_TEMPLATE = """# ctxgraph-code: Code Relationship Graph
28
41
 
29
42
  This project has a knowledge graph at `.ctxgraph/graph.db`.
30
43
  The graph knows about imports, class hierarchies, and function calls.
31
44
 
45
+ **Last build:** {build_time}
46
+
32
47
  **Available commands** (run these as shell commands in the terminal):
33
48
 
34
49
  - `ctxgraph-code query "search terms"` -- Find relevant files, classes, and functions
@@ -43,6 +58,9 @@ The graph knows about imports, class hierarchies, and function calls.
43
58
  - When exploring an unfamiliar area, run `query` to find relevant files, then read them.
44
59
  - When asked about architecture, run `overview` for the big picture.
45
60
  - For complex tasks, run `context "what I need to do"` for a focused summary.
61
+
62
+ **Note:** The graph is a static snapshot. If files have changed since the last build,
63
+ run `ctxgraph-code build` to refresh it.
46
64
  """
47
65
 
48
66
 
@@ -70,11 +88,14 @@ def build(
70
88
  repo_path: Optional[str] = typer.Argument(
71
89
  None, help="Path to repository (default: current directory)"
72
90
  ),
91
+ extensions: Optional[str] = typer.Option(
92
+ None, "--extensions", help="File extensions to scan, e.g. .py,.js,.ts"
93
+ ),
73
94
  exclude: Optional[list[str]] = typer.Option(
74
95
  None, "--exclude", "-e", help="Additional exclude patterns"
75
96
  ),
76
97
  ):
77
- """Build the knowledge graph from Python source files."""
98
+ """Build the knowledge graph from source files."""
78
99
  path = Path(repo_path).resolve() if repo_path else Path.cwd()
79
100
 
80
101
  settings = Settings(path)
@@ -82,11 +103,16 @@ def build(
82
103
  if exclude:
83
104
  user_patterns = list((user_patterns or []) + exclude)
84
105
 
106
+ exts = settings.extensions
107
+ if extensions:
108
+ exts = [e.strip() for e in extensions.split(",") if e.strip()]
109
+
85
110
  if not (path / ".ctxgraph").exists():
86
111
  (path / ".ctxgraph").mkdir(parents=True, exist_ok=True)
87
112
 
88
- with console.status(f"Analyzing {path}..."):
89
- stats = build_graph(path, exclude_patterns=user_patterns)
113
+ ext_label = ", ".join(exts)
114
+ with console.status(f"Scanning {ext_label} files in {path}..."):
115
+ stats = build_graph(path, exclude_patterns=user_patterns, extensions=exts)
90
116
 
91
117
  table = Table(title="Graph Build Complete")
92
118
  table.add_column("Metric", style="cyan")
@@ -247,16 +273,52 @@ def setup(
247
273
  repo_path: Optional[str] = typer.Argument(
248
274
  None, help="Path to repository (default: current directory)"
249
275
  ),
276
+ extensions: Optional[str] = typer.Option(
277
+ None, "--extensions", help="File extensions to scan, e.g. .py,.js,.ts"
278
+ ),
279
+ exclude: Optional[str] = typer.Option(
280
+ None, "--exclude", help="Exclude patterns, e.g. tests/,examples/"
281
+ ),
282
+ non_interactive: bool = typer.Option(
283
+ False, "--yes", "-y", help="Skip prompts, use defaults"
284
+ ),
250
285
  ):
251
- """Initialize config, build the graph, and configure Claude Code integration."""
286
+ """Initialize config, build the graph, and configure Claude Code."""
252
287
  path = Path(repo_path).resolve() if repo_path else Path.cwd()
253
288
 
254
- init_project(path)
289
+ if non_interactive:
290
+ exts = [".py"]
291
+ excl = []
292
+ elif extensions:
293
+ exts = [e.strip() for e in extensions.split(",") if e.strip()]
294
+ excl = [e.strip() for e in exclude.split(",") if e.strip()] if exclude else []
295
+ else:
296
+ console.print("[bold cyan]ctxgraph-code setup[/bold cyan]")
297
+ console.print("Let's configure your project graph.\n")
298
+
299
+ ext_input = typer.prompt(
300
+ "File extensions to scan (comma-separated)",
301
+ default=".py",
302
+ prompt_suffix=": ",
303
+ )
304
+ exts = [e.strip() for e in ext_input.split(",") if e.strip()]
305
+ if not exts:
306
+ exts = [".py"]
307
+
308
+ excl_input = typer.prompt(
309
+ "Exclude patterns (comma-separated, e.g. tests/,examples/)",
310
+ default="",
311
+ prompt_suffix=": ",
312
+ )
313
+ excl = [e.strip() for e in excl_input.split(",") if e.strip()] if excl_input.strip() else []
314
+
315
+ console.print()
316
+
317
+ init_project(path, extensions=exts, exclude_patterns=excl)
255
318
  console.print(f"[green][OK] Initialized .ctxgraph/[/green]")
256
319
 
257
- settings = Settings(path)
258
- with console.status(f"Building graph for {path}..."):
259
- stats = build_graph(path, exclude_patterns=settings.exclude_patterns)
320
+ with console.status(f"Scanning {', '.join(exts)} files in {path}..."):
321
+ stats = build_graph(path, exclude_patterns=excl, extensions=exts)
260
322
 
261
323
  table = Table(title="Graph Build Complete")
262
324
  table.add_column("Metric", style="cyan")
@@ -273,8 +335,13 @@ def setup(
273
335
  claude_dir = path / ".claude" / "commands"
274
336
  claude_dir.mkdir(parents=True, exist_ok=True)
275
337
  slash_path = claude_dir / "ctxgraph-code.md"
338
+
339
+ storage = get_storage(path)
340
+ build_label = _build_time_label(storage) if storage else "unknown"
341
+ content = SLASH_COMMAND_TEMPLATE.format(build_time=build_label)
342
+
276
343
  if not slash_path.exists():
277
- slash_path.write_text(SLASH_COMMAND_TEMPLATE, encoding="utf-8")
344
+ slash_path.write_text(content, encoding="utf-8")
278
345
  console.print(f"[green][OK] Created {slash_path}[/green]")
279
346
  else:
280
347
  console.print(f"[yellow] Skipped (already exists): {slash_path}[/yellow]")
@@ -284,6 +351,33 @@ def setup(
284
351
  console.print("Open Claude Code in this project and type [bold]/ctxgraph-code[/bold] to get started.")
285
352
 
286
353
 
354
+ @app.command()
355
+ def view(
356
+ repo_path: Optional[str] = typer.Option(
357
+ None, "--repo", "-r", help="Repository path"
358
+ ),
359
+ output: Optional[str] = typer.Option(
360
+ None, "--output", "-o", help="Save tree to file"
361
+ ),
362
+ ):
363
+ """Visualize the graph as a directory tree with symbols and edges."""
364
+ path = Path(repo_path).resolve() if repo_path else Path.cwd()
365
+
366
+ storage = get_storage(path)
367
+ if storage is None:
368
+ console.print("[red]No graph found. Run [bold]ctxgraph-code build[/bold] first.[/red]")
369
+ raise typer.Exit(1)
370
+
371
+ tree = render_treeview(storage)
372
+
373
+ if output:
374
+ out_path = Path(output)
375
+ out_path.write_text(tree, encoding="utf-8")
376
+ console.print(f"Saved tree to [bold]{out_path}[/bold]")
377
+ else:
378
+ console.print(tree)
379
+
380
+
287
381
  @app.command()
288
382
  def info(
289
383
  repo_path: Optional[str] = typer.Option(
@@ -329,7 +423,7 @@ def version():
329
423
  try:
330
424
  ver = _v("ctxgraph-code")
331
425
  except Exception:
332
- ver = "0.1.0"
426
+ ver = "0.1.2"
333
427
  console.print(f"ctxgraph-code version [bold]{ver}[/bold]")
334
428
 
335
429
 
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from ctxgraph_code.config.settings import create_default_config
7
+
8
+
9
+ def init_project(
10
+ repo_path: Path,
11
+ extensions: Optional[list[str]] = None,
12
+ exclude_patterns: Optional[list[str]] = None,
13
+ ) -> Path:
14
+ cfg_dir = repo_path / ".ctxgraph"
15
+ cfg_dir.mkdir(parents=True, exist_ok=True)
16
+
17
+ create_default_config(repo_path, extensions=extensions, exclude_patterns=exclude_patterns)
18
+
19
+ return cfg_dir
@@ -8,6 +8,7 @@ from typing import Optional
8
8
 
9
9
  DEFAULT_CONFIG = {
10
10
  "graph": {
11
+ "extensions": [".py"],
11
12
  "exclude": [],
12
13
  "follow_symlinks": False,
13
14
  "max_file_size_mb": 5,
@@ -43,6 +44,11 @@ class Settings:
43
44
  parsed = self._parse_toml(text)
44
45
  self._deep_merge(self._data, parsed)
45
46
 
47
+ @property
48
+ def extensions(self) -> list[str]:
49
+ exts = self._data["graph"].get("extensions", [".py"])
50
+ return [e if e.startswith(".") else f".{e}" for e in exts]
51
+
46
52
  @property
47
53
  def exclude_patterns(self) -> list[str]:
48
54
  return self._data["graph"].get("exclude", [])
@@ -67,8 +73,10 @@ class Settings:
67
73
  key = key.strip()
68
74
  value = value.strip()
69
75
 
70
- if (value.startswith('"') and value.endswith('"')) or \
71
- (value.startswith("'") and value.endswith("'")):
76
+ if value.startswith("[") and value.endswith("]"):
77
+ value = Settings._parse_toml_array(value)
78
+ elif (value.startswith('"') and value.endswith('"')) or \
79
+ (value.startswith("'") and value.endswith("'")):
72
80
  value = value[1:-1]
73
81
  else:
74
82
  value = Settings._parse_toml_value(value)
@@ -77,6 +85,21 @@ class Settings:
77
85
 
78
86
  return result
79
87
 
88
+ @staticmethod
89
+ def _parse_toml_array(text: str) -> list:
90
+ inner = text[1:-1].strip()
91
+ if not inner:
92
+ return []
93
+ items = []
94
+ for item in inner.split(","):
95
+ item = item.strip()
96
+ if (item.startswith('"') and item.endswith('"')) or \
97
+ (item.startswith("'") and item.endswith("'")):
98
+ items.append(item[1:-1])
99
+ else:
100
+ items.append(Settings._parse_toml_value(item))
101
+ return items
102
+
80
103
  @staticmethod
81
104
  def _parse_toml_value(value: str):
82
105
  if value.lower() in ("true", "false"):
@@ -97,7 +120,11 @@ class Settings:
97
120
  base[key] = value
98
121
 
99
122
 
100
- def create_default_config(repo_path: Path):
123
+ def create_default_config(
124
+ repo_path: Path,
125
+ extensions: Optional[list[str]] = None,
126
+ exclude_patterns: Optional[list[str]] = None,
127
+ ):
101
128
  config_dir = repo_path / ".ctxgraph"
102
129
  config_dir.mkdir(parents=True, exist_ok=True)
103
130
 
@@ -105,12 +132,20 @@ def create_default_config(repo_path: Path):
105
132
  if config_path.exists():
106
133
  return
107
134
 
135
+ ext_list = extensions or [".py"]
136
+ ext_line = ", ".join(f'"{e}"' for e in ext_list)
137
+
138
+ excl_list = exclude_patterns or []
139
+ excl_line = ", ".join(f'"{e}"' for e in excl_list) if excl_list else ""
140
+
108
141
  config_path.write_text(
109
- """# ctxgraph-code configuration
142
+ f"""# ctxgraph-code configuration
110
143
 
111
144
  [graph]
112
- # Additional exclude patterns (gitignore is used automatically)
113
- exclude = []
145
+ # File extensions to scan
146
+ extensions = [{ext_line}]
147
+ # Exclude patterns (gitignore patterns are excluded automatically)
148
+ exclude = [{excl_line}]
114
149
  # Follow symlinks when scanning files
115
150
  follow_symlinks = false
116
151
  # Skip files larger than this many MB
@@ -16,6 +16,7 @@ def build_graph(
16
16
  repo_path: str | Path,
17
17
  db_path: Optional[str | Path] = None,
18
18
  exclude_patterns: Optional[list[str]] = None,
19
+ extensions: Optional[list[str]] = None,
19
20
  ) -> dict:
20
21
  repo_path = Path(repo_path).resolve()
21
22
  if db_path is None:
@@ -29,8 +30,9 @@ def build_graph(
29
30
  combined = Graph()
30
31
  stats = {"files_analyzed": 0, "files_skipped": 0, "errors": 0}
31
32
 
32
- python_files = list(repo_path.rglob("*.py"))
33
- for file_path in python_files:
33
+ exts = set(extensions or [".py"])
34
+ scan_files = [f for f in repo_path.rglob("*") if f.suffix in exts and f.is_file()]
35
+ for file_path in scan_files:
34
36
  if should_exclude(file_path, repo_path, exclude_patterns):
35
37
  stats["files_skipped"] += 1
36
38
  continue
@@ -235,6 +235,107 @@ def render_context(storage: Storage, query: str, max_nodes: int = 15) -> str:
235
235
  return "\n".join(lines)
236
236
 
237
237
 
238
+ def render_treeview(storage: Storage) -> str:
239
+ all_nodes = storage.get_all_nodes()
240
+ all_edges = storage.get_all_edges()
241
+
242
+ file_nodes = sorted([n for n in all_nodes if n.type == "file"], key=lambda n: n.path or "")
243
+ symbol_map: dict[str, list[Node]] = {}
244
+ for n in all_nodes:
245
+ if n.type != "file":
246
+ symbol_map.setdefault(n.parent_id or "", []).append(n)
247
+
248
+ dir_tree: dict[str, dict] = {}
249
+ for node in file_nodes:
250
+ parts = (node.path or node.name).split("/")
251
+ for i in range(len(parts)):
252
+ parent = "/".join(parts[:i]) if i > 0 else "."
253
+ child = parts[i]
254
+ if parent not in dir_tree:
255
+ dir_tree[parent] = {"dirs": set(), "files": []}
256
+ if i == len(parts) - 1:
257
+ dir_tree[parent]["files"].append(node)
258
+ else:
259
+ dir_tree[parent]["dirs"].add(child)
260
+
261
+ stats = storage.stats()
262
+ bt = storage.get_metadata("build_time")
263
+ build_label = ""
264
+ if bt:
265
+ try:
266
+ from datetime import datetime
267
+ build_label = f" (built {datetime.fromtimestamp(float(bt)).strftime('%Y-%m-%d %H:%M')})"
268
+ except Exception:
269
+ pass
270
+
271
+ lines = [
272
+ f".ctxgraph/graph.db ({stats['nodes']} nodes, {stats['edges']} edges){build_label}",
273
+ "",
274
+ ]
275
+
276
+ def _render_dir(parent: str, prefix: str = ""):
277
+ entry = dir_tree.get(parent, {"dirs": set(), "files": []})
278
+ items: list[tuple[str, object]] = []
279
+ for d in sorted(entry["dirs"]):
280
+ items.append(("dir", d))
281
+ for f in sorted(entry["files"], key=lambda x: x.path or x.name):
282
+ items.append(("file", f))
283
+
284
+ for idx, (kind, obj) in enumerate(items):
285
+ last = idx == len(items) - 1
286
+ connector = "\\-- " if last else "+-- "
287
+ ext = " " if last else "| "
288
+
289
+ if kind == "dir":
290
+ name = str(obj)
291
+ lines.append(f"{prefix}{connector}{name}/")
292
+ child = name if parent == "." else parent + "/" + name
293
+ _render_dir(child, prefix + ext)
294
+ else:
295
+ node = obj
296
+ basename = (node.path or node.name).split("/")[-1]
297
+ lines.append(f"{prefix}{connector}{basename}")
298
+ symbols = sorted(symbol_map.get(node.id, []), key=lambda s: s.lineno)
299
+ if symbols:
300
+ sym_lines = []
301
+ for s in symbols:
302
+ tag = "C" if s.type == "class" else "M"
303
+ summary = f" -- {s.summary}" if s.summary else ""
304
+ sym_lines.append(f" [{tag}] {s.name}{summary}")
305
+ for sidx, sl in enumerate(sym_lines):
306
+ slast = sidx == len(sym_lines) - 1
307
+ sconn = "\\-- " if slast else "+-- "
308
+ lines.append(f"{prefix}{ext}{sconn}{sl}")
309
+
310
+ _render_dir(".")
311
+ lines.append("")
312
+
313
+ import_edges = [(s, t) for e in all_edges for s, t, r in [(e.source_id, e.target_id, e.relation)] if r == "imports"]
314
+ call_edges = [(s, t) for e in all_edges for s, t, r in [(e.source_id, e.target_id, e.relation)] if r == "calls"]
315
+
316
+ if import_edges:
317
+ lines.append(f"Imports ({len(import_edges)}):")
318
+ node_map = {n.id: n for n in all_nodes}
319
+ for src, tgt in sorted(import_edges, key=lambda x: (x[0], x[1]))[:20]:
320
+ sn = _short_name(src, node_map) or src
321
+ tn = _short_name(tgt, node_map) or tgt
322
+ lines.append(f" {sn} -> {tn}")
323
+ if len(import_edges) > 20:
324
+ lines.append(f" ... and {len(import_edges) - 20} more")
325
+
326
+ if call_edges:
327
+ lines.append(f"\nCalls ({len(call_edges)}):")
328
+ node_map = {n.id: n for n in all_nodes}
329
+ for src, tgt in sorted(call_edges, key=lambda x: (x[0], x[1]))[:15]:
330
+ sn = _short_name(src, node_map) or src
331
+ tn = _short_name(tgt, node_map) or tgt
332
+ lines.append(f" {sn} -> {tn}")
333
+ if len(call_edges) > 15:
334
+ lines.append(f" ... and {len(call_edges) - 15} more")
335
+
336
+ return "\n".join(lines)
337
+
338
+
238
339
  def _short_name(node_id: str, node_map: dict[str, Node]) -> Optional[str]:
239
340
  if node_id in node_map:
240
341
  n = node_map[node_id]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph-code
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions.
5
5
  Author: ctxgraph-code contributors
6
6
  License: MIT
@@ -70,11 +70,21 @@ ctxgraph-code setup
70
70
  ctxgraph-code setup
71
71
  ```
72
72
 
73
+ Interactive walkthrough — prompts for:
74
+ - **File extensions** to scan (`.py`, `.js`, `.ts`, etc.)
75
+ - **Exclude patterns** (folders like `tests/`, globs like `*.generated.py`)
76
+
73
77
  Does everything in one step:
74
- 1. Creates `.ctxgraph/config.toml` with defaults
75
- 2. Builds the knowledge graph from all Python files
78
+ 1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
79
+ 2. Builds the knowledge graph from all matching files
76
80
  3. Creates `.claude/commands/ctxgraph-code.md` with instructions for Claude Code
77
81
 
82
+ Non-interactive mode (skip prompts):
83
+ ```bash
84
+ ctxgraph-code setup --extensions .py,.js,.ts --exclude tests/,examples/
85
+ ctxgraph-code setup -y # all defaults
86
+ ```
87
+
78
88
  ### `init`
79
89
 
80
90
  ```bash
@@ -87,10 +97,12 @@ Creates the `.ctxgraph/` directory with a default `config.toml`.
87
97
 
88
98
  ```bash
89
99
  ctxgraph-code build
90
- ctxgraph-code build --exclude "tests/" --exclude "*.generated.py"
100
+ ctxgraph-code build --extensions .py,.js,.ts
101
+ ctxgraph-code build --exclude tests/ --exclude *.generated.py
91
102
  ```
92
103
 
93
- Scans all `*.py` files in the project, runs AST analysis:
104
+ Scans all matching files in the project, runs AST analysis. Extensions are read from config (`.py` by default, or whatever was set in `setup`).
105
+
94
106
  - **Imports**: which files import other files
95
107
  - **Class definitions**: class names, base classes, methods
96
108
  - **Function definitions**: function names, arguments
@@ -99,6 +111,8 @@ Scans all `*.py` files in the project, runs AST analysis:
99
111
 
100
112
  Stores the result in `.ctxgraph/graph.db`.
101
113
 
114
+ > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Claude Code will also rebuild when it detects the graph is stale.
115
+
102
116
  ### `query`
103
117
 
104
118
  ```bash
@@ -148,6 +162,17 @@ ctxgraph-code context "add pagination to the users endpoint"
148
162
 
149
163
  Generates a focused context summary: relevant files, their symbols, and dependency/call edges between them. This is the closest equivalent to `ctxgraph`'s capsule format.
150
164
 
165
+ ### `view`
166
+
167
+ ```bash
168
+ ctxgraph-code view
169
+ ctxgraph-code view --output tree.txt
170
+ ```
171
+
172
+ Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
173
+
174
+ Useful for a quick visual scan of the project structure without reading every file.
175
+
151
176
  ### `info`
152
177
 
153
178
  ```bash
@@ -234,11 +259,13 @@ Claude then uses these commands as needed during the conversation.
234
259
 
235
260
  ## Configuration
236
261
 
237
- Configure via `.ctxgraph/config.toml` (created by `init` or `setup`):
262
+ Configure via `.ctxgraph/config.toml` (created interactively by `setup` or manually):
238
263
 
239
264
  ```toml
240
265
  [graph]
241
- # Additional exclude patterns beyond defaults
266
+ # File extensions to scan
267
+ extensions = [".py", ".js", ".ts"]
268
+ # Exclude patterns beyond built-in defaults
242
269
  exclude = ["tests/", "examples/"]
243
270
  # Follow symlinks when scanning
244
271
  follow_symlinks = false
@@ -246,7 +273,7 @@ follow_symlinks = false
246
273
  max_file_size_mb = 5
247
274
  ```
248
275
 
249
- Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
276
+ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.git`, `node_modules`, `venv`, `.venv`, `dist`, `build`, `*.egg-info`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `migrations`, `*.min.js`, `*.min.css`.
250
277
 
251
278
  ---
252
279
 
@@ -256,10 +283,10 @@ Default exclusion patterns: `__pycache__`, `*.pyc`, `.git`, `node_modules`, `ven
256
283
 
257
284
  | Feature | ctxgraph | ctxgraph-code |
258
285
  |---------|----------|---------------|
259
- | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 8 (init, build, query, deps, usedby, overview, symbols, context, setup, info) |
286
+ | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
260
287
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
261
288
  | Chat sessions | Yes | No |
262
- | Visualizer | D3.js HTML + SVG | No |
289
+ | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
263
290
  | Skills system | Yes (customizable skill TOML files) | No |
264
291
  | MCP server | Yes | No |
265
292
  | Token savings | Yes (capsule DSL compression) | No |
@@ -1,14 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
-
5
- from ctxgraph_code.config.settings import create_default_config
6
-
7
-
8
- def init_project(repo_path: Path) -> Path:
9
- cfg_dir = repo_path / ".ctxgraph"
10
- cfg_dir.mkdir(parents=True, exist_ok=True)
11
-
12
- create_default_config(repo_path)
13
-
14
- return cfg_dir
File without changes