@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.7) {
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: Too short
50
- if (whyLower.length < 10) {
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
- const content = METHODOLOGY_CONTENT[args.topic];
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 declare function findTools(query: string): ToolRegistryEntry[];
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<ToolRegistryEntry[]>;
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.score > 0)
336
- .sort((a, b) => b.score - a.score)
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.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": {