nodebench-mcp 1.0.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.
Files changed (65) hide show
  1. package/README.md +237 -0
  2. package/dist/__tests__/tools.test.d.ts +1 -0
  3. package/dist/__tests__/tools.test.js +402 -0
  4. package/dist/__tests__/tools.test.js.map +1 -0
  5. package/dist/db.d.ts +4 -0
  6. package/dist/db.js +198 -0
  7. package/dist/db.js.map +1 -0
  8. package/dist/index.d.ts +19 -0
  9. package/dist/index.js +237 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/tools/documentTools.d.ts +5 -0
  12. package/dist/tools/documentTools.js +524 -0
  13. package/dist/tools/documentTools.js.map +1 -0
  14. package/dist/tools/documentationTools.d.ts +12 -0
  15. package/dist/tools/documentationTools.js +647 -0
  16. package/dist/tools/documentationTools.js.map +1 -0
  17. package/dist/tools/evalTools.d.ts +6 -0
  18. package/dist/tools/evalTools.js +335 -0
  19. package/dist/tools/evalTools.js.map +1 -0
  20. package/dist/tools/financialTools.d.ts +10 -0
  21. package/dist/tools/financialTools.js +403 -0
  22. package/dist/tools/financialTools.js.map +1 -0
  23. package/dist/tools/flywheelTools.d.ts +6 -0
  24. package/dist/tools/flywheelTools.js +366 -0
  25. package/dist/tools/flywheelTools.js.map +1 -0
  26. package/dist/tools/githubTools.d.ts +12 -0
  27. package/dist/tools/githubTools.js +432 -0
  28. package/dist/tools/githubTools.js.map +1 -0
  29. package/dist/tools/learningTools.d.ts +6 -0
  30. package/dist/tools/learningTools.js +199 -0
  31. package/dist/tools/learningTools.js.map +1 -0
  32. package/dist/tools/memoryTools.d.ts +5 -0
  33. package/dist/tools/memoryTools.js +137 -0
  34. package/dist/tools/memoryTools.js.map +1 -0
  35. package/dist/tools/metaTools.d.ts +7 -0
  36. package/dist/tools/metaTools.js +837 -0
  37. package/dist/tools/metaTools.js.map +1 -0
  38. package/dist/tools/planningTools.d.ts +5 -0
  39. package/dist/tools/planningTools.js +147 -0
  40. package/dist/tools/planningTools.js.map +1 -0
  41. package/dist/tools/qualityGateTools.d.ts +6 -0
  42. package/dist/tools/qualityGateTools.js +347 -0
  43. package/dist/tools/qualityGateTools.js.map +1 -0
  44. package/dist/tools/reconTools.d.ts +8 -0
  45. package/dist/tools/reconTools.js +729 -0
  46. package/dist/tools/reconTools.js.map +1 -0
  47. package/dist/tools/searchTools.d.ts +5 -0
  48. package/dist/tools/searchTools.js +145 -0
  49. package/dist/tools/searchTools.js.map +1 -0
  50. package/dist/tools/uiCaptureTools.d.ts +8 -0
  51. package/dist/tools/uiCaptureTools.js +339 -0
  52. package/dist/tools/uiCaptureTools.js.map +1 -0
  53. package/dist/tools/verificationTools.d.ts +6 -0
  54. package/dist/tools/verificationTools.js +472 -0
  55. package/dist/tools/verificationTools.js.map +1 -0
  56. package/dist/tools/visionTools.d.ts +12 -0
  57. package/dist/tools/visionTools.js +553 -0
  58. package/dist/tools/visionTools.js.map +1 -0
  59. package/dist/tools/webTools.d.ts +12 -0
  60. package/dist/tools/webTools.js +443 -0
  61. package/dist/tools/webTools.js.map +1 -0
  62. package/dist/types.d.ts +16 -0
  63. package/dist/types.js +2 -0
  64. package/dist/types.js.map +1 -0
  65. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # NodeBench MCP Server
