wiggum-cli 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates/scripts/feature-loop.sh.tmpl +81 -4
- package/dist/tui/app.js +2 -1
- package/dist/tui/components/ChatInput.js +27 -9
- package/dist/tui/components/RunCompletionSummary.d.ts +3 -9
- package/dist/tui/components/RunCompletionSummary.js +59 -14
- package/dist/tui/components/SummaryBox.d.ts +59 -0
- package/dist/tui/components/SummaryBox.js +97 -0
- package/dist/tui/screens/MainShell.js +23 -2
- package/dist/tui/screens/RunScreen.d.ts +116 -1
- package/dist/tui/screens/RunScreen.js +33 -5
- package/dist/tui/utils/build-run-summary.d.ts +24 -0
- package/dist/tui/utils/build-run-summary.js +241 -0
- package/dist/tui/utils/git-summary.d.ts +24 -0
- package/dist/tui/utils/git-summary.js +63 -0
- package/dist/tui/utils/input-utils.d.ts +20 -0
- package/dist/tui/utils/input-utils.js +27 -0
- package/dist/tui/utils/pr-summary.d.ts +34 -0
- package/dist/tui/utils/pr-summary.js +84 -0
- package/dist/utils/summary-file.d.ts +25 -0
- package/dist/utils/summary-file.js +37 -0
- package/package.json +1 -1
- package/src/templates/scripts/feature-loop.sh.tmpl +81 -4
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR and Issue utilities for enhanced run summary.
|
|
3
|
+
*/
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { logger } from '../../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Get PR information for a branch.
|
|
8
|
+
*
|
|
9
|
+
* @param projectRoot - Root directory of the git repository
|
|
10
|
+
* @param branchName - Branch name to look up
|
|
11
|
+
* @returns PR info object, or null if no PR exists for this branch
|
|
12
|
+
* @throws When gh CLI is unavailable or the command fails
|
|
13
|
+
*/
|
|
14
|
+
export function getPrForBranch(projectRoot, branchName) {
|
|
15
|
+
// Use gh pr list to find PR for this branch
|
|
16
|
+
const output = execFileSync('gh', ['pr', 'list', '--head', branchName, '--state', 'all', '--json', 'number,url,state,title', '--limit', '1'], {
|
|
17
|
+
cwd: projectRoot,
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
timeout: 10_000,
|
|
20
|
+
}).trim();
|
|
21
|
+
if (!output) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const prs = JSON.parse(output);
|
|
25
|
+
if (!Array.isArray(prs) || prs.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const pr = prs[0];
|
|
29
|
+
return {
|
|
30
|
+
number: pr.number,
|
|
31
|
+
url: pr.url,
|
|
32
|
+
state: pr.state,
|
|
33
|
+
title: pr.title,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get linked issue for a branch by parsing the PR body for closing keywords
|
|
38
|
+
* (Closes/Fixes/Resolves #N).
|
|
39
|
+
*
|
|
40
|
+
* @param projectRoot - Root directory of the git repository
|
|
41
|
+
* @param branchName - Branch name to look up
|
|
42
|
+
* @param prInfo - Optional pre-fetched PR info to avoid redundant gh call
|
|
43
|
+
* @returns Issue info object, or null if not found or gh not available
|
|
44
|
+
*/
|
|
45
|
+
export function getLinkedIssue(projectRoot, branchName, prInfo) {
|
|
46
|
+
try {
|
|
47
|
+
// Use provided PR info or fetch it
|
|
48
|
+
const pr = prInfo !== undefined ? prInfo : getPrForBranch(projectRoot, branchName);
|
|
49
|
+
if (!pr) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
// Get the PR body to look for issue references
|
|
53
|
+
const prBody = execFileSync('gh', ['pr', 'view', String(pr.number), '--json', 'body'], {
|
|
54
|
+
cwd: projectRoot,
|
|
55
|
+
encoding: 'utf-8',
|
|
56
|
+
timeout: 10_000,
|
|
57
|
+
}).trim();
|
|
58
|
+
const prData = JSON.parse(prBody);
|
|
59
|
+
const body = prData.body || '';
|
|
60
|
+
// Look for issue references in PR body (e.g., "Closes #123", "Fixes #456")
|
|
61
|
+
const issueMatch = body.match(/(?:closes|fixes|resolves)\s+#(\d+)/i);
|
|
62
|
+
if (!issueMatch) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const issueNumber = parseInt(issueMatch[1], 10);
|
|
66
|
+
// Fetch the issue details
|
|
67
|
+
const issueOutput = execFileSync('gh', ['issue', 'view', String(issueNumber), '--json', 'number,url,state,title'], {
|
|
68
|
+
cwd: projectRoot,
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
timeout: 10_000,
|
|
71
|
+
}).trim();
|
|
72
|
+
const issue = JSON.parse(issueOutput);
|
|
73
|
+
return {
|
|
74
|
+
number: issue.number,
|
|
75
|
+
url: issue.url,
|
|
76
|
+
state: issue.state,
|
|
77
|
+
title: issue.title,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger.warn(`getLinkedIssue failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary File Writer
|
|
3
|
+
* Persists enhanced run summaries to JSON files for later retrieval
|
|
4
|
+
*/
|
|
5
|
+
import type { RunSummary } from '../tui/screens/RunScreen.js';
|
|
6
|
+
/**
|
|
7
|
+
* Write enhanced run summary to a JSON file in the temp directory
|
|
8
|
+
*
|
|
9
|
+
* @param featureName - The feature name used to construct the file path
|
|
10
|
+
* @param summary - The complete RunSummary object to persist
|
|
11
|
+
* @returns Promise that resolves when the file is written, or rejects on error
|
|
12
|
+
*
|
|
13
|
+
* Uses RALPH_SUMMARY_TMP_DIR environment variable if set, otherwise os.tmpdir().
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* await writeRunSummaryFile('my-feature', {
|
|
18
|
+
* feature: 'my-feature',
|
|
19
|
+
* exitCode: 0,
|
|
20
|
+
* // ... other RunSummary fields
|
|
21
|
+
* });
|
|
22
|
+
* // Writes to: <tmpdir>/ralph-loop-my-feature.summary.json
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function writeRunSummaryFile(featureName: string, summary: RunSummary): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary File Writer
|
|
3
|
+
* Persists enhanced run summaries to JSON files for later retrieval
|
|
4
|
+
*/
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Write enhanced run summary to a JSON file in the temp directory
|
|
11
|
+
*
|
|
12
|
+
* @param featureName - The feature name used to construct the file path
|
|
13
|
+
* @param summary - The complete RunSummary object to persist
|
|
14
|
+
* @returns Promise that resolves when the file is written, or rejects on error
|
|
15
|
+
*
|
|
16
|
+
* Uses RALPH_SUMMARY_TMP_DIR environment variable if set, otherwise os.tmpdir().
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* await writeRunSummaryFile('my-feature', {
|
|
21
|
+
* feature: 'my-feature',
|
|
22
|
+
* exitCode: 0,
|
|
23
|
+
* // ... other RunSummary fields
|
|
24
|
+
* });
|
|
25
|
+
* // Writes to: <tmpdir>/ralph-loop-my-feature.summary.json
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export async function writeRunSummaryFile(featureName, summary) {
|
|
29
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(featureName)) {
|
|
30
|
+
throw new Error(`Invalid feature name: "${featureName}"`);
|
|
31
|
+
}
|
|
32
|
+
const dir = process.env.RALPH_SUMMARY_TMP_DIR ?? tmpdir();
|
|
33
|
+
const filePath = join(dir, `ralph-loop-${featureName}.summary.json`);
|
|
34
|
+
const jsonContent = JSON.stringify(summary, null, 2);
|
|
35
|
+
await writeFile(filePath, jsonContent, 'utf8');
|
|
36
|
+
logger.debug(`Summary written to ${filePath}`);
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -105,6 +105,8 @@ TOKENS_FILE="/tmp/ralph-loop-${1}.tokens"
|
|
|
105
105
|
CLAUDE_OUTPUT="/tmp/ralph-loop-${1}.output"
|
|
106
106
|
STATUS_FILE="/tmp/ralph-loop-${1}.status"
|
|
107
107
|
FINAL_STATUS_FILE="/tmp/ralph-loop-${1}.final"
|
|
108
|
+
PHASES_FILE="/tmp/ralph-loop-${1}.phases"
|
|
109
|
+
BASELINE_FILE="/tmp/ralph-loop-${1}.baseline"
|
|
108
110
|
|
|
109
111
|
# Initialize token tracking
|
|
110
112
|
init_tokens() {
|
|
@@ -139,6 +141,33 @@ parse_and_accumulate_tokens() {
|
|
|
139
141
|
# Initialize tokens
|
|
140
142
|
init_tokens
|
|
141
143
|
|
|
144
|
+
# Phase tracking helpers
|
|
145
|
+
write_phase_start() {
|
|
146
|
+
local phase_id="$1"
|
|
147
|
+
local timestamp=$(date +%s)
|
|
148
|
+
echo "${phase_id}|started|${timestamp}|" >> "$PHASES_FILE"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
write_phase_end() {
|
|
152
|
+
local phase_id="$1"
|
|
153
|
+
local status="$2" # success, failed, skipped
|
|
154
|
+
local timestamp=$(date +%s)
|
|
155
|
+
|
|
156
|
+
# Find the last line for this phase and update it
|
|
157
|
+
if [ -f "$PHASES_FILE" ]; then
|
|
158
|
+
# Get all lines except the last matching phase line
|
|
159
|
+
grep -v "^${phase_id}|started|" "$PHASES_FILE" > "${PHASES_FILE}.tmp" 2>/dev/null || true
|
|
160
|
+
# Find the start timestamp from the original file
|
|
161
|
+
local start_ts=$(grep "^${phase_id}|started|" "$PHASES_FILE" | tail -1 | cut -d'|' -f3)
|
|
162
|
+
# Write updated line
|
|
163
|
+
echo "${phase_id}|${status}|${start_ts}|${timestamp}" >> "${PHASES_FILE}.tmp"
|
|
164
|
+
mv "${PHASES_FILE}.tmp" "$PHASES_FILE"
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Initialize phase tracking
|
|
169
|
+
> "$PHASES_FILE"
|
|
170
|
+
|
|
142
171
|
FEATURE="${1:?Usage: ./feature-loop.sh <feature-name> [max-iterations] [max-e2e-attempts] [--worktree] [--resume] [--model MODEL]}"
|
|
143
172
|
MAX_ITERATIONS="${2:-$DEFAULT_MAX_ITERATIONS}"
|
|
144
173
|
MAX_E2E_ATTEMPTS="${3:-$DEFAULT_MAX_E2E}"
|
|
@@ -194,22 +223,42 @@ fi
|
|
|
194
223
|
# Create output file for monitoring
|
|
195
224
|
touch "$CLAUDE_OUTPUT"
|
|
196
225
|
|
|
226
|
+
# Record baseline commit
|
|
227
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
228
|
+
BASELINE_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
229
|
+
if [ -n "$BASELINE_COMMIT" ]; then
|
|
230
|
+
echo "$BASELINE_COMMIT" > "$BASELINE_FILE"
|
|
231
|
+
echo "Baseline commit: $BASELINE_COMMIT"
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
|
|
197
235
|
# Phase 3: Planning (if no implementation plan exists)
|
|
198
236
|
if [ ! -f "$PLAN_FILE" ]; then
|
|
199
237
|
echo "======================== PLANNING PHASE ========================"
|
|
238
|
+
write_phase_start "planning"
|
|
200
239
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
201
240
|
cat "$PROMPTS_DIR/PROMPT_feature.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || {
|
|
202
241
|
echo "ERROR: Planning phase failed"
|
|
242
|
+
write_phase_end "planning" "failed"
|
|
203
243
|
exit 1
|
|
204
244
|
}
|
|
205
245
|
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
246
|
+
write_phase_end "planning" "success"
|
|
247
|
+
else
|
|
248
|
+
echo "Plan file exists, skipping planning phase"
|
|
249
|
+
write_phase_start "planning"
|
|
250
|
+
write_phase_end "planning" "skipped"
|
|
206
251
|
fi
|
|
207
252
|
|
|
208
253
|
# Phase 4: Implementation loop
|
|
209
254
|
echo "======================== IMPLEMENTATION PHASE ========================"
|
|
255
|
+
write_phase_start "implementation"
|
|
256
|
+
IMPL_SUCCESS=true
|
|
210
257
|
while true; do
|
|
211
258
|
if [ $ITERATION -ge $MAX_ITERATIONS ]; then
|
|
212
259
|
echo "Reached max iterations: $MAX_ITERATIONS"
|
|
260
|
+
IMPL_SUCCESS=false
|
|
261
|
+
write_phase_end "implementation" "failed"
|
|
213
262
|
exit 1
|
|
214
263
|
fi
|
|
215
264
|
|
|
@@ -231,13 +280,20 @@ while true; do
|
|
|
231
280
|
|
|
232
281
|
sleep 2
|
|
233
282
|
done
|
|
283
|
+
if [ "$IMPL_SUCCESS" = true ]; then
|
|
284
|
+
write_phase_end "implementation" "success"
|
|
285
|
+
fi
|
|
234
286
|
|
|
235
287
|
# Phase 5: E2E Testing
|
|
236
288
|
echo "======================== E2E TESTING PHASE ========================"
|
|
237
289
|
E2E_TOTAL=$({ grep "^- \[.\].*E2E:" "$PLAN_FILE" 2>/dev/null || true; } | wc -l | tr -d ' ')
|
|
238
290
|
if [ "$E2E_TOTAL" -eq 0 ]; then
|
|
239
291
|
echo "No E2E scenarios defined, skipping E2E phase."
|
|
292
|
+
write_phase_start "e2e_testing"
|
|
293
|
+
write_phase_end "e2e_testing" "skipped"
|
|
240
294
|
else
|
|
295
|
+
write_phase_start "e2e_testing"
|
|
296
|
+
E2E_SUCCESS=false
|
|
241
297
|
E2E_ATTEMPT=0
|
|
242
298
|
while [ $E2E_ATTEMPT -lt $MAX_E2E_ATTEMPTS ]; do
|
|
243
299
|
E2E_ATTEMPT=$((E2E_ATTEMPT + 1))
|
|
@@ -253,6 +309,7 @@ else
|
|
|
253
309
|
|
|
254
310
|
if [ "$E2E_FAILED" -eq 0 ] && [ "$E2E_PENDING" -eq 0 ]; then
|
|
255
311
|
echo "All E2E tests passed!"
|
|
312
|
+
E2E_SUCCESS=true
|
|
256
313
|
break
|
|
257
314
|
fi
|
|
258
315
|
|
|
@@ -262,26 +319,46 @@ else
|
|
|
262
319
|
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
263
320
|
fi
|
|
264
321
|
done
|
|
322
|
+
|
|
323
|
+
if [ "$E2E_SUCCESS" = true ]; then
|
|
324
|
+
write_phase_end "e2e_testing" "success"
|
|
325
|
+
else
|
|
326
|
+
write_phase_end "e2e_testing" "failed"
|
|
327
|
+
fi
|
|
265
328
|
fi
|
|
266
329
|
|
|
267
330
|
# Phase 6: Spec Verification
|
|
268
331
|
echo "======================== SPEC VERIFICATION PHASE ========================"
|
|
332
|
+
write_phase_start "verification"
|
|
269
333
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
270
|
-
|
|
334
|
+
VERIFY_STATUS="success"
|
|
335
|
+
if ! cat "$PROMPTS_DIR/PROMPT_verify.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
|
|
336
|
+
VERIFY_STATUS="failed"
|
|
337
|
+
fi
|
|
271
338
|
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
339
|
+
write_phase_end "verification" "$VERIFY_STATUS"
|
|
272
340
|
|
|
273
341
|
# Phase 7: PR and Review
|
|
274
342
|
echo "======================== PR & REVIEW PHASE ========================"
|
|
343
|
+
write_phase_start "pr_review"
|
|
275
344
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
345
|
+
PR_STATUS="success"
|
|
276
346
|
if [ "$REVIEW_MODE" = "manual" ]; then
|
|
277
|
-
cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"
|
|
347
|
+
if ! cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
|
|
348
|
+
PR_STATUS="failed"
|
|
349
|
+
fi
|
|
278
350
|
else
|
|
279
|
-
cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"
|
|
351
|
+
if ! cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
|
|
352
|
+
PR_STATUS="failed"
|
|
353
|
+
fi
|
|
280
354
|
fi
|
|
281
355
|
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
356
|
+
write_phase_end "pr_review" "$PR_STATUS"
|
|
282
357
|
|
|
283
358
|
# Persist final status for TUI summaries
|
|
284
|
-
echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"
|
|
359
|
+
if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"; then
|
|
360
|
+
echo "WARNING: Failed to write final status file: $FINAL_STATUS_FILE" >&2
|
|
361
|
+
fi
|
|
285
362
|
|
|
286
363
|
# Cleanup temp files
|
|
287
364
|
rm -f "$STATUS_FILE" 2>/dev/null || true
|