wiggum-cli 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/ai/agents/context-enricher.d.ts +10 -0
  2. package/dist/ai/agents/context-enricher.d.ts.map +1 -1
  3. package/dist/ai/agents/context-enricher.js +63 -4
  4. package/dist/ai/agents/context-enricher.js.map +1 -1
  5. package/dist/ai/agents/index.js +3 -2
  6. package/dist/ai/agents/index.js.map +1 -1
  7. package/dist/ai/agents/mcp-detector.d.ts +3 -2
  8. package/dist/ai/agents/mcp-detector.d.ts.map +1 -1
  9. package/dist/ai/agents/mcp-detector.js +23 -3
  10. package/dist/ai/agents/mcp-detector.js.map +1 -1
  11. package/dist/ai/agents/stack-researcher.js +2 -0
  12. package/dist/ai/agents/stack-researcher.js.map +1 -1
  13. package/dist/ai/agents/synthesis-agent.d.ts.map +1 -1
  14. package/dist/ai/agents/synthesis-agent.js +32 -9
  15. package/dist/ai/agents/synthesis-agent.js.map +1 -1
  16. package/dist/ai/agents/tech-researcher.d.ts +11 -0
  17. package/dist/ai/agents/tech-researcher.d.ts.map +1 -1
  18. package/dist/ai/agents/tech-researcher.js +71 -1
  19. package/dist/ai/agents/tech-researcher.js.map +1 -1
  20. package/dist/ai/agents/types.d.ts +2 -0
  21. package/dist/ai/agents/types.d.ts.map +1 -1
  22. package/dist/ai/enhancer.js +1 -1
  23. package/dist/ai/enhancer.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/ai/agents/context-enricher.test.ts +190 -0
  26. package/src/ai/agents/context-enricher.ts +69 -4
  27. package/src/ai/agents/index.ts +3 -2
  28. package/src/ai/agents/mcp-detector.test.ts +64 -9
  29. package/src/ai/agents/mcp-detector.ts +23 -3
  30. package/src/ai/agents/stack-researcher.ts +2 -0
  31. package/src/ai/agents/synthesis-agent.ts +46 -9
  32. package/src/ai/agents/tech-researcher.test.ts +156 -0
  33. package/src/ai/agents/tech-researcher.ts +86 -1
  34. package/src/ai/agents/types.ts +2 -0
  35. package/src/ai/enhancer.ts +1 -1
@@ -24,6 +24,11 @@ import { getTracedAI } from '../../utils/tracing.js';
24
24
  const synthesisOutputSchema = z.object({
25
25
  implementationGuidelines: z.array(z.string()).describe('Short, actionable implementation guidelines'),
26
26
  possibleMissedTechnologies: z.array(z.string()).describe('Technologies that may have been missed (empty array if none)'),
27
+ technologyTools: z.object({
28
+ testing: z.array(z.string()).describe('Commands to run tests (e.g., "npm test", "npx vitest")'),
29
+ debugging: z.array(z.string()).describe('Debug flags, env vars, tools (e.g., "DEBUG=* npm run dev")'),
30
+ validation: z.array(z.string()).describe('Type checking, linting commands (e.g., "npm run lint", "npx tsc --noEmit")'),
31
+ }).optional(),
27
32
  });
28
33
 
