codemesh 0.1.6__tar.gz → 0.1.7__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 (72) hide show
  1. {codemesh-0.1.6 → codemesh-0.1.7}/PKG-INFO +1 -1
  2. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/__init__.py +1 -1
  3. codemesh-0.1.7/codemesh/viz/templates/index.html +369 -0
  4. {codemesh-0.1.6 → codemesh-0.1.7}/pyproject.toml +1 -1
  5. codemesh-0.1.6/codemesh/viz/templates/index.html +0 -359
  6. {codemesh-0.1.6 → codemesh-0.1.7}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  7. {codemesh-0.1.6 → codemesh-0.1.7}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  8. {codemesh-0.1.6 → codemesh-0.1.7}/.github/workflows/ci.yml +0 -0
  9. {codemesh-0.1.6 → codemesh-0.1.7}/.github/workflows/publish.yml +0 -0
  10. {codemesh-0.1.6 → codemesh-0.1.7}/.gitignore +0 -0
  11. {codemesh-0.1.6 → codemesh-0.1.7}/CHANGELOG.md +0 -0
  12. {codemesh-0.1.6 → codemesh-0.1.7}/CONTRIBUTING.md +0 -0
  13. {codemesh-0.1.6 → codemesh-0.1.7}/LICENSE +0 -0
  14. {codemesh-0.1.6 → codemesh-0.1.7}/Makefile +0 -0
  15. {codemesh-0.1.6 → codemesh-0.1.7}/README.md +0 -0
  16. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/__main__.py +0 -0
  17. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/cli/__init__.py +0 -0
  18. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/cli/init.py +0 -0
  19. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/cli/install_cmd.py +0 -0
  20. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/cli/main.py +0 -0
  21. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/context/__init__.py +0 -0
  22. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/context/builder.py +0 -0
  23. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/db/__init__.py +0 -0
  24. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/db/connection.py +0 -0
  25. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/db/queries.py +0 -0
  26. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/db/schema.py +0 -0
  27. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/embedding/__init__.py +0 -0
  28. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/__init__.py +0 -0
  29. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/__init__.py +0 -0
  30. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/c_family.py +0 -0
  31. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/go.py +0 -0
  32. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/java.py +0 -0
  33. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/python.py +0 -0
  34. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/rust.py +0 -0
  35. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/swift.py +0 -0
  36. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/languages/typescript.py +0 -0
  37. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/extraction/orchestrator.py +0 -0
  38. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/graph/__init__.py +0 -0
  39. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/graph/query_manager.py +0 -0
  40. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/graph/traverser.py +0 -0
  41. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/indexer.py +0 -0
  42. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/mcp/__init__.py +0 -0
  43. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/mcp/server.py +0 -0
  44. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/mcp/tools.py +0 -0
  45. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/querier.py +0 -0
  46. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/__init__.py +0 -0
  47. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/frameworks/__init__.py +0 -0
  48. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/frameworks/django.py +0 -0
  49. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/frameworks/fastapi.py +0 -0
  50. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/import_resolver.py +0 -0
  51. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/name_matcher.py +0 -0
  52. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/resolution/resolver.py +0 -0
  53. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/retrieval/__init__.py +0 -0
  54. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/search/__init__.py +0 -0
  55. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/sync/__init__.py +0 -0
  56. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/sync/watcher.py +0 -0
  57. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/types.py +0 -0
  58. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/viz/__init__.py +0 -0
  59. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/viz/graph_builder.py +0 -0
  60. {codemesh-0.1.6 → codemesh-0.1.7}/codemesh/viz/server.py +0 -0
  61. {codemesh-0.1.6 → codemesh-0.1.7}/tests/__init__.py +0 -0
  62. {codemesh-0.1.6 → codemesh-0.1.7}/tests/conftest.py +0 -0
  63. {codemesh-0.1.6 → codemesh-0.1.7}/tests/fixtures/__init__.py +0 -0
  64. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_adversarial.py +0 -0
  65. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_benchmark_repoqa.py +0 -0
  66. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_embedding_e2e.py +0 -0
  67. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_extraction.py +0 -0
  68. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_integration.py +0 -0
  69. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_llm_judge.py +0 -0
  70. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_performance.py +0 -0
  71. {codemesh-0.1.6 → codemesh-0.1.7}/tests/test_viz.py +0 -0
  72. {codemesh-0.1.6 → codemesh-0.1.7}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codemesh
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: BM25 keyword search and graph walk for code intelligence
5
5
  Project-URL: Homepage, https://github.com/gkatte/codemesh
