ctxgraph-code 0.5.0__tar.gz → 0.6.0__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 (39) hide show
  1. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/PKG-INFO +31 -9
  2. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/README.md +30 -8
  3. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/pyproject.toml +1 -1
  4. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/cli.py +299 -4
  5. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/hooks.py +2 -1
  6. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/graph/storage.py +36 -0
  7. ctxgraph_code-0.6.0/src/ctxgraph_code/render/__init__.py +20 -0
  8. ctxgraph_code-0.6.0/src/ctxgraph_code/render/mermaid.py +146 -0
  9. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code.egg-info/PKG-INFO +31 -9
  10. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code.egg-info/SOURCES.txt +3 -1
  11. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/setup.cfg +0 -0
  12. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/__init__.py +0 -0
  13. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/__main__.py +0 -0
  14. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/__init__.py +0 -0
  15. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/python/__init__.py +0 -0
  16. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/python/importer.py +0 -0
  17. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/python/semantic.py +0 -0
  18. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/python/symbols.py +0 -0
  19. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/treesitter/__init__.py +0 -0
  20. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/treesitter/analyzer.py +0 -0
  21. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/analyzers/treesitter/languages.py +0 -0
  22. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/__init__.py +0 -0
  23. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/build_status.py +0 -0
  24. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/global_paths.py +0 -0
  25. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/init.py +0 -0
  26. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/config/settings.py +0 -0
  27. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/exclude/__init__.py +0 -0
  28. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/exclude/patterns.py +0 -0
  29. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/graph/__init__.py +0 -0
  30. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/graph/builder.py +0 -0
  31. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/graph/models.py +0 -0
  32. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/graph/query.py +0 -0
  33. /ctxgraph_code-0.5.0/src/ctxgraph_code/render.py → /ctxgraph_code-0.6.0/src/ctxgraph_code/render/_text.py +0 -0
  34. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/view/__init__.py +0 -0
  35. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code/view/visualizer.py +0 -0
  36. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code.egg-info/dependency_links.txt +0 -0
  37. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code.egg-info/entry_points.txt +0 -0
  38. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/src/ctxgraph_code.egg-info/requires.txt +0 -0
  39. {ctxgraph_code-0.5.0 → ctxgraph_code-0.6.0}/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.5.0
3
+ Version: 0.6.0
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
@@ -30,6 +30,8 @@ Requires-Dist: pytest>=7.0; extra == "dev"
30
30
 
31
31
  ```bash
32
32
  pip install ctxgraph-code
33
+ # For multi-language support (C, Go, Rust, JS, TS, Java, etc.):
34
+ pip install 'ctxgraph-code[full]'
33
35
  cd my-project
34
36
  ctxgraph-code setup
35
37
  ```
@@ -52,10 +54,10 @@ These questions require running multiple `grep` commands or reading dependency c
52
54
  ## Quick Start
53
55
 
54
56
  ```bash
55
- # Install
56
- pip install ctxgraph-code
57
+ # Install (add [full] for multi-language support)
58
+ pip install 'ctxgraph-code[full]'
57
59
 
58
- # Navigate to your Python project
60
+ # Navigate to your project
59
61
  cd my-project
60
62
 
61
63
  # One-command setup: init + build + configure Claude Code
@@ -80,7 +82,14 @@ Interactive walkthrough — prompts for:
80
82
  Does everything in one step:
81
83
  1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
82
84
  2. Installs the `/ctxgraph-code` slash command globally (works in every Claude Code session)
83
- 3. Builds the knowledge graph from all matching files
85
+ 3. Builds the knowledge graph from all matching files — shows live per-graph progress:
86
+ ```
87
+ Building 5 graphs with 8 workers...
88
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
89
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
90
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
91
+ Built all 5 graphs in 2.1s
92
+ ```
84
93
 
85
94
  Non-interactive mode:
86
95
  ```bash
@@ -122,6 +131,15 @@ Scans all matching files in the project, runs AST analysis. Extensions are read
122
131
  - `--verbose` / `-v` — show per-file progress
123
132
  - `--no-summary` — skip docstring extraction for faster builds
124
133
 
134
+ Shows live per-graph progress as each completes:
135
+ ```
136
+ Building 5 graphs with 8 workers...
137
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
138
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
139
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
140
+ Built all 5 graphs in 2.1s
141
+ ```
142
+
125
143
  Stores graphs in `.ctxgraph/graphs/<dir>.db` (per-directory) or `.ctxgraph/graph.db` (combined).
126
144
 
127
145
  > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Use `--incremental` to only reprocess changed files.
@@ -225,10 +243,10 @@ ctxgraph-code probe "database connection pool"
225
243
  ctxgraph-code probe "user authentication" --max 3
