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.
Files changed (2) hide show
  1. package/dist/index.js +89 -279
  2. 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 MODEL4 = "claude-sonnet-4-20250514";
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: MODEL4,
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
- const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY;
22231
- if (!apiKey) {
22232
- console.log(import_chalk4.default.yellow("No API key found."));
22233
- console.log(" Set ANTHROPIC_API_KEY environment variable");
22234
- console.log(" Or use: radar fix --api-key sk-ant-...");
22235
- console.log("");
22236
- console.log(import_chalk4.default.dim("Tip: Set ANTHROPIC_API_KEY for AI-powered auto-fixes"));
22237
- console.log(import_chalk4.default.dim(" export ANTHROPIC_API_KEY=sk-ant-..."));
22238
- console.log(import_chalk4.default.dim(" Then run: radar fix ."));
22239
- console.log("");
22240
- console.log(import_chalk4.default.dim("Without AI, showing fix instructions only:"));
22241
- console.log("");
22242
- return runTextOnlyFix(targetPath, options);
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 totalCost = 0;
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 (totalCost >= options.maxCost) {
22095
+ if (options.maxFixes > 0 && fixCount >= options.maxFixes) {
22301
22096
  console.log(import_chalk4.default.yellow(`
22302
- Cost ceiling reached ($${totalCost.toFixed(2)}). Stopping AI fixes.`));
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 generateGroupedFix(
22324
- {
22325
- violations: lineViolations.map((v) => ({
22326
- file: v.file,
22327
- line: v.line,
22328
- ruleId: v.ruleId,
22329
- message: v.message,
22330
- severity: v.severity,
22331
- category: v.category
22332
- })),
22333
- fileContent,
22334
- surroundingCode,
22335
- framework,
22336
- orm
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 generateFix(
22343
- {
22344
- violation: {
22345
- file: violation.file,
22346
- line: violation.line,
22347
- ruleId: violation.ruleId,
22348
- message: violation.message,
22349
- severity: violation.severity,
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
- apiKey
22358
- );
22144
+ fileContent,
22145
+ surroundingCode,
22146
+ framework,
22147
+ orm
22148
+ });
22359
22149
  }
22360
- totalCost += 3e-3 * lineViolations.length;
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
- console.log(import_chalk4.default.red(` AI fix failed: ${message}`));
22415
- totalFailed += lineViolations.length;
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.dim(` AI cost: $${totalCost.toFixed(3)}`));
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-cost <amount>", "Max AI cost in dollars", "1.00").option("--api-key <key>", "Anthropic API key (or ANTHROPIC_API_KEY env var)").action(async (targetPath, options) => {
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
- maxCost: parseFloat(options.maxCost)
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.10.0",
3
+ "version": "1.11.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",