@brutalist/mcp 0.5.1 → 0.6.1
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 +65 -63
- package/dist/brutalist-server.d.ts +15 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +405 -357
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +8 -3
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +352 -50
- package/dist/cli-agents.js.map +1 -1
- package/dist/streaming/circuit-breaker.d.ts +186 -0
- package/dist/streaming/circuit-breaker.d.ts.map +1 -0
- package/dist/streaming/circuit-breaker.js +463 -0
- package/dist/streaming/circuit-breaker.js.map +1 -0
- package/dist/streaming/intelligent-buffer.d.ts +141 -0
- package/dist/streaming/intelligent-buffer.d.ts.map +1 -0
- package/dist/streaming/intelligent-buffer.js +555 -0
- package/dist/streaming/intelligent-buffer.js.map +1 -0
- package/dist/streaming/output-parser.d.ts +89 -0
- package/dist/streaming/output-parser.d.ts.map +1 -0
- package/dist/streaming/output-parser.js +349 -0
- package/dist/streaming/output-parser.js.map +1 -0
- package/dist/streaming/progress-tracker.d.ts +149 -0
- package/dist/streaming/progress-tracker.d.ts.map +1 -0
- package/dist/streaming/progress-tracker.js +519 -0
- package/dist/streaming/progress-tracker.js.map +1 -0
- package/dist/streaming/session-manager.d.ts +238 -0
- package/dist/streaming/session-manager.d.ts.map +1 -0
- package/dist/streaming/session-manager.js +546 -0
- package/dist/streaming/session-manager.js.map +1 -0
- package/dist/streaming/sse-transport.d.ts +95 -0
- package/dist/streaming/sse-transport.d.ts.map +1 -0
- package/dist/streaming/sse-transport.js +319 -0
- package/dist/streaming/sse-transport.js.map +1 -0
- package/dist/streaming/streaming-orchestrator.d.ts +153 -0
- package/dist/streaming/streaming-orchestrator.d.ts.map +1 -0
- package/dist/streaming/streaming-orchestrator.js +436 -0
- package/dist/streaming/streaming-orchestrator.js.map +1 -0
- package/dist/test-utils/process-manager.d.ts +61 -0
- package/dist/test-utils/process-manager.d.ts.map +1 -0
- package/dist/test-utils/process-manager.js +262 -0
- package/dist/test-utils/process-manager.js.map +1 -0
- package/dist/test-utils/server-harness.d.ts +73 -0
- package/dist/test-utils/server-harness.d.ts.map +1 -0
- package/dist/test-utils/server-harness.js +296 -0
- package/dist/test-utils/server-harness.js.map +1 -0
- package/dist/test-utils/streaming-fuzz.d.ts +57 -0
- package/dist/test-utils/streaming-fuzz.d.ts.map +1 -0
- package/dist/test-utils/streaming-fuzz.js +287 -0
- package/dist/test-utils/streaming-fuzz.js.map +1 -0
- package/dist/test-utils/test-isolation.d.ts +70 -0
- package/dist/test-utils/test-isolation.d.ts.map +1 -0
- package/dist/test-utils/test-isolation.js +193 -0
- package/dist/test-utils/test-isolation.js.map +1 -0
- package/dist/tool-definitions.d.ts +6 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +217 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/types/brutalist.d.ts +3 -19
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +51 -0
- package/dist/types/tool-config.d.ts.map +1 -0
- package/dist/types/tool-config.js +24 -0
- package/dist/types/tool-config.js.map +1 -0
- package/dist/utils/pagination.d.ts +2 -2
- package/dist/utils/pagination.d.ts.map +1 -1
- package/dist/utils/pagination.js +1 -1
- package/dist/utils/pagination.js.map +1 -1
- package/dist/utils/response-cache.d.ts +96 -0
- package/dist/utils/response-cache.d.ts.map +1 -0
- package/dist/utils/response-cache.js +371 -0
- package/dist/utils/response-cache.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +22 -3
- package/dist/utils.js.map +1 -1
- package/package.json +14 -4
package/dist/brutalist-server.js
CHANGED
|
@@ -6,7 +6,10 @@ import express from "express";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { CLIAgentOrchestrator } from './cli-agents.js';
|
|
8
8
|
import { logger } from './logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import { BASE_ROAST_SCHEMA } from './types/tool-config.js';
|
|
10
|
+
import { TOOL_CONFIGS } from './tool-definitions.js';
|
|
11
|
+
import { extractPaginationParams, parseCursor, PAGINATION_DEFAULTS, ResponseChunker, createPaginationMetadata, formatPaginationStatus, estimateTokenCount } from './utils/pagination.js';
|
|
12
|
+
import { ResponseCache } from './utils/response-cache.js';
|
|
10
13
|
// Use environment variable or fallback to manual version
|
|
11
14
|
const PACKAGE_VERSION = process.env.npm_package_version || "0.4.4";
|
|
12
15
|
export class BrutalistServer {
|
|
@@ -14,59 +17,120 @@ export class BrutalistServer {
|
|
|
14
17
|
config;
|
|
15
18
|
cliOrchestrator;
|
|
16
19
|
httpTransport;
|
|
20
|
+
responseCache;
|
|
21
|
+
actualPort;
|
|
22
|
+
shutdownHandler;
|
|
23
|
+
// Session tracking for security
|
|
24
|
+
activeSessions = new Map();
|
|
17
25
|
constructor(config = {}) {
|
|
18
26
|
this.config = {
|
|
19
27
|
workingDirectory: process.cwd(),
|
|
20
28
|
defaultTimeout: 1500000, // 25 minutes for thorough CLI analysis
|
|
21
|
-
enableSandbox: true,
|
|
22
29
|
transport: 'stdio', // Default to stdio for backward compatibility
|
|
23
30
|
httpPort: 3000,
|
|
24
31
|
...config
|
|
25
32
|
};
|
|
26
33
|
logger.debug("Initializing CLI Agent Orchestrator");
|
|
27
34
|
this.cliOrchestrator = new CLIAgentOrchestrator();
|
|
35
|
+
// Initialize response cache with configurable TTL
|
|
36
|
+
const cacheTTLHours = parseInt(process.env.BRUTALIST_CACHE_TTL_HOURS || '2', 10);
|
|
37
|
+
this.responseCache = new ResponseCache({
|
|
38
|
+
ttlHours: cacheTTLHours,
|
|
39
|
+
maxEntries: 50,
|
|
40
|
+
maxTotalSizeMB: 500,
|
|
41
|
+
maxEntrySizeMB: 10,
|
|
42
|
+
compressionThresholdMB: 1
|
|
43
|
+
});
|
|
44
|
+
logger.info(`📦 Response cache initialized with ${cacheTTLHours} hour TTL`);
|
|
28
45
|
this.server = new McpServer({
|
|
29
46
|
name: "brutalist-mcp",
|
|
30
47
|
version: PACKAGE_VERSION,
|
|
31
48
|
capabilities: {
|
|
32
|
-
tools: {}
|
|
49
|
+
tools: {},
|
|
50
|
+
logging: {},
|
|
51
|
+
experimental: {
|
|
52
|
+
streaming: true
|
|
53
|
+
}
|
|
33
54
|
}
|
|
34
55
|
});
|
|
35
56
|
this.registerTools();
|
|
36
57
|
}
|
|
37
58
|
handleStreamingEvent = (event) => {
|
|
38
|
-
// Send streaming event via MCP server (works for both stdio and HTTP transports)
|
|
39
59
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
if (!event.sessionId) {
|
|
61
|
+
logger.warn("⚠️ Streaming event without session ID - dropping for security");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
logger.debug(`🔄 Session-scoped streaming: ${event.type} from ${event.agent} to session ${event.sessionId.substring(0, 8)}...`);
|
|
65
|
+
// For HTTP transport: send session-specific notification
|
|
66
|
+
if (this.httpTransport) {
|
|
67
|
+
// Use MCP server's notification system with session context
|
|
68
|
+
this.server.server.notification({
|
|
69
|
+
method: "notifications/message",
|
|
70
|
+
params: {
|
|
71
|
+
level: 'info',
|
|
72
|
+
data: {
|
|
73
|
+
type: 'streaming_event',
|
|
74
|
+
sessionId: event.sessionId,
|
|
75
|
+
agent: event.agent,
|
|
76
|
+
eventType: event.type,
|
|
77
|
+
content: event.content?.substring(0, 1000), // Truncate for safety
|
|
78
|
+
timestamp: event.timestamp
|
|
79
|
+
},
|
|
80
|
+
logger: 'brutalist-mcp-streaming'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// For STDIO transport: still send but with session info
|
|
85
|
+
else {
|
|
86
|
+
this.server.sendLoggingMessage({
|
|
87
|
+
level: 'info',
|
|
88
|
+
data: {
|
|
89
|
+
sessionId: event.sessionId,
|
|
90
|
+
agent: event.agent,
|
|
91
|
+
type: event.type,
|
|
92
|
+
content: event.content?.substring(0, 500) // More restrictive for stdio
|
|
93
|
+
},
|
|
94
|
+
logger: 'brutalist-mcp-streaming'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Update session activity
|
|
98
|
+
if (this.activeSessions.has(event.sessionId)) {
|
|
99
|
+
this.activeSessions.get(event.sessionId).lastActivity = Date.now();
|
|
100
|
+
}
|
|
48
101
|
}
|
|
49
102
|
catch (error) {
|
|
50
|
-
logger.error("Failed to send streaming event",
|
|
103
|
+
logger.error("💥 Failed to send session-scoped streaming event", {
|
|
104
|
+
error: error instanceof Error ? error.message : String(error),
|
|
105
|
+
sessionId: event.sessionId?.substring(0, 8)
|
|
106
|
+
});
|
|
51
107
|
}
|
|
52
108
|
};
|
|
53
|
-
handleProgressUpdate = (progressToken, progress, total, message) => {
|
|
109
|
+
handleProgressUpdate = (progressToken, progress, total, message, sessionId) => {
|
|
54
110
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
if (!sessionId) {
|
|
112
|
+
logger.warn("⚠️ Progress update without session ID - dropping for security");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
logger.debug(`📊 Session progress: ${progress}/${total} for session ${sessionId.substring(0, 8)}...`);
|
|
116
|
+
// Send progress notification with session context
|
|
57
117
|
this.server.server.notification({
|
|
58
118
|
method: "notifications/progress",
|
|
59
119
|
params: {
|
|
60
120
|
progressToken,
|
|
61
121
|
progress,
|
|
62
122
|
total,
|
|
63
|
-
message
|
|
123
|
+
message: `[${sessionId.substring(0, 8)}] ${message}`, // Include session prefix
|
|
124
|
+
sessionId // Include in notification data
|
|
64
125
|
}
|
|
65
126
|
});
|
|
66
|
-
logger.debug(`✅ Sent progress notification: ${progress}/${total}`);
|
|
127
|
+
logger.debug(`✅ Sent session-scoped progress notification: ${progress}/${total}`);
|
|
67
128
|
}
|
|
68
129
|
catch (error) {
|
|
69
|
-
logger.error("Failed to send progress notification",
|
|
130
|
+
logger.error("💥 Failed to send progress notification", {
|
|
131
|
+
error: error instanceof Error ? error.message : String(error),
|
|
132
|
+
sessionId: sessionId?.substring(0, 8)
|
|
133
|
+
});
|
|
70
134
|
}
|
|
71
135
|
};
|
|
72
136
|
async start() {
|
|
@@ -104,13 +168,57 @@ export class BrutalistServer {
|
|
|
104
168
|
// Create Express app for HTTP handling
|
|
105
169
|
const app = express();
|
|
106
170
|
app.use(express.json({ limit: '10mb' })); // Add JSON size limit for security
|
|
107
|
-
//
|
|
171
|
+
// Secure CORS implementation
|
|
108
172
|
app.use((req, res, next) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
173
|
+
const origin = req.headers.origin;
|
|
174
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
175
|
+
// Define safe default origins for development
|
|
176
|
+
const defaultDevOrigins = [
|
|
177
|
+
'http://localhost:3000',
|
|
178
|
+
'http://127.0.0.1:3000',
|
|
179
|
+
'http://localhost:8080',
|
|
180
|
+
'http://127.0.0.1:8080',
|
|
181
|
+
'http://localhost:3001',
|
|
182
|
+
'http://127.0.0.1:3001'
|
|
183
|
+
];
|
|
184
|
+
// Get allowed origins from config or use defaults
|
|
185
|
+
const allowedOrigins = this.config.corsOrigins || defaultDevOrigins;
|
|
186
|
+
const allowWildcard = this.config.allowCORSWildcard === true && !isProduction;
|
|
187
|
+
// Determine if origin is allowed
|
|
188
|
+
let allowedOrigin = null;
|
|
189
|
+
if (allowWildcard) {
|
|
190
|
+
// Only in development with explicit opt-in
|
|
191
|
+
allowedOrigin = '*';
|
|
192
|
+
logger.warn("⚠️ Using wildcard CORS - only safe in development!");
|
|
193
|
+
}
|
|
194
|
+
else if (!origin) {
|
|
195
|
+
// No origin header (same-origin or direct server access)
|
|
196
|
+
allowedOrigin = defaultDevOrigins[0]; // Default fallback
|
|
197
|
+
}
|
|
198
|
+
else if (allowedOrigins.includes(origin)) {
|
|
199
|
+
// Explicitly allowed origin
|
|
200
|
+
allowedOrigin = origin;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Rejected origin
|
|
204
|
+
logger.warn(`🚫 CORS rejected origin: ${origin}`);
|
|
205
|
+
allowedOrigin = null;
|
|
206
|
+
}
|
|
207
|
+
// Set headers only if origin is allowed
|
|
208
|
+
if (allowedOrigin) {
|
|
209
|
+
res.header('Access-Control-Allow-Origin', allowedOrigin);
|
|
210
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
211
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
212
|
+
// Removed Authorization header for security
|
|
213
|
+
res.header('Access-Control-Allow-Credentials', 'false'); // Explicit false
|
|
214
|
+
}
|
|
112
215
|
if (req.method === 'OPTIONS') {
|
|
113
|
-
|
|
216
|
+
if (allowedOrigin) {
|
|
217
|
+
res.sendStatus(200);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
res.sendStatus(403); // Forbidden for disallowed origins
|
|
221
|
+
}
|
|
114
222
|
return;
|
|
115
223
|
}
|
|
116
224
|
next();
|
|
@@ -132,310 +240,63 @@ export class BrutalistServer {
|
|
|
132
240
|
res.json({ status: 'ok', transport: 'http-streaming', version: PACKAGE_VERSION });
|
|
133
241
|
});
|
|
134
242
|
// Start the HTTP server - bind to localhost only for security
|
|
135
|
-
const port = this.config.httpPort
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
server.close(() => {
|
|
145
|
-
logger.info('HTTP server closed');
|
|
146
|
-
process.exit(0);
|
|
243
|
+
const port = this.config.httpPort ?? 3000;
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const server = app.listen(port, '127.0.0.1', () => {
|
|
246
|
+
const actualPort = server.address()?.port || port;
|
|
247
|
+
this.actualPort = actualPort;
|
|
248
|
+
logger.info(`HTTP server listening on port ${actualPort}`);
|
|
249
|
+
logger.info(`MCP endpoint: http://localhost:${actualPort}/mcp`);
|
|
250
|
+
logger.info(`Health check: http://localhost:${actualPort}/health`);
|
|
251
|
+
resolve();
|
|
147
252
|
});
|
|
253
|
+
server.on('error', (error) => {
|
|
254
|
+
logger.error('HTTP server failed to start', error);
|
|
255
|
+
reject(error);
|
|
256
|
+
});
|
|
257
|
+
// Handle graceful shutdown - avoid duplicate listeners
|
|
258
|
+
if (!this.shutdownHandler) {
|
|
259
|
+
this.shutdownHandler = () => {
|
|
260
|
+
logger.info('Received SIGTERM, shutting down gracefully');
|
|
261
|
+
server.close(() => {
|
|
262
|
+
logger.info('HTTP server closed');
|
|
263
|
+
process.exit(0);
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
process.on('SIGTERM', this.shutdownHandler);
|
|
267
|
+
}
|
|
148
268
|
});
|
|
149
269
|
}
|
|
270
|
+
// Getter for actual listening port (useful for tests)
|
|
271
|
+
getActualPort() {
|
|
272
|
+
return this.actualPort;
|
|
273
|
+
}
|
|
274
|
+
// Cleanup method for tests - remove event listeners
|
|
275
|
+
cleanup() {
|
|
276
|
+
if (this.shutdownHandler) {
|
|
277
|
+
process.removeListener('SIGTERM', this.shutdownHandler);
|
|
278
|
+
this.shutdownHandler = undefined;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
150
281
|
registerTools() {
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
verbose: z.boolean().optional().describe("Include detailed execution information in output (default: false)"),
|
|
159
|
-
models: z.object({
|
|
160
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
161
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
162
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
163
|
-
}).optional().describe("Specific models to use for each CLI agent (defaults: codex=gpt-5, gemini=gemini-2.5-flash)"),
|
|
164
|
-
// Pagination parameters for large responses
|
|
165
|
-
offset: z.number().min(0).optional().describe("Character offset for response pagination (default: 0)"),
|
|
166
|
-
limit: z.number().min(1000).max(100000).optional().describe("Maximum characters per response chunk (default: 25000, max: 100000)"),
|
|
167
|
-
cursor: z.string().optional().describe("Pagination cursor from previous response (alternative to offset/limit)")
|
|
168
|
-
}, async (args, extra) => {
|
|
169
|
-
try {
|
|
170
|
-
const systemPrompt = `You are a battle-scarred principal engineer who has debugged production disasters for 15 years. Find security holes, performance bottlenecks, and maintainability nightmares in this codebase. Be brutal about what's broken but specific about what would actually work. Treat this like code that will kill people if it fails.`;
|
|
171
|
-
// Extract progressToken from request metadata for real-time streaming
|
|
172
|
-
const progressToken = extra._meta?.progressToken;
|
|
173
|
-
// Extract pagination parameters
|
|
174
|
-
const paginationParams = extractPaginationParams(args);
|
|
175
|
-
if (args.cursor) {
|
|
176
|
-
const cursorParams = parseCursor(args.cursor);
|
|
177
|
-
Object.assign(paginationParams, cursorParams);
|
|
178
|
-
}
|
|
179
|
-
const result = await this.executeBrutalistAnalysis("codebase", args.targetPath, systemPrompt, args.context, args.workingDirectory, args.enableSandbox, args.preferredCLI, args.verbose, args.models, progressToken);
|
|
180
|
-
return this.formatToolResponse(result, args.verbose, paginationParams);
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
return this.formatErrorResponse(error);
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
// ROAST_FILE_STRUCTURE: Directory hierarchy demolition
|
|
187
|
-
this.server.tool("roast_file_structure", "Deploy brutal AI critics to systematically destroy your file organization. These agents will navigate your actual directory structure and expose every organizational disaster, naming convention failure, and structural nightmare that makes your codebase unmaintainable.", {
|
|
188
|
-
targetPath: z.string().describe("Directory path to analyze"),
|
|
189
|
-
depth: z.number().optional().describe("Maximum directory depth to analyze (default: 3)"),
|
|
190
|
-
context: z.string().optional().describe("Additional context about the project structure"),
|
|
191
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
192
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
193
|
-
models: z.object({
|
|
194
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
195
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
196
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
197
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
198
|
-
}, async (args) => {
|
|
199
|
-
try {
|
|
200
|
-
const systemPrompt = `You are a brutal file organization critic. Your job is to systematically destroy the given directory structure by finding every organizational disaster, naming convention failure, and structural nightmare that makes codebases unmaintainable. Examine folder hierarchies, file naming patterns, separation of concerns, and overall project organization. Be ruthlessly honest about how poor organization will slow development and confuse developers. But after cataloguing this organizational hellscape, sketch out what sanity would actually look like.`;
|
|
201
|
-
const result = await this.executeBrutalistAnalysis("fileStructure", args.targetPath, systemPrompt, `Project structure analysis (depth: ${args.depth || 3}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
202
|
-
args.preferredCLI, undefined, // verbose
|
|
203
|
-
args.models);
|
|
204
|
-
return this.formatToolResponse(result);
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
return this.formatErrorResponse(error);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
// ROAST_DEPENDENCIES: Package management demolition
|
|
211
|
-
this.server.tool("roast_dependencies", "Deploy brutal AI critics to systematically destroy your dependency management. These agents will read your actual package files, analyze version conflicts, and expose every security vulnerability and compatibility nightmare in your dependency tree.", {
|
|
212
|
-
targetPath: z.string().describe("Path to package file (package.json, requirements.txt, Cargo.toml, etc.)"),
|
|
213
|
-
includeDevDeps: z.boolean().optional().describe("Include development dependencies in analysis (default: true)"),
|
|
214
|
-
context: z.string().optional().describe("Additional context about the project dependencies"),
|
|
215
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
216
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
217
|
-
models: z.object({
|
|
218
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
219
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
220
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
221
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
222
|
-
}, async (args) => {
|
|
223
|
-
try {
|
|
224
|
-
const systemPrompt = `You are a brutal dependency management critic. Your job is to systematically destroy the given dependency configuration by finding every security vulnerability, version conflict, compatibility nightmare, and bloat that will cause production failures. Examine package versions, security issues, licensing problems, and dependency tree complexity. Be ruthlessly honest about how poor dependency management will cause security breaches and deployment failures. After exposing this dependency dumpster fire, grudgingly admit what competent dependency management would require.`;
|
|
225
|
-
const result = await this.executeBrutalistAnalysis("dependencies", args.targetPath, systemPrompt, `Dependency analysis (dev deps: ${args.includeDevDeps ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
226
|
-
args.preferredCLI, undefined, // verbose
|
|
227
|
-
args.models);
|
|
228
|
-
return this.formatToolResponse(result);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
return this.formatErrorResponse(error);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
// ROAST_GIT_HISTORY: Version control demolition
|
|
235
|
-
this.server.tool("roast_git_history", "Deploy brutal AI critics to systematically destroy your git history and development practices. These agents will analyze your actual commit history, branching strategy, and code evolution to expose every workflow disaster and collaboration nightmare.", {
|
|
236
|
-
targetPath: z.string().describe("Git repository path to analyze"),
|
|
237
|
-
commitRange: z.string().optional().describe("Commit range to analyze (e.g., 'HEAD~10..HEAD', default: last 20 commits)"),
|
|
238
|
-
context: z.string().optional().describe("Additional context about the development workflow"),
|
|
239
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
240
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
241
|
-
models: z.object({
|
|
242
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
243
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
244
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
245
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
246
|
-
}, async (args) => {
|
|
247
|
-
try {
|
|
248
|
-
const systemPrompt = `You are a brutal git workflow critic. Your job is to systematically destroy the given git history and development practices by finding every workflow disaster, commit quality issue, and collaboration nightmare. Examine commit messages, branching strategies, merge patterns, and code evolution. Be ruthlessly honest about how poor git practices will cause deployment issues, collaboration failures, and development chaos. When you're done cataloguing this version control wasteland, reluctantly outline what professional git hygiene actually demands.`;
|
|
249
|
-
const result = await this.executeBrutalistAnalysis("gitHistory", args.targetPath, systemPrompt, `Git history analysis (range: ${args.commitRange || 'last 20 commits'}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
250
|
-
args.preferredCLI, undefined, // verbose
|
|
251
|
-
args.models);
|
|
252
|
-
return this.formatToolResponse(result);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
return this.formatErrorResponse(error);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
// ROAST_TEST_COVERAGE: Testing infrastructure demolition
|
|
259
|
-
this.server.tool("roast_test_coverage", "Deploy brutal AI critics to systematically destroy your testing strategy. These agents will analyze your actual test files, run coverage reports, and expose every testing gap and quality assurance nightmare that will let bugs slip into production.", {
|
|
260
|
-
targetPath: z.string().describe("Path to test directory or test configuration file"),
|
|
261
|
-
runCoverage: z.boolean().optional().describe("Attempt to run coverage analysis (default: true)"),
|
|
262
|
-
context: z.string().optional().describe("Additional context about the testing strategy"),
|
|
263
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
264
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
265
|
-
models: z.object({
|
|
266
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
267
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
268
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
269
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
270
|
-
}, async (args) => {
|
|
271
|
-
try {
|
|
272
|
-
const systemPrompt = `You are a brutal testing strategy critic. Your job is to systematically destroy the given testing approach by finding every testing gap, quality assurance nightmare, and coverage disaster that will let bugs slip into production. Examine test coverage, test quality, testing patterns, and CI/CD integration. Be ruthlessly honest about how poor testing will cause production failures and user-facing bugs. After dissecting this quality assurance horror show, begrudgingly spell out what it takes to actually catch bugs before users do.`;
|
|
273
|
-
const result = await this.executeBrutalistAnalysis("testCoverage", args.targetPath, systemPrompt, `Test coverage analysis (run coverage: ${args.runCoverage ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
274
|
-
args.preferredCLI, undefined, // verbose
|
|
275
|
-
args.models);
|
|
276
|
-
return this.formatToolResponse(result);
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
return this.formatErrorResponse(error);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
// ROAST_IDEA: Any idea destruction
|
|
283
|
-
this.server.tool("roast_idea", "Deploy brutal AI critics to systematically destroy ANY idea - business, technical, creative, or otherwise. These critics understand the gap between imagination and reality, finding where your concept will encounter the immovable forces of the world. They are harsh about delusions but wise about what might actually survive.", {
|
|
284
|
-
idea: z.string().describe("ANY idea to analyze and demolish - business, technical, creative, or otherwise"),
|
|
285
|
-
context: z.string().optional().describe("Additional context about goals, constraints, or background"),
|
|
286
|
-
timeline: z.string().optional().describe("Expected timeline or deadline"),
|
|
287
|
-
resources: z.string().optional().describe("Available resources (budget, team, time, skills)"),
|
|
288
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
289
|
-
models: z.object({
|
|
290
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
291
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
292
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
293
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
294
|
-
}, async (args) => {
|
|
295
|
-
try {
|
|
296
|
-
const systemPrompt = `You are a brutal idea critic who understands the gap between imagination and reality. Your job is to systematically destroy the given idea by finding where it will encounter the immovable forces of the real world. Be ruthlessly honest about why most ideas fail when they meet practical constraints, human nature, physics, logic, or simple implementation reality. After demolishing the delusions, concede what salvage operations might actually work.`;
|
|
297
|
-
const result = await this.executeBrutalistAnalysis("idea", args.idea, systemPrompt, `Context: ${args.context || 'none'}, Timeline: ${args.timeline || 'unspecified'}, Resources: ${args.resources || 'unknown'}`, undefined, // workingDirectory
|
|
298
|
-
undefined, // enableSandbox
|
|
299
|
-
args.preferredCLI, undefined, // verbose
|
|
300
|
-
args.models);
|
|
301
|
-
return this.formatToolResponse(result);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
return this.formatErrorResponse(error);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
// ROAST_ARCHITECTURE: System design demolition
|
|
308
|
-
this.server.tool("roast_architecture", "Deploy brutal AI critics to systematically destroy your system architecture. These critics have watched elegant designs collapse under real load, identifying every bottleneck, cost explosion, and scaling failure that will destroy your system. They are ruthless about why this won't survive production.", {
|
|
309
|
-
architecture: z.string().describe("Architecture description, diagram, or design document"),
|
|
310
|
-
scale: z.string().optional().describe("Expected scale/load (users, requests, data)"),
|
|
311
|
-
constraints: z.string().optional().describe("Budget, timeline, or technical constraints"),
|
|
312
|
-
deployment: z.string().optional().describe("Deployment environment and strategy"),
|
|
313
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
314
|
-
models: z.object({
|
|
315
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
316
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
317
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
318
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
319
|
-
}, async (args) => {
|
|
320
|
-
try {
|
|
321
|
-
const systemPrompt = `You are a brutal system architecture critic who has watched elegant designs collapse under real load. Your job is to systematically destroy the given architecture by finding every bottleneck, cost explosion, and scaling failure that will destroy the system in production. Examine scalability, reliability, cost, complexity, and operational challenges. Be ruthlessly honest about why this architecture won't survive production load. After crushing these architectural fantasies, reluctantly sketch what would actually scale without bankrupting the company.`;
|
|
322
|
-
const result = await this.executeBrutalistAnalysis("architecture", args.architecture, systemPrompt, `Scale: ${args.scale || 'unknown'}, Constraints: ${args.constraints || 'none specified'}, Deployment: ${args.deployment || 'unclear'}`, undefined, // workingDirectory
|
|
323
|
-
undefined, // enableSandbox
|
|
324
|
-
args.preferredCLI, undefined, // verbose
|
|
325
|
-
args.models);
|
|
326
|
-
return this.formatToolResponse(result);
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
return this.formatErrorResponse(error);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
// ROAST_RESEARCH: Academic project demolition
|
|
333
|
-
this.server.tool("roast_research", "Deploy brutal AI critics to systematically demolish your research methodology. These critics are supremely jaded peer reviewers who have rejected thousands of papers and watched countless studies fail to replicate. They find every statistical flaw, sampling bias, and reproducibility nightmare.", {
|
|
334
|
-
research: z.string().describe("Research description, methodology, or paper draft"),
|
|
335
|
-
field: z.string().optional().describe("Research field (ML, systems, theory, etc.)"),
|
|
336
|
-
claims: z.string().optional().describe("Main claims or contributions"),
|
|
337
|
-
data: z.string().optional().describe("Data sources, datasets, or experimental setup"),
|
|
338
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
339
|
-
models: z.object({
|
|
340
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
341
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
342
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
343
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
344
|
-
}, async (args) => {
|
|
345
|
-
try {
|
|
346
|
-
const systemPrompt = `You are a brutal research methodology critic - a supremely jaded peer reviewer who has rejected thousands of papers and watched countless studies fail to replicate. Your job is to systematically demolish the given research by finding every statistical flaw, sampling bias, reproducibility nightmare, and methodological disaster. Be ruthlessly honest about research quality, experimental design, and scientific rigor. After eviscerating this methodological train wreck, grudgingly admit what real science would demand.`;
|
|
347
|
-
const result = await this.executeBrutalistAnalysis("research", args.research, systemPrompt, `Field: ${args.field || 'unspecified'}, Claims: ${args.claims || 'unclear'}, Data: ${args.data || 'not provided'}`, undefined, // workingDirectory
|
|
348
|
-
undefined, // enableSandbox
|
|
349
|
-
args.preferredCLI, undefined, // verbose
|
|
350
|
-
args.models);
|
|
351
|
-
return this.formatToolResponse(result);
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
return this.formatErrorResponse(error);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
// ROAST_SECURITY: Security-focused attack vector analysis
|
|
358
|
-
this.server.tool("roast_security", "Deploy brutal AI critics to systematically annihilate your security design. These critics are battle-hardened penetration testers who find every authentication bypass, injection vulnerability, privilege escalation path, and social engineering opportunity that real attackers will exploit.", {
|
|
359
|
-
system: z.string().describe("System, application, or security design to analyze"),
|
|
360
|
-
assets: z.string().optional().describe("Critical assets or data to protect"),
|
|
361
|
-
threatModel: z.string().optional().describe("Known threats or attack vectors to consider"),
|
|
362
|
-
compliance: z.string().optional().describe("Compliance requirements (GDPR, HIPAA, etc.)"),
|
|
363
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
364
|
-
models: z.object({
|
|
365
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
366
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
367
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
368
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
369
|
-
}, async (args) => {
|
|
370
|
-
try {
|
|
371
|
-
const systemPrompt = `You are a brutal security critic - a battle-hardened penetration tester who finds every authentication bypass, injection vulnerability, privilege escalation path, and social engineering opportunity that real attackers will exploit. Your job is to systematically annihilate the given security design by finding every weakness that will lead to data breaches, system compromises, and security incidents. Be ruthlessly honest about security flaws and attack vectors. After obliterating these security delusions, begrudgingly outline what actual defense looks like.`;
|
|
372
|
-
const result = await this.executeBrutalistAnalysis("security", args.system, systemPrompt, `Assets: ${args.assets || 'unspecified'}, Threats: ${args.threatModel || 'unknown'}, Compliance: ${args.compliance || 'none specified'}`, undefined, // workingDirectory
|
|
373
|
-
undefined, // enableSandbox
|
|
374
|
-
args.preferredCLI, undefined, // verbose
|
|
375
|
-
args.models);
|
|
376
|
-
return this.formatToolResponse(result);
|
|
377
|
-
}
|
|
378
|
-
catch (error) {
|
|
379
|
-
return this.formatErrorResponse(error);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
// ROAST_PRODUCT: UX and market reality criticism
|
|
383
|
-
this.server.tool("roast_product", "Deploy brutal AI critics to systematically eviscerate your product concept. These critics are product veterans who understand why users really abandon things, finding every usability disaster, adoption barrier, and workflow failure that will drive users away in seconds.", {
|
|
384
|
-
product: z.string().describe("Product description, features, or user experience to analyze"),
|
|
385
|
-
users: z.string().optional().describe("Target users or user personas"),
|
|
386
|
-
competition: z.string().optional().describe("Competitive landscape or alternatives"),
|
|
387
|
-
metrics: z.string().optional().describe("Success metrics or KPIs"),
|
|
388
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
389
|
-
models: z.object({
|
|
390
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
391
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
392
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
393
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
394
|
-
}, async (args) => {
|
|
395
|
-
try {
|
|
396
|
-
const systemPrompt = `You are a brutal product critic - a product veteran who understands why users really abandon things. Your job is to systematically eviscerate the given product concept by finding every usability disaster, adoption barrier, and workflow failure that will drive users away in seconds. Examine user experience, market fit, competitive positioning, and business model viability. Be ruthlessly honest about why most products fail to gain adoption. After torching this product disaster, reluctantly suggest what might actually get users to stick around.`;
|
|
397
|
-
const result = await this.executeBrutalistAnalysis("product", args.product, systemPrompt, `Users: ${args.users || 'unclear'}, Competition: ${args.competition || 'unknown'}, Metrics: ${args.metrics || 'undefined'}`, undefined, // workingDirectory
|
|
398
|
-
undefined, // enableSandbox
|
|
399
|
-
args.preferredCLI, undefined, // verbose
|
|
400
|
-
args.models);
|
|
401
|
-
return this.formatToolResponse(result);
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
return this.formatErrorResponse(error);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
// ROAST_INFRASTRUCTURE: DevOps and operations demolition
|
|
408
|
-
this.server.tool("roast_infrastructure", "Deploy brutal AI critics to systematically obliterate your infrastructure design. These critics are grizzled site reliability engineers who find every single point of failure, scaling bottleneck, and operational nightmare that will cause outages when you least expect them.", {
|
|
409
|
-
infrastructure: z.string().describe("Infrastructure setup, deployment strategy, or operations plan"),
|
|
410
|
-
scale: z.string().optional().describe("Expected scale and load patterns"),
|
|
411
|
-
budget: z.string().optional().describe("Infrastructure budget or cost constraints"),
|
|
412
|
-
sla: z.string().optional().describe("SLA requirements or uptime targets"),
|
|
413
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
414
|
-
models: z.object({
|
|
415
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
416
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
417
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
418
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
419
|
-
}, async (args) => {
|
|
420
|
-
try {
|
|
421
|
-
const systemPrompt = `You are a brutal infrastructure critic - a grizzled site reliability engineer who finds every single point of failure, scaling bottleneck, and operational nightmare that will cause outages when you least expect them. Your job is to systematically obliterate the given infrastructure design by finding every weakness that will lead to downtime, cost overruns, and operational disasters. Be ruthlessly honest about infrastructure fragility and operational complexity. After demolishing this infrastructure fever dream, grudgingly map out what actually stays up at 3 AM.`;
|
|
422
|
-
const result = await this.executeBrutalistAnalysis("infrastructure", args.infrastructure, systemPrompt, `Scale: ${args.scale || 'unknown'}, Budget: ${args.budget || 'unlimited?'}, SLA: ${args.sla || 'undefined'}`, undefined, // workingDirectory
|
|
423
|
-
undefined, // enableSandbox
|
|
424
|
-
args.preferredCLI, undefined, // verbose
|
|
425
|
-
args.models);
|
|
426
|
-
return this.formatToolResponse(result);
|
|
427
|
-
}
|
|
428
|
-
catch (error) {
|
|
429
|
-
return this.formatErrorResponse(error);
|
|
430
|
-
}
|
|
282
|
+
// Register all roast tools using unified handler - DRY principle
|
|
283
|
+
TOOL_CONFIGS.forEach(config => {
|
|
284
|
+
const schema = {
|
|
285
|
+
...config.schemaExtensions,
|
|
286
|
+
...BASE_ROAST_SCHEMA
|
|
287
|
+
};
|
|
288
|
+
this.server.tool(config.name, config.description, schema, async (args, extra) => this.handleRoastTool(config, args, extra));
|
|
431
289
|
});
|
|
290
|
+
// Register special tools that don't follow the pattern
|
|
291
|
+
this.registerSpecialTools();
|
|
292
|
+
}
|
|
293
|
+
registerSpecialTools() {
|
|
432
294
|
// ROAST_CLI_DEBATE: Adversarial analysis between different CLI agents
|
|
433
295
|
this.server.tool("roast_cli_debate", "Deploy CLI agents in structured adversarial debate. Agents take opposing positions and systematically challenge each other's reasoning. Perfect for exploring complex topics from multiple perspectives and stress-testing ideas through rigorous intellectual discourse.", {
|
|
434
296
|
targetPath: z.string().describe("Topic, question, or concept to debate (NOT a file path - use natural language)"),
|
|
435
297
|
debateRounds: z.number().optional().describe("Number of debate rounds (default: 2, max: 10)"),
|
|
436
298
|
context: z.string().optional().describe("Additional context for the debate"),
|
|
437
299
|
workingDirectory: z.string().optional().describe("Working directory for analysis"),
|
|
438
|
-
enableSandbox: z.boolean().optional().describe("Enable sandbox mode for security"),
|
|
439
300
|
models: z.object({
|
|
440
301
|
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
441
302
|
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
@@ -444,7 +305,7 @@ export class BrutalistServer {
|
|
|
444
305
|
}, async (args) => {
|
|
445
306
|
return this.handleToolExecution(async () => {
|
|
446
307
|
const debateRounds = Math.min(args.debateRounds || 2, 10); // Limit to max 10 rounds to prevent DoS
|
|
447
|
-
const responses = await this.executeCLIDebate(args.targetPath, debateRounds, args.context, args.workingDirectory, args.
|
|
308
|
+
const responses = await this.executeCLIDebate(args.targetPath, debateRounds, args.context, args.workingDirectory, args.models);
|
|
448
309
|
return responses;
|
|
449
310
|
});
|
|
450
311
|
});
|
|
@@ -471,7 +332,7 @@ export class BrutalistServer {
|
|
|
471
332
|
roster += "- `cli_agent_roster` - This tool (show capabilities)\n\n";
|
|
472
333
|
roster += "## CLI Agent Capabilities\n";
|
|
473
334
|
roster += "**Claude Code** - Advanced analysis with direct system prompt injection\n";
|
|
474
|
-
roster += "**Codex** -
|
|
335
|
+
roster += "**Codex** - Secure execution with embedded brutal prompts\n";
|
|
475
336
|
roster += "**Gemini CLI** - Workspace context with environment variable system prompts\n\n";
|
|
476
337
|
// Add CLI context information
|
|
477
338
|
const cliContext = await this.cliOrchestrator.detectCLIContext();
|
|
@@ -479,6 +340,12 @@ export class BrutalistServer {
|
|
|
479
340
|
roster += `**Available CLIs:** ${cliContext.availableCLIs.join(', ') || 'None detected'}\n`;
|
|
480
341
|
roster += `**Current CLI:** ${cliContext.currentCLI || 'Unknown'}\n`;
|
|
481
342
|
roster += `**Smart Routing:** ${cliContext.currentCLI ? `Excludes ${cliContext.currentCLI} for analysis` : 'Uses all available CLIs'}\n\n`;
|
|
343
|
+
roster += "## Pagination Support (NEW in v0.5.2)\n";
|
|
344
|
+
roster += "**All tools now support intelligent pagination:**\n";
|
|
345
|
+
roster += "- Analysis results are cached with 2-hour TTL\n";
|
|
346
|
+
roster += "- Use `analysis_id` from response to paginate without re-running\n";
|
|
347
|
+
roster += "- Smart text chunking preserves readability\n";
|
|
348
|
+
roster += "- Example: `roast_codebase(analysis_id: 'a3f5c2d8', offset: 25000)`\n\n";
|
|
482
349
|
roster += "## Brutalist Philosophy\n";
|
|
483
350
|
roster += "*All tools use CLI agents with brutal system prompts for maximum reality-based criticism.*\n";
|
|
484
351
|
return {
|
|
@@ -490,12 +357,132 @@ export class BrutalistServer {
|
|
|
490
357
|
}
|
|
491
358
|
});
|
|
492
359
|
}
|
|
493
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Unified handler for all roast tools - DRY principle
|
|
362
|
+
*/
|
|
363
|
+
async handleRoastTool(config, args, extra) {
|
|
364
|
+
try {
|
|
365
|
+
const progressToken = extra._meta?.progressToken;
|
|
366
|
+
// Extract session context for security
|
|
367
|
+
const sessionId = extra?.sessionId ||
|
|
368
|
+
extra?._meta?.sessionId ||
|
|
369
|
+
extra?.headers?.['mcp-session-id'] ||
|
|
370
|
+
`anonymous-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
371
|
+
const requestId = `${sessionId}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
372
|
+
logger.debug(`🔐 Processing request with session: ${sessionId.substring(0, 8)}..., request: ${requestId.substring(0, 12)}...`);
|
|
373
|
+
// Track session activity
|
|
374
|
+
if (!this.activeSessions.has(sessionId)) {
|
|
375
|
+
this.activeSessions.set(sessionId, {
|
|
376
|
+
startTime: Date.now(),
|
|
377
|
+
requestCount: 0,
|
|
378
|
+
lastActivity: Date.now()
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
const sessionInfo = this.activeSessions.get(sessionId);
|
|
382
|
+
sessionInfo.requestCount++;
|
|
383
|
+
sessionInfo.lastActivity = Date.now();
|
|
384
|
+
// Debug logging: Log the received arguments to file
|
|
385
|
+
const fs = require('fs');
|
|
386
|
+
const debugLog = `/tmp/brutalist-tool-debug-${Date.now()}.log`;
|
|
387
|
+
const logMessage = (msg) => {
|
|
388
|
+
try {
|
|
389
|
+
fs.appendFileSync(debugLog, `${new Date().toISOString()}: ${msg}\n`);
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
// Ignore filesystem errors
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
logMessage(`🔧 ROAST TOOL DEBUG: Tool=${config.name}, primaryArgField=${config.primaryArgField}`);
|
|
396
|
+
logMessage(`🔧 ROAST TOOL DEBUG: args=${JSON.stringify(args, null, 2)}`);
|
|
397
|
+
logMessage(`🔧 ROAST TOOL DEBUG: extra=${JSON.stringify(extra, null, 2)}`);
|
|
398
|
+
// Extract pagination parameters
|
|
399
|
+
const paginationParams = extractPaginationParams(args);
|
|
400
|
+
if (args.cursor) {
|
|
401
|
+
const cursorParams = parseCursor(args.cursor);
|
|
402
|
+
Object.assign(paginationParams, cursorParams);
|
|
403
|
+
}
|
|
404
|
+
// Check cache if analysis_id provided
|
|
405
|
+
if (args.analysis_id && !args.force_refresh) {
|
|
406
|
+
const cachedContent = await this.responseCache.get(args.analysis_id, sessionId);
|
|
407
|
+
if (cachedContent) {
|
|
408
|
+
logger.info(`🎯 Session-validated cache hit for analysis_id: ${args.analysis_id}`);
|
|
409
|
+
const cachedResult = {
|
|
410
|
+
success: true,
|
|
411
|
+
responses: [{
|
|
412
|
+
agent: 'cached',
|
|
413
|
+
success: true,
|
|
414
|
+
output: cachedContent,
|
|
415
|
+
executionTime: 0
|
|
416
|
+
}]
|
|
417
|
+
};
|
|
418
|
+
return this.formatToolResponse(cachedResult, args.verbose, paginationParams, args.analysis_id);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
logger.info(`🔍 No valid cache entry for analysis_id: ${args.analysis_id} and session: ${sessionId?.substring(0, 8)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Generate cache key for this request
|
|
425
|
+
const cacheKey = this.responseCache.generateCacheKey(config.cacheKeyFields.reduce((acc, field) => {
|
|
426
|
+
acc.tool = config.name;
|
|
427
|
+
if (args[field] !== undefined)
|
|
428
|
+
acc[field] = args[field];
|
|
429
|
+
return acc;
|
|
430
|
+
}, {}));
|
|
431
|
+
// Check if we have a cached result (unless forcing refresh)
|
|
432
|
+
if (!args.force_refresh) {
|
|
433
|
+
const cachedContent = await this.responseCache.get(cacheKey, sessionId);
|
|
434
|
+
if (cachedContent) {
|
|
435
|
+
const analysisId = this.responseCache.generateAnalysisId(cacheKey);
|
|
436
|
+
logger.info(`🎯 Cache hit for new request, using analysis_id: ${analysisId}`);
|
|
437
|
+
const cachedResult = {
|
|
438
|
+
success: true,
|
|
439
|
+
responses: [{
|
|
440
|
+
agent: 'cached',
|
|
441
|
+
success: true,
|
|
442
|
+
output: cachedContent,
|
|
443
|
+
executionTime: 0
|
|
444
|
+
}]
|
|
445
|
+
};
|
|
446
|
+
return this.formatToolResponse(cachedResult, args.verbose, paginationParams, analysisId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Build context with custom builder if available
|
|
450
|
+
const context = config.contextBuilder ? config.contextBuilder(args) : args.context;
|
|
451
|
+
// Get the primary argument (targetPath, idea, architecture, etc.)
|
|
452
|
+
const primaryArg = args[config.primaryArgField];
|
|
453
|
+
logMessage(`🔧 PRIMARY ARG DEBUG: primaryArgField=${config.primaryArgField}, primaryArg="${primaryArg}"`);
|
|
454
|
+
logMessage(`🔧 PRIMARY ARG DEBUG: config.analysisType="${config.analysisType}"`);
|
|
455
|
+
// Run the analysis
|
|
456
|
+
const result = await this.executeBrutalistAnalysis(config.analysisType, primaryArg, config.systemPrompt, context, args.workingDirectory, args.preferredCLI, args.verbose, args.models, progressToken, sessionId, requestId);
|
|
457
|
+
// Cache the result if successful
|
|
458
|
+
let analysisId;
|
|
459
|
+
if (result.success && result.responses.length > 0) {
|
|
460
|
+
const fullContent = this.extractFullContent(result);
|
|
461
|
+
if (fullContent) {
|
|
462
|
+
const cacheData = config.cacheKeyFields.reduce((acc, field) => {
|
|
463
|
+
acc.tool = config.name;
|
|
464
|
+
if (args[field] !== undefined)
|
|
465
|
+
acc[field] = args[field];
|
|
466
|
+
return acc;
|
|
467
|
+
}, {});
|
|
468
|
+
const { analysisId: newId } = await this.responseCache.set(cacheData, fullContent, cacheKey, sessionId, // NEW: Bind to session
|
|
469
|
+
requestId // NEW: Track request
|
|
470
|
+
);
|
|
471
|
+
analysisId = newId;
|
|
472
|
+
logger.info(`✅ Cached analysis result with ID: ${analysisId} for session: ${sessionId?.substring(0, 8)}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return this.formatToolResponse(result, args.verbose, paginationParams, analysisId);
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
return this.formatErrorResponse(error);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async executeCLIDebate(targetPath, debateRounds, context, workingDirectory, models) {
|
|
494
482
|
logger.debug("Executing CLI debate", {
|
|
495
483
|
targetPath,
|
|
496
484
|
debateRounds,
|
|
497
485
|
workingDirectory,
|
|
498
|
-
enableSandbox
|
|
499
486
|
});
|
|
500
487
|
try {
|
|
501
488
|
// Get CLI context
|
|
@@ -539,13 +526,14 @@ Remember: You are ${agent.toUpperCase()}, the passionate champion of ${position.
|
|
|
539
526
|
logger.info(`🎭 ${agent.toUpperCase()} preparing initial position: ${position.split(':')[0]}`);
|
|
540
527
|
const response = await this.cliOrchestrator.executeSingleCLI(agent, assignedPrompt, assignedPrompt, {
|
|
541
528
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
542
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
543
529
|
timeout: (this.config.defaultTimeout || 60000) * 2,
|
|
544
530
|
models: models ? { [agent]: models[agent] } : undefined
|
|
545
531
|
});
|
|
546
532
|
if (response.success) {
|
|
547
533
|
debateContext.push(response);
|
|
548
|
-
|
|
534
|
+
if (response.output) {
|
|
535
|
+
fullDebateTranscript.get(agent)?.push(response.output);
|
|
536
|
+
}
|
|
549
537
|
}
|
|
550
538
|
}
|
|
551
539
|
// Subsequent rounds: Turn-based responses attacking specific arguments
|
|
@@ -587,13 +575,14 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
587
575
|
logger.info(`🔥 Round ${round}: ${currentAgent.toUpperCase()} responding to opponents (${assignedPosition.split(':')[0]})`);
|
|
588
576
|
const response = await this.cliOrchestrator.executeSingleCLI(currentAgent, confrontationalPrompt, confrontationalPrompt, {
|
|
589
577
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
590
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
591
578
|
timeout: (this.config.defaultTimeout || 60000) * 2,
|
|
592
579
|
models: models ? { [currentAgent]: models[currentAgent] } : undefined
|
|
593
580
|
});
|
|
594
581
|
if (response.success) {
|
|
595
582
|
debateContext.push(response);
|
|
596
|
-
|
|
583
|
+
if (response.output) {
|
|
584
|
+
fullDebateTranscript.get(currentAgent)?.push(response.output);
|
|
585
|
+
}
|
|
597
586
|
}
|
|
598
587
|
}
|
|
599
588
|
}
|
|
@@ -636,7 +625,9 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
636
625
|
if (!agentOutputs.has(response.agent)) {
|
|
637
626
|
agentOutputs.set(response.agent, []);
|
|
638
627
|
}
|
|
639
|
-
|
|
628
|
+
if (response.output) {
|
|
629
|
+
agentOutputs.get(response.agent)?.push(response.output);
|
|
630
|
+
}
|
|
640
631
|
});
|
|
641
632
|
synthesis += `## Key Points of Conflict\n\n`;
|
|
642
633
|
// Extract disagreements by looking for contradictory keywords
|
|
@@ -683,15 +674,14 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
683
674
|
}
|
|
684
675
|
return synthesis;
|
|
685
676
|
}
|
|
686
|
-
async executeBrutalistAnalysis(analysisType,
|
|
677
|
+
async executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, workingDirectory, preferredCLI, verbose, models, progressToken, sessionId, requestId) {
|
|
687
678
|
logger.info(`🏢 Starting brutalist analysis: ${analysisType}`);
|
|
688
|
-
logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI},
|
|
679
|
+
logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI}, primaryContent=${primaryContent}`);
|
|
689
680
|
logger.debug("Executing brutalist analysis", {
|
|
690
|
-
|
|
681
|
+
primaryContent,
|
|
691
682
|
analysisType,
|
|
692
683
|
systemPromptSpec,
|
|
693
684
|
workingDirectory,
|
|
694
|
-
enableSandbox,
|
|
695
685
|
preferredCLI
|
|
696
686
|
});
|
|
697
687
|
try {
|
|
@@ -702,16 +692,18 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
702
692
|
// Execute CLI agent analysis (single or multi-CLI based on preferences)
|
|
703
693
|
logger.info(`🔍 Executing brutalist analysis with timeout: ${this.config.defaultTimeout}ms`);
|
|
704
694
|
logger.info(`🔧 DEBUG: About to call cliOrchestrator.executeBrutalistAnalysis`);
|
|
705
|
-
const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType,
|
|
695
|
+
const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, {
|
|
706
696
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
707
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
708
697
|
timeout: this.config.defaultTimeout,
|
|
709
698
|
preferredCLI,
|
|
710
699
|
analysisType: analysisType,
|
|
711
700
|
models,
|
|
712
701
|
onStreamingEvent: this.handleStreamingEvent,
|
|
713
702
|
progressToken,
|
|
714
|
-
onProgress: progressToken
|
|
703
|
+
onProgress: progressToken && sessionId ?
|
|
704
|
+
(progress, total, message) => this.handleProgressUpdate(progressToken, progress, total, message, sessionId) : undefined,
|
|
705
|
+
sessionId,
|
|
706
|
+
requestId
|
|
715
707
|
});
|
|
716
708
|
logger.info(`🔧 DEBUG: cliOrchestrator.executeBrutalistAnalysis returned ${responses.length} responses`);
|
|
717
709
|
const successfulResponses = responses.filter(r => r.success);
|
|
@@ -725,7 +717,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
725
717
|
responses,
|
|
726
718
|
synthesis,
|
|
727
719
|
analysisType,
|
|
728
|
-
targetPath,
|
|
720
|
+
targetPath: primaryContent,
|
|
729
721
|
executionSummary: {
|
|
730
722
|
totalCLIs: responses.length,
|
|
731
723
|
successfulCLIs: successfulResponses.length,
|
|
@@ -743,7 +735,32 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
743
735
|
throw error;
|
|
744
736
|
}
|
|
745
737
|
}
|
|
746
|
-
|
|
738
|
+
/**
|
|
739
|
+
* Extract full content from analysis result for caching
|
|
740
|
+
*/
|
|
741
|
+
extractFullContent(result) {
|
|
742
|
+
if (result.synthesis) {
|
|
743
|
+
return result.synthesis;
|
|
744
|
+
}
|
|
745
|
+
else if (result.responses && result.responses.length > 0) {
|
|
746
|
+
const successfulResponses = result.responses.filter(r => r.success);
|
|
747
|
+
if (successfulResponses.length > 0) {
|
|
748
|
+
let output = `${successfulResponses.length} AI critics have systematically demolished your work.\n\n`;
|
|
749
|
+
successfulResponses.forEach((response, index) => {
|
|
750
|
+
output += `## Critic ${index + 1}: ${response.agent.toUpperCase()}\n`;
|
|
751
|
+
output += `*Execution time: ${response.executionTime}ms*\n\n`;
|
|
752
|
+
output += response.output;
|
|
753
|
+
// Only add separator between critics, not after the last one
|
|
754
|
+
if (index < successfulResponses.length - 1) {
|
|
755
|
+
output += '\n\n---\n\n';
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
return output;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
formatToolResponse(result, verbose = false, paginationParams, analysisId) {
|
|
747
764
|
logger.info(`🔧 DEBUG: formatToolResponse called with synthesis length: ${result.synthesis?.length || 0}`);
|
|
748
765
|
logger.info(`🔧 DEBUG: result.success=${result.success}, responses.length=${result.responses?.length || 0}`);
|
|
749
766
|
logger.info(`🔧 DEBUG: pagination params:`, paginationParams);
|
|
@@ -762,7 +779,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
762
779
|
}
|
|
763
780
|
// Handle pagination if params provided and content is substantial
|
|
764
781
|
if (paginationParams && primaryContent) {
|
|
765
|
-
return this.formatPaginatedResponse(primaryContent, paginationParams, result, verbose);
|
|
782
|
+
return this.formatPaginatedResponse(primaryContent, paginationParams, result, verbose, analysisId);
|
|
766
783
|
}
|
|
767
784
|
// Non-paginated response (legacy behavior)
|
|
768
785
|
if (primaryContent) {
|
|
@@ -795,41 +812,72 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
795
812
|
}]
|
|
796
813
|
};
|
|
797
814
|
}
|
|
798
|
-
formatPaginatedResponse(content, paginationParams, result, verbose) {
|
|
815
|
+
formatPaginatedResponse(content, paginationParams, result, verbose, analysisId) {
|
|
799
816
|
// Using imported pagination utilities
|
|
800
817
|
const offset = paginationParams.offset || 0;
|
|
801
818
|
const limit = paginationParams.limit || PAGINATION_DEFAULTS.DEFAULT_LIMIT;
|
|
802
819
|
logger.info(`🔧 DEBUG: Paginating content - offset: ${offset}, limit: ${limit}, total: ${content.length}`);
|
|
803
|
-
//
|
|
804
|
-
const
|
|
805
|
-
const
|
|
820
|
+
// Use ResponseChunker for intelligent boundary detection
|
|
821
|
+
const chunker = new ResponseChunker(limit, 200); // 200 char overlap
|
|
822
|
+
const chunks = chunker.chunkText(content);
|
|
823
|
+
// Find the appropriate chunk based on offset
|
|
824
|
+
let targetChunk = chunks[0]; // Default to first chunk
|
|
825
|
+
let currentOffset = 0;
|
|
826
|
+
for (const chunk of chunks) {
|
|
827
|
+
if (offset >= chunk.startOffset && offset < chunk.endOffset) {
|
|
828
|
+
targetChunk = chunk;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
currentOffset = chunk.endOffset;
|
|
832
|
+
}
|
|
833
|
+
const chunkContent = targetChunk.content;
|
|
834
|
+
const actualOffset = targetChunk.startOffset;
|
|
835
|
+
const endOffset = targetChunk.endOffset;
|
|
806
836
|
// Create pagination metadata
|
|
807
837
|
const pagination = createPaginationMetadata(content.length, paginationParams, limit);
|
|
808
838
|
const statusLine = formatPaginationStatus(pagination);
|
|
809
839
|
// Estimate token usage for user awareness
|
|
810
|
-
const chunkTokens = estimateTokenCount(
|
|
840
|
+
const chunkTokens = estimateTokenCount(chunkContent);
|
|
811
841
|
const totalTokens = estimateTokenCount(content);
|
|
812
842
|
// Format response with pagination info
|
|
813
843
|
let paginatedText = '';
|
|
814
|
-
// Add
|
|
844
|
+
// Add header
|
|
815
845
|
paginatedText += `# Brutalist Analysis Results\n\n`;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
if (
|
|
819
|
-
paginatedText +=
|
|
846
|
+
// Only show pagination metadata if pagination is actually needed
|
|
847
|
+
const needsPagination = pagination.totalChunks > 1 || pagination.hasMore;
|
|
848
|
+
if (needsPagination) {
|
|
849
|
+
paginatedText += `**📊 Pagination Status:** ${statusLine}\n`;
|
|
850
|
+
if (analysisId) {
|
|
851
|
+
paginatedText += `**🔑 Analysis ID:** ${analysisId}\n`;
|
|
852
|
+
}
|
|
853
|
+
paginatedText += `**🔢 Token Estimate:** ~${chunkTokens.toLocaleString()} tokens (chunk) / ~${totalTokens.toLocaleString()} tokens (total)\n\n`;
|
|
854
|
+
if (pagination.hasMore) {
|
|
855
|
+
if (analysisId) {
|
|
856
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`analysis_id: "${analysisId}", offset: ${endOffset}\`\n\n`;
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`offset: ${endOffset}\` for next chunk\n\n`;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
820
862
|
}
|
|
821
863
|
paginatedText += `---\n\n`;
|
|
822
864
|
// Add the actual content chunk
|
|
823
|
-
paginatedText +=
|
|
824
|
-
// Add footer
|
|
825
|
-
if (
|
|
826
|
-
paginatedText += `\n\n---\n\n`;
|
|
827
|
-
paginatedText += `📖 **End of chunk ${pagination.chunkIndex}/${pagination.totalChunks}**\n`;
|
|
828
|
-
paginatedText += `🔄 To continue: Use same tool with \`offset: ${endOffset}\``;
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
865
|
+
paginatedText += chunkContent;
|
|
866
|
+
// Add footer
|
|
867
|
+
if (needsPagination) {
|
|
831
868
|
paginatedText += `\n\n---\n\n`;
|
|
832
|
-
|
|
869
|
+
if (pagination.hasMore) {
|
|
870
|
+
paginatedText += `📖 **End of chunk ${pagination.chunkIndex}/${pagination.totalChunks}**\n`;
|
|
871
|
+
if (analysisId) {
|
|
872
|
+
paginatedText += `🔄 To continue: Include \`analysis_id: "${analysisId}"\` with \`offset: ${endOffset}\` in next request`;
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
paginatedText += `🔄 To continue: Use same tool with \`offset: ${endOffset}\``;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
paginatedText += `✅ **Complete analysis shown** (${content.length.toLocaleString()} characters total)`;
|
|
880
|
+
}
|
|
833
881
|
}
|
|
834
882
|
// Add verbose execution details if requested
|
|
835
883
|
if (verbose && result.executionSummary) {
|
|
@@ -840,7 +888,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
840
888
|
paginatedText += `- **Selected CLI:** ${result.executionSummary.selectedCLI}\n`;
|
|
841
889
|
}
|
|
842
890
|
}
|
|
843
|
-
logger.info(`🔧 DEBUG: Returning paginated chunk - ${
|
|
891
|
+
logger.info(`🔧 DEBUG: Returning paginated chunk - ${chunkContent.length} chars (${chunkTokens} tokens)`);
|
|
844
892
|
return {
|
|
845
893
|
content: [{
|
|
846
894
|
type: "text",
|
|
@@ -858,7 +906,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
858
906
|
sanitizedMessage = "Analysis timed out - try reducing scope or increasing timeout";
|
|
859
907
|
}
|
|
860
908
|
else if (error.message.includes('ENOENT') || error.message.includes('no such file')) {
|
|
861
|
-
sanitizedMessage =
|
|
909
|
+
sanitizedMessage = `DEBUG: Target path not found - Original error: ${error.message}`;
|
|
862
910
|
}
|
|
863
911
|
else if (error.message.includes('EACCES') || error.message.includes('permission denied')) {
|
|
864
912
|
sanitizedMessage = "Permission denied - check file access";
|