wiggum-cli 0.3.2 → 0.4.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.
Files changed (79) hide show
  1. package/README.md +6 -4
  2. package/dist/ai/agents/codebase-analyst.d.ts +3 -0
  3. package/dist/ai/agents/codebase-analyst.d.ts.map +1 -1
  4. package/dist/ai/agents/codebase-analyst.js +3 -0
  5. package/dist/ai/agents/codebase-analyst.js.map +1 -1
  6. package/dist/ai/agents/context-enricher.d.ts +11 -0
  7. package/dist/ai/agents/context-enricher.d.ts.map +1 -0
  8. package/dist/ai/agents/context-enricher.js +163 -0
  9. package/dist/ai/agents/context-enricher.js.map +1 -0
  10. package/dist/ai/agents/evaluator-optimizer.d.ts +13 -0
  11. package/dist/ai/agents/evaluator-optimizer.d.ts.map +1 -0
  12. package/dist/ai/agents/evaluator-optimizer.js +231 -0
  13. package/dist/ai/agents/evaluator-optimizer.js.map +1 -0
  14. package/dist/ai/agents/index.d.ts +21 -3
  15. package/dist/ai/agents/index.d.ts.map +1 -1
  16. package/dist/ai/agents/index.js +151 -82
  17. package/dist/ai/agents/index.js.map +1 -1
  18. package/dist/ai/agents/mcp-detector.d.ts +26 -0
  19. package/dist/ai/agents/mcp-detector.d.ts.map +1 -0
  20. package/dist/ai/agents/mcp-detector.js +186 -0
  21. package/dist/ai/agents/mcp-detector.js.map +1 -0
  22. package/dist/ai/agents/orchestrator.d.ts +3 -0
  23. package/dist/ai/agents/orchestrator.d.ts.map +1 -1
  24. package/dist/ai/agents/orchestrator.js +3 -0
  25. package/dist/ai/agents/orchestrator.js.map +1 -1
  26. package/dist/ai/agents/planning-orchestrator.d.ts +12 -0
  27. package/dist/ai/agents/planning-orchestrator.d.ts.map +1 -0
  28. package/dist/ai/agents/planning-orchestrator.js +133 -0
  29. package/dist/ai/agents/planning-orchestrator.js.map +1 -0
  30. package/dist/ai/agents/stack-researcher.d.ts +3 -0
  31. package/dist/ai/agents/stack-researcher.d.ts.map +1 -1
  32. package/dist/ai/agents/stack-researcher.js +3 -0
  33. package/dist/ai/agents/stack-researcher.js.map +1 -1
  34. package/dist/ai/agents/stack-utils.d.ts +11 -0
  35. package/dist/ai/agents/stack-utils.d.ts.map +1 -0
  36. package/dist/ai/agents/stack-utils.js +27 -0
  37. package/dist/ai/agents/stack-utils.js.map +1 -0
  38. package/dist/ai/agents/synthesis-agent.d.ts +11 -0
  39. package/dist/ai/agents/synthesis-agent.d.ts.map +1 -0
  40. package/dist/ai/agents/synthesis-agent.js +202 -0
  41. package/dist/ai/agents/synthesis-agent.js.map +1 -0
  42. package/dist/ai/agents/tech-researcher.d.ts +16 -0
  43. package/dist/ai/agents/tech-researcher.d.ts.map +1 -0
  44. package/dist/ai/agents/tech-researcher.js +208 -0
  45. package/dist/ai/agents/tech-researcher.js.map +1 -0
  46. package/dist/ai/agents/types.d.ts +121 -0
  47. package/dist/ai/agents/types.d.ts.map +1 -1
  48. package/dist/ai/agents/types.js +6 -0
  49. package/dist/ai/agents/types.js.map +1 -1
  50. package/dist/ai/index.d.ts +1 -1
  51. package/dist/ai/index.d.ts.map +1 -1
  52. package/dist/ai/index.js +14 -2
  53. package/dist/ai/index.js.map +1 -1
  54. package/dist/commands/init.d.ts.map +1 -1
  55. package/dist/commands/init.js +9 -2
  56. package/dist/commands/init.js.map +1 -1
  57. package/dist/utils/tracing.d.ts +5 -0
  58. package/dist/utils/tracing.d.ts.map +1 -1
  59. package/dist/utils/tracing.js +40 -1
  60. package/dist/utils/tracing.js.map +1 -1
  61. package/package.json +5 -2
  62. package/src/ai/agents/codebase-analyst.ts +3 -0
  63. package/src/ai/agents/context-enricher.ts +189 -0
  64. package/src/ai/agents/evaluator-optimizer.ts +277 -0
  65. package/src/ai/agents/index.ts +197 -104
  66. package/src/ai/agents/mcp-detector.test.ts +290 -0
  67. package/src/ai/agents/mcp-detector.ts +210 -0
  68. package/src/ai/agents/orchestrator.ts +3 -0
  69. package/src/ai/agents/planning-orchestrator.ts +140 -0
  70. package/src/ai/agents/stack-researcher.ts +3 -0
  71. package/src/ai/agents/stack-utils.ts +34 -0
  72. package/src/ai/agents/synthesis-agent.ts +240 -0
  73. package/src/ai/agents/tech-researcher.ts +262 -0
  74. package/src/ai/agents/types.ts +153 -0
  75. package/src/ai/index.ts +26 -5
  76. package/src/commands/init.ts +10 -2
  77. package/src/utils/tracing.ts +44 -1
  78. package/tsconfig.json +1 -1
  79. package/vitest.config.ts +7 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Tech Researcher Worker (Phase 2)