29
34
  /**
@@ -35,6 +40,7 @@ const SYNTHESIS_AGENT_SYSTEM_PROMPT = `You are a Synthesis Agent that merges ana
35
40
  Based on the enriched context and technology research, generate:
36
41
  1. Short implementation guidelines (5-10 words each) describing DISCOVERED patterns
37
42
  2. List any technologies that may have been missed
43
+ 3. Technology tools for testing, debugging, and validation
38
44
 
39
45
  ## Guidelines Style
40
46
  - Describe DISCOVERED patterns, not instructions to follow
@@ -48,6 +54,12 @@ Based on the enriched context and technology research, generate:
48
54
  - Bad: "Use Zod for validation"
49
55
  - Max 7 patterns, prioritize most distinctive features
50
56
 
57
+ ## Technology Tools
58
+ Based on the detected stack and available commands, provide:
59
+ - testing: Commands to verify changes (e.g., "npm test", "npx vitest")
60
+ - debugging: How to debug (e.g., "DEBUG=* npm run dev", "--verbose flag", "NODE_DEBUG=http")
61
+ - validation: Pre-commit checks (e.g., "npm run lint", "npx tsc --noEmit")
62
+
51
63
  ## Example Output
52
64
  {
53
65
  "implementationGuidelines": [
@@ -57,7 +69,12 @@ Based on the enriched context and technology research, generate:
57
69
  "Zod schemas in src/schemas for API validation",
58
70
  "Playwright E2E tests in tests/e2e"
59
71
  ],
60
- "possibleMissedTechnologies": ["Redis caching"]
72
+ "possibleMissedTechnologies": ["Redis caching"],
73
+ "technologyTools": {
74
+ "testing": ["npm test", "npx vitest --watch"],
75
+ "debugging": ["DEBUG=* npm run dev", "--verbose flag"],
76
+ "validation": ["npm run lint", "npx tsc --noEmit"]
77
+ }
61
78
  }`;
62
79
 
63
80
  /**
@@ -121,7 +138,7 @@ Generate concise, actionable implementation guidelines based on this analysis.`;
121
138
  }
122
139
 
123
140
  // Convert to MultiAgentAnalysis format for backward compatibility
124
- return buildMultiAgentAnalysis(input, synthesis.implementationGuidelines, synthesis.possibleMissedTechnologies);
141
+ return buildMultiAgentAnalysis(input, synthesis.implementationGuidelines, synthesis.possibleMissedTechnologies, synthesis.technologyTools);
125
142
  } catch (error) {
126
143
  if (verbose) {
127
144
  logger.error(`Synthesis Agent error: ${error instanceof Error ? error.message : String(error)}`);
@@ -132,13 +149,23 @@ Generate concise, actionable implementation guidelines based on this analysis.`;
132
149
  }
133
150
  }
134
151
 
152
+ /**
153
+ * Technology tools output from synthesis
154
+ */
155
+ interface TechnologyTools {
156
+ testing?: string[];
157
+ debugging?: string[];
158
+ validation?: string[];
159
+ }
160
+
135
161
  /**
136
162
  * Build MultiAgentAnalysis from synthesis input and generated guidelines
137
163
  */
