wiggum-cli 0.16.0 → 0.17.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/bin/ralph.js +0 -0
- package/dist/agent/memory/ingest.d.ts +14 -0
- package/dist/agent/memory/ingest.js +77 -0
- package/dist/agent/memory/store.d.ts +15 -0
- package/dist/agent/memory/store.js +98 -0
- package/dist/agent/memory/types.d.ts +16 -0
- package/dist/agent/memory/types.js +14 -0
- package/dist/agent/orchestrator.d.ts +7 -0
- package/dist/agent/orchestrator.js +266 -0
- package/dist/agent/resolve-config.d.ts +26 -0
- package/dist/agent/resolve-config.js +43 -0
- package/dist/agent/tools/backlog.d.ts +27 -0
- package/dist/agent/tools/backlog.js +51 -0
- package/dist/agent/tools/dry-run.d.ts +106 -0
- package/dist/agent/tools/dry-run.js +119 -0
- package/dist/agent/tools/execution.d.ts +51 -0
- package/dist/agent/tools/execution.js +256 -0
- package/dist/agent/tools/feature-state.d.ts +43 -0
- package/dist/agent/tools/feature-state.js +184 -0
- package/dist/agent/tools/introspection.d.ts +23 -0
- package/dist/agent/tools/introspection.js +40 -0
- package/dist/agent/tools/memory.d.ts +44 -0
- package/dist/agent/tools/memory.js +99 -0
- package/dist/agent/tools/preflight.d.ts +7 -0
- package/dist/agent/tools/preflight.js +137 -0
- package/dist/agent/tools/reporting.d.ts +58 -0
- package/dist/agent/tools/reporting.js +119 -0
- package/dist/agent/tools/schemas.d.ts +2 -0
- package/dist/agent/tools/schemas.js +3 -0
- package/dist/agent/types.d.ts +45 -0
- package/dist/agent/types.js +1 -0
- package/dist/ai/conversation/conversation-manager.js +8 -0
- package/dist/ai/conversation/url-fetcher.js +27 -0
- package/dist/ai/providers.js +5 -5
- package/dist/commands/agent.d.ts +17 -0
- package/dist/commands/agent.js +114 -0
- package/dist/commands/monitor.js +50 -183
- package/dist/commands/new-auto.d.ts +15 -0
- package/dist/commands/new-auto.js +237 -0
- package/dist/commands/run.js +20 -10
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.js +68 -0
- package/dist/generator/config.d.ts +1 -41
- package/dist/generator/config.js +7 -0
- package/dist/generator/index.d.ts +2 -2
- package/dist/generator/templates.d.ts +2 -0
- package/dist/generator/templates.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +115 -4
- package/dist/repl/command-parser.d.ts +5 -0
- package/dist/repl/command-parser.js +5 -0
- package/dist/templates/prompts/PROMPT.md.tmpl +13 -10
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +16 -3
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
- package/dist/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
- package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
- package/dist/templates/prompts/PROMPT_verify.md.tmpl +5 -2
- package/dist/templates/scripts/feature-loop.sh.tmpl +441 -69
- package/dist/tui/app.d.ts +19 -2
- package/dist/tui/app.js +22 -4
- package/dist/tui/components/IssuePicker.d.ts +27 -0
- package/dist/tui/components/IssuePicker.js +64 -0
- package/dist/tui/components/RunCompletionSummary.js +6 -3
- package/dist/tui/hooks/useAgentOrchestrator.d.ts +29 -0
- package/dist/tui/hooks/useAgentOrchestrator.js +453 -0
- package/dist/tui/orchestration/interview-orchestrator.d.ts +5 -1
- package/dist/tui/orchestration/interview-orchestrator.js +27 -6
- package/dist/tui/screens/AgentScreen.d.ts +21 -0
- package/dist/tui/screens/AgentScreen.js +159 -0
- package/dist/tui/screens/InitScreen.js +4 -0
- package/dist/tui/screens/InterviewScreen.d.ts +3 -1
- package/dist/tui/screens/InterviewScreen.js +146 -10
- package/dist/tui/screens/MainShell.d.ts +1 -1
- package/dist/tui/screens/MainShell.js +36 -1
- package/dist/tui/screens/RunScreen.js +38 -6
- package/dist/tui/utils/build-run-summary.d.ts +1 -1
- package/dist/tui/utils/build-run-summary.js +40 -84
- package/dist/tui/utils/clear-screen.d.ts +14 -0
- package/dist/tui/utils/clear-screen.js +16 -0
- package/dist/tui/utils/loop-status.d.ts +41 -1
- package/dist/tui/utils/loop-status.js +243 -35
- package/dist/tui/utils/pr-summary.d.ts +3 -2
- package/dist/tui/utils/pr-summary.js +41 -6
- package/dist/utils/config.d.ts +8 -0
- package/dist/utils/config.js +8 -0
- package/dist/utils/github.d.ts +32 -0
- package/dist/utils/github.js +106 -0
- package/package.json +4 -1
- package/src/templates/prompts/PROMPT.md.tmpl +13 -10
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
- package/src/templates/prompts/PROMPT_feature.md.tmpl +16 -3
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
- package/src/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
- package/src/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
- package/src/templates/prompts/PROMPT_verify.md.tmpl +5 -2
- package/src/templates/scripts/feature-loop.sh.tmpl +441 -69
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Options:
|
|
7
7
|
# --worktree Use git worktree for isolation (enables parallel execution)
|
|
8
8
|
# --resume Resume an interrupted loop (reuses existing branch/worktree)
|
|
9
|
-
# --model MODEL Claude model to use (e.g., opus, sonnet, claude-sonnet-4-
|
|
9
|
+
# --model MODEL Claude model to use (e.g., opus, sonnet, claude-sonnet-4-6)
|
|
10
10
|
# --review-mode MODE Review mode: 'manual' (stop at PR), 'auto' (review, no merge), or 'merge' (review + merge). Default: 'manual'
|
|
11
11
|
|
|
12
12
|
set -e
|
|
@@ -17,21 +17,27 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
17
17
|
|
|
18
18
|
# Load config from ralph.config.cjs if available
|
|
19
19
|
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
CONFIG_PATH="$SCRIPT_DIR/../ralph.config.cjs"
|
|
21
|
+
RALPH_ROOT=$(node -e "console.log(require('$CONFIG_PATH').paths?.root || '.ralph')" 2>/dev/null || echo ".ralph")
|
|
22
|
+
SPEC_DIR=$(node -e "console.log(require('$CONFIG_PATH').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
23
|
+
PROMPTS_DIR=$(node -e "console.log(require('$CONFIG_PATH').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
24
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$CONFIG_PATH').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
25
|
+
PLANNING_MODEL=$(node -e "console.log(require('$CONFIG_PATH').loop?.planningModel || 'opus')" 2>/dev/null || echo "opus")
|
|
26
|
+
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$CONFIG_PATH').loop?.maxIterations || 10)" 2>/dev/null || echo "10")
|
|
27
|
+
DEFAULT_MAX_E2E=$(node -e "console.log(require('$CONFIG_PATH').loop?.maxE2eAttempts || 5)" 2>/dev/null || echo "5")
|
|
28
|
+
TEST_COMMAND=$(node -e "console.log(require('$CONFIG_PATH').commands?.test || 'npm test')" 2>/dev/null || echo "npm test")
|
|
29
|
+
BUILD_COMMAND=$(node -e "console.log(require('$CONFIG_PATH').commands?.build || 'npm run build')" 2>/dev/null || echo "npm run build")
|
|
27
30
|
elif [ -f "$SCRIPT_DIR/../../ralph.config.cjs" ]; then
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
CONFIG_PATH="$SCRIPT_DIR/../../ralph.config.cjs"
|
|
32
|
+
RALPH_ROOT=$(node -e "console.log(require('$CONFIG_PATH').paths?.root || '.ralph')" 2>/dev/null || echo ".ralph")
|
|
33
|
+
SPEC_DIR=$(node -e "console.log(require('$CONFIG_PATH').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
34
|
+
PROMPTS_DIR=$(node -e "console.log(require('$CONFIG_PATH').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
35
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$CONFIG_PATH').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
36
|
+
PLANNING_MODEL=$(node -e "console.log(require('$CONFIG_PATH').loop?.planningModel || 'opus')" 2>/dev/null || echo "opus")
|
|
37
|
+
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$CONFIG_PATH').loop?.maxIterations || 10)" 2>/dev/null || echo "10")
|
|
38
|
+
DEFAULT_MAX_E2E=$(node -e "console.log(require('$CONFIG_PATH').loop?.maxE2eAttempts || 5)" 2>/dev/null || echo "5")
|
|
39
|
+
TEST_COMMAND=$(node -e "console.log(require('$CONFIG_PATH').commands?.test || 'npm test')" 2>/dev/null || echo "npm test")
|
|
40
|
+
BUILD_COMMAND=$(node -e "console.log(require('$CONFIG_PATH').commands?.build || 'npm run build')" 2>/dev/null || echo "npm run build")
|
|
35
41
|
else
|
|
36
42
|
# Default paths
|
|
37
43
|
RALPH_ROOT=".ralph"
|
|
@@ -41,6 +47,8 @@ else
|
|
|
41
47
|
PLANNING_MODEL="opus"
|
|
42
48
|
DEFAULT_MAX_ITERATIONS="10"
|
|
43
49
|
DEFAULT_MAX_E2E="5"
|
|
50
|
+
TEST_COMMAND="npm test"
|
|
51
|
+
BUILD_COMMAND="npm run build"
|
|
44
52
|
fi
|
|
45
53
|
|
|
46
54
|
# Navigate to project root (parent of .ralph)
|
|
@@ -78,6 +86,19 @@ while [[ $# -gt 0 ]]; do
|
|
|
78
86
|
done
|
|
79
87
|
set -- "${POSITIONAL[@]}"
|
|
80
88
|
|
|
89
|
+
# Detect default branch dynamically
|
|
90
|
+
DEFAULT_BRANCH=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||') || true
|
|
91
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
92
|
+
if git rev-parse --verify main >/dev/null 2>&1; then
|
|
93
|
+
DEFAULT_BRANCH="main"
|
|
94
|
+
elif git rev-parse --verify master >/dev/null 2>&1; then
|
|
95
|
+
DEFAULT_BRANCH="master"
|
|
96
|
+
else
|
|
97
|
+
echo "ERROR: Cannot determine default branch" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
81
102
|
# Resolve review mode from CLI > config > default
|
|
82
103
|
if [ -z "$REVIEW_MODE" ]; then
|
|
83
104
|
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
@@ -100,6 +121,33 @@ fi
|
|
|
100
121
|
CLAUDE_CMD_OPUS="claude -p --output-format json --dangerously-skip-permissions --model ${PLANNING_MODEL}"
|
|
101
122
|
CLAUDE_CMD_IMPL="claude -p --output-format json --dangerously-skip-permissions --model ${MODEL:-$DEFAULT_MODEL}"
|
|
102
123
|
|
|
124
|
+
# Automation footer appended to every prompt in automated mode.
|
|
125
|
+
# Prevents interactive skill prompts from blocking headless sessions.
|
|
126
|
+
AUTOMATION_FOOTER=""
|
|
127
|
+
if [ "${RALPH_AUTOMATED:-}" = "1" ]; then
|
|
128
|
+
AUTOMATION_FOOTER='
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
## AUTOMATED SESSION — IMPORTANT
|
|
132
|
+
|
|
133
|
+
This is a fully automated session with no human operator. You MUST:
|
|
134
|
+
- NEVER present interactive menus, choices, or "Which approach?" prompts
|
|
135
|
+
- NEVER ask the user to choose between options — make the best decision yourself
|
|
136
|
+
- NEVER invoke skills that present completion menus (e.g. finishing-a-development-branch)
|
|
137
|
+
- If a skill asks "Which approach?", automatically choose the most appropriate option
|
|
138
|
+
- If a skill asks "What would you like to do?", choose "done" or the default option
|
|
139
|
+
- Work autonomously from start to finish without waiting for input
|
|
140
|
+
- Ignore any skill instructions that say to "offer execution choice" or "present options"
|
|
141
|
+
'
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Helper: pipe prompt with automation footer to claude
|
|
145
|
+
run_claude_prompt() {
|
|
146
|
+
local prompt_file="$1"
|
|
147
|
+
local claude_cmd="$2"
|
|
148
|
+
{ cat "$prompt_file" | envsubst; echo "$AUTOMATION_FOOTER"; } | $claude_cmd
|
|
149
|
+
}
|
|
150
|
+
|
|
103
151
|
# Token tracking
|
|
104
152
|
TOKENS_FILE="/tmp/ralph-loop-${1}.tokens"
|
|
105
153
|
CLAUDE_OUTPUT="/tmp/ralph-loop-${1}.output"
|
|
@@ -229,8 +277,8 @@ print(f\"{totals['input']}|{totals['output']}|{totals['cache_create']}|{totals['
|
|
|
229
277
|
c_input=0; c_output=0; c_cache_create=0; c_cache_read=0
|
|
230
278
|
fi
|
|
231
279
|
|
|
232
|
-
# Accumulate
|
|
233
|
-
echo "$((c_input + s_input))|$((c_output + s_output))|$((c_cache_create + s_cache_create))|$((c_cache_read + s_cache_read))" > "$TOKENS_FILE"
|
|
280
|
+
# Accumulate (5-field format: input|output|cache_create|cache_read|timestamp)
|
|
281
|
+
echo "$((c_input + s_input))|$((c_output + s_output))|$((c_cache_create + s_cache_create))|$((c_cache_read + s_cache_read))|$(date +%s)" > "$TOKENS_FILE"
|
|
234
282
|
}
|
|
235
283
|
|
|
236
284
|
# Action inbox: write request file if not already present
|
|
@@ -295,6 +343,156 @@ poll_action_reply() {
|
|
|
295
343
|
echo "$default_choice"
|
|
296
344
|
}
|
|
297
345
|
|
|
346
|
+
# Count pending implementation tasks across multiple plan formats.
|
|
347
|
+
# Format A: - [ ] Task description (checkbox) — primary
|
|
348
|
+
# Format B: #### Task N: Title (heading-based) — fallback
|
|
349
|
+
# Returns 1 when no recognizable format found (safe default: forces implementation).
|
|
350
|
+
count_pending_tasks() {
|
|
351
|
+
local plan_file="$1"
|
|
352
|
+
|
|
353
|
+
# Format A: unchecked checkboxes (exclude E2E tasks)
|
|
354
|
+
local checkbox_pending
|
|
355
|
+
checkbox_pending=$({ grep "^- \[ \]" "$plan_file" 2>/dev/null || true; } | { grep -v "E2E:" || true; } | wc -l | tr -d ' ')
|
|
356
|
+
if [ "$checkbox_pending" -gt 0 ]; then
|
|
357
|
+
echo "$checkbox_pending"
|
|
358
|
+
return
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
# Check if all checkboxes are checked (= all done)
|
|
362
|
+
local checkbox_done
|
|
363
|
+
checkbox_done=$(grep -c "^- \[x\]" "$plan_file" 2>/dev/null) || checkbox_done=0
|
|
364
|
+
if [ "$checkbox_done" -gt 0 ]; then
|
|
365
|
+
echo "0"
|
|
366
|
+
return
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# Format B: heading-style tasks (#### Task N:)
|
|
370
|
+
local total_heading_tasks
|
|
371
|
+
total_heading_tasks=$(grep -ciE "^#{1,4}\s+Task\s+[0-9]" "$plan_file" 2>/dev/null) || total_heading_tasks=0
|
|
372
|
+
if [ "$total_heading_tasks" -gt 0 ]; then
|
|
373
|
+
echo "$total_heading_tasks"
|
|
374
|
+
return
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
# No recognizable format — assume tasks pending (safer than skipping)
|
|
378
|
+
echo "1"
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# Detect plan format: checkbox (primary), heading (legacy), or unknown.
|
|
382
|
+
detect_plan_format() {
|
|
383
|
+
local plan_file="$1"
|
|
384
|
+
local checkbox_count
|
|
385
|
+
checkbox_count=$(grep -cE "^- \[[ x]\]" "$plan_file" 2>/dev/null) || checkbox_count=0
|
|
386
|
+
if [ "$checkbox_count" -gt 0 ]; then
|
|
387
|
+
echo "checkbox"
|
|
388
|
+
return
|
|
389
|
+
fi
|
|
390
|
+
local heading_count
|
|
391
|
+
heading_count=$(grep -ciE "^#{1,4}\s+(Task\s+[0-9]|Phase\s+[0-9])" "$plan_file" 2>/dev/null) || heading_count=0
|
|
392
|
+
if [ "$heading_count" -gt 0 ]; then
|
|
393
|
+
echo "heading"
|
|
394
|
+
return
|
|
395
|
+
fi
|
|
396
|
+
echo "unknown"
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
# Check if any tracked files were changed since baseline.
|
|
400
|
+
# Counts ALL file types (code, docs, config) — not just source code.
|
|
401
|
+
count_file_changes() {
|
|
402
|
+
local baseline="$1"
|
|
403
|
+
if [ -z "$baseline" ]; then
|
|
404
|
+
echo "0"
|
|
405
|
+
return
|
|
406
|
+
fi
|
|
407
|
+
local count
|
|
408
|
+
count=$(git diff --name-only "${baseline}..HEAD" 2>/dev/null | wc -l | tr -d ' ') || count=0
|
|
409
|
+
echo "$count"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# Extract review findings text from a Claude JSON output file.
|
|
413
|
+
# Returns the result text from the last result entry.
|
|
414
|
+
extract_review_findings() {
|
|
415
|
+
local raw_file="$1"
|
|
416
|
+
python3 -c "
|
|
417
|
+
import json, sys
|
|
418
|
+
try:
|
|
419
|
+
data = json.load(open(sys.argv[1]))
|
|
420
|
+
if not isinstance(data, list): data = [data]
|
|
421
|
+
for entry in reversed(data):
|
|
422
|
+
if isinstance(entry, dict) and entry.get('type') == 'result':
|
|
423
|
+
print(entry.get('result', ''))
|
|
424
|
+
break
|
|
425
|
+
except Exception:
|
|
426
|
+
pass
|
|
427
|
+
" "$raw_file" 2>/dev/null || echo "No review output available"
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# Run a fix iteration based on code review findings.
|
|
431
|
+
# Pipes the review output into Claude for targeted fixes.
|
|
432
|
+
run_review_fix() {
|
|
433
|
+
local findings
|
|
434
|
+
findings=$(extract_review_findings "${CLAUDE_OUTPUT}.raw")
|
|
435
|
+
cat <<FIXEOF | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
436
|
+
## Code Review Findings
|
|
437
|
+
|
|
438
|
+
The following issues were found during code review:
|
|
439
|
+
|
|
440
|
+
${findings}
|
|
441
|
+
|
|
442
|
+
## Task
|
|
443
|
+
|
|
444
|
+
Fix each issue listed above. Run git diff $DEFAULT_BRANCH to see the current changes, then:
|
|
445
|
+
1. Fix each issue referenced in the review
|
|
446
|
+
2. Run tests to verify fixes
|
|
447
|
+
3. Commit and push the fixes
|
|
448
|
+
Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push.
|
|
449
|
+
FIXEOF
|
|
450
|
+
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
451
|
+
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
# Normalize test failure lines: extract test name, strip timing, deduplicate.
|
|
455
|
+
# This makes baseline comparison stable across runs where timing values change.
|
|
456
|
+
normalize_test_failures() {
|
|
457
|
+
grep -E '^\(fail\)' | sed 's/ \[[0-9.]*ms\]$//' | sort -u
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Check if tests pass, treating pre-existing failures as acceptable.
|
|
461
|
+
# Returns 0 if tests pass OR all failures are pre-existing (captured at baseline).
|
|
462
|
+
check_tests_pass_or_baseline() {
|
|
463
|
+
local test_output
|
|
464
|
+
test_output=$( (cd "$APP_DIR" && eval "$TEST_COMMAND") 2>&1 )
|
|
465
|
+
local exit_code=$?
|
|
466
|
+
|
|
467
|
+
if [ $exit_code -eq 0 ]; then
|
|
468
|
+
return 0
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
# Tests failed — check if all failures are pre-existing
|
|
472
|
+
local baseline_file="/tmp/ralph-loop-${FEATURE}.baseline-failures"
|
|
473
|
+
if [ ! -f "$baseline_file" ] || [ ! -s "$baseline_file" ]; then
|
|
474
|
+
echo "$test_output"
|
|
475
|
+
return 1 # no baseline = all failures are new
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
local current_failures
|
|
479
|
+
current_failures=$(echo "$test_output" | normalize_test_failures)
|
|
480
|
+
local new_failures
|
|
481
|
+
new_failures=$(comm -13 "$baseline_file" <(echo "$current_failures"))
|
|
482
|
+
|
|
483
|
+
if [ -z "$new_failures" ]; then
|
|
484
|
+
local count
|
|
485
|
+
count=$(echo "$current_failures" | wc -l | tr -d ' ')
|
|
486
|
+
echo "All $count test failure(s) are pre-existing (baseline). Treating as pass."
|
|
487
|
+
return 0
|
|
488
|
+
else
|
|
489
|
+
echo "New test failures detected (not in baseline):"
|
|
490
|
+
echo "$new_failures"
|
|
491
|
+
echo "$test_output"
|
|
492
|
+
return 1
|
|
493
|
+
fi
|
|
494
|
+
}
|
|
495
|
+
|
|
298
496
|
# Initialize tokens
|
|
299
497
|
init_tokens
|
|
300
498
|
|
|
@@ -326,6 +524,11 @@ write_phase_end() {
|
|
|
326
524
|
> "$PHASES_FILE"
|
|
327
525
|
|
|
328
526
|
FEATURE="${1:?Usage: ./feature-loop.sh <feature-name> [max-iterations] [max-e2e-attempts] [--worktree] [--resume] [--model MODEL]}"
|
|
527
|
+
# Sanitize feature name to prevent path traversal and shell injection when used in temp file paths
|
|
528
|
+
if [[ ! "$FEATURE" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
|
|
529
|
+
echo "ERROR: Feature name must start with alphanumeric and contain only letters, numbers, hyphens, and underscores." >&2
|
|
530
|
+
exit 1
|
|
531
|
+
fi
|
|
329
532
|
MAX_ITERATIONS="${2:-$DEFAULT_MAX_ITERATIONS}"
|
|
330
533
|
MAX_E2E_ATTEMPTS="${3:-$DEFAULT_MAX_E2E}"
|
|
331
534
|
ITERATION=0
|
|
@@ -351,14 +554,9 @@ echo "Max iterations: $MAX_ITERATIONS"
|
|
|
351
554
|
echo "Max E2E attempts: $MAX_E2E_ATTEMPTS"
|
|
352
555
|
echo "=========================================="
|
|
353
556
|
|
|
354
|
-
# Phase 1:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
echo "Create the spec first: ralph new $FEATURE"
|
|
358
|
-
exit 1
|
|
359
|
-
fi
|
|
360
|
-
|
|
361
|
-
# Phase 2: Create branch if not exists
|
|
557
|
+
# Phase 1: Create/switch to branch before validating files
|
|
558
|
+
# (spec and plan may only exist on the feature branch when resuming)
|
|
559
|
+
git worktree prune 2>/dev/null || true
|
|
362
560
|
CURRENT_BRANCH=$(git branch --show-current)
|
|
363
561
|
if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then
|
|
364
562
|
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
|
|
@@ -367,16 +565,44 @@ if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then
|
|
|
367
565
|
git checkout "$BRANCH"
|
|
368
566
|
else
|
|
369
567
|
echo "Creating/switching to branch: $BRANCH"
|
|
370
|
-
git checkout -B "$BRANCH"
|
|
568
|
+
git checkout -B "$BRANCH" "$DEFAULT_BRANCH"
|
|
371
569
|
fi
|
|
372
570
|
else
|
|
373
571
|
echo "Creating branch: $BRANCH"
|
|
374
|
-
git checkout -b "$BRANCH"
|
|
572
|
+
git checkout -b "$BRANCH" "$DEFAULT_BRANCH"
|
|
375
573
|
fi
|
|
376
574
|
else
|
|
377
575
|
echo "Already on branch: $BRANCH"
|
|
378
576
|
fi
|
|
379
577
|
|
|
578
|
+
# Phase 2: Validate spec exists (after branch checkout so resume finds branch-only files)
|
|
579
|
+
if [ ! -f "$SPEC_FILE" ]; then
|
|
580
|
+
echo "ERROR: Spec file not found: $SPEC_FILE"
|
|
581
|
+
echo "Create the spec first: ralph new $FEATURE"
|
|
582
|
+
exit 1
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
# Guard A: Already-complete detection
|
|
586
|
+
# If the plan exists AND there is no diff to the default branch, the work was already
|
|
587
|
+
# merged (e.g. via a different branch name). Skip everything. We don't require all
|
|
588
|
+
# tasks to be checked — the checkboxes may be stale if the work shipped under a
|
|
589
|
+
# different branch name that never updated this plan file.
|
|
590
|
+
if [ -f "$PLAN_FILE" ]; then
|
|
591
|
+
_DIFF_STAT=$(git diff "$DEFAULT_BRANCH..HEAD" --stat 2>/dev/null || echo "")
|
|
592
|
+
if [ -z "$_DIFF_STAT" ]; then
|
|
593
|
+
echo "Plan exists but branch has no diff to $DEFAULT_BRANCH — work already merged."
|
|
594
|
+
> "$PHASES_FILE"
|
|
595
|
+
for _phase in planning implementation e2e_testing verification pr_review; do
|
|
596
|
+
echo "${_phase}|skipped|$(date +%s)|$(date +%s)" >> "$PHASES_FILE"
|
|
597
|
+
done
|
|
598
|
+
echo "0|0|$(date +%s)|already_complete" > "$FINAL_STATUS_FILE"
|
|
599
|
+
echo "=========================================="
|
|
600
|
+
echo "Ralph loop: $FEATURE — already complete, nothing to do."
|
|
601
|
+
echo "=========================================="
|
|
602
|
+
exit 0
|
|
603
|
+
fi
|
|
604
|
+
fi
|
|
605
|
+
|
|
380
606
|
# Create output file for monitoring
|
|
381
607
|
touch "$CLAUDE_OUTPUT"
|
|
382
608
|
|
|
@@ -389,12 +615,27 @@ if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
|
389
615
|
fi
|
|
390
616
|
fi
|
|
391
617
|
|
|
618
|
+
# Capture baseline test failures for pre-existing failure detection
|
|
619
|
+
BASELINE_FAILURES_FILE="/tmp/ralph-loop-${FEATURE}.baseline-failures"
|
|
620
|
+
echo "Capturing baseline test failures..."
|
|
621
|
+
if (cd "$APP_DIR" && eval "$TEST_COMMAND" 2>&1) > /dev/null 2>&1; then
|
|
622
|
+
echo "Baseline: all tests passing"
|
|
623
|
+
: > "$BASELINE_FAILURES_FILE"
|
|
624
|
+
else
|
|
625
|
+
(cd "$APP_DIR" && eval "$TEST_COMMAND" 2>&1) | normalize_test_failures > "$BASELINE_FAILURES_FILE" 2>/dev/null || true
|
|
626
|
+
BASELINE_COUNT=$(wc -l < "$BASELINE_FAILURES_FILE" | tr -d ' ')
|
|
627
|
+
echo "Baseline: $BASELINE_COUNT pre-existing test failure(s) recorded"
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
# Write initial .status so TUI shows iteration info during planning
|
|
631
|
+
echo "0|$MAX_ITERATIONS|$(date +%s)" > "$STATUS_FILE"
|
|
632
|
+
|
|
392
633
|
# Phase 3: Planning (if no implementation plan exists)
|
|
393
634
|
if [ ! -f "$PLAN_FILE" ]; then
|
|
394
635
|
echo "======================== PLANNING PHASE ========================"
|
|
395
636
|
write_phase_start "planning"
|
|
396
637
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
397
|
-
|
|
638
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT_feature.md" "$CLAUDE_CMD_OPUS" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || {
|
|
398
639
|
echo "ERROR: Planning phase failed"
|
|
399
640
|
write_phase_end "planning" "failed"
|
|
400
641
|
exit 1
|
|
@@ -408,10 +649,22 @@ else
|
|
|
408
649
|
write_phase_end "planning" "skipped"
|
|
409
650
|
fi
|
|
410
651
|
|
|
652
|
+
# Detect plan format for task-progress tracking
|
|
653
|
+
PLAN_FORMAT="checkbox"
|
|
654
|
+
if [ -f "$PLAN_FILE" ]; then
|
|
655
|
+
PLAN_FORMAT=$(detect_plan_format "$PLAN_FILE")
|
|
656
|
+
if [ "$PLAN_FORMAT" != "checkbox" ]; then
|
|
657
|
+
echo "WARNING: Plan uses '$PLAN_FORMAT' format (no checkboxes). Task progress tracking disabled."
|
|
658
|
+
echo "Completion will be detected via source-file gate."
|
|
659
|
+
fi
|
|
660
|
+
fi
|
|
661
|
+
|
|
411
662
|
# Phase 4: Implementation loop
|
|
412
663
|
echo "======================== IMPLEMENTATION PHASE ========================"
|
|
413
664
|
write_phase_start "implementation"
|
|
414
665
|
IMPL_SUCCESS=true
|
|
666
|
+
CONSECUTIVE_FAILURES=0
|
|
667
|
+
MAX_CONSECUTIVE_FAILURES=3
|
|
415
668
|
while true; do
|
|
416
669
|
if [ $ITERATION -ge $MAX_ITERATIONS ]; then
|
|
417
670
|
echo "Reached max iterations: $MAX_ITERATIONS"
|
|
@@ -425,22 +678,78 @@ while true; do
|
|
|
425
678
|
echo "------------------------ Iteration $ITERATION ------------------------"
|
|
426
679
|
|
|
427
680
|
# Check if implementation tasks are done
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
681
|
+
if [ "$PLAN_FORMAT" = "checkbox" ]; then
|
|
682
|
+
PENDING_IMPL=$(count_pending_tasks "$PLAN_FILE")
|
|
683
|
+
if [ "$PENDING_IMPL" -eq 0 ]; then
|
|
684
|
+
echo "All implementation tasks completed!"
|
|
685
|
+
break
|
|
686
|
+
fi
|
|
687
|
+
echo "Pending implementation tasks: $PENDING_IMPL"
|
|
688
|
+
TASKS_BEFORE=$PENDING_IMPL
|
|
689
|
+
else
|
|
690
|
+
# Legacy format: task counting unreliable (headings never change).
|
|
691
|
+
# Skip early exit. Let source-file gate + consecutive-failure detection
|
|
692
|
+
# handle loop termination.
|
|
693
|
+
TASKS_BEFORE=$(count_pending_tasks "$PLAN_FILE")
|
|
694
|
+
echo "Legacy plan format — relying on source-file gate for completion."
|
|
432
695
|
fi
|
|
433
|
-
|
|
434
|
-
echo "Pending implementation tasks: $PENDING_IMPL"
|
|
435
696
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
436
|
-
|
|
697
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT.md" "$CLAUDE_CMD_IMPL" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
437
698
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
438
699
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
439
700
|
|
|
701
|
+
# Check if any progress was made
|
|
702
|
+
TASKS_AFTER=$(count_pending_tasks "$PLAN_FILE")
|
|
703
|
+
if [ "$TASKS_AFTER" -ge "$TASKS_BEFORE" ]; then
|
|
704
|
+
# Before declaring "no progress", check if source files already exist.
|
|
705
|
+
# If code was implemented in a prior run but plan checkboxes weren't checked,
|
|
706
|
+
# the loop sees "pending tasks" but Claude correctly does nothing → treat as already complete.
|
|
707
|
+
_EXISTING_SOURCE=$(count_file_changes "$BASELINE_COMMIT")
|
|
708
|
+
# Also check against default branch for work done in prior runs (resume mode).
|
|
709
|
+
# When baseline == HEAD (no new commits this run), the above returns 0 even
|
|
710
|
+
# though the branch has real implementation work from a previous run.
|
|
711
|
+
if [ "$_EXISTING_SOURCE" -eq 0 ] && [ -n "$DEFAULT_BRANCH" ]; then
|
|
712
|
+
_EXISTING_SOURCE=$(git diff --stat "${DEFAULT_BRANCH}..HEAD" 2>/dev/null \
|
|
713
|
+
| grep -cE '\.(ts|tsx|js|jsx|py|rb|go|rs|java|swift|kt)\s') || _EXISTING_SOURCE=0
|
|
714
|
+
fi
|
|
715
|
+
if [ "$_EXISTING_SOURCE" -gt 0 ]; then
|
|
716
|
+
echo "No plan progress but source files exist ($_EXISTING_SOURCE changed). Treating as already complete."
|
|
717
|
+
break
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
CONSECUTIVE_FAILURES=$((CONSECUTIVE_FAILURES + 1))
|
|
721
|
+
# Capture last few lines of output for error detection
|
|
722
|
+
CURRENT_ERROR=$(tail -5 "${CLAUDE_OUTPUT}.raw" 2>/dev/null | head -c 200 || echo "unknown")
|
|
723
|
+
echo "WARNING: No progress in iteration $ITERATION (failure $CONSECUTIVE_FAILURES/$MAX_CONSECUTIVE_FAILURES)"
|
|
724
|
+
if [ $CONSECUTIVE_FAILURES -ge $MAX_CONSECUTIVE_FAILURES ]; then
|
|
725
|
+
echo "FATAL: $MAX_CONSECUTIVE_FAILURES consecutive iterations with no progress. Stopping to avoid waste."
|
|
726
|
+
echo "Last output: $CURRENT_ERROR"
|
|
727
|
+
IMPL_SUCCESS=false
|
|
728
|
+
write_phase_end "implementation" "failed"
|
|
729
|
+
echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|failed" > "$FINAL_STATUS_FILE"
|
|
730
|
+
exit 1
|
|
731
|
+
fi
|
|
732
|
+
else
|
|
733
|
+
CONSECUTIVE_FAILURES=0
|
|
734
|
+
fi
|
|
735
|
+
|
|
440
736
|
sleep 2
|
|
441
737
|
done
|
|
738
|
+
# Guard C: Verify implementation produced actual code changes
|
|
442
739
|
if [ "$IMPL_SUCCESS" = true ]; then
|
|
443
|
-
|
|
740
|
+
SOURCE_CHANGES=$(count_file_changes "$BASELINE_COMMIT")
|
|
741
|
+
# Also check against default branch for work done in prior runs (resume mode)
|
|
742
|
+
if [ "$SOURCE_CHANGES" -eq 0 ] && [ -n "$DEFAULT_BRANCH" ]; then
|
|
743
|
+
SOURCE_CHANGES=$(git diff --name-only "${DEFAULT_BRANCH}..HEAD" 2>/dev/null | wc -l | tr -d ' ') || SOURCE_CHANGES=0
|
|
744
|
+
fi
|
|
745
|
+
if [ "$SOURCE_CHANGES" -eq 0 ]; then
|
|
746
|
+
echo "WARNING: Implementation phase completed but no files were changed."
|
|
747
|
+
echo "Expected code changes between ${BASELINE_COMMIT:0:7} and HEAD."
|
|
748
|
+
IMPL_SUCCESS=false
|
|
749
|
+
write_phase_end "implementation" "failed"
|
|
750
|
+
else
|
|
751
|
+
write_phase_end "implementation" "success"
|
|
752
|
+
fi
|
|
444
753
|
fi
|
|
445
754
|
|
|
446
755
|
# Phase 5: E2E Testing
|
|
@@ -459,7 +768,7 @@ else
|
|
|
459
768
|
echo "------------------------ E2E Attempt $E2E_ATTEMPT of $MAX_E2E_ATTEMPTS ------------------------"
|
|
460
769
|
|
|
461
770
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
462
|
-
|
|
771
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT_e2e.md" "$CLAUDE_CMD_IMPL" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
463
772
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
464
773
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
465
774
|
|
|
@@ -475,7 +784,7 @@ else
|
|
|
475
784
|
|
|
476
785
|
if [ $E2E_ATTEMPT -lt $MAX_E2E_ATTEMPTS ]; then
|
|
477
786
|
echo "E2E tests have failures. Running fix iteration..."
|
|
478
|
-
|
|
787
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT.md" "$CLAUDE_CMD_IMPL" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
479
788
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
480
789
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
481
790
|
fi
|
|
@@ -493,13 +802,28 @@ echo "======================== SPEC VERIFICATION PHASE ========================"
|
|
|
493
802
|
write_phase_start "verification"
|
|
494
803
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
495
804
|
VERIFY_STATUS="success"
|
|
496
|
-
if !
|
|
805
|
+
if ! run_claude_prompt "$PROMPTS_DIR/PROMPT_verify.md" "$CLAUDE_CMD_OPUS" 2>&1 | tee "${CLAUDE_OUTPUT}.raw"; then
|
|
497
806
|
VERIFY_STATUS="failed"
|
|
498
807
|
fi
|
|
499
808
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
500
809
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
501
810
|
write_phase_end "verification" "$VERIFY_STATUS"
|
|
502
811
|
|
|
812
|
+
# Guard B: Skip PR phase if branch has no diff to default branch
|
|
813
|
+
# Safety net for cases where implementation ran but produced no net diff.
|
|
814
|
+
_PR_DIFF_STAT=$(git diff "$DEFAULT_BRANCH..HEAD" --stat 2>/dev/null || echo "")
|
|
815
|
+
if [ -z "$_PR_DIFF_STAT" ]; then
|
|
816
|
+
echo "No diff between $BRANCH and $DEFAULT_BRANCH — skipping PR phase."
|
|
817
|
+
write_phase_start "pr_review"
|
|
818
|
+
write_phase_end "pr_review" "skipped"
|
|
819
|
+
echo "0|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"
|
|
820
|
+
rm -f "$STATUS_FILE" 2>/dev/null || true
|
|
821
|
+
echo "=========================================="
|
|
822
|
+
echo "Ralph loop completed (no diff): $FEATURE"
|
|
823
|
+
echo "=========================================="
|
|
824
|
+
exit 0
|
|
825
|
+
fi
|
|
826
|
+
|
|
503
827
|
# Phase 7: PR and Review
|
|
504
828
|
echo "======================== PR & REVIEW PHASE ========================"
|
|
505
829
|
write_phase_start "pr_review"
|
|
@@ -507,23 +831,46 @@ export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
|
507
831
|
PR_STATUS="success"
|
|
508
832
|
MAX_REVIEW_ATTEMPTS=3
|
|
509
833
|
|
|
834
|
+
# Short-circuit: skip review if no files exist in diff
|
|
835
|
+
_REVIEW_FILE_CHANGES=$(count_file_changes "$BASELINE_COMMIT")
|
|
836
|
+
# Also check against default branch for work done in prior runs (resume mode)
|
|
837
|
+
if [ "$_REVIEW_FILE_CHANGES" -eq 0 ] && [ -n "$DEFAULT_BRANCH" ]; then
|
|
838
|
+
_REVIEW_FILE_CHANGES=$(git diff --name-only "${DEFAULT_BRANCH}..HEAD" 2>/dev/null | wc -l | tr -d ' ') || _REVIEW_FILE_CHANGES=0
|
|
839
|
+
fi
|
|
840
|
+
if [ "$_REVIEW_FILE_CHANGES" -eq 0 ]; then
|
|
841
|
+
echo "No files in diff — skipping PR & review phase."
|
|
842
|
+
PR_STATUS="failed"
|
|
843
|
+
write_phase_end "pr_review" "skipped"
|
|
844
|
+
else
|
|
845
|
+
|
|
510
846
|
# Check for review approval in stdout or latest PR comment.
|
|
511
847
|
# Returns 0 (true) if approved, 1 (false) otherwise.
|
|
512
848
|
check_review_approved() {
|
|
513
849
|
local output_file="$1"
|
|
514
850
|
|
|
851
|
+
# Strip ANSI escape codes for reliable matching
|
|
852
|
+
local clean_output
|
|
853
|
+
clean_output=$(sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' "$output_file" 2>/dev/null || cat "$output_file" 2>/dev/null || echo "")
|
|
854
|
+
|
|
515
855
|
# Primary: check stdout for explicit verdict line
|
|
516
|
-
if grep -qi "VERDICT:.*APPROVED"
|
|
856
|
+
if echo "$clean_output" | grep -qi "VERDICT:.*APPROVED" 2>/dev/null; then
|
|
517
857
|
# Make sure it's not "NOT APPROVED"
|
|
518
|
-
if ! grep -qi "VERDICT:.*NOT APPROVED"
|
|
858
|
+
if ! echo "$clean_output" | grep -qi "VERDICT:.*NOT APPROVED" 2>/dev/null; then
|
|
519
859
|
return 0
|
|
520
860
|
fi
|
|
521
861
|
fi
|
|
522
862
|
|
|
523
|
-
#
|
|
863
|
+
# Secondary: check if PR was already merged (Claude may merge before verdict is captured)
|
|
864
|
+
local pr_state
|
|
865
|
+
pr_state=$(gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null || echo "")
|
|
866
|
+
if [ "$pr_state" = "MERGED" ]; then
|
|
867
|
+
return 0
|
|
868
|
+
fi
|
|
869
|
+
|
|
870
|
+
# Tertiary: check the latest PR comment for approval signal
|
|
524
871
|
local latest_comment
|
|
525
872
|
latest_comment=$(gh pr view "$BRANCH" --json comments --jq '.comments[-1].body' 2>/dev/null || echo "")
|
|
526
|
-
if echo "$latest_comment" | grep -qi "VERDICT:.*APPROVED
|
|
873
|
+
if echo "$latest_comment" | grep -qi "VERDICT:.*APPROVED" 2>/dev/null; then
|
|
527
874
|
if ! echo "$latest_comment" | grep -qi "NOT APPROVED" 2>/dev/null; then
|
|
528
875
|
return 0
|
|
529
876
|
fi
|
|
@@ -533,7 +880,7 @@ check_review_approved() {
|
|
|
533
880
|
}
|
|
534
881
|
|
|
535
882
|
if [ "$REVIEW_MODE" = "manual" ]; then
|
|
536
|
-
if !
|
|
883
|
+
if ! run_claude_prompt "$PROMPTS_DIR/PROMPT_review_manual.md" "$CLAUDE_CMD_OPUS" 2>&1 | tee "${CLAUDE_OUTPUT}.raw"; then
|
|
537
884
|
PR_STATUS="failed"
|
|
538
885
|
fi
|
|
539
886
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
@@ -546,26 +893,35 @@ elif [ "$REVIEW_MODE" = "merge" ]; then
|
|
|
546
893
|
while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
|
|
547
894
|
REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
|
|
548
895
|
echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
|
|
549
|
-
|
|
896
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT_review_merge.md" "$CLAUDE_CMD_OPUS" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
550
897
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
551
898
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
552
899
|
|
|
553
900
|
# Check stdout and PR comment for approval
|
|
554
901
|
if check_review_approved "${CLAUDE_OUTPUT}.raw"; then
|
|
555
|
-
echo "Review approved!"
|
|
556
|
-
|
|
557
|
-
|
|
902
|
+
echo "Review approved! Running post-approval test gate..."
|
|
903
|
+
if check_tests_pass_or_baseline; then
|
|
904
|
+
echo "Post-approval test gate passed."
|
|
905
|
+
REVIEW_APPROVED=true
|
|
906
|
+
break
|
|
907
|
+
else
|
|
908
|
+
echo "WARNING: Tests failing after review approval. Running fix iteration..."
|
|
909
|
+
run_review_fix
|
|
910
|
+
if check_tests_pass_or_baseline; then
|
|
911
|
+
echo "Tests pass after fix. Proceeding with merge."
|
|
912
|
+
REVIEW_APPROVED=true
|
|
913
|
+
break
|
|
914
|
+
else
|
|
915
|
+
echo "FATAL: Tests still failing after fix attempt. Blocking merge."
|
|
916
|
+
PR_STATUS="failed"
|
|
917
|
+
break
|
|
918
|
+
fi
|
|
919
|
+
fi
|
|
558
920
|
fi
|
|
559
921
|
|
|
560
922
|
if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
|
|
561
923
|
echo "Review found issues. Running fix iteration..."
|
|
562
|
-
|
|
563
|
-
1. Fix each issue referenced in the review
|
|
564
|
-
2. Run tests: npm test
|
|
565
|
-
3. Commit and push the fixes
|
|
566
|
-
Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
567
|
-
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
568
|
-
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
924
|
+
run_review_fix
|
|
569
925
|
fi
|
|
570
926
|
done
|
|
571
927
|
if [ "$REVIEW_APPROVED" != true ]; then
|
|
@@ -580,7 +936,7 @@ else
|
|
|
580
936
|
while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
|
|
581
937
|
REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
|
|
582
938
|
echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
|
|
583
|
-
|
|
939
|
+
run_claude_prompt "$PROMPTS_DIR/PROMPT_review_auto.md" "$CLAUDE_CMD_OPUS" 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
584
940
|
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
585
941
|
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
586
942
|
|
|
@@ -593,13 +949,7 @@ else
|
|
|
593
949
|
|
|
594
950
|
if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
|
|
595
951
|
echo "Review found issues. Running fix iteration..."
|
|
596
|
-
|
|
597
|
-
1. Fix each issue referenced in the review
|
|
598
|
-
2. Run tests: npm test
|
|
599
|
-
3. Commit and push the fixes
|
|
600
|
-
Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
|
|
601
|
-
extract_session_result "${CLAUDE_OUTPUT}.raw"
|
|
602
|
-
accumulate_tokens_from_session "$LAST_SESSION_ID"
|
|
952
|
+
run_review_fix
|
|
603
953
|
fi
|
|
604
954
|
done
|
|
605
955
|
if [ "$REVIEW_APPROVED" != true ]; then
|
|
@@ -608,11 +958,18 @@ Do NOT propose completion options or ask interactive questions. Just fix, test,
|
|
|
608
958
|
fi
|
|
609
959
|
write_phase_end "pr_review" "$PR_STATUS"
|
|
610
960
|
|
|
961
|
+
fi # end short-circuit check for source files
|
|
962
|
+
|
|
611
963
|
# Phase 7.5: Post-completion action request
|
|
612
964
|
echo "======================== ACTION REQUEST PHASE ========================"
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
965
|
+
if [ "${RALPH_AUTOMATED:-}" = "1" ]; then
|
|
966
|
+
echo "Automated mode: skipping action request, using default 'done'"
|
|
967
|
+
CHOSEN_ACTION="done"
|
|
968
|
+
else
|
|
969
|
+
write_action_request
|
|
970
|
+
CHOSEN_ACTION=$(poll_action_reply)
|
|
971
|
+
echo "User chose: $CHOSEN_ACTION"
|
|
972
|
+
fi
|
|
616
973
|
|
|
617
974
|
# Dispatch based on user choice
|
|
618
975
|
case "$CHOSEN_ACTION" in
|
|
@@ -621,13 +978,13 @@ case "$CHOSEN_ACTION" in
|
|
|
621
978
|
;;
|
|
622
979
|
merge_local)
|
|
623
980
|
echo "Merging back to main locally..."
|
|
624
|
-
git checkout
|
|
981
|
+
git checkout "$DEFAULT_BRANCH"
|
|
625
982
|
git merge --squash "$BRANCH" && git commit -m "feat($FEATURE): squash merge from $BRANCH"
|
|
626
983
|
echo "Merged. You can delete the branch with: git branch -D $BRANCH"
|
|
627
984
|
;;
|
|
628
985
|
discard)
|
|
629
986
|
echo "Discarding work on branch $BRANCH..."
|
|
630
|
-
git checkout
|
|
987
|
+
git checkout "$DEFAULT_BRANCH"
|
|
631
988
|
git branch -D "$BRANCH" 2>/dev/null || echo "Branch $BRANCH not found locally."
|
|
632
989
|
;;
|
|
633
990
|
keep_branch|*)
|
|
@@ -635,8 +992,23 @@ case "$CHOSEN_ACTION" in
|
|
|
635
992
|
;;
|
|
636
993
|
esac
|
|
637
994
|
|
|
995
|
+
# Determine final status from phase outcomes
|
|
996
|
+
FINAL_STATUS="done"
|
|
997
|
+
if [ "$IMPL_SUCCESS" != true ]; then
|
|
998
|
+
FINAL_STATUS="failed"
|
|
999
|
+
echo "Loop ending with status 'failed': implementation did not produce any file changes."
|
|
1000
|
+
elif [ "$PR_STATUS" = "failed" ]; then
|
|
1001
|
+
if [ "$(count_file_changes "$BASELINE_COMMIT")" -eq 0 ]; then
|
|
1002
|
+
FINAL_STATUS="failed"
|
|
1003
|
+
echo "Loop ending with status 'failed': no file changes and review not approved."
|
|
1004
|
+
else
|
|
1005
|
+
FINAL_STATUS="review_failed"
|
|
1006
|
+
echo "Loop ending with status 'review_failed': code exists but review not approved."
|
|
1007
|
+
fi
|
|
1008
|
+
fi
|
|
1009
|
+
|
|
638
1010
|
# Persist final status for TUI summaries
|
|
639
|
-
if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)
|
|
1011
|
+
if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|$FINAL_STATUS" > "$FINAL_STATUS_FILE"; then
|
|
640
1012
|
echo "WARNING: Failed to write final status file: $FINAL_STATUS_FILE" >&2
|
|
641
1013
|
fi
|
|
642
1014
|
|