wiggum-cli 0.15.0 → 0.16.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 (35) hide show
  1. package/README.md +7 -1
  2. package/dist/generator/templates.d.ts +1 -0
  3. package/dist/generator/templates.js +14 -1
  4. package/dist/index.d.ts +14 -1
  5. package/dist/index.js +222 -40
  6. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  7. package/dist/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  8. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +7 -2
  9. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +7 -2
  10. package/dist/templates/scripts/feature-loop.sh.tmpl +190 -46
  11. package/dist/tui/app.d.ts +16 -1
  12. package/dist/tui/app.js +11 -3
  13. package/dist/tui/components/ActivityFeed.d.ts +18 -0
  14. package/dist/tui/components/ActivityFeed.js +31 -0
  15. package/dist/tui/components/RunCompletionSummary.d.ts +27 -1
  16. package/dist/tui/components/RunCompletionSummary.js +97 -7
  17. package/dist/tui/components/SummaryBox.d.ts +4 -0
  18. package/dist/tui/components/SummaryBox.js +4 -2
  19. package/dist/tui/hooks/useBackgroundRuns.js +1 -1
  20. package/dist/tui/screens/RunScreen.d.ts +15 -15
  21. package/dist/tui/screens/RunScreen.js +58 -5
  22. package/dist/tui/utils/build-run-summary.js +4 -1
  23. package/dist/tui/utils/git-summary.d.ts +13 -0
  24. package/dist/tui/utils/git-summary.js +30 -0
  25. package/dist/tui/utils/loop-status.d.ts +54 -0
  26. package/dist/tui/utils/loop-status.js +213 -1
  27. package/dist/utils/ci.d.ts +8 -0
  28. package/dist/utils/ci.js +13 -0
  29. package/dist/utils/spec-names.js +5 -1
  30. package/package.json +7 -2
  31. package/src/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  32. package/src/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  33. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +7 -2
  34. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +7 -2
  35. package/src/templates/scripts/feature-loop.sh.tmpl +190 -46
package/README.md CHANGED
@@ -25,7 +25,7 @@
25
25
  </p>
26
26
 
27
27
  <p align="center">
28
- <img src=".github/screenshot.png" alt="Wiggum TUI — spec generation and autonomous coding loop" width="800">
28
+ <video src="https://github.com/user-attachments/assets/817edf0c-a7aa-418f-bf85-499be520fd94" width="800" controls></video>
29
29
  </p>
30
30
 
31
31
  ---
@@ -83,6 +83,12 @@ npx wiggum-cli init
83
83
 
84
84
  🔁 **Autonomous Coding Loops** — Hands specs to Claude Code (or any agent) and runs implement → test → fix cycles with git worktree isolation.
85
85
 
86
+ ✨ **Spec Autocomplete** — AI pre-fills spec names from your codebase context when running `/run`.
87
+
88
+ 📥 **Action Inbox** — Review AI decisions inline without breaking your flow. The loop pauses, you approve or redirect, it continues.
89
+
90
+ 📊 **Run Summaries** — See exactly what changed and why after each loop completes, with activity feed and diff stats.
91
+
86
92
  📋 **Tailored Prompts** — Generates prompts, guides, and scripts specific to your stack. Not generic templates — actual context about *your* project.
87
93
 
88
94
  🔌 **BYOK** — Bring your own API keys. Works with Anthropic, OpenAI, or OpenRouter. Keys stay local, never leave your machine.
