technical-debt-radar 1.12.0 → 1.13.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 +145 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ var require_credits = __commonJS({
161
161
  (function(AiOperation2) {
162
162
  AiOperation2["FIX"] = "fix";
163
163
  AiOperation2["FIX_GROUPED"] = "fix-grouped";
164
+ AiOperation2["FIX_MULTI"] = "fix-multi";
164
165
  AiOperation2["SCAN_SUMMARY"] = "scan-summary";
165
166
  AiOperation2["PR_COMMENT"] = "pr-comment";
166
167
  AiOperation2["CROSS_FILE"] = "cross-file";
@@ -168,6 +169,7 @@ var require_credits = __commonJS({
168
169
  exports2.CREDIT_COSTS = {
169
170
  [AiOperation.FIX]: 1,
170
171
  [AiOperation.FIX_GROUPED]: 1,
172
+ [AiOperation.FIX_MULTI]: 3,
171
173
  [AiOperation.SCAN_SUMMARY]: 1,
172
174
  [AiOperation.PR_COMMENT]: 3,
173
175
  [AiOperation.CROSS_FILE]: 5
@@ -20737,6 +20739,12 @@ var RadarApiClient = class _RadarApiClient {
20737
20739
  body: JSON.stringify(data)
20738
20740
  });
20739
20741
  }
20742
+ async requestMultiFix(data) {
20743
+ return this.fetch("/cli/ai/fix-multi", {
20744
+ method: "POST",
20745
+ body: JSON.stringify(data)
20746
+ });
20747
+ }
20740
20748
  };
20741
20749
 
20742
20750
  // src/commands/scan.ts
@@ -22045,6 +22053,102 @@ function groupBy(items, key) {
22045
22053
  }
22046
22054
  return result;
22047
22055
  }
22056
+ async function handleMultiFileFix(client, violation, violationFile, projectRoot, framework, architecture, filesToChange, options) {
22057
+ console.log(import_chalk4.default.magenta.bold("\n MULTI-FILE FIX"));
22058
+ console.log(import_chalk4.default.dim(" Gathering file contents..."));
22059
+ const files = {};
22060
+ const allPaths = [violationFile, ...filesToChange.map((f) => resolveViolationPath(f, projectRoot))];
22061
+ for (const fp of allPaths) {
22062
+ try {
22063
+ files[fp] = await fs5.readFile(fp, "utf-8");
22064
+ } catch {
22065
+ console.log(import_chalk4.default.yellow(` Could not read: ${fp} \u2014 skipping`));
22066
+ }
22067
+ }
22068
+ if (Object.keys(files).length < 2) {
22069
+ console.log(import_chalk4.default.yellow(" Not enough files found for multi-file fix."));
22070
+ return;
22071
+ }
22072
+ console.log(import_chalk4.default.dim(` Sending ${Object.keys(files).length} files to Radar for analysis...`));
22073
+ const result = await client.requestMultiFix({
22074
+ violation: {
22075
+ file: violation.file,
22076
+ line: violation.line,
22077
+ ruleId: violation.ruleId,
22078
+ message: violation.message,
22079
+ severity: violation.severity,
22080
+ category: violation.category
22081
+ },
22082
+ files,
22083
+ framework,
22084
+ architecture
22085
+ });
22086
+ console.log("");
22087
+ if (result.explanation) {
22088
+ console.log(import_chalk4.default.white.bold(" WHY:"));
22089
+ console.log(import_chalk4.default.white(` ${result.explanation}`));
22090
+ }
22091
+ if (result.risk) {
22092
+ console.log(import_chalk4.default.yellow(` RISK: ${result.risk}`));
22093
+ }
22094
+ if (result.pattern) {
22095
+ console.log(import_chalk4.default.cyan(` PATTERN: ${result.pattern}`));
22096
+ }
22097
+ console.log("");
22098
+ for (let i = 0; i < result.changes.length; i++) {
22099
+ const change = result.changes[i];
22100
+ const rel = path5.relative(projectRoot, resolveViolationPath(change.filePath, projectRoot));
22101
+ console.log(import_chalk4.default.bold(` FILE ${i + 1}: ${rel}`));
22102
+ console.log(import_chalk4.default.dim(` ${change.description}`));
22103
+ const beforeLines = change.before.split("\n");
22104
+ const afterLines = change.after.split("\n");
22105
+ let diffShown = 0;
22106
+ for (let j = 0; j < Math.max(beforeLines.length, afterLines.length) && diffShown < 10; j++) {
22107
+ if (beforeLines[j] !== afterLines[j]) {
22108
+ if (beforeLines[j]) console.log(import_chalk4.default.red(` - ${beforeLines[j]}`));
22109
+ if (afterLines[j]) console.log(import_chalk4.default.green(` + ${afterLines[j]}`));
22110
+ diffShown++;
22111
+ }
22112
+ }
22113
+ if (diffShown >= 10) console.log(import_chalk4.default.dim(" ... (more changes)"));
22114
+ console.log("");
22115
+ }
22116
+ console.log(import_chalk4.default.cyan(` ${result.creditsUsed} AI credits used (${result.creditsRemaining} remaining)`));
22117
+ if (options.dryRun) {
22118
+ console.log(import_chalk4.default.dim(" [dry-run] Would apply all changes"));
22119
+ return;
22120
+ }
22121
+ const originals = {};
22122
+ for (const change of result.changes) {
22123
+ const fp = resolveViolationPath(change.filePath, projectRoot);
22124
+ try {
22125
+ originals[fp] = await fs5.readFile(fp, "utf-8");
22126
+ } catch {
22127
+ originals[fp] = "";
22128
+ }
22129
+ }
22130
+ for (const change of result.changes) {
22131
+ const fp = resolveViolationPath(change.filePath, projectRoot);
22132
+ fsSync2.writeFileSync(fp, change.after, "utf-8");
22133
+ }
22134
+ console.log(import_chalk4.default.green(` Applied changes to ${result.changes.length} files`));
22135
+ if (options.safe) {
22136
+ const testCmd = detectTestCommand(projectRoot);
22137
+ if (testCmd) {
22138
+ console.log(import_chalk4.default.dim(` Running tests: ${testCmd}...`));
22139
+ const passed = runTests(projectRoot, testCmd);
22140
+ if (passed) {
22141
+ console.log(import_chalk4.default.green(" \u2705 Tests passed"));
22142
+ } else {
22143
+ console.log(import_chalk4.default.red(" \u274C Tests failed \u2014 reverting all changes"));
22144
+ for (const [fp, content] of Object.entries(originals)) {
22145
+ fsSync2.writeFileSync(fp, content, "utf-8");
22146
+ }
22147
+ return;
22148
+ }
22149
+ }
22150
+ }
22151
+ }
22048
22152
  async function fixCommand(targetPath, options) {
22049
22153
  const client = RadarApiClient.fromConfigOrEnv();
22050
22154
  if (!client) {
@@ -22195,13 +22299,29 @@ ${relativePath} (${fileViolations.length} violations)
22195
22299
  totalCreditsUsed += fix.creditsUsed;
22196
22300
  creditsRemaining = fix.creditsRemaining;
22197
22301
  fixCount++;
22302
+ if (fix.explanation) {
22303
+ console.log(import_chalk4.default.white.bold(" WHY THIS IS A VIOLATION:"));
22304
+ console.log(import_chalk4.default.white(` ${fix.explanation}`));
22305
+ }
22306
+ if (fix.risk) {
22307
+ console.log(import_chalk4.default.yellow(` RISK: ${fix.risk}`));
22308
+ }
22309
+ if (fix.pattern) {
22310
+ console.log(import_chalk4.default.cyan(` PATTERN: ${fix.pattern}`));
22311
+ }
22312
+ if (fix.multiFile && fix.filesToChange && fix.filesToChange.length > 0) {
22313
+ console.log(import_chalk4.default.magenta(` MULTI-FILE: Also needs changes in ${fix.filesToChange.length} other file(s):`));
22314
+ for (const f of fix.filesToChange) {
22315
+ console.log(import_chalk4.default.magenta(` \u2192 ${f}`));
22316
+ }
22317
+ }
22318
+ console.log("");
22198
22319
  console.log(import_chalk4.default.red(" BEFORE:"));
22199
22320
  fix.originalCode.split("\n").forEach((l) => console.log(import_chalk4.default.red(` - ${l}`)));
22200
22321
  console.log("");
22201
22322
  console.log(import_chalk4.default.green(" AFTER:"));
22202
22323
  fix.fixedCode.split("\n").forEach((l) => console.log(import_chalk4.default.green(` + ${l}`)));
22203
22324
  console.log("");
22204
- console.log(import_chalk4.default.dim(` ${fix.explanation}`));
22205
22325
  console.log(import_chalk4.default.dim(` Confidence: ${fix.confidence}`));
22206
22326
  console.log(import_chalk4.default.cyan(` ${fix.creditsUsed} AI credit used (${fix.creditsRemaining} remaining)`));
22207
22327
  console.log("");
@@ -22213,17 +22333,19 @@ ${relativePath} (${fileViolations.length} violations)
22213
22333
  shouldApply = true;
22214
22334
  if (!options.auto) console.log(import_chalk4.default.green(" Auto-applying (all)..."));
22215
22335
  } else {
22336
+ const choices = [
22337
+ { name: "Yes - apply this fix", value: "yes" },
22338
+ ...fix.multiFile && fix.filesToChange?.length ? [{ name: `Multi-file fix (${fix.filesToChange.length + 1} files, 3 credits)`, value: "multi" }] : [],
22339
+ { name: "No - skip", value: "no" },
22340
+ { name: "All - apply all remaining fixes", value: "all" },
22341
+ { name: "Quit - stop fixing", value: "quit" }
22342
+ ];
22216
22343
  const { action } = await import_inquirer2.default.prompt([
22217
22344
  {
22218
22345
  type: "list",
22219
22346
  name: "action",
22220
22347
  message: `Apply this fix? (${lineViolations.length} violation${lineViolations.length > 1 ? "s" : ""})`,
22221
- choices: [
22222
- { name: "Yes - apply this fix", value: "yes" },
22223
- { name: "No - skip", value: "no" },
22224
- { name: "All - apply all remaining fixes", value: "all" },
22225
- { name: "Quit - stop fixing", value: "quit" }
22226
- ]
22348
+ choices
22227
22349
  }
22228
22350
  ]);
22229
22351
  if (action === "quit") {
@@ -22239,6 +22361,22 @@ ${relativePath} (${fileViolations.length} violations)
22239
22361
  totalSkipped += lineViolations.length;
22240
22362
  continue;
22241
22363
  }
22364
+ if (action === "multi") {
22365
+ await handleMultiFileFix(
22366
+ client,
22367
+ lineViolations[0],
22368
+ absPath,
22369
+ projectRoot,
22370
+ framework,
22371
+ scanResult.config?.architecture,
22372
+ fix.filesToChange ?? [],
22373
+ options
22374
+ );
22375
+ totalFixed += lineViolations.length;
22376
+ fileContent = await fs5.readFile(absPath, "utf-8");
22377
+ lines = fileContent.split("\n");
22378
+ continue;
22379
+ }
22242
22380
  }
22243
22381
  if (shouldApply) {
22244
22382
  const originalContent = options.safe ? fileContent : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.12.0",
3
+ "version": "1.13.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",