@vibecheckai/cli 3.2.2 → 3.2.4
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.
- package/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,413 +1,413 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML Renderer
|
|
3
|
-
* Generates interactive visualization of the Proof Graph
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
function escapeHtml(str) {
|
|
9
|
-
return String(str || "")
|
|
10
|
-
.replace(/&/g, "&")
|
|
11
|
-
.replace(/</g, "<")
|
|
12
|
-
.replace(/>/g, ">")
|
|
13
|
-
.replace(/"/g, """);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Generate interactive HTML visualization
|
|
18
|
-
*/
|
|
19
|
-
function renderGraphHtml(graph) {
|
|
20
|
-
const nodesJson = JSON.stringify(graph.nodes);
|
|
21
|
-
const edgesJson = JSON.stringify(graph.edges);
|
|
22
|
-
const brokenJson = JSON.stringify(graph.brokenEdges);
|
|
23
|
-
|
|
24
|
-
return `<!DOCTYPE html>
|
|
25
|
-
<html lang="en">
|
|
26
|
-
<head>
|
|
27
|
-
<meta charset="UTF-8">
|
|
28
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
29
|
-
<title>Reality Proof Graph - Vibecheck</title>
|
|
30
|
-
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
31
|
-
<style>
|
|
32
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
33
|
-
body {
|
|
34
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
35
|
-
background: #0d1117;
|
|
36
|
-
color: #c9d1d9;
|
|
37
|
-
overflow: hidden;
|
|
38
|
-
}
|
|
39
|
-
.header {
|
|
40
|
-
position: fixed;
|
|
41
|
-
top: 0;
|
|
42
|
-
left: 0;
|
|
43
|
-
right: 0;
|
|
44
|
-
height: 60px;
|
|
45
|
-
background: #161b22;
|
|
46
|
-
border-bottom: 1px solid #30363d;
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
padding: 0 20px;
|
|
50
|
-
z-index: 100;
|
|
51
|
-
}
|
|
52
|
-
.header h1 {
|
|
53
|
-
font-size: 18px;
|
|
54
|
-
font-weight: 600;
|
|
55
|
-
color: #58a6ff;
|
|
56
|
-
}
|
|
57
|
-
.stats {
|
|
58
|
-
margin-left: auto;
|
|
59
|
-
display: flex;
|
|
60
|
-
gap: 20px;
|
|
61
|
-
font-size: 13px;
|
|
62
|
-
}
|
|
63
|
-
.stat { display: flex; align-items: center; gap: 6px; }
|
|
64
|
-
.stat-dot {
|
|
65
|
-
width: 8px;
|
|
66
|
-
height: 8px;
|
|
67
|
-
border-radius: 50%;
|
|
68
|
-
}
|
|
69
|
-
.stat-dot.green { background: #3fb950; }
|
|
70
|
-
.stat-dot.yellow { background: #d29922; }
|
|
71
|
-
.stat-dot.red { background: #f85149; }
|
|
72
|
-
|
|
73
|
-
.sidebar {
|
|
74
|
-
position: fixed;
|
|
75
|
-
top: 60px;
|
|
76
|
-
right: 0;
|
|
77
|
-
width: 350px;
|
|
78
|
-
bottom: 0;
|
|
79
|
-
background: #161b22;
|
|
80
|
-
border-left: 1px solid #30363d;
|
|
81
|
-
overflow-y: auto;
|
|
82
|
-
z-index: 90;
|
|
83
|
-
}
|
|
84
|
-
.sidebar-section {
|
|
85
|
-
padding: 16px;
|
|
86
|
-
border-bottom: 1px solid #30363d;
|
|
87
|
-
}
|
|
88
|
-
.sidebar-section h2 {
|
|
89
|
-
font-size: 14px;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
margin-bottom: 12px;
|
|
92
|
-
color: #8b949e;
|
|
93
|
-
text-transform: uppercase;
|
|
94
|
-
letter-spacing: 0.5px;
|
|
95
|
-
}
|
|
96
|
-
.broken-edge {
|
|
97
|
-
background: #21262d;
|
|
98
|
-
border-radius: 6px;
|
|
99
|
-
padding: 12px;
|
|
100
|
-
margin-bottom: 8px;
|
|
101
|
-
border-left: 3px solid #f85149;
|
|
102
|
-
}
|
|
103
|
-
.broken-edge.warn { border-left-color: #d29922; }
|
|
104
|
-
.broken-edge-title {
|
|
105
|
-
font-size: 13px;
|
|
106
|
-
font-weight: 500;
|
|
107
|
-
margin-bottom: 4px;
|
|
108
|
-
}
|
|
109
|
-
.broken-edge-reason {
|
|
110
|
-
font-size: 12px;
|
|
111
|
-
color: #8b949e;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.graph-container {
|
|
115
|
-
position: fixed;
|
|
116
|
-
top: 60px;
|
|
117
|
-
left: 0;
|
|
118
|
-
right: 350px;
|
|
119
|
-
bottom: 0;
|
|
120
|
-
}
|
|
121
|
-
svg { width: 100%; height: 100%; }
|
|
122
|
-
|
|
123
|
-
.node {
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
transition: opacity 0.2s;
|
|
126
|
-
}
|
|
127
|
-
.node:hover { opacity: 0.8; }
|
|
128
|
-
.node circle {
|
|
129
|
-
stroke: #30363d;
|
|
130
|
-
stroke-width: 2;
|
|
131
|
-
}
|
|
132
|
-
.node text {
|
|
133
|
-
font-size: 10px;
|
|
134
|
-
fill: #c9d1d9;
|
|
135
|
-
pointer-events: none;
|
|
136
|
-
}
|
|
137
|
-
.node.ui_action circle { fill: #a371f7; }
|
|
138
|
-
.node.client_function circle { fill: #58a6ff; }
|
|
139
|
-
.node.network_call circle { fill: #3fb950; }
|
|
140
|
-
.node.server_route circle { fill: #d29922; }
|
|
141
|
-
.node.handler circle { fill: #f0883e; }
|
|
142
|
-
.node.db_call circle { fill: #f85149; }
|
|
143
|
-
.node.external_call circle { fill: #bc8cff; }
|
|
144
|
-
|
|
145
|
-
.edge {
|
|
146
|
-
fill: none;
|
|
147
|
-
stroke: #30363d;
|
|
148
|
-
stroke-width: 1.5;
|
|
149
|
-
}
|
|
150
|
-
.edge.verified { stroke: #3fb950; }
|
|
151
|
-
.edge.broken { stroke: #f85149; stroke-dasharray: 4; }
|
|
152
|
-
|
|
153
|
-
.tooltip {
|
|
154
|
-
position: fixed;
|
|
155
|
-
background: #21262d;
|
|
156
|
-
border: 1px solid #30363d;
|
|
157
|
-
border-radius: 6px;
|
|
158
|
-
padding: 12px;
|
|
159
|
-
font-size: 12px;
|
|
160
|
-
max-width: 300px;
|
|
161
|
-
pointer-events: none;
|
|
162
|
-
z-index: 200;
|
|
163
|
-
display: none;
|
|
164
|
-
}
|
|
165
|
-
.tooltip.visible { display: block; }
|
|
166
|
-
.tooltip-title {
|
|
167
|
-
font-weight: 600;
|
|
168
|
-
margin-bottom: 8px;
|
|
169
|
-
color: #58a6ff;
|
|
170
|
-
}
|
|
171
|
-
.tooltip-row {
|
|
172
|
-
display: flex;
|
|
173
|
-
margin-bottom: 4px;
|
|
174
|
-
}
|
|
175
|
-
.tooltip-label {
|
|
176
|
-
color: #8b949e;
|
|
177
|
-
width: 60px;
|
|
178
|
-
}
|
|
179
|
-
.tooltip-value {
|
|
180
|
-
flex: 1;
|
|
181
|
-
word-break: break-all;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.legend {
|
|
185
|
-
position: fixed;
|
|
186
|
-
bottom: 20px;
|
|
187
|
-
left: 20px;
|
|
188
|
-
background: #161b22;
|
|
189
|
-
border: 1px solid #30363d;
|
|
190
|
-
border-radius: 6px;
|
|
191
|
-
padding: 12px;
|
|
192
|
-
font-size: 11px;
|
|
193
|
-
z-index: 90;
|
|
194
|
-
}
|
|
195
|
-
.legend-item {
|
|
196
|
-
display: flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
gap: 8px;
|
|
199
|
-
margin-bottom: 6px;
|
|
200
|
-
}
|
|
201
|
-
.legend-dot {
|
|
202
|
-
width: 12px;
|
|
203
|
-
height: 12px;
|
|
204
|
-
border-radius: 50%;
|
|
205
|
-
}
|
|
206
|
-
</style>
|
|
207
|
-
</head>
|
|
208
|
-
<body>
|
|
209
|
-
<div class="header">
|
|
210
|
-
<h1>🔍 Reality Proof Graph</h1>
|
|
211
|
-
<div class="stats">
|
|
212
|
-
<div class="stat">
|
|
213
|
-
<div class="stat-dot green"></div>
|
|
214
|
-
<span id="stat-valid">0</span> valid edges
|
|
215
|
-
</div>
|
|
216
|
-
<div class="stat">
|
|
217
|
-
<div class="stat-dot red"></div>
|
|
218
|
-
<span id="stat-broken">0</span> broken edges
|
|
219
|
-
</div>
|
|
220
|
-
<div class="stat">
|
|
221
|
-
<div class="stat-dot yellow"></div>
|
|
222
|
-
<span id="stat-coverage">0</span>% coverage
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
<div class="graph-container" id="graph"></div>
|
|
228
|
-
|
|
229
|
-
<div class="sidebar">
|
|
230
|
-
<div class="sidebar-section">
|
|
231
|
-
<h2>Broken Edges</h2>
|
|
232
|
-
<div id="broken-list"></div>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<div class="tooltip" id="tooltip"></div>
|
|
237
|
-
|
|
238
|
-
<div class="legend">
|
|
239
|
-
<div class="legend-item"><div class="legend-dot" style="background:#a371f7"></div> UI Action</div>
|
|
240
|
-
<div class="legend-item"><div class="legend-dot" style="background:#58a6ff"></div> Client Function</div>
|
|
241
|
-
<div class="legend-item"><div class="legend-dot" style="background:#3fb950"></div> Network Call</div>
|
|
242
|
-
<div class="legend-item"><div class="legend-dot" style="background:#d29922"></div> Server Route</div>
|
|
243
|
-
<div class="legend-item"><div class="legend-dot" style="background:#f85149"></div> DB Call</div>
|
|
244
|
-
<div class="legend-item"><div class="legend-dot" style="background:#bc8cff"></div> External Call</div>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<script>
|
|
248
|
-
const nodes = ${nodesJson};
|
|
249
|
-
const edges = ${edgesJson};
|
|
250
|
-
const brokenEdges = ${brokenJson};
|
|
251
|
-
|
|
252
|
-
// Update stats
|
|
253
|
-
document.getElementById('stat-valid').textContent = edges.length;
|
|
254
|
-
document.getElementById('stat-broken').textContent = brokenEdges.length;
|
|
255
|
-
const total = edges.length + brokenEdges.length;
|
|
256
|
-
const coverage = total > 0 ? Math.round((edges.length / total) * 100) : 100;
|
|
257
|
-
document.getElementById('stat-coverage').textContent = coverage;
|
|
258
|
-
|
|
259
|
-
// Render broken edges list
|
|
260
|
-
const brokenList = document.getElementById('broken-list');
|
|
261
|
-
if (brokenEdges.length === 0) {
|
|
262
|
-
brokenList.innerHTML = '<div style="color:#8b949e;font-size:13px">No broken edges found</div>';
|
|
263
|
-
} else {
|
|
264
|
-
brokenList.innerHTML = brokenEdges.map(b => \`
|
|
265
|
-
<div class="broken-edge \${b.severity === 'WARN' ? 'warn' : ''}">
|
|
266
|
-
<div class="broken-edge-title">\${escapeHtml(b.reason)}</div>
|
|
267
|
-
<div class="broken-edge-reason">\${b.route ? b.route.method + ' ' + b.route.path : b.edge.from}</div>
|
|
268
|
-
</div>
|
|
269
|
-
\`).join('');
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function escapeHtml(str) {
|
|
273
|
-
const div = document.createElement('div');
|
|
274
|
-
div.textContent = str;
|
|
275
|
-
return div.innerHTML;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// D3 Graph
|
|
279
|
-
const container = document.getElementById('graph');
|
|
280
|
-
const width = container.clientWidth;
|
|
281
|
-
const height = container.clientHeight;
|
|
282
|
-
|
|
283
|
-
const svg = d3.select('#graph')
|
|
284
|
-
.append('svg')
|
|
285
|
-
.attr('width', width)
|
|
286
|
-
.attr('height', height);
|
|
287
|
-
|
|
288
|
-
const g = svg.append('g');
|
|
289
|
-
|
|
290
|
-
// Zoom
|
|
291
|
-
const zoom = d3.zoom()
|
|
292
|
-
.scaleExtent([0.1, 4])
|
|
293
|
-
.on('zoom', (event) => g.attr('transform', event.transform));
|
|
294
|
-
svg.call(zoom);
|
|
295
|
-
|
|
296
|
-
// Build node map
|
|
297
|
-
const nodeMap = new Map(nodes.map(n => [n.id, n]));
|
|
298
|
-
|
|
299
|
-
// Filter edges to only those with valid nodes
|
|
300
|
-
const validEdges = edges.filter(e => nodeMap.has(e.from) && nodeMap.has(e.to));
|
|
301
|
-
|
|
302
|
-
// Simulation
|
|
303
|
-
const simulation = d3.forceSimulation(nodes)
|
|
304
|
-
.force('link', d3.forceLink(validEdges).id(d => d.id).distance(100))
|
|
305
|
-
.force('charge', d3.forceManyBody().strength(-300))
|
|
306
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
307
|
-
.force('collision', d3.forceCollide().radius(30));
|
|
308
|
-
|
|
309
|
-
// Edges
|
|
310
|
-
const link = g.append('g')
|
|
311
|
-
.selectAll('line')
|
|
312
|
-
.data(validEdges)
|
|
313
|
-
.join('line')
|
|
314
|
-
.attr('class', d => 'edge' + (d.verifiedAt === 'runtime' ? ' verified' : ''));
|
|
315
|
-
|
|
316
|
-
// Nodes
|
|
317
|
-
const node = g.append('g')
|
|
318
|
-
.selectAll('g')
|
|
319
|
-
.data(nodes)
|
|
320
|
-
.join('g')
|
|
321
|
-
.attr('class', d => 'node ' + d.type)
|
|
322
|
-
.call(d3.drag()
|
|
323
|
-
.on('start', dragstarted)
|
|
324
|
-
.on('drag', dragged)
|
|
325
|
-
.on('end', dragended));
|
|
326
|
-
|
|
327
|
-
node.append('circle')
|
|
328
|
-
.attr('r', 12);
|
|
329
|
-
|
|
330
|
-
node.append('text')
|
|
331
|
-
.attr('dy', 25)
|
|
332
|
-
.attr('text-anchor', 'middle')
|
|
333
|
-
.text(d => d.type.split('_').map(w => w[0]).join('').toUpperCase());
|
|
334
|
-
|
|
335
|
-
// Tooltip
|
|
336
|
-
const tooltip = document.getElementById('tooltip');
|
|
337
|
-
|
|
338
|
-
node.on('mouseover', (event, d) => {
|
|
339
|
-
tooltip.innerHTML = \`
|
|
340
|
-
<div class="tooltip-title">\${d.type}</div>
|
|
341
|
-
<div class="tooltip-row"><span class="tooltip-label">File:</span><span class="tooltip-value">\${escapeHtml(d.file)}</span></div>
|
|
342
|
-
<div class="tooltip-row"><span class="tooltip-label">Line:</span><span class="tooltip-value">\${d.line}</span></div>
|
|
343
|
-
\${d.url ? '<div class="tooltip-row"><span class="tooltip-label">URL:</span><span class="tooltip-value">' + escapeHtml(d.url) + '</span></div>' : ''}
|
|
344
|
-
\${d.path ? '<div class="tooltip-row"><span class="tooltip-label">Path:</span><span class="tooltip-value">' + escapeHtml(d.path) + '</span></div>' : ''}
|
|
345
|
-
\`;
|
|
346
|
-
tooltip.style.left = (event.clientX + 10) + 'px';
|
|
347
|
-
tooltip.style.top = (event.clientY + 10) + 'px';
|
|
348
|
-
tooltip.classList.add('visible');
|
|
349
|
-
})
|
|
350
|
-
.on('mouseout', () => tooltip.classList.remove('visible'));
|
|
351
|
-
|
|
352
|
-
simulation.on('tick', () => {
|
|
353
|
-
link
|
|
354
|
-
.attr('x1', d => d.source.x)
|
|
355
|
-
.attr('y1', d => d.source.y)
|
|
356
|
-
.attr('x2', d => d.target.x)
|
|
357
|
-
.attr('y2', d => d.target.y);
|
|
358
|
-
|
|
359
|
-
node.attr('transform', d => \`translate(\${d.x},\${d.y})\`);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
function dragstarted(event) {
|
|
363
|
-
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
364
|
-
event.subject.fx = event.subject.x;
|
|
365
|
-
event.subject.fy = event.subject.y;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function dragged(event) {
|
|
369
|
-
event.subject.fx = event.x;
|
|
370
|
-
event.subject.fy = event.y;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function dragended(event) {
|
|
374
|
-
if (!event.active) simulation.alphaTarget(0);
|
|
375
|
-
event.subject.fx = null;
|
|
376
|
-
event.subject.fy = null;
|
|
377
|
-
}
|
|
378
|
-
</script>
|
|
379
|
-
</body>
|
|
380
|
-
</html>`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Render Mermaid diagram for graph
|
|
385
|
-
*/
|
|
386
|
-
function renderGraphMermaid(graph) {
|
|
387
|
-
const lines = ["graph TD"];
|
|
388
|
-
|
|
389
|
-
// Add nodes
|
|
390
|
-
for (const node of graph.nodes) {
|
|
391
|
-
const label = `${node.type}\\n${node.file.split("/").pop()}:${node.line}`;
|
|
392
|
-
lines.push(` ${node.id}["${label}"]`);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Add edges
|
|
396
|
-
for (const edge of graph.edges) {
|
|
397
|
-
const style = edge.verifiedAt === "runtime" ? "==>" : "-->";
|
|
398
|
-
lines.push(` ${edge.from} ${style} ${edge.to}`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Add broken edges with different style
|
|
402
|
-
for (const broken of graph.brokenEdges) {
|
|
403
|
-
lines.push(` ${broken.edge.from} -.-> ${broken.edge.to}`);
|
|
404
|
-
lines.push(` style ${broken.edge.to} fill:#f85149`);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return lines.join("\n");
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
module.exports = {
|
|
411
|
-
renderGraphHtml,
|
|
412
|
-
renderGraphMermaid
|
|
413
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* HTML Renderer
|
|
3
|
+
* Generates interactive visualization of the Proof Graph
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
function escapeHtml(str) {
|
|
9
|
+
return String(str || "")
|
|
10
|
+
.replace(/&/g, "&")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">")
|
|
13
|
+
.replace(/"/g, """);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate interactive HTML visualization
|
|
18
|
+
*/
|
|
19
|
+
function renderGraphHtml(graph) {
|
|
20
|
+
const nodesJson = JSON.stringify(graph.nodes);
|
|
21
|
+
const edgesJson = JSON.stringify(graph.edges);
|
|
22
|
+
const brokenJson = JSON.stringify(graph.brokenEdges);
|
|
23
|
+
|
|
24
|
+
return `<!DOCTYPE html>
|
|
25
|
+
<html lang="en">
|
|
26
|
+
<head>
|
|
27
|
+
<meta charset="UTF-8">
|
|
28
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
29
|
+
<title>Reality Proof Graph - Vibecheck</title>
|
|
30
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
31
|
+
<style>
|
|
32
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
33
|
+
body {
|
|
34
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
35
|
+
background: #0d1117;
|
|
36
|
+
color: #c9d1d9;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
.header {
|
|
40
|
+
position: fixed;
|
|
41
|
+
top: 0;
|
|
42
|
+
left: 0;
|
|
43
|
+
right: 0;
|
|
44
|
+
height: 60px;
|
|
45
|
+
background: #161b22;
|
|
46
|
+
border-bottom: 1px solid #30363d;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 0 20px;
|
|
50
|
+
z-index: 100;
|
|
51
|
+
}
|
|
52
|
+
.header h1 {
|
|
53
|
+
font-size: 18px;
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
color: #58a6ff;
|
|
56
|
+
}
|
|
57
|
+
.stats {
|
|
58
|
+
margin-left: auto;
|
|
59
|
+
display: flex;
|
|
60
|
+
gap: 20px;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
}
|
|
63
|
+
.stat { display: flex; align-items: center; gap: 6px; }
|
|
64
|
+
.stat-dot {
|
|
65
|
+
width: 8px;
|
|
66
|
+
height: 8px;
|
|
67
|
+
border-radius: 50%;
|
|
68
|
+
}
|
|
69
|
+
.stat-dot.green { background: #3fb950; }
|
|
70
|
+
.stat-dot.yellow { background: #d29922; }
|
|
71
|
+
.stat-dot.red { background: #f85149; }
|
|
72
|
+
|
|
73
|
+
.sidebar {
|
|
74
|
+
position: fixed;
|
|
75
|
+
top: 60px;
|
|
76
|
+
right: 0;
|
|
77
|
+
width: 350px;
|
|
78
|
+
bottom: 0;
|
|
79
|
+
background: #161b22;
|
|
80
|
+
border-left: 1px solid #30363d;
|
|
81
|
+
overflow-y: auto;
|
|
82
|
+
z-index: 90;
|
|
83
|
+
}
|
|
84
|
+
.sidebar-section {
|
|
85
|
+
padding: 16px;
|
|
86
|
+
border-bottom: 1px solid #30363d;
|
|
87
|
+
}
|
|
88
|
+
.sidebar-section h2 {
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
margin-bottom: 12px;
|
|
92
|
+
color: #8b949e;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.5px;
|
|
95
|
+
}
|
|
96
|
+
.broken-edge {
|
|
97
|
+
background: #21262d;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
padding: 12px;
|
|
100
|
+
margin-bottom: 8px;
|
|
101
|
+
border-left: 3px solid #f85149;
|
|
102
|
+
}
|
|
103
|
+
.broken-edge.warn { border-left-color: #d29922; }
|
|
104
|
+
.broken-edge-title {
|
|
105
|
+
font-size: 13px;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
margin-bottom: 4px;
|
|
108
|
+
}
|
|
109
|
+
.broken-edge-reason {
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
color: #8b949e;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.graph-container {
|
|
115
|
+
position: fixed;
|
|
116
|
+
top: 60px;
|
|
117
|
+
left: 0;
|
|
118
|
+
right: 350px;
|
|
119
|
+
bottom: 0;
|
|
120
|
+
}
|
|
121
|
+
svg { width: 100%; height: 100%; }
|
|
122
|
+
|
|
123
|
+
.node {
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
transition: opacity 0.2s;
|
|
126
|
+
}
|
|
127
|
+
.node:hover { opacity: 0.8; }
|
|
128
|
+
.node circle {
|
|
129
|
+
stroke: #30363d;
|
|
130
|
+
stroke-width: 2;
|
|
131
|
+
}
|
|
132
|
+
.node text {
|
|
133
|
+
font-size: 10px;
|
|
134
|
+
fill: #c9d1d9;
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
}
|
|
137
|
+
.node.ui_action circle { fill: #a371f7; }
|
|
138
|
+
.node.client_function circle { fill: #58a6ff; }
|
|
139
|
+
.node.network_call circle { fill: #3fb950; }
|
|
140
|
+
.node.server_route circle { fill: #d29922; }
|
|
141
|
+
.node.handler circle { fill: #f0883e; }
|
|
142
|
+
.node.db_call circle { fill: #f85149; }
|
|
143
|
+
.node.external_call circle { fill: #bc8cff; }
|
|
144
|
+
|
|
145
|
+
.edge {
|
|
146
|
+
fill: none;
|
|
147
|
+
stroke: #30363d;
|
|
148
|
+
stroke-width: 1.5;
|
|
149
|
+
}
|
|
150
|
+
.edge.verified { stroke: #3fb950; }
|
|
151
|
+
.edge.broken { stroke: #f85149; stroke-dasharray: 4; }
|
|
152
|
+
|
|
153
|
+
.tooltip {
|
|
154
|
+
position: fixed;
|
|
155
|
+
background: #21262d;
|
|
156
|
+
border: 1px solid #30363d;
|
|
157
|
+
border-radius: 6px;
|
|
158
|
+
padding: 12px;
|
|
159
|
+
font-size: 12px;
|
|
160
|
+
max-width: 300px;
|
|
161
|
+
pointer-events: none;
|
|
162
|
+
z-index: 200;
|
|
163
|
+
display: none;
|
|
164
|
+
}
|
|
165
|
+
.tooltip.visible { display: block; }
|
|
166
|
+
.tooltip-title {
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
margin-bottom: 8px;
|
|
169
|
+
color: #58a6ff;
|
|
170
|
+
}
|
|
171
|
+
.tooltip-row {
|
|
172
|
+
display: flex;
|
|
173
|
+
margin-bottom: 4px;
|
|
174
|
+
}
|
|
175
|
+
.tooltip-label {
|
|
176
|
+
color: #8b949e;
|
|
177
|
+
width: 60px;
|
|
178
|
+
}
|
|
179
|
+
.tooltip-value {
|
|
180
|
+
flex: 1;
|
|
181
|
+
word-break: break-all;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.legend {
|
|
185
|
+
position: fixed;
|
|
186
|
+
bottom: 20px;
|
|
187
|
+
left: 20px;
|
|
188
|
+
background: #161b22;
|
|
189
|
+
border: 1px solid #30363d;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
padding: 12px;
|
|
192
|
+
font-size: 11px;
|
|
193
|
+
z-index: 90;
|
|
194
|
+
}
|
|
195
|
+
.legend-item {
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: 8px;
|
|
199
|
+
margin-bottom: 6px;
|
|
200
|
+
}
|
|
201
|
+
.legend-dot {
|
|
202
|
+
width: 12px;
|
|
203
|
+
height: 12px;
|
|
204
|
+
border-radius: 50%;
|
|
205
|
+
}
|
|
206
|
+
</style>
|
|
207
|
+
</head>
|
|
208
|
+
<body>
|
|
209
|
+
<div class="header">
|
|
210
|
+
<h1>🔍 Reality Proof Graph</h1>
|
|
211
|
+
<div class="stats">
|
|
212
|
+
<div class="stat">
|
|
213
|
+
<div class="stat-dot green"></div>
|
|
214
|
+
<span id="stat-valid">0</span> valid edges
|
|
215
|
+
</div>
|
|
216
|
+
<div class="stat">
|
|
217
|
+
<div class="stat-dot red"></div>
|
|
218
|
+
<span id="stat-broken">0</span> broken edges
|
|
219
|
+
</div>
|
|
220
|
+
<div class="stat">
|
|
221
|
+
<div class="stat-dot yellow"></div>
|
|
222
|
+
<span id="stat-coverage">0</span>% coverage
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="graph-container" id="graph"></div>
|
|
228
|
+
|
|
229
|
+
<div class="sidebar">
|
|
230
|
+
<div class="sidebar-section">
|
|
231
|
+
<h2>Broken Edges</h2>
|
|
232
|
+
<div id="broken-list"></div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="tooltip" id="tooltip"></div>
|
|
237
|
+
|
|
238
|
+
<div class="legend">
|
|
239
|
+
<div class="legend-item"><div class="legend-dot" style="background:#a371f7"></div> UI Action</div>
|
|
240
|
+
<div class="legend-item"><div class="legend-dot" style="background:#58a6ff"></div> Client Function</div>
|
|
241
|
+
<div class="legend-item"><div class="legend-dot" style="background:#3fb950"></div> Network Call</div>
|
|
242
|
+
<div class="legend-item"><div class="legend-dot" style="background:#d29922"></div> Server Route</div>
|
|
243
|
+
<div class="legend-item"><div class="legend-dot" style="background:#f85149"></div> DB Call</div>
|
|
244
|
+
<div class="legend-item"><div class="legend-dot" style="background:#bc8cff"></div> External Call</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<script>
|
|
248
|
+
const nodes = ${nodesJson};
|
|
249
|
+
const edges = ${edgesJson};
|
|
250
|
+
const brokenEdges = ${brokenJson};
|
|
251
|
+
|
|
252
|
+
// Update stats
|
|
253
|
+
document.getElementById('stat-valid').textContent = edges.length;
|
|
254
|
+
document.getElementById('stat-broken').textContent = brokenEdges.length;
|
|
255
|
+
const total = edges.length + brokenEdges.length;
|
|
256
|
+
const coverage = total > 0 ? Math.round((edges.length / total) * 100) : 100;
|
|
257
|
+
document.getElementById('stat-coverage').textContent = coverage;
|
|
258
|
+
|
|
259
|
+
// Render broken edges list
|
|
260
|
+
const brokenList = document.getElementById('broken-list');
|
|
261
|
+
if (brokenEdges.length === 0) {
|
|
262
|
+
brokenList.innerHTML = '<div style="color:#8b949e;font-size:13px">No broken edges found</div>';
|
|
263
|
+
} else {
|
|
264
|
+
brokenList.innerHTML = brokenEdges.map(b => \`
|
|
265
|
+
<div class="broken-edge \${b.severity === 'WARN' ? 'warn' : ''}">
|
|
266
|
+
<div class="broken-edge-title">\${escapeHtml(b.reason)}</div>
|
|
267
|
+
<div class="broken-edge-reason">\${b.route ? b.route.method + ' ' + b.route.path : b.edge.from}</div>
|
|
268
|
+
</div>
|
|
269
|
+
\`).join('');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function escapeHtml(str) {
|
|
273
|
+
const div = document.createElement('div');
|
|
274
|
+
div.textContent = str;
|
|
275
|
+
return div.innerHTML;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// D3 Graph
|
|
279
|
+
const container = document.getElementById('graph');
|
|
280
|
+
const width = container.clientWidth;
|
|
281
|
+
const height = container.clientHeight;
|
|
282
|
+
|
|
283
|
+
const svg = d3.select('#graph')
|
|
284
|
+
.append('svg')
|
|
285
|
+
.attr('width', width)
|
|
286
|
+
.attr('height', height);
|
|
287
|
+
|
|
288
|
+
const g = svg.append('g');
|
|
289
|
+
|
|
290
|
+
// Zoom
|
|
291
|
+
const zoom = d3.zoom()
|
|
292
|
+
.scaleExtent([0.1, 4])
|
|
293
|
+
.on('zoom', (event) => g.attr('transform', event.transform));
|
|
294
|
+
svg.call(zoom);
|
|
295
|
+
|
|
296
|
+
// Build node map
|
|
297
|
+
const nodeMap = new Map(nodes.map(n => [n.id, n]));
|
|
298
|
+
|
|
299
|
+
// Filter edges to only those with valid nodes
|
|
300
|
+
const validEdges = edges.filter(e => nodeMap.has(e.from) && nodeMap.has(e.to));
|
|
301
|
+
|
|
302
|
+
// Simulation
|
|
303
|
+
const simulation = d3.forceSimulation(nodes)
|
|
304
|
+
.force('link', d3.forceLink(validEdges).id(d => d.id).distance(100))
|
|
305
|
+
.force('charge', d3.forceManyBody().strength(-300))
|
|
306
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
307
|
+
.force('collision', d3.forceCollide().radius(30));
|
|
308
|
+
|
|
309
|
+
// Edges
|
|
310
|
+
const link = g.append('g')
|
|
311
|
+
.selectAll('line')
|
|
312
|
+
.data(validEdges)
|
|
313
|
+
.join('line')
|
|
314
|
+
.attr('class', d => 'edge' + (d.verifiedAt === 'runtime' ? ' verified' : ''));
|
|
315
|
+
|
|
316
|
+
// Nodes
|
|
317
|
+
const node = g.append('g')
|
|
318
|
+
.selectAll('g')
|
|
319
|
+
.data(nodes)
|
|
320
|
+
.join('g')
|
|
321
|
+
.attr('class', d => 'node ' + d.type)
|
|
322
|
+
.call(d3.drag()
|
|
323
|
+
.on('start', dragstarted)
|
|
324
|
+
.on('drag', dragged)
|
|
325
|
+
.on('end', dragended));
|
|
326
|
+
|
|
327
|
+
node.append('circle')
|
|
328
|
+
.attr('r', 12);
|
|
329
|
+
|
|
330
|
+
node.append('text')
|
|
331
|
+
.attr('dy', 25)
|
|
332
|
+
.attr('text-anchor', 'middle')
|
|
333
|
+
.text(d => d.type.split('_').map(w => w[0]).join('').toUpperCase());
|
|
334
|
+
|
|
335
|
+
// Tooltip
|
|
336
|
+
const tooltip = document.getElementById('tooltip');
|
|
337
|
+
|
|
338
|
+
node.on('mouseover', (event, d) => {
|
|
339
|
+
tooltip.innerHTML = \`
|
|
340
|
+
<div class="tooltip-title">\${d.type}</div>
|
|
341
|
+
<div class="tooltip-row"><span class="tooltip-label">File:</span><span class="tooltip-value">\${escapeHtml(d.file)}</span></div>
|
|
342
|
+
<div class="tooltip-row"><span class="tooltip-label">Line:</span><span class="tooltip-value">\${d.line}</span></div>
|
|
343
|
+
\${d.url ? '<div class="tooltip-row"><span class="tooltip-label">URL:</span><span class="tooltip-value">' + escapeHtml(d.url) + '</span></div>' : ''}
|
|
344
|
+
\${d.path ? '<div class="tooltip-row"><span class="tooltip-label">Path:</span><span class="tooltip-value">' + escapeHtml(d.path) + '</span></div>' : ''}
|
|
345
|
+
\`;
|
|
346
|
+
tooltip.style.left = (event.clientX + 10) + 'px';
|
|
347
|
+
tooltip.style.top = (event.clientY + 10) + 'px';
|
|
348
|
+
tooltip.classList.add('visible');
|
|
349
|
+
})
|
|
350
|
+
.on('mouseout', () => tooltip.classList.remove('visible'));
|
|
351
|
+
|
|
352
|
+
simulation.on('tick', () => {
|
|
353
|
+
link
|
|
354
|
+
.attr('x1', d => d.source.x)
|
|
355
|
+
.attr('y1', d => d.source.y)
|
|
356
|
+
.attr('x2', d => d.target.x)
|
|
357
|
+
.attr('y2', d => d.target.y);
|
|
358
|
+
|
|
359
|
+
node.attr('transform', d => \`translate(\${d.x},\${d.y})\`);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
function dragstarted(event) {
|
|
363
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
364
|
+
event.subject.fx = event.subject.x;
|
|
365
|
+
event.subject.fy = event.subject.y;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function dragged(event) {
|
|
369
|
+
event.subject.fx = event.x;
|
|
370
|
+
event.subject.fy = event.y;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function dragended(event) {
|
|
374
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
375
|
+
event.subject.fx = null;
|
|
376
|
+
event.subject.fy = null;
|
|
377
|
+
}
|
|
378
|
+
</script>
|
|
379
|
+
</body>
|
|
380
|
+
</html>`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Render Mermaid diagram for graph
|
|
385
|
+
*/
|
|
386
|
+
function renderGraphMermaid(graph) {
|
|
387
|
+
const lines = ["graph TD"];
|
|
388
|
+
|
|
389
|
+
// Add nodes
|
|
390
|
+
for (const node of graph.nodes) {
|
|
391
|
+
const label = `${node.type}\\n${node.file.split("/").pop()}:${node.line}`;
|
|
392
|
+
lines.push(` ${node.id}["${label}"]`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Add edges
|
|
396
|
+
for (const edge of graph.edges) {
|
|
397
|
+
const style = edge.verifiedAt === "runtime" ? "==>" : "-->";
|
|
398
|
+
lines.push(` ${edge.from} ${style} ${edge.to}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Add broken edges with different style
|
|
402
|
+
for (const broken of graph.brokenEdges) {
|
|
403
|
+
lines.push(` ${broken.edge.from} -.-> ${broken.edge.to}`);
|
|
404
|
+
lines.push(` style ${broken.edge.to} fill:#f85149`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
renderGraphHtml,
|
|
412
|
+
renderGraphMermaid
|
|
413
|
+
};
|