bobs-workshop 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/bin/bobs-mcp.js +130 -0
  4. package/dist/api/taskLogger.js +106 -0
  5. package/dist/api/taskLogger.js.map +1 -0
  6. package/dist/cli/checker.js +401 -0
  7. package/dist/cli/checker.js.map +1 -0
  8. package/dist/cli/cleanup.js +131 -0
  9. package/dist/cli/cleanup.js.map +1 -0
  10. package/dist/cli/debug.js +157 -0
  11. package/dist/cli/debug.js.map +1 -0
  12. package/dist/cli/health.js +97 -0
  13. package/dist/cli/health.js.map +1 -0
  14. package/dist/cli/setup.js +81 -0
  15. package/dist/cli/setup.js.map +1 -0
  16. package/dist/cli/workshop.js +42 -0
  17. package/dist/cli/workshop.js.map +1 -0
  18. package/dist/dashboard/server.js +1206 -0
  19. package/dist/dashboard/server.js.map +1 -0
  20. package/dist/index.js +757 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/prompts/architect.js +157 -0
  23. package/dist/prompts/architect.js.map +1 -0
  24. package/dist/prompts/debugger.js +201 -0
  25. package/dist/prompts/debugger.js.map +1 -0
  26. package/dist/prompts/engineer.js +171 -0
  27. package/dist/prompts/engineer.js.map +1 -0
  28. package/dist/prompts/orchestrator.js +225 -0
  29. package/dist/prompts/orchestrator.js.map +1 -0
  30. package/dist/prompts/reviewer.js +199 -0
  31. package/dist/prompts/reviewer.js.map +1 -0
  32. package/dist/services/activitySummarizer.js +353 -0
  33. package/dist/services/activitySummarizer.js.map +1 -0
  34. package/dist/services/changeValidator.js +396 -0
  35. package/dist/services/changeValidator.js.map +1 -0
  36. package/dist/services/claudeOrchestrator.js +343 -0
  37. package/dist/services/claudeOrchestrator.js.map +1 -0
  38. package/dist/services/fileMonitor.js +250 -0
  39. package/dist/services/fileMonitor.js.map +1 -0
  40. package/dist/services/implementationSummarizer.js +306 -0
  41. package/dist/services/implementationSummarizer.js.map +1 -0
  42. package/dist/services/liveMonitor.js +315 -0
  43. package/dist/services/liveMonitor.js.map +1 -0
  44. package/dist/services/mcpAuditLogger.js +104 -0
  45. package/dist/services/mcpAuditLogger.js.map +1 -0
  46. package/dist/services/mcpLogger.js +223 -0
  47. package/dist/services/mcpLogger.js.map +1 -0
  48. package/dist/services/tmuxManager.js +541 -0
  49. package/dist/services/tmuxManager.js.map +1 -0
  50. package/dist/tools/approvalTools.js +244 -0
  51. package/dist/tools/approvalTools.js.map +1 -0
  52. package/dist/tools/autoDebugger.js +147 -0
  53. package/dist/tools/autoDebugger.js.map +1 -0
  54. package/dist/tools/cleanupService.js +221 -0
  55. package/dist/tools/cleanupService.js.map +1 -0
  56. package/dist/tools/dashboardTools.js +359 -0
  57. package/dist/tools/dashboardTools.js.map +1 -0
  58. package/dist/tools/developmentNudges.js +336 -0
  59. package/dist/tools/developmentNudges.js.map +1 -0
  60. package/dist/tools/gitTools.js +741 -0
  61. package/dist/tools/gitTools.js.map +1 -0
  62. package/dist/tools/orchestratorTools.js +765 -0
  63. package/dist/tools/orchestratorTools.js.map +1 -0
  64. package/dist/tools/searchTools.js +788 -0
  65. package/dist/tools/searchTools.js.map +1 -0
  66. package/dist/tools/specTools.js +350 -0
  67. package/dist/tools/specTools.js.map +1 -0
  68. package/dist/tools/tmuxTools.js +100 -0
  69. package/dist/tools/tmuxTools.js.map +1 -0
  70. package/dist/tools/workRecorder.js +215 -0
  71. package/dist/tools/workRecorder.js.map +1 -0
  72. package/dist/tools/worktreeTools.js +705 -0
  73. package/dist/tools/worktreeTools.js.map +1 -0
  74. package/dist/utils/__tests__/integration.test.js +57 -0
  75. package/dist/utils/__tests__/integration.test.js.map +1 -0
  76. package/dist/utils/__tests__/serverDetection.test.js +151 -0
  77. package/dist/utils/__tests__/serverDetection.test.js.map +1 -0
  78. package/dist/utils/errorHandling.js +336 -0
  79. package/dist/utils/errorHandling.js.map +1 -0
  80. package/dist/utils/processManager.js +172 -0
  81. package/dist/utils/processManager.js.map +1 -0
  82. package/dist/utils/reliability.js +263 -0
  83. package/dist/utils/reliability.js.map +1 -0
  84. package/dist/utils/responseFormatter.js +250 -0
  85. package/dist/utils/responseFormatter.js.map +1 -0
  86. package/dist/utils/serverDetection.js +133 -0
  87. package/dist/utils/serverDetection.js.map +1 -0
  88. package/dist/utils/specMigration.js +105 -0
  89. package/dist/utils/specMigration.js.map +1 -0
  90. package/dist/validation/schemas.js +299 -0
  91. package/dist/validation/schemas.js.map +1 -0
  92. package/package.json +79 -0
  93. package/scripts/init-workspace.js +63 -0
  94. package/scripts/install-search-tools.js +116 -0
