ralphflow 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ralphflow.js CHANGED
@@ -1,69 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ copyTemplate,
3
4
  getDb,
4
5
  incrementIteration,
5
6
  isLoopComplete,
6
- listFlows,
7
7
  loadConfig,
8
8
  markLoopComplete,
9
9
  markLoopRunning,
10
+ resetLoopState,
10
11
  resolveFlowDir,
11
12
  resolveLoop,
12
13
  showStatus
13
- } from "./chunk-GVOJO5IN.js";
14
+ } from "./chunk-TCCMQDVT.js";
14
15
 
15
16
  // src/cli/index.ts
16
17
  import { Command as Command6 } from "commander";
17
- import chalk8 from "chalk";
18
+ import chalk7 from "chalk";
19
+ import { exec } from "child_process";
18
20
 
19
21
  // src/cli/init.ts
20
22
  import { Command } from "commander";
21
23
  import chalk2 from "chalk";
22
24
 
23
25
  // src/core/init.ts
24
- import { existsSync as existsSync2, readdirSync } from "fs";
25
- import { join as join2 } from "path";
26
+ import { existsSync, readdirSync } from "fs";
27
+ import { join } from "path";
26
28
  import { createInterface } from "readline";
27
29
  import chalk from "chalk";
28
-
29
- // src/core/template.ts
30
- import { readFileSync, mkdirSync, cpSync, existsSync } from "fs";
31
- import { join, dirname } from "path";
32
- import { fileURLToPath } from "url";
33
- var __dirname = dirname(fileURLToPath(import.meta.url));
34
- function resolveTemplatePath(templateName) {
35
- const candidates = [
36
- join(__dirname, "..", "templates", templateName),
37
- // dev: src/core/ -> src/templates/
38
- join(__dirname, "..", "src", "templates", templateName)
39
- // bundled: dist/ -> src/templates/
40
- ];
41
- for (const candidate of candidates) {
42
- if (existsSync(candidate)) {
43
- return candidate;
44
- }
45
- }
46
- throw new Error(
47
- `Template "${templateName}" not found. Searched:
48
- ${candidates.join("\n")}`
49
- );
50
- }
51
- function copyTemplate(templateName, targetDir) {
52
- const templatePath = resolveTemplatePath(templateName);
53
- const loopsDir = join(templatePath, "loops");
54
- if (!existsSync(loopsDir)) {
55
- throw new Error(`Template "${templateName}" has no loops/ directory`);
56
- }
57
- mkdirSync(targetDir, { recursive: true });
58
- cpSync(loopsDir, targetDir, { recursive: true });
59
- const yamlSrc = join(templatePath, "ralphflow.yaml");
60
- if (existsSync(yamlSrc)) {
61
- const yamlDest = join(targetDir, "ralphflow.yaml");
62
- cpSync(yamlSrc, yamlDest);
63
- }
64
- }
65
-
66
- // src/core/init.ts
67
30
  function ask(rl, question) {
68
31
  return new Promise((resolve) => {
69
32
  rl.question(chalk.cyan("? ") + question + " ", (answer) => {
@@ -73,9 +36,9 @@ function ask(rl, question) {
73
36
  }
74
37
  var TEMPLATES = ["code-implementation", "research"];
75
38
  async function initProject(cwd, options = {}) {
76
- const ralphFlowDir = join2(cwd, ".ralph-flow");
77
- const claudeMdPath = join2(cwd, "CLAUDE.md");
78
- if (!existsSync2(claudeMdPath)) {
39
+ const ralphFlowDir = join(cwd, ".ralph-flow");
40
+ const claudeMdPath = join(cwd, "CLAUDE.md");
41
+ if (!existsSync(claudeMdPath)) {
79
42
  console.log();
80
43
  console.log(chalk.yellow(" No CLAUDE.md found."));
81
44
  console.log(chalk.dim(' Create one with: claude "Initialize CLAUDE.md for this project"'));
@@ -83,8 +46,8 @@ async function initProject(cwd, options = {}) {
83
46
  console.log();
84
47
  return;
85
48
  }
86
- if (existsSync2(ralphFlowDir)) {
87
- const existing = listFlows2(ralphFlowDir);
49
+ if (existsSync(ralphFlowDir)) {
50
+ const existing = listFlows(ralphFlowDir);
88
51
  if (existing.length > 0 && !options.template) {
89
52
  console.log();
90
53
  console.log(chalk.bold(" Existing flows:"));
@@ -116,8 +79,8 @@ async function initProject(cwd, options = {}) {
116
79
  }
117
80
  rl.close();
118
81
  console.log();
119
- const flowDir = join2(ralphFlowDir, flowName);
120
- if (existsSync2(flowDir)) {
82
+ const flowDir = join(ralphFlowDir, flowName);
83
+ if (existsSync(flowDir)) {
121
84
  console.log(chalk.yellow(` Flow "${flowName}" already exists at .ralph-flow/${flowName}/`));
122
85
  return;
123
86
  }
@@ -127,7 +90,7 @@ async function initProject(cwd, options = {}) {
127
90
  console.log(chalk.dim(` Next: npx ralphflow run story --flow ${flowName}`));
128
91
  console.log();
129
92
  }
130
- function listFlows2(ralphFlowDir) {
93
+ function listFlows(ralphFlowDir) {
131
94
  try {
132
95
  return readdirSync(ralphFlowDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
133
96
  } catch {
@@ -153,14 +116,14 @@ import { Command as Command2 } from "commander";
153
116
  import chalk4 from "chalk";
154
117
 
155
118
  // src/core/runner.ts
156
- import { readFileSync as readFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync, readdirSync as readdirSync2, unlinkSync } from "fs";
157
- import { join as join3, basename } from "path";
119
+ import { readFileSync, existsSync as existsSync2, mkdirSync, writeFileSync, readdirSync as readdirSync2, unlinkSync } from "fs";
120
+ import { join as join2, basename } from "path";
158
121
  import chalk3 from "chalk";
159
122
 
160
123
  // src/core/claude.ts
161
124
  import { spawn } from "child_process";
162
125
  async function spawnClaude(options) {
163
- const { prompt, model, cwd } = options;
126
+ const { prompt, model, cwd, env: extraEnv } = options;
164
127
  const args = ["--dangerously-skip-permissions", prompt];
165
128
  if (model) {
166
129
  args.unshift("--model", model);
@@ -169,7 +132,7 @@ async function spawnClaude(options) {
169
132
  const child = spawn("claude", args, {
170
133
  cwd,
171
134
  stdio: "inherit",
172
- env: { ...process.env }
135
+ env: { ...process.env, ...extraEnv }
173
136
  });
174
137
  child.on("error", (err) => {
175
138
  reject(new Error(`Failed to spawn claude: ${err.message}`));
@@ -186,8 +149,8 @@ async function spawnClaude(options) {
186
149
 
187
150
  // src/core/runner.ts
188
151
  function agentsDir(flowDir, loop) {
189
- const loopDir = join3(flowDir, loop.tracker, "..");
190
- return join3(loopDir, ".agents");
152
+ const loopDir = join2(flowDir, loop.tracker, "..");
153
+ return join2(loopDir, ".agents");
191
154
  }
192
155
  function isProcessAlive(pid) {
193
156
  try {
@@ -198,22 +161,22 @@ function isProcessAlive(pid) {
198
161
  }
199
162
  }
200
163
  function cleanStaleAgents(dir) {
201
- if (!existsSync3(dir)) return;
164
+ if (!existsSync2(dir)) return;
202
165
  for (const file of readdirSync2(dir)) {
203
166
  if (!file.endsWith(".lock")) continue;
204
- const pidStr = readFileSync2(join3(dir, file), "utf-8").trim();
167
+ const pidStr = readFileSync(join2(dir, file), "utf-8").trim();
205
168
  const pid = parseInt(pidStr, 10);
206
169
  if (isNaN(pid) || !isProcessAlive(pid)) {
207
- unlinkSync(join3(dir, file));
170
+ unlinkSync(join2(dir, file));
208
171
  }
209
172
  }
210
173
  }
211
174
  function acquireAgentId(dir, maxAgents) {
212
- mkdirSync2(dir, { recursive: true });
175
+ mkdirSync(dir, { recursive: true });
213
176
  cleanStaleAgents(dir);
214
177
  for (let n = 1; n <= maxAgents; n++) {
215
- const lockFile = join3(dir, `agent-${n}.lock`);
216
- if (!existsSync3(lockFile)) {
178
+ const lockFile = join2(dir, `agent-${n}.lock`);
179
+ if (!existsSync2(lockFile)) {
217
180
  writeFileSync(lockFile, String(process.pid));
218
181
  return `agent-${n}`;
219
182
  }
@@ -221,22 +184,27 @@ function acquireAgentId(dir, maxAgents) {
221
184
  throw new Error(`All ${maxAgents} agent slots are occupied. Wait for one to finish or increase max_agents.`);
222
185
  }
223
186
  function releaseAgentId(dir, agentName) {
224
- const lockFile = join3(dir, `${agentName}.lock`);
187
+ const lockFile = join2(dir, `${agentName}.lock`);
225
188
  try {
226
189
  unlinkSync(lockFile);
227
190
  } catch {
228
191
  }
229
192
  }
193
+ function isMultiAgentLoop(loop) {
194
+ return loop.multi_agent !== false && loop.multi_agent.enabled === true;
195
+ }
230
196
  function checkTrackerForCompletion(flowDir, loop) {
231
- const trackerPath = join3(flowDir, loop.tracker);
232
- if (!existsSync3(trackerPath)) return false;
233
- const content = readFileSync2(trackerPath, "utf-8");
197
+ const trackerPath = join2(flowDir, loop.tracker);
198
+ if (!existsSync2(trackerPath)) return false;
199
+ const content = readFileSync(trackerPath, "utf-8");
234
200
  return content.includes(`<promise>${loop.completion}</promise>`) || content.includes(loop.completion);
235
201
  }
236
202
  function checkTrackerMetadataCompletion(flowDir, loop) {
237
- const trackerPath = join3(flowDir, loop.tracker);
238
- if (!existsSync3(trackerPath)) return false;
239
- const content = readFileSync2(trackerPath, "utf-8");
203
+ const trackerPath = join2(flowDir, loop.tracker);
204
+ if (!existsSync2(trackerPath)) return false;
205
+ const content = readFileSync(trackerPath, "utf-8");
206
+ const unchecked = (content.match(/- \[ \]/g) || []).length;
207
+ if (unchecked > 0) return false;
240
208
  const completedMatch = content.match(/^- completed_(?:tasks|stories): \[(.+)\]$/m);
241
209
  if (!completedMatch) return false;
242
210
  const completedItems = completedMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
@@ -247,10 +215,10 @@ function checkTrackerMetadataCompletion(flowDir, loop) {
247
215
  if (pendingLines && pendingLines.length > 0) return false;
248
216
  return true;
249
217
  }
250
- function checkTrackerCheckboxes(flowDir, loop) {
251
- const trackerPath = join3(flowDir, loop.tracker);
252
- if (!existsSync3(trackerPath)) return false;
253
- const content = readFileSync2(trackerPath, "utf-8");
218
+ function checkTrackerAllChecked(flowDir, loop) {
219
+ const trackerPath = join2(flowDir, loop.tracker);
220
+ if (!existsSync2(trackerPath)) return false;
221
+ const content = readFileSync(trackerPath, "utf-8");
254
222
  const checked = (content.match(/- \[x\]/gi) || []).length;
255
223
  const unchecked = (content.match(/- \[ \]/g) || []).length;
256
224
  return checked > 0 && unchecked === 0;
@@ -261,7 +229,7 @@ async function runLoop(loopName, options) {
261
229
  const { key, loop } = resolveLoop(config, loopName);
262
230
  let agentName;
263
231
  let agentDir;
264
- if (options.multiAgent) {
232
+ if (options.multiAgent || isMultiAgentLoop(loop)) {
265
233
  if (loop.multi_agent === false) {
266
234
  throw new Error(`Loop "${loop.name}" does not support multi-agent mode.`);
267
235
  }
@@ -289,33 +257,41 @@ async function runLoop(loopName, options) {
289
257
  process.exit(143);
290
258
  });
291
259
  try {
292
- await iterationLoop(loop, flowDir, options, agentName);
260
+ await iterationLoop(key, loop, flowDir, options, agentName);
293
261
  } finally {
294
262
  cleanup();
295
263
  }
296
264
  }
297
- async function iterationLoop(loop, flowDir, options, agentName, db, flowName) {
265
+ async function iterationLoop(configKey, loop, flowDir, options, agentName, db, flowName, forceFirstIteration) {
298
266
  const loopKey = loop.name;
267
+ const appName = basename(flowDir);
299
268
  for (let i = 1; i <= options.maxIterations; i++) {
300
- if (db && flowName && isLoopComplete(db, flowName, loopKey)) {
301
- console.log(chalk3.green(` \u2713 ${loop.name} \u2014 already complete`));
302
- return;
303
- }
304
- if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
305
- if (db && flowName) markLoopComplete(db, flowName, loopKey);
306
- console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete`));
307
- return;
269
+ if (!(forceFirstIteration && i === 1)) {
270
+ if (db && flowName && isLoopComplete(db, flowName, loopKey)) {
271
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 already complete`));
272
+ return;
273
+ }
274
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop) || checkTrackerAllChecked(flowDir, loop)) {
275
+ if (db && flowName) markLoopComplete(db, flowName, loopKey);
276
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete`));
277
+ return;
278
+ }
308
279
  }
309
280
  const label = agentName ? chalk3.dim(` [${agentName}] Iteration ${i}/${options.maxIterations}`) : chalk3.dim(` Iteration ${i}/${options.maxIterations}`);
310
281
  console.log(label);
311
282
  const prompt = readPrompt(loop, flowDir, agentName);
283
+ const effectiveModel = options.model || loop.model;
312
284
  const result = await spawnClaude({
313
285
  prompt,
314
- model: options.model,
315
- cwd: options.cwd
286
+ model: effectiveModel,
287
+ cwd: options.cwd,
288
+ env: {
289
+ RALPHFLOW_APP: appName,
290
+ RALPHFLOW_LOOP: configKey
291
+ }
316
292
  });
317
293
  if (db && flowName) incrementIteration(db, flowName, loopKey);
318
- if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
294
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop) || checkTrackerAllChecked(flowDir, loop)) {
319
295
  if (db && flowName) markLoopComplete(db, flowName, loopKey);
320
296
  console.log();
321
297
  console.log(chalk3.green(` Loop complete: ${loop.completion}`));
@@ -336,54 +312,145 @@ async function iterationLoop(loop, flowDir, options, agentName, db, flowName) {
336
312
  }
337
313
  console.log(chalk3.yellow(` Max iterations (${options.maxIterations}) reached.`));
338
314
  }
339
- async function runAllLoops(options) {
340
- const flowDir = resolveFlowDir(options.cwd, options.flow);
341
- const config = loadConfig(flowDir);
342
- const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
343
- console.log();
344
- console.log(chalk3.bold(" RalphFlow \u2014 Running all loops"));
345
- console.log();
346
- for (const [key, loop] of sortedLoops) {
347
- console.log(chalk3.bold(` Starting: ${loop.name}`));
348
- await iterationLoop(loop, flowDir, options);
349
- }
350
- console.log(chalk3.green(" All loops complete."));
351
- }
352
315
  async function runE2E(options) {
353
316
  const flowDir = resolveFlowDir(options.cwd, options.flow);
354
317
  const config = loadConfig(flowDir);
355
318
  const flowName = options.flow || basename(flowDir);
356
319
  const db = getDb(options.cwd);
357
- const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
358
- console.log();
359
- console.log(chalk3.bold(" RalphFlow \u2014 E2E"));
360
- console.log();
361
- for (const [key, loop] of sortedLoops) {
362
- const loopKey = loop.name;
363
- if (isLoopComplete(db, flowName, loopKey)) {
364
- console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
365
- continue;
366
- }
367
- if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
368
- markLoopComplete(db, flowName, loopKey);
369
- console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
370
- continue;
320
+ for (const loop of Object.values(config.loops)) {
321
+ resetLoopState(db, flowName, loop.name);
322
+ }
323
+ let cycle = 1;
324
+ while (true) {
325
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
326
+ console.log();
327
+ console.log(chalk3.bold(` RalphFlow \u2014 E2E` + (cycle > 1 ? ` (cycle ${cycle})` : "")));
328
+ console.log();
329
+ let anyLoopRan = false;
330
+ for (let idx = 0; idx < sortedLoops.length; idx++) {
331
+ const [key, loop] = sortedLoops[idx];
332
+ const loopKey = loop.name;
333
+ const isFirstLoop = idx === 0;
334
+ if (!isFirstLoop) {
335
+ if (isLoopComplete(db, flowName, loopKey)) {
336
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
337
+ continue;
338
+ }
339
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop) || checkTrackerAllChecked(flowDir, loop)) {
340
+ markLoopComplete(db, flowName, loopKey);
341
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
342
+ continue;
343
+ }
344
+ }
345
+ anyLoopRan = true;
346
+ markLoopRunning(db, flowName, loopKey);
347
+ console.log(chalk3.bold(` \u2192 ${loop.name}`));
348
+ let agentName;
349
+ let agentDir;
350
+ if (isMultiAgentLoop(loop)) {
351
+ agentDir = agentsDir(flowDir, loop);
352
+ agentName = acquireAgentId(agentDir, loop.multi_agent.max_agents);
353
+ }
354
+ try {
355
+ await iterationLoop(key, loop, flowDir, options, agentName, db, flowName, isFirstLoop);
356
+ } finally {
357
+ if (agentDir && agentName) releaseAgentId(agentDir, agentName);
358
+ }
359
+ if (isLoopComplete(db, flowName, loopKey)) {
360
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 done`));
361
+ } else {
362
+ console.log(chalk3.yellow(` \u26A0 ${loop.name} \u2014 max iterations, advancing`));
363
+ }
364
+ console.log();
371
365
  }
372
- markLoopRunning(db, flowName, loopKey);
373
- console.log(chalk3.bold(` \u2192 ${loop.name}`));
374
- await iterationLoop(loop, flowDir, options, void 0, db, flowName);
375
- if (isLoopComplete(db, flowName, loopKey)) {
376
- console.log(chalk3.green(` \u2713 ${loop.name} \u2014 done`));
377
- } else {
378
- console.log(chalk3.yellow(` \u26A0 ${loop.name} \u2014 max iterations, advancing`));
366
+ if (!hasUndeliveredStories(flowDir, config)) {
367
+ break;
379
368
  }
380
- console.log();
369
+ console.log(chalk3.cyan(" \u21BB Undelivered stories found \u2014 starting new cycle"));
370
+ prepareNextCycle(flowDir, config, db, flowName);
371
+ cycle++;
381
372
  }
382
373
  console.log(chalk3.green(" \u2713 E2E complete"));
383
374
  }
375
+ function hasUndeliveredStories(flowDir, config) {
376
+ const storyEntity = config.entities?.STORY;
377
+ if (!storyEntity) return false;
378
+ const storiesPath = join2(flowDir, storyEntity.data_file);
379
+ if (!existsSync2(storiesPath)) return false;
380
+ const storiesContent = readFileSync(storiesPath, "utf-8");
381
+ const storyIds = [...storiesContent.matchAll(/^## (STORY-\d+):/gm)].map((m) => m[1]);
382
+ if (storyIds.length === 0) return false;
383
+ const sortedLoops = Object.values(config.loops).sort((a, b) => a.order - b.order);
384
+ const deliveryLoop = sortedLoops[sortedLoops.length - 1];
385
+ if (!deliveryLoop) return false;
386
+ const deliveryTrackerPath = join2(flowDir, deliveryLoop.tracker);
387
+ if (!existsSync2(deliveryTrackerPath)) return true;
388
+ const deliveryContent = readFileSync(deliveryTrackerPath, "utf-8");
389
+ const deliveredSection = deliveryContent.split("## Delivered")[1] || "";
390
+ const deliveredIds = [...deliveredSection.matchAll(/\[x\]\s*(STORY-\d+)/gi)].map((m) => m[1]);
391
+ return storyIds.some((id) => !deliveredIds.includes(id));
392
+ }
393
+ function prepareNextCycle(flowDir, config, db, flowName) {
394
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
395
+ for (const [, loop] of sortedLoops) {
396
+ resetLoopState(db, flowName, loop.name);
397
+ }
398
+ const storyLoop = sortedLoops.find(([key]) => key.includes("story"));
399
+ if (storyLoop) {
400
+ const [, storyLoopConfig] = storyLoop;
401
+ const trackerPath = join2(flowDir, storyLoopConfig.tracker);
402
+ if (existsSync2(trackerPath)) {
403
+ let content = readFileSync(trackerPath, "utf-8");
404
+ content = content.replace(new RegExp(`<promise>${storyLoopConfig.completion}</promise>`, "g"), "");
405
+ content = content.replace(new RegExp(storyLoopConfig.completion, "g"), "");
406
+ const storyEntity = config.entities?.STORY;
407
+ if (storyEntity) {
408
+ const storiesPath = join2(flowDir, storyEntity.data_file);
409
+ if (existsSync2(storiesPath)) {
410
+ const storiesContent = readFileSync(storiesPath, "utf-8");
411
+ const allStories = [...storiesContent.matchAll(/^## (STORY-\d+): (.+)$/gm)].map((m) => ({ id: m[1], title: m[2] }));
412
+ const existingIds = [...content.matchAll(/\[[ x]\]\s*(STORY-\d+)/gi)].map((m) => m[1]);
413
+ const newStories = allStories.filter((s) => !existingIds.includes(s.id));
414
+ if (newStories.length > 0) {
415
+ const queueSection = "## Stories Queue";
416
+ const queueIdx = content.indexOf(queueSection);
417
+ if (queueIdx !== -1) {
418
+ const afterQueue = content.indexOf("\n##", queueIdx + queueSection.length);
419
+ const insertPos = afterQueue !== -1 ? afterQueue : content.length;
420
+ const newEntries = newStories.map((s) => `- [ ] ${s.id}: ${s.title}`).join("\n");
421
+ content = content.slice(0, insertPos) + "\n" + newEntries + "\n" + content.slice(insertPos);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ content = content.replace(/^- active_story: .+$/m, "- active_story: none");
427
+ content = content.replace(/^- stage: .+$/m, "- stage: analyze");
428
+ content = content.replace(/^- pending_stories: .+$/m, "- pending_stories: []");
429
+ writeFileSync(trackerPath, content);
430
+ }
431
+ }
432
+ const deliveryLoop = sortedLoops[sortedLoops.length - 1];
433
+ if (deliveryLoop) {
434
+ const [, deliveryLoopConfig] = deliveryLoop;
435
+ const trackerPath = join2(flowDir, deliveryLoopConfig.tracker);
436
+ if (existsSync2(trackerPath)) {
437
+ let content = readFileSync(trackerPath, "utf-8");
438
+ content = content.replace(new RegExp(`<promise>${deliveryLoopConfig.completion}</promise>`, "g"), "");
439
+ content = content.replace(new RegExp(deliveryLoopConfig.completion, "g"), "");
440
+ content = content.replace(
441
+ /(## Delivery Queue\n)[\s\S]*?(## Delivered)/,
442
+ "$1\n$2"
443
+ );
444
+ content = content.replace(/^- active_story: .+$/m, "- active_story: none");
445
+ content = content.replace(/^- stage: .+$/m, "- stage: idle");
446
+ content = content.replace(/^- feedback: .+$/m, "- feedback: none");
447
+ writeFileSync(trackerPath, content);
448
+ }
449
+ }
450
+ }
384
451
  function readPrompt(loop, flowDir, agentName) {
385
- const promptPath = join3(flowDir, loop.prompt);
386
- let prompt = readFileSync2(promptPath, "utf-8");
452
+ const promptPath = join2(flowDir, loop.prompt);
453
+ let prompt = readFileSync(promptPath, "utf-8");
387
454
  const appName = basename(flowDir);
388
455
  prompt = prompt.replaceAll("{{APP_NAME}}", appName);
389
456
  if (agentName && loop.multi_agent !== false) {
@@ -400,7 +467,7 @@ var runCommand = new Command2("run").description("Run a loop").argument("<loop>"
400
467
  try {
401
468
  let dashboardHandle;
402
469
  if (opts.ui) {
403
- const { startDashboard } = await import("./server-O6J52DZT.js");
470
+ const { startDashboard } = await import("./server-DOSLU36L.js");
404
471
  dashboardHandle = await startDashboard({ cwd: process.cwd() });
405
472
  }
406
473
  await runLoop(loop, {
@@ -426,7 +493,7 @@ var e2eCommand = new Command3("e2e").description("Run all loops end-to-end with
426
493
  try {
427
494
  let dashboardHandle;
428
495
  if (opts.ui) {
429
- const { startDashboard } = await import("./server-O6J52DZT.js");
496
+ const { startDashboard } = await import("./server-DOSLU36L.js");
430
497
  dashboardHandle = await startDashboard({ cwd: process.cwd() });
431
498
  }
432
499
  await runE2E({
@@ -463,167 +530,32 @@ var statusCommand = new Command4("status").description("Show pipeline status").o
463
530
  // src/cli/dashboard.ts
464
531
  import { Command as Command5 } from "commander";
465
532
  var dashboardCommand = new Command5("dashboard").alias("ui").description("Start the web dashboard").option("-p, --port <port>", "Port number", "4242").action(async (opts) => {
466
- const { startDashboard } = await import("./server-O6J52DZT.js");
533
+ const { startDashboard } = await import("./server-DOSLU36L.js");
467
534
  await startDashboard({ cwd: process.cwd(), port: parseInt(opts.port, 10) });
468
535
  });
469
536
 
470
- // src/cli/menu.ts
471
- import { select, input, confirm } from "@inquirer/prompts";
472
- import chalk7 from "chalk";
473
- async function interactiveMenu(cwd) {
474
- console.log();
475
- console.log(chalk7.bold(" RalphFlow"));
476
- console.log();
477
- try {
478
- const flows = listFlows(cwd);
479
- if (flows.length === 0) {
480
- const action = await select({
481
- message: "What would you like to do?",
482
- choices: [
483
- { name: "Initialize a new app", value: "init" }
484
- ]
485
- });
486
- if (action === "init") {
487
- await handleInit(cwd);
488
- }
489
- } else {
490
- const action = await select({
491
- message: "What would you like to do?",
492
- choices: [
493
- { name: "Run end-to-end", value: "e2e" },
494
- { name: "Run a loop", value: "run" },
495
- { name: "Run all loops in sequence", value: "run-all" },
496
- { name: "Initialize a new app", value: "init" },
497
- { name: "Check status", value: "status" },
498
- { name: "Open dashboard", value: "dashboard" }
499
- ]
500
- });
501
- switch (action) {
502
- case "e2e":
503
- await handleE2E(cwd, flows);
504
- break;
505
- case "run":
506
- await handleRunLoop(cwd, flows);
507
- break;
508
- case "run-all":
509
- await handleRunAll(cwd, flows);
510
- break;
511
- case "init":
512
- await handleInit(cwd);
513
- break;
514
- case "status":
515
- await handleStatus(cwd);
516
- break;
517
- case "dashboard":
518
- await handleDashboard(cwd);
519
- break;
520
- }
521
- }
522
- } catch (err) {
523
- if (err && typeof err === "object" && "name" in err && err.name === "ExitPromptError") {
524
- console.log();
525
- console.log(chalk7.dim(" Cancelled."));
526
- process.exit(0);
527
- }
528
- throw err;
529
- }
530
- }
531
- var TEMPLATES2 = ["code-implementation", "research"];
532
- async function handleInit(cwd) {
533
- const template = await select({
534
- message: "Which template?",
535
- choices: TEMPLATES2.map((t) => ({ name: t, value: t }))
536
- });
537
- const name = await input({
538
- message: "Flow name?",
539
- default: template
540
- });
541
- await initProject(cwd, { template, name });
542
- const shouldRun = await confirm({
543
- message: "Run the first loop now?",
544
- default: true
545
- });
546
- if (shouldRun) {
547
- const flowDir = resolveFlowDir(cwd, name);
548
- const config = loadConfig(flowDir);
549
- const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
550
- if (sortedLoops.length > 0) {
551
- const [firstKey] = sortedLoops[0];
552
- await runLoop(firstKey, {
553
- multiAgent: false,
554
- maxIterations: 30,
555
- cwd,
556
- flow: name
557
- });
558
- }
559
- }
560
- }
561
- async function selectFlow(flows) {
562
- if (flows.length === 1) return flows[0];
563
- return await select({
564
- message: "Which flow?",
565
- choices: flows.map((f) => ({ name: f, value: f }))
566
- });
567
- }
568
- async function handleRunLoop(cwd, flows) {
569
- const flow = await selectFlow(flows);
570
- const flowDir = resolveFlowDir(cwd, flow);
571
- const config = loadConfig(flowDir);
572
- const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
573
- const loopKey = await select({
574
- message: "Which loop?",
575
- choices: sortedLoops.map(([key, loop]) => ({
576
- name: loop.name,
577
- value: key
578
- }))
579
- });
580
- await runLoop(loopKey, {
581
- multiAgent: false,
582
- maxIterations: 30,
583
- cwd,
584
- flow
585
- });
586
- }
587
- async function handleRunAll(cwd, flows) {
588
- const flow = await selectFlow(flows);
589
- await runAllLoops({
590
- multiAgent: false,
591
- maxIterations: 30,
592
- cwd,
593
- flow
594
- });
595
- }
596
- async function handleE2E(cwd, flows) {
597
- const flow = await selectFlow(flows);
598
- await runE2E({
599
- multiAgent: false,
600
- maxIterations: 30,
601
- cwd,
602
- flow
603
- });
604
- }
605
- async function handleStatus(cwd) {
606
- await showStatus(cwd);
607
- }
608
- async function handleDashboard(cwd) {
609
- const { startDashboard } = await import("./server-O6J52DZT.js");
610
- await startDashboard({ cwd });
611
- }
612
-
613
537
  // src/cli/index.ts
614
538
  var program = new Command6().name("ralphflow").description("Multi-agent AI workflow orchestration for Claude Code").version("0.1.0").addCommand(initCommand).addCommand(runCommand).addCommand(e2eCommand).addCommand(statusCommand).addCommand(dashboardCommand).action(async () => {
615
- await interactiveMenu(process.cwd());
539
+ const port = 4242;
540
+ const { startDashboard } = await import("./server-DOSLU36L.js");
541
+ await startDashboard({ cwd: process.cwd(), port });
542
+ const url = `http://localhost:${port}`;
543
+ exec(`open "${url}"`, (err) => {
544
+ if (err) {
545
+ console.log(chalk7.dim(` Open ${url} in your browser`));
546
+ }
547
+ });
616
548
  });
617
549
  process.on("SIGINT", () => {
618
550
  console.log();
619
- console.log(chalk8.dim(" Interrupted."));
551
+ console.log(chalk7.dim(" Interrupted."));
620
552
  process.exit(130);
621
553
  });
622
554
  program.configureOutput({
623
555
  writeErr: (str) => {
624
556
  const clean = str.replace(/^error: /, "");
625
557
  if (clean.trim()) {
626
- console.error(chalk8.red(` ${clean.trim()}`));
558
+ console.error(chalk7.red(` ${clean.trim()}`));
627
559
  }
628
560
  }
629
561
  });