@@ -28,6 +28,7 @@ export interface TemplateVariables {
28
28
  typecheckCommand: string;
29
29
  formatCommand: string;
30
30
  appDir: string;
31
+ isTui: string;
31
32
  aiEntryPoints: string;
32
33
  aiKeyDirectories: string;
33
34
  aiNamingConventions: string;
@@ -3,7 +3,7 @@
3
3
  * Reads template files with {{variable}} placeholders and substitutes values
4
4
  */
5
5
  import { readFile, readdir } from 'node:fs/promises';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
7
  import { join, dirname } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  const __filename = fileURLToPath(import.meta.url);
@@ -150,6 +150,18 @@ export function extractVariables(scanResult, customVars = {}) {
150
150
  existsSync(join(projectRoot, 'src', 'main.ts'))) {
151
151
  appDir = 'src';
152
152
  }
153
+ // Detect TUI (Ink) projects
154
+ let isTui = '';
155
+ try {
156
+ const pkgPath = join(projectRoot, 'package.json');
157
+ if (existsSync(pkgPath)) {
158
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
159
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
160
+ if (allDeps['ink'])
161
+ isTui = 'true';
162
+ }
163
+ }
164
+ catch { /* ignore */ }
153
165
  return {
154
166
  projectName,
155
167
  projectRoot,
@@ -166,6 +178,7 @@ export function extractVariables(scanResult, customVars = {}) {
166
178
  stylingVersion,
167
179
  stylingVariant,
168
180
  appDir,
181
+ isTui,
169
182
  ...commands,
170
183
  ...aiData,
171
184
  ...customVars,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,19 @@
1
+ /**
2
+ * Parsed CLI arguments
3
+ */
4
+ export interface ParsedArgs {
5
+ command: string | undefined;
6
+ positionalArgs: string[];
7
+ flags: Record<string, string | boolean>;
8
+ }
9
+ /**
10
+ * Parse CLI arguments into command, positional args, and flags.
11
+ * Supports: --flag value, --flag=value, boolean flags, short flags (-i, -y, -e, -f, -h, -v).
12
+ */
13
+ export declare function parseCliArgs(argv: string[]): ParsedArgs;
1
14
  /**
2
15
  * Main entry point for the Wiggum CLI
3
- * TUI-first: routes args to appropriate TUI screens
16
+ * TUI-first: routes args to appropriate TUI screens or CLI commands
4
17
  */
5
18
  export declare function main(): Promise<void>;
6
19
  export { displayHeader } from './utils/header.js';
package/dist/index.js CHANGED
@@ -9,6 +9,87 @@ import { loadApiKeysFromEnvLocal } from './utils/env.js';
9
9
  import { readFileSync } from 'fs';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { dirname, join } from 'path';
12
+ import { runCommand } from './commands/run.js';
13
+ import { monitorCommand } from './commands/monitor.js';
14
+ import { handleConfigCommand } from './commands/config.js';
15
+ import { isCI } from './utils/ci.js';
16
+ /**
17
+ * Normalize a flag name: strip '--' prefix and convert kebab-case to camelCase
18
+ */
19
+ function normalizeFlagName(flag) {
20
+ return flag.replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase());
21
+ }
22
+ /**
23
+ * Parse CLI arguments into command, positional args, and flags.
24
+ * Supports: --flag value, --flag=value, boolean flags, short flags (-i, -y, -e, -f, -h, -v).
25
+ */
26
+ export function parseCliArgs(argv) {
27
+ const positionalArgs = [];
28
+ const flags = {};
29
+ let command;
30
+ const shortFlags = {
31
+ '-i': 'interactive',
32
+ '-y': 'yes',
33
+ '-e': 'edit',
34
+ '-f': 'force',
35
+ '-h': 'help',
36
+ '-v': 'version',
37
+ };
38
+ // Flags that consume the next argument as their value
39
+ const valueFlagSet = new Set([
40
+ '--model',
41
+ '--max-iterations',
42
+ '--max-e2e-attempts',
43
+ '--interval',
44
+ '--provider',
45
+ '--review-mode',
46
+ ]);
47
+ let i = 0;
48
+ while (i < argv.length) {
49
+ const arg = argv[i];
50
+ if (arg in shortFlags) {
51
+ flags[shortFlags[arg]] = true;
52
+ i++;
53
+ continue;
54
+ }
55
+ if (arg.startsWith('--') && arg.includes('=')) {
56
+ const eqIdx = arg.indexOf('=');
57
+ const key = normalizeFlagName(arg.slice(0, eqIdx));
58
+ const value = arg.slice(eqIdx + 1);
59
+ flags[key] = value;
60
+ i++;
61
+ continue;
62
+ }
63
+ if (arg.startsWith('--')) {
64
+ const normalized = normalizeFlagName(arg);
65
+ if (valueFlagSet.has(arg) && i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
66
+ flags[normalized] = argv[i + 1];
67
+ i += 2;
68
+ }
69
+ else {
70
+ flags[normalized] = true;
71
+ i++;
72
+ }
73
+ continue;
74
+ }
75
+ if (command === undefined) {
76
+ command = arg;
77
+ }
78
+ else {
79
+ positionalArgs.push(arg);
80
+ }
81
+ i++;
82
+ }
83
+ return { command, positionalArgs, flags };
84
+ }
85
+ function parseIntFlag(value, flagName) {
86
+ const n = parseInt(value, 10);
87
+ if (Number.isNaN(n)) {
88
+ console.error(`Error: ${flagName} must be a number, got "${value}"`);
89
+ process.exit(1);
90
+ }
91
+ return n;
92
+ }
12
93
  /**
13
94
  * Get version from package.json
14
95
  */
@@ -29,7 +110,8 @@ function getVersion() {
29
110
  * Start Ink TUI mode
30
111
  * Called when wiggum is invoked with no arguments or with screen-routing args
31
112
  */
32
- async function startInkTui(initialScreen = 'shell', interviewFeature) {
113
+ async function startInkTui(initialScreen = 'shell', options) {
114
+ const interviewFeature = options?.interviewFeature;
33
115
  const projectRoot = process.cwd();
34
116
  const version = getVersion();
35
117
  /**
@@ -75,11 +157,16 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
75
157
  scanResult: initialState.scanResult,
76
158
  }
77
159
  : undefined;
160
+ // Build run props if starting on run/monitor screen
161
+ const runProps = options?.runFeature
162
+ ? { featureName: options.runFeature, monitorOnly: options.monitorOnly }
163
+ : undefined;
78
164
  const instance = renderApp({
79
165
  screen: initialScreen,
80
166
  initialSessionState: initialState,
81
167
  version,
82
168
  interviewProps,
169
+ runProps,
83
170
  onComplete: (specPath) => {
84
171
  // Spec was saved to disk by app.tsx (avoid stdout noise during TUI)
85
172
  logger.debug(`Created spec: ${specPath}`);
@@ -93,62 +180,157 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
93
180
  }
94
181
  /**
95
182
  * Main entry point for the Wiggum CLI
96
- * TUI-first: routes args to appropriate TUI screens
183
+ * TUI-first: routes args to appropriate TUI screens or CLI commands
97
184
  */
98
185
  export async function main() {
99
186
  // Load API keys from .ralph/.env.local before any provider detection
100
187
  loadApiKeysFromEnvLocal();
101
- const args = process.argv.slice(2);
188
+ const parsed = parseCliArgs(process.argv.slice(2));
102
189
  // Check for updates (non-blocking, fails silently)
103
190
  await notifyIfUpdateAvailable();
104
- // No args = start with shell
105
- if (args.length === 0) {
191
+ // Handle --help / -h
192
+ if (parsed.flags.help) {
193
+ console.log(`
194
+ Wiggum CLI - AI-powered feature development assistant
195
+
196
+ Usage:
197
+ wiggum Start interactive TUI
198
+ wiggum init Initialize project (TUI)
199
+ wiggum new <name> Create new feature spec (TUI)
200
+ wiggum run <feature> Run feature development loop
201
+ wiggum monitor <feature> Monitor a running feature loop
202
+ wiggum config [args...] Manage API keys and settings
203
+
204
+ Options for run:
205
+ --worktree Use git worktree isolation
206
+ --resume Resume from last checkpoint
207
+ --model <model> AI model to use
208
+ --max-iterations <n> Maximum loop iterations
209
+ --max-e2e-attempts <n> Maximum E2E test attempts
210
+ --review-mode <mode> Review mode: manual, auto, merge
211
+
212
+ Options for monitor:
213
+ --interval <seconds> Refresh interval (default: 5)
214
+ --bash Use bash monitor script
215
+ --stream Force headless streaming output (skip TUI)
216
+
217
+ Options for init:
218
+ --provider <name> AI provider (anthropic, openai, openrouter)
219
+ -i, --interactive Interactive mode
220
+ -y, --yes Accept all defaults
221
+
222
+ Options for new:
223
+ --provider <name> AI provider
224
+ --model <model> AI model
225
+ -e, --edit Open in editor after creation
226
+ -f, --force Overwrite existing spec
227
+
228
+ In the TUI:
229
+ /init Initialize or reconfigure project
230
+ /new <name> Create a new feature specification
231
+ /run <name> Run the feature development loop
232
+ /monitor <name> Monitor a running feature loop
233
+ /sync Sync context from git history
234
+ /config [set <svc> <key>] Manage API keys and settings
235
+ /help Show available commands
236
+ /exit Exit the application
237
+
238
+ Press Esc to cancel any operation.
239
+ `);
240
+ return;
241
+ }
242
+ // Handle --version / -v
243
+ if (parsed.flags.version) {
244
+ console.log(getVersion());
245
+ return;
246
+ }
247
+ // No command = start with shell
248
+ if (!parsed.command) {
106
249
  await startInkTui('shell');
107
250
  return;
108
251
  }
109
- // Route commands to TUI screens
110
- const command = args[0];
111
- switch (command) {
112
- case 'init':
113
- // Start TUI at init screen
252
+ switch (parsed.command) {
253
+ case 'init': {
254
+ // TODO: pass parsed flags to startInkTui once TUI supports init flags
114
255
  await startInkTui('init');
115
256
  break;
116
- case 'new':
117
- // Start TUI at interview screen with feature name
118
- const featureName = args[1];
257
+ }
258
+ case 'new': {
259
+ const featureName = parsed.positionalArgs[0];
119
260
  if (!featureName) {
120
- logger.error('Feature name required. Usage: wiggum new <feature-name>');
261
+ console.error('Error: <name> is required for "new"');
262
+ console.error('Usage: wiggum new <name> [--provider <name>] [--model <model>] [-e] [-f]');
121
263
  process.exit(1);
122
264
  }
123
- await startInkTui('interview', featureName);
265
+ // TODO: pass parsed flags to startInkTui once TUI supports new flags
266
+ await startInkTui('interview', { interviewFeature: featureName });
124
267
  break;
125
- case '--help':
126
- case '-h':
127
- // Show help
128
- console.log(`
129
- Wiggum CLI - AI-powered feature development assistant
130
-
131
- Usage:
132
- wiggum Start interactive TUI
133
- wiggum init Initialize project (TUI)
134
- wiggum new <name> Create new feature spec (TUI)
135
-
136
- In the TUI:
137
- /init Initialize or reconfigure project
138
- /new <name> Create a new feature specification
139
- /help Show available commands
140
- /exit Exit the application
141
-
142
- Press Esc to cancel any operation.
143
- `);
144
- return;
145
- case '--version':
146
- case '-v':
147
- console.log(getVersion());
148
- return;
268
+ }
269
+ case 'run': {
270
+ const feature = parsed.positionalArgs[0];
271
+ if (!feature) {
272
+ console.error('Error: <feature> is required for "run"');
273
+ console.error('Usage: wiggum run <feature> [--worktree] [--resume] [--model <model>] [--max-iterations <n>] [--max-e2e-attempts <n>]');
274
+ process.exit(1);
275
+ }
276
+ const runOptions = {
277
+ worktree: parsed.flags.worktree === true,
278
+ resume: parsed.flags.resume === true,
279
+ model: typeof parsed.flags.model === 'string' ? parsed.flags.model : undefined,
280
+ maxIterations: typeof parsed.flags.maxIterations === 'string' ? parseIntFlag(parsed.flags.maxIterations, '--max-iterations') : undefined,
281
+ maxE2eAttempts: typeof parsed.flags.maxE2eAttempts === 'string' ? parseIntFlag(parsed.flags.maxE2eAttempts, '--max-e2e-attempts') : undefined,
282
+ reviewMode: typeof parsed.flags.reviewMode === 'string' ? parsed.flags.reviewMode : undefined,
283
+ };
284
+ await runCommand(feature, runOptions);
285
+ break;
286
+ }
287
+ case 'monitor': {
288
+ const feature = parsed.positionalArgs[0];
289
+ if (!feature) {
290
+ console.error('Error: <feature> is required for "monitor"');
291
+ console.error('Usage: wiggum monitor <feature> [--interval <seconds>] [--bash] [--stream]');
292
+ process.exit(1);
293
+ }
294
+ const interval = typeof parsed.flags.interval === 'string'
295
+ ? parseIntFlag(parsed.flags.interval, '--interval')
296
+ : undefined;
297
+ // Routing order per spec:
298
+ // 1. --bash → always use bash script path
299
+ // 2. --stream → force headless streaming
300
+ // 3. TTY and not CI → start Ink TUI in monitor-only mode
301
+ // 4. default → headless streaming monitor
302
+ if (parsed.flags.bash === true) {
303
+ await monitorCommand(feature, { bash: true, interval });
304
+ }
305
+ else if (parsed.flags.stream === true) {
306
+ await monitorCommand(feature, { interval });
307
+ }
308
+ else if (process.stdout.isTTY && !isCI()) {
309
+ try {
310
+ await startInkTui('run', { runFeature: feature, monitorOnly: true });
311
+ }
312
+ catch (err) {
313
+ logger.error(`TUI failed to start: ${err instanceof Error ? err.message : String(err)}. Falling back to headless monitor.`);
314
+ await monitorCommand(feature, { interval });
315
+ }
316
+ }
317
+ else {
318
+ await monitorCommand(feature, { interval });
319
+ }
320
+ break;
321
+ }
322
+ case 'config': {
323
+ const provider = getAvailableProvider();
324
+ const model = provider
325
+ ? (AVAILABLE_MODELS[provider].find((m) => m.hint?.includes('recommended'))?.value ?? AVAILABLE_MODELS[provider][0].value)
326
+ : 'sonnet';
327
+ const state = createSessionState(process.cwd(), provider, model);
328
+ await handleConfigCommand(parsed.positionalArgs, state);
329
+ break;
330
+ }
149
331
  default:
150
332
  // Unknown command - start TUI at shell
151
- logger.warn(`Unknown command: ${command}. Starting TUI...`);
333
+ logger.warn(`Unknown command: ${parsed.command}. Starting TUI...`);
152
334
  await startInkTui('shell');
153
335
  }
154
336
  }
@@ -12,10 +12,160 @@ Pay special attention to "E2E Pitfalls" section to avoid known issues.
12
12
  Before E2E testing, verify:
13
13
  1. Build passes: `cd {{appDir}} && {{buildCommand}}`
14
14
  2. All unit tests pass: `cd {{appDir}} && {{testCommand}}`
15
+ {{#unless isTui}}
15
16
  3. Clear cache if issues: `rm -rf {{appDir}}/.next`
17
+ {{/unless}}
16
18
 
17
19
  If either fails, fix issues before proceeding with E2E tests.
18
20
 
21
+ {{#if isTui}}
22
+ ## Task
23
+ Execute automated E2E tests for the completed TUI feature using the xterm.js bridge and agent-browser.
24
+
25
+ ### Step 1: Start Bridge
26
+ Check the bridge is running:
27
+ ```bash
28
+ curl -s http://localhost:3999/health || (cd {{projectRoot}} && npm run e2e:bridge &)
29
+ sleep 3
30
+ ```
31
+
32
+ ### Step 2: Parse E2E Test Scenarios
33
+ Read E2E test scenarios from @.ralph/specs/$FEATURE-implementation-plan.md.
34
+ Each scenario is marked with `- [ ] E2E:` prefix and follows this format:
35
+
36
+ ```
37
+ - [ ] E2E: [Scenario name]
38
+ - **Command:** [wiggum command, e.g., init, new auth-flow]
39
+ - **CWD:** [working directory, e.g., e2e/fixtures/bare-project]
40
+ - **Steps:**
41
+ 1. [Action] -> [expected terminal output]
42
+ - **Verify:** [text that should appear in terminal]
43
+ ```
44
+
45
+ ### Step 3: Execute Each Scenario
46
+
47
+ For each scenario:
48
+
49
+ 1. **Open the TUI in the bridge:**
50
+ ```bash
51
+ agent-browser open "http://localhost:3999?cmd=<command>&cwd=<path>"
52
+ ```
53
+
54
+ 2. **Wait for terminal ready:**
55
+ ```bash
56
+ agent-browser wait --text "Ready" --source title --timeout 10000
57
+ ```
58
+
59
+ 3. **Read terminal content:**
60
+ ```bash
61
+ agent-browser eval "document.getElementById('terminal-mirror').textContent"
62
+ ```
63
+
64
+ 4. **Interact with TUI:**
65
+ ```bash
66
+ # Click to focus terminal
67
+ agent-browser snapshot -i
68
+ agent-browser click @<terminal-ref>
69
+
70
+ # Type text (Enter = press Enter after)
71
+ agent-browser type @<terminal-ref> "/help"
72
+ agent-browser key Enter
73
+
74
+ # Arrow navigation
75
+ agent-browser key ArrowDown
76
+ agent-browser key ArrowDown
77
+ agent-browser key Enter
78
+
79
+ # Escape
80
+ agent-browser key Escape
81
+ ```
82
+
83
+ 5. **Assert expected output:**
84
+ ```bash
85
+ # Read terminal and check for expected text
86
+ CONTENT=$(agent-browser eval "document.getElementById('terminal-mirror').textContent")
87
+ # Verify CONTENT contains expected strings
88
+ ```
89
+
90
+ 6. **Reset between scenarios:**
91
+ ```bash
92
+ agent-browser close
93
+ ```
94
+
95
+ ### TUI Interaction Cheatsheet
96
+
97
+ | Action | Command |
98
+ |--------|---------|
99
+ | Open TUI | `agent-browser open "http://localhost:3999?cmd=init&cwd=/path"` |
100
+ | Read screen | `agent-browser eval "document.getElementById('terminal-mirror').textContent"` |
101
+ | Take snapshot | `agent-browser snapshot -i` |
102
+ | Click element | `agent-browser click @ref` |
103
+ | Type text | `agent-browser type @ref "text"` |
104
+ | Press Enter | `agent-browser key Enter` |
105
+ | Arrow down | `agent-browser key ArrowDown` |
106
+ | Escape | `agent-browser key Escape` |
107
+ | Screenshot | `agent-browser screenshot e2e-failure.png` |
108
+ | Wait for text | `agent-browser wait --text "expected" --timeout 10000` |
109
+ | Close session | `agent-browser close` |
110
+
111
+ ### Key Rules
112
+ - Always wait for expected text before asserting (TUI renders async via React)
113
+ - Use `agent-browser eval` with `terminal-mirror` for reliable text reading
114
+ - Take screenshots on failures for debugging
115
+ - Each scenario navigates to a fresh URL (clean state)
116
+ - Wait 500ms after key presses before reading (Ink re-render delay)
117
+
118
+ ### Step 4: Report Results
119
+ Update @.ralph/specs/$FEATURE-implementation-plan.md for each scenario:
120
+
121
+ **Passed:**
122
+ ```markdown
123
+ - [x] E2E: scenario name - PASSED
124
+ ```
125
+
126
+ **Failed:**
127
+ ```markdown
128
+ - [ ] E2E: scenario name - FAILED: [brief reason]
129
+ - Error: [what went wrong]
130
+ - Screenshot: [if captured]
131
+ - Fix needed: [suggested action]
132
+ ```
133
+
134
+ ## Error Recovery
135
+
136
+ If a scenario fails:
137
+ 1. Document the failure with specific error details
138
+ 2. Take a screenshot: `agent-browser screenshot e2e-failure-<scenario>.png`
139
+ 3. Note what fix is likely needed (code bug vs test spec issue)
140
+ 4. Continue with remaining scenarios
141
+ 5. At end, summary shows total passed/failed
142
+
143
+ Failures will trigger a fix iteration in the loop.
144
+
145
+ ## Completion
146
+
147
+ When all scenarios are executed:
148
+ 1. Update implementation plan with results for each scenario
149
+ 2. Update the Implementation Summary status to `[PASSED]` if all passed
150
+ 3. **Commit the updated implementation plan:**
151
+ ```bash
152
+ git add -A && git commit -m "test($FEATURE): E2E tests passed via agent-browser"
153
+ ```
154
+ 4. **Push to remote:**
155
+ ```bash
156
+ git push origin feat/$FEATURE
157
+ ```
158
+ 5. If all passed: signal ready for PR phase
159
+ 6. If any failed: failures documented, loop will retry after fix iteration
160
+
161
+ ## Learning Capture
162
+ If E2E testing revealed issues worth remembering, append to @.ralph/LEARNINGS.md:
163
+ - Flaky test patterns -> Add under "## Anti-Patterns" > "E2E Pitfalls"
164
+ - TUI timing issues -> Add under "## Anti-Patterns"
165
+ - Useful agent-browser techniques -> Add under "## Tool Usage"
166
+
167
+ Format: `- [YYYY-MM-DD] [$FEATURE] Brief description`
168
+ {{else}}
19
169
  ## Task
20
170
  Execute automated E2E tests for the completed feature using Playwright MCP tools.
21
171
 
@@ -232,3 +382,4 @@ If E2E testing revealed issues worth remembering, append to @.ralph/LEARNINGS.md
232
382
  - Timing issues or race conditions -> Add under "## Anti-Patterns"
233
383
 
234
384
  Format: `- [YYYY-MM-DD] [$FEATURE] Brief description`
385
+ {{/if}}
@@ -44,6 +44,28 @@ Study @.ralph/specs/$FEATURE.md for feature specification.
44
44
  - [ ] Task N (additional polish)
45
45
 
46
46
  ### Phase 5: E2E Testing
47
+ {{#if isTui}}
48
+ TUI E2E tests executed via xterm.js bridge + agent-browser.
49
+ Fixture projects in `e2e/fixtures/`. Bridge at `http://localhost:3999`.
50
+
51
+ - [ ] E2E: [Scenario name] - [brief description]
52
+ - **Command:** [wiggum command, e.g., init, new auth-flow]
53
+ - **CWD:** [working directory, e.g., e2e/fixtures/bare-project]
54
+ - **Steps:**
55
+ 1. [Action] -> [expected terminal output]
56
+ 2. [Action] -> [expected terminal output]
57
+ - **Verify:** [text that should appear in terminal]
58
+
59
+ Example TUI E2E scenario:
60
+ - [ ] E2E: Init in bare project - happy path
61
+ - **Command:** init
62
+ - **CWD:** e2e/fixtures/bare-project
63
+ - **Steps:**
64
+ 1. Open bridge with init command -> Welcome screen renders
65
+ 2. Arrow down to select option -> Option highlighted
66
+ 3. Press Enter -> Next screen appears
67
+ - **Verify:** "initialized" text visible in terminal
68
+ {{else}}
47
69
  Browser-based tests executed via Playwright MCP tools.
48
70
 
49
71
  - [ ] E2E: [Scenario name] - [brief description]
@@ -67,6 +89,7 @@ Example E2E scenario:
67
89
  5. Wait for "Thank You!" -> Success card displays
68
90
  - **Verify:** "successfully submitted" text visible
69
91
  - **Database check:** SELECT * FROM survey_responses WHERE survey_id = '{surveyId}'
92
+ {{/if}}
70
93
 
71
94
  ## Done
72
95
  - [x] Completed task - [commit hash]
@@ -90,12 +90,17 @@ Run: git diff main
90
90
 
91
91
  Respond with:
92
92
  - APPROVED if everything looks good
93
- - Or list specific issues with file:line references that need to be fixed"
93
+ - Or list specific issues with file:line references that need to be fixed
94
+
95
+ IMPORTANT: After posting any PR review comment, you MUST print your final verdict as the LAST line of your output. Print exactly one of:
96
+ VERDICT: APPROVED
97
+ VERDICT: NOT APPROVED
98
+ This line is parsed by the automation — do not omit it."
94
99
  fi
95
100
  ```
96
101
 
97
102
  **Handle review feedback:**
98
- - If Claude outputs "APPROVED" -> Done. The PR is ready for manual merge by the user.
103
+ - If Claude outputs "VERDICT: APPROVED" -> Done. The PR is ready for manual merge by the user.
99
104
  - If Claude lists issues:
100
105
  1. Address each issue with code fixes
101
106
  2. Commit: `git -C {{appDir}} add -A && git -C {{appDir}} commit -m "fix($FEATURE): address review feedback"`
@@ -90,12 +90,17 @@ Run: git diff main
90
90
 
91
91
  Respond with:
92
92
  - APPROVED if everything looks good
93
- - Or list specific issues with file:line references that need to be fixed"
93
+ - Or list specific issues with file:line references that need to be fixed
94
+
95
+ IMPORTANT: After posting any PR review comment, you MUST print your final verdict as the LAST line of your output. Print exactly one of:
96
+ VERDICT: APPROVED
97
+ VERDICT: NOT APPROVED
98
+ This line is parsed by the automation — do not omit it."
94
99
  fi
95
100
  ```
96
101
 
97
102
  **Handle review feedback:**
98
- - If Claude outputs "APPROVED" -> Proceed to Step 5 (rebase and merge)
103
+ - If Claude outputs "VERDICT: APPROVED" -> Proceed to Step 5 (rebase and merge)
99
104
  - If Claude lists issues:
100
105
  1. Address each issue with code fixes
101
106
  2. Commit: `git -C {{appDir}} add -A && git -C {{appDir}} commit -m "fix($FEATURE): address review feedback"`