dingdawg-code-review 1.0.0 → 2.0.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.d.ts +8 -2
- package/dist/index.js +208 -451
- package/package.json +1 -1
- package/src/index.ts +230 -506
package/dist/index.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* dingdawg-code-review —
|
|
4
|
+
* dingdawg-code-review v2 — Thin Client MCP Server
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* FREE tier: basic local pattern-matching code review (the hook)
|
|
7
|
+
* PAID tier: LLM-powered deep analysis via DingDawg API
|
|
7
8
|
*
|
|
8
9
|
* Install: npx dingdawg-code-review
|
|
9
10
|
* Claude Code: claude mcp add dingdawg-code-review npx dingdawg-code-review
|
|
11
|
+
*
|
|
12
|
+
* Set DINGDAWG_API_KEY for paid features:
|
|
13
|
+
* export DINGDAWG_API_KEY=your_key
|
|
14
|
+
*
|
|
15
|
+
* Optional: DINGDAWG_MODEL to choose the analysis model (default: gpt-5.4-mini)
|
|
10
16
|
*/
|
|
11
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -50,19 +56,27 @@ const os = __importStar(require("os"));
|
|
|
50
56
|
const path = __importStar(require("path"));
|
|
51
57
|
const crypto = __importStar(require("crypto"));
|
|
52
58
|
// ---------------------------------------------------------------------------
|
|
59
|
+
// Configuration
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
const API_BASE = process.env.DINGDAWG_API_URL || "https://api-production-f951.up.railway.app";
|
|
62
|
+
const API_ENDPOINT = `${API_BASE}/v1/govern/execute`;
|
|
63
|
+
const API_KEY = process.env.DINGDAWG_API_KEY || "";
|
|
64
|
+
const MODEL = process.env.DINGDAWG_MODEL || "gpt-5.4-mini";
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
53
66
|
// Persistent rate limiting — survives process restart via filesystem
|
|
54
67
|
// ---------------------------------------------------------------------------
|
|
68
|
+
const FREE_TIER_LIMIT = 10;
|
|
55
69
|
const RATE_FILE = path.join(os.homedir(), ".dingdawg", "code-review-usage.json");
|
|
56
70
|
const MACHINE_ID = crypto.createHash("sha256")
|
|
57
71
|
.update(`${os.hostname()}-${os.userInfo().username}-${os.platform()}-${os.arch()}`)
|
|
58
72
|
.digest("hex").slice(0, 16);
|
|
59
|
-
const FREE_LIMITS = {
|
|
60
|
-
review_code: 10,
|
|
61
|
-
review_pr: 5,
|
|
62
|
-
suggest_fix: 20,
|
|
63
|
-
};
|
|
64
73
|
function checkFreeRateLimit(tool) {
|
|
65
|
-
const
|
|
74
|
+
const limits = {
|
|
75
|
+
review_code: 10,
|
|
76
|
+
review_pr: 5,
|
|
77
|
+
suggest_fix: 20,
|
|
78
|
+
};
|
|
79
|
+
const limit = limits[tool] ?? FREE_TIER_LIMIT;
|
|
66
80
|
const key = `${MACHINE_ID}_${tool}`;
|
|
67
81
|
const now = Date.now();
|
|
68
82
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
@@ -97,301 +111,72 @@ function checkFreeRateLimit(tool) {
|
|
|
97
111
|
const current = store[key].count;
|
|
98
112
|
return { allowed: true, remaining: limit - current };
|
|
99
113
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
function generateReceiptId() {
|
|
104
|
-
return `rcpt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
105
|
-
}
|
|
106
|
-
function governanceEnvelope(tool, findings, extra = {}) {
|
|
107
|
-
const severityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
108
|
-
for (const f of findings) {
|
|
109
|
-
severityCounts[f.severity] = (severityCounts[f.severity] || 0) + 1;
|
|
114
|
+
async function callApi(tool, input) {
|
|
115
|
+
if (!API_KEY) {
|
|
116
|
+
return { success: false, error: "no_api_key" };
|
|
110
117
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(API_ENDPOINT, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
agent: "code-review",
|
|
127
|
+
tool,
|
|
128
|
+
input,
|
|
129
|
+
model: MODEL,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
const body = await res.text().catch(() => "");
|
|
134
|
+
return { success: false, error: `API returned ${res.status}: ${body}` };
|
|
135
|
+
}
|
|
136
|
+
const data = await res.json();
|
|
137
|
+
return { success: true, data };
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
return { success: false, error: `API request failed: ${message}` };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function upgradeMessage() {
|
|
145
|
+
return [
|
|
146
|
+
"",
|
|
147
|
+
"━━━ Upgrade to DingDawg Pro ━━━",
|
|
148
|
+
"Get LLM-powered deep code analysis, architecture review,",
|
|
149
|
+
"security audit, and automated fix generation.",
|
|
150
|
+
"",
|
|
151
|
+
" export DINGDAWG_API_KEY=your_key",
|
|
152
|
+
"",
|
|
153
|
+
"Get your key at: https://dingdawg.com/developers",
|
|
154
|
+
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
155
|
+
].join("\n");
|
|
127
156
|
}
|
|
128
157
|
const ANALYSIS_RULES = [
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
id: "SEC-002",
|
|
141
|
-
pattern: /exec\s*\(\s*(['"`]|[^)]*\+)/g,
|
|
142
|
-
severity: "critical",
|
|
143
|
-
category: "security",
|
|
144
|
-
title: "Command injection risk",
|
|
145
|
-
description: "String concatenation in exec/spawn calls can allow command injection if user input is included.",
|
|
146
|
-
suggestion: "Use parameterized commands with execFile() or spawn() with an argument array instead of shell strings.",
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
id: "SEC-003",
|
|
150
|
-
pattern: /innerHTML\s*=\s*[^'"`]/g,
|
|
151
|
-
severity: "high",
|
|
152
|
-
category: "security",
|
|
153
|
-
title: "Potential XSS via innerHTML",
|
|
154
|
-
description: "Setting innerHTML with dynamic content can introduce cross-site scripting vulnerabilities.",
|
|
155
|
-
suggestion: "Use textContent for plain text, or sanitize HTML with DOMPurify before assignment.",
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: "SEC-004",
|
|
159
|
-
pattern: /(?:password|secret|api_?key|token|private_?key)\s*[:=]\s*['"`][^'"`]{3,}/gi,
|
|
160
|
-
severity: "critical",
|
|
161
|
-
category: "security",
|
|
162
|
-
title: "Hardcoded secret detected",
|
|
163
|
-
description: "Credentials or API keys appear to be hardcoded in source. These will be exposed in version control.",
|
|
164
|
-
suggestion: "Move secrets to environment variables or a secrets manager. Never commit credentials to source code.",
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
id: "SEC-005",
|
|
168
|
-
pattern: /\bhttp:\/\/(?!localhost|127\.0\.0\.1)/g,
|
|
169
|
-
severity: "medium",
|
|
170
|
-
category: "security",
|
|
171
|
-
title: "Insecure HTTP URL",
|
|
172
|
-
description: "Non-localhost HTTP URLs transmit data in plaintext, vulnerable to man-in-the-middle attacks.",
|
|
173
|
-
suggestion: "Use HTTPS for all external URLs. Enforce TLS in production.",
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
id: "SEC-006",
|
|
177
|
-
pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\+\s*(?:req\.|params\.|query\.|body\.|input|user)/gi,
|
|
178
|
-
severity: "critical",
|
|
179
|
-
category: "security",
|
|
180
|
-
title: "SQL injection risk",
|
|
181
|
-
description: "SQL query built with string concatenation using user input. This is a classic SQL injection vector.",
|
|
182
|
-
suggestion: "Use parameterized queries or prepared statements. Never concatenate user input into SQL strings.",
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
id: "SEC-007",
|
|
186
|
-
pattern: /dangerouslySetInnerHTML/g,
|
|
187
|
-
severity: "high",
|
|
188
|
-
category: "security",
|
|
189
|
-
title: "React dangerouslySetInnerHTML",
|
|
190
|
-
description: "dangerouslySetInnerHTML bypasses React's XSS protections. If the content includes user input, XSS is possible.",
|
|
191
|
-
suggestion: "Sanitize content with DOMPurify before passing to dangerouslySetInnerHTML, or restructure to avoid it.",
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
id: "SEC-008",
|
|
195
|
-
pattern: /new\s+Function\s*\(/g,
|
|
196
|
-
severity: "high",
|
|
197
|
-
category: "security",
|
|
198
|
-
title: "Dynamic function constructor",
|
|
199
|
-
description: "new Function() is equivalent to eval() and poses the same code injection risks.",
|
|
200
|
-
suggestion: "Refactor to avoid dynamic code generation. Use lookup tables or strategy patterns instead.",
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
id: "SEC-009",
|
|
204
|
-
pattern: /(?:cors|Access-Control-Allow-Origin)\s*[:=]\s*['"`]\*['"`]/gi,
|
|
205
|
-
severity: "medium",
|
|
206
|
-
category: "security",
|
|
207
|
-
title: "Wildcard CORS policy",
|
|
208
|
-
description: "Allowing all origins (\\*) in CORS can expose your API to cross-origin attacks from malicious sites.",
|
|
209
|
-
suggestion: "Restrict CORS to specific trusted origins in production.",
|
|
210
|
-
},
|
|
211
|
-
// --- Code Quality ---
|
|
212
|
-
{
|
|
213
|
-
id: "QUAL-001",
|
|
214
|
-
pattern: /\bcatch\s*\(\s*\w*\s*\)\s*\{\s*\}/g,
|
|
215
|
-
severity: "medium",
|
|
216
|
-
category: "quality",
|
|
217
|
-
title: "Empty catch block",
|
|
218
|
-
description: "Swallowing exceptions silently hides errors and makes debugging extremely difficult.",
|
|
219
|
-
suggestion: "Log the error, rethrow it, or handle it explicitly. At minimum add a comment explaining why it's empty.",
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
id: "QUAL-002",
|
|
223
|
-
pattern: /\/\/\s*TODO(?::|\s)/gi,
|
|
224
|
-
severity: "low",
|
|
225
|
-
category: "quality",
|
|
226
|
-
title: "TODO comment found",
|
|
227
|
-
description: "TODO comments indicate unfinished work that may be forgotten.",
|
|
228
|
-
suggestion: "Track TODOs in your issue tracker. Resolve before merging to main.",
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
id: "QUAL-003",
|
|
232
|
-
pattern: /console\.(log|warn|error|debug|info)\s*\(/g,
|
|
233
|
-
severity: "low",
|
|
234
|
-
category: "quality",
|
|
235
|
-
title: "Console statement in code",
|
|
236
|
-
description: "Console statements should not be in production code. They leak information and clutter output.",
|
|
237
|
-
suggestion: "Use a structured logging library (winston, pino) or remove debug statements before committing.",
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
id: "QUAL-004",
|
|
241
|
-
pattern: /any(?:\s*[;,)\]}]|\s*$)/gm,
|
|
242
|
-
severity: "medium",
|
|
243
|
-
category: "quality",
|
|
244
|
-
title: "TypeScript 'any' type usage",
|
|
245
|
-
description: "Using 'any' disables type checking, defeating the purpose of TypeScript and hiding potential bugs.",
|
|
246
|
-
suggestion: "Replace 'any' with a specific type, 'unknown', or a generic. Use type narrowing for dynamic data.",
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
id: "QUAL-005",
|
|
250
|
-
pattern: /(?:^|\n).{200,}/g,
|
|
251
|
-
severity: "low",
|
|
252
|
-
category: "quality",
|
|
253
|
-
title: "Overly long line",
|
|
254
|
-
description: "Lines exceeding 200 characters hurt readability and make code reviews harder.",
|
|
255
|
-
suggestion: "Break long lines into multiple shorter lines. Configure your formatter to enforce a max line length.",
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
id: "QUAL-006",
|
|
259
|
-
pattern: /\bvar\s+/g,
|
|
260
|
-
severity: "medium",
|
|
261
|
-
category: "quality",
|
|
262
|
-
title: "Use of 'var' keyword",
|
|
263
|
-
description: "'var' has function scope and hoisting behavior that leads to subtle bugs.",
|
|
264
|
-
suggestion: "Use 'const' for values that don't change, 'let' for values that do. Never use 'var'.",
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
id: "QUAL-007",
|
|
268
|
-
pattern: /==(?!=)/g,
|
|
269
|
-
severity: "low",
|
|
270
|
-
category: "quality",
|
|
271
|
-
title: "Loose equality operator",
|
|
272
|
-
description: "== performs type coercion which can produce unexpected results (e.g., '' == 0 is true).",
|
|
273
|
-
suggestion: "Use === (strict equality) to avoid type coercion surprises.",
|
|
274
|
-
},
|
|
275
|
-
// --- Performance ---
|
|
276
|
-
{
|
|
277
|
-
id: "PERF-001",
|
|
278
|
-
pattern: /new\s+RegExp\s*\(/g,
|
|
279
|
-
severity: "low",
|
|
280
|
-
category: "performance",
|
|
281
|
-
title: "Regex compiled inside function/loop",
|
|
282
|
-
description: "Creating RegExp objects inside frequently-called code recompiles the regex on every call.",
|
|
283
|
-
suggestion: "Move the RegExp to a module-level constant so it's compiled once.",
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
id: "PERF-002",
|
|
287
|
-
pattern: /JSON\.parse\s*\(\s*JSON\.stringify\s*\(/g,
|
|
288
|
-
severity: "medium",
|
|
289
|
-
category: "performance",
|
|
290
|
-
title: "Deep clone via JSON round-trip",
|
|
291
|
-
description: "JSON.parse(JSON.stringify(obj)) is slow for large objects and drops functions, Dates, undefined, etc.",
|
|
292
|
-
suggestion: "Use structuredClone() (Node 17+/modern browsers) or a dedicated deep-clone library.",
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
id: "PERF-003",
|
|
296
|
-
pattern: /\.forEach\s*\(\s*async/g,
|
|
297
|
-
severity: "high",
|
|
298
|
-
category: "performance",
|
|
299
|
-
title: "Async callback in forEach",
|
|
300
|
-
description: "Array.forEach does not await async callbacks. All iterations fire simultaneously with no error handling.",
|
|
301
|
-
suggestion: "Use a for...of loop with await, or Promise.all(arr.map(async ...)) for controlled concurrency.",
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
id: "PERF-004",
|
|
305
|
-
pattern: /await\s+.*\bfor\s*\(/g,
|
|
306
|
-
severity: "medium",
|
|
307
|
-
category: "performance",
|
|
308
|
-
title: "Await inside loop",
|
|
309
|
-
description: "Awaiting inside a loop runs operations sequentially when they could run in parallel.",
|
|
310
|
-
suggestion: "Collect promises and use Promise.all() for parallel execution, or use for await...of for async iterables.",
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
id: "PERF-005",
|
|
314
|
-
pattern: /document\.querySelector(?:All)?\s*\([^)]+\)/g,
|
|
315
|
-
severity: "low",
|
|
316
|
-
category: "performance",
|
|
317
|
-
title: "DOM query in potential hot path",
|
|
318
|
-
description: "Repeated DOM queries are expensive. If this runs in a loop or event handler, cache the result.",
|
|
319
|
-
suggestion: "Cache the DOM reference in a variable outside the loop/handler.",
|
|
320
|
-
},
|
|
321
|
-
// --- Best Practices ---
|
|
322
|
-
{
|
|
323
|
-
id: "BP-001",
|
|
324
|
-
pattern: /(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+\w+\s*\([^)]{100,}\)/g,
|
|
325
|
-
severity: "medium",
|
|
326
|
-
category: "best-practice",
|
|
327
|
-
title: "Function with too many parameters",
|
|
328
|
-
description: "Functions with many parameters are hard to call correctly and maintain.",
|
|
329
|
-
suggestion: "Use an options/config object parameter instead. Destructure for clarity.",
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
id: "BP-002",
|
|
333
|
-
pattern: /catch\s*\(\s*\w+\s*\)\s*\{[^}]*throw\s+\w+\s*;?\s*\}/g,
|
|
334
|
-
severity: "low",
|
|
335
|
-
category: "best-practice",
|
|
336
|
-
title: "Catch and rethrow without modification",
|
|
337
|
-
description: "Catching an error only to rethrow it adds noise without value.",
|
|
338
|
-
suggestion: "Remove the try/catch if you're not modifying the error, or wrap it with additional context.",
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
id: "BP-003",
|
|
342
|
-
pattern: /(?:^|\n)\s*(?:\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)\s*(?:\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)\s*(?:\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)\s*(?:\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)/g,
|
|
343
|
-
severity: "low",
|
|
344
|
-
category: "best-practice",
|
|
345
|
-
title: "Excessive consecutive comments",
|
|
346
|
-
description: "Large blocks of comments may indicate commented-out code or over-documentation.",
|
|
347
|
-
suggestion: "Remove commented-out code. Use clear naming and small functions instead of excessive comments.",
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
id: "BP-004",
|
|
351
|
-
pattern: /(?:process\.exit|os\._exit|sys\.exit)\s*\(\s*[^)]*\)/g,
|
|
352
|
-
severity: "medium",
|
|
353
|
-
category: "best-practice",
|
|
354
|
-
title: "Hard process exit",
|
|
355
|
-
description: "Calling process.exit() skips cleanup, open handles, and graceful shutdown procedures.",
|
|
356
|
-
suggestion: "Throw an error or return an error code to let the application shut down gracefully.",
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
id: "BP-005",
|
|
360
|
-
pattern: /(?:\.then\s*\([^)]*\)\s*){3,}/g,
|
|
361
|
-
severity: "medium",
|
|
362
|
-
category: "best-practice",
|
|
363
|
-
title: "Deeply chained promises",
|
|
364
|
-
description: "Long .then() chains are hard to read and debug compared to async/await.",
|
|
365
|
-
suggestion: "Refactor to async/await for better readability and error handling.",
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
id: "BP-006",
|
|
369
|
-
pattern: /(?:^|\n)(?:.*\n){300,}/g,
|
|
370
|
-
severity: "medium",
|
|
371
|
-
category: "best-practice",
|
|
372
|
-
title: "File exceeds 300 lines",
|
|
373
|
-
description: "Large files are harder to navigate, test, and maintain.",
|
|
374
|
-
suggestion: "Consider splitting into smaller, focused modules with clear responsibilities.",
|
|
375
|
-
},
|
|
158
|
+
{ id: "SEC-001", pattern: /eval\s*\(/g, severity: "critical", category: "security", title: "Use of eval()", description: "eval() executes arbitrary code and is a major injection vector.", suggestion: "Replace eval() with JSON.parse() or a safe expression parser." },
|
|
159
|
+
{ id: "SEC-002", pattern: /exec\s*\(\s*(['"`]|[^)]*\+)/g, severity: "critical", category: "security", title: "Command injection risk", description: "String concatenation in exec/spawn calls can allow command injection.", suggestion: "Use execFile() or spawn() with argument array." },
|
|
160
|
+
{ id: "SEC-003", pattern: /innerHTML\s*=\s*[^'"`]/g, severity: "high", category: "security", title: "Potential XSS via innerHTML", description: "Setting innerHTML with dynamic content can introduce XSS.", suggestion: "Use textContent or sanitize with DOMPurify." },
|
|
161
|
+
{ id: "SEC-004", pattern: /(?:password|secret|api_?key|token|private_?key)\s*[:=]\s*['"`][^'"`]{3,}/gi, severity: "critical", category: "security", title: "Hardcoded secret detected", description: "Credentials appear to be hardcoded in source.", suggestion: "Move secrets to environment variables." },
|
|
162
|
+
{ id: "SEC-005", pattern: /\bhttp:\/\/(?!localhost|127\.0\.0\.1)/g, severity: "medium", category: "security", title: "Insecure HTTP URL", description: "Non-localhost HTTP URLs transmit data in plaintext.", suggestion: "Use HTTPS for all external URLs." },
|
|
163
|
+
{ id: "SEC-006", pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\+\s*(?:req\.|params\.|query\.|body\.|input|user)/gi, severity: "critical", category: "security", title: "SQL injection risk", description: "SQL query built with string concatenation using user input.", suggestion: "Use parameterized queries or prepared statements." },
|
|
164
|
+
{ id: "QUAL-001", pattern: /\bcatch\s*\(\s*\w*\s*\)\s*\{\s*\}/g, severity: "medium", category: "quality", title: "Empty catch block", description: "Swallowing exceptions silently hides errors.", suggestion: "Log or handle the error explicitly." },
|
|
165
|
+
{ id: "QUAL-002", pattern: /\/\/\s*TODO(?::|\s)/gi, severity: "low", category: "quality", title: "TODO comment found", description: "TODO comments indicate unfinished work.", suggestion: "Resolve before merging to main." },
|
|
166
|
+
{ id: "QUAL-003", pattern: /console\.(log|warn|error|debug|info)\s*\(/g, severity: "low", category: "quality", title: "Console statement in code", description: "Console statements should not be in production code.", suggestion: "Use a structured logging library." },
|
|
167
|
+
{ id: "PERF-001", pattern: /\.forEach\s*\(\s*async/g, severity: "high", category: "performance", title: "Async callback in forEach", description: "Array.forEach does not await async callbacks.", suggestion: "Use for...of with await or Promise.all(arr.map(...))." },
|
|
168
|
+
{ id: "BP-001", pattern: /\bvar\s+/g, severity: "medium", category: "best-practice", title: "Use of 'var' keyword", description: "'var' has function scope and hoisting issues.", suggestion: "Use 'const' or 'let' instead." },
|
|
376
169
|
];
|
|
377
|
-
function
|
|
170
|
+
function analyzeCodeLocal(code) {
|
|
378
171
|
const findings = [];
|
|
379
|
-
const lines = code.split("\n");
|
|
380
172
|
for (const rule of ANALYSIS_RULES) {
|
|
381
|
-
// Skip DOM-specific rules for non-frontend code
|
|
382
|
-
if (rule.id === "PERF-005" && language && !["javascript", "typescript", "jsx", "tsx"].includes(language)) {
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
// Reset regex state
|
|
386
173
|
rule.pattern.lastIndex = 0;
|
|
387
174
|
let match;
|
|
388
175
|
const matchedLines = [];
|
|
389
176
|
while ((match = rule.pattern.exec(code)) !== null) {
|
|
390
|
-
// Find the line number
|
|
391
177
|
const beforeMatch = code.slice(0, match.index);
|
|
392
178
|
const lineNum = beforeMatch.split("\n").length;
|
|
393
179
|
matchedLines.push(`line ${lineNum}`);
|
|
394
|
-
// Prevent infinite loops on zero-width matches
|
|
395
180
|
if (match[0].length === 0) {
|
|
396
181
|
rule.pattern.lastIndex++;
|
|
397
182
|
}
|
|
@@ -408,122 +193,23 @@ function analyzeCode(code, language) {
|
|
|
408
193
|
});
|
|
409
194
|
}
|
|
410
195
|
}
|
|
411
|
-
// Sort findings: critical first, then high, medium, low, info
|
|
412
196
|
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
413
197
|
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
414
198
|
return findings;
|
|
415
199
|
}
|
|
416
|
-
function analyzePRDiff(diff, title) {
|
|
417
|
-
const findings = [];
|
|
418
|
-
const testCoverageGaps = [];
|
|
419
|
-
// Extract only added lines for analysis (lines starting with +, not ++)
|
|
420
|
-
const addedLines = diff.split("\n")
|
|
421
|
-
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
422
|
-
.map((line) => line.slice(1))
|
|
423
|
-
.join("\n");
|
|
424
|
-
// Run the standard code analysis on added lines
|
|
425
|
-
const codeFindings = analyzeCode(addedLines);
|
|
426
|
-
findings.push(...codeFindings);
|
|
427
|
-
// Check for breaking change patterns in the diff
|
|
428
|
-
let breakingRisk = "none";
|
|
429
|
-
const breakingPatterns = [
|
|
430
|
-
{ pattern: /^-\s*export\s+/gm, risk: "high", desc: "Exported symbol removed" },
|
|
431
|
-
{ pattern: /^-\s*(?:public|protected)\s+/gm, risk: "medium", desc: "Public/protected API removed" },
|
|
432
|
-
{ pattern: /^-.*(?:interface|type)\s+\w+/gm, risk: "medium", desc: "Type definition removed" },
|
|
433
|
-
{ pattern: /^-\s*(?:app|router)\.\s*(?:get|post|put|delete|patch)\s*\(/gm, risk: "high", desc: "API endpoint removed" },
|
|
434
|
-
{ pattern: /(?:DROP\s+TABLE|ALTER\s+TABLE.*DROP\s+COLUMN)/gi, risk: "high", desc: "Database schema destructive change" },
|
|
435
|
-
];
|
|
436
|
-
const riskOrder = { none: 0, low: 1, medium: 2, high: 3 };
|
|
437
|
-
for (const bp of breakingPatterns) {
|
|
438
|
-
bp.pattern.lastIndex = 0;
|
|
439
|
-
if (bp.pattern.test(diff)) {
|
|
440
|
-
if (riskOrder[bp.risk] > riskOrder[breakingRisk]) {
|
|
441
|
-
breakingRisk = bp.risk;
|
|
442
|
-
}
|
|
443
|
-
findings.push({
|
|
444
|
-
id: `BREAK-${findings.length + 1}`,
|
|
445
|
-
severity: bp.risk === "high" ? "high" : "medium",
|
|
446
|
-
category: "quality",
|
|
447
|
-
title: `Breaking change: ${bp.desc}`,
|
|
448
|
-
description: `This diff appears to ${bp.desc.toLowerCase()}. This could break downstream consumers.`,
|
|
449
|
-
suggestion: "Ensure backward compatibility or document the breaking change in release notes.",
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// Test coverage gap detection
|
|
454
|
-
const addedFiles = diff.match(/^\+\+\+\s+b\/(.+)/gm)?.map((l) => l.replace(/^\+\+\+\s+b\//, "")) ?? [];
|
|
455
|
-
const hasTestFiles = addedFiles.some((f) => f.includes("test") || f.includes("spec") || f.includes("__tests__"));
|
|
456
|
-
const hasSourceChanges = addedFiles.some((f) => !f.includes("test") && !f.includes("spec") && !f.includes("__tests__") &&
|
|
457
|
-
(f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".py") || f.endsWith(".go") || f.endsWith(".rs")));
|
|
458
|
-
if (hasSourceChanges && !hasTestFiles) {
|
|
459
|
-
testCoverageGaps.push("Source files modified but no test files included in this PR.");
|
|
460
|
-
}
|
|
461
|
-
// Check for new functions without corresponding tests
|
|
462
|
-
const newFunctions = addedLines.match(/(?:function|const|let)\s+(\w+)\s*(?:=\s*(?:async\s*)?\(|\()/g);
|
|
463
|
-
if (newFunctions && newFunctions.length > 3 && !hasTestFiles) {
|
|
464
|
-
testCoverageGaps.push(`${newFunctions.length} new functions added without test coverage.`);
|
|
465
|
-
}
|
|
466
|
-
// Check for new API endpoints without tests
|
|
467
|
-
const newEndpoints = addedLines.match(/(?:app|router)\.\s*(?:get|post|put|delete|patch)\s*\(/g);
|
|
468
|
-
if (newEndpoints && !hasTestFiles) {
|
|
469
|
-
testCoverageGaps.push(`${newEndpoints.length} new API endpoint(s) added without test coverage.`);
|
|
470
|
-
}
|
|
471
|
-
// Decision logic
|
|
472
|
-
const hasCritical = findings.some((f) => f.severity === "critical");
|
|
473
|
-
const hasHigh = findings.some((f) => f.severity === "high");
|
|
474
|
-
const decision = hasCritical || (hasHigh && findings.filter((f) => f.severity === "high").length >= 2)
|
|
475
|
-
? "request-changes"
|
|
476
|
-
: "approve";
|
|
477
|
-
// Summary
|
|
478
|
-
const categoryCount = {};
|
|
479
|
-
for (const f of findings) {
|
|
480
|
-
categoryCount[f.category] = (categoryCount[f.category] || 0) + 1;
|
|
481
|
-
}
|
|
482
|
-
const summaryParts = [];
|
|
483
|
-
if (findings.length === 0) {
|
|
484
|
-
summaryParts.push("Clean PR. No issues detected.");
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
summaryParts.push(`Found ${findings.length} issue(s):`);
|
|
488
|
-
for (const [cat, count] of Object.entries(categoryCount)) {
|
|
489
|
-
summaryParts.push(`${count} ${cat}`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
if (testCoverageGaps.length > 0) {
|
|
493
|
-
summaryParts.push(`Test coverage gaps: ${testCoverageGaps.length}`);
|
|
494
|
-
}
|
|
495
|
-
return {
|
|
496
|
-
decision,
|
|
497
|
-
findings,
|
|
498
|
-
summary: summaryParts.join(" "),
|
|
499
|
-
breaking_change_risk: breakingRisk,
|
|
500
|
-
test_coverage_gaps: testCoverageGaps,
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
// ---------------------------------------------------------------------------
|
|
504
|
-
// Fix suggestion engine
|
|
505
|
-
// ---------------------------------------------------------------------------
|
|
506
|
-
function generateFix(findingId, code) {
|
|
507
|
-
const rule = ANALYSIS_RULES.find((r) => r.id === findingId);
|
|
508
|
-
if (!rule)
|
|
509
|
-
return null;
|
|
510
|
-
return {
|
|
511
|
-
fix: rule.suggestion,
|
|
512
|
-
explanation: `${rule.title}: ${rule.description}\n\nRecommended approach: ${rule.suggestion}`,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
200
|
// ---------------------------------------------------------------------------
|
|
516
201
|
// MCP Server
|
|
517
202
|
// ---------------------------------------------------------------------------
|
|
518
203
|
const server = new mcp_js_1.McpServer({
|
|
519
204
|
name: "dingdawg-code-review",
|
|
520
|
-
version: "
|
|
205
|
+
version: "2.0.0",
|
|
521
206
|
});
|
|
522
207
|
// ---------------------------------------------------------------------------
|
|
523
|
-
// review_code — FREE
|
|
208
|
+
// review_code — FREE local scan + API deep analysis
|
|
524
209
|
// ---------------------------------------------------------------------------
|
|
525
210
|
server.tool("review_code", "Scan code for security vulnerabilities, code quality issues, performance problems, and best practice violations. " +
|
|
526
|
-
"Returns findings with severity levels, line hints, and fix suggestions. FREE: 10 reviews/day."
|
|
211
|
+
"Returns findings with severity levels, line hints, and fix suggestions. FREE: 10 reviews/day. " +
|
|
212
|
+
"Deep LLM-powered analysis available with API key.", {
|
|
527
213
|
code: zod_1.z.string().min(1, "Code cannot be empty").describe("The code snippet or file contents to review"),
|
|
528
214
|
language: zod_1.z.string().optional().describe("Programming language (typescript, javascript, python, etc.)"),
|
|
529
215
|
filename: zod_1.z.string().optional().describe("Filename for context (e.g., 'auth.ts')"),
|
|
@@ -534,39 +220,69 @@ server.tool("review_code", "Scan code for security vulnerabilities, code quality
|
|
|
534
220
|
content: [{
|
|
535
221
|
type: "text",
|
|
536
222
|
text: JSON.stringify({
|
|
537
|
-
error: "Free tier limit reached (10 code reviews per 24 hours).
|
|
538
|
-
upgrade: "Get unlimited access
|
|
539
|
-
|
|
540
|
-
}
|
|
223
|
+
error: "Free tier limit reached (10 code reviews per 24 hours). Resets automatically.",
|
|
224
|
+
upgrade: "Get unlimited access: export DINGDAWG_API_KEY=your_key — https://dingdawg.com/developers",
|
|
225
|
+
governed: true,
|
|
226
|
+
}),
|
|
541
227
|
}],
|
|
542
228
|
};
|
|
543
229
|
}
|
|
544
|
-
|
|
230
|
+
// Local analysis — free tier
|
|
231
|
+
const localFindings = analyzeCodeLocal(code);
|
|
232
|
+
// If API key is set, enhance with LLM-powered deep analysis
|
|
233
|
+
if (API_KEY) {
|
|
234
|
+
const apiResult = await callApi("review_code", {
|
|
235
|
+
code,
|
|
236
|
+
language: language || "",
|
|
237
|
+
filename: filename || "",
|
|
238
|
+
});
|
|
239
|
+
if (apiResult.success && apiResult.data) {
|
|
240
|
+
return {
|
|
241
|
+
content: [{
|
|
242
|
+
type: "text",
|
|
243
|
+
text: JSON.stringify({
|
|
244
|
+
mode: "deep_analysis",
|
|
245
|
+
powered_by: "DingDawg Code Review API",
|
|
246
|
+
...apiResult.data,
|
|
247
|
+
receipt_id: `cr_${Date.now().toString(36)}`,
|
|
248
|
+
governed: true,
|
|
249
|
+
}, null, 2),
|
|
250
|
+
}],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Free tier response with teaser
|
|
545
255
|
return {
|
|
546
256
|
content: [{
|
|
547
257
|
type: "text",
|
|
548
258
|
text: JSON.stringify({
|
|
259
|
+
mode: "local_basic",
|
|
549
260
|
filename: filename || "inline",
|
|
550
261
|
language: language || "auto-detected",
|
|
551
262
|
lines_scanned: code.split("\n").length,
|
|
552
|
-
findings,
|
|
263
|
+
findings: localFindings.slice(0, 5),
|
|
264
|
+
total_issues: localFindings.length,
|
|
265
|
+
teaser: localFindings.length > 0
|
|
266
|
+
? `Found ${localFindings.length} issue(s). Get LLM-powered deep analysis, architecture review, and automated fixes with: export DINGDAWG_API_KEY=your_key`
|
|
267
|
+
: "No issues detected with basic scan. Deep LLM analysis catches 10x more. Get your key at https://dingdawg.com/developers",
|
|
268
|
+
upgrade_url: "https://dingdawg.com/developers",
|
|
553
269
|
also_available: {
|
|
554
|
-
suggest_fix: "Run suggest_fix with a finding ID
|
|
555
|
-
review_pr: "Run review_pr to review
|
|
556
|
-
compliance: "Run npx dingdawg-compliance for AI compliance reports.",
|
|
557
|
-
shield: "Run npx dingdawg-shield for AI security scanning.",
|
|
270
|
+
suggest_fix: "Run suggest_fix with a finding ID for fix suggestions.",
|
|
271
|
+
review_pr: "Run review_pr to review a PR diff.",
|
|
558
272
|
},
|
|
273
|
+
receipt_id: `cr_${Date.now().toString(36)}`,
|
|
559
274
|
free_reviews_remaining: rateCheck.remaining,
|
|
560
|
-
|
|
275
|
+
governed: true,
|
|
561
276
|
}, null, 2),
|
|
562
277
|
}],
|
|
563
278
|
};
|
|
564
279
|
});
|
|
565
280
|
// ---------------------------------------------------------------------------
|
|
566
|
-
// review_pr — FREE
|
|
281
|
+
// review_pr — FREE basic + PAID deep PR review
|
|
567
282
|
// ---------------------------------------------------------------------------
|
|
568
283
|
server.tool("review_pr", "Review a pull request diff. Checks for breaking changes, security issues, test coverage gaps. " +
|
|
569
|
-
"Returns structured review with approve/request-changes decision. FREE: 5 PR reviews/day."
|
|
284
|
+
"Returns structured review with approve/request-changes decision. FREE: 5 PR reviews/day. " +
|
|
285
|
+
"Deep LLM-powered review available with API key.", {
|
|
570
286
|
diff: zod_1.z.string().min(1, "Diff cannot be empty").describe("The unified diff content of the pull request"),
|
|
571
287
|
title: zod_1.z.string().optional().describe("PR title for context"),
|
|
572
288
|
description: zod_1.z.string().optional().describe("PR description for context"),
|
|
@@ -577,40 +293,70 @@ server.tool("review_pr", "Review a pull request diff. Checks for breaking change
|
|
|
577
293
|
content: [{
|
|
578
294
|
type: "text",
|
|
579
295
|
text: JSON.stringify({
|
|
580
|
-
error: "Free tier limit reached (5 PR reviews per 24 hours).
|
|
581
|
-
upgrade: "Get unlimited access
|
|
582
|
-
|
|
583
|
-
}
|
|
296
|
+
error: "Free tier limit reached (5 PR reviews per 24 hours). Resets automatically.",
|
|
297
|
+
upgrade: "Get unlimited access: export DINGDAWG_API_KEY=your_key — https://dingdawg.com/developers",
|
|
298
|
+
governed: true,
|
|
299
|
+
}),
|
|
584
300
|
}],
|
|
585
301
|
};
|
|
586
302
|
}
|
|
587
|
-
|
|
303
|
+
// If API key is set, use deep analysis
|
|
304
|
+
if (API_KEY) {
|
|
305
|
+
const apiResult = await callApi("review_pr", {
|
|
306
|
+
diff,
|
|
307
|
+
title: title || "",
|
|
308
|
+
description: description || "",
|
|
309
|
+
});
|
|
310
|
+
if (apiResult.success && apiResult.data) {
|
|
311
|
+
return {
|
|
312
|
+
content: [{
|
|
313
|
+
type: "text",
|
|
314
|
+
text: JSON.stringify({
|
|
315
|
+
mode: "deep_analysis",
|
|
316
|
+
powered_by: "DingDawg Code Review API",
|
|
317
|
+
...apiResult.data,
|
|
318
|
+
receipt_id: `pr_${Date.now().toString(36)}`,
|
|
319
|
+
governed: true,
|
|
320
|
+
}, null, 2),
|
|
321
|
+
}],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Free tier — basic analysis on added lines
|
|
326
|
+
const addedLines = diff.split("\n")
|
|
327
|
+
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
328
|
+
.map((line) => line.slice(1))
|
|
329
|
+
.join("\n");
|
|
330
|
+
const findings = analyzeCodeLocal(addedLines);
|
|
331
|
+
const hasCritical = findings.some((f) => f.severity === "critical");
|
|
332
|
+
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
333
|
+
const decision = hasCritical || highCount >= 2 ? "request-changes" : "approve";
|
|
588
334
|
return {
|
|
589
335
|
content: [{
|
|
590
336
|
type: "text",
|
|
591
337
|
text: JSON.stringify({
|
|
338
|
+
mode: "local_basic",
|
|
592
339
|
pr_title: title || "untitled",
|
|
593
|
-
decision
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
},
|
|
340
|
+
decision,
|
|
341
|
+
findings_count: findings.length,
|
|
342
|
+
top_findings: findings.slice(0, 3),
|
|
343
|
+
teaser: findings.length > 0
|
|
344
|
+
? `Found ${findings.length} issue(s) in added lines. Deep LLM review catches breaking changes, test gaps, and architecture issues. export DINGDAWG_API_KEY=your_key`
|
|
345
|
+
: "Basic scan clean. LLM-powered review provides breaking change detection, test coverage analysis, and architecture review.",
|
|
346
|
+
upgrade_url: "https://dingdawg.com/developers",
|
|
347
|
+
receipt_id: `pr_${Date.now().toString(36)}`,
|
|
602
348
|
free_pr_reviews_remaining: rateCheck.remaining,
|
|
603
|
-
|
|
349
|
+
governed: true,
|
|
604
350
|
}, null, 2),
|
|
605
351
|
}],
|
|
606
352
|
};
|
|
607
353
|
});
|
|
608
354
|
// ---------------------------------------------------------------------------
|
|
609
|
-
// suggest_fix — FREE
|
|
355
|
+
// suggest_fix — FREE basic + PAID LLM fix generation
|
|
610
356
|
// ---------------------------------------------------------------------------
|
|
611
|
-
server.tool("suggest_fix", "Given a finding ID from review_code or review_pr, generate a
|
|
612
|
-
"FREE: 20 suggestions/day.", {
|
|
613
|
-
finding_id: zod_1.z.string().describe("The finding ID from a review (e.g., SEC-001, QUAL-003
|
|
357
|
+
server.tool("suggest_fix", "Given a finding ID from review_code or review_pr, generate a fix suggestion. " +
|
|
358
|
+
"FREE: 20 suggestions/day. LLM-powered code generation available with API key.", {
|
|
359
|
+
finding_id: zod_1.z.string().describe("The finding ID from a review (e.g., SEC-001, QUAL-003)"),
|
|
614
360
|
code_context: zod_1.z.string().optional().describe("The code surrounding the finding for a more targeted fix"),
|
|
615
361
|
}, async ({ finding_id, code_context }) => {
|
|
616
362
|
const rateCheck = checkFreeRateLimit("suggest_fix");
|
|
@@ -619,52 +365,63 @@ server.tool("suggest_fix", "Given a finding ID from review_code or review_pr, ge
|
|
|
619
365
|
content: [{
|
|
620
366
|
type: "text",
|
|
621
367
|
text: JSON.stringify({
|
|
622
|
-
error: "Free tier limit reached (20 fix suggestions per 24 hours).
|
|
623
|
-
upgrade: "Get unlimited access
|
|
624
|
-
|
|
625
|
-
}
|
|
368
|
+
error: "Free tier limit reached (20 fix suggestions per 24 hours). Resets automatically.",
|
|
369
|
+
upgrade: "Get unlimited access: export DINGDAWG_API_KEY=your_key — https://dingdawg.com/developers",
|
|
370
|
+
governed: true,
|
|
371
|
+
}),
|
|
626
372
|
}],
|
|
627
373
|
};
|
|
628
374
|
}
|
|
629
|
-
|
|
630
|
-
if (
|
|
375
|
+
// If API key is set, get LLM-powered fix
|
|
376
|
+
if (API_KEY) {
|
|
377
|
+
const apiResult = await callApi("suggest_fix", {
|
|
378
|
+
finding_id,
|
|
379
|
+
code_context: code_context || "",
|
|
380
|
+
});
|
|
381
|
+
if (apiResult.success && apiResult.data) {
|
|
382
|
+
return {
|
|
383
|
+
content: [{
|
|
384
|
+
type: "text",
|
|
385
|
+
text: JSON.stringify({
|
|
386
|
+
mode: "deep_analysis",
|
|
387
|
+
powered_by: "DingDawg Code Review API",
|
|
388
|
+
...apiResult.data,
|
|
389
|
+
receipt_id: `fix_${Date.now().toString(36)}`,
|
|
390
|
+
governed: true,
|
|
391
|
+
}, null, 2),
|
|
392
|
+
}],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Free tier — match against local rules
|
|
397
|
+
const rule = ANALYSIS_RULES.find((r) => r.id === finding_id);
|
|
398
|
+
if (!rule) {
|
|
631
399
|
return {
|
|
632
400
|
content: [{
|
|
633
401
|
type: "text",
|
|
634
402
|
text: JSON.stringify({
|
|
635
403
|
finding_id,
|
|
636
|
-
error: `No rule found for
|
|
637
|
-
|
|
638
|
-
...governanceEnvelope("suggest_fix", []),
|
|
404
|
+
error: `No rule found for '${finding_id}'. Valid IDs: ${ANALYSIS_RULES.map((r) => r.id).join(", ")}`,
|
|
405
|
+
governed: true,
|
|
639
406
|
}, null, 2),
|
|
640
407
|
}],
|
|
641
408
|
};
|
|
642
409
|
}
|
|
643
|
-
// Build a contextual finding for governance
|
|
644
|
-
const rule = ANALYSIS_RULES.find((r) => r.id === finding_id);
|
|
645
|
-
const finding = {
|
|
646
|
-
id: rule.id,
|
|
647
|
-
severity: rule.severity,
|
|
648
|
-
category: rule.category,
|
|
649
|
-
title: rule.title,
|
|
650
|
-
description: rule.description,
|
|
651
|
-
suggestion: rule.suggestion,
|
|
652
|
-
};
|
|
653
410
|
return {
|
|
654
411
|
content: [{
|
|
655
412
|
type: "text",
|
|
656
413
|
text: JSON.stringify({
|
|
414
|
+
mode: "local_basic",
|
|
657
415
|
finding_id,
|
|
658
416
|
severity: rule.severity,
|
|
659
417
|
category: rule.category,
|
|
660
|
-
fix:
|
|
661
|
-
explanation:
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
},
|
|
418
|
+
fix: rule.suggestion,
|
|
419
|
+
explanation: `${rule.title}: ${rule.description}\n\nRecommended: ${rule.suggestion}`,
|
|
420
|
+
teaser: "Get LLM-powered code generation with actual fix diffs using: export DINGDAWG_API_KEY=your_key",
|
|
421
|
+
upgrade_url: "https://dingdawg.com/developers",
|
|
422
|
+
receipt_id: `fix_${Date.now().toString(36)}`,
|
|
666
423
|
free_suggestions_remaining: rateCheck.remaining,
|
|
667
|
-
|
|
424
|
+
governed: true,
|
|
668
425
|
}, null, 2),
|
|
669
426
|
}],
|
|
670
427
|
};
|