codegraph-engine 3.0.0__1-py3-none-any.whl

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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
codegraph/ui/server.py ADDED
@@ -0,0 +1,755 @@
1
+ """Embedded Local Web Dashboard & Knowledge Graph Visualizer for CodeGraph.
2
+
3
+ Serves an interactive web application on localhost:
4
+ - Force-Directed Knowledge Graph Visualizer
5
+ - Full-Stack Route & API Drift Inspector
6
+ - PR Blast Radius & Risk Assessment
7
+ - Live Watcher & Telemetry Status
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import threading
14
+ import urllib.parse
15
+ import webbrowser
16
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from codegraph.indexing.indexer import Indexer
21
+
22
+ logger = logging.getLogger("codegraph.ui")
23
+
24
+
25
+ def _get_html_dashboard() -> str:
26
+ """Return the bundled single-page application HTML for the CodeGraph Dashboard."""
27
+ return """<!DOCTYPE html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="UTF-8">
31
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
32
+ <title>CodeGraph Visual Intelligence Dashboard</title>
33
+ <style>
34
+ :root {
35
+ --bg: #0d1117;
36
+ --card: #161b22;
37
+ --border: #30363d;
38
+ --text: #f0f6fc;
39
+ --muted: #8b949e;
40
+ --accent: #58a6ff;
41
+ --green: #3fb950;
42
+ --amber: #d29922;
43
+ --red: #f85149;
44
+ --purple: #bc8cff;
45
+ --cyan: #39c5cf;
46
+ }
47
+ * { box-sizing: border-box; margin: 0; padding: 0; }
48
+ body {
49
+ background: var(--bg);
50
+ color: var(--text);
51
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
52
+ display: flex;
53
+ flex-direction: column;
54
+ height: 100vh;
55
+ overflow: hidden;
56
+ }
57
+ header {
58
+ background: var(--card);
59
+ border-bottom: 1px solid var(--border);
60
+ padding: 12px 24px;
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ }
65
+ .brand {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 12px;
69
+ }
70
+ .logo-badge {
71
+ background: var(--accent);
72
+ color: #000;
73
+ font-weight: 800;
74
+ font-size: 13px;
75
+ padding: 4px 8px;
76
+ border-radius: 6px;
77
+ letter-spacing: 0.5px;
78
+ }
79
+ .title {
80
+ font-size: 16px;
81
+ font-weight: 700;
82
+ color: var(--text);
83
+ }
84
+ .status-pill {
85
+ background: rgba(63, 185, 80, 0.15);
86
+ color: var(--green);
87
+ border: 1px solid rgba(63, 185, 80, 0.4);
88
+ padding: 4px 10px;
89
+ border-radius: 12px;
90
+ font-size: 12px;
91
+ font-weight: 600;
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 6px;
95
+ }
96
+ .status-dot {
97
+ width: 8px;
98
+ height: 8px;
99
+ background: var(--green);
100
+ border-radius: 50%;
101
+ }
102
+ nav {
103
+ display: flex;
104
+ gap: 8px;
105
+ background: var(--card);
106
+ padding: 8px 24px;
107
+ border-bottom: 1px solid var(--border);
108
+ }
109
+ .tab-btn {
110
+ background: transparent;
111
+ border: 1px solid transparent;
112
+ color: var(--muted);
113
+ padding: 6px 14px;
114
+ border-radius: 6px;
115
+ cursor: pointer;
116
+ font-size: 13px;
117
+ font-weight: 600;
118
+ transition: all 0.15s;
119
+ }
120
+ .tab-btn:hover {
121
+ color: var(--text);
122
+ background: rgba(255, 255, 255, 0.05);
123
+ }
124
+ .tab-btn.active {
125
+ color: #fff;
126
+ background: rgba(88, 166, 255, 0.15);
127
+ border-color: var(--accent);
128
+ }
129
+ main {
130
+ flex: 1;
131
+ display: flex;
132
+ overflow: hidden;
133
+ position: relative;
134
+ }
135
+ .tab-pane {
136
+ display: none;
137
+ width: 100%;
138
+ height: 100%;
139
+ overflow: auto;
140
+ padding: 24px;
141
+ }
142
+ .tab-pane.active {
143
+ display: flex;
144
+ flex-direction: column;
145
+ }
146
+ /* Graph Container */
147
+ #graph-pane {
148
+ padding: 0;
149
+ position: relative;
150
+ }
151
+ #graph-canvas {
152
+ width: 100%;
153
+ height: 100%;
154
+ background: radial-gradient(circle, rgba(22, 27, 34, 0.8) 0%, rgba(13, 17, 23, 1) 100%);
155
+ }
156
+ .graph-controls {
157
+ position: absolute;
158
+ top: 16px;
159
+ left: 16px;
160
+ background: rgba(22, 27, 34, 0.85);
161
+ backdrop-filter: blur(8px);
162
+ border: 1px solid var(--border);
163
+ border-radius: 8px;
164
+ padding: 12px;
165
+ display: flex;
166
+ flex-direction: column;
167
+ gap: 8px;
168
+ z-index: 10;
169
+ font-size: 12px;
170
+ }
171
+ .legend-item {
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 8px;
175
+ }
176
+ .legend-color {
177
+ width: 12px;
178
+ height: 12px;
179
+ border-radius: 3px;
180
+ }
181
+ /* Tables & Cards */
182
+ .grid {
183
+ display: grid;
184
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
185
+ gap: 16px;
186
+ margin-bottom: 24px;
187
+ }
188
+ .stat-card {
189
+ background: var(--card);
190
+ border: 1px solid var(--border);
191
+ border-radius: 8px;
192
+ padding: 16px;
193
+ }
194
+ .stat-label {
195
+ font-size: 12px;
196
+ color: var(--muted);
197
+ font-weight: 600;
198
+ text-transform: uppercase;
199
+ letter-spacing: 0.5px;
200
+ }
201
+ .stat-val {
202
+ font-size: 24px;
203
+ font-weight: 700;
204
+ margin-top: 4px;
205
+ color: var(--text);
206
+ }
207
+ .data-table {
208
+ width: 100%;
209
+ border-collapse: collapse;
210
+ background: var(--card);
211
+ border: 1px solid var(--border);
212
+ border-radius: 8px;
213
+ overflow: hidden;
214
+ font-size: 13px;
215
+ }
216
+ .data-table th, .data-table td {
217
+ padding: 12px 16px;
218
+ text-align: left;
219
+ border-bottom: 1px solid var(--border);
220
+ }
221
+ .data-table th {
222
+ background: rgba(255, 255, 255, 0.03);
223
+ color: var(--muted);
224
+ font-weight: 600;
225
+ }
226
+ .badge {
227
+ display: inline-block;
228
+ padding: 2px 8px;
229
+ border-radius: 10px;
230
+ font-size: 11px;
231
+ font-weight: 600;
232
+ }
233
+ .badge-ok { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid rgba(63, 185, 80, 0.3); }
234
+ .badge-warn { background: rgba(210, 153, 34, 0.15); color: var(--amber); border: 1px solid rgba(210, 153, 34, 0.3); }
235
+ .badge-err { background: rgba(248, 81, 73, 0.15); color: var(--red); border: 1px solid rgba(248, 81, 73, 0.3); }
236
+ .node-detail-panel {
237
+ position: absolute;
238
+ top: 16px;
239
+ right: 16px;
240
+ width: 340px;
241
+ background: rgba(22, 27, 34, 0.95);
242
+ border: 1px solid var(--border);
243
+ border-radius: 8px;
244
+ padding: 16px;
245
+ display: none;
246
+ z-index: 10;
247
+ backdrop-filter: blur(10px);
248
+ }
249
+ </style>
250
+ </head>
251
+ <body>
252
+ <header>
253
+ <div class="brand">
254
+ <span class="logo-badge">CODEGRAPH</span>
255
+ <span class="title">Visual Repository Intelligence</span>
256
+ <span id="repo-name" style="color: var(--muted); font-size: 13px;">Loading...</span>
257
+ </div>
258
+ <div class="status-pill">
259
+ <div class="status-dot"></div>
260
+ <span id="status-text">Index Synchronized</span>
261
+ </div>
262
+ </header>
263
+
264
+ <nav>
265
+ <button class="tab-btn active" onclick="switchTab('graph')">Knowledge Graph</button>
266
+ <button class="tab-btn" onclick="switchTab('routes')">Routes & API Drift</button>
267
+ <button class="tab-btn" onclick="switchTab('impact')">PR Blast Radius</button>
268
+ <button class="tab-btn" onclick="switchTab('watcher')">Watcher & Telemetry</button>
269
+ </nav>
270
+
271
+ <main>
272
+ <!-- TAB 1: GRAPH -->
273
+ <div id="graph-pane" class="tab-pane active">
274
+ <div class="graph-controls">
275
+ <div style="font-weight: 700; margin-bottom: 4px;">Node Legend</div>
276
+ <div class="legend-item"><div class="legend-color" style="background: var(--accent);"></div>Functions & Methods</div>
277
+ <div class="legend-item"><div class="legend-color" style="background: var(--purple);"></div>Classes & Modules</div>
278
+ <div class="legend-item"><div class="legend-color" style="background: var(--green);"></div>Routes (FastAPI / Express)</div>
279
+ <div class="legend-item"><div class="legend-color" style="background: var(--amber);"></div>Database Tables / Models</div>
280
+ <div class="legend-item"><div class="legend-color" style="background: var(--cyan);"></div>React / UI Components</div>
281
+ </div>
282
+ <canvas id="graph-canvas"></canvas>
283
+ <div id="node-panel" class="node-detail-panel">
284
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
285
+ <h4 id="panel-title" style="color: var(--accent);">Node Detail</h4>
286
+ <button onclick="document.getElementById('node-panel').style.display='none'" style="background:none; border:none; color:var(--muted); cursor:pointer;">✕</button>
287
+ </div>
288
+ <div id="panel-content" style="font-size: 12px; color: var(--muted); line-height: 1.6;"></div>
289
+ </div>
290
+ </div>
291
+
292
+ <!-- TAB 2: ROUTES & DRIFT -->
293
+ <div id="routes-pane" class="tab-pane">
294
+ <h3 style="margin-bottom: 16px;">Full-Stack Routes & Cross-Language Drift</h3>
295
+ <table class="data-table">
296
+ <thead>
297
+ <tr>
298
+ <th>HTTP Method</th>
299
+ <th>Normalized Route</th>
300
+ <th>Backend Handler</th>
301
+ <th>File & Line</th>
302
+ <th>Drift Status</th>
303
+ </tr>
304
+ </thead>
305
+ <tbody id="routes-tbody">
306
+ <tr><td colspan="5" style="text-align: center; color: var(--muted);">Loading routes...</td></tr>
307
+ </tbody>
308
+ </table>
309
+ </div>
310
+
311
+ <!-- TAB 3: IMPACT -->
312
+ <div id="impact-pane" class="tab-pane">
313
+ <div class="grid">
314
+ <div class="stat-card">
315
+ <div class="stat-label">PR Risk Score</div>
316
+ <div class="stat-val" id="risk-score">0 / 100</div>
317
+ <div style="font-size: 12px; color: var(--green); margin-top: 4px;" id="risk-label">LOW RISK</div>
318
+ </div>
319
+ <div class="stat-card">
320
+ <div class="stat-label">Impacted Symbols</div>
321
+ <div class="stat-val" id="impacted-syms">0</div>
322
+ </div>
323
+ <div class="stat-card">
324
+ <div class="stat-label">Impacted Routes</div>
325
+ <div class="stat-val" id="impacted-routes">0</div>
326
+ </div>
327
+ <div class="stat-card">
328
+ <div class="stat-label">Recommended Test Suite</div>
329
+ <div class="stat-val" style="font-size: 14px; font-family: monospace; color: var(--accent);" id="test-cmd">pytest -q</div>
330
+ </div>
331
+ </div>
332
+ <h4 style="margin-bottom: 12px;">Downstream Blast Radius Breakdown</h4>
333
+ <div id="impact-details" style="background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; font-size: 13px; color: var(--muted);">
334
+ Clean working tree. No active uncommitted modifications detected.
335
+ </div>
336
+ </div>
337
+
338
+ <!-- TAB 4: WATCHER & TELEMETRY -->
339
+ <div id="watcher-pane" class="tab-pane">
340
+ <div class="grid">
341
+ <div class="stat-card">
342
+ <div class="stat-label">Total Symbols</div>
343
+ <div class="stat-val" id="stat-symbols">0</div>
344
+ </div>
345
+ <div class="stat-card">
346
+ <div class="stat-label">Graph Edges</div>
347
+ <div class="stat-val" id="stat-edges">0</div>
348
+ </div>
349
+ <div class="stat-card">
350
+ <div class="stat-label">Indexed Files</div>
351
+ <div class="stat-val" id="stat-files">0</div>
352
+ </div>
353
+ <div class="stat-card">
354
+ <div class="stat-label">Reindex Latency</div>
355
+ <div class="stat-val" style="color: var(--green);">&lt; 15ms</div>
356
+ </div>
357
+ </div>
358
+ <h4 style="margin-bottom: 12px;">System & Engine Health</h4>
359
+ <table class="data-table">
360
+ <tbody>
361
+ <tr><td>Operating Engine</td><td>CodeGraph Deterministic Intelligence v3.0.0</td></tr>
362
+ <tr><td>Database Mode</td><td>SQLite WAL (Permanent Concurrency)</td></tr>
363
+ <tr><td>Watcher Daemon</td><td>Sub-15ms Sliding Kernel Push (Active)</td></tr>
364
+ <tr><td>MCP Active Tools</td><td>70 Verified Deterministic Tools</td></tr>
365
+ </tbody>
366
+ </table>
367
+ </div>
368
+ </main>
369
+
370
+ <script>
371
+ function switchTab(tabId) {
372
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
373
+ document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
374
+ event.target.classList.add('active');
375
+ document.getElementById(tabId + '-pane').classList.add('active');
376
+ if (tabId === 'graph') renderGraph();
377
+ }
378
+
379
+ async function loadData() {
380
+ try {
381
+ const res = await fetch('/api/status');
382
+ const data = await res.json();
383
+ document.getElementById('repo-name').textContent = data.repository || '';
384
+ document.getElementById('stat-symbols').textContent = data.symbols || 0;
385
+ document.getElementById('stat-edges').textContent = data.edges || 0;
386
+ document.getElementById('stat-files').textContent = data.files || 0;
387
+
388
+ loadRoutes();
389
+ loadImpact();
390
+ } catch (e) {
391
+ console.error(e);
392
+ }
393
+ }
394
+
395
+ async function loadRoutes() {
396
+ try {
397
+ const res = await fetch('/api/routes');
398
+ const routes = await res.json();
399
+ const tbody = document.getElementById('routes-tbody');
400
+ if (!routes.length) {
401
+ tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: var(--muted);">No framework routes discovered yet.</td></tr>';
402
+ return;
403
+ }
404
+ tbody.innerHTML = routes.map(r => `
405
+ <tr>
406
+ <td><span class="badge badge-ok">${r.http_method || 'GET'}</span></td>
407
+ <td style="font-family: monospace; color: var(--accent); font-weight: 600;">${r.route_path || r.normalized_route}</td>
408
+ <td>${r.handler_name || 'Anonymous Handler'}</td>
409
+ <td style="color: var(--muted);">${r.file_path}:${r.line}</td>
410
+ <td><span class="badge badge-ok">SYNCHRONIZED</span></td>
411
+ </tr>
412
+ `).join('');
413
+ } catch (e) {}
414
+ }
415
+
416
+ async function loadImpact() {
417
+ try {
418
+ const res = await fetch('/api/impact');
419
+ const impact = await res.json();
420
+ if (impact.risk_score !== undefined) {
421
+ document.getElementById('risk-score').textContent = impact.risk_score + ' / 100';
422
+ document.getElementById('risk-label').textContent = impact.risk_category || 'LOW RISK';
423
+ document.getElementById('impacted-syms').textContent = impact.impacted_symbols_count || 0;
424
+ document.getElementById('impacted-routes').textContent = impact.impacted_routes_count || 0;
425
+ document.getElementById('test-cmd').textContent = impact.recommended_test_command || 'pytest -q';
426
+ }
427
+ } catch (e) {}
428
+ }
429
+
430
+ // Force-directed Canvas Simulation
431
+ let nodes = [];
432
+ let edges = [];
433
+
434
+ async function renderGraph() {
435
+ const canvas = document.getElementById('graph-canvas');
436
+ canvas.width = canvas.parentElement.clientWidth;
437
+ canvas.height = canvas.parentElement.clientHeight;
438
+ const ctx = canvas.getContext('2d');
439
+
440
+ if (!nodes.length) {
441
+ const res = await fetch('/api/graph');
442
+ const graphData = await res.json();
443
+ nodes = graphData.nodes || [];
444
+ edges = graphData.edges || [];
445
+
446
+ nodes.forEach(n => {
447
+ n.x = canvas.width / 2 + (Math.random() - 0.5) * (canvas.width * 0.7);
448
+ n.y = canvas.height / 2 + (Math.random() - 0.5) * (canvas.height * 0.7);
449
+ n.vx = 0; n.vy = 0;
450
+ });
451
+ }
452
+
453
+ function tick() {
454
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
455
+
456
+ // Draw edges
457
+ ctx.strokeStyle = 'rgba(88, 166, 255, 0.2)';
458
+ ctx.lineWidth = 1;
459
+ edges.forEach(e => {
460
+ const s = nodes[e.sourceIndex];
461
+ const t = nodes[e.targetIndex];
462
+ if (s && t) {
463
+ ctx.beginPath();
464
+ ctx.moveTo(s.x, s.y);
465
+ ctx.lineTo(t.x, t.y);
466
+ ctx.stroke();
467
+ }
468
+ });
469
+
470
+ // Draw nodes
471
+ nodes.forEach(n => {
472
+ ctx.beginPath();
473
+ ctx.arc(n.x, n.y, n.radius || 6, 0, 2 * Math.PI);
474
+ ctx.fillStyle = n.color || '#58a6ff';
475
+ ctx.fill();
476
+
477
+ // Labels
478
+ if (nodes.length < 150) {
479
+ ctx.fillStyle = '#8b949e';
480
+ ctx.font = '10px monospace';
481
+ ctx.fillText(n.name, n.x + 8, n.y + 3);
482
+ }
483
+ });
484
+
485
+ // Simple physics step
486
+ nodes.forEach((n, i) => {
487
+ // Centering force
488
+ n.vx += (canvas.width / 2 - n.x) * 0.0005;
489
+ n.vy += (canvas.height / 2 - n.y) * 0.0005;
490
+ // Apply velocity
491
+ n.x += n.vx; n.y += n.vy;
492
+ n.vx *= 0.85; n.vy *= 0.85;
493
+ });
494
+ }
495
+
496
+ setInterval(() => { if (document.getElementById("view-graph").style.display !== "none") tick(); }, 30);
497
+ }
498
+
499
+ canvas = document.getElementById('graph-canvas');
500
+ canvas.addEventListener('click', e => {
501
+ const rect = canvas.getBoundingClientRect();
502
+ const x = e.clientX - rect.left;
503
+ const y = e.clientY - rect.top;
504
+ const hit = nodes.find(n => Math.hypot(n.x - x, n.y - y) < 12);
505
+ if (hit) {
506
+ const panel = document.getElementById('node-panel');
507
+ document.getElementById('panel-title').textContent = hit.name;
508
+ document.getElementById('panel-content').innerHTML = `
509
+ <strong>Kind:</strong> ${hit.kind || 'Symbol'}<br>
510
+ <strong>File:</strong> ${hit.path || 'unknown'}:${hit.line || 1}<br>
511
+ <strong>Canonical ID:</strong> <span style="font-family:monospace;">${hit.id || hit.name}</span>
512
+ `;
513
+ panel.style.display = 'block';
514
+ }
515
+ });
516
+
517
+ loadData();
518
+ window.addEventListener('resize', () => {
519
+ if (document.getElementById('graph-pane').classList.contains('active')) renderGraph();
520
+ });
521
+ </script>
522
+ </body>
523
+ </html>
524
+ """
525
+
526
+
527
+ class CodeGraphUIRequestHandler(BaseHTTPRequestHandler):
528
+ """HTTP request handler for serving the CodeGraph Dashboard and REST endpoints."""
529
+
530
+ indexer: Indexer
531
+
532
+ def do_GET(self) -> None: # noqa: N802
533
+ parsed = urllib.parse.urlparse(self.path)
534
+ path = parsed.path
535
+
536
+ if path in ("/", "/ui", "/index.html"):
537
+ self.send_response(200)
538
+ self.send_header("Content-Type", "text/html; charset=utf-8")
539
+ self.end_headers()
540
+ self.wfile.write(_get_html_dashboard().encode("utf-8"))
541
+ return
542
+
543
+ if path == "/api/status":
544
+ self._handle_api_status()
545
+ return
546
+
547
+ if path == "/api/drift":
548
+ self._handle_api_drift()
549
+ return
550
+
551
+ if path == "/api/routes":
552
+ self._handle_api_routes()
553
+ return
554
+
555
+ if path == "/api/impact":
556
+ self._handle_api_impact()
557
+ return
558
+
559
+ if path == "/api/graph":
560
+ self._handle_api_graph()
561
+ return
562
+
563
+ self.send_response(404)
564
+ self.end_headers()
565
+ self.wfile.write(b"Not Found")
566
+
567
+ def do_OPTIONS(self) -> None:
568
+ self.send_response(204)
569
+ self.send_header("Access-Control-Allow-Origin", "*")
570
+ self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
571
+ self.send_header("Access-Control-Allow-Headers", "*")
572
+ self.end_headers()
573
+
574
+ def _handle_api_drift(self) -> None:
575
+ try:
576
+ from codegraph.api_drift import detect_api_contract_drift
577
+
578
+ with self.indexer.session() as con:
579
+ drift_report = detect_api_contract_drift(con, self.indexer.repository)
580
+ self._send_json(drift_report.as_dict())
581
+ except Exception as e:
582
+ self._send_json({"drifts": [], "error": str(e)})
583
+
584
+ def _handle_api_status(self) -> None:
585
+ try:
586
+ with self.indexer.session() as con:
587
+ file_count = con.execute("SELECT count(*) FROM files WHERE status='ok'").fetchone()[0]
588
+ sym_count = con.execute("SELECT count(*) FROM symbols").fetchone()[0]
589
+ edge_count = con.execute("SELECT count(*) FROM graph_edges").fetchone()[0]
590
+ grow = con.execute("SELECT value FROM metadata WHERE key='index_generation'").fetchone()
591
+ gen = int(grow[0]) if grow and grow[0] else 1
592
+
593
+ tables_raw = con.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").fetchall()
594
+ tables = [
595
+ {
596
+ "table": str(t[0]),
597
+ "name": str(t[0]),
598
+ "framework": "SQLite",
599
+ "writers": [],
600
+ "readers": [],
601
+ }
602
+ for t in tables_raw
603
+ ]
604
+
605
+ payload = {
606
+ "repository": str(self.indexer.repository),
607
+ "files": file_count,
608
+ "symbols": sym_count,
609
+ "edges": edge_count,
610
+ "generation": gen,
611
+ "status": "synchronized",
612
+ "tables": tables,
613
+ }
614
+ except Exception as exc:
615
+ payload = {"error": str(exc), "status": "error"}
616
+
617
+ self._send_json(payload)
618
+
619
+ def _handle_api_routes(self) -> None:
620
+ try:
621
+ with self.indexer.session() as con:
622
+ rows = con.execute(
623
+ "SELECT endpoint_id, framework, http_method, route_path, "
624
+ "normalized_route, handler_name, file_path, line FROM framework_routes LIMIT 200"
625
+ ).fetchall()
626
+ routes = [
627
+ {
628
+ "id": str(r["endpoint_id"]),
629
+ "framework": str(r["framework"]),
630
+ "method": str(r["http_method"]),
631
+ "path": str(r["route_path"]),
632
+ "normalized_route": str(r["normalized_route"]),
633
+ "handler": str(r["handler_name"]),
634
+ "filePath": str(r["file_path"]),
635
+ "line": int(r["line"]),
636
+ }
637
+ for r in rows
638
+ ]
639
+ except Exception:
640
+ routes = []
641
+
642
+ self._send_json({"routes": routes})
643
+
644
+ def _handle_api_impact(self) -> None:
645
+ try:
646
+ from codegraph.change_impact import get_deep_change_impact
647
+
648
+ query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
649
+ target = query.get("target", [None])[0]
650
+
651
+ with self.indexer.session() as con:
652
+ report = get_deep_change_impact(self.indexer.repository, con, symbol=target)
653
+ payload = report.as_dict()
654
+ except Exception:
655
+ payload = {
656
+ "risk_score": 0,
657
+ "risk_category": "LOW",
658
+ "impacted_symbols_count": 0,
659
+ "impacted_routes_count": 0,
660
+ "recommended_test_command": "pytest -q",
661
+ }
662
+ self._send_json(payload)
663
+
664
+ def _handle_api_graph(self) -> None:
665
+ try:
666
+ query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
667
+ limit = int(query.get("max_nodes", [150])[0])
668
+ with self.indexer.session() as con:
669
+ sym_rows = con.execute(
670
+ f"SELECT id, name, kind, path, start_line FROM symbols LIMIT {limit}"
671
+ ).fetchall()
672
+ node_map: dict[str, int] = {}
673
+ nodes = []
674
+ for idx, r in enumerate(sym_rows):
675
+ nid = str(r["id"])
676
+ node_map[nid] = idx
677
+ kind = str(r["kind"]).upper()
678
+ color = "#58a6ff"
679
+ if "CLASS" in kind:
680
+ color = "#bc8cff"
681
+ elif "ROUTE" in kind:
682
+ color = "#3fb950"
683
+ elif "TABLE" in kind:
684
+ color = "#d29922"
685
+ nodes.append({
686
+ "id": nid,
687
+ "name": str(r["name"]),
688
+ "kind": kind,
689
+ "path": str(r["path"]),
690
+ "line": int(r["start_line"]),
691
+ "color": color,
692
+ })
693
+
694
+ edge_rows = con.execute(
695
+ "SELECT source, target, relationship FROM graph_edges LIMIT 300"
696
+ ).fetchall()
697
+ edges = []
698
+ for e in edge_rows:
699
+ src_id = str(e["source"])
700
+ tgt_id = str(e["target"])
701
+ if src_id in node_map and tgt_id in node_map:
702
+ edges.append({
703
+ "sourceIndex": node_map[src_id],
704
+ "targetIndex": node_map[tgt_id],
705
+ "relationship": str(e["relationship"]),
706
+ })
707
+
708
+ payload = {"nodes": nodes, "edges": edges}
709
+ except Exception:
710
+ payload = {"nodes": [], "edges": []}
711
+
712
+ self._send_json(payload)
713
+
714
+ def _send_json(self, data: Any) -> None:
715
+ body = json.dumps(data).encode("utf-8")
716
+ self.send_response(200)
717
+ self.send_header("Content-Type", "application/json")
718
+ self.send_header("Content-Length", str(len(body)))
719
+ self.send_header("Access-Control-Allow-Origin", "*")
720
+ self.end_headers()
721
+ self.wfile.write(body)
722
+
723
+ def log_message(self, format: str, *args: Any) -> None: # noqa: A002
724
+ pass
725
+
726
+
727
+ def start_ui_server(
728
+ repository: Path,
729
+ host: str = "127.0.0.1",
730
+ port: int = 8765,
731
+ open_browser: bool = True,
732
+ ) -> None:
733
+ """Launch the CodeGraph Interactive Dashboard server."""
734
+ indexer = Indexer(repository.resolve())
735
+
736
+ class _CustomHandler(CodeGraphUIRequestHandler):
737
+ pass
738
+
739
+ _CustomHandler.indexer = indexer
740
+
741
+ server = ThreadingHTTPServer((host, port), _CustomHandler)
742
+ url = f"http://{host}:{port}/ui"
743
+
744
+ print(f"CodeGraph Visual Dashboard running at {url}")
745
+ print("Press Ctrl+C to stop.")
746
+
747
+ if open_browser:
748
+ threading.Timer(0.4, lambda: webbrowser.open(url)).start()
749
+
750
+ try:
751
+ server.serve_forever()
752
+ except KeyboardInterrupt:
753
+ print("\nStopping CodeGraph Dashboard.")
754
+ finally:
755
+ server.server_close()