138
164
  function buildMultiAgentAnalysis(
139
165
  input: SynthesisInput,
140
166
  implementationGuidelines: string[],
141
- possibleMissedTechnologies?: string[]
167
+ possibleMissedTechnologies?: string[],
168
+ technologyTools?: TechnologyTools
142
169
  ): MultiAgentAnalysis {
143
170
  // Convert EnrichedContext to CodebaseAnalysis format
144
171
  const codebaseAnalysis: CodebaseAnalysis = {
@@ -161,7 +188,7 @@ function buildMultiAgentAnalysis(
161
188
  };
162
189
 
163
190
  // Merge tech research into StackResearch format
164
- const stackResearch: StackResearch = mergeTechResearch(input.techResearch);
191
+ const stackResearch: StackResearch = mergeTechResearch(input.techResearch, technologyTools);
165
192
 
166
193
  // Convert MCP servers to legacy format
167
194
  const mcpServers: McpRecommendations = convertToLegacyMcpRecommendations(input.mcpServers);
@@ -176,13 +203,22 @@ function buildMultiAgentAnalysis(
176
203
  /**
177
204
  * Merge multiple TechResearchResult into a single StackResearch
178
205
  */
179
- function mergeTechResearch(techResearch: SynthesisInput['techResearch']): StackResearch {
206
+ function mergeTechResearch(
207
+ techResearch: SynthesisInput['techResearch'],
208
+ technologyTools?: TechnologyTools
209
+ ): StackResearch {
210
+ // Use technologyTools from synthesis if available
211
+ const synthesisTesting = technologyTools?.testing || [];
212
+ const synthesisDebugging = technologyTools?.debugging || [];
213
+ const synthesisValidation = technologyTools?.validation || [];
214
+
180
215
  if (techResearch.length === 0) {
181
216
  return {
182
217
  bestPractices: ['Follow project conventions'],
183
218
  antiPatterns: ['Avoid skipping tests'],
184
- testingTools: ['npm test'],
185
- debuggingTools: ['console.log'],
219
+ testingTools: synthesisTesting.length > 0 ? synthesisTesting : ['npm test'],
220
+ debuggingTools: synthesisDebugging.length > 0 ? synthesisDebugging : ['console.log'],
221
+ validationTools: synthesisValidation.length > 0 ? synthesisValidation : ['npm run lint'],
186
222
  documentationHints: ['Check official docs'],
187
223
  researchMode: 'knowledge-only',
188
224
  };
@@ -191,7 +227,7 @@ function mergeTechResearch(techResearch: SynthesisInput['techResearch']): StackR
191
227
  // Merge all research results
192
228
  const bestPractices: string[] = [];
193
229
  const antiPatterns: string[] = [];
194
- const testingTips: string[] = [];
230
+ const testingTips: string[] = [...synthesisTesting]; // Start with synthesis testing tools
195
231
  const documentationHints: string[] = [];
196
232
  let researchMode: StackResearch['researchMode'] = 'knowledge-only';
197
233
 
@@ -212,7 +248,8 @@ function mergeTechResearch(techResearch: SynthesisInput['techResearch']): StackR
212
248
  bestPractices: [...new Set(bestPractices)].slice(0, 10),
213
249
  antiPatterns: [...new Set(antiPatterns)].slice(0, 10),
214
250
  testingTools: [...new Set(testingTips)].slice(0, 5),
215
- debuggingTools: [], // Not collected in new format
251
+ debuggingTools: [...new Set(synthesisDebugging)].slice(0, 5),
252
+ validationTools: [...new Set(synthesisValidation)].slice(0, 5),
216
253
  documentationHints: [...new Set(documentationHints)].slice(0, 5),
217
254
  researchMode,
218
255
  };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Tests for Tech Researcher
3
+ *
4
+ * Run with: npx vitest run src/ai/agents/tech-researcher.test.ts
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { getDocumentationHints, DOCUMENTATION_HINTS } from './tech-researcher.js';
9
+
10
+ describe('getDocumentationHints', () => {
11
+ describe('direct matches', () => {
12
+ it('returns hints for exact match "Next.js"', () => {
13
+ const result = getDocumentationHints('Next.js');
14
+ expect(result).toEqual(DOCUMENTATION_HINTS['Next.js']);
15
+ expect(result).toContain('https://nextjs.org/docs/app');
16
+ });
17
+
18
+ it('returns hints for exact match "React"', () => {
19
+ const result = getDocumentationHints('React');
20
+ expect(result).toEqual(DOCUMENTATION_HINTS['React']);
21
+ expect(result).toContain('https://react.dev');
22
+ });
23
+
24
+ it('returns hints for exact match "MCP"', () => {
25
+ const result = getDocumentationHints('MCP');
26
+ expect(result).toEqual(DOCUMENTATION_HINTS['MCP']);
27
+ expect(result).toContain('https://modelcontextprotocol.io/docs');
28
+ });
29
+
30
+ it('returns hints for exact match "Vitest"', () => {
31
+ const result = getDocumentationHints('Vitest');
32
+ expect(result).toEqual(DOCUMENTATION_HINTS['Vitest']);
33
+ expect(result).toContain('https://vitest.dev/guide');
34
+ });
35
+
36
+ it('returns hints for exact match "TypeScript"', () => {
37
+ const result = getDocumentationHints('TypeScript');
38
+ expect(result).toEqual(DOCUMENTATION_HINTS['TypeScript']);
39
+ expect(result).toContain('https://www.typescriptlang.org/docs');
40
+ });
41
+ });
42
+
43
+ describe('partial matches (case-insensitive)', () => {
44
+ it('matches "next.js" (lowercase) to Next.js', () => {
45
+ const result = getDocumentationHints('next.js');
46
+ expect(result).toEqual(DOCUMENTATION_HINTS['Next.js']);
47
+ });
48
+
49
+ it('matches "REACT" (uppercase) to React', () => {
50
+ const result = getDocumentationHints('REACT');
51
+ expect(result).toEqual(DOCUMENTATION_HINTS['React']);
52
+ });
53
+
54
+ it('matches "typescript" (lowercase) to TypeScript', () => {
55
+ const result = getDocumentationHints('typescript');
56
+ expect(result).toEqual(DOCUMENTATION_HINTS['TypeScript']);
57
+ });
58
+
59
+ it('matches "vitest" (lowercase) to Vitest', () => {
60
+ const result = getDocumentationHints('vitest');
61
+ expect(result).toEqual(DOCUMENTATION_HINTS['Vitest']);
62
+ });
63
+ });
64
+
65
+ describe('partial string matches', () => {
66
+ it('matches "Next.js 14" to Next.js', () => {
67
+ const result = getDocumentationHints('Next.js 14');
68
+ expect(result).toEqual(DOCUMENTATION_HINTS['Next.js']);
69
+ });
70
+
71
+ it('matches "React 18" to React', () => {
72
+ const result = getDocumentationHints('React 18');
73
+ expect(result).toEqual(DOCUMENTATION_HINTS['React']);
74
+ });
75
+
76
+ it('matches "MCP Server" key exactly', () => {
77
+ const result = getDocumentationHints('MCP Server');
78
+ expect(result).toEqual(DOCUMENTATION_HINTS['MCP Server']);
79
+ });
80
+ });
81
+
82
+ describe('fallback behavior', () => {
83
+ it('returns generic hint for unknown technology', () => {
84
+ const result = getDocumentationHints('UnknownFramework');
85
+ expect(result).toEqual(['Check official UnknownFramework documentation']);
86
+ });
87
+
88
+ it('returns generic hint for empty string', () => {
89
+ const result = getDocumentationHints('');
90
+ expect(result).toEqual(['Check official documentation']);
91
+ });
92
+
93
+ it('returns generic hint for technology not in mapping', () => {
94
+ const result = getDocumentationHints('SomeRandomLib');
95
+ expect(result).toEqual(['Check official SomeRandomLib documentation']);
96
+ });
97
+ });
98
+
99
+ describe('specific technology coverage', () => {
100
+ it('has MCP ecosystem hints', () => {
101
+ expect(DOCUMENTATION_HINTS['MCP']).toBeDefined();
102
+ expect(DOCUMENTATION_HINTS['MCP Server']).toBeDefined();
103
+ expect(DOCUMENTATION_HINTS['@modelcontextprotocol/sdk']).toBeDefined();
104
+ });
105
+
106
+ it('has frontend framework hints', () => {
107
+ expect(DOCUMENTATION_HINTS['Next.js']).toBeDefined();
108
+ expect(DOCUMENTATION_HINTS['React']).toBeDefined();
109
+ expect(DOCUMENTATION_HINTS['Vue']).toBeDefined();
110
+ expect(DOCUMENTATION_HINTS['Svelte']).toBeDefined();
111
+ expect(DOCUMENTATION_HINTS['Nuxt']).toBeDefined();
112
+ });
113
+
114
+ it('has backend framework hints', () => {
115
+ expect(DOCUMENTATION_HINTS['Express']).toBeDefined();
116
+ expect(DOCUMENTATION_HINTS['Fastify']).toBeDefined();
117
+ expect(DOCUMENTATION_HINTS['Hono']).toBeDefined();
118
+ expect(DOCUMENTATION_HINTS['NestJS']).toBeDefined();
119
+ });
120
+
121
+ it('has testing tool hints', () => {
122
+ expect(DOCUMENTATION_HINTS['Vitest']).toBeDefined();
123
+ expect(DOCUMENTATION_HINTS['Jest']).toBeDefined();
124
+ expect(DOCUMENTATION_HINTS['Playwright']).toBeDefined();
125
+ });
126
+
127
+ it('has database/ORM hints', () => {
128
+ expect(DOCUMENTATION_HINTS['Prisma']).toBeDefined();
129
+ expect(DOCUMENTATION_HINTS['Drizzle']).toBeDefined();
130
+ expect(DOCUMENTATION_HINTS['Supabase']).toBeDefined();
131
+ });
132
+
133
+ it('has CLI tool hints', () => {
134
+ expect(DOCUMENTATION_HINTS['Commander']).toBeDefined();
135
+ expect(DOCUMENTATION_HINTS['Yargs']).toBeDefined();
136
+ });
137
+ });
138
+ });
139
+
140
+ describe('DOCUMENTATION_HINTS', () => {
141
+ it('has valid URLs for all entries', () => {
142
+ for (const [tech, hints] of Object.entries(DOCUMENTATION_HINTS)) {
143
+ expect(Array.isArray(hints)).toBe(true);
144
+ expect(hints.length).toBeGreaterThan(0);
145
+ for (const hint of hints) {
146
+ expect(hint).toMatch(/^https?:\/\//);
147
+ }
148
+ }
149
+ });
150
+
151
+ it('has no duplicate entries', () => {
152
+ const keys = Object.keys(DOCUMENTATION_HINTS);
153
+ const uniqueKeys = new Set(keys);
154
+ expect(keys.length).toBe(uniqueKeys.size);
155
+ });
156
+ });
@@ -13,6 +13,90 @@ import { logger } from '../../utils/logger.js';
13
13
  import { parseJsonSafe } from '../../utils/json-repair.js';
14
14
  import { getTracedAI } from '../../utils/tracing.js';
15
15
 
16
+ /**
17
+ * Documentation hints mapping for common technologies
18
+ * Used to provide useful links when AI research fails or as supplements
19
+ * Exported for testing
20
+ */
21
+ export const DOCUMENTATION_HINTS: Record<string, string[]> = {
22
+ // MCP ecosystem
23
+ 'MCP': ['https://modelcontextprotocol.io/docs', 'https://modelcontextprotocol.io/docs/tools/inspector'],
24
+ 'MCP Server': ['https://modelcontextprotocol.io/docs', 'https://modelcontextprotocol.io/docs/tools/inspector'],
25
+ '@modelcontextprotocol/sdk': ['https://modelcontextprotocol.io/docs/tools/inspector'],
26
+
27
+ // Frontend frameworks
28
+ 'Next.js': ['https://nextjs.org/docs/app', 'https://nextjs.org/docs/app/building-your-application'],
29
+ 'React': ['https://react.dev', 'https://react.dev/learn'],
30
+ 'Vue': ['https://vuejs.org/guide', 'https://vuejs.org/api'],
31
+ 'Svelte': ['https://svelte.dev/docs', 'https://kit.svelte.dev/docs'],
32
+ 'Nuxt': ['https://nuxt.com/docs', 'https://nuxt.com/docs/api'],
33
+
34
+ // Backend frameworks
35
+ 'Express': ['https://expressjs.com/en/guide', 'https://expressjs.com/en/api.html'],
36
+ 'Fastify': ['https://fastify.dev/docs/latest', 'https://fastify.dev/docs/latest/Guides/Getting-Started'],
37
+ 'Hono': ['https://hono.dev/docs', 'https://hono.dev/docs/guides'],
38
+ 'NestJS': ['https://docs.nestjs.com', 'https://docs.nestjs.com/first-steps'],
39
+
40
+ // Testing
41
+ 'Vitest': ['https://vitest.dev/guide', 'https://vitest.dev/api'],
42
+ 'Jest': ['https://jestjs.io/docs/getting-started', 'https://jestjs.io/docs/api'],
43
+ 'Playwright': ['https://playwright.dev/docs/intro', 'https://playwright.dev/docs/api/class-test'],
44
+
45
+ // Validation
46
+ 'Zod': ['https://zod.dev', 'https://zod.dev/?id=primitives'],
47
+ 'Yup': ['https://github.com/jquense/yup#api'],
48
+
49
+ // Database
50
+ 'Prisma': ['https://www.prisma.io/docs', 'https://www.prisma.io/docs/orm/prisma-client'],
51
+ 'Drizzle': ['https://orm.drizzle.team/docs/overview', 'https://orm.drizzle.team/docs/sql-schema-declaration'],
52
+ 'Supabase': ['https://supabase.com/docs', 'https://supabase.com/docs/guides/database'],
53
+
54
+ // TypeScript
55
+ 'TypeScript': ['https://www.typescriptlang.org/docs', 'https://www.typescriptlang.org/docs/handbook'],
56
+
57
+ // CLI tools
58
+ 'Commander': ['https://github.com/tj/commander.js#readme'],
59
+ 'Yargs': ['https://yargs.js.org/docs'],
60
+ };
61
+
62
+ /**
63
+ * Get documentation hints for a technology
64
+ * Exported for testing
65
+ */
66
+ export function getDocumentationHints(technology: string): string[] {
67
+ // Direct match
68
+ if (DOCUMENTATION_HINTS[technology]) {
69
+ return DOCUMENTATION_HINTS[technology];
70
+ }
71
+
72
+ // Empty string should return generic fallback
73
+ if (!technology.trim()) {
74
+ return [`Check official ${technology} documentation`];
75
+ }
76
+
77
+ const lowerTech = technology.toLowerCase();
78
+
79
+ // Case-insensitive exact match first
80
+ for (const [key, hints] of Object.entries(DOCUMENTATION_HINTS)) {
81
+ if (key.toLowerCase() === lowerTech) {
82
+ return hints;
83
+ }
84
+ }
85
+
86
+ // Partial match - prefer longer keys to avoid "React" matching "React Native"
87
+ // Sort keys by length descending so longer/more specific matches win
88
+ const sortedKeys = Object.keys(DOCUMENTATION_HINTS).sort((a, b) => b.length - a.length);
89
+
90
+ for (const key of sortedKeys) {
91
+ const lowerKey = key.toLowerCase();
92
+ if (lowerTech.includes(lowerKey) || lowerKey.includes(lowerTech)) {
93
+ return DOCUMENTATION_HINTS[key];
94
+ }
95
+ }
96
+
97
+ return [`Check official ${technology} documentation`];
98
+ }
99
+
16
100
  /**
17
101
  * Get the current year for dynamic prompt generation
18
102
  */
@@ -265,6 +349,7 @@ function parseTechResearch(
265
349
 
266
350
  /**
267
351
  * Get default tech research when parsing fails
352
+ * Uses the DOCUMENTATION_HINTS mapping for relevant URLs
268
353
  */
269
354
  function getDefaultTechResearch(
270
355
  technology: string,
@@ -275,7 +360,7 @@ function getDefaultTechResearch(
275
360
  bestPractices: ['Follow official documentation', 'Use TypeScript for type safety'],
276
361
  antiPatterns: ['Avoid deprecated APIs', 'Don\'t skip error handling'],
277
362
  testingTips: ['Write unit tests for core logic', 'Test edge cases'],
278
- documentationHints: [`Check official ${technology} documentation`],
363
+ documentationHints: getDocumentationHints(technology),
279
364
  researchMode,
280
365
  };
281
366
  }
@@ -157,6 +157,8 @@ export interface StackResearch {
157
157
  testingTools: string[];
158
158
  /** Technology-specific debugging tools */
159
159
  debuggingTools: string[];
160
+ /** Technology-specific validation tools (linting, type checking) */
161
+ validationTools: string[];
160
162
  /** Documentation hints and links */
161
163
  documentationHints: string[];
162
164
  /** Whether research was performed with tools or knowledge-only */
@@ -452,7 +452,7 @@ function convertMultiAgentToAIAnalysis(multiAgent: MultiAgentAnalysis): AIAnalys
452
452
  technologyTools: {
453
453
  testing: stackResearch.testingTools,
454
454
  debugging: stackResearch.debuggingTools,
455
- validation: [],
455
+ validation: stackResearch.validationTools,
456
456
  },
457
457
  technologyPractices: {
458
458
  projectType: codebaseAnalysis.projectContext.projectType,