wiggum-cli 0.12.1 → 0.13.1
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/index.js +7 -6
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
- package/dist/tui/app.d.ts +12 -22
- package/dist/tui/app.js +130 -314
- package/dist/tui/components/AppShell.d.ts +47 -0
- package/dist/tui/components/AppShell.js +19 -0
- package/dist/tui/components/FooterStatusBar.js +2 -3
- package/dist/tui/components/HeaderContent.d.ts +28 -0
- package/dist/tui/components/HeaderContent.js +16 -0
- package/dist/tui/components/MessageList.d.ts +9 -7
- package/dist/tui/components/MessageList.js +23 -17
- package/dist/tui/components/RunCompletionSummary.d.ts +22 -0
- package/dist/tui/components/RunCompletionSummary.js +23 -0
- package/dist/tui/components/SpecCompletionSummary.d.ts +47 -0
- package/dist/tui/components/SpecCompletionSummary.js +124 -0
- package/dist/tui/components/TipsBar.d.ts +24 -0
- package/dist/tui/components/TipsBar.js +23 -0
- package/dist/tui/components/WiggumBanner.js +8 -3
- package/dist/tui/hooks/useBackgroundRuns.d.ts +52 -0
- package/dist/tui/hooks/useBackgroundRuns.js +121 -0
- package/dist/tui/orchestration/interview-orchestrator.js +1 -1
- package/dist/tui/screens/InitScreen.d.ts +13 -8
- package/dist/tui/screens/InitScreen.js +86 -87
- package/dist/tui/screens/InterviewScreen.d.ts +11 -8
- package/dist/tui/screens/InterviewScreen.js +145 -99
- package/dist/tui/screens/MainShell.d.ts +13 -12
- package/dist/tui/screens/MainShell.js +65 -69
- package/dist/tui/screens/RunScreen.d.ts +17 -1
- package/dist/tui/screens/RunScreen.js +235 -80
- package/dist/tui/screens/index.d.ts +0 -2
- package/dist/tui/screens/index.js +0 -1
- package/dist/tui/utils/loop-status.d.ts +22 -3
- package/dist/tui/utils/loop-status.js +65 -15
- package/package.json +5 -1
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
- package/dist/tui/screens/WelcomeScreen.d.ts +0 -44
- package/dist/tui/screens/WelcomeScreen.js +0 -54
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* InitScreen - Full Ink-based init workflow
|
|
3
3
|
*
|
|
4
|
-
* Handles the complete project initialization flow within the TUI
|
|
4
|
+
* Handles the complete project initialization flow within the TUI.
|
|
5
|
+
* High-level steps (some may be skipped if already configured):
|
|
5
6
|
* 1. Scanning project structure
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
7
|
+
* 2. Provider selection
|
|
8
|
+
* 3. API key collection (if needed) + optional save
|
|
9
|
+
* 4. Model selection
|
|
10
|
+
* 5. AI analysis (agentic)
|
|
11
|
+
* 6. Confirmation + file generation
|
|
12
|
+
*
|
|
13
|
+
* Wrapped in AppShell for consistent layout.
|
|
10
14
|
*/
|
|
11
15
|
import React from 'react';
|
|
12
16
|
import type { SessionState } from '../../repl/session-state.js';
|
|
@@ -14,6 +18,8 @@ import type { SessionState } from '../../repl/session-state.js';
|
|
|
14
18
|
* Props for the InitScreen component
|
|
15
19
|
*/
|
|
16
20
|
export interface InitScreenProps {
|
|
21
|
+
/** Pre-built header element from App */
|
|
22
|
+
header: React.ReactNode;
|
|
17
23
|
/** Project root directory */
|
|
18
24
|
projectRoot: string;
|
|
19
25
|
/** Current session state */
|
|
@@ -26,7 +32,6 @@ export interface InitScreenProps {
|
|
|
26
32
|
/**
|
|
27
33
|
* InitScreen component
|
|
28
34
|
*
|
|
29
|
-
* The complete Ink-based init workflow
|
|
30
|
-
* init flow with native Ink components.
|
|
35
|
+
* The complete Ink-based init workflow wrapped in AppShell.
|
|
31
36
|
*/
|
|
32
|
-
export declare function InitScreen({ projectRoot, sessionState, onComplete, onCancel, }: InitScreenProps): React.ReactElement;
|
|
37
|
+
export declare function InitScreen({ header, projectRoot, sessionState, onComplete, onCancel, }: InitScreenProps): React.ReactElement;
|
|
@@ -2,19 +2,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* InitScreen - Full Ink-based init workflow
|
|
4
4
|
*
|
|
5
|
-
* Handles the complete project initialization flow within the TUI
|
|
5
|
+
* Handles the complete project initialization flow within the TUI.
|
|
6
|
+
* High-level steps (some may be skipped if already configured):
|
|
6
7
|
* 1. Scanning project structure
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
8
|
+
* 2. Provider selection
|
|
9
|
+
* 3. API key collection (if needed) + optional save
|
|
10
|
+
* 4. Model selection
|
|
11
|
+
* 5. AI analysis (agentic)
|
|
12
|
+
* 6. Confirmation + file generation
|
|
13
|
+
*
|
|
14
|
+
* Wrapped in AppShell for consistent layout.
|
|
11
15
|
*/
|
|
12
16
|
import { useEffect, useRef, useCallback } from 'react';
|
|
13
17
|
import { Box, Text, useInput } from 'ink';
|
|
14
|
-
import { colors,
|
|
18
|
+
import { colors, phase } from '../theme.js';
|
|
15
19
|
import { useInit, INIT_PHASE_CONFIGS, INIT_TOTAL_PHASES } from '../hooks/useInit.js';
|
|
16
|
-
import {
|
|
17
|
-
import { WorkingIndicator } from '../components/WorkingIndicator.js';
|
|
20
|
+
import { AppShell } from '../components/AppShell.js';
|
|
18
21
|
import { Select } from '../components/Select.js';
|
|
19
22
|
import { PasswordInput } from '../components/PasswordInput.js';
|
|
20
23
|
import { Confirm } from '../components/Confirm.js';
|
|
@@ -30,17 +33,11 @@ import { saveContext, toPersistedScanResult, toPersistedAIAnalysis, getGitMetada
|
|
|
30
33
|
import { logger } from '../../utils/logger.js';
|
|
31
34
|
import path from 'node:path';
|
|
32
35
|
import { updateSessionState } from '../../repl/session-state.js';
|
|
33
|
-
/**
|
|
34
|
-
* Provider options for the select component
|
|
35
|
-
*/
|
|
36
36
|
const PROVIDER_OPTIONS = [
|
|
37
37
|
{ value: 'anthropic', label: 'Anthropic', hint: 'recommended' },
|
|
38
38
|
{ value: 'openai', label: 'OpenAI' },
|
|
39
39
|
{ value: 'openrouter', label: 'OpenRouter', hint: 'multiple providers' },
|
|
40
40
|
];
|
|
41
|
-
/**
|
|
42
|
-
* Get model options for a provider
|
|
43
|
-
*/
|
|
44
41
|
function getModelOptions(provider) {
|
|
45
42
|
return AVAILABLE_MODELS[provider].map((m) => ({
|
|
46
43
|
value: m.value,
|
|
@@ -51,24 +48,18 @@ function getModelOptions(provider) {
|
|
|
51
48
|
/**
|
|
52
49
|
* InitScreen component
|
|
53
50
|
*
|
|
54
|
-
* The complete Ink-based init workflow
|
|
55
|
-
* init flow with native Ink components.
|
|
51
|
+
* The complete Ink-based init workflow wrapped in AppShell.
|
|
56
52
|
*/
|
|
57
|
-
export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, }) {
|
|
53
|
+
export function InitScreen({ header, projectRoot, sessionState, onComplete, onCancel, }) {
|
|
58
54
|
const { state, initialize, setScanResult, setExistingProvider, selectProvider, setApiKey, setSaveKey, selectModel, setAiProgress, updateToolCall, setEnhancedResult, setAiError, confirmGeneration, setGenerating, setGenerationComplete, setError, } = useInit();
|
|
59
|
-
// Store API key in ref (not in state for security)
|
|
60
55
|
const apiKeyRef = useRef(null);
|
|
61
|
-
// Track if AI analysis has started
|
|
62
56
|
const aiAnalysisStarted = useRef(false);
|
|
63
|
-
// Track if generation has started
|
|
64
57
|
const generationStarted = useRef(false);
|
|
65
|
-
|
|
66
|
-
useInput((input, key) => {
|
|
58
|
+
useInput((_input, key) => {
|
|
67
59
|
if (key.escape) {
|
|
68
60
|
onCancel();
|
|
69
61
|
}
|
|
70
62
|
});
|
|
71
|
-
// Initialize on mount
|
|
72
63
|
useEffect(() => {
|
|
73
64
|
initialize(projectRoot);
|
|
74
65
|
}, [projectRoot, initialize]);
|
|
@@ -76,39 +67,37 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
76
67
|
useEffect(() => {
|
|
77
68
|
if (state.phase !== 'scanning' || state.scanResult)
|
|
78
69
|
return;
|
|
70
|
+
let cancelled = false;
|
|
79
71
|
const runScan = async () => {
|
|
80
72
|
try {
|
|
81
73
|
const scanner = new Scanner();
|
|
82
74
|
const result = await scanner.scan(projectRoot);
|
|
75
|
+
if (cancelled)
|
|
76
|
+
return;
|
|
83
77
|
setScanResult(result);
|
|
84
|
-
// Check for existing API key
|
|
85
78
|
const existingProvider = getAvailableProvider();
|
|
86
79
|
if (existingProvider) {
|
|
87
80
|
setExistingProvider(existingProvider);
|
|
88
81
|
}
|
|
89
|
-
else {
|
|
90
|
-
// Need to collect API key - go to provider select
|
|
91
|
-
// This is done by checking state.hasApiKey in render
|
|
92
|
-
}
|
|
93
82
|
}
|
|
94
83
|
catch (error) {
|
|
84
|
+
if (cancelled)
|
|
85
|
+
return;
|
|
95
86
|
setError(`Failed to scan project: ${error instanceof Error ? error.message : String(error)}`);
|
|
96
87
|
}
|
|
97
88
|
};
|
|
98
89
|
runScan();
|
|
90
|
+
return () => { cancelled = true; };
|
|
99
91
|
}, [state.phase, state.scanResult, projectRoot, setScanResult, setExistingProvider, setError]);
|
|
100
|
-
// Transition from scan to provider select if no API key
|
|
101
92
|
useEffect(() => {
|
|
102
93
|
if (state.phase === 'scanning' && state.scanResult && !state.hasApiKey) {
|
|
103
|
-
// Check one more time for available provider (in case env was set after initial check)
|
|
104
94
|
const existingProvider = getAvailableProvider();
|
|
105
95
|
if (existingProvider) {
|
|
106
96
|
setExistingProvider(existingProvider);
|
|
107
97
|
}
|
|
108
|
-
// Otherwise stay at scanning which will show provider-select
|
|
109
98
|
}
|
|
110
99
|
}, [state.phase, state.scanResult, state.hasApiKey, setExistingProvider]);
|
|
111
|
-
// Run AI analysis
|
|
100
|
+
// Run AI analysis
|
|
112
101
|
useEffect(() => {
|
|
113
102
|
if (state.phase !== 'ai-analysis' || aiAnalysisStarted.current)
|
|
114
103
|
return;
|
|
@@ -116,7 +105,12 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
116
105
|
return;
|
|
117
106
|
aiAnalysisStarted.current = true;
|
|
118
107
|
const runAnalysis = async () => {
|
|
119
|
-
|
|
108
|
+
try {
|
|
109
|
+
initTracing();
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
logger.error(`Failed to init tracing: ${err instanceof Error ? err.message : String(err)}`);
|
|
113
|
+
}
|
|
120
114
|
const aiEnhancer = new AIEnhancer({
|
|
121
115
|
provider: state.provider,
|
|
122
116
|
model: state.model,
|
|
@@ -150,7 +144,6 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
150
144
|
else {
|
|
151
145
|
setEnhancedResult(enhancedResult);
|
|
152
146
|
}
|
|
153
|
-
// Persist context for /sync and /new
|
|
154
147
|
try {
|
|
155
148
|
const git = await getGitMetadata(projectRoot);
|
|
156
149
|
await saveContext({
|
|
@@ -162,8 +155,9 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
162
155
|
}, projectRoot);
|
|
163
156
|
}
|
|
164
157
|
catch (saveErr) {
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
const reason = saveErr instanceof Error ? saveErr.message : String(saveErr);
|
|
159
|
+
logger.error(`Failed to save project context: ${reason}`);
|
|
160
|
+
setAiError(`Warning: AI analysis succeeded but context could not be saved (${reason}). Run /sync after init to persist context.`);
|
|
167
161
|
}
|
|
168
162
|
}
|
|
169
163
|
catch (error) {
|
|
@@ -173,9 +167,13 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
173
167
|
await flushTracing();
|
|
174
168
|
}
|
|
175
169
|
};
|
|
176
|
-
runAnalysis()
|
|
170
|
+
runAnalysis().catch((err) => {
|
|
171
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
172
|
+
logger.error(`AI analysis failed unexpectedly: ${reason}`);
|
|
173
|
+
setAiError(reason);
|
|
174
|
+
});
|
|
177
175
|
}, [state.phase, state.scanResult, state.provider, state.model, setAiProgress, updateToolCall, setEnhancedResult, setAiError]);
|
|
178
|
-
// Run generation
|
|
176
|
+
// Run generation
|
|
179
177
|
useEffect(() => {
|
|
180
178
|
if (state.phase !== 'generating' || generationStarted.current)
|
|
181
179
|
return;
|
|
@@ -183,7 +181,6 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
183
181
|
return;
|
|
184
182
|
generationStarted.current = true;
|
|
185
183
|
const runGeneration = async () => {
|
|
186
|
-
// Use enhanced result if available, otherwise use scan result
|
|
187
184
|
const sourceResult = state.enhancedResult || state.scanResult;
|
|
188
185
|
const generator = new Generator({
|
|
189
186
|
existingFiles: 'backup',
|
|
@@ -193,18 +190,22 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
193
190
|
try {
|
|
194
191
|
setGenerating('Writing configuration files...');
|
|
195
192
|
const generationResult = await generator.generate(sourceResult);
|
|
196
|
-
// Extract generated file paths (keep full relative paths)
|
|
197
193
|
const generatedFiles = generationResult.writeSummary.results
|
|
198
194
|
.filter((f) => f.action === 'created' || f.action === 'backed_up' || f.action === 'overwritten')
|
|
199
195
|
.map((f) => path.relative(projectRoot, f.path));
|
|
200
|
-
// Save API key to .ralph/.env.local if requested
|
|
201
196
|
if (state.apiKeyEnteredThisSession && state.saveKeyToEnv && state.provider && apiKeyRef.current) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
try {
|
|
198
|
+
const envVar = getApiKeyEnvVar(state.provider);
|
|
199
|
+
const envLocalPath = path.join(projectRoot, '.ralph', '.env.local');
|
|
200
|
+
writeKeysToEnvFile(envLocalPath, { [envVar]: apiKeyRef.current });
|
|
201
|
+
}
|
|
202
|
+
catch (envErr) {
|
|
203
|
+
const reason = envErr instanceof Error ? envErr.message : String(envErr);
|
|
204
|
+
logger.error(`Failed to save API key to .env.local: ${reason}`);
|
|
205
|
+
setError(`API key was not saved to .env.local (${reason}). You may need to set the environment variable manually.`);
|
|
206
|
+
}
|
|
205
207
|
}
|
|
206
208
|
setGenerationComplete(generatedFiles);
|
|
207
|
-
// Load config and update session state
|
|
208
209
|
const config = await loadConfigWithDefaults(projectRoot);
|
|
209
210
|
const newSessionState = updateSessionState(sessionState, {
|
|
210
211
|
provider: state.provider ?? undefined,
|
|
@@ -213,14 +214,17 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
213
214
|
config,
|
|
214
215
|
initialized: true,
|
|
215
216
|
});
|
|
216
|
-
// Pass generated files to completion handler for thread history
|
|
217
217
|
onComplete(newSessionState, generatedFiles);
|
|
218
218
|
}
|
|
219
219
|
catch (error) {
|
|
220
220
|
setError(`Failed to generate files: ${error instanceof Error ? error.message : String(error)}`);
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
|
-
runGeneration()
|
|
223
|
+
runGeneration().catch((err) => {
|
|
224
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
225
|
+
logger.error(`Generation failed unexpectedly: ${reason}`);
|
|
226
|
+
setError(reason);
|
|
227
|
+
});
|
|
224
228
|
}, [
|
|
225
229
|
state.phase,
|
|
226
230
|
state.scanResult,
|
|
@@ -236,49 +240,44 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
236
240
|
setError,
|
|
237
241
|
onComplete,
|
|
238
242
|
]);
|
|
239
|
-
// Handle provider selection
|
|
240
243
|
const handleProviderSelect = useCallback((provider) => {
|
|
241
244
|
selectProvider(provider);
|
|
242
245
|
}, [selectProvider]);
|
|
243
|
-
// Handle API key input
|
|
244
246
|
const handleApiKeySubmit = useCallback((key) => {
|
|
245
247
|
if (!state.provider)
|
|
246
248
|
return;
|
|
247
|
-
// Store key in ref
|
|
248
249
|
apiKeyRef.current = key;
|
|
249
|
-
// Set key in environment for this session
|
|
250
250
|
const envVar = getApiKeyEnvVar(state.provider);
|
|
251
251
|
process.env[envVar] = key;
|
|
252
252
|
setApiKey(key);
|
|
253
253
|
}, [state.provider, setApiKey]);
|
|
254
|
-
// Handle save key confirmation
|
|
255
254
|
const handleSaveKeyConfirm = useCallback((save) => {
|
|
256
255
|
setSaveKey(save);
|
|
257
256
|
}, [setSaveKey]);
|
|
258
|
-
// Handle model selection
|
|
259
257
|
const handleModelSelect = useCallback((model) => {
|
|
260
258
|
selectModel(model);
|
|
261
259
|
}, [selectModel]);
|
|
262
|
-
// Handle generation confirmation
|
|
263
260
|
const handleConfirmGeneration = useCallback((confirmed) => {
|
|
264
261
|
confirmGeneration(confirmed);
|
|
265
262
|
}, [confirmGeneration]);
|
|
266
|
-
// Get current phase config
|
|
267
263
|
const phaseConfig = INIT_PHASE_CONFIGS[state.phase];
|
|
268
|
-
|
|
269
|
-
|
|
264
|
+
const phaseString = `${phaseConfig.name} (${phaseConfig.number}/${INIT_TOTAL_PHASES})`;
|
|
265
|
+
// Determine if we're in a "working" state (show spinner)
|
|
266
|
+
const isWorking = (state.phase === 'scanning' && !state.scanResult) ||
|
|
267
|
+
state.phase === 'ai-analysis' ||
|
|
268
|
+
state.phase === 'generating';
|
|
269
|
+
const workingStatus = state.phase === 'scanning' ? (state.workingStatus || 'Scanning project structure...')
|
|
270
|
+
: state.phase === 'ai-analysis' ? (state.workingStatus || 'Analyzing codebase...')
|
|
271
|
+
: state.phase === 'generating' ? (state.workingStatus || 'Generating configuration files...')
|
|
272
|
+
: '';
|
|
273
|
+
// Build the input element based on phase
|
|
274
|
+
const renderInput = () => {
|
|
270
275
|
switch (state.phase) {
|
|
271
276
|
case 'scanning':
|
|
272
277
|
if (state.scanResult && !state.hasApiKey) {
|
|
273
|
-
// Scan done but no API key - show provider select
|
|
274
278
|
return (_jsx(Select, { message: "Select your AI provider:", options: PROVIDER_OPTIONS, onSelect: handleProviderSelect, onCancel: onCancel }));
|
|
275
279
|
}
|
|
276
|
-
|
|
277
|
-
return (_jsx(WorkingIndicator, { state: {
|
|
278
|
-
isWorking: true,
|
|
279
|
-
status: state.workingStatus || 'Scanning project structure...',
|
|
280
|
-
hint: 'esc to cancel',
|
|
281
|
-
} }));
|
|
280
|
+
return null;
|
|
282
281
|
case 'provider-select':
|
|
283
282
|
return (_jsx(Select, { message: "Select your AI provider:", options: PROVIDER_OPTIONS, onSelect: handleProviderSelect, onCancel: onCancel }));
|
|
284
283
|
case 'key-input':
|
|
@@ -289,37 +288,37 @@ export function InitScreen({ projectRoot, sessionState, onComplete, onCancel, })
|
|
|
289
288
|
if (!state.provider)
|
|
290
289
|
return null;
|
|
291
290
|
return (_jsx(Select, { message: "Select model:", options: getModelOptions(state.provider), onSelect: handleModelSelect, onCancel: onCancel }));
|
|
292
|
-
case 'ai-analysis':
|
|
293
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: ["Running AI analysis with", ' ', _jsxs(Text, { color: colors.blue, children: [state.provider, "/", state.model] })] }) }), state.toolCalls.length > 0 && (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: state.toolCalls.map((tc) => (_jsx(ToolCallCard, { toolName: tc.actionName, status: tc.status === 'running' ? 'running' : tc.status === 'success' ? 'complete' : tc.status === 'error' ? 'error' : 'pending', input: tc.description, output: tc.output, error: tc.error }, tc.id))) })), _jsx(WorkingIndicator, { state: {
|
|
294
|
-
isWorking: true,
|
|
295
|
-
status: state.workingStatus || 'Analyzing codebase...',
|
|
296
|
-
hint: 'esc to cancel',
|
|
297
|
-
} })] }));
|
|
298
291
|
case 'confirm':
|
|
299
|
-
return (
|
|
300
|
-
case 'generating':
|
|
301
|
-
return (_jsx(WorkingIndicator, { state: {
|
|
302
|
-
isWorking: true,
|
|
303
|
-
status: state.workingStatus || 'Generating configuration files...',
|
|
304
|
-
hint: 'please wait',
|
|
305
|
-
} }));
|
|
306
|
-
case 'complete':
|
|
307
|
-
// Brief completion message - full summary added to thread by App
|
|
308
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: colors.green, children: [theme.chars.bullet, " "] }), _jsx(Text, { children: "Initialization complete." })] }));
|
|
309
|
-
case 'error':
|
|
310
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.pink, bold: true, children: "Error" }), _jsx(Text, { color: colors.pink, children: state.error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Esc to go back" }) })] }));
|
|
292
|
+
return (_jsx(Confirm, { message: "Generate Ralph configuration files?", onConfirm: handleConfirmGeneration, onCancel: onCancel, initialValue: true }));
|
|
311
293
|
default:
|
|
312
294
|
return null;
|
|
313
295
|
}
|
|
314
296
|
};
|
|
315
|
-
//
|
|
297
|
+
// Scan summary display
|
|
316
298
|
const renderScanSummary = () => {
|
|
317
299
|
if (!state.scanResult || state.phase === 'scanning')
|
|
318
300
|
return null;
|
|
319
301
|
const { stack } = state.scanResult;
|
|
320
|
-
return (_jsxs(Box, {
|
|
302
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.yellow, bold: true, children: "Detected Stack" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [stack.framework && (_jsxs(Text, { children: ["Framework:", ' ', _jsxs(Text, { color: colors.blue, children: [stack.framework.name, stack.framework.version ? ` ${stack.framework.version}` : ''] })] })), _jsxs(Text, { children: ["Language: ", _jsx(Text, { color: colors.blue, children: "TypeScript" })] }), stack.testing?.unit && (_jsxs(Text, { children: ["Testing: ", _jsx(Text, { color: colors.blue, children: stack.testing.unit.name })] })), _jsxs(Text, { children: ["Package Manager:", ' ', _jsx(Text, { color: colors.blue, children: stack.packageManager?.name || 'npm' })] })] })] }));
|
|
321
303
|
};
|
|
322
|
-
//
|
|
323
|
-
const
|
|
324
|
-
|
|
304
|
+
// Phase-specific content (displayed in content area)
|
|
305
|
+
const renderPhaseContent = () => {
|
|
306
|
+
switch (state.phase) {
|
|
307
|
+
case 'ai-analysis':
|
|
308
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: ["Running AI analysis with", ' ', _jsxs(Text, { color: colors.blue, children: [state.provider, "/", state.model] })] }) }), state.toolCalls.length > 0 && (_jsx(Box, { flexDirection: "column", children: state.toolCalls.map((tc) => (_jsx(ToolCallCard, { toolName: tc.actionName, status: tc.status === 'running' ? 'running' : tc.status === 'success' ? 'complete' : tc.status === 'error' ? 'error' : 'pending', input: tc.description, output: tc.output, error: tc.error }, tc.id))) }))] }));
|
|
309
|
+
case 'confirm':
|
|
310
|
+
return (_jsxs(Box, { flexDirection: "column", children: [state.enhancedResult?.aiAnalysis && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: colors.green, children: [phase.complete, " "] }), _jsx(Text, { children: "AI Analysis Complete" })] }), state.tokenUsage && (_jsxs(Text, { dimColor: true, children: ["Tokens: ", state.tokenUsage.inputTokens, " in / ", state.tokenUsage.outputTokens, " out"] }))] })), state.error && (_jsxs(Text, { color: colors.orange, children: ["Warning: ", state.error] }))] }));
|
|
311
|
+
case 'complete':
|
|
312
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: colors.green, children: [phase.complete, " "] }), _jsx(Text, { children: "Initialization complete." })] }));
|
|
313
|
+
case 'error':
|
|
314
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.pink, bold: true, children: "Error" }), _jsx(Text, { color: colors.pink, children: state.error }), _jsx(Text, { dimColor: true, children: "Press Esc to go back" })] }));
|
|
315
|
+
default:
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
return (_jsxs(AppShell, { header: header, tips: state.phase === 'error' ? 'Esc to go back' : 'Esc to cancel', isWorking: isWorking, workingStatus: workingStatus, workingHint: "esc to cancel", error: state.phase === 'error' ? state.error : null, input: renderInput(), footerStatus: {
|
|
320
|
+
action: 'Initialize Project',
|
|
321
|
+
phase: phaseString,
|
|
322
|
+
path: projectRoot,
|
|
323
|
+
}, children: [renderScanSummary(), renderPhaseContent()] }));
|
|
325
324
|
}
|
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
* 2. Goals - Understand what to build
|
|
8
8
|
* 3. Interview - Clarifying questions
|
|
9
9
|
* 4. Generation - Generate the specification
|
|
10
|
+
* 5. Complete - Show summary and return to shell
|
|
11
|
+
*
|
|
12
|
+
* Wrapped in AppShell for consistent layout. On completion,
|
|
13
|
+
* shows SpecCompletionSummary inline before returning to shell.
|
|
10
14
|
*/
|
|
11
15
|
import React from 'react';
|
|
12
16
|
import type { AIProvider } from '../../ai/providers.js';
|
|
13
17
|
import type { ScanResult } from '../../scanner/types.js';
|
|
18
|
+
import type { Message } from '../components/MessageList.js';
|
|
14
19
|
/**
|
|
15
20
|
* Props for the InterviewScreen component
|
|
16
21
|
*/
|
|
17
22
|
export interface InterviewScreenProps {
|
|
23
|
+
/** Pre-built header element from App */
|
|
24
|
+
header: React.ReactNode;
|
|
18
25
|
/** Name of the feature being specified */
|
|
19
26
|
featureName: string;
|
|
20
27
|
/** Project root directory path */
|
|
@@ -27,8 +34,8 @@ export interface InterviewScreenProps {
|
|
|
27
34
|
scanResult?: ScanResult;
|
|
28
35
|
/** Path to specs directory (relative to project root, defaults to '.ralph/specs') */
|
|
29
36
|
specsPath?: string;
|
|
30
|
-
/** Called when spec generation is complete - receives spec and
|
|
31
|
-
onComplete: (spec: string, messages:
|
|
37
|
+
/** Called when spec generation is complete - receives spec, messages, and specPath */
|
|
38
|
+
onComplete: (spec: string, messages: Message[], specPath: string) => void;
|
|
32
39
|
/** Called when user cancels the interview */
|
|
33
40
|
onCancel: () => void;
|
|
34
41
|
}
|
|
@@ -36,10 +43,6 @@ export interface InterviewScreenProps {
|
|
|
36
43
|
* InterviewScreen component
|
|
37
44
|
*
|
|
38
45
|
* The main screen for the /new command interview flow. Combines all TUI
|
|
39
|
-
* components
|
|
40
|
-
* FooterStatusBar) to create the complete interview experience.
|
|
41
|
-
*
|
|
42
|
-
* Uses the useSpecGenerator hook for state and InterviewOrchestrator
|
|
43
|
-
* to bridge to the AI conversation.
|
|
46
|
+
* components within an AppShell layout.
|
|
44
47
|
*/
|
|
45
|
-
export declare function InterviewScreen({ featureName, projectRoot, provider, model, scanResult, specsPath, onComplete, onCancel, }: InterviewScreenProps): React.ReactElement;
|
|
48
|
+
export declare function InterviewScreen({ header, featureName, projectRoot, provider, model, scanResult, specsPath, onComplete, onCancel, }: InterviewScreenProps): React.ReactElement;
|