package/dist/index.js ADDED
@@ -0,0 +1,757 @@
1
+ // src/index.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ // Import all tool handlers
6
+ import { specCreateHandler, specUpdateHandler, specGetHandler, specListHandler } from "./tools/specTools.js";
7
+ import { worktreeCreateHandler } from "./tools/worktreeTools.js";
8
+ import { stopDevServer } from "./utils/processManager.js";
9
+ import { hybridSearchHandler } from "./tools/searchTools.js";
10
+ import { dashboardLaunchHandler, dashboardUpdateHandler } from "./tools/dashboardTools.js";
11
+ import { workshopHandler } from "./tools/orchestratorTools.js";
12
+ // REMOVED: Approval tools imports - use structured outputs instead
13
+ import { getGitStatus, autoCommitChanges, autoMergeAndCleanup, findWorktreeForSpec } from "./tools/gitTools.js";
14
+ import { autoDebugError, shouldInvokeDebugger } from "./tools/autoDebugger.js";
15
+ import { cleanupService } from "./tools/cleanupService.js";
16
+ import { changeValidator } from "./services/changeValidator.js";
17
+ import { implementationSummarizer } from "./services/implementationSummarizer.js";
18
+ import { mcpLogger } from "./services/mcpLogger.js";
19
+ import { formatConciseResponse } from "./utils/responseFormatter.js";
20
+ import { withTimeout, createCircuitBreaker, globalHealthChecker } from "./utils/reliability.js";
21
+ import { withErrorHandling, globalErrorCollector } from "./utils/errorHandling.js";
22
+ // Import all prompts
23
+ import { ARCHITECT_PROMPT } from "./prompts/architect.js";
24
+ import { ENGINEER_PROMPT } from "./prompts/engineer.js";
25
+ import { DEBUGGER_PROMPT } from "./prompts/debugger.js";
26
+ import { REVIEWER_PROMPT } from "./prompts/reviewer.js";
27
+ // Global circuit breakers for external operations
28
+ const fileSystemCircuitBreaker = createCircuitBreaker({
29
+ threshold: 5,
30
+ timeout: 15000,
31
+ resetTimeout: 30000
32
+ });
33
+ // Helper function to determine appropriate timeout for different tools
34
+ function getToolTimeout(toolName) {
35
+ // Longer timeouts for complex operations
36
+ if (toolName.includes('search') || toolName.includes('hybrid')) {
37
+ return 45000; // 45 seconds for search operations
38
+ }
39
+ if (toolName.includes('workflow') || toolName.includes('create')) {
40
+ return 60000; // 60 seconds for workflow operations
41
+ }
42
+ return 20000; // 20 seconds default
43
+ }
44
+ // Initialize health checks for system components
45
+ async function initializeHealthChecks() {
46
+ // MCP server health check
47
+ globalHealthChecker.register('mcp_server', async () => {
48
+ try {
49
+ // Basic functionality check
50
+ return process.uptime() > 0;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ });
56
+ // File system health check
57
+ globalHealthChecker.register('file_system', async () => {
58
+ try {
59
+ const fs = await import('fs-extra');
60
+ const path = await import('path');
61
+ const testPath = path.resolve(process.cwd(), '.bob');
62
+ await fs.ensureDir(testPath);
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ });
69
+ // Dashboard service check
70
+ globalHealthChecker.register('dashboard', async () => {
71
+ try {
72
+ // Check if we can create the dashboard server
73
+ const { createDashboardServer } = await import('./dashboard/server.js');
74
+ const app = createDashboardServer();
75
+ return app !== null;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ });
81
+ console.log("Health checks initialized");
82
+ }
83
+ // Graceful shutdown handler
84
+ async function gracefulShutdown(server) {
85
+ console.log("Initiating graceful shutdown...");
86
+ try {
87
+ // Stop auto-cleanup service
88
+ const { cleanupService } = await import('./tools/cleanupService.js');
89
+ cleanupService.stopAutoCleanup();
90
+ console.log("Auto-cleanup service stopped");
91
+ // Perform final cleanup
92
+ await cleanupService.performCleanup({
93
+ maxWorktreeAge: 7,
94
+ cleanTempFiles: true,
95
+ stopOrphanedRecorders: true,
96
+ forceCleanup: false
97
+ });
98
+ console.log("Final cleanup completed");
99
+ // Log shutdown statistics
100
+ const errorStats = globalErrorCollector.getStats();
101
+ console.log("Error statistics:", errorStats);
102
+ console.log("Graceful shutdown completed");
103
+ }
104
+ catch (error) {
105
+ console.error("Error during shutdown:", error);
106
+ }
107
+ }
108
+ // Enhanced CLI output formatting for modern terminal experience
109
+ function generateToolStatusMessage(toolName, args, status, duration, result, error) {
110
+ const toolCategory = getToolCategory(toolName);
111
+ const categoryIcon = getCategoryIcon(toolCategory);
112
+ const statusIcon = getStatusIcon(status);
113
+ const toolDisplayName = getToolDisplayName(toolName);
114
+ switch (status) {
115
+ case 'started':
116
+ return `${categoryIcon} [${toolCategory.toUpperCase()}] ${toolDisplayName} → ⏳ Starting...`;
117
+ case 'completed':
118
+ const durationText = formatDuration(duration || 0);
119
+ const summary = extractResultSummary(toolName, result);
120
+ return `${categoryIcon} [${toolCategory.toUpperCase()}] ${toolDisplayName} → ${statusIcon} ${summary} (${durationText})`;
121
+ case 'error':
122
+ const errorDuration = formatDuration(duration || 0);
123
+ return `${categoryIcon} [${toolCategory.toUpperCase()}] ${toolDisplayName} → ❌ Failed: ${error} (${errorDuration})`;
124
+ default:
125
+ return `${categoryIcon} [${toolCategory.toUpperCase()}] ${toolDisplayName}`;
126
+ }
127
+ }
128
+ function getToolCategory(toolName) {
129
+ if (toolName.includes('manual'))
130
+ return 'manual';
131
+ if (toolName.includes('search'))
132
+ return 'search';
133
+ if (toolName.includes('workflow'))
134
+ return 'workflow';
135
+ if (toolName.includes('dashboard'))
136
+ return 'dashboard';
137
+ if (toolName.includes('worktree'))
138
+ return 'worktree';
139
+ if (toolName.includes('validate'))
140
+ return 'validation';
141
+ if (toolName.includes('summarize'))
142
+ return 'analysis';
143
+ if (toolName.includes('health'))
144
+ return 'system';
145
+ if (toolName.includes('cleanup'))
146
+ return 'maintenance';
147
+ if (toolName.includes('workshop'))
148
+ return 'orchestration';
149
+ return 'tool';
150
+ }
151
+ function getCategoryIcon(category) {
152
+ const icons = {
153
+ 'manual': '📋',
154
+ 'search': '🔍',
155
+ 'workflow': '🔄',
156
+ 'dashboard': '💻',
157
+ 'worktree': '🌳',
158
+ 'validation': '✅',
159
+ 'analysis': '📊',
160
+ 'system': '🛠️',
161
+ 'maintenance': '🧹',
162
+ 'session': '📺',
163
+ 'orchestration': '🎯',
164
+ 'tool': '⚙️'
165
+ };
166
+ return icons[category] || '⚙️';
167
+ }
168
+ function getStatusIcon(status) {
169
+ const icons = {
170
+ 'completed': '✅',
171
+ 'error': '❌',
172
+ 'started': '⏳',
173
+ 'success': '✅'
174
+ };
175
+ return icons[status] || '📄';
176
+ }
177
+ function getToolDisplayName(toolName) {
178
+ return toolName.replace('bob.', '').replace(/\./g, '.');
179
+ }
180
+ function formatDuration(ms) {
181
+ if (ms < 1000)
182
+ return `${ms}ms`;
183
+ if (ms < 60000)
184
+ return `${(ms / 1000).toFixed(1)}s`;
185
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
186
+ }
187
+ function extractResultSummary(toolName, result) {
188
+ if (!result)
189
+ return 'Completed';
190
+ // Custom summaries based on tool type
191
+ if (toolName.includes('manual')) {
192
+ if (result.spec_id) {
193
+ const action = toolName.includes('create') ? 'Created' : 'Updated';
194
+ return `${action} ${result.spec_id.split('-').pop() || 'manual'}`;
195
+ }
196
+ return 'Manual operation completed';
197
+ }
198
+ if (toolName.includes('search')) {
199
+ if (result.stats?.totalResults !== undefined) {
200
+ const count = result.stats.totalResults;
201
+ return `${count} result${count !== 1 ? 's' : ''} found`;
202
+ }
203
+ if (result.lexicalHits && result.semanticHits) {
204
+ const total = result.lexicalHits.length + result.semanticHits.length;
205
+ return `${total} result${total !== 1 ? 's' : ''} found`;
206
+ }
207
+ return 'Search completed';
208
+ }
209
+ if (toolName.includes('workflow')) {
210
+ if (toolName.includes('start') && result.manual?.spec_id) {
211
+ return `Workflow started for ${result.manual.spec_id.split('-').pop()}`;
212
+ }
213
+ if (toolName.includes('deploy')) {
214
+ return 'Deployment completed';
215
+ }
216
+ return 'Workflow operation completed';
217
+ }
218
+ if (toolName.includes('health')) {
219
+ const status = result.status || result.overall_healthy;
220
+ return status === 'healthy' || status === true ? 'System healthy' : 'Issues detected';
221
+ }
222
+ if (toolName.includes('validate')) {
223
+ if (result.overall_compliance) {
224
+ return `Validation: ${result.overall_compliance}`;
225
+ }
226
+ return 'Validation completed';
227
+ }
228
+ return 'Operation completed';
229
+ }
230
+ async function bootstrap() {
231
+ const server = new McpServer({
232
+ name: "bob-mcp",
233
+ version: "0.1.0"
234
+ });
235
+ // Initialize MCP logging with error handling
236
+ try {
237
+ mcpLogger.initialize(server.server);
238
+ console.log("MCP logging initialized successfully");
239
+ }
240
+ catch (error) {
241
+ console.error("Failed to initialize MCP logging:", error);
242
+ // Continue without logging rather than failing completely
243
+ }
244
+ // Initialize health checks
245
+ await initializeHealthChecks();
246
+ // Enhanced tool call wrapper with reliability features and modern CLI output
247
+ const withToolLogging = (toolName, handler) => {
248
+ return withErrorHandling(async (args, extra) => {
249
+ const startTime = Date.now();
250
+ const specId = (args && typeof args === 'object' && 'spec_id' in args) ? args.spec_id : undefined;
251
+ // Start logical operation for tool call
252
+ const operationId = await mcpLogger.startLogicalOperation('tool_call', specId, `🔧 Executing ${toolName}`);
253
+ // Generate enhanced status message for Claude Code terminal
254
+ const statusMessage = generateToolStatusMessage(toolName, args, 'started');
255
+ console.log(statusMessage);
256
+ await mcpLogger.logToolCall(toolName, args, specId);
257
+ try {
258
+ // Apply timeout to tool operations (30 seconds for most tools)
259
+ const timeoutMs = getToolTimeout(toolName);
260
+ const result = await withTimeout(() => handler(args, extra), { timeout: timeoutMs });
261
+ const duration = Date.now() - startTime;
262
+ // Add success sub-event
263
+ await mcpLogger.addSubEvent(operationId, {
264
+ timestamp: new Date().toISOString(),
265
+ event: 'tool_success',
266
+ details: { tool_name: toolName, duration_ms: duration }
267
+ });
268
+ // Generate success status message
269
+ const successMessage = generateToolStatusMessage(toolName, args, 'completed', duration, result);
270
+ console.log(successMessage);
271
+ await mcpLogger.logToolSuccess(toolName, duration, specId);
272
+ await mcpLogger.completeLogicalOperation(operationId, true);
273
+ // Return enhanced result with status summary
274
+ return {
275
+ ...result,
276
+ _meta: {
277
+ tool: toolName,
278
+ duration_ms: duration,
279
+ status: 'success',
280
+ summary: extractResultSummary(toolName, result)
281
+ }
282
+ };
283
+ }
284
+ catch (error) {
285
+ const duration = Date.now() - startTime;
286
+ const errorMsg = error instanceof Error ? error.message : String(error);
287
+ // Add error sub-event
288
+ await mcpLogger.addSubEvent(operationId, {
289
+ timestamp: new Date().toISOString(),
290
+ event: 'tool_error',
291
+ details: { tool_name: toolName, error: errorMsg }
292
+ });
293
+ // Generate error status message
294
+ const errorMessage = generateToolStatusMessage(toolName, args, 'error', duration, null, errorMsg);
295
+ console.log(errorMessage);
296
+ await mcpLogger.logToolError(toolName, errorMsg, duration, specId);
297
+ await mcpLogger.completeLogicalOperation(operationId, false);
298
+ // Auto-debug if we have a spec_id and this is a critical error
299
+ if (specId && shouldInvokeDebugger(error)) {
300
+ try {
301
+ await autoDebugError({
302
+ spec_id: specId,
303
+ operation: `MCP tool execution: ${toolName}`,
304
+ error: error,
305
+ context: { args, toolName, duration }
306
+ });
307
+ console.log(`[AUTO-DEBUG] Created debug log for ${toolName} error in spec ${specId}`);
308
+ }
309
+ catch (debugError) {
310
+ console.log(`[AUTO-DEBUG] Failed to create debug log: ${debugError}`);
311
+ }
312
+ }
313
+ throw error;
314
+ }
315
+ }, `MCP tool: ${toolName}`, (args) => ({
316
+ tool_name: toolName,
317
+ spec_id: (args && typeof args === 'object' && 'spec_id' in args) ? args.spec_id : undefined
318
+ }));
319
+ };
320
+ // Register orchestrator tool (main entry point)
321
+ server.registerTool("bob.workshop", {
322
+ title: "🔧 Intelligent workflow orchestrator for all development tasks",
323
+ description: "🔧 Main entry point for all development workflows. Analyzes problems, asks clarifying questions when needed, and routes to appropriate modes (Architect, Engineer, Debugger, Reviewer). Use this as your primary tool for any development task.",
324
+ inputSchema: {
325
+ problem: z.string().min(5).describe("Problem statement or task description (e.g. 'Add JWT authentication', 'Fix login bug', 'Review user service')"),
326
+ mode: z.enum(["architect", "engineer", "debugger", "reviewer"]).optional().describe("Preferred mode (optional - will be auto-determined based on problem)"),
327
+ spec_id: z.string().optional().describe("Continue work on existing manual (optional)"),
328
+ clarifications: z.record(z.string()).optional().describe("Answers to previous clarifying questions")
329
+ }
330
+ }, withToolLogging("bob.workshop", async ({ problem, mode, spec_id, clarifications }) => {
331
+ console.log("bob.workshop orchestrator called with:", { problem, mode, spec_id, clarifications });
332
+ const result = await workshopHandler({ problem, mode, spec_id, clarifications });
333
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
334
+ }));
335
+ // Register Manual management tools (new workshop terminology)
336
+ server.registerTool("bob.manual.create", {
337
+ title: "🔧 Start a new feature manual for this project",
338
+ description: "🔧 Start a new feature manual for this project. Use this when building new apps, features, or components. This creates the manual that drives planning, coding, debugging, and review.",
339
+ inputSchema: {
340
+ title: z.string().describe("Title of the feature/app to build (e.g. 'Sudoku PWA', 'User Authentication')"),
341
+ author: z.string().optional().describe("Author of the manual"),
342
+ category: z.string().optional().describe("Category: frontend, backend, fullstack, devtools, etc."),
343
+ priority: z.string().optional().describe("Priority: low, medium, high, critical"),
344
+ tags: z.array(z.string()).optional().describe("Tags for organization (e.g. ['pwa', 'mobile', 'game'])")
345
+ }
346
+ }, withToolLogging("bob.manual.create", async ({ title, author, category, priority, tags }) => {
347
+ console.log("bob.manual.create MCP tool called with:", { title, author, category, priority, tags });
348
+ const result = await specCreateHandler({ title, author, category, priority, tags, initial_state: "draft" });
349
+ const conciseText = await formatConciseResponse("bob.manual.create", result);
350
+ return { content: [{ type: "text", text: conciseText }] };
351
+ }));
352
+ server.registerTool("bob.manual.update", {
353
+ title: "🔧 Update details or logs in a manual",
354
+ description: "🔧 Update details or logs in a manual (implementation notes, debug entries, etc.). Use this to add progress updates, execution logs, or debug information to an existing manual.",
355
+ inputSchema: {
356
+ spec_id: z.string().describe("Manual ID to update"),
357
+ section: z.enum(["executive_summary", "product_specifications", "architecture_analysis", "implementation_plan", "testing", "risk_assessment", "review"]).optional().describe("Section to update"),
358
+ content: z.string().optional().describe("Content to add to the section"),
359
+ execution_log: z.object({
360
+ timestamp: z.string(),
361
+ engineer: z.string(),
362
+ action: z.string(),
363
+ task_id: z.string(),
364
+ files_changed: z.array(z.string()),
365
+ commit_hash: z.string(),
366
+ note: z.string()
367
+ }).optional().describe("Execution log entry"),
368
+ debug_log: z.object({
369
+ timestamp: z.string(),
370
+ issue: z.string(),
371
+ root_cause: z.string(),
372
+ fix: z.string(),
373
+ confidence: z.string()
374
+ }).optional().describe("Debug log entry"),
375
+ state: z.enum(["draft", "ready", "engineered", "done"]).optional().describe("Manual state to set explicitly")
376
+ }
377
+ }, withToolLogging("bob.manual.update", async ({ spec_id, section, content, execution_log, debug_log, state }) => {
378
+ const result = await specUpdateHandler({ spec_id, section, content, execution_log, debug_log, state });
379
+ // Detect mode based on what type of update this is
380
+ let mode = 'unknown';
381
+ if (execution_log)
382
+ mode = 'engineer';
383
+ else if (debug_log)
384
+ mode = 'debugger';
385
+ else if (section === 'architecture_analysis' || section === 'product_specifications')
386
+ mode = 'architect';
387
+ else if (section === 'review')
388
+ mode = 'reviewer';
389
+ const conciseText = await formatConciseResponse("bob.manual.update", result);
390
+ return { content: [{ type: "text", text: conciseText }] };
391
+ }));
392
+ server.registerTool("bob.manual.get", {
393
+ title: "🔧 Retrieve a specific manual",
394
+ description: "🔧 Retrieve a specific manual with all its details, logs, and current status. Use this to review existing manuals before making updates or continuing work.",
395
+ inputSchema: {
396
+ spec_id: z.string().describe("Manual ID to retrieve")
397
+ }
398
+ }, withToolLogging("bob.manual.get", async ({ spec_id }) => {
399
+ const result = await specGetHandler({ spec_id });
400
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
401
+ }));
402
+ server.registerTool("bob.manual.list", {
403
+ title: "🔧 List all project manuals",
404
+ description: "🔧 List all project manuals with their current status. Use this to see what features/components are planned, in progress, or completed.",
405
+ inputSchema: {
406
+ filter: z.string().optional().describe("Optional filter by title or category")
407
+ }
408
+ }, async ({ filter }) => {
409
+ const result = await specListHandler({ filter });
410
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
411
+ });
412
+ // REMOVED: Legacy bob.spec.* tools - use bob.manual.* instead
413
+ // REMOVED: Individual worktree and git tools - use bob.workflow.* instead
414
+ // The following tools have been consolidated into bob.workflow.start and bob.workflow.deploy:
415
+ // - bob.worktree.list (use bob.worktree.debug with action: "status" instead)
416
+ // - bob.git.commit (automated by bob.workflow.deploy)
417
+ // - bob.git.merge (automated by bob.workflow.deploy)
418
+ // Advanced worktree maintenance tool (kept for debugging)
419
+ server.registerTool("bob.worktree.debug", {
420
+ title: "🔧 Debug and maintain worktrees",
421
+ description: "🔧 Comprehensive worktree maintenance tool. Handles repair (fix orphaned worktrees), cleanup (remove stale worktrees), validate (check worktree status), and status (detailed report). Essential for fail-safe worktree management.",
422
+ inputSchema: {
423
+ action: z.enum(["repair", "cleanup", "validate", "status"]).describe("Debug action: 'status' = report all worktrees, 'validate' = check manual worktree, 'repair' = fix orphaned worktrees, 'cleanup' = remove stale worktrees"),
424
+ spec_id: z.string().optional().describe("Optional manual ID for validate/repair actions"),
425
+ max_age_days: z.number().optional().default(7).describe("Max age in days for cleanup action (default: 7)")
426
+ }
427
+ }, withToolLogging("bob.worktree.debug", async ({ action, spec_id, max_age_days = 7 }) => {
428
+ const { worktreeDebugHandler } = await import("./tools/worktreeTools.js");
429
+ const result = await worktreeDebugHandler({ action, spec_id, max_age_days });
430
+ const conciseText = await formatConciseResponse("bob.worktree.debug", result);
431
+ return { content: [{ type: "text", text: conciseText }] };
432
+ }));
433
+ // REMOVED: Old search tools - use bob.code.search instead
434
+ server.registerTool("bob.code.search", {
435
+ title: "🔧 Unified lexical + semantic search",
436
+ description: "🔧 Hybrid search combining ripgrep (lexical) and semgrep (semantic) for comprehensive code search. Primary search tool with phase-aware execution and normalized outputs.",
437
+ inputSchema: {
438
+ query: z.string().describe("Search query to find in codebase"),
439
+ mode: z.enum(["lexical", "semantic", "auto"]).optional().default("auto").describe("Search mode: lexical (ripgrep only), semantic (semgrep only), or auto (both with smart scoping)"),
440
+ phase: z.enum(["architect", "engineer", "debugger", "reviewer"]).optional().describe("Caller phase for phase-aware Semgrep rule selection"),
441
+ path: z.string().optional().describe("Root path or subdirectory to search (optional)"),
442
+ includeHidden: z.boolean().optional().default(false).describe("Include hidden/ignored files (equivalent to rg -uuu)"),
443
+ followGitIgnore: z.boolean().optional().default(true).describe("Respect .gitignore rules (default: true)"),
444
+ fileTypes: z.array(z.string()).optional().describe("File types to include (e.g., ['ts', 'tsx', 'py'])"),
445
+ maxHits: z.number().optional().default(200).describe("Maximum total results across lexical and semantic"),
446
+ contextLines: z.number().optional().default(2).describe("Context lines before/after matches"),
447
+ timeoutMs: z.number().optional().default(4000).describe("Timeout in milliseconds")
448
+ }
449
+ }, async ({ query, mode, phase, path, includeHidden, followGitIgnore, fileTypes, maxHits, contextLines, timeoutMs }) => {
450
+ const result = await hybridSearchHandler({
451
+ query,
452
+ mode: mode || "auto",
453
+ phase,
454
+ path,
455
+ includeHidden: includeHidden || false,
456
+ followGitIgnore: followGitIgnore !== false,
457
+ fileTypes,
458
+ maxHits: maxHits || 200,
459
+ contextLines: contextLines || 2,
460
+ timeoutMs: timeoutMs || 4000
461
+ });
462
+ // Keep search results verbose since Claude needs to see the content
463
+ // But add concise summary to _meta for status messages
464
+ const enhancedResult = {
465
+ ...result,
466
+ _meta: {
467
+ ...(result._meta || {}),
468
+ summary: formatConciseResponse("bob.code.search", result)
469
+ }
470
+ };
471
+ return {
472
+ content: [{
473
+ type: "text",
474
+ text: JSON.stringify(enhancedResult, null, 2)
475
+ }]
476
+ };
477
+ });
478
+ // Register dashboard tools
479
+ server.registerTool("bob.dashboard.launch", {
480
+ title: "🔧 Open the visual workshop management interface",
481
+ description: "🔧 Launch the visual workshop dashboard for tracking manuals, worktrees, and logs on port 4577. Orchestrator should launch this when starting new feature development. Architect MUST launch this when working on new manuals. Returns status: 'launched', 'already_running', or 'already_running_external'. Process exits cleanly without hanging."
482
+ }, async () => {
483
+ const result = await dashboardLaunchHandler();
484
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
485
+ });
486
+ server.registerTool("bob.dashboard.update", {
487
+ title: "🔧 Send real-time updates to workshop dashboard",
488
+ description: "🔧 Push update to dashboard. Send real-time notifications to the dashboard about manual progress, server status, or other development events.",
489
+ inputSchema: {
490
+ spec_id: z.string().describe("Manual ID to send update for"),
491
+ event: z.string().describe("Event description (e.g., 'server started', 'implementation completed')"),
492
+ data: z.object({}).optional().describe("Additional event data")
493
+ }
494
+ }, async ({ spec_id, event, data }) => {
495
+ const result = await dashboardUpdateHandler({ spec_id, event, data });
496
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
497
+ });
498
+ // REMOVED: Approval tools - use structured outputs with approval_required flag instead
499
+ // Register Simplified Workflow tools (bundled git + worktree operations)
500
+ server.registerTool("bob.workflow.start", {
501
+ title: "🔧 Start new feature development workflow",
502
+ description: "🔧 Architect workflow: Create manual + worktree + launch dashboard. Bundles manual creation, git worktree setup, and dashboard launch for streamlined feature development start.",
503
+ inputSchema: {
504
+ title: z.string().describe("Title of the feature/app to build"),
505
+ author: z.string().optional().describe("Author of the manual"),
506
+ category: z.string().optional().describe("Category: frontend, backend, fullstack, devtools, etc."),
507
+ priority: z.string().optional().describe("Priority: low, medium, high, critical"),
508
+ tags: z.array(z.string()).optional().describe("Tags for organization"),
509
+ branch_name: z.string().optional().describe("Custom branch name (auto-generated if not provided)")
510
+ }
511
+ }, withToolLogging("bob.workflow.start", async ({ title, author, category, priority, tags, branch_name }) => {
512
+ console.log("bob.workflow.start called with:", { title, author, category, priority, tags, branch_name });
513
+ // Create manual
514
+ const manual = await specCreateHandler({ title, author, category, priority, tags, initial_state: "draft" });
515
+ // Create worktree with auto-generated branch name if not provided
516
+ const branchName = branch_name || `feature/${title.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')}`;
517
+ const worktree = await worktreeCreateHandler({ spec_id: manual.spec_id, branch: branchName });
518
+ // Launch dashboard
519
+ const dashboard = await dashboardLaunchHandler();
520
+ // Send dashboard notification
521
+ await dashboardUpdateHandler({
522
+ spec_id: manual.spec_id,
523
+ event: "workflow_started",
524
+ data: { title, branch: branchName, dashboard_url: "http://localhost:4577" }
525
+ });
526
+ // Log workflow initialization via execution log
527
+ await specUpdateHandler({
528
+ spec_id: manual.spec_id,
529
+ execution_log: {
530
+ timestamp: new Date().toISOString(),
531
+ engineer: "architect",
532
+ action: "planning: milestone_completed",
533
+ task_id: "WORKFLOW_INIT",
534
+ files_changed: [`manual: ${manual.spec_id}`, `worktree: ${branchName}`, "dashboard: launched"],
535
+ commit_hash: "pending",
536
+ note: "[planning] 🎯 Milestone: Feature development workflow started. Deliverables: manual: " + manual.spec_id + ", worktree: " + branchName + ", dashboard: launched. Next: complete requirements analysis, create implementation plan, start development"
537
+ }
538
+ });
539
+ // NOTE: Dev server startup moved to post-test phase in Engineer workflow
540
+ // This prevents early resource conflicts and aligns with 3-gate approval system
541
+ const workflowResult = {
542
+ manual: manual,
543
+ worktree: worktree,
544
+ dashboard: dashboard,
545
+ workflow_status: "started",
546
+ work_recording: "active",
547
+ note: "Dev server will be started after implementation and testing"
548
+ };
549
+ const conciseText = await formatConciseResponse("bob.workflow.start", workflowResult);
550
+ return {
551
+ content: [{
552
+ type: "text",
553
+ text: conciseText
554
+ }]
555
+ };
556
+ }));
557
+ server.registerTool("bob.workflow.deploy", {
558
+ title: "🔧 Complete implementation and deploy to main",
559
+ description: "🔧 Engineer workflow: Commit changes + merge to main + cleanup worktree. Bundles git operations for streamlined feature completion and deployment.",
560
+ inputSchema: {
561
+ spec_id: z.string().describe("Manual ID for the completed implementation"),
562
+ action_description: z.string().describe("Brief description of what was implemented"),
563
+ files_changed: z.array(z.string()).optional().describe("List of files changed (auto-detected if not provided)"),
564
+ target_branch: z.string().optional().default("main").describe("Target branch to merge into")
565
+ }
566
+ }, withToolLogging("bob.workflow.deploy", async ({ spec_id, action_description, files_changed, target_branch = "main" }) => {
567
+ console.log("bob.workflow.deploy called with:", { spec_id, action_description, files_changed, target_branch });
568
+ // Get git status and auto-detect changed files if not provided
569
+ const gitStatus = await getGitStatus();
570
+ const changedFiles = files_changed || [...gitStatus.staged_files, ...gitStatus.unstaged_files, ...gitStatus.untracked_files];
571
+ // NEW: Stop any running dev server before deployment
572
+ let serverCleanupInfo = null;
573
+ try {
574
+ console.log(`Stopping dev server for manual ${spec_id}...`);
575
+ const serverStopped = await stopDevServer(spec_id);
576
+ if (serverStopped) {
577
+ serverCleanupInfo = { server_stopped: true };
578
+ console.log(`Dev server stopped successfully for manual ${spec_id}`);
579
+ // Log server cleanup
580
+ await specUpdateHandler({
581
+ spec_id,
582
+ execution_log: {
583
+ timestamp: new Date().toISOString(),
584
+ engineer: "system",
585
+ action: "server_stopped",
586
+ task_id: "auto-server-cleanup",
587
+ files_changed: [],
588
+ commit_hash: "pending",
589
+ note: `Auto-stopped dev server before deployment`
590
+ }
591
+ });
592
+ }
593
+ else {
594
+ serverCleanupInfo = { server_stopped: false, reason: "no_server_running" };
595
+ console.log(`No running server found for manual ${spec_id}`);
596
+ }
597
+ }
598
+ catch (serverError) {
599
+ console.warn('Server cleanup failed (non-critical):', serverError);
600
+ serverCleanupInfo = { server_stopped: false, error: String(serverError) };
601
+ }
602
+ // Find worktree path for this spec
603
+ const worktreePath = await findWorktreeForSpec(spec_id);
604
+ console.log(`Found worktree path: ${worktreePath}`);
605
+ // Commit changes
606
+ const commit = await autoCommitChanges({
607
+ spec_id,
608
+ files_changed: changedFiles,
609
+ action_description,
610
+ worktree_path: worktreePath || undefined
611
+ });
612
+ // Merge and cleanup
613
+ const merge = await autoMergeAndCleanup({
614
+ spec_id,
615
+ target_branch,
616
+ cleanup_worktree: true
617
+ });
618
+ // Send dashboard notification
619
+ await dashboardUpdateHandler({
620
+ spec_id,
621
+ event: "workflow_deployed",
622
+ data: {
623
+ action_description,
624
+ files_changed: changedFiles,
625
+ target_branch,
626
+ commit_hash: commit.commit_hash
627
+ }
628
+ });
629
+ // Record completion milestone
630
+ await specUpdateHandler({
631
+ spec_id: spec_id,
632
+ execution_log: {
633
+ timestamp: new Date().toISOString(),
634
+ engineer: "engineer",
635
+ action: "implementation: workflow_deployment_completed",
636
+ task_id: "WORKFLOW_DEPLOY",
637
+ files_changed: changedFiles,
638
+ commit_hash: commit.commit_hash,
639
+ note: `🚀 Successfully deployed implementation: ${action_description}. Committed ${changedFiles.length} files and merged to ${target_branch}. Commit: ${commit.commit_hash}`
640
+ }
641
+ });
642
+ const deployResult = {
643
+ commit: commit,
644
+ merge: merge,
645
+ server_cleanup: serverCleanupInfo,
646
+ workflow_status: "deployed",
647
+ work_recording: "completed"
648
+ };
649
+ const conciseText = await formatConciseResponse("bob.workflow.deploy", deployResult);
650
+ return {
651
+ content: [{
652
+ type: "text",
653
+ text: conciseText
654
+ }]
655
+ };
656
+ }));
657
+ // Health check tool migrated to CLI: use "bobs health"
658
+ // Register change validation tool
659
+ server.registerTool("bob.validate.changes", {
660
+ title: "🔍 Validate implementation changes against manual",
661
+ description: "🔍 Validate recent file changes against manual expectations. Analyzes actual diffs, checks compliance, and provides human-readable summaries of what was implemented.",
662
+ inputSchema: {
663
+ spec_id: z.string().describe("Manual ID to validate changes against")
664
+ }
665
+ }, withToolLogging("bob.validate.changes", async ({ spec_id }) => {
666
+ console.log("bob.validate.changes called with:", { spec_id });
667
+ if (!spec_id) {
668
+ throw new Error("spec_id is required");
669
+ }
670
+ const validationResult = await changeValidator.validateChangesForSpec(spec_id);
671
+ // Update manual with validation results
672
+ await changeValidator.updateSpecWithValidation(validationResult);
673
+ return { content: [{ type: "text", text: JSON.stringify(validationResult, null, 2) }] };
674
+ }));
675
+ // Register implementation summarizer tool
676
+ server.registerTool("bob.summarize.implementation", {
677
+ title: "📊 Generate implementation summary from chokidar observations",
678
+ description: "📊 Analyze file changes observed by chokidar and generate comprehensive implementation summaries with compliance analysis, git commits, and actionable insights. Updates the manual's implementation logs.",
679
+ inputSchema: {
680
+ spec_id: z.string().describe("Manual ID to generate implementation summary for"),
681
+ lookback_hours: z.number().optional().default(24).describe("Hours to look back for changes (default: 24)")
682
+ }
683
+ }, withToolLogging("bob.summarize.implementation", async ({ spec_id, lookback_hours = 24 }) => {
684
+ console.log("bob.summarize.implementation called with:", { spec_id, lookback_hours });
685
+ if (!spec_id) {
686
+ throw new Error("spec_id is required");
687
+ }
688
+ const summary = await implementationSummarizer.generateSummaryForSpec(spec_id, lookback_hours);
689
+ // Update manual with summary
690
+ await implementationSummarizer.updateSpecWithSummary(summary);
691
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
692
+ }));
693
+ // Cleanup tools migrated to CLI: use "bobs cleanup" and "bobs cleanup --stats"
694
+ // Debug tool - no parameters
695
+ server.registerTool("bob.debug", {
696
+ title: "🔧 Debug Bob's Workshop server state",
697
+ description: "🔧 Debug tool with no parameters. Use this to check Bob's Workshop internal state and troubleshoot issues.",
698
+ }, async () => {
699
+ console.log("bob.debug called successfully");
700
+ return {
701
+ content: [
702
+ {
703
+ type: "text",
704
+ text: "Debug tool works!",
705
+ },
706
+ ],
707
+ };
708
+ });
709
+ // Register prompts
710
+ server.registerPrompt("Architect", { description: "Requirements analysis and technical planning specialist" }, async () => ({
711
+ messages: [{ role: "user", content: [{ type: "text", text: ARCHITECT_PROMPT }] }]
712
+ }));
713
+ server.registerPrompt("Engineer", { description: "Implementation specialist following engineering best practices" }, async () => ({
714
+ messages: [{ role: "user", content: [{ type: "text", text: ENGINEER_PROMPT }] }]
715
+ }));
716
+ server.registerPrompt("Debugger", { description: "Issue diagnosis and resolution specialist" }, async () => ({
717
+ messages: [{ role: "user", content: [{ type: "text", text: DEBUGGER_PROMPT }] }]
718
+ }));
719
+ server.registerPrompt("Reviewer", { description: "Quality assurance and security audit specialist" }, async () => ({
720
+ messages: [{ role: "user", content: [{ type: "text", text: REVIEWER_PROMPT }] }]
721
+ }));
722
+ // Start auto-cleanup service
723
+ cleanupService.startAutoCleanup(30); // Every 30 minutes
724
+ console.log("Auto-cleanup service started (30-minute intervals)");
725
+ // Set up graceful shutdown handlers
726
+ const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
727
+ signals.forEach(signal => {
728
+ process.on(signal, async () => {
729
+ console.log(`Received ${signal}, initiating graceful shutdown`);
730
+ await gracefulShutdown(server);
731
+ process.exit(0);
732
+ });
733
+ });
734
+ // Handle uncaught exceptions and rejections
735
+ process.on('uncaughtException', async (error) => {
736
+ console.error('Uncaught Exception:', error);
737
+ globalErrorCollector.add(await import('./utils/errorHandling.js').then(m => m.enhanceError(error, m.createErrorContext('uncaught_exception'))));
738
+ await gracefulShutdown(server);
739
+ process.exit(1);
740
+ });
741
+ process.on('unhandledRejection', async (reason, promise) => {
742
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
743
+ const error = reason instanceof Error ? reason : new Error(String(reason));
744
+ globalErrorCollector.add(await import('./utils/errorHandling.js').then(m => m.enhanceError(error, m.createErrorContext('unhandled_rejection'))));
745
+ await gracefulShutdown(server);
746
+ process.exit(1);
747
+ });
748
+ console.log("Starting Bob MCP server with enhanced reliability...");
749
+ const transport = new StdioServerTransport();
750
+ await server.connect(transport);
751
+ console.log("Bob MCP server started successfully");
752
+ }
753
+ bootstrap().catch(err => {
754
+ console.error("Failed to start Bob MCP:", err);
755
+ process.exit(1);
756
+ });
757
+ //# sourceMappingURL=index.js.map