baro-ai 0.47.5 → 0.47.7

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/cli.mjs CHANGED
@@ -39754,6 +39754,13 @@ var Conductor = class extends BaseObserver {
39754
39754
  * is true only when this list is empty.
39755
39755
  */
39756
39756
  globalDropped = [];
39757
+ /**
39758
+ * Extra end-of-run recovery attempts, separate from each StoryAgent's own
39759
+ * retry loop. A story may exhaust its normal attempts, block the DAG, and
39760
+ * still be recoverable after sibling stories have landed more context/code.
39761
+ */
39762
+ recoveryAttempts = /* @__PURE__ */ new Map();
39763
+ maxRecoveryAttemptsPerStory = 1;
39757
39764
  totalAttempts = 0;
39758
39765
  appliedReplans = 0;
39759
39766
  currentLevel = null;
@@ -39860,10 +39867,23 @@ var Conductor = class extends BaseObserver {
39860
39867
  async handleLevelCompute() {
39861
39868
  if (this.phase !== "computing") return;
39862
39869
  if (!this.prd) return;
39863
- const levels = buildDag(this.prd.userStories, { onlyIncomplete: true });
39870
+ const blockedStoryIds = this.computeBlockedStoryIds();
39871
+ const failedIds = new Set(this.globalFailed);
39872
+ const runnableStories = this.prd.userStories.filter(
39873
+ (s2) => !s2.passes && !failedIds.has(s2.id) && !blockedStoryIds.has(s2.id)
39874
+ );
39875
+ const levels = buildDag(runnableStories);
39864
39876
  if (levels.length === 0) {
39865
39877
  const allPassed = this.prd.userStories.every((s2) => s2.passes) && this.globalDropped.length === 0;
39866
- this.terminateRun(allPassed, null);
39878
+ if (!allPassed && this.globalFailed.length > 0) {
39879
+ const recovered = await this.tryStartRecoveryLevel(
39880
+ this.globalFailed,
39881
+ blockedStoryIds.size > 0 ? `blocked dependencies: ${[...blockedStoryIds].join(", ")}` : "terminal failed stories"
39882
+ );
39883
+ if (recovered) return;
39884
+ }
39885
+ const abortReason = allPassed ? null : this.globalFailed.length > 0 ? blockedStoryIds.size > 0 ? `blocked by failed dependencies: failed ${this.globalFailed.join(", ")}; blocked ${[...blockedStoryIds].join(", ")}` : `stories failed: ${this.globalFailed.join(", ")}` : null;
39886
+ this.terminateRun(allPassed, abortReason);
39867
39887
  return;
39868
39888
  }
39869
39889
  const level = levels[0];
@@ -39913,6 +39933,7 @@ var Conductor = class extends BaseObserver {
39913
39933
  if (item.success) {
39914
39934
  this.currentLevel.passed.push(item.storyId);
39915
39935
  this.globalCompleted.push(item.storyId);
39936
+ this.removeGlobalFailed(item.storyId);
39916
39937
  if (this.prd) {
39917
39938
  this.prd = markStoryPassed(this.prd, item.storyId, item.durationSecs);
39918
39939
  savePrd(this.opts.prdPath, this.prd);
@@ -39933,7 +39954,7 @@ var Conductor = class extends BaseObserver {
39933
39954
  }
39934
39955
  } else {
39935
39956
  this.currentLevel.failed.push(item.storyId);
39936
- this.globalFailed.push(item.storyId);
39957
+ this.addGlobalFailed(item.storyId);
39937
39958
  }
39938
39959
  await this.fillSpawnSlots();
39939
39960
  if (this.currentLevel.pending.size === 0) {
@@ -40017,6 +40038,16 @@ ${prompt}`;
40017
40038
  if (this.pendingReplans.length > 0 && this.prd) {
40018
40039
  const drained = this.pendingReplans.splice(0);
40019
40040
  for (const replan of drained) {
40041
+ if (replan.removedStoryIds.length > 0 && replan.addedStories.length === 0) {
40042
+ this.emit(
40043
+ ConductorState.create({
40044
+ phase: "running_level",
40045
+ detail: `skip proposal deferred (source=${replan.source}, -${replan.removedStoryIds.length}): ${replan.reason}`,
40046
+ currentLevel: lvl.ordinal
40047
+ })
40048
+ );
40049
+ continue;
40050
+ }
40020
40051
  this.prd = applyReplan(this.prd, replan);
40021
40052
  this.appliedReplans += 1;
40022
40053
  replannedThisLevel = true;
@@ -40049,6 +40080,11 @@ ${prompt}`;
40049
40080
  const anySuccess = lvl.passed.length > 0;
40050
40081
  const totalThisLevel = lvl.passed.length + lvl.failed.length;
40051
40082
  if (!anySuccess && totalThisLevel > 0 && !replannedThisLevel) {
40083
+ const recovered = await this.tryStartRecoveryLevel(
40084
+ lvl.failed,
40085
+ "all stories in level failed"
40086
+ );
40087
+ if (recovered) return;
40052
40088
  this.terminateRun(
40053
40089
  false,
40054
40090
  "all stories in level failed; aborting remaining levels"
@@ -40094,6 +40130,99 @@ ${prompt}`;
40094
40130
  }
40095
40131
  this.resolveDone(summary);
40096
40132
  }
40133
+ /**
40134
+ * Stories whose dependency chain includes a terminally failed story cannot
40135
+ * become runnable in this run. Keep them out of the remaining DAG so
40136
+ * `buildDag` cannot silently promote them after the failed dependency is
40137
+ * filtered out. Do NOT mark them passed/dropped here: this is a checkpoint,
40138
+ * and the user may choose to rerun the failed prerequisite on resume.
40139
+ */
40140
+ computeBlockedStoryIds() {
40141
+ if (!this.prd || this.globalFailed.length === 0) {
40142
+ return /* @__PURE__ */ new Set();
40143
+ }
40144
+ const failed = new Set(this.globalFailed);
40145
+ const blocked = /* @__PURE__ */ new Set();
40146
+ let changed = true;
40147
+ while (changed) {
40148
+ changed = false;
40149
+ for (const story of this.prd.userStories) {
40150
+ if (story.passes || failed.has(story.id) || blocked.has(story.id)) {
40151
+ continue;
40152
+ }
40153
+ if ((story.dependsOn ?? []).some((id) => failed.has(id) || blocked.has(id))) {
40154
+ blocked.add(story.id);
40155
+ changed = true;
40156
+ }
40157
+ }
40158
+ }
40159
+ return blocked;
40160
+ }
40161
+ addGlobalFailed(storyId) {
40162
+ if (!this.globalFailed.includes(storyId)) {
40163
+ this.globalFailed.push(storyId);
40164
+ }
40165
+ }
40166
+ removeGlobalFailed(storyId) {
40167
+ for (let i2 = this.globalFailed.length - 1; i2 >= 0; i2--) {
40168
+ if (this.globalFailed[i2] === storyId) {
40169
+ this.globalFailed.splice(i2, 1);
40170
+ }
40171
+ }
40172
+ }
40173
+ async tryStartRecoveryLevel(candidateIds, reason) {
40174
+ if (!this.prd) return false;
40175
+ const seen = /* @__PURE__ */ new Set();
40176
+ const stories = [];
40177
+ for (const id of candidateIds) {
40178
+ if (seen.has(id)) continue;
40179
+ seen.add(id);
40180
+ const attempts = this.recoveryAttempts.get(id) ?? 0;
40181
+ if (attempts >= this.maxRecoveryAttemptsPerStory) continue;
40182
+ const story = this.prd.userStories.find((s2) => s2.id === id);
40183
+ if (!story || story.passes) continue;
40184
+ stories.push(story);
40185
+ }
40186
+ if (stories.length === 0) return false;
40187
+ for (const story of stories) {
40188
+ this.recoveryAttempts.set(
40189
+ story.id,
40190
+ (this.recoveryAttempts.get(story.id) ?? 0) + 1
40191
+ );
40192
+ this.removeGlobalFailed(story.id);
40193
+ }
40194
+ const ordinal = (this.currentLevel?.ordinal ?? 0) + 1;
40195
+ const totalLevelsHint = ordinal;
40196
+ this.currentLevel = {
40197
+ ordinal,
40198
+ totalLevelsHint,
40199
+ storyIds: stories.map((s2) => s2.id),
40200
+ pending: new Set(stories.map((s2) => s2.id)),
40201
+ passed: [],
40202
+ failed: [],
40203
+ perStoryAttempts: /* @__PURE__ */ new Map()
40204
+ };
40205
+ this.emit(
40206
+ LevelStarted.create({
40207
+ ordinal,
40208
+ totalLevelsHint,
40209
+ storyIds: this.currentLevel.storyIds
40210
+ })
40211
+ );
40212
+ this.emit(
40213
+ ConductorState.create({
40214
+ phase: "running_level",
40215
+ detail: `auto-recovery retry for ${this.currentLevel.storyIds.join(", ")} (${reason})`,
40216
+ currentLevel: ordinal,
40217
+ totalLevels: totalLevelsHint,
40218
+ storyIds: this.currentLevel.storyIds
40219
+ })
40220
+ );
40221
+ this.phase = "running";
40222
+ this.spawnQueue.push(...stories);
40223
+ await this.fillSpawnSlots();
40224
+ return true;
40225
+ }
40097
40226
  resolvePrompt(story) {
40098
40227
  const candidatePath = this.opts.promptTemplatePath ?? join(this.opts.cwd, "prompt.md");
40099
40228
  let prompt;
@@ -40920,8 +41049,10 @@ var Finalizer = class extends BaseObserver {
40920
41049
  }
40921
41050
  async finalize(run) {
40922
41051
  if (!this.opts.createPr) return;
40923
- if (!run.success && run.completedStories.length === 0) {
40924
- this.log("[finalizer] run failed before producing any commits; skipping PR");
41052
+ if (!run.success) {
41053
+ this.log(
41054
+ `[finalizer] run did not complete successfully (${run.abortReason ?? "no reason"}); skipping PR`
41055
+ );
40925
41056
  this.emit(
40926
41057
  PrCreated.create({
40927
41058
  url: null,