@homenshum/convex-mcp-nodebench 0.9.8 → 0.10.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 +9 -3
- package/dist/tools/actionAuditTools.js +4 -10
- package/dist/tools/authorizationTools.js +9 -39
- package/dist/tools/critterTools.js +14 -0
- package/dist/tools/functionTools.js +4 -7
- package/dist/tools/methodologyTools.js +14 -0
- package/dist/tools/qualityGateTools.js +6 -7
- package/dist/tools/queryEfficiencyTools.js +3 -6
- package/dist/tools/reportingTools.js +1 -1
- package/dist/tools/storageAuditTools.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ for (const tool of ALL_TOOLS) {
|
|
|
79
79
|
// ── Server setup ────────────────────────────────────────────────────
|
|
80
80
|
const server = new Server({
|
|
81
81
|
name: "convex-mcp-nodebench",
|
|
82
|
-
version: "0.9.
|
|
82
|
+
version: "0.9.4",
|
|
83
83
|
}, {
|
|
84
84
|
capabilities: {
|
|
85
85
|
tools: {},
|
|
@@ -349,7 +349,9 @@ After running all audits, summarize:
|
|
|
349
349
|
- Total issues by severity (critical/warning/info)
|
|
350
350
|
- Top 5 most impactful issues to fix first
|
|
351
351
|
- Quality gate score and grade
|
|
352
|
-
- Trend direction if previous audits exist (use convex_audit_diff)
|
|
352
|
+
- Trend direction if previous audits exist (use convex_audit_diff)
|
|
353
|
+
|
|
354
|
+
IMPORTANT — Analyst diagnostic: For every issue found, trace the ROOT CAUSE — don't just list what's wrong, explain WHY it happened. Ask "why" 5 times. Record root causes with convex_record_gotcha so they don't recur.`,
|
|
353
355
|
},
|
|
354
356
|
},
|
|
355
357
|
],
|
|
@@ -373,6 +375,8 @@ After running all audits, summarize:
|
|
|
373
375
|
6. convex_schema_migration_plan — Compare against previous snapshot for breaking changes
|
|
374
376
|
7. convex_quality_gate — Final quality check with thresholds
|
|
375
377
|
|
|
378
|
+
For each blocker: diagnose the ROOT CAUSE, not just the symptom. Explain WHY the issue exists and how to prevent it from recurring.
|
|
379
|
+
|
|
376
380
|
Report: DEPLOY or DO NOT DEPLOY with specific blockers to fix.`,
|
|
377
381
|
},
|
|
378
382
|
},
|
|
@@ -396,7 +400,9 @@ Report: DEPLOY or DO NOT DEPLOY with specific blockers to fix.`,
|
|
|
396
400
|
5. convex_audit_pagination — Unbounded numItems (DoS risk)
|
|
397
401
|
6. convex_audit_transaction_safety — Race condition risks
|
|
398
402
|
|
|
399
|
-
Focus on: unauthorized data access, unvalidated inputs, missing error boundaries, and potential data corruption vectors
|
|
403
|
+
Focus on: unauthorized data access, unvalidated inputs, missing error boundaries, and potential data corruption vectors.
|
|
404
|
+
|
|
405
|
+
Analyst diagnostic: For each security finding, trace upstream to the ROOT CAUSE. Don't just flag the symptom — explain what system condition allowed the vulnerability to exist. Record findings with convex_record_gotcha.`,
|
|
400
406
|
},
|
|
401
407
|
},
|
|
402
408
|
],
|
|
@@ -71,16 +71,11 @@ function auditActions(convexDir) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
const body = lines.slice(startLine, endLine).join("\n");
|
|
74
|
-
// Check 1: ctx.db access in action (
|
|
75
|
-
|
|
76
|
-
// e.g. ctx.runMutation(async (ctx) => { ctx.db.patch(...) }) — the inner ctx is a mutation context
|
|
77
|
-
const hasInlineCallback = /ctx\.run(Mutation|Query)\s*\(\s*async\s*\(/.test(body);
|
|
78
|
-
if (/ctx\.db\.(get|query|insert|patch|replace|delete)\s*\(/.test(body) && !hasInlineCallback) {
|
|
74
|
+
// Check 1: ctx.db access in action (FATAL — not allowed)
|
|
75
|
+
if (/ctx\.db\.(get|query|insert|patch|replace|delete)\s*\(/.test(body)) {
|
|
79
76
|
actionsWithDbAccess++;
|
|
80
|
-
// internalAction ctx.db is a warning (not client-callable, likely called from controlled contexts)
|
|
81
|
-
// public action ctx.db is a warning too (runtime error but caught during development/testing)
|
|
82
77
|
issues.push({
|
|
83
|
-
severity: "
|
|
78
|
+
severity: "critical",
|
|
84
79
|
location: `${relativePath}:${startLine + 1}`,
|
|
85
80
|
functionName: funcName,
|
|
86
81
|
message: `${funcType} "${funcName}" accesses ctx.db directly. Actions cannot access the database — use ctx.runQuery/ctx.runMutation instead.`,
|
|
@@ -90,9 +85,8 @@ function auditActions(convexDir) {
|
|
|
90
85
|
// Check 2: Node API usage without "use node"
|
|
91
86
|
if (!hasUseNode && (nodeApis.test(body) || nodeCryptoApis.test(body))) {
|
|
92
87
|
actionsWithoutNodeDirective++;
|
|
93
|
-
// Warning: missing directive is a deployment concern caught during development
|
|
94
88
|
issues.push({
|
|
95
|
-
severity: "
|
|
89
|
+
severity: "critical",
|
|
96
90
|
location: `${relativePath}:${startLine + 1}`,
|
|
97
91
|
functionName: funcName,
|
|
98
92
|
message: `${funcType} "${funcName}" uses Node.js APIs but file lacks "use node" directive. Will fail in Convex runtime.`,
|
|
@@ -41,27 +41,6 @@ function auditAuthorization(convexDir) {
|
|
|
41
41
|
const content = readFileSync(filePath, "utf-8");
|
|
42
42
|
const relativePath = filePath.replace(convexDir, "").replace(/^[\\/]/, "");
|
|
43
43
|
const lines = content.split("\n");
|
|
44
|
-
// Pre-scan: detect local helper functions that wrap getAuthUserId/getUserIdentity
|
|
45
|
-
// Pattern: function getSafeUserId(ctx) { ... getAuthUserId(ctx) ... }
|
|
46
|
-
const authHelperNames = [];
|
|
47
|
-
const helperFuncPattern = /(?:async\s+)?function\s+(\w+)\s*\(/g;
|
|
48
|
-
let hm;
|
|
49
|
-
while ((hm = helperFuncPattern.exec(content)) !== null) {
|
|
50
|
-
const hStart = content.slice(0, hm.index).split("\n").length - 1;
|
|
51
|
-
const hBody = lines.slice(hStart, Math.min(hStart + 30, lines.length)).join("\n");
|
|
52
|
-
if (/getAuthUserId|getUserIdentity|getAuthSessionId/.test(hBody)) {
|
|
53
|
-
authHelperNames.push(hm[1]);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// Also check arrow function helpers: const getUserId = async (ctx) => { ... }
|
|
57
|
-
const arrowHelperPattern = /(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g;
|
|
58
|
-
while ((hm = arrowHelperPattern.exec(content)) !== null) {
|
|
59
|
-
const hStart = content.slice(0, hm.index).split("\n").length - 1;
|
|
60
|
-
const hBody = lines.slice(hStart, Math.min(hStart + 20, lines.length)).join("\n");
|
|
61
|
-
if (/getAuthUserId|getUserIdentity|getAuthSessionId/.test(hBody)) {
|
|
62
|
-
authHelperNames.push(hm[1]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
44
|
for (let i = 0; i < lines.length; i++) {
|
|
66
45
|
const line = lines[i];
|
|
67
46
|
for (const ft of funcTypes) {
|
|
@@ -90,14 +69,10 @@ function auditAuthorization(convexDir) {
|
|
|
90
69
|
}
|
|
91
70
|
}
|
|
92
71
|
const body = lines.slice(i, endLine).join("\n");
|
|
93
|
-
|
|
94
|
-
const hasDirectAuth = /ctx\.auth\.getUserIdentity\s*\(\s*\)/.test(body) ||
|
|
72
|
+
const hasAuthCheck = /ctx\.auth\.getUserIdentity\s*\(\s*\)/.test(body) ||
|
|
95
73
|
/getUserIdentity/.test(body) ||
|
|
96
74
|
/getAuthUserId/.test(body) ||
|
|
97
75
|
/getAuthSessionId/.test(body);
|
|
98
|
-
const callsAuthHelper = authHelperNames.length > 0 &&
|
|
99
|
-
authHelperNames.some(h => new RegExp(`\\b${h}\\s*\\(`).test(body));
|
|
100
|
-
const hasAuthCheck = hasDirectAuth || callsAuthHelper;
|
|
101
76
|
const hasDbWrite = dbWriteOps.test(body);
|
|
102
77
|
const isSensitiveName = writeSensitive.test(funcName);
|
|
103
78
|
if (hasAuthCheck) {
|
|
@@ -106,13 +81,11 @@ function auditAuthorization(convexDir) {
|
|
|
106
81
|
const identityAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+ctx\.auth\.getUserIdentity\s*\(\s*\)/);
|
|
107
82
|
if (identityAssign) {
|
|
108
83
|
const varName = identityAssign[1];
|
|
109
|
-
|
|
110
|
-
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null|===\\s*${varName}|!==\\s*${varName}`).test(body);
|
|
84
|
+
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*!${varName}\\s*\\)`).test(body);
|
|
111
85
|
if (!hasNullCheck) {
|
|
112
86
|
uncheckedIdentity++;
|
|
113
|
-
// Queries can intentionally return different data for auth/unauth — warning not critical
|
|
114
87
|
issues.push({
|
|
115
|
-
severity: "
|
|
88
|
+
severity: "critical",
|
|
116
89
|
location: `${relativePath}:${i + 1}`,
|
|
117
90
|
functionName: funcName,
|
|
118
91
|
message: `${ft} "${funcName}" calls getUserIdentity() but doesn't check for null. Unauthenticated users will get undefined identity.`,
|
|
@@ -124,13 +97,11 @@ function auditAuthorization(convexDir) {
|
|
|
124
97
|
const authUserAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+getAuthUserId\s*\(/);
|
|
125
98
|
if (authUserAssign) {
|
|
126
99
|
const varName = authUserAssign[1];
|
|
127
|
-
|
|
128
|
-
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null|===\\s*${varName}|!==\\s*${varName}`).test(body);
|
|
100
|
+
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|throw.*!${varName}`).test(body);
|
|
129
101
|
if (!hasNullCheck) {
|
|
130
102
|
uncheckedIdentity++;
|
|
131
|
-
// Queries can intentionally return different data for auth/unauth — warning not critical
|
|
132
103
|
issues.push({
|
|
133
|
-
severity: "
|
|
104
|
+
severity: "critical",
|
|
134
105
|
location: `${relativePath}:${i + 1}`,
|
|
135
106
|
functionName: funcName,
|
|
136
107
|
message: `${ft} "${funcName}" calls getAuthUserId() but doesn't check for null. Unauthenticated users will get null userId.`,
|
|
@@ -141,13 +112,11 @@ function auditAuthorization(convexDir) {
|
|
|
141
112
|
}
|
|
142
113
|
else {
|
|
143
114
|
withoutAuth++;
|
|
144
|
-
//
|
|
145
|
-
// Downgraded from critical — missing auth is a security posture issue, not a runtime failure.
|
|
146
|
-
// Many monorepo mutations are system-level (called by actions/schedulers), not client-facing.
|
|
115
|
+
// Critical: public mutation/action with DB writes but no auth
|
|
147
116
|
if ((ft === "mutation" || ft === "action") && hasDbWrite) {
|
|
148
117
|
const sensitiveHint = isSensitiveName ? ` Name "${funcName}" suggests a destructive operation.` : "";
|
|
149
118
|
issues.push({
|
|
150
|
-
severity: "
|
|
119
|
+
severity: "critical",
|
|
151
120
|
location: `${relativePath}:${i + 1}`,
|
|
152
121
|
functionName: funcName,
|
|
153
122
|
message: `Public ${ft} "${funcName}" writes to DB without auth check. Any client can call this.${sensitiveHint}`,
|
|
@@ -155,8 +124,9 @@ function auditAuthorization(convexDir) {
|
|
|
155
124
|
});
|
|
156
125
|
}
|
|
157
126
|
else if (isSensitiveName) {
|
|
127
|
+
// Only flag sensitive name separately if not already caught by DB-write check
|
|
158
128
|
issues.push({
|
|
159
|
-
severity: "
|
|
129
|
+
severity: "critical",
|
|
160
130
|
location: `${relativePath}:${i + 1}`,
|
|
161
131
|
functionName: funcName,
|
|
162
132
|
message: `Public ${ft} "${funcName}" has a sensitive name but no auth check. Consider making it internal or adding auth.`,
|
|
@@ -137,6 +137,20 @@ function scoreCritterCheck(input) {
|
|
|
137
137
|
score += 10;
|
|
138
138
|
feedback.push("Good: success criteria defined — this makes the deploy gate concrete.");
|
|
139
139
|
}
|
|
140
|
+
// ── Check 10: Bandaid detection — symptom fixes without root-cause reasoning ──
|
|
141
|
+
const bandaidPatterns = [
|
|
142
|
+
"add try.?catch", "wrap in try", "catch the error", "suppress the error",
|
|
143
|
+
"add optional chaining", "add \\?\\.", "silence the warning",
|
|
144
|
+
"add as any", "cast to any", "ignore the type",
|
|
145
|
+
"add a timeout", "increase the timeout", "add a delay",
|
|
146
|
+
"delete the test", "skip the test", "disable the test",
|
|
147
|
+
"hide the error", "remove the warning",
|
|
148
|
+
];
|
|
149
|
+
const bandaidRegex = new RegExp(bandaidPatterns.join("|"), "i");
|
|
150
|
+
if (bandaidRegex.test(taskLower) && !whyLower.includes("root cause") && !whyLower.includes("because") && whyLower.length < 50) {
|
|
151
|
+
score -= 20;
|
|
152
|
+
feedback.push("Bandaid alert: this looks like a symptom fix. What's the root cause? Diagnose like an analyst, not a junior dev.");
|
|
153
|
+
}
|
|
140
154
|
score = Math.max(0, Math.min(100, score));
|
|
141
155
|
let verdict;
|
|
142
156
|
if (score >= 70) {
|
|
@@ -57,7 +57,7 @@ function extractFunctions(convexDir) {
|
|
|
57
57
|
filePath,
|
|
58
58
|
relativePath,
|
|
59
59
|
line: i + 1,
|
|
60
|
-
hasArgs: /args\s*:\s*[\{\v]/.test(chunk) || /args\s*:\s*v\./.test(chunk)
|
|
60
|
+
hasArgs: /args\s*:\s*[\{\v]/.test(chunk) || /args\s*:\s*v\./.test(chunk),
|
|
61
61
|
hasReturns: /returns\s*:\s*v\./.test(chunk),
|
|
62
62
|
hasHandler: /handler\s*:/.test(chunk),
|
|
63
63
|
});
|
|
@@ -76,10 +76,8 @@ function auditFunctions(convexDir) {
|
|
|
76
76
|
if (fn.type === "httpAction")
|
|
77
77
|
continue; // httpActions don't have args/returns validators
|
|
78
78
|
if (!fn.hasArgs) {
|
|
79
|
-
// Missing args validator is a best practice recommendation, not a runtime failure.
|
|
80
|
-
// Functions without args simply accept no arguments — no unvalidated input risk.
|
|
81
79
|
issues.push({
|
|
82
|
-
severity: "
|
|
80
|
+
severity: "critical",
|
|
83
81
|
location: `${fn.relativePath}:${fn.line}`,
|
|
84
82
|
functionName: fn.name,
|
|
85
83
|
message: `${fn.type} "${fn.name}" is missing args validator`,
|
|
@@ -96,9 +94,8 @@ function auditFunctions(convexDir) {
|
|
|
96
94
|
});
|
|
97
95
|
}
|
|
98
96
|
if (!fn.hasHandler) {
|
|
99
|
-
// Old shorthand syntax (query(async (ctx) => {})) works fine — just a style recommendation
|
|
100
97
|
issues.push({
|
|
101
|
-
severity: "
|
|
98
|
+
severity: "critical",
|
|
102
99
|
location: `${fn.relativePath}:${fn.line}`,
|
|
103
100
|
functionName: fn.name,
|
|
104
101
|
message: `${fn.type} "${fn.name}" is missing handler property (may be using old syntax)`,
|
|
@@ -287,7 +284,7 @@ export const functionTools = [
|
|
|
287
284
|
const functions = extractFunctions(convexDir);
|
|
288
285
|
// Store audit result
|
|
289
286
|
const db = getDb();
|
|
290
|
-
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "
|
|
287
|
+
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "function_audit", JSON.stringify(issues), issues.length);
|
|
291
288
|
const critical = issues.filter((i) => i.severity === "critical");
|
|
292
289
|
const warnings = issues.filter((i) => i.severity === "warning");
|
|
293
290
|
// Aggregate issues by category for cleaner output
|
|
@@ -83,6 +83,19 @@ const METHODOLOGY_CONTENT = {
|
|
|
83
83
|
],
|
|
84
84
|
tools: ["convex_suggest_indexes", "convex_audit_schema"],
|
|
85
85
|
},
|
|
86
|
+
analyst_diagnostic: {
|
|
87
|
+
title: "Analyst Diagnostic — Root Cause Over Bandaids",
|
|
88
|
+
description: "Guide yourself like an analyst diagnosing the root cause, NOT a junior dev slapping on a bandaid. Mandatory for all bug work.",
|
|
89
|
+
steps: [
|
|
90
|
+
"1. REPRODUCE: Confirm the exact failure mode before touching any code",
|
|
91
|
+
"2. TRACE UPSTREAM: Walk from symptom → intermediate state → root cause. Don't stop at the first error you see",
|
|
92
|
+
"3. ASK 'WHY' 5 TIMES: Each answer should go one level deeper into the system",
|
|
93
|
+
"4. FIX THE CAUSE: The right fix makes the symptom impossible, not just invisible",
|
|
94
|
+
"5. VERIFY NO SIDEWAYS SHIFT: Bandaids move bugs, they don't fix them — check adjacent behavior",
|
|
95
|
+
"6. RECORD: Use convex_record_gotcha with the root cause so the next person doesn't re-discover it",
|
|
96
|
+
],
|
|
97
|
+
tools: ["convex_search_gotchas", "convex_record_gotcha", "convex_critter_check"],
|
|
98
|
+
},
|
|
86
99
|
};
|
|
87
100
|
// ── Tool Definitions ────────────────────────────────────────────────
|
|
88
101
|
export const methodologyTools = [
|
|
@@ -101,6 +114,7 @@ export const methodologyTools = [
|
|
|
101
114
|
"convex_deploy_verification",
|
|
102
115
|
"convex_knowledge_management",
|
|
103
116
|
"convex_index_optimization",
|
|
117
|
+
"analyst_diagnostic",
|
|
104
118
|
],
|
|
105
119
|
description: "Which methodology to explain",
|
|
106
120
|
},
|
|
@@ -77,18 +77,17 @@ function runQualityGate(projectDir, thresholds) {
|
|
|
77
77
|
}
|
|
78
78
|
catch { /* skip */ }
|
|
79
79
|
}
|
|
80
|
-
// Check 4: Type safety (as any casts)
|
|
80
|
+
// Check 4: Type safety (as any casts)
|
|
81
81
|
const typeSafety = getLatest("type_safety");
|
|
82
82
|
if (typeSafety) {
|
|
83
83
|
try {
|
|
84
84
|
const issues = JSON.parse(typeSafety.issues_json);
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const asAnyIssues = Array.isArray(issues)
|
|
86
|
+
? issues.filter((i) => i.message?.includes("as any")).length
|
|
87
|
+
: 0;
|
|
87
88
|
// Each as-any issue represents a FILE, count from message for actual number
|
|
88
89
|
const actualCasts = Array.isArray(issues)
|
|
89
90
|
? issues.reduce((sum, i) => {
|
|
90
|
-
if (testEvalPattern.test(i.location ?? ""))
|
|
91
|
-
return sum; // skip test/eval files
|
|
92
91
|
const countMatch = i.message?.match(/(\d+)\s+`as any`/);
|
|
93
92
|
return sum + (countMatch ? parseInt(countMatch[1], 10) : 0);
|
|
94
93
|
}, 0)
|
|
@@ -103,13 +102,13 @@ function runQualityGate(projectDir, thresholds) {
|
|
|
103
102
|
}
|
|
104
103
|
catch { /* skip */ }
|
|
105
104
|
}
|
|
106
|
-
// Check 5: Unbounded collects
|
|
105
|
+
// Check 5: Unbounded collects
|
|
107
106
|
const queryEfficiency = getLatest("query_efficiency");
|
|
108
107
|
if (queryEfficiency) {
|
|
109
108
|
try {
|
|
110
109
|
const issues = JSON.parse(queryEfficiency.issues_json);
|
|
111
110
|
const unbounded = Array.isArray(issues)
|
|
112
|
-
? issues.filter((i) => i.
|
|
111
|
+
? issues.filter((i) => i.message?.includes(".collect()")).length
|
|
113
112
|
: 0;
|
|
114
113
|
checks.push({
|
|
115
114
|
metric: "unbounded_collects",
|
|
@@ -63,16 +63,13 @@ function auditQueryEfficiency(convexDir) {
|
|
|
63
63
|
const chain = lines.slice(chainStart, i + 1).join("\n");
|
|
64
64
|
if (!/\.take\s*\(/.test(chain) && !/\.paginate\s*\(/.test(chain)) {
|
|
65
65
|
collectWithoutLimit++;
|
|
66
|
+
// Check if it's a bounded table or could be large
|
|
66
67
|
const tableMatch = chain.match(/\.query\s*\(\s*["'](\w+)["']\s*\)/);
|
|
67
|
-
// Indexed queries are bounded by the index range — lower severity
|
|
68
|
-
const hasIndex = /\.withIndex\s*\(/.test(chain);
|
|
69
68
|
issues.push({
|
|
70
|
-
severity:
|
|
69
|
+
severity: "warning",
|
|
71
70
|
location: `${relativePath}:${i + 1}`,
|
|
72
71
|
message: `.collect() without .take() limit${tableMatch ? ` on table "${tableMatch[1]}"` : ""}. Could return entire table.`,
|
|
73
|
-
fix:
|
|
74
|
-
? "Consider adding .take(N) for predictable result sizes, even with index filtering"
|
|
75
|
-
: "Add .take(N) before .collect(), or use .paginate() for large result sets",
|
|
72
|
+
fix: "Add .take(N) before .collect(), or use .paginate() for large result sets",
|
|
76
73
|
});
|
|
77
74
|
}
|
|
78
75
|
}
|
|
@@ -75,7 +75,7 @@ function buildSarif(projectDir, auditTypes, limit) {
|
|
|
75
75
|
tool: {
|
|
76
76
|
driver: {
|
|
77
77
|
name: "convex-mcp-nodebench",
|
|
78
|
-
version: "0.9.
|
|
78
|
+
version: "0.9.4",
|
|
79
79
|
informationUri: "https://www.npmjs.com/package/@homenshum/convex-mcp-nodebench",
|
|
80
80
|
rules: [...rulesMap.values()],
|
|
81
81
|
},
|
|
@@ -133,7 +133,7 @@ export const storageAuditTools = [
|
|
|
133
133
|
}
|
|
134
134
|
const { issues, stats } = auditStorageUsage(convexDir);
|
|
135
135
|
const db = getDb();
|
|
136
|
-
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "
|
|
136
|
+
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "storage_usage", JSON.stringify(issues), issues.length);
|
|
137
137
|
return {
|
|
138
138
|
summary: {
|
|
139
139
|
...stats,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homenshum/convex-mcp-nodebench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Convex-specific MCP server applying NodeBench self-instruct diligence patterns to Convex development. Schema audit, function compliance, deployment gates, persistent gotcha DB, and methodology guidance. Complements Context7 (raw docs) and official Convex MCP (deployment introspection) with structured verification workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|