@homenshum/convex-mcp-nodebench 0.9.4 → 0.9.5
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
CHANGED
|
@@ -72,7 +72,10 @@ function auditActions(convexDir) {
|
|
|
72
72
|
}
|
|
73
73
|
const body = lines.slice(startLine, endLine).join("\n");
|
|
74
74
|
// Check 1: ctx.db access in action (FATAL — not allowed)
|
|
75
|
-
if
|
|
75
|
+
// BUT: skip if ctx.db is inside an inline ctx.runMutation/ctx.runQuery callback
|
|
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) {
|
|
76
79
|
actionsWithDbAccess++;
|
|
77
80
|
issues.push({
|
|
78
81
|
severity: "critical",
|
|
@@ -81,7 +81,8 @@ function auditAuthorization(convexDir) {
|
|
|
81
81
|
const identityAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+ctx\.auth\.getUserIdentity\s*\(\s*\)/);
|
|
82
82
|
if (identityAssign) {
|
|
83
83
|
const varName = identityAssign[1];
|
|
84
|
-
|
|
84
|
+
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var
|
|
85
|
+
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`).test(body);
|
|
85
86
|
if (!hasNullCheck) {
|
|
86
87
|
uncheckedIdentity++;
|
|
87
88
|
issues.push({
|
|
@@ -97,7 +98,8 @@ function auditAuthorization(convexDir) {
|
|
|
97
98
|
const authUserAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+getAuthUserId\s*\(/);
|
|
98
99
|
if (authUserAssign) {
|
|
99
100
|
const varName = authUserAssign[1];
|
|
100
|
-
|
|
101
|
+
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var
|
|
102
|
+
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`).test(body);
|
|
101
103
|
if (!hasNullCheck) {
|
|
102
104
|
uncheckedIdentity++;
|
|
103
105
|
issues.push({
|
|
@@ -77,17 +77,18 @@ 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) — exclude test/eval files
|
|
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
|
-
|
|
87
|
-
: 0;
|
|
85
|
+
// Exclude test, eval, benchmark, and fixture files from production quality gate
|
|
86
|
+
const testEvalPattern = /test|__tests__|spec|\.test\.|\.spec\.|fixtures|mocks|evaluation|liveEval|liveApiSmoke|benchmark|calibration|harness|quickTest|comprehensiveTest|comprehensiveEval/i;
|
|
88
87
|
// Each as-any issue represents a FILE, count from message for actual number
|
|
89
88
|
const actualCasts = Array.isArray(issues)
|
|
90
89
|
? issues.reduce((sum, i) => {
|
|
90
|
+
if (testEvalPattern.test(i.location ?? ""))
|
|
91
|
+
return sum; // skip test/eval files
|
|
91
92
|
const countMatch = i.message?.match(/(\d+)\s+`as any`/);
|
|
92
93
|
return sum + (countMatch ? parseInt(countMatch[1], 10) : 0);
|
|
93
94
|
}, 0)
|
|
@@ -102,13 +103,13 @@ function runQualityGate(projectDir, thresholds) {
|
|
|
102
103
|
}
|
|
103
104
|
catch { /* skip */ }
|
|
104
105
|
}
|
|
105
|
-
// Check 5: Unbounded collects
|
|
106
|
+
// Check 5: Unbounded collects (only warning-level — indexed collects are downgraded to info)
|
|
106
107
|
const queryEfficiency = getLatest("query_efficiency");
|
|
107
108
|
if (queryEfficiency) {
|
|
108
109
|
try {
|
|
109
110
|
const issues = JSON.parse(queryEfficiency.issues_json);
|
|
110
111
|
const unbounded = Array.isArray(issues)
|
|
111
|
-
? issues.filter((i) => i.message?.includes(".collect()")).length
|
|
112
|
+
? issues.filter((i) => i.severity === "warning" && i.message?.includes(".collect()")).length
|
|
112
113
|
: 0;
|
|
113
114
|
checks.push({
|
|
114
115
|
metric: "unbounded_collects",
|
|
@@ -63,13 +63,16 @@ 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
|
|
67
66
|
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);
|
|
68
69
|
issues.push({
|
|
69
|
-
severity: "warning",
|
|
70
|
+
severity: hasIndex ? "info" : "warning",
|
|
70
71
|
location: `${relativePath}:${i + 1}`,
|
|
71
72
|
message: `.collect() without .take() limit${tableMatch ? ` on table "${tableMatch[1]}"` : ""}. Could return entire table.`,
|
|
72
|
-
fix:
|
|
73
|
+
fix: hasIndex
|
|
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",
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -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.5",
|
|
79
79
|
informationUri: "https://www.npmjs.com/package/@homenshum/convex-mcp-nodebench",
|
|
80
80
|
rules: [...rulesMap.values()],
|
|
81
81
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homenshum/convex-mcp-nodebench",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
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": {
|