ctxgraph-code 0.1.2__tar.gz → 0.1.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.
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/PKG-INFO +10 -6
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/README.md +9 -5
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/pyproject.toml +1 -1
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/cli.py +34 -10
- ctxgraph_code-0.1.3/src/ctxgraph_code/view/__init__.py +0 -0
- ctxgraph_code-0.1.3/src/ctxgraph_code/view/visualizer.py +288 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/PKG-INFO +10 -6
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/SOURCES.txt +3 -1
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/setup.cfg +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/__main__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/importer.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/semantic.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/symbols.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/init.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/settings.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/exclude/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/exclude/patterns.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/__init__.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/builder.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/models.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/query.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/storage.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/render.py +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/dependency_links.txt +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/entry_points.txt +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/requires.txt +0 -0
- {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/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.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -165,13 +165,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
|
|
|
165
165
|
### `view`
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
|
-
ctxgraph-code view
|
|
169
|
-
ctxgraph-code view --
|
|
168
|
+
ctxgraph-code view # generates interactive D3.js HTML and opens browser
|
|
169
|
+
ctxgraph-code view --no-open # generate HTML without opening browser
|
|
170
|
+
ctxgraph-code view --tree # show text tree instead (useful in terminal)
|
|
171
|
+
ctxgraph-code view --output graph.html # save to custom path
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
|
|
173
175
|
|
|
174
|
-
|
|
176
|
+
The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
|
|
177
|
+
|
|
178
|
+
Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
|
|
175
179
|
|
|
176
180
|
### `info`
|
|
177
181
|
|
|
@@ -286,7 +290,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
|
|
|
286
290
|
| 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) |
|
|
287
291
|
| LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
|
|
288
292
|
| Chat sessions | Yes | No |
|
|
289
|
-
| Visualizer | D3.js HTML + SVG |
|
|
293
|
+
| Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
|
|
290
294
|
| Skills system | Yes (customizable skill TOML files) | No |
|
|
291
295
|
| MCP server | Yes | No |
|
|
292
296
|
| Token savings | Yes (capsule DSL compression) | No |
|
|
@@ -142,13 +142,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
|
|
|
142
142
|
### `view`
|
|
143
143
|
|
|
144
144
|
```bash
|
|
145
|
-
ctxgraph-code view
|
|
146
|
-
ctxgraph-code view --
|
|
145
|
+
ctxgraph-code view # generates interactive D3.js HTML and opens browser
|
|
146
|
+
ctxgraph-code view --no-open # generate HTML without opening browser
|
|
147
|
+
ctxgraph-code view --tree # show text tree instead (useful in terminal)
|
|
148
|
+
ctxgraph-code view --output graph.html # save to custom path
|
|
147
149
|
```
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
|
|
154
|
+
|
|
155
|
+
Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
|
|
152
156
|
|
|
153
157
|
### `info`
|
|
154
158
|
|
|
@@ -263,7 +267,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
|
|
|
263
267
|
| 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) |
|
|
264
268
|
| LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
|
|
265
269
|
| Chat sessions | Yes | No |
|
|
266
|
-
| Visualizer | D3.js HTML + SVG |
|
|
270
|
+
| Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
|
|
267
271
|
| Skills system | Yes (customizable skill TOML files) | No |
|
|
268
272
|
| MCP server | Yes | No |
|
|
269
273
|
| 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.
|
|
7
|
+
version = "0.1.3"
|
|
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,7 +17,6 @@ from ctxgraph_code.render import (
|
|
|
17
17
|
render_deps,
|
|
18
18
|
render_overview,
|
|
19
19
|
render_symbols,
|
|
20
|
-
render_treeview,
|
|
21
20
|
render_usedby,
|
|
22
21
|
)
|
|
23
22
|
|
|
@@ -357,10 +356,16 @@ def view(
|
|
|
357
356
|
None, "--repo", "-r", help="Repository path"
|
|
358
357
|
),
|
|
359
358
|
output: Optional[str] = typer.Option(
|
|
360
|
-
None, "--output", "-o", help="Save
|
|
359
|
+
None, "--output", "-o", help="Save graph HTML to file"
|
|
360
|
+
),
|
|
361
|
+
no_open: bool = typer.Option(
|
|
362
|
+
False, "--no-open", help="Generate HTML but don't open browser"
|
|
363
|
+
),
|
|
364
|
+
tree: bool = typer.Option(
|
|
365
|
+
False, "--tree", help="Show text tree instead of interactive graph"
|
|
361
366
|
),
|
|
362
367
|
):
|
|
363
|
-
"""
|
|
368
|
+
"""Open an interactive D3.js graph in the browser."""
|
|
364
369
|
path = Path(repo_path).resolve() if repo_path else Path.cwd()
|
|
365
370
|
|
|
366
371
|
storage = get_storage(path)
|
|
@@ -368,14 +373,33 @@ def view(
|
|
|
368
373
|
console.print("[red]No graph found. Run [bold]ctxgraph-code build[/bold] first.[/red]")
|
|
369
374
|
raise typer.Exit(1)
|
|
370
375
|
|
|
371
|
-
tree
|
|
376
|
+
if tree:
|
|
377
|
+
from ctxgraph_code.render import render_treeview
|
|
378
|
+
text = render_treeview(storage)
|
|
379
|
+
if output:
|
|
380
|
+
out_path = Path(output)
|
|
381
|
+
out_path.write_text(text, encoding="utf-8")
|
|
382
|
+
console.print(f"Saved tree to [bold]{out_path}[/bold]")
|
|
383
|
+
else:
|
|
384
|
+
console.print(text)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
from ctxgraph_code.view.visualizer import render_view
|
|
388
|
+
html = render_view(storage)
|
|
389
|
+
|
|
390
|
+
graph_dir = path / ".ctxgraph"
|
|
391
|
+
graph_dir.mkdir(parents=True, exist_ok=True)
|
|
392
|
+
out_path = Path(output) if output else (graph_dir / "graph.html")
|
|
393
|
+
out_path.write_text(html, encoding="utf-8")
|
|
394
|
+
|
|
395
|
+
console.print(f"Graph saved to [bold]{out_path}[/bold]")
|
|
372
396
|
|
|
373
|
-
if
|
|
374
|
-
|
|
375
|
-
out_path.
|
|
376
|
-
console.print(
|
|
397
|
+
if not no_open:
|
|
398
|
+
import webbrowser
|
|
399
|
+
webbrowser.open(str(out_path.resolve()))
|
|
400
|
+
console.print("[green]Opened in browser.[/green]")
|
|
377
401
|
else:
|
|
378
|
-
console.print(
|
|
402
|
+
console.print(f"Open {out_path} in a browser to view.")
|
|
379
403
|
|
|
380
404
|
|
|
381
405
|
@app.command()
|
|
@@ -423,7 +447,7 @@ def version():
|
|
|
423
447
|
try:
|
|
424
448
|
ver = _v("ctxgraph-code")
|
|
425
449
|
except Exception:
|
|
426
|
-
ver = "0.1.
|
|
450
|
+
ver = "0.1.3"
|
|
427
451
|
console.print(f"ctxgraph-code version [bold]{ver}[/bold]")
|
|
428
452
|
|
|
429
453
|
|
|
File without changes
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from ctxgraph_code.graph.storage import Storage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def render_view(storage: Storage) -> str:
|
|
9
|
+
nodes = storage.get_all_nodes()
|
|
10
|
+
edges = storage.get_all_edges()
|
|
11
|
+
|
|
12
|
+
file_nodes = [n for n in nodes if n.type == "file"]
|
|
13
|
+
symbol_nodes = [n for n in nodes if n.type != "file"]
|
|
14
|
+
|
|
15
|
+
graph_data: dict = {
|
|
16
|
+
"nodes": [],
|
|
17
|
+
"links": [],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
file_node_ids = {n.id for n in file_nodes}
|
|
21
|
+
|
|
22
|
+
for node in file_nodes:
|
|
23
|
+
graph_data["nodes"].append(
|
|
24
|
+
{
|
|
25
|
+
"id": node.id,
|
|
26
|
+
"label": _short_path(node.path or node.name),
|
|
27
|
+
"type": "file",
|
|
28
|
+
"summary": (node.summary or "")[:100],
|
|
29
|
+
"importance": node.importance,
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
for node in symbol_nodes:
|
|
34
|
+
graph_data["nodes"].append(
|
|
35
|
+
{
|
|
36
|
+
"id": node.id,
|
|
37
|
+
"label": node.name,
|
|
38
|
+
"type": node.type,
|
|
39
|
+
"summary": (node.summary or "")[:100],
|
|
40
|
+
"importance": node.importance,
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
seen_links = set()
|
|
45
|
+
for edge in edges:
|
|
46
|
+
if edge.source_id in file_node_ids or edge.target_id in file_node_ids:
|
|
47
|
+
key = (edge.source_id, edge.target_id)
|
|
48
|
+
if key not in seen_links:
|
|
49
|
+
seen_links.add(key)
|
|
50
|
+
graph_data["links"].append(
|
|
51
|
+
{
|
|
52
|
+
"source": edge.source_id,
|
|
53
|
+
"target": edge.target_id,
|
|
54
|
+
"relation": edge.relation,
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
json_data = json.dumps(graph_data, indent=2)
|
|
59
|
+
template = _get_html_template()
|
|
60
|
+
return template.replace("/* GRAPH_DATA */", json_data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _short_path(path: str) -> str:
|
|
64
|
+
parts = path.split("/")
|
|
65
|
+
if len(parts) > 3:
|
|
66
|
+
return "/".join(parts[:2] + ["..."] + parts[-1:])
|
|
67
|
+
return path
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_html_template() -> str:
|
|
71
|
+
return """<!DOCTYPE html>
|
|
72
|
+
<html lang="en">
|
|
73
|
+
<head>
|
|
74
|
+
<meta charset="UTF-8">
|
|
75
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
76
|
+
<title>ctxgraph-code - Knowledge Graph</title>
|
|
77
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
78
|
+
<style>
|
|
79
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
80
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; overflow: hidden; }
|
|
81
|
+
#container { width: 100vw; height: 100vh; position: relative; }
|
|
82
|
+
svg { width: 100%; height: 100%; }
|
|
83
|
+
#toolbar { position: absolute; top: 16px; left: 16px; z-index: 10; display: flex; gap: 8px; align-items: center; background: #161b22; padding: 12px 16px; border-radius: 8px; border: 1px solid #30363d; }
|
|
84
|
+
#toolbar input { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 6px 12px; border-radius: 4px; width: 220px; font-size: 13px; }
|
|
85
|
+
#toolbar select { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 6px 8px; border-radius: 4px; font-size: 13px; }
|
|
86
|
+
#toolbar label { font-size: 13px; color: #8b949e; }
|
|
87
|
+
#legend { position: absolute; bottom: 16px; left: 16px; z-index: 10; background: #161b22; padding: 12px; border-radius: 8px; border: 1px solid #30363d; font-size: 12px; display: flex; gap: 16px; }
|
|
88
|
+
.legend-item { display: flex; align-items: center; gap: 6px; }
|
|
89
|
+
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
|
90
|
+
#tooltip { position: absolute; z-index: 20; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px; font-size: 13px; max-width: 400px; pointer-events: none; display: none; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
|
|
91
|
+
#tooltip .tt-name { font-weight: 600; color: #58a6ff; margin-bottom: 4px; }
|
|
92
|
+
#tooltip .tt-type { color: #8b949e; font-size: 11px; margin-bottom: 4px; }
|
|
93
|
+
#tooltip .tt-summary { color: #c9d1d9; font-size: 12px; }
|
|
94
|
+
#stats { position: absolute; bottom: 16px; right: 16px; z-index: 10; background: #161b22; padding: 8px 12px; border-radius: 8px; border: 1px solid #30363d; font-size: 11px; color: #8b949e; }
|
|
95
|
+
.link { stroke-opacity: 0.4; }
|
|
96
|
+
.link.imports { stroke: #58a6ff; }
|
|
97
|
+
.link.calls { stroke: #3fb950; }
|
|
98
|
+
.link.defines { stroke: #d29922; }
|
|
99
|
+
.link.extends { stroke: #bc8cff; }
|
|
100
|
+
.node { cursor: pointer; transition: opacity 0.2s; }
|
|
101
|
+
.node:hover { opacity: 0.8; }
|
|
102
|
+
.node-label { font-size: 11px; fill: #8b949e; pointer-events: none; text-shadow: 0 1px 2px #0d1117, 0 -1px 2px #0d1117, 1px 0 2px #0d1117, -1px 0 2px #0d1117; }
|
|
103
|
+
.node.highlighted .node-label { fill: #c9d1d9; font-weight: 600; }
|
|
104
|
+
.link.highlighted { stroke-opacity: 0.8; }
|
|
105
|
+
</style>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div id="container">
|
|
109
|
+
<div id="toolbar">
|
|
110
|
+
<label>Search:</label>
|
|
111
|
+
<input type="text" id="search" placeholder="Search nodes..." oninput="filterGraph(this.value)">
|
|
112
|
+
<label>Filter:</label>
|
|
113
|
+
<select id="filterType" onchange="filterByType(this.value)">
|
|
114
|
+
<option value="all">All</option>
|
|
115
|
+
<option value="file">Files</option>
|
|
116
|
+
<option value="class">Classes</option>
|
|
117
|
+
<option value="function">Functions</option>
|
|
118
|
+
</select>
|
|
119
|
+
</div>
|
|
120
|
+
<div id="legend">
|
|
121
|
+
<div class="legend-item"><div class="legend-dot" style="background:#58a6ff"></div> File</div>
|
|
122
|
+
<div class="legend-item"><div class="legend-dot" style="background:#d29922"></div> Class</div>
|
|
123
|
+
<div class="legend-item"><div class="legend-dot" style="background:#3fb950"></div> Function</div>
|
|
124
|
+
<div class="legend-item"><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="4,2"/></svg> Import</div>
|
|
125
|
+
<div class="legend-item"><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#3fb950" stroke-width="1.5" stroke-dasharray="2,2"/></svg> Call</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div id="stats"></div>
|
|
128
|
+
<div id="tooltip">
|
|
129
|
+
<div class="tt-name"></div>
|
|
130
|
+
<div class="tt-type"></div>
|
|
131
|
+
<div class="tt-summary"></div>
|
|
132
|
+
</div>
|
|
133
|
+
<svg width="100%" height="100%"></svg>
|
|
134
|
+
</div>
|
|
135
|
+
<script>
|
|
136
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
137
|
+
const graphData = /* GRAPH_DATA */;
|
|
138
|
+
const width = window.innerWidth;
|
|
139
|
+
const height = window.innerHeight;
|
|
140
|
+
|
|
141
|
+
const svg = d3.select("svg");
|
|
142
|
+
svg.attr("width", width).attr("height", height);
|
|
143
|
+
svg.attr("viewBox", `0 0 ${width} ${height}`);
|
|
144
|
+
|
|
145
|
+
const g = svg.append("g");
|
|
146
|
+
|
|
147
|
+
const zoom = d3.zoom()
|
|
148
|
+
.scaleExtent([0.1, 4])
|
|
149
|
+
.on("zoom", (event) => g.attr("transform", event.transform));
|
|
150
|
+
|
|
151
|
+
svg.call(zoom);
|
|
152
|
+
|
|
153
|
+
const colorMap = { file: "#58a6ff", class: "#d29922", function: "#3fb950", module: "#bc8cff" };
|
|
154
|
+
|
|
155
|
+
const simulation = d3.forceSimulation(graphData.nodes)
|
|
156
|
+
.force("link", d3.forceLink(graphData.links).id(d => d.id).distance(d => {
|
|
157
|
+
return d.relation === "imports" ? 100 : 80;
|
|
158
|
+
}))
|
|
159
|
+
.force("charge", d3.forceManyBody().strength(-200))
|
|
160
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
161
|
+
.force("collision", d3.forceCollide(20));
|
|
162
|
+
|
|
163
|
+
const link = g.append("g")
|
|
164
|
+
.selectAll("line")
|
|
165
|
+
.data(graphData.links)
|
|
166
|
+
.join("line")
|
|
167
|
+
.attr("class", d => `link ${d.relation}`)
|
|
168
|
+
.attr("stroke-width", 1.5);
|
|
169
|
+
|
|
170
|
+
const node = g.append("g")
|
|
171
|
+
.selectAll("g")
|
|
172
|
+
.data(graphData.nodes)
|
|
173
|
+
.join("g")
|
|
174
|
+
.attr("class", "node")
|
|
175
|
+
.call(d3.drag()
|
|
176
|
+
.on("start", (event, d) => {
|
|
177
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
178
|
+
d.fx = d.x;
|
|
179
|
+
d.fy = d.y;
|
|
180
|
+
})
|
|
181
|
+
.on("drag", (event, d) => {
|
|
182
|
+
d.fx = event.x;
|
|
183
|
+
d.fy = event.y;
|
|
184
|
+
})
|
|
185
|
+
.on("end", (event, d) => {
|
|
186
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
187
|
+
d.fx = null;
|
|
188
|
+
d.fy = null;
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
node.append("circle")
|
|
193
|
+
.attr("r", d => 5 + (d.importance || 0.5) * 8)
|
|
194
|
+
.attr("fill", d => colorMap[d.type] || "#8b949e")
|
|
195
|
+
.attr("stroke", "#161b22")
|
|
196
|
+
.attr("stroke-width", 1.5);
|
|
197
|
+
|
|
198
|
+
node.append("text")
|
|
199
|
+
.attr("class", "node-label")
|
|
200
|
+
.text(d => d.label)
|
|
201
|
+
.attr("dx", d => 10 + (d.importance || 0.5) * 5)
|
|
202
|
+
.attr("dy", 4);
|
|
203
|
+
|
|
204
|
+
node.on("mouseover", function(event, d) {
|
|
205
|
+
const tt = d3.select("#tooltip");
|
|
206
|
+
tt.style("display", "block");
|
|
207
|
+
tt.select(".tt-name").text(d.label);
|
|
208
|
+
tt.select(".tt-type").text(`Type: ${d.type}`);
|
|
209
|
+
tt.select(".tt-summary").text(d.summary || "");
|
|
210
|
+
|
|
211
|
+
const connected = new Set();
|
|
212
|
+
graphData.links.forEach(l => {
|
|
213
|
+
const sid = typeof l.source === 'object' ? l.source.id : l.source;
|
|
214
|
+
const tid = typeof l.target === 'object' ? l.target.id : l.target;
|
|
215
|
+
if (sid === d.id) connected.add(tid);
|
|
216
|
+
if (tid === d.id) connected.add(sid);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
d3.selectAll(".node").each(function(n) {
|
|
220
|
+
if (n.id === d.id || connected.has(n.id)) {
|
|
221
|
+
d3.select(this).classed("highlighted", true);
|
|
222
|
+
d3.select(this).select("circle").attr("opacity", 1);
|
|
223
|
+
d3.select(this).select("text").attr("opacity", 1);
|
|
224
|
+
} else {
|
|
225
|
+
d3.select(this).select("circle").attr("opacity", 0.15);
|
|
226
|
+
d3.select(this).select("text").attr("opacity", 0.15);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
d3.selectAll(".link").each(function(l) {
|
|
231
|
+
const sid = typeof l.source === 'object' ? l.source.id : l.source;
|
|
232
|
+
const tid = typeof l.target === 'object' ? l.target.id : l.target;
|
|
233
|
+
if (sid === d.id || tid === d.id) {
|
|
234
|
+
d3.select(this).classed("highlighted", true);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
})
|
|
238
|
+
.on("mousemove", function(event) {
|
|
239
|
+
d3.select("#tooltip")
|
|
240
|
+
.style("left", (event.pageX + 12) + "px")
|
|
241
|
+
.style("top", (event.pageY - 10) + "px");
|
|
242
|
+
})
|
|
243
|
+
.on("mouseout", function() {
|
|
244
|
+
d3.select("#tooltip").style("display", "none");
|
|
245
|
+
d3.selectAll(".node").each(function(n) {
|
|
246
|
+
d3.select(this).classed("highlighted", false);
|
|
247
|
+
d3.select(this).select("circle").attr("opacity", 1);
|
|
248
|
+
d3.select(this).select("text").attr("opacity", 1);
|
|
249
|
+
});
|
|
250
|
+
d3.selectAll(".link").classed("highlighted", false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
simulation.on("tick", () => {
|
|
254
|
+
link
|
|
255
|
+
.attr("x1", d => d.source.x)
|
|
256
|
+
.attr("y1", d => d.source.y)
|
|
257
|
+
.attr("x2", d => d.target.x)
|
|
258
|
+
.attr("y2", d => d.target.y);
|
|
259
|
+
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
d3.select("#stats").text(`Nodes: ${graphData.nodes.length} | Edges: ${graphData.links.length}`);
|
|
263
|
+
|
|
264
|
+
window.filterGraph = function(query) {
|
|
265
|
+
const q = query.toLowerCase();
|
|
266
|
+
d3.selectAll(".node").each(function(d) {
|
|
267
|
+
const match = !q || d.label.toLowerCase().includes(q) || (d.summary && d.summary.toLowerCase().includes(q));
|
|
268
|
+
d3.select(this).style("display", match ? null : "none");
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
window.filterByType = function(type) {
|
|
273
|
+
d3.selectAll(".node").each(function(d) {
|
|
274
|
+
const match = type === "all" || d.type === type;
|
|
275
|
+
d3.select(this).style("display", match ? null : "none");
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
window.addEventListener("resize", () => {
|
|
280
|
+
const w = window.innerWidth;
|
|
281
|
+
const h = window.innerHeight;
|
|
282
|
+
svg.attr("width", w).attr("height", h);
|
|
283
|
+
svg.attr("viewBox", `0 0 ${w} ${h}`);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
</script>
|
|
287
|
+
</body>
|
|
288
|
+
</html>"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxgraph-code
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -165,13 +165,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
|
|
|
165
165
|
### `view`
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
|
-
ctxgraph-code view
|
|
169
|
-
ctxgraph-code view --
|
|
168
|
+
ctxgraph-code view # generates interactive D3.js HTML and opens browser
|
|
169
|
+
ctxgraph-code view --no-open # generate HTML without opening browser
|
|
170
|
+
ctxgraph-code view --tree # show text tree instead (useful in terminal)
|
|
171
|
+
ctxgraph-code view --output graph.html # save to custom path
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
|
|
173
175
|
|
|
174
|
-
|
|
176
|
+
The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
|
|
177
|
+
|
|
178
|
+
Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
|
|
175
179
|
|
|
176
180
|
### `info`
|
|
177
181
|
|
|
@@ -286,7 +290,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
|
|
|
286
290
|
| 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) |
|
|
287
291
|
| LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
|
|
288
292
|
| Chat sessions | Yes | No |
|
|
289
|
-
| Visualizer | D3.js HTML + SVG |
|
|
293
|
+
| Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
|
|
290
294
|
| Skills system | Yes (customizable skill TOML files) | No |
|
|
291
295
|
| MCP server | Yes | No |
|
|
292
296
|
| Token savings | Yes (capsule DSL compression) | No |
|
|
@@ -24,4 +24,6 @@ src/ctxgraph_code/graph/__init__.py
|
|
|
24
24
|
src/ctxgraph_code/graph/builder.py
|
|
25
25
|
src/ctxgraph_code/graph/models.py
|
|
26
26
|
src/ctxgraph_code/graph/query.py
|
|
27
|
-
src/ctxgraph_code/graph/storage.py
|
|
27
|
+
src/ctxgraph_code/graph/storage.py
|
|
28
|
+
src/ctxgraph_code/view/__init__.py
|
|
29
|
+
src/ctxgraph_code/view/visualizer.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|