technical-debt-radar 1.9.0 → 1.10.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.
Files changed (2) hide show
  1. package/dist/index.js +157 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -231,11 +231,11 @@ var require_plans = __commonJS({
231
231
  [PlanId.FREE]: {
232
232
  id: PlanId.FREE,
233
233
  name: "Free",
234
- description: "Unlimited scans, warn mode",
234
+ description: "5 scans/month, warn mode",
235
235
  priceMonthly: 0,
236
236
  priceAnnual: 0,
237
237
  priceMonthlyIfAnnual: 0,
238
- limits: { maxRepos: 2, maxMembers: 1, aiCreditsPerMonth: 0, maxOrgs: 1, scansPerMonth: -1 },
238
+ limits: { maxRepos: 2, maxMembers: 1, aiCreditsPerMonth: 0, maxOrgs: 1, scansPerMonth: 5 },
239
239
  features: {
240
240
  ...ALL_CLI,
241
241
  cliFixAI: false,
@@ -270,7 +270,7 @@ var require_plans = __commonJS({
270
270
  priceMonthly: 15,
271
271
  priceAnnual: 144,
272
272
  priceMonthlyIfAnnual: 12,
273
- limits: { maxRepos: 5, maxMembers: 1, aiCreditsPerMonth: 50, maxOrgs: 1, scansPerMonth: -1 },
273
+ limits: { maxRepos: 5, maxMembers: 1, aiCreditsPerMonth: 50, maxOrgs: 1, scansPerMonth: 200 },
274
274
  features: {
275
275
  ...ALL_CLI,
276
276
  cliFixAI: true,
@@ -305,7 +305,7 @@ var require_plans = __commonJS({
305
305
  priceMonthly: 49,
306
306
  priceAnnual: 468,
307
307
  priceMonthlyIfAnnual: 39,
308
- limits: { maxRepos: -1, maxMembers: -1, aiCreditsPerMonth: 500, maxOrgs: 3, scansPerMonth: -1 },
308
+ limits: { maxRepos: -1, maxMembers: -1, aiCreditsPerMonth: 500, maxOrgs: 3, scansPerMonth: 1e3 },
309
309
  features: {
310
310
  ...ALL_CLI,
311
311
  cliFixAI: true,
@@ -340,7 +340,7 @@ var require_plans = __commonJS({
340
340
  priceMonthly: 99,
341
341
  priceAnnual: 948,
342
342
  priceMonthlyIfAnnual: 79,
343
- limits: { maxRepos: -1, maxMembers: -1, aiCreditsPerMonth: 1800, maxOrgs: 10, scansPerMonth: -1 },
343
+ limits: { maxRepos: -1, maxMembers: -1, aiCreditsPerMonth: 1800, maxOrgs: 10, scansPerMonth: 5e3 },
344
344
  features: {
345
345
  ...ALL_FEATURES_TRUE,
346
346
  sso: false,
@@ -399,6 +399,145 @@ var require_plans = __commonJS({
399
399
  }
400
400
  });
401
401
 
402
+ // ../../packages/shared/dist/graph-builder.js
403
+ var require_graph_builder = __commonJS({
404
+ "../../packages/shared/dist/graph-builder.js"(exports2) {
405
+ "use strict";
406
+ Object.defineProperty(exports2, "__esModule", { value: true });
407
+ exports2.buildImportGraphData = buildImportGraphData2;
408
+ var LAYER_COLORS = {
409
+ api: "#4CAF50",
410
+ application: "#2196F3",
411
+ domain: "#FF9800",
412
+ infrastructure: "#F44336",
413
+ shared: "#9C27B0",
414
+ common: "#9C27B0",
415
+ presentation: "#4CAF50",
416
+ business: "#2196F3",
417
+ "data-access": "#F44336",
418
+ models: "#FF9800",
419
+ controllers: "#4CAF50",
420
+ views: "#9E9E9E",
421
+ ports: "#2196F3",
422
+ adapters: "#F44336",
423
+ handlers: "#4CAF50",
424
+ commands: "#2196F3",
425
+ queries: "#FF9800",
426
+ events: "#9C27B0",
427
+ config: "#607D8B"
428
+ };
429
+ function buildImportGraphData2(edges, violations, files, modules) {
430
+ const nodeMap = /* @__PURE__ */ new Map();
431
+ const violationsByFile = /* @__PURE__ */ new Map();
432
+ for (const v of violations) {
433
+ violationsByFile.set(v.file, (violationsByFile.get(v.file) ?? 0) + 1);
434
+ }
435
+ const fileLoc = /* @__PURE__ */ new Map();
436
+ for (const f of files) {
437
+ if (f.linesOfCode) {
438
+ fileLoc.set(f.path, f.linesOfCode);
439
+ }
440
+ }
441
+ const ensureNode = (filePath, layer, module3) => {
442
+ if (nodeMap.has(filePath))
443
+ return;
444
+ const parts = filePath.split("/");
445
+ const fileName = parts[parts.length - 1] ?? filePath;
446
+ const label = fileName.replace(/\.(ts|tsx|js|jsx)$/, "");
447
+ const ext = fileName.match(/\.(service|controller|module|entity|repository|guard|pipe|middleware|dto|resolver|gateway)/);
448
+ nodeMap.set(filePath, {
449
+ id: filePath,
450
+ label,
451
+ module: module3 ?? void 0,
452
+ layer: layer ?? void 0,
453
+ type: ext ? ext[1] : "file",
454
+ violations: violationsByFile.get(filePath) ?? 0,
455
+ complexity: 0,
456
+ linesOfCode: fileLoc.get(filePath) ?? 0,
457
+ isHotspot: (violationsByFile.get(filePath) ?? 0) >= 3
458
+ });
459
+ };
460
+ for (const edge of edges) {
461
+ ensureNode(edge.source, edge.sourceLayer, edge.sourceModule);
462
+ ensureNode(edge.target, edge.targetLayer, edge.targetModule);
463
+ }
464
+ const circularFiles = /* @__PURE__ */ new Set();
465
+ for (const v of violations) {
466
+ if (v.type === "circular_dependency") {
467
+ circularFiles.add(v.file);
468
+ }
469
+ }
470
+ const graphEdges = edges.map((e) => {
471
+ const isCircular = circularFiles.has(e.source) && circularFiles.has(e.target);
472
+ const matchingViolation = violations.find((v) => v.category === "architecture" && v.file === e.source && v.message.includes(e.target.split("/").pop() ?? ""));
473
+ const isViolation = !!matchingViolation || isCircular;
474
+ let type = "import";
475
+ let violationType = null;
476
+ let message = null;
477
+ if (isCircular) {
478
+ type = "circular";
479
+ violationType = "circular-dependency";
480
+ message = `Circular dependency involving ${e.source} and ${e.target}`;
481
+ } else if (matchingViolation) {
482
+ type = "violation";
483
+ violationType = matchingViolation.type;
484
+ message = matchingViolation.message;
485
+ }
486
+ return { source: e.source, target: e.target, type, isViolation, violationType, message };
487
+ });
488
+ const moduleMap = /* @__PURE__ */ new Map();
489
+ for (const node of nodeMap.values()) {
490
+ if (node.module) {
491
+ const existing = moduleMap.get(node.module);
492
+ if (existing) {
493
+ existing.nodeCount++;
494
+ existing.violations += node.violations;
495
+ } else {
496
+ const policyMod = modules?.find((m) => m.name === node.module);
497
+ moduleMap.set(node.module, {
498
+ nodeCount: 1,
499
+ violations: node.violations,
500
+ path: policyMod?.pathPattern ?? ""
501
+ });
502
+ }
503
+ }
504
+ }
505
+ const graphModules = Array.from(moduleMap.entries()).map(([name, data]) => ({
506
+ name,
507
+ path: data.path,
508
+ nodeCount: data.nodeCount,
509
+ violations: data.violations
510
+ }));
511
+ const layerSet = /* @__PURE__ */ new Set();
512
+ for (const node of nodeMap.values()) {
513
+ if (node.layer)
514
+ layerSet.add(node.layer);
515
+ }
516
+ const layers = Array.from(layerSet).map((name) => ({
517
+ name,
518
+ color: LAYER_COLORS[name] ?? "#9E9E9E"
519
+ }));
520
+ const nodes = Array.from(nodeMap.values());
521
+ const violationEdgeCount = graphEdges.filter((e) => e.isViolation && e.type !== "circular").length;
522
+ const circularEdgeCount = graphEdges.filter((e) => e.type === "circular").length;
523
+ return {
524
+ nodes,
525
+ edges: graphEdges,
526
+ modules: graphModules,
527
+ layers,
528
+ stats: {
529
+ totalNodes: nodes.length,
530
+ totalEdges: graphEdges.length,
531
+ violationEdges: violationEdgeCount,
532
+ circularEdges: circularEdgeCount,
533
+ modules: graphModules.length,
534
+ layers: layers.length
535
+ }
536
+ };
537
+ }
538
+ }
539
+ });
540
+
402
541
  // ../../packages/shared/dist/index.js
403
542
  var require_dist = __commonJS({
404
543
  "../../packages/shared/dist/index.js"(exports2) {
@@ -420,9 +559,14 @@ var require_dist = __commonJS({
420
559
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
421
560
  };
422
561
  Object.defineProperty(exports2, "__esModule", { value: true });
562
+ exports2.buildImportGraphData = void 0;
423
563
  __exportStar(require_types(), exports2);
424
564
  __exportStar(require_constants(), exports2);
425
565
  __exportStar(require_plans(), exports2);
566
+ var graph_builder_1 = require_graph_builder();
567
+ Object.defineProperty(exports2, "buildImportGraphData", { enumerable: true, get: function() {
568
+ return graph_builder_1.buildImportGraphData;
569
+ } });
426
570
  }
427
571
  });
428
572
 
@@ -20261,6 +20405,7 @@ var os2 = __toESM(require("os"));
20261
20405
  var import_chalk = __toESM(require("chalk"));
20262
20406
  var import_policy_engine = __toESM(require_dist2());
20263
20407
  var import_analyzers = __toESM(require_dist3());
20408
+ var import_shared = __toESM(require_dist());
20264
20409
  var import_analyzers2 = __toESM(require_dist3());
20265
20410
 
20266
20411
  // src/ai/scan-summary-generator.ts
@@ -20819,6 +20964,12 @@ async function scanCommand(targetPath, options) {
20819
20964
  const blocking = result.violations.filter((v) => v.gateAction === "block");
20820
20965
  const warns = result.violations.filter((v) => v.gateAction === "warn");
20821
20966
  const byCat = countByCategory(result.violations);
20967
+ const graphData = result.importGraph && result.importGraph.length > 0 ? (0, import_shared.buildImportGraphData)(
20968
+ result.importGraph,
20969
+ result.violations,
20970
+ changedFiles.map((f) => ({ path: f.path, linesOfCode: f.content?.split("\n").length })),
20971
+ policy.modules
20972
+ ) : void 0;
20822
20973
  const reportData = {
20823
20974
  repoName: path2.basename(path2.resolve(targetPath)),
20824
20975
  violations: result.violations.map((v) => ({
@@ -20842,6 +20993,7 @@ async function scanCommand(targetPath, options) {
20842
20993
  duration: Date.now() - scanStart,
20843
20994
  usedAi: shouldRunAi,
20844
20995
  aiCreditsUsed: 0,
20996
+ importGraphData: graphData,
20845
20997
  // PR metadata from CI environment (set by GitHub Action or GitLab CI)
20846
20998
  ...process.env.RADAR_PR_NUMBER ? {
20847
20999
  prNumber: parseInt(process.env.RADAR_PR_NUMBER, 10),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Stop Node.js production crashes before merge. 47 detection patterns across 5 categories.",
5
5
  "bin": {
6
6
  "radar": "dist/index.js",