fabis-ralph-loop 0.1.0 → 1.0.2
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/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/generate.mjs +2 -1
- package/dist/generate.mjs.map +1 -1
- package/dist/{generators.mjs → gitignore.mjs} +20 -2
- package/dist/gitignore.mjs.map +1 -0
- package/dist/index.d.mts +74 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/dist/init.mjs +2 -1
- package/dist/init.mjs.map +1 -1
- package/dist/loader.mjs +41 -5
- package/dist/loader.mjs.map +1 -1
- package/dist/templates/Dockerfile.ejs +3 -2
- package/dist/uac-templates/skills/update-fabis-ralph-loop-config/SKILL.md +24 -0
- package/package.json +8 -3
- package/dist/generators.mjs.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kristaps Fabians Geikins
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# fabis-ralph-loop
|
|
2
|
+
|
|
3
|
+
CLI for setting up and running Claude Ralph autonomous coding loops in Docker containers.
|
|
4
|
+
|
|
5
|
+
Define a `fabis-ralph-loop.config.ts`, then use the CLI to generate Docker artifacts, manage the container lifecycle, and run iterative autonomous coding loops. Each iteration, a Claude agent picks the next user story from a PRD, implements it, runs quality checks, commits, and stops — then a fresh session picks up the next one.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add fabis-ralph-loop
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js >= 22.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. Scaffold config file and generate all files
|
|
19
|
+
npx fabis-ralph-loop init
|
|
20
|
+
|
|
21
|
+
# 2. Edit fabis-ralph-loop.config.ts to match your project
|
|
22
|
+
|
|
23
|
+
# 3. Regenerate files after config changes
|
|
24
|
+
npx fabis-ralph-loop generate
|
|
25
|
+
|
|
26
|
+
# 4. Start the container (auto-attaches a shell)
|
|
27
|
+
npx fabis-ralph-loop start
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Before starting the container, prepare a PRD using the generated skills in your IDE:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
/prd # describe your feature → .ralph/prd-feature.md
|
|
34
|
+
/ralph # convert PRD to JSON → .ralph/prd.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then inside the container, kick off the loop:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Run 20 iterations of the autonomous coding loop
|
|
41
|
+
run-fabis-ralph-loop 20
|
|
42
|
+
|
|
43
|
+
# Override the model
|
|
44
|
+
run-fabis-ralph-loop 20 --model opus
|
|
45
|
+
|
|
46
|
+
# Enable verbose progress output
|
|
47
|
+
run-fabis-ralph-loop 20 --verbose
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`run-fabis-ralph-loop` is a wrapper script installed in the container's PATH that pins the same CLI version as the host. Ctrl+C gracefully stops the current iteration; pressing it twice force-exits.
|
|
51
|
+
|
|
52
|
+
## How It Works
|
|
53
|
+
|
|
54
|
+
1. **Generate** — `fabis-ralph-loop generate` creates Docker artifacts (`.ralph-container/`), a prompt template (`ralph-prompt.md`), and AI skills
|
|
55
|
+
2. **Prepare a PRD** — Use `/prd` in your IDE to write a Product Requirements Document, then `/ralph` to convert it into `.ralph/prd.json`
|
|
56
|
+
3. **Start** — `fabis-ralph-loop start` builds the container image, starts it, runs the entrypoint (git safety, auth validation, direnv, setup hooks), then drops you into a shell
|
|
57
|
+
4. **Run the loop** — `run-fabis-ralph-loop <iterations>` feeds the prompt to the Claude agent each iteration. The agent reads the PRD, picks the highest-priority incomplete story, implements it, runs backpressure commands (lint, typecheck, tests), commits, and stops. The next iteration picks up the next story.
|
|
58
|
+
5. **Completion** — When all stories pass, the agent outputs a completion signal and the loop exits early
|
|
59
|
+
|
|
60
|
+
The container is sandboxed: git pushes are blocked, and each iteration runs in a fresh agent session so context doesn't leak between stories.
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
Create a `fabis-ralph-loop.config.ts` in your project root:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { defineConfig } from 'fabis-ralph-loop'
|
|
68
|
+
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
project: {
|
|
71
|
+
name: 'my-project',
|
|
72
|
+
description: 'What the project does',
|
|
73
|
+
context: 'Additional context for the AI agent',
|
|
74
|
+
backpressureCommands: [
|
|
75
|
+
{ name: 'typecheck', command: 'pnpm tsc --noEmit' },
|
|
76
|
+
{ name: 'lint', command: 'pnpm eslint .' },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
container: {
|
|
80
|
+
name: 'my-project-ralph',
|
|
81
|
+
playwright: true, // auto-configures Playwright MCP + headless Chromium
|
|
82
|
+
systemPackages: ['ripgrep'],
|
|
83
|
+
env: { NODE_ENV: 'development' },
|
|
84
|
+
hooks: {
|
|
85
|
+
rootSetup: ['apt-get install -y some-package'],
|
|
86
|
+
userSetup: ['npm install -g some-tool'],
|
|
87
|
+
entrypointSetup: ['pnpm install'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
defaults: {
|
|
91
|
+
model: 'sonnet',
|
|
92
|
+
sleepBetweenMs: 2000,
|
|
93
|
+
},
|
|
94
|
+
output: {
|
|
95
|
+
mode: 'direct', // or 'uac' for universal-ai-config integration
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Overrides Config
|
|
101
|
+
|
|
102
|
+
For environment-specific settings that shouldn't be committed (different models, debug flags, local API keys), create a `fabis-ralph-loop.overrides.config.ts`:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { defineOverridesConfig } from 'fabis-ralph-loop'
|
|
106
|
+
|
|
107
|
+
export default defineOverridesConfig({
|
|
108
|
+
defaults: {
|
|
109
|
+
model: 'opus',
|
|
110
|
+
verbose: true,
|
|
111
|
+
},
|
|
112
|
+
container: {
|
|
113
|
+
env: { DEBUG: 'true' },
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The overrides file is **gitignored automatically** by `fabis-ralph-loop init`. It gets deep-merged on top of the base config: objects merge recursively, arrays are replaced entirely, and scalars are overwritten.
|
|
119
|
+
|
|
120
|
+
## CLI Commands
|
|
121
|
+
|
|
122
|
+
All commands run on the **host machine** via `npx fabis-ralph-loop <command>`:
|
|
123
|
+
|
|
124
|
+
| Command | Description |
|
|
125
|
+
| ---------- | --------------------------------------------------------- |
|
|
126
|
+
| `init` | Scaffold config and generate all files |
|
|
127
|
+
| `generate` | Regenerate files from config |
|
|
128
|
+
| `start` | Build and start the container (attaches shell by default) |
|
|
129
|
+
| `stop` | Stop and remove the container |
|
|
130
|
+
| `restart` | Stop + start the container |
|
|
131
|
+
| `logs` | Follow container logs |
|
|
132
|
+
| `run <n>` | Execute _n_ loop iterations (can also run from host) |
|
|
133
|
+
| `exec` | Run an arbitrary command inside the container |
|
|
134
|
+
|
|
135
|
+
### Notable flags
|
|
136
|
+
|
|
137
|
+
- `generate --dry-run` — preview without writing files
|
|
138
|
+
- `generate --only <container|prompt|skills>` — generate a specific subset
|
|
139
|
+
- `start --no-attach` / `restart --no-attach` — don't attach a shell after starting
|
|
140
|
+
- `run --model <model>` — override the default model
|
|
141
|
+
- `run --verbose` — enable verbose progress output
|
|
142
|
+
|
|
143
|
+
### Inside the container
|
|
144
|
+
|
|
145
|
+
The container has `run-fabis-ralph-loop` on the PATH — this is the primary way to kick off loop iterations. It wraps `fabis-ralph-loop run` pinned to the same version as your host install.
|
|
146
|
+
|
|
147
|
+
## Generated Skills
|
|
148
|
+
|
|
149
|
+
`fabis-ralph-loop generate` seeds AI skills into your project that power the Ralph workflow. These are slash commands available to the Claude agent both inside the container and in your IDE:
|
|
150
|
+
|
|
151
|
+
- **`/prd`** — Generate a Product Requirements Document. Describe a feature, answer a few clarifying questions, and get a structured PRD saved to `.ralph/prd-<feature>.md`.
|
|
152
|
+
- **`/ralph`** — Convert a PRD into `.ralph/prd.json`, the structured format the loop consumes. Splits stories into iteration-sized chunks, orders by dependencies, and ensures each has verifiable acceptance criteria.
|
|
153
|
+
- **`/update-fabis-ralph-loop-config`** — Edit `fabis-ralph-loop.config.ts` without manually reading the schema. Useful for adding backpressure commands, container packages, env vars, or hooks.
|
|
154
|
+
|
|
155
|
+
### Typical workflow
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
# In your IDE (before starting the container):
|
|
159
|
+
/prd # describe your feature, answer questions → .ralph/prd-feature.md
|
|
160
|
+
/ralph # convert PRD to JSON → .ralph/prd.json
|
|
161
|
+
|
|
162
|
+
# Inside the container:
|
|
163
|
+
run-fabis-ralph-loop 20 # let the loop implement it
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## What Gets Generated
|
|
167
|
+
|
|
168
|
+
Running `generate` creates:
|
|
169
|
+
|
|
170
|
+
- **`.ralph-container/Dockerfile`** — container image with system packages, user setup, and optional Playwright
|
|
171
|
+
- **`.ralph-container/docker-compose.yml`** — compose config with volumes, networking, and environment
|
|
172
|
+
- **`.ralph-container/entrypoint.ts`** — container entrypoint that bootstraps the environment
|
|
173
|
+
- **`ralph-prompt.md`** — the prompt fed to the AI agent each iteration
|
|
174
|
+
- **Skills** — AI tool skills for PRD creation and Ralph workflow (output location depends on `output.mode`)
|
|
175
|
+
|
|
176
|
+
## Programmatic API
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { loadRalphConfig, generateAll, defineConfig } from 'fabis-ralph-loop'
|
|
180
|
+
import type { RalphLoopConfig, ResolvedConfig } from 'fabis-ralph-loop'
|
|
181
|
+
|
|
182
|
+
const { config, projectRoot } = await loadRalphConfig()
|
|
183
|
+
await generateAll(config, projectRoot)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|
package/dist/generate.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as loadRalphConfig } from "./loader.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { n as generateAll, t as ensureGitignoreBlock } from "./gitignore.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
|
|
5
5
|
//#region src/commands/generate.ts
|
|
@@ -21,6 +21,7 @@ var generate_default = defineCommand({
|
|
|
21
21
|
},
|
|
22
22
|
async run({ args }) {
|
|
23
23
|
const config = await loadRalphConfig();
|
|
24
|
+
if (!args["dry-run"]) await ensureGitignoreBlock();
|
|
24
25
|
const only = args.only;
|
|
25
26
|
await generateAll(config, process.cwd(), {
|
|
26
27
|
dryRun: args["dry-run"],
|
package/dist/generate.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.mjs","names":[],"sources":["../src/commands/generate.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\n\nexport default defineCommand({\n meta: {\n name: 'generate',\n description: 'Regenerate all files from config (idempotent)',\n },\n args: {\n 'dry-run': {\n type: 'boolean',\n description: 'Preview what would be generated',\n default: false,\n },\n only: {\n type: 'string',\n description: 'Only generate specific type: container|prompt|skills',\n },\n },\n async run({ args }) {\n const config = await loadRalphConfig()\n const only = args.only as 'container' | 'prompt' | 'skills' | undefined\n await generateAll(config, process.cwd(), {\n dryRun: args['dry-run'],\n only,\n })\n },\n})\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"generate.mjs","names":[],"sources":["../src/commands/generate.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\nimport { ensureGitignoreBlock } from '../utils/gitignore.js'\n\nexport default defineCommand({\n meta: {\n name: 'generate',\n description: 'Regenerate all files from config (idempotent)',\n },\n args: {\n 'dry-run': {\n type: 'boolean',\n description: 'Preview what would be generated',\n default: false,\n },\n only: {\n type: 'string',\n description: 'Only generate specific type: container|prompt|skills',\n },\n },\n async run({ args }) {\n const config = await loadRalphConfig()\n if (!args['dry-run']) {\n await ensureGitignoreBlock()\n }\n const only = args.only as 'container' | 'prompt' | 'skills' | undefined\n await generateAll(config, process.cwd(), {\n dryRun: args['dry-run'],\n only,\n })\n },\n})\n"],"mappings":";;;;;AAKA,uBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,WAAW;GACT,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,SAAS,MAAM,iBAAiB;AACtC,MAAI,CAAC,KAAK,WACR,OAAM,sBAAsB;EAE9B,MAAM,OAAO,KAAK;AAClB,QAAM,YAAY,QAAQ,QAAQ,KAAK,EAAE;GACvC,QAAQ,KAAK;GACb;GACD,CAAC;;CAEL,CAAC"}
|
|
@@ -218,5 +218,23 @@ async function generateAll(config, projectRoot, options = {}) {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
//#endregion
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
//#region src/utils/gitignore.ts
|
|
222
|
+
const MARKER_START = "# >>> fabis-ralph-loop >>>";
|
|
223
|
+
const GITIGNORE_BLOCK = `${MARKER_START}
|
|
224
|
+
/fabis-ralph-loop.overrides.*
|
|
225
|
+
# <<< fabis-ralph-loop <<<`;
|
|
226
|
+
/**
|
|
227
|
+
* Idempotently add fabis-ralph-loop gitignore entries to .gitignore.
|
|
228
|
+
* Uses marker comments to detect existing blocks and avoid duplication.
|
|
229
|
+
*/
|
|
230
|
+
async function ensureGitignoreBlock(cwd = process.cwd()) {
|
|
231
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
232
|
+
let content = "";
|
|
233
|
+
if (existsSync(gitignorePath)) content = await readFile(gitignorePath, "utf8");
|
|
234
|
+
if (content.includes(MARKER_START)) return;
|
|
235
|
+
await writeFile(gitignorePath, content.trimEnd() ? `${content.trimEnd()}\n\n${GITIGNORE_BLOCK}\n` : `${GITIGNORE_BLOCK}\n`, "utf8");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
export { generateAll as n, ensureGitignoreBlock as t };
|
|
240
|
+
//# sourceMappingURL=gitignore.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.mjs","names":[],"sources":["../src/utils/template.ts","../src/utils/version.ts","../src/generators/dockerfile.ts","../src/generators/compose.ts","../src/generators/entrypoint.ts","../src/generators/prompt.ts","../src/generators/skills.ts","../src/generators/index.ts","../src/utils/gitignore.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nimport ejs from 'ejs'\n\n/**\n * Resolve a bundled asset directory (templates, static, uac-templates).\n * In dist (flat layout): assets are siblings of the compiled files.\n * In src (nested layout via tsx): assets are siblings of the parent dir.\n */\nexport function resolveAssetDir(assetName: string, metaUrl: string): string {\n const dir = dirname(fileURLToPath(metaUrl))\n const sibling = join(dir, assetName)\n if (existsSync(sibling)) return sibling\n return join(dir, '..', assetName)\n}\n\nconst TEMPLATES_DIR = resolveAssetDir('templates', import.meta.url)\n\nexport async function renderTemplate(\n templateName: string,\n data: Record<string, unknown>,\n): Promise<string> {\n const templatePath = join(TEMPLATES_DIR, templateName)\n const template = await readFile(templatePath, 'utf8')\n return ejs.render(template, data, { async: false }) as string\n}\n\nexport const GENERATED_HEADER = `# Generated by fabis-ralph-loop — DO NOT EDIT MANUALLY\n# Regenerate with: npx fabis-ralph-loop generate\n`\n","import { createRequire } from 'node:module'\n\nexport function getPackageVersion(): string {\n try {\n const require = createRequire(import.meta.url)\n const pkg = require('../../package.json') as { version: string }\n return pkg.version\n } catch {\n return 'latest'\n }\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport { getPackageVersion } from '../utils/version.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\n/**\n * Detect whether the base image already includes Node.js.\n */\nfunction isNodeBaseImage(baseImage: string): boolean {\n return /^node[:/]/i.test(baseImage)\n}\n\nexport async function generateDockerfile(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('Dockerfile.ejs', {\n generatedHeader: GENERATED_HEADER,\n baseImage: config.container.baseImage,\n systemPackages: config.container.systemPackages,\n installNode: !isNodeBaseImage(config.container.baseImage),\n playwright: config.container.playwright,\n hooks: config.container.hooks,\n user,\n createUser: user === 'sandbox',\n homeDir: `/home/${user}`,\n packageVersion: getPackageVersion(),\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateCompose(config: ResolvedConfig): Promise<string> {\n const homeDir = `/home/${config.container.user}`\n\n // Ensure .claude config is always persisted with the correct home dir\n const persistVolumes: Record<string, string> = {\n 'ralph-claude-config': `${homeDir}/.claude`,\n ...Object.fromEntries(\n Object.entries(config.container.persistVolumes).map(([name, path]) => [\n name,\n path.replace('/home/sandbox', homeDir),\n ]),\n ),\n }\n\n return renderTemplate('docker-compose.yml.ejs', {\n generatedHeader: GENERATED_HEADER,\n containerName: config.container.name,\n shmSize: config.container.shmSize,\n networkMode: config.container.networkMode,\n capabilities: config.container.capabilities,\n shadowVolumes: config.container.shadowVolumes,\n persistVolumes,\n extraVolumes: config.container.volumes,\n env: config.container.env,\n homeDir,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateEntrypoint(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('entrypoint.ts.ejs', {\n generatedHeader: GENERATED_HEADER.replace(/^# /gm, '// '),\n agent: config.defaults.agent,\n shadowVolumes: config.container.shadowVolumes,\n entrypointSetup: config.container.hooks.entrypointSetup,\n user,\n homeDir: `/home/${user}`,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generatePrompt(config: ResolvedConfig): Promise<string> {\n return renderTemplate('ralph-prompt.md.ejs', {\n generatedHeader: GENERATED_HEADER,\n projectName: config.project.name,\n projectDescription: config.project.description,\n projectContext: config.project.context,\n backpressureCommands: config.project.backpressureCommands,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n completionSignal: config.defaults.completionSignal,\n })\n}\n","import { readdir, readFile, writeFile, mkdir, rm } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport ejs from 'ejs'\nimport { consola } from 'consola'\nimport { generate, writeGeneratedFiles } from 'universal-ai-config'\nimport type { ResolvedConfig } from '../config/schema.js'\nimport { resolveAssetDir } from '../utils/template.js'\n\nconst UAC_TEMPLATES_DIR = resolveAssetDir('uac-templates', import.meta.url)\n\nfunction buildLevel1Variables(config: ResolvedConfig): Record<string, unknown> {\n return {\n backpressureCommands: config.project.backpressureCommands,\n projectName: config.project.name,\n projectContext: config.project.context,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n config,\n }\n}\n\nasync function discoverSkills(): Promise<string[]> {\n const skillsDir = join(UAC_TEMPLATES_DIR, 'skills')\n const entries = await readdir(skillsDir, { withFileTypes: true })\n return entries.filter((e) => e.isDirectory()).map((e) => e.name)\n}\n\nexport async function generateSkills(config: ResolvedConfig, projectRoot: string): Promise<void> {\n if (config.output.mode === 'direct') {\n await generateDirect(config, projectRoot)\n } else {\n await generateUac(config, projectRoot)\n }\n}\n\nasync function generateDirect(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n\n // Render Level 1 EJS and write to a temp dir structured as UAC templates\n const tempDir = join(tmpdir(), `ralph-skills-${Date.now()}`)\n\n try {\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(tempDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n }\n\n // Use UAC's generate() API for the second pass (handles Level 2 EJS + frontmatter mapping)\n const files = await generate({\n root: projectRoot,\n targets: ['claude'],\n types: ['skills'],\n overrides: { templatesDir: tempDir },\n })\n\n await writeGeneratedFiles(files, projectRoot)\n consola.info(`Generated ${files.length} skill file(s)`)\n } finally {\n await rm(tempDir, { recursive: true, force: true })\n }\n}\n\nasync function generateUac(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n let count = 0\n\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n // Render Level 1 EJS — Level 2 <%% %> becomes <% %> in output\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(projectRoot, config.output.uacTemplatesDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n count++\n }\n\n consola.info(`Generated ${count} skill template(s) to ${config.output.uacTemplatesDir}/skills/`)\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { consola } from 'consola'\nimport { generateDockerfile } from './dockerfile.js'\nimport { generateCompose } from './compose.js'\nimport { generateEntrypoint } from './entrypoint.js'\nimport { generatePrompt } from './prompt.js'\nimport { generateSkills } from './skills.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\ninterface GenerateOptions {\n dryRun?: boolean\n only?: 'container' | 'prompt' | 'skills'\n}\n\ninterface GeneratedFile {\n path: string\n content: string\n}\n\nexport async function generateAll(\n config: ResolvedConfig,\n projectRoot: string,\n options: GenerateOptions = {},\n): Promise<GeneratedFile[]> {\n const files: GeneratedFile[] = []\n\n if (!options.only || options.only === 'container') {\n const containerDir = join(projectRoot, '.ralph-container')\n await mkdir(containerDir, { recursive: true })\n\n const dockerfile = await generateDockerfile(config)\n files.push({ path: join('.ralph-container', 'Dockerfile'), content: dockerfile })\n\n const entrypoint = await generateEntrypoint(config)\n files.push({ path: join('.ralph-container', 'entrypoint.ts'), content: entrypoint })\n\n const compose = await generateCompose(config)\n files.push({ path: join('.ralph-container', 'docker-compose.yml'), content: compose })\n }\n\n if (!options.only || options.only === 'prompt') {\n const prompt = await generatePrompt(config)\n files.push({ path: join('.ralph-container', 'ralph-prompt.md'), content: prompt })\n }\n\n if (options.dryRun) {\n for (const file of files) {\n consola.info(`[dry-run] Would write: ${file.path}`)\n }\n } else {\n for (const file of files) {\n const fullPath = join(projectRoot, file.path)\n await mkdir(join(fullPath, '..'), { recursive: true })\n await writeFile(fullPath, file.content, 'utf8')\n consola.success(`Written: ${file.path}`)\n }\n }\n\n if (!options.only || options.only === 'skills') {\n if (options.dryRun) {\n consola.info('[dry-run] Would generate skills')\n } else {\n await generateSkills(config, projectRoot)\n }\n }\n\n return files\n}\n","import { readFile, writeFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nconst MARKER_START = '# >>> fabis-ralph-loop >>>'\nconst MARKER_END = '# <<< fabis-ralph-loop <<<'\n\nconst GITIGNORE_BLOCK = `${MARKER_START}\n/fabis-ralph-loop.overrides.*\n${MARKER_END}`\n\n/**\n * Idempotently add fabis-ralph-loop gitignore entries to .gitignore.\n * Uses marker comments to detect existing blocks and avoid duplication.\n */\nexport async function ensureGitignoreBlock(cwd: string = process.cwd()): Promise<void> {\n const gitignorePath = join(cwd, '.gitignore')\n\n let content = ''\n if (existsSync(gitignorePath)) {\n content = await readFile(gitignorePath, 'utf8')\n }\n\n if (content.includes(MARKER_START)) return\n\n const newContent = content.trimEnd()\n ? `${content.trimEnd()}\\n\\n${GITIGNORE_BLOCK}\\n`\n : `${GITIGNORE_BLOCK}\\n`\n\n await writeFile(gitignorePath, newContent, 'utf8')\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAWA,SAAgB,gBAAgB,WAAmB,SAAyB;CAC1E,MAAM,MAAM,QAAQ,cAAc,QAAQ,CAAC;CAC3C,MAAM,UAAU,KAAK,KAAK,UAAU;AACpC,KAAI,WAAW,QAAQ,CAAE,QAAO;AAChC,QAAO,KAAK,KAAK,MAAM,UAAU;;AAGnC,MAAM,gBAAgB,gBAAgB,aAAa,OAAO,KAAK,IAAI;AAEnE,eAAsB,eACpB,cACA,MACiB;CAEjB,MAAM,WAAW,MAAM,SADF,KAAK,eAAe,aAAa,EACR,OAAO;AACrD,QAAO,IAAI,OAAO,UAAU,MAAM,EAAE,OAAO,OAAO,CAAC;;AAGrD,MAAa,mBAAmB;;;;;;AC3BhC,SAAgB,oBAA4B;AAC1C,KAAI;AAGF,SAFgB,cAAc,OAAO,KAAK,IAAI,CAC1B,qBAAqB,CAC9B;SACL;AACN,SAAO;;;;;;;;;ACDX,SAAS,gBAAgB,WAA4B;AACnD,QAAO,aAAa,KAAK,UAAU;;AAGrC,eAAsB,mBAAmB,QAAyC;CAChF,MAAM,OAAO,OAAO,UAAU;AAC9B,QAAO,eAAe,kBAAkB;EACtC,iBAAiB;EACjB,WAAW,OAAO,UAAU;EAC5B,gBAAgB,OAAO,UAAU;EACjC,aAAa,CAAC,gBAAgB,OAAO,UAAU,UAAU;EACzD,YAAY,OAAO,UAAU;EAC7B,OAAO,OAAO,UAAU;EACxB;EACA,YAAY,SAAS;EACrB,SAAS,SAAS;EAClB,gBAAgB,mBAAmB;EACpC,CAAC;;;;;ACrBJ,eAAsB,gBAAgB,QAAyC;CAC7E,MAAM,UAAU,SAAS,OAAO,UAAU;CAG1C,MAAM,iBAAyC;EAC7C,uBAAuB,GAAG,QAAQ;EAClC,GAAG,OAAO,YACR,OAAO,QAAQ,OAAO,UAAU,eAAe,CAAC,KAAK,CAAC,MAAM,UAAU,CACpE,MACA,KAAK,QAAQ,iBAAiB,QAAQ,CACvC,CAAC,CACH;EACF;AAED,QAAO,eAAe,0BAA0B;EAC9C,iBAAiB;EACjB,eAAe,OAAO,UAAU;EAChC,SAAS,OAAO,UAAU;EAC1B,aAAa,OAAO,UAAU;EAC9B,cAAc,OAAO,UAAU;EAC/B,eAAe,OAAO,UAAU;EAChC;EACA,cAAc,OAAO,UAAU;EAC/B,KAAK,OAAO,UAAU;EACtB;EACD,CAAC;;;;;ACzBJ,eAAsB,mBAAmB,QAAyC;CAChF,MAAM,OAAO,OAAO,UAAU;AAC9B,QAAO,eAAe,qBAAqB;EACzC,iBAAiB,iBAAiB,QAAQ,SAAS,MAAM;EACzD,OAAO,OAAO,SAAS;EACvB,eAAe,OAAO,UAAU;EAChC,iBAAiB,OAAO,UAAU,MAAM;EACxC;EACA,SAAS,SAAS;EACnB,CAAC;;;;;ACTJ,eAAsB,eAAe,QAAyC;AAC5E,QAAO,eAAe,uBAAuB;EAC3C,iBAAiB;EACjB,aAAa,OAAO,QAAQ;EAC5B,oBAAoB,OAAO,QAAQ;EACnC,gBAAgB,OAAO,QAAQ;EAC/B,sBAAsB,OAAO,QAAQ;EACrC,cAAc,OAAO,QAAQ;EAC7B,YAAY,OAAO,UAAU;EAC7B,kBAAkB,OAAO,SAAS;EACnC,CAAC;;;;;ACJJ,MAAM,oBAAoB,gBAAgB,iBAAiB,OAAO,KAAK,IAAI;AAE3E,SAAS,qBAAqB,QAAiD;AAC7E,QAAO;EACL,sBAAsB,OAAO,QAAQ;EACrC,aAAa,OAAO,QAAQ;EAC5B,gBAAgB,OAAO,QAAQ;EAC/B,cAAc,OAAO,QAAQ;EAC7B,YAAY,OAAO,UAAU;EAC7B;EACD;;AAGH,eAAe,iBAAoC;AAGjD,SADgB,MAAM,QADJ,KAAK,mBAAmB,SAAS,EACV,EAAE,eAAe,MAAM,CAAC,EAClD,QAAQ,MAAM,EAAE,aAAa,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK;;AAGlE,eAAsB,eAAe,QAAwB,aAAoC;AAC/F,KAAI,OAAO,OAAO,SAAS,SACzB,OAAM,eAAe,QAAQ,YAAY;KAEzC,OAAM,YAAY,QAAQ,YAAY;;AAI1C,eAAe,eAAe,QAAwB,aAAoC;CACxF,MAAM,YAAY,qBAAqB,OAAO;CAC9C,MAAM,SAAS,MAAM,gBAAgB;CAGrC,MAAM,UAAU,KAAK,QAAQ,EAAE,gBAAgB,KAAK,KAAK,GAAG;AAE5D,KAAI;AACF,OAAK,MAAM,SAAS,QAAQ;GAE1B,MAAM,WAAW,MAAM,SADF,KAAK,mBAAmB,UAAU,OAAO,WAAW,EAC3B,OAAO;GACrD,MAAM,WAAW,IAAI,OAAO,UAAU,UAAU;GAEhD,MAAM,SAAS,KAAK,SAAS,UAAU,MAAM;AAC7C,SAAM,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;AACxC,SAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,UAAU,OAAO;;EAI7D,MAAM,QAAQ,MAAM,SAAS;GAC3B,MAAM;GACN,SAAS,CAAC,SAAS;GACnB,OAAO,CAAC,SAAS;GACjB,WAAW,EAAE,cAAc,SAAS;GACrC,CAAC;AAEF,QAAM,oBAAoB,OAAO,YAAY;AAC7C,UAAQ,KAAK,aAAa,MAAM,OAAO,gBAAgB;WAC/C;AACR,QAAM,GAAG,SAAS;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;;AAIvD,eAAe,YAAY,QAAwB,aAAoC;CACrF,MAAM,YAAY,qBAAqB,OAAO;CAC9C,MAAM,SAAS,MAAM,gBAAgB;CACrC,IAAI,QAAQ;AAEZ,MAAK,MAAM,SAAS,QAAQ;EAE1B,MAAM,WAAW,MAAM,SADF,KAAK,mBAAmB,UAAU,OAAO,WAAW,EAC3B,OAAO;EAErD,MAAM,WAAW,IAAI,OAAO,UAAU,UAAU;EAEhD,MAAM,SAAS,KAAK,aAAa,OAAO,OAAO,iBAAiB,UAAU,MAAM;AAChF,QAAM,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;AACxC,QAAM,UAAU,KAAK,QAAQ,WAAW,EAAE,UAAU,OAAO;AAC3D;;AAGF,SAAQ,KAAK,aAAa,MAAM,wBAAwB,OAAO,OAAO,gBAAgB,UAAU;;;;;AClElG,eAAsB,YACpB,QACA,aACA,UAA2B,EAAE,EACH;CAC1B,MAAM,QAAyB,EAAE;AAEjC,KAAI,CAAC,QAAQ,QAAQ,QAAQ,SAAS,aAAa;AAEjD,QAAM,MADe,KAAK,aAAa,mBAAmB,EAChC,EAAE,WAAW,MAAM,CAAC;EAE9C,MAAM,aAAa,MAAM,mBAAmB,OAAO;AACnD,QAAM,KAAK;GAAE,MAAM,KAAK,oBAAoB,aAAa;GAAE,SAAS;GAAY,CAAC;EAEjF,MAAM,aAAa,MAAM,mBAAmB,OAAO;AACnD,QAAM,KAAK;GAAE,MAAM,KAAK,oBAAoB,gBAAgB;GAAE,SAAS;GAAY,CAAC;EAEpF,MAAM,UAAU,MAAM,gBAAgB,OAAO;AAC7C,QAAM,KAAK;GAAE,MAAM,KAAK,oBAAoB,qBAAqB;GAAE,SAAS;GAAS,CAAC;;AAGxF,KAAI,CAAC,QAAQ,QAAQ,QAAQ,SAAS,UAAU;EAC9C,MAAM,SAAS,MAAM,eAAe,OAAO;AAC3C,QAAM,KAAK;GAAE,MAAM,KAAK,oBAAoB,kBAAkB;GAAE,SAAS;GAAQ,CAAC;;AAGpF,KAAI,QAAQ,OACV,MAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK,0BAA0B,KAAK,OAAO;KAGrD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,KAAK,aAAa,KAAK,KAAK;AAC7C,QAAM,MAAM,KAAK,UAAU,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AACtD,QAAM,UAAU,UAAU,KAAK,SAAS,OAAO;AAC/C,UAAQ,QAAQ,YAAY,KAAK,OAAO;;AAI5C,KAAI,CAAC,QAAQ,QAAQ,QAAQ,SAAS,SACpC,KAAI,QAAQ,OACV,SAAQ,KAAK,kCAAkC;KAE/C,OAAM,eAAe,QAAQ,YAAY;AAI7C,QAAO;;;;;AC/DT,MAAM,eAAe;AAGrB,MAAM,kBAAkB,GAAG,aAAa;;;;;;;AAQxC,eAAsB,qBAAqB,MAAc,QAAQ,KAAK,EAAiB;CACrF,MAAM,gBAAgB,KAAK,KAAK,aAAa;CAE7C,IAAI,UAAU;AACd,KAAI,WAAW,cAAc,CAC3B,WAAU,MAAM,SAAS,eAAe,OAAO;AAGjD,KAAI,QAAQ,SAAS,aAAa,CAAE;AAMpC,OAAM,UAAU,eAJG,QAAQ,SAAS,GAChC,GAAG,QAAQ,SAAS,CAAC,MAAM,gBAAgB,MAC3C,GAAG,gBAAgB,KAEoB,OAAO"}
|
package/dist/index.d.mts
CHANGED
|
@@ -71,7 +71,27 @@ interface GeneratedFile {
|
|
|
71
71
|
}
|
|
72
72
|
declare function generateAll(config: ResolvedConfig, projectRoot: string, options?: GenerateOptions): Promise<GeneratedFile[]>;
|
|
73
73
|
//#endregion
|
|
74
|
+
//#region src/config/merge.d.ts
|
|
75
|
+
/**
|
|
76
|
+
* Deep merge two config objects.
|
|
77
|
+
*
|
|
78
|
+
* Merge strategy:
|
|
79
|
+
* - Arrays: overrides REPLACE base arrays entirely
|
|
80
|
+
* - Plain objects: merge recursively
|
|
81
|
+
* - Scalars: overrides replace base values
|
|
82
|
+
* - undefined values in overrides are skipped
|
|
83
|
+
*/
|
|
84
|
+
declare function mergeConfigs<T extends Record<string, unknown>>(base: T, overrides: Record<string, unknown>): T;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/utils/gitignore.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* Idempotently add fabis-ralph-loop gitignore entries to .gitignore.
|
|
89
|
+
* Uses marker comments to detect existing blocks and avoid duplication.
|
|
90
|
+
*/
|
|
91
|
+
declare function ensureGitignoreBlock(cwd?: string): Promise<void>;
|
|
92
|
+
//#endregion
|
|
74
93
|
//#region src/index.d.ts
|
|
94
|
+
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
|
75
95
|
/**
|
|
76
96
|
* Helper for defining a typed ralph-loop config.
|
|
77
97
|
*/
|
|
@@ -120,6 +140,59 @@ declare function defineConfig(config: RalphLoopConfig): {
|
|
|
120
140
|
uacTemplatesDir?: string | undefined;
|
|
121
141
|
} | undefined;
|
|
122
142
|
};
|
|
143
|
+
/**
|
|
144
|
+
* Helper for defining a typed ralph-loop overrides config.
|
|
145
|
+
* All fields are optional — only specify what you want to override.
|
|
146
|
+
*/
|
|
147
|
+
declare function defineOverridesConfig(config: DeepPartial<RalphLoopConfig>): {
|
|
148
|
+
project?: {
|
|
149
|
+
name?: string | undefined;
|
|
150
|
+
description?: string | undefined;
|
|
151
|
+
context?: string | undefined;
|
|
152
|
+
backpressureCommands?: ({
|
|
153
|
+
name?: string | undefined;
|
|
154
|
+
command?: string | undefined;
|
|
155
|
+
} | undefined)[] | undefined;
|
|
156
|
+
openAppSkill?: string | undefined;
|
|
157
|
+
} | undefined;
|
|
158
|
+
container?: {
|
|
159
|
+
name?: string | undefined;
|
|
160
|
+
baseImage?: string | undefined;
|
|
161
|
+
user?: string | undefined;
|
|
162
|
+
systemPackages?: (string | undefined)[] | undefined;
|
|
163
|
+
playwright?: boolean | undefined;
|
|
164
|
+
networkMode?: string | undefined;
|
|
165
|
+
env?: {
|
|
166
|
+
[x: string]: string | undefined;
|
|
167
|
+
} | undefined;
|
|
168
|
+
shmSize?: string | undefined;
|
|
169
|
+
capabilities?: (string | undefined)[] | undefined;
|
|
170
|
+
volumes?: (string | undefined)[] | undefined;
|
|
171
|
+
shadowVolumes?: (string | undefined)[] | undefined;
|
|
172
|
+
persistVolumes?: {
|
|
173
|
+
[x: string]: string | undefined;
|
|
174
|
+
} | undefined;
|
|
175
|
+
hooks?: {
|
|
176
|
+
rootSetup?: (string | undefined)[] | undefined;
|
|
177
|
+
userSetup?: (string | undefined)[] | undefined;
|
|
178
|
+
entrypointSetup?: (string | undefined)[] | undefined;
|
|
179
|
+
} | undefined;
|
|
180
|
+
} | undefined;
|
|
181
|
+
setup?: {
|
|
182
|
+
preStartCommand?: string | undefined;
|
|
183
|
+
} | undefined;
|
|
184
|
+
defaults?: {
|
|
185
|
+
agent?: "claude" | undefined;
|
|
186
|
+
model?: string | undefined;
|
|
187
|
+
verbose?: boolean | undefined;
|
|
188
|
+
sleepBetweenMs?: number | undefined;
|
|
189
|
+
completionSignal?: string | undefined;
|
|
190
|
+
} | undefined;
|
|
191
|
+
output?: {
|
|
192
|
+
mode?: "direct" | "uac" | undefined;
|
|
193
|
+
uacTemplatesDir?: string | undefined;
|
|
194
|
+
} | undefined;
|
|
195
|
+
};
|
|
123
196
|
//#endregion
|
|
124
|
-
export { type BackpressureCommand, type RalphLoopConfig, type ResolvedConfig, defineConfig, generateAll, loadRalphConfig, ralphLoopConfigSchema };
|
|
197
|
+
export { type BackpressureCommand, DeepPartial, type RalphLoopConfig, type ResolvedConfig, defineConfig, defineOverridesConfig, ensureGitignoreBlock, generateAll, loadRalphConfig, mergeConfigs, ralphLoopConfigSchema };
|
|
125
198
|
//# sourceMappingURL=index.d.mts.map
|
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/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;;;
|
|
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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as ralphLoopConfigSchema, t as loadRalphConfig } from "./loader.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { n as mergeConfigs, r as ralphLoopConfigSchema, t as loadRalphConfig } from "./loader.mjs";
|
|
2
|
+
import { n as generateAll, t as ensureGitignoreBlock } from "./gitignore.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/index.ts
|
|
5
5
|
/**
|
|
@@ -8,7 +8,14 @@ import { t as generateAll } from "./generators.mjs";
|
|
|
8
8
|
function defineConfig(config) {
|
|
9
9
|
return config;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Helper for defining a typed ralph-loop overrides config.
|
|
13
|
+
* All fields are optional — only specify what you want to override.
|
|
14
|
+
*/
|
|
15
|
+
function defineOverridesConfig(config) {
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
//#endregion
|
|
13
|
-
export { defineConfig, generateAll, loadRalphConfig, ralphLoopConfigSchema };
|
|
20
|
+
export { defineConfig, defineOverridesConfig, ensureGitignoreBlock, generateAll, loadRalphConfig, mergeConfigs, ralphLoopConfigSchema };
|
|
14
21
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export { ralphLoopConfigSchema } from './config/schema.js'\nexport type { RalphLoopConfig, ResolvedConfig, BackpressureCommand } from './config/schema.js'\nexport { loadRalphConfig } from './config/loader.js'\nexport { generateAll } from './generators/index.js'\n\n/**\n * Helper for defining a typed ralph-loop config.\n */\nexport function defineConfig(config: import('./config/schema.js').RalphLoopConfig) {\n return config\n}\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export { ralphLoopConfigSchema } from './config/schema.js'\nexport type { RalphLoopConfig, ResolvedConfig, BackpressureCommand } from './config/schema.js'\nexport { loadRalphConfig } from './config/loader.js'\nexport { generateAll } from './generators/index.js'\nexport { mergeConfigs } from './config/merge.js'\nexport { ensureGitignoreBlock } from './utils/gitignore.js'\n\nexport type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T\n\n/**\n * Helper for defining a typed ralph-loop config.\n */\nexport function defineConfig(config: import('./config/schema.js').RalphLoopConfig) {\n return config\n}\n\n/**\n * Helper for defining a typed ralph-loop overrides config.\n * All fields are optional — only specify what you want to override.\n */\nexport function defineOverridesConfig(\n config: DeepPartial<import('./config/schema.js').RalphLoopConfig>,\n) {\n return config\n}\n"],"mappings":";;;;;;;AAYA,SAAgB,aAAa,QAAsD;AACjF,QAAO;;;;;;AAOT,SAAgB,sBACd,QACA;AACA,QAAO"}
|
package/dist/init.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as loadRalphConfig } from "./loader.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { n as generateAll, t as ensureGitignoreBlock } from "./gitignore.mjs";
|
|
3
3
|
import { writeFile } from "node:fs/promises";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
@@ -45,6 +45,7 @@ var init_default = defineCommand({
|
|
|
45
45
|
await writeFile(configPath, SAMPLE_CONFIG, "utf8");
|
|
46
46
|
consola.success(`Created ${configPath}`);
|
|
47
47
|
}
|
|
48
|
+
await ensureGitignoreBlock();
|
|
48
49
|
await generateAll(await loadRalphConfig(), process.cwd());
|
|
49
50
|
consola.success("Init complete. Edit fabis-ralph-loop.config.ts and run `fabis-ralph-loop generate` to regenerate.");
|
|
50
51
|
}
|
package/dist/init.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["../src/commands/init.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { writeFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { consola } from 'consola'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'fabis-ralph-loop'\n\nexport default defineConfig({\n container: {\n name: 'my-ralph-container',\n baseImage: 'node:22-bookworm',\n // playwright: true,\n // shadowVolumes: ['/workspace/node_modules'],\n hooks: {\n rootSetup: [\n // 'RUN npm install -g pnpm@10',\n ],\n userSetup: [\n // 'RUN corepack enable',\n ],\n },\n },\n project: {\n name: 'My Project',\n description: '',\n context: '- **Monorepo** managed with npm\\\\n- **TypeScript strict mode** everywhere',\n },\n output: {\n mode: 'direct',\n },\n})\n`\n\nexport default defineCommand({\n meta: {\n name: 'init',\n description: 'Scaffold ralph-loop config and generate all files',\n },\n async run() {\n const configPath = 'fabis-ralph-loop.config.ts'\n\n if (existsSync(configPath)) {\n consola.warn(`${configPath} already exists. Regenerating files from existing config.`)\n } else {\n await writeFile(configPath, SAMPLE_CONFIG, 'utf8')\n consola.success(`Created ${configPath}`)\n }\n\n // Load config and generate\n const config = await loadRalphConfig()\n await generateAll(config, process.cwd())\n\n consola.success(\n 'Init complete. Edit fabis-ralph-loop.config.ts and run `fabis-ralph-loop generate` to regenerate.',\n )\n },\n})\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"init.mjs","names":[],"sources":["../src/commands/init.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { writeFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { consola } from 'consola'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\nimport { ensureGitignoreBlock } from '../utils/gitignore.js'\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'fabis-ralph-loop'\n\nexport default defineConfig({\n container: {\n name: 'my-ralph-container',\n baseImage: 'node:22-bookworm',\n // playwright: true,\n // shadowVolumes: ['/workspace/node_modules'],\n hooks: {\n rootSetup: [\n // 'RUN npm install -g pnpm@10',\n ],\n userSetup: [\n // 'RUN corepack enable',\n ],\n },\n },\n project: {\n name: 'My Project',\n description: '',\n context: '- **Monorepo** managed with npm\\\\n- **TypeScript strict mode** everywhere',\n },\n output: {\n mode: 'direct',\n },\n})\n`\n\nexport default defineCommand({\n meta: {\n name: 'init',\n description: 'Scaffold ralph-loop config and generate all files',\n },\n async run() {\n const configPath = 'fabis-ralph-loop.config.ts'\n\n if (existsSync(configPath)) {\n consola.warn(`${configPath} already exists. Regenerating files from existing config.`)\n } else {\n await writeFile(configPath, SAMPLE_CONFIG, 'utf8')\n consola.success(`Created ${configPath}`)\n }\n\n await ensureGitignoreBlock()\n\n // Load config and generate\n const config = await loadRalphConfig()\n await generateAll(config, process.cwd())\n\n consola.success(\n 'Init complete. Edit fabis-ralph-loop.config.ts and run `fabis-ralph-loop generate` to regenerate.',\n )\n },\n})\n"],"mappings":";;;;;;;;AAQA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BtB,mBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,MAAM;EACV,MAAM,aAAa;AAEnB,MAAI,WAAW,WAAW,CACxB,SAAQ,KAAK,GAAG,WAAW,2DAA2D;OACjF;AACL,SAAM,UAAU,YAAY,eAAe,OAAO;AAClD,WAAQ,QAAQ,WAAW,aAAa;;AAG1C,QAAM,sBAAsB;AAI5B,QAAM,YADS,MAAM,iBAAiB,EACZ,QAAQ,KAAK,CAAC;AAExC,UAAQ,QACN,oGACD;;CAEJ,CAAC"}
|
package/dist/loader.mjs
CHANGED
|
@@ -73,22 +73,58 @@ function applyPlaywrightDefaults(config) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/config/merge.ts
|
|
78
|
+
/**
|
|
79
|
+
* Deep merge two config objects.
|
|
80
|
+
*
|
|
81
|
+
* Merge strategy:
|
|
82
|
+
* - Arrays: overrides REPLACE base arrays entirely
|
|
83
|
+
* - Plain objects: merge recursively
|
|
84
|
+
* - Scalars: overrides replace base values
|
|
85
|
+
* - undefined values in overrides are skipped
|
|
86
|
+
*/
|
|
87
|
+
function mergeConfigs(base, overrides) {
|
|
88
|
+
return deepMerge(base, overrides);
|
|
89
|
+
}
|
|
90
|
+
function isPlainObject(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
|
93
|
+
function deepMerge(base, overrides) {
|
|
94
|
+
const result = { ...base };
|
|
95
|
+
for (const key of Object.keys(overrides)) {
|
|
96
|
+
const overrideValue = overrides[key];
|
|
97
|
+
const baseValue = base[key];
|
|
98
|
+
if (overrideValue === void 0) continue;
|
|
99
|
+
if (Array.isArray(overrideValue)) result[key] = overrideValue;
|
|
100
|
+
else if (isPlainObject(overrideValue) && isPlainObject(baseValue)) result[key] = deepMerge(baseValue, overrideValue);
|
|
101
|
+
else result[key] = overrideValue;
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
76
106
|
//#endregion
|
|
77
107
|
//#region src/config/loader.ts
|
|
78
108
|
async function loadRalphConfig(cwd) {
|
|
79
|
-
const { config } = await loadConfig({
|
|
109
|
+
const { config: baseConfig } = await loadConfig({
|
|
80
110
|
name: "fabis-ralph-loop",
|
|
81
111
|
cwd
|
|
82
112
|
});
|
|
83
|
-
if (!
|
|
84
|
-
const
|
|
113
|
+
if (!baseConfig || Object.keys(baseConfig).length === 0) throw new Error("No fabis-ralph-loop config found. Run `fabis-ralph-loop init` to create one.");
|
|
114
|
+
const { config: overridesConfig } = await loadConfig({
|
|
115
|
+
name: "fabis-ralph-loop.overrides",
|
|
116
|
+
cwd
|
|
117
|
+
});
|
|
118
|
+
const merged = overridesConfig && Object.keys(overridesConfig).length > 0 ? mergeConfigs(baseConfig, overridesConfig) : baseConfig;
|
|
119
|
+
const parsed = ralphLoopConfigSchema.safeParse(merged);
|
|
85
120
|
if (!parsed.success) {
|
|
86
121
|
const issues = parsed.error.issues.map((issue) => ` ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
87
|
-
|
|
122
|
+
const suffix = overridesConfig && Object.keys(overridesConfig).length > 0 ? " (after merging overrides)" : "";
|
|
123
|
+
throw new Error(`Invalid fabis-ralph-loop config${suffix}:\n${issues}`);
|
|
88
124
|
}
|
|
89
125
|
return applyPlaywrightDefaults(parsed.data);
|
|
90
126
|
}
|
|
91
127
|
|
|
92
128
|
//#endregion
|
|
93
|
-
export {
|
|
129
|
+
export { mergeConfigs as n, ralphLoopConfigSchema as r, loadRalphConfig as t };
|
|
94
130
|
//# sourceMappingURL=loader.mjs.map
|
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/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","import { loadConfig } from 'c12'\nimport { ralphLoopConfigSchema } from './schema.js'\nimport { applyPlaywrightDefaults } from './defaults.js'\nimport type { RalphLoopConfig, ResolvedConfig } from './schema.js'\n\nexport async function loadRalphConfig(cwd?: string): Promise<ResolvedConfig> {\n const { config } = await loadConfig<RalphLoopConfig>({\n name: 'fabis-ralph-loop',\n cwd,\n })\n\n if (!
|
|
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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"}
|
|
@@ -70,8 +70,9 @@ RUN echo '<%= user %> ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
|
|
70
70
|
RUN printf 'eval "$(direnv hook bash)" || true\n' >> /etc/bash.bashrc \
|
|
71
71
|
&& printf '[ "$PWD" = "/workspace" ] && eval "$(direnv export bash 2>&1)" 2>/dev/null || true\n' >> /etc/bash.bashrc
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
RUN
|
|
73
|
+
# Pre-install Ralph loop CLI so it's available immediately (no npx download on first run)
|
|
74
|
+
RUN npm install -g fabis-ralph-loop@<%= packageVersion %>
|
|
75
|
+
RUN printf '#!/bin/bash\nexec fabis-ralph-loop run "$@"\n' \
|
|
75
76
|
> /usr/local/bin/run-fabis-ralph-loop && chmod +x /usr/local/bin/run-fabis-ralph-loop
|
|
76
77
|
|
|
77
78
|
WORKDIR /workspace
|
|
@@ -225,6 +225,30 @@ Controls how Ralph generates skill files for the AI agent.
|
|
|
225
225
|
|
|
226
226
|
---
|
|
227
227
|
|
|
228
|
+
## Overrides File
|
|
229
|
+
|
|
230
|
+
For environment-specific settings that shouldn't be committed, users can create `fabis-ralph-loop.overrides.config.ts`:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { defineOverridesConfig } from 'fabis-ralph-loop'
|
|
234
|
+
|
|
235
|
+
export default defineOverridesConfig({
|
|
236
|
+
// Only specify what you want to override
|
|
237
|
+
defaults: { model: 'opus', verbose: true },
|
|
238
|
+
container: { env: { DEBUG: 'true' } },
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**When to suggest overrides vs base config:**
|
|
243
|
+
|
|
244
|
+
- Local-only settings (debug flags, developer preferences) → overrides file
|
|
245
|
+
- Environment-specific values (API keys, local URLs) → overrides file
|
|
246
|
+
- Project-wide settings shared by the team → base config
|
|
247
|
+
|
|
248
|
+
The overrides file is gitignored by default. It is deep-merged: objects merge recursively, arrays are replaced entirely, scalars are overwritten.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
228
252
|
## Common Tasks
|
|
229
253
|
|
|
230
254
|
### Adding a new backpressure command
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabis-ralph-loop",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "CLI for setting up and running Claude Ralph autonomous coding loops in Docker containers",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/fabis94/fabis-ralph-loop.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"types": "./dist/index.d.mts",
|
|
7
11
|
"exports": {
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
"format": "prettier --write .",
|
|
30
34
|
"format:check": "prettier --check .",
|
|
31
35
|
"unused:check": "knip",
|
|
32
|
-
"check": "eslint . &&
|
|
36
|
+
"check": "eslint . && tsc --noEmit && knip",
|
|
33
37
|
"prepare": "husky",
|
|
34
38
|
"frl": "pnpm dev"
|
|
35
39
|
},
|
|
@@ -70,5 +74,6 @@
|
|
|
70
74
|
"engines": {
|
|
71
75
|
"node": ">=22.0.0"
|
|
72
76
|
},
|
|
73
|
-
"license": "MIT"
|
|
77
|
+
"license": "MIT",
|
|
78
|
+
"packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc"
|
|
74
79
|
}
|
package/dist/generators.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generators.mjs","names":[],"sources":["../src/utils/template.ts","../src/utils/version.ts","../src/generators/dockerfile.ts","../src/generators/compose.ts","../src/generators/entrypoint.ts","../src/generators/prompt.ts","../src/generators/skills.ts","../src/generators/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nimport ejs from 'ejs'\n\n/**\n * Resolve a bundled asset directory (templates, static, uac-templates).\n * In dist (flat layout): assets are siblings of the compiled files.\n * In src (nested layout via tsx): assets are siblings of the parent dir.\n */\nexport function resolveAssetDir(assetName: string, metaUrl: string): string {\n const dir = dirname(fileURLToPath(metaUrl))\n const sibling = join(dir, assetName)\n if (existsSync(sibling)) return sibling\n return join(dir, '..', assetName)\n}\n\nconst TEMPLATES_DIR = resolveAssetDir('templates', import.meta.url)\n\nexport async function renderTemplate(\n templateName: string,\n data: Record<string, unknown>,\n): Promise<string> {\n const templatePath = join(TEMPLATES_DIR, templateName)\n const template = await readFile(templatePath, 'utf8')\n return ejs.render(template, data, { async: false }) as string\n}\n\nexport const GENERATED_HEADER = `# Generated by fabis-ralph-loop — DO NOT EDIT MANUALLY\n# Regenerate with: npx fabis-ralph-loop generate\n`\n","import { createRequire } from 'node:module'\n\nexport function getPackageVersion(): string {\n try {\n const require = createRequire(import.meta.url)\n const pkg = require('../../package.json') as { version: string }\n return pkg.version\n } catch {\n return 'latest'\n }\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport { getPackageVersion } from '../utils/version.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\n/**\n * Detect whether the base image already includes Node.js.\n */\nfunction isNodeBaseImage(baseImage: string): boolean {\n return /^node[:/]/i.test(baseImage)\n}\n\nexport async function generateDockerfile(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('Dockerfile.ejs', {\n generatedHeader: GENERATED_HEADER,\n baseImage: config.container.baseImage,\n systemPackages: config.container.systemPackages,\n installNode: !isNodeBaseImage(config.container.baseImage),\n playwright: config.container.playwright,\n hooks: config.container.hooks,\n user,\n createUser: user === 'sandbox',\n homeDir: `/home/${user}`,\n packageVersion: getPackageVersion(),\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateCompose(config: ResolvedConfig): Promise<string> {\n const homeDir = `/home/${config.container.user}`\n\n // Ensure .claude config is always persisted with the correct home dir\n const persistVolumes: Record<string, string> = {\n 'ralph-claude-config': `${homeDir}/.claude`,\n ...Object.fromEntries(\n Object.entries(config.container.persistVolumes).map(([name, path]) => [\n name,\n path.replace('/home/sandbox', homeDir),\n ]),\n ),\n }\n\n return renderTemplate('docker-compose.yml.ejs', {\n generatedHeader: GENERATED_HEADER,\n containerName: config.container.name,\n shmSize: config.container.shmSize,\n networkMode: config.container.networkMode,\n capabilities: config.container.capabilities,\n shadowVolumes: config.container.shadowVolumes,\n persistVolumes,\n extraVolumes: config.container.volumes,\n env: config.container.env,\n homeDir,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateEntrypoint(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('entrypoint.ts.ejs', {\n generatedHeader: GENERATED_HEADER.replace(/^# /gm, '// '),\n agent: config.defaults.agent,\n shadowVolumes: config.container.shadowVolumes,\n entrypointSetup: config.container.hooks.entrypointSetup,\n user,\n homeDir: `/home/${user}`,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generatePrompt(config: ResolvedConfig): Promise<string> {\n return renderTemplate('ralph-prompt.md.ejs', {\n generatedHeader: GENERATED_HEADER,\n projectName: config.project.name,\n projectDescription: config.project.description,\n projectContext: config.project.context,\n backpressureCommands: config.project.backpressureCommands,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n completionSignal: config.defaults.completionSignal,\n })\n}\n","import { readdir, readFile, writeFile, mkdir, rm } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport ejs from 'ejs'\nimport { consola } from 'consola'\nimport { generate, writeGeneratedFiles } from 'universal-ai-config'\nimport type { ResolvedConfig } from '../config/schema.js'\nimport { resolveAssetDir } from '../utils/template.js'\n\nconst UAC_TEMPLATES_DIR = resolveAssetDir('uac-templates', import.meta.url)\n\nfunction buildLevel1Variables(config: ResolvedConfig): Record<string, unknown> {\n return {\n backpressureCommands: config.project.backpressureCommands,\n projectName: config.project.name,\n projectContext: config.project.context,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n config,\n }\n}\n\nasync function discoverSkills(): Promise<string[]> {\n const skillsDir = join(UAC_TEMPLATES_DIR, 'skills')\n const entries = await readdir(skillsDir, { withFileTypes: true })\n return entries.filter((e) => e.isDirectory()).map((e) => e.name)\n}\n\nexport async function generateSkills(config: ResolvedConfig, projectRoot: string): Promise<void> {\n if (config.output.mode === 'direct') {\n await generateDirect(config, projectRoot)\n } else {\n await generateUac(config, projectRoot)\n }\n}\n\nasync function generateDirect(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n\n // Render Level 1 EJS and write to a temp dir structured as UAC templates\n const tempDir = join(tmpdir(), `ralph-skills-${Date.now()}`)\n\n try {\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(tempDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n }\n\n // Use UAC's generate() API for the second pass (handles Level 2 EJS + frontmatter mapping)\n const files = await generate({\n root: projectRoot,\n targets: ['claude'],\n types: ['skills'],\n overrides: { templatesDir: tempDir },\n })\n\n await writeGeneratedFiles(files, projectRoot)\n consola.info(`Generated ${files.length} skill file(s)`)\n } finally {\n await rm(tempDir, { recursive: true, force: true })\n }\n}\n\nasync function generateUac(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n let count = 0\n\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n // Render Level 1 EJS — Level 2 <%% %> becomes <% %> in output\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(projectRoot, config.output.uacTemplatesDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n count++\n }\n\n consola.info(`Generated ${count} skill template(s) to ${config.output.uacTemplatesDir}/skills/`)\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { consola } from 'consola'\nimport { generateDockerfile } from './dockerfile.js'\nimport { generateCompose } from './compose.js'\nimport { generateEntrypoint } from './entrypoint.js'\nimport { generatePrompt } from './prompt.js'\nimport { generateSkills } from './skills.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\ninterface GenerateOptions {\n dryRun?: boolean\n only?: 'container' | 'prompt' | 'skills'\n}\n\ninterface GeneratedFile {\n path: string\n content: string\n}\n\nexport async function generateAll(\n config: ResolvedConfig,\n projectRoot: string,\n options: GenerateOptions = {},\n): Promise<GeneratedFile[]> {\n const files: GeneratedFile[] = []\n\n if (!options.only || options.only === 'container') {\n const containerDir = join(projectRoot, '.ralph-container')\n await mkdir(containerDir, { recursive: true })\n\n const dockerfile = await generateDockerfile(config)\n files.push({ path: join('.ralph-container', 'Dockerfile'), content: dockerfile })\n\n const entrypoint = await generateEntrypoint(config)\n files.push({ path: join('.ralph-container', 'entrypoint.ts'), content: entrypoint })\n\n const compose = await generateCompose(config)\n files.push({ path: join('.ralph-container', 'docker-compose.yml'), content: compose })\n }\n\n if (!options.only || options.only === 'prompt') {\n const prompt = await generatePrompt(config)\n files.push({ path: join('.ralph-container', 'ralph-prompt.md'), content: prompt })\n }\n\n if (options.dryRun) {\n for (const file of files) {\n consola.info(`[dry-run] Would write: ${file.path}`)\n }\n } else {\n for (const file of files) {\n const fullPath = join(projectRoot, file.path)\n await mkdir(join(fullPath, '..'), { recursive: true })\n await writeFile(fullPath, file.content, 'utf8')\n consola.success(`Written: ${file.path}`)\n }\n }\n\n if (!options.only || options.only === 'skills') {\n if (options.dryRun) {\n consola.info('[dry-run] Would generate skills')\n } else {\n await generateSkills(config, projectRoot)\n }\n }\n\n return files\n}\n"],"mappings":"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"}
|