wiggum-cli 0.14.0 → 0.15.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/README.md +24 -5
- package/dist/ai/providers.js +19 -14
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +2 -2
- package/dist/index.js +7 -1
- package/dist/repl/session-state.d.ts +2 -0
- package/dist/templates/config/ralph.config.cjs.tmpl +1 -1
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +7 -41
- package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +163 -0
- package/dist/templates/scripts/feature-loop-actions.test.ts +92 -0
- package/dist/templates/scripts/feature-loop.sh.tmpl +157 -7
- package/dist/tui/app.js +20 -2
- package/dist/tui/components/ChatInput.d.ts +3 -1
- package/dist/tui/components/ChatInput.js +23 -4
- package/dist/tui/components/CommandDropdown.d.ts +3 -1
- package/dist/tui/components/CommandDropdown.js +10 -7
- package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
- package/dist/tui/components/SpecCompletionSummary.js +26 -9
- package/dist/tui/components/SummaryBox.d.ts +0 -3
- package/dist/tui/components/SummaryBox.js +4 -2
- package/dist/tui/orchestration/interview-orchestrator.js +35 -5
- package/dist/tui/screens/MainShell.js +2 -1
- package/dist/tui/screens/RunScreen.js +81 -12
- package/dist/tui/utils/action-inbox.d.ts +43 -0
- package/dist/tui/utils/action-inbox.js +109 -0
- package/dist/tui/utils/polishGoal.d.ts +37 -0
- package/dist/tui/utils/polishGoal.js +170 -0
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/fuzzy-match.d.ts +5 -0
- package/dist/utils/fuzzy-match.js +16 -0
- package/dist/utils/spec-names.d.ts +6 -0
- package/dist/utils/spec-names.js +23 -0
- package/package.json +9 -4
- package/src/templates/config/ralph.config.cjs.tmpl +1 -1
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +7 -41
- package/src/templates/prompts/PROMPT_review_merge.md.tmpl +163 -0
- package/src/templates/scripts/feature-loop-actions.test.ts +92 -0
- package/src/templates/scripts/feature-loop.sh.tmpl +157 -7
|
@@ -95,7 +95,7 @@ fi
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
**Handle review feedback:**
|
|
98
|
-
- If Claude outputs "APPROVED" ->
|
|
98
|
+
- If Claude outputs "APPROVED" -> Done. The PR is ready for manual merge by the user.
|
|
99
99
|
- If Claude lists issues:
|
|
100
100
|
1. Address each issue with code fixes
|
|
101
101
|
2. Commit: `git -C {{appDir}} add -A && git -C {{appDir}} commit -m "fix($FEATURE): address review feedback"`
|
|
@@ -103,47 +103,14 @@ fi
|
|
|
103
103
|
4. Re-run the Claude review command above
|
|
104
104
|
- Max 3 review iterations before requiring manual intervention
|
|
105
105
|
|
|
106
|
-
### Step 5:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
cd {{appDir}} && git rebase origin/main
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
If rebase has conflicts:
|
|
114
|
-
1. Resolve conflicts in affected files
|
|
115
|
-
2. `git add .` the resolved files
|
|
116
|
-
3. `git rebase --continue`
|
|
117
|
-
4. Re-run tests: `{{testCommand}} && {{buildCommand}}`
|
|
118
|
-
|
|
119
|
-
Push rebased branch:
|
|
120
|
-
```bash
|
|
121
|
-
cd {{appDir}} && git push --force-with-lease origin feat/$FEATURE
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Step 6: Merge PR
|
|
125
|
-
When Claude review is approved and branch is rebased:
|
|
126
|
-
```bash
|
|
127
|
-
cd {{appDir}} && gh pr merge --squash --delete-branch
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Step 7: Post-Merge Cleanup
|
|
131
|
-
1. If using worktree, remove it:
|
|
132
|
-
```bash
|
|
133
|
-
# Only if this feature used a worktree ({{appDir}}-$FEATURE directory exists)
|
|
134
|
-
git -C {{appDir}} worktree remove "../{{appDir}}-$FEATURE" 2>/dev/null || true
|
|
135
|
-
```
|
|
136
|
-
2. Checkout main and pull:
|
|
137
|
-
```bash
|
|
138
|
-
git -C {{appDir}} checkout main && git -C {{appDir}} pull
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Note: Spec status updates are handled in the Spec Verification phase before PR creation.
|
|
106
|
+
### Step 5: Final Summary
|
|
107
|
+
After review is complete (approved or max iterations reached):
|
|
108
|
+
1. Post a summary comment on the PR with the review outcome
|
|
109
|
+
2. Do NOT merge — the user will review and merge manually
|
|
142
110
|
|
|
143
111
|
## Rules
|
|
144
|
-
- Do NOT merge
|
|
145
|
-
- Address ALL review comments before
|
|
146
|
-
- Use squash merge to keep history clean
|
|
112
|
+
- Do NOT merge the PR — auto mode only reviews, the user merges
|
|
113
|
+
- Address ALL review comments before marking as approved
|
|
147
114
|
- If gh CLI fails, check authentication: `gh auth status`
|
|
148
115
|
- Keep review conversation focused and professional
|
|
149
116
|
|
|
@@ -152,7 +119,6 @@ Note: Spec status updates are handled in the Spec Verification phase before PR c
|
|
|
152
119
|
- **gh auth error** -> Run: `gh auth login`
|
|
153
120
|
- **PR already exists** -> Use: `gh pr view` to see status
|
|
154
121
|
- **Claude Code CLI not installed** -> Install: `npm install -g @anthropic-ai/claude-code`
|
|
155
|
-
- **Rebase conflicts** -> Resolve carefully, re-run all tests after
|
|
156
122
|
|
|
157
123
|
## Learning Capture
|
|
158
124
|
If the review revealed patterns worth remembering, append to @.ralph/LEARNINGS.md:
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
Study @.ralph/AGENTS.md for commands and patterns.
|
|
3
|
+
Study @.ralph/specs/$FEATURE.md for feature specification.
|
|
4
|
+
Study @.ralph/specs/$FEATURE-implementation-plan.md for completed tasks.
|
|
5
|
+
|
|
6
|
+
## Learnings
|
|
7
|
+
Read @.ralph/LEARNINGS.md for patterns from previous features.
|
|
8
|
+
Capture any review feedback patterns for future iterations.
|
|
9
|
+
|
|
10
|
+
## Task
|
|
11
|
+
All implementation and E2E tasks are complete. Create PR, review, and merge.
|
|
12
|
+
|
|
13
|
+
### Step 1: Verify Ready State
|
|
14
|
+
1. Check all tasks are complete in implementation plan (no `- [ ]` items)
|
|
15
|
+
2. Verify tests pass: `cd {{appDir}} && {{testCommand}}`
|
|
16
|
+
3. Verify build succeeds: `cd {{appDir}} && {{buildCommand}}`
|
|
17
|
+
|
|
18
|
+
If any fail, fix before proceeding.
|
|
19
|
+
|
|
20
|
+
### Step 2: Check Git Status
|
|
21
|
+
```bash
|
|
22
|
+
cd {{appDir}} && git status
|
|
23
|
+
cd {{appDir}} && git log --oneline -5
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Ensure:
|
|
27
|
+
- On branch `feat/$FEATURE`
|
|
28
|
+
- All changes are committed
|
|
29
|
+
- Branch is pushed to remote
|
|
30
|
+
|
|
31
|
+
If uncommitted changes exist:
|
|
32
|
+
```bash
|
|
33
|
+
git -C {{appDir}} add -A && git -C {{appDir}} commit -m "chore($FEATURE): final cleanup"
|
|
34
|
+
git -C {{appDir}} push origin feat/$FEATURE
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Step 3: Create PR
|
|
38
|
+
Check if PR already exists:
|
|
39
|
+
```bash
|
|
40
|
+
cd {{appDir}} && gh pr list --head feat/$FEATURE
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If no PR exists, create one:
|
|
44
|
+
```bash
|
|
45
|
+
cd {{appDir}} && gh pr create --base main --head feat/$FEATURE \
|
|
46
|
+
--title "feat($FEATURE): [read description from spec]" \
|
|
47
|
+
--body "$(cat <<'EOF'
|
|
48
|
+
## Summary
|
|
49
|
+
[Read from spec Purpose section]
|
|
50
|
+
|
|
51
|
+
## Changes
|
|
52
|
+
[Read from implementation plan - list completed phases]
|
|
53
|
+
|
|
54
|
+
## Testing
|
|
55
|
+
- [x] Unit/integration tests: 97 passing
|
|
56
|
+
- [x] E2E tests: All scenarios passed via Playwright MCP
|
|
57
|
+
- [x] Build succeeds
|
|
58
|
+
|
|
59
|
+
## E2E Test Results
|
|
60
|
+
[Copy from implementation plan Phase 9]
|
|
61
|
+
|
|
62
|
+
Generated with Claude Code
|
|
63
|
+
EOF
|
|
64
|
+
)"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 4: Request Claude Code Review
|
|
68
|
+
|
|
69
|
+
Run automated code review using Claude Code CLI:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Check if Claude Code CLI is installed
|
|
73
|
+
if ! command -v claude &> /dev/null; then
|
|
74
|
+
echo "WARNING: Claude Code CLI not installed. Manual review needed."
|
|
75
|
+
cd {{appDir}} && gh pr comment --body "Manual review requested - Claude Code CLI not available. Install: https://docs.anthropic.com/en/docs/claude-code/overview"
|
|
76
|
+
else
|
|
77
|
+
echo "Running Claude Code review..."
|
|
78
|
+
|
|
79
|
+
cd {{appDir}} && claude -p "You are reviewing a PR for the $FEATURE feature.
|
|
80
|
+
|
|
81
|
+
Review the git diff against main and check:
|
|
82
|
+
- Code quality and patterns consistency
|
|
83
|
+
- Test coverage adequacy
|
|
84
|
+
- Potential bugs or edge cases
|
|
85
|
+
- Security concerns (injection, XSS, etc.)
|
|
86
|
+
- Performance implications
|
|
87
|
+
- Error handling completeness
|
|
88
|
+
|
|
89
|
+
Run: git diff main
|
|
90
|
+
|
|
91
|
+
Respond with:
|
|
92
|
+
- APPROVED if everything looks good
|
|
93
|
+
- Or list specific issues with file:line references that need to be fixed"
|
|
94
|
+
fi
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Handle review feedback:**
|
|
98
|
+
- If Claude outputs "APPROVED" -> Proceed to Step 5 (rebase and merge)
|
|
99
|
+
- If Claude lists issues:
|
|
100
|
+
1. Address each issue with code fixes
|
|
101
|
+
2. Commit: `git -C {{appDir}} add -A && git -C {{appDir}} commit -m "fix($FEATURE): address review feedback"`
|
|
102
|
+
3. Push: `git -C {{appDir}} push origin feat/$FEATURE`
|
|
103
|
+
4. Re-run the Claude review command above
|
|
104
|
+
- Max 3 review iterations before requiring manual intervention
|
|
105
|
+
|
|
106
|
+
### Step 5: Rebase Before Merge (for Parallel Execution)
|
|
107
|
+
Before merging, ensure branch is up-to-date with main:
|
|
108
|
+
```bash
|
|
109
|
+
cd {{appDir}} && git fetch origin main
|
|
110
|
+
cd {{appDir}} && git rebase origin/main
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If rebase has conflicts:
|
|
114
|
+
1. Resolve conflicts in affected files
|
|
115
|
+
2. `git add .` the resolved files
|
|
116
|
+
3. `git rebase --continue`
|
|
117
|
+
4. Re-run tests: `{{testCommand}} && {{buildCommand}}`
|
|
118
|
+
|
|
119
|
+
Push rebased branch:
|
|
120
|
+
```bash
|
|
121
|
+
cd {{appDir}} && git push --force-with-lease origin feat/$FEATURE
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Step 6: Merge PR
|
|
125
|
+
When Claude review is approved and branch is rebased:
|
|
126
|
+
```bash
|
|
127
|
+
cd {{appDir}} && gh pr merge --squash --delete-branch
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Step 7: Post-Merge Cleanup
|
|
131
|
+
1. If using worktree, remove it:
|
|
132
|
+
```bash
|
|
133
|
+
# Only if this feature used a worktree ({{appDir}}-$FEATURE directory exists)
|
|
134
|
+
git -C {{appDir}} worktree remove "../{{appDir}}-$FEATURE" 2>/dev/null || true
|
|
135
|
+
```
|
|
136
|
+
2. Checkout main and pull:
|
|
137
|
+
```bash
|
|
138
|
+
git -C {{appDir}} checkout main && git -C {{appDir}} pull
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Note: Spec status updates are handled in the Spec Verification phase before PR creation.
|
|
142
|
+
|
|
143
|
+
## Rules
|
|
144
|
+
- Do NOT merge without Claude Code approval
|
|
145
|
+
- Address ALL review comments before merging
|
|
146
|
+
- Use squash merge to keep history clean
|
|
147
|
+
- If gh CLI fails, check authentication: `gh auth status`
|
|
148
|
+
- Keep review conversation focused and professional
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
- **gh: command not found** -> Install GitHub CLI: `brew install gh`
|
|
152
|
+
- **gh auth error** -> Run: `gh auth login`
|
|
153
|
+
- **PR already exists** -> Use: `gh pr view` to see status
|
|
154
|
+
- **Claude Code CLI not installed** -> Install: `npm install -g @anthropic-ai/claude-code`
|
|
155
|
+
- **Rebase conflicts** -> Resolve carefully, re-run all tests after
|
|
156
|
+
|
|
157
|
+
## Learning Capture
|
|
158
|
+
If the review revealed patterns worth remembering, append to @.ralph/LEARNINGS.md:
|
|
159
|
+
- Code quality feedback -> Add under "## Anti-Patterns" or "## Patterns"
|
|
160
|
+
- Common review issues -> Add under "## Anti-Patterns"
|
|
161
|
+
- Good practices identified -> Add under "## Patterns"
|
|
162
|
+
|
|
163
|
+
Format: `- [YYYY-MM-DD] [$FEATURE] Brief description`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the action request JSON embedded in feature-loop.sh.tmpl
|
|
3
|
+
*
|
|
4
|
+
* The shell template contains a JSON payload written by write_action_request().
|
|
5
|
+
* These tests validate that the JSON structure matches the ActionRequest schema
|
|
6
|
+
* expected by the TUI and action-inbox helpers.
|
|
7
|
+
*
|
|
8
|
+
* Note: Full bash integration tests are out of scope. This focuses on the
|
|
9
|
+
* JSON schema validation by reading the template file directly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const TEMPLATE_PATH = join(__dirname, 'feature-loop.sh.tmpl');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract the JSON block written by write_action_request() from the template.
|
|
22
|
+
* The JSON lives between `cat > "$action_file" << 'EOF'` and `EOF`.
|
|
23
|
+
*/
|
|
24
|
+
function extractActionRequestJson(): unknown {
|
|
25
|
+
const content = readFileSync(TEMPLATE_PATH, 'utf-8');
|
|
26
|
+
|
|
27
|
+
// Match the heredoc block: cat > ... << 'EOF' ... EOF
|
|
28
|
+
const match = content.match(/cat\s*>\s*"\$action_file"\s*<<\s*'EOF'\s*\n([\s\S]*?)\nEOF/);
|
|
29
|
+
if (!match?.[1]) {
|
|
30
|
+
throw new Error('Could not find action request JSON in feature-loop.sh.tmpl');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return JSON.parse(match[1]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('feature-loop.sh.tmpl — action request JSON schema', () => {
|
|
37
|
+
it('parses the embedded JSON without errors', () => {
|
|
38
|
+
const parsed = extractActionRequestJson();
|
|
39
|
+
expect(parsed).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('has a non-empty string id field', () => {
|
|
43
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
44
|
+
expect(typeof parsed.id).toBe('string');
|
|
45
|
+
expect((parsed.id as string).length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('has a non-empty string prompt field', () => {
|
|
49
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
50
|
+
expect(typeof parsed.prompt).toBe('string');
|
|
51
|
+
expect((parsed.prompt as string).length).toBeGreaterThan(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('has a choices array with at least one entry', () => {
|
|
55
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
56
|
+
expect(Array.isArray(parsed.choices)).toBe(true);
|
|
57
|
+
expect((parsed.choices as unknown[]).length).toBeGreaterThan(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('each choice has a non-empty string id and label', () => {
|
|
61
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
62
|
+
const choices = parsed.choices as Array<Record<string, unknown>>;
|
|
63
|
+
|
|
64
|
+
for (const choice of choices) {
|
|
65
|
+
expect(typeof choice.id).toBe('string');
|
|
66
|
+
expect((choice.id as string).length).toBeGreaterThan(0);
|
|
67
|
+
expect(typeof choice.label).toBe('string');
|
|
68
|
+
expect((choice.label as string).length).toBeGreaterThan(0);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('has a non-empty string default field', () => {
|
|
73
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
74
|
+
expect(typeof parsed.default).toBe('string');
|
|
75
|
+
expect((parsed.default as string).length).toBeGreaterThan(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('default value matches one of the choice ids', () => {
|
|
79
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
80
|
+
const choices = parsed.choices as Array<Record<string, unknown>>;
|
|
81
|
+
const choiceIds = choices.map((c) => c.id as string);
|
|
82
|
+
expect(choiceIds).toContain(parsed.default as string);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('all required ActionRequest fields are present', () => {
|
|
86
|
+
const parsed = extractActionRequestJson() as Record<string, unknown>;
|
|
87
|
+
expect(parsed).toHaveProperty('id');
|
|
88
|
+
expect(parsed).toHaveProperty('prompt');
|
|
89
|
+
expect(parsed).toHaveProperty('choices');
|
|
90
|
+
expect(parsed).toHaveProperty('default');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -6,8 +6,8 @@
|
|
|
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-5-
|
|
10
|
-
# --review-mode MODE Review mode: 'manual' (stop at PR) or '
|
|
9
|
+
# --model MODEL Claude model to use (e.g., opus, sonnet, claude-sonnet-4-5-20250929)
|
|
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
|
|
13
13
|
set -o pipefail
|
|
@@ -91,8 +91,8 @@ if [ -z "$REVIEW_MODE" ]; then
|
|
|
91
91
|
fi
|
|
92
92
|
|
|
93
93
|
# Validate review mode
|
|
94
|
-
if [ "$REVIEW_MODE" != "manual" ] && [ "$REVIEW_MODE" != "auto" ]; then
|
|
95
|
-
echo "ERROR: Invalid review mode: '$REVIEW_MODE'. Allowed values are 'manual' or '
|
|
94
|
+
if [ "$REVIEW_MODE" != "manual" ] && [ "$REVIEW_MODE" != "auto" ] && [ "$REVIEW_MODE" != "merge" ]; then
|
|
95
|
+
echo "ERROR: Invalid review mode: '$REVIEW_MODE'. Allowed values are 'manual', 'auto', or 'merge'." >&2
|
|
96
96
|
exit 1
|
|
97
97
|
fi
|
|
98
98
|
|
|
@@ -138,6 +138,68 @@ parse_and_accumulate_tokens() {
|
|
|
138
138
|
echo "${new_input}|${new_output}" > "$TOKENS_FILE"
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
# Action inbox: write request file if not already present
|
|
142
|
+
write_action_request() {
|
|
143
|
+
local action_file="/tmp/ralph-loop-${FEATURE}.action.json"
|
|
144
|
+
if [ -f "$action_file" ]; then
|
|
145
|
+
echo "WARNING: Action request file already exists, skipping write: $action_file" >&2
|
|
146
|
+
return 0
|
|
147
|
+
fi
|
|
148
|
+
cat > "$action_file" << 'EOF'
|
|
149
|
+
{
|
|
150
|
+
"id": "post_pr_choice",
|
|
151
|
+
"prompt": "Loop complete. What would you like to do?",
|
|
152
|
+
"choices": [
|
|
153
|
+
{"id": "done", "label": "Done — end loop"},
|
|
154
|
+
{"id": "merge_local", "label": "Merge back to main locally"},
|
|
155
|
+
{"id": "keep_branch", "label": "Keep branch as-is"},
|
|
156
|
+
{"id": "discard", "label": "Discard this work"}
|
|
157
|
+
],
|
|
158
|
+
"default": "done"
|
|
159
|
+
}
|
|
160
|
+
EOF
|
|
161
|
+
echo "Action request written: $action_file"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Action inbox: poll for reply file, fallback to default after 15 minutes
|
|
165
|
+
poll_action_reply() {
|
|
166
|
+
local action_file="/tmp/ralph-loop-${FEATURE}.action.json"
|
|
167
|
+
local reply_file="/tmp/ralph-loop-${FEATURE}.action.reply.json"
|
|
168
|
+
local default_choice="keep_branch"
|
|
169
|
+
local timeout=900 # 15 minutes in seconds
|
|
170
|
+
local elapsed=0
|
|
171
|
+
|
|
172
|
+
# Read default from action file if present
|
|
173
|
+
if [ -f "$action_file" ]; then
|
|
174
|
+
local parsed_default
|
|
175
|
+
parsed_default=$(node -e "try { const d=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.parse(d).default||'keep_branch'); } catch(e) { console.log('keep_branch'); }" "$action_file" 2>/dev/null || echo "keep_branch")
|
|
176
|
+
if [ -n "$parsed_default" ]; then
|
|
177
|
+
default_choice="$parsed_default"
|
|
178
|
+
fi
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
while [ $elapsed -lt $timeout ]; do
|
|
182
|
+
if [ -f "$reply_file" ]; then
|
|
183
|
+
local choice
|
|
184
|
+
choice=$(node -e "try { const d=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.parse(d).choice||''); } catch(e) { console.log(''); }" "$reply_file" 2>/dev/null || echo "")
|
|
185
|
+
if [ -n "$choice" ]; then
|
|
186
|
+
echo "User selected: $choice" >&2
|
|
187
|
+
# Cleanup both files
|
|
188
|
+
rm -f "$action_file" "$reply_file" 2>/dev/null || true
|
|
189
|
+
echo "$choice"
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
fi
|
|
193
|
+
sleep 1
|
|
194
|
+
elapsed=$((elapsed + 1))
|
|
195
|
+
done
|
|
196
|
+
|
|
197
|
+
# Timeout: use default
|
|
198
|
+
echo "Action reply timeout after ${timeout}s, using default: $default_choice" >&2
|
|
199
|
+
rm -f "$action_file" "$reply_file" 2>/dev/null || true
|
|
200
|
+
echo "$default_choice"
|
|
201
|
+
}
|
|
202
|
+
|
|
141
203
|
# Initialize tokens
|
|
142
204
|
init_tokens
|
|
143
205
|
|
|
@@ -343,18 +405,106 @@ echo "======================== PR & REVIEW PHASE ========================"
|
|
|
343
405
|
write_phase_start "pr_review"
|
|
344
406
|
export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
|
|
345
407
|
PR_STATUS="success"
|
|
408
|
+
MAX_REVIEW_ATTEMPTS=3
|
|
409
|
+
|
|
346
410
|
if [ "$REVIEW_MODE" = "manual" ]; then
|
|
347
411
|
if ! cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
|
|
348
412
|
PR_STATUS="failed"
|
|
349
413
|
fi
|
|
350
|
-
|
|
351
|
-
|
|
414
|
+
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
415
|
+
|
|
416
|
+
elif [ "$REVIEW_MODE" = "merge" ]; then
|
|
417
|
+
# Merge mode: create PR, iterate review+fixes until approved, then merge
|
|
418
|
+
REVIEW_ATTEMPT=0
|
|
419
|
+
REVIEW_APPROVED=false
|
|
420
|
+
while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
|
|
421
|
+
REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
|
|
422
|
+
echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
|
|
423
|
+
cat "$PROMPTS_DIR/PROMPT_review_merge.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || true
|
|
424
|
+
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
425
|
+
|
|
426
|
+
# Check if output contains APPROVED
|
|
427
|
+
if grep -qi "APPROVED" "$CLAUDE_OUTPUT" 2>/dev/null; then
|
|
428
|
+
echo "Review approved!"
|
|
429
|
+
REVIEW_APPROVED=true
|
|
430
|
+
break
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
|
|
434
|
+
echo "Review found issues. Running fix iteration..."
|
|
435
|
+
echo "Fix the issues found in the code review above. Run git diff main to see the current changes, then:
|
|
436
|
+
1. Fix each issue referenced in the review
|
|
437
|
+
2. Run tests: npm test
|
|
438
|
+
3. Commit and push the fixes
|
|
439
|
+
Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "$CLAUDE_OUTPUT" || true
|
|
440
|
+
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
441
|
+
fi
|
|
442
|
+
done
|
|
443
|
+
if [ "$REVIEW_APPROVED" != true ]; then
|
|
444
|
+
echo "Review not approved after $MAX_REVIEW_ATTEMPTS attempts."
|
|
352
445
|
PR_STATUS="failed"
|
|
353
446
|
fi
|
|
447
|
+
|
|
448
|
+
else
|
|
449
|
+
# Auto mode: create PR, iterate review+fixes until approved (no merge)
|
|
450
|
+
REVIEW_ATTEMPT=0
|
|
451
|
+
REVIEW_APPROVED=false
|
|
452
|
+
while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
|
|
453
|
+
REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
|
|
454
|
+
echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
|
|
455
|
+
cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || true
|
|
456
|
+
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
457
|
+
|
|
458
|
+
# Check if output contains APPROVED
|
|
459
|
+
if grep -qi "APPROVED" "$CLAUDE_OUTPUT" 2>/dev/null; then
|
|
460
|
+
echo "Review approved!"
|
|
461
|
+
REVIEW_APPROVED=true
|
|
462
|
+
break
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
|
|
466
|
+
echo "Review found issues. Running fix iteration..."
|
|
467
|
+
echo "Fix the issues found in the code review above. Run git diff main to see the current changes, then:
|
|
468
|
+
1. Fix each issue referenced in the review
|
|
469
|
+
2. Run tests: npm test
|
|
470
|
+
3. Commit and push the fixes
|
|
471
|
+
Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "$CLAUDE_OUTPUT" || true
|
|
472
|
+
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
473
|
+
fi
|
|
474
|
+
done
|
|
475
|
+
if [ "$REVIEW_APPROVED" != true ]; then
|
|
476
|
+
echo "Review not approved after $MAX_REVIEW_ATTEMPTS attempts. PR ready for manual review."
|
|
477
|
+
fi
|
|
354
478
|
fi
|
|
355
|
-
parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
|
|
356
479
|
write_phase_end "pr_review" "$PR_STATUS"
|
|
357
480
|
|
|
481
|
+
# Phase 7.5: Post-completion action request
|
|
482
|
+
echo "======================== ACTION REQUEST PHASE ========================"
|
|
483
|
+
write_action_request
|
|
484
|
+
CHOSEN_ACTION=$(poll_action_reply)
|
|
485
|
+
echo "User chose: $CHOSEN_ACTION"
|
|
486
|
+
|
|
487
|
+
# Dispatch based on user choice
|
|
488
|
+
case "$CHOSEN_ACTION" in
|
|
489
|
+
done)
|
|
490
|
+
echo "Loop complete. Exiting."
|
|
491
|
+
;;
|
|
492
|
+
merge_local)
|
|
493
|
+
echo "Merging back to main locally..."
|
|
494
|
+
git checkout main 2>/dev/null || git checkout master
|
|
495
|
+
git merge --squash "$BRANCH" && git commit -m "feat($FEATURE): squash merge from $BRANCH"
|
|
496
|
+
echo "Merged. You can delete the branch with: git branch -D $BRANCH"
|
|
497
|
+
;;
|
|
498
|
+
discard)
|
|
499
|
+
echo "Discarding work on branch $BRANCH..."
|
|
500
|
+
git checkout main 2>/dev/null || git checkout master
|
|
501
|
+
git branch -D "$BRANCH" 2>/dev/null || echo "Branch $BRANCH not found locally."
|
|
502
|
+
;;
|
|
503
|
+
keep_branch|*)
|
|
504
|
+
echo "Keeping branch $BRANCH as-is."
|
|
505
|
+
;;
|
|
506
|
+
esac
|
|
507
|
+
|
|
358
508
|
# Persist final status for TUI summaries
|
|
359
509
|
if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"; then
|
|
360
510
|
echo "WARNING: Failed to write final status file: $FINAL_STATUS_FILE" >&2
|