2
+
3
+ A fully local, zero-config MCP server with 46 tools for AI-powered development workflows.
4
+
5
+ **Features:**
6
+ - Web search (Gemini/OpenAI/Perplexity)
7
+ - GitHub repository discovery and analysis
8
+ - Job market research
9
+ - AGENTS.md self-maintenance
10
+ - AI vision for screenshot analysis
11
+ - 6-phase verification flywheel
12
+ - SQLite-backed learning database
13
+
14
+ ## Quick Start (1 minute)
15
+
16
+ ### 1. Add to Claude Code settings
17
+
18
+ Add to `~/.claude/settings.json`:
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "nodebench": {
24
+ "command": "npx",
25
+ "args": ["-y", "nodebench-mcp"]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ That's it. Restart Claude Code and you have 46 tools.
32
+
33
+ ---
34
+
35
+ ## Alternative: Build from source
36
+
37
+ ```bash
38
+ git clone https://github.com/nodebench/nodebench-ai.git
39
+ cd nodebench-ai/packages/mcp-local
40
+ npm install && npm run build
41
+ ```
42
+
43
+ Then use absolute path in settings:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "nodebench": {
49
+ "command": "node",
50
+ "args": ["/path/to/packages/mcp-local/dist/index.js"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### 3. Add API keys (optional but recommended)
57
+
58
+ Add to your shell profile (`~/.bashrc`, `~/.zshrc`, or Windows Environment Variables):
59
+
60
+ ```bash
61
+ # Required for web search (pick one)
62
+ export GEMINI_API_KEY="your-key" # Best: Google Search grounding
63
+ export OPENAI_API_KEY="your-key" # Alternative: GPT-4o web search
64
+ export PERPLEXITY_API_KEY="your-key" # Alternative: Perplexity
65
+
66
+ # Required for GitHub (higher rate limits)
67
+ export GITHUB_TOKEN="your-token" # github.com/settings/tokens
68
+
69
+ # Required for vision analysis (pick one)
70
+ export GEMINI_API_KEY="your-key" # Best: Gemini 2.5 Flash
71
+ export OPENAI_API_KEY="your-key" # Alternative: GPT-4o
72
+ export ANTHROPIC_API_KEY="your-key" # Alternative: Claude
73
+ ```
74
+
75
+ ### 4. Restart Claude Code
76
+
77
+ ```bash
78
+ # Quit and reopen Claude Code, or run:
79
+ claude --mcp-debug
80
+ ```
81
+
82
+ ### 5. Test it works
83
+
84
+ In Claude Code, try these prompts:
85
+
86
+ ```
87
+ # Check your environment
88
+ > Use setup_local_env to check my development environment
89
+
90
+ # Search GitHub
91
+ > Use search_github to find TypeScript MCP servers with at least 100 stars
92
+
93
+ # Fetch documentation
94
+ > Use fetch_url to read https://modelcontextprotocol.io/introduction
95
+
96
+ # Get methodology
97
+ > Use getMethodology("overview") to see all available workflows
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Tool Categories
103
+
104
+ | Category | Tools | Description |
105
+ |----------|-------|-------------|
106
+ | **Web** | `web_search`, `fetch_url` | Search the web, fetch URLs as markdown |
107
+ | **GitHub** | `search_github`, `analyze_repo` | Find repos, analyze tech stacks |
108
+ | **Documentation** | `update_agents_md`, `research_job_market`, `setup_local_env` | Self-maintaining docs, job research |
109
+ | **Vision** | `discover_vision_env`, `analyze_screenshot`, `manipulate_screenshot` | AI-powered image analysis |
110
+ | **UI Capture** | `capture_ui_screenshot`, `capture_responsive_suite` | Browser screenshots (requires Playwright) |
111
+ | **Verification** | `start_cycle`, `log_phase`, `complete_cycle` | 6-phase dev workflow |
112
+ | **Eval** | `start_eval_run`, `log_test_result`, `list_eval_runs` | Test case tracking |
113
+ | **Quality Gates** | `run_quality_gate`, `get_gate_history` | Pass/fail checkpoints |
114
+ | **Learning** | `record_learning`, `search_learnings`, `search_all_knowledge` | Persistent knowledge base |
115
+ | **Flywheel** | `run_closed_loop`, `check_framework_updates` | Automated workflows |
116
+ | **Recon** | `run_recon`, `log_recon_finding`, `log_gap` | Discovery and gap tracking |
117
+ | **Meta** | `findTools`, `getMethodology` | Tool discovery, methodology guides |
118
+
119
+ ---
120
+
121
+ ## Methodology Topics (15 total)
122
+
123
+ Ask Claude: `Use getMethodology("topic_name")`
124
+
125
+ - `overview` — See all methodologies
126
+ - `verification` — 6-phase development cycle
127
+ - `eval` — Test case management
128
+ - `flywheel` — Continuous improvement loop
129
+ - `mandatory_flywheel` — Required verification for changes
130
+ - `reconnaissance` — Codebase discovery
131
+ - `quality_gates` — Pass/fail checkpoints
132
+ - `ui_ux_qa` — Frontend verification
133
+ - `agentic_vision` — AI-powered visual QA
134
+ - `closed_loop` — Build/test before presenting
135
+ - `learnings` — Knowledge persistence
136
+ - `project_ideation` — Validate ideas before building
137
+ - `tech_stack_2026` — Dependency management
138
+ - `telemetry_setup` — Observability setup
139
+ - `agents_md_maintenance` — Keep docs in sync
140
+
141
+ ---
142
+
143
+ ## VSCode Extension Setup
144
+
145
+ If using the Claude Code VSCode extension:
146
+
147
+ 1. Open VSCode Settings (Ctrl/Cmd + ,)
148
+ 2. Search for "Claude Code MCP"
149
+ 3. Add server configuration:
150
+
151
+ ```json
152
+ {
153
+ "claude-code.mcpServers": {
154
+ "nodebench": {
155
+ "command": "node",
156
+ "args": ["/absolute/path/to/packages/mcp-local/dist/index.js"]
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Optional Dependencies
165
+
166
+ Install for additional features:
167
+
168
+ ```bash
169
+ # Screenshot capture (headless browser)
170
+ npm install playwright
171
+ npx playwright install chromium
172
+
173
+ # Image manipulation
174
+ npm install sharp
175
+
176
+ # HTML parsing (already included)
177
+ npm install cheerio
178
+
179
+ # AI providers (pick your preferred)
180
+ npm install @google/genai # Gemini
181
+ npm install openai # OpenAI
182
+ npm install @anthropic-ai/sdk # Anthropic
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Troubleshooting
188
+
189
+ **"No search provider available"**
190
+ - Set at least one API key: `GEMINI_API_KEY`, `OPENAI_API_KEY`, or `PERPLEXITY_API_KEY`
191
+
192
+ **"GitHub API error 403"**
193
+ - Set `GITHUB_TOKEN` for higher rate limits (60/hour without, 5000/hour with)
194
+
195
+ **"Cannot find module"**
196
+ - Run `npm run build` in the mcp-local directory
197
+
198
+ **MCP not connecting**
199
+ - Check path is absolute in settings.json
200
+ - Run `claude --mcp-debug` to see connection errors
201
+ - Ensure Node.js >= 18
202
+
203
+ ---
204
+
205
+ ## Example Workflows
206
+
207
+ ### Research a new project idea
208
+
209
+ ```
210
+ 1. Use getMethodology("project_ideation") for the 6-step process
211
+ 2. Use web_search to validate market demand
212
+ 3. Use search_github to find similar projects
213
+ 4. Use analyze_repo to study competitor implementations
214
+ 5. Use research_job_market to understand skill demand
215
+ ```
216
+
217
+ ### Analyze a GitHub repo before using it
218
+
219
+ ```
220
+ 1. Use search_github({ query: "mcp server", language: "typescript", minStars: 100 })
221
+ 2. Use analyze_repo({ repoUrl: "owner/repo" }) to see tech stack and patterns
222
+ 3. Use fetch_url to read their documentation
223
+ ```
224
+
225
+ ### Set up a new development environment
226
+
227
+ ```
228
+ 1. Use setup_local_env to scan current environment
229
+ 2. Follow the recommendations to install missing SDKs
230
+ 3. Use getMethodology("tech_stack_2026") for ongoing maintenance
231
+ ```
232
+
233
+ ---
234
+
235
+ ## License
236
+
237
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Automated tests for NodeBench MCP tools.
3
+ * Covers: static, unit, integration layers.
4
+ * Live E2E layer is tested via bash pipe in the flywheel step.
5
+ */
6
+ import { describe, it, expect } from "vitest";
7
+ import { verificationTools } from "../tools/verificationTools.js";
8
+ import { reconTools } from "../tools/reconTools.js";
9
+ import { uiCaptureTools } from "../tools/uiCaptureTools.js";
10
+ import { visionTools } from "../tools/visionTools.js";
11
+ import { evalTools } from "../tools/evalTools.js";
12
+ import { qualityGateTools } from "../tools/qualityGateTools.js";
13
+ import { flywheelTools } from "../tools/flywheelTools.js";
14
+ import { learningTools } from "../tools/learningTools.js";
15
+ import { createMetaTools } from "../tools/metaTools.js";
16
+ import { webTools } from "../tools/webTools.js";
17
+ import { githubTools } from "../tools/githubTools.js";
18
+ import { documentationTools } from "../tools/documentationTools.js";
19
+ // Assemble all tools like index.ts does
20
+ const domainTools = [
21
+ ...verificationTools,
22
+ ...evalTools,
23
+ ...qualityGateTools,
24
+ ...learningTools,
25
+ ...flywheelTools,
26
+ ...reconTools,
27
+ ...uiCaptureTools,
28
+ ...visionTools,
29
+ ...webTools,
30
+ ...githubTools,
31
+ ...documentationTools,
32
+ ];
33
+ const allTools = [...domainTools, ...createMetaTools(domainTools)];
34
+ // ═══════════════════════════════════════════════════════════════════════════
35
+ // STATIC LAYER — structure validation
36
+ // ═══════════════════════════════════════════════════════════════════════════
37
+ describe("Static: tool structure", () => {
38
+ it("should have 46 tools total", () => {
39
+ expect(allTools.length).toBe(46);
40
+ });
41
+ it("every tool has name, description, inputSchema, handler", () => {
42
+ for (const tool of allTools) {
43
+ expect(tool.name).toBeTruthy();
44
+ expect(typeof tool.name).toBe("string");
45
+ expect(tool.description).toBeTruthy();
46
+ expect(typeof tool.description).toBe("string");
47
+ expect(tool.inputSchema).toBeTruthy();
48
+ expect(typeof tool.handler).toBe("function");
49
+ }
50
+ });
51
+ it("tool names are unique", () => {
52
+ const names = allTools.map((t) => t.name);
53
+ expect(new Set(names).size).toBe(names.length);
54
+ });
55
+ it("abandon_cycle tool exists in verificationTools", () => {
56
+ const tool = verificationTools.find((t) => t.name === "abandon_cycle");
57
+ expect(tool).toBeDefined();
58
+ expect(tool.inputSchema.required).toContain("cycleId");
59
+ });
60
+ });
61
+ describe("Static: ui_ux_qa preset", () => {
62
+ it("should return 8 rules from get_gate_preset", async () => {
63
+ const tool = findTool("get_gate_preset");
64
+ const result = (await tool.handler({ preset: "ui_ux_qa" }));
65
+ expect(result.preset).toBe("ui_ux_qa");
66
+ expect(result.ruleCount).toBe(8);
67
+ expect(result.rules.map((r) => r.name)).toContain("component_renders");
68
+ expect(result.rules.map((r) => r.name)).toContain("keyboard_navigable");
69
+ expect(result.rules.map((r) => r.name)).toContain("aria_labels_present");
70
+ expect(result.rules.map((r) => r.name)).toContain("storybook_story_exists");
71
+ });
72
+ it("should accept ui_ux_qa gate results via run_quality_gate", async () => {
73
+ const tool = findTool("run_quality_gate");
74
+ const result = (await tool.handler({
75
+ gateName: "ui_ux_qa",
76
+ target: "TestComponent",
77
+ rules: [
78
+ { name: "component_renders", passed: true },
79
+ { name: "responsive_check", passed: true },
80
+ { name: "keyboard_navigable", passed: false },
81
+ { name: "aria_labels_present", passed: true },
82
+ { name: "loading_states_handled", passed: true },
83
+ { name: "no_console_errors", passed: true },
84
+ { name: "visual_consistency", passed: true },
85
+ { name: "storybook_story_exists", passed: false },
86
+ ],
87
+ }));
88
+ expect(result.passed).toBe(false);
89
+ expect(result.totalRules).toBe(8);
90
+ expect(result.passedCount).toBe(6);
91
+ expect(result.failures).toContain("keyboard_navigable");
92
+ expect(result.failures).toContain("storybook_story_exists");
93
+ });
94
+ });
95
+ describe("Static: ui capture tools", () => {
96
+ it("should include capture_ui_screenshot and capture_responsive_suite", () => {
97
+ const names = allTools.map((t) => t.name);
98
+ expect(names).toContain("capture_ui_screenshot");
99
+ expect(names).toContain("capture_responsive_suite");
100
+ });
101
+ it("capture_ui_screenshot requires url parameter", () => {
102
+ const tool = allTools.find((t) => t.name === "capture_ui_screenshot");
103
+ expect(tool.inputSchema.required).toContain("url");
104
+ });
105
+ it("capture_responsive_suite requires url and label", () => {
106
+ const tool = allTools.find((t) => t.name === "capture_responsive_suite");
107
+ expect(tool.inputSchema.required).toContain("url");
108
+ expect(tool.inputSchema.required).toContain("label");
109
+ });
110
+ it("capture_ui_screenshot has viewport enum with expected presets", () => {
111
+ const tool = allTools.find((t) => t.name === "capture_ui_screenshot");
112
+ const viewportProp = tool.inputSchema.properties.viewport;
113
+ expect(viewportProp.enum).toContain("mobile");
114
+ expect(viewportProp.enum).toContain("tablet");
115
+ expect(viewportProp.enum).toContain("desktop");
116
+ expect(viewportProp.enum).toContain("wide");
117
+ expect(viewportProp.enum).toContain("custom");
118
+ });
119
+ });
120
+ describe("Static: vision tools", () => {
121
+ it("should include discover_vision_env, analyze_screenshot, manipulate_screenshot", () => {
122
+ const names = allTools.map((t) => t.name);
123
+ expect(names).toContain("discover_vision_env");
124
+ expect(names).toContain("analyze_screenshot");
125
+ expect(names).toContain("manipulate_screenshot");
126
+ });
127
+ it("analyze_screenshot requires imageBase64 and has rawContent", () => {
128
+ const tool = allTools.find((t) => t.name === "analyze_screenshot");
129
+ expect(tool.inputSchema.required).toContain("imageBase64");
130
+ expect(tool.rawContent).toBe(true);
131
+ });
132
+ it("manipulate_screenshot requires imageBase64 and operation", () => {
133
+ const tool = allTools.find((t) => t.name === "manipulate_screenshot");
134
+ expect(tool.inputSchema.required).toContain("imageBase64");
135
+ expect(tool.inputSchema.required).toContain("operation");
136
+ expect(tool.rawContent).toBe(true);
137
+ });
138
+ it("discover_vision_env has no required params", () => {
139
+ const tool = allTools.find((t) => t.name === "discover_vision_env");
140
+ const required = tool.inputSchema.required;
141
+ expect(required ?? []).toEqual([]);
142
+ });
143
+ });
144
+ describe("Unit: discover_vision_env", () => {
145
+ it("should return environment scan without errors", async () => {
146
+ const tool = allTools.find((t) => t.name === "discover_vision_env");
147
+ const result = (await tool.handler({}));
148
+ expect(result).toHaveProperty("apiKeys");
149
+ expect(result).toHaveProperty("sdks");
150
+ expect(result).toHaveProperty("providers");
151
+ expect(result).toHaveProperty("canAnalyze");
152
+ expect(result).toHaveProperty("canManipulate");
153
+ expect(result).toHaveProperty("canCapture");
154
+ expect(typeof result.canAnalyze).toBe("boolean");
155
+ }, 15000); // Allow time for dynamic imports
156
+ });
157
+ describe("Static: agentic_vision methodology", () => {
158
+ it("should return agentic_vision methodology from getMethodology", async () => {
159
+ const tool = allTools.find((t) => t.name === "getMethodology");
160
+ const result = (await tool.handler({ topic: "agentic_vision" }));
161
+ expect(result.title).toContain("Agentic Vision");
162
+ expect(result.steps.length).toBe(6);
163
+ expect(result.steps[0].name).toBe("Discover");
164
+ expect(result.steps[2].name).toBe("Analyze");
165
+ });
166
+ });
167
+ describe("Static: web tools", () => {
168
+ it("should include web_search and fetch_url tools", () => {
169
+ const names = allTools.map((t) => t.name);
170
+ expect(names).toContain("web_search");
171
+ expect(names).toContain("fetch_url");
172
+ });
173
+ it("web_search requires query parameter", () => {
174
+ const tool = allTools.find((t) => t.name === "web_search");
175
+ expect(tool.inputSchema.required).toContain("query");
176
+ });
177
+ it("fetch_url requires url parameter", () => {
178
+ const tool = allTools.find((t) => t.name === "fetch_url");
179
+ expect(tool.inputSchema.required).toContain("url");
180
+ });
181
+ });
182
+ describe("Static: github tools", () => {
183
+ it("should include search_github and analyze_repo tools", () => {
184
+ const names = allTools.map((t) => t.name);
185
+ expect(names).toContain("search_github");
186
+ expect(names).toContain("analyze_repo");
187
+ });
188
+ it("search_github requires query parameter", () => {
189
+ const tool = allTools.find((t) => t.name === "search_github");
190
+ expect(tool.inputSchema.required).toContain("query");
191
+ });
192
+ it("analyze_repo requires repoUrl parameter", () => {
193
+ const tool = allTools.find((t) => t.name === "analyze_repo");
194
+ expect(tool.inputSchema.required).toContain("repoUrl");
195
+ });
196
+ });
197
+ describe("Static: documentation tools", () => {
198
+ it("should include update_agents_md, research_job_market, and setup_local_env tools", () => {
199
+ const names = allTools.map((t) => t.name);
200
+ expect(names).toContain("update_agents_md");
201
+ expect(names).toContain("research_job_market");
202
+ expect(names).toContain("setup_local_env");
203
+ });
204
+ it("update_agents_md requires operation parameter", () => {
205
+ const tool = allTools.find((t) => t.name === "update_agents_md");
206
+ expect(tool.inputSchema.required).toContain("operation");
207
+ });
208
+ it("research_job_market requires role parameter", () => {
209
+ const tool = allTools.find((t) => t.name === "research_job_market");
210
+ expect(tool.inputSchema.required).toContain("role");
211
+ });
212
+ });
213
+ describe("Static: new methodology topics", () => {
214
+ it("should return project_ideation methodology with 6 steps", async () => {
215
+ const tool = allTools.find((t) => t.name === "getMethodology");
216
+ const result = (await tool.handler({ topic: "project_ideation" }));
217
+ expect(result.title).toContain("Project Ideation");
218
+ expect(result.steps.length).toBe(6);
219
+ expect(result.steps[0].name).toBe("Define Concept");
220
+ });
221
+ it("should return tech_stack_2026 methodology with 5 steps", async () => {
222
+ const tool = allTools.find((t) => t.name === "getMethodology");
223
+ const result = (await tool.handler({ topic: "tech_stack_2026" }));
224
+ expect(result.title).toContain("Tech Stack");
225
+ expect(result.steps.length).toBe(5);
226
+ });
227
+ it("should return telemetry_setup methodology with 5 steps", async () => {
228
+ const tool = allTools.find((t) => t.name === "getMethodology");
229
+ const result = (await tool.handler({ topic: "telemetry_setup" }));
230
+ expect(result.title).toContain("Telemetry");
231
+ expect(result.steps.length).toBe(5);
232
+ });
233
+ it("should return agents_md_maintenance methodology with 5 steps", async () => {
234
+ const tool = allTools.find((t) => t.name === "getMethodology");
235
+ const result = (await tool.handler({ topic: "agents_md_maintenance" }));
236
+ expect(result.title).toContain("AGENTS.md");
237
+ expect(result.steps.length).toBe(5);
238
+ });
239
+ it("overview should include all 15 methodology topics", async () => {
240
+ const tool = allTools.find((t) => t.name === "getMethodology");
241
+ const result = (await tool.handler({ topic: "overview" }));
242
+ const topics = Object.keys(result.steps[0].topics);
243
+ expect(topics).toContain("project_ideation");
244
+ expect(topics).toContain("tech_stack_2026");
245
+ expect(topics).toContain("telemetry_setup");
246
+ expect(topics).toContain("agents_md_maintenance");
247
+ expect(topics.length).toBe(14); // All topics listed in overview
248
+ });
249
+ });
250
+ describe("Unit: setup_local_env", () => {
251
+ it("should return environment status without errors", async () => {
252
+ const tool = allTools.find((t) => t.name === "setup_local_env");
253
+ // Skip SDK checks to avoid timeout from dynamic imports
254
+ const result = (await tool.handler({ checkSdks: false }));
255
+ expect(result).toHaveProperty("environment");
256
+ expect(result).toHaveProperty("apiKeys");
257
+ expect(result).toHaveProperty("capabilities");
258
+ expect(result).toHaveProperty("recommendation");
259
+ expect(result.environment).toHaveProperty("nodeVersion");
260
+ expect(result.environment).toHaveProperty("packageManager");
261
+ });
262
+ });
263
+ describe("Unit: research_job_market", () => {
264
+ it("should return job market data for known roles", async () => {
265
+ const tool = allTools.find((t) => t.name === "research_job_market");
266
+ const result = (await tool.handler({ role: "AI Engineer" }));
267
+ expect(result.role).toBe("AI Engineer");
268
+ expect(result).toHaveProperty("commonRequirements");
269
+ expect(result).toHaveProperty("emergingSkills");
270
+ expect(result).toHaveProperty("salaryRange");
271
+ expect(result).toHaveProperty("recommendation");
272
+ expect(result.commonRequirements.length).toBeGreaterThan(0);
273
+ });
274
+ });
275
+ // ═══════════════════════════════════════════════════════════════════════════
276
+ // UNIT LAYER — individual tool behavior
277
+ // ═══════════════════════════════════════════════════════════════════════════
278
+ const findTool = (name) => allTools.find((t) => t.name === name);
279
+ describe("Unit: abandon_cycle", () => {
280
+ it("should abandon an active cycle", async () => {
281
+ // Create a cycle first
282
+ const startTool = findTool("start_verification_cycle");
283
+ const cycle = (await startTool.handler({
284
+ title: "test-abandon-cycle",
285
+ description: "test cycle for abandon",
286
+ }));
287
+ expect(cycle.cycleId).toBeTruthy();
288
+ // Abandon it
289
+ const abandonTool = findTool("abandon_cycle");
290
+ const result = (await abandonTool.handler({
291
+ cycleId: cycle.cycleId,
292
+ reason: "test cleanup",
293
+ }));
294
+ expect(result.abandoned).toBe(true);
295
+ expect(result.reason).toBe("test cleanup");
296
+ });
297
+ it("should skip already-abandoned cycles", async () => {
298
+ const startTool = findTool("start_verification_cycle");
299
+ const cycle = (await startTool.handler({
300
+ title: "test-double-abandon",
301
+ description: "test",
302
+ }));
303
+ const abandonTool = findTool("abandon_cycle");
304
+ await abandonTool.handler({ cycleId: cycle.cycleId });
305
+ const result2 = (await abandonTool.handler({
306
+ cycleId: cycle.cycleId,
307
+ }));
308
+ expect(result2.skipped).toBe(true);
309
+ });
310
+ it("should throw on nonexistent cycle", async () => {
311
+ const abandonTool = findTool("abandon_cycle");
312
+ await expect(abandonTool.handler({ cycleId: "nonexistent_cycle_id" })).rejects.toThrow("Cycle not found");
313
+ });
314
+ });
315
+ describe("Unit: search_all_knowledge", () => {
316
+ it("should return results structure with gaps field", async () => {
317
+ const tool = findTool("search_all_knowledge");
318
+ const result = (await tool.handler({
319
+ query: "test",
320
+ }));
321
+ expect(result).toHaveProperty("query");
322
+ expect(result).toHaveProperty("totalResults");
323
+ expect(result).toHaveProperty("learnings");
324
+ expect(result).toHaveProperty("reconFindings");
325
+ expect(result).toHaveProperty("gaps");
326
+ expect(result).toHaveProperty("_contributeBack");
327
+ });
328
+ });
329
+ // ═══════════════════════════════════════════════════════════════════════════
330
+ // INTEGRATION LAYER — multi-tool chain
331
+ // ═══════════════════════════════════════════════════════════════════════════
332
+ describe("Integration: full verification cycle chain", () => {
333
+ it("start → log_phase → log_gap → resolve_gap → abandon", async () => {
334
+ // 1. Start cycle
335
+ const cycle = (await findTool("start_verification_cycle").handler({
336
+ title: "integration-test-cycle",
337
+ description: "full chain test",
338
+ }));
339
+ expect(cycle.cycleId).toBeTruthy();
340
+ // 2. Log phase 1 findings
341
+ const phase1 = (await findTool("log_phase_findings").handler({
342
+ cycleId: cycle.cycleId,
343
+ phaseNumber: 1,
344
+ status: "passed",
345
+ findings: { summary: "context gathered" },
346
+ }));
347
+ expect(phase1.phaseRecorded).toBe(1);
348
+ expect(phase1.phaseStatus).toBe("passed");
349
+ // 3. Log a gap
350
+ const gap = (await findTool("log_gap").handler({
351
+ cycleId: cycle.cycleId,
352
+ severity: "LOW",
353
+ title: "test gap for integration",
354
+ description: "This is a test gap",
355
+ rootCause: "testing",
356
+ fixStrategy: "resolve in test",
357
+ }));
358
+ expect(gap.gapId).toBeTruthy();
359
+ // 4. Resolve the gap
360
+ const resolved = (await findTool("resolve_gap").handler({
361
+ gapId: gap.gapId,
362
+ }));
363
+ expect(resolved.status).toBe("resolved");
364
+ // 5. Abandon the cycle (cleanup)
365
+ const abandoned = (await findTool("abandon_cycle").handler({
366
+ cycleId: cycle.cycleId,
367
+ reason: "integration test cleanup",
368
+ }));
369
+ expect(abandoned.abandoned).toBe(true);
370
+ });
371
+ });
372
+ describe("Integration: search finds logged gaps", () => {
373
+ it("should find gaps via search_all_knowledge after logging", async () => {
374
+ const uniqueMarker = `vitest-marker-${Date.now()}`;
375
+ // Create a cycle and gap with a unique marker
376
+ const cycle = (await findTool("start_verification_cycle").handler({
377
+ title: `search-test-${uniqueMarker}`,
378
+ description: "test",
379
+ }));
380
+ await findTool("log_gap").handler({
381
+ cycleId: cycle.cycleId,
382
+ severity: "LOW",
383
+ title: `gap-${uniqueMarker}`,
384
+ description: `Testing search finds this gap ${uniqueMarker}`,
385
+ rootCause: "test",
386
+ fixStrategy: "none",
387
+ });
388
+ // Search for it
389
+ const results = (await findTool("search_all_knowledge").handler({
390
+ query: uniqueMarker,
391
+ }));
392
+ expect(results.gaps.length).toBeGreaterThanOrEqual(1);
393
+ expect(results.gaps[0].title).toContain(uniqueMarker);
394
+ expect(results.gaps[0].status).toBe("open");
395
+ // Cleanup
396
+ await findTool("abandon_cycle").handler({
397
+ cycleId: cycle.cycleId,
398
+ reason: "test cleanup",
399
+ });
400
+ });
401
+ });
402
+ //# sourceMappingURL=tools.test.js.map