@visualprd/mcp-server 1.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/README.md +396 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/dist/intelligence/context-optimizer.d.ts +93 -0
- package/dist/intelligence/context-optimizer.d.ts.map +1 -0
- package/dist/intelligence/context-optimizer.js +481 -0
- package/dist/intelligence/context-optimizer.js.map +1 -0
- package/dist/intelligence/error-analyzer.d.ts +49 -0
- package/dist/intelligence/error-analyzer.d.ts.map +1 -0
- package/dist/intelligence/error-analyzer.js +765 -0
- package/dist/intelligence/error-analyzer.js.map +1 -0
- package/dist/intelligence/gap-filler.d.ts +56 -0
- package/dist/intelligence/gap-filler.d.ts.map +1 -0
- package/dist/intelligence/gap-filler.js +410 -0
- package/dist/intelligence/gap-filler.js.map +1 -0
- package/dist/intelligence/guidance-generator.d.ts +43 -0
- package/dist/intelligence/guidance-generator.d.ts.map +1 -0
- package/dist/intelligence/guidance-generator.js +314 -0
- package/dist/intelligence/guidance-generator.js.map +1 -0
- package/dist/intelligence/index.d.ts +132 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/index.js +683 -0
- package/dist/intelligence/index.js.map +1 -0
- package/dist/server-http.d.ts +9 -0
- package/dist/server-http.d.ts.map +1 -0
- package/dist/server-http.js +141 -0
- package/dist/server-http.js.map +1 -0
- package/dist/services/api-key-service.d.ts +68 -0
- package/dist/services/api-key-service.d.ts.map +1 -0
- package/dist/services/api-key-service.js +298 -0
- package/dist/services/api-key-service.js.map +1 -0
- package/dist/services/llm-client.d.ts +66 -0
- package/dist/services/llm-client.d.ts.map +1 -0
- package/dist/services/llm-client.js +141 -0
- package/dist/services/llm-client.js.map +1 -0
- package/dist/services/model-registry.d.ts +135 -0
- package/dist/services/model-registry.d.ts.map +1 -0
- package/dist/services/model-registry.js +276 -0
- package/dist/services/model-registry.js.map +1 -0
- package/dist/services/visualprd-client.d.ts +191 -0
- package/dist/services/visualprd-client.d.ts.map +1 -0
- package/dist/services/visualprd-client.js +805 -0
- package/dist/services/visualprd-client.js.map +1 -0
- package/dist/tools/index.d.ts +803 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +570 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +497 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Intelligence Layer
|
|
4
|
+
*
|
|
5
|
+
* The brain of the MCP server - orchestrates context optimization,
|
|
6
|
+
* error analysis, gap filling, and guidance generation.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.GuidanceGenerator = exports.GapFiller = exports.ErrorAnalyzer = exports.ContextOptimizer = exports.IntelligenceLayer = void 0;
|
|
10
|
+
const context_optimizer_js_1 = require("./context-optimizer.js");
|
|
11
|
+
const error_analyzer_js_1 = require("./error-analyzer.js");
|
|
12
|
+
const gap_filler_js_1 = require("./gap-filler.js");
|
|
13
|
+
const guidance_generator_js_1 = require("./guidance-generator.js");
|
|
14
|
+
const llm_client_js_1 = require("../services/llm-client.js");
|
|
15
|
+
class IntelligenceLayer {
|
|
16
|
+
contextOptimizer;
|
|
17
|
+
errorAnalyzer;
|
|
18
|
+
gapFiller;
|
|
19
|
+
guidanceGenerator;
|
|
20
|
+
client;
|
|
21
|
+
llmClient = null;
|
|
22
|
+
config;
|
|
23
|
+
// Intelligence cache
|
|
24
|
+
cache = new Map();
|
|
25
|
+
CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
26
|
+
constructor(client, config) {
|
|
27
|
+
this.client = client;
|
|
28
|
+
this.config = config || { projectId: '', apiKey: '', apiUrl: '' };
|
|
29
|
+
this.contextOptimizer = new context_optimizer_js_1.ContextOptimizer();
|
|
30
|
+
this.errorAnalyzer = new error_analyzer_js_1.ErrorAnalyzer();
|
|
31
|
+
this.gapFiller = new gap_filler_js_1.GapFiller();
|
|
32
|
+
this.guidanceGenerator = new guidance_generator_js_1.GuidanceGenerator();
|
|
33
|
+
// Initialize LLM client if API key is available
|
|
34
|
+
const openRouterKey = process.env.OPENROUTER_API_KEY;
|
|
35
|
+
if (openRouterKey) {
|
|
36
|
+
this.llmClient = new llm_client_js_1.LLMClient({
|
|
37
|
+
apiKey: openRouterKey,
|
|
38
|
+
model: config?.thinkingModel || 'anthropic/claude-3.5-sonnet',
|
|
39
|
+
});
|
|
40
|
+
console.error(`[Intelligence] LLM initialized with model: ${this.llmClient.getModel()}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error('[Intelligence] No OPENROUTER_API_KEY - running in rule-based mode only');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Update the thinking model dynamically
|
|
48
|
+
*/
|
|
49
|
+
setThinkingModel(model) {
|
|
50
|
+
if (this.llmClient) {
|
|
51
|
+
this.llmClient.setModel(model);
|
|
52
|
+
this.config.thinkingModel = model;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error('[Intelligence] Cannot set model - LLM client not initialized');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set intelligence mode (fast = rule-based, deep = AI)
|
|
60
|
+
*/
|
|
61
|
+
setIntelligenceMode(mode) {
|
|
62
|
+
this.config.intelligenceMode = mode;
|
|
63
|
+
console.error(`[Intelligence] Mode set to: ${mode}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get the underlying VisualPRD client for direct V2 method access
|
|
67
|
+
*/
|
|
68
|
+
getClient() {
|
|
69
|
+
return this.client;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if AI mode is available and enabled
|
|
73
|
+
*/
|
|
74
|
+
isAIModeEnabled() {
|
|
75
|
+
return this.llmClient !== null && this.config.intelligenceMode === 'deep';
|
|
76
|
+
}
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// MAIN INTELLIGENCE METHODS
|
|
79
|
+
// ============================================================================
|
|
80
|
+
/**
|
|
81
|
+
* Get the next build step with full intelligent context
|
|
82
|
+
*/
|
|
83
|
+
async getNextBuildStep() {
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
// 1. Load project data
|
|
86
|
+
const projectData = await this.client.loadProjectData();
|
|
87
|
+
// 2. Get next available prompt
|
|
88
|
+
const nextPrompt = await this.client.getNextBuildPrompt();
|
|
89
|
+
if (!nextPrompt) {
|
|
90
|
+
// All steps complete!
|
|
91
|
+
return {
|
|
92
|
+
step: null,
|
|
93
|
+
relatedEntities: {
|
|
94
|
+
pages: [],
|
|
95
|
+
schemas: [],
|
|
96
|
+
endpoints: [],
|
|
97
|
+
techStack: [],
|
|
98
|
+
},
|
|
99
|
+
contextualGuidance: {
|
|
100
|
+
technicalConsiderations: [],
|
|
101
|
+
potentialIssues: [],
|
|
102
|
+
testingCriteria: [],
|
|
103
|
+
},
|
|
104
|
+
buildProgress: await this.client.getBuildProgress(),
|
|
105
|
+
message: '🎉 All build steps complete! Your project is ready.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// 3. Decide on intelligence tier for this request
|
|
109
|
+
const decision = this.selectIntelligenceTier('get_next_step', nextPrompt);
|
|
110
|
+
// 4. Optimize context for this prompt
|
|
111
|
+
const optimizedContext = await this.contextOptimizer.optimizeContext(nextPrompt, projectData);
|
|
112
|
+
// 5. Analyze for gaps (lightweight)
|
|
113
|
+
const gapReport = await this.gapFiller.analyzeGaps(nextPrompt, projectData);
|
|
114
|
+
// 6. Generate guidance (rule-based or AI-powered)
|
|
115
|
+
let guidance;
|
|
116
|
+
if (this.isAIModeEnabled()) {
|
|
117
|
+
guidance = await this.generateAIGuidance(nextPrompt, projectData, optimizedContext, gapReport);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
guidance = this.guidanceGenerator.generateGuidance({
|
|
121
|
+
prompt: nextPrompt,
|
|
122
|
+
projectData,
|
|
123
|
+
relatedPages: optimizedContext.pages,
|
|
124
|
+
relatedSchemas: optimizedContext.schemas,
|
|
125
|
+
relatedEndpoints: optimizedContext.endpoints,
|
|
126
|
+
relatedTechStack: optimizedContext.techStack,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// 7. Add gap warnings to guidance
|
|
130
|
+
if (gapReport.gaps.length > 0) {
|
|
131
|
+
const highPriorityGaps = gapReport.gaps.filter(g => g.severity === 'high');
|
|
132
|
+
if (highPriorityGaps.length > 0) {
|
|
133
|
+
guidance.potentialIssues.unshift(...highPriorityGaps.map(g => `⚠️ ${g.description}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 8. Get build progress
|
|
137
|
+
const buildProgress = await this.client.getBuildProgress();
|
|
138
|
+
const processingTime = Date.now() - startTime;
|
|
139
|
+
return {
|
|
140
|
+
step: nextPrompt,
|
|
141
|
+
relatedEntities: {
|
|
142
|
+
pages: optimizedContext.pages,
|
|
143
|
+
schemas: optimizedContext.schemas,
|
|
144
|
+
endpoints: optimizedContext.endpoints,
|
|
145
|
+
techStack: optimizedContext.techStack,
|
|
146
|
+
designSystem: optimizedContext.designSystem,
|
|
147
|
+
},
|
|
148
|
+
contextualGuidance: guidance,
|
|
149
|
+
buildProgress,
|
|
150
|
+
message: `Ready to work on step ${buildProgress.currentStep} of ${buildProgress.totalSteps}: "${nextPrompt.title}"`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Validate and mark a step as complete
|
|
155
|
+
*/
|
|
156
|
+
async markStepComplete(promptId, completionData) {
|
|
157
|
+
const validationIssues = [];
|
|
158
|
+
// 1. Get project data and prompt
|
|
159
|
+
const projectData = await this.client.loadProjectData();
|
|
160
|
+
const prompt = projectData.buildPrompts.find(p => p.promptId === promptId);
|
|
161
|
+
if (!prompt) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
validationIssues: [{ type: 'error', message: 'Prompt not found' }],
|
|
165
|
+
message: `Could not find prompt with ID: ${promptId}`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// 2. Validate completion (lightweight checks)
|
|
169
|
+
if (!completionData.filesCreated?.length) {
|
|
170
|
+
validationIssues.push({
|
|
171
|
+
type: 'warning',
|
|
172
|
+
message: 'No files were created - are you sure this step is complete?'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (completionData.testResults?.failed && completionData.testResults.failed > 0) {
|
|
176
|
+
validationIssues.push({
|
|
177
|
+
type: 'warning',
|
|
178
|
+
message: `${completionData.testResults.failed} tests are failing - consider fixing before marking complete`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// 3. Check test criteria (if any)
|
|
182
|
+
if (prompt.testCriteria?.length && !completionData.completionNotes) {
|
|
183
|
+
validationIssues.push({
|
|
184
|
+
type: 'warning',
|
|
185
|
+
message: 'This step has test criteria - please provide completion notes about how they were met'
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// 4. Mark complete (even with warnings - user can override)
|
|
189
|
+
const success = await this.client.markPromptComplete(promptId, completionData);
|
|
190
|
+
if (!success) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
validationIssues: [{ type: 'error', message: 'Failed to update database' }],
|
|
194
|
+
message: 'Could not mark step as complete. Please try again.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// 5. Get next step automatically
|
|
198
|
+
const nextStepResponse = await this.getNextBuildStep();
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
validationIssues,
|
|
202
|
+
nextStep: nextStepResponse,
|
|
203
|
+
message: validationIssues.length > 0
|
|
204
|
+
? `✅ Step "${prompt.title}" marked complete with ${validationIssues.length} warning(s).`
|
|
205
|
+
: `✅ Step "${prompt.title}" marked complete!`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Analyze an error and provide fixes
|
|
210
|
+
*/
|
|
211
|
+
async analyzeError(error) {
|
|
212
|
+
// 1. Get project data and prompt
|
|
213
|
+
const projectData = await this.client.loadProjectData();
|
|
214
|
+
const prompt = projectData.buildPrompts.find(p => p.promptId === error.promptId);
|
|
215
|
+
if (!prompt) {
|
|
216
|
+
return {
|
|
217
|
+
diagnosis: {
|
|
218
|
+
rootCause: 'Unknown prompt',
|
|
219
|
+
explanation: `Could not find prompt ${error.promptId} for context`,
|
|
220
|
+
errorCategory: error.errorType,
|
|
221
|
+
severity: 'medium',
|
|
222
|
+
},
|
|
223
|
+
suggestedFixes: [{
|
|
224
|
+
approach: 'Provide Context',
|
|
225
|
+
steps: ['Ensure the promptId is correct', 'Try again with valid prompt ID'],
|
|
226
|
+
confidence: 0.5,
|
|
227
|
+
}],
|
|
228
|
+
relatedDocs: {},
|
|
229
|
+
shouldUncheckStep: false,
|
|
230
|
+
shouldInjectNewStep: false,
|
|
231
|
+
message: 'Could not find prompt for error context.',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// 2. Analyze the error
|
|
235
|
+
const analysis = await this.errorAnalyzer.analyzeError(error, {
|
|
236
|
+
prompt,
|
|
237
|
+
projectData,
|
|
238
|
+
});
|
|
239
|
+
// 3. Should we inject a new step?
|
|
240
|
+
if (analysis.shouldInjectNewStep && analysis.newStep) {
|
|
241
|
+
const newStep = await this.client.injectBuildPrompt(error.promptId, {
|
|
242
|
+
title: analysis.newStep.title || 'Fix Required Issue',
|
|
243
|
+
category: analysis.newStep.category || 'bugfix',
|
|
244
|
+
instruction: analysis.newStep.instruction || 'Address the error before continuing',
|
|
245
|
+
reason: `Automatically injected due to error: ${error.errorMessage.substring(0, 100)}`,
|
|
246
|
+
});
|
|
247
|
+
if (newStep) {
|
|
248
|
+
analysis.newStep = newStep;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// 4. Should we uncheck current step?
|
|
252
|
+
if (analysis.shouldUncheckStep) {
|
|
253
|
+
await this.client.uncheckPrompt(error.promptId);
|
|
254
|
+
}
|
|
255
|
+
// 5. Log the error
|
|
256
|
+
await this.client.logError('current-session', // TODO: Get actual session ID
|
|
257
|
+
error.promptId, error, analysis.diagnosis);
|
|
258
|
+
return {
|
|
259
|
+
...analysis,
|
|
260
|
+
message: this.formatErrorMessage(analysis),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Get detailed information about an entity
|
|
265
|
+
*/
|
|
266
|
+
async getEntityDetails(entityType, entityId, options = {}) {
|
|
267
|
+
const projectData = await this.client.loadProjectData();
|
|
268
|
+
let entity;
|
|
269
|
+
// Find the entity
|
|
270
|
+
switch (entityType) {
|
|
271
|
+
case 'page':
|
|
272
|
+
entity = await this.client.getPage(entityId);
|
|
273
|
+
break;
|
|
274
|
+
case 'schema':
|
|
275
|
+
entity = await this.client.getSchema(entityId);
|
|
276
|
+
break;
|
|
277
|
+
case 'endpoint':
|
|
278
|
+
entity = await this.client.getEndpoint(entityId);
|
|
279
|
+
break;
|
|
280
|
+
case 'tech':
|
|
281
|
+
entity = await this.client.getTechStackItem(entityId);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
if (!entity) {
|
|
285
|
+
throw new Error(`${entityType} with ID "${entityId}" not found`);
|
|
286
|
+
}
|
|
287
|
+
const result = { entity, entityType };
|
|
288
|
+
// Get relationships if requested
|
|
289
|
+
if (options.includeRelationships && entityType === 'schema') {
|
|
290
|
+
const schema = entity;
|
|
291
|
+
result.relationships = schema.relationships || [];
|
|
292
|
+
// Find reverse relationships
|
|
293
|
+
const reverseRels = projectData.schemas
|
|
294
|
+
.filter(s => s.id !== entity.id)
|
|
295
|
+
.flatMap(s => (s.relationships || [])
|
|
296
|
+
.filter(r => this.normalize(r.target) === this.normalize(schema.collectionName))
|
|
297
|
+
.map(r => ({
|
|
298
|
+
...r,
|
|
299
|
+
source: s.collectionName,
|
|
300
|
+
})));
|
|
301
|
+
result.relationships.push(...reverseRels);
|
|
302
|
+
}
|
|
303
|
+
// Get usage information
|
|
304
|
+
if (options.includeRelationships) {
|
|
305
|
+
const usedBy = {};
|
|
306
|
+
// Find prompts that reference this entity
|
|
307
|
+
const relatedPrompts = projectData.buildPrompts.filter(p => {
|
|
308
|
+
const refs = p.relatedDocuments || {};
|
|
309
|
+
switch (entityType) {
|
|
310
|
+
case 'page':
|
|
311
|
+
return refs.pages?.some(ref => this.normalize(ref) === this.normalize(entityId));
|
|
312
|
+
case 'schema':
|
|
313
|
+
return refs.schemas?.some(ref => this.normalize(ref) === this.normalize(entityId));
|
|
314
|
+
case 'endpoint':
|
|
315
|
+
return refs.endpoints?.some(ref => this.normalize(ref) === this.normalize(entityId));
|
|
316
|
+
case 'tech':
|
|
317
|
+
return refs.techStack?.some(ref => this.normalize(ref) === this.normalize(entityId));
|
|
318
|
+
default:
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
if (relatedPrompts.length > 0) {
|
|
323
|
+
usedBy.prompts = relatedPrompts.map(p => p.title);
|
|
324
|
+
}
|
|
325
|
+
result.usedBy = usedBy;
|
|
326
|
+
}
|
|
327
|
+
// Generate usage examples if requested
|
|
328
|
+
if (options.includeUsageExamples) {
|
|
329
|
+
result.usageExamples = this.generateUsageExamples(entityType, entity);
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Get debugging suggestions when stuck
|
|
335
|
+
*/
|
|
336
|
+
async getDebugSuggestions(currentTask, issue, context) {
|
|
337
|
+
const projectData = await this.client.loadProjectData();
|
|
338
|
+
const suggestions = [];
|
|
339
|
+
const fullContext = `${currentTask} ${issue} ${context || ''}`.toLowerCase();
|
|
340
|
+
// Pattern-based suggestions
|
|
341
|
+
if (fullContext.includes('not loading') || fullContext.includes("doesn't load")) {
|
|
342
|
+
suggestions.push({
|
|
343
|
+
approach: 'Check Data Fetching',
|
|
344
|
+
reasoning: 'Loading issues are often related to async data fetching',
|
|
345
|
+
steps: [
|
|
346
|
+
'Add console.log before and after fetch calls',
|
|
347
|
+
'Check browser Network tab for failed requests',
|
|
348
|
+
'Verify the endpoint URL is correct',
|
|
349
|
+
'Check for CORS issues in console',
|
|
350
|
+
],
|
|
351
|
+
confidence: 0.85,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (fullContext.includes('style') || fullContext.includes('css') || fullContext.includes('layout')) {
|
|
355
|
+
const designSystem = await this.client.getDesignSystem();
|
|
356
|
+
suggestions.push({
|
|
357
|
+
approach: 'Review Design System',
|
|
358
|
+
reasoning: 'Styling issues may be due to design system misalignment',
|
|
359
|
+
steps: [
|
|
360
|
+
'Check design-system.md for correct color/spacing values',
|
|
361
|
+
'Use browser DevTools to inspect computed styles',
|
|
362
|
+
'Verify Tailwind classes are correct',
|
|
363
|
+
'Check for CSS specificity conflicts',
|
|
364
|
+
],
|
|
365
|
+
confidence: 0.8,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (fullContext.includes('auth') || fullContext.includes('login') || fullContext.includes('permission')) {
|
|
369
|
+
suggestions.push({
|
|
370
|
+
approach: 'Debug Authentication',
|
|
371
|
+
reasoning: 'Auth issues often stem from token or permission problems',
|
|
372
|
+
steps: [
|
|
373
|
+
'Check if auth token exists: console.log(auth.currentUser)',
|
|
374
|
+
'Verify token hasn\'t expired',
|
|
375
|
+
'Review Firestore security rules',
|
|
376
|
+
'Check if user has required role/permissions',
|
|
377
|
+
],
|
|
378
|
+
confidence: 0.9,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (fullContext.includes('upload') || fullContext.includes('file') || fullContext.includes('image')) {
|
|
382
|
+
const firebaseTech = projectData.techStack.find(t => t.name.toLowerCase().includes('firebase'));
|
|
383
|
+
suggestions.push({
|
|
384
|
+
approach: 'Debug File Upload',
|
|
385
|
+
reasoning: 'File uploads require Storage configuration',
|
|
386
|
+
steps: [
|
|
387
|
+
'Verify Firebase Storage is initialized',
|
|
388
|
+
'Check Storage security rules allow uploads',
|
|
389
|
+
'Ensure file size is within limits',
|
|
390
|
+
'Verify correct MIME type handling',
|
|
391
|
+
],
|
|
392
|
+
confidence: 0.85,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Fallback suggestion
|
|
396
|
+
if (suggestions.length === 0) {
|
|
397
|
+
suggestions.push({
|
|
398
|
+
approach: 'Systematic Debugging',
|
|
399
|
+
reasoning: 'General debugging approach for unknown issues',
|
|
400
|
+
steps: [
|
|
401
|
+
'Add console.log statements to trace execution flow',
|
|
402
|
+
'Check browser console for errors',
|
|
403
|
+
'Review recent code changes',
|
|
404
|
+
'Try to reproduce with minimal example',
|
|
405
|
+
'Search error message online for solutions',
|
|
406
|
+
],
|
|
407
|
+
confidence: 0.6,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
// Add relevant context from project
|
|
411
|
+
const additionalContext = {};
|
|
412
|
+
// Find relevant schemas based on keywords
|
|
413
|
+
const relevantSchemas = projectData.schemas.filter(s => fullContext.includes(this.normalize(s.collectionName)));
|
|
414
|
+
if (relevantSchemas.length > 0) {
|
|
415
|
+
additionalContext.schemas = relevantSchemas.map(s => ({
|
|
416
|
+
collectionName: s.collectionName,
|
|
417
|
+
fields: s.fields?.map(f => f.fieldName),
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
// Find relevant endpoints
|
|
421
|
+
const relevantEndpoints = projectData.endpoints.filter(e => fullContext.includes(this.normalize(e.name)) ||
|
|
422
|
+
fullContext.includes(this.normalize(e.path || '')));
|
|
423
|
+
if (relevantEndpoints.length > 0) {
|
|
424
|
+
additionalContext.endpoints = relevantEndpoints.map(e => ({
|
|
425
|
+
name: e.name,
|
|
426
|
+
method: e.method,
|
|
427
|
+
path: e.path,
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
return { suggestions, additionalContext, missingInformation: [] };
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Inject a new build step dynamically
|
|
434
|
+
*/
|
|
435
|
+
async injectDynamicStep(insertAfterPromptId, stepData) {
|
|
436
|
+
const newStep = await this.client.injectBuildPrompt(insertAfterPromptId, {
|
|
437
|
+
title: stepData.title,
|
|
438
|
+
category: stepData.category,
|
|
439
|
+
instruction: stepData.instruction,
|
|
440
|
+
reason: stepData.reason,
|
|
441
|
+
estimatedMinutes: stepData.estimatedMinutes,
|
|
442
|
+
});
|
|
443
|
+
if (!newStep) {
|
|
444
|
+
return {
|
|
445
|
+
success: false,
|
|
446
|
+
message: 'Failed to inject new step. Check that the insertAfter prompt exists.',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
newStep,
|
|
452
|
+
message: `✅ New step "${stepData.title}" injected after prompt ${insertAfterPromptId}`,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// INTELLIGENCE DECISION MAKING
|
|
457
|
+
// ============================================================================
|
|
458
|
+
/**
|
|
459
|
+
* Select appropriate intelligence tier based on task
|
|
460
|
+
*/
|
|
461
|
+
selectIntelligenceTier(task, context) {
|
|
462
|
+
// Simple tasks use rule-based logic (free)
|
|
463
|
+
const simpleTaskPatterns = [
|
|
464
|
+
'get_next_step',
|
|
465
|
+
'mark_complete',
|
|
466
|
+
'get_entity',
|
|
467
|
+
'get_progress',
|
|
468
|
+
];
|
|
469
|
+
if (simpleTaskPatterns.some(p => task.includes(p))) {
|
|
470
|
+
return {
|
|
471
|
+
action: 'rule_based',
|
|
472
|
+
reasoning: 'Simple lookup or status change - no AI needed',
|
|
473
|
+
cost: 'free',
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
// Pattern matching for known error types
|
|
477
|
+
if (task === 'analyze_error' && context?.errorType) {
|
|
478
|
+
return {
|
|
479
|
+
action: 'pattern_match',
|
|
480
|
+
reasoning: 'Error analysis using known patterns',
|
|
481
|
+
cost: 'free',
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// Complex reasoning tasks may need AI
|
|
485
|
+
if (task === 'debug_suggestions' || task === 'gap_analysis') {
|
|
486
|
+
return {
|
|
487
|
+
action: 'lightweight_ai',
|
|
488
|
+
reasoning: 'Requires contextual understanding',
|
|
489
|
+
cost: 'cheap',
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
// Default to rule-based
|
|
493
|
+
return {
|
|
494
|
+
action: 'rule_based',
|
|
495
|
+
reasoning: 'Default to rules to minimize cost',
|
|
496
|
+
cost: 'free',
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
// ============================================================================
|
|
500
|
+
// UTILITY METHODS
|
|
501
|
+
// ============================================================================
|
|
502
|
+
normalize(str) {
|
|
503
|
+
return (str || '')
|
|
504
|
+
.toLowerCase()
|
|
505
|
+
.replace(/[^a-z0-9]/g, '')
|
|
506
|
+
.trim();
|
|
507
|
+
}
|
|
508
|
+
formatErrorMessage(analysis) {
|
|
509
|
+
const parts = [];
|
|
510
|
+
parts.push(`**Diagnosis:** ${analysis.diagnosis.rootCause}`);
|
|
511
|
+
parts.push(`**Severity:** ${analysis.diagnosis.severity}`);
|
|
512
|
+
parts.push('');
|
|
513
|
+
parts.push('**Suggested Fixes:**');
|
|
514
|
+
for (const fix of analysis.suggestedFixes.slice(0, 3)) {
|
|
515
|
+
parts.push(`- ${fix.approach} (${Math.round(fix.confidence * 100)}% confidence)`);
|
|
516
|
+
for (const step of fix.steps.slice(0, 3)) {
|
|
517
|
+
parts.push(` • ${step}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (analysis.shouldInjectNewStep && analysis.newStep) {
|
|
521
|
+
parts.push('');
|
|
522
|
+
parts.push(`⚠️ **New step injected:** "${analysis.newStep.title}"`);
|
|
523
|
+
}
|
|
524
|
+
if (analysis.shouldUncheckStep) {
|
|
525
|
+
parts.push('');
|
|
526
|
+
parts.push('⚠️ **Current step unchecked** - needs to be completed after fix');
|
|
527
|
+
}
|
|
528
|
+
return parts.join('\n');
|
|
529
|
+
}
|
|
530
|
+
generateUsageExamples(entityType, entity) {
|
|
531
|
+
const examples = [];
|
|
532
|
+
switch (entityType) {
|
|
533
|
+
case 'schema':
|
|
534
|
+
examples.push({
|
|
535
|
+
context: 'Fetching documents',
|
|
536
|
+
code: `const snapshot = await db.collection('${entity.collectionName}').get();\nconst items = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));`,
|
|
537
|
+
});
|
|
538
|
+
examples.push({
|
|
539
|
+
context: 'Adding a document',
|
|
540
|
+
code: `await db.collection('${entity.collectionName}').add({\n // ${entity.fields?.map((f) => f.fieldName).join(', ')}\n});`,
|
|
541
|
+
});
|
|
542
|
+
break;
|
|
543
|
+
case 'endpoint':
|
|
544
|
+
examples.push({
|
|
545
|
+
context: 'Calling the API',
|
|
546
|
+
code: `const response = await fetch('${entity.path}', {\n method: '${entity.method}',\n headers: { 'Content-Type': 'application/json' },\n ${entity.method !== 'GET' ? 'body: JSON.stringify(data),' : ''}\n});\nconst result = await response.json();`,
|
|
547
|
+
});
|
|
548
|
+
break;
|
|
549
|
+
case 'page':
|
|
550
|
+
examples.push({
|
|
551
|
+
context: 'Routing to page',
|
|
552
|
+
code: entity.route
|
|
553
|
+
? `import { useRouter } from 'next/navigation';\n// ...\nrouter.push('${entity.route}');`
|
|
554
|
+
: '// Define route in app router',
|
|
555
|
+
});
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
return examples;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Generate AI-powered guidance for a build step
|
|
562
|
+
* Uses completely generic prompts that adapt to any project type
|
|
563
|
+
*/
|
|
564
|
+
async generateAIGuidance(prompt, projectData, context, gapReport) {
|
|
565
|
+
if (!this.llmClient) {
|
|
566
|
+
// Fallback to rule-based if LLM not available
|
|
567
|
+
return this.guidanceGenerator.generateGuidance({
|
|
568
|
+
prompt,
|
|
569
|
+
projectData,
|
|
570
|
+
relatedPages: context.pages,
|
|
571
|
+
relatedSchemas: context.schemas,
|
|
572
|
+
relatedEndpoints: context.endpoints,
|
|
573
|
+
relatedTechStack: context.techStack,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
// Generic system prompt - no project-type assumptions
|
|
577
|
+
const systemPrompt = `You are an expert software engineer assistant.
|
|
578
|
+
|
|
579
|
+
Your role is to provide practical, actionable guidance for development tasks.
|
|
580
|
+
Analyze the provided context carefully and identify:
|
|
581
|
+
1. What technical considerations apply to this specific task
|
|
582
|
+
2. What issues the developer might face based on the actual project structure
|
|
583
|
+
3. How to verify the task is complete correctly
|
|
584
|
+
4. What implicit requirements exist that aren't explicitly stated
|
|
585
|
+
|
|
586
|
+
Be specific to the actual project data provided. Do not make assumptions about the project type.
|
|
587
|
+
Do not provide generic advice - every suggestion must be grounded in the actual context.
|
|
588
|
+
Focus on what's relevant to THIS specific task in THIS specific project.`;
|
|
589
|
+
// Build context dynamically from actual project data
|
|
590
|
+
const techStackList = projectData.techStack.map(t => `${t.name}${t.version ? ` v${t.version}` : ''} (${t.category})`).join('\n- ') || 'Not specified';
|
|
591
|
+
const schemaContext = context.schemas.length > 0
|
|
592
|
+
? context.schemas.map(s => `${s.collectionName}: fields=[${s.fields.map(f => `${f.fieldName}:${f.dataType}${f.required ? '*' : ''}`).join(', ')}]`).join('\n- ')
|
|
593
|
+
: 'No schemas relevant to this task';
|
|
594
|
+
const pageContext = context.pages.length > 0
|
|
595
|
+
? context.pages.map(p => `${p.pageName}${p.route ? ` (${p.route})` : ''}: ${p.purpose || p.description || 'No description'}`).join('\n- ')
|
|
596
|
+
: 'No pages relevant to this task';
|
|
597
|
+
const endpointContext = context.endpoints.length > 0
|
|
598
|
+
? context.endpoints.map(e => `${e.method} ${e.path}: ${e.description || e.name}`).join('\n- ')
|
|
599
|
+
: 'No endpoints relevant to this task';
|
|
600
|
+
const gapContext = gapReport.gaps.length > 0
|
|
601
|
+
? gapReport.gaps.map(g => `[${g.severity.toUpperCase()}] ${g.description}`).join('\n- ')
|
|
602
|
+
: 'None identified';
|
|
603
|
+
const userPrompt = `# Current Task
|
|
604
|
+
Title: ${prompt.title}
|
|
605
|
+
Category: ${prompt.category}
|
|
606
|
+
Instruction: ${prompt.instruction || 'Complete this step as described'}
|
|
607
|
+
${prompt.expectedOutcome ? `Expected Outcome: ${prompt.expectedOutcome}` : ''}
|
|
608
|
+
${prompt.testCriteria?.length ? `Test Criteria:\n- ${prompt.testCriteria.join('\n- ')}` : ''}
|
|
609
|
+
|
|
610
|
+
# Project Context
|
|
611
|
+
Name: ${projectData.projectName}
|
|
612
|
+
Description: ${projectData.description || 'No description provided'}
|
|
613
|
+
|
|
614
|
+
## Technology Stack
|
|
615
|
+
- ${techStackList}
|
|
616
|
+
|
|
617
|
+
## Relevant Database Schemas
|
|
618
|
+
- ${schemaContext}
|
|
619
|
+
|
|
620
|
+
## Relevant Pages/Views
|
|
621
|
+
- ${pageContext}
|
|
622
|
+
|
|
623
|
+
## Relevant API Endpoints
|
|
624
|
+
- ${endpointContext}
|
|
625
|
+
|
|
626
|
+
## Identified Gaps
|
|
627
|
+
- ${gapContext}
|
|
628
|
+
|
|
629
|
+
# Your Task
|
|
630
|
+
Provide guidance in JSON format:
|
|
631
|
+
{
|
|
632
|
+
"technicalConsiderations": ["specific considerations for this task based on the context"],
|
|
633
|
+
"potentialIssues": ["specific issues that could arise based on the actual project structure"],
|
|
634
|
+
"testingCriteria": ["specific ways to verify this task is complete"],
|
|
635
|
+
"suggestedApproach": "A step-by-step approach specific to this task",
|
|
636
|
+
"implicitRequirements": ["Things not stated but implied by the context"]
|
|
637
|
+
}`;
|
|
638
|
+
try {
|
|
639
|
+
const { result } = await this.llmClient.analyzeJSON(systemPrompt, userPrompt, { maxTokens: 2000 });
|
|
640
|
+
// Add implicit requirements as potential issues
|
|
641
|
+
const allIssues = [...(result.potentialIssues || [])];
|
|
642
|
+
if (result.implicitRequirements?.length) {
|
|
643
|
+
allIssues.push(...result.implicitRequirements.map(r => `⚠️ Implicit: ${r}`));
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
technicalConsiderations: result.technicalConsiderations || [],
|
|
647
|
+
potentialIssues: allIssues,
|
|
648
|
+
testingCriteria: result.testingCriteria || [],
|
|
649
|
+
suggestedApproach: result.suggestedApproach,
|
|
650
|
+
relatedPatterns: result.relatedPatterns,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
console.error('[Intelligence] AI guidance failed, falling back to rules:', error);
|
|
655
|
+
return this.guidanceGenerator.generateGuidance({
|
|
656
|
+
prompt,
|
|
657
|
+
projectData,
|
|
658
|
+
relatedPages: context.pages,
|
|
659
|
+
relatedSchemas: context.schemas,
|
|
660
|
+
relatedEndpoints: context.endpoints,
|
|
661
|
+
relatedTechStack: context.techStack,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Clear intelligence cache
|
|
667
|
+
*/
|
|
668
|
+
clearCache() {
|
|
669
|
+
this.cache.clear();
|
|
670
|
+
this.client.clearCache();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
exports.IntelligenceLayer = IntelligenceLayer;
|
|
674
|
+
// Re-export individual components
|
|
675
|
+
var context_optimizer_js_2 = require("./context-optimizer.js");
|
|
676
|
+
Object.defineProperty(exports, "ContextOptimizer", { enumerable: true, get: function () { return context_optimizer_js_2.ContextOptimizer; } });
|
|
677
|
+
var error_analyzer_js_2 = require("./error-analyzer.js");
|
|
678
|
+
Object.defineProperty(exports, "ErrorAnalyzer", { enumerable: true, get: function () { return error_analyzer_js_2.ErrorAnalyzer; } });
|
|
679
|
+
var gap_filler_js_2 = require("./gap-filler.js");
|
|
680
|
+
Object.defineProperty(exports, "GapFiller", { enumerable: true, get: function () { return gap_filler_js_2.GapFiller; } });
|
|
681
|
+
var guidance_generator_js_2 = require("./guidance-generator.js");
|
|
682
|
+
Object.defineProperty(exports, "GuidanceGenerator", { enumerable: true, get: function () { return guidance_generator_js_2.GuidanceGenerator; } });
|
|
683
|
+
//# sourceMappingURL=index.js.map
|