wiggum-cli 0.17.2 → 0.17.3
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/README.md +8 -2
- package/dist/agent/orchestrator.d.ts +1 -1
- package/dist/agent/orchestrator.js +19 -4
- package/dist/agent/tools/backlog.js +8 -4
- package/dist/agent/tools/execution.js +1 -1
- package/dist/agent/tools/introspection.js +26 -4
- package/dist/commands/config.js +96 -2
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +47 -2
- package/dist/generator/config.js +13 -2
- package/dist/index.js +7 -1
- package/dist/repl/command-parser.d.ts +1 -1
- package/dist/repl/command-parser.js +1 -1
- package/dist/templates/config/ralph.config.cjs.tmpl +9 -2
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +16 -89
- package/dist/templates/prompts/PROMPT_e2e_fix.md.tmpl +55 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +12 -98
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +52 -49
- package/dist/templates/prompts/PROMPT_review_manual.md.tmpl +30 -2
- package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +59 -69
- package/dist/templates/prompts/PROMPT_verify.md.tmpl +7 -0
- package/dist/templates/root/README.md.tmpl +2 -3
- package/dist/templates/scripts/feature-loop.sh.tmpl +777 -90
- package/dist/templates/scripts/loop.sh.tmpl +5 -1
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +0 -2
- package/dist/tui/app.d.ts +5 -1
- package/dist/tui/app.js +12 -2
- package/dist/tui/hooks/useAgentOrchestrator.js +16 -7
- package/dist/tui/hooks/useInit.d.ts +5 -1
- package/dist/tui/hooks/useInit.js +20 -2
- package/dist/tui/screens/InitScreen.js +12 -1
- package/dist/tui/screens/MainShell.js +70 -6
- package/dist/tui/screens/RunScreen.d.ts +6 -2
- package/dist/tui/screens/RunScreen.js +48 -6
- package/dist/tui/utils/loop-status.d.ts +15 -0
- package/dist/tui/utils/loop-status.js +89 -27
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.js +14 -0
- package/package.json +1 -1
- package/src/templates/config/ralph.config.cjs.tmpl +9 -2
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +16 -89
- package/src/templates/prompts/PROMPT_e2e_fix.md.tmpl +55 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +12 -98
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +52 -49
- package/src/templates/prompts/PROMPT_review_manual.md.tmpl +30 -2
- package/src/templates/prompts/PROMPT_review_merge.md.tmpl +59 -69
- package/src/templates/prompts/PROMPT_verify.md.tmpl +7 -0
- package/src/templates/root/README.md.tmpl +2 -3
- package/src/templates/scripts/feature-loop.sh.tmpl +777 -90
- package/src/templates/scripts/loop.sh.tmpl +5 -1
- package/src/templates/scripts/ralph-monitor.sh.tmpl +0 -2
|
@@ -12,12 +12,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
12
12
|
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
13
13
|
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
14
14
|
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
15
|
+
CLAUDE_PERMISSION_MODE=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.claudePermissionMode || 'default')" 2>/dev/null || echo "default")
|
|
15
16
|
elif [ -f "$SCRIPT_DIR/../../ralph.config.cjs" ]; then
|
|
16
17
|
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
17
18
|
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
19
|
+
CLAUDE_PERMISSION_MODE=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.claudePermissionMode || 'default')" 2>/dev/null || echo "default")
|
|
18
20
|
else
|
|
19
21
|
PROMPTS_DIR=".ralph/prompts"
|
|
20
22
|
DEFAULT_MODEL="sonnet"
|
|
23
|
+
CLAUDE_PERMISSION_MODE="default"
|
|
21
24
|
fi
|
|
22
25
|
|
|
23
26
|
# Navigate to project root
|
|
@@ -31,6 +34,7 @@ ITERATION=0
|
|
|
31
34
|
echo "Starting Ralph loop"
|
|
32
35
|
echo " Prompt: $PROMPT_FILE"
|
|
33
36
|
echo " Model: $MODEL"
|
|
37
|
+
echo " Claude permission mode: $CLAUDE_PERMISSION_MODE"
|
|
34
38
|
echo " Max iterations: $MAX_ITERATIONS (0 = infinite)"
|
|
35
39
|
echo "Press Ctrl+C to stop"
|
|
36
40
|
echo ""
|
|
@@ -50,7 +54,7 @@ while true; do
|
|
|
50
54
|
ITERATION=$((ITERATION + 1))
|
|
51
55
|
echo "======================== ITERATION $ITERATION ========================"
|
|
52
56
|
|
|
53
|
-
cat "$PROMPT_FILE" | claude -p --
|
|
57
|
+
cat "$PROMPT_FILE" | claude -p --permission-mode "$CLAUDE_PERMISSION_MODE" --model "$MODEL"
|
|
54
58
|
|
|
55
59
|
echo ""
|
|
56
60
|
sleep 2
|
|
@@ -73,8 +73,6 @@ detect_phase() {
|
|
|
73
73
|
echo "Planning"
|
|
74
74
|
elif pgrep -f "PROMPT_e2e.md" > /dev/null 2>&1; then
|
|
75
75
|
echo "E2E Testing"
|
|
76
|
-
elif pgrep -f "PROMPT_verify.md" > /dev/null 2>&1; then
|
|
77
|
-
echo "Verification"
|
|
78
76
|
elif pgrep -f "PROMPT_review_manual.md" > /dev/null 2>&1; then
|
|
79
77
|
echo "PR Review"
|
|
80
78
|
elif pgrep -f "PROMPT_review_auto.md" > /dev/null 2>&1; then
|
package/dist/tui/app.d.ts
CHANGED
|
@@ -55,7 +55,11 @@ export interface RunAppProps {
|
|
|
55
55
|
/** If true, opens in monitor-only (read-only) mode — no loop is spawned */
|
|
56
56
|
monitorOnly?: boolean;
|
|
57
57
|
/** Review mode override */
|
|
58
|
-
reviewMode?: 'manual' | 'auto';
|
|
58
|
+
reviewMode?: 'manual' | 'auto' | 'merge';
|
|
59
|
+
/** Implementation CLI override */
|
|
60
|
+
cli?: 'claude' | 'codex';
|
|
61
|
+
/** Review CLI override */
|
|
62
|
+
reviewCli?: 'claude' | 'codex';
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
61
65
|
* Props for the main App component
|
package/dist/tui/app.js
CHANGED
|
@@ -34,7 +34,13 @@ interviewProps, runProps, agentProps, onComplete, onExit, }) {
|
|
|
34
34
|
const [currentScreen, setCurrentScreen] = useState(initialScreen);
|
|
35
35
|
const [screenProps, setScreenProps] = useState(() => {
|
|
36
36
|
if (initialScreen === 'run' && runProps) {
|
|
37
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
featureName: runProps.featureName,
|
|
39
|
+
monitorOnly: runProps.monitorOnly,
|
|
40
|
+
reviewMode: runProps.reviewMode,
|
|
41
|
+
cli: runProps.cli,
|
|
42
|
+
reviewCli: runProps.reviewCli,
|
|
43
|
+
};
|
|
38
44
|
}
|
|
39
45
|
if (interviewProps) {
|
|
40
46
|
return { featureName: interviewProps.featureName };
|
|
@@ -211,7 +217,9 @@ interviewProps, runProps, agentProps, onComplete, onExit, }) {
|
|
|
211
217
|
return null; // useEffect will redirect to shell
|
|
212
218
|
}
|
|
213
219
|
const reviewMode = screenProps?.reviewMode;
|
|
214
|
-
|
|
220
|
+
const cli = screenProps?.cli;
|
|
221
|
+
const reviewCli = screenProps?.reviewCli;
|
|
222
|
+
return (_jsx(RunScreen, { header: headerElement, featureName: featureName, projectRoot: sessionState.projectRoot, sessionState: sessionState, monitorOnly: monitorOnly, reviewMode: reviewMode, cli: cli, reviewCli: reviewCli, onComplete: handleRunComplete, onBackground: handleRunBackground, onCancel: () => navigate('shell') }));
|
|
215
223
|
}
|
|
216
224
|
case 'agent': {
|
|
217
225
|
// Merge CLI-provided agentProps with navigation screenProps (from /agent command)
|
|
@@ -219,6 +227,8 @@ interviewProps, runProps, agentProps, onComplete, onExit, }) {
|
|
|
219
227
|
...agentProps,
|
|
220
228
|
...(screenProps?.dryRun != null ? { dryRun: screenProps.dryRun } : {}),
|
|
221
229
|
...(screenProps?.maxItems != null ? { maxItems: screenProps.maxItems } : {}),
|
|
230
|
+
...(screenProps?.maxSteps != null ? { maxSteps: screenProps.maxSteps } : {}),
|
|
231
|
+
...(screenProps?.labels != null ? { labels: screenProps.labels } : {}),
|
|
222
232
|
...(screenProps?.reviewMode != null ? { reviewMode: screenProps.reviewMode } : {}),
|
|
223
233
|
...(screenProps?.issues != null ? { issues: screenProps.issues } : {}),
|
|
224
234
|
};
|
|
@@ -10,7 +10,7 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
10
10
|
import { resolveAgentEnv } from '../../agent/resolve-config.js';
|
|
11
11
|
import { createAgentOrchestrator, } from '../../agent/orchestrator.js';
|
|
12
12
|
import { initTracing, flushTracing } from '../../utils/tracing.js';
|
|
13
|
-
import { readCurrentPhase, readLoopStatus,
|
|
13
|
+
import { readCurrentPhase, readLoopStatus, parseLoopLogDelta, parsePhaseChanges, parseImplementationPlan, getLoopLogPath, shouldSkipLine, getGitBranch, } from '../utils/loop-status.js';
|
|
14
14
|
import { getRecentCommits } from '../utils/git-summary.js';
|
|
15
15
|
const MAX_LOG_ENTRIES = 500;
|
|
16
16
|
function now() {
|
|
@@ -31,7 +31,7 @@ function appendLog(prev, message, level = 'info') {
|
|
|
31
31
|
* Interpret a tool call name and extract relevant info from args/results
|
|
32
32
|
* to drive TUI state transitions.
|
|
33
33
|
*/
|
|
34
|
-
function interpretToolCalls(event, setActiveIssue, setQueue, setCompleted, setLogEntries, pollingRef, stopLoopPolling, ranLoopRef, activeIssueRef) {
|
|
34
|
+
function interpretToolCalls(event, setActiveIssue, setQueue, setCompleted, setLogEntries, pollingRef, stopLoopPolling, ranLoopRef, activeIssueRef, issueNumberFilter) {
|
|
35
35
|
// If a new tool call arrives while polling, the runLoop tool has finished — stop polling
|
|
36
36
|
for (const tc of event.toolCalls) {
|
|
37
37
|
if (tc.toolName !== 'runLoop' && pollingRef.current) {
|
|
@@ -207,7 +207,12 @@ function interpretToolCalls(event, setActiveIssue, setQueue, setCompleted, setLo
|
|
|
207
207
|
// Only update queue from unfiltered listIssues calls (full backlog scan).
|
|
208
208
|
// Filtered calls (e.g. labels: ["bug"]) are P0/blocker checks — not the backlog.
|
|
209
209
|
if (!listIssuesHasLabelFilter) {
|
|
210
|
-
|
|
210
|
+
// When --issues is configured, defensively filter queue to only those issues
|
|
211
|
+
// (the tool should already filter, but this guards against edge cases)
|
|
212
|
+
const relevantIssues = issueNumberFilter
|
|
213
|
+
? issues.filter(i => issueNumberFilter.has((i.number ?? i.issueNumber)))
|
|
214
|
+
: issues;
|
|
215
|
+
const queueItems = relevantIssues.map((issue) => ({
|
|
211
216
|
issueNumber: (issue.number ?? issue.issueNumber),
|
|
212
217
|
title: issue.title ?? `Issue #${issue.number ?? issue.issueNumber}`,
|
|
213
218
|
labels: Array.isArray(issue.labels) ? issue.labels : [],
|
|
@@ -268,7 +273,7 @@ export function useAgentOrchestrator(options) {
|
|
|
268
273
|
const state = {
|
|
269
274
|
featureName,
|
|
270
275
|
interval: null,
|
|
271
|
-
|
|
276
|
+
lastLogCursor: 0,
|
|
272
277
|
lastPhases: undefined,
|
|
273
278
|
};
|
|
274
279
|
// Accumulated activity events across polls
|
|
@@ -304,9 +309,10 @@ export function useAgentOrchestrator(options) {
|
|
|
304
309
|
const recentCommits = getRecentCommits(options.projectRoot, 3);
|
|
305
310
|
// Parse loop log for new events
|
|
306
311
|
const logPath = getLoopLogPath(featureName);
|
|
307
|
-
const
|
|
312
|
+
const logDelta = parseLoopLogDelta(logPath, state.lastLogCursor);
|
|
313
|
+
const logEvents = logDelta.events;
|
|
314
|
+
state.lastLogCursor = logDelta.nextCursor;
|
|
308
315
|
if (logEvents.length > 0) {
|
|
309
|
-
state.lastLogTimestamp = logEvents[logEvents.length - 1].timestamp + 1;
|
|
310
316
|
activityEventsAcc.push(...logEvents);
|
|
311
317
|
if (activityEventsAcc.length > MAX_ACTIVITY) {
|
|
312
318
|
activityEventsAcc.splice(0, activityEventsAcc.length - MAX_ACTIVITY);
|
|
@@ -374,6 +380,9 @@ export function useAgentOrchestrator(options) {
|
|
|
374
380
|
model: options.modelOverride,
|
|
375
381
|
});
|
|
376
382
|
setLogEntries((prev) => appendLog(prev, `Using ${env.provider}/${env.modelId ?? 'default'} on ${env.owner}/${env.repo}`));
|
|
383
|
+
const issueFilter = options.issues?.length
|
|
384
|
+
? new Set(options.issues)
|
|
385
|
+
: undefined;
|
|
377
386
|
const agentConfig = {
|
|
378
387
|
model: env.model,
|
|
379
388
|
modelId: env.modelId,
|
|
@@ -388,7 +397,7 @@ export function useAgentOrchestrator(options) {
|
|
|
388
397
|
reviewMode: options.reviewMode,
|
|
389
398
|
dryRun: options.dryRun,
|
|
390
399
|
onStepUpdate: (event) => {
|
|
391
|
-
interpretToolCalls(event, setActiveIssue, setQueue, setCompleted, setLogEntries, pollingRef, stopLoopPolling, ranLoopRef, activeIssueRef);
|
|
400
|
+
interpretToolCalls(event, setActiveIssue, setQueue, setCompleted, setLogEntries, pollingRef, stopLoopPolling, ranLoopRef, activeIssueRef, issueFilter);
|
|
392
401
|
},
|
|
393
402
|
onProgress: (toolName, line) => {
|
|
394
403
|
// Detect generateSpec execution start (onStepFinish fires too late)
|
|
@@ -19,7 +19,7 @@ import type { ActionStatus } from '../components/ActionOutput.js';
|
|
|
19
19
|
/**
|
|
20
20
|
* Init workflow phases
|
|
21
21
|
*/
|
|
22
|
-
export type InitPhase = 'scanning' | 'provider-select' | 'key-input' | 'key-save' | 'model-select' | 'ai-analysis' | 'confirm' | 'generating' | 'complete' | 'error';
|
|
22
|
+
export type InitPhase = 'scanning' | 'provider-select' | 'key-input' | 'key-save' | 'model-select' | 'coding-cli-select' | 'ai-analysis' | 'confirm' | 'generating' | 'complete' | 'error';
|
|
23
23
|
/**
|
|
24
24
|
* Phase configuration for display
|
|
25
25
|
*/
|
|
@@ -66,6 +66,8 @@ export interface InitState {
|
|
|
66
66
|
provider: AIProvider | null;
|
|
67
67
|
/** Selected model */
|
|
68
68
|
model: string | null;
|
|
69
|
+
/** Selected coding CLI for generated loop config */
|
|
70
|
+
codingCli: 'claude' | 'codex';
|
|
69
71
|
/** API key entered (not persisted in state for security) */
|
|
70
72
|
hasApiKey: boolean;
|
|
71
73
|
/** Whether API key was entered this session (needs save prompt) */
|
|
@@ -109,6 +111,8 @@ export interface UseInitReturn {
|
|
|
109
111
|
setSaveKey: (save: boolean) => void;
|
|
110
112
|
/** Select model and advance to AI analysis */
|
|
111
113
|
selectModel: (model: string) => void;
|
|
114
|
+
/** Select coding CLI and advance to AI analysis */
|
|
115
|
+
selectCodingCli: (cli: 'claude' | 'codex') => void;
|
|
112
116
|
/** Set AI analysis progress */
|
|
113
117
|
setAiProgress: (status: string) => void;
|
|
114
118
|
/** Update tool call (add or update status) */
|
|
@@ -42,6 +42,11 @@ export const INIT_PHASE_CONFIGS = {
|
|
|
42
42
|
name: 'Model',
|
|
43
43
|
description: 'Select AI model',
|
|
44
44
|
},
|
|
45
|
+
'coding-cli-select': {
|
|
46
|
+
number: 3,
|
|
47
|
+
name: 'Coding CLI',
|
|
48
|
+
description: 'Select coding CLI for loops',
|
|
49
|
+
},
|
|
45
50
|
'ai-analysis': {
|
|
46
51
|
number: 4,
|
|
47
52
|
name: 'Analysis',
|
|
@@ -82,6 +87,7 @@ const initialState = {
|
|
|
82
87
|
enhancedResult: null,
|
|
83
88
|
provider: null,
|
|
84
89
|
model: null,
|
|
90
|
+
codingCli: 'claude',
|
|
85
91
|
hasApiKey: false,
|
|
86
92
|
apiKeyEnteredThisSession: false,
|
|
87
93
|
saveKeyToEnv: false,
|
|
@@ -168,12 +174,22 @@ export function useInit() {
|
|
|
168
174
|
}));
|
|
169
175
|
}, []);
|
|
170
176
|
/**
|
|
171
|
-
* Select model and advance to
|
|
177
|
+
* Select model and advance to coding CLI selection
|
|
172
178
|
*/
|
|
173
179
|
const selectModel = useCallback((model) => {
|
|
174
180
|
setState((prev) => ({
|
|
175
181
|
...prev,
|
|
176
182
|
model,
|
|
183
|
+
phase: 'coding-cli-select',
|
|
184
|
+
}));
|
|
185
|
+
}, []);
|
|
186
|
+
/**
|
|
187
|
+
* Select coding CLI and advance to AI analysis
|
|
188
|
+
*/
|
|
189
|
+
const selectCodingCli = useCallback((cli) => {
|
|
190
|
+
setState((prev) => ({
|
|
191
|
+
...prev,
|
|
192
|
+
codingCli: cli,
|
|
177
193
|
phase: 'ai-analysis',
|
|
178
194
|
isWorking: true,
|
|
179
195
|
workingStatus: 'Initializing AI analysis...',
|
|
@@ -325,7 +341,8 @@ export function useInit() {
|
|
|
325
341
|
'key-input': 'provider-select',
|
|
326
342
|
'key-save': 'key-input',
|
|
327
343
|
'model-select': prev.apiKeyEnteredThisSession ? 'key-save' : 'provider-select',
|
|
328
|
-
'
|
|
344
|
+
'coding-cli-select': 'model-select',
|
|
345
|
+
'ai-analysis': 'coding-cli-select',
|
|
329
346
|
confirm: 'ai-analysis',
|
|
330
347
|
generating: 'confirm',
|
|
331
348
|
};
|
|
@@ -345,6 +362,7 @@ export function useInit() {
|
|
|
345
362
|
setApiKey,
|
|
346
363
|
setSaveKey,
|
|
347
364
|
selectModel,
|
|
365
|
+
selectCodingCli,
|
|
348
366
|
setAiProgress,
|
|
349
367
|
updateToolCall,
|
|
350
368
|
setEnhancedResult,
|
|
@@ -38,6 +38,10 @@ const PROVIDER_OPTIONS = [
|
|
|
38
38
|
{ value: 'openai', label: 'OpenAI' },
|
|
39
39
|
{ value: 'openrouter', label: 'OpenRouter', hint: 'multiple providers' },
|
|
40
40
|
];
|
|
41
|
+
const CODING_CLI_OPTIONS = [
|
|
42
|
+
{ value: 'claude', label: 'Claude Code', hint: 'default' },
|
|
43
|
+
{ value: 'codex', label: 'Codex (OpenAI)' },
|
|
44
|
+
];
|
|
41
45
|
function getModelOptions(provider) {
|
|
42
46
|
return AVAILABLE_MODELS[provider].map((m) => ({
|
|
43
47
|
value: m.value,
|
|
@@ -51,7 +55,7 @@ function getModelOptions(provider) {
|
|
|
51
55
|
* The complete Ink-based init workflow wrapped in AppShell.
|
|
52
56
|
*/
|
|
53
57
|
export function InitScreen({ header, projectRoot, sessionState, onComplete, onCancel, }) {
|
|
54
|
-
const { state, initialize, setScanResult, setExistingProvider, selectProvider, setApiKey, setSaveKey, selectModel, setAiProgress, updateToolCall, setEnhancedResult, setAiError, confirmGeneration, setGenerating, setGenerationComplete, setError, } = useInit();
|
|
58
|
+
const { state, initialize, setScanResult, setExistingProvider, selectProvider, setApiKey, setSaveKey, selectModel, selectCodingCli, setAiProgress, updateToolCall, setEnhancedResult, setAiError, confirmGeneration, setGenerating, setGenerationComplete, setError, } = useInit();
|
|
55
59
|
const apiKeyRef = useRef(null);
|
|
56
60
|
const aiAnalysisStarted = useRef(false);
|
|
57
61
|
const generationStarted = useRef(false);
|
|
@@ -189,6 +193,8 @@ export function InitScreen({ header, projectRoot, sessionState, onComplete, onCa
|
|
|
189
193
|
customVariables: {
|
|
190
194
|
...(state.provider ? { agentProvider: state.provider } : {}),
|
|
191
195
|
...(state.model ? { agentModel: state.model } : {}),
|
|
196
|
+
codingCli: state.codingCli,
|
|
197
|
+
reviewCli: state.codingCli,
|
|
192
198
|
},
|
|
193
199
|
});
|
|
194
200
|
try {
|
|
@@ -261,6 +267,9 @@ export function InitScreen({ header, projectRoot, sessionState, onComplete, onCa
|
|
|
261
267
|
const handleModelSelect = useCallback((model) => {
|
|
262
268
|
selectModel(model);
|
|
263
269
|
}, [selectModel]);
|
|
270
|
+
const handleCodingCliSelect = useCallback((cli) => {
|
|
271
|
+
selectCodingCli(cli);
|
|
272
|
+
}, [selectCodingCli]);
|
|
264
273
|
const handleConfirmGeneration = useCallback((confirmed) => {
|
|
265
274
|
confirmGeneration(confirmed);
|
|
266
275
|
}, [confirmGeneration]);
|
|
@@ -292,6 +301,8 @@ export function InitScreen({ header, projectRoot, sessionState, onComplete, onCa
|
|
|
292
301
|
if (!state.provider)
|
|
293
302
|
return null;
|
|
294
303
|
return (_jsx(Select, { message: "Select model:", options: getModelOptions(state.provider), onSelect: handleModelSelect, onCancel: onCancel }));
|
|
304
|
+
case 'coding-cli-select':
|
|
305
|
+
return (_jsx(Select, { message: "Which coding CLI do you use for loops?", options: CODING_CLI_OPTIONS, onSelect: handleCodingCliSelect, onCancel: onCancel }));
|
|
295
306
|
case 'confirm':
|
|
296
307
|
return (_jsx(Confirm, { message: "Generate Ralph configuration files?", onConfirm: handleConfirmGeneration, onCancel: onCancel, initialValue: true }));
|
|
297
308
|
default:
|
|
@@ -137,6 +137,8 @@ export function MainShell({ header, sessionState, onNavigate, backgroundRuns, in
|
|
|
137
137
|
}
|
|
138
138
|
// Parse optional flags, separating them from positional args
|
|
139
139
|
let reviewMode;
|
|
140
|
+
let cli;
|
|
141
|
+
let reviewCli;
|
|
140
142
|
const positional = [];
|
|
141
143
|
for (let i = 0; i < args.length; i++) {
|
|
142
144
|
if (args[i] === '--review-mode') {
|
|
@@ -146,18 +148,44 @@ export function MainShell({ header, sessionState, onNavigate, backgroundRuns, in
|
|
|
146
148
|
}
|
|
147
149
|
continue;
|
|
148
150
|
}
|
|
151
|
+
if (args[i] === '--cli') {
|
|
152
|
+
if (i + 1 < args.length) {
|
|
153
|
+
cli = args[i + 1];
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (args[i] === '--review-cli') {
|
|
159
|
+
if (i + 1 < args.length) {
|
|
160
|
+
reviewCli = args[i + 1];
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (args[i]?.startsWith('--')) {
|
|
166
|
+
addSystemMessage(`Unknown flag '${args[i]}' for /run.`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
149
169
|
positional.push(args[i]);
|
|
150
170
|
}
|
|
151
|
-
if (reviewMode !== undefined &&
|
|
152
|
-
addSystemMessage(`Invalid --review-mode value '${reviewMode}'. Use 'manual' or '
|
|
171
|
+
if (reviewMode !== undefined && !['manual', 'auto', 'merge'].includes(reviewMode)) {
|
|
172
|
+
addSystemMessage(`Invalid --review-mode value '${reviewMode}'. Use 'manual', 'auto', or 'merge'.`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (cli !== undefined && !['claude', 'codex'].includes(cli)) {
|
|
176
|
+
addSystemMessage(`Invalid --cli value '${cli}'. Use 'claude' or 'codex'.`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (reviewCli !== undefined && !['claude', 'codex'].includes(reviewCli)) {
|
|
180
|
+
addSystemMessage(`Invalid --review-cli value '${reviewCli}'. Use 'claude' or 'codex'.`);
|
|
153
181
|
return;
|
|
154
182
|
}
|
|
155
183
|
const featureName = positional[0];
|
|
156
184
|
if (!featureName) {
|
|
157
|
-
addSystemMessage('Feature name required. Usage: /run <feature-name> [--review-mode auto|
|
|
185
|
+
addSystemMessage('Feature name required. Usage: /run <feature-name> [--review-mode manual|auto|merge] [--cli claude|codex] [--review-cli claude|codex]');
|
|
158
186
|
return;
|
|
159
187
|
}
|
|
160
|
-
onNavigate('run', { featureName, reviewMode });
|
|
188
|
+
onNavigate('run', { featureName, reviewMode, cli, reviewCli });
|
|
161
189
|
}, [sessionState.initialized, addSystemMessage, onNavigate]);
|
|
162
190
|
const handleMonitor = useCallback((args) => {
|
|
163
191
|
if (args.length === 0) {
|
|
@@ -198,29 +226,65 @@ export function MainShell({ header, sessionState, onNavigate, backgroundRuns, in
|
|
|
198
226
|
// Parse optional flags
|
|
199
227
|
let dryRun = false;
|
|
200
228
|
let maxItems;
|
|
229
|
+
let maxSteps;
|
|
201
230
|
let reviewMode;
|
|
231
|
+
let labels;
|
|
232
|
+
let issues;
|
|
202
233
|
for (let i = 0; i < args.length; i++) {
|
|
203
234
|
if (args[i] === '--dry-run') {
|
|
204
235
|
dryRun = true;
|
|
205
236
|
}
|
|
206
237
|
else if (args[i] === '--max-items' && i + 1 < args.length) {
|
|
207
238
|
maxItems = parseInt(args[i + 1], 10);
|
|
208
|
-
if (Number.isNaN(maxItems)) {
|
|
239
|
+
if (Number.isNaN(maxItems) || maxItems < 1) {
|
|
209
240
|
addSystemMessage(`Invalid --max-items value '${args[i + 1]}'. Must be a number.`);
|
|
210
241
|
return;
|
|
211
242
|
}
|
|
212
243
|
i++;
|
|
213
244
|
}
|
|
245
|
+
else if (args[i] === '--max-steps' && i + 1 < args.length) {
|
|
246
|
+
maxSteps = parseInt(args[i + 1], 10);
|
|
247
|
+
if (Number.isNaN(maxSteps) || maxSteps < 1) {
|
|
248
|
+
addSystemMessage(`Invalid --max-steps value '${args[i + 1]}'. Must be a number.`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
i++;
|
|
252
|
+
}
|
|
253
|
+
else if (args[i] === '--labels' && i + 1 < args.length) {
|
|
254
|
+
labels = args[i + 1].split(',').map(l => l.trim()).filter(Boolean);
|
|
255
|
+
if (labels.length === 0) {
|
|
256
|
+
addSystemMessage(`Invalid --labels value '${args[i + 1]}'. Use comma-separated labels.`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
i++;
|
|
260
|
+
}
|
|
261
|
+
else if (args[i] === '--issues' && i + 1 < args.length) {
|
|
262
|
+
const raw = args[i + 1];
|
|
263
|
+
const parsed = raw.split(',').map((s) => {
|
|
264
|
+
const n = parseInt(s.trim(), 10);
|
|
265
|
+
return Number.isNaN(n) || n < 1 ? null : n;
|
|
266
|
+
});
|
|
267
|
+
if (parsed.some((n) => n == null)) {
|
|
268
|
+
addSystemMessage(`Invalid --issues value '${raw}'. Use comma-separated issue numbers.`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
issues = parsed;
|
|
272
|
+
i++;
|
|
273
|
+
}
|
|
214
274
|
else if (args[i] === '--review-mode' && i + 1 < args.length) {
|
|
215
275
|
reviewMode = args[i + 1];
|
|
216
276
|
i++;
|
|
217
277
|
}
|
|
278
|
+
else if (args[i]?.startsWith('--')) {
|
|
279
|
+
addSystemMessage(`Unknown flag '${args[i]}' for /agent.`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
218
282
|
}
|
|
219
283
|
if (reviewMode !== undefined && !['manual', 'auto', 'merge'].includes(reviewMode)) {
|
|
220
284
|
addSystemMessage(`Invalid --review-mode value '${reviewMode}'. Use 'manual', 'auto', or 'merge'.`);
|
|
221
285
|
return;
|
|
222
286
|
}
|
|
223
|
-
onNavigate('agent', { dryRun, maxItems, reviewMode });
|
|
287
|
+
onNavigate('agent', { dryRun, maxItems, maxSteps, reviewMode, labels, issues });
|
|
224
288
|
}, [sessionState.initialized, addSystemMessage, onNavigate]);
|
|
225
289
|
const handleIssueCommand = useCallback(async (searchQuery) => {
|
|
226
290
|
if (!sessionState.initialized) {
|
|
@@ -150,10 +150,14 @@ export interface RunScreenProps {
|
|
|
150
150
|
/** Monitor-only mode: don't spawn, just poll status */
|
|
151
151
|
monitorOnly?: boolean;
|
|
152
152
|
/** Override review mode from CLI flags (takes precedence over config) */
|
|
153
|
-
reviewMode?: 'manual' | 'auto';
|
|
153
|
+
reviewMode?: 'manual' | 'auto' | 'merge';
|
|
154
|
+
/** Override implementation CLI from CLI flags (takes precedence over config) */
|
|
155
|
+
cli?: 'claude' | 'codex';
|
|
156
|
+
/** Override review CLI from CLI flags (takes precedence over config) */
|
|
157
|
+
reviewCli?: 'claude' | 'codex';
|
|
154
158
|
onComplete: (summary: RunSummary) => void;
|
|
155
159
|
/** Called when user presses Esc to background the run */
|
|
156
160
|
onBackground?: (featureName: string) => void;
|
|
157
161
|
onCancel: () => void;
|
|
158
162
|
}
|
|
159
|
-
export declare function RunScreen({ header, featureName, projectRoot, sessionState, monitorOnly, reviewMode: reviewModeProp, onComplete, onBackground, onCancel, }: RunScreenProps): React.ReactElement;
|
|
163
|
+
export declare function RunScreen({ header, featureName, projectRoot, sessionState, monitorOnly, reviewMode: reviewModeProp, cli: cliProp, reviewCli: reviewCliProp, onComplete, onBackground, onCancel, }: RunScreenProps): React.ReactElement;
|
|
@@ -33,6 +33,15 @@ import { readActionRequest, writeActionReply, cleanupActionFiles } from '../util
|
|
|
33
33
|
const POLL_INTERVAL_MS = 2500;
|
|
34
34
|
const ERROR_TAIL_LINES = 12;
|
|
35
35
|
const RUN_REVIEW_MODES = ['manual', 'auto', 'merge'];
|
|
36
|
+
const RUN_LOOP_CLIS = ['claude', 'codex'];
|
|
37
|
+
const DEFAULT_CODEX_LOOP_MODEL = 'gpt-5.3-codex';
|
|
38
|
+
function getLoopModelLabel(defaultClaudeModel, codingCli, reviewCli) {
|
|
39
|
+
if (codingCli === 'codex' && reviewCli === 'codex')
|
|
40
|
+
return DEFAULT_CODEX_LOOP_MODEL;
|
|
41
|
+
if (codingCli === 'claude' && reviewCli === 'claude')
|
|
42
|
+
return defaultClaudeModel;
|
|
43
|
+
return `${defaultClaudeModel} (claude) / ${DEFAULT_CODEX_LOOP_MODEL} (codex)`;
|
|
44
|
+
}
|
|
36
45
|
function findFeatureLoopScript(projectRoot, scriptsDir) {
|
|
37
46
|
const localScript = join(projectRoot, scriptsDir, 'feature-loop.sh');
|
|
38
47
|
if (existsSync(localScript)) {
|
|
@@ -48,6 +57,15 @@ function findFeatureLoopScript(projectRoot, scriptsDir) {
|
|
|
48
57
|
}
|
|
49
58
|
return null;
|
|
50
59
|
}
|
|
60
|
+
function scriptSupportsCliFlags(scriptPath) {
|
|
61
|
+
try {
|
|
62
|
+
const script = readFileSync(scriptPath, 'utf-8');
|
|
63
|
+
return script.includes('--cli') && script.includes('--review-cli');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
51
69
|
function findSpecFile(projectRoot, feature, specsDir) {
|
|
52
70
|
const possiblePaths = [
|
|
53
71
|
join(projectRoot, specsDir, `${feature}.md`),
|
|
@@ -87,7 +105,7 @@ function readLogTail(logPath, maxLines) {
|
|
|
87
105
|
return `[Unable to read log: ${err instanceof Error ? err.message : String(err)}]`;
|
|
88
106
|
}
|
|
89
107
|
}
|
|
90
|
-
export function RunScreen({ header, featureName, projectRoot, sessionState, monitorOnly = false, reviewMode: reviewModeProp, onComplete, onBackground, onCancel, }) {
|
|
108
|
+
export function RunScreen({ header, featureName, projectRoot, sessionState, monitorOnly = false, reviewMode: reviewModeProp, cli: cliProp, reviewCli: reviewCliProp, onComplete, onBackground, onCancel, }) {
|
|
91
109
|
const [status, setStatus] = useState(() => {
|
|
92
110
|
try {
|
|
93
111
|
return readLoopStatus(featureName);
|
|
@@ -115,6 +133,8 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
115
133
|
const [baselineCommit, setBaselineCommit] = useState(null);
|
|
116
134
|
const [recentCommits, setRecentCommits] = useState([]);
|
|
117
135
|
const [reviewMode, setReviewMode] = useState(reviewModeProp ?? 'manual');
|
|
136
|
+
const [loopCli, setLoopCli] = useState('claude');
|
|
137
|
+
const [reviewCli, setReviewCli] = useState('claude');
|
|
118
138
|
const [loopModel, setLoopModel] = useState(null);
|
|
119
139
|
const childRef = useRef(null);
|
|
120
140
|
const stopRequestedRef = useRef(false);
|
|
@@ -351,8 +371,12 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
351
371
|
const config = sessionState.config ?? await loadConfigWithDefaults(projectRoot);
|
|
352
372
|
specsDirRef.current = config.paths.specs;
|
|
353
373
|
if (!cancelled) {
|
|
354
|
-
setLoopModel(config.loop.defaultModel);
|
|
355
374
|
setReviewMode((prev) => reviewModeProp ?? config.loop.reviewMode ?? prev);
|
|
375
|
+
const effectiveCli = (cliProp ?? config.loop.codingCli ?? 'claude');
|
|
376
|
+
const effectiveReviewCli = (reviewCliProp ?? config.loop.reviewCli ?? effectiveCli);
|
|
377
|
+
setLoopModel(getLoopModelLabel(config.loop.defaultModel, effectiveCli, effectiveReviewCli));
|
|
378
|
+
setLoopCli(effectiveCli);
|
|
379
|
+
setReviewCli(effectiveReviewCli);
|
|
356
380
|
}
|
|
357
381
|
}
|
|
358
382
|
catch (err) {
|
|
@@ -396,7 +420,11 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
396
420
|
configRootRef.current = config.paths.root;
|
|
397
421
|
maxIterationsRef.current = config.loop.maxIterations;
|
|
398
422
|
maxE2eAttemptsRef.current = config.loop.maxE2eAttempts;
|
|
399
|
-
|
|
423
|
+
const effectiveCli = (cliProp ?? config.loop.codingCli ?? 'claude');
|
|
424
|
+
const effectiveReviewCli = (reviewCliProp ?? config.loop.reviewCli ?? effectiveCli);
|
|
425
|
+
setLoopModel(getLoopModelLabel(config.loop.defaultModel, effectiveCli, effectiveReviewCli));
|
|
426
|
+
setLoopCli(effectiveCli);
|
|
427
|
+
setReviewCli(effectiveReviewCli);
|
|
400
428
|
const specFile = findSpecFile(projectRoot, featureName, config.paths.specs);
|
|
401
429
|
if (!specFile) {
|
|
402
430
|
setError(`Spec file not found for "${featureName}". Run /new ${featureName} first.`);
|
|
@@ -409,6 +437,16 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
409
437
|
setIsStarting(false);
|
|
410
438
|
return;
|
|
411
439
|
}
|
|
440
|
+
if (!RUN_LOOP_CLIS.includes(effectiveCli) || !RUN_LOOP_CLIS.includes(effectiveReviewCli)) {
|
|
441
|
+
setError(`Invalid CLI selection. Allowed values are: ${RUN_LOOP_CLIS.join(', ')}`);
|
|
442
|
+
setIsStarting(false);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if ((effectiveCli !== 'claude' || effectiveReviewCli !== 'claude') && !scriptSupportsCliFlags(scriptPath)) {
|
|
446
|
+
setError('Your feature-loop.sh is outdated and does not support --cli/--review-cli. Re-run /init, then try again.');
|
|
447
|
+
setIsStarting(false);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
412
450
|
const effectiveReviewMode = reviewModeProp ?? config.loop.reviewMode ?? 'manual';
|
|
413
451
|
setReviewMode(effectiveReviewMode);
|
|
414
452
|
if (effectiveReviewMode !== 'manual' && effectiveReviewMode !== 'auto' && effectiveReviewMode !== 'merge') {
|
|
@@ -428,6 +466,10 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
428
466
|
featureName,
|
|
429
467
|
String(config.loop.maxIterations),
|
|
430
468
|
String(config.loop.maxE2eAttempts),
|
|
469
|
+
'--cli',
|
|
470
|
+
effectiveCli,
|
|
471
|
+
'--review-cli',
|
|
472
|
+
effectiveReviewCli,
|
|
431
473
|
'--review-mode',
|
|
432
474
|
effectiveReviewMode,
|
|
433
475
|
];
|
|
@@ -551,7 +593,7 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
551
593
|
// Note: refreshStatusRef (not refreshStatus) is used inside to avoid re-spawning
|
|
552
594
|
// the child process when the callback identity changes due to actionRequest updates.
|
|
553
595
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
554
|
-
}, [featureName, projectRoot, monitorOnly, sessionState.config]);
|
|
596
|
+
}, [featureName, projectRoot, monitorOnly, sessionState.config, reviewModeProp, cliProp, reviewCliProp]);
|
|
555
597
|
const totalTasks = tasks.tasksDone + tasks.tasksPending;
|
|
556
598
|
const totalE2e = tasks.e2eDone + tasks.e2ePending;
|
|
557
599
|
const totalAll = totalTasks + totalE2e;
|
|
@@ -613,8 +655,8 @@ export function RunScreen({ header, featureName, projectRoot, sessionState, moni
|
|
|
613
655
|
action: 'Run Loop',
|
|
614
656
|
phase: phaseLine,
|
|
615
657
|
path: featureName,
|
|
616
|
-
extra: `review: ${reviewMode}`,
|
|
617
|
-
}, children: completionSummary ? (_jsx(RunCompletionSummary, { summary: completionSummary })) : (!error && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { children: "Phase: " }), _jsx(Text, { color: colors.yellow, children: phaseLine }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Iter: " }), _jsx(Text, { color: colors.green, children: status.iteration }), _jsxs(Text, { dimColor: true, children: ["/", status.maxIterations || maxIterationsRef.current || '-'] }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Branch: " }), _jsx(Text, { color: colors.blue, children: branch }), loopModel && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Model: " }), _jsx(Text, { color: colors.blue, children: loopModel })] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { children: "Tokens: " }), _jsx(Text, { color: colors.pink, children: formatNumber(totalTokens) }), _jsxs(Text, { dimColor: true, children: [" (in:", formatNumber(status.tokensInput), " out:", formatNumber(status.tokensOutput), " cache:", formatNumber(status.cacheRead), ")"] }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsxs(Text, { dimColor: true, children: ["Elapsed: ", formatDuration(startTimeRef.current)] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, children: [_jsx(Text, { bold: true, children: padLabel('Implementation:') }), _jsx(ProgressBar, { percent: percentTasks }), _jsxs(Text, { children: [String(percentTasks).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", tasks.tasksDone] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", tasks.tasksPending] })] }), totalE2e > 0 && (_jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, children: [_jsx(Text, { bold: true, children: padLabel('E2E Tests:') }), _jsx(ProgressBar, { percent: percentE2e }), _jsxs(Text, { children: [String(percentE2e).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", tasks.e2eDone] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", tasks.e2ePending] })] })), _jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: padLabel('Overall:') }), _jsx(ProgressBar, { percent: percentAll }), _jsxs(Text, { children: [String(percentAll).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", doneAll] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", totalAll - doneAll] })] })] }), recentCommits.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Recent Commits" }), recentCommits.map((c) => (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { dimColor: true, children: [" ", c.hash, " ", c.title] }) }, c.hash)))] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Activity" }), _jsx(ActivityFeed, { events: activityEvents, latestCommit: baselineCommit && latestCommit && baselineCommit !== latestCommit
|
|
658
|
+
extra: `review: ${reviewMode} | cli: ${loopCli} | review-cli: ${reviewCli}`,
|
|
659
|
+
}, children: completionSummary ? (_jsx(RunCompletionSummary, { summary: completionSummary })) : (!error && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { children: "Phase: " }), _jsx(Text, { color: colors.yellow, children: phaseLine }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Iter: " }), _jsx(Text, { color: colors.green, children: status.iteration }), _jsxs(Text, { dimColor: true, children: ["/", status.maxIterations || maxIterationsRef.current || '-'] }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Branch: " }), _jsx(Text, { color: colors.blue, children: branch }), loopModel && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "Model: " }), _jsx(Text, { color: colors.blue, children: loopModel })] })), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsx(Text, { children: "CLI: " }), _jsx(Text, { color: colors.blue, children: loopCli }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { color: colors.blue, children: reviewCli })] }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { children: "Tokens: " }), _jsx(Text, { color: colors.pink, children: formatNumber(totalTokens) }), _jsxs(Text, { dimColor: true, children: [" (in:", formatNumber(status.tokensInput), " out:", formatNumber(status.tokensOutput), " cache:", formatNumber(status.cacheRead), ")"] }), _jsx(Text, { dimColor: true, children: theme.statusLine.separator }), _jsxs(Text, { dimColor: true, children: ["Elapsed: ", formatDuration(startTimeRef.current)] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, children: [_jsx(Text, { bold: true, children: padLabel('Implementation:') }), _jsx(ProgressBar, { percent: percentTasks }), _jsxs(Text, { children: [String(percentTasks).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", tasks.tasksDone] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", tasks.tasksPending] })] }), totalE2e > 0 && (_jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, children: [_jsx(Text, { bold: true, children: padLabel('E2E Tests:') }), _jsx(ProgressBar, { percent: percentE2e }), _jsxs(Text, { children: [String(percentE2e).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", tasks.e2eDone] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", tasks.e2ePending] })] })), _jsxs(Box, { flexDirection: "row", alignItems: "center", gap: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: padLabel('Overall:') }), _jsx(ProgressBar, { percent: percentAll }), _jsxs(Text, { children: [String(percentAll).padStart(3), "%"] }), _jsxs(Text, { color: colors.green, children: ['\u2713', " ", doneAll] }), _jsxs(Text, { color: colors.yellow, children: ['\u25cb', " ", totalAll - doneAll] })] })] }), recentCommits.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Recent Commits" }), recentCommits.map((c) => (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { dimColor: true, children: [" ", c.hash, " ", c.title] }) }, c.hash)))] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Activity" }), _jsx(ActivityFeed, { events: activityEvents, latestCommit: baselineCommit && latestCommit && baselineCommit !== latestCommit
|
|
618
660
|
? `${baselineCommit} \u2192 ${latestCommit}`
|
|
619
661
|
: latestCommit || undefined })] })] }))) }));
|
|
620
662
|
}
|
|
@@ -109,6 +109,14 @@ export interface ActivityEvent {
|
|
|
109
109
|
/** Inferred status based on event content */
|
|
110
110
|
status: 'success' | 'error' | 'in-progress';
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Incremental parse result for loop logs.
|
|
114
|
+
* `nextCursor` is a character offset for the next poll.
|
|
115
|
+
*/
|
|
116
|
+
export interface LoopLogDelta {
|
|
117
|
+
events: ActivityEvent[];
|
|
118
|
+
nextCursor: number;
|
|
119
|
+
}
|
|
112
120
|
/**
|
|
113
121
|
* Returns true if a log line should be excluded from the activity feed.
|
|
114
122
|
*/
|
|
@@ -123,6 +131,13 @@ export declare function shouldSkipLine(line: string): boolean;
|
|
|
123
131
|
* @param since - Optional epoch ms cutoff; only return events at or after this time.
|
|
124
132
|
*/
|
|
125
133
|
export declare function parseLoopLog(logPath: string, since?: number): ActivityEvent[];
|
|
134
|
+
/**
|
|
135
|
+
* Incrementally parse only the new portion of a loop log.
|
|
136
|
+
*
|
|
137
|
+
* The cursor is a character offset in UTF-16 code units. If the file shrinks,
|
|
138
|
+
* parsing restarts from the beginning.
|
|
139
|
+
*/
|
|
140
|
+
export declare function parseLoopLogDelta(logPath: string, cursor?: number): LoopLogDelta;
|
|
126
141
|
/**
|
|
127
142
|
* Detect phase changes by comparing current phases file to a known previous state,
|
|
128
143
|
* and emit activity events for newly completed or started phases.
|