226
244
  ```
227
245
 
228
- Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first 40 lines of matched files with syntax highlighting.
246
+ Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first N lines of matched files with automatic syntax highlighting per language.
229
247
 
230
248
  - `--max` / `-m` — max files to probe (default: 5)
231
- - `--context` / `-c` — lines of context around matches (default: 3)
249
+ - `--context` / `-c` — lines to show per file (default: 40, use 0 for full file)
232
250
 
233
251
  ### `install-hooks`
234
252
 
@@ -317,7 +335,10 @@ This catches the common case where you edit a file after building the graph and
317
335
  | **Multiprocessing** | Combined graphs split files across CPU cores via `multiprocessing.Pool` |
318
336
  | **`--jobs`** | Control parallelism level (default: CPU count) |
319
337
  | **Incremental builds** | `--incremental` caches file mtimes, only reprocesses changed files |
320
- | **Trivial file skip** | `_quick_scan()` pre-checks before AST parse for files with no imports/classes/functions |
338
+ | **Trivial file skip** | `_quick_scan()` pre-checks all files (Python and non-Python) — skips full parse for files with no code |
339
+ | **`follow_symlinks` config** | Respects `follow_symlinks = false` setting to avoid duplicate/broken symlinks |
340
+ | **`max_file_size_mb` config** | Skips files exceeding the configured size limit before reading |
341
+ | **Live build progress** | Per-graph status + timing as each completes during parallel builds |
321
342
  | **Cached excludes** | `lru_cache` on `should_exclude()` during `os.walk` |
322
343
  | **Batch SQLite inserts** | `executemany` instead of per-row `INSERT` statements |
323
344
  | **`--no-summary`** | Skips docstring extraction (fastest rebuilds) |
@@ -389,7 +410,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
389
410
 
390
411
  | Feature | ctxgraph | ctxgraph-code |
391
412
  |---------|----------|---------------|
392
- | CLI commands | 9+ | 16 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks) |
413
+ | CLI commands | 9+ | 17 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks, version) |
393
414
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
394
415
  | Chat sessions | Yes | No |
395
416
  | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
@@ -404,6 +425,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
404
425
 
405
426
  - Python 3.10+
406
427
  - A Claude Code subscription (for the `/ctxgraph-code` slash command — the graph itself works standalone)
428
+ - For multi-language analysis (C, Go, Rust, JS, TS, Java, etc.): `pip install 'ctxgraph-code[full]'` to install tree-sitter support
407
429
 
408
430
  ---
409
431
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  ```bash
6
6
  pip install ctxgraph-code
7
+ # For multi-language support (C, Go, Rust, JS, TS, Java, etc.):
8
+ pip install 'ctxgraph-code[full]'
7
9
  cd my-project
8
10
  ctxgraph-code setup
9
11
  ```
@@ -26,10 +28,10 @@ These questions require running multiple `grep` commands or reading dependency c
26
28
  ## Quick Start
27
29
 
28
30
  ```bash
29
- # Install
30
- pip install ctxgraph-code
31
+ # Install (add [full] for multi-language support)
32
+ pip install 'ctxgraph-code[full]'
31
33
 
32
- # Navigate to your Python project
34
+ # Navigate to your project
33
35
  cd my-project
34
36
 
35
37
  # One-command setup: init + build + configure Claude Code
@@ -54,7 +56,14 @@ Interactive walkthrough — prompts for:
54
56
  Does everything in one step:
55
57
  1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
56
58
  2. Installs the `/ctxgraph-code` slash command globally (works in every Claude Code session)
57
- 3. Builds the knowledge graph from all matching files
59
+ 3. Builds the knowledge graph from all matching files — shows live per-graph progress:
60
+ ```
61
+ Building 5 graphs with 8 workers...
62
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
63
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
64
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
65
+ Built all 5 graphs in 2.1s
66
+ ```
58
67
 
59
68
  Non-interactive mode:
60
69
  ```bash
@@ -96,6 +105,15 @@ Scans all matching files in the project, runs AST analysis. Extensions are read
96
105
  - `--verbose` / `-v` — show per-file progress
97
106
  - `--no-summary` — skip docstring extraction for faster builds
98
107
 
108
+ Shows live per-graph progress as each completes:
109
+ ```
110
+ Building 5 graphs with 8 workers...
111
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
112
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
113
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
114
+ Built all 5 graphs in 2.1s
115
+ ```
116
+
99
117
  Stores graphs in `.ctxgraph/graphs/<dir>.db` (per-directory) or `.ctxgraph/graph.db` (combined).
100
118
 
101
119
  > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Use `--incremental` to only reprocess changed files.
@@ -199,10 +217,10 @@ ctxgraph-code probe "database connection pool"
199
217
  ctxgraph-code probe "user authentication" --max 3
200
218
  ```
201
219
 
202
- Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first 40 lines of matched files with syntax highlighting.
220
+ Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first N lines of matched files with automatic syntax highlighting per language.
203
221
 
204
222
  - `--max` / `-m` — max files to probe (default: 5)
205
- - `--context` / `-c` — lines of context around matches (default: 3)
223
+ - `--context` / `-c` — lines to show per file (default: 40, use 0 for full file)
206
224
 
207
225
  ### `install-hooks`
208
226
 
@@ -291,7 +309,10 @@ This catches the common case where you edit a file after building the graph and
291
309
  | **Multiprocessing** | Combined graphs split files across CPU cores via `multiprocessing.Pool` |
292
310
  | **`--jobs`** | Control parallelism level (default: CPU count) |
293
311
  | **Incremental builds** | `--incremental` caches file mtimes, only reprocesses changed files |
294
- | **Trivial file skip** | `_quick_scan()` pre-checks before AST parse for files with no imports/classes/functions |
312
+ | **Trivial file skip** | `_quick_scan()` pre-checks all files (Python and non-Python) — skips full parse for files with no code |
313
+ | **`follow_symlinks` config** | Respects `follow_symlinks = false` setting to avoid duplicate/broken symlinks |
314
+ | **`max_file_size_mb` config** | Skips files exceeding the configured size limit before reading |
315
+ | **Live build progress** | Per-graph status + timing as each completes during parallel builds |
295
316
  | **Cached excludes** | `lru_cache` on `should_exclude()` during `os.walk` |
