ralph-cli-sandboxed 0.2.3 → 0.2.5

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.
@@ -33,9 +33,24 @@ function getCliProviderSnippet(cliProvider) {
33
33
  }
34
34
  return provider.docker.install;
35
35
  }
36
- function generateDockerfile(language, javaVersion, cliProvider) {
36
+ function generateDockerfile(language, javaVersion, cliProvider, dockerConfig) {
37
37
  const languageSnippet = getLanguageSnippet(language, javaVersion);
38
38
  const cliSnippet = getCliProviderSnippet(cliProvider);
39
+ // Build git config section if configured
40
+ let gitConfigSection = '';
41
+ if (dockerConfig?.git && (dockerConfig.git.name || dockerConfig.git.email)) {
42
+ const gitCommands = [];
43
+ if (dockerConfig.git.name) {
44
+ gitCommands.push(`git config --global user.name "${dockerConfig.git.name}"`);
45
+ }
46
+ if (dockerConfig.git.email) {
47
+ gitCommands.push(`git config --global user.email "${dockerConfig.git.email}"`);
48
+ }
49
+ gitConfigSection = `
50
+ # Configure git identity
51
+ RUN ${gitCommands.join(' \\\n && ')}
52
+ `;
53
+ }
39
54
  return `# Ralph CLI Sandbox Environment
40
55
  # Based on Claude Code devcontainer
41
56
  # Generated by ralph-cli
@@ -133,6 +148,7 @@ RUN echo 'alias ll="ls -la"' >> /etc/bash.bashrc && \\
133
148
 
134
149
  # Switch to non-root user
135
150
  USER node
151
+ ${gitConfigSection}
136
152
  WORKDIR /workspace
137
153
 
138
154
  # Default to zsh
@@ -218,7 +234,40 @@ iptables -I OUTPUT -p tcp --dport 80 -m set --match-set allowed_ips dst -j ACCEP
218
234
  echo "Firewall initialized. Only allowed destinations are accessible."
219
235
  echo "Allowed: GitHub, npm, Anthropic API, local network"
220
236
  `;
221
- function generateDockerCompose(imageName) {
237
+ function generateDockerCompose(imageName, dockerConfig) {
238
+ // Build ports section if configured
239
+ let portsSection = '';
240
+ if (dockerConfig?.ports && dockerConfig.ports.length > 0) {
241
+ const portLines = dockerConfig.ports.map(port => ` - "${port}"`).join('\n');
242
+ portsSection = ` ports:\n${portLines}\n`;
243
+ }
244
+ // Build volumes array: base volumes + custom volumes
245
+ const baseVolumes = [
246
+ ' # Mount project root (two levels up from .ralph/docker/)',
247
+ ' - ../..:/workspace',
248
+ " # Mount host's ~/.claude for Pro/Max OAuth credentials",
249
+ ' - ${HOME}/.claude:/home/node/.claude',
250
+ ` - ${imageName}-history:/commandhistory`,
251
+ ];
252
+ if (dockerConfig?.volumes && dockerConfig.volumes.length > 0) {
253
+ const customVolumeLines = dockerConfig.volumes.map(vol => ` - ${vol}`);
254
+ baseVolumes.push(...customVolumeLines);
255
+ }
256
+ const volumesSection = baseVolumes.join('\n');
257
+ // Build environment section if configured
258
+ let environmentSection = '';
259
+ if (dockerConfig?.environment && Object.keys(dockerConfig.environment).length > 0) {
260
+ const envLines = Object.entries(dockerConfig.environment)
261
+ .map(([key, value]) => ` - ${key}=${value}`)
262
+ .join('\n');
263
+ environmentSection = ` environment:\n${envLines}\n`;
264
+ }
265
+ else {
266
+ // Keep the commented placeholder for users who don't have config
267
+ environmentSection = ` # Uncomment to use API key instead of OAuth:
268
+ # environment:
269
+ # - ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}\n`;
270
+ }
222
271
  return `# Ralph CLI Docker Compose
223
272
  # Generated by ralph-cli
224
273
 
@@ -228,16 +277,9 @@ services:
228
277
  build:
229
278
  context: .
230
279
  dockerfile: Dockerfile
231
- volumes:
232
- # Mount project root (two levels up from .ralph/docker/)
233
- - ../..:/workspace
234
- # Mount host's ~/.claude for Pro/Max OAuth credentials
235
- - \${HOME}/.claude:/home/node/.claude
236
- - ${imageName}-history:/commandhistory
237
- # Uncomment to use API key instead of OAuth:
238
- # environment:
239
- # - ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
240
- working_dir: /workspace
280
+ ${portsSection} volumes:
281
+ ${volumesSection}
282
+ ${environmentSection} working_dir: /workspace
241
283
  stdin_open: true
242
284
  tty: true
243
285
  cap_add:
@@ -255,7 +297,7 @@ dist
255
297
  .git
256
298
  *.log
257
299
  `;
258
- async function generateFiles(ralphDir, language, imageName, force = false, javaVersion, cliProvider) {
300
+ async function generateFiles(ralphDir, language, imageName, force = false, javaVersion, cliProvider, dockerConfig) {
259
301
  const dockerDir = join(ralphDir, DOCKER_DIR);
260
302
  // Create docker directory
261
303
  if (!existsSync(dockerDir)) {
@@ -263,9 +305,9 @@ async function generateFiles(ralphDir, language, imageName, force = false, javaV
263
305
  console.log(`Created ${DOCKER_DIR}/`);
264
306
  }
265
307
  const files = [
266
- { name: "Dockerfile", content: generateDockerfile(language, javaVersion, cliProvider) },
308
+ { name: "Dockerfile", content: generateDockerfile(language, javaVersion, cliProvider, dockerConfig) },
267
309
  { name: "init-firewall.sh", content: FIREWALL_SCRIPT },
268
- { name: "docker-compose.yml", content: generateDockerCompose(imageName) },
310
+ { name: "docker-compose.yml", content: generateDockerCompose(imageName, dockerConfig) },
269
311
  { name: ".dockerignore", content: DOCKERIGNORE },
270
312
  ];
271
313
  for (const file of files) {
@@ -356,7 +398,7 @@ function getCliProviderConfig(cliProvider) {
356
398
  modelConfig: provider.modelConfig,
357
399
  };
358
400
  }
359
- async function runContainer(ralphDir, imageName, language, javaVersion, cliProvider) {
401
+ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvider, dockerConfig) {
360
402
  const dockerDir = join(ralphDir, DOCKER_DIR);
361
403
  const dockerfileExists = existsSync(join(dockerDir, "Dockerfile"));
362
404
  const hasImage = await imageExists(imageName);
@@ -364,7 +406,7 @@ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvi
364
406
  if (!dockerfileExists || !hasImage) {
365
407
  if (!dockerfileExists) {
366
408
  console.log("Docker folder not found. Initializing docker setup...\n");
367
- await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider);
409
+ await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider, dockerConfig);
368
410
  console.log("");
369
411
  }
370
412
  if (!hasImage) {
@@ -612,7 +654,7 @@ export async function dockerInit(silent = false) {
612
654
  console.log(`CLI provider: ${config.cliProvider}`);
613
655
  }
614
656
  console.log(`Image name: ${imageName}\n`);
615
- await generateFiles(ralphDir, config.language, imageName, true, config.javaVersion, config.cliProvider);
657
+ await generateFiles(ralphDir, config.language, imageName, true, config.javaVersion, config.cliProvider, config.docker);
616
658
  if (!silent) {
617
659
  console.log(`
618
660
  Docker files generated in .ralph/docker/
@@ -700,7 +742,7 @@ INSTALLING PACKAGES (works with Docker & Podman):
700
742
  await buildImage(ralphDir);
701
743
  break;
702
744
  case "run":
703
- await runContainer(ralphDir, imageName, config.language, config.javaVersion, config.cliProvider);
745
+ await runContainer(ralphDir, imageName, config.language, config.javaVersion, config.cliProvider, config.docker);
704
746
  break;
705
747
  case "clean":
706
748
  await cleanImage(imageName, ralphDir);
@@ -719,7 +761,7 @@ INSTALLING PACKAGES (works with Docker & Podman):
719
761
  console.log(`CLI provider: ${config.cliProvider}`);
720
762
  }
721
763
  console.log(`Image name: ${imageName}\n`);
722
- await generateFiles(ralphDir, config.language, imageName, force, config.javaVersion, config.cliProvider);
764
+ await generateFiles(ralphDir, config.language, imageName, force, config.javaVersion, config.cliProvider, config.docker);
723
765
  console.log(`
724
766
  Docker files generated in .ralph/docker/
725
767
 
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "child_process";
2
- import { readFileSync, writeFileSync, unlinkSync } from "fs";
2
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer } from "../utils/config.js";
5
5
  import { resolvePromptVariables } from "../templates/prompts.js";
@@ -29,6 +29,48 @@ function createFilteredPrd(prdPath, baseDir, category) {
29
29
  hasIncomplete: filteredItems.length > 0
30
30
  };
31
31
  }
32
+ /**
33
+ * Syncs passes flags from prd-tasks.json back to prd.json.
34
+ * If the LLM marked any item as passes: true in prd-tasks.json,
35
+ * find the matching item in prd.json and update it.
36
+ * Returns the number of items synced.
37
+ */
38
+ function syncPassesFromTasks(tasksPath, prdPath) {
39
+ // Check if tasks file exists
40
+ if (!existsSync(tasksPath)) {
41
+ return 0;
42
+ }
43
+ try {
44
+ const tasksContent = readFileSync(tasksPath, "utf-8");
45
+ const tasks = JSON.parse(tasksContent);
46
+ const prdContent = readFileSync(prdPath, "utf-8");
47
+ const prd = JSON.parse(prdContent);
48
+ let synced = 0;
49
+ // Find tasks that were marked as passing
50
+ for (const task of tasks) {
51
+ if (task.passes === true) {
52
+ // Find matching item in prd by description
53
+ const match = prd.find(item => item.description === task.description ||
54
+ item.description.includes(task.description) ||
55
+ task.description.includes(item.description));
56
+ if (match && !match.passes) {
57
+ match.passes = true;
58
+ synced++;
59
+ }
60
+ }
61
+ }
62
+ // Write back if any items were synced
63
+ if (synced > 0) {
64
+ writeFileSync(prdPath, JSON.stringify(prd, null, 2) + "\n");
65
+ console.log(`\x1b[32mSynced ${synced} completed item(s) from prd-tasks.json to prd.json\x1b[0m`);
66
+ }
67
+ return synced;
68
+ }
69
+ catch {
70
+ // Ignore errors - the validation step will handle any issues
71
+ return 0;
72
+ }
73
+ }
32
74
  async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig, debug, model) {
33
75
  return new Promise((resolve, reject) => {
34
76
  let output = "";
@@ -223,8 +265,8 @@ export async function run(args) {
223
265
  });
224
266
  const paths = getPaths();
225
267
  const cliConfig = getCliConfig(config);
226
- // Safety margin for iteration limit (recalculated dynamically each iteration)
227
- const ITERATION_SAFETY_MARGIN = 3;
268
+ // Progress tracking: stop only if no tasks complete after N iterations
269
+ const MAX_ITERATIONS_WITHOUT_PROGRESS = 3;
228
270
  // Get requested iteration count (may be adjusted dynamically)
229
271
  const requestedIterations = parseInt(filteredArgs[0]) || Infinity;
230
272
  // Container is required, so always run with skip-permissions
@@ -252,21 +294,19 @@ export async function run(args) {
252
294
  let consecutiveFailures = 0;
253
295
  let lastExitCode = 0;
254
296
  let iterationCount = 0;
297
+ // Progress tracking for --all mode
298
+ // Progress = tasks completed OR new tasks added (allows ralph to expand the PRD)
299
+ const initialCounts = countPrdItems(paths.prd, category);
300
+ let lastCompletedCount = initialCounts.complete;
301
+ let lastTotalCount = initialCounts.total;
302
+ let iterationsWithoutProgress = 0;
255
303
  try {
256
304
  while (true) {
257
305
  iterationCount++;
258
- // Dynamic iteration limit: recalculate based on current incomplete count
259
- // This allows the limit to expand if tasks are added during the run
260
306
  const currentCounts = countPrdItems(paths.prd, category);
261
- const dynamicMaxIterations = currentCounts.incomplete + ITERATION_SAFETY_MARGIN;
262
307
  // Check if we should stop (not in loop mode)
263
- if (!loopMode) {
264
- if (allMode && iterationCount > dynamicMaxIterations) {
265
- console.log(`\nStopping: reached iteration limit (${dynamicMaxIterations}) with ${currentCounts.incomplete} tasks remaining.`);
266
- console.log("This may indicate tasks are not completing. Check the PRD and progress.");
267
- break;
268
- }
269
- if (!allMode && iterationCount > Math.min(requestedIterations, dynamicMaxIterations)) {
308
+ if (!loopMode && !allMode) {
309
+ if (iterationCount > requestedIterations) {
270
310
  break;
271
311
  }
272
312
  }
@@ -278,7 +318,7 @@ export async function run(args) {
278
318
  console.log(`Iteration ${iterationCount}`);
279
319
  }
280
320
  else {
281
- console.log(`Iteration ${iterationCount} of ${Math.min(requestedIterations, dynamicMaxIterations)}`);
321
+ console.log(`Iteration ${iterationCount} of ${requestedIterations}`);
282
322
  }
283
323
  console.log(`${"=".repeat(50)}\n`);
284
324
  // Load a valid copy of the PRD before handing to the LLM
@@ -342,6 +382,9 @@ export async function run(args) {
342
382
  }
343
383
  }
344
384
  const { exitCode, output } = await runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig, debug, model);
385
+ // Sync any completed items from prd-tasks.json back to prd.json
386
+ // This catches cases where the LLM updated prd-tasks.json instead of prd.json
387
+ syncPassesFromTasks(filteredPrdPath, paths.prd);
345
388
  // Clean up temp file after each iteration
346
389
  try {
347
390
  unlinkSync(filteredPrdPath);
@@ -352,6 +395,29 @@ export async function run(args) {
352
395
  filteredPrdPath = null;
353
396
  // Validate and recover PRD if the LLM corrupted it
354
397
  validateAndRecoverPrd(paths.prd, validPrd);
398
+ // Track progress for --all mode: stop if no progress after N iterations
399
+ // Progress = tasks completed OR new tasks added (allows ralph to expand the PRD)
400
+ if (allMode) {
401
+ const progressCounts = countPrdItems(paths.prd, category);
402
+ const tasksCompleted = progressCounts.complete > lastCompletedCount;
403
+ const tasksAdded = progressCounts.total > lastTotalCount;
404
+ if (tasksCompleted || tasksAdded) {
405
+ // Progress made - reset counter
406
+ iterationsWithoutProgress = 0;
407
+ lastCompletedCount = progressCounts.complete;
408
+ lastTotalCount = progressCounts.total;
409
+ }
410
+ else {
411
+ iterationsWithoutProgress++;
412
+ }
413
+ if (iterationsWithoutProgress >= MAX_ITERATIONS_WITHOUT_PROGRESS) {
414
+ console.log(`\nStopping: no progress after ${MAX_ITERATIONS_WITHOUT_PROGRESS} consecutive iterations.`);
415
+ console.log(`(No tasks completed and no new tasks added)`);
416
+ console.log(`Status: ${progressCounts.complete}/${progressCounts.total} complete, ${progressCounts.incomplete} remaining.`);
417
+ console.log("Check the PRD and task definitions for issues.");
418
+ break;
419
+ }
420
+ }
355
421
  if (exitCode !== 0) {
356
422
  console.error(`\n${cliConfig.command} exited with code ${exitCode}`);
357
423
  // Track consecutive failures to detect persistent errors (e.g., missing API key)
@@ -98,8 +98,8 @@
98
98
  "description": "Sourcegraph's AMP coding agent",
99
99
  "command": "amp",
100
100
  "defaultArgs": [],
101
- "yoloArgs": ["--yolo"],
102
- "promptArgs": [],
101
+ "yoloArgs": ["--dangerously-allow-all"],
102
+ "promptArgs": ["-x"],
103
103
  "docker": {
104
104
  "install": "# Install AMP CLI (as node user)\nRUN su - node -c 'curl -fsSL https://ampcode.com/install.sh | bash' \\\n && echo 'export PATH=\"$HOME/.amp/bin:$PATH\"' >> /home/node/.zshrc",
105
105
  "note": "Check 'amp --help' for available flags"
@@ -13,6 +13,10 @@
13
13
  { "name": "Hono", "description": "Lightweight web framework" },
14
14
  { "name": "Drizzle ORM", "description": "TypeScript ORM" },
15
15
  { "name": "Prisma", "description": "Type-safe database ORM" },
16
+ { "name": "React", "description": "UI component library" },
17
+ { "name": "Tailwind CSS", "description": "Utility-first CSS framework" },
18
+ { "name": "tRPC", "description": "End-to-end typesafe APIs" },
19
+ { "name": "Zod", "description": "TypeScript-first schema validation" },
16
20
  { "name": "SQLite", "description": "Embedded SQL database" },
17
21
  { "name": "PostgreSQL", "description": "Advanced SQL database" }
18
22
  ]
@@ -30,11 +34,19 @@
30
34
  { "name": "Fastify", "description": "High-performance web framework" },
31
35
  { "name": "NestJS", "description": "Progressive Node.js framework" },
32
36
  { "name": "Next.js", "description": "React framework for production" },
37
+ { "name": "Remix", "description": "Full stack web framework" },
33
38
  { "name": "Prisma", "description": "Type-safe database ORM" },
34
39
  { "name": "TypeORM", "description": "TypeScript ORM" },
40
+ { "name": "Drizzle ORM", "description": "TypeScript ORM with SQL-like syntax" },
41
+ { "name": "tRPC", "description": "End-to-end typesafe APIs" },
42
+ { "name": "Zod", "description": "TypeScript-first schema validation" },
43
+ { "name": "Turborepo", "description": "High-performance monorepo build system" },
44
+ { "name": "SWC", "description": "Fast TypeScript/JavaScript compiler" },
35
45
  { "name": "Jest", "description": "JavaScript testing framework" },
36
46
  { "name": "Vitest", "description": "Fast unit testing framework" },
47
+ { "name": "Playwright", "description": "End-to-end testing framework" },
37
48
  { "name": "PostgreSQL", "description": "Advanced SQL database" },
49
+ { "name": "MySQL", "description": "Popular SQL database" },
38
50
  { "name": "MongoDB", "description": "NoSQL document database" },
39
51
  { "name": "Redis", "description": "In-memory data store" }
40
52
  ]
@@ -54,7 +66,16 @@
54
66
  { "name": "SQLAlchemy", "description": "SQL toolkit and ORM" },
55
67
  { "name": "Pydantic", "description": "Data validation library" },
56
68
  { "name": "Celery", "description": "Distributed task queue" },
69
+ { "name": "LangChain", "description": "Framework for LLM applications" },
70
+ { "name": "Alembic", "description": "Database migration tool" },
71
+ { "name": "Poetry", "description": "Dependency management and packaging" },
72
+ { "name": "Ruff", "description": "Fast Python linter and formatter" },
73
+ { "name": "Uvicorn", "description": "ASGI web server" },
74
+ { "name": "Typer", "description": "CLI application framework" },
75
+ { "name": "Polars", "description": "Fast DataFrame library" },
76
+ { "name": "uv", "description": "Fast Python package installer and resolver" },
57
77
  { "name": "PostgreSQL", "description": "Advanced SQL database" },
78
+ { "name": "MySQL", "description": "Popular SQL database" },
58
79
  { "name": "Redis", "description": "In-memory data store" },
59
80
  { "name": "pytest", "description": "Testing framework" }
60
81
  ]
@@ -74,7 +95,14 @@
74
95
  { "name": "Fiber", "description": "Express-inspired web framework" },
75
96
  { "name": "GORM", "description": "ORM library for Go" },
76
97
  { "name": "sqlx", "description": "SQL extensions for Go" },
98
+ { "name": "Chi", "description": "Lightweight, idiomatic HTTP router" },
99
+ { "name": "Cobra", "description": "CLI application framework" },
100
+ { "name": "Viper", "description": "Configuration management library" },
101
+ { "name": "Wire", "description": "Compile-time dependency injection" },
102
+ { "name": "testify", "description": "Testing toolkit with assertions and mocks" },
77
103
  { "name": "PostgreSQL", "description": "Advanced SQL database" },
104
+ { "name": "MongoDB", "description": "NoSQL document database" },
105
+ { "name": "MySQL", "description": "Popular SQL database" },
78
106
  { "name": "Redis", "description": "In-memory data store" }
79
107
  ]
80
108
  },
@@ -94,7 +122,13 @@
94
122
  { "name": "SQLx", "description": "Async SQL toolkit" },
95
123
  { "name": "Tokio", "description": "Async runtime" },
96
124
  { "name": "Serde", "description": "Serialization framework" },
97
- { "name": "PostgreSQL", "description": "Advanced SQL database" }
125
+ { "name": "Tauri", "description": "Desktop app framework" },
126
+ { "name": "SeaORM", "description": "Async ORM for Rust" },
127
+ { "name": "Clap", "description": "Command-line argument parser" },
128
+ { "name": "Tracing", "description": "Application-level tracing framework" },
129
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
130
+ { "name": "Redis", "description": "In-memory data store" },
131
+ { "name": "MongoDB", "description": "NoSQL document database" }
98
132
  ]
99
133
  },
100
134
  "java": {
@@ -113,9 +147,16 @@
113
147
  { "name": "Micronaut", "description": "Modern JVM framework" },
114
148
  { "name": "Hibernate", "description": "ORM framework" },
115
149
  { "name": "JPA", "description": "Java Persistence API" },
150
+ { "name": "Gradle", "description": "Build automation tool" },
151
+ { "name": "Lombok", "description": "Boilerplate code reduction library" },
152
+ { "name": "MapStruct", "description": "Bean mapping code generator" },
153
+ { "name": "Flyway", "description": "Database migration tool" },
154
+ { "name": "Testcontainers", "description": "Integration testing with containers" },
116
155
  { "name": "JUnit", "description": "Testing framework" },
117
156
  { "name": "PostgreSQL", "description": "Advanced SQL database" },
118
- { "name": "MySQL", "description": "Popular SQL database" }
157
+ { "name": "MySQL", "description": "Popular SQL database" },
158
+ { "name": "Redis", "description": "In-memory data store" },
159
+ { "name": "MongoDB", "description": "NoSQL document database" }
119
160
  ]
120
161
  },
121
162
  "kotlin": {
@@ -138,10 +179,210 @@
138
179
  { "name": "Kotest", "description": "Kotlin testing framework" },
139
180
  { "name": "kotlinx.coroutines", "description": "Coroutines for async programming" },
140
181
  { "name": "kotlinx.serialization", "description": "Multiplatform serialization" },
182
+ { "name": "Arrow", "description": "Functional companion to Kotlin's standard library" },
183
+ { "name": "Mockk", "description": "Mocking library for Kotlin" },
184
+ { "name": "Flyway", "description": "Database migration tool" },
185
+ { "name": "Compose Multiplatform", "description": "Declarative UI framework for Kotlin" },
186
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
187
+ { "name": "MySQL", "description": "Popular SQL database" },
188
+ { "name": "Redis", "description": "In-memory data store" },
189
+ { "name": "MongoDB", "description": "NoSQL document database" }
190
+ ]
191
+ },
192
+ "csharp": {
193
+ "name": "C#/.NET",
194
+ "description": "C# with .NET SDK",
195
+ "checkCommand": "dotnet build",
196
+ "testCommand": "dotnet test",
197
+ "docker": {
198
+ "install": "# Install .NET SDK 8.0\nRUN apt-get update && apt-get install -y dotnet-sdk-8.0 && rm -rf /var/lib/apt/lists/*"
199
+ },
200
+ "technologies": [
201
+ { "name": "ASP.NET Core", "description": "Web framework for .NET" },
202
+ { "name": "Blazor", "description": "Web UI framework using C#" },
203
+ { "name": "Entity Framework Core", "description": "Modern ORM for .NET" },
204
+ { "name": "Dapper", "description": "Simple object mapper for .NET" },
205
+ { "name": "xUnit", "description": "Unit testing framework" },
206
+ { "name": "NUnit", "description": "Testing framework for .NET" },
141
207
  { "name": "PostgreSQL", "description": "Advanced SQL database" },
208
+ { "name": "SQL Server", "description": "Microsoft SQL database" },
209
+ { "name": "Redis", "description": "In-memory data store" },
142
210
  { "name": "MongoDB", "description": "NoSQL document database" }
143
211
  ]
144
212
  },
213
+ "ruby": {
214
+ "name": "Ruby",
215
+ "description": "Ruby with Bundler",
216
+ "checkCommand": "bundle exec rubocop --fail-level error",
217
+ "testCommand": "bundle exec rspec",
218
+ "docker": {
219
+ "install": "# Install Ruby via rbenv\nRUN apt-get update && apt-get install -y \\\n rbenv \\\n ruby-build \\\n && rm -rf /var/lib/apt/lists/*\nRUN rbenv install 3.3.0 && rbenv global 3.3.0\nENV PATH=\"/root/.rbenv/shims:/root/.rbenv/bin:$PATH\"\nRUN gem install bundler"
220
+ },
221
+ "technologies": [
222
+ { "name": "Ruby on Rails", "description": "Full-stack web framework" },
223
+ { "name": "Sinatra", "description": "Lightweight web framework" },
224
+ { "name": "Hanami", "description": "Modern Ruby web framework" },
225
+ { "name": "ActiveRecord", "description": "ORM for Ruby" },
226
+ { "name": "Sequel", "description": "Database toolkit for Ruby" },
227
+ { "name": "RSpec", "description": "BDD testing framework" },
228
+ { "name": "Minitest", "description": "Testing framework" },
229
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
230
+ { "name": "MySQL", "description": "Popular SQL database" },
231
+ { "name": "Redis", "description": "In-memory data store" },
232
+ { "name": "Sidekiq", "description": "Background job processor" }
233
+ ]
234
+ },
235
+ "php": {
236
+ "name": "PHP",
237
+ "description": "PHP with Composer",
238
+ "checkCommand": "composer validate && php -l",
239
+ "testCommand": "vendor/bin/phpunit",
240
+ "docker": {
241
+ "install": "# Install PHP and Composer\nRUN apt-get update && apt-get install -y \\\n php \\\n php-cli \\\n php-mbstring \\\n php-xml \\\n php-curl \\\n php-zip \\\n php-pgsql \\\n php-mysql \\\n php-redis \\\n unzip \\\n && rm -rf /var/lib/apt/lists/*\n# Install Composer\nRUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer"
242
+ },
243
+ "technologies": [
244
+ { "name": "Laravel", "description": "Full-stack PHP framework" },
245
+ { "name": "Symfony", "description": "Modular PHP framework" },
246
+ { "name": "Slim", "description": "Micro framework for PHP" },
247
+ { "name": "Doctrine ORM", "description": "PHP database abstraction" },
248
+ { "name": "Eloquent", "description": "Laravel's ORM" },
249
+ { "name": "PHPUnit", "description": "Testing framework for PHP" },
250
+ { "name": "Pest", "description": "Elegant PHP testing framework" },
251
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
252
+ { "name": "MySQL", "description": "Popular SQL database" },
253
+ { "name": "Redis", "description": "In-memory data store" }
254
+ ]
255
+ },
256
+ "swift": {
257
+ "name": "Swift",
258
+ "description": "Swift with Swift Package Manager",
259
+ "checkCommand": "swift build",
260
+ "testCommand": "swift test",
261
+ "docker": {
262
+ "install": "# Install Swift toolchain\nRUN apt-get update && apt-get install -y \\\n binutils \\\n git \\\n gnupg2 \\\n libc6-dev \\\n libcurl4-openssl-dev \\\n libedit2 \\\n libgcc-11-dev \\\n libpython3-dev \\\n libsqlite3-0 \\\n libstdc++-11-dev \\\n libxml2-dev \\\n libz3-dev \\\n pkg-config \\\n tzdata \\\n unzip \\\n zlib1g-dev \\\n && rm -rf /var/lib/apt/lists/*\nRUN ARCH=$(dpkg --print-architecture) && \\\n if [ \"$ARCH\" = \"amd64\" ]; then SWIFT_ARCH=\"x86_64\"; else SWIFT_ARCH=\"aarch64\"; fi && \\\n curl -fsSL https://download.swift.org/swift-5.10-release/ubuntu2204/swift-5.10-RELEASE/swift-5.10-RELEASE-ubuntu22.04-${SWIFT_ARCH}.tar.gz | tar -xz -C /opt\nENV PATH=\"/opt/swift-5.10-RELEASE-ubuntu22.04/usr/bin:$PATH\""
263
+ },
264
+ "technologies": [
265
+ { "name": "Vapor", "description": "Server-side Swift web framework" },
266
+ { "name": "Hummingbird", "description": "Lightweight Swift web framework" },
267
+ { "name": "Fluent ORM", "description": "Swift ORM for Vapor" },
268
+ { "name": "SwiftNIO", "description": "Event-driven network framework" },
269
+ { "name": "XCTest", "description": "Swift testing framework" },
270
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
271
+ { "name": "SQLite", "description": "Embedded SQL database" },
272
+ { "name": "Redis", "description": "In-memory data store" }
273
+ ]
274
+ },
275
+ "elixir": {
276
+ "name": "Elixir",
277
+ "description": "Elixir with Mix build tool",
278
+ "checkCommand": "mix compile --warnings-as-errors",
279
+ "testCommand": "mix test",
280
+ "docker": {
281
+ "install": "# Install Erlang and Elixir\nRUN apt-get update && apt-get install -y \\\n erlang \\\n elixir \\\n && rm -rf /var/lib/apt/lists/*\nRUN mix local.hex --force && mix local.rebar --force"
282
+ },
283
+ "technologies": [
284
+ { "name": "Phoenix", "description": "Web framework for Elixir" },
285
+ { "name": "Ecto", "description": "Database wrapper and query generator" },
286
+ { "name": "LiveView", "description": "Real-time server-rendered HTML" },
287
+ { "name": "Oban", "description": "Background job processing" },
288
+ { "name": "ExUnit", "description": "Built-in testing framework" },
289
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
290
+ { "name": "Redis", "description": "In-memory data store" }
291
+ ]
292
+ },
293
+ "scala": {
294
+ "name": "Scala",
295
+ "description": "Scala with sbt build tool",
296
+ "checkCommand": "sbt compile",
297
+ "testCommand": "sbt test",
298
+ "docker": {
299
+ "install": "# Install Java and sbt\nRUN apt-get update && apt-get install -y \\\n openjdk-17-jdk \\\n apt-transport-https \\\n gnupg \\\n && rm -rf /var/lib/apt/lists/*\nENV JAVA_HOME=\"/usr/lib/jvm/java-17-openjdk-$(dpkg --print-architecture)\"\n# Install sbt\nRUN echo \"deb https://repo.scala-sbt.org/scalasbt/debian all main\" | tee /etc/apt/sources.list.d/sbt.list && \\\n curl -sL \"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823\" | gpg --dearmor -o /etc/apt/trusted.gpg.d/sbt.gpg && \\\n apt-get update && apt-get install -y sbt && rm -rf /var/lib/apt/lists/*"
300
+ },
301
+ "technologies": [
302
+ { "name": "Play Framework", "description": "High-velocity web framework" },
303
+ { "name": "Akka HTTP", "description": "HTTP server and client toolkit" },
304
+ { "name": "http4s", "description": "Typeful, functional HTTP library" },
305
+ { "name": "Slick", "description": "Functional relational mapping" },
306
+ { "name": "Doobie", "description": "Functional JDBC layer" },
307
+ { "name": "ScalaTest", "description": "Testing framework for Scala" },
308
+ { "name": "Specs2", "description": "Software specification library" },
309
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
310
+ { "name": "Kafka", "description": "Distributed event streaming platform" },
311
+ { "name": "Spark", "description": "Unified analytics engine" }
312
+ ]
313
+ },
314
+ "zig": {
315
+ "name": "Zig",
316
+ "description": "Zig programming language",
317
+ "checkCommand": "zig build",
318
+ "testCommand": "zig build test",
319
+ "docker": {
320
+ "install": "# Install Zig compiler\nRUN ARCH=$(dpkg --print-architecture) && \\\n if [ \"$ARCH\" = \"amd64\" ]; then ZIG_ARCH=\"x86_64\"; else ZIG_ARCH=\"aarch64\"; fi && \\\n curl -fsSL https://ziglang.org/download/0.11.0/zig-linux-${ZIG_ARCH}-0.11.0.tar.xz | tar -xJ -C /opt && \\\n ln -s /opt/zig-linux-${ZIG_ARCH}-0.11.0/zig /usr/local/bin/zig"
321
+ },
322
+ "technologies": [
323
+ { "name": "std.http", "description": "Zig standard library HTTP server/client" },
324
+ { "name": "SQLite", "description": "Embedded SQL database" },
325
+ { "name": "PostgreSQL", "description": "Advanced SQL database" }
326
+ ]
327
+ },
328
+ "haskell": {
329
+ "name": "Haskell",
330
+ "description": "Haskell with Stack build tool",
331
+ "checkCommand": "stack build",
332
+ "testCommand": "stack test",
333
+ "docker": {
334
+ "install": "# Install GHC and Stack\nRUN apt-get update && apt-get install -y \\\n curl \\\n gcc \\\n g++ \\\n make \\\n libffi-dev \\\n libgmp-dev \\\n libtinfo-dev \\\n zlib1g-dev \\\n && rm -rf /var/lib/apt/lists/*\n# Install Stack\nRUN curl -sSL https://get.haskellstack.org/ | sh\n# Setup GHC via Stack\nRUN stack setup"
335
+ },
336
+ "technologies": [
337
+ { "name": "Servant", "description": "Type-level web API framework" },
338
+ { "name": "Yesod", "description": "Full-stack Haskell web framework" },
339
+ { "name": "Scotty", "description": "Lightweight web framework" },
340
+ { "name": "Persistent", "description": "Type-safe database access" },
341
+ { "name": "Esqueleto", "description": "Type-safe EDSL for SQL queries" },
342
+ { "name": "HSpec", "description": "BDD-style testing framework" },
343
+ { "name": "QuickCheck", "description": "Property-based testing library" },
344
+ { "name": "PostgreSQL", "description": "Advanced SQL database" }
345
+ ]
346
+ },
347
+ "clojure": {
348
+ "name": "Clojure",
349
+ "description": "Clojure with Leiningen build tool",
350
+ "checkCommand": "lein check",
351
+ "testCommand": "lein test",
352
+ "docker": {
353
+ "install": "# Install Java and Leiningen\nRUN apt-get update && apt-get install -y \\\n openjdk-17-jdk \\\n && rm -rf /var/lib/apt/lists/*\nENV JAVA_HOME=\"/usr/lib/jvm/java-17-openjdk-$(dpkg --print-architecture)\"\n# Install Leiningen\nRUN curl -fsSL https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein -o /usr/local/bin/lein && \\\n chmod +x /usr/local/bin/lein && \\\n lein"
354
+ },
355
+ "technologies": [
356
+ { "name": "Ring", "description": "HTTP server abstraction" },
357
+ { "name": "Compojure", "description": "Routing library for Ring" },
358
+ { "name": "Pedestal", "description": "Server-side web framework" },
359
+ { "name": "next.jdbc", "description": "Modern Clojure JDBC wrapper" },
360
+ { "name": "HoneySQL", "description": "SQL as Clojure data structures" },
361
+ { "name": "clojure.test", "description": "Built-in testing framework" },
362
+ { "name": "Midje", "description": "Testing framework with mocking" },
363
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
364
+ { "name": "Datomic", "description": "Immutable database system" }
365
+ ]
366
+ },
367
+ "deno": {
368
+ "name": "Deno (TypeScript)",
369
+ "description": "Deno runtime with TypeScript",
370
+ "checkCommand": "deno check **/*.ts",
371
+ "testCommand": "deno test",
372
+ "docker": {
373
+ "install": "# Install Deno\nRUN curl -fsSL https://deno.land/install.sh | sh\nENV DENO_INSTALL=\"/root/.deno\"\nENV PATH=\"${DENO_INSTALL}/bin:${PATH}\""
374
+ },
375
+ "technologies": [
376
+ { "name": "Fresh", "description": "Next-gen web framework for Deno" },
377
+ { "name": "Oak", "description": "Middleware framework for Deno" },
378
+ { "name": "Hono", "description": "Lightweight web framework" },
379
+ { "name": "Drizzle ORM", "description": "TypeScript ORM" },
380
+ { "name": "Deno KV", "description": "Built-in key-value database" },
381
+ { "name": "PostgreSQL", "description": "Advanced SQL database" },
382
+ { "name": "SQLite", "description": "Embedded SQL database" },
383
+ { "name": "Redis", "description": "In-memory data store" }
384
+ ]
385
+ },
145
386
  "none": {
146
387
  "name": "None (custom)",
147
388
  "description": "Custom configuration",
@@ -15,6 +15,15 @@ export interface RalphConfig {
15
15
  javaVersion?: number;
16
16
  cli?: CliConfig;
17
17
  cliProvider?: string;
18
+ docker?: {
19
+ ports?: string[];
20
+ volumes?: string[];
21
+ environment?: Record<string, string>;
22
+ git?: {
23
+ name?: string;
24
+ email?: string;
25
+ };
26
+ };
18
27
  }
19
28
  export declare const DEFAULT_CLI_CONFIG: CliConfig;
20
29
  export declare function getCliConfig(config: RalphConfig): CliConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-cli-sandboxed",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "AI-driven development automation CLI for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {