tachibot-mcp 2.17.0 → 2.18.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/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ All notable changes to TachiBot MCP will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.18.0] - 2026-03-21
9
+
10
+ ### Added
11
+ - **Goal-oriented checkpoints** — `planner_maker` and `planner_runner` now accept `goal` parameter for success criteria tracking
12
+ - **6 checkpoint gates** with 5 different models (no adjacent repeats): step1 (Gemini Sherlock), 10% (Grok), 25% (GPT + amendment protocol), 50% (Qwen), 80% (Kimi decompose), 100% (GPT+Gemini dual judge)
13
+ - **Reflexion Lite** — at 100%, Gemini reflects on what worked/failed, lesson saved to devlog
14
+ - **Amendment protocol** — at 25%, structured plan revision (evidence + proposed changes + impact) with human gate
15
+ - **Unblinded checkpoints** — `diff`, `testResults`, `modifiedFiles` params replace blind `code.substring(0,1500)` with real evidence
16
+ - **`files` param on all analysis tools** — 39 tools across 9 files can now read ACTUAL CODE from disk via `readFilesIntoContext()`
17
+ - **Shared `src/utils/file-reader.ts`** — reusable file reader with line range support (`file.ts:100-200`), size limits, directory expansion
18
+ - **Blueprint skill updated** — `goal` param, prompt template, `planner_runner` as default execution path
19
+
20
+ ### Fixed
21
+ - **Step index reset bug** — filtered arrays used local index instead of original step number (found by 3-model consensus: Kimi + Gemini + Qwen reading actual code)
22
+ - **Truncation indicators** — `code.substring()` now adds `[truncated]` so judge models know they're seeing partial code
23
+
24
+ ## [2.17.2] - 2026-03-21
25
+
26
+ ### Added
27
+ - **`files` parameter on 13+ more tools** — grok_architect, grok_brainstorm, grok_reason_v4, openai_explain, kimi_code, kimi_long_context, gemini_judge, gemini_brainstorm, gemini_query, gemini_summarize, qwq_reason, qwen_competitive, qwen_general (38 tools now support `files`)
28
+ - **Directory expansion in file reader** — pass `src/tools/` to read all code files in a directory (non-recursive, capped at 20 files)
29
+ - **Smart char budget** — multi-file reads distribute token budget across files to prevent context overflow
30
+
31
+ ## [2.17.1] - 2026-03-21
32
+
33
+ ### Fixed
34
+ - **kimi_decompose readability overhaul** — output now uses OVERVIEW/STRUCTURE/DETAILS/RISKS sections instead of dense inline metadata
35
+ - **Reasoning leak stripped** — Kimi K2.5 dumps CoT into content; now extracted via `<output>` tags with OVERVIEW fallback
36
+ - **Conflicting FORMAT_INSTRUCTION removed** — emoji headers and verdict lines no longer clash with decomposition formatting
37
+ - **Heartbeat interval fixed** — was incorrectly set to 240s instead of default 5s; network timeout now correctly passed to callOpenRouter (360s)
38
+ - **Type safety** — args typed from zod schema, unused `log` removed, `||` replaced with `??`
39
+
40
+ ### Changed
41
+ - **Smart decomposition** — model now infers context, constraints, risks, and measurable criteria even when user doesn't state them
42
+ - **Tuned for format adherence** — temperature 0.3 (was 0.5), maxTokens 4500 (was 6000), timeout 360s (was 180s default)
43
+
8
44
  ## [2.17.0] - 2026-03-21
9
45
 
10
46
  ### Changed
@@ -5,6 +5,7 @@ import { Architect } from "../modes/architect.js";
5
5
  import { CodeReviewer } from "../modes/code-reviewer.js";
6
6
  import { DocumentationWriter } from "../modes/documentation-writer.js";
7
7
  import { TestArchitect } from "../modes/test-architect.js";
8
+ import { readFilesIntoContext } from "../utils/file-reader.js";
8
9
  // Workflow tools removed - using workflow-runner.ts instead
9
10
  const auditor = new Auditor();
10
11
  const commitGuardian = new CommitGuardian();