296
317
  | **Batch SQLite inserts** | `executemany` instead of per-row `INSERT` statements |
297
318
  | **`--no-summary`** | Skips docstring extraction (fastest rebuilds) |
@@ -363,7 +384,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
363
384
 
364
385
  | Feature | ctxgraph | ctxgraph-code |
365
386
  |---------|----------|---------------|
366
- | CLI commands | 9+ | 16 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks) |
387
+ | CLI commands | 9+ | 17 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks, version) |
367
388
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
368
389
  | Chat sessions | Yes | No |
369
390
  | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
@@ -378,6 +399,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
378
399
 
379
400
  - Python 3.10+
380
401
  - A Claude Code subscription (for the `/ctxgraph-code` slash command — the graph itself works standalone)
402
+ - For multi-language analysis (C, Go, Rust, JS, TS, Java, etc.): `pip install 'ctxgraph-code[full]'` to install tree-sitter support
381
403
 
382
404
  ---
383
405
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxgraph-code"
7
- version = "0.5.0"
7
+ version = "0.6.0"
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"}
@@ -268,7 +268,12 @@ SLASH_COMMAND_TEMPLATE = """# ctxgraph-code: Code Relationship Graph
268
268
  - `ctxgraph-code overview --dir <name>` -- Show project structure for a specific graph
269
269
  - `ctxgraph-code symbols <path>` -- List classes/functions defined in a file
270
270
  - `ctxgraph-code context "task"` -- Generate a focused context summary
271
+ - `ctxgraph-code subgraph "task"` -- Extract a focused subgraph with inline source for a task
272
+ - `ctxgraph-code diff` -- Compare graph with filesystem to find new/removed/changed files
273
+ - `ctxgraph-code mermaid --type classDiagram` -- Export graph as Mermaid diagram
271
274
  - `ctxgraph-code view --dir <name>` -- Visualize a graph interactively
275
+ - `ctxgraph-code build-status` -- Show last build status and timing
276
+ - `ctxgraph-code install-slash` -- (Re)install this slash command
272
277
 
273
278
  **Tip:** Use `--dir <name>` to scope queries to a per-directory graph.
274
279
  When passing a file path like `auth/login.py`, the correct graph is auto-detected.
@@ -386,13 +391,13 @@ def _build_dirs_parallel(path, exts, user_patterns, top_dirs, graphs_dir, jobs,
386
391
  edges = stats.get("total_edges", 0)
387
392
  t = stats.get("elapsed_seconds", 0)
388
393
  console.print(
389
- f" [green][/green] {label}/ "
394
+ f" [green]OK[/green] {label}/ "
390
395
  f"({files} files, {nodes} nodes, {edges} edges, {t}s)"
391
396
  )
392
397
  except Exception as e:
393
398
  results.append((label, str(e)))
394
399
  err_count += 1
395
- console.print(f" [red][/red] {label}/ ([red]{e}[/red])")
400
+ console.print(f" [red]FAIL[/red] {label}/ ([red]{e}[/red])")
396
401
 
397
402
  elapsed_total = time.time() - _build_progress_start
398
403
  if err_count:
@@ -819,7 +824,7 @@ def setup(
819
824
  )
820
825
 
821
826
 
822
- @app.command(name="build-status")
827
+ @app.command(name="build-status")
823
828
  def build_status(
824
829
  repo_path: Optional[str] = typer.Option(
825
830
  None, "--repo", "-r", help="Repository path"
@@ -1105,10 +1110,300 @@ def version():
1105
1110
  try:
1106
1111
  ver = _v("ctxgraph-code")
1107
1112
  except Exception:
1108
- ver = "0.3.0"
1113
+ ver = "0.6.0"
1109
1114
  console.print(f"ctxgraph-code version [bold]{ver}[/bold]")
1110
1115
 
1111
1116
 
1117
+ @app.command()
1118
+ def mermaid(
1119
+ repo_path: Optional[str] = typer.Option(
1120
+ None, "--repo", "-r", help="Repository path"
1121
+ ),
1122
+ output: Optional[str] = typer.Option(
1123
+ None, "--output", "-o", help="Save Mermaid output to file"
1124
+ ),
1125
+ max_nodes: int = typer.Option(
1126
+ 50, "--max-nodes", "-n", help="Maximum nodes in diagram"
1127
+ ),
1128
+ dir_name: Optional[str] = typer.Option(
1129
+ None, "--dir", "-d", help="Directory graph to use"
1130
+ ),
1131
+ diagram_type: str = typer.Argument(
1132
+ "classDiagram", help="Diagram type: classDiagram, flowchart, sequence"
1133
+ ),
1134
+ ):
1135
+ """Export the graph as a Mermaid diagram.
1136
+
1137
+ Supported diagram types: classDiagram, flowchart, sequence.
1138
+ Outputs to console by default, or to a file with --output.
1139
+ """
1140
+ path = Path(repo_path).resolve() if repo_path else Path.cwd()
1141
+ storage = _resolve_storage(path, dir_name=dir_name)
1142
+
1143
+ from ctxgraph_code.render.mermaid import MermaidError, render_mermaid
1144
+
1145
+ try:
1146
+ result = render_mermaid(
1147
+ storage,
1148
+ output_type=diagram_type,
1149
+ max_nodes=max_nodes,
1150
+ )
1151
+ except MermaidError as e:
1152
+ console.print(f"[red]{e}[/red]")
1153
+ raise typer.Exit(1)
1154
+
1155
+ if output:
1156
+ out_path = Path(output)
1157
+ out_path.write_text(result, encoding="utf-8")
1158
+ console.print(f"[green]Mermaid diagram saved to [bold]{out_path}[/bold][/green]")
1159
+ else:
1160
+ console.print(result)
1161
+
1162
+
1163
+ @app.command()
1164
+ def subgraph(
1165
+ query: str = typer.Argument(..., help="Task description"),
1166
+ repo_path: Optional[str] = typer.Option(
1167
+ None, "--repo", "-r", help="Repository path"
1168
+ ),
1169
+ max_nodes: int = typer.Option(
1170
+ 10, "--max-nodes", "-n", help="Maximum nodes in subgraph"
1171
+ ),
1172
+ dir_name: Optional[str] = typer.Option(
1173
+ None, "--dir", "-d", help="Directory graph to query"
1174
+ ),
1175
+ ):
1176
+ """Extract a focused subgraph relevant to a task description.
1177
+
1178
+ Returns matching nodes, their relationships, and inline source code
1179
+ for a compact context window.
1180
+ """
1181
+ import hashlib
1182
+
1183
+ path = Path(repo_path).resolve() if repo_path else Path.cwd()
1184
+ storage = _resolve_storage(path, dir_name=dir_name)
1185
+
1186
+ results = search_relevant_nodes(storage, query, max_nodes=max_nodes)
1187
+ if not results:
1188
+ console.print("[yellow]No relevant nodes found for subgraph.[/yellow]")
1189
+ raise typer.Exit()
1190
+
1191
+ matched_ids = {n.id for n, _ in results}
1192
+ edge_ids: set[str] = set()
1193
+ edges = storage.get_edges_for_nodes(matched_ids)
1194
+ for e in edges:
1195
+ if e.source_id in matched_ids:
1196
+ edge_ids.add(e.target_id)
1197
+ if e.target_id in matched_ids:
1198
+ edge_ids.add(e.source_id)
1199
+
1200
+ all_ids = matched_ids | edge_ids
1201
+ node_map: dict[str, object] = {}
1202
+ for nid in all_ids:
1203
+ n = storage.get_node(nid)
1204
+ if n:
1205
+ node_map[nid] = n
1206
+
1207
+ file_nodes_in_subgraph = {
1208
+ n for n in node_map.values()
1209
+ if hasattr(n, 'type') and n.type == "file"
1210
+ }
1211
+
1212
+ lines = [
1213
+ f"Subgraph for: {query}",
1214
+ f"Nodes: {len(matched_ids)} seed + {len(edge_ids)} related = {len(all_ids)} total",
1215
+ "",
1216
+ ]
1217
+
1218
+ # Group by file
1219
+ file_groups: dict[str, list] = {}
1220
+ for n in sorted(node_map.values(), key=lambda x: x.path or ""):
1221
+ fp = getattr(n, 'path', None) or ""
1222
+ file_groups.setdefault(fp, []).append(n)
1223
+
1224
+ for fp, nodes in sorted(file_groups.items()):
1225
+ file_node = next((n for n in nodes if n.type == "file"), None)
1226
+ if file_node:
1227
+ tag = "F"
1228
+ lines.append(f" [{tag}] [bold]{file_node.name}[/bold] [blue]{file_node.path or '-'}[/blue]")
1229
+ if file_node.summary:
1230
+ lines.append(f" {file_node.summary}")
1231
+ symbols = [n for n in nodes if n.type in ("class", "function")]
1232
+ if symbols:
1233
+ for s in symbols:
1234
+ tag = "C" if s.type == "class" else "M"
1235
+ lines.append(f" [{tag}] {s.name} (line {s.lineno})")
1236
+ if s.summary:
1237
+ lines.append(f" {s.summary}")
1238
+
1239
+ if edges:
1240
+ lines.append("")
1241
+ lines.append(" Relationships:")
1242
+ for e in edges[:15]:
1243
+ src = node_map.get(e.source_id)
1244
+ tgt = node_map.get(e.target_id)
1245
+ src_name = src.name if src else e.source_id
1246
+ tgt_name = tgt.name if tgt else e.target_id
1247
+ lines.append(f" {src_name} --[{e.relation}]--> {tgt_name}")
1248
+
1249
+ # Inline source for each file node
1250
+ lines.append("")
1251
+ for n in sorted(file_nodes_in_subgraph, key=lambda x: x.path or ""):
1252
+ fp = path / n.path if n.path else None
1253
+ if fp and fp.is_file():
1254
+ try:
1255
+ code = fp.read_text(encoding="utf-8", errors="replace")
1256
+ lines.append(f" === {n.path} ===")
1257
+ snippet = code.splitlines()[:60]
1258
+ lines.extend(snippet)
1259
+ if len(code.splitlines()) > 60:
1260
+ lines.append(f"... ({len(code.splitlines()) - 60} more lines)")
1261
+ lines.append("")
1262
+ except OSError:
1263
+ pass
1264
+
1265
+ console.print("\n".join(lines))
1266
+
1267
+
1268
+ @app.command()
1269
+ def diff(
1270
+ repo_path: Optional[str] = typer.Option(
1271
+ None, "--repo", "-r", help="Repository path"
1272
+ ),
1273
+ dir_name: Optional[str] = typer.Option(
1274
+ None, "--dir", "-d", help="Directory graph to compare"
1275
+ ),
1276
+ ref_branch: Optional[str] = typer.Option(
1277
+ None, "--ref", help="Git reference/branch to diff against (requires git)"
1278
+ ),
1279
+ ):
1280
+ """Compare the graph with the filesystem.
1281
+
1282
+ Shows files that have been added, removed, or changed since the
1283
+ graph was built. Use --ref for a git-aware diff against a branch.
1284
+ """
1285
+ import hashlib
1286
+ import json as j
1287
+
1288
+ path = Path(repo_path).resolve() if repo_path else Path.cwd()
1289
+ storage = _resolve_storage(path, dir_name=dir_name)
1290
+
1291
+ new_files: list[str] = []
1292
+ removed_files: list[str] = []
1293
+ changed_files: list[str] = []
1294
+
1295
+ graph_paths = set(storage.get_file_paths())
1296
+ hashes_json = storage.get_metadata("content_hashes")
1297
+ stored_hashes: dict[str, str] = {}
1298
+ if hashes_json:
1299
+ try:
1300
+ stored_hashes = j.loads(hashes_json)
1301
+ except (j.JSONDecodeError, OSError):
1302
+ pass
1303
+
1304
+ if ref_branch:
1305
+ try:
1306
+ import subprocess
1307
+ result = subprocess.run(
1308
+ ["git", "diff", "--name-only", ref_branch],
1309
+ capture_output=True, text=True, check=False, cwd=str(path),
1310
+ )
1311
+ if result.returncode == 0:
1312
+ git_files = set(result.stdout.strip().splitlines()) if result.stdout.strip() else set()
1313
+ for gf in git_files:
1314
+ if gf not in graph_paths:
1315
+ new_files.append(gf)
1316
+ for gp in graph_paths:
1317
+ if gp not in git_files:
1318
+ removed_files.append(gp)
1319
+ # Changed files = git diff --name-only from HEAD
1320
+ head_result = subprocess.run(
1321
+ ["git", "diff", "--name-only", "HEAD"],
1322
+ capture_output=True, text=True, check=False, cwd=str(path),
1323
+ )
1324
+ if head_result.returncode == 0:
1325
+ changed_files = [f for f in head_result.stdout.strip().splitlines() if f in graph_paths]
1326
+ else:
1327
+ console.print(f"[red]git diff failed: {result.stderr.strip()}[/red]")
1328
+ console.print("[yellow]Falling back to filesystem comparison...[/yellow]")
1329
+ ref_branch = None
1330
+ except FileNotFoundError:
1331
+ console.print("[yellow]Git not found. Falling back to filesystem comparison...[/yellow]")
1332
+ ref_branch = None
1333
+
1334
+ if not ref_branch:
1335
+ for gp in graph_paths:
1336
+ fp = path / gp
1337
+ if not fp.is_file():
1338
+ removed_files.append(gp)
1339
+
1340
+ src_extensions = {".py", ".js", ".ts", ".tsx", ".go", ".rs", ".c", ".h", ".cpp", ".java", ".rb", ".kt", ".swift"}
1341
+ for root, _dirs, files in os.walk(path):
1342
+ _dirs[:] = [d for d in _dirs if not d.startswith(".") and d not in ("node_modules", "venv", ".venv", "env", "dist", "build", "__pycache__")]
1343
+ for f in files:
1344
+ ext = os.path.splitext(f)[1].lower()
1345
+ if ext not in src_extensions:
1346
+ continue
1347
+ rel_path = os.path.relpath(os.path.join(root, f), path).replace("\\", "/")
1348
+ if rel_path not in graph_paths:
1349
+ new_files.append(rel_path)
1350
+
1351
+ # Content hash comparison for changed detection
1352
+ if stored_hashes:
1353
+ for rel_path, stored_hash in stored_hashes.items():
1354
+ fp = path / rel_path
1355
+ if fp.is_file():
1356
+ try:
1357
+ actual = hashlib.sha256(
1358
+ fp.read_text(encoding="utf-8", errors="replace")
1359
+ .encode("utf-8")
1360
+ ).hexdigest()
1361
+ if actual != stored_hash:
1362
+ changed_files.append(rel_path)
1363
+ except OSError:
1364
+ changed_files.append(rel_path)
1365
+
1366
+ lines = ["Graph vs Filesystem Diff", ""]
1367
+ if new_files:
1368
+ lines.append(f" [green]+ {len(new_files)} new file(s):[/green]")
1369
+ for f in sorted(new_files)[:10]:
1370
+ lines.append(f" + {f}")
1371
+ if len(new_files) > 10:
1372
+ lines.append(f" ... and {len(new_files) - 10} more")
1373
+ else:
1374
+ lines.append(" [green]+ No new files[/green]")
1375
+
1376
+ if removed_files:
1377
+ lines.append(f"")
1378
+ lines.append(f" [red]- {len(removed_files)} removed file(s):[/red]")
1379
+ for f in sorted(removed_files)[:10]:
1380
+ lines.append(f" - {f}")
1381
+ if len(removed_files) > 10:
1382
+ lines.append(f" ... and {len(removed_files) - 10} more")
1383
+ else:
1384
+ lines.append(f"")
1385
+ lines.append(" [red]- No removed files[/red]")
1386
+
1387
+ if changed_files:
1388
+ lines.append(f"")
1389
+ lines.append(f" [yellow]~ {len(changed_files)} changed file(s):[/yellow]")
1390
+ for f in sorted(changed_files)[:10]:
1391
+ lines.append(f" ~ {f}")
1392
+ if len(changed_files) > 10:
1393
+ lines.append(f" ... and {len(changed_files) - 10} more")
1394
+ else:
1395
+ lines.append(f"")
1396
+ lines.append(" [yellow]~ No changed files[/yellow]")
1397
+
1398
+ lines.append("")
1399
+ if new_files or removed_files or changed_files:
1400
+ lines.append("[yellow]Run [bold]ctxgraph-code build --incremental[/bold] to update the graph.[/yellow]")
1401
+ else:
1402
+ lines.append("[green]Graph is up to date with the filesystem.[/green]")
1403
+
1404
+ console.print("\n".join(lines))
1405
+
1406
+
1112
1407
  if __name__ == "__main__":
1113
1408
  app()
1114
1409
 
@@ -144,7 +144,8 @@ def compute_hint_summary(repo_path: Path) -> Optional[str]:
144
144
  )
145
145
 
146
146
  lines.append(
147
- "Use `ctxgraph-code probe \"<question>\"` to search or "
147
+ "Use `ctxgraph-code probe \"<question>\"`, "
148
+ "`ctxgraph-code subgraph \"<task>\"`, or "
148
149
  "`/ctxgraph-code` for help."
149
150
  )
150
151
 
@@ -224,6 +224,42 @@ class Storage:
224
224
  )
225
225
  self.conn.commit()
226
226
 
227
+ def get_nodes_by_file_path(self, file_path: str) -> list[Node]:
228
+ rows = self.conn.execute(
229
+ "SELECT * FROM nodes WHERE path = ?", (file_path,)
230
+ ).fetchall()
231
+ return [
232
+ Node(
233
+ id=r["id"],
234
+ type=r["type"],
235
+ name=r["name"],
236
+ path=r["path"],
237
+ parent_id=r["parent_id"],
238
+ summary=r["summary"],
239
+ importance=r["importance"],
240
+ size_bytes=r["size_bytes"],
241
+ lineno=r["lineno"],
242
+ )
243
+ for r in rows
244
+ ]
245
+
246
+ def get_file_paths(self) -> list[str]:
247
+ rows = self.conn.execute(
248
+ "SELECT DISTINCT path FROM nodes WHERE type = 'file' AND path IS NOT NULL"
249
+ ).fetchall()
250
+ return [r[0] for r in rows]
251
+
252
+ def get_content_hash(self, file_path: str) -> Optional[str]:
253
+ hashes_json = self.get_metadata("content_hashes")
254
+ if not hashes_json:
255
+ return None
256
+ try:
257
+ import json
258
+ stored = json.loads(hashes_json)
259
+ return stored.get(file_path)
260
+ except (json.JSONDecodeError, OSError):
261
+ return None
262
+
227
263
  def save_metadata(self, key: str, value: str):
228
264
  self.conn.execute(
229
265
  "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)",
@@ -0,0 +1,20 @@
1
+ from ctxgraph_code.render._text import (
2
+ render_context,
3
+ render_deps,
4
+ render_overview,
5
+ render_symbols,
6
+ render_treeview,
7
+ render_usedby,
8
+ )
9
+
10
+ from ctxgraph_code.render.mermaid import render_mermaid as render_mermaid
11
+
12
+ __all__ = [
13
+ "render_context",
14
+ "render_deps",
15
+ "render_overview",
16
+ "render_symbols",
17
+ "render_treeview",
18
+ "render_usedby",
19
+ "render_mermaid",
20
+ ]
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from ctxgraph_code.graph.storage import Storage
6
+
7
+
8
+ class MermaidError(Exception):
9
+ pass
10
+
11
+
12
+ def render_mermaid(
13
+ storage: Storage,
14
+ output_type: str = "classDiagram",
15
+ max_nodes: int = 50,
16
+ ) -> str:
17
+ output_type = output_type or "classDiagram"
18
+ supported = {"classDiagram", "flowchart", "sequence"}
19
+ if output_type not in supported:
20
+ raise MermaidError(
21
+ f"Unsupported diagram type '{output_type}'. "
22
+ f"Choose from: {', '.join(sorted(supported))}"
23
+ )
24
+
25
+ all_nodes = storage.get_all_nodes()
26
+ all_edges = storage.get_all_edges()
27
+
28
+ if output_type == "classDiagram":
29
+ return _render_class_diagram(all_nodes, all_edges, max_nodes)
30
+ elif output_type == "flowchart":
31
+ return _render_flowchart(all_nodes, all_edges, max_nodes)
32
+ elif output_type == "sequence":
33
+ return _render_sequence(all_nodes, all_edges, max_nodes)
34
+ return ""
35
+
36
+
37
+ def _safe_id(name: str) -> str:
38
+ safe = "".join(c if c.isalnum() or c == "_" else "_" for c in name)
39
+ if safe and safe[0].isdigit():
40
+ safe = "n" + safe
41
+ return safe or "node"
42
+
43
+
44
+ def _render_class_diagram(nodes: list, edges: list, max_nodes: int) -> str:
45
+ lines = ["classDiagram"]
46
+ added = 0
47
+
48
+ class_nodes = [n for n in nodes if n.type == "class"]
49
+ file_nodes = [n for n in nodes if n.type == "file"]
50
+
51
+ for cls in class_nodes[:max_nodes]:
52
+ nid = _safe_id(cls.name)
53
+ lines.append(f" class {nid} {{")
54
+ if cls.summary:
55
+ lines.append(f" +{cls.summary}")
56
+ lines.append(" }")
57
+ added += 1
58
+ if added >= max_nodes:
59
+ break
60
+
61
+ if added < max_nodes:
62
+ remaining = max_nodes - added
63
+ for f in file_nodes[:remaining]:
64
+ nid = _safe_id(f.name)
65
+ label = f.path or f.name
66
+ lines.append(f" class {nid} {{")
67
+ lines.append(f" +File: {label}")
68
+ if f.summary:
69
+ lines.append(f" +{f.summary}")
70
+ lines.append(" }")
71
+
72
+ inheritance_edges = [e for e in edges if e.relation == "inherits"]
73
+ for e in inheritance_edges:
74
+ src_name = _find_node_name(nodes, e.source_id)
75
+ tgt_name = _find_node_name(nodes, e.target_id)
76
+ if src_name and tgt_name:
77
+ lines.append(f" {_safe_id(src_name)} --|> {_safe_id(tgt_name)}")
78
+
79
+ lines.append("")
80
+ return "\n".join(lines)
81
+
82
+
83
+ def _render_flowchart(nodes: list, edges: list, max_nodes: int) -> str:
84
+ lines = ["flowchart TD"]
85
+ node_map = {n.id: n for n in nodes}
86
+ shown: set[str] = set()
87
+
88
+ sorted_edges = sorted(edges, key=lambda e: e.weight or 1.0, reverse=True)
89
+ for e in sorted_edges[:max_nodes * 2]:
90
+ src = node_map.get(e.source_id)
91
+ tgt = node_map.get(e.target_id)
92
+ if not src or not tgt:
93
+ continue
94
+ for n in (src, tgt):
95
+ if n.id not in shown and len(shown) < max_nodes:
96
+ nid = _safe_id(n.name)
97
+ label = n.path or n.name
98
+ lines.append(f" {nid}[\"{label}\"]")
99
+ shown.add(n.id)
100
+ if src.id in shown and tgt.id in shown:
101
+ src_id = _safe_id(src.name)
102
+ tgt_id = _safe_id(tgt.name)
103
+ rel = e.relation or "link"
104
+ lines.append(f" {src_id} -->|{rel}| {tgt_id}")
105
+
106
+ if not shown and nodes:
107
+ for n in nodes[:max_nodes]:
108
+ nid = _safe_id(n.name)
109
+ label = n.path or n.name
110
+ lines.append(f" {nid}[\"{label}\"]")
111
+
112
+ lines.append("")
113
+ return "\n".join(lines)
114
+
115
+
116
+ def _render_sequence(nodes: list, edges: list, max_nodes: int) -> str:
117
+ lines = ["sequenceDiagram"]
118
+ participants: set[str] = set()
119
+ node_map = {n.id: n for n in nodes}
120
+
121
+ sorted_edges = sorted(edges, key=lambda e: e.weight or 1.0, reverse=True)
122
+ for e in sorted_edges[:max_nodes * 2]:
123
+ src = node_map.get(e.source_id)
124
+ tgt = node_map.get(e.target_id)
125
+ if not src or not tgt:
126
+ continue
127
+ for label, n in [("", src), ("", tgt)]:
128
+ if n.id not in participants:
129
+ safe = _safe_id(n.name)
130
+ display = n.path or n.name
131
+ lines.append(f" participant {safe} as \"{display}\"")
132
+ participants.add(n.id)
133
+ src_safe = _safe_id(src.name)
134
+ tgt_safe = _safe_id(tgt.name)
135
+ rel = e.relation or "calls"
136
+ lines.append(f" {src_safe}->>+{tgt_safe}: {rel}")
137
+
138
+ lines.append("")
139
+ return "\n".join(lines)
140
+
141
+
142
+ def _find_node_name(nodes: list, node_id: str) -> Optional[str]:
143
+ for n in nodes:
144
+ if n.id == node_id:
145
+ return n.name
146
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph-code
3
- Version: 0.5.0
3
+ Version: 0.6.0
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
@@ -30,6 +30,8 @@ Requires-Dist: pytest>=7.0; extra == "dev"
30
30
 
31
31
  ```bash
32
32
  pip install ctxgraph-code
33
+ # For multi-language support (C, Go, Rust, JS, TS, Java, etc.):
34
+ pip install 'ctxgraph-code[full]'
33
35
  cd my-project
34
36
  ctxgraph-code setup
35
37
  ```
@@ -52,10 +54,10 @@ These questions require running multiple `grep` commands or reading dependency c
52
54
  ## Quick Start
53
55
 
54
56
  ```bash
55
- # Install
56
- pip install ctxgraph-code
57
+ # Install (add [full] for multi-language support)
58
+ pip install 'ctxgraph-code[full]'
57
59
 
58
- # Navigate to your Python project
60
+ # Navigate to your project
59
61
  cd my-project
60
62
 
61
63
  # One-command setup: init + build + configure Claude Code
@@ -80,7 +82,14 @@ Interactive walkthrough — prompts for:
80
82
  Does everything in one step:
81
83
  1. Creates `.ctxgraph/config.toml` with your chosen extensions and excludes
82
84
  2. Installs the `/ctxgraph-code` slash command globally (works in every Claude Code session)
83
- 3. Builds the knowledge graph from all matching files
85
+ 3. Builds the knowledge graph from all matching files — shows live per-graph progress:
86
+ ```
87
+ Building 5 graphs with 8 workers...
88
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
89
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
90
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
91
+ Built all 5 graphs in 2.1s
92
+ ```
84
93
 
85
94
  Non-interactive mode:
86
95
  ```bash
@@ -122,6 +131,15 @@ Scans all matching files in the project, runs AST analysis. Extensions are read
122
131
  - `--verbose` / `-v` — show per-file progress
123
132
  - `--no-summary` — skip docstring extraction for faster builds
124
133
 
134
+ Shows live per-graph progress as each completes:
135
+ ```
136
+ Building 5 graphs with 8 workers...
137
+ ✔ src/ (42 files, 156 nodes, 34 edges, 0.8s)
138
+ ✔ api/ (18 files, 73 nodes, 12 edges, 0.4s)
139
+ ✔ tests/ (31 files, 89 nodes, 0 edges, 0.6s)
140
+ Built all 5 graphs in 2.1s
141
+ ```
142
+
125
143
  Stores graphs in `.ctxgraph/graphs/<dir>.db` (per-directory) or `.ctxgraph/graph.db` (combined).
126
144
 
127
145
  > The graph is a **static snapshot**. If code changes, run `ctxgraph-code build` again to refresh. Use `--incremental` to only reprocess changed files.
@@ -225,10 +243,10 @@ ctxgraph-code probe "database connection pool"
225
243
  ctxgraph-code probe "user authentication" --max 3
226
244
  ```
227
245
 
228
- Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first 40 lines of matched files with syntax highlighting.
246
+ Searches the graph for relevant nodes **and reads the actual source code** inline. Claude gets paths + source in one command, saving 1–2 tool calls. Shows the first N lines of matched files with automatic syntax highlighting per language.
229
247
 
230
248
  - `--max` / `-m` — max files to probe (default: 5)
231
- - `--context` / `-c` — lines of context around matches (default: 3)
249
+ - `--context` / `-c` — lines to show per file (default: 40, use 0 for full file)
232
250
 
233
251
  ### `install-hooks`
234
252
 
@@ -317,7 +335,10 @@ This catches the common case where you edit a file after building the graph and
317
335
  | **Multiprocessing** | Combined graphs split files across CPU cores via `multiprocessing.Pool` |
318
336
  | **`--jobs`** | Control parallelism level (default: CPU count) |
319
337
  | **Incremental builds** | `--incremental` caches file mtimes, only reprocesses changed files |
320
- | **Trivial file skip** | `_quick_scan()` pre-checks before AST parse for files with no imports/classes/functions |
338
+ | **Trivial file skip** | `_quick_scan()` pre-checks all files (Python and non-Python) — skips full parse for files with no code |
339
+ | **`follow_symlinks` config** | Respects `follow_symlinks = false` setting to avoid duplicate/broken symlinks |
340
+ | **`max_file_size_mb` config** | Skips files exceeding the configured size limit before reading |
341
+ | **Live build progress** | Per-graph status + timing as each completes during parallel builds |
321
342
  | **Cached excludes** | `lru_cache` on `should_exclude()` during `os.walk` |
322
343
  | **Batch SQLite inserts** | `executemany` instead of per-row `INSERT` statements |
323
344
  | **`--no-summary`** | Skips docstring extraction (fastest rebuilds) |
@@ -389,7 +410,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
389
410
 
390
411
  | Feature | ctxgraph | ctxgraph-code |
391
412
  |---------|----------|---------------|
392
- | CLI commands | 9+ | 16 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks) |
413
+ | CLI commands | 9+ | 17 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info, install-slash, build-status, probe, install-hooks, uninstall-hooks, version) |
393
414
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
394
415
  | Chat sessions | Yes | No |
395
416
  | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
@@ -404,6 +425,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
404
425
 
405
426
  - Python 3.10+
406
427
  - A Claude Code subscription (for the `/ctxgraph-code` slash command — the graph itself works standalone)
428
+ - For multi-language analysis (C, Go, Rust, JS, TS, Java, etc.): `pip install 'ctxgraph-code[full]'` to install tree-sitter support
407
429
 
408
430
  ---
409
431
 
@@ -3,7 +3,6 @@ pyproject.toml
3
3
  src/ctxgraph_code/__init__.py
4
4
  src/ctxgraph_code/__main__.py
5
5
  src/ctxgraph_code/cli.py
6
- src/ctxgraph_code/render.py
7
6
  src/ctxgraph_code.egg-info/PKG-INFO
8
7
  src/ctxgraph_code.egg-info/SOURCES.txt
9
8
  src/ctxgraph_code.egg-info/dependency_links.txt
@@ -31,5 +30,8 @@ src/ctxgraph_code/graph/builder.py
31
30
  src/ctxgraph_code/graph/models.py
32
31
  src/ctxgraph_code/graph/query.py
33
32
  src/ctxgraph_code/graph/storage.py
33
+ src/ctxgraph_code/render/__init__.py
34
+ src/ctxgraph_code/render/_text.py
35
+ src/ctxgraph_code/render/mermaid.py
34
36
  src/ctxgraph_code/view/__init__.py
35
37
  src/ctxgraph_code/view/visualizer.py
File without changes