6
6
  Project-URL: Repository, https://github.com/gkatte/codemesh
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.1.6"
5
+ __version__ = "0.1.7"
@@ -0,0 +1,369 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CodeMesh — Graph Visualization</style>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }
10
+
11
+ #cy { width: 100vw; height: 100vh; }
12
+
13
+ /* Toolbar */
14
+ #toolbar { position: absolute; top: 12px; left: 12px; z-index: 10; display: flex; gap: 6px; align-items: center; }
15
+ #toolbar input, #toolbar select { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 6px 10px; border-radius: 6px; font-size: 13px; }
16
+ #toolbar input { width: 200px; }
17
+ #toolbar input:focus { outline: none; border-color: #3b82f6; }
18
+ #toolbar button { background: #3b82f6; border: none; color: #fff; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
19
+ #toolbar button:hover { background: #2563eb; }
20
+ #toolbar button.secondary { background: #1e293b; border: 1px solid #334155; color: #94a3b8; }
21
+ #toolbar button.secondary:hover { background: #334155; color: #e2e8f0; }
22
+
23
+ /* Detail sidebar */
24
+ #detail { position: absolute; top: 12px; right: 12px; width: 320px; background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 14px; display: none; z-index: 10; max-height: 80vh; overflow-y: auto; }
25
+ #detail h3 { font-size: 14px; margin-bottom: 8px; color: #93c5fd; word-break: break-all; }
26
+ #detail .close { position: absolute; top: 8px; right: 12px; cursor: pointer; color: #94a3b8; font-size: 18px; }
27
+ #detail .close:hover { color: #e2e8f0; }
28
+ #detail .field { margin-bottom: 8px; }
29
+ #detail .label { font-size: 10px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
30
+ #detail .value { font-size: 12px; word-break: break-all; }
31
+ #detail code { font-family: monospace; font-size: 11px; background: #0f172a; padding: 8px; border-radius: 4px; white-space: pre-wrap; margin-top: 4px; max-height: 200px; overflow-y: auto; display: block; }
32
+
33
+ /* Stats & Legend */
34
+ #stats { position: absolute; bottom: 12px; left: 12px; z-index: 10; font-size: 11px; color: #64748b; }
35
+ #legend { position: absolute; bottom: 12px; right: 12px; z-index: 10; background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 10px 14px; font-size: 11px; }
36
+ #legend .item { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
37
+ #legend .swatch { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
38
+ #legend .edge-line { width: 14px; height: 2px; flex-shrink: 0; }
39
+
40
+ /* Loading overlay */
41
+ #loading { position: absolute; inset: 0; z-index: 100; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #0f172a; gap: 12px; }
42
+ #loading .spinner { width: 32px; height: 32px; border: 3px solid #1e293b; border-top-color: #3b82f6; border-radius: 50%; animation: spin 0.8s linear infinite; }
43
+ #loading .msg { font-size: 13px; color: #64748b; }
44
+ @keyframes spin { to { transform: rotate(360deg); } }
45
+
46
+ /* Light theme */
47
+ body.light { background: #f8fafc; color: #1e293b; }
48
+ body.light #toolbar input, body.light #toolbar select { background: #fff; border-color: #cbd5e1; color: #1e293b; }
49
+ body.light #detail { background: #fff; border-color: #cbd5e1; }
50
+ body.light #legend { background: #fff; border-color: #cbd5e1; }
51
+ body.light #stats { color: #94a3b8; }
52
+ #detail .section-title { font-size: 11px; color: #94a3b8; font-weight: 600; margin: 10px 0 4px; border-top: 1px solid #334155; padding-top: 8px; }
53
+ #detail .btn-row { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
54
+ #detail .btn-row button { background: #334155; border: 1px solid #475569; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; }
55
+ #detail .btn-row button:hover { background: #475569; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+
60
+ <div id="loading"><div class="spinner"></div><div class="msg">Loading graph…</div></div>
61
+
62
+ <div id="toolbar">
63
+ <input type="text" id="search" placeholder="Search symbols… (Enter or /)" />
64
+ <select id="kindFilter"><option value="">All kinds</option></select>
65
+ <select id="langFilter"><option value="">All languages</option></select>
66
+ <button onclick="refresh()">Go</button>
67
+ <button class="secondary" onclick="reset()">Reset</button>
68
+ <button class="secondary" onclick="fit()">Fit</button>
69
+ <button class="secondary" id="export-btn" onclick="exportGraphSVG()" title="Export SVG">⬇</button>
70
+ </div>
71
+
72
+ <div id="detail">
73
+ <span class="close" onclick="closeDetail()">&times;</span>
74
+ <h3 id="detailName"></h3>
75
+ <div class="field"><div class="label">Kind</div><div class="value" id="detailKind"></div></div>
76
+ <div class="field"><div class="label">Qualified Name</div><div class="value" id="detailQName"></div></div>
77
+ <div class="field"><div class="label">File</div><div class="value" id="detailFile"></div></div>
78
+ <div class="field"><div class="label">Line</div><div class="value" id="detailLine"></div></div>
79
+ <div class="field"><div class="label">Signature</div><code id="detailSig"></code></div>
80
+ <div class="field"><div class="label">Docstring</div><code id="detailDoc"></code></div>
81
+ <div class="btn-row">
82
+ <button onclick="focusNeighbors()">Neighbors</button>
83
+ <button onclick="focusComponent()">Component</button>
84
+ </div>
85
+ </div>
86
+
87
+ <div id="stats"></div>
88
+
89
+ <div id="legend">
90
+ <div class="section-title" style="margin:0 0 6px;border:none;padding:0;color:#94a3b8;font-size:10px;">Node Kinds</div>
91
+ <div class="item"><div class="swatch" style="background:#3b82f6"></div>function</div>
92
+ <div class="item"><div class="swatch" style="background:#22c55e"></div>class</div>
93
+ <div class="item"><div class="swatch" style="background:#8b5cf6"></div>method</div>
94
+ <div class="item"><div class="swatch" style="background:#6b7280"></div>module / file</div>
95
+ <div class="item"><div class="swatch" style="background:#f97316"></div>variable</div>
96
+ <div class="section-title" style="margin:8px 0 4px;color:#94a3b8;font-size:10px;">Edge Kinds</div>
97
+ <div class="item"><div class="edge-line" style="background:#f59e0b"></div>calls</div>
98
+ <div class="item"><div class="edge-line" style="background:#ef4444"></div>imports</div>
99
+ <div class="item"><div class="edge-line" style="background:#a855f7"></div>extends / impl</div>
100
+ </div>
101
+
102
+ <div id="cy"></div>
103
+
104
+ <script src="https://unpkg.com/cytoscape@3.30.0/dist/cytoscape.min.js"></script>
105
+
106
+ <script>
107
+ const NODE_COLORS = { function: '#3b82f6', class: '#22c55e', module: '#6b7280', method: '#8b5cf6', variable: '#f97316', interface: '#06b6d4', type_alias: '#ec4899', enum: '#14b8a6', struct: '#14b8a6', constant: '#f59e0b', decorator: '#a855f7', parameter: '#64748b', property: '#06b6d4', trait: '#14b8a6', file: '#475569', import: '#ef4444', unknown: '#94a3b8' };
108
+
109
+ let cy;
110
+ let allNodes = [];
111
+ let allEdges = [];
112
+
113
+ function hideLoading() {
114
+ const el = document.getElementById('loading');
115
+ if (el) el.style.display = 'none';
116
+ }
117
+
118
+ function showError(msg) {
119
+ hideLoading();
120
+ const el = document.getElementById('loading');
121
+ if (el) {
122
+ el.style.display = 'flex';
123
+ el.innerHTML = `<div style="color:#ef4444;font-size:13px;text-align:center;padding:20px;">⚠ ${msg}</div>`;
124
+ }
125
+ }
126
+
127
+ async function fetchJSON(url) {
128
+ const r = await fetch(url);
129
+ if (!r.ok) throw new Error(`HTTP ${r.status}: ${url}`);
130
+ return r.json();
131
+ }
132
+
133
+ function buildElements(graph) {
134
+ // Filter out file nodes for cleaner visualization, keep them for stats
135
+ const visibleNodes = graph.nodes.filter(n => n.data.kind !== 'file');
136
+ const visibleIds = new Set(visibleNodes.map(n => n.data.id));
137
+ const visibleEdges = graph.edges.filter(e => visibleIds.has(e.data.source) && visibleIds.has(e.data.target));
138
+ // Also filter out "contains" edges which are just structural
139
+ const renderEdges = visibleEdges.filter(e => e.data.kind !== 'contains');
140
+ return {
141
+ nodes: visibleNodes.map(n => ({ data: { ...n.data, id: n.data.id } })),
142
+ edges: renderEdges.map(e => ({ data: { ...e.data, id: e.data.id || (e.data.source + '->' + e.data.target), source: e.data.source, target: e.data.target } })),
143
+ };
144
+ }
145
+
146
+ function initCytoscape(elements) {
147
+ cy = cytoscape({
148
+ container: document.getElementById('cy'),
149
+ elements: elements,
150
+ style: [
151
+ {
152
+ selector: 'node',
153
+ style: {
154
+ 'background-color': ele => NODE_COLORS[ele.data('kind')] || '#94a3b8',
155
+ 'label': 'data(name)',
156
+ 'width': ele => Math.max(16, Math.min(40, (ele.data('degree') || 1) * 4 + 12)),
157
+ 'height': ele => Math.max(16, Math.min(40, (ele.data('degree') || 1) * 4 + 12)),
158
+ 'font-size': 8,
159
+ 'color': '#e2e8f0',
160
+ 'text-valign': 'bottom',
161
+ 'text-halign': 'center',
162
+ 'text-margin-y': 5,
163
+ 'text-background-color': '#1e293b',
164
+ 'text-background-opacity': 0.8,
165
+ 'text-background-padding': 2,
166
+ 'border-width': 0,
167
+ }
168
+ },
169
+ {
170
+ selector: 'edge',
171
+ style: {
172
+ 'width': 0.8,
173
+ 'line-color': '#334155',
174
+ 'target-arrow-color': '#475569',
175
+ 'target-arrow-shape': 'triangle',
176
+ 'curve-style': 'bezier',
177
+ 'arrow-scale': 0.6,
178
+ 'opacity': 0.3,
179
+ }
180
+ },
181
+ {
182
+ selector: 'node:selected',
183
+ style: {
184
+ 'border-width': 2,
185
+ 'border-color': '#3b82f6',
186
+ 'background-color': '#3b82f6',
187
+ }
188
+ }
189
+ ],
190
+ layout: {
191
+ name: 'cose',
192
+ idealEdgeLength: 80,
193
+ nodeOverlap: 20,
194
+ padding: 30,
195
+ animate: true,
196
+ animationDuration: 800,
197
+ randomize: false,
198
+ },
199
+ });
200
+
201
+ // Click node → show detail
202
+ cy.on('tap', 'node', async evt => {
203
+ const nodeId = evt.target.id();
204
+ try {
205
+ const detail = await fetchJSON(`/api/node/${nodeId}`);
206
+ showDetail(detail);
207
+ } catch (e) { /* ignore */ }
208
+ });
209
+
210
+ // Click background → close detail
211
+ cy.on('tap', evt => { if (evt.target === cy) closeDetail(); });
212
+
213
+ // Double-click → focus subgraph
214
+ cy.on('dbltap', 'node', evt => {
215
+ const node = evt.target;
216
+ cy.elements().unselect();
217
+ node.neighborhood().selectAll();
218
+ });
219
+ }
220
+
221
+ async function initGraph() {
222
+ try {
223
+ const graph = await fetchJSON('/api/graph?depth=2');
224
+ hideLoading();
225
+
226
+ allNodes = graph.nodes;
227
+ allEdges = graph.edges;
228
+
229
+ const elements = buildElements(graph);
230
+ initCytoscape(elements);
231
+
232
+ // Populate filter dropdowns
233
+ const kinds = [...new Set(allNodes.map(n => n.data.kind))].sort();
234
+ const langs = [...new Set(allNodes.map(n => n.data.language).filter(Boolean))].sort();
235
+ kinds.forEach(k => { const o = document.createElement('option'); o.value = k; o.textContent = k; document.getElementById('kindFilter').appendChild(o); });
236
+ langs.forEach(l => { const o = document.createElement('option'); o.value = l; o.textContent = l; document.getElementById('langFilter').appendChild(l); });
237
+
238
+ // Stats
239
+ const stats = await fetchJSON('/api/stats');
240
+ document.getElementById('stats').textContent = `${stats.total_nodes.toLocaleString()} nodes · ${stats.total_edges.toLocaleString()} edges`;
241
+
242
+ } catch (e) {
243
+ showError(`Failed to load graph: ${e.message}`);
244
+ }
245
+ }
246
+
247
+ function showDetail(d) {
248
+ document.getElementById('detailName').textContent = d.name;
249
+ document.getElementById('detailKind').textContent = d.kind;
250
+ document.getElementById('detailQName').textContent = d.qualified_name || '—';
251
+ document.getElementById('detailFile').textContent = d.file_path || '—';
252
+ document.getElementById('detailLine').textContent = (d.start_line && d.end_line) ? `L${d.start_line}–L${d.end_line}` : '—';
253
+ document.getElementById('detailSig').textContent = d.signature || '—';
254
+ document.getElementById('detailDoc').textContent = d.docstring || '—';
255
+ document.getElementById('detail').style.display = 'block';
256
+ }
257
+
258
+ function closeDetail() { document.getElementById('detail').style.display = 'none'; }
259
+
260
+ async function refresh() {
261
+ const params = new URLSearchParams();
262
+ const kind = document.getElementById('kindFilter').value;
263
+ const lang = document.getElementById('langFilter').value;
264
+ const search = document.getElementById('search').value.trim();
265
+ if (kind) params.append('kind', kind);
266
+ if (lang) params.append('language', lang);
267
+ if (search) params.append('symbol', search);
268
+ params.append('depth', '3');
269
+
270
+ try {
271
+ const graph = await fetchJSON(`/api/graph?${params}`);
272
+ if (!cy) return;
273
+ cy.elements().remove();
274
+ const elements = buildElements(graph);
275
+ cy.add([...elements.nodes, ...elements.edges]);
276
+ cy.layout({ name: 'cose', idealEdgeLength: 80, nodeOverlap: 20, padding: 30, animate: true, animationDuration: 600 }).run();
277
+ } catch (e) { /* ignore */ }
278
+ }
279
+
280
+ function reset() {
281
+ document.getElementById('kindFilter').value = '';
282
+ document.getElementById('langFilter').value = '';
283
+ document.getElementById('search').value = '';
284
+ refresh();
285
+ }
286
+
287
+ function fit() { if (cy) cy.fit(); }
288
+
289
+ function exportGraphSVG() {
290
+ if (!cy) return;
291
+ const svg = cy.svg({ full: true });
292
+ const blob = new Blob([svg], { type: 'image/svg+xml' });
293
+ const url = URL.createObjectURL(blob);
294
+ const a = document.createElement('a');
295
+ a.href = url; a.download = 'codemesh-graph.svg'; a.click();
296
+ URL.revokeObjectURL(url);
297
+ }
298
+
299
+ function focusNeighbors() {
300
+ if (!cy) return;
301
+ const selected = cy.$(':selected');
302
+ if (!selected.length) return;
303
+ const neighborhood = selected.neighborhood();
304
+ cy.elements().unselect().style('opacity', 0.15);
305
+ neighborhood.select();
306
+ neighborhood.style('opacity', 1);
307
+ cy.fit(neighborhood, 30);
308
+ }
309
+
310
+ function focusComponent() {
311
+ if (!cy) return;
312
+ const selected = cy.$(':selected');
313
+ if (!selected.length) return;
314
+ // BFS to find connected component
315
+ const visited = new Set();
316
+ const queue = [selected[0].id()];
317
+ while (queue.length) {
318
+ const id = queue.pop();
319
+ if (visited.has(id)) continue;
320
+ visited.add(id);
321
+ const n = cy.getElementById(id);
322
+ if (n.length) {
323
+ n.neighborhood('node').forEach(ne => {
324
+ if (!visited.has(ne.id())) queue.push(ne.id());
325
+ });
326
+ }
327
+ }
328
+ const component = cy.nodes().filter(n => visited.has(n.id()));
329
+ cy.elements().unselect().style('opacity', 0.15);
330
+ component.select();
331
+ component.style('opacity', 1);
332
+ cy.fit(component, 30);
333
+ }
334
+
335
+ function highlightNodes(ids) {
336
+ if (!cy || !ids.length) return;
337
+ cy.elements().unselect().style('opacity', 0.15);
338
+ const selected = cy.nodes().filter(n => ids.includes(n.id()));
339
+ selected.select();
340
+ selected.style('opacity', 1);
341
+ const connected = selected.connectedEdges();
342
+ connected.style('opacity', 0.6);
343
+ cy.fit(selected, 50);
344
+ }
345
+
346
+ // Keyboard shortcuts
347
+ document.addEventListener('keydown', e => {
348
+ if (e.key === '/' && document.activeElement.tagName !== 'INPUT') {
349
+ e.preventDefault();
350
+ document.getElementById('search').focus();
351
+ }
352
+ if (e.key === 'Escape') {
353
+ closeDetail();
354
+ document.getElementById('search').blur();
355
+ }
356
+ if (e.key === 'f' && document.activeElement.tagName !== 'INPUT') {
357
+ fit();
358
+ }
359
+ });
360
+
361
+ document.getElementById('search').addEventListener('keydown', e => {
362
+ if (e.key === 'Enter') refresh();
363
+ });
364
+
365
+ // Start
366
+ initGraph();
367
+ </script>
368
+ </body>
369
+ </html>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codemesh"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "BM25 keyword search and graph walk for code intelligence"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,359 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>CodeMesh — Graph Visualization</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }
10
-
11
- /* Layout: graph left, embeddings right */
12
- #container { display: flex; width: 100vw; height: 100vh; }
13
- #graph-panel { flex: 1; position: relative; }
14
- #embed-panel { width: 380px; border-left: 1px solid #1e293b; position: relative; display: flex; flex-direction: column; }
15
- #embed-header { padding: 10px 14px; border-bottom: 1px solid #1e293b; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
16
- #embed-header h2 { font-size: 13px; font-weight: 600; color: #94a3b8; }
17
- #embed-controls { display: flex; gap: 6px; align-items: center; }
18
- #embed-controls button { background: #1e293b; border: 1px solid #334155; color: #94a3b8; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; }
19
- #embed-controls button:hover { background: #334155; color: #e2e8f0; }
20
- #embed-controls button.active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
21
- #embed-chart { flex: 1; min-height: 0; }
22
- #embed-stats { padding: 8px 14px; border-top: 1px solid #1e293b; font-size: 11px; color: #64748b; flex-shrink: 0; }
23
-
24
- #cy { width: 100%; height: 100%; }
25
-
26
- /* Toolbar */
27
- #toolbar { position: absolute; top: 12px; left: 12px; z-index: 10; display: flex; gap: 6px; align-items: center; }
28
- #toolbar input, #toolbar select { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 6px 10px; border-radius: 6px; font-size: 13px; }
29
- #toolbar input { width: 180px; }
30
- #toolbar input:focus { outline: none; border-color: #3b82f6; }
31
- #toolbar button { background: #3b82f6; border: none; color: #fff; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
32
- #toolbar button:hover { background: #2563eb; }
33
- #toolbar button.secondary { background: #1e293b; border: 1px solid #334155; color: #94a3b8; }
34
- #toolbar button.secondary:hover { background: #334155; color: #e2e8f0; }
35
-
36
- /* Detail sidebar */
37
- #detail { position: absolute; top: 12px; right: 12px; width: 300px; background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 14px; display: none; z-index: 10; max-height: 80vh; overflow-y: auto; }
38
- #detail h3 { font-size: 14px; margin-bottom: 8px; color: #93c5fd; }
39
- #detail .close { position: absolute; top: 8px; right: 12px; cursor: pointer; color: #94a3b8; font-size: 18px; }
40
- #detail .close:hover { color: #e2e8f0; }
41
- #detail .field { margin-bottom: 8px; }
42
- #detail .label { font-size: 10px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
43
- #detail .value { font-size: 12px; word-break: break-all; }
44
- #detail .docstring { font-family: monospace; font-size: 11px; background: #0f172a; padding: 8px; border-radius: 4px; white-space: pre-wrap; margin-top: 4px; max-height: 200px; overflow-y: auto; }
45
-
46
- /* Stats & Legend */
47
- #stats { position: absolute; bottom: 12px; left: 12px; z-index: 10; font-size: 11px; color: #64748b; }
48
- #legend { position: absolute; bottom: 12px; right: 12px; z-index: 10; background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 8px 12px; font-size: 11px; }
49
- #legend .item { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
50
- #legend .swatch { width: 10px; height: 10px; border-radius: 2px; }
51
-
52
- /* Theme toggle */
53
- body.light { background: #f8fafc; color: #1e293b; }
54
- body.light #toolbar input, body.light #toolbar select { background: #fff; border-color: #cbd5e1; color: #1e293b; }
55
- body.light #detail { background: #fff; border-color: #cbd5e1; }
56
- body.light #legend { background: #fff; border-color: #cbd5e1; }
57
- body.light #stats { color: #94a3b8; }
58
- body.light #embed-panel { border-left-color: #e2e8f0; }
59
- body.light #embed-header { border-bottom-color: #e2e8f0; }
60
- body.light #embed-stats { border-top-color: #e2e8f0; }
61
-
62
- /* Export button */
63
- #export-btn { position: absolute; top: 12px; right: 12px; z-index: 11; }
64
- </style>
65
- </head>
66
- <body>
67
-
68
- <div id="container">
69
- <div id="graph-panel">
70
- <div id="toolbar">
71
- <input type="text" id="search" placeholder="Search symbols... (Enter)" />
72
- <select id="kindFilter"><option value="">All kinds</option></select>
73
- <select id="langFilter"><option value="">All languages</option></select>
74
- <button onclick="refresh()">Go</button>
75
- <button class="secondary" onclick="reset()">Reset</button>
76
- <button class="secondary" onclick="fit()">Fit</button>
77
- <button class="secondary" id="theme-btn" onclick="toggleTheme()">☀️</button>
78
- </div>
79
-
80
- <div id="detail">
81
- <span class="close" onclick="closeDetail()">&times;</span>
82
- <h3 id="detailName"></h3>
83
- <div class="field"><div class="label">Kind</div><div class="value" id="detailKind"></div></div>
84
- <div class="field"><div class="label">Qualified Name</div><div class="value" id="detailQName"></div></div>
85
- <div class="field"><div class="label">File</div><div class="value" id="detailFile"></div></div>
86
- <div class="field"><div class="label">Line</div><div class="value" id="detailLine"></div></div>
87
- <div class="field"><div class="label">Signature</div><div class="value" id="detailSig" style="font-family:monospace"></div></div>
88
- <div class="field"><div class="label">Docstring</div><div class="docstring" id="detailDoc"></div></div>
89
- </div>
90
-
91
- <div id="stats"></div>
92
-
93
- <div id="legend">
94
- <div class="item"><div class="swatch" style="background:#3b82f6"></div>function</div>
95
- <div class="item"><div class="swatch" style="background:#22c55e"></div>class</div>
96
- <div class="item"><div class="swatch" style="background:#8b5cf6"></div>method</div>
97
- <div class="item"><div class="swatch" style="background:#6b7280"></div>module</div>
98
- <div class="item"><div class="swatch" style="background:#f59e0b"></div>calls</div>
99
- <div class="item"><div class="swatch" style="background:#ef4444"></div>imports</div>
100
- <div class="item"><div class="swatch" style="background:#a855f7"></div>extends</div>
101
- </div>
102
-
103
- <div id="cy"></div>
104
- </div>
105
-
106
- <div id="embed-panel">
107
- <div id="embed-header">
108
- <h2>Embedding Space</h2>
109
- <div id="embed-controls">
110
- <button id="btn-2d" class="active" onclick="setDims(2)">2D</button>
111
- <button id="btn-3d" onclick="setDims(3)">3D</button>
112
- <button onclick="exportEmbedPNG()">📷</button>
113
- </div>
114
- </div>
115
- <div id="embed-chart"></div>
116
- <div id="embed-stats"></div>
117
- </div>
118
- </div>
119
-
120
- <script src="https://unpkg.com/cytoscape@3.30.0/dist/cytoscape.min.js"></script>
121
- <script src="https://unpkg.com/layout-base@2.0.1/layout-base.js"></script>
122
- <script src="https://unpkg.com/cytoscape-cola@2.5.1/cytoscape-cola.js"></script>
123
- <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
124
-
125
- <script>
126
- const NODE_COLORS = { function: '#3b82f6', class: '#22c55e', module: '#6b7280', method: '#8b5cf6', variable: '#f97316', interface: '#06b6d4', type_alias: '#ec4899', enum: '#14b8a6', struct: '#14b8a6', constant: '#f59e0b', decorator: '#a855f7', parameter: '#64748b', property: '#06b6d4', trait: '#14b8a6', file: '#6b7280', import: '#ef4444', unknown: '#94a3b8' };
127
- const EDGE_COLORS = { calls: '#f59e0b', imports: '#ef4444', extends: '#a855f7', contains: '#475569', implements: '#a855f7', references: '#64748b', instantiates: '#8b5cf6', returns: '#22c55e', type_of: '#06b6d4', overrides: '#8b55f7', exports: '#22c55e', decorates: '#a855f7' };
128
-
129
- let cy;
130
- let embedData = [];
131
- let currentDims = 2;
132
- let isDark = true;
133
-
134
- cytoscape.use(window.cytoscapeCola);
135
-
136
- async function init() {
137
- await Promise.all([initGraph(), initEmbeddings()]);
138
- }
139
-
140
- async function initGraph() {
141
- const graph = await fetch('/api/graph').then(r => r.json());
142
- cy = cytoscape({
143
- container: document.getElementById('cy'),
144
- elements: { nodes: graph.nodes, edges: graph.edges },
145
- style: [
146
- { selector: 'node', style: { 'background-color': ele => NODE_COLORS[ele.data('kind')] || '#94a3b8', 'label': 'data(name)', 'width': 24, 'height': 24, 'font-size': 8, 'color': '#e2e8f0', 'text-valign': 'bottom', 'text-halign': 'center', 'text-margin-y': 4, 'text-background-color': '#1e293b', 'text-background-opacity': 0.7, 'text-background-padding': 1 }
147
- },
148
- { selector: 'edge', style: { 'width': 1, 'line-color': ele => EDGE_COLORS[ele.data('kind')] || '#475569', 'target-arrow-color': ele => EDGE_COLORS[ele.data('kind')] || '#475569', 'target-arrow-shape': 'triangle', 'curve-style': 'bezier', 'arrow-scale': 0.7, 'opacity': 0.4 }
149
- }
150
- ],
151
- layout: { name: 'cola', infinite: true, fit: false, padding: 10, nodeSpacing: 30, edgeLength: 100, avoidOverlap: true, randomize: false }
152
- });
153
-
154
- cy.on('tap', 'node', async evt => {
155
- const nodeId = evt.target.id();
156
- const detail = await fetch(`/api/node/${nodeId}`).then(r => r.json());
157
- showDetail(detail);
158
- // Highlight in embedding plot
159
- highlightEmbedNode(nodeId);
160
- });
161
-
162
- cy.on('tap', evt => { if (evt.target === cy) closeDetail(); });
163
- document.getElementById('search').addEventListener('keydown', e => { if (e.key === 'Enter') refresh(); });
164
-
165
- // Populate filter dropdowns
166
- const kinds = [...new Set(graph.nodes.map(n => n.data.kind))].sort();
167
- const langs = [...new Set(graph.nodes.map(n => n.data.language))].sort();
168
- kinds.forEach(k => { const o = document.createElement('option'); o.value = k; o.textContent = k; document.getElementById('kindFilter').appendChild(o); });
169
- langs.forEach(l => { const o = document.createElement('option'); o.value = l; o.textContent = l; document.getElementById('langFilter').appendChild(o); });
170
-
171
- const stats = await fetch('/api/stats').then(r => r.json());
172
- document.getElementById('stats').textContent = `${stats.total_nodes} nodes, ${stats.total_edges} edges`;
173
- }
174
-
175
- async function initEmbeddings() {
176
- try {
177
- const stats = await fetch('/api/embedding-stats').then(r => r.json());
178
- if (stats.embedded_nodes === 0) {
179
- document.getElementById('embed-stats').textContent = 'No embeddings indexed. Run embedding model first.';
180
- document.getElementById('embed-chart').innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#64748b;font-size:12px;">No embedding data</div>';
181
- return;
182
- }
183
- embedData = await fetch(`/api/embeddings?dims=${currentDims}`).then(r => r.json());
184
- document.getElementById('embed-stats').textContent = `${stats.embedded_nodes} embedded / ${stats.total_nodes} total · model: ${stats.model}`;
185
- renderEmbeddings();
186
- } catch (e) {
187
- document.getElementById('embed-stats').textContent = 'Embedding data unavailable';
188
- }
189
- }
190
-
191
- function renderEmbeddings() {
192
- if (!embedData.length) return;
193
-
194
- // Group by kind for traces
195
- const byKind = {};
196
- for (const p of embedData) {
197
- const k = p.kind || 'unknown';
198
- if (!byKind[k]) byKind[k] = [];
199
- byKind[k].push(p);
200
- }
201
-
202
- const colors = NODE_COLORS;
203
- const traces = Object.entries(byKind).map(([kind, points]) => {
204
- const is3d = currentDims === 3 && points[0].z !== undefined;
205
- const base = {
206
- name: kind,
207
- mode: 'markers',
208
- text: points.map(p => `${p.name}<br>${p.file_path}<br>degree: ${p.degree}`),
209
- hoverinfo: 'text',
210
- marker: { size: points.map(p => Math.max(4, Math.min(12, p.degree || 1))), color: colors[kind] || '#94a3b8', opacity: 0.8 },
211
- };
212
- if (is3d) {
213
- return { ...base, type: 'scatter3d', x: points.map(p => p.x), y: points.map(p => p.y), z: points.map(p => p.z) };
214
- }
215
- return { ...base, type: 'scattergl', x: points.map(p => p.x), y: points.map(p => p.y) };
216
- });
217
-
218
- const is3d = currentDims === 3;
219
- const layout = {
220
- paper_bgcolor: isDark ? '#0f172a' : '#f8fafc',
221
- plot_bgcolor: isDark ? '#0f172a' : '#f8fafc',
222
- margin: { l: 0, r: 0, t: 0, b: 0 },
223
- showlegend: true,
224
- legend: { font: { size: 9, color: isDark ? '#94a3b8' : '#64748b' }, itemsizing: 'constant' },
225
- hoverlabel: { bgcolor: isDark ? '#1e293b' : '#fff', font: { size: 11, color: isDark ? '#e2e8f0' : '#1e293b' } },
226
- };
227
-
228
- if (is3d) {
229
- layout.scene = {
230
- xaxis: { showgrid: false, zeroline: false, showticklabels: false, backgroundcolor: isDark ? '#0f172a' : '#f8fafc' },
231
- yaxis: { showgrid: false, zeroline: false, showticklabels: false, backgroundcolor: isDark ? '#0f172a' : '#f8fafc' },
232
- zaxis: { showgrid: false, zeroline: false, showticklabels: false, backgroundcolor: isDark ? '#0f172a' : '#f8fafc' },
233
- camera: { eye: { x: 1.5, y: 1.5, z: 1.5 } },
234
- };
235
- } else {
236
- layout.xaxis = { showgrid: false, zeroline: false, showticklabels: false, gridcolor: isDark ? '#1e293b' : '#e2e8f0' };
237
- layout.yaxis = { showgrid: false, zeroline: false, showticklabels: false, gridcolor: isDark ? '#1e293b' : '#e2e8f0' };
238
- }
239
-
240
- const config = { responsive: true, displayModeBar: false };
241
- Plotly.newPlot('embed-chart', traces, layout, config);
242
-
243
- // Cross-link: click embedding point → highlight in graph
244
- document.getElementById('embed-chart').on('plotly_click', evt => {
245
- const idx = evt.points[0].pointIndex;
246
- const kind = evt.points[0].data.name;
247
- const node = byKind[kind][idx];
248
- if (node && cy) {
249
- const cyNode = cy.getElementById(node.id);
250
- if (cyNode.length) {
251
- cy.elements().unselect();
252
- cyNode.select();
253
- cy.center(cyNode);
254
- fetch(`/api/node/${node.id}`).then(r => r.json()).then(showDetail);
255
- }
256
- }
257
- });
258
- }
259
-
260
- function highlightEmbedNode(nodeId) {
261
- const chart = document.getElementById('embed-chart');
262
- if (!chart.data) return;
263
- // Find which trace and index this node is in
264
- for (let t = 0; t < chart.data.length; t++) {
265
- const trace = chart.data[t];
266
- const idx = embedData.findIndex(d => d.id === nodeId && d.kind === trace.name);
267
- if (idx >= 0) {
268
- Plotly.restyle(chart, { 'marker.opacity': chart.data.map((_, i) => i === t ? 0.4 : 0.1) }, [t]);
269
- setTimeout(() => { Plotly.restyle(chart, { 'marker.opacity': 0.8 }, [t]); }, 1500);
270
- break;
271
- }
272
- }
273
- }
274
-
275
- async function setDims(dims) {
276
- currentDims = dims;
277
- document.getElementById('btn-2d').classList.toggle('active', dims === 2);
278
- document.getElementById('btn-3d').classList.toggle('active', dims === 3);
279
- embedData = await fetch(`/api/embeddings?dims=${dims}`).then(r => r.json());
280
- renderEmbeddings();
281
- }
282
-
283
- function exportEmbedPNG() {
284
- Plotly.downloadImage('embed-chart', { format: 'png', width: 1200, height: 800, filename: 'codemesh-embeddings' });
285
- }
286
-
287
- function exportGraphSVG() {
288
- if (!cy) return;
289
- const svg = cy.svg({ full: true });
290
- const blob = new Blob([svg], { type: 'image/svg+xml' });
291
- const url = URL.createObjectURL(blob);
292
- const a = document.createElement('a');
293
- a.href = url; a.download = 'codemesh-graph.svg'; a.click();
294
- URL.revokeObjectURL(url);
295
- }
296
-
297
- function toggleTheme() {
298
- isDark = !isDark;
299
- document.body.classList.toggle('light', !isDark);
300
- document.getElementById('theme-btn').textContent = isDark ? '☀️' : '🌙';
301
- renderEmbeddings();
302
- }
303
-
304
- function fit() { if (cy) cy.fit(); }
305
-
306
- async function refresh() {
307
- const params = new URLSearchParams();
308
- const kind = document.getElementById('kindFilter').value;
309
- const lang = document.getElementById('langFilter').value;
310
- const search = document.getElementById('search').value.trim();
311
- if (kind) params.append('kind', kind);
312
- if (lang) params.append('language', lang);
313
- if (search) params.append('symbol', search);
314
- params.append('depth', '3');
315
- const graph = await fetch(`/api/graph?${params}`).then(r => r.json());
316
- cy.elements().remove();
317
- cy.add([...graph.nodes, ...graph.edges]);
318
- cy.layout({ name: 'cola', infinite: true, fit: false, padding: 10, nodeSpacing: 30, edgeLength: 100, avoidOverlap: true, randomize: false }).run();
319
- }
320
-
321
- function reset() {
322
- document.getElementById('kindFilter').value = '';
323
- document.getElementById('langFilter').value = '';
324
- document.getElementById('search').value = '';
325
- refresh();
326
- }
327
-
328
- function showDetail(d) {
329
- document.getElementById('detailName').textContent = d.name;
330
- document.getElementById('detailKind').textContent = d.kind;
331
- document.getElementById('detailQName').textContent = d.qualified_name;
332
- document.getElementById('detailFile').textContent = d.file_path;
333
- document.getElementById('detailLine').textContent = `L${d.start_line}–${d.end_line}`;
334
- document.getElementById('detailSig').textContent = d.signature || '—';
335
- document.getElementById('detailDoc').textContent = d.docstring || '—';
336
- document.getElementById('detail').style.display = 'block';
337
- }
338
-
339
- function closeDetail() { document.getElementById('detail').style.display = 'none'; }
340
-
341
- // Keyboard shortcuts
342
- document.addEventListener('keydown', e => {
343
- if (e.key === '/' && document.activeElement.tagName !== 'INPUT') {
344
- e.preventDefault();
345
- document.getElementById('search').focus();
346
- }
347
- if (e.key === 'Escape') {
348
- closeDetail();
349
- document.getElementById('search').blur();
350
- }
351
- if (e.key === 'f' && document.activeElement.tagName !== 'INPUT') {
352
- fit();
353
- }
354
- });
355
-
356
- init();
357
- </script>
358
- </body>
359
- </html>
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