technical-debt-radar 1.11.0 → 1.12.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.
- package/dist/index.js +92 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21949,6 +21949,7 @@ async function validateCommand(options) {
|
|
|
21949
21949
|
var fs5 = __toESM(require("fs/promises"));
|
|
21950
21950
|
var fsSync2 = __toESM(require("fs"));
|
|
21951
21951
|
var path5 = __toESM(require("path"));
|
|
21952
|
+
var import_child_process2 = require("child_process");
|
|
21952
21953
|
var import_chalk4 = __toESM(require("chalk"));
|
|
21953
21954
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
21954
21955
|
var import_analyzers3 = __toESM(require_dist3());
|
|
@@ -22003,6 +22004,38 @@ async function runScan(targetPath, options) {
|
|
|
22003
22004
|
policy
|
|
22004
22005
|
};
|
|
22005
22006
|
}
|
|
22007
|
+
async function quickScanFile(targetPath, filePath, options) {
|
|
22008
|
+
try {
|
|
22009
|
+
const result = await runScan(targetPath, { ...options, file: filePath });
|
|
22010
|
+
return result.violations;
|
|
22011
|
+
} catch {
|
|
22012
|
+
return [];
|
|
22013
|
+
}
|
|
22014
|
+
}
|
|
22015
|
+
function resolveViolationPath(vFile, projectRoot) {
|
|
22016
|
+
if (path5.isAbsolute(vFile)) return vFile;
|
|
22017
|
+
return path5.resolve(projectRoot, vFile);
|
|
22018
|
+
}
|
|
22019
|
+
function detectTestCommand(projectRoot) {
|
|
22020
|
+
try {
|
|
22021
|
+
const pkg = JSON.parse(fsSync2.readFileSync(path5.join(projectRoot, "package.json"), "utf-8"));
|
|
22022
|
+
const scripts = pkg.scripts ?? {};
|
|
22023
|
+
if (scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1') return "npm test";
|
|
22024
|
+
if (scripts["test:unit"]) return "npm run test:unit";
|
|
22025
|
+
if (scripts.jest) return "npm run jest";
|
|
22026
|
+
return null;
|
|
22027
|
+
} catch {
|
|
22028
|
+
return null;
|
|
22029
|
+
}
|
|
22030
|
+
}
|
|
22031
|
+
function runTests(projectRoot, testCmd) {
|
|
22032
|
+
try {
|
|
22033
|
+
(0, import_child_process2.execSync)(testCmd, { cwd: projectRoot, stdio: "pipe", timeout: 12e4 });
|
|
22034
|
+
return true;
|
|
22035
|
+
} catch {
|
|
22036
|
+
return false;
|
|
22037
|
+
}
|
|
22038
|
+
}
|
|
22006
22039
|
function groupBy(items, key) {
|
|
22007
22040
|
const result = {};
|
|
22008
22041
|
for (const item of items) {
|
|
@@ -22034,6 +22067,16 @@ async function fixCommand(targetPath, options) {
|
|
|
22034
22067
|
console.error(import_chalk4.default.red("\u274C Invalid or expired token. Run: radar login"));
|
|
22035
22068
|
process.exit(1);
|
|
22036
22069
|
}
|
|
22070
|
+
const projectRoot = path5.resolve(targetPath);
|
|
22071
|
+
let testCmd = null;
|
|
22072
|
+
if (options.safe) {
|
|
22073
|
+
testCmd = detectTestCommand(projectRoot);
|
|
22074
|
+
if (!testCmd) {
|
|
22075
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F No test command found in package.json. --safe will skip test verification."));
|
|
22076
|
+
} else {
|
|
22077
|
+
console.log(import_chalk4.default.dim(` Test command: ${testCmd}`));
|
|
22078
|
+
}
|
|
22079
|
+
}
|
|
22037
22080
|
console.log(import_chalk4.default.blue("Scanning for violations..."));
|
|
22038
22081
|
const scanResult = await runScan(targetPath, {
|
|
22039
22082
|
config: options.config,
|
|
@@ -22054,10 +22097,11 @@ async function fixCommand(targetPath, options) {
|
|
|
22054
22097
|
}
|
|
22055
22098
|
console.log(import_chalk4.default.yellow(`Found ${violations.length} violations. Generating AI fixes...
|
|
22056
22099
|
`));
|
|
22057
|
-
const byFile = groupBy(violations, (v) => v.file);
|
|
22100
|
+
const byFile = groupBy(violations, (v) => resolveViolationPath(v.file, projectRoot));
|
|
22058
22101
|
let totalFixed = 0;
|
|
22059
22102
|
let totalSkipped = 0;
|
|
22060
22103
|
let totalFailed = 0;
|
|
22104
|
+
let totalReverted = 0;
|
|
22061
22105
|
let totalCreditsUsed = 0;
|
|
22062
22106
|
let creditsRemaining = -1;
|
|
22063
22107
|
let fixCount = 0;
|
|
@@ -22067,15 +22111,16 @@ async function fixCommand(targetPath, options) {
|
|
|
22067
22111
|
const orm = scanResult.config?.stack?.orm || "Prisma";
|
|
22068
22112
|
for (const [filePath, fileViolations] of Object.entries(byFile)) {
|
|
22069
22113
|
if (quit) break;
|
|
22070
|
-
const
|
|
22114
|
+
const absPath = path5.resolve(filePath);
|
|
22115
|
+
const relativePath = path5.relative(projectRoot, absPath);
|
|
22071
22116
|
console.log(import_chalk4.default.bold(`
|
|
22072
22117
|
${relativePath} (${fileViolations.length} violations)
|
|
22073
22118
|
`));
|
|
22074
22119
|
let fileContent;
|
|
22075
22120
|
try {
|
|
22076
|
-
fileContent = await fs5.readFile(
|
|
22121
|
+
fileContent = await fs5.readFile(absPath, "utf-8");
|
|
22077
22122
|
} catch {
|
|
22078
|
-
console.log(import_chalk4.default.red(` Could not read file: ${
|
|
22123
|
+
console.log(import_chalk4.default.red(` Could not read file: ${absPath}`));
|
|
22079
22124
|
totalFailed += fileViolations.length;
|
|
22080
22125
|
continue;
|
|
22081
22126
|
}
|
|
@@ -22164,9 +22209,9 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22164
22209
|
if (options.dryRun) {
|
|
22165
22210
|
console.log(import_chalk4.default.dim(" [dry-run] Would apply this fix"));
|
|
22166
22211
|
totalSkipped += lineViolations.length;
|
|
22167
|
-
} else if (applyAll || options.auto
|
|
22212
|
+
} else if (applyAll || options.auto) {
|
|
22168
22213
|
shouldApply = true;
|
|
22169
|
-
console.log(import_chalk4.default.green(" Auto-applying..."));
|
|
22214
|
+
if (!options.auto) console.log(import_chalk4.default.green(" Auto-applying (all)..."));
|
|
22170
22215
|
} else {
|
|
22171
22216
|
const { action } = await import_inquirer2.default.prompt([
|
|
22172
22217
|
{
|
|
@@ -22196,24 +22241,52 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22196
22241
|
}
|
|
22197
22242
|
}
|
|
22198
22243
|
if (shouldApply) {
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
console.log(import_chalk4.default.green(`
|
|
22202
|
-
|
|
22244
|
+
const originalContent = options.safe ? fileContent : null;
|
|
22245
|
+
applyFix(absPath, fix);
|
|
22246
|
+
console.log(import_chalk4.default.green(` Applied fix (${lineViolations.length} violation${lineViolations.length > 1 ? "s" : ""})`));
|
|
22247
|
+
const remaining = await quickScanFile(targetPath, absPath, {
|
|
22248
|
+
config: options.config,
|
|
22249
|
+
rules: options.rules
|
|
22250
|
+
});
|
|
22251
|
+
const stillPresent = remaining.some(
|
|
22252
|
+
(rv) => lineViolations.some(
|
|
22253
|
+
(lv) => rv.ruleId === lv.ruleId && rv.file === lv.file && Math.abs(rv.line - lv.line) <= 3
|
|
22254
|
+
)
|
|
22255
|
+
);
|
|
22256
|
+
if (stillPresent) {
|
|
22257
|
+
console.log(import_chalk4.default.yellow(" \u26A0\uFE0F Fix applied but violation persists \u2014 manual review needed"));
|
|
22258
|
+
} else {
|
|
22259
|
+
console.log(import_chalk4.default.green(" \u2705 Violation resolved"));
|
|
22260
|
+
}
|
|
22261
|
+
if (options.safe && testCmd) {
|
|
22262
|
+
console.log(import_chalk4.default.dim(` Running tests: ${testCmd}...`));
|
|
22263
|
+
const testsPassed = runTests(projectRoot, testCmd);
|
|
22264
|
+
if (testsPassed) {
|
|
22265
|
+
console.log(import_chalk4.default.green(" \u2705 Tests passed"));
|
|
22266
|
+
totalFixed += lineViolations.length;
|
|
22267
|
+
} else {
|
|
22268
|
+
console.log(import_chalk4.default.red(" \u274C Tests failed \u2014 reverting fix"));
|
|
22269
|
+
fsSync2.writeFileSync(absPath, originalContent, "utf-8");
|
|
22270
|
+
totalReverted += lineViolations.length;
|
|
22271
|
+
}
|
|
22272
|
+
} else {
|
|
22273
|
+
totalFixed += lineViolations.length;
|
|
22274
|
+
}
|
|
22275
|
+
fileContent = await fs5.readFile(absPath, "utf-8");
|
|
22203
22276
|
lines = fileContent.split("\n");
|
|
22204
22277
|
}
|
|
22205
22278
|
} catch (error) {
|
|
22206
22279
|
const message = error instanceof Error ? error.message : String(error);
|
|
22207
22280
|
if (message.includes("403") && message.includes("credits exhausted")) {
|
|
22208
|
-
console.log(import_chalk4.default.red(
|
|
22281
|
+
console.log(import_chalk4.default.red(" \u274C AI credits exhausted. Upgrade for more: radar upgrade"));
|
|
22209
22282
|
quit = true;
|
|
22210
22283
|
break;
|
|
22211
22284
|
} else if (message.includes("403") && message.includes("Solo")) {
|
|
22212
|
-
console.log(import_chalk4.default.red(
|
|
22285
|
+
console.log(import_chalk4.default.red(" \u274C radar fix requires Solo plan or higher. Upgrade: radar upgrade"));
|
|
22213
22286
|
quit = true;
|
|
22214
22287
|
break;
|
|
22215
22288
|
} else if (message.includes("401")) {
|
|
22216
|
-
console.log(import_chalk4.default.red(
|
|
22289
|
+
console.log(import_chalk4.default.red(" \u274C Authentication failed. Run: radar login"));
|
|
22217
22290
|
quit = true;
|
|
22218
22291
|
break;
|
|
22219
22292
|
} else {
|
|
@@ -22227,16 +22300,17 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22227
22300
|
console.log(import_chalk4.default.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
22228
22301
|
console.log(import_chalk4.default.bold(" FIX SUMMARY"));
|
|
22229
22302
|
console.log(import_chalk4.default.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
22230
|
-
console.log(import_chalk4.default.green(` Fixed:
|
|
22231
|
-
console.log(import_chalk4.default.yellow(` Skipped:
|
|
22232
|
-
if (
|
|
22303
|
+
console.log(import_chalk4.default.green(` Fixed: ${totalFixed}`));
|
|
22304
|
+
console.log(import_chalk4.default.yellow(` Skipped: ${totalSkipped}`));
|
|
22305
|
+
if (totalReverted > 0) console.log(import_chalk4.default.red(` Reverted: ${totalReverted} (tests failed)`));
|
|
22306
|
+
if (totalFailed > 0) console.log(import_chalk4.default.red(` Failed: ${totalFailed}`));
|
|
22233
22307
|
console.log(import_chalk4.default.cyan(` AI credits used: ${totalCreditsUsed}`));
|
|
22234
22308
|
if (creditsRemaining >= 0) {
|
|
22235
22309
|
console.log(import_chalk4.default.cyan(` Credits remaining: ${creditsRemaining}`));
|
|
22236
22310
|
}
|
|
22237
22311
|
console.log("");
|
|
22238
22312
|
if (totalFixed > 0 && !options.dryRun) {
|
|
22239
|
-
console.log(import_chalk4.default.blue("Re-scanning to verify fixes...\n"));
|
|
22313
|
+
console.log(import_chalk4.default.blue("Re-scanning to verify all fixes...\n"));
|
|
22240
22314
|
const newResult = await runScan(targetPath, {
|
|
22241
22315
|
config: options.config,
|
|
22242
22316
|
rules: options.rules,
|
|
@@ -22627,7 +22701,7 @@ program.command("check <file>").description("Check a single file against policy"
|
|
|
22627
22701
|
program.command("init").description("Generate radar.yml + rules.yml from project structure").option("--path <dir>", "Target directory", ".").option("--architecture <pattern>", "Force architecture: ddd, hexagonal, clean, layered, mvc, event-driven").option("--regenerate-rules", "Regenerate rules.yml from existing radar.yml", false).option("--no-ai", "Skip AI refinement (deterministic only)").action(async (options) => {
|
|
22628
22702
|
await initCommand(options);
|
|
22629
22703
|
});
|
|
22630
|
-
program.command("fix <path>").description("AI-powered fix: generates code diffs, applies with confirmation").option("-c, --config <path>", "Path to radar.yml", "./radar.yml").option("-r, --rules <path>", "Path to rules.yml (default: ./rules.yml)").option("--severity <level>", "Only include: critical, all", "all").option("--dry-run", "Show fixes without applying", false).option("--auto", "Apply all
|
|
22704
|
+
program.command("fix <path>").description("AI-powered fix: generates code diffs, applies with confirmation").option("-c, --config <path>", "Path to radar.yml", "./radar.yml").option("-r, --rules <path>", "Path to rules.yml (default: ./rules.yml)").option("--severity <level>", "Only include: critical, all", "all").option("--dry-run", "Show fixes without applying", false).option("--auto", "Apply all fixes automatically without asking", false).option("--safe", "Run tests after each fix, revert on failure", false).option("--file <file>", "Fix single file only").option("--max-fixes <count>", "Max number of AI fixes to generate (0 = unlimited)", "0").action(async (targetPath, options) => {
|
|
22631
22705
|
await fixCommand(targetPath, {
|
|
22632
22706
|
...options,
|
|
22633
22707
|
maxFixes: parseInt(options.maxFixes, 10)
|