wiggum-cli 0.2.5 → 0.3.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 +73 -60
- package/dist/ai/agents/codebase-analyst.d.ts +11 -0
- package/dist/ai/agents/codebase-analyst.d.ts.map +1 -0
- package/dist/ai/agents/codebase-analyst.js +150 -0
- package/dist/ai/agents/codebase-analyst.js.map +1 -0
- package/dist/ai/agents/index.d.ts +16 -0
- package/dist/ai/agents/index.d.ts.map +1 -0
- package/dist/ai/agents/index.js +85 -0
- package/dist/ai/agents/index.js.map +1 -0
- package/dist/ai/agents/orchestrator.d.ts +15 -0
- package/dist/ai/agents/orchestrator.d.ts.map +1 -0
- package/dist/ai/agents/orchestrator.js +187 -0
- package/dist/ai/agents/orchestrator.js.map +1 -0
- package/dist/ai/agents/stack-researcher.d.ts +15 -0
- package/dist/ai/agents/stack-researcher.d.ts.map +1 -0
- package/dist/ai/agents/stack-researcher.js +274 -0
- package/dist/ai/agents/stack-researcher.js.map +1 -0
- package/dist/ai/agents/types.d.ts +123 -0
- package/dist/ai/agents/types.d.ts.map +1 -0
- package/dist/ai/agents/types.js +6 -0
- package/dist/ai/agents/types.js.map +1 -0
- package/dist/ai/enhancer.d.ts +39 -1
- package/dist/ai/enhancer.d.ts.map +1 -1
- package/dist/ai/enhancer.js +105 -9
- package/dist/ai/enhancer.js.map +1 -1
- package/dist/ai/index.d.ts +4 -2
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +5 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/prompts.d.ts +2 -2
- package/dist/ai/prompts.d.ts.map +1 -1
- package/dist/ai/prompts.js +66 -4
- package/dist/ai/prompts.js.map +1 -1
- package/dist/ai/providers.d.ts +28 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +40 -0
- package/dist/ai/providers.js.map +1 -1
- package/dist/ai/tools/context7.d.ts +34 -0
- package/dist/ai/tools/context7.d.ts.map +1 -0
- package/dist/ai/tools/context7.js +135 -0
- package/dist/ai/tools/context7.js.map +1 -0
- package/dist/ai/tools/index.d.ts +7 -0
- package/dist/ai/tools/index.d.ts.map +1 -0
- package/dist/ai/tools/index.js +7 -0
- package/dist/ai/tools/index.js.map +1 -0
- package/dist/ai/tools/tavily.d.ts +27 -0
- package/dist/ai/tools/tavily.d.ts.map +1 -0
- package/dist/ai/tools/tavily.js +75 -0
- package/dist/ai/tools/tavily.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -12
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +2 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +233 -154
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/colors.d.ts.map +1 -1
- package/dist/utils/colors.js +10 -3
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/header.d.ts +1 -1
- package/dist/utils/header.js +3 -3
- package/dist/utils/header.js.map +1 -1
- package/package.json +3 -3
- package/src/ai/agents/codebase-analyst.ts +172 -0
- package/src/ai/agents/index.ts +147 -0
- package/src/ai/agents/orchestrator.ts +222 -0
- package/src/ai/agents/stack-researcher.ts +298 -0
- package/src/ai/agents/types.ts +132 -0
- package/src/ai/enhancer.ts +159 -9
- package/src/ai/index.ts +31 -1
- package/src/ai/prompts.ts +67 -4
- package/src/ai/providers.ts +48 -0
- package/src/ai/tools/context7.ts +167 -0
- package/src/ai/tools/index.ts +17 -0
- package/src/ai/tools/tavily.ts +101 -0
- package/src/cli.ts +14 -12
- package/src/commands/init.ts +278 -173
- package/src/utils/colors.ts +11 -3
- package/src/utils/header.ts +3 -3
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack Researcher Agent
|
|
3
|
+
* Researches best practices and tools for the detected stack
|
|
4
|
+
* Gracefully degrades when optional services are unavailable
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { generateText, stepCountIs, type LanguageModel, type Tool } from 'ai';
|
|
8
|
+
import type { StackResearch, StackResearcherInput, AgentCapabilities } from './types.js';
|
|
9
|
+
import type { DetectedStack } from '../../scanner/types.js';
|
|
10
|
+
import { createTavilySearchTool } from '../tools/tavily.js';
|
|
11
|
+
import { createContext7Tool } from '../tools/context7.js';
|
|
12
|
+
import { isReasoningModel } from '../providers.js';
|
|
13
|
+
import { logger } from '../../utils/logger.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* System prompt for Stack Researcher with tools
|
|
17
|
+
*/
|
|
18
|
+
const STACK_RESEARCHER_WITH_TOOLS_PROMPT = `You are a Stack Researcher agent with access to web search and documentation lookup tools.
|
|
19
|
+
|
|
20
|
+
## Your Mission
|
|
21
|
+
Research the detected technology stack to find:
|
|
22
|
+
1. Current best practices
|
|
23
|
+
2. Common anti-patterns to avoid
|
|
24
|
+
3. Testing tools and patterns
|
|
25
|
+
4. Debugging approaches
|
|
26
|
+
5. Useful documentation links
|
|
27
|
+
|
|
28
|
+
## Tools Available
|
|
29
|
+
- tavilySearch: Search the web for current best practices and patterns
|
|
30
|
+
- context7Lookup: Look up library documentation
|
|
31
|
+
|
|
32
|
+
## Research Strategy
|
|
33
|
+
1. Search for "[technology] best practices 2024"
|
|
34
|
+
2. Search for "[project type] testing patterns"
|
|
35
|
+
3. Look up documentation for key dependencies
|
|
36
|
+
4. Search for "[framework] anti-patterns"
|
|
37
|
+
|
|
38
|
+
## Output Format
|
|
39
|
+
After research, output ONLY valid JSON with this structure:
|
|
40
|
+
{
|
|
41
|
+
"bestPractices": [
|
|
42
|
+
"Use TypeScript strict mode",
|
|
43
|
+
"Implement proper error boundaries"
|
|
44
|
+
],
|
|
45
|
+
"antiPatterns": [
|
|
46
|
+
"Don't use any type",
|
|
47
|
+
"Avoid prop drilling"
|
|
48
|
+
],
|
|
49
|
+
"testingTools": [
|
|
50
|
+
"npx vitest",
|
|
51
|
+
"npx playwright test"
|
|
52
|
+
],
|
|
53
|
+
"debuggingTools": [
|
|
54
|
+
"React DevTools",
|
|
55
|
+
"DEBUG=* npm run dev"
|
|
56
|
+
],
|
|
57
|
+
"documentationHints": [
|
|
58
|
+
"React docs: react.dev",
|
|
59
|
+
"Vitest: vitest.dev"
|
|
60
|
+
],
|
|
61
|
+
"researchMode": "full"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Keep each item concise (5-15 words max). Max 5 items per array.`;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* System prompt for Stack Researcher without tools (knowledge-only)
|
|
68
|
+
*/
|
|
69
|
+
const STACK_RESEARCHER_KNOWLEDGE_ONLY_PROMPT = `You are a Stack Researcher agent. You don't have web access, so rely on your training knowledge.
|
|
70
|
+
|
|
71
|
+
## Your Mission
|
|
72
|
+
Based on your knowledge, provide:
|
|
73
|
+
1. Best practices for the detected technologies
|
|
74
|
+
2. Common anti-patterns to avoid
|
|
75
|
+
3. Testing tools commonly used with this stack
|
|
76
|
+
4. Debugging approaches
|
|
77
|
+
5. Documentation hints
|
|
78
|
+
|
|
79
|
+
## Important Notes
|
|
80
|
+
- Be explicit about what you're confident about vs uncertain
|
|
81
|
+
- Focus on well-established practices from your training
|
|
82
|
+
- Mention if something might be outdated
|
|
83
|
+
|
|
84
|
+
## Output Format
|
|
85
|
+
Output ONLY valid JSON with this structure:
|
|
86
|
+
{
|
|
87
|
+
"bestPractices": [
|
|
88
|
+
"Use TypeScript strict mode",
|
|
89
|
+
"Implement proper error boundaries"
|
|
90
|
+
],
|
|
91
|
+
"antiPatterns": [
|
|
92
|
+
"Don't use any type",
|
|
93
|
+
"Avoid prop drilling"
|
|
94
|
+
],
|
|
95
|
+
"testingTools": [
|
|
96
|
+
"npm test",
|
|
97
|
+
"npx vitest"
|
|
98
|
+
],
|
|
99
|
+
"debuggingTools": [
|
|
100
|
+
"console.log debugging",
|
|
101
|
+
"Node.js inspector"
|
|
102
|
+
],
|
|
103
|
+
"documentationHints": [
|
|
104
|
+
"Check official docs for updates",
|
|
105
|
+
"Framework docs: [URL]"
|
|
106
|
+
],
|
|
107
|
+
"researchMode": "knowledge-only"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Keep each item concise (5-15 words max). Max 5 items per array.
|
|
111
|
+
Note: Research mode should reflect that you're using training knowledge only.`;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create the research prompt based on stack and project type
|
|
115
|
+
*/
|
|
116
|
+
function createResearchPrompt(stack: DetectedStack, projectType: string, hasTools: boolean): string {
|
|
117
|
+
const technologies: string[] = [];
|
|
118
|
+
|
|
119
|
+
// Collect all detected technologies
|
|
120
|
+
if (stack.framework) technologies.push(stack.framework.name);
|
|
121
|
+
if (stack.testing?.unit) technologies.push(stack.testing.unit.name);
|
|
122
|
+
if (stack.testing?.e2e) technologies.push(stack.testing.e2e.name);
|
|
123
|
+
if (stack.orm) technologies.push(stack.orm.name);
|
|
124
|
+
if (stack.database) technologies.push(stack.database.name);
|
|
125
|
+
if (stack.stateManagement) technologies.push(stack.stateManagement.name);
|
|
126
|
+
if (stack.auth) technologies.push(stack.auth.name);
|
|
127
|
+
if (stack.mcp?.isProject) technologies.push('MCP Server');
|
|
128
|
+
|
|
129
|
+
const techList = technologies.length > 0 ? technologies.join(', ') : 'Unknown stack';
|
|
130
|
+
|
|
131
|
+
if (hasTools) {
|
|
132
|
+
return `Research best practices for this stack:
|
|
133
|
+
|
|
134
|
+
Project Type: ${projectType}
|
|
135
|
+
Technologies: ${techList}
|
|
136
|
+
|
|
137
|
+
Use the available tools to search for:
|
|
138
|
+
1. Current best practices for ${projectType} projects
|
|
139
|
+
2. Testing patterns for ${techList}
|
|
140
|
+
3. Common anti-patterns to avoid
|
|
141
|
+
|
|
142
|
+
Then produce your research findings as JSON.`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return `Based on your knowledge, provide best practices for this stack:
|
|
146
|
+
|
|
147
|
+
Project Type: ${projectType}
|
|
148
|
+
Technologies: ${techList}
|
|
149
|
+
|
|
150
|
+
Provide:
|
|
151
|
+
1. Best practices for ${projectType} projects
|
|
152
|
+
2. Testing tools commonly used with ${techList}
|
|
153
|
+
3. Anti-patterns to avoid
|
|
154
|
+
4. Debugging approaches
|
|
155
|
+
|
|
156
|
+
Output your findings as JSON. Be clear this is based on training knowledge.`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Determine research mode based on capabilities
|
|
161
|
+
*/
|
|
162
|
+
function getResearchMode(capabilities: AgentCapabilities): StackResearch['researchMode'] {
|
|
163
|
+
if (capabilities.hasTavily && capabilities.hasContext7) return 'full';
|
|
164
|
+
if (capabilities.hasTavily) return 'web-only';
|
|
165
|
+
if (capabilities.hasContext7) return 'docs-only';
|
|
166
|
+
return 'knowledge-only';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run the Stack Researcher agent
|
|
171
|
+
*/
|
|
172
|
+
export async function runStackResearcher(
|
|
173
|
+
model: LanguageModel,
|
|
174
|
+
modelId: string,
|
|
175
|
+
input: StackResearcherInput,
|
|
176
|
+
options: { tavilyApiKey?: string; context7ApiKey?: string },
|
|
177
|
+
verbose: boolean = false
|
|
178
|
+
): Promise<StackResearch | null> {
|
|
179
|
+
const tools: Record<string, Tool> = {};
|
|
180
|
+
|
|
181
|
+
// Add tools based on available keys
|
|
182
|
+
if (options.tavilyApiKey) {
|
|
183
|
+
tools.tavilySearch = createTavilySearchTool(options.tavilyApiKey);
|
|
184
|
+
}
|
|
185
|
+
if (options.context7ApiKey) {
|
|
186
|
+
tools.context7Lookup = createContext7Tool(options.context7ApiKey);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const hasTools = Object.keys(tools).length > 0;
|
|
190
|
+
const researchMode = getResearchMode(input.capabilities);
|
|
191
|
+
|
|
192
|
+
if (verbose) {
|
|
193
|
+
logger.info(`Stack Researcher running in ${researchMode} mode`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const systemPrompt = hasTools
|
|
197
|
+
? STACK_RESEARCHER_WITH_TOOLS_PROMPT
|
|
198
|
+
: STACK_RESEARCHER_KNOWLEDGE_ONLY_PROMPT;
|
|
199
|
+
|
|
200
|
+
const prompt = createResearchPrompt(input.stack, input.projectType, hasTools);
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const result = await generateText({
|
|
204
|
+
model,
|
|
205
|
+
system: systemPrompt,
|
|
206
|
+
prompt,
|
|
207
|
+
...(hasTools ? { tools, stopWhen: stepCountIs(8) } : {}),
|
|
208
|
+
maxOutputTokens: 2000,
|
|
209
|
+
...(isReasoningModel(modelId) ? {} : { temperature: 0.3 }),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Parse the response
|
|
213
|
+
const research = parseStackResearch(result.text, result.steps, researchMode, verbose);
|
|
214
|
+
return research;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (verbose) {
|
|
217
|
+
logger.error(`Stack Researcher error: ${error instanceof Error ? error.message : String(error)}`);
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Parse the stack research from agent response
|
|
225
|
+
*/
|
|
226
|
+
function parseStackResearch(
|
|
227
|
+
text: string,
|
|
228
|
+
steps: Array<{ text?: string }> | undefined,
|
|
229
|
+
researchMode: StackResearch['researchMode'],
|
|
230
|
+
verbose: boolean
|
|
231
|
+
): StackResearch | null {
|
|
232
|
+
// Try to get text from the result or steps
|
|
233
|
+
let textToParse = text;
|
|
234
|
+
|
|
235
|
+
if (!textToParse || textToParse.trim() === '') {
|
|
236
|
+
const stepsList = steps || [];
|
|
237
|
+
for (let i = stepsList.length - 1; i >= 0; i--) {
|
|
238
|
+
const step = stepsList[i];
|
|
239
|
+
if (step.text && step.text.trim() !== '') {
|
|
240
|
+
textToParse = step.text;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!textToParse || textToParse.trim() === '') {
|
|
247
|
+
if (verbose) {
|
|
248
|
+
logger.warn('Stack Researcher: No text output found');
|
|
249
|
+
}
|
|
250
|
+
return getDefaultStackResearch(researchMode);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
// Remove markdown code blocks if present
|
|
255
|
+
let jsonText = textToParse;
|
|
256
|
+
const jsonMatch = textToParse.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
257
|
+
if (jsonMatch) {
|
|
258
|
+
jsonText = jsonMatch[1];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Find JSON object
|
|
262
|
+
const objectMatch = jsonText.match(/\{[\s\S]*\}/);
|
|
263
|
+
if (objectMatch) {
|
|
264
|
+
jsonText = objectMatch[0];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const parsed = JSON.parse(jsonText) as Partial<StackResearch>;
|
|
268
|
+
|
|
269
|
+
// Build result with defaults for missing fields
|
|
270
|
+
return {
|
|
271
|
+
bestPractices: parsed.bestPractices || [],
|
|
272
|
+
antiPatterns: parsed.antiPatterns || [],
|
|
273
|
+
testingTools: parsed.testingTools || [],
|
|
274
|
+
debuggingTools: parsed.debuggingTools || [],
|
|
275
|
+
documentationHints: parsed.documentationHints || [],
|
|
276
|
+
researchMode: researchMode,
|
|
277
|
+
};
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (verbose) {
|
|
280
|
+
logger.warn(`Stack Researcher: Failed to parse JSON - ${error instanceof Error ? error.message : String(error)}`);
|
|
281
|
+
}
|
|
282
|
+
return getDefaultStackResearch(researchMode);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get default stack research when parsing fails
|
|
288
|
+
*/
|
|
289
|
+
function getDefaultStackResearch(researchMode: StackResearch['researchMode']): StackResearch {
|
|
290
|
+
return {
|
|
291
|
+
bestPractices: ['Follow project conventions', 'Write tests for new code'],
|
|
292
|
+
antiPatterns: ['Avoid skipping tests', 'Don\'t ignore type errors'],
|
|
293
|
+
testingTools: ['npm test'],
|
|
294
|
+
debuggingTools: ['console.log', 'debugger statement'],
|
|
295
|
+
documentationHints: ['Check package.json for dependencies'],
|
|
296
|
+
researchMode,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Types and Interfaces
|
|
3
|
+
* Defines the structure for multi-agent analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ScanResult, DetectedStack } from '../../scanner/types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Codebase analysis result from the Codebase Analyst agent
|
|
10
|
+
*/
|
|
11
|
+
export interface CodebaseAnalysis {
|
|
12
|
+
/** Project structure and context */
|
|
13
|
+
projectContext: {
|
|
14
|
+
/** Key entry point files */
|
|
15
|
+
entryPoints: string[];
|
|
16
|
+
/** Important directories and their purposes */
|
|
17
|
+
keyDirectories: Record<string, string>;
|
|
18
|
+
/** Naming conventions used */
|
|
19
|
+
namingConventions?: string;
|
|
20
|
+
/** The primary project type (MCP Server, REST API, React SPA, CLI, Library) */
|
|
21
|
+
projectType: string;
|
|
22
|
+
};
|
|
23
|
+
/** Detected commands from package.json */
|
|
24
|
+
commands: {
|
|
25
|
+
test?: string;
|
|
26
|
+
lint?: string;
|
|
27
|
+
typecheck?: string;
|
|
28
|
+
build?: string;
|
|
29
|
+
dev?: string;
|
|
30
|
+
format?: string;
|
|
31
|
+
};
|
|
32
|
+
/** Short, actionable implementation guidelines */
|
|
33
|
+
implementationGuidelines: string[];
|
|
34
|
+
/** Additional technologies that may have been missed */
|
|
35
|
+
possibleMissedTechnologies?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Stack research result from the Stack Researcher agent
|
|
40
|
+
*/
|
|
41
|
+
export interface StackResearch {
|
|
42
|
+
/** Best practices for the detected stack */
|
|
43
|
+
bestPractices: string[];
|
|
44
|
+
/** Anti-patterns to avoid */
|
|
45
|
+
antiPatterns: string[];
|
|
46
|
+
/** Technology-specific testing tools */
|
|
47
|
+
testingTools: string[];
|
|
48
|
+
/** Technology-specific debugging tools */
|
|
49
|
+
debuggingTools: string[];
|
|
50
|
+
/** Documentation hints and links */
|
|
51
|
+
documentationHints: string[];
|
|
52
|
+
/** Whether research was performed with tools or knowledge-only */
|
|
53
|
+
researchMode: 'full' | 'web-only' | 'docs-only' | 'knowledge-only';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* MCP server recommendations
|
|
58
|
+
*/
|
|
59
|
+
export interface McpRecommendations {
|
|
60
|
+
/** Essential MCP servers for this stack */
|
|
61
|
+
essential: string[];
|
|
62
|
+
/** Recommended but optional MCP servers */
|
|
63
|
+
recommended: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Combined result from all agents
|
|
68
|
+
*/
|
|
69
|
+
export interface MultiAgentAnalysis {
|
|
70
|
+
/** Codebase analysis from the Codebase Analyst */
|
|
71
|
+
codebaseAnalysis: CodebaseAnalysis;
|
|
72
|
+
/** Stack research from the Stack Researcher */
|
|
73
|
+
stackResearch: StackResearch;
|
|
74
|
+
/** MCP server recommendations (merged from both agents) */
|
|
75
|
+
mcpServers: McpRecommendations;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Agent capabilities based on available API keys
|
|
80
|
+
*/
|
|
81
|
+
export interface AgentCapabilities {
|
|
82
|
+
/** Tavily web search available */
|
|
83
|
+
hasTavily: boolean;
|
|
84
|
+
/** Context7 documentation lookup available */
|
|
85
|
+
hasContext7: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Options for running agents
|
|
90
|
+
*/
|
|
91
|
+
export interface AgentOptions {
|
|
92
|
+
/** Tavily API key (optional) */
|
|
93
|
+
tavilyApiKey?: string;
|
|
94
|
+
/** Context7 API key (optional) */
|
|
95
|
+
context7ApiKey?: string;
|
|
96
|
+
/** Enable verbose logging */
|
|
97
|
+
verbose?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Input for the Codebase Analyst agent
|
|
102
|
+
*/
|
|
103
|
+
export interface CodebaseAnalystInput {
|
|
104
|
+
/** The scan result from the scanner */
|
|
105
|
+
scanResult: ScanResult;
|
|
106
|
+
/** Project root directory */
|
|
107
|
+
projectRoot: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Input for the Stack Researcher agent
|
|
112
|
+
*/
|
|
113
|
+
export interface StackResearcherInput {
|
|
114
|
+
/** The detected stack */
|
|
115
|
+
stack: DetectedStack;
|
|
116
|
+
/** The identified project type */
|
|
117
|
+
projectType: string;
|
|
118
|
+
/** Agent capabilities */
|
|
119
|
+
capabilities: AgentCapabilities;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Input for the Orchestrator agent
|
|
124
|
+
*/
|
|
125
|
+
export interface OrchestratorInput {
|
|
126
|
+
/** Codebase analysis result */
|
|
127
|
+
codebaseAnalysis: CodebaseAnalysis;
|
|
128
|
+
/** Stack research result */
|
|
129
|
+
stackResearch: StackResearch;
|
|
130
|
+
/** The detected stack */
|
|
131
|
+
stack: DetectedStack;
|
|
132
|
+
}
|
package/src/ai/enhancer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ScanResult, DetectedStack, DetectionResult } from '../scanner/type
|
|
|
8
8
|
import { getModel, type AIProvider, hasApiKey, getApiKeyEnvVar, isReasoningModel } from './providers.js';
|
|
9
9
|
import { SYSTEM_PROMPT, SYSTEM_PROMPT_AGENTIC, createAnalysisPrompt } from './prompts.js';
|
|
10
10
|
import { createExplorationTools } from './tools.js';
|
|
11
|
+
import { runMultiAgentAnalysis, type MultiAgentAnalysis } from './agents/index.js';
|
|
11
12
|
import { logger } from '../utils/logger.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -44,6 +45,32 @@ export interface McpRecommendations {
|
|
|
44
45
|
recommended?: string[];
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Technology-specific testing and debugging tools
|
|
50
|
+
*/
|
|
51
|
+
export interface TechnologyTools {
|
|
52
|
+
/** Testing commands specific to the detected technologies */
|
|
53
|
+
testing?: string[];
|
|
54
|
+
/** Debugging/inspection tools for the stack */
|
|
55
|
+
debugging?: string[];
|
|
56
|
+
/** Linting/validation tools beyond the standard ones */
|
|
57
|
+
validation?: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Technology-specific best practices
|
|
62
|
+
*/
|
|
63
|
+
export interface TechnologyPractices {
|
|
64
|
+
/** The primary project type (e.g., "MCP Server", "REST API", "React SPA") */
|
|
65
|
+
projectType?: string;
|
|
66
|
+
/** Practices specific to the detected technologies */
|
|
67
|
+
practices?: string[];
|
|
68
|
+
/** Anti-patterns to avoid for this stack */
|
|
69
|
+
antiPatterns?: string[];
|
|
70
|
+
/** Links to relevant documentation (optional) */
|
|
71
|
+
documentationHints?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
/**
|
|
48
75
|
* AI analysis result - focused on actionable outputs
|
|
49
76
|
*/
|
|
@@ -58,6 +85,10 @@ export interface AIAnalysisResult {
|
|
|
58
85
|
mcpServers?: McpRecommendations;
|
|
59
86
|
/** Additional technologies that may have been missed */
|
|
60
87
|
possibleMissedTechnologies?: string[];
|
|
88
|
+
/** Technology-specific tools for testing/debugging */
|
|
89
|
+
technologyTools?: TechnologyTools;
|
|
90
|
+
/** Technology-specific best practices based on detected stack */
|
|
91
|
+
technologyPractices?: TechnologyPractices;
|
|
61
92
|
}
|
|
62
93
|
|
|
63
94
|
/**
|
|
@@ -79,12 +110,23 @@ export interface EnhancerOptions {
|
|
|
79
110
|
verbose?: boolean;
|
|
80
111
|
/** Use agentic mode with tools for deeper codebase exploration */
|
|
81
112
|
agentic?: boolean;
|
|
113
|
+
/** Tavily API key for web search (optional) */
|
|
114
|
+
tavilyApiKey?: string;
|
|
115
|
+
/** Context7 API key for documentation lookup (optional) */
|
|
116
|
+
context7ApiKey?: string;
|
|
82
117
|
}
|
|
83
118
|
|
|
84
119
|
/**
|
|
85
120
|
* Parse AI response JSON safely
|
|
86
121
|
*/
|
|
87
|
-
function parseAIResponse(text: string): AIAnalysisResult | null {
|
|
122
|
+
function parseAIResponse(text: string, verbose: boolean = false): AIAnalysisResult | null {
|
|
123
|
+
if (!text || text.trim() === '') {
|
|
124
|
+
if (verbose) {
|
|
125
|
+
logger.warn('AI response text is empty');
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
88
130
|
try {
|
|
89
131
|
// Try to extract JSON from the response
|
|
90
132
|
// The AI might wrap it in markdown code blocks
|
|
@@ -96,15 +138,33 @@ function parseAIResponse(text: string): AIAnalysisResult | null {
|
|
|
96
138
|
jsonText = jsonMatch[1];
|
|
97
139
|
}
|
|
98
140
|
|
|
99
|
-
// Try to find JSON object
|
|
141
|
+
// Try to find JSON object - use greedy match for the outermost braces
|
|
142
|
+
// This handles cases where there's text before/after the JSON
|
|
100
143
|
const objectMatch = jsonText.match(/\{[\s\S]*\}/);
|
|
101
144
|
if (objectMatch) {
|
|
102
145
|
jsonText = objectMatch[0];
|
|
103
146
|
}
|
|
104
147
|
|
|
105
|
-
|
|
148
|
+
// Try to parse JSON
|
|
149
|
+
const result = JSON.parse(jsonText) as AIAnalysisResult;
|
|
150
|
+
|
|
151
|
+
// Validate that we got the expected structure
|
|
152
|
+
if (!result || typeof result !== 'object') {
|
|
153
|
+
if (verbose) {
|
|
154
|
+
logger.warn('AI response parsed but is not an object');
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
106
160
|
} catch (error) {
|
|
107
|
-
|
|
161
|
+
if (verbose) {
|
|
162
|
+
logger.warn(`Failed to parse AI response as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
163
|
+
// Log first 500 chars of response for debugging
|
|
164
|
+
logger.warn(`Response preview: ${text.substring(0, 500)}...`);
|
|
165
|
+
} else {
|
|
166
|
+
logger.warn('Failed to parse AI response as JSON');
|
|
167
|
+
}
|
|
108
168
|
return null;
|
|
109
169
|
}
|
|
110
170
|
}
|
|
@@ -148,12 +208,16 @@ export class AIEnhancer {
|
|
|
148
208
|
private model?: string;
|
|
149
209
|
private verbose: boolean;
|
|
150
210
|
private agentic: boolean;
|
|
211
|
+
private tavilyApiKey?: string;
|
|
212
|
+
private context7ApiKey?: string;
|
|
151
213
|
|
|
152
214
|
constructor(options: EnhancerOptions = {}) {
|
|
153
215
|
this.provider = options.provider || 'anthropic';
|
|
154
216
|
this.model = options.model;
|
|
155
217
|
this.verbose = options.verbose || false;
|
|
156
218
|
this.agentic = options.agentic || false;
|
|
219
|
+
this.tavilyApiKey = options.tavilyApiKey;
|
|
220
|
+
this.context7ApiKey = options.context7ApiKey;
|
|
157
221
|
}
|
|
158
222
|
|
|
159
223
|
/**
|
|
@@ -260,12 +324,41 @@ export class AIEnhancer {
|
|
|
260
324
|
}
|
|
261
325
|
|
|
262
326
|
/**
|
|
263
|
-
* Agentic enhancement mode - use
|
|
327
|
+
* Agentic enhancement mode - use multi-agent system to explore codebase
|
|
264
328
|
*/
|
|
265
329
|
private async enhanceAgentic(
|
|
266
330
|
model: ReturnType<typeof getModel>['model'],
|
|
267
331
|
modelId: string,
|
|
268
332
|
scanResult: ScanResult
|
|
333
|
+
): Promise<AIAnalysisResult | null> {
|
|
334
|
+
// Use the multi-agent system for deeper analysis
|
|
335
|
+
const multiAgentResult = await runMultiAgentAnalysis(
|
|
336
|
+
model,
|
|
337
|
+
modelId,
|
|
338
|
+
scanResult,
|
|
339
|
+
{
|
|
340
|
+
tavilyApiKey: this.tavilyApiKey,
|
|
341
|
+
context7ApiKey: this.context7ApiKey,
|
|
342
|
+
verbose: this.verbose,
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
if (!multiAgentResult) {
|
|
347
|
+
// Fall back to simple agentic mode
|
|
348
|
+
return this.enhanceLegacyAgentic(model, modelId, scanResult);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Convert MultiAgentAnalysis to AIAnalysisResult for backward compatibility
|
|
352
|
+
return convertMultiAgentToAIAnalysis(multiAgentResult);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Legacy agentic mode (fallback when multi-agent fails)
|
|
357
|
+
*/
|
|
358
|
+
private async enhanceLegacyAgentic(
|
|
359
|
+
model: ReturnType<typeof getModel>['model'],
|
|
360
|
+
modelId: string,
|
|
361
|
+
scanResult: ScanResult
|
|
269
362
|
): Promise<AIAnalysisResult | null> {
|
|
270
363
|
const tools = createExplorationTools(scanResult.projectRoot);
|
|
271
364
|
|
|
@@ -300,22 +393,79 @@ When done exploring, output your final analysis as valid JSON matching this stru
|
|
|
300
393
|
}`;
|
|
301
394
|
|
|
302
395
|
// Use agentic loop - AI will call tools until it has enough info
|
|
303
|
-
|
|
304
|
-
const { text } = await generateText({
|
|
396
|
+
const result = await generateText({
|
|
305
397
|
model,
|
|
306
398
|
system: SYSTEM_PROMPT_AGENTIC,
|
|
307
399
|
prompt,
|
|
308
400
|
tools,
|
|
309
401
|
stopWhen: stepCountIs(10),
|
|
310
402
|
maxOutputTokens: 4000,
|
|
311
|
-
// Reasoning models don't support temperature
|
|
312
403
|
...(isReasoningModel(modelId) ? {} : { temperature: 0.3 }),
|
|
313
404
|
});
|
|
314
405
|
|
|
315
|
-
|
|
406
|
+
// Try to get text from the result
|
|
407
|
+
let textToParse = result.text;
|
|
408
|
+
|
|
409
|
+
// If text is empty, try to extract from steps
|
|
410
|
+
if (!textToParse || textToParse.trim() === '') {
|
|
411
|
+
if (this.verbose) {
|
|
412
|
+
logger.info(`No direct text output, checking ${result.steps?.length || 0} steps...`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const steps = result.steps || [];
|
|
416
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
417
|
+
const step = steps[i];
|
|
418
|
+
if (step.text && step.text.trim() !== '') {
|
|
419
|
+
textToParse = step.text;
|
|
420
|
+
if (this.verbose) {
|
|
421
|
+
logger.info(`Found text in step ${i + 1}`);
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (this.verbose && (!textToParse || textToParse.trim() === '')) {
|
|
429
|
+
logger.warn('No text output found in response or steps');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return parseAIResponse(textToParse, this.verbose);
|
|
316
433
|
}
|
|
317
434
|
}
|
|
318
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Convert MultiAgentAnalysis to AIAnalysisResult for backward compatibility
|
|
438
|
+
*/
|
|
439
|
+
function convertMultiAgentToAIAnalysis(multiAgent: MultiAgentAnalysis): AIAnalysisResult {
|
|
440
|
+
const { codebaseAnalysis, stackResearch, mcpServers } = multiAgent;
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
projectContext: {
|
|
444
|
+
entryPoints: codebaseAnalysis.projectContext.entryPoints,
|
|
445
|
+
keyDirectories: codebaseAnalysis.projectContext.keyDirectories,
|
|
446
|
+
namingConventions: codebaseAnalysis.projectContext.namingConventions,
|
|
447
|
+
},
|
|
448
|
+
commands: codebaseAnalysis.commands,
|
|
449
|
+
implementationGuidelines: codebaseAnalysis.implementationGuidelines,
|
|
450
|
+
mcpServers: {
|
|
451
|
+
essential: mcpServers.essential,
|
|
452
|
+
recommended: mcpServers.recommended,
|
|
453
|
+
},
|
|
454
|
+
possibleMissedTechnologies: codebaseAnalysis.possibleMissedTechnologies,
|
|
455
|
+
technologyTools: {
|
|
456
|
+
testing: stackResearch.testingTools,
|
|
457
|
+
debugging: stackResearch.debuggingTools,
|
|
458
|
+
validation: [],
|
|
459
|
+
},
|
|
460
|
+
technologyPractices: {
|
|
461
|
+
projectType: codebaseAnalysis.projectContext.projectType,
|
|
462
|
+
practices: stackResearch.bestPractices,
|
|
463
|
+
antiPatterns: stackResearch.antiPatterns,
|
|
464
|
+
documentationHints: stackResearch.documentationHints,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
319
469
|
/**
|
|
320
470
|
* Convenience function to enhance scan results with AI
|
|
321
471
|
*/
|