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.
Files changed (42) hide show
  1. package/README.md +32 -22
  2. package/dist/src/config/timeout-config.js +18 -8
  3. package/dist/src/profiles/balanced.js +10 -2
  4. package/dist/src/profiles/code_focus.js +10 -2
  5. package/dist/src/profiles/full.js +10 -2
  6. package/dist/src/profiles/heavy_coding.js +10 -2
  7. package/dist/src/profiles/minimal.js +10 -2
  8. package/dist/src/profiles/research_power.js +10 -2
  9. package/dist/src/server.js +6 -1
  10. package/dist/src/tools/diff-review-tool.js +120 -0
  11. package/dist/src/tools/doctor-tool.js +143 -0
  12. package/dist/src/tools/jury-tool.js +18 -11
  13. package/dist/src/tools/panel.js +24 -0
  14. package/dist/src/tools/plan-critique-tool.js +117 -0
  15. package/dist/src/tools/prompt-technique-tools.js +1 -1
  16. package/dist/src/tools/provider-catalog.js +140 -0
  17. package/dist/src/tools/registry.js +16 -0
  18. package/dist/src/tools/security-review-tool.js +71 -0
  19. package/dist/src/tools/tachi-tool.js +49 -2
  20. package/dist/src/tools/testgen-tool.js +57 -0
  21. package/dist/src/utils/api-keys.js +4 -2
  22. package/dist/src/utils/smart-api-client.js +13 -4
  23. package/dist/src/utils/streaming-helper.js +1 -1
  24. package/dist/src/utils/tool-config.js +17 -0
  25. package/docs/plans/monetization-roadmap/avenues.html +179 -0
  26. package/docs/plans/monetization-roadmap/hosting.html +114 -0
  27. package/docs/plans/monetization-roadmap/index.html +89 -0
  28. package/docs/plans/monetization-roadmap/plan-90.html +94 -0
  29. package/docs/plans/monetization-roadmap/style.css +241 -0
  30. package/docs/superpowers/plans/2026-07-01-gap-tools.md +1147 -0
  31. package/package.json +1 -1
  32. package/profiles/balanced.json +7 -3
  33. package/profiles/code_focus.json +7 -3
  34. package/profiles/full.json +7 -3
  35. package/profiles/heavy_coding.json +7 -3
  36. package/profiles/minimal.json +7 -3
  37. package/profiles/research_power.json +7 -3
  38. package/skills/redteam/SKILL.md +21 -0
  39. package/skills/review/SKILL.md +21 -0
  40. package/tools.config.json +4 -11
  41. package/profiles/debug_intensive.json +0 -33
  42. 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
