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.
Files changed (38) hide show
  1. package/README.md +24 -5
  2. package/dist/ai/providers.js +19 -14
  3. package/dist/commands/run.d.ts +1 -1
  4. package/dist/commands/run.js +2 -2
  5. package/dist/index.js +7 -1
  6. package/dist/repl/session-state.d.ts +2 -0
  7. package/dist/templates/config/ralph.config.cjs.tmpl +1 -1
  8. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +7 -41
  9. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +163 -0
  10. package/dist/templates/scripts/feature-loop-actions.test.ts +92 -0
  11. package/dist/templates/scripts/feature-loop.sh.tmpl +157 -7
  12. package/dist/tui/app.js +20 -2
  13. package/dist/tui/components/ChatInput.d.ts +3 -1
  14. package/dist/tui/components/ChatInput.js +23 -4
  15. package/dist/tui/components/CommandDropdown.d.ts +3 -1
  16. package/dist/tui/components/CommandDropdown.js +10 -7
  17. package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
  18. package/dist/tui/components/SpecCompletionSummary.js +26 -9
  19. package/dist/tui/components/SummaryBox.d.ts +0 -3
  20. package/dist/tui/components/SummaryBox.js +4 -2
  21. package/dist/tui/orchestration/interview-orchestrator.js +35 -5
  22. package/dist/tui/screens/MainShell.js +2 -1
  23. package/dist/tui/screens/RunScreen.js +81 -12
  24. package/dist/tui/utils/action-inbox.d.ts +43 -0
  25. package/dist/tui/utils/action-inbox.js +109 -0
  26. package/dist/tui/utils/polishGoal.d.ts +37 -0
  27. package/dist/tui/utils/polishGoal.js +170 -0
  28. package/dist/utils/config.d.ts +1 -1
  29. package/dist/utils/fuzzy-match.d.ts +5 -0
  30. package/dist/utils/fuzzy-match.js +16 -0
  31. package/dist/utils/spec-names.d.ts +6 -0
  32. package/dist/utils/spec-names.js +23 -0
  33. package/package.json +9 -4
  34. package/src/templates/config/ralph.config.cjs.tmpl +1 -1
  35. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +7 -41
  36. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +163 -0
  37. package/src/templates/scripts/feature-loop-actions.test.ts +92 -0
  38. package/src/templates/scripts/feature-loop.sh.tmpl +157 -7
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), Codex, or any CLI-based coding agent, running an autonomous **implement → test → fix** loop until the feature ships.
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 / Codex
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
- └── PROMPT_review_auto.md # PR review (auto - review + merge)
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: 50) |
222
- | `--max-e2e-attempts <n>` | Max E2E retries (default: 3) |
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).
@@ -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-5-20250514', label: 'Claude Opus 4.5', hint: 'most capable' },
38
- { value: 'claude-sonnet-4-5-20250514', label: 'Claude Sonnet 4.5', hint: 'recommended' },
39
- { value: 'claude-haiku-4-5-20250514', label: 'Claude Haiku 4.5', hint: 'fastest' },
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.1', label: 'GPT-5.1', hint: 'most capable' },
43
- { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', hint: 'best for code' },
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: 'deepseek/deepseek-v3.2', label: 'DeepSeek v3.2', hint: 'efficient' },
50
- { value: 'z-ai/glm-4.7', label: 'GLM 4.7', hint: 'Z-AI' },
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-20250514',
60
- openai: 'gpt-5.1',
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-20250514',
68
- opus: 'claude-opus-4-5-20250514',
69
- haiku: 'claude-haiku-4-5-20250514',
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
@@ -8,7 +8,7 @@ export interface RunOptions {
8
8
  model?: string;
9
9
  maxIterations?: number;
10
10
  maxE2eAttempts?: number;
11
- reviewMode?: 'manual' | 'auto';
11
+ reviewMode?: 'manual' | 'auto' | 'merge';
12
12
  }
13
13
  /**
14
14
  * Run the feature development loop for a specific feature
@@ -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 'auto'.`);
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
- return createSessionState(projectRoot, provider, // May be null if no API key
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" -> Proceed to Step 5 (rebase and merge)
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: 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.
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 without Claude Code approval
145
- - Address ALL review comments before merging
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
+ });