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.
- package/README.md +7 -1
- package/dist/generator/templates.d.ts +1 -0
- package/dist/generator/templates.js +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +222 -40
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +23 -0
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +7 -2
- package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +7 -2
- package/dist/templates/scripts/feature-loop.sh.tmpl +190 -46
- package/dist/tui/app.d.ts +16 -1
- package/dist/tui/app.js +11 -3
- package/dist/tui/components/ActivityFeed.d.ts +18 -0
- package/dist/tui/components/ActivityFeed.js +31 -0
- package/dist/tui/components/RunCompletionSummary.d.ts +27 -1
- package/dist/tui/components/RunCompletionSummary.js +97 -7
- package/dist/tui/components/SummaryBox.d.ts +4 -0
- package/dist/tui/components/SummaryBox.js +4 -2
- package/dist/tui/hooks/useBackgroundRuns.js +1 -1
- package/dist/tui/screens/RunScreen.d.ts +15 -15
- package/dist/tui/screens/RunScreen.js +58 -5
- package/dist/tui/utils/build-run-summary.js +4 -1
- package/dist/tui/utils/git-summary.d.ts +13 -0
- package/dist/tui/utils/git-summary.js +30 -0
- package/dist/tui/utils/loop-status.d.ts +54 -0
- package/dist/tui/utils/loop-status.js +213 -1
- package/dist/utils/ci.d.ts +8 -0
- package/dist/utils/ci.js +13 -0
- package/dist/utils/spec-names.js +5 -1
- package/package.json +7 -2
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +23 -0
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +7 -2
- package/src/templates/prompts/PROMPT_review_merge.md.tmpl +7 -2
- 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
|
-
<
|
|
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.
|
|
@@ -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',
|
|
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
|
|
188
|
+
const parsed = parseCliArgs(process.argv.slice(2));
|
|
102
189
|
// Check for updates (non-blocking, fails silently)
|
|
103
190
|
await notifyIfUpdateAvailable();
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
const featureName =
|
|
257
|
+
}
|
|
258
|
+
case 'new': {
|
|
259
|
+
const featureName = parsed.positionalArgs[0];
|
|
119
260
|
if (!featureName) {
|
|
120
|
-
|
|
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
|
-
|
|
265
|
+
// TODO: pass parsed flags to startInkTui once TUI supports new flags
|
|
266
|
+
await startInkTui('interview', { interviewFeature: featureName });
|
|
124
267
|
break;
|
|
125
|
-
|
|
126
|
-
case '
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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"`
|