technical-debt-radar 1.12.0 → 1.13.1
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 +148 -9
- 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
|
|
@@ -21954,7 +21962,8 @@ var import_chalk4 = __toESM(require("chalk"));
|
|
|
21954
21962
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
21955
21963
|
var import_analyzers3 = __toESM(require_dist3());
|
|
21956
21964
|
function applyFix(filePath, fix) {
|
|
21957
|
-
const
|
|
21965
|
+
const fullPath = path5.isAbsolute(filePath) ? filePath : path5.resolve(filePath);
|
|
21966
|
+
const content = fsSync2.readFileSync(fullPath, "utf-8");
|
|
21958
21967
|
const lines = content.split("\n");
|
|
21959
21968
|
const fixedLines = fix.fixedCode.split("\n");
|
|
21960
21969
|
const start = fix.startLine - 1;
|
|
@@ -21974,7 +21983,7 @@ function applyFix(filePath, fix) {
|
|
|
21974
21983
|
}
|
|
21975
21984
|
}
|
|
21976
21985
|
}
|
|
21977
|
-
fsSync2.writeFileSync(
|
|
21986
|
+
fsSync2.writeFileSync(fullPath, lines.join("\n"), "utf-8");
|
|
21978
21987
|
}
|
|
21979
21988
|
async function runScan(targetPath, options) {
|
|
21980
21989
|
const { compiled: policy } = await loadPolicy(options.config, options.rules);
|
|
@@ -22045,6 +22054,102 @@ function groupBy(items, key) {
|
|
|
22045
22054
|
}
|
|
22046
22055
|
return result;
|
|
22047
22056
|
}
|
|
22057
|
+
async function handleMultiFileFix(client, violation, violationFile, projectRoot, framework, architecture, filesToChange, options) {
|
|
22058
|
+
console.log(import_chalk4.default.magenta.bold("\n MULTI-FILE FIX"));
|
|
22059
|
+
console.log(import_chalk4.default.dim(" Gathering file contents..."));
|
|
22060
|
+
const files = {};
|
|
22061
|
+
const allPaths = [violationFile, ...filesToChange.map((f) => resolveViolationPath(f, projectRoot))];
|
|
22062
|
+
for (const fp of allPaths) {
|
|
22063
|
+
try {
|
|
22064
|
+
files[fp] = await fs5.readFile(fp, "utf-8");
|
|
22065
|
+
} catch {
|
|
22066
|
+
console.log(import_chalk4.default.yellow(` Could not read: ${fp} \u2014 skipping`));
|
|
22067
|
+
}
|
|
22068
|
+
}
|
|
22069
|
+
if (Object.keys(files).length < 2) {
|
|
22070
|
+
console.log(import_chalk4.default.yellow(" Not enough files found for multi-file fix."));
|
|
22071
|
+
return;
|
|
22072
|
+
}
|
|
22073
|
+
console.log(import_chalk4.default.dim(` Sending ${Object.keys(files).length} files to Radar for analysis...`));
|
|
22074
|
+
const result = await client.requestMultiFix({
|
|
22075
|
+
violation: {
|
|
22076
|
+
file: violation.file,
|
|
22077
|
+
line: violation.line,
|
|
22078
|
+
ruleId: violation.ruleId,
|
|
22079
|
+
message: violation.message,
|
|
22080
|
+
severity: violation.severity,
|
|
22081
|
+
category: violation.category
|
|
22082
|
+
},
|
|
22083
|
+
files,
|
|
22084
|
+
framework,
|
|
22085
|
+
architecture
|
|
22086
|
+
});
|
|
22087
|
+
console.log("");
|
|
22088
|
+
if (result.explanation) {
|
|
22089
|
+
console.log(import_chalk4.default.white.bold(" WHY:"));
|
|
22090
|
+
console.log(import_chalk4.default.white(` ${result.explanation}`));
|
|
22091
|
+
}
|
|
22092
|
+
if (result.risk) {
|
|
22093
|
+
console.log(import_chalk4.default.yellow(` RISK: ${result.risk}`));
|
|
22094
|
+
}
|
|
22095
|
+
if (result.pattern) {
|
|
22096
|
+
console.log(import_chalk4.default.cyan(` PATTERN: ${result.pattern}`));
|
|
22097
|
+
}
|
|
22098
|
+
console.log("");
|
|
22099
|
+
for (let i = 0; i < result.changes.length; i++) {
|
|
22100
|
+
const change = result.changes[i];
|
|
22101
|
+
const rel = path5.relative(projectRoot, resolveViolationPath(change.filePath, projectRoot));
|
|
22102
|
+
console.log(import_chalk4.default.bold(` FILE ${i + 1}: ${rel}`));
|
|
22103
|
+
console.log(import_chalk4.default.dim(` ${change.description}`));
|
|
22104
|
+
const beforeLines = change.before.split("\n");
|
|
22105
|
+
const afterLines = change.after.split("\n");
|
|
22106
|
+
let diffShown = 0;
|
|
22107
|
+
for (let j = 0; j < Math.max(beforeLines.length, afterLines.length) && diffShown < 10; j++) {
|
|
22108
|
+
if (beforeLines[j] !== afterLines[j]) {
|
|
22109
|
+
if (beforeLines[j]) console.log(import_chalk4.default.red(` - ${beforeLines[j]}`));
|
|
22110
|
+
if (afterLines[j]) console.log(import_chalk4.default.green(` + ${afterLines[j]}`));
|
|
22111
|
+
diffShown++;
|
|
22112
|
+
}
|
|
22113
|
+
}
|
|
22114
|
+
if (diffShown >= 10) console.log(import_chalk4.default.dim(" ... (more changes)"));
|
|
22115
|
+
console.log("");
|
|
22116
|
+
}
|
|
22117
|
+
console.log(import_chalk4.default.cyan(` ${result.creditsUsed} AI credits used (${result.creditsRemaining} remaining)`));
|
|
22118
|
+
if (options.dryRun) {
|
|
22119
|
+
console.log(import_chalk4.default.dim(" [dry-run] Would apply all changes"));
|
|
22120
|
+
return;
|
|
22121
|
+
}
|
|
22122
|
+
const originals = {};
|
|
22123
|
+
for (const change of result.changes) {
|
|
22124
|
+
const fp = resolveViolationPath(change.filePath, projectRoot);
|
|
22125
|
+
try {
|
|
22126
|
+
originals[fp] = await fs5.readFile(fp, "utf-8");
|
|
22127
|
+
} catch {
|
|
22128
|
+
originals[fp] = "";
|
|
22129
|
+
}
|
|
22130
|
+
}
|
|
22131
|
+
for (const change of result.changes) {
|
|
22132
|
+
const fp = resolveViolationPath(change.filePath, projectRoot);
|
|
22133
|
+
fsSync2.writeFileSync(fp, change.after, "utf-8");
|
|
22134
|
+
}
|
|
22135
|
+
console.log(import_chalk4.default.green(` Applied changes to ${result.changes.length} files`));
|
|
22136
|
+
if (options.safe) {
|
|
22137
|
+
const testCmd = detectTestCommand(projectRoot);
|
|
22138
|
+
if (testCmd) {
|
|
22139
|
+
console.log(import_chalk4.default.dim(` Running tests: ${testCmd}...`));
|
|
22140
|
+
const passed = runTests(projectRoot, testCmd);
|
|
22141
|
+
if (passed) {
|
|
22142
|
+
console.log(import_chalk4.default.green(" \u2705 Tests passed"));
|
|
22143
|
+
} else {
|
|
22144
|
+
console.log(import_chalk4.default.red(" \u274C Tests failed \u2014 reverting all changes"));
|
|
22145
|
+
for (const [fp, content] of Object.entries(originals)) {
|
|
22146
|
+
fsSync2.writeFileSync(fp, content, "utf-8");
|
|
22147
|
+
}
|
|
22148
|
+
return;
|
|
22149
|
+
}
|
|
22150
|
+
}
|
|
22151
|
+
}
|
|
22152
|
+
}
|
|
22048
22153
|
async function fixCommand(targetPath, options) {
|
|
22049
22154
|
const client = RadarApiClient.fromConfigOrEnv();
|
|
22050
22155
|
if (!client) {
|
|
@@ -22195,13 +22300,29 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22195
22300
|
totalCreditsUsed += fix.creditsUsed;
|
|
22196
22301
|
creditsRemaining = fix.creditsRemaining;
|
|
22197
22302
|
fixCount++;
|
|
22303
|
+
if (fix.explanation) {
|
|
22304
|
+
console.log(import_chalk4.default.white.bold(" WHY THIS IS A VIOLATION:"));
|
|
22305
|
+
console.log(import_chalk4.default.white(` ${fix.explanation}`));
|
|
22306
|
+
}
|
|
22307
|
+
if (fix.risk) {
|
|
22308
|
+
console.log(import_chalk4.default.yellow(` RISK: ${fix.risk}`));
|
|
22309
|
+
}
|
|
22310
|
+
if (fix.pattern) {
|
|
22311
|
+
console.log(import_chalk4.default.cyan(` PATTERN: ${fix.pattern}`));
|
|
22312
|
+
}
|
|
22313
|
+
if (fix.multiFile && fix.filesToChange && fix.filesToChange.length > 0) {
|
|
22314
|
+
console.log(import_chalk4.default.magenta(` MULTI-FILE: Also needs changes in ${fix.filesToChange.length} other file(s):`));
|
|
22315
|
+
for (const f of fix.filesToChange) {
|
|
22316
|
+
console.log(import_chalk4.default.magenta(` \u2192 ${f}`));
|
|
22317
|
+
}
|
|
22318
|
+
}
|
|
22319
|
+
console.log("");
|
|
22198
22320
|
console.log(import_chalk4.default.red(" BEFORE:"));
|
|
22199
22321
|
fix.originalCode.split("\n").forEach((l) => console.log(import_chalk4.default.red(` - ${l}`)));
|
|
22200
22322
|
console.log("");
|
|
22201
22323
|
console.log(import_chalk4.default.green(" AFTER:"));
|
|
22202
22324
|
fix.fixedCode.split("\n").forEach((l) => console.log(import_chalk4.default.green(` + ${l}`)));
|
|
22203
22325
|
console.log("");
|
|
22204
|
-
console.log(import_chalk4.default.dim(` ${fix.explanation}`));
|
|
22205
22326
|
console.log(import_chalk4.default.dim(` Confidence: ${fix.confidence}`));
|
|
22206
22327
|
console.log(import_chalk4.default.cyan(` ${fix.creditsUsed} AI credit used (${fix.creditsRemaining} remaining)`));
|
|
22207
22328
|
console.log("");
|
|
@@ -22213,17 +22334,19 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22213
22334
|
shouldApply = true;
|
|
22214
22335
|
if (!options.auto) console.log(import_chalk4.default.green(" Auto-applying (all)..."));
|
|
22215
22336
|
} else {
|
|
22337
|
+
const choices = [
|
|
22338
|
+
{ name: "Yes - apply this fix", value: "yes" },
|
|
22339
|
+
...fix.multiFile && fix.filesToChange?.length ? [{ name: `Multi-file fix (${fix.filesToChange.length + 1} files, 3 credits)`, value: "multi" }] : [],
|
|
22340
|
+
{ name: "No - skip", value: "no" },
|
|
22341
|
+
{ name: "All - apply all remaining fixes", value: "all" },
|
|
22342
|
+
{ name: "Quit - stop fixing", value: "quit" }
|
|
22343
|
+
];
|
|
22216
22344
|
const { action } = await import_inquirer2.default.prompt([
|
|
22217
22345
|
{
|
|
22218
22346
|
type: "list",
|
|
22219
22347
|
name: "action",
|
|
22220
22348
|
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
|
-
]
|
|
22349
|
+
choices
|
|
22227
22350
|
}
|
|
22228
22351
|
]);
|
|
22229
22352
|
if (action === "quit") {
|
|
@@ -22239,6 +22362,22 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22239
22362
|
totalSkipped += lineViolations.length;
|
|
22240
22363
|
continue;
|
|
22241
22364
|
}
|
|
22365
|
+
if (action === "multi") {
|
|
22366
|
+
await handleMultiFileFix(
|
|
22367
|
+
client,
|
|
22368
|
+
lineViolations[0],
|
|
22369
|
+
absPath,
|
|
22370
|
+
projectRoot,
|
|
22371
|
+
framework,
|
|
22372
|
+
scanResult.config?.architecture,
|
|
22373
|
+
fix.filesToChange ?? [],
|
|
22374
|
+
options
|
|
22375
|
+
);
|
|
22376
|
+
totalFixed += lineViolations.length;
|
|
22377
|
+
fileContent = await fs5.readFile(absPath, "utf-8");
|
|
22378
|
+
lines = fileContent.split("\n");
|
|
22379
|
+
continue;
|
|
22380
|
+
}
|
|
22242
22381
|
}
|
|
22243
22382
|
if (shouldApply) {
|
|
22244
22383
|
const originalContent = options.safe ? fileContent : null;
|