@homenshum/convex-mcp-nodebench 0.3.0 → 0.4.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 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,133 @@
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
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.7) {
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"];
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: Too short
50
+ if (whyLower.length < 10) {
51
+ score -= 25;
52
+ feedback.push("The 'why' is too short. What problem does this solve?");
53
+ }
54
+ if (whoLower.length < 3) {
55
+ score -= 25;
56
+ feedback.push("The 'who' is too short. Specify who benefits.");
57
+ }
58
+ // Check 4: Deference over understanding
59
+ const deferPatterns = ["was told", "asked to", "ticket says", "was asked", "jira", "they said"];
60
+ if (deferPatterns.some((p) => whyLower.includes(p))) {
61
+ score -= 15;
62
+ feedback.push("Citing authority instead of understanding purpose. Why does this matter to the product?");
63
+ }
64
+ // Bonus for specificity
65
+ if (input.success_looks_like && input.success_looks_like.length > 20) {
66
+ score += 10;
67
+ feedback.push("Good: success criteria defined — this makes the deploy gate concrete.");
68
+ }
69
+ score = Math.max(0, Math.min(100, score));
70
+ let verdict;
71
+ if (score >= 70) {
72
+ verdict = "proceed";
73
+ if (feedback.length === 0) {
74
+ feedback.push("Clear intent. Proceed with confidence.");
75
+ }
76
+ }
77
+ else if (score >= 40) {
78
+ verdict = "reconsider";
79
+ feedback.push("Pause. Sharpen your answers before writing Convex code.");
80
+ }
81
+ else {
82
+ verdict = "stop";
83
+ feedback.push("Stop: purpose unclear. Do not proceed.");
84
+ }
85
+ return { score, verdict, feedback };
86
+ }
87
+ // ── Tool definition ─────────────────────────────────────────────────────────
88
+ export const critterTools = [
89
+ {
90
+ name: "convex_critter_check",
91
+ description: "The accountability partner that wants to know everything — answer 'Why are you doing this? Who is it for?' before starting Convex work. " +
92
+ "Scores for circular reasoning, vague audiences, and deference-over-understanding. " +
93
+ "The friction is the feature: slowing down prevents unnecessary schema changes, unneeded indexes, and functions nobody calls.",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ task: {
98
+ type: "string",
99
+ description: "What you are about to do (e.g. 'Add a new table for user preferences')",
100
+ },
101
+ why: {
102
+ type: "string",
103
+ description: "Why are you doing this? What user problem does it solve?",
104
+ },
105
+ who: {
106
+ type: "string",
107
+ description: "Who is this for? Name a specific role, persona, or API consumer.",
108
+ },
109
+ success_looks_like: {
110
+ type: "string",
111
+ description: "Optional: What does success look like? How will you verify this worked?",
112
+ },
113
+ },
114
+ required: ["task", "why", "who"],
115
+ },
116
+ handler: async (args) => {
117
+ ensureCritterTable();
118
+ const result = scoreCritterCheck(args);
119
+ const db = getDb();
120
+ db.prepare(`INSERT INTO critter_checks (task, why, who, success_looks_like, score, verdict, feedback)
121
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(args.task, args.why, args.who, args.success_looks_like ?? null, result.score, result.verdict, JSON.stringify(result.feedback));
122
+ return {
123
+ score: result.score,
124
+ verdict: result.verdict,
125
+ feedback: result.feedback,
126
+ tip: result.verdict === "proceed"
127
+ ? "Critter check passed. Proceed with clear intent."
128
+ : "Sharpen your answers and re-run convex_critter_check.",
129
+ };
130
+ },
131
+ },
132
+ ];
133
+ //# 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
- const chunk = lines.slice(startLine, Math.min(startLine + 80, lines.length)).join("\n");
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",
@@ -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
- issues.push({
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("*"))
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homenshum/convex-mcp-nodebench",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": {