wiggum-cli 0.7.3 → 0.7.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.
- package/dist/ai/conversation/conversation-manager.d.ts +31 -1
- package/dist/ai/conversation/conversation-manager.d.ts.map +1 -1
- package/dist/ai/conversation/conversation-manager.js +48 -3
- package/dist/ai/conversation/conversation-manager.js.map +1 -1
- package/dist/ai/conversation/index.d.ts +3 -2
- package/dist/ai/conversation/index.d.ts.map +1 -1
- package/dist/ai/conversation/index.js +1 -0
- package/dist/ai/conversation/index.js.map +1 -1
- package/dist/ai/conversation/interview-tools.d.ts +85 -0
- package/dist/ai/conversation/interview-tools.d.ts.map +1 -0
- package/dist/ai/conversation/interview-tools.js +255 -0
- package/dist/ai/conversation/interview-tools.js.map +1 -0
- package/dist/ai/conversation/spec-generator.d.ts +36 -0
- package/dist/ai/conversation/spec-generator.d.ts.map +1 -1
- package/dist/ai/conversation/spec-generator.js +219 -30
- package/dist/ai/conversation/spec-generator.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +10 -20
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/repl-prompts.d.ts +58 -0
- package/dist/utils/repl-prompts.d.ts.map +1 -0
- package/dist/utils/repl-prompts.js +231 -0
- package/dist/utils/repl-prompts.js.map +1 -0
- package/dist/utils/tui.d.ts +61 -0
- package/dist/utils/tui.d.ts.map +1 -0
- package/dist/utils/tui.js +214 -0
- package/dist/utils/tui.js.map +1 -0
- package/package.json +1 -1
- package/src/ai/conversation/conversation-manager.ts +66 -4
- package/src/ai/conversation/index.ts +7 -0
- package/src/ai/conversation/interview-tools.ts +293 -0
- package/src/ai/conversation/spec-generator.ts +287 -34
- package/src/commands/init.ts +10 -22
- package/src/utils/repl-prompts.ts +286 -0
- package/src/utils/tui.ts +262 -0
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spec Generator
|
|
3
3
|
* AI-powered feature specification generator with interview flow
|
|
4
|
+
* Enhanced with codebase tools and Claude Code-like UX
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import readline from 'node:readline';
|
|
7
8
|
import pc from 'picocolors';
|
|
8
9
|
import { ConversationManager } from './conversation-manager.js';
|
|
9
|
-
import { fetchContent
|
|
10
|
+
import { fetchContent } from './url-fetcher.js';
|
|
11
|
+
import { createInterviewTools } from './interview-tools.js';
|
|
12
|
+
import { createTavilySearchTool, canUseTavily } from '../tools/tavily.js';
|
|
13
|
+
import { createContext7Tools, canUseContext7 } from '../tools/context7.js';
|
|
10
14
|
import type { AIProvider } from '../providers.js';
|
|
11
15
|
import type { ScanResult } from '../../scanner/types.js';
|
|
16
|
+
import type { EnhancedScanResult, AIAnalysisResult } from '../enhancer.js';
|
|
12
17
|
import { simpson } from '../../utils/colors.js';
|
|
18
|
+
import {
|
|
19
|
+
displayPhaseHeader,
|
|
20
|
+
displayToolUse,
|
|
21
|
+
displaySessionContext,
|
|
22
|
+
displayGarbledInputWarning,
|
|
23
|
+
type Phase,
|
|
24
|
+
} from '../../utils/tui.js';
|
|
13
25
|
|
|
14
26
|
/** Maximum number of interview questions before auto-completing */
|
|
15
27
|
const MAX_INTERVIEW_QUESTIONS = 10;
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Session context from /init analysis
|
|
31
|
+
*/
|
|
32
|
+
export interface SessionContext {
|
|
33
|
+
entryPoints?: string[];
|
|
34
|
+
keyDirectories?: Record<string, string>;
|
|
35
|
+
commands?: { build?: string; dev?: string; test?: string };
|
|
36
|
+
namingConventions?: string;
|
|
37
|
+
implementationGuidelines?: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
17
40
|
/**
|
|
18
41
|
* Spec generator options
|
|
19
42
|
*/
|
|
@@ -23,6 +46,12 @@ export interface SpecGeneratorOptions {
|
|
|
23
46
|
provider: AIProvider;
|
|
24
47
|
model: string;
|
|
25
48
|
scanResult?: ScanResult;
|
|
49
|
+
/** Rich session context from /init */
|
|
50
|
+
sessionContext?: SessionContext;
|
|
51
|
+
/** Tavily API key for web search */
|
|
52
|
+
tavilyApiKey?: string;
|
|
53
|
+
/** Context7 API key for docs lookup */
|
|
54
|
+
context7ApiKey?: string;
|
|
26
55
|
}
|
|
27
56
|
|
|
28
57
|
/**
|
|
@@ -48,9 +77,10 @@ async function promptUser(prompt: string): Promise<string> {
|
|
|
48
77
|
}
|
|
49
78
|
|
|
50
79
|
/**
|
|
51
|
-
* Display streaming text
|
|
80
|
+
* Display streaming text with AI prefix
|
|
52
81
|
*/
|
|
53
82
|
async function displayStream(stream: AsyncIterable<string>): Promise<string> {
|
|
83
|
+
process.stdout.write(simpson.blue('AI: '));
|
|
54
84
|
let fullText = '';
|
|
55
85
|
for await (const chunk of stream) {
|
|
56
86
|
process.stdout.write(chunk);
|
|
@@ -60,7 +90,39 @@ async function displayStream(stream: AsyncIterable<string>): Promise<string> {
|
|
|
60
90
|
return fullText;
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Check if input looks garbled (common paste issues)
|
|
95
|
+
*/
|
|
96
|
+
function looksGarbled(input: string): boolean {
|
|
97
|
+
const trimmed = input.trim();
|
|
98
|
+
|
|
99
|
+
// Too short to be meaningful
|
|
100
|
+
if (trimmed.length < 3) return false;
|
|
101
|
+
|
|
102
|
+
// Common patterns from truncated pastes
|
|
103
|
+
const garbledPatterns = [
|
|
104
|
+
/^[a-z]+';$/i, // Just "js';" or "ts';"
|
|
105
|
+
/^[,\.\;\{\}\[\]]+$/, // Just punctuation
|
|
106
|
+
/^\s*['"`]\s*$/, // Just quotes
|
|
107
|
+
/^[a-z]{1,3}$/i, // Just 1-3 letters
|
|
108
|
+
/^\d+$/, // Just numbers
|
|
109
|
+
/^[^\w\s]+$/, // Only special characters
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
return garbledPatterns.some(p => p.test(trimmed));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build enhanced system prompt with project context and tool awareness
|
|
117
|
+
*/
|
|
118
|
+
function buildSystemPrompt(
|
|
119
|
+
sessionContext?: SessionContext,
|
|
120
|
+
hasTools?: { codebase: boolean; tavily: boolean; context7: boolean }
|
|
121
|
+
): string {
|
|
122
|
+
const parts: string[] = [];
|
|
123
|
+
|
|
124
|
+
// Base prompt
|
|
125
|
+
parts.push(`You are an expert product manager and technical writer helping to create detailed feature specifications.
|
|
64
126
|
|
|
65
127
|
Your role is to:
|
|
66
128
|
1. Understand the user's feature goals through targeted questions
|
|
@@ -71,8 +133,82 @@ When interviewing:
|
|
|
71
133
|
- Ask one focused question at a time
|
|
72
134
|
- Acknowledge answers before asking the next question
|
|
73
135
|
- Stop asking when you have enough information (usually 3-5 questions)
|
|
74
|
-
- Say "I have enough information to generate the spec" when ready
|
|
136
|
+
- Say "I have enough information to generate the spec" when ready`);
|
|
137
|
+
|
|
138
|
+
// Add tool awareness
|
|
139
|
+
if (hasTools) {
|
|
140
|
+
const toolList: string[] = [];
|
|
141
|
+
if (hasTools.codebase) {
|
|
142
|
+
toolList.push('- read_file: Read project files to understand existing code');
|
|
143
|
+
toolList.push('- search_codebase: Search for patterns, functions, or imports');
|
|
144
|
+
toolList.push('- list_directory: Explore project structure');
|
|
145
|
+
}
|
|
146
|
+
if (hasTools.tavily) {
|
|
147
|
+
toolList.push('- tavily_search: Search the web for best practices and documentation');
|
|
148
|
+
}
|
|
149
|
+
if (hasTools.context7) {
|
|
150
|
+
toolList.push('- resolveLibraryId/queryDocs: Look up library documentation');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (toolList.length > 0) {
|
|
154
|
+
parts.push(`
|
|
155
|
+
## Available Tools
|
|
156
|
+
You have access to the following tools to help understand the project and gather information:
|
|
157
|
+
${toolList.join('\n')}
|
|
158
|
+
|
|
159
|
+
USE THESE TOOLS PROACTIVELY:
|
|
160
|
+
- When the user describes a feature, read relevant files to understand existing patterns
|
|
161
|
+
- When unsure about implementation, search the codebase for similar code
|
|
162
|
+
- When discussing best practices, search the web for current recommendations
|
|
163
|
+
- Don't ask the user to paste code - read it yourself`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add project context from /init
|
|
168
|
+
if (sessionContext) {
|
|
169
|
+
const contextParts: string[] = ['## Project Context (from analysis)'];
|
|
170
|
+
|
|
171
|
+
if (sessionContext.entryPoints && sessionContext.entryPoints.length > 0) {
|
|
172
|
+
contextParts.push(`\nEntry Points:\n${sessionContext.entryPoints.map(e => `- ${e}`).join('\n')}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (sessionContext.keyDirectories && Object.keys(sessionContext.keyDirectories).length > 0) {
|
|
176
|
+
contextParts.push(`\nKey Directories:`);
|
|
177
|
+
for (const [dir, purpose] of Object.entries(sessionContext.keyDirectories)) {
|
|
178
|
+
contextParts.push(`- ${dir}: ${purpose}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
75
181
|
|
|
182
|
+
if (sessionContext.commands) {
|
|
183
|
+
const cmds = sessionContext.commands;
|
|
184
|
+
const cmdList = Object.entries(cmds).filter(([_, v]) => v);
|
|
185
|
+
if (cmdList.length > 0) {
|
|
186
|
+
contextParts.push(`\nCommands:`);
|
|
187
|
+
for (const [name, cmd] of cmdList) {
|
|
188
|
+
contextParts.push(`- ${name}: ${cmd}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (sessionContext.namingConventions) {
|
|
194
|
+
contextParts.push(`\nNaming Conventions: ${sessionContext.namingConventions}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (sessionContext.implementationGuidelines && sessionContext.implementationGuidelines.length > 0) {
|
|
198
|
+
contextParts.push(`\nImplementation Guidelines:`);
|
|
199
|
+
for (const guideline of sessionContext.implementationGuidelines) {
|
|
200
|
+
contextParts.push(`- ${guideline}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (contextParts.length > 1) {
|
|
205
|
+
parts.push(contextParts.join('\n'));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Spec format
|
|
210
|
+
parts.push(`
|
|
211
|
+
## Spec Format
|
|
76
212
|
When generating the spec, use this format:
|
|
77
213
|
|
|
78
214
|
# [Feature Name] Feature Specification
|
|
@@ -104,8 +240,30 @@ When generating the spec, use this format:
|
|
|
104
240
|
- [ ] Specific, testable conditions
|
|
105
241
|
|
|
106
242
|
## Out of Scope
|
|
107
|
-
- Items explicitly not included
|
|
108
|
-
|
|
243
|
+
- Items explicitly not included`);
|
|
244
|
+
|
|
245
|
+
return parts.join('\n\n');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extract session context from EnhancedScanResult
|
|
250
|
+
*/
|
|
251
|
+
function extractSessionContext(scanResult: ScanResult): SessionContext | undefined {
|
|
252
|
+
// Check if this is an EnhancedScanResult with aiAnalysis
|
|
253
|
+
const enhanced = scanResult as EnhancedScanResult;
|
|
254
|
+
if (!enhanced.aiAnalysis) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const ai = enhanced.aiAnalysis;
|
|
259
|
+
return {
|
|
260
|
+
entryPoints: ai.projectContext?.entryPoints,
|
|
261
|
+
keyDirectories: ai.projectContext?.keyDirectories,
|
|
262
|
+
commands: ai.commands,
|
|
263
|
+
namingConventions: ai.projectContext?.namingConventions,
|
|
264
|
+
implementationGuidelines: ai.implementationGuidelines,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
109
267
|
|
|
110
268
|
/**
|
|
111
269
|
* AI-powered spec generator with interview flow
|
|
@@ -116,15 +274,61 @@ export class SpecGenerator {
|
|
|
116
274
|
private readonly featureName: string;
|
|
117
275
|
private readonly projectRoot: string;
|
|
118
276
|
private generatedSpec: string = '';
|
|
277
|
+
private questionCount: number = 0;
|
|
278
|
+
private readonly hasTools: { codebase: boolean; tavily: boolean; context7: boolean };
|
|
279
|
+
private readonly sessionContext?: SessionContext;
|
|
119
280
|
|
|
120
281
|
constructor(options: SpecGeneratorOptions) {
|
|
121
282
|
this.featureName = options.featureName;
|
|
122
283
|
this.projectRoot = options.projectRoot;
|
|
123
284
|
|
|
285
|
+
// Get API keys from options or environment
|
|
286
|
+
const tavilyApiKey = options.tavilyApiKey || process.env.TAVILY_API_KEY;
|
|
287
|
+
const context7ApiKey = options.context7ApiKey || process.env.CONTEXT7_API_KEY;
|
|
288
|
+
|
|
289
|
+
// Track which tools are available
|
|
290
|
+
this.hasTools = {
|
|
291
|
+
codebase: true, // Always available
|
|
292
|
+
tavily: canUseTavily(tavilyApiKey),
|
|
293
|
+
context7: canUseContext7(context7ApiKey),
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Build tools object
|
|
297
|
+
const tools: Record<string, unknown> = {};
|
|
298
|
+
|
|
299
|
+
// Add codebase tools
|
|
300
|
+
const codebaseTools = createInterviewTools(options.projectRoot);
|
|
301
|
+
Object.assign(tools, codebaseTools);
|
|
302
|
+
|
|
303
|
+
// Add Tavily search if available
|
|
304
|
+
if (this.hasTools.tavily && tavilyApiKey) {
|
|
305
|
+
tools.tavily_search = createTavilySearchTool(tavilyApiKey);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Add Context7 tools if available
|
|
309
|
+
if (this.hasTools.context7 && context7ApiKey) {
|
|
310
|
+
const context7Tools = createContext7Tools(context7ApiKey);
|
|
311
|
+
Object.assign(tools, context7Tools);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Extract session context from scan result or use provided
|
|
315
|
+
this.sessionContext = options.sessionContext || (
|
|
316
|
+
options.scanResult ? extractSessionContext(options.scanResult) : undefined
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Build enhanced system prompt
|
|
320
|
+
const systemPrompt = buildSystemPrompt(this.sessionContext, this.hasTools);
|
|
321
|
+
|
|
322
|
+
// Create conversation manager with tools
|
|
124
323
|
this.conversation = new ConversationManager({
|
|
125
324
|
provider: options.provider,
|
|
126
325
|
model: options.model,
|
|
127
|
-
systemPrompt
|
|
326
|
+
systemPrompt,
|
|
327
|
+
tools: tools as Record<string, never>,
|
|
328
|
+
onToolUse: (toolName, args) => {
|
|
329
|
+
displayToolUse(toolName, args);
|
|
330
|
+
},
|
|
331
|
+
maxToolSteps: 8,
|
|
128
332
|
});
|
|
129
333
|
|
|
130
334
|
if (options.scanResult) {
|
|
@@ -132,11 +336,37 @@ export class SpecGenerator {
|
|
|
132
336
|
}
|
|
133
337
|
}
|
|
134
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Display the current phase header
|
|
341
|
+
*/
|
|
342
|
+
private displayHeader(): void {
|
|
343
|
+
displayPhaseHeader(this.featureName, this.phase, {
|
|
344
|
+
current: this.questionCount,
|
|
345
|
+
max: MAX_INTERVIEW_QUESTIONS,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Display session context at start
|
|
351
|
+
*/
|
|
352
|
+
private displayContext(): void {
|
|
353
|
+
// Build project name from package.json or directory
|
|
354
|
+
const projectName = this.projectRoot.split('/').pop() || 'Project';
|
|
355
|
+
|
|
356
|
+
displaySessionContext({
|
|
357
|
+
projectName,
|
|
358
|
+
entryPoints: this.sessionContext?.entryPoints,
|
|
359
|
+
tools: this.hasTools,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
135
363
|
/**
|
|
136
364
|
* Phase 1: Gather context from URLs/files
|
|
137
365
|
*/
|
|
138
366
|
private async gatherContext(): Promise<void> {
|
|
139
|
-
|
|
367
|
+
this.displayHeader();
|
|
368
|
+
this.displayContext();
|
|
369
|
+
|
|
140
370
|
console.log(simpson.yellow('Context Gathering'));
|
|
141
371
|
console.log(pc.dim('Share any reference URLs or files (press Enter to skip):'));
|
|
142
372
|
console.log('');
|
|
@@ -148,7 +378,7 @@ export class SpecGenerator {
|
|
|
148
378
|
break;
|
|
149
379
|
}
|
|
150
380
|
|
|
151
|
-
process.stdout.write(pc.dim('Fetching... '));
|
|
381
|
+
process.stdout.write(pc.dim(' Fetching... '));
|
|
152
382
|
const result = await fetchContent(input, this.projectRoot);
|
|
153
383
|
|
|
154
384
|
if (result.error) {
|
|
@@ -166,7 +396,8 @@ export class SpecGenerator {
|
|
|
166
396
|
* Phase 2: Discuss goals
|
|
167
397
|
*/
|
|
168
398
|
private async discussGoals(): Promise<void> {
|
|
169
|
-
|
|
399
|
+
this.displayHeader();
|
|
400
|
+
|
|
170
401
|
console.log(simpson.yellow('Feature Goals'));
|
|
171
402
|
console.log(pc.dim('Describe what you want to build:'));
|
|
172
403
|
console.log('');
|
|
@@ -188,9 +419,10 @@ export class SpecGenerator {
|
|
|
188
419
|
|
|
189
420
|
console.log('');
|
|
190
421
|
const response = await this.conversation.chat(
|
|
191
|
-
`The user wants to create a feature called "${this.featureName}".
|
|
422
|
+
`The user wants to create a feature called "${this.featureName}". First, use your tools to explore the codebase and understand the existing structure. Then acknowledge their goals and ask your first clarifying question.`
|
|
192
423
|
);
|
|
193
424
|
|
|
425
|
+
console.log('');
|
|
194
426
|
console.log(simpson.blue('AI:'), response);
|
|
195
427
|
console.log('');
|
|
196
428
|
|
|
@@ -201,19 +433,21 @@ export class SpecGenerator {
|
|
|
201
433
|
* Phase 3: Conduct interview
|
|
202
434
|
*/
|
|
203
435
|
private async conductInterview(): Promise<void> {
|
|
436
|
+
this.displayHeader();
|
|
437
|
+
|
|
204
438
|
console.log(simpson.yellow('Interview'));
|
|
205
439
|
console.log(pc.dim('Answer the questions (type "done" when ready to generate spec):'));
|
|
206
440
|
console.log('');
|
|
207
441
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
while (questionCount < MAX_INTERVIEW_QUESTIONS) {
|
|
442
|
+
while (this.questionCount < MAX_INTERVIEW_QUESTIONS) {
|
|
211
443
|
const answer = await promptUser(`${simpson.brown('you>')} `);
|
|
212
444
|
|
|
445
|
+
// Handle exit commands
|
|
213
446
|
if (answer.toLowerCase() === 'done' || answer.toLowerCase() === 'skip') {
|
|
214
447
|
break;
|
|
215
448
|
}
|
|
216
449
|
|
|
450
|
+
// Handle empty input
|
|
217
451
|
if (!answer) {
|
|
218
452
|
console.log(pc.dim('(Press Enter again to skip, or type your answer)'));
|
|
219
453
|
const confirm = await promptUser(`${simpson.brown('you>')} `);
|
|
@@ -221,38 +455,57 @@ export class SpecGenerator {
|
|
|
221
455
|
break;
|
|
222
456
|
}
|
|
223
457
|
// Process the confirmation as the answer
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log('');
|
|
228
|
-
} else {
|
|
229
|
-
console.log('');
|
|
230
|
-
const response = await this.conversation.chat(answer);
|
|
231
|
-
console.log(simpson.blue('AI:'), response);
|
|
232
|
-
console.log('');
|
|
458
|
+
await this.processAnswer(confirm);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
233
461
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
response.toLowerCase().includes("let me generate") ||
|
|
239
|
-
response.toLowerCase().includes("i'll now generate")
|
|
240
|
-
) {
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
462
|
+
// Check for garbled input
|
|
463
|
+
if (looksGarbled(answer)) {
|
|
464
|
+
displayGarbledInputWarning(answer);
|
|
465
|
+
continue;
|
|
243
466
|
}
|
|
244
467
|
|
|
245
|
-
|
|
468
|
+
// Process normal answer
|
|
469
|
+
const shouldBreak = await this.processAnswer(answer);
|
|
470
|
+
if (shouldBreak) break;
|
|
246
471
|
}
|
|
247
472
|
|
|
248
473
|
this.phase = 'generation';
|
|
249
474
|
}
|
|
250
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Process a user answer and get AI response
|
|
478
|
+
*/
|
|
479
|
+
private async processAnswer(answer: string): Promise<boolean> {
|
|
480
|
+
console.log('');
|
|
481
|
+
const response = await this.conversation.chat(answer);
|
|
482
|
+
console.log('');
|
|
483
|
+
console.log(simpson.blue('AI:'), response);
|
|
484
|
+
console.log('');
|
|
485
|
+
|
|
486
|
+
this.questionCount++;
|
|
487
|
+
|
|
488
|
+
// Check if AI indicates it has enough information
|
|
489
|
+
const lowerResponse = response.toLowerCase();
|
|
490
|
+
if (
|
|
491
|
+
lowerResponse.includes('enough information') ||
|
|
492
|
+
lowerResponse.includes('ready to generate') ||
|
|
493
|
+
lowerResponse.includes("let me generate") ||
|
|
494
|
+
lowerResponse.includes("i'll now generate") ||
|
|
495
|
+
lowerResponse.includes("i will now generate")
|
|
496
|
+
) {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
251
503
|
/**
|
|
252
504
|
* Phase 4: Generate spec
|
|
253
505
|
*/
|
|
254
506
|
private async generateSpec(): Promise<string> {
|
|
255
|
-
|
|
507
|
+
this.displayHeader();
|
|
508
|
+
|
|
256
509
|
console.log(simpson.yellow('Generating Specification...'));
|
|
257
510
|
console.log('');
|
|
258
511
|
|
package/src/commands/init.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
getAvailableProvider,
|
|
19
19
|
AVAILABLE_MODELS,
|
|
20
20
|
} from '../ai/providers.js';
|
|
21
|
-
import * as
|
|
21
|
+
import * as replPrompts from '../utils/repl-prompts.js';
|
|
22
22
|
import fs from 'fs';
|
|
23
23
|
import path from 'path';
|
|
24
24
|
import {
|
|
@@ -33,23 +33,8 @@ import { createShimmerSpinner, type ShimmerSpinner } from '../utils/spinner.js';
|
|
|
33
33
|
import { startRepl, createSessionState } from '../repl/index.js';
|
|
34
34
|
import { loadConfigWithDefaults } from '../utils/config.js';
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* Works correctly in both CLI and REPL contexts
|
|
39
|
-
*/
|
|
40
|
-
async function securePasswordInput(message: string): Promise<string | null> {
|
|
41
|
-
const result = await prompts.password({
|
|
42
|
-
message,
|
|
43
|
-
mask: '*',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (prompts.isCancel(result)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Return trimmed input, filtering any control characters
|
|
51
|
-
return (result as string).trim().replace(/[\x00-\x1F\x7F]/g, '') || null;
|
|
52
|
-
}
|
|
36
|
+
// Use REPL-friendly prompts for interactive input
|
|
37
|
+
const prompts = replPrompts;
|
|
53
38
|
|
|
54
39
|
export interface InitOptions {
|
|
55
40
|
provider?: AIProvider;
|
|
@@ -154,8 +139,10 @@ async function collectApiKeys(
|
|
|
154
139
|
provider = providerChoice as AIProvider;
|
|
155
140
|
const envVar = getApiKeyEnvVar(provider);
|
|
156
141
|
|
|
157
|
-
// Get API key with
|
|
158
|
-
const apiKeyInput = await
|
|
142
|
+
// Get API key with masked input
|
|
143
|
+
const apiKeyInput = await prompts.password({
|
|
144
|
+
message: `Enter your ${envVar}:`,
|
|
145
|
+
});
|
|
159
146
|
|
|
160
147
|
if (!apiKeyInput) {
|
|
161
148
|
logger.error('API key is required to use Ralph.');
|
|
@@ -382,12 +369,13 @@ export async function runInitWorkflow(
|
|
|
382
369
|
logger.success('Wiggum initialized successfully!');
|
|
383
370
|
|
|
384
371
|
// Load config and return result
|
|
372
|
+
// Use enhancedResult to preserve aiAnalysis for session context
|
|
385
373
|
const config = await loadConfigWithDefaults(projectRoot);
|
|
386
374
|
return {
|
|
387
375
|
success: true,
|
|
388
376
|
provider: apiKeys.provider,
|
|
389
377
|
model: apiKeys.model,
|
|
390
|
-
scanResult,
|
|
378
|
+
scanResult: enhancedResult,
|
|
391
379
|
config,
|
|
392
380
|
};
|
|
393
381
|
} else {
|
|
@@ -397,7 +385,7 @@ export async function runInitWorkflow(
|
|
|
397
385
|
success: true, // Still return success to continue
|
|
398
386
|
provider: apiKeys.provider,
|
|
399
387
|
model: apiKeys.model,
|
|
400
|
-
scanResult,
|
|
388
|
+
scanResult: enhancedResult,
|
|
401
389
|
config,
|
|
402
390
|
};
|
|
403
391
|
}
|