@homenshum/convex-mcp-nodebench 0.7.0 → 0.9.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 +243 -2
- package/dist/tools/architectTools.d.ts +15 -0
- package/dist/tools/architectTools.js +526 -0
- package/dist/tools/qualityGateTools.d.ts +2 -0
- package/dist/tools/qualityGateTools.js +204 -0
- package/dist/tools/reportingTools.d.ts +2 -0
- package/dist/tools/reportingTools.js +240 -0
- package/dist/tools/schedulerTools.d.ts +2 -0
- package/dist/tools/schedulerTools.js +197 -0
- package/dist/tools/toolRegistry.js +117 -0
- package/dist/tools/vectorSearchTools.d.ts +2 -0
- package/dist/tools/vectorSearchTools.js +192 -0
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -411,6 +411,123 @@ export const REGISTRY = [
|
|
|
411
411
|
phase: "deploy",
|
|
412
412
|
complexity: "medium",
|
|
413
413
|
},
|
|
414
|
+
// ── Reporting Tools ─────────────────────
|
|
415
|
+
{
|
|
416
|
+
name: "convex_export_sarif",
|
|
417
|
+
category: "integration",
|
|
418
|
+
tags: ["sarif", "export", "report", "github", "code-scanning", "ci", "static-analysis"],
|
|
419
|
+
quickRef: {
|
|
420
|
+
nextAction: "Upload the SARIF file to GitHub Code Scanning or open in VS Code SARIF Viewer",
|
|
421
|
+
nextTools: ["convex_audit_diff", "convex_quality_gate"],
|
|
422
|
+
methodology: "convex_deploy_verification",
|
|
423
|
+
relatedGotchas: [],
|
|
424
|
+
confidence: "high",
|
|
425
|
+
},
|
|
426
|
+
phase: "deploy",
|
|
427
|
+
complexity: "low",
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "convex_audit_diff",
|
|
431
|
+
category: "deployment",
|
|
432
|
+
tags: ["diff", "baseline", "trend", "new-issues", "fixed", "improving", "degrading", "comparison"],
|
|
433
|
+
quickRef: {
|
|
434
|
+
nextAction: "Focus on fixing new issues first, then tackle existing ones",
|
|
435
|
+
nextTools: ["convex_export_sarif", "convex_quality_gate"],
|
|
436
|
+
methodology: "convex_deploy_verification",
|
|
437
|
+
relatedGotchas: [],
|
|
438
|
+
confidence: "high",
|
|
439
|
+
},
|
|
440
|
+
phase: "deploy",
|
|
441
|
+
complexity: "medium",
|
|
442
|
+
},
|
|
443
|
+
// ── Vector Search Tools ─────────────────
|
|
444
|
+
{
|
|
445
|
+
name: "convex_audit_vector_search",
|
|
446
|
+
category: "schema",
|
|
447
|
+
tags: ["vector", "search", "embedding", "dimension", "similarity", "vectorIndex", "float64", "AI", "RAG"],
|
|
448
|
+
quickRef: {
|
|
449
|
+
nextAction: "Fix dimension mismatches and add filterFields to vector indexes for better performance",
|
|
450
|
+
nextTools: ["convex_audit_schema", "convex_suggest_indexes"],
|
|
451
|
+
methodology: "convex_schema_audit",
|
|
452
|
+
relatedGotchas: [],
|
|
453
|
+
confidence: "high",
|
|
454
|
+
},
|
|
455
|
+
phase: "audit",
|
|
456
|
+
complexity: "medium",
|
|
457
|
+
},
|
|
458
|
+
// ── Scheduler Tools ─────────────────────
|
|
459
|
+
{
|
|
460
|
+
name: "convex_audit_schedulers",
|
|
461
|
+
category: "function",
|
|
462
|
+
tags: ["scheduler", "runAfter", "runAt", "schedule", "cron", "infinite-loop", "backoff", "retry", "delayed"],
|
|
463
|
+
quickRef: {
|
|
464
|
+
nextAction: "Fix self-scheduling loops (add termination conditions) and implement exponential backoff",
|
|
465
|
+
nextTools: ["convex_check_crons", "convex_audit_actions"],
|
|
466
|
+
methodology: "convex_function_compliance",
|
|
467
|
+
relatedGotchas: [],
|
|
468
|
+
confidence: "high",
|
|
469
|
+
},
|
|
470
|
+
phase: "audit",
|
|
471
|
+
complexity: "medium",
|
|
472
|
+
},
|
|
473
|
+
// ── Quality Gate Tools ──────────────────
|
|
474
|
+
{
|
|
475
|
+
name: "convex_quality_gate",
|
|
476
|
+
category: "deployment",
|
|
477
|
+
tags: ["quality", "gate", "score", "grade", "threshold", "sonarqube", "metrics", "pass-fail", "A-F"],
|
|
478
|
+
quickRef: {
|
|
479
|
+
nextAction: "Fix blockers to raise your grade, then run again to verify improvement",
|
|
480
|
+
nextTools: ["convex_audit_diff", "convex_export_sarif", "convex_pre_deploy_gate"],
|
|
481
|
+
methodology: "convex_deploy_verification",
|
|
482
|
+
relatedGotchas: [],
|
|
483
|
+
confidence: "high",
|
|
484
|
+
},
|
|
485
|
+
phase: "deploy",
|
|
486
|
+
complexity: "high",
|
|
487
|
+
},
|
|
488
|
+
// ── Architect Tools ──────────────────────
|
|
489
|
+
{
|
|
490
|
+
name: "convex_scan_capabilities",
|
|
491
|
+
category: "architect",
|
|
492
|
+
tags: ["scan", "capabilities", "structure", "patterns", "analysis", "functions", "schema", "data-access", "regex"],
|
|
493
|
+
quickRef: {
|
|
494
|
+
nextAction: "Use the capability report to identify what patterns exist before implementing new features",
|
|
495
|
+
nextTools: ["convex_verify_concept", "convex_generate_plan"],
|
|
496
|
+
methodology: "convex_schema_audit",
|
|
497
|
+
relatedGotchas: [],
|
|
498
|
+
confidence: "high",
|
|
499
|
+
},
|
|
500
|
+
phase: "audit",
|
|
501
|
+
complexity: "low",
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "convex_verify_concept",
|
|
505
|
+
category: "architect",
|
|
506
|
+
tags: ["verify", "concept", "signatures", "gap-analysis", "implementation", "check", "progress", "regex"],
|
|
507
|
+
quickRef: {
|
|
508
|
+
nextAction: "Pass missing signatures to convex_generate_plan to get Convex-specific implementation steps",
|
|
509
|
+
nextTools: ["convex_generate_plan", "convex_scan_capabilities"],
|
|
510
|
+
methodology: "convex_schema_audit",
|
|
511
|
+
relatedGotchas: [],
|
|
512
|
+
confidence: "high",
|
|
513
|
+
},
|
|
514
|
+
phase: "audit",
|
|
515
|
+
complexity: "low",
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "convex_generate_plan",
|
|
519
|
+
category: "architect",
|
|
520
|
+
tags: ["plan", "implementation", "strategy", "missing", "signatures", "steps", "inject", "convex-specific"],
|
|
521
|
+
quickRef: {
|
|
522
|
+
nextAction: "Implement each step in order, then re-verify with convex_verify_concept to track progress",
|
|
523
|
+
nextTools: ["convex_verify_concept", "convex_quality_gate"],
|
|
524
|
+
methodology: "convex_function_compliance",
|
|
525
|
+
relatedGotchas: [],
|
|
526
|
+
confidence: "high",
|
|
527
|
+
},
|
|
528
|
+
phase: "implement",
|
|
529
|
+
complexity: "low",
|
|
530
|
+
},
|
|
414
531
|
];
|
|
415
532
|
export function getQuickRef(toolName) {
|
|
416
533
|
const entry = REGISTRY.find((e) => e.name === toolName);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { getDb, genId } from "../db.js";
|
|
4
|
+
import { getQuickRef } from "./toolRegistry.js";
|
|
5
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
6
|
+
function findConvexDir(projectDir) {
|
|
7
|
+
const candidates = [join(projectDir, "convex"), join(projectDir, "src", "convex")];
|
|
8
|
+
for (const c of candidates) {
|
|
9
|
+
if (existsSync(c))
|
|
10
|
+
return c;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function collectTsFiles(dir) {
|
|
15
|
+
const results = [];
|
|
16
|
+
if (!existsSync(dir))
|
|
17
|
+
return results;
|
|
18
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const full = join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "_generated") {
|
|
22
|
+
results.push(...collectTsFiles(full));
|
|
23
|
+
}
|
|
24
|
+
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
25
|
+
results.push(full);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
// Common embedding model dimensions
|
|
31
|
+
const KNOWN_DIMENSIONS = {
|
|
32
|
+
384: "all-MiniLM-L6-v2",
|
|
33
|
+
512: "e5-small",
|
|
34
|
+
768: "text-embedding-004 / all-mpnet-base-v2",
|
|
35
|
+
1024: "e5-large / cohere-embed-v3",
|
|
36
|
+
1536: "text-embedding-3-small / text-embedding-ada-002",
|
|
37
|
+
3072: "text-embedding-3-large",
|
|
38
|
+
};
|
|
39
|
+
function auditVectorSearch(convexDir) {
|
|
40
|
+
const issues = [];
|
|
41
|
+
const schemaPath = join(convexDir, "schema.ts");
|
|
42
|
+
// Parse schema for vectorIndex definitions
|
|
43
|
+
const vectorIndexes = [];
|
|
44
|
+
if (existsSync(schemaPath)) {
|
|
45
|
+
const schema = readFileSync(schemaPath, "utf-8");
|
|
46
|
+
const lines = schema.split("\n");
|
|
47
|
+
for (let i = 0; i < lines.length; i++) {
|
|
48
|
+
// Match .vectorIndex("name", { ... })
|
|
49
|
+
const viMatch = lines[i].match(/\.vectorIndex\s*\(\s*["']([^"']+)["']/);
|
|
50
|
+
if (viMatch) {
|
|
51
|
+
// Look ahead for dimensions and filterFields
|
|
52
|
+
const chunk = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
|
|
53
|
+
const dimMatch = chunk.match(/dimensions\s*:\s*(\d+)/);
|
|
54
|
+
const filterMatch = chunk.match(/filterFields\s*:\s*\[([^\]]*)\]/);
|
|
55
|
+
const dims = dimMatch ? parseInt(dimMatch[1], 10) : 0;
|
|
56
|
+
const filters = filterMatch
|
|
57
|
+
? filterMatch[1].match(/["']([^"']+)["']/g)?.map(s => s.replace(/["']/g, "")) ?? []
|
|
58
|
+
: [];
|
|
59
|
+
vectorIndexes.push({ table: viMatch[1], dimensions: dims, filterFields: filters, line: i + 1 });
|
|
60
|
+
// Check: uncommon dimension size
|
|
61
|
+
if (dims > 0 && !KNOWN_DIMENSIONS[dims]) {
|
|
62
|
+
const nearest = Object.keys(KNOWN_DIMENSIONS)
|
|
63
|
+
.map(Number)
|
|
64
|
+
.sort((a, b) => Math.abs(a - dims) - Math.abs(b - dims))[0];
|
|
65
|
+
issues.push({
|
|
66
|
+
severity: "warning",
|
|
67
|
+
location: `schema.ts:${i + 1}`,
|
|
68
|
+
message: `Vector index "${viMatch[1]}" has ${dims} dimensions — not a standard embedding size. Did you mean ${nearest} (${KNOWN_DIMENSIONS[nearest]})?`,
|
|
69
|
+
fix: `Verify your embedding model output size matches ${dims} dimensions`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Check: no filter fields
|
|
73
|
+
if (filters.length === 0) {
|
|
74
|
+
issues.push({
|
|
75
|
+
severity: "info",
|
|
76
|
+
location: `schema.ts:${i + 1}`,
|
|
77
|
+
message: `Vector index "${viMatch[1]}" has no filterFields. Vector searches will scan all vectors — add filters for better performance.`,
|
|
78
|
+
fix: 'Add filterFields: ["field1", "field2"] to narrow vector search scope',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Check: vector field declared as v.array(v.number()) instead of v.array(v.float64())
|
|
83
|
+
if (/v\.array\s*\(\s*v\.number\s*\(\s*\)\s*\)/.test(lines[i]) &&
|
|
84
|
+
/embed|vector|embedding/i.test(lines.slice(Math.max(0, i - 3), i + 1).join("\n"))) {
|
|
85
|
+
issues.push({
|
|
86
|
+
severity: "warning",
|
|
87
|
+
location: `schema.ts:${i + 1}`,
|
|
88
|
+
message: "Vector field uses v.array(v.number()) — Convex vector search requires v.array(v.float64()) for proper storage.",
|
|
89
|
+
fix: "Change to v.array(v.float64()) for vector fields",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Scan code files for .vectorSearch() usage
|
|
95
|
+
const files = collectTsFiles(convexDir);
|
|
96
|
+
let vectorSearchCallCount = 0;
|
|
97
|
+
for (const filePath of files) {
|
|
98
|
+
const content = readFileSync(filePath, "utf-8");
|
|
99
|
+
const relativePath = filePath.replace(convexDir, "").replace(/^[\\/]/, "");
|
|
100
|
+
const lines = content.split("\n");
|
|
101
|
+
for (let i = 0; i < lines.length; i++) {
|
|
102
|
+
const vsMatch = lines[i].match(/\.vectorSearch\s*\(\s*["']([^"']+)["']/);
|
|
103
|
+
if (vsMatch) {
|
|
104
|
+
vectorSearchCallCount++;
|
|
105
|
+
const indexName = vsMatch[1];
|
|
106
|
+
// Check if the referenced index exists
|
|
107
|
+
const matchingIdx = vectorIndexes.find(vi => vi.table === indexName);
|
|
108
|
+
if (!matchingIdx && vectorIndexes.length > 0) {
|
|
109
|
+
issues.push({
|
|
110
|
+
severity: "critical",
|
|
111
|
+
location: `${relativePath}:${i + 1}`,
|
|
112
|
+
message: `vectorSearch references index "${indexName}" which is not defined in schema.ts.`,
|
|
113
|
+
fix: `Add a .vectorIndex("${indexName}", { ... }) to the appropriate table in schema.ts`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Check: no filter parameter when filterFields exist
|
|
117
|
+
const chunk = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
|
|
118
|
+
if (matchingIdx && matchingIdx.filterFields.length > 0 && !/filter\s*:/.test(chunk)) {
|
|
119
|
+
issues.push({
|
|
120
|
+
severity: "info",
|
|
121
|
+
location: `${relativePath}:${i + 1}`,
|
|
122
|
+
message: `vectorSearch on "${indexName}" doesn't use filter — available filterFields: ${matchingIdx.filterFields.join(", ")}`,
|
|
123
|
+
fix: "Add filter parameter to narrow results and improve performance",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Check: hardcoded vector dimensions in code (should match schema)
|
|
127
|
+
const vecLiteralMatch = chunk.match(/new\s+Float64Array\s*\(\s*(\d+)\s*\)|Array\s*\(\s*(\d+)\s*\)\.fill/);
|
|
128
|
+
if (vecLiteralMatch && matchingIdx) {
|
|
129
|
+
const codeDims = parseInt(vecLiteralMatch[1] || vecLiteralMatch[2], 10);
|
|
130
|
+
if (codeDims !== matchingIdx.dimensions && matchingIdx.dimensions > 0) {
|
|
131
|
+
issues.push({
|
|
132
|
+
severity: "critical",
|
|
133
|
+
location: `${relativePath}:${i + 1}`,
|
|
134
|
+
message: `Vector dimensions mismatch: code uses ${codeDims} but schema defines ${matchingIdx.dimensions} for index "${indexName}".`,
|
|
135
|
+
fix: `Ensure embedding model output (${codeDims}) matches schema dimensions (${matchingIdx.dimensions})`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const tablesWithVectors = [...new Set(vectorIndexes.map(vi => vi.table))];
|
|
143
|
+
const dimensions = [...new Set(vectorIndexes.map(vi => vi.dimensions).filter(d => d > 0))];
|
|
144
|
+
return {
|
|
145
|
+
issues,
|
|
146
|
+
stats: {
|
|
147
|
+
vectorIndexCount: vectorIndexes.length,
|
|
148
|
+
vectorSearchCallCount,
|
|
149
|
+
tablesWithVectors,
|
|
150
|
+
dimensions,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// ── Tool Definition ─────────────────────────────────────────────────
|
|
155
|
+
export const vectorSearchTools = [
|
|
156
|
+
{
|
|
157
|
+
name: "convex_audit_vector_search",
|
|
158
|
+
description: "Audit Convex vector search implementation: validates vectorIndex dimensions against known embedding models, checks for missing filterFields, v.array(v.float64()) usage, dimension mismatches between schema and code, and undefined index references.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
projectDir: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Absolute path to the project root containing a convex/ directory",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
required: ["projectDir"],
|
|
168
|
+
},
|
|
169
|
+
handler: async (args) => {
|
|
170
|
+
const projectDir = resolve(args.projectDir);
|
|
171
|
+
const convexDir = findConvexDir(projectDir);
|
|
172
|
+
if (!convexDir) {
|
|
173
|
+
return { error: "No convex/ directory found" };
|
|
174
|
+
}
|
|
175
|
+
const { issues, stats } = auditVectorSearch(convexDir);
|
|
176
|
+
const db = getDb();
|
|
177
|
+
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "vector_search", JSON.stringify(issues), issues.length);
|
|
178
|
+
return {
|
|
179
|
+
summary: {
|
|
180
|
+
...stats,
|
|
181
|
+
totalIssues: issues.length,
|
|
182
|
+
critical: issues.filter(i => i.severity === "critical").length,
|
|
183
|
+
warnings: issues.filter(i => i.severity === "warning").length,
|
|
184
|
+
knownDimensions: KNOWN_DIMENSIONS,
|
|
185
|
+
},
|
|
186
|
+
issues: issues.slice(0, 30),
|
|
187
|
+
quickRef: getQuickRef("convex_audit_vector_search"),
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
//# sourceMappingURL=vectorSearchTools.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface ConvexQuickRef {
|
|
|
13
13
|
}
|
|
14
14
|
export interface ToolRegistryEntry {
|
|
15
15
|
name: string;
|
|
16
|
-
category: "schema" | "function" | "deployment" | "learning" | "methodology" | "integration";
|
|
16
|
+
category: "schema" | "function" | "deployment" | "learning" | "methodology" | "integration" | "architect";
|
|
17
17
|
tags: string[];
|
|
18
18
|
quickRef: ConvexQuickRef;
|
|
19
19
|
phase: "audit" | "implement" | "test" | "deploy" | "learn" | "meta";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homenshum/convex-mcp-nodebench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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": {
|