3
+ * Researches best practices for a specific technology
4
+ * Multiple instances run in parallel via runTechResearchPool
5
+ */
6
+
7
+ import { stepCountIs, type LanguageModel, type Tool } from 'ai';
8
+ import type { TechResearcherInput, TechResearchResult, AgentCapabilities, AgentOptions } from './types.js';
9
+ import { createTavilySearchTool } from '../tools/tavily.js';
10
+ import { createContext7Tool } from '../tools/context7.js';
11
+ import { isReasoningModel } from '../providers.js';
12
+ import { logger } from '../../utils/logger.js';
13
+ import { parseJsonSafe } from '../../utils/json-repair.js';
14
+ import { getTracedAI } from '../../utils/tracing.js';
15
+
16
+ /**
17
+ * System prompt for Tech Researcher with tools
18
+ */
19
+ const TECH_RESEARCHER_WITH_TOOLS_PROMPT = `You are a Tech Researcher worker focused on a single technology.
20
+
21
+ ## Your Mission
22
+ Research the specified technology to find:
23
+ 1. Current best practices (2024+)
24
+ 2. Common anti-patterns to avoid
25
+ 3. Testing tips and patterns
26
+ 4. Useful documentation links
27
+
28
+ ## Tools Available
29
+ - tavilySearch: Search the web for current best practices
30
+ - context7Lookup: Look up library documentation
31
+
32
+ ## Research Strategy
33
+ 1. Search for "[technology] best practices 2024"
34
+ 2. Search for "[technology] testing patterns"
35
+ 3. Look up documentation for key features
36
+
37
+ ## Output Format
38
+ Output ONLY valid JSON:
39
+ {
40
+ "technology": "Next.js 14",
41
+ "bestPractices": ["Use App Router for new projects", "Enable strict TypeScript"],
42
+ "antiPatterns": ["Don't use pages/ and app/ together", "Avoid client components for static content"],
43
+ "testingTips": ["Use @testing-library/react", "Mock next/navigation for routing tests"],
44
+ "documentationHints": ["App Router: nextjs.org/docs/app", "Data Fetching: nextjs.org/docs/app/building-your-application/data-fetching"],
45
+ "researchMode": "full"
46
+ }
47
+
48
+ Keep each item concise (5-15 words max). Max 5 items per array.`;
49
+
50
+ /**
51
+ * System prompt for Tech Researcher without tools (knowledge-only)
52
+ */
53
+ const TECH_RESEARCHER_KNOWLEDGE_ONLY_PROMPT = `You are a Tech Researcher worker. You don't have web access, so rely on your training knowledge.
54
+
55
+ ## Your Mission
56
+ Based on your knowledge of the specified technology, provide:
57
+ 1. Best practices (note if potentially outdated)
58
+ 2. Common anti-patterns to avoid
59
+ 3. Testing tips
60
+ 4. Documentation hints
61
+
62
+ ## Output Format
63
+ Output ONLY valid JSON:
64
+ {
65
+ "technology": "React",
66
+ "bestPractices": ["Use functional components with hooks", "Memoize expensive computations"],
67
+ "antiPatterns": ["Don't mutate state directly", "Avoid prop drilling"],
68
+ "testingTips": ["Use React Testing Library", "Test behavior not implementation"],
69
+ "documentationHints": ["React docs: react.dev", "Testing: testing-library.com"],
70
+ "researchMode": "knowledge-only"
71
+ }
72
+
73
+ Keep each item concise (5-15 words max). Max 5 items per array.`;
74
+
75
+ /**
76
+ * Determine research mode based on capabilities
77
+ */
78
+ function getResearchMode(capabilities: AgentCapabilities): TechResearchResult['researchMode'] {
79
+ if (capabilities.hasTavily && capabilities.hasContext7) return 'full';
80
+ if (capabilities.hasTavily) return 'web-only';
81
+ if (capabilities.hasContext7) return 'docs-only';
82
+ return 'knowledge-only';
83
+ }
84
+
85
+ /**
86
+ * Run a single Tech Researcher worker for one technology
87
+ */
88
+ export async function runTechResearcher(
89
+ model: LanguageModel,
90
+ modelId: string,
91
+ input: TechResearcherInput,
92
+ options: AgentOptions,
93
+ verbose: boolean = false
94
+ ): Promise<TechResearchResult> {
95
+ const tools: Record<string, Tool> = {};
96
+
97
+ // Add tools based on available keys
98
+ if (options.tavilyApiKey) {
99
+ tools.tavilySearch = createTavilySearchTool(options.tavilyApiKey);
100
+ }
101
+ if (options.context7ApiKey) {
102
+ tools.context7Lookup = createContext7Tool(options.context7ApiKey);
103
+ }
104
+
105
+ const hasTools = Object.keys(tools).length > 0;
106
+ const researchMode = getResearchMode(input.capabilities);
107
+
108
+ if (verbose) {
109
+ logger.info(`Tech Researcher [${input.technology}]: ${researchMode} mode`);
110
+ }
111
+
112
+ const systemPrompt = hasTools
113
+ ? TECH_RESEARCHER_WITH_TOOLS_PROMPT
114
+ : TECH_RESEARCHER_KNOWLEDGE_ONLY_PROMPT;
115
+
116
+ const prompt = `Research best practices for: ${input.technology}
117
+
118
+ Provide current best practices, anti-patterns to avoid, testing tips, and documentation hints.
119
+ Output your findings as JSON.`;
120
+
121
+ try {
122
+ const { generateText } = getTracedAI();
123
+
124
+ const result = await generateText({
125
+ model,
126
+ system: systemPrompt,
127
+ prompt,
128
+ ...(hasTools ? { tools, stopWhen: stepCountIs(3) } : {}),
129
+ maxOutputTokens: 2000,
130
+ ...(isReasoningModel(modelId) ? {} : { temperature: 0.3 }),
131
+ experimental_telemetry: {
132
+ isEnabled: true,
133
+ metadata: {
134
+ agent: 'tech-researcher',
135
+ technology: input.technology,
136
+ researchMode,
137
+ },
138
+ },
139
+ });
140
+
141
+ const research = parseTechResearch(result.text, result.steps, input.technology, researchMode, verbose);
142
+ return research;
143
+ } catch (error) {
144
+ if (verbose) {
145
+ logger.error(`Tech Researcher [${input.technology}] error: ${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+
148
+ return getDefaultTechResearch(input.technology, researchMode);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Run multiple Tech Researchers in parallel for a list of technologies
154
+ */
155
+ export async function runTechResearchPool(
156
+ model: LanguageModel,
157
+ modelId: string,
158
+ technologies: string[],
159
+ options: AgentOptions,
160
+ verbose: boolean = false
161
+ ): Promise<TechResearchResult[]> {
162
+ if (technologies.length === 0) {
163
+ return [];
164
+ }
165
+
166
+ // Determine capabilities once
167
+ const capabilities: AgentCapabilities = {
168
+ hasTavily: !!options.tavilyApiKey,
169
+ hasContext7: !!options.context7ApiKey,
170
+ };
171
+
172
+ if (verbose) {
173
+ logger.info(`Tech Research Pool: Starting ${technologies.length} parallel researchers`);
174
+ }
175
+
176
+ // Run all researchers in parallel
177
+ const results = await Promise.all(
178
+ technologies.map(technology =>
179
+ runTechResearcher(
180
+ model,
181
+ modelId,
182
+ { technology, capabilities },
183
+ options,
184
+ verbose
185
+ )
186
+ )
187
+ );
188
+
189
+ if (verbose) {
190
+ logger.info(`Tech Research Pool: Completed ${results.length} research tasks`);
191
+ }
192
+
193
+ return results;
194
+ }
195
+
196
+ /**
197
+ * Parse tech research from agent response
198
+ */
199
+ function parseTechResearch(
200
+ text: string,
201
+ steps: Array<{ text?: string }> | undefined,
202
+ technology: string,
203
+ researchMode: TechResearchResult['researchMode'],
204
+ verbose: boolean
205
+ ): TechResearchResult {
206
+ // Try to get text from the result or steps
207
+ let textToParse = text;
208
+
209
+ if (!textToParse || textToParse.trim() === '') {
210
+ const stepsList = steps || [];
211
+ for (let i = stepsList.length - 1; i >= 0; i--) {
212
+ const step = stepsList[i];
213
+ if (step.text && step.text.trim() !== '') {
214
+ textToParse = step.text;
215
+ break;
216
+ }
217
+ }
218
+ }
219
+
220
+ if (!textToParse || textToParse.trim() === '') {
221
+ if (verbose) {
222
+ logger.warn(`Tech Researcher [${technology}]: No text output found`);
223
+ }
224
+ return getDefaultTechResearch(technology, researchMode);
225
+ }
226
+
227
+ // Use safe JSON parser with repair capabilities
228
+ const parsed = parseJsonSafe<Partial<TechResearchResult>>(textToParse);
229
+
230
+ if (!parsed) {
231
+ if (verbose) {
232
+ logger.warn(`Tech Researcher [${technology}]: Failed to parse JSON response`);
233
+ }
234
+ return getDefaultTechResearch(technology, researchMode);
235
+ }
236
+
237
+ return {
238
+ technology,
239
+ bestPractices: parsed.bestPractices || [],
240
+ antiPatterns: parsed.antiPatterns || [],
241
+ testingTips: parsed.testingTips || [],
242
+ documentationHints: parsed.documentationHints || [],
243
+ researchMode,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Get default tech research when parsing fails
249
+ */
250
+ function getDefaultTechResearch(
251
+ technology: string,
252
+ researchMode: TechResearchResult['researchMode']
253
+ ): TechResearchResult {
254
+ return {
255
+ technology,
256
+ bestPractices: ['Follow official documentation', 'Use TypeScript for type safety'],
257
+ antiPatterns: ['Avoid deprecated APIs', 'Don\'t skip error handling'],
258
+ testingTips: ['Write unit tests for core logic', 'Test edge cases'],
259
+ documentationHints: [`Check official ${technology} documentation`],
260
+ researchMode,
261
+ };
262
+ }
@@ -1,12 +1,122 @@
1
1
  /**
2
2
  * Agent Types and Interfaces
3
3
  * Defines the structure for multi-agent analysis
4
+ *
5
+ * Architecture: Orchestrator-Worker + Evaluator-Optimizer
6
+ * Phase 1: Planning Orchestrator (creates analysis plan)
7
+ * Phase 2: Parallel Workers (context enricher + tech researchers)
8
+ * Phase 3: Synthesis (merge results + MCP detection)
9
+ * Phase 4: Evaluator-Optimizer (QA loop)
4
10
  */
5
11
 
6
12
  import type { ScanResult, DetectedStack } from '../../scanner/types.js';
7
13
 
14
+ // ============================================================
15
+ // Phase 1: Planning Orchestrator Types
16
+ // ============================================================
17
+
18
+ /**
19
+ * Analysis plan created by the Planning Orchestrator
20
+ * Guides the parallel workers in Phase 2
21
+ */
22
+ export interface AnalysisPlan {
23
+ /** Key areas to explore in the codebase */
24
+ areasToExplore: string[];
25
+ /** Technologies to research in depth */
26
+ technologiesToResearch: string[];
27
+ /** Specific questions that need answers for implementation guidance */
28
+ questionsToAnswer: string[];
29
+ /** Estimated complexity of the project */
30
+ estimatedComplexity: 'low' | 'medium' | 'high';
31
+ }
32
+
33
+ // ============================================================
34
+ // Phase 2: Worker Types
35
+ // ============================================================
36
+
37
+ /**
38
+ * Enriched context from codebase exploration
39
+ * Output from the Context Enricher worker
40
+ */
41
+ export interface EnrichedContext {
42
+ /** Key entry point files */
43
+ entryPoints: string[];
44
+ /** Important directories and their purposes */
45
+ keyDirectories: Record<string, string>;
46
+ /** Naming conventions used */
47
+ namingConventions: string;
48
+ /** Detected commands from package.json */
49
+ commands: Record<string, string>;
50
+ /** Answers to the orchestrator's questions */
51
+ answeredQuestions: Record<string, string>;
52
+ /** The primary project type detected */
53
+ projectType: string;
54
+ }
55
+
56
+ /**
57
+ * Research result for a single technology
58
+ * Output from a Tech Researcher worker
59
+ */
60
+ export interface TechResearchResult {
61
+ /** Technology that was researched */
62
+ technology: string;
63
+ /** Best practices for this technology */
64
+ bestPractices: string[];
65
+ /** Anti-patterns to avoid */
66
+ antiPatterns: string[];
67
+ /** Testing tips and tools */
68
+ testingTips: string[];
69
+ /** Documentation hints and links */
70
+ documentationHints: string[];
71
+ /** Whether research used tools or knowledge only */
72
+ researchMode: 'full' | 'web-only' | 'docs-only' | 'knowledge-only';
73
+ }
74
+
75
+ // ============================================================
76
+ // Phase 3: Synthesis Types
77
+ // ============================================================
78
+
79
+ /**
80
+ * MCP servers focused on ralph loop essentials
81
+ */
82
+ export interface RalphMcpServers {
83
+ /** Database MCP server if applicable */
84
+ database?: string;
85
+ /** E2E testing MCP (always playwright for ralph) */
86
+ e2eTesting: string;
87
+ /** Any additional recommended MCPs */
88
+ additional: string[];
89
+ }
90
+
91
+ // ============================================================
92
+ // Phase 4: Evaluator-Optimizer Types
93
+ // ============================================================
94
+
95
+ /**
96
+ * Evaluation result from the QA evaluator
97
+ */
98
+ export interface EvaluationResult {
99
+ /** Quality score from 1-10 */
100
+ qualityScore: number;
101
+ /** Whether entry points were identified */
102
+ hasEntryPoints: boolean;
103
+ /** Whether implementation guidelines were provided */
104
+ hasImplementationGuidelines: boolean;
105
+ /** Whether relevant MCP servers were recommended */
106
+ hasRelevantMcpServers: boolean;
107
+ /** Specific issues found */
108
+ specificIssues: string[];
109
+ /** Suggestions for improvement */
110
+ improvementSuggestions: string[];
111
+ }
112
+
113
+ // ============================================================
114
+ // Legacy Types (kept for backward compatibility)
115
+ // ============================================================
116
+
8
117
  /**
9
118
  * Codebase analysis result from the Codebase Analyst agent
119
+ * @deprecated Use EnrichedContext for new code
10
120
  */
11
121
  export interface CodebaseAnalysis {
12
122
  /** Project structure and context */
@@ -121,6 +231,7 @@ export interface StackResearcherInput {
121
231
 
122
232
  /**
123
233
  * Input for the Orchestrator agent
234
+ * @deprecated Use new agent architecture
124
235
  */
125
236
  export interface OrchestratorInput {
126
237
  /** Codebase analysis result */
@@ -130,3 +241,45 @@ export interface OrchestratorInput {
130
241
  /** The detected stack */
131
242
  stack: DetectedStack;
132
243
  }
244
+
245
+ // ============================================================
246
+ // New Agent Input Types
247
+ // ============================================================
248
+
249
+ /**
250
+ * Input for the Context Enricher worker
251
+ */
252
+ export interface ContextEnricherInput {
253
+ /** The scan result from the scanner */
254
+ scanResult: ScanResult;
255
+ /** Areas to explore from the analysis plan */
256
+ areasToExplore: string[];
257
+ /** Questions to answer from the analysis plan */
258
+ questionsToAnswer: string[];
259
+ }
260
+
261
+ /**
262
+ * Input for a Tech Researcher worker
263
+ */
264
+ export interface TechResearcherInput {
265
+ /** Technology to research */
266
+ technology: string;
267
+ /** Agent capabilities (determines which tools are available) */
268
+ capabilities: AgentCapabilities;
269
+ }
270
+
271
+ /**
272
+ * Input for the Synthesis agent
273
+ */
274
+ export interface SynthesisInput {
275
+ /** Enriched context from codebase exploration */
276
+ enrichedContext: EnrichedContext;
277
+ /** Research results for each technology */
278
+ techResearch: TechResearchResult[];
279
+ /** Detected MCP servers */
280
+ mcpServers: RalphMcpServers;
281
+ /** The original analysis plan */
282
+ plan: AnalysisPlan;
283
+ /** The detected stack from scanner */
284
+ stack: DetectedStack;
285
+ }
package/src/ai/index.ts CHANGED
@@ -44,19 +44,40 @@ export {
44
44
  canUseContext7,
45
45
  } from './tools/index.js';
46
46
 
47
- // Agents
47
+ // Agents - New 4-phase architecture
48
48
  export {
49
+ // Main orchestration
49
50
  runMultiAgentAnalysis,
50
- runCodebaseAnalyst,
51
- runStackResearcher,
52
- runOrchestrator,
53
- mergeAgentResults,
51
+ // Phase 1: Planning
52
+ runPlanningOrchestrator,
53
+ // Phase 2: Parallel workers
54
+ runContextEnricher,
55
+ runTechResearcher,
56
+ runTechResearchPool,
57
+ // Phase 3: Synthesis + MCP detection
58
+ runSynthesisAgent,
59
+ detectRalphMcpServers,
60
+ convertToLegacyMcpRecommendations,
61
+ // Phase 4: QA loop
62
+ runEvaluatorOptimizer,
63
+ // New types
64
+ type AnalysisPlan,
65
+ type EnrichedContext,
66
+ type TechResearchResult,
67
+ type RalphMcpServers,
68
+ type EvaluationResult,
69
+ // Legacy types (backward compatibility)
54
70
  type CodebaseAnalysis,
55
71
  type StackResearch,
56
72
  type McpRecommendations,
57
73
  type MultiAgentAnalysis,
58
74
  type AgentCapabilities,
59
75
  type AgentOptions,
76
+ // Legacy agents (deprecated, kept for backward compatibility)
77
+ runCodebaseAnalyst,
78
+ runStackResearcher,
79
+ runOrchestrator,
80
+ mergeAgentResults,
60
81
  } from './agents/index.js';
61
82
 
62
83
  // AI enhancer
@@ -26,6 +26,7 @@ import pc from 'picocolors';
26
26
  import fs from 'fs';
27
27
  import path from 'path';
28
28
  import { simpson, sectionHeader, drawLine } from '../utils/colors.js';
29
+ import { flushTracing } from '../utils/tracing.js';
29
30
 
30
31
  export interface InitOptions {
31
32
  provider?: AIProvider;
@@ -109,8 +110,8 @@ async function collectApiKeys(
109
110
  const providerChoice = await prompts.select({
110
111
  message: 'Select your AI provider:',
111
112
  options: [
112
- { value: 'anthropic', label: 'Anthropic (Claude)', hint: 'recommended' },
113
- { value: 'openai', label: 'OpenAI (GPT-4/5)' },
113
+ { value: 'anthropic', label: 'Anthropic', hint: 'recommended' },
114
+ { value: 'openai', label: 'OpenAI' },
114
115
  { value: 'openrouter', label: 'OpenRouter', hint: 'multiple providers' },
115
116
  ],
116
117
  });
@@ -261,6 +262,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
261
262
  if (!apiKeys) {
262
263
  // In --yes mode, null means missing API key (hard failure)
263
264
  // In interactive mode, null means user cancelled
265
+ await flushTracing();
264
266
  if (options.yes) {
265
267
  process.exit(1);
266
268
  }
@@ -281,6 +283,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
281
283
  } catch (error) {
282
284
  spinner.stop('Scan failed');
283
285
  logger.error(`Failed to scan project: ${error instanceof Error ? error.message : String(error)}`);
286
+ await flushTracing();
284
287
  process.exit(1);
285
288
  }
286
289
 
@@ -342,6 +345,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
342
345
  });
343
346
 
344
347
  if (prompts.isCancel(shouldContinue) || !shouldContinue) {
348
+ await flushTracing();
345
349
  logger.info('Initialization cancelled');
346
350
  return;
347
351
  }
@@ -365,6 +369,9 @@ export async function initCommand(options: InitOptions): Promise<void> {
365
369
  console.log(simpson.yellow('─── Generation Results ───'));
366
370
  console.log(formatGenerationResult(generationResult));
367
371
 
372
+ // Flush tracing spans before completing
373
+ await flushTracing();
374
+
368
375
  if (generationResult.success) {
369
376
  console.log('');
370
377
  logger.success('Ralph initialized successfully!');
@@ -380,6 +387,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
380
387
  } catch (error) {
381
388
  spinner.stop('Generation failed');
382
389
  logger.error(`Failed to generate files: ${error instanceof Error ? error.message : String(error)}`);
390
+ await flushTracing();
383
391
  process.exit(1);
384
392
  }
385
393
  }
@@ -3,7 +3,7 @@
3
3
  * Provides AI call tracing for debugging and analysis
4
4
  */
5
5
 
6
- import { initLogger, wrapAISDK } from 'braintrust';
6
+ import { initLogger, wrapAISDK, flush as braintrustFlush } from 'braintrust';
7
7
  import * as ai from 'ai';
8
8
 
9
9
  // Re-export traced utilities
@@ -13,6 +13,7 @@ export { traced, currentSpan, wrapTraced } from 'braintrust';
13
13
  * Initialize Braintrust logger if API key is available
14
14
  */
15
15
  let loggerInitialized = false;
16
+ let exitHandlersRegistered = false;
16
17
 
17
18
  export function initTracing(): void {
18
19
  if (loggerInitialized) return;
@@ -29,11 +30,53 @@ export function initTracing(): void {
29
30
  projectName: process.env.BRAINTRUST_PROJECT_NAME || 'wiggum-cli',
30
31
  });
31
32
  loggerInitialized = true;
33
+
34
+ // Register exit handlers to flush spans before process exits
35
+ registerExitHandlers();
32
36
  } catch {
33
37
  // Silently fail if tracing can't be initialized
34
38
  }
35
39
  }
36
40
 
41
+ /**
42
+ * Register process exit handlers to flush tracing spans
43
+ */
44
+ function registerExitHandlers(): void {
45
+ if (exitHandlersRegistered) return;
46
+ exitHandlersRegistered = true;
47
+
48
+ // Flush on normal exit
49
+ process.on('beforeExit', async () => {
50
+ await flushTracing();
51
+ });
52
+
53
+ // Flush on SIGINT (Ctrl+C)
54
+ process.on('SIGINT', async () => {
55
+ await flushTracing();
56
+ process.exit(0);
57
+ });
58
+
59
+ // Flush on SIGTERM
60
+ process.on('SIGTERM', async () => {
61
+ await flushTracing();
62
+ process.exit(0);
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Flush all pending tracing spans to Braintrust
68
+ * Call this before process exit to ensure all spans are recorded
69
+ */
70
+ export async function flushTracing(): Promise<void> {
71
+ if (!loggerInitialized) return;
72
+
73
+ try {
74
+ await braintrustFlush();
75
+ } catch {
76
+ // Silently fail if flush fails
77
+ }
78
+ }
79
+
37
80
  /**
38
81
  * Check if tracing is enabled
39
82
  */
package/tsconfig.json CHANGED
@@ -15,5 +15,5 @@
15
15
  "resolveJsonModule": true
16
16
  },
17
17
  "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
18
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
19
19
  }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['src/**/*.test.ts'],
6
+ },
7
+ });