wiggum-cli 0.13.2 → 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 +236 -9
- package/dist/tui/app.js +22 -3
- package/dist/tui/components/ChatInput.d.ts +3 -1
- package/dist/tui/components/ChatInput.js +50 -13
- package/dist/tui/components/CommandDropdown.d.ts +3 -1
- package/dist/tui/components/CommandDropdown.js +10 -7
- package/dist/tui/components/RunCompletionSummary.d.ts +3 -9
- package/dist/tui/components/RunCompletionSummary.js +59 -14
- package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
- package/dist/tui/components/SpecCompletionSummary.js +26 -9
- package/dist/tui/components/SummaryBox.d.ts +56 -0
- package/dist/tui/components/SummaryBox.js +99 -0
- package/dist/tui/orchestration/interview-orchestrator.js +35 -5
- package/dist/tui/screens/MainShell.js +25 -3
- package/dist/tui/screens/RunScreen.d.ts +116 -1
- package/dist/tui/screens/RunScreen.js +114 -17
- package/dist/tui/utils/action-inbox.d.ts +43 -0
- package/dist/tui/utils/action-inbox.js +109 -0
- 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/polishGoal.d.ts +37 -0
- package/dist/tui/utils/polishGoal.js +170 -0
- package/dist/tui/utils/pr-summary.d.ts +34 -0
- package/dist/tui/utils/pr-summary.js +84 -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/dist/utils/summary-file.d.ts +25 -0
- package/dist/utils/summary-file.js +37 -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 +236 -9
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/wiggum-cli"><img src="https://img.shields.io/npm/v/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="npm"></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/wiggum-cli"><img src="https://img.shields.io/npm/dm/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="downloads"></a>
|
|
12
|
+
<a href="https://github.com/federiconeri/wiggum-cli/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/federiconeri/wiggum-cli/ci.yml?branch=main&color=F8DB27&labelColor=1a1a1a&style=flat-square&label=CI" alt="CI"></a>
|
|
12
13
|
<a href="https://github.com/federiconeri/wiggum-cli/stargazers"><img src="https://img.shields.io/github/stars/federiconeri/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="stars"></a>
|
|
13
14
|
<a href="https://github.com/federiconeri/wiggum-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20+%20Commons%20Clause-F8DB27?labelColor=1a1a1a&style=flat-square" alt="license"></a>
|
|
14
15
|
<img src="https://img.shields.io/node/v/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="node">
|
|
@@ -18,16 +19,22 @@
|
|
|
18
19
|
<a href="#-quick-start">Quick Start</a> ·
|
|
19
20
|
<a href="#-how-it-works">How It Works</a> ·
|
|
20
21
|
<a href="https://wiggum.app">Website</a> ·
|
|
22
|
+
<a href="https://wiggum.app/blog">Blog</a> ·
|
|
23
|
+
<a href="https://wiggum.app/pricing">Pricing</a> ·
|
|
21
24
|
<a href="https://github.com/federiconeri/wiggum-cli/issues">Issues</a>
|
|
22
25
|
</p>
|
|
23
26
|
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src=".github/screenshot.png" alt="Wiggum TUI — spec generation and autonomous coding loop" width="800">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
24
31
|
---
|
|
25
32
|
|
|
26
33
|
## What is Wiggum?
|
|
27
34
|
|
|
28
35
|
Wiggum is an **AI agent** that plugs into any codebase and makes it ready for autonomous feature development — no configuration, no boilerplate.
|
|
29
36
|
|
|
30
|
-
It works in two phases. First, **Wiggum itself is the agent**: it scans your project, detects your stack, and runs an AI-guided interview to produce detailed specs, prompts, and scripts — all tailored to your codebase. Then it delegates the actual coding to [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
37
|
+
It works in two phases. First, **Wiggum itself is the agent**: it scans your project, detects your stack, and runs an AI-guided interview to produce detailed specs, prompts, and scripts — all tailored to your codebase. Then it delegates the actual coding to [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or any CLI-based coding agent, running an autonomous **implement → test → fix** loop until the feature ships.
|
|
31
38
|
|
|
32
39
|
Plug & play. Point it at a repo. It figures out the rest.
|
|
33
40
|
|
|
@@ -41,7 +48,7 @@ Plug & play. Point it at a repo. It figures out the rest.
|
|
|
41
48
|
│ plug&play prompts guides until done │
|
|
42
49
|
│ │ │ │
|
|
43
50
|
└────────────────────────────┘ └────────────────────┘
|
|
44
|
-
runs in your terminal Claude Code /
|
|
51
|
+
runs in your terminal Claude Code / any agent
|
|
45
52
|
```
|
|
46
53
|
|
|
47
54
|
---
|
|
@@ -163,7 +170,8 @@ $ wiggum
|
|
|
163
170
|
│ ├── PROMPT_e2e.md # E2E testing
|
|
164
171
|
│ ├── PROMPT_verify.md # Verification
|
|
165
172
|
│ ├── PROMPT_review_manual.md # PR review (manual - stop at PR)
|
|
166
|
-
│
|
|
173
|
+
│ ├── PROMPT_review_auto.md # PR review (auto - review, no merge)
|
|
174
|
+
│ └── PROMPT_review_merge.md # PR review (merge - review + auto-merge)
|
|
167
175
|
├── guides/
|
|
168
176
|
│ ├── AGENTS.md # Agent instructions (CLAUDE.md)
|
|
169
177
|
│ ├── FRONTEND.md # Frontend patterns
|
|
@@ -218,8 +226,9 @@ Run the autonomous development loop.
|
|
|
218
226
|
| `--worktree` | Git worktree isolation (parallel features) |
|
|
219
227
|
| `--resume` | Resume an interrupted loop |
|
|
220
228
|
| `--model <model>` | Claude model (`opus`, `sonnet`) |
|
|
221
|
-
| `--max-iterations <n>` | Max iterations (default:
|
|
222
|
-
| `--max-e2e-attempts <n>` | Max E2E retries (default:
|
|
229
|
+
| `--max-iterations <n>` | Max iterations (default: 10) |
|
|
230
|
+
| `--max-e2e-attempts <n>` | Max E2E retries (default: 5) |
|
|
231
|
+
| `--review-mode <mode>` | `manual` (stop at PR), `auto` (review, no merge), or `merge` (review + merge). Default: `manual` |
|
|
223
232
|
|
|
224
233
|
</details>
|
|
225
234
|
|
|
@@ -307,6 +316,16 @@ npm test
|
|
|
307
316
|
|
|
308
317
|
---
|
|
309
318
|
|
|
319
|
+
## 📖 Learn More
|
|
320
|
+
|
|
321
|
+
- [What Is Wiggum CLI?](https://wiggum.app/blog/what-is-wiggum-cli) — Overview of the autonomous coding agent
|
|
322
|
+
- [What Is the Ralph Loop?](https://wiggum.app/blog/what-is-the-ralph-loop) — Deep dive into the Ralph loop methodology
|
|
323
|
+
- [Wiggum vs Bash Scripts](https://wiggum.app/blog/wiggum-vs-ralph-wiggum-scripts) — Why spec generation matters
|
|
324
|
+
- [Roadmap](https://wiggum.app/roadmap) — What's coming next
|
|
325
|
+
- [Changelog](https://wiggum.app/changelog) — Release history
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
310
329
|
## 📄 License
|
|
311
330
|
|
|
312
331
|
**MIT + Commons Clause** — see [LICENSE](LICENSE).
|
package/dist/ai/providers.js
CHANGED
|
@@ -34,21 +34,25 @@ export const KNOWN_API_KEYS = [
|
|
|
34
34
|
*/
|
|
35
35
|
export const AVAILABLE_MODELS = {
|
|
36
36
|
anthropic: [
|
|
37
|
-
{ value: 'claude-opus-4-
|
|
38
|
-
{ value: 'claude-sonnet-4-5-
|
|
39
|
-
{ value: 'claude-haiku-4-5-
|
|
37
|
+
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6', hint: 'most capable' },
|
|
38
|
+
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', hint: 'recommended' },
|
|
39
|
+
{ value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', hint: 'fastest' },
|
|
40
40
|
],
|
|
41
41
|
openai: [
|
|
42
|
-
{ value: 'gpt-5.
|
|
43
|
-
{ value: 'gpt-5.
|
|
42
|
+
{ value: 'gpt-5.2', label: 'GPT-5.2', hint: 'most capable' },
|
|
43
|
+
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', hint: 'best for code' },
|
|
44
|
+
{ value: 'gpt-5.1', label: 'GPT-5.1', hint: 'previous gen' },
|
|
45
|
+
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', hint: 'previous codex' },
|
|
44
46
|
{ value: 'gpt-5-mini', label: 'GPT-5 Mini', hint: 'fastest' },
|
|
45
47
|
],
|
|
46
48
|
openrouter: [
|
|
47
|
-
{ value: 'google/gemini-3-pro-preview', label: 'Gemini 3 Pro', hint: 'Google' },
|
|
48
|
-
{ value: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash', hint: 'fast' },
|
|
49
|
-
{ value: '
|
|
50
|
-
{ value: '
|
|
49
|
+
{ value: 'google/gemini-3-pro-preview', label: 'Gemini 3 Pro Preview', hint: 'Google' },
|
|
50
|
+
{ value: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash Preview', hint: 'fast' },
|
|
51
|
+
{ value: 'moonshotai/kimi-k2.5', label: 'Kimi K2.5', hint: 'Moonshot' },
|
|
52
|
+
{ value: 'deepseek/deepseek-v3.2', label: 'DeepSeek V3.2', hint: 'efficient' },
|
|
51
53
|
{ value: 'minimax/minimax-m2.1', label: 'MiniMax M2.1', hint: 'MiniMax' },
|
|
54
|
+
{ value: 'z-ai/glm-4.7', label: 'GLM 4.7', hint: 'Z-AI' },
|
|
55
|
+
{ value: 'x-ai/grok-4.1-fast', label: 'Grok 4.1 Fast', hint: 'xAI' },
|
|
52
56
|
],
|
|
53
57
|
};
|
|
54
58
|
/**
|
|
@@ -56,17 +60,17 @@ export const AVAILABLE_MODELS = {
|
|
|
56
60
|
* Using balanced models for good results
|
|
57
61
|
*/
|
|
58
62
|
const DEFAULT_MODELS = {
|
|
59
|
-
anthropic: 'claude-sonnet-4-5-
|
|
60
|
-
openai: 'gpt-5.
|
|
63
|
+
anthropic: 'claude-sonnet-4-5-20250929',
|
|
64
|
+
openai: 'gpt-5.2',
|
|
61
65
|
openrouter: 'google/gemini-3-pro-preview',
|
|
62
66
|
};
|
|
63
67
|
/**
|
|
64
68
|
* Anthropic shorthand aliases for legacy configs
|
|
65
69
|
*/
|
|
66
70
|
const ANTHROPIC_MODEL_ALIASES = {
|
|
67
|
-
sonnet: 'claude-sonnet-4-5-
|
|
68
|
-
opus: 'claude-opus-4-
|
|
69
|
-
haiku: 'claude-haiku-4-5-
|
|
71
|
+
sonnet: 'claude-sonnet-4-5-20250929',
|
|
72
|
+
opus: 'claude-opus-4-6',
|
|
73
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
70
74
|
};
|
|
71
75
|
/**
|
|
72
76
|
* Check if a model id is an Anthropic alias (sonnet/opus/haiku)
|
|
@@ -173,6 +177,7 @@ const REASONING_MODELS = [
|
|
|
173
177
|
'o3', 'o3-mini',
|
|
174
178
|
'gpt-5', 'gpt-5.1', 'gpt-5-mini',
|
|
175
179
|
'gpt-5.1-codex', 'gpt-5.1-codex-max',
|
|
180
|
+
'gpt-5.2', 'gpt-5.2-codex',
|
|
176
181
|
];
|
|
177
182
|
/**
|
|
178
183
|
* Check if a model is a reasoning model that doesn't support temperature
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -113,8 +113,8 @@ export async function runCommand(feature, options = {}) {
|
|
|
113
113
|
}
|
|
114
114
|
// Resolve and validate reviewMode
|
|
115
115
|
const reviewMode = options.reviewMode ?? config.loop.reviewMode ?? 'manual';
|
|
116
|
-
if (reviewMode !== 'manual' && reviewMode !== 'auto') {
|
|
117
|
-
logger.error(`Invalid reviewMode '${reviewMode}'. Allowed values are 'manual' or '
|
|
116
|
+
if (reviewMode !== 'manual' && reviewMode !== 'auto' && reviewMode !== 'merge') {
|
|
117
|
+
logger.error(`Invalid reviewMode '${reviewMode}'. Allowed values are 'manual', 'auto', or 'merge'.`);
|
|
118
118
|
process.exit(1);
|
|
119
119
|
}
|
|
120
120
|
args.push('--review-mode', reviewMode);
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSessionState } from './repl/session-state.js';
|
|
2
2
|
import { hasConfig, loadConfigWithDefaults } from './utils/config.js';
|
|
3
|
+
import { listSpecNames } from './utils/spec-names.js';
|
|
3
4
|
import { AVAILABLE_MODELS, getAvailableProvider, isAnthropicAlias } from './ai/providers.js';
|
|
4
5
|
import { notifyIfUpdateAvailable } from './utils/update-check.js';
|
|
5
6
|
import { renderApp } from './tui/app.js';
|
|
@@ -54,9 +55,14 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
|
|
|
54
55
|
model = configuredModel;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
const specsDir = config
|
|
59
|
+
? join(projectRoot, config.paths.specs)
|
|
60
|
+
: join(projectRoot, '.ralph/specs');
|
|
61
|
+
const specNames = await listSpecNames(specsDir);
|
|
62
|
+
const state = createSessionState(projectRoot, provider, // May be null if no API key
|
|
58
63
|
model, undefined, // No scan result yet
|
|
59
64
|
config, isInitialized);
|
|
65
|
+
return { ...state, specNames };
|
|
60
66
|
}
|
|
61
67
|
const initialState = await createCurrentSessionState();
|
|
62
68
|
// Build interview props if starting on interview screen
|
|
@@ -25,6 +25,8 @@ export interface SessionState {
|
|
|
25
25
|
conversationContext?: string;
|
|
26
26
|
/** Whether /init has been run in this session */
|
|
27
27
|
initialized: boolean;
|
|
28
|
+
/** Cached spec names from the configured specs directory */
|
|
29
|
+
specNames?: string[];
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Create a new session state
|
|
@@ -34,6 +34,6 @@ module.exports = {
|
|
|
34
34
|
maxE2eAttempts: 5,
|
|
35
35
|
defaultModel: 'sonnet',
|
|
36
36
|
planningModel: 'opus',
|
|
37
|
-
reviewMode: 'manual', // 'manual' = stop at PR, 'auto' = review + auto-merge
|
|
37
|
+
reviewMode: 'manual', // 'manual' = stop at PR, 'auto' = review (no merge), 'merge' = review + auto-merge
|
|
38
38
|
},
|
|
39
39
|
};
|
|
@@ -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
|
+
});
|