@homenshum/convex-mcp-nodebench 0.4.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.
|
@@ -32,22 +32,26 @@ function scoreCritterCheck(input) {
|
|
|
32
32
|
const taskLower = input.task.toLowerCase().trim();
|
|
33
33
|
const whyLower = input.why.toLowerCase().trim();
|
|
34
34
|
const whoLower = input.who.toLowerCase().trim();
|
|
35
|
-
// Check 1: Circular reasoning
|
|
35
|
+
// Check 1: Circular reasoning (threshold 0.5)
|
|
36
36
|
const taskWords = new Set(taskLower.split(/\s+/).filter((w) => w.length > 3));
|
|
37
37
|
const whyWords = whyLower.split(/\s+/).filter((w) => w.length > 3);
|
|
38
38
|
const overlap = whyWords.filter((w) => taskWords.has(w));
|
|
39
|
-
if (whyWords.length > 0 && overlap.length / whyWords.length > 0.
|
|
39
|
+
if (whyWords.length > 0 && overlap.length / whyWords.length > 0.5) {
|
|
40
40
|
score -= 30;
|
|
41
41
|
feedback.push("Circular: your 'why' mostly restates the task. What user outcome does this enable?");
|
|
42
42
|
}
|
|
43
43
|
// Check 2: Vague audience
|
|
44
|
-
const vagueAudiences = ["users", "everyone", "people", "the team", "stakeholders", "clients"];
|
|
44
|
+
const vagueAudiences = ["users", "everyone", "people", "the team", "stakeholders", "clients", "customers", "developers"];
|
|
45
45
|
if (vagueAudiences.includes(whoLower)) {
|
|
46
46
|
score -= 20;
|
|
47
47
|
feedback.push(`"${input.who}" is too broad. Which user role or API consumer specifically?`);
|
|
48
48
|
}
|
|
49
|
-
// Check 3:
|
|
50
|
-
if (whyLower.length
|
|
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) {
|
|
51
55
|
score -= 25;
|
|
52
56
|
feedback.push("The 'why' is too short. What problem does this solve?");
|
|
53
57
|
}
|
|
@@ -61,6 +65,73 @@ function scoreCritterCheck(input) {
|
|
|
61
65
|
score -= 15;
|
|
62
66
|
feedback.push("Citing authority instead of understanding purpose. Why does this matter to the product?");
|
|
63
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
|
+
}
|
|
64
135
|
// Bonus for specificity
|
|
65
136
|
if (input.success_looks_like && input.success_looks_like.length > 20) {
|
|
66
137
|
score += 10;
|
|
@@ -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,
|
|
@@ -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[]>;
|
|
@@ -329,12 +329,11 @@ export function findTools(query) {
|
|
|
329
329
|
const termIdf = index.idf.get(qt) ?? 0;
|
|
330
330
|
score += termIdf * (termTf * (k1 + 1)) / (termTf + k1 * (1 - b + b * (dl / index.avgDl)));
|
|
331
331
|
}
|
|
332
|
-
return { entry, score };
|
|
332
|
+
return { ...entry, _score: score };
|
|
333
333
|
});
|
|
334
334
|
return scored
|
|
335
|
-
.filter((s) => s.
|
|
336
|
-
.sort((a, b) => b.
|
|
337
|
-
.map((s) => s.entry);
|
|
335
|
+
.filter((s) => s._score > 0)
|
|
336
|
+
.sort((a, b) => b._score - a._score);
|
|
338
337
|
}
|
|
339
338
|
/**
|
|
340
339
|
* Async wrapper around findTools that fuses BM25 results with embedding RRF
|
|
@@ -375,6 +374,7 @@ export async function findToolsWithEmbedding(query) {
|
|
|
375
374
|
}
|
|
376
375
|
return [...allEntries.values()]
|
|
377
376
|
.filter((e) => fusedScores.has(e.name))
|
|
378
|
-
.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 }));
|
|
379
379
|
}
|
|
380
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.4.
|
|
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": {
|