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.
Files changed (37) hide show
  1. package/dist/index.js +7 -6
  2. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
  3. package/dist/tui/app.d.ts +12 -22
  4. package/dist/tui/app.js +130 -314
  5. package/dist/tui/components/AppShell.d.ts +47 -0
  6. package/dist/tui/components/AppShell.js +19 -0
  7. package/dist/tui/components/FooterStatusBar.js +2 -3
  8. package/dist/tui/components/HeaderContent.d.ts +28 -0
  9. package/dist/tui/components/HeaderContent.js +16 -0
  10. package/dist/tui/components/MessageList.d.ts +9 -7
  11. package/dist/tui/components/MessageList.js +23 -17
  12. package/dist/tui/components/RunCompletionSummary.d.ts +22 -0
  13. package/dist/tui/components/RunCompletionSummary.js +23 -0
  14. package/dist/tui/components/SpecCompletionSummary.d.ts +47 -0
  15. package/dist/tui/components/SpecCompletionSummary.js +124 -0
  16. package/dist/tui/components/TipsBar.d.ts +24 -0
  17. package/dist/tui/components/TipsBar.js +23 -0
  18. package/dist/tui/components/WiggumBanner.js +8 -3
  19. package/dist/tui/hooks/useBackgroundRuns.d.ts +52 -0
  20. package/dist/tui/hooks/useBackgroundRuns.js +121 -0
  21. package/dist/tui/orchestration/interview-orchestrator.js +1 -1
  22. package/dist/tui/screens/InitScreen.d.ts +13 -8
  23. package/dist/tui/screens/InitScreen.js +86 -87
  24. package/dist/tui/screens/InterviewScreen.d.ts +11 -8
  25. package/dist/tui/screens/InterviewScreen.js +145 -99
  26. package/dist/tui/screens/MainShell.d.ts +13 -12
  27. package/dist/tui/screens/MainShell.js +65 -69
  28. package/dist/tui/screens/RunScreen.d.ts +17 -1
  29. package/dist/tui/screens/RunScreen.js +235 -80
  30. package/dist/tui/screens/index.d.ts +0 -2
  31. package/dist/tui/screens/index.js +0 -1
  32. package/dist/tui/utils/loop-status.d.ts +22 -3
  33. package/dist/tui/utils/loop-status.js +65 -15
  34. package/package.json +5 -1
  35. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
  36. package/dist/tui/screens/WelcomeScreen.d.ts +0 -44
  37. 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. API key collection (if needed)
7
- * 3. Model selection
8
- * 4. AI analysis
9
- * 5. File generation
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. Replaces the readline-based
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. API key collection (if needed)
8
- * 3. Model selection
9
- * 4. AI analysis
10
- * 5. File generation
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, theme } from '../theme.js';
18
+ import { colors, phase } from '../theme.js';
15
19
  import { useInit, INIT_PHASE_CONFIGS, INIT_TOTAL_PHASES } from '../hooks/useInit.js';
16
- import { FooterStatusBar } from '../components/FooterStatusBar.js';
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. Replaces the readline-based
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
- // Handle Escape to cancel
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 when in ai-analysis phase
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
- initTracing();
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
- logger.error(`Failed to save project context: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
166
- // Non-blocking: don't fail /init if context save fails
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 when in generating phase
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
- const envVar = getApiKeyEnvVar(state.provider);
203
- const envLocalPath = path.join(projectRoot, '.ralph', '.env.local');
204
- writeKeysToEnvFile(envLocalPath, { [envVar]: apiKeyRef.current });
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
- // Render based on current phase
269
- const renderPhaseContent = () => {
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
- // Still scanning
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 (_jsxs(Box, { flexDirection: "column", children: [state.enhancedResult?.aiAnalysis && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.green, children: "AI Analysis Complete" }), state.tokenUsage && (_jsxs(Text, { dimColor: true, children: ["Tokens: ", state.tokenUsage.inputTokens, " in / ", state.tokenUsage.outputTokens, " out"] }))] })), state.error && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.orange, children: ["Warning: ", state.error] }) })), _jsx(Confirm, { message: "Generate Ralph configuration files?", onConfirm: handleConfirmGeneration, onCancel: onCancel, initialValue: true })] }));
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
- // Show scan result summary when available
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, { marginBottom: 1, 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' })] })] })] }));
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
- // Build phase string for status line
323
- const phaseString = `${phaseConfig.name} (${phaseConfig.number}/${INIT_TOTAL_PHASES})`;
324
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginTop: 1, children: renderScanSummary() }), _jsx(Box, { marginTop: 1, children: renderPhaseContent() }), _jsx(FooterStatusBar, { action: "Initialize Project", phase: phaseString, path: projectRoot })] }));
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 conversation messages */
31
- onComplete: (spec: string, messages: import('../components/MessageList.js').Message[]) => void;
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 (MessageList, WorkingIndicator, ChatInput, MultiSelect,
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;