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