ralph-cli-sandboxed 0.6.4 → 0.6.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.
@@ -118,11 +118,11 @@ function setupWorktreeRalphDir(worktreePath, branchItems, workspacePaths) {
118
118
  }
119
119
  /**
120
120
  * Creates a filtered PRD file containing only incomplete items (passes: false).
121
- * Optionally filters by category if specified.
121
+ * Optionally filters by category and/or branch if specified.
122
122
  * Expands @{filepath} references to include file contents.
123
123
  * Returns the path to the temp file, or null if all items pass.
124
124
  */
125
- function createFilteredPrd(prdPath, baseDir, category) {
125
+ function createFilteredPrd(prdPath, baseDir, category, branchFilterActive, branchFilter) {
126
126
  // Use readPrdFile to handle both JSON and YAML formats
127
127
  const parsed = readPrdFile(prdPath);
128
128
  if (!parsed) {
@@ -145,6 +145,10 @@ function createFilteredPrd(prdPath, baseDir, category) {
145
145
  if (category) {
146
146
  filteredItems = filteredItems.filter((item) => item.category === category);
147
147
  }
148
+ // Apply branch filter if specified
149
+ if (branchFilterActive) {
150
+ filteredItems = applyBranchFilter(filteredItems, branchFilter);
151
+ }
148
152
  // Expand @{filepath} references in description and steps
149
153
  const expandedItems = expandPrdFileReferences(filteredItems, baseDir);
150
154
  // Write to .ralph/prd-tasks.json so LLMs see a sensible path
@@ -406,11 +410,24 @@ function formatElapsedTime(startTime, endTime) {
406
410
  parts.push(`${seconds}s`);
407
411
  return parts.join(" ");
408
412
  }
413
+ /**
414
+ * Filters PRD items by branch.
415
+ * - branchFilter = "name": only items whose branch field matches "name"
416
+ * - branchFilter = "": only items that have any branch field set (non-empty)
417
+ */
418
+ function applyBranchFilter(items, branchFilter) {
419
+ if (branchFilter === "") {
420
+ // --branch without value: include only items that have a branch set
421
+ return items.filter((item) => item.branch && item.branch.length > 0);
422
+ }
423
+ // --branch <name>: include only items whose branch matches
424
+ return items.filter((item) => item.branch === branchFilter);
425
+ }
409
426
  /**
410
427
  * Counts total and incomplete items in the PRD.
411
- * Optionally filters by category if specified.
428
+ * Optionally filters by category and/or branch if specified.
412
429
  */
413
- function countPrdItems(prdPath, category) {
430
+ function countPrdItems(prdPath, category, branchFilterActive, branchFilter) {
414
431
  // Use readPrdFile to handle both JSON and YAML formats
415
432
  const parsed = readPrdFile(prdPath);
416
433
  if (!parsed) {
@@ -430,7 +447,10 @@ function countPrdItems(prdPath, category) {
430
447
  const items = parsed.content;
431
448
  let filteredItems = items;
432
449
  if (category) {
433
- filteredItems = items.filter((item) => item.category === category);
450
+ filteredItems = filteredItems.filter((item) => item.category === category);
451
+ }
452
+ if (branchFilterActive) {
453
+ filteredItems = applyBranchFilter(filteredItems, branchFilter);
434
454
  }
435
455
  const complete = filteredItems.filter((item) => item.passes === true).length;
436
456
  const incomplete = filteredItems.filter((item) => item.passes === false).length;
@@ -537,6 +557,8 @@ function loadValidPrd(prdPath) {
537
557
  export async function run(args) {
538
558
  // Parse flags
539
559
  let category;
560
+ let branchFilter; // undefined = no filter, "" = any branch, "name" = specific branch
561
+ let branchFilterActive = false; // true when --branch flag is present
540
562
  let model;
541
563
  let loopMode = false;
542
564
  let allModeExplicit = false;
@@ -554,6 +576,18 @@ export async function run(args) {
554
576
  process.exit(1);
555
577
  }
556
578
  }
579
+ else if (args[i] === "--branch" || args[i] === "-b") {
580
+ branchFilterActive = true;
581
+ // Check if next arg is a branch name (not another flag)
582
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
583
+ branchFilter = args[i + 1];
584
+ i++; // Skip the branch value
585
+ }
586
+ else {
587
+ // --branch without a value: filter to items that have any branch set
588
+ branchFilter = "";
589
+ }
590
+ }
557
591
  else if (args[i] === "--model" || args[i] === "-m") {
558
592
  if (i + 1 < args.length) {
559
593
  model = args[i + 1];
@@ -638,7 +672,7 @@ export async function run(args) {
638
672
  // Container is required, so always run with skip-permissions
639
673
  const sandboxed = true;
640
674
  if (allMode) {
641
- const counts = countPrdItems(paths.prd, category);
675
+ const counts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
642
676
  console.log("Starting ralph in --all mode (runs until all tasks complete)...");
643
677
  console.log(`PRD Status: ${counts.complete}/${counts.total} complete, ${counts.incomplete} remaining`);
644
678
  }
@@ -651,6 +685,25 @@ export async function run(args) {
651
685
  if (category) {
652
686
  console.log(`Filtering PRD items by category: ${category}`);
653
687
  }
688
+ if (branchFilterActive) {
689
+ if (branchFilter === "") {
690
+ console.log("Filtering PRD items to only branched items");
691
+ }
692
+ else {
693
+ console.log(`Filtering PRD items by branch: ${branchFilter}`);
694
+ }
695
+ // Check if any items match the branch filter; if not, exit early
696
+ const branchCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
697
+ if (branchCounts.total === 0) {
698
+ if (branchFilter === "") {
699
+ console.error("\nNo PRD items have a branch field set.");
700
+ }
701
+ else {
702
+ console.error(`\nNo PRD items match branch "${branchFilter}".`);
703
+ }
704
+ process.exit(1);
705
+ }
706
+ }
654
707
  if (streamJson?.enabled) {
655
708
  console.log("Stream JSON output enabled - displaying formatted Claude output");
656
709
  if (streamJson.saveRawJson) {
@@ -668,7 +721,7 @@ export async function run(args) {
668
721
  let iterationCount = 0;
669
722
  // Progress tracking for --all mode
670
723
  // Progress = tasks completed OR new tasks added (allows ralph to expand the PRD)
671
- const initialCounts = countPrdItems(paths.prd, category);
724
+ const initialCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
672
725
  let lastCompletedCount = initialCounts.complete;
673
726
  let lastTotalCount = initialCounts.total;
674
727
  let iterationsWithoutProgress = 0;
@@ -796,7 +849,7 @@ export async function run(args) {
796
849
  try {
797
850
  while (true) {
798
851
  iterationCount++;
799
- const currentCounts = countPrdItems(paths.prd, category);
852
+ const currentCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
800
853
  // Check if we should stop (not in loop mode)
801
854
  if (!loopMode && !allMode) {
802
855
  if (iterationCount > requestedIterations) {
@@ -823,6 +876,9 @@ export async function run(args) {
823
876
  if (category) {
824
877
  itemsForIteration = itemsForIteration.filter((item) => item.category === category);
825
878
  }
879
+ if (branchFilterActive) {
880
+ itemsForIteration = applyBranchFilter(itemsForIteration, branchFilter);
881
+ }
826
882
  const branchGroups = groupItemsByBranch(itemsForIteration);
827
883
  // Check if there are any incomplete items
828
884
  if (branchGroups.size === 0) {
@@ -838,7 +894,7 @@ export async function run(args) {
838
894
  console.log("=".repeat(50));
839
895
  while (true) {
840
896
  await sleep(POLL_INTERVAL_MS);
841
- const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category);
897
+ const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
842
898
  if (newItems) {
843
899
  console.log("\nNew incomplete item(s) detected! Resuming...");
844
900
  break;
@@ -850,7 +906,7 @@ export async function run(args) {
850
906
  else {
851
907
  console.log("\n" + "=".repeat(50));
852
908
  if (allMode) {
853
- const counts = countPrdItems(paths.prd, category);
909
+ const counts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
854
910
  if (category) {
855
911
  console.log(`PRD COMPLETE - All "${category}" tasks finished!`);
856
912
  }
@@ -980,7 +1036,7 @@ export async function run(args) {
980
1036
  console.log(`\n\x1b[36m--- No-branch items (${noBranchItems.length} item(s)) ---\x1b[0m`);
981
1037
  }
982
1038
  // Create filtered PRD for no-branch items only (or all items if no branches exist)
983
- const { tempPath } = createFilteredPrd(paths.prd, paths.dir, category);
1039
+ const { tempPath } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
984
1040
  filteredPrdPath = tempPath;
985
1041
  // If there are branch groups, rewrite prd-tasks.json to only include no-branch items
986
1042
  if (hasBranches) {
@@ -999,7 +1055,7 @@ export async function run(args) {
999
1055
  validateAndRecoverPrd(paths.prd, validPrd);
1000
1056
  // Track progress for --all mode: stop if no progress after N iterations
1001
1057
  if (allMode) {
1002
- const progressCounts = countPrdItems(paths.prd, category);
1058
+ const progressCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
1003
1059
  const tasksCompleted = progressCounts.complete > lastCompletedCount;
1004
1060
  const tasksAdded = progressCounts.total > lastTotalCount;
1005
1061
  if (tasksCompleted || tasksAdded) {
@@ -1060,7 +1116,7 @@ export async function run(args) {
1060
1116
  // so its COMPLETE signal means "this group is done", not "all PRD items are done".
1061
1117
  // We must verify the full PRD before treating this as a global completion.
1062
1118
  if (iterOutput.includes("<promise>COMPLETE</promise>")) {
1063
- const fullCounts = countPrdItems(paths.prd, category);
1119
+ const fullCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
1064
1120
  if (fullCounts.incomplete > 0) {
1065
1121
  // There are still incomplete items in other groups — continue the loop
1066
1122
  if (debug) {
@@ -1074,7 +1130,7 @@ export async function run(args) {
1074
1130
  console.log("=".repeat(50));
1075
1131
  while (true) {
1076
1132
  await sleep(POLL_INTERVAL_MS);
1077
- const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category);
1133
+ const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
1078
1134
  if (newItems) {
1079
1135
  console.log("\nNew incomplete item(s) detected! Resuming...");
1080
1136
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-cli-sandboxed",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "AI-driven development automation CLI for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {