bobs-workshop 0.3.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +199 -210
- package/bin/bobs-workshop.js +109 -0
- package/config/agents.json +27 -0
- package/dist/plugins/bobs-workshop.js +34 -0
- package/dist/tools/background-agent/cancel.d.ts +3 -0
- package/dist/tools/background-agent/cancel.d.ts.map +1 -0
- package/dist/tools/background-agent/cancel.js +52 -0
- package/dist/tools/background-agent/concurrency.d.ts +15 -0
- package/dist/tools/background-agent/concurrency.d.ts.map +1 -0
- package/dist/tools/background-agent/concurrency.js +61 -0
- package/dist/tools/background-agent/index.d.ts +8 -0
- package/dist/tools/background-agent/index.d.ts.map +1 -0
- package/dist/tools/background-agent/index.js +7 -0
- package/dist/tools/background-agent/launch.d.ts +6 -0
- package/dist/tools/background-agent/launch.d.ts.map +1 -0
- package/dist/tools/background-agent/launch.js +33 -0
- package/dist/tools/background-agent/list.d.ts +7 -0
- package/dist/tools/background-agent/list.d.ts.map +1 -0
- package/dist/tools/background-agent/list.js +40 -0
- package/dist/tools/background-agent/manager.d.ts +29 -0
- package/dist/tools/background-agent/manager.d.ts.map +1 -0
- package/dist/tools/background-agent/manager.js +377 -0
- package/dist/tools/background-agent/output.d.ts +3 -0
- package/dist/tools/background-agent/output.d.ts.map +1 -0
- package/dist/tools/background-agent/output.js +41 -0
- package/dist/tools/background-agent/types.d.ts +46 -0
- package/dist/tools/background-agent/types.d.ts.map +1 -0
- package/dist/tools/background-agent/types.js +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/manual/index.d.ts +3 -0
- package/dist/tools/manual/index.d.ts.map +1 -0
- package/dist/tools/manual/index.js +2 -0
- package/dist/tools/manual/manual-update.d.ts +4 -0
- package/dist/tools/manual/manual-update.d.ts.map +1 -0
- package/dist/tools/manual/manual-update.js +190 -0
- package/dist/tools/manual/verify-manual.d.ts +4 -0
- package/dist/tools/manual/verify-manual.d.ts.map +1 -0
- package/dist/tools/manual/verify-manual.js +46 -0
- package/package.json +35 -67
- package/postinstall.js +190 -0
- package/src/agents/alice.md +466 -0
- package/src/agents/bob-rev.md +493 -0
- package/src/agents/bob-send.md +277 -0
- package/src/agents/bob.md +442 -0
- package/src/agents/trace.md +451 -0
- package/src/plugins/bobs-workshop.ts +45 -0
- package/src/skills/api-patterns/SKILL.md +376 -0
- package/src/skills/architecture/SKILL.md +271 -0
- package/src/skills/bobs-workshop/performance/icon.svg +3 -0
- package/src/skills/brainstorming/SKILL.md +210 -0
- package/src/skills/clean-code/SKILL.md +151 -0
- package/src/skills/code-review-checklist/SKILL.md +220 -0
- package/src/skills/database-design/SKILL.md +271 -0
- package/src/skills/exploration/SKILL.md +257 -0
- package/src/skills/frontend-ui-ux/SKILL.md +78 -0
- package/src/skills/git-master/SKILL.md +1105 -0
- package/src/skills/performance/SKILL.md +144 -0
- package/src/skills/performance/icon.svg +3 -0
- package/src/skills/plan-writing/SKILL.md +225 -0
- package/src/skills/security/SKILL.md +410 -0
- package/src/skills/simplification/SKILL.md +238 -0
- package/src/skills/systematic-debugging/SKILL.md +175 -0
- package/src/skills/testing-patterns/SKILL.md +305 -0
- package/src/skills/verification/SKILL.md +286 -0
- package/src/tools/background-agent/cancel.ts +67 -0
- package/src/tools/background-agent/concurrency.ts +71 -0
- package/src/tools/background-agent/index.ts +7 -0
- package/src/tools/background-agent/launch.ts +39 -0
- package/src/tools/background-agent/list.ts +50 -0
- package/src/tools/background-agent/manager.ts +455 -0
- package/src/tools/background-agent/output.ts +57 -0
- package/src/tools/background-agent/types.ts +55 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/manual/index.ts +2 -0
- package/src/tools/manual/manual-update.ts +197 -0
- package/src/tools/manual/verify-manual.ts +55 -0
- package/uninstall.js +64 -0
- package/Claude.md +0 -162
- package/bin/bobs-mcp-server.js +0 -11
- package/bin/bobs-mcp.js +0 -130
- package/dist/api/taskLogger.js +0 -106
- package/dist/api/taskLogger.js.map +0 -1
- package/dist/cli/checker.js +0 -401
- package/dist/cli/checker.js.map +0 -1
- package/dist/cli/cleanup.js +0 -131
- package/dist/cli/cleanup.js.map +0 -1
- package/dist/cli/debug.js +0 -157
- package/dist/cli/debug.js.map +0 -1
- package/dist/cli/health.js +0 -97
- package/dist/cli/health.js.map +0 -1
- package/dist/cli/setup.js +0 -81
- package/dist/cli/setup.js.map +0 -1
- package/dist/cli/workshop.js +0 -42
- package/dist/cli/workshop.js.map +0 -1
- package/dist/dashboard/server.js +0 -1203
- package/dist/dashboard/server.js.map +0 -1
- package/dist/index.js +0 -960
- package/dist/index.js.map +0 -1
- package/dist/prompts/architect.js +0 -221
- package/dist/prompts/architect.js.map +0 -1
- package/dist/prompts/debugger.js +0 -257
- package/dist/prompts/debugger.js.map +0 -1
- package/dist/prompts/engineer.js +0 -249
- package/dist/prompts/engineer.js.map +0 -1
- package/dist/prompts/orchestrator.js +0 -304
- package/dist/prompts/orchestrator.js.map +0 -1
- package/dist/prompts/reviewer.js +0 -289
- package/dist/prompts/reviewer.js.map +0 -1
- package/dist/services/activitySummarizer.js +0 -388
- package/dist/services/activitySummarizer.js.map +0 -1
- package/dist/services/changeValidator.js +0 -396
- package/dist/services/changeValidator.js.map +0 -1
- package/dist/services/claudeOrchestrator.js +0 -343
- package/dist/services/claudeOrchestrator.js.map +0 -1
- package/dist/services/fileMonitor.js +0 -250
- package/dist/services/fileMonitor.js.map +0 -1
- package/dist/services/implementationSummarizer.js +0 -306
- package/dist/services/implementationSummarizer.js.map +0 -1
- package/dist/services/liveMonitor.js +0 -315
- package/dist/services/liveMonitor.js.map +0 -1
- package/dist/services/mcpAuditLogger.js +0 -104
- package/dist/services/mcpAuditLogger.js.map +0 -1
- package/dist/services/mcpLogger.js +0 -223
- package/dist/services/mcpLogger.js.map +0 -1
- package/dist/services/tmuxManager.js +0 -541
- package/dist/services/tmuxManager.js.map +0 -1
- package/dist/tools/approvalTools.js +0 -244
- package/dist/tools/approvalTools.js.map +0 -1
- package/dist/tools/autoDebugger.js +0 -147
- package/dist/tools/autoDebugger.js.map +0 -1
- package/dist/tools/cleanupService.js +0 -221
- package/dist/tools/cleanupService.js.map +0 -1
- package/dist/tools/dashboardTools.js +0 -342
- package/dist/tools/dashboardTools.js.map +0 -1
- package/dist/tools/developmentNudges.js +0 -336
- package/dist/tools/developmentNudges.js.map +0 -1
- package/dist/tools/gitTools.js +0 -741
- package/dist/tools/gitTools.js.map +0 -1
- package/dist/tools/orchestratorTools.js +0 -832
- package/dist/tools/orchestratorTools.js.map +0 -1
- package/dist/tools/searchCache.js +0 -64
- package/dist/tools/searchCache.js.map +0 -1
- package/dist/tools/searchTools.js +0 -1107
- package/dist/tools/searchTools.js.map +0 -1
- package/dist/tools/semgrep-patterns.js +0 -296
- package/dist/tools/semgrep-patterns.js.map +0 -1
- package/dist/tools/specTools.js +0 -332
- package/dist/tools/specTools.js.map +0 -1
- package/dist/tools/structural/__tests__/orchestrator.test.js +0 -61
- package/dist/tools/structural/__tests__/orchestrator.test.js.map +0 -1
- package/dist/tools/structural/cache.js +0 -226
- package/dist/tools/structural/cache.js.map +0 -1
- package/dist/tools/structural/engines/python/index.js +0 -118
- package/dist/tools/structural/engines/python/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js +0 -97
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js.map +0 -1
- package/dist/tools/structural/engines/typescript/analyzer.js +0 -433
- package/dist/tools/structural/engines/typescript/analyzer.js.map +0 -1
- package/dist/tools/structural/engines/typescript/index.js +0 -381
- package/dist/tools/structural/engines/typescript/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/utils.js +0 -279
- package/dist/tools/structural/engines/typescript/utils.js.map +0 -1
- package/dist/tools/structural/index.js +0 -248
- package/dist/tools/structural/index.js.map +0 -1
- package/dist/tools/structural/types.js +0 -18
- package/dist/tools/structural/types.js.map +0 -1
- package/dist/tools/tmuxTools.js +0 -100
- package/dist/tools/tmuxTools.js.map +0 -1
- package/dist/tools/workRecorder.js +0 -215
- package/dist/tools/workRecorder.js.map +0 -1
- package/dist/tools/worktreeTools.js +0 -705
- package/dist/tools/worktreeTools.js.map +0 -1
- package/dist/utils/__tests__/integration.test.js +0 -57
- package/dist/utils/__tests__/integration.test.js.map +0 -1
- package/dist/utils/__tests__/serverDetection.test.js +0 -151
- package/dist/utils/__tests__/serverDetection.test.js.map +0 -1
- package/dist/utils/errorHandling.js +0 -336
- package/dist/utils/errorHandling.js.map +0 -1
- package/dist/utils/processManager.js +0 -172
- package/dist/utils/processManager.js.map +0 -1
- package/dist/utils/reliability.js +0 -263
- package/dist/utils/reliability.js.map +0 -1
- package/dist/utils/responseFormatter.js +0 -250
- package/dist/utils/responseFormatter.js.map +0 -1
- package/dist/utils/serverDetection.js +0 -133
- package/dist/utils/serverDetection.js.map +0 -1
- package/dist/utils/specMigration.js +0 -105
- package/dist/utils/specMigration.js.map +0 -1
- package/dist/validation/schemas.js +0 -299
- package/dist/validation/schemas.js.map +0 -1
- package/public/.well-known/mcp/manifest.json +0 -473
- package/public/index.html +0 -3157
- package/public/index.html.backup +0 -2805
- package/public/index.html.backup2 +0 -1292
- package/scripts/cleanup-system-logs.ts +0 -121
- package/scripts/init-workspace.js +0 -63
- package/scripts/install-search-tools.js +0 -116
package/dist/dashboard/server.js
DELETED
|
@@ -1,1203 +0,0 @@
|
|
|
1
|
-
// src/dashboard/server.ts
|
|
2
|
-
import express from "express";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import fs from "fs-extra";
|
|
6
|
-
import { specCreateHandler, specUpdateHandler, specGetHandler, specListHandler } from "../tools/specTools.js";
|
|
7
|
-
import { worktreeCreateHandler, worktreeListHandler, worktreeMergeHandler, worktreeRemoveHandler } from "../tools/worktreeTools.js";
|
|
8
|
-
import { semgrepSearchHandler, enhancedFileSearchHandler } from "../tools/searchTools.js";
|
|
9
|
-
import { createApprovalRequest, processApprovalResponse, checkApprovalStatus, handleApprovalTimeout } from "../tools/approvalTools.js";
|
|
10
|
-
import { fileMonitor } from "../services/fileMonitor.js";
|
|
11
|
-
import { mcpLogger } from "../services/mcpLogger.js";
|
|
12
|
-
import multer from "multer";
|
|
13
|
-
import { taskService, createErrorResponse, createSuccessResponse } from "../api/taskLogger.js";
|
|
14
|
-
class NotificationManager {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.notifications = [];
|
|
17
|
-
this.maxNotifications = 1000;
|
|
18
|
-
}
|
|
19
|
-
addNotification(notification) {
|
|
20
|
-
const fullNotification = {
|
|
21
|
-
id: `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
22
|
-
...notification
|
|
23
|
-
};
|
|
24
|
-
this.notifications.push(fullNotification);
|
|
25
|
-
// Keep only the last N notifications
|
|
26
|
-
if (this.notifications.length > this.maxNotifications) {
|
|
27
|
-
this.notifications = this.notifications.slice(-this.maxNotifications);
|
|
28
|
-
}
|
|
29
|
-
console.log(`📢 Dashboard notification: [${fullNotification.spec_id || 'system'}] ${fullNotification.event}`);
|
|
30
|
-
return fullNotification;
|
|
31
|
-
}
|
|
32
|
-
getRecentNotifications(limit = 50) {
|
|
33
|
-
return this.notifications.slice(-limit);
|
|
34
|
-
}
|
|
35
|
-
getNotificationsBySpec(spec_id, limit = 20) {
|
|
36
|
-
return this.notifications
|
|
37
|
-
.filter(n => n.spec_id === spec_id)
|
|
38
|
-
.slice(-limit);
|
|
39
|
-
}
|
|
40
|
-
clearOldNotifications(olderThanHours = 24) {
|
|
41
|
-
const cutoff = new Date(Date.now() - (olderThanHours * 60 * 60 * 1000));
|
|
42
|
-
const originalLength = this.notifications.length;
|
|
43
|
-
this.notifications = this.notifications.filter(n => new Date(n.timestamp) > cutoff);
|
|
44
|
-
return originalLength - this.notifications.length;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const notificationManager = new NotificationManager();
|
|
48
|
-
// Helper functions for enhanced file change descriptions
|
|
49
|
-
function generateFileChangeDescription(change) {
|
|
50
|
-
const fileName = change.file.split('/').pop() || change.file;
|
|
51
|
-
const fileExt = change.file.split('.').pop() || '';
|
|
52
|
-
let action = '';
|
|
53
|
-
let actionDetails = '';
|
|
54
|
-
switch (change.type) {
|
|
55
|
-
case 'added':
|
|
56
|
-
action = '➕ Created';
|
|
57
|
-
actionDetails = 'New file added to project';
|
|
58
|
-
break;
|
|
59
|
-
case 'changed':
|
|
60
|
-
action = '✏️ Modified';
|
|
61
|
-
actionDetails = 'Existing file updated with changes';
|
|
62
|
-
break;
|
|
63
|
-
case 'unlinked':
|
|
64
|
-
action = '🗑️ Deleted';
|
|
65
|
-
actionDetails = 'File removed from project';
|
|
66
|
-
break;
|
|
67
|
-
default:
|
|
68
|
-
action = '📝 Updated';
|
|
69
|
-
actionDetails = 'File content or structure changed';
|
|
70
|
-
}
|
|
71
|
-
const fileTypeInfo = getFileTypeDescription(fileExt);
|
|
72
|
-
const timeAgo = getTimeAgo(change.timestamp);
|
|
73
|
-
const linesInfo = change.lines_changed ? ` (${change.lines_changed} lines)` : '';
|
|
74
|
-
return `${action} ${fileTypeInfo} ${fileName}${linesInfo} • ${actionDetails} ${timeAgo}`;
|
|
75
|
-
}
|
|
76
|
-
function categorizeFileChange(filePath) {
|
|
77
|
-
if (filePath.includes('test') || filePath.includes('spec'))
|
|
78
|
-
return 'test';
|
|
79
|
-
if (filePath.includes('util') || filePath.includes('helper'))
|
|
80
|
-
return 'utility';
|
|
81
|
-
if (filePath.includes('component') || filePath.includes('ui'))
|
|
82
|
-
return 'component';
|
|
83
|
-
if (filePath.includes('service') || filePath.includes('api'))
|
|
84
|
-
return 'service';
|
|
85
|
-
if (filePath.includes('config') || filePath.endsWith('.json'))
|
|
86
|
-
return 'config';
|
|
87
|
-
if (filePath.includes('style') || filePath.endsWith('.css'))
|
|
88
|
-
return 'style';
|
|
89
|
-
if (filePath.endsWith('.md') || filePath.includes('doc'))
|
|
90
|
-
return 'documentation';
|
|
91
|
-
return 'core';
|
|
92
|
-
}
|
|
93
|
-
function getFileTypeDescription(ext) {
|
|
94
|
-
const typeMap = {
|
|
95
|
-
'ts': 'TypeScript file',
|
|
96
|
-
'js': 'JavaScript file',
|
|
97
|
-
'json': 'JSON config',
|
|
98
|
-
'md': 'documentation',
|
|
99
|
-
'css': 'stylesheet',
|
|
100
|
-
'html': 'HTML file',
|
|
101
|
-
'vue': 'Vue component',
|
|
102
|
-
'jsx': 'React component',
|
|
103
|
-
'tsx': 'React TypeScript component',
|
|
104
|
-
'yml': 'YAML config',
|
|
105
|
-
'yaml': 'YAML config'
|
|
106
|
-
};
|
|
107
|
-
return typeMap[ext] || 'file';
|
|
108
|
-
}
|
|
109
|
-
async function getFileGitDiff(filePath) {
|
|
110
|
-
try {
|
|
111
|
-
const { simpleGit } = await import('simple-git');
|
|
112
|
-
const gitInstance = simpleGit(process.cwd());
|
|
113
|
-
// Get the diff for the file (current worktree vs main branch to show all changes)
|
|
114
|
-
const diff = await gitInstance.diff(['--numstat', 'main', '--', filePath]);
|
|
115
|
-
if (!diff.trim()) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
const lines = diff.trim().split('\n');
|
|
119
|
-
const stats = lines[0].split('\t');
|
|
120
|
-
if (stats.length >= 2) {
|
|
121
|
-
const linesAdded = parseInt(stats[0]) || 0;
|
|
122
|
-
const linesRemoved = parseInt(stats[1]) || 0;
|
|
123
|
-
let diffSummary = '';
|
|
124
|
-
if (linesAdded > 0 && linesRemoved > 0) {
|
|
125
|
-
diffSummary = `+${linesAdded} -${linesRemoved}`;
|
|
126
|
-
}
|
|
127
|
-
else if (linesAdded > 0) {
|
|
128
|
-
diffSummary = `+${linesAdded}`;
|
|
129
|
-
}
|
|
130
|
-
else if (linesRemoved > 0) {
|
|
131
|
-
diffSummary = `-${linesRemoved}`;
|
|
132
|
-
}
|
|
133
|
-
return { linesAdded, linesRemoved, diffSummary };
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
console.error('Error getting git diff for file:', filePath, error);
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function generateHumanReadableActivityDescription(activity) {
|
|
143
|
-
if (!activity)
|
|
144
|
-
return 'Unknown activity';
|
|
145
|
-
// Handle verbose mode-specific details first
|
|
146
|
-
if (activity.data?.verbose_details) {
|
|
147
|
-
const verboseDescription = generateVerboseActivityDescription(activity);
|
|
148
|
-
if (verboseDescription)
|
|
149
|
-
return verboseDescription;
|
|
150
|
-
}
|
|
151
|
-
// Handle different types of activities
|
|
152
|
-
if (activity.type === 'mcp_operation') {
|
|
153
|
-
return generateMcpOperationDescription(activity);
|
|
154
|
-
}
|
|
155
|
-
if (activity.action) {
|
|
156
|
-
return generateActionDescription(activity);
|
|
157
|
-
}
|
|
158
|
-
if (activity.note) {
|
|
159
|
-
return activity.note;
|
|
160
|
-
}
|
|
161
|
-
return 'Activity completed';
|
|
162
|
-
}
|
|
163
|
-
function generateVerboseActivityDescription(activity) {
|
|
164
|
-
const mode = activity.data?.mode;
|
|
165
|
-
const verbose = activity.data?.verbose_details;
|
|
166
|
-
if (!mode || !verbose)
|
|
167
|
-
return null;
|
|
168
|
-
switch (mode) {
|
|
169
|
-
case 'engineer':
|
|
170
|
-
if (verbose.engineer) {
|
|
171
|
-
const eng = verbose.engineer;
|
|
172
|
-
let description = '';
|
|
173
|
-
if (eng.task_description) {
|
|
174
|
-
description += `🔧 ${eng.task_description}`;
|
|
175
|
-
}
|
|
176
|
-
if (eng.completion_percentage !== undefined) {
|
|
177
|
-
description += ` (${eng.completion_percentage}% complete)`;
|
|
178
|
-
}
|
|
179
|
-
if (eng.tasks_completed?.length) {
|
|
180
|
-
description += ` • Completed: ${eng.tasks_completed.join(', ')}`;
|
|
181
|
-
}
|
|
182
|
-
if (eng.files_modified?.length) {
|
|
183
|
-
description += ` • Files: ${eng.files_modified.length} modified`;
|
|
184
|
-
}
|
|
185
|
-
if (eng.build_status === 'success') {
|
|
186
|
-
description += ' ✅ Build passed';
|
|
187
|
-
}
|
|
188
|
-
else if (eng.build_status === 'failed') {
|
|
189
|
-
description += ' ❌ Build failed';
|
|
190
|
-
}
|
|
191
|
-
if (eng.test_status === 'passed') {
|
|
192
|
-
description += ' 🧪 Tests passed';
|
|
193
|
-
}
|
|
194
|
-
else if (eng.test_status === 'failed') {
|
|
195
|
-
description += ' 🧪 Tests failed';
|
|
196
|
-
}
|
|
197
|
-
return description || 'Engineer task completed';
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
200
|
-
case 'debugger':
|
|
201
|
-
if (verbose.debugger) {
|
|
202
|
-
const dbg = verbose.debugger;
|
|
203
|
-
let description = '';
|
|
204
|
-
if (dbg.issue_description) {
|
|
205
|
-
description += `🐛 Issue: ${dbg.issue_description}`;
|
|
206
|
-
}
|
|
207
|
-
if (dbg.root_cause) {
|
|
208
|
-
description += ` • Root cause: ${dbg.root_cause}`;
|
|
209
|
-
}
|
|
210
|
-
if (dbg.fix_description) {
|
|
211
|
-
description += ` • Fix: ${dbg.fix_description}`;
|
|
212
|
-
}
|
|
213
|
-
if (dbg.confidence_level) {
|
|
214
|
-
const confidenceIcon = dbg.confidence_level === 'high' ? '🎯' :
|
|
215
|
-
dbg.confidence_level === 'medium' ? '⚖️' : '❓';
|
|
216
|
-
description += ` ${confidenceIcon} Confidence: ${dbg.confidence_level}`;
|
|
217
|
-
}
|
|
218
|
-
if (dbg.files_affected?.length) {
|
|
219
|
-
description += ` • Affected: ${dbg.files_affected.length} files`;
|
|
220
|
-
}
|
|
221
|
-
return description || 'Debug task completed';
|
|
222
|
-
}
|
|
223
|
-
break;
|
|
224
|
-
case 'architect':
|
|
225
|
-
if (verbose.architect) {
|
|
226
|
-
const arch = verbose.architect;
|
|
227
|
-
let description = '';
|
|
228
|
-
if (arch.spec_sections_completed?.length) {
|
|
229
|
-
description += `📋 Completed: ${arch.spec_sections_completed.join(', ')}`;
|
|
230
|
-
}
|
|
231
|
-
if (arch.research_findings?.length) {
|
|
232
|
-
description += ` • Research: ${arch.research_findings.length} findings`;
|
|
233
|
-
}
|
|
234
|
-
if (arch.design_decisions?.length) {
|
|
235
|
-
description += ` • Decisions: ${arch.design_decisions.length} made`;
|
|
236
|
-
}
|
|
237
|
-
if (arch.codebase_insights?.length) {
|
|
238
|
-
description += ` • Insights: ${arch.codebase_insights.length} discovered`;
|
|
239
|
-
}
|
|
240
|
-
if (arch.architecture_patterns?.length) {
|
|
241
|
-
description += ` • Patterns: ${arch.architecture_patterns.join(', ')}`;
|
|
242
|
-
}
|
|
243
|
-
return description || 'Architecture task completed';
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
function generateMcpOperationDescription(activity) {
|
|
250
|
-
const tool = activity.tool_name || activity.operation || 'unknown';
|
|
251
|
-
const status = activity.status || 'completed';
|
|
252
|
-
const toolDescriptions = {
|
|
253
|
-
'bob.workshop': '🔧 Workshop orchestration',
|
|
254
|
-
'bob.manual.create': '📝 Manual creation',
|
|
255
|
-
'bob.manual.update': '✏️ Manual update',
|
|
256
|
-
'bob.manual.get': '📖 Manual retrieval',
|
|
257
|
-
'bob.workflow.start': '🚀 Workflow initiation',
|
|
258
|
-
'bob.workflow.deploy': '🚢 Deployment process',
|
|
259
|
-
'bob.code.search': '🔍 Code search',
|
|
260
|
-
'bob.validate.changes': '✅ Change validation',
|
|
261
|
-
'bob.summarize.implementation': '📊 Implementation summary',
|
|
262
|
-
'bob.dashboard.launch': '💻 Dashboard launch'
|
|
263
|
-
};
|
|
264
|
-
const description = toolDescriptions[tool] || `🔧 ${tool.replace('bob.', '').replace(/[._]/g, ' ')}`;
|
|
265
|
-
const statusIcon = status === 'success' ? '✅' : status === 'error' ? '❌' : '⏳';
|
|
266
|
-
return `${description} ${statusIcon}`;
|
|
267
|
-
}
|
|
268
|
-
function generateActionDescription(activity) {
|
|
269
|
-
const action = activity.action || '';
|
|
270
|
-
const actionDescriptions = {
|
|
271
|
-
'implementation_complete': '✅ Code implementation completed successfully',
|
|
272
|
-
'validation_compliant': '✅ Validation passed - changes are compliant',
|
|
273
|
-
'validation_partial': '⚠️ Validation completed with some issues',
|
|
274
|
-
'validation_non_compliant': '❌ Validation failed - changes need review',
|
|
275
|
-
'worktree_created': '🌳 Development workspace created',
|
|
276
|
-
'worktree_merged': '🔄 Changes merged to main branch',
|
|
277
|
-
'worktree_removed': '🗑️ Development workspace removed',
|
|
278
|
-
'worktree_status_changed': '🔄 Workspace status updated',
|
|
279
|
-
'commit_created': '💾 Changes committed to repository',
|
|
280
|
-
'files_modified': '📝 Files updated in project',
|
|
281
|
-
'test_run': '🧪 Tests executed',
|
|
282
|
-
'build_completed': '🏗️ Build process completed',
|
|
283
|
-
'deployment_started': '🚀 Deployment initiated',
|
|
284
|
-
'deployment_completed': '✅ Deployment completed successfully',
|
|
285
|
-
'debug_session': '🐛 Debug session completed',
|
|
286
|
-
'code_review': '👀 Code review performed',
|
|
287
|
-
'documentation_updated': '📚 Documentation updated'
|
|
288
|
-
};
|
|
289
|
-
// Try exact match first
|
|
290
|
-
if (actionDescriptions[action]) {
|
|
291
|
-
return actionDescriptions[action];
|
|
292
|
-
}
|
|
293
|
-
// Try partial matches
|
|
294
|
-
if (action.includes('implement'))
|
|
295
|
-
return '🛠️ Implementation work completed';
|
|
296
|
-
if (action.includes('validate') || action.includes('validation'))
|
|
297
|
-
return '✅ Validation process completed';
|
|
298
|
-
if (action.includes('commit'))
|
|
299
|
-
return '💾 Code changes committed';
|
|
300
|
-
if (action.includes('merge'))
|
|
301
|
-
return '🔄 Code changes merged';
|
|
302
|
-
if (action.includes('test'))
|
|
303
|
-
return '🧪 Testing completed';
|
|
304
|
-
if (action.includes('build'))
|
|
305
|
-
return '🏗️ Build process completed';
|
|
306
|
-
if (action.includes('deploy'))
|
|
307
|
-
return '🚀 Deployment completed';
|
|
308
|
-
if (action.includes('debug'))
|
|
309
|
-
return '🐛 Debug process completed';
|
|
310
|
-
if (action.includes('review'))
|
|
311
|
-
return '👀 Review completed';
|
|
312
|
-
if (action.includes('create'))
|
|
313
|
-
return '✨ Creation completed';
|
|
314
|
-
if (action.includes('update') || action.includes('modify'))
|
|
315
|
-
return '✏️ Update completed';
|
|
316
|
-
if (action.includes('delete') || action.includes('remove'))
|
|
317
|
-
return '🗑️ Removal completed';
|
|
318
|
-
// Fallback: humanize the action name
|
|
319
|
-
const humanized = action
|
|
320
|
-
.replace(/[_-]/g, ' ')
|
|
321
|
-
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
322
|
-
return `📋 ${humanized}`;
|
|
323
|
-
}
|
|
324
|
-
function getTimeAgo(timestamp) {
|
|
325
|
-
const now = new Date();
|
|
326
|
-
const changeTime = new Date(timestamp);
|
|
327
|
-
const diffMs = now.getTime() - changeTime.getTime();
|
|
328
|
-
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
329
|
-
if (diffMins < 1)
|
|
330
|
-
return 'just now';
|
|
331
|
-
if (diffMins < 60)
|
|
332
|
-
return `${diffMins}m ago`;
|
|
333
|
-
const diffHours = Math.floor(diffMins / 60);
|
|
334
|
-
if (diffHours < 24)
|
|
335
|
-
return `${diffHours}h ago`;
|
|
336
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
337
|
-
return `${diffDays}d ago`;
|
|
338
|
-
}
|
|
339
|
-
// Compatibility for import.meta in different environments
|
|
340
|
-
const getFilename = () => {
|
|
341
|
-
// Check if we're in a test environment
|
|
342
|
-
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
343
|
-
return "/src/dashboard/server.ts";
|
|
344
|
-
}
|
|
345
|
-
// Check if import.meta is available
|
|
346
|
-
if (typeof import.meta !== 'undefined' && import.meta.url) {
|
|
347
|
-
return fileURLToPath(import.meta.url);
|
|
348
|
-
}
|
|
349
|
-
// Fallback
|
|
350
|
-
return "/src/dashboard/server.ts";
|
|
351
|
-
};
|
|
352
|
-
const __filename = getFilename();
|
|
353
|
-
const __dirname = path.dirname(__filename);
|
|
354
|
-
const projectRoot = path.resolve(__dirname, "../..");
|
|
355
|
-
export function createDashboardServer() {
|
|
356
|
-
const app = express();
|
|
357
|
-
app.use(express.json());
|
|
358
|
-
app.use(express.static(path.join(projectRoot, "public")));
|
|
359
|
-
app.use("/src", express.static(path.join(projectRoot, "src")));
|
|
360
|
-
// Multer configuration for image uploads
|
|
361
|
-
const storage = multer.memoryStorage();
|
|
362
|
-
const upload = multer({
|
|
363
|
-
storage,
|
|
364
|
-
limits: {
|
|
365
|
-
fileSize: 5 * 1024 * 1024, // 5MB limit
|
|
366
|
-
},
|
|
367
|
-
fileFilter: (req, file, cb) => {
|
|
368
|
-
// Accept common image formats
|
|
369
|
-
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'];
|
|
370
|
-
if (allowedTypes.includes(file.mimetype)) {
|
|
371
|
-
cb(null, true);
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
cb(new Error('Invalid file type. Only PNG, JPEG, GIF, and WebP are allowed.'));
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
// SPEC management endpoints
|
|
379
|
-
app.post("/api/tools/bob.spec.create", async (req, res) => {
|
|
380
|
-
try {
|
|
381
|
-
const result = await specCreateHandler(req.body);
|
|
382
|
-
res.json(result);
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
app.post("/api/tools/bob.spec.update", async (req, res) => {
|
|
389
|
-
try {
|
|
390
|
-
const result = await specUpdateHandler(req.body);
|
|
391
|
-
res.json(result);
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
app.post("/api/tools/bob.spec.get", async (req, res) => {
|
|
398
|
-
try {
|
|
399
|
-
const result = await specGetHandler(req.body);
|
|
400
|
-
res.json(result);
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
app.get("/api/tools/bob.spec.list", async (req, res) => {
|
|
407
|
-
try {
|
|
408
|
-
const result = await specListHandler({ filter: req.query.filter });
|
|
409
|
-
res.json(result);
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
app.post("/api/tools/bob.spec.delete", async (req, res) => {
|
|
416
|
-
try {
|
|
417
|
-
const { spec_id } = req.body;
|
|
418
|
-
if (!spec_id) {
|
|
419
|
-
return res.status(400).json({ error: "spec_id is required" });
|
|
420
|
-
}
|
|
421
|
-
const specsDir = path.resolve(process.cwd(), ".bob/specs");
|
|
422
|
-
const filePath = path.join(specsDir, `${spec_id}.json`);
|
|
423
|
-
const indexPath = path.join(specsDir, "index.json");
|
|
424
|
-
// Check if spec exists
|
|
425
|
-
if (!await fs.pathExists(filePath)) {
|
|
426
|
-
return res.status(404).json({ error: `SPEC ${spec_id} not found` });
|
|
427
|
-
}
|
|
428
|
-
// Delete the spec file
|
|
429
|
-
await fs.remove(filePath);
|
|
430
|
-
// Update index.json
|
|
431
|
-
if (await fs.pathExists(indexPath)) {
|
|
432
|
-
const indexData = await fs.readJson(indexPath);
|
|
433
|
-
indexData.specs = (indexData.specs || []).filter((spec) => spec.spec_id !== spec_id);
|
|
434
|
-
await fs.writeFile(indexPath, JSON.stringify(indexData, null, 2), "utf8");
|
|
435
|
-
}
|
|
436
|
-
res.json({ status: "deleted", spec_id });
|
|
437
|
-
}
|
|
438
|
-
catch (error) {
|
|
439
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
// Image upload endpoint for inline image support
|
|
443
|
-
app.post("/api/images/upload-inline", upload.single('image'), async (req, res) => {
|
|
444
|
-
try {
|
|
445
|
-
if (!req.file) {
|
|
446
|
-
return res.status(400).json({ error: "No image file provided" });
|
|
447
|
-
}
|
|
448
|
-
const workingDir = process.cwd();
|
|
449
|
-
const uploadsDir = path.join(workingDir, '.mcp', 'uploads');
|
|
450
|
-
// Ensure uploads directory exists
|
|
451
|
-
await fs.ensureDir(uploadsDir);
|
|
452
|
-
// Generate auto-increment filename
|
|
453
|
-
const files = await fs.readdir(uploadsDir);
|
|
454
|
-
const imageFiles = files.filter(f => /\.(png|jpg|jpeg|gif|webp)$/i.test(f));
|
|
455
|
-
const nextNumber = imageFiles.length + 1;
|
|
456
|
-
// Get file extension
|
|
457
|
-
const ext = path.extname(req.file.originalname) || '.png';
|
|
458
|
-
const filename = `img${nextNumber}${ext}`;
|
|
459
|
-
const filePath = path.join(uploadsDir, filename);
|
|
460
|
-
// Save file to uploads directory
|
|
461
|
-
await fs.writeFile(filePath, req.file.buffer);
|
|
462
|
-
// Return placeholder data for frontend
|
|
463
|
-
res.json({
|
|
464
|
-
success: true,
|
|
465
|
-
placeholder: {
|
|
466
|
-
id: `img${nextNumber}`,
|
|
467
|
-
filename: filename,
|
|
468
|
-
path: `./mcp/uploads/${filename}`,
|
|
469
|
-
size: req.file.size,
|
|
470
|
-
mimetype: req.file.mimetype
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
// Add notification
|
|
474
|
-
notificationManager.addNotification({
|
|
475
|
-
event: "image_uploaded",
|
|
476
|
-
data: {
|
|
477
|
-
filename: filename,
|
|
478
|
-
size: req.file.size,
|
|
479
|
-
path: `./mcp/uploads/${filename}`
|
|
480
|
-
},
|
|
481
|
-
timestamp: new Date().toISOString()
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
catch (error) {
|
|
485
|
-
console.error('Image upload error:', error);
|
|
486
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
// Worktree endpoints
|
|
490
|
-
app.get("/api/tools/bob.worktree.list", async (req, res) => {
|
|
491
|
-
try {
|
|
492
|
-
const result = await worktreeListHandler();
|
|
493
|
-
res.json(result);
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
app.post("/api/tools/bob.worktree.create", async (req, res) => {
|
|
500
|
-
try {
|
|
501
|
-
const result = await worktreeCreateHandler(req.body);
|
|
502
|
-
res.json(result);
|
|
503
|
-
}
|
|
504
|
-
catch (error) {
|
|
505
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
app.post("/api/tools/bob.worktree.merge", async (req, res) => {
|
|
509
|
-
try {
|
|
510
|
-
const result = await worktreeMergeHandler(req.body);
|
|
511
|
-
res.json(result);
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
app.post("/api/tools/bob.worktree.remove", async (req, res) => {
|
|
518
|
-
try {
|
|
519
|
-
const result = await worktreeRemoveHandler(req.body);
|
|
520
|
-
res.json(result);
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
// Research endpoints
|
|
527
|
-
app.post("/api/tools/bob.search.enhanced", async (req, res) => {
|
|
528
|
-
try {
|
|
529
|
-
const result = await enhancedFileSearchHandler(req.body);
|
|
530
|
-
res.json(result);
|
|
531
|
-
}
|
|
532
|
-
catch (error) {
|
|
533
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
app.post("/api/tools/bob.search.semgrep", async (req, res) => {
|
|
537
|
-
try {
|
|
538
|
-
const result = await semgrepSearchHandler(req.body);
|
|
539
|
-
res.json(result);
|
|
540
|
-
}
|
|
541
|
-
catch (error) {
|
|
542
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
// User approval endpoints
|
|
546
|
-
app.get("/api/approvals/:spec_id", async (req, res) => {
|
|
547
|
-
try {
|
|
548
|
-
const { spec_id } = req.params;
|
|
549
|
-
const approval_type = req.query.approval_type;
|
|
550
|
-
const result = await checkApprovalStatus(spec_id, approval_type);
|
|
551
|
-
res.json(result);
|
|
552
|
-
}
|
|
553
|
-
catch (error) {
|
|
554
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
app.post("/api/approvals/request", async (req, res) => {
|
|
558
|
-
try {
|
|
559
|
-
const { spec_id, approval_type, message, context } = req.body;
|
|
560
|
-
if (!spec_id || !approval_type || !message) {
|
|
561
|
-
return res.status(400).json({
|
|
562
|
-
error: "spec_id, approval_type, and message are required"
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
const result = await createApprovalRequest({ spec_id, approval_type, message, context });
|
|
566
|
-
// Send real-time notification to dashboard
|
|
567
|
-
notificationManager.addNotification({
|
|
568
|
-
spec_id,
|
|
569
|
-
event: "approval_requested",
|
|
570
|
-
data: {
|
|
571
|
-
approval_type,
|
|
572
|
-
message,
|
|
573
|
-
approval_id: result.approval_id,
|
|
574
|
-
dashboard_url: result.dashboard_url
|
|
575
|
-
},
|
|
576
|
-
timestamp: new Date().toISOString()
|
|
577
|
-
});
|
|
578
|
-
res.json(result);
|
|
579
|
-
}
|
|
580
|
-
catch (error) {
|
|
581
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
app.post("/api/approvals/respond", async (req, res) => {
|
|
585
|
-
try {
|
|
586
|
-
const { spec_id, approval_type, approved, feedback, timestamp } = req.body;
|
|
587
|
-
if (!spec_id || !approval_type || typeof approved !== 'boolean') {
|
|
588
|
-
return res.status(400).json({
|
|
589
|
-
error: "spec_id, approval_type, and approved (boolean) are required"
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
const result = await processApprovalResponse({
|
|
593
|
-
spec_id,
|
|
594
|
-
approval_type,
|
|
595
|
-
approved,
|
|
596
|
-
feedback,
|
|
597
|
-
timestamp
|
|
598
|
-
});
|
|
599
|
-
// Send real-time notification to dashboard
|
|
600
|
-
notificationManager.addNotification({
|
|
601
|
-
spec_id,
|
|
602
|
-
event: "approval_responded",
|
|
603
|
-
data: {
|
|
604
|
-
approval_type,
|
|
605
|
-
approved,
|
|
606
|
-
feedback,
|
|
607
|
-
new_state: result.new_state,
|
|
608
|
-
approval_id: result.approval_id
|
|
609
|
-
},
|
|
610
|
-
timestamp: result.timestamp
|
|
611
|
-
});
|
|
612
|
-
res.json(result);
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
app.post("/api/approvals/timeout", async (req, res) => {
|
|
619
|
-
try {
|
|
620
|
-
const { spec_id, approval_type } = req.body;
|
|
621
|
-
if (!spec_id || !approval_type) {
|
|
622
|
-
return res.status(400).json({
|
|
623
|
-
error: "spec_id and approval_type are required"
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
const result = await handleApprovalTimeout(spec_id, approval_type);
|
|
627
|
-
// Send real-time notification to dashboard
|
|
628
|
-
notificationManager.addNotification({
|
|
629
|
-
spec_id,
|
|
630
|
-
event: "approval_timeout",
|
|
631
|
-
data: {
|
|
632
|
-
approval_type,
|
|
633
|
-
approval_id: result.approval_id,
|
|
634
|
-
timeout_at: result.timeout_at
|
|
635
|
-
},
|
|
636
|
-
timestamp: new Date().toISOString()
|
|
637
|
-
});
|
|
638
|
-
res.json(result);
|
|
639
|
-
}
|
|
640
|
-
catch (error) {
|
|
641
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
// File monitoring endpoints
|
|
645
|
-
app.get("/api/file-changes", (req, res) => {
|
|
646
|
-
try {
|
|
647
|
-
const limit = parseInt(req.query.limit) || 50;
|
|
648
|
-
const changes = fileMonitor.getRecentChanges(limit);
|
|
649
|
-
res.json({ changes });
|
|
650
|
-
}
|
|
651
|
-
catch (error) {
|
|
652
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
app.get("/api/untracked-changes", (req, res) => {
|
|
656
|
-
try {
|
|
657
|
-
const untracked = fileMonitor.getUntrackedChanges();
|
|
658
|
-
res.json({ untracked });
|
|
659
|
-
}
|
|
660
|
-
catch (error) {
|
|
661
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
app.post("/api/assign-to-spec", async (req, res) => {
|
|
665
|
-
try {
|
|
666
|
-
const { files, spec_id } = req.body;
|
|
667
|
-
await fileMonitor.manuallyAssignToSpec(files, spec_id);
|
|
668
|
-
res.json({ status: "assigned" });
|
|
669
|
-
}
|
|
670
|
-
catch (error) {
|
|
671
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
// Enhanced dashboard data endpoint
|
|
675
|
-
app.get("/api/dashboard-data", async (req, res) => {
|
|
676
|
-
try {
|
|
677
|
-
const specsDir = path.resolve(projectRoot, ".bob/specs");
|
|
678
|
-
const indexPath = path.join(specsDir, "index.json");
|
|
679
|
-
if (!await fs.pathExists(indexPath)) {
|
|
680
|
-
return res.json({ specs: [], untracked: [], recent_changes: [] });
|
|
681
|
-
}
|
|
682
|
-
const indexData = await fs.readJson(indexPath);
|
|
683
|
-
const enhancedSpecs = [];
|
|
684
|
-
for (const specSummary of indexData.specs || []) {
|
|
685
|
-
const specPath = path.join(specsDir, `${specSummary.spec_id}.json`);
|
|
686
|
-
if (await fs.pathExists(specPath)) {
|
|
687
|
-
const specData = await fs.readJson(specPath);
|
|
688
|
-
// Parse implementation plan into task checklist
|
|
689
|
-
const implementationPlan = specData.implementation_plan || [];
|
|
690
|
-
const executionLogs = specData.execution_logs || specData.execution_log || [];
|
|
691
|
-
const taskChecklist = implementationPlan.map((task) => {
|
|
692
|
-
const isCompleted = executionLogs.some((log) => log.task_id === task.task_id);
|
|
693
|
-
const associatedLogs = executionLogs.filter((log) => log.task_id === task.task_id);
|
|
694
|
-
return {
|
|
695
|
-
task_id: task.task_id,
|
|
696
|
-
description: task.description,
|
|
697
|
-
completed: isCompleted,
|
|
698
|
-
execution_count: associatedLogs.length,
|
|
699
|
-
last_updated: associatedLogs.length > 0 ? associatedLogs[associatedLogs.length - 1].timestamp : null
|
|
700
|
-
};
|
|
701
|
-
});
|
|
702
|
-
// Create per-SPEC timeline: Prioritize enhanced_activities, fallback to legacy logs
|
|
703
|
-
const mcpOperations = specData.mcp_tool_operations || [];
|
|
704
|
-
// PRIORITY 1: Enhanced activities (new unified format)
|
|
705
|
-
const enhancedActivitiesTimeline = (specData.enhanced_activities || []).map(async (activity) => {
|
|
706
|
-
// Enrich files with real git diff data
|
|
707
|
-
const enrichedFiles = await Promise.all((activity.files_changed || []).map(async (file) => {
|
|
708
|
-
const gitDiff = await getFileGitDiff(file.path);
|
|
709
|
-
return {
|
|
710
|
-
...file,
|
|
711
|
-
lines_added: gitDiff?.linesAdded || file.lines_added || 0,
|
|
712
|
-
lines_removed: gitDiff?.linesRemoved || file.lines_removed || 0,
|
|
713
|
-
diff_summary: gitDiff?.diffSummary || file.diff_summary || ''
|
|
714
|
-
};
|
|
715
|
-
}));
|
|
716
|
-
return {
|
|
717
|
-
...activity,
|
|
718
|
-
files_changed: enrichedFiles,
|
|
719
|
-
human_description: activity.summary,
|
|
720
|
-
is_enhanced: true // Flag to identify enhanced activities in frontend
|
|
721
|
-
};
|
|
722
|
-
});
|
|
723
|
-
const resolvedEnhancedActivities = await Promise.all(enhancedActivitiesTimeline);
|
|
724
|
-
// PRIORITY 2: Legacy logs (for backward compatibility)
|
|
725
|
-
const legacyTimeline = specData.enhanced_activities && specData.enhanced_activities.length > 0 ? [] : [
|
|
726
|
-
...executionLogs.map((log) => ({
|
|
727
|
-
...log,
|
|
728
|
-
type: 'execution',
|
|
729
|
-
role: 'engineer',
|
|
730
|
-
human_description: generateHumanReadableActivityDescription(log),
|
|
731
|
-
is_enhanced: false
|
|
732
|
-
})),
|
|
733
|
-
...(specData.debug_logs || specData.debug_log || []).map((log) => ({
|
|
734
|
-
...log,
|
|
735
|
-
type: 'debug',
|
|
736
|
-
role: 'debugger',
|
|
737
|
-
human_description: generateHumanReadableActivityDescription(log),
|
|
738
|
-
is_enhanced: false
|
|
739
|
-
})),
|
|
740
|
-
...(specData.activity || []).map((activity) => ({
|
|
741
|
-
...activity,
|
|
742
|
-
type: activity.type || 'activity',
|
|
743
|
-
role: activity.role || 'system',
|
|
744
|
-
action: activity.action || activity.note || 'Activity logged',
|
|
745
|
-
human_description: generateHumanReadableActivityDescription(activity),
|
|
746
|
-
is_enhanced: false
|
|
747
|
-
}))
|
|
748
|
-
];
|
|
749
|
-
// Add MCP operations to timeline
|
|
750
|
-
const mcpTimeline = mcpOperations.map((op) => ({
|
|
751
|
-
timestamp: op.timestamp,
|
|
752
|
-
type: 'mcp_operation',
|
|
753
|
-
role: 'mcp_system',
|
|
754
|
-
tool_name: op.tool_name,
|
|
755
|
-
success: op.success,
|
|
756
|
-
duration_ms: op.duration_ms,
|
|
757
|
-
parameters: op.input_parameters,
|
|
758
|
-
error_message: op.error_message,
|
|
759
|
-
human_description: generateHumanReadableActivityDescription(op),
|
|
760
|
-
is_enhanced: false
|
|
761
|
-
}));
|
|
762
|
-
// Combine and sort by timestamp
|
|
763
|
-
const timeline = [
|
|
764
|
-
...resolvedEnhancedActivities,
|
|
765
|
-
...legacyTimeline,
|
|
766
|
-
...mcpTimeline
|
|
767
|
-
].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
768
|
-
// Get file changes for this SPEC
|
|
769
|
-
const fileChanges = specData.file_changes || [];
|
|
770
|
-
// Get approval information for this SPEC
|
|
771
|
-
let approvalInfo = {
|
|
772
|
-
pending_approvals: [],
|
|
773
|
-
recent_approvals: [],
|
|
774
|
-
approval_history: []
|
|
775
|
-
};
|
|
776
|
-
try {
|
|
777
|
-
const approvalStatus = await checkApprovalStatus(specSummary.spec_id);
|
|
778
|
-
approvalInfo = {
|
|
779
|
-
pending_approvals: approvalStatus.pending_approvals || [],
|
|
780
|
-
recent_approvals: approvalStatus.all_approvals?.filter((a) => a.status === 'approved' || a.status === 'rejected').slice(-3) || [], // Last 3 completed approvals
|
|
781
|
-
approval_history: approvalStatus.all_approvals || []
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
catch (error) {
|
|
785
|
-
console.log(`Failed to get approval status for ${specSummary.spec_id}:`, error);
|
|
786
|
-
}
|
|
787
|
-
// Determine if awaiting user input
|
|
788
|
-
const awaitingApproval = approvalInfo.pending_approvals.length > 0;
|
|
789
|
-
const currentApprovalType = awaitingApproval ? approvalInfo.pending_approvals[0]?.approval_type : null;
|
|
790
|
-
// Get change validation information
|
|
791
|
-
const changeValidations = specData.change_validations || [];
|
|
792
|
-
const latestValidation = changeValidations.length > 0 ? changeValidations[changeValidations.length - 1] : null;
|
|
793
|
-
// Get implementation summaries
|
|
794
|
-
const implementationSummaries = specData.implementation_summaries || [];
|
|
795
|
-
const latestSummary = implementationSummaries.length > 0 ? implementationSummaries[implementationSummaries.length - 1] : null;
|
|
796
|
-
// Process file changes into human-readable format with validation status
|
|
797
|
-
const enhancedFileChanges = await Promise.all(fileChanges.slice(-20).map(async (change) => {
|
|
798
|
-
// Find validation data for this file
|
|
799
|
-
const validationData = latestValidation?.file_analyses?.find((analysis) => analysis.file === change.file);
|
|
800
|
-
// Get git diff information
|
|
801
|
-
const gitDiff = await getFileGitDiff(change.file);
|
|
802
|
-
return {
|
|
803
|
-
...change,
|
|
804
|
-
humanReadable: generateFileChangeDescription(change),
|
|
805
|
-
category: categorizeFileChange(change.file),
|
|
806
|
-
compliance_status: validationData?.compliance || 'unknown',
|
|
807
|
-
compliance_reason: validationData?.reason,
|
|
808
|
-
lines_changed: validationData?.lineCount || (gitDiff ? gitDiff.linesAdded + gitDiff.linesRemoved : 0),
|
|
809
|
-
human_description: validationData?.humanSummary || generateFileChangeDescription(change),
|
|
810
|
-
git_diff: gitDiff ? {
|
|
811
|
-
lines_added: gitDiff.linesAdded,
|
|
812
|
-
lines_removed: gitDiff.linesRemoved,
|
|
813
|
-
diff_summary: gitDiff.diffSummary,
|
|
814
|
-
total_changes: gitDiff.linesAdded + gitDiff.linesRemoved
|
|
815
|
-
} : null
|
|
816
|
-
};
|
|
817
|
-
}));
|
|
818
|
-
// Extract worktree metadata from SPEC
|
|
819
|
-
const worktreeInfo = specData.worktree || {
|
|
820
|
-
branch: '',
|
|
821
|
-
path: '',
|
|
822
|
-
status: 'pending',
|
|
823
|
-
created_at: '',
|
|
824
|
-
removed_at: ''
|
|
825
|
-
};
|
|
826
|
-
enhancedSpecs.push({
|
|
827
|
-
...specSummary,
|
|
828
|
-
task_checklist: taskChecklist,
|
|
829
|
-
timeline,
|
|
830
|
-
file_changes: enhancedFileChanges,
|
|
831
|
-
completion_percentage: Math.round((taskChecklist.filter((t) => t.completed).length / Math.max(taskChecklist.length, 1)) * 100),
|
|
832
|
-
approval_info: approvalInfo,
|
|
833
|
-
awaiting_approval: awaitingApproval,
|
|
834
|
-
current_approval_type: currentApprovalType,
|
|
835
|
-
user_action_required: awaitingApproval,
|
|
836
|
-
worktree: {
|
|
837
|
-
branch: worktreeInfo.branch,
|
|
838
|
-
path: worktreeInfo.path,
|
|
839
|
-
status: worktreeInfo.status,
|
|
840
|
-
created_at: worktreeInfo.created_at,
|
|
841
|
-
removed_at: worktreeInfo.removed_at,
|
|
842
|
-
is_active: worktreeInfo.status === 'active',
|
|
843
|
-
display_path: worktreeInfo.path ? worktreeInfo.path.replace(process.cwd(), '.') : ''
|
|
844
|
-
},
|
|
845
|
-
change_validation: latestValidation ? {
|
|
846
|
-
overall_compliance: latestValidation.overall_compliance,
|
|
847
|
-
last_validated: latestValidation.timestamp,
|
|
848
|
-
file_analyses: latestValidation.file_analyses || [],
|
|
849
|
-
human_readable_changes: latestValidation.summary?.human_readable_changes || [],
|
|
850
|
-
recommendations: latestValidation.recommendations || []
|
|
851
|
-
} : null,
|
|
852
|
-
implementation_summary: latestSummary ? {
|
|
853
|
-
period_start: latestSummary.period_start,
|
|
854
|
-
period_end: latestSummary.period_end,
|
|
855
|
-
files_changed_count: latestSummary.files_changed?.length || 0,
|
|
856
|
-
implementation_notes: latestSummary.implementation_notes || [],
|
|
857
|
-
compliance_overview: latestSummary.compliance_overview || {
|
|
858
|
-
total_files: 0,
|
|
859
|
-
compliant_files: 0,
|
|
860
|
-
deviation_files: 0,
|
|
861
|
-
unexpected_files: 0,
|
|
862
|
-
overall_status: 'unknown'
|
|
863
|
-
},
|
|
864
|
-
actionable_insights: latestSummary.actionable_insights || [],
|
|
865
|
-
git_commits_count: latestSummary.git_commits?.length || 0
|
|
866
|
-
} : null
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
// Get untracked changes
|
|
871
|
-
const untrackedChanges = fileMonitor.getUntrackedChanges();
|
|
872
|
-
const recentChanges = fileMonitor.getRecentChanges(50);
|
|
873
|
-
// Get recent notifications and convert them to dashboard format
|
|
874
|
-
const recentNotifications = notificationManager.getRecentNotifications(20);
|
|
875
|
-
const notificationChanges = recentNotifications.map(notification => ({
|
|
876
|
-
type: 'notification',
|
|
877
|
-
path: `[${notification.event}]`,
|
|
878
|
-
timestamp: notification.timestamp,
|
|
879
|
-
spec_id: notification.spec_id,
|
|
880
|
-
event: notification.event,
|
|
881
|
-
data: notification.data,
|
|
882
|
-
role: notification.data?.role || 'system'
|
|
883
|
-
}));
|
|
884
|
-
// Get MCP tool operations from all SPECs for recent activity
|
|
885
|
-
const mcpOperationChanges = [];
|
|
886
|
-
for (const spec of enhancedSpecs) {
|
|
887
|
-
const mcpOps = spec.timeline?.filter((item) => item.type === 'mcp_operation') || [];
|
|
888
|
-
mcpOperationChanges.push(...mcpOps.slice(-5).map((op) => ({
|
|
889
|
-
type: 'mcp_operation',
|
|
890
|
-
path: `[MCP] ${op.tool_name}`,
|
|
891
|
-
timestamp: op.timestamp,
|
|
892
|
-
spec_id: spec.spec_id,
|
|
893
|
-
event: op.tool_name,
|
|
894
|
-
tool_name: op.tool_name,
|
|
895
|
-
success: op.success,
|
|
896
|
-
duration_ms: op.duration_ms,
|
|
897
|
-
parameters: op.parameters,
|
|
898
|
-
role: 'mcp_system'
|
|
899
|
-
})));
|
|
900
|
-
}
|
|
901
|
-
// Combine file changes, notifications, and MCP operations, sort by timestamp
|
|
902
|
-
const allChanges = [
|
|
903
|
-
...recentChanges.map((change) => ({ ...change, type: 'file' })),
|
|
904
|
-
...notificationChanges,
|
|
905
|
-
...mcpOperationChanges
|
|
906
|
-
].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, 50);
|
|
907
|
-
// Sort specs by updated_at descending (latest first)
|
|
908
|
-
const sortedSpecs = enhancedSpecs.sort((a, b) => new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime());
|
|
909
|
-
res.json({
|
|
910
|
-
specs: sortedSpecs,
|
|
911
|
-
untracked: untrackedChanges,
|
|
912
|
-
recent_changes: allChanges,
|
|
913
|
-
timestamp: new Date().toISOString()
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
catch (error) {
|
|
917
|
-
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
// Dashboard notification endpoints
|
|
921
|
-
app.post("/api/updates", async (req, res) => {
|
|
922
|
-
try {
|
|
923
|
-
const { spec_id, event, data, timestamp, enhanced_activity } = req.body;
|
|
924
|
-
if (!event) {
|
|
925
|
-
return res.status(400).json({ error: "Event is required" });
|
|
926
|
-
}
|
|
927
|
-
const notification = notificationManager.addNotification({
|
|
928
|
-
spec_id,
|
|
929
|
-
event,
|
|
930
|
-
data: data || {},
|
|
931
|
-
timestamp: timestamp || new Date().toISOString()
|
|
932
|
-
});
|
|
933
|
-
// Note: Enhanced activities are persisted via bob.manual.update only
|
|
934
|
-
// This endpoint is just for real-time UI notifications
|
|
935
|
-
res.json({
|
|
936
|
-
status: "notification_received",
|
|
937
|
-
notification_id: notification.id
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
catch (error) {
|
|
941
|
-
res.status(500).json({
|
|
942
|
-
error: error instanceof Error ? error.message : String(error)
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
app.get("/api/notifications", (req, res) => {
|
|
947
|
-
try {
|
|
948
|
-
const limit = parseInt(req.query.limit) || 50;
|
|
949
|
-
const spec_id = req.query.spec_id;
|
|
950
|
-
let notifications;
|
|
951
|
-
if (spec_id) {
|
|
952
|
-
notifications = notificationManager.getNotificationsBySpec(spec_id, limit);
|
|
953
|
-
}
|
|
954
|
-
else {
|
|
955
|
-
notifications = notificationManager.getRecentNotifications(limit);
|
|
956
|
-
}
|
|
957
|
-
res.json({
|
|
958
|
-
notifications,
|
|
959
|
-
count: notifications.length,
|
|
960
|
-
timestamp: new Date().toISOString()
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
catch (error) {
|
|
964
|
-
res.status(500).json({
|
|
965
|
-
error: error instanceof Error ? error.message : String(error)
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
app.post("/api/notifications/cleanup", (req, res) => {
|
|
970
|
-
try {
|
|
971
|
-
const hours = parseInt(req.body.hours) || 24;
|
|
972
|
-
const removed = notificationManager.clearOldNotifications(hours);
|
|
973
|
-
res.json({
|
|
974
|
-
status: "cleanup_completed",
|
|
975
|
-
removed_count: removed
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
catch (error) {
|
|
979
|
-
res.status(500).json({
|
|
980
|
-
error: error instanceof Error ? error.message : String(error)
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
// SSE endpoint for real-time events
|
|
985
|
-
app.get('/api/events/stream', (req, res) => {
|
|
986
|
-
res.writeHead(200, {
|
|
987
|
-
'Content-Type': 'text/event-stream',
|
|
988
|
-
'Cache-Control': 'no-cache',
|
|
989
|
-
'Connection': 'keep-alive',
|
|
990
|
-
'Access-Control-Allow-Origin': '*'
|
|
991
|
-
});
|
|
992
|
-
// Send initial connection event
|
|
993
|
-
res.write(`data: ${JSON.stringify({ type: 'connected', timestamp: new Date().toISOString() })}\n\n`);
|
|
994
|
-
// Subscribe to logical operations
|
|
995
|
-
const handleOperation = (operation) => {
|
|
996
|
-
res.write(`data: ${JSON.stringify(operation)}\n\n`);
|
|
997
|
-
};
|
|
998
|
-
mcpLogger.eventEmitter.on('logical_operation_start', handleOperation);
|
|
999
|
-
mcpLogger.eventEmitter.on('logical_operation_update', handleOperation);
|
|
1000
|
-
mcpLogger.eventEmitter.on('logical_operation_complete', handleOperation);
|
|
1001
|
-
// Cleanup on disconnect
|
|
1002
|
-
req.on('close', () => {
|
|
1003
|
-
mcpLogger.eventEmitter.off('logical_operation_start', handleOperation);
|
|
1004
|
-
mcpLogger.eventEmitter.off('logical_operation_update', handleOperation);
|
|
1005
|
-
mcpLogger.eventEmitter.off('logical_operation_complete', handleOperation);
|
|
1006
|
-
});
|
|
1007
|
-
// Keep connection alive
|
|
1008
|
-
const keepAlive = setInterval(() => {
|
|
1009
|
-
res.write(': keepalive\n\n');
|
|
1010
|
-
}, 30000);
|
|
1011
|
-
req.on('close', () => clearInterval(keepAlive));
|
|
1012
|
-
});
|
|
1013
|
-
// Health check
|
|
1014
|
-
// MCP logs endpoint for activity timeline
|
|
1015
|
-
app.get("/api/mcp-logs", (req, res) => {
|
|
1016
|
-
const specId = req.query.spec_id;
|
|
1017
|
-
const logs = mcpLogger.getLogs(specId);
|
|
1018
|
-
res.json({ logs });
|
|
1019
|
-
});
|
|
1020
|
-
// Task Logger API Endpoints
|
|
1021
|
-
// GET /api/tasks - List all tasks
|
|
1022
|
-
app.get("/api/tasks", (req, res) => {
|
|
1023
|
-
try {
|
|
1024
|
-
const { priority, status, search } = req.query;
|
|
1025
|
-
let tasks;
|
|
1026
|
-
if (priority) {
|
|
1027
|
-
tasks = taskService.getTasksByPriority(priority);
|
|
1028
|
-
}
|
|
1029
|
-
else if (status) {
|
|
1030
|
-
tasks = taskService.getTasksByStatus(status);
|
|
1031
|
-
}
|
|
1032
|
-
else if (search) {
|
|
1033
|
-
tasks = taskService.searchTasks(search);
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
tasks = taskService.getAllTasks();
|
|
1037
|
-
}
|
|
1038
|
-
res.json(createSuccessResponse({
|
|
1039
|
-
tasks,
|
|
1040
|
-
count: tasks.length,
|
|
1041
|
-
total: taskService.getTaskCount()
|
|
1042
|
-
}));
|
|
1043
|
-
}
|
|
1044
|
-
catch (error) {
|
|
1045
|
-
console.error("[TaskLogger API] Error listing tasks:", error);
|
|
1046
|
-
res.status(500).json(createErrorResponse("Failed to list tasks", 500));
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
// POST /api/tasks - Create new task
|
|
1050
|
-
app.post("/api/tasks", (req, res) => {
|
|
1051
|
-
try {
|
|
1052
|
-
const task = taskService.createTask(req.body);
|
|
1053
|
-
res.status(201).json(createSuccessResponse(task, "Task created successfully"));
|
|
1054
|
-
}
|
|
1055
|
-
catch (error) {
|
|
1056
|
-
console.error("[TaskLogger API] Error creating task:", error);
|
|
1057
|
-
if (error instanceof Error && error.message.includes("validation")) {
|
|
1058
|
-
res.status(400).json(createErrorResponse(error.message));
|
|
1059
|
-
}
|
|
1060
|
-
else {
|
|
1061
|
-
res.status(500).json(createErrorResponse("Failed to create task", 500));
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
// GET /api/tasks/:id - Get specific task
|
|
1066
|
-
app.get("/api/tasks/:id", (req, res) => {
|
|
1067
|
-
try {
|
|
1068
|
-
const task = taskService.getTaskById(req.params.id);
|
|
1069
|
-
if (!task) {
|
|
1070
|
-
return res.status(404).json(createErrorResponse("Task not found", 404));
|
|
1071
|
-
}
|
|
1072
|
-
res.json(createSuccessResponse(task));
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
console.error("[TaskLogger API] Error getting task:", error);
|
|
1076
|
-
res.status(500).json(createErrorResponse("Failed to get task", 500));
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
|
-
// PUT /api/tasks/:id - Update task
|
|
1080
|
-
app.put("/api/tasks/:id", (req, res) => {
|
|
1081
|
-
try {
|
|
1082
|
-
const task = taskService.updateTask(req.params.id, req.body);
|
|
1083
|
-
if (!task) {
|
|
1084
|
-
return res.status(404).json(createErrorResponse("Task not found", 404));
|
|
1085
|
-
}
|
|
1086
|
-
res.json(createSuccessResponse(task, "Task updated successfully"));
|
|
1087
|
-
}
|
|
1088
|
-
catch (error) {
|
|
1089
|
-
console.error("[TaskLogger API] Error updating task:", error);
|
|
1090
|
-
if (error instanceof Error && error.message.includes("validation")) {
|
|
1091
|
-
res.status(400).json(createErrorResponse(error.message));
|
|
1092
|
-
}
|
|
1093
|
-
else {
|
|
1094
|
-
res.status(500).json(createErrorResponse("Failed to update task", 500));
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
});
|
|
1098
|
-
// DELETE /api/tasks/:id - Delete task
|
|
1099
|
-
app.delete("/api/tasks/:id", (req, res) => {
|
|
1100
|
-
try {
|
|
1101
|
-
const deleted = taskService.deleteTask(req.params.id);
|
|
1102
|
-
if (!deleted) {
|
|
1103
|
-
return res.status(404).json(createErrorResponse("Task not found", 404));
|
|
1104
|
-
}
|
|
1105
|
-
res.json(createSuccessResponse(null, "Task deleted successfully"));
|
|
1106
|
-
}
|
|
1107
|
-
catch (error) {
|
|
1108
|
-
console.error("[TaskLogger API] Error deleting task:", error);
|
|
1109
|
-
res.status(500).json(createErrorResponse("Failed to delete task", 500));
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
app.get("/api/git-diff", async (req, res) => {
|
|
1113
|
-
try {
|
|
1114
|
-
const { file, commit } = req.query;
|
|
1115
|
-
if (!file) {
|
|
1116
|
-
return res.status(400).json({ error: "File parameter is required" });
|
|
1117
|
-
}
|
|
1118
|
-
const gitDiff = await getFileGitDiff(file);
|
|
1119
|
-
if (gitDiff) {
|
|
1120
|
-
res.json({
|
|
1121
|
-
file: file,
|
|
1122
|
-
lines_added: gitDiff.linesAdded,
|
|
1123
|
-
lines_removed: gitDiff.linesRemoved,
|
|
1124
|
-
diff_summary: gitDiff.diffSummary,
|
|
1125
|
-
total_changes: gitDiff.linesAdded + gitDiff.linesRemoved
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
else {
|
|
1129
|
-
res.json({
|
|
1130
|
-
file: file,
|
|
1131
|
-
lines_added: 0,
|
|
1132
|
-
lines_removed: 0,
|
|
1133
|
-
diff_summary: "",
|
|
1134
|
-
total_changes: 0
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
catch (error) {
|
|
1139
|
-
console.error("Error getting git diff:", error);
|
|
1140
|
-
res.status(500).json({ error: "Failed to get git diff" });
|
|
1141
|
-
}
|
|
1142
|
-
});
|
|
1143
|
-
// Serve MCP manifest at /.well-known/mcp/manifest.json
|
|
1144
|
-
app.get("/.well-known/mcp/manifest.json", async (req, res) => {
|
|
1145
|
-
try {
|
|
1146
|
-
const manifestPath = path.join(projectRoot, "public", ".well-known", "mcp", "manifest.json");
|
|
1147
|
-
if (!await fs.pathExists(manifestPath)) {
|
|
1148
|
-
return res.status(404).json({ error: "Manifest not found" });
|
|
1149
|
-
}
|
|
1150
|
-
const manifest = await fs.readJson(manifestPath);
|
|
1151
|
-
// Set appropriate headers for manifest serving
|
|
1152
|
-
res.setHeader('Content-Type', 'application/json');
|
|
1153
|
-
res.setHeader('Cache-Control', 'public, max-age=300'); // 5 minutes cache
|
|
1154
|
-
res.json(manifest);
|
|
1155
|
-
}
|
|
1156
|
-
catch (error) {
|
|
1157
|
-
console.error("Error serving manifest:", error);
|
|
1158
|
-
res.status(500).json({ error: "Failed to serve manifest" });
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1161
|
-
// Project info endpoint - returns current repository name based on process.cwd()
|
|
1162
|
-
app.get("/api/project-info", (req, res) => {
|
|
1163
|
-
try {
|
|
1164
|
-
const cwd = process.cwd();
|
|
1165
|
-
const repoName = path.basename(cwd);
|
|
1166
|
-
res.json({
|
|
1167
|
-
repoName,
|
|
1168
|
-
workingDirectory: cwd
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
catch (error) {
|
|
1172
|
-
res.status(500).json({
|
|
1173
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1174
|
-
repoName: 'unknown',
|
|
1175
|
-
workingDirectory: process.cwd()
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
});
|
|
1179
|
-
app.get("/api/health", (req, res) => {
|
|
1180
|
-
res.json({ status: "ok", uptime: process.uptime() });
|
|
1181
|
-
});
|
|
1182
|
-
return app;
|
|
1183
|
-
}
|
|
1184
|
-
// Start server when run directly
|
|
1185
|
-
const isMainModule = () => {
|
|
1186
|
-
// Don't auto-start in test environments
|
|
1187
|
-
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
1188
|
-
return false;
|
|
1189
|
-
}
|
|
1190
|
-
// Check if import.meta is available
|
|
1191
|
-
if (typeof import.meta !== 'undefined' && import.meta.url) {
|
|
1192
|
-
return import.meta.url === `file://${process.argv[1]}`;
|
|
1193
|
-
}
|
|
1194
|
-
return false;
|
|
1195
|
-
};
|
|
1196
|
-
if (isMainModule()) {
|
|
1197
|
-
const app = createDashboardServer();
|
|
1198
|
-
const port = 4577; // Match the expected dashboard port
|
|
1199
|
-
app.listen(port, () => {
|
|
1200
|
-
console.log(`Bob's Workshop dashboard running on http://localhost:${port}`);
|
|
1201
|
-
});
|
|
1202
|
-
} // Test git diff functionality - timestamp: Sun 28 Sep 2025 19:38:38 IST
|
|
1203
|
-
//# sourceMappingURL=server.js.map
|