@homenshum/convex-mcp-nodebench 0.3.0 → 0.4.1
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 +2 -0
- package/dist/tools/critterTools.d.ts +12 -0
- package/dist/tools/critterTools.js +204 -0
- package/dist/tools/functionTools.js +21 -2
- package/dist/tools/integrationBridgeTools.js +4 -0
- package/dist/tools/methodologyTools.js +8 -1
- package/dist/tools/schemaTools.js +14 -8
- package/dist/tools/toolRegistry.d.ts +5 -2
- package/dist/tools/toolRegistry.js +20 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { integrationBridgeTools } from "./tools/integrationBridgeTools.js";
|
|
|
25
25
|
import { cronTools } from "./tools/cronTools.js";
|
|
26
26
|
import { componentTools } from "./tools/componentTools.js";
|
|
27
27
|
import { httpTools } from "./tools/httpTools.js";
|
|
28
|
+
import { critterTools } from "./tools/critterTools.js";
|
|
28
29
|
import { CONVEX_GOTCHAS } from "./gotchaSeed.js";
|
|
29
30
|
import { REGISTRY } from "./tools/toolRegistry.js";
|
|
30
31
|
import { initEmbeddingIndex } from "./tools/embeddingProvider.js";
|
|
@@ -39,6 +40,7 @@ const ALL_TOOLS = [
|
|
|
39
40
|
...cronTools,
|
|
40
41
|
...componentTools,
|
|
41
42
|
...httpTools,
|
|
43
|
+
...critterTools,
|
|
42
44
|
];
|
|
43
45
|
const toolMap = new Map();
|
|
44
46
|
for (const tool of ALL_TOOLS) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Critter Tools — The accountability partner that wants to know everything.
|
|
3
|
+
*
|
|
4
|
+
* Convex-flavored version: "Why are you making this schema change? Who needs this function?"
|
|
5
|
+
* The friction is the feature — slowing down to think prevents Convex-specific pitfalls
|
|
6
|
+
* like unnecessary indexes, over-normalized schemas, and functions nobody calls.
|
|
7
|
+
*
|
|
8
|
+
* 1 tool:
|
|
9
|
+
* - convex_critter_check: Pre-action intentionality check for Convex work
|
|
10
|
+
*/
|
|
11
|
+
import type { McpTool } from "../types.js";
|
|
12
|
+
export declare const critterTools: McpTool[];
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Critter Tools — The accountability partner that wants to know everything.
|
|
3
|
+
*
|
|
4
|
+
* Convex-flavored version: "Why are you making this schema change? Who needs this function?"
|
|
5
|
+
* The friction is the feature — slowing down to think prevents Convex-specific pitfalls
|
|
6
|
+
* like unnecessary indexes, over-normalized schemas, and functions nobody calls.
|
|
7
|
+
*
|
|
8
|
+
* 1 tool:
|
|
9
|
+
* - convex_critter_check: Pre-action intentionality check for Convex work
|
|
10
|
+
*/
|
|
11
|
+
import { getDb } from "../db.js";
|
|
12
|
+
// ── DB setup ────────────────────────────────────────────────────────────────
|
|
13
|
+
function ensureCritterTable() {
|
|
14
|
+
const db = getDb();
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS critter_checks (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
task TEXT NOT NULL,
|
|
19
|
+
why TEXT NOT NULL,
|
|
20
|
+
who TEXT NOT NULL,
|
|
21
|
+
success_looks_like TEXT,
|
|
22
|
+
score INTEGER NOT NULL,
|
|
23
|
+
verdict TEXT NOT NULL,
|
|
24
|
+
feedback TEXT NOT NULL,
|
|
25
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
function scoreCritterCheck(input) {
|
|
30
|
+
const feedback = [];
|
|
31
|
+
let score = 100;
|
|
32
|
+
const taskLower = input.task.toLowerCase().trim();
|
|
33
|
+
const whyLower = input.why.toLowerCase().trim();
|
|
34
|
+
const whoLower = input.who.toLowerCase().trim();
|
|
35
|
+
// Check 1: Circular reasoning (threshold 0.5)
|
|
36
|
+
const taskWords = new Set(taskLower.split(/\s+/).filter((w) => w.length > 3));
|
|
37
|
+
const whyWords = whyLower.split(/\s+/).filter((w) => w.length > 3);
|
|
38
|
+
const overlap = whyWords.filter((w) => taskWords.has(w));
|
|
39
|
+
if (whyWords.length > 0 && overlap.length / whyWords.length > 0.5) {
|
|
40
|
+
score -= 30;
|
|
41
|
+
feedback.push("Circular: your 'why' mostly restates the task. What user outcome does this enable?");
|
|
42
|
+
}
|
|
43
|
+
// Check 2: Vague audience
|
|
44
|
+
const vagueAudiences = ["users", "everyone", "people", "the team", "stakeholders", "clients", "customers", "developers"];
|
|
45
|
+
if (vagueAudiences.includes(whoLower)) {
|
|
46
|
+
score -= 20;
|
|
47
|
+
feedback.push(`"${input.who}" is too broad. Which user role or API consumer specifically?`);
|
|
48
|
+
}
|
|
49
|
+
// Check 3: Empty or too short
|
|
50
|
+
if (whyLower.length === 0) {
|
|
51
|
+
score -= 40;
|
|
52
|
+
feedback.push("Empty 'why': you haven't stated any purpose at all.");
|
|
53
|
+
}
|
|
54
|
+
else if (whyLower.length < 10) {
|
|
55
|
+
score -= 25;
|
|
56
|
+
feedback.push("The 'why' is too short. What problem does this solve?");
|
|
57
|
+
}
|
|
58
|
+
if (whoLower.length < 3) {
|
|
59
|
+
score -= 25;
|
|
60
|
+
feedback.push("The 'who' is too short. Specify who benefits.");
|
|
61
|
+
}
|
|
62
|
+
// Check 4: Deference over understanding
|
|
63
|
+
const deferPatterns = ["was told", "asked to", "ticket says", "was asked", "jira", "they said"];
|
|
64
|
+
if (deferPatterns.some((p) => whyLower.includes(p))) {
|
|
65
|
+
score -= 15;
|
|
66
|
+
feedback.push("Citing authority instead of understanding purpose. Why does this matter to the product?");
|
|
67
|
+
}
|
|
68
|
+
// Check 5: Non-answer patterns (count matches, -20 each, cap -40)
|
|
69
|
+
const nonAnswerPatterns = [
|
|
70
|
+
"just because", "don't know", "not sure", "why not", "might need it",
|
|
71
|
+
"no reason", "no idea", "whatever", "idk", "tbd",
|
|
72
|
+
];
|
|
73
|
+
const nonAnswerHits = nonAnswerPatterns.filter((p) => whyLower.includes(p)).length;
|
|
74
|
+
if (nonAnswerHits > 0) {
|
|
75
|
+
const nonAnswerPenalty = Math.min(nonAnswerHits * 20, 40);
|
|
76
|
+
score -= nonAnswerPenalty;
|
|
77
|
+
feedback.push("Non-answer: your 'why' signals unclear purpose. What specific problem does this solve?");
|
|
78
|
+
}
|
|
79
|
+
// Check 6: Repetitive padding (why + who)
|
|
80
|
+
const whyAllWords = whyLower.split(/\s+/).filter((w) => w.length > 2);
|
|
81
|
+
if (whyAllWords.length >= 5) {
|
|
82
|
+
const whyUniqueWords = new Set(whyAllWords);
|
|
83
|
+
if (whyUniqueWords.size / whyAllWords.length < 0.4) {
|
|
84
|
+
score -= 25;
|
|
85
|
+
feedback.push("Repetitive: your 'why' repeats the same words. Articulate distinct reasoning.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const whoAllWords = whoLower.split(/\s+/).filter((w) => w.length > 2);
|
|
89
|
+
if (whoAllWords.length >= 5) {
|
|
90
|
+
const whoUniqueWords = new Set(whoAllWords);
|
|
91
|
+
if (whoUniqueWords.size / whoAllWords.length < 0.4) {
|
|
92
|
+
score -= 25;
|
|
93
|
+
feedback.push("Repetitive: your 'who' repeats the same words. Name a real audience.");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Check 7: Buzzword-heavy corporate-speak (scans why + who)
|
|
97
|
+
const buzzwords = [
|
|
98
|
+
"leverage", "synergies", "synergy", "paradigm", "holistic", "alignment",
|
|
99
|
+
"transformation", "innovative", "disruptive", "best practices",
|
|
100
|
+
"streamline", "ecosystem", "actionable", "circle back",
|
|
101
|
+
];
|
|
102
|
+
const allText = `${whyLower} ${whoLower}`;
|
|
103
|
+
const buzzCount = buzzwords.filter((b) => allText.includes(b)).length;
|
|
104
|
+
if (buzzCount >= 4) {
|
|
105
|
+
score -= 35;
|
|
106
|
+
feedback.push("Buzzword-heavy: corporate-speak without concrete meaning. What specific problem does this solve?");
|
|
107
|
+
}
|
|
108
|
+
else if (buzzCount >= 3) {
|
|
109
|
+
score -= 30;
|
|
110
|
+
feedback.push("Buzzword-heavy: corporate-speak without concrete meaning. What specific problem does this solve?");
|
|
111
|
+
}
|
|
112
|
+
else if (buzzCount >= 2) {
|
|
113
|
+
score -= 20;
|
|
114
|
+
feedback.push("Buzzword-heavy: corporate-speak without concrete meaning. What specific problem does this solve?");
|
|
115
|
+
}
|
|
116
|
+
// Check 8: Hedging language
|
|
117
|
+
const hedgeWords = ["could", "potentially", "maybe", "possibly", "might", "perhaps", "hopefully"];
|
|
118
|
+
const hedgeCount = hedgeWords.filter((h) => {
|
|
119
|
+
const regex = new RegExp(`\\b${h}\\b`, "i");
|
|
120
|
+
return regex.test(whyLower);
|
|
121
|
+
}).length;
|
|
122
|
+
if (hedgeCount >= 2) {
|
|
123
|
+
score -= 15;
|
|
124
|
+
feedback.push("Hedging: too many 'could/maybe/potentially' signals uncertain value. Be definitive.");
|
|
125
|
+
}
|
|
126
|
+
// Check 9: Task-word echo — same word from task repeated 3+ times in why
|
|
127
|
+
for (const tw of taskWords) {
|
|
128
|
+
const twCount = whyWords.filter((w) => w === tw).length;
|
|
129
|
+
if (twCount >= 3) {
|
|
130
|
+
score -= 20;
|
|
131
|
+
feedback.push(`Echo: "${tw}" appears ${twCount} times in your 'why' — this is filler, not reasoning.`);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Bonus for specificity
|
|
136
|
+
if (input.success_looks_like && input.success_looks_like.length > 20) {
|
|
137
|
+
score += 10;
|
|
138
|
+
feedback.push("Good: success criteria defined — this makes the deploy gate concrete.");
|
|
139
|
+
}
|
|
140
|
+
score = Math.max(0, Math.min(100, score));
|
|
141
|
+
let verdict;
|
|
142
|
+
if (score >= 70) {
|
|
143
|
+
verdict = "proceed";
|
|
144
|
+
if (feedback.length === 0) {
|
|
145
|
+
feedback.push("Clear intent. Proceed with confidence.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else if (score >= 40) {
|
|
149
|
+
verdict = "reconsider";
|
|
150
|
+
feedback.push("Pause. Sharpen your answers before writing Convex code.");
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
verdict = "stop";
|
|
154
|
+
feedback.push("Stop: purpose unclear. Do not proceed.");
|
|
155
|
+
}
|
|
156
|
+
return { score, verdict, feedback };
|
|
157
|
+
}
|
|
158
|
+
// ── Tool definition ─────────────────────────────────────────────────────────
|
|
159
|
+
export const critterTools = [
|
|
160
|
+
{
|
|
161
|
+
name: "convex_critter_check",
|
|
162
|
+
description: "The accountability partner that wants to know everything — answer 'Why are you doing this? Who is it for?' before starting Convex work. " +
|
|
163
|
+
"Scores for circular reasoning, vague audiences, and deference-over-understanding. " +
|
|
164
|
+
"The friction is the feature: slowing down prevents unnecessary schema changes, unneeded indexes, and functions nobody calls.",
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
task: {
|
|
169
|
+
type: "string",
|
|
170
|
+
description: "What you are about to do (e.g. 'Add a new table for user preferences')",
|
|
171
|
+
},
|
|
172
|
+
why: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "Why are you doing this? What user problem does it solve?",
|
|
175
|
+
},
|
|
176
|
+
who: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "Who is this for? Name a specific role, persona, or API consumer.",
|
|
179
|
+
},
|
|
180
|
+
success_looks_like: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Optional: What does success look like? How will you verify this worked?",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: ["task", "why", "who"],
|
|
186
|
+
},
|
|
187
|
+
handler: async (args) => {
|
|
188
|
+
ensureCritterTable();
|
|
189
|
+
const result = scoreCritterCheck(args);
|
|
190
|
+
const db = getDb();
|
|
191
|
+
db.prepare(`INSERT INTO critter_checks (task, why, who, success_looks_like, score, verdict, feedback)
|
|
192
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(args.task, args.why, args.who, args.success_looks_like ?? null, result.score, result.verdict, JSON.stringify(result.feedback));
|
|
193
|
+
return {
|
|
194
|
+
score: result.score,
|
|
195
|
+
verdict: result.verdict,
|
|
196
|
+
feedback: result.feedback,
|
|
197
|
+
tip: result.verdict === "proceed"
|
|
198
|
+
? "Critter check passed. Proceed with clear intent."
|
|
199
|
+
: "Sharpen your answers and re-run convex_critter_check.",
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
//# sourceMappingURL=critterTools.js.map
|
|
@@ -147,14 +147,33 @@ function auditFunctions(convexDir) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
// Check 4: Cross-call violations — queries CANNOT call runMutation or runAction
|
|
150
|
+
// Use brace-depth tracking to find exact function boundaries (avoids false positives)
|
|
150
151
|
for (const fn of functions) {
|
|
151
152
|
if (fn.type !== "query" && fn.type !== "internalQuery")
|
|
152
153
|
continue;
|
|
153
154
|
const content = readFileSync(fn.filePath, "utf-8");
|
|
154
155
|
const lines = content.split("\n");
|
|
155
|
-
// Find the function body (rough: from export line to next export or end)
|
|
156
156
|
const startLine = fn.line - 1;
|
|
157
|
-
|
|
157
|
+
// Find the function body by tracking brace depth from the opening ({
|
|
158
|
+
// The pattern is: export const X = query({ ... });
|
|
159
|
+
let depth = 0;
|
|
160
|
+
let foundOpen = false;
|
|
161
|
+
let endLine = Math.min(startLine + 80, lines.length);
|
|
162
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
163
|
+
for (const ch of lines[i]) {
|
|
164
|
+
if (ch === "{") {
|
|
165
|
+
depth++;
|
|
166
|
+
foundOpen = true;
|
|
167
|
+
}
|
|
168
|
+
if (ch === "}")
|
|
169
|
+
depth--;
|
|
170
|
+
}
|
|
171
|
+
if (foundOpen && depth <= 0) {
|
|
172
|
+
endLine = i + 1;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const chunk = lines.slice(startLine, endLine).join("\n");
|
|
158
177
|
if (/ctx\.runMutation/.test(chunk)) {
|
|
159
178
|
issues.push({
|
|
160
179
|
severity: "critical",
|
|
@@ -235,9 +235,13 @@ export const integrationBridgeTools = [
|
|
|
235
235
|
}
|
|
236
236
|
catch { /* ignore */ }
|
|
237
237
|
}
|
|
238
|
+
const parsed = JSON.parse(snapshot.schemaJson);
|
|
238
239
|
return {
|
|
239
240
|
snapshotId: id,
|
|
240
241
|
tableCount: snapshot.tableCount,
|
|
242
|
+
tables: parsed.tables,
|
|
243
|
+
totalIndexes: parsed.totalIndexes,
|
|
244
|
+
indexes: parsed.indexes,
|
|
241
245
|
diff,
|
|
242
246
|
quickRef: getQuickRef("convex_audit_schema"),
|
|
243
247
|
};
|
|
@@ -108,7 +108,13 @@ export const methodologyTools = [
|
|
|
108
108
|
required: ["topic"],
|
|
109
109
|
},
|
|
110
110
|
handler: async (args) => {
|
|
111
|
-
|
|
111
|
+
// Default to overview when topic is missing
|
|
112
|
+
let topic = args.topic || "overview";
|
|
113
|
+
// Accept short names: "schema_audit" -> "convex_schema_audit"
|
|
114
|
+
if (!METHODOLOGY_CONTENT[topic] && METHODOLOGY_CONTENT[`convex_${topic}`]) {
|
|
115
|
+
topic = `convex_${topic}`;
|
|
116
|
+
}
|
|
117
|
+
const content = METHODOLOGY_CONTENT[topic];
|
|
112
118
|
if (!content) {
|
|
113
119
|
return {
|
|
114
120
|
error: `Unknown topic: ${args.topic}`,
|
|
@@ -149,6 +155,7 @@ export const methodologyTools = [
|
|
|
149
155
|
matchingTools: results.length,
|
|
150
156
|
tools: results.map((r) => ({
|
|
151
157
|
name: r.name,
|
|
158
|
+
score: Math.round(r._score * 100) / 100,
|
|
152
159
|
category: r.category,
|
|
153
160
|
phase: r.phase,
|
|
154
161
|
complexity: r.complexity,
|
|
@@ -120,20 +120,26 @@ function analyzeSchema(schemaContent, filePath) {
|
|
|
120
120
|
gotchaKey: "index_name_include_fields",
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
// Check: v.any() usage (defeats validator purpose)
|
|
123
|
+
// Check: v.any() usage (defeats validator purpose) — aggregate
|
|
124
|
+
const vAnyLines = [];
|
|
124
125
|
lines.forEach((line, i) => {
|
|
125
126
|
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
126
127
|
return;
|
|
127
128
|
if (/v\.any\s*\(\s*\)/.test(line)) {
|
|
128
|
-
|
|
129
|
-
severity: "warning",
|
|
130
|
-
location: `${filePath}:${i + 1}`,
|
|
131
|
-
message: "v.any() defeats the purpose of validators. Use a specific validator type.",
|
|
132
|
-
fix: "Replace v.any() with the appropriate validator (v.string(), v.object({...}), etc.)",
|
|
133
|
-
gotchaKey: "avoid_v_any",
|
|
134
|
-
});
|
|
129
|
+
vAnyLines.push(i + 1);
|
|
135
130
|
}
|
|
136
131
|
});
|
|
132
|
+
if (vAnyLines.length > 0) {
|
|
133
|
+
const examples = vAnyLines.slice(0, 5).map((l) => `line ${l}`).join(", ");
|
|
134
|
+
const more = vAnyLines.length > 5 ? ` (+${vAnyLines.length - 5} more)` : "";
|
|
135
|
+
issues.push({
|
|
136
|
+
severity: "warning",
|
|
137
|
+
location: filePath,
|
|
138
|
+
message: `${vAnyLines.length} uses of v.any() defeat the purpose of validators. Locations: ${examples}${more}`,
|
|
139
|
+
fix: "Replace v.any() with specific validators (v.string(), v.object({...}), v.union(...), etc.)",
|
|
140
|
+
gotchaKey: "avoid_v_any",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
137
143
|
// Check: _creationTime or _id in schema definition (system fields)
|
|
138
144
|
lines.forEach((line, i) => {
|
|
139
145
|
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
@@ -2,9 +2,12 @@ import type { ConvexQuickRef, ToolRegistryEntry } from "../types.js";
|
|
|
2
2
|
export declare const REGISTRY: ToolRegistryEntry[];
|
|
3
3
|
export declare function getQuickRef(toolName: string): ConvexQuickRef | null;
|
|
4
4
|
export declare function getToolsByCategory(category: string): ToolRegistryEntry[];
|
|
5
|
-
export
|
|
5
|
+
export interface ScoredToolEntry extends ToolRegistryEntry {
|
|
6
|
+
_score: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function findTools(query: string): ScoredToolEntry[];
|
|
6
9
|
/**
|
|
7
10
|
* Async wrapper around findTools that fuses BM25 results with embedding RRF
|
|
8
11
|
* when a neural embedding provider is available. Falls back to plain findTools otherwise.
|
|
9
12
|
*/
|
|
10
|
-
export declare function findToolsWithEmbedding(query: string): Promise<
|
|
13
|
+
export declare function findToolsWithEmbedding(query: string): Promise<ScoredToolEntry[]>;
|
|
@@ -246,6 +246,21 @@ export const REGISTRY = [
|
|
|
246
246
|
phase: "audit",
|
|
247
247
|
complexity: "low",
|
|
248
248
|
},
|
|
249
|
+
// ── Critter Tools ──────────────────────────
|
|
250
|
+
{
|
|
251
|
+
name: "convex_critter_check",
|
|
252
|
+
category: "methodology",
|
|
253
|
+
tags: ["intentionality", "why", "who", "purpose", "audience", "reflection", "pre-action", "critter"],
|
|
254
|
+
quickRef: {
|
|
255
|
+
nextAction: "Critter check done. If verdict is 'proceed', start your Convex work. If 'reconsider', sharpen answers.",
|
|
256
|
+
nextTools: ["convex_audit_schema", "convex_audit_functions", "convex_search_gotchas"],
|
|
257
|
+
methodology: "convex_intentionality",
|
|
258
|
+
relatedGotchas: [],
|
|
259
|
+
confidence: "high",
|
|
260
|
+
},
|
|
261
|
+
phase: "meta",
|
|
262
|
+
complexity: "low",
|
|
263
|
+
},
|
|
249
264
|
];
|
|
250
265
|
export function getQuickRef(toolName) {
|
|
251
266
|
const entry = REGISTRY.find((e) => e.name === toolName);
|
|
@@ -314,12 +329,11 @@ export function findTools(query) {
|
|
|
314
329
|
const termIdf = index.idf.get(qt) ?? 0;
|
|
315
330
|
score += termIdf * (termTf * (k1 + 1)) / (termTf + k1 * (1 - b + b * (dl / index.avgDl)));
|
|
316
331
|
}
|
|
317
|
-
return { entry, score };
|
|
332
|
+
return { ...entry, _score: score };
|
|
318
333
|
});
|
|
319
334
|
return scored
|
|
320
|
-
.filter((s) => s.
|
|
321
|
-
.sort((a, b) => b.
|
|
322
|
-
.map((s) => s.entry);
|
|
335
|
+
.filter((s) => s._score > 0)
|
|
336
|
+
.sort((a, b) => b._score - a._score);
|
|
323
337
|
}
|
|
324
338
|
/**
|
|
325
339
|
* Async wrapper around findTools that fuses BM25 results with embedding RRF
|
|
@@ -360,6 +374,7 @@ export async function findToolsWithEmbedding(query) {
|
|
|
360
374
|
}
|
|
361
375
|
return [...allEntries.values()]
|
|
362
376
|
.filter((e) => fusedScores.has(e.name))
|
|
363
|
-
.sort((a, b) => (fusedScores.get(b.name) ?? 0) - (fusedScores.get(a.name) ?? 0))
|
|
377
|
+
.sort((a, b) => (fusedScores.get(b.name) ?? 0) - (fusedScores.get(a.name) ?? 0))
|
|
378
|
+
.map((e) => ({ ...e, _score: fusedScores.get(e.name) ?? 0 }));
|
|
364
379
|
}
|
|
365
380
|
//# sourceMappingURL=toolRegistry.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homenshum/convex-mcp-nodebench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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": {
|