@@ -18,11 +19,15 @@ export const auditorTool = {
18
19
  description: "Evidence-based audit. Put the CONTEXT in the 'context' parameter.",
19
20
  parameters: z.object({
20
21
  context: z.string().describe("What to audit (REQUIRED - put your audit request here)"),
22
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
21
23
  evidenceRequired: z.boolean().optional().describe("Require evidence for claims")
22
24
  }),
23
25
  execute: async (args, { log }) => {
24
26
  log.info("Starting audit");
25
- const result = await auditor.audit(args.context, {
27
+ const fileContext = args.files?.length
28
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
29
+ : "";
30
+ const result = await auditor.audit(args.context + fileContext, {
26
31
  evidenceRequired: args.evidenceRequired
27
32
  });
28
33
  log.info("Audit complete", {
@@ -38,6 +43,7 @@ export const commitGuardianTool = {
38
43
  description: "Pre-commit validation. Put the CONTEXT in the 'context' parameter.",
39
44
  parameters: z.object({
40
45
  context: z.string().describe("Code changes to validate (REQUIRED - put your diff/changes here)"),
46
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
41
47
  strict: z.boolean().optional().describe("Use strict validation rules"),
42
48
  checkSecurity: z.boolean().optional().describe("Check for security issues"),
43
49
  checkQuality: z.boolean().optional().describe("Check code quality"),
@@ -45,7 +51,10 @@ export const commitGuardianTool = {
45
51
  }),
46
52
  execute: async (args, { log }) => {
47
53
  log.info("Validating commit");
48
- const result = await commitGuardian.validate(args.context, {
54
+ const fileContext = args.files?.length
55
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
56
+ : "";
57
+ const result = await commitGuardian.validate(args.context + fileContext, {
49
58
  strict: args.strict,
50
59
  checkSecurity: args.checkSecurity,
51
60
  checkQuality: args.checkQuality,
@@ -66,6 +75,7 @@ export const architectTool = {
66
75
  parameters: z.object({
67
76
  query: z.string().describe("What to analyze in the codebase (REQUIRED - put your question here)"),
68
77
  path: z.string().optional().describe("Path to the codebase to analyze"),
78
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
69
79
  depth: z.enum(["shallow", "normal", "deep"])
70
80
  .optional()
71
81
  .describe("Analysis depth - must be one of: shallow, normal, deep"),
@@ -76,7 +86,10 @@ export const architectTool = {
76
86
  depth: args.depth || "normal",
77
87
  path: args.path
78
88
  });
79
- const result = await architect.analyze(args.query, {
89
+ const fileContext = args.files?.length
90
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
91
+ : "";
92
+ const result = await architect.analyze(args.query + fileContext, {
80
93
  path: args.path,
81
94
  depth: args.depth,
82
95
  focusAreas: args.focusAreas
@@ -98,6 +111,7 @@ export const codeReviewerTool = {
98
111
  parameters: z.object({
99
112
  code: z.string().describe("The actual source code to review (REQUIRED - put your code here)"),
100
113
  language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
114
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
101
115
  focusAreas: z.array(z.enum(['security', 'performance', 'readability', 'bugs', 'best-practices']))
102
116
  .optional()
103
117
  .describe("Focus areas - array of: security, performance, readability, bugs, best-practices"),
@@ -111,7 +125,10 @@ export const codeReviewerTool = {
111
125
  language: args.language || 'auto-detect',
112
126
  focusAreas: args.focusAreas || 'all'
113
127
  });
114
- const result = await codeReviewer.review(args.code, {
128
+ const fileContext = args.files?.length
129
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
130
+ : "";
131
+ const result = await codeReviewer.review(args.code + fileContext, {
115
132
  language: args.language,
116
133
  focusAreas: args.focusAreas,
117
134
  severity: args.severity,
@@ -131,6 +148,7 @@ export const documentationWriterTool = {
131
148
  description: "Documentation generation. Put the CODE in the 'code' parameter.",
132
149
  parameters: z.object({
133
150
  code: z.string().describe("The source code to document (REQUIRED - put your code here)"),
151
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
134
152
  style: z.enum(['narrative', 'technical', 'beginner-friendly', 'api-reference'])
135
153
  .optional()
136
154
  .describe("Documentation style - must be one of: narrative, technical, beginner-friendly, api-reference"),
@@ -145,7 +163,10 @@ export const documentationWriterTool = {
145
163
  style: args.style || 'narrative',
146
164
  format: args.format || 'markdown'
147
165
  });
148
- const result = await documentationWriter.generateDocs(args.code, {
166
+ const fileContext = args.files?.length
167
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
168
+ : "";
169
+ const result = await documentationWriter.generateDocs(args.code + fileContext, {
149
170
  style: args.style,
150
171
  includeExamples: args.includeExamples,
151
172
  generateToc: args.generateToc,
@@ -166,6 +187,7 @@ export const testArchitectTool = {
166
187
  description: "Test suite design. Put the CODE in the 'code' parameter.",
167
188
  parameters: z.object({
168
189
  code: z.string().describe("The source code to create tests for (REQUIRED - put your code here)"),
190
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
169
191
  testFramework: z.enum(['jest', 'mocha', 'vitest', 'cypress', 'playwright'])
170
192
  .optional()
171
193
  .describe("Test framework - must be one of: jest, mocha, vitest, cypress, playwright"),
@@ -183,7 +205,10 @@ export const testArchitectTool = {
183
205
  testTypes: args.testTypes || ['unit', 'integration', 'e2e'],
184
206
  coverage: args.coverage || 'thorough'
185
207
  });
186
- const result = await testArchitect.architectTests(args.code, {
208
+ const fileContext = args.files?.length
209
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
210
+ : "";
211
+ const result = await testArchitect.architectTests(args.code + fileContext, {
187
212
  testFramework: args.testFramework,
188
213
  testTypes: args.testTypes,
189
214
  coverage: args.coverage,
@@ -11,6 +11,7 @@ import { stripFormatting } from "../utils/format-stripper.js";
11
11
  import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
12
12
  import { withHeartbeat } from "../utils/streaming-helper.js";
13
13
  import { getTimeoutConfig } from "../config/timeout-config.js";
14
+ import { readFilesIntoContext } from "../utils/file-reader.js";
14
15
  // Note: renderOutput is applied centrally in server.ts safeAddTool() - no need to import here
15
16
  // NOTE: dotenv is loaded in server.ts before any imports
16
17
  // No need to reload here - just read from process.env
@@ -171,7 +172,8 @@ export const geminiQueryTool = {
171
172
  model: z.enum(["gemini-3", "pro", "flash"])
172
173
  .optional()
173
174
  .default("gemini-3")
174
- .describe("Model variant - must be one of: gemini-3 (default), pro, flash")
175
+ .describe("Model variant - must be one of: gemini-3 (default), pro, flash"),
176
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE.")
175
177
  }),
176
178
  execute: async (args, { log, reportProgress }) => {
177
179
  let model = GEMINI_MODELS.GEMINI_3_PRO; // Default to Gemini 3
@@ -182,8 +184,9 @@ export const geminiQueryTool = {
182
184
  model = GEMINI_MODELS.PRO;
183
185
  }
184
186
  // Skip validation - queries may contain code or LLM-generated content
187
+ const fileContext = args.files?.length ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}` : "";
185
188
  const reportFn = reportProgress ?? (async () => { });
186
- const result = await withHeartbeat(() => callGemini(args.prompt, model, undefined, 0.7, 'llm-orchestration'), reportFn);
189
+ const result = await withHeartbeat(() => callGemini(args.prompt + fileContext, model, undefined, 0.7, 'llm-orchestration'), reportFn);
187
190
  return stripFormatting(result);
188
191
  }
189
192
  };
@@ -200,7 +203,8 @@ export const geminiBrainstormTool = {
200
203
  parameters: z.object({
201
204
  prompt: z.string().describe("The ideas or topic to organize and refine (REQUIRED - put raw ideas or topic here)"),
202
205
  claudeThoughts: z.string().optional().describe("Claude's initial thoughts or raw ideas to cluster and refine"),
203
- maxClusters: z.number().optional().default(5).describe("Number of idea clusters to create (default: 5)")
206
+ maxClusters: z.number().optional().default(5).describe("Number of idea clusters to create (default: 5)"),
207
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE.")
204
208
  }),
205
209
  execute: async (args, { log, reportProgress }) => {
206
210
  const systemPrompt = `Convergent synthesis engine. Output consumed by automated toolchain.
@@ -226,8 +230,11 @@ Per cluster: Name | Score (1-10) | Top ideas (ranked) | Key insight
226
230
  Final: Which cluster has highest expected value and why. State the meta-pattern.
227
231
  No preamble. Structured output only.
228
232
  ${FORMAT_INSTRUCTION}`;
233
+ const fileContext = args.files?.length
234
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
235
+ : "";
229
236
  const reportFn = reportProgress ?? (async () => { });
230
- const response = await withHeartbeat(() => callGemini(args.prompt, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.7, 'llm-orchestration'), reportFn);
237
+ const response = await withHeartbeat(() => callGemini(args.prompt + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.7, 'llm-orchestration'), reportFn);
231
238
  return stripFormatting(response);
232
239
  }
233
240
  };
@@ -241,6 +248,7 @@ export const geminiAnalyzeCodeTool = {
241
248
  parameters: z.object({
242
249
  code: z.string().describe("The actual source code to analyze (REQUIRED - put your code here)"),
243
250
  language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
251
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
244
252
  focus: z.string().optional().default("general").describe("Analysis focus (e.g., quality, security, performance, bugs, general)")
245
253
  }),
246
254
  execute: async (args, { log, reportProgress }) => {
@@ -255,9 +263,12 @@ export const geminiAnalyzeCodeTool = {
255
263
  const systemPrompt = `Expert code reviewer. ${args.language || ''} code.
256
264
  ${focusText}.
257
265
  ${FORMAT_INSTRUCTION}`;
266
+ const fileContext = args.files?.length
267
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
268
+ : "";
258
269
  // Skip validation - code analysis naturally contains patterns that trigger false positives
259
270
  const reportFn = reportProgress ?? (async () => { });
260
- const result = await withHeartbeat(() => callGemini(`Analyze this code:\n\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\``, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
271
+ const result = await withHeartbeat(() => callGemini(`Analyze this code:\n\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\`${fileContext}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
261
272
  return stripFormatting(result);
262
273
  }
263
274
  };
@@ -270,6 +281,7 @@ export const geminiAnalyzeTextTool = {
270
281
  description: "Rhetorical analysis: dissect arguments for bias, logical fallacies, and persuasion tactics. Use for evaluating claims, detecting manipulation, or understanding argument structure. Put the TEXT in the 'text' parameter.",
271
282
  parameters: z.object({
272
283
  text: z.string().describe("The text to analyze (REQUIRED - put your text here)"),
284
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
273
285
  type: z.string()
274
286
  .optional()
275
287
  .default("rhetoric")
@@ -306,8 +318,11 @@ OUTPUT:
306
318
  ${analysisText}
307
319
  No preamble. Structured output only.
308
320
  ${FORMAT_INSTRUCTION}`;
321
+ const fileContext = args.files?.length
322
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
323
+ : "";
309
324
  const reportFn = reportProgress ?? (async () => { });
310
- const result = await withHeartbeat(() => callGemini(`Analyze this text:\n\n${args.text}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
325
+ const result = await withHeartbeat(() => callGemini(`Analyze this text:\n\n${args.text}${fileContext}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
311
326
  return stripFormatting(result);
312
327
  }
313
328
  };
@@ -327,7 +342,8 @@ export const geminiSummarizeTool = {
327
342
  format: z.enum(["paragraph", "bullet-points", "outline"])
328
343
  .optional()
329
344
  .default("paragraph")
330
- .describe("Output format - must be one of: paragraph, bullet-points, outline")
345
+ .describe("Output format - must be one of: paragraph, bullet-points, outline"),
346
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE.")
331
347
  }),
332
348
  execute: async (args, { log, reportProgress }) => {
333
349
  const lengthGuides = {
@@ -349,8 +365,9 @@ Focus on:
349
365
  - Conclusions and implications
350
366
  ${FORMAT_INSTRUCTION}`;
351
367
  // Skip validation for internal summarization calls
368
+ const fileContext = args.files?.length ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}` : "";
352
369
  const reportFn = reportProgress ?? (async () => { });
353
- const result = await withHeartbeat(() => callGemini(`Summarize this content:\n\n${args.content}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
370
+ const result = await withHeartbeat(() => callGemini(`Summarize this content:\n\n${args.content}` + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
354
371
  return stripFormatting(result);
355
372
  }
356
373
  };
@@ -411,7 +428,8 @@ export const geminiJudgeTool = {
411
428
  mode: z.enum(["synthesize", "evaluate", "rank", "resolve"])
412
429
  .optional()
413
430
  .default("synthesize")
414
- .describe("Judge mode: synthesize (merge best), evaluate (score each), rank (order by quality), resolve (settle conflicts)")
431
+ .describe("Judge mode: synthesize (merge best), evaluate (score each), rank (order by quality), resolve (settle conflicts)"),
432
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE.")
415
433
  }),
416
434
  execute: async (args, { log, reportProgress }) => {
417
435
  // Resolve perspectives from fallback params (AI clients sometimes use wrong param name)
@@ -475,8 +493,11 @@ ${FORMAT_INSTRUCTION}`;
475
493
  const userPrompt = args.question
476
494
  ? `QUESTION: ${args.question}\n\nPERSPECTIVES TO JUDGE:\n${args.perspectives}`
477
495
  : args.perspectives;
496
+ const fileContext = args.files?.length
497
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
498
+ : "";
478
499
  const reportFn = reportProgress ?? (async () => { });
479
- const result = await withHeartbeat(() => callGemini(userPrompt, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
500
+ const result = await withHeartbeat(() => callGemini(userPrompt + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
480
501
  return stripFormatting(result);
481
502
  }
482
503
  };
@@ -7,6 +7,7 @@ import { config } from "dotenv";
7
7
  import * as path from 'path';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { getGrokApiKey, hasGrokApiKey } from "../utils/api-keys.js";
10
+ import { readFilesIntoContext } from "../utils/file-reader.js";
10
11
  import { link } from "../utils/ansi-renderer.js";
11
12
  import { stripFormatting } from "../utils/format-stripper.js";
12
13
  import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
@@ -222,10 +223,11 @@ export const grokReasonEnhanced = {
222
223
  context: z.string().optional(),
223
224
  useHeavy: z.boolean().optional(),
224
225
  enableLiveSearch: z.boolean().optional(),
225
- maxSteps: z.number().optional()
226
+ maxSteps: z.number().optional(),
227
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE.")
226
228
  }),
227
229
  execute: async (args, { log }) => {
228
- const { problem, approach = "first-principles", context, useHeavy = false, enableLiveSearch = false, maxSteps = 5 } = args;
230
+ const { problem, approach = "first-principles", context, useHeavy = false, enableLiveSearch = false, maxSteps = 5, files } = args;
229
231
  const approachPrompts = {
230
232
  analytical: "Break down the problem systematically and analyze each component",
231
233
  creative: "Think outside the box and consider unconventional solutions",
@@ -233,6 +235,7 @@ export const grokReasonEnhanced = {
233
235
  "first-principles": "Break down to fundamental truths and build up from there",
234
236
  "multi-agent": "Consider multiple perspectives and synthesize them"
235
237
  };
238
+ const fileContext = files?.length ? `\n\nSOURCE CODE:\n${readFilesIntoContext(files)}` : "";
236
239
  const messages = [
237
240
  {
238
241
  role: "system",
@@ -245,7 +248,7 @@ ${FORMAT_INSTRUCTION}`
245
248
  },
246
249
  {
247
250
  role: "user",
248
- content: problem
251
+ content: problem + fileContext
249
252
  }
250
253
  ];
251
254
  const modelName = useHeavy ? 'Grok-4-Heavy' : 'Grok-4.1';
@@ -13,6 +13,7 @@ import { stripFormatting } from "../utils/format-stripper.js";
13
13
  import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
14
14
  import { tryOpenRouterGateway, isGatewayEnabled } from "../utils/openrouter-gateway.js";
15
15
  import { withHeartbeat } from "../utils/streaming-helper.js";
16
+ import { readFilesIntoContext } from "../utils/file-reader.js";
16
17
  // Note: renderOutput is applied centrally in server.ts safeAddTool() - no need to import here
17
18
  const __filename = fileURLToPath(import.meta.url);
18
19
  const __dirname = path.dirname(__filename);
@@ -130,6 +131,7 @@ export const grokReasonTool = {
130
131
  .optional()
131
132
  .describe("Reasoning approach (e.g., analytical, creative, systematic, first-principles)"),
132
133
  context: z.string().optional().describe("Additional context for the problem"),
134
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
133
135
  useHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for complex tasks")
134
136
  }),
135
137
  execute: async (args, { log, reportProgress }) => {
@@ -140,6 +142,9 @@ export const grokReasonTool = {
140
142
  systematic: "Follow a step-by-step logical process",
141
143
  "first-principles": "Break down to fundamental truths and build up from there"
142
144
  };
145
+ const fileContext = args.files?.length
146
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
147
+ : "";
143
148
  const messages = [
144
149
  {
145
150
  role: "system",
@@ -150,7 +155,7 @@ ${FORMAT_INSTRUCTION}`
150
155
  },
151
156
  {
152
157
  role: "user",
153
- content: problem
158
+ content: problem + fileContext
154
159
  }
155
160
  ];
156
161
  // Use GROK_4_1_FAST_REASONING by default (latest with enhanced reasoning!), GROK_4_HEAVY only if explicitly requested
@@ -175,6 +180,7 @@ export const grokCodeTool = {
175
180
  .describe("Code task (e.g., analyze, optimize, debug, review, refactor)"),
176
181
  code: z.string().describe("The actual source code to analyze (REQUIRED - put your code here)"),
177
182
  language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
183
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
178
184
  requirements: z.string().optional().describe("Specific requirements or focus areas")
179
185
  }),
180
186
  execute: async (args, { log, reportProgress }) => {
@@ -186,6 +192,9 @@ export const grokCodeTool = {
186
192
  review: "Review this code for best practices and improvements",
187
193
  refactor: "Refactor this code for better maintainability and clarity"
188
194
  };
195
+ const fileContext = args.files?.length
196
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
197
+ : "";
189
198
  const messages = [
190
199
  {
191
200
  role: "system",
@@ -197,7 +206,7 @@ ${FORMAT_INSTRUCTION}`
197
206
  },
198
207
  {
199
208
  role: "user",
200
- content: `Code:\n\`\`\`${language || ''}\n${code}\n\`\`\``
209
+ content: `Code:\n\`\`\`${language || ''}\n${code}\n\`\`\`` + fileContext
201
210
  }
202
211
  ];
203
212
  log?.info(`Using Grok 4.1 Fast Non-Reasoning (2M context, tool-calling optimized, $0.20/$0.50)`);
@@ -218,6 +227,7 @@ export const grokDebugTool = {
218
227
  issue: z.string().describe("Description of the issue or bug (REQUIRED - put your problem here)"),
219
228
  code: z.string().optional().describe("Relevant code that has the issue"),
220
229
  error: z.string().optional().describe("Error message or stack trace"),
230
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
221
231
  context: z.string().optional().describe("Additional context about the environment or conditions")
222
232
  }),
223
233
  execute: async (args, { log, reportProgress }) => {
@@ -232,6 +242,9 @@ export const grokDebugTool = {
232
242
  if (context) {
233
243
  prompt += `\nContext: ${context}\n`;
234
244
  }
245
+ const fileContext = args.files?.length
246
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
247
+ : "";
235
248
  const messages = [
236
249
  {
237
250
  role: "system",
@@ -245,7 +258,7 @@ ${FORMAT_INSTRUCTION}`
245
258
  },
246
259
  {
247
260
  role: "user",
248
- content: prompt
261
+ content: prompt + fileContext
249
262
  }
250
263
  ];
251
264
  log?.info(`Using Grok 4.1 Fast Non-Reasoning for debugging (tool-calling optimized, $0.20/$0.50)`);
@@ -267,10 +280,14 @@ export const grokArchitectTool = {
267
280
  constraints: z.string().optional().describe("Technical or business constraints to consider"),
268
281
  scale: z.string()
269
282
  .optional()
270
- .describe("Expected scale (e.g., small, medium, large, enterprise)")
283
+ .describe("Expected scale (e.g., small, medium, large, enterprise)"),
284
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
271
285
  }),
272
286
  execute: async (args, { log, reportProgress }) => {
273
287
  const { requirements, constraints, scale } = args;
288
+ const fileContext = args.files?.length
289
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
290
+ : "";
274
291
  const messages = [
275
292
  {
276
293
  role: "system",
@@ -282,7 +299,7 @@ ${FORMAT_INSTRUCTION}`
282
299
  },
283
300
  {
284
301
  role: "user",
285
- content: requirements
302
+ content: requirements + fileContext
286
303
  }
287
304
  ];
288
305
  log?.info(`Using Grok 4.1 Fast Reasoning for architecture (latest model, $0.20/$0.50)`);
@@ -303,10 +320,14 @@ export const grokBrainstormTool = {
303
320
  topic: z.string().describe("The topic to brainstorm about (REQUIRED - put your idea/topic here)"),
304
321
  constraints: z.string().optional().describe("Any constraints or requirements to consider"),
305
322
  numIdeas: z.number().optional().describe("Number of radical rebuilds to generate (default: 5)"),
306
- forceHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for deeper creativity")
323
+ forceHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for deeper creativity"),
324
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
307
325
  }),
308
326
  execute: async (args, { log, reportProgress }) => {
309
327
  const { topic, constraints, numIdeas = 5, forceHeavy = false } = args;
328
+ const fileContext = args.files?.length
329
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
330
+ : "";
310
331
  const messages = [
311
332
  {
312
333
  role: "system",
@@ -332,7 +353,7 @@ ${FORMAT_INSTRUCTION}`
332
353
  },
333
354
  {
334
355
  role: "user",
335
- content: topic
356
+ content: topic + fileContext
336
357
  }
337
358
  ];
338
359
  const model = forceHeavy ? GrokModel.GROK_4_HEAVY : GrokModel.GROK_4_1_FAST_REASONING;
@@ -13,6 +13,7 @@ import { OPENAI_MODELS } from "../config/model-constants.js";
13
13
  import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
14
14
  import { stripFormatting } from "../utils/format-stripper.js";
15
15
  import { withHeartbeat } from "../utils/streaming-helper.js";
16
+ import { readFilesIntoContext } from "../utils/file-reader.js";
16
17
  const __filename = fileURLToPath(import.meta.url);
17
18
  const __dirname = path.dirname(__filename);
18
19
  config({ path: path.resolve(__dirname, '../../../.env') });
@@ -342,6 +343,7 @@ export const openaiGpt5ReasonTool = {
342
343
  parameters: z.object({
343
344
  query: z.string().describe("The question or problem to reason about (REQUIRED - put your question here)"),
344
345
  context: z.string().optional().describe("Additional context for the reasoning task"),
346
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
345
347
  mode: z.string()
346
348
  .optional()
347
349
  .default("analytical")
@@ -354,6 +356,9 @@ export const openaiGpt5ReasonTool = {
354
356
  logical: "Use formal logic and systematic deduction",
355
357
  analytical: "Break down complex problems into components"
356
358
  };
359
+ const fileContext = args.files?.length
360
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
361
+ : "";
357
362
  const messages = [
358
363
  {
359
364
  role: "system",
@@ -365,7 +370,7 @@ ${FORMAT_INSTRUCTION}`
365
370
  },
366
371
  {
367
372
  role: "user",
368
- content: args.query
373
+ content: args.query + fileContext
369
374
  }
370
375
  ];
371
376
  // Use heartbeat to prevent MCP timeout during reasoning
@@ -383,6 +388,7 @@ export const openAIBrainstormTool = {
383
388
  parameters: z.object({
384
389
  problem: z.string().describe("The engineering problem or design tradeoff to brainstorm about (REQUIRED)"),
385
390
  constraints: z.string().optional().describe("Technical constraints: language, framework, performance requirements, team size"),
391
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
386
392
  quantity: z.number().optional().describe("Number of approaches to generate (default: 5)"),
387
393
  model: z.enum(["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro"])
388
394
  .optional()
@@ -396,6 +402,9 @@ export const openAIBrainstormTool = {
396
402
  const { problem, constraints, quantity = 5, model = OPENAI_MODELS.DEFAULT, reasoning_effort = "medium", max_tokens = 6000 } = args;
397
403
  // GPT-5.4 reasoning tokens eat into max_output_tokens — enforce minimum
398
404
  const effectiveMaxTokens = Math.max(max_tokens, 4000);
405
+ const fileContext = args.files?.length
406
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
407
+ : "";
399
408
  const messages = [
400
409
  {
401
410
  role: "system",
@@ -430,7 +439,7 @@ ${FORMAT_INSTRUCTION}`
430
439
  },
431
440
  {
432
441
  role: "user",
433
- content: problem
442
+ content: problem + fileContext
434
443
  }
435
444
  ];
436
445
  const modelEnum = model;
@@ -447,6 +456,7 @@ export const openaiCodeReviewTool = {
447
456
  parameters: z.object({
448
457
  code: z.string().describe("The actual source code to review (REQUIRED - put your code here)"),
449
458
  language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
459
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
450
460
  focusAreas: z.array(z.enum(["security", "performance", "readability", "bugs", "best-practices"]))
451
461
  .optional()
452
462
  .describe("Focus areas - array of: security, performance, readability, bugs, best-practices")
@@ -455,6 +465,9 @@ export const openaiCodeReviewTool = {
455
465
  const focusText = args.focusAreas
456
466
  ? `Focus especially on: ${args.focusAreas.join(', ')}`
457
467
  : "Review all aspects: security, performance, readability, bugs, and best practices";
468
+ const fileContext = args.files?.length
469
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
470
+ : "";
458
471
  const messages = [
459
472
  {
460
473
  role: "system",
@@ -467,7 +480,7 @@ ${FORMAT_INSTRUCTION}`
467
480
  },
468
481
  {
469
482
  role: "user",
470
- content: `Review this code:\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\``
483
+ content: `Review this code:\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\`` + fileContext
471
484
  }
472
485
  ];
473
486
  // Use heartbeat to prevent MCP timeout
@@ -491,7 +504,8 @@ export const openaiExplainTool = {
491
504
  style: z.enum(["technical", "simple", "analogy", "visual"])
492
505
  .optional()
493
506
  .default("simple")
494
- .describe("Explanation style - must be one of: technical, simple, analogy, visual")
507
+ .describe("Explanation style - must be one of: technical, simple, analogy, visual"),
508
+ files: z.array(z.string()).optional().describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
495
509
  }),
496
510
  execute: async (args, { log }) => {
497
511
  const levelPrompts = {
@@ -505,6 +519,9 @@ export const openaiExplainTool = {
505
519
  analogy: "Use analogies and metaphors",
506
520
  visual: "Describe with visual concepts and diagrams"
507
521
  };
522
+ const fileContext = args.files?.length
523
+ ? `\n\nSOURCE CODE:\n${readFilesIntoContext(args.files)}`
524
+ : "";
508
525
  const messages = [
509
526
  {
510
527
  role: "system",
@@ -516,7 +533,7 @@ ${FORMAT_INSTRUCTION}`
516
533
  },
517
534
  {
518
535
  role: "user",
519
- content: `Explain: ${args.topic}`
536
+ content: `Explain: ${args.topic}` + fileContext
520
537
  }
521
538
  ];
522
539
  return await callOpenAI(messages, OPENAI_MODELS.DEFAULT, 0.7, 2500, "low");