baro-ai 0.110.0 → 0.111.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/cli.mjs CHANGED
@@ -44476,6 +44476,14 @@ var WorktreeManager = class {
44476
44476
  try {
44477
44477
  await this.autoCommitLeftovers(storyId, path6);
44478
44478
  const mergeTarget = candidateSeal ? await this.sealedMergeTarget(storyId, path6, candidateSeal) : branch;
44479
+ const blocked = await this.hostCheckoutBlocks(mergeTarget);
44480
+ if (blocked.length) {
44481
+ this.markPreserved(storyId);
44482
+ throw new WorktreeRefusalError(
44483
+ "host_checkout_dirty",
44484
+ `story ${storyId} cannot land: the host checkout has uncommitted changes on [${blocked.join(", ")}]; the story is intact on ${branch} \u2014 commit or stash those files, then merge it`
44485
+ );
44486
+ }
44479
44487
  const msg = `baro: merge story ${storyId}`;
44480
44488
  try {
44481
44489
  await runRepositoryCommand("git", ["merge", "--no-ff", "-m", msg, mergeTarget], {
@@ -44501,8 +44509,9 @@ var WorktreeManager = class {
44501
44509
  }
44502
44510
  if (!this.resolveConflictsWithTheirs) {
44503
44511
  this.markPreserved(storyId);
44504
- throw new Error(
44505
- `story ${storyId} conflicts with already-merged work` + (conflicts.length ? ` on [${conflicts.join(", ")}]` : "")
44512
+ throw new WorktreeRefusalError(
44513
+ "merge_conflict",
44514
+ conflicts.length ? `story ${storyId} conflicts with already-merged work on [${conflicts.join(", ")}]` : `story ${storyId} merge-back failed without a conflict: ${gitFailureDetail(error)}`
44506
44515
  );
44507
44516
  }
44508
44517
  this.log(
@@ -44997,6 +45006,23 @@ ${missing.join("\n")}
44997
45006
  }
44998
45007
  this.preserved.delete(storyId);
44999
45008
  }
45009
+ /** Tracked paths dirty in the host checkout that the merge would also
45010
+ * write. git refuses such a merge outright; reported as a conflict it
45011
+ * sent two recoveries after the same wall on the first self-hosting run. */
45012
+ async hostCheckoutBlocks(mergeTarget) {
45013
+ const [{ stdout: status }, { stdout: touched }] = await Promise.all([
45014
+ runRepositoryCommand("git", ["status", "--porcelain", "--untracked-files=no"], {
45015
+ cwd: this.repoRoot
45016
+ }),
45017
+ runRepositoryCommand("git", ["diff", "--name-only", "HEAD", mergeTarget], {
45018
+ cwd: this.repoRoot
45019
+ })
45020
+ ]);
45021
+ const dirty = new Set(
45022
+ status.split("\n").filter(Boolean).map(porcelainPath)
45023
+ );
45024
+ return touched.split("\n").map((line) => line.trim()).filter((path6) => path6 && dirty.has(path6));
45025
+ }
45000
45026
  async conflictedPaths() {
45001
45027
  try {
45002
45028
  const { stdout } = await runRepositoryCommand(
@@ -45077,6 +45103,18 @@ function rmSyncQuiet(path6) {
45077
45103
  function errMsg(e2) {
45078
45104
  return e2?.message ?? String(e2);
45079
45105
  }
45106
+ function gitFailureDetail(e2) {
45107
+ if (e2 instanceof RepositoryCommandError) {
45108
+ const line = e2.stderr.split("\n").find((l) => l.trim());
45109
+ if (line) return line.trim();
45110
+ }
45111
+ return errMsg(e2);
45112
+ }
45113
+ function porcelainPath(line) {
45114
+ const entry = line.slice(3);
45115
+ const renamed = entry.indexOf(" -> ");
45116
+ return (renamed === -1 ? entry : entry.slice(renamed + 4)).trim();
45117
+ }
45080
45118
 
45081
45119
  // ../baro-orchestrator/src/runtime/story-outcome-authority.ts
45082
45120
  var StoryOutcomeAuthority = class {
@@ -66597,6 +66635,10 @@ function translateDeclaredTests(cwd, requirements, packageManagers) {
66597
66635
  if (requirement.declarationError) {
66598
66636
  return incomplete(requirement, requirement.declarationError);
66599
66637
  }
66638
+ const scoped = splitCdPrefix(requirement.command);
66639
+ if (scoped) {
66640
+ return translateCdScoped(cwd, requirement, scoped, packageManagers);
66641
+ }
66600
66642
  const parsed = tokenize2(requirement.command);
66601
66643
  if (typeof parsed === "string") return incomplete(requirement, parsed);
66602
66644
  return dispatchDeclared(
@@ -66660,6 +66702,68 @@ function dispatchDeclared(cwd, requirement, parsed, packageManagers, insideDdev)
66660
66702
  "unsupported declared test; allowed tools are npm/pnpm/yarn, exact npx rstest run paths, cargo, node, git diff --check, composer, vendor/bin/phpunit, and ddev exec"
66661
66703
  );
66662
66704
  }
66705
+ var CD_PREFIX = /^cd\s+([^\s&]+)\s*&&\s*([^]*)$/;
66706
+ function splitCdPrefix(command) {
66707
+ if (typeof command !== "string" || command.length > MAX_COMMAND_LENGTH) {
66708
+ return null;
66709
+ }
66710
+ const match = CD_PREFIX.exec(command.trim());
66711
+ return match ? { dir: match[1], inner: match[2] } : null;
66712
+ }
66713
+ function translateCdScoped(root, requirement, scoped, packageManagers) {
66714
+ const dirTokens = tokenize2(scoped.dir);
66715
+ if (typeof dirTokens === "string") return incomplete(requirement, dirTokens);
66716
+ const parsed = tokenize2(scoped.inner);
66717
+ if (typeof parsed === "string") return incomplete(requirement, parsed);
66718
+ const dir = dirTokens.tokens[0];
66719
+ const contained = containedPath(root, dir, false);
66720
+ let isDirectory = false;
66721
+ if (contained.path) {
66722
+ try {
66723
+ isDirectory = statSync3(
66724
+ realpathSync(resolve4(root, contained.path))
66725
+ ).isDirectory();
66726
+ } catch {
66727
+ isDirectory = false;
66728
+ }
66729
+ }
66730
+ if (!contained.path || !isDirectory) {
66731
+ return incomplete(
66732
+ requirement,
66733
+ `cd target '${dir}' must be an existing directory inside the repository`
66734
+ );
66735
+ }
66736
+ if (contained.path === ".") {
66737
+ return dispatchDeclared(root, requirement, parsed, packageManagers, false);
66738
+ }
66739
+ const rel = contained.path;
66740
+ const scopeCwd = resolve4(root, rel);
66741
+ const scopedIncomplete = (reason) => incomplete(requirement, `cd ${rel}: ${reason}`);
66742
+ const tool = parsed.tokens[0];
66743
+ if (tool === "ddev" || tool === "npx") {
66744
+ return scopedIncomplete(
66745
+ `'${tool}' declarations cannot be scoped with cd; declare them from the repository root`
66746
+ );
66747
+ }
66748
+ const alias = trustedScriptAlias(scopeCwd, parsed.tokens);
66749
+ if (alias || /^(npm|pnpm|yarn)$/.test(tool ?? "")) {
66750
+ const base = alias ? ["npm", "run", alias] : parsed.tokens;
66751
+ const [manager, operation, ...rest] = base;
66752
+ const tokens2 = operation === void 0 ? base : [manager, operation, `--workspace=${rel}`, ...rest];
66753
+ const spec2 = translatePackage(
66754
+ root,
66755
+ requirement,
66756
+ { normalized: tokens2.join(" "), tokens: tokens2 },
66757
+ packageManagers
66758
+ );
66759
+ return spec2.incompleteReason === void 0 ? spec2 : scopedIncomplete(spec2.incompleteReason);
66760
+ }
66761
+ const spec = tool === "node" ? translateNode(scopeCwd, requirement, parsed, root) : dispatchDeclared(scopeCwd, requirement, parsed, packageManagers, false);
66762
+ if (spec.incompleteReason !== void 0) {
66763
+ return scopedIncomplete(spec.incompleteReason);
66764
+ }
66765
+ return { ...spec, label: `cd ${rel} && ${spec.label}`, cwd: scopeCwd };
66766
+ }
66663
66767
  function unwrapQuotedToken(token) {
66664
66768
  if (!/["']/.test(token)) return token;
66665
66769
  if (token.length >= 2 && (token[0] === '"' || token[0] === "'") && token[token.length - 1] === token[0]) {
@@ -67166,12 +67270,12 @@ function containedPath(cwd, candidate, requireFile) {
67166
67270
  }
67167
67271
  return { path: (fromRoot || ".").replace(/\\/g, "/") };
67168
67272
  }
67169
- function translateNode(cwd, requirement, parsed) {
67273
+ function translateNode(cwd, requirement, parsed, manifestRoot = cwd) {
67170
67274
  const hasTsxLoader = parsed.tokens[1] === "--import" && parsed.tokens[2] === "tsx";
67171
67275
  const loaderArgs = hasTsxLoader ? ["--import", "tsx"] : [];
67172
67276
  const rest = parsed.tokens.slice(1 + loaderArgs.length);
67173
67277
  const mode = rest[0];
67174
- if (rest.length === 1 && typeof mode === "string" && !mode.startsWith("-") && !existsSync5(join8(cwd, "package.json"))) {
67278
+ if (rest.length === 1 && typeof mode === "string" && !mode.startsWith("-") && !existsSync5(join8(cwd, "package.json")) && !existsSync5(join8(manifestRoot, "package.json"))) {
67175
67279
  const contained = containedPath(cwd, mode, true);
67176
67280
  if (!contained.path) {
67177
67281
  return incomplete(
@@ -69255,27 +69359,31 @@ var GitCoordinator = class extends SerializedObserver {
69255
69359
  );
69256
69360
  } catch (e2) {
69257
69361
  const error = e2?.message ?? String(e2);
69362
+ const invariant = e2?.invariant ?? "unknown";
69258
69363
  let branch = worktrees.branchName(storyId);
69259
69364
  let retryable = false;
69260
69365
  let preparationError = null;
69261
- try {
69262
- branch = await worktrees.prepareConflictRetry(storyId);
69263
- retryable = true;
69264
- } catch (prepareError) {
69265
- preparationError = prepareError?.message ?? String(prepareError);
69266
- retryable = isRepositoryCommandSignalDeath(e2);
69366
+ const blockedByHost = invariant === "host_checkout_dirty";
69367
+ if (!blockedByHost) {
69368
+ try {
69369
+ branch = await worktrees.prepareConflictRetry(storyId);
69370
+ retryable = true;
69371
+ } catch (prepareError) {
69372
+ preparationError = prepareError?.message ?? String(prepareError);
69373
+ retryable = isRepositoryCommandSignalDeath(e2);
69374
+ }
69267
69375
  }
69268
69376
  this.emitIntegrationRefused({
69269
69377
  storyId,
69270
69378
  runId: correlation?.runId ?? null,
69271
69379
  leaseId: correlation?.leaseId ?? null,
69272
- invariant: e2?.invariant ?? "unknown",
69380
+ invariant,
69273
69381
  detail: error,
69274
69382
  branch,
69275
69383
  retryable,
69276
69384
  // Only a successful prepareConflictRetry mints an immutable
69277
69385
  // recovery ref; otherwise `branch` is still the logical one.
69278
- recoveryRef: preparationError ? null : branch
69386
+ recoveryRef: preparationError || blockedByHost ? null : branch
69279
69387
  });
69280
69388
  this.emitBus(
69281
69389
  StoryMergeFailed.create({
@@ -69287,7 +69395,7 @@ var GitCoordinator = class extends SerializedObserver {
69287
69395
  })
69288
69396
  );
69289
69397
  log2(
69290
- retryable ? `[git] merge-back failed; attempt preserved at ${branch} and queued for recovery: ${error}` : `[git] merge-back failed; worktree preserved for manual recovery: ${error}`
69398
+ retryable ? `[git] merge-back failed; attempt preserved at ${branch} and queued for recovery: ${error}` : blockedByHost ? `[git] merge-back blocked by the host checkout; story kept on ${branch}: ${error}` : `[git] merge-back failed; worktree preserved for manual recovery: ${error}`
69291
69399
  );
69292
69400
  if (emitTui) {
69293
69401
  emit({ type: "push_status", id: storyId, success: false, error });