fabis-ralph-loop 1.4.2 → 1.4.3
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 +1 -1
- package/dist/index.d.mts +6 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/loader.mjs +6 -2
- package/dist/loader.mjs.map +1 -1
- package/dist/templates/ralph-prompt.md.ejs +58 -1
- package/dist/uac-templates/skills/prd/SKILL.md +12 -0
- package/dist/uac-templates/skills/ralph/SKILL.md +18 -2
- package/dist/uac-templates/skills/update-fabis-ralph-loop-config/SKILL.md +17 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ export default defineConfig({
|
|
|
78
78
|
},
|
|
79
79
|
container: {
|
|
80
80
|
name: 'my-project-ralph',
|
|
81
|
-
playwright: true, // auto-configures Playwright
|
|
81
|
+
playwright: true, // auto-configures Playwright CLI + headless Chromium (or 'mcp' for MCP mode)
|
|
82
82
|
systemPackages: ['ripgrep'],
|
|
83
83
|
env: { NODE_ENV: 'development' },
|
|
84
84
|
hooks: {
|
package/dist/index.d.mts
CHANGED
|
@@ -11,7 +11,10 @@ declare const ralphLoopConfigSchema: z.ZodObject<{
|
|
|
11
11
|
baseImage: z.ZodDefault<z.ZodString>;
|
|
12
12
|
user: z.ZodDefault<z.ZodString>;
|
|
13
13
|
systemPackages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14
|
-
playwright: z.ZodDefault<z.ZodBoolean
|
|
14
|
+
playwright: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
|
|
15
|
+
cli: "cli";
|
|
16
|
+
mcp: "mcp";
|
|
17
|
+
}>]>>, z.ZodTransform<false | "cli" | "mcp", boolean | "cli" | "mcp">>;
|
|
15
18
|
networkMode: z.ZodDefault<z.ZodString>;
|
|
16
19
|
env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
17
20
|
shmSize: z.ZodDefault<z.ZodString>;
|
|
@@ -111,7 +114,7 @@ declare function defineConfig(config: RalphLoopConfig): {
|
|
|
111
114
|
baseImage?: string | undefined;
|
|
112
115
|
user?: string | undefined;
|
|
113
116
|
systemPackages?: string[] | undefined;
|
|
114
|
-
playwright?: boolean | undefined;
|
|
117
|
+
playwright?: boolean | "cli" | "mcp" | undefined;
|
|
115
118
|
networkMode?: string | undefined;
|
|
116
119
|
env?: Record<string, string> | undefined;
|
|
117
120
|
shmSize?: string | undefined;
|
|
@@ -160,7 +163,7 @@ declare function defineOverridesConfig(config: DeepPartial<RalphLoopConfig>): {
|
|
|
160
163
|
baseImage?: string | undefined;
|
|
161
164
|
user?: string | undefined;
|
|
162
165
|
systemPackages?: (string | undefined)[] | undefined;
|
|
163
|
-
playwright?: boolean | undefined;
|
|
166
|
+
playwright?: boolean | "cli" | "mcp" | undefined;
|
|
164
167
|
networkMode?: string | undefined;
|
|
165
168
|
env?: {
|
|
166
169
|
[x: string]: string | undefined;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/generators/index.ts","../src/config/merge.ts","../src/utils/gitignore.ts","../src/index.ts"],"mappings":";;;cAEM,yBAAA,EAAyB,CAAA,CAAA,SAAA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/generators/index.ts","../src/config/merge.ts","../src/utils/gitignore.ts","../src/index.ts"],"mappings":";;;cAEM,yBAAA,EAAyB,CAAA,CAAA,SAAA;;;;cA6DlB,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,cAAA,GAAiB,CAAA,CAAE,MAAA,QAAc,qBAAA;AAAA,KACjC,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;;;iBCnE3B,eAAA,CAAgB,GAAA,YAAe,OAAA,CAAQ,cAAA;;;UCInD,eAAA;EACR,MAAA;EACA,IAAA;AAAA;AAAA,UAGQ,aAAA;EACR,IAAA;EACA,OAAA;AAAA;AAAA,iBAGoB,WAAA,CACpB,MAAA,EAAQ,cAAA,EACR,WAAA,UACA,OAAA,GAAS,eAAA,GACR,OAAA,CAAQ,aAAA;;;;;;AFxBY;;;;;;iBGSP,YAAA,WAAuB,MAAA,kBAAA,CACrC,IAAA,EAAM,CAAA,EACN,SAAA,EAAW,MAAA,oBACV,CAAA;;;;;;AHZoB;iBIeD,oBAAA,CAAqB,GAAA,YAA8B,OAAA;;;KCR7D,WAAA,MAAiB,CAAA,gCAAiC,CAAA,IAAK,WAAA,CAAY,CAAA,CAAE,CAAA,OAAQ,CAAA;;;;iBAKzE,YAAA,CAAa,MAAA,EAAD,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQZ,qBAAA,CACd,MAAA,EAAQ,WAAA,CAD2B,eAAA"}
|
package/dist/loader.mjs
CHANGED
|
@@ -16,7 +16,11 @@ const containerSchema = z.object({
|
|
|
16
16
|
baseImage: z.string().min(1).default("node:22-bookworm"),
|
|
17
17
|
user: z.string().min(1).default("sandbox"),
|
|
18
18
|
systemPackages: z.array(z.string()).default([]),
|
|
19
|
-
playwright: z.boolean().default(false)
|
|
19
|
+
playwright: z.union([z.boolean(), z.enum(["cli", "mcp"])]).default(false).transform((val) => {
|
|
20
|
+
if (val === true) return "cli";
|
|
21
|
+
if (val === false) return false;
|
|
22
|
+
return val;
|
|
23
|
+
}),
|
|
20
24
|
networkMode: z.string().default("host"),
|
|
21
25
|
env: z.record(z.string(), z.string()).default({}),
|
|
22
26
|
shmSize: z.string().default("64m"),
|
|
@@ -56,7 +60,7 @@ const ralphLoopConfigSchema = z.object({
|
|
|
56
60
|
//#endregion
|
|
57
61
|
//#region src/config/defaults.ts
|
|
58
62
|
/**
|
|
59
|
-
* Apply Playwright-specific defaults when playwright is enabled.
|
|
63
|
+
* Apply Playwright-specific defaults when playwright is enabled ('cli' or 'mcp').
|
|
60
64
|
* Merges SYS_ADMIN capability and 2gb shm_size if not already set.
|
|
61
65
|
*/
|
|
62
66
|
function applyPlaywrightDefaults(config) {
|
package/dist/loader.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.mjs","names":[],"sources":["../src/config/schema.ts","../src/config/defaults.ts","../src/config/merge.ts","../src/config/loader.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst backpressureCommandSchema = z.object({\n name: z.string().min(1),\n command: z.string().min(1),\n})\n\nconst containerHooksSchema = z.object({\n rootSetup: z.array(z.string()).default([]),\n userSetup: z.array(z.string()).default([]),\n entrypointSetup: z.array(z.string()).default([]),\n})\n\nconst containerSchema = z.object({\n name: z.string().min(1),\n baseImage: z.string().min(1).default('node:22-bookworm'),\n user: z.string().min(1).default('sandbox'),\n systemPackages: z.array(z.string()).default([]),\n playwright: z.boolean().default(false),\n networkMode: z.string().default('host'),\n env: z.record(z.string(), z.string()).default({}),\n shmSize: z.string().default('64m'),\n capabilities: z.array(z.string()).default([]),\n volumes: z.array(z.string()).default([]),\n shadowVolumes: z.array(z.string()).default([]),\n persistVolumes: z\n .record(z.string(), z.string())\n .default({ 'ralph-claude-config': '/home/sandbox/.claude' }),\n hooks: containerHooksSchema.prefault({}),\n})\n\nconst setupSchema = z.object({\n preStartCommand: z.string().default(''),\n})\n\nconst defaultsSchema = z.object({\n agent: z.literal('claude').default('claude'),\n model: z.string().default('sonnet'),\n verbose: z.boolean().default(false),\n sleepBetweenMs: z.number().int().min(0).default(2000),\n completionSignal: z.string().default('RALPH_WORK_FULLY_DONE'),\n})\n\nconst projectSchema = z.object({\n name: z.string().min(1),\n description: z.string().default(''),\n context: z.string().default(''),\n backpressureCommands: z.array(backpressureCommandSchema).default([]),\n openAppSkill: z.string().default(''),\n})\n\nconst outputSchema = z.object({\n mode: z.enum(['direct', 'uac']).default('direct'),\n uacTemplatesDir: z.string().default('.universal-ai-config'),\n})\n\nexport const ralphLoopConfigSchema = z.object({\n container: containerSchema.prefault({ name: 'ralph-container' }),\n setup: setupSchema.prefault({}),\n defaults: defaultsSchema.prefault({}),\n project: projectSchema,\n output: outputSchema.prefault({}),\n})\n\nexport type RalphLoopConfig = z.input<typeof ralphLoopConfigSchema>\nexport type ResolvedConfig = z.output<typeof ralphLoopConfigSchema>\nexport type BackpressureCommand = z.infer<typeof backpressureCommandSchema>\n","import type { ResolvedConfig } from './schema.js'\n\n/**\n * Apply Playwright-specific defaults when playwright is enabled.\n * Merges SYS_ADMIN capability and 2gb shm_size if not already set.\n */\nexport function applyPlaywrightDefaults(config: ResolvedConfig): ResolvedConfig {\n if (!config.container.playwright) return config\n\n const shmSize = config.container.shmSize === '64m' ? '2gb' : config.container.shmSize\n\n const capabilities = config.container.capabilities.includes('SYS_ADMIN')\n ? config.container.capabilities\n : [...config.container.capabilities, 'SYS_ADMIN']\n\n return {\n ...config,\n container: {\n ...config.container,\n shmSize,\n capabilities,\n },\n }\n}\n","/**\n * Deep merge two config objects.\n *\n * Merge strategy:\n * - Arrays: overrides REPLACE base arrays entirely\n * - Plain objects: merge recursively\n * - Scalars: overrides replace base values\n * - undefined values in overrides are skipped\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n base: T,\n overrides: Record<string, unknown>,\n): T {\n return deepMerge(base, overrides) as T\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction deepMerge(\n base: Record<string, unknown>,\n overrides: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base }\n\n for (const key of Object.keys(overrides)) {\n const overrideValue = overrides[key]\n const baseValue = base[key]\n\n if (overrideValue === undefined) continue\n\n if (Array.isArray(overrideValue)) {\n result[key] = overrideValue\n } else if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n result[key] = deepMerge(baseValue, overrideValue)\n } else {\n result[key] = overrideValue\n }\n }\n\n return result\n}\n","import { loadConfig } from 'c12'\nimport { ralphLoopConfigSchema } from './schema.js'\nimport { applyPlaywrightDefaults } from './defaults.js'\nimport { mergeConfigs } from './merge.js'\nimport type { RalphLoopConfig, ResolvedConfig } from './schema.js'\n\nexport async function loadRalphConfig(cwd?: string): Promise<ResolvedConfig> {\n const { config: baseConfig } = await loadConfig<RalphLoopConfig>({\n name: 'fabis-ralph-loop',\n cwd,\n })\n\n if (!baseConfig || Object.keys(baseConfig).length === 0) {\n throw new Error('No fabis-ralph-loop config found. Run `fabis-ralph-loop init` to create one.')\n }\n\n const { config: overridesConfig } = await loadConfig<Partial<RalphLoopConfig>>({\n name: 'fabis-ralph-loop.overrides',\n cwd,\n })\n\n const merged =\n overridesConfig && Object.keys(overridesConfig).length > 0\n ? mergeConfigs(baseConfig, overridesConfig as RalphLoopConfig)\n : baseConfig\n\n const parsed = ralphLoopConfigSchema.safeParse(merged)\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => ` ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n')\n const suffix =\n overridesConfig && Object.keys(overridesConfig).length > 0 ? ' (after merging overrides)' : ''\n throw new Error(`Invalid fabis-ralph-loop config${suffix}:\\n${issues}`)\n }\n\n return applyPlaywrightDefaults(parsed.data)\n}\n"],"mappings":";;;;AAEA,MAAM,4BAA4B,EAAE,OAAO;CACzC,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACvB,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,CAAC;AAEF,MAAM,uBAAuB,EAAE,OAAO;CACpC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CAC1C,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CAC1C,iBAAiB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,kBAAkB,EAAE,OAAO;CAC/B,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACvB,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,mBAAmB;CACxD,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,UAAU;CAC1C,gBAAgB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CAC/C,YAAY,EAAE,SAAS,CAAC,QAAQ,MAAM;
|
|
1
|
+
{"version":3,"file":"loader.mjs","names":[],"sources":["../src/config/schema.ts","../src/config/defaults.ts","../src/config/merge.ts","../src/config/loader.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst backpressureCommandSchema = z.object({\n name: z.string().min(1),\n command: z.string().min(1),\n})\n\nconst containerHooksSchema = z.object({\n rootSetup: z.array(z.string()).default([]),\n userSetup: z.array(z.string()).default([]),\n entrypointSetup: z.array(z.string()).default([]),\n})\n\nconst containerSchema = z.object({\n name: z.string().min(1),\n baseImage: z.string().min(1).default('node:22-bookworm'),\n user: z.string().min(1).default('sandbox'),\n systemPackages: z.array(z.string()).default([]),\n playwright: z\n .union([z.boolean(), z.enum(['cli', 'mcp'])])\n .default(false)\n .transform((val) => {\n if (val === true) return 'cli' as const\n if (val === false) return false as const\n return val\n }),\n networkMode: z.string().default('host'),\n env: z.record(z.string(), z.string()).default({}),\n shmSize: z.string().default('64m'),\n capabilities: z.array(z.string()).default([]),\n volumes: z.array(z.string()).default([]),\n shadowVolumes: z.array(z.string()).default([]),\n persistVolumes: z\n .record(z.string(), z.string())\n .default({ 'ralph-claude-config': '/home/sandbox/.claude' }),\n hooks: containerHooksSchema.prefault({}),\n})\n\nconst setupSchema = z.object({\n preStartCommand: z.string().default(''),\n})\n\nconst defaultsSchema = z.object({\n agent: z.literal('claude').default('claude'),\n model: z.string().default('sonnet'),\n verbose: z.boolean().default(false),\n sleepBetweenMs: z.number().int().min(0).default(2000),\n completionSignal: z.string().default('RALPH_WORK_FULLY_DONE'),\n})\n\nconst projectSchema = z.object({\n name: z.string().min(1),\n description: z.string().default(''),\n context: z.string().default(''),\n backpressureCommands: z.array(backpressureCommandSchema).default([]),\n openAppSkill: z.string().default(''),\n})\n\nconst outputSchema = z.object({\n mode: z.enum(['direct', 'uac']).default('direct'),\n uacTemplatesDir: z.string().default('.universal-ai-config'),\n})\n\nexport const ralphLoopConfigSchema = z.object({\n container: containerSchema.prefault({ name: 'ralph-container' }),\n setup: setupSchema.prefault({}),\n defaults: defaultsSchema.prefault({}),\n project: projectSchema,\n output: outputSchema.prefault({}),\n})\n\nexport type RalphLoopConfig = z.input<typeof ralphLoopConfigSchema>\nexport type ResolvedConfig = z.output<typeof ralphLoopConfigSchema>\nexport type BackpressureCommand = z.infer<typeof backpressureCommandSchema>\n","import type { ResolvedConfig } from './schema.js'\n\n/**\n * Apply Playwright-specific defaults when playwright is enabled ('cli' or 'mcp').\n * Merges SYS_ADMIN capability and 2gb shm_size if not already set.\n */\nexport function applyPlaywrightDefaults(config: ResolvedConfig): ResolvedConfig {\n if (!config.container.playwright) return config\n\n const shmSize = config.container.shmSize === '64m' ? '2gb' : config.container.shmSize\n\n const capabilities = config.container.capabilities.includes('SYS_ADMIN')\n ? config.container.capabilities\n : [...config.container.capabilities, 'SYS_ADMIN']\n\n return {\n ...config,\n container: {\n ...config.container,\n shmSize,\n capabilities,\n },\n }\n}\n","/**\n * Deep merge two config objects.\n *\n * Merge strategy:\n * - Arrays: overrides REPLACE base arrays entirely\n * - Plain objects: merge recursively\n * - Scalars: overrides replace base values\n * - undefined values in overrides are skipped\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n base: T,\n overrides: Record<string, unknown>,\n): T {\n return deepMerge(base, overrides) as T\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction deepMerge(\n base: Record<string, unknown>,\n overrides: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base }\n\n for (const key of Object.keys(overrides)) {\n const overrideValue = overrides[key]\n const baseValue = base[key]\n\n if (overrideValue === undefined) continue\n\n if (Array.isArray(overrideValue)) {\n result[key] = overrideValue\n } else if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {\n result[key] = deepMerge(baseValue, overrideValue)\n } else {\n result[key] = overrideValue\n }\n }\n\n return result\n}\n","import { loadConfig } from 'c12'\nimport { ralphLoopConfigSchema } from './schema.js'\nimport { applyPlaywrightDefaults } from './defaults.js'\nimport { mergeConfigs } from './merge.js'\nimport type { RalphLoopConfig, ResolvedConfig } from './schema.js'\n\nexport async function loadRalphConfig(cwd?: string): Promise<ResolvedConfig> {\n const { config: baseConfig } = await loadConfig<RalphLoopConfig>({\n name: 'fabis-ralph-loop',\n cwd,\n })\n\n if (!baseConfig || Object.keys(baseConfig).length === 0) {\n throw new Error('No fabis-ralph-loop config found. Run `fabis-ralph-loop init` to create one.')\n }\n\n const { config: overridesConfig } = await loadConfig<Partial<RalphLoopConfig>>({\n name: 'fabis-ralph-loop.overrides',\n cwd,\n })\n\n const merged =\n overridesConfig && Object.keys(overridesConfig).length > 0\n ? mergeConfigs(baseConfig, overridesConfig as RalphLoopConfig)\n : baseConfig\n\n const parsed = ralphLoopConfigSchema.safeParse(merged)\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => ` ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n')\n const suffix =\n overridesConfig && Object.keys(overridesConfig).length > 0 ? ' (after merging overrides)' : ''\n throw new Error(`Invalid fabis-ralph-loop config${suffix}:\\n${issues}`)\n }\n\n return applyPlaywrightDefaults(parsed.data)\n}\n"],"mappings":"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"}
|
|
@@ -76,7 +76,64 @@ Only add patterns that are **general and reusable**, not story-specific details.
|
|
|
76
76
|
- Use `import type { ... }` for type-only imports (separate from value imports)
|
|
77
77
|
<% if (openAppSkill || playwright) { %>
|
|
78
78
|
## Browser Testing
|
|
79
|
-
<% if (playwright) { %>
|
|
79
|
+
<% if (playwright === 'cli') { %>
|
|
80
|
+
This container has `@playwright/cli` installed with headless Chromium. **Do NOT use Playwright MCP or Chrome MCP tools — use the CLI instead.**
|
|
81
|
+
|
|
82
|
+
Run commands via:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
playwright-cli <command>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Essential Commands
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|---------|-------------|
|
|
92
|
+
| `open [url]` | Launch browser, optionally navigate to URL |
|
|
93
|
+
| `goto <url>` | Navigate to a URL |
|
|
94
|
+
| `snapshot` | Get page structure (preferred over screenshots) |
|
|
95
|
+
| `screenshot [ref]` | Capture visual screenshot |
|
|
96
|
+
| `click <ref>` | Click an element |
|
|
97
|
+
| `fill <ref> <text>` | Fill a text field |
|
|
98
|
+
| `type <text>` | Type into focused element |
|
|
99
|
+
| `hover <ref>` | Hover over element |
|
|
100
|
+
| `select <ref> <val>` | Choose dropdown option |
|
|
101
|
+
| `press <key>` | Press a keyboard key |
|
|
102
|
+
| `eval <func> [ref]` | Execute JavaScript on the page |
|
|
103
|
+
| `console [level]` | Display console messages |
|
|
104
|
+
| `network` | List HTTP requests since page load |
|
|
105
|
+
| `tab-list` | List open tabs |
|
|
106
|
+
| `tab-new [url]` | Open new tab |
|
|
107
|
+
| `tab-select <index>` | Switch tab |
|
|
108
|
+
| `close` | Close the page |
|
|
109
|
+
|
|
110
|
+
### Session Management
|
|
111
|
+
|
|
112
|
+
Use named sessions to keep browser state between commands:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
playwright-cli -s=mytest open http://localhost:3000
|
|
116
|
+
playwright-cli -s=mytest snapshot
|
|
117
|
+
playwright-cli -s=mytest click <ref>
|
|
118
|
+
playwright-cli -s=mytest close
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Interaction Pattern
|
|
122
|
+
|
|
123
|
+
1. `snapshot` — understand page structure, get element refs
|
|
124
|
+
2. Interact — `click`, `fill`, `type` using refs from snapshot
|
|
125
|
+
3. `snapshot` or `screenshot` — verify result
|
|
126
|
+
4. `console error` — check for errors
|
|
127
|
+
|
|
128
|
+
For any story that changes UI:
|
|
129
|
+
|
|
130
|
+
1. Ensure the dev servers are running before testing
|
|
131
|
+
2. Use `playwright-cli` to navigate and verify the changes
|
|
132
|
+
3. Capture a screenshot if helpful for the progress log
|
|
133
|
+
<% if (openAppSkill) { %>
|
|
134
|
+
Read `<%= openAppSkill %>` for the full app launch and authentication procedure.
|
|
135
|
+
<% } -%>
|
|
136
|
+
<% } else if (playwright === 'mcp') { %>
|
|
80
137
|
This container has **Playwright MCP** configured with headless Chromium. For any story that changes UI:
|
|
81
138
|
|
|
82
139
|
1. Ensure the dev servers are running before testing
|
|
@@ -92,13 +92,25 @@ Each story should be small enough to implement in one focused session.
|
|
|
92
92
|
- [ ] Specific verifiable criterion
|
|
93
93
|
- [ ] Another criterion
|
|
94
94
|
- [ ] Typecheck passes
|
|
95
|
+
<% if (playwright === 'cli') { -%>
|
|
96
|
+
- [ ] **[UI stories only]** Verify in browser using Playwright CLI
|
|
97
|
+
<% } else if (playwright === 'mcp') { -%>
|
|
95
98
|
- [ ] **[UI stories only]** Verify in browser using Playwright MCP tools
|
|
99
|
+
<% } else if (playwright) { -%>
|
|
100
|
+
- [ ] **[UI stories only]** Verify in browser
|
|
101
|
+
<% } -%>
|
|
96
102
|
```
|
|
97
103
|
|
|
98
104
|
**Important:**
|
|
99
105
|
|
|
100
106
|
- Acceptance criteria must be verifiable, not vague. "Works correctly" is bad. "Button shows confirmation dialog before deleting" is good.
|
|
107
|
+
<% if (playwright === 'cli') { -%>
|
|
108
|
+
- **For any story with UI changes:** Always include "Verify in browser using Playwright CLI" as acceptance criteria.
|
|
109
|
+
<% } else if (playwright === 'mcp') { -%>
|
|
101
110
|
- **For any story with UI changes:** Always include "Verify in browser using Playwright MCP tools" as acceptance criteria.
|
|
111
|
+
<% } else if (playwright) { -%>
|
|
112
|
+
- **For any story with UI changes:** Always include "Verify in browser" as acceptance criteria.
|
|
113
|
+
<% } -%>
|
|
102
114
|
|
|
103
115
|
### 4. Functional Requirements
|
|
104
116
|
|
|
@@ -113,13 +113,24 @@ For stories with testable logic, also include:
|
|
|
113
113
|
"Tests pass"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
<% if (playwright) { %>
|
|
117
|
+
|
|
116
118
|
### For stories that change UI, also include:
|
|
117
119
|
|
|
118
120
|
```
|
|
121
|
+
<% if (playwright === 'cli') { -%>
|
|
122
|
+
"Verify in browser using Playwright CLI"
|
|
123
|
+
<% } else { -%>
|
|
119
124
|
"Verify in browser using Playwright MCP tools"
|
|
125
|
+
<% } -%>
|
|
120
126
|
```
|
|
121
127
|
|
|
128
|
+
<% if (playwright === 'cli') { -%>
|
|
129
|
+
Frontend stories are NOT complete until visually verified. Ralph will use `playwright-cli` to navigate to the page, take snapshots/screenshots, and confirm changes work.
|
|
130
|
+
<% } else { -%>
|
|
122
131
|
Frontend stories are NOT complete until visually verified. Ralph will use Playwright MCP tools to navigate to the page, interact with the UI, and confirm changes work.
|
|
132
|
+
<% } -%>
|
|
133
|
+
<% } %>
|
|
123
134
|
|
|
124
135
|
---
|
|
125
136
|
|
|
@@ -200,8 +211,9 @@ Add ability to mark tasks with different statuses.
|
|
|
200
211
|
"acceptanceCriteria": [
|
|
201
212
|
"Each task card shows colored status badge",
|
|
202
213
|
"Badge colors: gray=pending, blue=in_progress, green=done",
|
|
203
|
-
"Typecheck passes"
|
|
204
|
-
"Verify in browser using Playwright
|
|
214
|
+
"Typecheck passes"<% if (playwright === 'cli') { %>,
|
|
215
|
+
"Verify in browser using Playwright CLI"<% } else if (playwright === 'mcp') { %>,
|
|
216
|
+
"Verify in browser using Playwright MCP tools"<% } %>
|
|
205
217
|
],
|
|
206
218
|
"priority": 2,
|
|
207
219
|
"passes": false,
|
|
@@ -234,6 +246,10 @@ Before writing `.ralph/prd.json`, verify:
|
|
|
234
246
|
- [ ] Each story is completable in one iteration (small enough)
|
|
235
247
|
- [ ] Stories are ordered by dependency (schema to backend to UI)
|
|
236
248
|
- [ ] Every story has "Typecheck passes" as criterion
|
|
249
|
+
<% if (playwright === 'cli') { -%>
|
|
250
|
+
- [ ] UI stories have "Verify in browser using Playwright CLI" as criterion
|
|
251
|
+
<% } else if (playwright === 'mcp') { -%>
|
|
237
252
|
- [ ] UI stories have "Verify in browser using Playwright MCP tools" as criterion
|
|
253
|
+
<% } -%>
|
|
238
254
|
- [ ] Acceptance criteria are verifiable (not vague)
|
|
239
255
|
- [ ] No story depends on a later story
|
|
@@ -88,21 +88,21 @@ backpressureCommands: [
|
|
|
88
88
|
|
|
89
89
|
Docker container configuration. Controls the environment Ralph runs in.
|
|
90
90
|
|
|
91
|
-
| Key | Type
|
|
92
|
-
| ---------------- |
|
|
93
|
-
| `name` | `string`
|
|
94
|
-
| `baseImage` | `string`
|
|
95
|
-
| `user` | `string`
|
|
96
|
-
| `systemPackages` | `string[]`
|
|
97
|
-
| `playwright` | `boolean`
|
|
98
|
-
| `networkMode` | `string`
|
|
99
|
-
| `env` | `Record<string, string>`
|
|
100
|
-
| `shmSize` | `string`
|
|
101
|
-
| `capabilities` | `string[]`
|
|
102
|
-
| `volumes` | `string[]`
|
|
103
|
-
| `shadowVolumes` | `string[]`
|
|
104
|
-
| `persistVolumes` | `Record<string, string>`
|
|
105
|
-
| `hooks` | `ContainerHooks`
|
|
91
|
+
| Key | Type | Default | Purpose |
|
|
92
|
+
| ---------------- | --------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `name` | `string` | `'ralph-container'` | Docker container name. Change if running multiple Ralph instances |
|
|
94
|
+
| `baseImage` | `string` | `'node:22-bookworm'` | Base Docker image. Use a different Node version or distro if needed |
|
|
95
|
+
| `user` | `string` | `'sandbox'` | Container user. Default creates `sandbox` (UID 1000). Set to existing user (e.g. `'node'`) to reuse it |
|
|
96
|
+
| `systemPackages` | `string[]` | `[]` | APT packages to install (e.g., `['postgresql-client', 'redis-tools']`) |
|
|
97
|
+
| `playwright` | `boolean \| 'cli' \| 'mcp'` | `false` | Enable Playwright browser testing. `true` or `'cli'` uses @playwright/cli (preferred). `'mcp'` uses Playwright MCP server. Auto-adds `SYS_ADMIN` capability and sets `shmSize` to `'2gb'` |
|
|
98
|
+
| `networkMode` | `string` | `'host'` | Docker network mode. Use `'bridge'` if host networking causes conflicts |
|
|
99
|
+
| `env` | `Record<string, string>` | `{}` | Environment variables injected into the container |
|
|
100
|
+
| `shmSize` | `string` | `'64m'` | Shared memory size. Auto-upgraded to `'2gb'` when `playwright` is enabled |
|
|
101
|
+
| `capabilities` | `string[]` | `[]` | Docker capabilities (e.g., `['SYS_ADMIN']`). Auto-added when `playwright` is enabled |
|
|
102
|
+
| `volumes` | `string[]` | `[]` | Additional Docker volume mounts (standard Docker `-v` syntax) |
|
|
103
|
+
| `shadowVolumes` | `string[]` | `[]` | Paths to exclude from the project mount using anonymous volumes (see below) |
|
|
104
|
+
| `persistVolumes` | `Record<string, string>` | `{ 'ralph-claude-config': '/home/sandbox/.claude' }` | Named volumes that persist across container restarts. Key = volume name, value = container path |
|
|
105
|
+
| `hooks` | `ContainerHooks` | `{}` | Dockerfile build hooks (see below) |
|
|
106
106
|
|
|
107
107
|
#### Shadow Volumes
|
|
108
108
|
|
|
@@ -161,7 +161,7 @@ container: {
|
|
|
161
161
|
**When to update `container`:**
|
|
162
162
|
|
|
163
163
|
- The project needs system-level dependencies → `systemPackages`
|
|
164
|
-
- The project uses Playwright or browser-based tests → `playwright: true`
|
|
164
|
+
- The project uses Playwright or browser-based tests → `playwright: true` (CLI mode, preferred) or `playwright: 'mcp'`
|
|
165
165
|
- Need API keys or secrets in the container → `env` (prefer secrets management over hardcoding)
|
|
166
166
|
- Running multiple Ralph instances side-by-side → change `name`
|
|
167
167
|
- Need to persist additional directories across runs → `persistVolumes`
|
|
@@ -266,7 +266,7 @@ backpressureCommands: [
|
|
|
266
266
|
|
|
267
267
|
```typescript
|
|
268
268
|
container: {
|
|
269
|
-
playwright: true,
|
|
269
|
+
playwright: true, // CLI mode (default, preferred) — or 'mcp' for Playwright MCP
|
|
270
270
|
}
|
|
271
271
|
```
|
|
272
272
|
|