code-kg 0.1.0
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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +68 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/codekg-config.d.ts +14 -0
- package/dist/codekg-config.js +81 -0
- package/dist/codekg-config.js.map +1 -0
- package/dist/commands/graph.d.ts +7 -0
- package/dist/commands/graph.js +49 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +13 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/insights.d.ts +7 -0
- package/dist/commands/insights.js +31 -0
- package/dist/commands/insights.js.map +1 -0
- package/dist/commands/path.d.ts +7 -0
- package/dist/commands/path.js +47 -0
- package/dist/commands/path.js.map +1 -0
- package/dist/commands/reindex.d.ts +6 -0
- package/dist/commands/reindex.js +13 -0
- package/dist/commands/reindex.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.js +20 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +8 -0
- package/dist/commands/serve.js +25 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +30 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +602 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/babel-fallback.d.ts +7 -0
- package/dist/indexer/babel-fallback.js +583 -0
- package/dist/indexer/babel-fallback.js.map +1 -0
- package/dist/indexer/file-discovery.d.ts +6 -0
- package/dist/indexer/file-discovery.js +106 -0
- package/dist/indexer/file-discovery.js.map +1 -0
- package/dist/indexer/indexer.d.ts +90 -0
- package/dist/indexer/indexer.js +620 -0
- package/dist/indexer/indexer.js.map +1 -0
- package/dist/indexer/metrics.d.ts +39 -0
- package/dist/indexer/metrics.js +147 -0
- package/dist/indexer/metrics.js.map +1 -0
- package/dist/indexer/structure-detector.d.ts +16 -0
- package/dist/indexer/structure-detector.js +159 -0
- package/dist/indexer/structure-detector.js.map +1 -0
- package/dist/indexer/ts-parser.d.ts +86 -0
- package/dist/indexer/ts-parser.js +437 -0
- package/dist/indexer/ts-parser.js.map +1 -0
- package/dist/model/edge.d.ts +21 -0
- package/dist/model/edge.js +25 -0
- package/dist/model/edge.js.map +1 -0
- package/dist/model/graph.d.ts +10 -0
- package/dist/model/graph.js +20 -0
- package/dist/model/graph.js.map +1 -0
- package/dist/model/node.d.ts +24 -0
- package/dist/model/node.js +22 -0
- package/dist/model/node.js.map +1 -0
- package/dist/query/query-dsl.d.ts +17 -0
- package/dist/query/query-dsl.js +75 -0
- package/dist/query/query-dsl.js.map +1 -0
- package/dist/query/query-engine.d.ts +32 -0
- package/dist/query/query-engine.js +126 -0
- package/dist/query/query-engine.js.map +1 -0
- package/dist/server/api.d.ts +19 -0
- package/dist/server/api.js +29 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/app.d.ts +7 -0
- package/dist/server/app.js +509 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/graph-controller.d.ts +147 -0
- package/dist/server/graph-controller.js +186 -0
- package/dist/server/graph-controller.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.js +19 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/ui.d.ts +1 -0
- package/dist/server/ui.js +1165 -0
- package/dist/server/ui.js.map +1 -0
- package/dist/store/sqlite-store.d.ts +83 -0
- package/dist/store/sqlite-store.js +647 -0
- package/dist/store/sqlite-store.js.map +1 -0
- package/dist/utils/ids.d.ts +2 -0
- package/dist/utils/ids.js +9 -0
- package/dist/utils/ids.js.map +1 -0
- package/dist/utils/path.d.ts +3 -0
- package/dist/utils/path.js +21 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderUiHtml = renderUiHtml;
|
|
4
|
+
function renderUiHtml() {
|
|
5
|
+
return `<!doctype html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<title>Code Knowledge Graph</title>
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700&family=Rajdhani:wght@500;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg-0: #05060f;
|
|
17
|
+
--bg-1: #090b18;
|
|
18
|
+
--panel: #0d1020cc;
|
|
19
|
+
--panel-solid: #0e1224;
|
|
20
|
+
--line: #3af7ff44;
|
|
21
|
+
--line-strong: #3af7ff88;
|
|
22
|
+
--ink: #d5f4ff;
|
|
23
|
+
--muted: #87afc3;
|
|
24
|
+
--cyan: #3af7ff;
|
|
25
|
+
--pink: #ff2de1;
|
|
26
|
+
--lime: #b8ff39;
|
|
27
|
+
--amber: #ffb84d;
|
|
28
|
+
--danger: #ff4d8b;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
* {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
min-height: 100vh;
|
|
38
|
+
color: var(--ink);
|
|
39
|
+
font-family: "Rajdhani", "Segoe UI", sans-serif;
|
|
40
|
+
background:
|
|
41
|
+
radial-gradient(1200px 600px at 12% 8%, #0f2a4d88, transparent 60%),
|
|
42
|
+
radial-gradient(1000px 560px at 88% 0%, #4b124e77, transparent 58%),
|
|
43
|
+
radial-gradient(900px 500px at 50% 100%, #0f5b4f4f, transparent 62%),
|
|
44
|
+
linear-gradient(155deg, var(--bg-0), var(--bg-1));
|
|
45
|
+
overflow-x: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
body::before {
|
|
49
|
+
content: "";
|
|
50
|
+
position: fixed;
|
|
51
|
+
inset: 0;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
opacity: 0.24;
|
|
54
|
+
background-image:
|
|
55
|
+
repeating-linear-gradient(
|
|
56
|
+
to right,
|
|
57
|
+
transparent 0,
|
|
58
|
+
transparent 23px,
|
|
59
|
+
#3af7ff0f 24px,
|
|
60
|
+
transparent 25px
|
|
61
|
+
),
|
|
62
|
+
repeating-linear-gradient(
|
|
63
|
+
to bottom,
|
|
64
|
+
transparent 0,
|
|
65
|
+
transparent 23px,
|
|
66
|
+
#ff2de10f 24px,
|
|
67
|
+
transparent 25px
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.app-shell {
|
|
72
|
+
position: relative;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
max-width: 1680px;
|
|
75
|
+
margin: 0 auto;
|
|
76
|
+
padding: 18px 18px 26px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.stage {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
transform: translateY(8px);
|
|
82
|
+
animation: reveal 520ms cubic-bezier(.18,.84,.29,.99) forwards;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.stage:nth-child(2) { animation-delay: 80ms; }
|
|
86
|
+
.stage:nth-child(3) { animation-delay: 160ms; }
|
|
87
|
+
|
|
88
|
+
.topbar {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: flex-end;
|
|
91
|
+
justify-content: space-between;
|
|
92
|
+
gap: 14px;
|
|
93
|
+
margin-bottom: 14px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.title-block h1 {
|
|
97
|
+
margin: 0;
|
|
98
|
+
font: 700 1.52rem/1.1 "Orbitron", "Rajdhani", sans-serif;
|
|
99
|
+
letter-spacing: 0.08em;
|
|
100
|
+
text-transform: uppercase;
|
|
101
|
+
color: #edfeff;
|
|
102
|
+
text-shadow: 0 0 14px #3af7ff70;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.title-block p {
|
|
106
|
+
margin: 6px 0 0;
|
|
107
|
+
color: var(--muted);
|
|
108
|
+
font-family: "Space Mono", monospace;
|
|
109
|
+
font-size: 0.77rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.status-chip {
|
|
113
|
+
border: 1px solid #3af7ff66;
|
|
114
|
+
background: #0a1a2dcc;
|
|
115
|
+
box-shadow: 0 0 22px #3af7ff26 inset;
|
|
116
|
+
border-radius: 999px;
|
|
117
|
+
padding: 8px 14px;
|
|
118
|
+
font: 700 0.75rem/1 "Space Mono", monospace;
|
|
119
|
+
letter-spacing: 0.04em;
|
|
120
|
+
color: var(--cyan);
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
gap: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.status-chip::before {
|
|
127
|
+
content: "";
|
|
128
|
+
width: 8px;
|
|
129
|
+
height: 8px;
|
|
130
|
+
border-radius: 50%;
|
|
131
|
+
background: currentColor;
|
|
132
|
+
box-shadow: 0 0 12px currentColor;
|
|
133
|
+
animation: pulse 1.7s ease-in-out infinite;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.control-strip {
|
|
137
|
+
display: grid;
|
|
138
|
+
gap: 10px;
|
|
139
|
+
grid-template-columns: 190px minmax(220px, 1fr) auto auto;
|
|
140
|
+
margin-bottom: 12px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.control {
|
|
144
|
+
border: 1px solid var(--line);
|
|
145
|
+
background: var(--panel);
|
|
146
|
+
border-radius: 12px;
|
|
147
|
+
padding: 8px 10px;
|
|
148
|
+
backdrop-filter: blur(7px);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.control span {
|
|
152
|
+
display: block;
|
|
153
|
+
font-size: 0.7rem;
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
letter-spacing: 0.08em;
|
|
156
|
+
color: #7ac8d8;
|
|
157
|
+
margin-bottom: 4px;
|
|
158
|
+
font-family: "Space Mono", monospace;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
select,
|
|
162
|
+
input {
|
|
163
|
+
width: 100%;
|
|
164
|
+
border: 1px solid #3af7ff3d;
|
|
165
|
+
background: #070d19;
|
|
166
|
+
color: var(--ink);
|
|
167
|
+
border-radius: 8px;
|
|
168
|
+
height: 34px;
|
|
169
|
+
padding: 0 10px;
|
|
170
|
+
font: 600 0.95rem "Rajdhani", sans-serif;
|
|
171
|
+
outline: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
select:focus,
|
|
175
|
+
input:focus {
|
|
176
|
+
border-color: #3af7ff99;
|
|
177
|
+
box-shadow: 0 0 0 2px #3af7ff22;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.neon-btn {
|
|
181
|
+
height: 100%;
|
|
182
|
+
min-height: 52px;
|
|
183
|
+
border: 1px solid #3af7ff6d;
|
|
184
|
+
border-radius: 12px;
|
|
185
|
+
padding: 0 16px;
|
|
186
|
+
background: linear-gradient(130deg, #10243b, #1a0f39);
|
|
187
|
+
color: #ddf9ff;
|
|
188
|
+
font: 700 0.8rem "Space Mono", monospace;
|
|
189
|
+
text-transform: uppercase;
|
|
190
|
+
letter-spacing: 0.08em;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease;
|
|
193
|
+
box-shadow: 0 0 16px #3af7ff24;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.neon-btn:hover {
|
|
197
|
+
transform: translateY(-1px);
|
|
198
|
+
border-color: #3af7ffb8;
|
|
199
|
+
box-shadow: 0 0 18px #3af7ff66;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.workspace {
|
|
203
|
+
display: grid;
|
|
204
|
+
gap: 12px;
|
|
205
|
+
grid-template-columns: minmax(360px, 1fr) 380px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.card {
|
|
209
|
+
border: 1px solid var(--line);
|
|
210
|
+
border-radius: 16px;
|
|
211
|
+
background: var(--panel);
|
|
212
|
+
backdrop-filter: blur(8px);
|
|
213
|
+
box-shadow: inset 0 0 36px #0ff1ff0d, 0 16px 46px #02050f8f;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.graph-panel {
|
|
217
|
+
min-height: 760px;
|
|
218
|
+
display: grid;
|
|
219
|
+
grid-template-rows: auto 1fr auto;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.panel-head {
|
|
224
|
+
border-bottom: 1px solid var(--line);
|
|
225
|
+
padding: 12px 14px 10px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.panel-head h2 {
|
|
229
|
+
margin: 0;
|
|
230
|
+
font: 700 0.96rem "Orbitron", "Rajdhani", sans-serif;
|
|
231
|
+
letter-spacing: 0.06em;
|
|
232
|
+
text-transform: uppercase;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.legend {
|
|
236
|
+
margin-top: 9px;
|
|
237
|
+
display: flex;
|
|
238
|
+
flex-wrap: wrap;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.legend-pill {
|
|
243
|
+
border: 1px solid #ffffff1e;
|
|
244
|
+
border-radius: 999px;
|
|
245
|
+
padding: 4px 9px;
|
|
246
|
+
font: 700 0.66rem "Space Mono", monospace;
|
|
247
|
+
letter-spacing: 0.03em;
|
|
248
|
+
color: #d4ecf3;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.legend-pill.cyan { border-color: #3af7ff66; color: #9ff8ff; }
|
|
252
|
+
.legend-pill.pink { border-color: #ff2de166; color: #ff9ff1; }
|
|
253
|
+
.legend-pill.lime { border-color: #b8ff3966; color: #ddff95; }
|
|
254
|
+
|
|
255
|
+
.graph-wrap {
|
|
256
|
+
position: relative;
|
|
257
|
+
min-height: 500px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#graphCanvas {
|
|
261
|
+
position: absolute;
|
|
262
|
+
inset: 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.graph-empty {
|
|
266
|
+
position: absolute;
|
|
267
|
+
inset: 0;
|
|
268
|
+
display: grid;
|
|
269
|
+
place-content: center;
|
|
270
|
+
padding: 14px;
|
|
271
|
+
text-align: center;
|
|
272
|
+
color: var(--muted);
|
|
273
|
+
font: 700 0.9rem "Space Mono", monospace;
|
|
274
|
+
background: linear-gradient(to bottom, transparent, #06091495);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.panel-foot {
|
|
278
|
+
border-top: 1px solid var(--line);
|
|
279
|
+
padding: 10px 14px;
|
|
280
|
+
display: flex;
|
|
281
|
+
justify-content: space-between;
|
|
282
|
+
align-items: center;
|
|
283
|
+
font: 700 0.67rem "Space Mono", monospace;
|
|
284
|
+
color: #7ab6cb;
|
|
285
|
+
letter-spacing: 0.03em;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.side-rail {
|
|
289
|
+
display: grid;
|
|
290
|
+
grid-template-rows: repeat(6, minmax(120px, auto));
|
|
291
|
+
gap: 10px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.info-card {
|
|
295
|
+
padding: 10px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.info-card h3 {
|
|
299
|
+
margin: 0 0 8px;
|
|
300
|
+
font: 700 0.74rem "Orbitron", sans-serif;
|
|
301
|
+
letter-spacing: 0.08em;
|
|
302
|
+
text-transform: uppercase;
|
|
303
|
+
color: #c8f5ff;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.stat-grid {
|
|
307
|
+
display: grid;
|
|
308
|
+
gap: 8px;
|
|
309
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.stat {
|
|
313
|
+
border: 1px solid #3af7ff30;
|
|
314
|
+
background: #081022;
|
|
315
|
+
border-radius: 12px;
|
|
316
|
+
padding: 8px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.stat .label {
|
|
320
|
+
font: 700 0.66rem "Space Mono", monospace;
|
|
321
|
+
text-transform: uppercase;
|
|
322
|
+
letter-spacing: 0.04em;
|
|
323
|
+
color: #86b4cc;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.stat .value {
|
|
327
|
+
margin-top: 3px;
|
|
328
|
+
font: 700 1.05rem "Orbitron", sans-serif;
|
|
329
|
+
color: #f0fdff;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.stack-list {
|
|
333
|
+
margin: 0;
|
|
334
|
+
padding: 0;
|
|
335
|
+
list-style: none;
|
|
336
|
+
display: grid;
|
|
337
|
+
gap: 6px;
|
|
338
|
+
max-height: 168px;
|
|
339
|
+
overflow: auto;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.stack-list li {
|
|
343
|
+
border: 1px solid #ffffff1a;
|
|
344
|
+
border-radius: 10px;
|
|
345
|
+
background: #090f1d;
|
|
346
|
+
padding: 7px 8px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.stack-list .head {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: baseline;
|
|
352
|
+
justify-content: space-between;
|
|
353
|
+
gap: 8px;
|
|
354
|
+
font: 700 0.78rem "Rajdhani", sans-serif;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.stack-list .meta {
|
|
358
|
+
margin-top: 2px;
|
|
359
|
+
color: #7ca9bd;
|
|
360
|
+
font: 700 0.64rem "Space Mono", monospace;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.badge {
|
|
364
|
+
border-radius: 999px;
|
|
365
|
+
border: 1px solid #ffffff2d;
|
|
366
|
+
padding: 2px 7px;
|
|
367
|
+
font: 700 0.61rem "Space Mono", monospace;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.badge.low { border-color: #3af7ff66; color: #9ff8ff; }
|
|
371
|
+
.badge.medium { border-color: #ffb84d88; color: #ffd596; }
|
|
372
|
+
.badge.high { border-color: #ff4d8b88; color: #ff98b9; }
|
|
373
|
+
|
|
374
|
+
.inspector {
|
|
375
|
+
border: 1px solid #3af7ff26;
|
|
376
|
+
border-radius: 10px;
|
|
377
|
+
background: #090f1d;
|
|
378
|
+
min-height: 130px;
|
|
379
|
+
padding: 8px;
|
|
380
|
+
font: 700 0.68rem/1.45 "Space Mono", monospace;
|
|
381
|
+
color: #b4d9e8;
|
|
382
|
+
white-space: pre-wrap;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.path-controls {
|
|
386
|
+
display: grid;
|
|
387
|
+
gap: 6px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.path-actions {
|
|
391
|
+
display: flex;
|
|
392
|
+
gap: 8px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.mini-btn {
|
|
396
|
+
border: 1px solid #3af7ff55;
|
|
397
|
+
border-radius: 9px;
|
|
398
|
+
background: #0b1428;
|
|
399
|
+
color: #d5f4ff;
|
|
400
|
+
font: 700 0.68rem "Space Mono", monospace;
|
|
401
|
+
text-transform: uppercase;
|
|
402
|
+
letter-spacing: 0.04em;
|
|
403
|
+
padding: 7px 8px;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@media (max-width: 1300px) {
|
|
408
|
+
.workspace {
|
|
409
|
+
grid-template-columns: 1fr;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.side-rail {
|
|
413
|
+
grid-template-columns: repeat(2, minmax(260px, 1fr));
|
|
414
|
+
grid-template-rows: none;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@media (max-width: 860px) {
|
|
419
|
+
.control-strip {
|
|
420
|
+
grid-template-columns: 1fr;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.topbar {
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
align-items: flex-start;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.graph-panel {
|
|
429
|
+
min-height: 620px;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.side-rail {
|
|
433
|
+
grid-template-columns: 1fr;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
@keyframes reveal {
|
|
438
|
+
to {
|
|
439
|
+
opacity: 1;
|
|
440
|
+
transform: translateY(0);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
@keyframes pulse {
|
|
445
|
+
0%,
|
|
446
|
+
100% {
|
|
447
|
+
transform: scale(1);
|
|
448
|
+
opacity: 0.7;
|
|
449
|
+
}
|
|
450
|
+
50% {
|
|
451
|
+
transform: scale(1.15);
|
|
452
|
+
opacity: 1;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
</style>
|
|
456
|
+
</head>
|
|
457
|
+
<body>
|
|
458
|
+
<div class="app-shell">
|
|
459
|
+
<header class="topbar stage">
|
|
460
|
+
<div class="title-block">
|
|
461
|
+
<h1>Codegraph Cyber Deck</h1>
|
|
462
|
+
<p>Three dimensional dependency scanning and symbol intelligence</p>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="status-chip" id="statusChip">Syncing graph matrix...</div>
|
|
465
|
+
</header>
|
|
466
|
+
|
|
467
|
+
<section class="control-strip stage">
|
|
468
|
+
<label class="control">
|
|
469
|
+
<span>Graph View</span>
|
|
470
|
+
<select id="viewSelect">
|
|
471
|
+
<option value="fileDeps">File Dependency Graph</option>
|
|
472
|
+
<option value="symbolGraph">Symbol Graph</option>
|
|
473
|
+
<option value="all">All Relationships</option>
|
|
474
|
+
</select>
|
|
475
|
+
</label>
|
|
476
|
+
|
|
477
|
+
<label class="control">
|
|
478
|
+
<span>Search Nodes</span>
|
|
479
|
+
<input id="searchInput" placeholder="Type file, function, class, route" />
|
|
480
|
+
</label>
|
|
481
|
+
|
|
482
|
+
<button class="neon-btn" id="focusSearch">Focus Match</button>
|
|
483
|
+
<button class="neon-btn" id="refreshAll">Refresh</button>
|
|
484
|
+
</section>
|
|
485
|
+
|
|
486
|
+
<main class="workspace stage">
|
|
487
|
+
<section class="graph-panel card">
|
|
488
|
+
<div class="panel-head">
|
|
489
|
+
<h2>Three Dimensional Code Graph</h2>
|
|
490
|
+
<div class="legend">
|
|
491
|
+
<span class="legend-pill cyan">Overview</span>
|
|
492
|
+
<span class="legend-pill cyan">File Dependency Graph</span>
|
|
493
|
+
<span class="legend-pill pink">Symbol Graph</span>
|
|
494
|
+
<span class="legend-pill lime">API Route Map</span>
|
|
495
|
+
<span class="legend-pill pink">Circular Dependency View</span>
|
|
496
|
+
<span class="legend-pill lime">Refactor Insights</span>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="graph-wrap" id="graphWrap">
|
|
500
|
+
<div id="graphCanvas"></div>
|
|
501
|
+
<div class="graph-empty" id="graphEmpty">Bootstrapping 3D renderer...</div>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="panel-foot">
|
|
504
|
+
<span id="graphCounts">nodes: 0 | edges: 0</span>
|
|
505
|
+
<span>drag to orbit, wheel to zoom, click a node to inspect</span>
|
|
506
|
+
</div>
|
|
507
|
+
</section>
|
|
508
|
+
|
|
509
|
+
<aside class="side-rail">
|
|
510
|
+
<section class="info-card card">
|
|
511
|
+
<h3>Overview</h3>
|
|
512
|
+
<div class="stat-grid">
|
|
513
|
+
<div class="stat"><div class="label">Files</div><div class="value" id="filesCount">0</div></div>
|
|
514
|
+
<div class="stat"><div class="label">Symbols</div><div class="value" id="symbolsCount">0</div></div>
|
|
515
|
+
<div class="stat"><div class="label">Routes</div><div class="value" id="routesCount">0</div></div>
|
|
516
|
+
<div class="stat"><div class="label">Cycles</div><div class="value" id="cyclesCount">0</div></div>
|
|
517
|
+
</div>
|
|
518
|
+
</section>
|
|
519
|
+
|
|
520
|
+
<section class="info-card card">
|
|
521
|
+
<h3>Node Inspector</h3>
|
|
522
|
+
<div class="inspector" id="inspector">Select a node in the graph to inspect direct inbound and outbound edges.</div>
|
|
523
|
+
</section>
|
|
524
|
+
|
|
525
|
+
<section class="info-card card">
|
|
526
|
+
<h3>Neighborhood + Pathfinder</h3>
|
|
527
|
+
<div class="path-controls">
|
|
528
|
+
<input id="pathFrom" placeholder="From node id (or select node)" />
|
|
529
|
+
<input id="pathTo" placeholder="To node id" />
|
|
530
|
+
<input id="pathDepth" type="number" min="1" max="128" value="8" />
|
|
531
|
+
<div class="path-actions">
|
|
532
|
+
<button class="mini-btn" id="expandNeighborhood">Expand Neighborhood</button>
|
|
533
|
+
<button class="mini-btn" id="findPath">Find Path</button>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="inspector" id="pathOutput">Use selected node + depth to expand local graph, or provide from/to to highlight a shortest path.</div>
|
|
536
|
+
</div>
|
|
537
|
+
</section>
|
|
538
|
+
|
|
539
|
+
<section class="info-card card">
|
|
540
|
+
<h3>API Route Map</h3>
|
|
541
|
+
<ul class="stack-list" id="routesList">
|
|
542
|
+
<li><div class="meta">Loading routes...</div></li>
|
|
543
|
+
</ul>
|
|
544
|
+
</section>
|
|
545
|
+
|
|
546
|
+
<section class="info-card card">
|
|
547
|
+
<h3>Circular Dependency View</h3>
|
|
548
|
+
<ul class="stack-list" id="cyclesList">
|
|
549
|
+
<li><div class="meta">Loading SCC cycles...</div></li>
|
|
550
|
+
</ul>
|
|
551
|
+
</section>
|
|
552
|
+
|
|
553
|
+
<section class="info-card card">
|
|
554
|
+
<h3>Refactor Insights</h3>
|
|
555
|
+
<ul class="stack-list" id="insightsList">
|
|
556
|
+
<li><div class="meta">Computing hotspots and orphan modules...</div></li>
|
|
557
|
+
</ul>
|
|
558
|
+
</section>
|
|
559
|
+
</aside>
|
|
560
|
+
</main>
|
|
561
|
+
</div>
|
|
562
|
+
|
|
563
|
+
<script src="/assets/vendor/three.min.js"></script>
|
|
564
|
+
<script src="/assets/vendor/3d-force-graph.min.js"></script>
|
|
565
|
+
<script>
|
|
566
|
+
(() => {
|
|
567
|
+
const state = {
|
|
568
|
+
view: 'fileDeps',
|
|
569
|
+
graph: null,
|
|
570
|
+
graphData: { nodes: [], links: [] },
|
|
571
|
+
selectedNodeId: '',
|
|
572
|
+
searchTerm: '',
|
|
573
|
+
lastMeta: null,
|
|
574
|
+
lastCycles: [],
|
|
575
|
+
lastRoutes: [],
|
|
576
|
+
lastInsights: null
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const NODE_COLORS = {
|
|
580
|
+
file: '#3af7ff',
|
|
581
|
+
function: '#ff2de1',
|
|
582
|
+
class: '#b8ff39',
|
|
583
|
+
interface: '#ffb84d',
|
|
584
|
+
variable: '#9f8cff',
|
|
585
|
+
route: '#10f8a5',
|
|
586
|
+
directory: '#6ea8ff',
|
|
587
|
+
entrypoint: '#ff6d6d'
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const EDGE_COLORS = {
|
|
591
|
+
IMPORTS: '#3af7ff',
|
|
592
|
+
DEPENDS_ON: '#ff2de1',
|
|
593
|
+
CALLS: '#b8ff39',
|
|
594
|
+
REFERENCES: '#ffb84d',
|
|
595
|
+
HANDLES_ROUTE: '#10f8a5',
|
|
596
|
+
DEFINES: '#9f8cff',
|
|
597
|
+
EXPORTS: '#8fd4ff',
|
|
598
|
+
CONTAINS: '#4e6ea0'
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const els = {
|
|
602
|
+
status: document.getElementById('statusChip'),
|
|
603
|
+
view: document.getElementById('viewSelect'),
|
|
604
|
+
search: document.getElementById('searchInput'),
|
|
605
|
+
focus: document.getElementById('focusSearch'),
|
|
606
|
+
refresh: document.getElementById('refreshAll'),
|
|
607
|
+
graphWrap: document.getElementById('graphWrap'),
|
|
608
|
+
graphCanvas: document.getElementById('graphCanvas'),
|
|
609
|
+
graphEmpty: document.getElementById('graphEmpty'),
|
|
610
|
+
graphCounts: document.getElementById('graphCounts'),
|
|
611
|
+
filesCount: document.getElementById('filesCount'),
|
|
612
|
+
symbolsCount: document.getElementById('symbolsCount'),
|
|
613
|
+
routesCount: document.getElementById('routesCount'),
|
|
614
|
+
cyclesCount: document.getElementById('cyclesCount'),
|
|
615
|
+
inspector: document.getElementById('inspector'),
|
|
616
|
+
pathFrom: document.getElementById('pathFrom'),
|
|
617
|
+
pathTo: document.getElementById('pathTo'),
|
|
618
|
+
pathDepth: document.getElementById('pathDepth'),
|
|
619
|
+
expandNeighborhood: document.getElementById('expandNeighborhood'),
|
|
620
|
+
findPath: document.getElementById('findPath'),
|
|
621
|
+
pathOutput: document.getElementById('pathOutput'),
|
|
622
|
+
routesList: document.getElementById('routesList'),
|
|
623
|
+
cyclesList: document.getElementById('cyclesList'),
|
|
624
|
+
insightsList: document.getElementById('insightsList')
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
function colorForNode(type) {
|
|
628
|
+
const key = String(type || '').toLowerCase();
|
|
629
|
+
return NODE_COLORS[key] || '#d5f4ff';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function colorForEdge(type) {
|
|
633
|
+
const key = String(type || '').toUpperCase();
|
|
634
|
+
return EDGE_COLORS[key] || '#4f6e8a';
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function setStatus(text, tone) {
|
|
638
|
+
els.status.textContent = text;
|
|
639
|
+
if (tone === 'error') {
|
|
640
|
+
els.status.style.color = '#ff8ab5';
|
|
641
|
+
els.status.style.borderColor = '#ff4d8b88';
|
|
642
|
+
els.status.style.background = '#2a1020cc';
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (tone === 'warn') {
|
|
646
|
+
els.status.style.color = '#ffd899';
|
|
647
|
+
els.status.style.borderColor = '#ffb84d88';
|
|
648
|
+
els.status.style.background = '#2a1d10cc';
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
els.status.style.color = '#3af7ff';
|
|
652
|
+
els.status.style.borderColor = '#3af7ff66';
|
|
653
|
+
els.status.style.background = '#0a1a2dcc';
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function readEnvelope(url, init) {
|
|
657
|
+
const response = await fetch(url, init);
|
|
658
|
+
const payload = await response.json();
|
|
659
|
+
if (!response.ok || !payload || payload.ok !== true) {
|
|
660
|
+
const message = payload && payload.error && payload.error.message
|
|
661
|
+
? payload.error.message
|
|
662
|
+
: 'Request failed for ' + url;
|
|
663
|
+
throw new Error(message);
|
|
664
|
+
}
|
|
665
|
+
return payload.data;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function toGraphData(apiData) {
|
|
669
|
+
const rawNodes = Array.isArray(apiData && apiData.nodes) ? apiData.nodes : [];
|
|
670
|
+
const rawEdges = Array.isArray(apiData && apiData.edges) ? apiData.edges : [];
|
|
671
|
+
const degree = {};
|
|
672
|
+
const links = rawEdges.map((edge) => {
|
|
673
|
+
const source = edge.from || edge.source;
|
|
674
|
+
const target = edge.to || edge.target;
|
|
675
|
+
if (source) {
|
|
676
|
+
degree[source] = (degree[source] || 0) + 1;
|
|
677
|
+
}
|
|
678
|
+
if (target) {
|
|
679
|
+
degree[target] = (degree[target] || 0) + 1;
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
id: edge.id,
|
|
683
|
+
source,
|
|
684
|
+
target,
|
|
685
|
+
type: edge.type,
|
|
686
|
+
__color: colorForEdge(edge.type),
|
|
687
|
+
__highlight: false
|
|
688
|
+
};
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const nodes = rawNodes.map((node) => {
|
|
692
|
+
const nodeId = node.id;
|
|
693
|
+
const d = degree[nodeId] || 0;
|
|
694
|
+
return {
|
|
695
|
+
id: nodeId,
|
|
696
|
+
name: node.name || node.id,
|
|
697
|
+
type: node.type || 'unknown',
|
|
698
|
+
path: node.path || '',
|
|
699
|
+
props: node.props || {},
|
|
700
|
+
val: 2.6 + Math.min(14, d * 0.9),
|
|
701
|
+
__color: colorForNode(node.type),
|
|
702
|
+
__matched: false,
|
|
703
|
+
__selected: false
|
|
704
|
+
};
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
return { nodes, links };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function ensureGraph() {
|
|
711
|
+
if (state.graph) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (typeof window.ForceGraph3D !== 'function') {
|
|
716
|
+
els.graphEmpty.textContent = '3D renderer failed to load. Check local vendor asset routes.';
|
|
717
|
+
setStatus('Renderer unavailable', 'error');
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const graph = window.ForceGraph3D()(els.graphCanvas)
|
|
722
|
+
.backgroundColor('#05060f')
|
|
723
|
+
.showNavInfo(false)
|
|
724
|
+
.nodeRelSize(3.2)
|
|
725
|
+
.nodeOpacity(0.95)
|
|
726
|
+
.nodeLabel((node) => {
|
|
727
|
+
const name = String(node.name || node.id || 'node');
|
|
728
|
+
const type = String(node.type || 'unknown');
|
|
729
|
+
const path = node.path ? ' | ' + String(node.path) : '';
|
|
730
|
+
return name + ' [' + type + ']' + path;
|
|
731
|
+
})
|
|
732
|
+
.nodeColor((node) => {
|
|
733
|
+
if (node.__selected) {
|
|
734
|
+
return '#ff2de1';
|
|
735
|
+
}
|
|
736
|
+
if (node.__matched) {
|
|
737
|
+
return '#f6ff4d';
|
|
738
|
+
}
|
|
739
|
+
return node.__color || '#d5f4ff';
|
|
740
|
+
})
|
|
741
|
+
.nodeVal((node) => node.val || 3)
|
|
742
|
+
.linkColor((link) => {
|
|
743
|
+
if (link.__highlight) {
|
|
744
|
+
return '#f6ff4d';
|
|
745
|
+
}
|
|
746
|
+
return link.__color || '#5f7fa1';
|
|
747
|
+
})
|
|
748
|
+
.linkWidth((link) => (link.__highlight ? 2.2 : 0.75))
|
|
749
|
+
.linkOpacity(0.66)
|
|
750
|
+
.linkDirectionalParticles((link) => (link.__highlight ? 4 : 1))
|
|
751
|
+
.linkDirectionalParticleWidth((link) => (link.__highlight ? 2.4 : 1.25))
|
|
752
|
+
.linkDirectionalParticleColor((link) => (link.__highlight ? '#f6ff4d' : '#3af7ff'))
|
|
753
|
+
.onNodeClick((node) => {
|
|
754
|
+
if (!node || !node.id) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
focusNode(node);
|
|
758
|
+
selectNode(String(node.id));
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
state.graph = graph;
|
|
762
|
+
|
|
763
|
+
const resize = () => {
|
|
764
|
+
const rect = els.graphWrap.getBoundingClientRect();
|
|
765
|
+
graph.width(Math.max(320, rect.width));
|
|
766
|
+
graph.height(Math.max(280, rect.height));
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
window.addEventListener('resize', resize);
|
|
770
|
+
resize();
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function focusNode(node) {
|
|
775
|
+
if (!state.graph) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const x = node.x || 0;
|
|
780
|
+
const y = node.y || 0;
|
|
781
|
+
const z = node.z || 0;
|
|
782
|
+
const base = Math.hypot(x, y, z) || 1;
|
|
783
|
+
const dist = 120;
|
|
784
|
+
const ratio = 1 + dist / base;
|
|
785
|
+
state.graph.cameraPosition(
|
|
786
|
+
{ x: x * ratio, y: y * ratio, z: z * ratio },
|
|
787
|
+
node,
|
|
788
|
+
1000
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function renderGraph() {
|
|
793
|
+
if (!ensureGraph()) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const nodeCount = state.graphData.nodes.length;
|
|
798
|
+
const edgeCount = state.graphData.links.length;
|
|
799
|
+
els.graphCounts.textContent = 'nodes: ' + nodeCount + ' | edges: ' + edgeCount;
|
|
800
|
+
|
|
801
|
+
if (nodeCount === 0) {
|
|
802
|
+
els.graphEmpty.style.display = 'grid';
|
|
803
|
+
state.graph.graphData({ nodes: [], links: [] });
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
els.graphEmpty.style.display = 'none';
|
|
808
|
+
state.graph.graphData(state.graphData);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function applySearchHighlights() {
|
|
812
|
+
const term = state.searchTerm.trim().toLowerCase();
|
|
813
|
+
const matchedNodeIds = new Set();
|
|
814
|
+
|
|
815
|
+
state.graphData.nodes.forEach((node) => {
|
|
816
|
+
const target = (String(node.name || '') + ' ' + String(node.path || '') + ' ' + String(node.type || '')).toLowerCase();
|
|
817
|
+
const matched = term.length > 0 && target.includes(term);
|
|
818
|
+
node.__matched = matched;
|
|
819
|
+
node.__selected = state.selectedNodeId === node.id;
|
|
820
|
+
if (matched) {
|
|
821
|
+
matchedNodeIds.add(node.id);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
state.graphData.links.forEach((link) => {
|
|
826
|
+
const bySearch = term.length > 0 && (matchedNodeIds.has(link.source) || matchedNodeIds.has(link.target));
|
|
827
|
+
const bySelection = state.selectedNodeId && (link.source === state.selectedNodeId || link.target === state.selectedNodeId);
|
|
828
|
+
link.__highlight = Boolean(bySearch || bySelection);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function loadMeta() {
|
|
833
|
+
const data = await readEnvelope('/api/meta');
|
|
834
|
+
state.lastMeta = data;
|
|
835
|
+
const counts = data && data.counts ? data.counts : {};
|
|
836
|
+
els.filesCount.textContent = String(counts.files || 0);
|
|
837
|
+
els.symbolsCount.textContent = String(counts.symbols || 0);
|
|
838
|
+
els.routesCount.textContent = String(counts.routes || 0);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async function loadGraph() {
|
|
842
|
+
const view = state.view === 'all' ? 'all' : state.view;
|
|
843
|
+
const data = await readEnvelope('/api/graph?view=' + encodeURIComponent(view));
|
|
844
|
+
state.graphData = toGraphData(data);
|
|
845
|
+
applySearchHighlights();
|
|
846
|
+
renderGraph();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function renderRoutes(routes) {
|
|
850
|
+
const entries = Array.isArray(routes) ? routes : [];
|
|
851
|
+
if (entries.length === 0) {
|
|
852
|
+
els.routesList.innerHTML = '<li><div class="meta">No routes detected in this graph view.</div></li>';
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
els.routesList.innerHTML = '';
|
|
857
|
+
entries.slice(0, 22).forEach((route) => {
|
|
858
|
+
const li = document.createElement('li');
|
|
859
|
+
const head = document.createElement('div');
|
|
860
|
+
head.className = 'head';
|
|
861
|
+
const name = document.createElement('span');
|
|
862
|
+
name.textContent = (route.method || 'ANY') + ' ' + (route.path || '/');
|
|
863
|
+
const badge = document.createElement('span');
|
|
864
|
+
badge.className = 'badge low';
|
|
865
|
+
badge.textContent = 'route';
|
|
866
|
+
head.appendChild(name);
|
|
867
|
+
head.appendChild(badge);
|
|
868
|
+
|
|
869
|
+
const meta = document.createElement('div');
|
|
870
|
+
meta.className = 'meta';
|
|
871
|
+
meta.textContent = String(route.handler || 'handler') + (route.file ? ' | ' + route.file : '');
|
|
872
|
+
|
|
873
|
+
li.appendChild(head);
|
|
874
|
+
li.appendChild(meta);
|
|
875
|
+
els.routesList.appendChild(li);
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
async function loadRoutes() {
|
|
880
|
+
const data = await readEnvelope('/api/routes');
|
|
881
|
+
state.lastRoutes = Array.isArray(data && data.routes) ? data.routes : [];
|
|
882
|
+
renderRoutes(state.lastRoutes);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function renderCycles(cycles) {
|
|
886
|
+
const entries = Array.isArray(cycles) ? cycles : [];
|
|
887
|
+
els.cyclesCount.textContent = String(entries.length);
|
|
888
|
+
if (entries.length === 0) {
|
|
889
|
+
els.cyclesList.innerHTML = '<li><div class="meta">No circular dependencies found.</div></li>';
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
els.cyclesList.innerHTML = '';
|
|
894
|
+
entries.slice(0, 20).forEach((cycle) => {
|
|
895
|
+
const li = document.createElement('li');
|
|
896
|
+
const head = document.createElement('div');
|
|
897
|
+
head.className = 'head';
|
|
898
|
+
const label = document.createElement('span');
|
|
899
|
+
label.textContent = cycle.id || 'cycle';
|
|
900
|
+
const badge = document.createElement('span');
|
|
901
|
+
const severity = String(cycle.severity || 'low').toLowerCase();
|
|
902
|
+
badge.className = 'badge ' + (severity === 'high' || severity === 'medium' ? severity : 'low');
|
|
903
|
+
badge.textContent = severity;
|
|
904
|
+
head.appendChild(label);
|
|
905
|
+
head.appendChild(badge);
|
|
906
|
+
|
|
907
|
+
const meta = document.createElement('div');
|
|
908
|
+
meta.className = 'meta';
|
|
909
|
+
meta.textContent = String(cycle.size || 0) + ' nodes';
|
|
910
|
+
|
|
911
|
+
li.appendChild(head);
|
|
912
|
+
li.appendChild(meta);
|
|
913
|
+
els.cyclesList.appendChild(li);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
async function loadCycles() {
|
|
918
|
+
const data = await readEnvelope('/api/cycles');
|
|
919
|
+
state.lastCycles = Array.isArray(data && data.cycles) ? data.cycles : [];
|
|
920
|
+
renderCycles(state.lastCycles);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function renderInsights(result) {
|
|
924
|
+
if (!result || typeof result !== 'object') {
|
|
925
|
+
els.insightsList.innerHTML = '<li><div class="meta">No insight payload returned.</div></li>';
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const hotspots = Array.isArray(result.hotspots) ? result.hotspots : [];
|
|
930
|
+
const orphanModules = Array.isArray(result.orphanModules) ? result.orphanModules : [];
|
|
931
|
+
const unusedExports = Array.isArray(result.unusedExports) ? result.unusedExports : [];
|
|
932
|
+
|
|
933
|
+
const lines = [];
|
|
934
|
+
hotspots.slice(0, 4).forEach((item) => {
|
|
935
|
+
lines.push({
|
|
936
|
+
label: item.path || item.nodeId || 'unknown hotspot',
|
|
937
|
+
meta: 'score ' + String(item.score || 0) + ' | in ' + String(item.fanIn || 0) + ' | out ' + String(item.fanOut || 0),
|
|
938
|
+
badge: 'hotspot'
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
orphanModules.slice(0, 2).forEach((item) => {
|
|
942
|
+
lines.push({
|
|
943
|
+
label: item.path || item.nodeId || 'orphan module',
|
|
944
|
+
meta: 'orphan',
|
|
945
|
+
badge: 'orphan'
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
unusedExports.slice(0, 2).forEach((item) => {
|
|
949
|
+
lines.push({
|
|
950
|
+
label: (item.name || 'export') + ' in ' + (item.filePath || item.fileId || 'file'),
|
|
951
|
+
meta: 'unused export',
|
|
952
|
+
badge: 'unused'
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
if (lines.length === 0) {
|
|
957
|
+
els.insightsList.innerHTML = '<li><div class="meta">No refactor insights available.</div></li>';
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
els.insightsList.innerHTML = '';
|
|
962
|
+
lines.forEach((line) => {
|
|
963
|
+
const li = document.createElement('li');
|
|
964
|
+
const head = document.createElement('div');
|
|
965
|
+
head.className = 'head';
|
|
966
|
+
const label = document.createElement('span');
|
|
967
|
+
label.textContent = line.label;
|
|
968
|
+
const badge = document.createElement('span');
|
|
969
|
+
badge.className = 'badge low';
|
|
970
|
+
badge.textContent = line.badge;
|
|
971
|
+
head.appendChild(label);
|
|
972
|
+
head.appendChild(badge);
|
|
973
|
+
|
|
974
|
+
const meta = document.createElement('div');
|
|
975
|
+
meta.className = 'meta';
|
|
976
|
+
meta.textContent = line.meta;
|
|
977
|
+
|
|
978
|
+
li.appendChild(head);
|
|
979
|
+
li.appendChild(meta);
|
|
980
|
+
els.insightsList.appendChild(li);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async function loadInsights() {
|
|
985
|
+
const data = await readEnvelope('/api/query', {
|
|
986
|
+
method: 'POST',
|
|
987
|
+
headers: { 'content-type': 'application/json' },
|
|
988
|
+
body: JSON.stringify({ type: 'refactorInsights', limit: 6 })
|
|
989
|
+
});
|
|
990
|
+
state.lastInsights = data && data.result ? data.result : null;
|
|
991
|
+
renderInsights(state.lastInsights);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function selectNode(nodeId) {
|
|
995
|
+
state.selectedNodeId = nodeId;
|
|
996
|
+
if (els.pathFrom) {
|
|
997
|
+
els.pathFrom.value = nodeId;
|
|
998
|
+
}
|
|
999
|
+
applySearchHighlights();
|
|
1000
|
+
renderGraph();
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
const data = await readEnvelope('/api/node/' + encodeURIComponent(nodeId));
|
|
1004
|
+
const node = data && data.node ? data.node : { id: nodeId };
|
|
1005
|
+
const inbound = Array.isArray(data && data.inbound) ? data.inbound : [];
|
|
1006
|
+
const outbound = Array.isArray(data && data.outbound) ? data.outbound : [];
|
|
1007
|
+
const lines = [
|
|
1008
|
+
'node: ' + String(node.id || ''),
|
|
1009
|
+
'type: ' + String(node.type || ''),
|
|
1010
|
+
'name: ' + String(node.name || ''),
|
|
1011
|
+
'path: ' + String(node.path || ''),
|
|
1012
|
+
'inbound edges: ' + String(inbound.length),
|
|
1013
|
+
'outbound edges: ' + String(outbound.length)
|
|
1014
|
+
];
|
|
1015
|
+
els.inspector.textContent = lines.join('\\n');
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
els.inspector.textContent = 'Could not load node details: ' + String(error && error.message ? error.message : error);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async function expandNeighborhood() {
|
|
1022
|
+
const seed = (els.pathFrom && els.pathFrom.value ? els.pathFrom.value.trim() : '') || state.selectedNodeId;
|
|
1023
|
+
if (!seed) {
|
|
1024
|
+
setStatus('Select a node or provide a seed node id first.', 'warn');
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const depth = Number.parseInt((els.pathDepth && els.pathDepth.value) || '1', 10);
|
|
1028
|
+
const boundedDepth = Number.isInteger(depth) ? Math.max(1, Math.min(12, depth)) : 1;
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
const data = await readEnvelope(
|
|
1032
|
+
'/api/subgraph?seed=' +
|
|
1033
|
+
encodeURIComponent(seed) +
|
|
1034
|
+
'&depth=' +
|
|
1035
|
+
encodeURIComponent(String(boundedDepth)) +
|
|
1036
|
+
'&direction=both'
|
|
1037
|
+
);
|
|
1038
|
+
state.graphData = toGraphData(data);
|
|
1039
|
+
state.searchTerm = '';
|
|
1040
|
+
if (els.search) {
|
|
1041
|
+
els.search.value = '';
|
|
1042
|
+
}
|
|
1043
|
+
applySearchHighlights();
|
|
1044
|
+
renderGraph();
|
|
1045
|
+
els.pathOutput.textContent =
|
|
1046
|
+
'Neighborhood loaded around ' +
|
|
1047
|
+
seed +
|
|
1048
|
+
'\\n' +
|
|
1049
|
+
'depth: ' +
|
|
1050
|
+
String(boundedDepth) +
|
|
1051
|
+
'\\n' +
|
|
1052
|
+
'nodes: ' +
|
|
1053
|
+
String(state.graphData.nodes.length) +
|
|
1054
|
+
', edges: ' +
|
|
1055
|
+
String(state.graphData.links.length);
|
|
1056
|
+
setStatus('Neighborhood graph loaded.', 'ok');
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
setStatus('Subgraph load failed: ' + String(error && error.message ? error.message : error), 'error');
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
async function findPathBetweenNodes() {
|
|
1063
|
+
const from = els.pathFrom && els.pathFrom.value ? els.pathFrom.value.trim() : '';
|
|
1064
|
+
const to = els.pathTo && els.pathTo.value ? els.pathTo.value.trim() : '';
|
|
1065
|
+
if (!from || !to) {
|
|
1066
|
+
setStatus('Provide both from/to node ids for pathfinder.', 'warn');
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
const depth = Number.parseInt((els.pathDepth && els.pathDepth.value) || '8', 10);
|
|
1070
|
+
const maxDepth = Number.isInteger(depth) ? Math.max(1, Math.min(128, depth)) : 8;
|
|
1071
|
+
|
|
1072
|
+
try {
|
|
1073
|
+
const data = await readEnvelope(
|
|
1074
|
+
'/api/path?from=' +
|
|
1075
|
+
encodeURIComponent(from) +
|
|
1076
|
+
'&to=' +
|
|
1077
|
+
encodeURIComponent(to) +
|
|
1078
|
+
'&maxDepth=' +
|
|
1079
|
+
encodeURIComponent(String(maxDepth))
|
|
1080
|
+
);
|
|
1081
|
+
const nodeIds = Array.isArray(data && data.nodeIds) ? data.nodeIds : [];
|
|
1082
|
+
const edgeIds = Array.isArray(data && data.edgeIds) ? data.edgeIds : [];
|
|
1083
|
+
const nodeIdSet = new Set(nodeIds);
|
|
1084
|
+
const edgeIdSet = new Set(edgeIds);
|
|
1085
|
+
|
|
1086
|
+
state.graphData.nodes.forEach((node) => {
|
|
1087
|
+
node.__selected = nodeIdSet.has(node.id);
|
|
1088
|
+
});
|
|
1089
|
+
state.graphData.links.forEach((link) => {
|
|
1090
|
+
link.__highlight = edgeIdSet.has(link.id);
|
|
1091
|
+
});
|
|
1092
|
+
renderGraph();
|
|
1093
|
+
|
|
1094
|
+
if (!data || data.found !== true) {
|
|
1095
|
+
els.pathOutput.textContent = 'No path found within depth ' + String(maxDepth) + '.';
|
|
1096
|
+
setStatus('No path found.', 'warn');
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
els.pathOutput.textContent =
|
|
1101
|
+
'Path found\\n' +
|
|
1102
|
+
'length: ' +
|
|
1103
|
+
String(data.length || 0) +
|
|
1104
|
+
'\\n' +
|
|
1105
|
+
'nodes: ' +
|
|
1106
|
+
nodeIds.join(' -> ');
|
|
1107
|
+
setStatus('Path highlighted in graph.', 'ok');
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
setStatus('Path query failed: ' + String(error && error.message ? error.message : error), 'error');
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function focusFirstMatch() {
|
|
1114
|
+
const match = state.graphData.nodes.find((node) => node.__matched);
|
|
1115
|
+
if (!match) {
|
|
1116
|
+
setStatus('No nodes match current search.', 'warn');
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
setStatus('Focused search match: ' + String(match.name || match.id), 'ok');
|
|
1121
|
+
selectNode(String(match.id));
|
|
1122
|
+
focusNode(match);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function syncAll() {
|
|
1126
|
+
setStatus('Syncing graph matrix...', 'ok');
|
|
1127
|
+
try {
|
|
1128
|
+
await Promise.all([loadMeta(), loadGraph(), loadRoutes(), loadCycles(), loadInsights()]);
|
|
1129
|
+
setStatus('Cyber deck online. Orbit and click nodes to inspect.', 'ok');
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
setStatus('Sync failed: ' + String(error && error.message ? error.message : error), 'error');
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
els.view.addEventListener('change', async () => {
|
|
1136
|
+
state.view = els.view.value;
|
|
1137
|
+
await loadGraph().catch((error) => {
|
|
1138
|
+
setStatus('Graph view load failed: ' + String(error && error.message ? error.message : error), 'error');
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
els.search.addEventListener('input', () => {
|
|
1143
|
+
state.searchTerm = els.search.value || '';
|
|
1144
|
+
applySearchHighlights();
|
|
1145
|
+
renderGraph();
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
els.search.addEventListener('keydown', (event) => {
|
|
1149
|
+
if (event.key === 'Enter') {
|
|
1150
|
+
focusFirstMatch();
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
els.focus.addEventListener('click', focusFirstMatch);
|
|
1155
|
+
els.refresh.addEventListener('click', syncAll);
|
|
1156
|
+
els.expandNeighborhood.addEventListener('click', expandNeighborhood);
|
|
1157
|
+
els.findPath.addEventListener('click', findPathBetweenNodes);
|
|
1158
|
+
|
|
1159
|
+
syncAll();
|
|
1160
|
+
})();
|
|
1161
|
+
</script>
|
|
1162
|
+
</body>
|
|
1163
|
+
</html>`;
|
|
1164
|
+
}
|
|
1165
|
+
//# sourceMappingURL=ui.js.map
|