technical-debt-radar 1.10.0 → 1.11.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 +89 -279
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18867,7 +18867,7 @@ var require_cross_file_ai = __commonJS({
|
|
|
18867
18867
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18868
18868
|
exports2.analyzeCrossFileWithAI = analyzeCrossFileWithAI2;
|
|
18869
18869
|
var sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
18870
|
-
var
|
|
18870
|
+
var MODEL3 = "claude-sonnet-4-20250514";
|
|
18871
18871
|
var MAX_TOKENS = 3e3;
|
|
18872
18872
|
async function analyzeCrossFileWithAI2(suspects, framework, orm, apiKey) {
|
|
18873
18873
|
if (suspects.length === 0)
|
|
@@ -18922,7 +18922,7 @@ ${suspectsText}
|
|
|
18922
18922
|
For each, determine if it's a real risk. Return JSON array.`;
|
|
18923
18923
|
try {
|
|
18924
18924
|
const response = await client.messages.create({
|
|
18925
|
-
model:
|
|
18925
|
+
model: MODEL3,
|
|
18926
18926
|
max_tokens: MAX_TOKENS,
|
|
18927
18927
|
system: systemPrompt,
|
|
18928
18928
|
messages: [{ role: "user", content: userPrompt }]
|
|
@@ -20725,6 +20725,18 @@ var RadarApiClient = class _RadarApiClient {
|
|
|
20725
20725
|
body: JSON.stringify(data)
|
|
20726
20726
|
});
|
|
20727
20727
|
}
|
|
20728
|
+
async requestFix(data) {
|
|
20729
|
+
return this.fetch("/cli/ai/fix", {
|
|
20730
|
+
method: "POST",
|
|
20731
|
+
body: JSON.stringify(data)
|
|
20732
|
+
});
|
|
20733
|
+
}
|
|
20734
|
+
async requestFixGrouped(data) {
|
|
20735
|
+
return this.fetch("/cli/ai/fix-grouped", {
|
|
20736
|
+
method: "POST",
|
|
20737
|
+
body: JSON.stringify(data)
|
|
20738
|
+
});
|
|
20739
|
+
}
|
|
20728
20740
|
};
|
|
20729
20741
|
|
|
20730
20742
|
// src/commands/scan.ts
|
|
@@ -21940,200 +21952,6 @@ var path5 = __toESM(require("path"));
|
|
|
21940
21952
|
var import_chalk4 = __toESM(require("chalk"));
|
|
21941
21953
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
21942
21954
|
var import_analyzers3 = __toESM(require_dist3());
|
|
21943
|
-
|
|
21944
|
-
// src/ai/fix-generator.ts
|
|
21945
|
-
var import_sdk3 = __toESM(require("@anthropic-ai/sdk"));
|
|
21946
|
-
var MODEL3 = "claude-sonnet-4-20250514";
|
|
21947
|
-
var MAX_FIX_TOKENS = 2e3;
|
|
21948
|
-
async function generateFix(request, apiKey) {
|
|
21949
|
-
const client = new import_sdk3.default({ apiKey });
|
|
21950
|
-
const ruleHint = getRuleSpecificHints(request.violation.ruleId, request.framework);
|
|
21951
|
-
const systemPrompt = `You are a senior Node.js/TypeScript developer fixing code violations found by a static analysis tool called Technical Debt Radar.
|
|
21952
|
-
|
|
21953
|
-
RULES:
|
|
21954
|
-
- Return ONLY the fixed code snippet, not the entire file
|
|
21955
|
-
- Keep the fix minimal \u2014 change only what's needed to resolve the violation
|
|
21956
|
-
- Preserve existing code style, indentation, and conventions
|
|
21957
|
-
- Add imports if needed (show them separately)
|
|
21958
|
-
- Use the project's framework patterns (${request.framework}, ${request.orm})
|
|
21959
|
-
${ruleHint ? `
|
|
21960
|
-
SPECIFIC GUIDANCE FOR THIS RULE:
|
|
21961
|
-
${ruleHint}` : ""}
|
|
21962
|
-
|
|
21963
|
-
RESPONSE FORMAT (JSON only, no markdown):
|
|
21964
|
-
{
|
|
21965
|
-
"originalCode": "the exact lines that need to change",
|
|
21966
|
-
"fixedCode": "the replacement code",
|
|
21967
|
-
"explanation": "one sentence explaining the fix",
|
|
21968
|
-
"startLine": <first line number to replace>,
|
|
21969
|
-
"endLine": <last line number to replace>,
|
|
21970
|
-
"newImports": ["import { X } from 'y'"] or [],
|
|
21971
|
-
"confidence": "high" | "medium" | "low"
|
|
21972
|
-
}`;
|
|
21973
|
-
const userPrompt = `Fix this violation:
|
|
21974
|
-
|
|
21975
|
-
RULE: ${request.violation.ruleId}
|
|
21976
|
-
MESSAGE: ${request.violation.message}
|
|
21977
|
-
FILE: ${request.violation.file}
|
|
21978
|
-
LINE: ${request.violation.line}
|
|
21979
|
-
SEVERITY: ${request.violation.severity}
|
|
21980
|
-
|
|
21981
|
-
SURROUNDING CODE (lines ${Math.max(1, request.violation.line - 15)}-${request.violation.line + 15}):
|
|
21982
|
-
\`\`\`typescript
|
|
21983
|
-
${request.surroundingCode}
|
|
21984
|
-
\`\`\`
|
|
21985
|
-
|
|
21986
|
-
FULL FILE (for context):
|
|
21987
|
-
\`\`\`typescript
|
|
21988
|
-
${request.fileContent}
|
|
21989
|
-
\`\`\`
|
|
21990
|
-
|
|
21991
|
-
Generate the minimal fix. Return JSON only.`;
|
|
21992
|
-
const response = await client.messages.create({
|
|
21993
|
-
model: MODEL3,
|
|
21994
|
-
max_tokens: MAX_FIX_TOKENS,
|
|
21995
|
-
system: systemPrompt,
|
|
21996
|
-
messages: [{ role: "user", content: userPrompt }]
|
|
21997
|
-
});
|
|
21998
|
-
const textBlock = response.content.find((b) => b.type === "text");
|
|
21999
|
-
if (!textBlock || textBlock.type !== "text") {
|
|
22000
|
-
throw new Error("No text content in AI response");
|
|
22001
|
-
}
|
|
22002
|
-
const cleaned = textBlock.text.replace(/^```json\s*/m, "").replace(/^```\s*/m, "").replace(/```\s*$/m, "").trim();
|
|
22003
|
-
const parsed = JSON.parse(cleaned);
|
|
22004
|
-
return {
|
|
22005
|
-
violation: request.violation,
|
|
22006
|
-
originalCode: String(parsed.originalCode || ""),
|
|
22007
|
-
fixedCode: String(parsed.fixedCode || ""),
|
|
22008
|
-
explanation: String(parsed.explanation || ""),
|
|
22009
|
-
startLine: Number(parsed.startLine),
|
|
22010
|
-
endLine: Number(parsed.endLine),
|
|
22011
|
-
newImports: Array.isArray(parsed.newImports) ? parsed.newImports.map(String) : [],
|
|
22012
|
-
confidence: validateConfidence(parsed.confidence)
|
|
22013
|
-
};
|
|
22014
|
-
}
|
|
22015
|
-
async function generateGroupedFix(request, apiKey) {
|
|
22016
|
-
const client = new import_sdk3.default({ apiKey });
|
|
22017
|
-
const ruleHints = request.violations.map((v) => getRuleSpecificHints(v.ruleId, request.framework)).filter(Boolean);
|
|
22018
|
-
const hintsBlock = ruleHints.length > 0 ? `
|
|
22019
|
-
SPECIFIC GUIDANCE:
|
|
22020
|
-
${ruleHints.join("\n")}` : "";
|
|
22021
|
-
const systemPrompt = `You are a senior Node.js/TypeScript developer fixing code violations found by a static analysis tool called Technical Debt Radar.
|
|
22022
|
-
|
|
22023
|
-
RULES:
|
|
22024
|
-
- Return ONLY the fixed code snippet, not the entire file
|
|
22025
|
-
- Keep the fix minimal \u2014 change only what's needed to resolve ALL violations listed
|
|
22026
|
-
- Preserve existing code style, indentation, and conventions
|
|
22027
|
-
- Add imports if needed (show them separately)
|
|
22028
|
-
- Use the project's framework patterns (${request.framework}, ${request.orm})
|
|
22029
|
-
- You MUST address EVERY violation listed \u2014 do not skip any
|
|
22030
|
-
${hintsBlock}
|
|
22031
|
-
|
|
22032
|
-
RESPONSE FORMAT (JSON only, no markdown):
|
|
22033
|
-
{
|
|
22034
|
-
"originalCode": "the exact lines that need to change",
|
|
22035
|
-
"fixedCode": "the replacement code",
|
|
22036
|
-
"explanation": "one sentence explaining the fix",
|
|
22037
|
-
"startLine": <first line number to replace>,
|
|
22038
|
-
"endLine": <last line number to replace>,
|
|
22039
|
-
"newImports": ["import { X } from 'y'"] or [],
|
|
22040
|
-
"confidence": "high" | "medium" | "low"
|
|
22041
|
-
}`;
|
|
22042
|
-
const violationList = request.violations.map((v, i) => ` ${i + 1}. [${v.ruleId}] ${v.message} (line ${v.line}, severity: ${v.severity})`).join("\n");
|
|
22043
|
-
const refLine = request.violations[0].line;
|
|
22044
|
-
const userPrompt = `Fix these ${request.violations.length} violations in the same code section:
|
|
22045
|
-
|
|
22046
|
-
${violationList}
|
|
22047
|
-
|
|
22048
|
-
FILE: ${request.violations[0].file}
|
|
22049
|
-
|
|
22050
|
-
SURROUNDING CODE (lines ${Math.max(1, refLine - 15)}-${refLine + 15}):
|
|
22051
|
-
\`\`\`typescript
|
|
22052
|
-
${request.surroundingCode}
|
|
22053
|
-
\`\`\`
|
|
22054
|
-
|
|
22055
|
-
FULL FILE (for context):
|
|
22056
|
-
\`\`\`typescript
|
|
22057
|
-
${request.fileContent}
|
|
22058
|
-
\`\`\`
|
|
22059
|
-
|
|
22060
|
-
Generate ONE fix that addresses ALL ${request.violations.length} violations. Return JSON only.`;
|
|
22061
|
-
const response = await client.messages.create({
|
|
22062
|
-
model: MODEL3,
|
|
22063
|
-
max_tokens: MAX_FIX_TOKENS,
|
|
22064
|
-
system: systemPrompt,
|
|
22065
|
-
messages: [{ role: "user", content: userPrompt }]
|
|
22066
|
-
});
|
|
22067
|
-
const textBlock = response.content.find((b) => b.type === "text");
|
|
22068
|
-
if (!textBlock || textBlock.type !== "text") {
|
|
22069
|
-
throw new Error("No text content in AI response");
|
|
22070
|
-
}
|
|
22071
|
-
const cleaned = textBlock.text.replace(/^```json\s*/m, "").replace(/^```\s*/m, "").replace(/```\s*$/m, "").trim();
|
|
22072
|
-
const parsed = JSON.parse(cleaned);
|
|
22073
|
-
return {
|
|
22074
|
-
violation: request.violations[0],
|
|
22075
|
-
originalCode: String(parsed.originalCode || ""),
|
|
22076
|
-
fixedCode: String(parsed.fixedCode || ""),
|
|
22077
|
-
explanation: String(parsed.explanation || ""),
|
|
22078
|
-
startLine: Number(parsed.startLine),
|
|
22079
|
-
endLine: Number(parsed.endLine),
|
|
22080
|
-
newImports: Array.isArray(parsed.newImports) ? parsed.newImports.map(String) : [],
|
|
22081
|
-
confidence: validateConfidence(parsed.confidence)
|
|
22082
|
-
};
|
|
22083
|
-
}
|
|
22084
|
-
function validateConfidence(value) {
|
|
22085
|
-
if (value === "high" || value === "medium" || value === "low") {
|
|
22086
|
-
return value;
|
|
22087
|
-
}
|
|
22088
|
-
return "medium";
|
|
22089
|
-
}
|
|
22090
|
-
function getRuleSpecificHints(ruleId, framework) {
|
|
22091
|
-
const hints = {
|
|
22092
|
-
"missing-null-guard": `Add a null/undefined check after the query. In ${framework === "NestJS" ? "NestJS, throw new NotFoundException()" : 'Express, return res.status(404).json({ error: "Not found" })'}. Check BEFORE any property access.`,
|
|
22093
|
-
"sync-fs-in-handler": "Replace fs.readFileSync/writeFileSync with await fs.promises.readFile/writeFile. Make the function async if needed.",
|
|
22094
|
-
"sync-crypto": "Replace crypto.pbkdf2Sync with the async crypto.pbkdf2 wrapped in a Promise, or use argon2/bcrypt async alternatives.",
|
|
22095
|
-
"sync-compression": "Replace zlib.deflateSync/gzipSync with the async stream or callback versions.",
|
|
22096
|
-
"redos-vulnerable-regex": "Rewrite the regex to avoid nested quantifiers. Remove patterns like (a+)+, (a|b)*, or use a regex linting library.",
|
|
22097
|
-
"n-plus-one-query": "Replace the loop + individual query pattern with a batch query. Use include/join/populate for relations, or WHERE IN for ID lists.",
|
|
22098
|
-
"unbounded-find-many": "Add pagination: { take: 20, skip: offset } for Prisma, { limit: 20, offset } for Sequelize, .limit(20) for Mongoose/Drizzle/Knex.",
|
|
22099
|
-
"find-many-no-where": "Add a where/filter clause to prevent full table scans.",
|
|
22100
|
-
"raw-sql-no-limit": "Add LIMIT clause to the SQL query.",
|
|
22101
|
-
"unhandled-promise-rejection": "Add await before the async call, or explicitly handle the promise with .catch().",
|
|
22102
|
-
"external-call-no-timeout": "Add { timeout: 10000 } (10s) to the HTTP client config. For axios: axios.post(url, data, { timeout: 10000 }). For fetch: use AbortController.",
|
|
22103
|
-
"empty-catch-block": "Add error logging inside the catch block: console.error(error) or logger.error(error).",
|
|
22104
|
-
"retry-without-backoff": "Replace fixed delay with exponential backoff: delay * Math.pow(2, attempt).",
|
|
22105
|
-
"missing-error-logging": "Add a logging statement in the catch block.",
|
|
22106
|
-
"missing-try-catch": "Wrap the multiple await calls in a try/catch block with proper error handling.",
|
|
22107
|
-
"transaction-no-timeout": "Add a timeout option to the transaction: { timeout: 5000 }.",
|
|
22108
|
-
"unfiltered-count-large-table": "Add a where/filter clause to the count query."
|
|
22109
|
-
};
|
|
22110
|
-
return hints[ruleId] || "";
|
|
22111
|
-
}
|
|
22112
|
-
|
|
22113
|
-
// src/commands/fix.ts
|
|
22114
|
-
function buildFixInstruction(violation) {
|
|
22115
|
-
const lines = [];
|
|
22116
|
-
lines.push(`In ${violation.file} line ${violation.line}:`);
|
|
22117
|
-
if (violation.suggestion) {
|
|
22118
|
-
lines.push(` ${violation.suggestion}`);
|
|
22119
|
-
} else {
|
|
22120
|
-
lines.push(` ${violation.message}`);
|
|
22121
|
-
}
|
|
22122
|
-
return lines.join("\n");
|
|
22123
|
-
}
|
|
22124
|
-
function formatFixPrompt(violations) {
|
|
22125
|
-
if (violations.length === 0) {
|
|
22126
|
-
return "No issues found \u2014 nothing to fix.";
|
|
22127
|
-
}
|
|
22128
|
-
const lines = [];
|
|
22129
|
-
lines.push("Fix these issues in my codebase:");
|
|
22130
|
-
lines.push("");
|
|
22131
|
-
violations.forEach((v, i) => {
|
|
22132
|
-
lines.push(`${i + 1}. ${buildFixInstruction(v)}`);
|
|
22133
|
-
lines.push("");
|
|
22134
|
-
});
|
|
22135
|
-
return lines.join("\n").trimEnd();
|
|
22136
|
-
}
|
|
22137
21955
|
function applyFix(filePath, fix) {
|
|
22138
21956
|
const content = fsSync2.readFileSync(filePath, "utf-8");
|
|
22139
21957
|
const lines = content.split("\n");
|
|
@@ -22185,33 +22003,6 @@ async function runScan(targetPath, options) {
|
|
|
22185
22003
|
policy
|
|
22186
22004
|
};
|
|
22187
22005
|
}
|
|
22188
|
-
async function runTextOnlyFix(targetPath, options) {
|
|
22189
|
-
const { compiled: policy } = await loadPolicy(options.config, options.rules);
|
|
22190
|
-
const files = await collectTsFiles(targetPath);
|
|
22191
|
-
if (files.length === 0) {
|
|
22192
|
-
console.log(import_chalk4.default.yellow("No .ts/.tsx files found."));
|
|
22193
|
-
process.exit(0);
|
|
22194
|
-
}
|
|
22195
|
-
const changedFiles = await Promise.all(
|
|
22196
|
-
files.map(async (filePath) => ({
|
|
22197
|
-
path: filePath,
|
|
22198
|
-
content: await fs5.readFile(filePath, "utf-8"),
|
|
22199
|
-
status: "added"
|
|
22200
|
-
}))
|
|
22201
|
-
);
|
|
22202
|
-
const input = { changedFiles, policy, headSha: "local", projectRoot: path5.resolve(targetPath) };
|
|
22203
|
-
const result = await (0, import_analyzers3.runFullAnalysis)(input);
|
|
22204
|
-
let violations = result.violations.filter((v) => v.severity === "critical" || v.severity === "warning").sort((a, b) => {
|
|
22205
|
-
if (a.severity === "critical" && b.severity !== "critical") return -1;
|
|
22206
|
-
if (a.severity !== "critical" && b.severity === "critical") return 1;
|
|
22207
|
-
return b.debtPoints - a.debtPoints;
|
|
22208
|
-
});
|
|
22209
|
-
if (options.severity === "critical") {
|
|
22210
|
-
violations = violations.filter((v) => v.severity === "critical");
|
|
22211
|
-
}
|
|
22212
|
-
const output = formatFixPrompt(violations);
|
|
22213
|
-
console.log(output);
|
|
22214
|
-
}
|
|
22215
22006
|
function groupBy(items, key) {
|
|
22216
22007
|
const result = {};
|
|
22217
22008
|
for (const item of items) {
|
|
@@ -22224,22 +22015,24 @@ function groupBy(items, key) {
|
|
|
22224
22015
|
async function fixCommand(targetPath, options) {
|
|
22225
22016
|
const client = RadarApiClient.fromConfigOrEnv();
|
|
22226
22017
|
if (!client) {
|
|
22227
|
-
console.error(import_chalk4.default.red("Authentication required. Run: radar login"));
|
|
22018
|
+
console.error(import_chalk4.default.red("\u274C Authentication required. Run: radar login"));
|
|
22228
22019
|
process.exit(1);
|
|
22229
22020
|
}
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22021
|
+
let verified;
|
|
22022
|
+
try {
|
|
22023
|
+
verified = await client.verifyToken();
|
|
22024
|
+
} catch (err) {
|
|
22025
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22026
|
+
if (msg.includes("Network error")) {
|
|
22027
|
+
console.error(import_chalk4.default.red("\u26A0\uFE0F Could not reach Radar API. Check your connection."));
|
|
22028
|
+
} else {
|
|
22029
|
+
console.error(import_chalk4.default.red("\u274C Authentication failed. Run: radar login"));
|
|
22030
|
+
}
|
|
22031
|
+
process.exit(1);
|
|
22032
|
+
}
|
|
22033
|
+
if (!verified?.valid) {
|
|
22034
|
+
console.error(import_chalk4.default.red("\u274C Invalid or expired token. Run: radar login"));
|
|
22035
|
+
process.exit(1);
|
|
22243
22036
|
}
|
|
22244
22037
|
console.log(import_chalk4.default.blue("Scanning for violations..."));
|
|
22245
22038
|
const scanResult = await runScan(targetPath, {
|
|
@@ -22265,7 +22058,9 @@ async function fixCommand(targetPath, options) {
|
|
|
22265
22058
|
let totalFixed = 0;
|
|
22266
22059
|
let totalSkipped = 0;
|
|
22267
22060
|
let totalFailed = 0;
|
|
22268
|
-
let
|
|
22061
|
+
let totalCreditsUsed = 0;
|
|
22062
|
+
let creditsRemaining = -1;
|
|
22063
|
+
let fixCount = 0;
|
|
22269
22064
|
let applyAll = false;
|
|
22270
22065
|
let quit = false;
|
|
22271
22066
|
const framework = scanResult.config?.stack?.framework || "NestJS";
|
|
@@ -22297,9 +22092,9 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22297
22092
|
const lineGroups = [...byLine.entries()].sort((a, b) => b[0] - a[0]);
|
|
22298
22093
|
for (const [lineNum, lineViolations] of lineGroups) {
|
|
22299
22094
|
if (quit) break;
|
|
22300
|
-
if (
|
|
22095
|
+
if (options.maxFixes > 0 && fixCount >= options.maxFixes) {
|
|
22301
22096
|
console.log(import_chalk4.default.yellow(`
|
|
22302
|
-
|
|
22097
|
+
Fix limit reached (${options.maxFixes}). Stopping.`));
|
|
22303
22098
|
quit = true;
|
|
22304
22099
|
break;
|
|
22305
22100
|
}
|
|
@@ -22318,46 +22113,43 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22318
22113
|
}
|
|
22319
22114
|
console.log("");
|
|
22320
22115
|
try {
|
|
22116
|
+
console.log(import_chalk4.default.dim(" Requesting AI fix from Radar..."));
|
|
22321
22117
|
let fix;
|
|
22322
22118
|
if (lineViolations.length > 1) {
|
|
22323
|
-
fix = await
|
|
22324
|
-
{
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
},
|
|
22338
|
-
apiKey
|
|
22339
|
-
);
|
|
22119
|
+
fix = await client.requestFixGrouped({
|
|
22120
|
+
violations: lineViolations.map((v) => ({
|
|
22121
|
+
file: v.file,
|
|
22122
|
+
line: v.line,
|
|
22123
|
+
ruleId: v.ruleId,
|
|
22124
|
+
message: v.message,
|
|
22125
|
+
severity: v.severity,
|
|
22126
|
+
category: v.category
|
|
22127
|
+
})),
|
|
22128
|
+
fileContent,
|
|
22129
|
+
surroundingCode,
|
|
22130
|
+
framework,
|
|
22131
|
+
orm
|
|
22132
|
+
});
|
|
22340
22133
|
} else {
|
|
22341
22134
|
const violation = lineViolations[0];
|
|
22342
|
-
fix = await
|
|
22343
|
-
{
|
|
22344
|
-
|
|
22345
|
-
|
|
22346
|
-
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
category: violation.category
|
|
22351
|
-
},
|
|
22352
|
-
fileContent,
|
|
22353
|
-
surroundingCode,
|
|
22354
|
-
framework,
|
|
22355
|
-
orm
|
|
22135
|
+
fix = await client.requestFix({
|
|
22136
|
+
violation: {
|
|
22137
|
+
file: violation.file,
|
|
22138
|
+
line: violation.line,
|
|
22139
|
+
ruleId: violation.ruleId,
|
|
22140
|
+
message: violation.message,
|
|
22141
|
+
severity: violation.severity,
|
|
22142
|
+
category: violation.category
|
|
22356
22143
|
},
|
|
22357
|
-
|
|
22358
|
-
|
|
22144
|
+
fileContent,
|
|
22145
|
+
surroundingCode,
|
|
22146
|
+
framework,
|
|
22147
|
+
orm
|
|
22148
|
+
});
|
|
22359
22149
|
}
|
|
22360
|
-
|
|
22150
|
+
totalCreditsUsed += fix.creditsUsed;
|
|
22151
|
+
creditsRemaining = fix.creditsRemaining;
|
|
22152
|
+
fixCount++;
|
|
22361
22153
|
console.log(import_chalk4.default.red(" BEFORE:"));
|
|
22362
22154
|
fix.originalCode.split("\n").forEach((l) => console.log(import_chalk4.default.red(` - ${l}`)));
|
|
22363
22155
|
console.log("");
|
|
@@ -22366,6 +22158,7 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22366
22158
|
console.log("");
|
|
22367
22159
|
console.log(import_chalk4.default.dim(` ${fix.explanation}`));
|
|
22368
22160
|
console.log(import_chalk4.default.dim(` Confidence: ${fix.confidence}`));
|
|
22161
|
+
console.log(import_chalk4.default.cyan(` ${fix.creditsUsed} AI credit used (${fix.creditsRemaining} remaining)`));
|
|
22369
22162
|
console.log("");
|
|
22370
22163
|
let shouldApply = false;
|
|
22371
22164
|
if (options.dryRun) {
|
|
@@ -22411,8 +22204,22 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22411
22204
|
}
|
|
22412
22205
|
} catch (error) {
|
|
22413
22206
|
const message = error instanceof Error ? error.message : String(error);
|
|
22414
|
-
|
|
22415
|
-
|
|
22207
|
+
if (message.includes("403") && message.includes("credits exhausted")) {
|
|
22208
|
+
console.log(import_chalk4.default.red(` \u274C AI credits exhausted. Upgrade for more: radar upgrade`));
|
|
22209
|
+
quit = true;
|
|
22210
|
+
break;
|
|
22211
|
+
} else if (message.includes("403") && message.includes("Solo")) {
|
|
22212
|
+
console.log(import_chalk4.default.red(` \u274C radar fix requires Solo plan or higher. Upgrade: radar upgrade`));
|
|
22213
|
+
quit = true;
|
|
22214
|
+
break;
|
|
22215
|
+
} else if (message.includes("401")) {
|
|
22216
|
+
console.log(import_chalk4.default.red(` \u274C Authentication failed. Run: radar login`));
|
|
22217
|
+
quit = true;
|
|
22218
|
+
break;
|
|
22219
|
+
} else {
|
|
22220
|
+
console.log(import_chalk4.default.red(` AI fix failed: ${message}`));
|
|
22221
|
+
totalFailed += lineViolations.length;
|
|
22222
|
+
}
|
|
22416
22223
|
}
|
|
22417
22224
|
}
|
|
22418
22225
|
}
|
|
@@ -22423,7 +22230,10 @@ ${relativePath} (${fileViolations.length} violations)
|
|
|
22423
22230
|
console.log(import_chalk4.default.green(` Fixed: ${totalFixed}`));
|
|
22424
22231
|
console.log(import_chalk4.default.yellow(` Skipped: ${totalSkipped}`));
|
|
22425
22232
|
if (totalFailed > 0) console.log(import_chalk4.default.red(` Failed: ${totalFailed}`));
|
|
22426
|
-
console.log(import_chalk4.default.
|
|
22233
|
+
console.log(import_chalk4.default.cyan(` AI credits used: ${totalCreditsUsed}`));
|
|
22234
|
+
if (creditsRemaining >= 0) {
|
|
22235
|
+
console.log(import_chalk4.default.cyan(` Credits remaining: ${creditsRemaining}`));
|
|
22236
|
+
}
|
|
22427
22237
|
console.log("");
|
|
22428
22238
|
if (totalFixed > 0 && !options.dryRun) {
|
|
22429
22239
|
console.log(import_chalk4.default.blue("Re-scanning to verify fixes...\n"));
|
|
@@ -22817,10 +22627,10 @@ program.command("check <file>").description("Check a single file against policy"
|
|
|
22817
22627
|
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) => {
|
|
22818
22628
|
await initCommand(options);
|
|
22819
22629
|
});
|
|
22820
|
-
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 high-confidence fixes without asking", false).option("--file <file>", "Fix single file only").option("--max-
|
|
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 high-confidence fixes without asking", 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) => {
|
|
22821
22631
|
await fixCommand(targetPath, {
|
|
22822
22632
|
...options,
|
|
22823
|
-
|
|
22633
|
+
maxFixes: parseInt(options.maxFixes, 10)
|
|
22824
22634
|
});
|
|
22825
22635
|
});
|
|
22826
22636
|
program.command("validate").description("Validate radar.yml + rules.yml syntax and cross-references").option("-c, --config <path>", "Path to radar.yml", "./radar.yml").option("-r, --rules <path>", "Path to rules.yml (default: ./rules.yml)").action(async (options) => {
|