- [![Version](https://img.shields.io/badge/version-2.23.2-blue.svg)](https://www.npmjs.com/package/tachibot-mcp)
8
- [![Tools](https://img.shields.io/badge/tools-57_active-brightgreen.svg)](#-tool-ecosystem-57-tools)
7
+ [![Version](https://img.shields.io/badge/version-2.24.0-blue.svg)](https://www.npmjs.com/package/tachibot-mcp)
8
+ [![Tools](https://img.shields.io/badge/tools-61_active-brightgreen.svg)](#-tool-ecosystem-61-tools)
9
9
  [![License](https://img.shields.io/badge/license-AGPL--3.0-green.svg)](LICENSE)
10
- [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org)
11
11
  [![MCP](https://img.shields.io/badge/MCP-Compatible-purple.svg)](https://modelcontextprotocol.io)
12
12
 
13
- **57 AI tools. 12 providers. One protocol.**
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-57-tools) · [Documentation](https://tachibot.com/docs)
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 12 slash commands for Claude Code. These orchestrate the tools into powerful workflows:
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
- - **57 AI Tools** across 12 providers &mdash; Perplexity, Grok, GPT-5, Gemini, Qwen, Kimi, MiniMax, DeepSeek, GLM (Zhipu), StepFun, ERNIE (Baidu), plus free local models (Ollama / LM Studio / llama.cpp / vLLM)
101
+ - **61 AI Tools** across 12 providers &mdash; 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) &mdash; Flash/search tier; reasoning default stays `gemini-3.1-pro-preview`
101
103
  - **Multi-Model Council** &mdash; planner_maker synthesizes plans from 5+ models into bite-sized TDD steps
102
104
  - **Smart Routing** &mdash; 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** | 12 | Quick tasks, low token budget |
116
+ | **Minimal** | 13 | Quick tasks, low token budget |
115
117
  | **Research Power** | 35 | Deep investigation, multi-source |
116
- | **Code Focus** | 34 | Software development, SWE tasks |
117
- | **Balanced** | 45 | General-purpose, mixed workflows |
118
- | **Heavy Coding** (default) | 50 | Max code tools + agentic workflows |
119
- | **Full** | 57 | Everything enabled |
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** &mdash; 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
- ### Setup
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)** &mdash; 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 (57 Tools)
191
+ ## Tool Ecosystem (61 Tools)
182
192
 
183
- ### Research & Search (6)
184
- `perplexity_ask` &#183; `perplexity_research` &#183; `perplexity_reason` &#183; `grok_search` &#183; `openai_search` &#183; `gemini_search`
193
+ ### Research & Search (5)
194
+ `perplexity_ask` &#183; `perplexity_reason` &#183; `grok_search` &#183; `openai_search` &#183; `gemini_search`
185
195
 
186
196
  ### Reasoning & Planning (13)
187
197
  `grok_reason` &#183; `openai_reason` &#183; `qwen_reason` &#183; `qwq_reason` &#183; `kimi_thinking` &#183; `kimi_decompose` &#183; `deepseek_reason` &#183; `glm_reason` &#183; `stepfun_reason` &#183; `ernie_reason` &#183; `planner_maker` &#183; `planner_runner` &#183; `list_plans`
188
198
 
189
- ### Code Intelligence (9)
190
- `kimi_code` &#183; `grok_code` &#183; `grok_debug` &#183; `qwen_coder` &#183; `qwen_algo` &#183; `qwen_competitive` &#183; `deepseek_algo` &#183; `minimax_code` &#183; `minimax_agent`
199
+ ### Code Intelligence (10)
200
+ `kimi_code` &#183; `grok_code` &#183; `grok_debug` &#183; `qwen_coder` &#183; `qwen_algo` &#183; `qwen_competitive` &#183; `deepseek_algo` &#183; `minimax_code` &#183; `minimax_agent` &#183; `testgen`
191
201
 
192
- ### Analysis & Judgment (11)
193
- `gemini_analyze_text` &#183; `gemini_analyze_code` &#183; `gemini_judge` &#183; `jury` &#183; `gemini_brainstorm` &#183; `openai_brainstorm` &#183; `openai_code_review` &#183; `openai_explain` &#183; `grok_brainstorm` &#183; `grok_architect` &#183; `kimi_long_context`
202
+ ### Analysis & Judgment (14)
203
+ `gemini_analyze_text` &#183; `gemini_analyze_code` &#183; `gemini_judge` &#183; `jury` &#183; `diff_review` &#183; `plan_critique` &#183; `gemini_brainstorm` &#183; `openai_brainstorm` &#183; `openai_code_review` &#183; `openai_explain` &#183; `grok_brainstorm` &#183; `grok_architect` &#183; `security_review` &#183; `kimi_long_context`
194
204
 
195
- ### Meta & Orchestration (5)
196
- `think` &#183; `nextThought` &#183; `focus` &#183; `tachi` &#183; `usage_stats`
205
+ ### Meta & Orchestration (6)
206
+ `think` &#183; `nextThought` &#183; `focus` &#183; `tachi` &#183; `doctor` &#183; `usage_stats`
197
207
 
198
208
  ### Workflows (9)
199
209
  `workflow` &#183; `workflow_start` &#183; `continue_workflow` &#183; `list_workflows` &#183; `create_workflow` &#183; `visualize_workflow` &#183; `workflow_status` &#183; `validate_workflow` &#183; `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
- return new Promise((_, reject) => {
84
- setTimeout(() => {
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
- return Promise.race([
97
- promise,
98
- createTimeoutPromise(timeoutMs, timeoutMessage)
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: 180000 // 3 minutes - high/xhigh reasoning effort
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 (~Xk tokens, 18 tools)",
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 (~Xk tokens, 15 tools)",
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: "All tools enabled for maximum capability (57 tools)",
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: "Default profile — heavy coding with all reasoning & code tools (40 tools)",
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 (~Xk tokens, 8 tools)",
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 (~Xk tokens, 13 tools)",
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
  };
@@ -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
+ }