@aiready/visualizer 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,121 @@ var GraphBuilder = class _GraphBuilder {
166
184
  if (Array.isArray(toolData)) return toolData;
167
185
  return toolData.results ?? toolData.issues ?? [];
168
186
  };
187
+ this.processPatterns(builder, getResults(ToolName.PatternDetect, "patterns"), rootDir, bumpIssue);
188
+ this.processDuplicates(builder, report, rootDir, fileIssues);
189
+ this.processContext(builder, getResults(ToolName.ContextAnalyzer, "context"), rootDir, bumpIssue);
190
+ this.processToolResults(builder, ToolName.DocDrift, "docDrift", report, bumpIssue, "Doc-Drift Issue");
191
+ this.processToolResults(builder, ToolName.DependencyHealth, "dependencyHealth", report, bumpIssue, "Dependency Issue");
192
+ this.processToolResults(builder, ToolName.ContractEnforcement, "contractEnforcement", report, bumpIssue, "Contract Gap");
193
+ return this.finalizeGraph(builder, fileIssues, report);
194
+ }
195
+ static processPatterns(builder, results, rootDir, bumpIssue) {
169
196
  const basenameMap = /* @__PURE__ */ new Map();
170
- const patternResults = getResults(ToolName.PatternDetect, "patterns");
171
- patternResults.forEach((p) => {
197
+ results.forEach((p) => {
172
198
  const fileName = p.fileName ?? p.file;
173
199
  if (fileName) {
174
- const base = path.basename(fileName);
200
+ const base = path2.basename(fileName);
175
201
  if (!basenameMap.has(base)) basenameMap.set(base, /* @__PURE__ */ new Set());
176
202
  basenameMap.get(base).add(fileName);
177
203
  }
178
204
  });
179
- patternResults.forEach((entry) => {
180
- const file = entry.fileName ?? entry.file;
205
+ results.forEach((entry) => {
206
+ const normalized = normalizeAnalysisResult(entry);
207
+ const file = normalized.fileName;
181
208
  if (!file) return;
182
209
  builder.addNode(
183
210
  file,
184
- `Issues: ${(entry.issues ?? []).length}`,
185
- entry.metrics?.tokenCost ?? 5
211
+ `Issues: ${normalized.issues.length}`,
212
+ normalized.metrics.tokenCost || GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
186
213
  );
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);
214
+ const rawIssues = Array.isArray(entry.issues) ? entry.issues : [];
215
+ if (rawIssues.length > 0) {
216
+ rawIssues.forEach((issue) => {
217
+ bumpIssue(file, rankSeverity(issue.severity));
218
+ });
219
+ } else {
220
+ normalized.issues.forEach((issue) => {
221
+ bumpIssue(file, issue.severity);
193
222
  });
194
223
  }
195
- (entry.issues ?? []).forEach((issue) => {
196
- const message = issue.message || "";
197
- const refs = builder.extractReferencedPaths(message);
224
+ normalized.issues.forEach((issue) => {
225
+ const refs = extractReferencedPaths(issue.message);
198
226
  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);
227
+ const target = path2.isAbsolute(ref) ? ref : path2.resolve(path2.dirname(file), ref);
228
+ builder.addNode(target, "Referenced file", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
204
229
  builder.addEdge(file, target, "reference");
205
230
  });
206
- const percMatch = (message.match(/(\d+)%/) || [])[1];
231
+ const percMatch = (issue.message.match(/(\d+)%/) || [])[1];
207
232
  const perc = percMatch ? parseInt(percMatch, 10) : null;
208
- const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(message) || perc && perc >= 50;
233
+ const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(issue.message) || perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_THRESHOLD;
209
234
  if (wantFuzzy) {
210
- const fileGroup = builder.getPackageGroup(file);
235
+ const fileGroup = getPackageGroup(file);
211
236
  for (const [base, pathsSet] of basenameMap.entries()) {
212
- if (!message.includes(base) || base === path.basename(file))
213
- continue;
237
+ if (!issue.message.includes(base) || base === path2.basename(file)) continue;
214
238
  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);
239
+ const targetGroup = getPackageGroup(target);
240
+ if (fileGroup !== targetGroup && !(perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_HIGH_THRESHOLD)) continue;
241
+ builder.addNode(target, "Fuzzy match", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
218
242
  builder.addEdge(file, target, "similarity");
219
243
  }
220
244
  }
221
245
  }
222
246
  });
223
247
  });
248
+ }
249
+ static processDuplicates(builder, report, rootDir, fileIssues) {
224
250
  const patternData = report[ToolName.PatternDetect] || report.patternDetect || report.patterns || {};
225
251
  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
252
  duplicates.forEach((dup) => {
227
- builder.addNode(dup.file1, "Similarity target", 5);
228
- builder.addNode(dup.file2, "Similarity target", 5);
253
+ builder.addNode(dup.file1, "Similarity target", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
254
+ builder.addNode(dup.file2, "Similarity target", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
229
255
  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;
256
+ [dup.file1, dup.file2].forEach((file) => {
257
+ const id = path2.resolve(rootDir, file);
258
+ if (!fileIssues.has(id)) {
259
+ fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
260
+ }
261
+ fileIssues.get(id).duplicates += 1;
262
+ });
238
263
  });
239
- const contextResults = getResults(ToolName.ContextAnalyzer, "context");
240
- contextResults.forEach((ctx) => {
241
- const file = ctx.fileName ?? ctx.file;
264
+ }
265
+ static processContext(builder, results, rootDir, bumpIssue) {
266
+ results.forEach((ctx) => {
267
+ const normalized = normalizeAnalysisResult(ctx);
268
+ const file = normalized.fileName;
242
269
  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
- }
270
+ builder.addNode(file, `Deps: ${ctx.dependencyCount || 0}`, GRAPH_CONSTANTS.DEFAULT_CONTEXT_SIZE);
271
+ normalized.issues.forEach((issue) => {
272
+ bumpIssue(file, issue.severity);
273
+ });
252
274
  (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))
257
- return;
258
- builder.addNode(resolvedRel, "Related file", 5);
259
- const n = builder.nodesMap.get(
260
- path.resolve(builder.rootDir, resolvedRel)
261
- );
262
- if (n) n.size = (n.size || 1) + 2;
263
- try {
264
- builder.addEdge(file, resolvedRel, "related");
265
- } catch {
275
+ const resolvedRel = path2.isAbsolute(rel) ? rel : path2.resolve(path2.dirname(file), rel);
276
+ const resolvedFile = path2.resolve(builder.rootDir, file);
277
+ const resolvedTarget = path2.resolve(builder.rootDir, resolvedRel);
278
+ const keyA = `${resolvedFile}->${resolvedTarget}`;
279
+ const keyB = `${resolvedTarget}->${resolvedFile}`;
280
+ if (builder["edgesSet"].has(keyA) || builder["edgesSet"].has(keyB)) return;
281
+ builder.addNode(resolvedRel, "Related file", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
282
+ const node = builder["nodesMap"].get(resolvedTarget);
283
+ if (node) {
284
+ node.size = (node.size || 1) + 2;
266
285
  }
286
+ builder.addEdge(file, resolvedRel, "related");
267
287
  });
268
- const absoluteFile = path.resolve(builder.rootDir, file);
269
- const fileDir = path.dirname(absoluteFile);
288
+ const fileDir = path2.dirname(path2.resolve(builder.rootDir, file));
270
289
  (ctx.dependencyList ?? []).forEach((dep) => {
271
290
  if (dep.startsWith(".") || dep.startsWith("/")) {
272
291
  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")
292
+ path2.resolve(fileDir, dep),
293
+ path2.resolve(fileDir, dep + ".ts"),
294
+ path2.resolve(fileDir, dep + ".tsx"),
295
+ path2.resolve(fileDir, dep + ".js"),
296
+ path2.resolve(fileDir, dep, "index.ts"),
297
+ path2.resolve(fileDir, dep, "index.tsx")
279
298
  ];
280
299
  for (const p of possiblePaths) {
281
300
  if (fs.existsSync(p)) {
282
- builder.addNode(p, "Dependency", 2);
301
+ builder.addNode(p, "Dependency", GRAPH_CONSTANTS.DEFAULT_DEPENDENCY_SIZE);
283
302
  builder.addEdge(file, p, "dependency");
284
303
  break;
285
304
  }
@@ -287,100 +306,60 @@ var GraphBuilder = class _GraphBuilder {
287
306
  }
288
307
  });
289
308
  });
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);
309
+ }
310
+ static processToolResults(builder, toolName, legacyKey, report, bumpIssue, title) {
311
+ const camelKey = toolName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
312
+ const toolData = report[toolName] ?? report[camelKey] ?? report[legacyKey];
313
+ if (!toolData) return;
314
+ const results = Array.isArray(toolData) ? toolData : toolData.results ?? toolData.issues ?? [];
315
+ results.forEach((item) => {
316
+ if (!Array.isArray(item.issues) && (item.severity || item.message)) {
317
+ const file2 = item.fileName ?? item.file ?? item.location?.file;
318
+ if (file2) {
319
+ builder.addNode(file2, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
320
+ bumpIssue(file2, rankSeverity(item.severity));
321
+ }
322
+ return;
309
323
  }
310
- });
311
- const contractEnforcementResults = getResults(
312
- ToolName.ContractEnforcement,
313
- "contractEnforcement"
314
- );
315
- contractEnforcementResults.forEach((issue) => {
316
- const file = issue.fileName ?? issue.location?.file;
324
+ const normalized = normalizeAnalysisResult(item);
325
+ const file = normalized.fileName;
317
326
  if (file) {
318
- builder.addNode(file, "Contract Gap", 5);
319
- const sev = rankSeverity(issue.severity ?? null);
320
- bumpIssue(file, sev);
327
+ builder.addNode(file, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
328
+ normalized.issues.forEach((issue) => {
329
+ bumpIssue(file, issue.severity);
330
+ });
321
331
  }
322
332
  });
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
- };
333
+ }
334
+ static finalizeGraph(builder, fileIssues, report) {
335
+ const graph = builder.build();
343
336
  let criticalIssues = 0;
344
337
  let majorIssues = 0;
345
338
  let minorIssues = 0;
346
339
  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;
340
+ graph.nodes.forEach((node) => {
341
+ const record = fileIssues.get(node.id);
342
+ if (record) {
343
+ node.duplicates = record.duplicates || 0;
344
+ node.color = getColorForSeverity(record.maxSeverity);
345
+ node.group = getPackageGroup(node.id);
346
+ if (record.maxSeverity === Severity.Critical) criticalIssues += record.count;
347
+ else if (record.maxSeverity === Severity.Major) majorIssues += record.count;
348
+ else if (record.maxSeverity === Severity.Minor) minorIssues += record.count;
349
+ else if (record.maxSeverity === Severity.Info) infoIssues += record.count;
358
350
  } 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
351
+ node.color = getColorForSeverity(null);
352
+ node.group = getPackageGroup(node.id);
353
+ node.duplicates = 0;
383
354
  }
355
+ });
356
+ graph.metadata = {
357
+ ...graph.metadata,
358
+ criticalIssues,
359
+ majorIssues,
360
+ minorIssues,
361
+ infoIssues,
362
+ tokenBudget: report.scoring?.tokenBudget
384
363
  };
385
364
  return graph;
386
365
  }
@@ -390,11 +369,7 @@ function createSampleGraph() {
390
369
  builder.addNode("src/components/Button.tsx", "Button", 15);
391
370
  builder.addNode("src/utils/helpers.ts", "helpers", 12);
392
371
  builder.addNode("src/services/api.ts", "api", 18);
393
- builder.addEdge(
394
- "src/components/Button.tsx",
395
- "src/utils/helpers.ts",
396
- "dependency"
397
- );
372
+ builder.addEdge("src/components/Button.tsx", "src/utils/helpers.ts", "dependency");
398
373
  builder.addEdge("src/utils/helpers.ts", "src/services/api.ts", "dependency");
399
374
  return builder.build();
400
375
  }