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.
- package/README.md +237 -0
- package/dist/__tests__/tools.test.d.ts +1 -0
- package/dist/__tests__/tools.test.js +402 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/db.d.ts +4 -0
- package/dist/db.js +198 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +237 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/documentTools.d.ts +5 -0
- package/dist/tools/documentTools.js +524 -0
- package/dist/tools/documentTools.js.map +1 -0
- package/dist/tools/documentationTools.d.ts +12 -0
- package/dist/tools/documentationTools.js +647 -0
- package/dist/tools/documentationTools.js.map +1 -0
- package/dist/tools/evalTools.d.ts +6 -0
- package/dist/tools/evalTools.js +335 -0
- package/dist/tools/evalTools.js.map +1 -0
- package/dist/tools/financialTools.d.ts +10 -0
- package/dist/tools/financialTools.js +403 -0
- package/dist/tools/financialTools.js.map +1 -0
- package/dist/tools/flywheelTools.d.ts +6 -0
- package/dist/tools/flywheelTools.js +366 -0
- package/dist/tools/flywheelTools.js.map +1 -0
- package/dist/tools/githubTools.d.ts +12 -0
- package/dist/tools/githubTools.js +432 -0
- package/dist/tools/githubTools.js.map +1 -0
- package/dist/tools/learningTools.d.ts +6 -0
- package/dist/tools/learningTools.js +199 -0
- package/dist/tools/learningTools.js.map +1 -0
- package/dist/tools/memoryTools.d.ts +5 -0
- package/dist/tools/memoryTools.js +137 -0
- package/dist/tools/memoryTools.js.map +1 -0
- package/dist/tools/metaTools.d.ts +7 -0
- package/dist/tools/metaTools.js +837 -0
- package/dist/tools/metaTools.js.map +1 -0
- package/dist/tools/planningTools.d.ts +5 -0
- package/dist/tools/planningTools.js +147 -0
- package/dist/tools/planningTools.js.map +1 -0
- package/dist/tools/qualityGateTools.d.ts +6 -0
- package/dist/tools/qualityGateTools.js +347 -0
- package/dist/tools/qualityGateTools.js.map +1 -0
- package/dist/tools/reconTools.d.ts +8 -0
- package/dist/tools/reconTools.js +729 -0
- package/dist/tools/reconTools.js.map +1 -0
- package/dist/tools/searchTools.d.ts +5 -0
- package/dist/tools/searchTools.js +145 -0
- package/dist/tools/searchTools.js.map +1 -0
- package/dist/tools/uiCaptureTools.d.ts +8 -0
- package/dist/tools/uiCaptureTools.js +339 -0
- package/dist/tools/uiCaptureTools.js.map +1 -0
- package/dist/tools/verificationTools.d.ts +6 -0
- package/dist/tools/verificationTools.js +472 -0
- package/dist/tools/verificationTools.js.map +1 -0
- package/dist/tools/visionTools.d.ts +12 -0
- package/dist/tools/visionTools.js +553 -0
- package/dist/tools/visionTools.js.map +1 -0
- package/dist/tools/webTools.d.ts +12 -0
- package/dist/tools/webTools.js +443 -0
- package/dist/tools/webTools.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- 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
|