tachibot-mcp 2.23.3 → 2.24.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 +32 -22
- package/dist/src/config/timeout-config.js +18 -8
- package/dist/src/profiles/balanced.js +10 -2
- package/dist/src/profiles/code_focus.js +10 -2
- package/dist/src/profiles/full.js +10 -2
- package/dist/src/profiles/heavy_coding.js +10 -2
- package/dist/src/profiles/minimal.js +10 -2
- package/dist/src/profiles/research_power.js +10 -2
- package/dist/src/server.js +6 -1
- package/dist/src/tools/diff-review-tool.js +120 -0
- package/dist/src/tools/doctor-tool.js +143 -0
- package/dist/src/tools/jury-tool.js +18 -11
- package/dist/src/tools/panel.js +24 -0
- package/dist/src/tools/plan-critique-tool.js +117 -0
- package/dist/src/tools/prompt-technique-tools.js +1 -1
- package/dist/src/tools/provider-catalog.js +140 -0
- package/dist/src/tools/registry.js +16 -0
- package/dist/src/tools/security-review-tool.js +71 -0
- package/dist/src/tools/tachi-tool.js +49 -2
- package/dist/src/tools/testgen-tool.js +57 -0
- package/dist/src/utils/api-keys.js +4 -2
- package/dist/src/utils/smart-api-client.js +13 -4
- package/dist/src/utils/streaming-helper.js +1 -1
- package/dist/src/utils/tool-config.js +17 -0
- package/docs/plans/monetization-roadmap/avenues.html +179 -0
- package/docs/plans/monetization-roadmap/hosting.html +114 -0
- package/docs/plans/monetization-roadmap/index.html +89 -0
- package/docs/plans/monetization-roadmap/plan-90.html +94 -0
- package/docs/plans/monetization-roadmap/style.css +241 -0
- package/docs/superpowers/plans/2026-07-01-gap-tools.md +1147 -0
- package/package.json +1 -1
- package/profiles/balanced.json +7 -3
- package/profiles/code_focus.json +7 -3
- package/profiles/full.json +7 -3
- package/profiles/heavy_coding.json +7 -3
- package/profiles/minimal.json +7 -3
- package/profiles/research_power.json +7 -3
- package/skills/redteam/SKILL.md +21 -0
- package/skills/review/SKILL.md +21 -0
- package/tools.config.json +4 -11
- package/profiles/debug_intensive.json +0 -33
- package/profiles/workflow_builder.json +0 -36
package/README.md
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
### Multi-Model AI Orchestration Platform
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/tachibot-mcp)
|
|
8
|
+
[](#-tool-ecosystem-61-tools)
|
|
9
9
|
[](LICENSE)
|
|
10
|
-
[](https://nodejs.org)
|
|
11
11
|
[](https://modelcontextprotocol.io)
|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**61 AI tools. 12 providers. One protocol.**
|
|
14
14
|
|
|
15
15
|
Orchestrate Perplexity, Grok, GPT-5.5, Gemini, Qwen, Kimi K2.7-Code, and MiniMax M3
|
|
16
16
|
from Claude Code, Claude Desktop, Cursor, or any MCP client.
|
|
17
17
|
|
|
18
|
-
[Get Started](#-quick-start) · [View Tools](#-tool-ecosystem-
|
|
18
|
+
[Get Started](#-quick-start) · [View Tools](#-tool-ecosystem-61-tools) · [Documentation](https://tachibot.com/docs)
|
|
19
19
|
|
|
20
20
|
<br>
|
|
21
21
|
|
|
@@ -70,7 +70,7 @@ Techniques are embedded directly in tool system prompts for automatic applicatio
|
|
|
70
70
|
|
|
71
71
|
## Skills (Claude Code)
|
|
72
72
|
|
|
73
|
-
TachiBot ships with
|
|
73
|
+
TachiBot ships with 14 slash commands for Claude Code. These orchestrate the tools into powerful workflows:
|
|
74
74
|
|
|
75
75
|
| Skill | What it does | Example |
|
|
76
76
|
|-------|-------------|---------|
|
|
@@ -85,6 +85,8 @@ TachiBot ships with 12 slash commands for Claude Code. These orchestrate the too
|
|
|
85
85
|
| `/lens` | Long-context analysis over Kimi's 256K window | `/lens find inconsistencies in this spec` |
|
|
86
86
|
| `/reflect` | Grounded reflexion loop — critique vs external evidence | `/reflect harden this auth middleware` |
|
|
87
87
|
| `/tot` | Tree-of-Thought: branch → jury-prune → synthesize | `/tot design a rate limiter` |
|
|
88
|
+
| `/review` | Multi-model diff review — panel + Gemini judge verdict | `/review` (or paste a diff) |
|
|
89
|
+
| `/redteam` | Adversarial plan red-team — pre-mortem, risks, plan edits | `/redteam <paste plan>` |
|
|
88
90
|
| `/tachi` | Help - see available skills, tools, key status | `/tachi` |
|
|
89
91
|
|
|
90
92
|
Skills automatically adapt to your configured API keys. Even with just 1-2 providers, all skills work.
|
|
@@ -96,7 +98,7 @@ Skills automatically adapt to your configured API keys. Even with just 1-2 provi
|
|
|
96
98
|
## Key Features
|
|
97
99
|
|
|
98
100
|
### Multi-Model Intelligence
|
|
99
|
-
- **
|
|
101
|
+
- **61 AI Tools** across 12 providers — Perplexity, Grok, GPT-5, Gemini, Qwen, Kimi, MiniMax, DeepSeek, GLM (Zhipu), StepFun, ERNIE (Baidu), plus free local models (Ollama / LM Studio / llama.cpp / vLLM)
|
|
100
102
|
- **Gemini 3.5 Flash** (`gemini-3.5-flash`, GA May 19 2026) — Flash/search tier; reasoning default stays `gemini-3.1-pro-preview`
|
|
101
103
|
- **Multi-Model Council** — planner_maker synthesizes plans from 5+ models into bite-sized TDD steps
|
|
102
104
|
- **Smart Routing** — Automatic model selection for optimal results
|
|
@@ -111,12 +113,12 @@ Skills automatically adapt to your configured API keys. Even with just 1-2 provi
|
|
|
111
113
|
### Tool Profiles
|
|
112
114
|
| Profile | Tools | Best For |
|
|
113
115
|
|---------|-------|----------|
|
|
114
|
-
| **Minimal** |
|
|
116
|
+
| **Minimal** | 13 | Quick tasks, low token budget |
|
|
115
117
|
| **Research Power** | 35 | Deep investigation, multi-source |
|
|
116
|
-
| **Code Focus** |
|
|
117
|
-
| **Balanced** |
|
|
118
|
-
| **Heavy Coding**
|
|
119
|
-
| **Full** |
|
|
118
|
+
| **Code Focus** | 39 | Software development, SWE tasks |
|
|
119
|
+
| **Balanced** | 50 | General-purpose, mixed workflows |
|
|
120
|
+
| **Heavy Coding** | 54 | Max code tools + agentic workflows |
|
|
121
|
+
| **Full** (default) | 61 | Everything enabled |
|
|
120
122
|
|
|
121
123
|
### Developer Experience
|
|
122
124
|
- **Claude Code** — First-class support
|
|
@@ -134,7 +136,15 @@ Skills automatically adapt to your configured API keys. Even with just 1-2 provi
|
|
|
134
136
|
npm install -g tachibot-mcp
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
###
|
|
139
|
+
### Claude Code (one-liner)
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
claude mcp add tachibot -- npx -y -p tachibot-mcp tachibot
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then verify with `/mcp`. Add API keys with `--env`, e.g. `--env OPENROUTER_API_KEY=sk-or-xxx --env PERPLEXITY_API_KEY=pplx-xxx`.
|
|
146
|
+
|
|
147
|
+
### Setup (Claude Desktop)
|
|
138
148
|
|
|
139
149
|
**Gateway Mode (Recommended)** — 2 keys, all providers:
|
|
140
150
|
|
|
@@ -178,22 +188,22 @@ See [Installation Guide](docs/INSTALLATION_BOTH.md) for detailed instructions.
|
|
|
178
188
|
|
|
179
189
|
---
|
|
180
190
|
|
|
181
|
-
## Tool Ecosystem (
|
|
191
|
+
## Tool Ecosystem (61 Tools)
|
|
182
192
|
|
|
183
|
-
### Research & Search (
|
|
184
|
-
`perplexity_ask` · `
|
|
193
|
+
### Research & Search (5)
|
|
194
|
+
`perplexity_ask` · `perplexity_reason` · `grok_search` · `openai_search` · `gemini_search`
|
|
185
195
|
|
|
186
196
|
### Reasoning & Planning (13)
|
|
187
197
|
`grok_reason` · `openai_reason` · `qwen_reason` · `qwq_reason` · `kimi_thinking` · `kimi_decompose` · `deepseek_reason` · `glm_reason` · `stepfun_reason` · `ernie_reason` · `planner_maker` · `planner_runner` · `list_plans`
|
|
188
198
|
|
|
189
|
-
### Code Intelligence (
|
|
190
|
-
`kimi_code` · `grok_code` · `grok_debug` · `qwen_coder` · `qwen_algo` · `qwen_competitive` · `deepseek_algo` · `minimax_code` · `minimax_agent`
|
|
199
|
+
### Code Intelligence (10)
|
|
200
|
+
`kimi_code` · `grok_code` · `grok_debug` · `qwen_coder` · `qwen_algo` · `qwen_competitive` · `deepseek_algo` · `minimax_code` · `minimax_agent` · `testgen`
|
|
191
201
|
|
|
192
|
-
### Analysis & Judgment (
|
|
193
|
-
`gemini_analyze_text` · `gemini_analyze_code` · `gemini_judge` · `jury` · `gemini_brainstorm` · `openai_brainstorm` · `openai_code_review` · `openai_explain` · `grok_brainstorm` · `grok_architect` · `kimi_long_context`
|
|
202
|
+
### Analysis & Judgment (14)
|
|
203
|
+
`gemini_analyze_text` · `gemini_analyze_code` · `gemini_judge` · `jury` · `diff_review` · `plan_critique` · `gemini_brainstorm` · `openai_brainstorm` · `openai_code_review` · `openai_explain` · `grok_brainstorm` · `grok_architect` · `security_review` · `kimi_long_context`
|
|
194
204
|
|
|
195
|
-
### Meta & Orchestration (
|
|
196
|
-
`think` · `nextThought` · `focus` · `tachi` · `usage_stats`
|
|
205
|
+
### Meta & Orchestration (6)
|
|
206
|
+
`think` · `nextThought` · `focus` · `tachi` · `doctor` · `usage_stats`
|
|
197
207
|
|
|
198
208
|
### Workflows (9)
|
|
199
209
|
`workflow` · `workflow_start` · `continue_workflow` · `list_workflows` · `create_workflow` · `visualize_workflow` · `workflow_status` · `validate_workflow` · `validate_workflow_file`
|
|
@@ -77,14 +77,19 @@ export function formatTimeout(ms) {
|
|
|
77
77
|
return `${minutes}m ${remainingSeconds}s`;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Create timeout promise for use with Promise.race()
|
|
80
|
+
* Create timeout promise for use with Promise.race(), plus a cancel handle.
|
|
81
|
+
* The race's loser leaves its timer scheduled unless the caller cancels it —
|
|
82
|
+
* without that, a fast-resolving `promise` still leaves this setTimeout alive
|
|
83
|
+
* for the full `ms`, holding the event loop open.
|
|
81
84
|
*/
|
|
82
85
|
export function createTimeoutPromise(ms, message) {
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
let timer;
|
|
87
|
+
const promise = new Promise((_, reject) => {
|
|
88
|
+
timer = setTimeout(() => {
|
|
85
89
|
reject(new Error(message || `Operation timed out after ${formatTimeout(ms)}`));
|
|
86
90
|
}, ms);
|
|
87
91
|
});
|
|
92
|
+
return { promise, cancel: () => clearTimeout(timer) };
|
|
88
93
|
}
|
|
89
94
|
/**
|
|
90
95
|
* Wrap promise with timeout
|
|
@@ -93,10 +98,13 @@ export async function withTimeout(promise, timeoutMs, toolName) {
|
|
|
93
98
|
const timeoutMessage = toolName
|
|
94
99
|
? `${toolName} operation timed out after ${formatTimeout(timeoutMs)}`
|
|
95
100
|
: `Operation timed out after ${formatTimeout(timeoutMs)}`;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
const raceTimeout = createTimeoutPromise(timeoutMs, timeoutMessage);
|
|
102
|
+
try {
|
|
103
|
+
return await Promise.race([promise, raceTimeout.promise]);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
raceTimeout.cancel();
|
|
107
|
+
}
|
|
100
108
|
}
|
|
101
109
|
/**
|
|
102
110
|
* Check if we should show progress (operation exceeds threshold)
|
|
@@ -127,7 +135,9 @@ export const SMART_TIMEOUT_DEFAULTS = {
|
|
|
127
135
|
},
|
|
128
136
|
openai: {
|
|
129
137
|
base: 60000, // 60 seconds - GPT-5.4 reasoning needs more time
|
|
130
|
-
max:
|
|
138
|
+
max: 600000 // 10 minutes - GPT-5.5 high/xhigh effort can exceed the
|
|
139
|
+
// OpenAI SDK's 900s default; 180s was cutting off real
|
|
140
|
+
// reasoning runs (verified 2026-07 deep-research)
|
|
131
141
|
},
|
|
132
142
|
anthropic: {
|
|
133
143
|
base: 20000, // 20 seconds
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const balancedProfile = {
|
|
2
|
-
description: "Balanced set for general use (
|
|
2
|
+
description: "Balanced set for general use (50 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
think: true,
|
|
5
5
|
focus: true,
|
|
6
6
|
tachi: true,
|
|
7
|
+
doctor: true,
|
|
7
8
|
nextThought: true,
|
|
8
9
|
usage_stats: true,
|
|
9
10
|
perplexity_ask: true,
|
|
10
11
|
perplexity_reason: true,
|
|
11
|
-
perplexity_research: false,
|
|
12
12
|
grok_reason: true,
|
|
13
13
|
grok_code: true,
|
|
14
14
|
grok_debug: false,
|
|
@@ -59,5 +59,13 @@ export const balancedProfile = {
|
|
|
59
59
|
planner_maker: true,
|
|
60
60
|
planner_runner: true,
|
|
61
61
|
list_plans: true,
|
|
62
|
+
// Test generation
|
|
63
|
+
testgen: true,
|
|
64
|
+
// Security audit
|
|
65
|
+
security_review: true,
|
|
66
|
+
// Diff-aware code review
|
|
67
|
+
diff_review: true,
|
|
68
|
+
// Adversarial plan red-team
|
|
69
|
+
plan_critique: true,
|
|
62
70
|
}
|
|
63
71
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const codeFocusProfile = {
|
|
2
|
-
description: "Code-heavy work with debugging and analysis (
|
|
2
|
+
description: "Code-heavy work with debugging and analysis (39 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
think: true,
|
|
5
5
|
focus: true,
|
|
6
6
|
tachi: true,
|
|
7
|
+
doctor: true,
|
|
7
8
|
nextThought: true,
|
|
8
9
|
usage_stats: true,
|
|
9
10
|
perplexity_ask: true,
|
|
10
11
|
perplexity_reason: false,
|
|
11
|
-
perplexity_research: false,
|
|
12
12
|
grok_reason: true,
|
|
13
13
|
grok_code: true,
|
|
14
14
|
grok_debug: true,
|
|
@@ -59,5 +59,13 @@ export const codeFocusProfile = {
|
|
|
59
59
|
planner_maker: true,
|
|
60
60
|
planner_runner: true,
|
|
61
61
|
list_plans: true,
|
|
62
|
+
// Test generation
|
|
63
|
+
testgen: true,
|
|
64
|
+
// Security audit
|
|
65
|
+
security_review: true,
|
|
66
|
+
// Diff-aware code review
|
|
67
|
+
diff_review: true,
|
|
68
|
+
// Adversarial plan red-team
|
|
69
|
+
plan_critique: true,
|
|
62
70
|
}
|
|
63
71
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const fullProfile = {
|
|
2
|
-
description: "
|
|
2
|
+
description: "Default profile — all tools enabled for maximum capability (61 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
think: true,
|
|
5
5
|
focus: true,
|
|
6
6
|
tachi: true,
|
|
7
|
+
doctor: true,
|
|
7
8
|
nextThought: true,
|
|
8
9
|
usage_stats: true,
|
|
9
10
|
perplexity_ask: true,
|
|
10
11
|
perplexity_reason: true,
|
|
11
|
-
perplexity_research: true,
|
|
12
12
|
grok_reason: true,
|
|
13
13
|
grok_code: true,
|
|
14
14
|
grok_debug: true,
|
|
@@ -59,5 +59,13 @@ export const fullProfile = {
|
|
|
59
59
|
planner_maker: true,
|
|
60
60
|
planner_runner: true,
|
|
61
61
|
list_plans: true,
|
|
62
|
+
// Test generation
|
|
63
|
+
testgen: true,
|
|
64
|
+
// Security audit
|
|
65
|
+
security_review: true,
|
|
66
|
+
// Diff-aware code review
|
|
67
|
+
diff_review: true,
|
|
68
|
+
// Adversarial plan red-team
|
|
69
|
+
plan_critique: true,
|
|
62
70
|
}
|
|
63
71
|
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export const heavyCodingProfile = {
|
|
2
|
-
description: "
|
|
2
|
+
description: "Heavy coding with all reasoning & code tools (54 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
// Core reasoning - all enabled
|
|
5
5
|
think: true,
|
|
6
6
|
focus: true,
|
|
7
7
|
tachi: true,
|
|
8
|
+
doctor: true,
|
|
8
9
|
nextThought: true,
|
|
9
10
|
usage_stats: true,
|
|
10
11
|
// Perplexity - all enabled for research
|
|
11
12
|
perplexity_ask: true,
|
|
12
13
|
perplexity_reason: true,
|
|
13
|
-
perplexity_research: true,
|
|
14
14
|
// Grok - all enabled for heavy coding
|
|
15
15
|
grok_reason: true,
|
|
16
16
|
grok_code: true,
|
|
@@ -66,5 +66,13 @@ export const heavyCodingProfile = {
|
|
|
66
66
|
planner_maker: true,
|
|
67
67
|
planner_runner: true,
|
|
68
68
|
list_plans: true,
|
|
69
|
+
// Test generation
|
|
70
|
+
testgen: true,
|
|
71
|
+
// Security audit
|
|
72
|
+
security_review: true,
|
|
73
|
+
// Diff-aware code review
|
|
74
|
+
diff_review: true,
|
|
75
|
+
// Adversarial plan red-team
|
|
76
|
+
plan_critique: true,
|
|
69
77
|
}
|
|
70
78
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const minimalProfile = {
|
|
2
|
-
description: "Minimal essential tools for basic tasks (
|
|
2
|
+
description: "Minimal essential tools for basic tasks (13 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
think: true,
|
|
5
5
|
focus: true,
|
|
6
6
|
tachi: true,
|
|
7
|
+
doctor: true,
|
|
7
8
|
nextThought: true,
|
|
8
9
|
usage_stats: true,
|
|
9
10
|
perplexity_ask: true,
|
|
10
11
|
perplexity_reason: false,
|
|
11
|
-
perplexity_research: false,
|
|
12
12
|
grok_reason: true,
|
|
13
13
|
grok_code: false,
|
|
14
14
|
grok_debug: false,
|
|
@@ -59,5 +59,13 @@ export const minimalProfile = {
|
|
|
59
59
|
planner_maker: false,
|
|
60
60
|
planner_runner: false,
|
|
61
61
|
list_plans: false,
|
|
62
|
+
// Test generation
|
|
63
|
+
testgen: false,
|
|
64
|
+
// Security audit
|
|
65
|
+
security_review: false,
|
|
66
|
+
// Diff-aware code review
|
|
67
|
+
diff_review: false,
|
|
68
|
+
// Adversarial plan red-team
|
|
69
|
+
plan_critique: false,
|
|
62
70
|
}
|
|
63
71
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const researchPowerProfile = {
|
|
2
|
-
description: "Research-focused with Grok search + all Perplexity + brainstorming (
|
|
2
|
+
description: "Research-focused with Grok search + all Perplexity + brainstorming (35 tools)",
|
|
3
3
|
tools: {
|
|
4
4
|
think: true,
|
|
5
5
|
focus: true,
|
|
6
6
|
tachi: true,
|
|
7
|
+
doctor: true,
|
|
7
8
|
nextThought: true,
|
|
8
9
|
usage_stats: true,
|
|
9
10
|
perplexity_ask: true,
|
|
10
11
|
perplexity_reason: true,
|
|
11
|
-
perplexity_research: true,
|
|
12
12
|
grok_reason: true,
|
|
13
13
|
grok_code: false,
|
|
14
14
|
grok_debug: false,
|
|
@@ -59,5 +59,13 @@ export const researchPowerProfile = {
|
|
|
59
59
|
planner_maker: true,
|
|
60
60
|
planner_runner: true,
|
|
61
61
|
list_plans: true,
|
|
62
|
+
// Test generation
|
|
63
|
+
testgen: false,
|
|
64
|
+
// Security audit
|
|
65
|
+
security_review: false,
|
|
66
|
+
// Diff-aware code review
|
|
67
|
+
diff_review: false,
|
|
68
|
+
// Adversarial plan red-team
|
|
69
|
+
plan_critique: false,
|
|
62
70
|
}
|
|
63
71
|
};
|
package/dist/src/server.js
CHANGED
|
@@ -616,12 +616,17 @@ async function initializeServer() {
|
|
|
616
616
|
console.error("✅ Server.start() called successfully");
|
|
617
617
|
// Keep the process alive with a heartbeat
|
|
618
618
|
// This ensures the server doesn't exit prematurely
|
|
619
|
+
// .unref() so this timer is never itself the reason the process stays up —
|
|
620
|
+
// real MCP runs already stay alive via process.stdin.resume() and the
|
|
621
|
+
// active transport; this only logs a liveness ping while that's true.
|
|
622
|
+
// Without unref, importing this module (e.g. test/golden/emit-schema.ts
|
|
623
|
+
// reading tool schemas) leaks the interval past test teardown.
|
|
619
624
|
setInterval(() => {
|
|
620
625
|
// Heartbeat to keep process alive
|
|
621
626
|
// Log every 30 seconds to show we're still alive
|
|
622
627
|
const now = new Date().toISOString();
|
|
623
628
|
console.error(`💓 Heartbeat: Server still alive at ${now}`);
|
|
624
|
-
}, 30000); // Every 30 seconds
|
|
629
|
+
}, 30000).unref(); // Every 30 seconds
|
|
625
630
|
console.error("✅ Heartbeat interval established");
|
|
626
631
|
console.error("✅ Server started successfully and listening for MCP commands");
|
|
627
632
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff_review — multi-model, diff-AWARE code review.
|
|
3
|
+
* Differs from openai_code_review (whole-file, single model) and jury (free-
|
|
4
|
+
* text, no code structure): reviewers are scoped to the changed lines, then a
|
|
5
|
+
* Gemini judge dedupes and severity-ranks into ONE actionable list.
|
|
6
|
+
* Gated on Gemini (judge); panelists self-drop when their key is missing.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { defineModelTool } from "./factory/define-model-tool.js";
|
|
10
|
+
import { callOpenRouter, OpenRouterModel } from "./openrouter-tools.js";
|
|
11
|
+
import { callOpenAI } from "./openai-tools.js";
|
|
12
|
+
import { callGemini } from "./gemini-tools.js";
|
|
13
|
+
import { hasOpenAIApiKey, hasOpenRouterApiKey } from "../utils/api-keys.js";
|
|
14
|
+
import { OPENAI_MODELS } from "../config/model-constants.js";
|
|
15
|
+
import { readFilesIntoContext } from "../utils/file-reader.js";
|
|
16
|
+
import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
|
|
17
|
+
import { withHeartbeat } from "../utils/streaming-helper.js";
|
|
18
|
+
import { runPanel } from "./panel.js";
|
|
19
|
+
const PANELIST_MAX_TOKENS = 8000; // same rationale as JUROR_MAX_TOKENS (jury-tool.ts)
|
|
20
|
+
export function buildDiffReviewerPrompt(args) {
|
|
21
|
+
const focus = args.focus || "all";
|
|
22
|
+
const fileContext = args.files?.length
|
|
23
|
+
? `\n\nSURROUNDING CODE (context only — do NOT review unchanged code):\n${readFilesIntoContext(args.files)}`
|
|
24
|
+
: "";
|
|
25
|
+
return `Review this diff. Flag issues ONLY on changed lines and lines directly adjacent to/affected by the change — do not review the rest of the file.
|
|
26
|
+
|
|
27
|
+
${args.intent ? `STATED INTENT OF THE CHANGE: ${args.intent}\nAlso flag any way the diff does NOT accomplish this intent.\n` : ""}FOCUS: ${focus} (security | perf | correctness | style | all).
|
|
28
|
+
|
|
29
|
+
FOR EACH ISSUE: severity (blocker | major | minor | nit), file:line from the diff hunks, what breaks and the concrete input/state that triggers it, suggested fix (one line).
|
|
30
|
+
Look specifically for: regressions the change introduces, missed edge cases in the new logic, security implications of new data flows, and behavior the intent implies but the diff doesn't implement.
|
|
31
|
+
If you find nothing at a severity, say so explicitly.
|
|
32
|
+
|
|
33
|
+
DIFF:
|
|
34
|
+
${args.diff}${fileContext}`;
|
|
35
|
+
}
|
|
36
|
+
export function buildDiffJudgePrompt(perspectives, args) {
|
|
37
|
+
const floor = args.severityFloor || "nit";
|
|
38
|
+
const body = perspectives
|
|
39
|
+
.map((p, i) => `=== REVIEWER ${i + 1}: ${p.label} ===\n${p.text}`)
|
|
40
|
+
.join("\n\n");
|
|
41
|
+
return `You are the presiding reviewer. Below are independent reviews of the SAME diff.
|
|
42
|
+
|
|
43
|
+
MERGE THEM INTO ONE LIST:
|
|
44
|
+
1. Deduplicate findings that describe the same underlying issue (keep the clearest wording; note "flagged by N/${perspectives.length} reviewers").
|
|
45
|
+
2. Discard findings that misread the diff (verify each against the DIFF below).
|
|
46
|
+
3. Rank by severity: blocker > major > minor > nit. OMIT everything below severity floor: ${floor}.
|
|
47
|
+
4. Every finding keeps its file:line anchor and one-line fix.
|
|
48
|
+
|
|
49
|
+
END WITH: verdict line — "MERGEABLE", "MERGEABLE WITH FIXES", or "DO NOT MERGE", plus the single most important fix.
|
|
50
|
+
|
|
51
|
+
DIFF:
|
|
52
|
+
${args.diff}
|
|
53
|
+
|
|
54
|
+
${body}`;
|
|
55
|
+
}
|
|
56
|
+
function buildPanel() {
|
|
57
|
+
const panel = [];
|
|
58
|
+
if (hasOpenRouterApiKey()) {
|
|
59
|
+
panel.push({
|
|
60
|
+
key: "kimi",
|
|
61
|
+
label: "Kimi K2.7-Code (SWE regressions)",
|
|
62
|
+
call: (q) => callOpenRouter([
|
|
63
|
+
{ role: "system", content: `You are Kimi K2.7-Code, an SWE-specialized reviewer. Hunt regressions and missed edge cases in diffs. ${FORMAT_INSTRUCTION}` },
|
|
64
|
+
{ role: "user", content: q },
|
|
65
|
+
], OpenRouterModel.KIMI_K2_7_CODE, 0.3, PANELIST_MAX_TOKENS),
|
|
66
|
+
});
|
|
67
|
+
panel.push({
|
|
68
|
+
key: "deepseek",
|
|
69
|
+
label: "DeepSeek V4 Pro (correctness & security)",
|
|
70
|
+
call: (q) => callOpenRouter([
|
|
71
|
+
{ role: "system", content: `You are DeepSeek V4 Pro reviewing a diff. Rigorously verify correctness of the new logic and security of new data flows. ${FORMAT_INSTRUCTION}` },
|
|
72
|
+
{ role: "user", content: q },
|
|
73
|
+
], OpenRouterModel.DEEPSEEK_V4_PRO, 0.2, PANELIST_MAX_TOKENS),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (hasOpenAIApiKey()) {
|
|
77
|
+
panel.push({
|
|
78
|
+
key: "gpt",
|
|
79
|
+
label: "GPT-5.5 (intent & API-contract)",
|
|
80
|
+
call: (q) => callOpenAI([
|
|
81
|
+
{ role: "system", content: `You review diffs for intent mismatches and API-contract breaks (types, error paths, backward compatibility). ${FORMAT_INSTRUCTION}` },
|
|
82
|
+
{ role: "user", content: q },
|
|
83
|
+
], OPENAI_MODELS.DEFAULT, // explicit: undefined falls back to INSTANT (gpt-5.4-mini), contradicting the GPT-5.5 label
|
|
84
|
+
0.3, PANELIST_MAX_TOKENS, "high"),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return panel;
|
|
88
|
+
}
|
|
89
|
+
export const diffReviewTool = defineModelTool({
|
|
90
|
+
name: "diff_review",
|
|
91
|
+
description: "Multi-model diff-aware code review: 2-3 lab-diverse reviewers (Kimi K2.7-Code, DeepSeek V4 Pro, GPT-5.5) scoped to the changed lines, deduplicated and severity-ranked by a Gemini judge. Provide the unified diff in 'diff'.",
|
|
92
|
+
parameters: z.object({
|
|
93
|
+
diff: z.string().describe("Unified diff to review (git diff output) — REQUIRED"),
|
|
94
|
+
intent: z.string().optional().describe("What the change is SUPPOSED to do (enables intent-mismatch detection)"),
|
|
95
|
+
files: z.array(z.string()).optional().describe("File paths for surrounding context. Supports line ranges: 'src/foo.ts:100-200'."),
|
|
96
|
+
focus: z.enum(["security", "perf", "correctness", "style", "all"]).optional().default("all").describe("Review focus"),
|
|
97
|
+
severityFloor: z.enum(["blocker", "major", "minor", "nit"]).optional().default("nit").describe("Omit findings below this severity"),
|
|
98
|
+
}),
|
|
99
|
+
execute: async (args, { reportProgress }) => {
|
|
100
|
+
if (!args.diff?.trim()) {
|
|
101
|
+
return "Error: 'diff' is required — paste the unified diff (e.g. `git diff` output).";
|
|
102
|
+
}
|
|
103
|
+
const panel = buildPanel();
|
|
104
|
+
if (panel.length === 0) {
|
|
105
|
+
return "Error: no reviewers available — diff_review needs OPENROUTER_API_KEY and/or OPENAI_API_KEY in addition to the Gemini key. Run `doctor` for setup status.";
|
|
106
|
+
}
|
|
107
|
+
const reviewerPrompt = buildDiffReviewerPrompt(args);
|
|
108
|
+
const perspectives = await withHeartbeat(() => runPanel(panel, reviewerPrompt), reportProgress, 10000);
|
|
109
|
+
if (perspectives.length === 0) {
|
|
110
|
+
return "Error: all reviewers failed (provider outage or quota). Try again or run `doctor`.";
|
|
111
|
+
}
|
|
112
|
+
const judgePrompt = buildDiffJudgePrompt(perspectives, args);
|
|
113
|
+
const verdict = await withHeartbeat(() => callGemini(judgePrompt, undefined, `You are Gemini 3 Pro, the presiding code reviewer synthesizing a panel review of one diff. Be decisive; keep only verified findings. ${FORMAT_INSTRUCTION}`, 0.3), reportProgress, 10000);
|
|
114
|
+
const roster = perspectives.map((p) => p.label).join(", ");
|
|
115
|
+
return `DIFF REVIEW (${perspectives.length} reviewers: ${roster})\n\n${verdict}`;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
export function getAllDiffReviewTools() {
|
|
119
|
+
return [diffReviewTool];
|
|
120
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Tool — zero-cost diagnostic for "why am I only seeing N tools?"
|
|
3
|
+
*
|
|
4
|
+
* Tools self-gate registration on API keys (src/tools/registry.ts), so a user
|
|
5
|
+
* with one key silently sees a fraction of the full set with no explanation.
|
|
6
|
+
* `doctor` makes the gating legible: detected keys, which tools are hidden and
|
|
7
|
+
* why, the active profile and where it came from, and a concrete first step.
|
|
8
|
+
*
|
|
9
|
+
* Registers UNCONDITIONALLY (no API-key gate) — it costs nothing and is most
|
|
10
|
+
* useful precisely when no keys are set.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { defineModelTool } from "./factory/define-model-tool.js";
|
|
14
|
+
import { hasGrokApiKey, hasOpenAIApiKey, hasPerplexityApiKey, hasGeminiApiKey, hasOpenRouterApiKey, hasLocalLLM, } from "../utils/api-keys.js";
|
|
15
|
+
import { getActiveProfile, getProfileSource } from "../utils/tool-config.js";
|
|
16
|
+
import { toolRouter } from "./tool-router.js";
|
|
17
|
+
import { PROVIDER_GROUPS } from "./provider-catalog.js";
|
|
18
|
+
const RULE = "─".repeat(48);
|
|
19
|
+
/** One line per key provider: ✓/✗ plus the env var(s) that set it. */
|
|
20
|
+
function renderKeyStatus() {
|
|
21
|
+
const rows = PROVIDER_GROUPS.map((g) => {
|
|
22
|
+
const ok = g.available();
|
|
23
|
+
const mark = ok ? "✓" : "✗";
|
|
24
|
+
const label = g.label.padEnd(16);
|
|
25
|
+
const hint = ok ? g.envHint : `set ${g.envHint}`;
|
|
26
|
+
const note = g.note ? ` — ${g.note}` : "";
|
|
27
|
+
return ` ${mark} ${label} (${hint})${note}`;
|
|
28
|
+
});
|
|
29
|
+
return rows.join("\n");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* "Try this first" — pick a concrete entry point from the keys that ARE set,
|
|
33
|
+
* so the suggestion always actually works for this user.
|
|
34
|
+
*/
|
|
35
|
+
function suggestFirstStep() {
|
|
36
|
+
const gemini = hasGeminiApiKey();
|
|
37
|
+
const openrouter = hasOpenRouterApiKey();
|
|
38
|
+
const grok = hasGrokApiKey();
|
|
39
|
+
const perplexity = hasPerplexityApiKey();
|
|
40
|
+
const openai = hasOpenAIApiKey();
|
|
41
|
+
const local = hasLocalLLM();
|
|
42
|
+
const anyKey = gemini || openrouter || grok || perplexity || openai || local;
|
|
43
|
+
if (!anyKey) {
|
|
44
|
+
return [
|
|
45
|
+
" No API keys detected — external tools can't run yet.",
|
|
46
|
+
" Add ONE key to your .env, then restart the MCP server:",
|
|
47
|
+
" • OPENROUTER_API_KEY unlocks the most tools (Qwen/Kimi/DeepSeek/GLM/…)",
|
|
48
|
+
" • or GOOGLE_API_KEY / XAI_API_KEY / OPENAI_API_KEY / PERPLEXITY_API_KEY",
|
|
49
|
+
" (think, focus, nextThought, tachi and doctor work with no key.)",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
// A Gemini judge + at least one juror-capable key = the jury is the best demo.
|
|
53
|
+
if (gemini && (openrouter || grok || openai || perplexity)) {
|
|
54
|
+
return ' jury question="What\'s the best way to X?" (parallel panel → Gemini synthesis)';
|
|
55
|
+
}
|
|
56
|
+
if (grok) {
|
|
57
|
+
return ' tachi query="how does X work?" (auto-routes to Grok live search)';
|
|
58
|
+
}
|
|
59
|
+
if (perplexity) {
|
|
60
|
+
return ' perplexity_ask query="..." (web-grounded answer with sources)';
|
|
61
|
+
}
|
|
62
|
+
if (openrouter) {
|
|
63
|
+
return ' deepseek_reason query="..." (frontier open-weight reasoning)';
|
|
64
|
+
}
|
|
65
|
+
if (openai) {
|
|
66
|
+
return ' openai_reason query="..." (GPT-5.5 deep reasoning)';
|
|
67
|
+
}
|
|
68
|
+
if (gemini) {
|
|
69
|
+
return ' gemini_brainstorm query="..." (fast ideation)';
|
|
70
|
+
}
|
|
71
|
+
// local only
|
|
72
|
+
return ' local_query query="..." (offline, zero-cost via your local server)';
|
|
73
|
+
}
|
|
74
|
+
export const doctorTool = defineModelTool({
|
|
75
|
+
name: "doctor",
|
|
76
|
+
description: "Diagnose your TachiBot setup: which API keys are detected, which tools are available vs hidden (and why), the active profile, and a suggested first step. Zero-cost, needs no API key. Call it when tools seem missing.",
|
|
77
|
+
parameters: z.object({}),
|
|
78
|
+
execute: async (_args, context) => {
|
|
79
|
+
context?.log?.info?.("Running TachiBot doctor diagnostic");
|
|
80
|
+
// (a) API keys
|
|
81
|
+
const keys = renderKeyStatus();
|
|
82
|
+
// (b) Tool availability report from the router (available vs hidden + why)
|
|
83
|
+
const routerStatus = toolRouter.getStatus();
|
|
84
|
+
// Also summarize the key-gated tool count across ALL providers (the router
|
|
85
|
+
// only covers a subset of categories), so the "hidden" story is complete.
|
|
86
|
+
let gatedTotal = 0;
|
|
87
|
+
let gatedHidden = 0;
|
|
88
|
+
const hiddenByProvider = [];
|
|
89
|
+
for (const g of PROVIDER_GROUPS) {
|
|
90
|
+
gatedTotal += g.tools.length;
|
|
91
|
+
if (!g.available()) {
|
|
92
|
+
gatedHidden += g.tools.length;
|
|
93
|
+
hiddenByProvider.push(` ✗ ${g.label}: ${g.tools.length} tool(s) hidden (set ${g.envHint})`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const gatedVisible = gatedTotal - gatedHidden;
|
|
97
|
+
const hiddenSummary = hiddenByProvider.length > 0
|
|
98
|
+
? hiddenByProvider.join("\n")
|
|
99
|
+
: " All key-gated providers are configured — nothing hidden by missing keys.";
|
|
100
|
+
// (c) Active profile + where it was resolved from
|
|
101
|
+
const profile = getActiveProfile();
|
|
102
|
+
const profileName = profile?.name ?? "default";
|
|
103
|
+
const profileDesc = profile?.description ? `\n ${profile.description}` : "";
|
|
104
|
+
const profileFrom = getProfileSource();
|
|
105
|
+
// (d) Try-this-first suggestion, tailored to available keys
|
|
106
|
+
const suggestion = suggestFirstStep();
|
|
107
|
+
return [
|
|
108
|
+
"TACHIBOT DOCTOR",
|
|
109
|
+
"═".repeat(48),
|
|
110
|
+
"",
|
|
111
|
+
"API KEYS DETECTED",
|
|
112
|
+
RULE,
|
|
113
|
+
keys,
|
|
114
|
+
"",
|
|
115
|
+
"KEY-GATED TOOLS",
|
|
116
|
+
RULE,
|
|
117
|
+
` Visible: ${gatedVisible}/${gatedTotal} (${gatedHidden} hidden by missing keys)`,
|
|
118
|
+
hiddenSummary,
|
|
119
|
+
" Note: think, focus, nextThought, tachi, doctor + workflow/prompt tools",
|
|
120
|
+
" are always on (no key needed); profile membership still applies.",
|
|
121
|
+
"",
|
|
122
|
+
"ACTIVE PROFILE",
|
|
123
|
+
RULE,
|
|
124
|
+
` Profile: ${profileName} (source: ${profileFrom})${profileDesc}`,
|
|
125
|
+
" Switch with TACHIBOT_PROFILE=<name> or activeProfile in tools.config.json.",
|
|
126
|
+
" Profiles: minimal, code_focus, research_power, balanced, heavy_coding, full.",
|
|
127
|
+
"",
|
|
128
|
+
"TOOL ROUTER STATUS",
|
|
129
|
+
RULE,
|
|
130
|
+
routerStatus,
|
|
131
|
+
"",
|
|
132
|
+
"TRY THIS FIRST",
|
|
133
|
+
RULE,
|
|
134
|
+
suggestion,
|
|
135
|
+
"",
|
|
136
|
+
"Run tachi (no query) for the full tool + skill catalog.",
|
|
137
|
+
].join("\n");
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
/** Export for central registry (mirrors getTachiTools). */
|
|
141
|
+
export function getDoctorTools() {
|
|
142
|
+
return [doctorTool];
|
|
143
|
+
}
|