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 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 MCP + headless Chromium
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;
@@ -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;;;;cAsDlB,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;;;iBC5D3B,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"}
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) {
@@ -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":"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"}
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 MCP tools"
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 | 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` | `false` | Enable Playwright browser testing. 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: true` |
101
- | `capabilities` | `string[]` | `[]` | Docker capabilities (e.g., `['SYS_ADMIN']`). Auto-added when `playwright: true` |
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) |
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabis-ralph-loop",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "CLI for setting up and running Claude Ralph autonomous coding loops in Docker containers",
5
5
  "repository": {
6
6
  "type": "git",