@aiready/visualizer 0.7.3 → 0.7.5

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/dist/index.js CHANGED
@@ -1,6 +1,79 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
3
- import { UnifiedReportSchema, ToolName, Severity } from '@aiready/core';
2
+ import path2 from 'path';
3
+ import { Severity, UnifiedReportSchema, ToolName, normalizeAnalysisResult } from '@aiready/core';
4
+
5
+ // src/graph/builder.ts
6
+ var GRAPH_CONSTANTS = {
7
+ DEFAULT_NODE_SIZE: 1,
8
+ DEFAULT_REFERENCE_SIZE: 5,
9
+ DEFAULT_DEPENDENCY_SIZE: 2,
10
+ DEFAULT_CONTEXT_SIZE: 10,
11
+ FUZZY_MATCH_THRESHOLD: 50,
12
+ FUZZY_MATCH_HIGH_THRESHOLD: 80,
13
+ COLORS: {
14
+ CRITICAL: "#ff4d4f",
15
+ MAJOR: "#ff9900",
16
+ MINOR: "#ffd666",
17
+ INFO: "#91d5ff",
18
+ DEFAULT: "#97c2fc"
19
+ },
20
+ SEVERITY_ORDER: {
21
+ [Severity.Critical]: 3,
22
+ [Severity.Major]: 2,
23
+ [Severity.Minor]: 1,
24
+ [Severity.Info]: 0
25
+ }
26
+ };
27
+ function normalizeLabel(filePath, rootDir) {
28
+ try {
29
+ return path2.relative(rootDir, filePath);
30
+ } catch {
31
+ return filePath;
32
+ }
33
+ }
34
+ function extractReferencedPaths(message) {
35
+ if (!message || typeof message !== "string") return [];
36
+ const reAbs = /\/(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
37
+ const reRel = /(?:\.\/|\.\.\/)(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
38
+ const abs = message.match(reAbs) ?? [];
39
+ const rel = message.match(reRel) ?? [];
40
+ return abs.concat(rel);
41
+ }
42
+ function getPackageGroup(fp) {
43
+ if (!fp) return void 0;
44
+ const parts = fp.split(path2.sep);
45
+ const pkgIdx = parts.indexOf("packages");
46
+ if (pkgIdx >= 0 && parts.length > pkgIdx + 1)
47
+ return `packages/${parts[pkgIdx + 1]}`;
48
+ const landingIdx = parts.indexOf("landing");
49
+ if (landingIdx >= 0) return "landing";
50
+ const scriptsIdx = parts.indexOf("scripts");
51
+ if (scriptsIdx >= 0) return "scripts";
52
+ return parts.length > 1 ? parts[1] : parts[0];
53
+ }
54
+ function rankSeverity(s) {
55
+ if (!s) return null;
56
+ const ss = String(s).toLowerCase();
57
+ if (ss.includes("critical")) return Severity.Critical;
58
+ if (ss.includes("major")) return Severity.Major;
59
+ if (ss.includes("minor")) return Severity.Minor;
60
+ if (ss.includes("info")) return Severity.Info;
61
+ return null;
62
+ }
63
+ function getColorForSeverity(sev) {
64
+ switch (sev) {
65
+ case Severity.Critical:
66
+ return GRAPH_CONSTANTS.COLORS.CRITICAL;
67
+ case Severity.Major:
68
+ return GRAPH_CONSTANTS.COLORS.MAJOR;
69
+ case Severity.Minor:
70
+ return GRAPH_CONSTANTS.COLORS.MINOR;
71
+ case Severity.Info:
72
+ return GRAPH_CONSTANTS.COLORS.INFO;
73
+ default:
74
+ return GRAPH_CONSTANTS.COLORS.DEFAULT;
75
+ }
76
+ }
4
77
 
5
78
  // src/graph/builder.ts
6
79
  var GraphBuilder = class _GraphBuilder {
@@ -10,97 +83,59 @@ var GraphBuilder = class _GraphBuilder {
10
83
  this.edges = [];
11
84
  this.edgesSet = /* @__PURE__ */ new Set();
12
85
  }
13
- normalizeLabel(filePath) {
14
- try {
15
- return path.relative(this.rootDir, filePath);
16
- } catch {
17
- return filePath;
18
- }
19
- }
20
- extractReferencedPaths(message) {
21
- if (!message || typeof message !== "string") return [];
22
- const reAbs = /\/(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
23
- const reRel = /(?:\.\/|\.\.\/)(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
24
- const abs = message.match(reAbs) ?? [];
25
- const rel = message.match(reRel) ?? [];
26
- return abs.concat(rel);
27
- }
28
- getPackageGroup(fp) {
29
- if (!fp) return null;
30
- const parts = fp.split(path.sep);
31
- const pkgIdx = parts.indexOf("packages");
32
- if (pkgIdx >= 0 && parts.length > pkgIdx + 1)
33
- return `packages/${parts[pkgIdx + 1]}`;
34
- const landingIdx = parts.indexOf("landing");
35
- if (landingIdx >= 0) return "landing";
36
- const scriptsIdx = parts.indexOf("scripts");
37
- if (scriptsIdx >= 0) return "scripts";
38
- return parts.length > 1 ? parts[1] : parts[0];
39
- }
40
86
  /**
41
87
  * Add a new node to the graph or update an existing one.
42
- *
43
- * @param file - Unique identifier for the file (node ID).
44
- * @param title - Optional title or description for the node.
45
- * @param value - Numerical value representing the node size/weight.
46
88
  */
47
- addNode(file, title = "", value = 1) {
89
+ addNode(file, title = "", size = GRAPH_CONSTANTS.DEFAULT_NODE_SIZE) {
48
90
  if (!file) return;
49
- const id = path.resolve(this.rootDir, file);
50
- if (!this.nodesMap.has(id)) {
91
+ const id = path2.resolve(this.rootDir, file);
92
+ const existingNode = this.nodesMap.get(id);
93
+ if (!existingNode) {
51
94
  const node = {
52
95
  id,
53
96
  path: id,
54
- label: this.normalizeLabel(id),
97
+ label: normalizeLabel(id, this.rootDir),
55
98
  title,
56
- size: value ?? 1
99
+ size
57
100
  };
58
101
  this.nodesMap.set(id, node);
59
102
  } else {
60
- const node = this.nodesMap.get(id);
61
- if (title && (!node.title || !node.title.includes(title))) {
62
- node.title = (node.title ? node.title + "\n" : "") + title;
103
+ if (title && (!existingNode.title || !existingNode.title.includes(title))) {
104
+ existingNode.title = (existingNode.title ? existingNode.title + "\n" : "") + title;
105
+ }
106
+ if (size > (existingNode.size ?? 0)) {
107
+ existingNode.size = size;
63
108
  }
64
- if (value > (node.size ?? 0)) node.size = value;
65
109
  }
66
110
  }
67
111
  /**
68
112
  * Add a directed edge between two nodes in the graph.
69
- *
70
- * @param from - Source node ID (file path).
71
- * @param to - Target node ID (file path).
72
- * @param type - Type of relationship (e.g., 'dependency', 'reference').
73
113
  */
74
114
  addEdge(from, to, type = "link") {
75
115
  if (!from || !to) return;
76
- const a = path.resolve(this.rootDir, from);
77
- const b = path.resolve(this.rootDir, to);
78
- if (a === b) return;
79
- const key = `${a}->${b}`;
116
+ const source = path2.resolve(this.rootDir, from);
117
+ const target = path2.resolve(this.rootDir, to);
118
+ if (source === target) return;
119
+ const key = `${source}->${target}`;
80
120
  if (!this.edgesSet.has(key)) {
81
- this.edges.push({ source: a, target: b, type });
121
+ this.edges.push({ source, target, type });
82
122
  this.edgesSet.add(key);
83
123
  }
84
124
  }
85
125
  /**
86
126
  * Build the final GraphData object from collected nodes and edges.
87
- *
88
- * @returns Consolidated graph data structure.
89
127
  */
90
128
  build() {
91
129
  const nodes = Array.from(this.nodesMap.values());
92
- const edges = this.edges.map(
93
- (e) => ({ source: e.source, target: e.target, type: e.type })
94
- );
95
130
  return {
96
131
  nodes,
97
- edges,
132
+ edges: [...this.edges],
98
133
  clusters: [],
99
134
  issues: [],
100
135
  metadata: {
101
136
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
102
137
  totalFiles: nodes.length,
103
- totalDependencies: edges.length,
138
+ totalDependencies: this.edges.length,
104
139
  analysisTypes: [],
105
140
  criticalIssues: 0,
106
141
  majorIssues: 0,
@@ -115,10 +150,6 @@ var GraphBuilder = class _GraphBuilder {
115
150
  }
116
151
  /**
117
152
  * Static helper to build graph from an AIReady report JSON.
118
- *
119
- * @param report - Unified AIReady report object.
120
- * @param rootDir - Root directory for path resolution.
121
- * @returns Fully populated GraphData for visualization.
122
153
  */
123
154
  static buildFromReport(report, rootDir = process.cwd()) {
124
155
  const validation = UnifiedReportSchema.safeParse(report);
@@ -129,31 +160,18 @@ var GraphBuilder = class _GraphBuilder {
129
160
  }
130
161
  const builder = new _GraphBuilder(rootDir);
131
162
  const fileIssues = /* @__PURE__ */ new Map();
132
- const rankSeverity = (s) => {
133
- if (!s) return null;
134
- const ss = String(s).toLowerCase();
135
- if (ss.includes("critical")) return Severity.Critical;
136
- if (ss.includes("major")) return Severity.Major;
137
- if (ss.includes("minor")) return Severity.Minor;
138
- if (ss.includes("info")) return Severity.Info;
139
- return null;
140
- };
141
- const bumpIssue = (file, sev) => {
163
+ const bumpIssue = (file, severity) => {
142
164
  if (!file) return;
143
- const id = path.resolve(rootDir, file);
144
- if (!fileIssues.has(id))
165
+ const id = path2.resolve(rootDir, file);
166
+ if (!fileIssues.has(id)) {
145
167
  fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
146
- const rec = fileIssues.get(id);
147
- rec.count += 1;
148
- if (sev) {
149
- const order = {
150
- [Severity.Critical]: 3,
151
- [Severity.Major]: 2,
152
- [Severity.Minor]: 1,
153
- [Severity.Info]: 0
154
- };
155
- if (!rec.maxSeverity || order[sev] > order[rec.maxSeverity])
156
- rec.maxSeverity = sev;
168
+ }
169
+ const record = fileIssues.get(id);
170
+ record.count += 1;
171
+ if (severity) {
172
+ if (!record.maxSeverity || GRAPH_CONSTANTS.SEVERITY_ORDER[severity] > GRAPH_CONSTANTS.SEVERITY_ORDER[record.maxSeverity]) {
173
+ record.maxSeverity = severity;
174
+ }
157
175
  }
158
176
  };
159
177
  const getResults = (toolKey, legacyKey) => {
@@ -163,120 +181,183 @@ var GraphBuilder = class _GraphBuilder {
163
181
  if (Array.isArray(toolData)) return toolData;
164
182
  return toolData.results ?? toolData.issues ?? [];
165
183
  };
184
+ this.processPatterns(
185
+ builder,
186
+ getResults(ToolName.PatternDetect, "patterns"),
187
+ rootDir,
188
+ bumpIssue
189
+ );
190
+ this.processDuplicates(builder, report, rootDir, fileIssues);
191
+ this.processContext(
192
+ builder,
193
+ getResults(ToolName.ContextAnalyzer, "context"),
194
+ rootDir,
195
+ bumpIssue
196
+ );
197
+ this.processToolResults(
198
+ builder,
199
+ ToolName.DocDrift,
200
+ "docDrift",
201
+ report,
202
+ bumpIssue,
203
+ "Doc-Drift Issue"
204
+ );
205
+ this.processToolResults(
206
+ builder,
207
+ ToolName.DependencyHealth,
208
+ "dependencyHealth",
209
+ report,
210
+ bumpIssue,
211
+ "Dependency Issue"
212
+ );
213
+ this.processToolResults(
214
+ builder,
215
+ ToolName.ContractEnforcement,
216
+ "contractEnforcement",
217
+ report,
218
+ bumpIssue,
219
+ "Contract Gap"
220
+ );
221
+ return this.finalizeGraph(builder, fileIssues, report);
222
+ }
223
+ static processPatterns(builder, results, rootDir, bumpIssue) {
166
224
  const basenameMap = /* @__PURE__ */ new Map();
167
- const patternResults = getResults(ToolName.PatternDetect, "patterns");
168
- patternResults.forEach((p) => {
225
+ results.forEach((p) => {
169
226
  const fileName = p.fileName ?? p.file;
170
227
  if (fileName) {
171
- const base = path.basename(fileName);
228
+ const base = path2.basename(fileName);
172
229
  if (!basenameMap.has(base)) basenameMap.set(base, /* @__PURE__ */ new Set());
173
230
  basenameMap.get(base).add(fileName);
174
231
  }
175
232
  });
176
- patternResults.forEach((entry) => {
177
- const file = entry.fileName ?? entry.file;
233
+ results.forEach((entry) => {
234
+ const normalized = normalizeAnalysisResult(entry);
235
+ const file = normalized.fileName;
178
236
  if (!file) return;
179
237
  builder.addNode(
180
238
  file,
181
- `Issues: ${(entry.issues ?? []).length}`,
182
- entry.metrics?.tokenCost ?? 5
239
+ `Issues: ${normalized.issues.length}`,
240
+ normalized.metrics.tokenCost || GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
183
241
  );
184
- if ((entry.issues ?? []).length > 0) {
185
- (entry.issues ?? []).forEach((issue) => {
186
- const sev = rankSeverity(
187
- issue.severity ?? issue.severityLevel ?? null
188
- );
189
- bumpIssue(file, sev);
242
+ const rawIssues = Array.isArray(entry.issues) ? entry.issues : [];
243
+ if (rawIssues.length > 0) {
244
+ rawIssues.forEach((issue) => {
245
+ bumpIssue(file, rankSeverity(issue.severity));
246
+ });
247
+ } else {
248
+ normalized.issues.forEach((issue) => {
249
+ bumpIssue(file, issue.severity);
190
250
  });
191
251
  }
192
- (entry.issues ?? []).forEach((issue) => {
193
- const message = issue.message || "";
194
- const refs = builder.extractReferencedPaths(message);
252
+ normalized.issues.forEach((issue) => {
253
+ const refs = extractReferencedPaths(issue.message);
195
254
  refs.forEach((ref) => {
196
- let target = ref;
197
- if (!path.isAbsolute(ref)) {
198
- target = path.resolve(path.dirname(file), ref);
199
- }
200
- builder.addNode(target, "Referenced file", 5);
255
+ const target = path2.isAbsolute(ref) ? ref : path2.resolve(path2.dirname(file), ref);
256
+ builder.addNode(
257
+ target,
258
+ "Referenced file",
259
+ GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
260
+ );
201
261
  builder.addEdge(file, target, "reference");
202
262
  });
203
- const percMatch = (message.match(/(\d+)%/) || [])[1];
263
+ const percMatch = (issue.message.match(/(\d+)%/) || [])[1];
204
264
  const perc = percMatch ? parseInt(percMatch, 10) : null;
205
- const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(message) || perc && perc >= 50;
265
+ const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(issue.message) || perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_THRESHOLD;
206
266
  if (wantFuzzy) {
207
- const fileGroup = builder.getPackageGroup(file);
267
+ const fileGroup = getPackageGroup(file);
208
268
  for (const [base, pathsSet] of basenameMap.entries()) {
209
- if (!message.includes(base) || base === path.basename(file))
269
+ if (!issue.message.includes(base) || base === path2.basename(file))
210
270
  continue;
211
271
  for (const target of pathsSet) {
212
- const targetGroup = builder.getPackageGroup(target);
213
- if (fileGroup !== targetGroup && !(perc && perc >= 80)) continue;
214
- builder.addNode(target, "Fuzzy match", 5);
272
+ const targetGroup = getPackageGroup(target);
273
+ if (fileGroup !== targetGroup && !(perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_HIGH_THRESHOLD))
274
+ continue;
275
+ builder.addNode(
276
+ target,
277
+ "Fuzzy match",
278
+ GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
279
+ );
215
280
  builder.addEdge(file, target, "similarity");
216
281
  }
217
282
  }
218
283
  }
219
284
  });
220
285
  });
286
+ }
287
+ static processDuplicates(builder, report, rootDir, fileIssues) {
221
288
  const patternData = report[ToolName.PatternDetect] || report.patternDetect || report.patterns || {};
222
289
  const duplicates = (Array.isArray(patternData.duplicates) ? patternData.duplicates : null) || (patternData.summary && Array.isArray(patternData.summary.duplicates) ? patternData.summary.duplicates : null) || (Array.isArray(report.duplicates) ? report.duplicates : []);
223
290
  duplicates.forEach((dup) => {
224
- builder.addNode(dup.file1, "Similarity target", 5);
225
- builder.addNode(dup.file2, "Similarity target", 5);
291
+ builder.addNode(
292
+ dup.file1,
293
+ "Similarity target",
294
+ GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
295
+ );
296
+ builder.addNode(
297
+ dup.file2,
298
+ "Similarity target",
299
+ GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
300
+ );
226
301
  builder.addEdge(dup.file1, dup.file2, "similarity");
227
- const f1 = path.resolve(rootDir, dup.file1);
228
- const f2 = path.resolve(rootDir, dup.file2);
229
- if (!fileIssues.has(f1))
230
- fileIssues.set(f1, { count: 0, maxSeverity: null, duplicates: 0 });
231
- if (!fileIssues.has(f2))
232
- fileIssues.set(f2, { count: 0, maxSeverity: null, duplicates: 0 });
233
- fileIssues.get(f1).duplicates += 1;
234
- fileIssues.get(f2).duplicates += 1;
302
+ [dup.file1, dup.file2].forEach((file) => {
303
+ const id = path2.resolve(rootDir, file);
304
+ if (!fileIssues.has(id)) {
305
+ fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
306
+ }
307
+ fileIssues.get(id).duplicates += 1;
308
+ });
235
309
  });
236
- const contextResults = getResults(ToolName.ContextAnalyzer, "context");
237
- contextResults.forEach((ctx) => {
238
- const file = ctx.fileName ?? ctx.file;
310
+ }
311
+ static processContext(builder, results, rootDir, bumpIssue) {
312
+ results.forEach((ctx) => {
313
+ const normalized = normalizeAnalysisResult(ctx);
314
+ const file = normalized.fileName;
239
315
  if (!file) return;
240
- builder.addNode(file, `Deps: ${ctx.dependencyCount || 0}`, 10);
241
- if (ctx.issues && Array.isArray(ctx.issues)) {
242
- ctx.issues.forEach((issue) => {
243
- const sev = rankSeverity(
244
- typeof issue === "string" ? ctx.severity : issue.severity ?? issue.severityLevel ?? null
245
- );
246
- bumpIssue(file, sev);
247
- });
248
- }
316
+ builder.addNode(
317
+ file,
318
+ `Deps: ${ctx.dependencyCount || 0}`,
319
+ GRAPH_CONSTANTS.DEFAULT_CONTEXT_SIZE
320
+ );
321
+ normalized.issues.forEach((issue) => {
322
+ bumpIssue(file, issue.severity);
323
+ });
249
324
  (ctx.relatedFiles ?? []).forEach((rel) => {
250
- const resolvedRel = path.isAbsolute(rel) ? rel : path.resolve(path.dirname(file), rel);
251
- const keyA = `${path.resolve(builder.rootDir, file)}->${path.resolve(builder.rootDir, resolvedRel)}`;
252
- const keyB = `${path.resolve(builder.rootDir, resolvedRel)}->${path.resolve(builder.rootDir, file)}`;
253
- if (builder.edgesSet.has(keyA) || builder.edgesSet.has(keyB))
325
+ const resolvedRel = path2.isAbsolute(rel) ? rel : path2.resolve(path2.dirname(file), rel);
326
+ const resolvedFile = path2.resolve(builder.rootDir, file);
327
+ const resolvedTarget = path2.resolve(builder.rootDir, resolvedRel);
328
+ const keyA = `${resolvedFile}->${resolvedTarget}`;
329
+ const keyB = `${resolvedTarget}->${resolvedFile}`;
330
+ if (builder["edgesSet"].has(keyA) || builder["edgesSet"].has(keyB))
254
331
  return;
255
- builder.addNode(resolvedRel, "Related file", 5);
256
- const n = builder.nodesMap.get(
257
- path.resolve(builder.rootDir, resolvedRel)
332
+ builder.addNode(
333
+ resolvedRel,
334
+ "Related file",
335
+ GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
258
336
  );
259
- if (n) n.size = (n.size || 1) + 2;
260
- try {
261
- builder.addEdge(file, resolvedRel, "related");
262
- } catch {
337
+ const node = builder["nodesMap"].get(resolvedTarget);
338
+ if (node) {
339
+ node.size = (node.size || 1) + 2;
263
340
  }
341
+ builder.addEdge(file, resolvedRel, "related");
264
342
  });
265
- const absoluteFile = path.resolve(builder.rootDir, file);
266
- const fileDir = path.dirname(absoluteFile);
343
+ const fileDir = path2.dirname(path2.resolve(builder.rootDir, file));
267
344
  (ctx.dependencyList ?? []).forEach((dep) => {
268
345
  if (dep.startsWith(".") || dep.startsWith("/")) {
269
346
  const possiblePaths = [
270
- path.resolve(fileDir, dep),
271
- path.resolve(fileDir, dep + ".ts"),
272
- path.resolve(fileDir, dep + ".tsx"),
273
- path.resolve(fileDir, dep + ".js"),
274
- path.resolve(fileDir, dep, "index.ts"),
275
- path.resolve(fileDir, dep, "index.tsx")
347
+ path2.resolve(fileDir, dep),
348
+ path2.resolve(fileDir, dep + ".ts"),
349
+ path2.resolve(fileDir, dep + ".tsx"),
350
+ path2.resolve(fileDir, dep + ".js"),
351
+ path2.resolve(fileDir, dep, "index.ts"),
352
+ path2.resolve(fileDir, dep, "index.tsx")
276
353
  ];
277
354
  for (const p of possiblePaths) {
278
355
  if (fs.existsSync(p)) {
279
- builder.addNode(p, "Dependency", 2);
356
+ builder.addNode(
357
+ p,
358
+ "Dependency",
359
+ GRAPH_CONSTANTS.DEFAULT_DEPENDENCY_SIZE
360
+ );
280
361
  builder.addEdge(file, p, "dependency");
281
362
  break;
282
363
  }
@@ -284,100 +365,64 @@ var GraphBuilder = class _GraphBuilder {
284
365
  }
285
366
  });
286
367
  });
287
- const docDriftResults = getResults(ToolName.DocDrift, "docDrift");
288
- docDriftResults.forEach((issue) => {
289
- const file = issue.fileName ?? issue.location?.file;
290
- if (file) {
291
- builder.addNode(file, "Doc-Drift Issue", 5);
292
- const sev = rankSeverity(issue.severity ?? null);
293
- bumpIssue(file, sev);
294
- }
295
- });
296
- const depsResults = getResults(
297
- ToolName.DependencyHealth,
298
- "dependencyHealth"
299
- );
300
- depsResults.forEach((issue) => {
301
- const file = issue.fileName ?? issue.location?.file;
302
- if (file) {
303
- builder.addNode(file, "Dependency Issue", 5);
304
- const sev = rankSeverity(issue.severity ?? null);
305
- bumpIssue(file, sev);
368
+ }
369
+ static processToolResults(builder, toolName, legacyKey, report, bumpIssue, title) {
370
+ const camelKey = toolName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
371
+ const toolData = report[toolName] ?? report[camelKey] ?? report[legacyKey];
372
+ if (!toolData) return;
373
+ const results = Array.isArray(toolData) ? toolData : toolData.results ?? toolData.issues ?? [];
374
+ results.forEach((item) => {
375
+ if (!Array.isArray(item.issues) && (item.severity || item.message)) {
376
+ const file2 = item.fileName ?? item.file ?? item.location?.file;
377
+ if (file2) {
378
+ builder.addNode(file2, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
379
+ bumpIssue(file2, rankSeverity(item.severity));
380
+ }
381
+ return;
306
382
  }
307
- });
308
- const contractEnforcementResults = getResults(
309
- ToolName.ContractEnforcement,
310
- "contractEnforcement"
311
- );
312
- contractEnforcementResults.forEach((issue) => {
313
- const file = issue.fileName ?? issue.location?.file;
383
+ const normalized = normalizeAnalysisResult(item);
384
+ const file = normalized.fileName;
314
385
  if (file) {
315
- builder.addNode(file, "Contract Gap", 5);
316
- const sev = rankSeverity(issue.severity ?? null);
317
- bumpIssue(file, sev);
386
+ builder.addNode(file, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
387
+ normalized.issues.forEach((issue) => {
388
+ bumpIssue(file, issue.severity);
389
+ });
318
390
  }
319
391
  });
320
- const nodes = Array.from(builder.nodesMap.values());
321
- const edges = builder.edges;
322
- const colorFor = (sev) => {
323
- switch (sev) {
324
- case Severity.Critical:
325
- return "#ff4d4f";
326
- // red
327
- case Severity.Major:
328
- return "#ff9900";
329
- // orange
330
- case Severity.Minor:
331
- return "#ffd666";
332
- // yellow
333
- case Severity.Info:
334
- return "#91d5ff";
335
- // light blue
336
- default:
337
- return "#97c2fc";
338
- }
339
- };
392
+ }
393
+ static finalizeGraph(builder, fileIssues, report) {
394
+ const graph = builder.build();
340
395
  let criticalIssues = 0;
341
396
  let majorIssues = 0;
342
397
  let minorIssues = 0;
343
398
  let infoIssues = 0;
344
- for (const node of nodes) {
345
- const n = node;
346
- const rec = fileIssues.get(n.id);
347
- if (rec) {
348
- n.duplicates = rec.duplicates || 0;
349
- n.color = colorFor(rec.maxSeverity);
350
- n.group = builder.getPackageGroup(n.id) || void 0;
351
- if (rec.maxSeverity === Severity.Critical) criticalIssues += rec.count;
352
- else if (rec.maxSeverity === Severity.Major) majorIssues += rec.count;
353
- else if (rec.maxSeverity === Severity.Minor) minorIssues += rec.count;
354
- else if (rec.maxSeverity === Severity.Info) infoIssues += rec.count;
399
+ graph.nodes.forEach((node) => {
400
+ const record = fileIssues.get(node.id);
401
+ if (record) {
402
+ node.duplicates = record.duplicates || 0;
403
+ node.color = getColorForSeverity(record.maxSeverity);
404
+ node.group = getPackageGroup(node.id);
405
+ if (record.maxSeverity === Severity.Critical)
406
+ criticalIssues += record.count;
407
+ else if (record.maxSeverity === Severity.Major)
408
+ majorIssues += record.count;
409
+ else if (record.maxSeverity === Severity.Minor)
410
+ minorIssues += record.count;
411
+ else if (record.maxSeverity === Severity.Info)
412
+ infoIssues += record.count;
355
413
  } else {
356
- n.color = colorFor(null);
357
- n.group = builder.getPackageGroup(n.id) || void 0;
358
- n.duplicates = 0;
359
- }
360
- }
361
- const graph = {
362
- nodes,
363
- edges,
364
- clusters: [],
365
- issues: [],
366
- metadata: {
367
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
368
- totalFiles: nodes.length,
369
- totalDependencies: edges.length,
370
- analysisTypes: [],
371
- criticalIssues,
372
- majorIssues,
373
- minorIssues,
374
- infoIssues,
375
- tokenBudget: report.scoring?.tokenBudget
376
- },
377
- truncated: {
378
- nodes: false,
379
- edges: false
414
+ node.color = getColorForSeverity(null);
415
+ node.group = getPackageGroup(node.id);
416
+ node.duplicates = 0;
380
417
  }
418
+ });
419
+ graph.metadata = {
420
+ ...graph.metadata,
421
+ criticalIssues,
422
+ majorIssues,
423
+ minorIssues,
424
+ infoIssues,
425
+ tokenBudget: report.scoring?.tokenBudget
381
426
  };
382
427
  return graph;
383
428
  }