@remixhq/claude-plugin 0.1.16 → 0.1.17

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.
@@ -49218,7 +49218,7 @@ function summarizeUnifiedDiff(diff) {
49218
49218
  return { changedFilesCount, insertions, deletions };
49219
49219
  }
49220
49220
 
49221
- // node_modules/@remixhq/core/dist/chunk-4L3ZBZUQ.js
49221
+ // node_modules/@remixhq/core/dist/chunk-IXWQWFYT.js
49222
49222
  var import_promises14 = __toESM(require("fs/promises"), 1);
49223
49223
  var import_path3 = __toESM(require("path"), 1);
49224
49224
  var import_promises15 = __toESM(require("fs/promises"), 1);
@@ -49272,7 +49272,8 @@ function buildBindingFileV3(params) {
49272
49272
  repoFingerprint: params.repoFingerprint,
49273
49273
  remoteUrl: params.remoteUrl,
49274
49274
  defaultBranch: params.defaultBranch,
49275
- branchBindings: params.branchBindings
49275
+ branchBindings: params.branchBindings,
49276
+ ...params.explicitRootBinding ? { explicitRootBinding: params.explicitRootBinding } : {}
49276
49277
  };
49277
49278
  }
49278
49279
  function normalizeBranchName(value) {
@@ -49291,7 +49292,7 @@ function normalizeBranchBinding(value) {
49291
49292
  upstreamAppId: value.upstreamAppId,
49292
49293
  threadId: value.threadId ?? null,
49293
49294
  laneId: value.laneId ?? null,
49294
- bindingMode: value.bindingMode === "legacy" ? "legacy" : "lane"
49295
+ bindingMode: value.bindingMode === "legacy" ? "legacy" : value.bindingMode === "explicit_root" ? "explicit_root" : "lane"
49295
49296
  };
49296
49297
  }
49297
49298
  function buildResolvedBinding(params) {
@@ -49365,6 +49366,7 @@ async function readCollabBindingState(repoRoot, options) {
49365
49366
  defaultBranch: migratedFile.defaultBranch,
49366
49367
  currentBranch,
49367
49368
  branchBindings: migratedFile.branchBindings,
49369
+ explicitRootBinding: null,
49368
49370
  binding: buildResolvedBinding({
49369
49371
  fallbackProjectId: projectId,
49370
49372
  repoFingerprint: migratedFile.repoFingerprint,
@@ -49408,7 +49410,22 @@ async function readCollabBindingState(repoRoot, options) {
49408
49410
  };
49409
49411
  shouldPersistNormalizedBranchBindings = true;
49410
49412
  }
49411
- if (persist && ("explicitBinding" in file || shouldPersistNormalizedBranchBindings || parsed.schemaVersion === 2)) {
49413
+ let explicitRootBinding = normalizeBranchBinding(file.explicitRootBinding ?? null);
49414
+ if (explicitRootBinding && !explicitRootBinding.projectId && legacyProjectId) {
49415
+ explicitRootBinding = {
49416
+ ...explicitRootBinding,
49417
+ projectId: legacyProjectId
49418
+ };
49419
+ shouldPersistNormalizedBranchBindings = true;
49420
+ }
49421
+ if (explicitRootBinding && explicitRootBinding.bindingMode !== "explicit_root") {
49422
+ explicitRootBinding = {
49423
+ ...explicitRootBinding,
49424
+ bindingMode: "explicit_root"
49425
+ };
49426
+ shouldPersistNormalizedBranchBindings = true;
49427
+ }
49428
+ if (persist && ("explicitBinding" in file || "explicitRootBinding" in file || shouldPersistNormalizedBranchBindings || parsed.schemaVersion === 2)) {
49412
49429
  try {
49413
49430
  await writeJsonAtomic2(
49414
49431
  filePath,
@@ -49416,7 +49433,8 @@ async function readCollabBindingState(repoRoot, options) {
49416
49433
  repoFingerprint: file.repoFingerprint ?? null,
49417
49434
  remoteUrl: file.remoteUrl ?? null,
49418
49435
  defaultBranch: file.defaultBranch ?? null,
49419
- branchBindings
49436
+ branchBindings,
49437
+ explicitRootBinding
49420
49438
  })
49421
49439
  );
49422
49440
  } catch {
@@ -49427,8 +49445,23 @@ async function readCollabBindingState(repoRoot, options) {
49427
49445
  branchBindings,
49428
49446
  currentBranch: resolvedBranch,
49429
49447
  defaultBranch: normalizeBranchName(file.defaultBranch),
49430
- legacyProjectId
49448
+ legacyProjectId: explicitRootBinding?.projectId ?? legacyProjectId
49431
49449
  });
49450
+ const resolvedBinding = buildResolvedBinding({
49451
+ fallbackProjectId,
49452
+ repoFingerprint: file.repoFingerprint ?? null,
49453
+ remoteUrl: file.remoteUrl ?? null,
49454
+ defaultBranch: file.defaultBranch ?? null,
49455
+ branchName: resolvedBranch,
49456
+ binding: resolvedBranch ? branchBindings[resolvedBranch] ?? null : null
49457
+ }) ?? (resolvedBranch && resolvedBranch === normalizeBranchName(file.defaultBranch) && explicitRootBinding ? buildResolvedBinding({
49458
+ fallbackProjectId,
49459
+ repoFingerprint: file.repoFingerprint ?? null,
49460
+ remoteUrl: file.remoteUrl ?? null,
49461
+ defaultBranch: file.defaultBranch ?? null,
49462
+ branchName: normalizeBranchName(file.defaultBranch),
49463
+ binding: explicitRootBinding
49464
+ }) : null);
49432
49465
  return {
49433
49466
  schemaVersion: parsed.schemaVersion,
49434
49467
  projectId: fallbackProjectId,
@@ -49437,14 +49470,15 @@ async function readCollabBindingState(repoRoot, options) {
49437
49470
  defaultBranch: file.defaultBranch ?? null,
49438
49471
  currentBranch,
49439
49472
  branchBindings,
49440
- binding: buildResolvedBinding({
49473
+ explicitRootBinding: buildResolvedBinding({
49441
49474
  fallbackProjectId,
49442
49475
  repoFingerprint: file.repoFingerprint ?? null,
49443
49476
  remoteUrl: file.remoteUrl ?? null,
49444
49477
  defaultBranch: file.defaultBranch ?? null,
49445
- branchName: resolvedBranch,
49446
- binding: resolvedBranch ? branchBindings[resolvedBranch] ?? null : null
49447
- })
49478
+ branchName: normalizeBranchName(file.defaultBranch),
49479
+ binding: explicitRootBinding
49480
+ }),
49481
+ binding: resolvedBinding
49448
49482
  };
49449
49483
  } catch {
49450
49484
  return null;
@@ -49468,13 +49502,39 @@ async function writeCollabBinding(repoRoot, binding) {
49468
49502
  laneId: binding.laneId ?? null,
49469
49503
  bindingMode: binding.bindingMode ?? "lane"
49470
49504
  };
49505
+ const explicitRootBinding = binding.bindingMode === "explicit_root" ? {
49506
+ ...branchBindings[branchName],
49507
+ bindingMode: "explicit_root"
49508
+ } : existing?.explicitRootBinding ? {
49509
+ projectId: existing.explicitRootBinding.projectId,
49510
+ currentAppId: existing.explicitRootBinding.currentAppId,
49511
+ upstreamAppId: existing.explicitRootBinding.upstreamAppId,
49512
+ threadId: existing.explicitRootBinding.threadId,
49513
+ laneId: existing.explicitRootBinding.laneId,
49514
+ bindingMode: "explicit_root"
49515
+ } : null;
49471
49516
  await writeJsonAtomic2(
49472
49517
  filePath,
49473
49518
  buildBindingFileV3({
49474
49519
  repoFingerprint: binding.repoFingerprint ?? null,
49475
49520
  remoteUrl: binding.remoteUrl ?? null,
49476
49521
  defaultBranch: binding.defaultBranch ?? null,
49477
- branchBindings
49522
+ branchBindings,
49523
+ explicitRootBinding
49524
+ })
49525
+ );
49526
+ return filePath;
49527
+ }
49528
+ async function writeCollabBindingSnapshot(params) {
49529
+ const filePath = getCollabBindingPath(params.repoRoot);
49530
+ await writeJsonAtomic2(
49531
+ filePath,
49532
+ buildBindingFileV3({
49533
+ repoFingerprint: params.repoFingerprint,
49534
+ remoteUrl: params.remoteUrl,
49535
+ defaultBranch: params.defaultBranch,
49536
+ branchBindings: params.branchBindings,
49537
+ explicitRootBinding: params.explicitRootBinding ?? null
49478
49538
  })
49479
49539
  );
49480
49540
  return filePath;
@@ -49784,6 +49844,10 @@ function normalizeBranchName2(value) {
49784
49844
  }
49785
49845
  function buildBindingFromLane(state, lane) {
49786
49846
  if (!lane.currentAppId || !lane.upstreamAppId) return null;
49847
+ const resolvedBranch = normalizeBranchName2(lane.branchName) ?? state.currentBranch ?? null;
49848
+ const resolvedDefaultBranch = normalizeBranchName2(lane.defaultBranch) ?? normalizeBranchName2(state.defaultBranch);
49849
+ const explicitRootProjectId = state.explicitRootBinding?.projectId ?? null;
49850
+ const bindingMode = explicitRootProjectId && lane.projectId === explicitRootProjectId && resolvedBranch && resolvedBranch === resolvedDefaultBranch ? "explicit_root" : "lane";
49787
49851
  return {
49788
49852
  schemaVersion: 3,
49789
49853
  projectId: lane.projectId ?? state.projectId,
@@ -49794,8 +49858,8 @@ function buildBindingFromLane(state, lane) {
49794
49858
  remoteUrl: lane.remoteUrl ?? state.remoteUrl ?? null,
49795
49859
  defaultBranch: lane.defaultBranch ?? state.defaultBranch ?? null,
49796
49860
  laneId: lane.laneId ?? null,
49797
- branchName: lane.branchName ?? state.currentBranch ?? null,
49798
- bindingMode: "lane"
49861
+ branchName: resolvedBranch,
49862
+ bindingMode
49799
49863
  };
49800
49864
  }
49801
49865
  function shouldPersistRemoteLaneMetadata(localBinding, lane) {
@@ -49824,6 +49888,16 @@ async function persistResolvedLane(repoRoot, binding) {
49824
49888
  });
49825
49889
  return readCollabBinding(repoRoot);
49826
49890
  }
49891
+ function buildAmbiguousResolution(params) {
49892
+ return {
49893
+ status: "ambiguous_family_selection",
49894
+ currentBranch: params.currentBranch,
49895
+ projectIds: Array.isArray(params.lane.projectIds) ? params.lane.projectIds.filter((value) => typeof value === "string" && value.trim().length > 0) : [],
49896
+ repoFingerprint: params.lane.repoFingerprint ?? params.state.repoFingerprint,
49897
+ remoteUrl: params.lane.remoteUrl ?? params.state.remoteUrl,
49898
+ defaultBranch: params.lane.defaultBranch ?? params.state.defaultBranch
49899
+ };
49900
+ }
49827
49901
  async function resolveActiveLaneBinding(params) {
49828
49902
  const state = await readCollabBindingState(params.repoRoot);
49829
49903
  if (!state) {
@@ -49846,13 +49920,16 @@ async function resolveActiveLaneBinding(params) {
49846
49920
  };
49847
49921
  }
49848
49922
  const laneResp2 = await params.api.resolveProjectLaneBinding({
49849
- projectId: localBinding.projectId ?? state.projectId ?? void 0,
49923
+ projectId: state.explicitRootBinding?.projectId ?? (requireRemoteLane ? void 0 : localBinding.projectId ?? state.projectId ?? void 0),
49850
49924
  repoFingerprint: state.repoFingerprint ?? void 0,
49851
49925
  remoteUrl: state.remoteUrl ?? void 0,
49852
49926
  defaultBranch: state.defaultBranch ?? void 0,
49853
49927
  branchName: currentBranch
49854
49928
  });
49855
49929
  const lane2 = unwrapResponseObject(laneResp2, "project lane binding");
49930
+ if (lane2.status === "ambiguous_family_selection") {
49931
+ return buildAmbiguousResolution({ state, currentBranch, lane: lane2 });
49932
+ }
49856
49933
  if (lane2.status === "resolved") {
49857
49934
  const resolvedBranch = normalizeBranchName2(lane2.branchName);
49858
49935
  const resolvedProjectId = lane2.projectId ?? state.projectId;
@@ -49895,12 +49972,12 @@ async function resolveActiveLaneBinding(params) {
49895
49972
  return {
49896
49973
  status: "missing_branch_binding",
49897
49974
  currentBranch,
49898
- projectId: state.projectId,
49899
- repoFingerprint: state.repoFingerprint,
49900
- remoteUrl: state.remoteUrl,
49901
- defaultBranch: state.defaultBranch,
49902
- upstreamAppId: localBinding.upstreamAppId ?? null,
49903
- threadId: localBinding.threadId ?? null
49975
+ projectId: lane2.projectId ?? state.projectId,
49976
+ repoFingerprint: lane2.repoFingerprint ?? state.repoFingerprint,
49977
+ remoteUrl: lane2.remoteUrl ?? state.remoteUrl,
49978
+ defaultBranch: lane2.defaultBranch ?? state.defaultBranch,
49979
+ upstreamAppId: lane2.upstreamAppId ?? localBinding.upstreamAppId ?? null,
49980
+ threadId: lane2.threadId ?? localBinding.threadId ?? null
49904
49981
  };
49905
49982
  }
49906
49983
  return {
@@ -49923,13 +50000,16 @@ async function resolveActiveLaneBinding(params) {
49923
50000
  };
49924
50001
  }
49925
50002
  const laneResp = await params.api.resolveProjectLaneBinding({
49926
- projectId: state.projectId ?? void 0,
50003
+ projectId: state.explicitRootBinding?.projectId ?? state.projectId ?? void 0,
49927
50004
  repoFingerprint: state.repoFingerprint ?? void 0,
49928
50005
  remoteUrl: state.remoteUrl ?? void 0,
49929
50006
  defaultBranch: state.defaultBranch ?? void 0,
49930
50007
  branchName: currentBranch
49931
50008
  });
49932
50009
  const lane = unwrapResponseObject(laneResp, "project lane binding");
50010
+ if (lane.status === "ambiguous_family_selection") {
50011
+ return buildAmbiguousResolution({ state, currentBranch, lane });
50012
+ }
49933
50013
  if (lane.status === "resolved") {
49934
50014
  const binding = buildBindingFromLane(state, lane);
49935
50015
  if (binding) {
@@ -49941,18 +50021,15 @@ async function resolveActiveLaneBinding(params) {
49941
50021
  };
49942
50022
  }
49943
50023
  }
49944
- if (lane.status === "binding_not_found") {
49945
- return { status: "not_bound", currentBranch };
49946
- }
49947
50024
  return {
49948
50025
  status: "missing_branch_binding",
49949
50026
  currentBranch,
49950
- projectId: lane.projectId ?? state.projectId,
50027
+ projectId: lane.projectId ?? state.explicitRootBinding?.projectId ?? state.projectId,
49951
50028
  repoFingerprint: lane.repoFingerprint ?? state.repoFingerprint,
49952
50029
  remoteUrl: lane.remoteUrl ?? state.remoteUrl,
49953
50030
  defaultBranch: lane.defaultBranch ?? state.defaultBranch,
49954
- upstreamAppId: lane.upstreamAppId ?? null,
49955
- threadId: lane.threadId ?? null
50031
+ upstreamAppId: lane.upstreamAppId ?? state.explicitRootBinding?.upstreamAppId ?? null,
50032
+ threadId: lane.threadId ?? state.explicitRootBinding?.threadId ?? null
49956
50033
  };
49957
50034
  }
49958
50035
  async function ensureActiveLaneBinding(params) {
@@ -49972,6 +50049,12 @@ async function ensureActiveLaneBinding(params) {
49972
50049
  hint: `Local app ${resolved.binding.currentAppId}; server app ${resolved.resolvedLane.currentAppId ?? "(unknown)"}. Repair the branch binding before running ${params.operation ?? "this command"}.`
49973
50050
  });
49974
50051
  }
50052
+ if (resolved.status === "ambiguous_family_selection") {
50053
+ throw new RemixError("Multiple canonical Remix families match this repository.", {
50054
+ exitCode: 2,
50055
+ hint: "This checkout is not specific enough to choose a single family for the current branch. Continue from a checkout already bound to the intended family, or run `remix collab init --force-new` to create a new canonical family."
50056
+ });
50057
+ }
49975
50058
  if (resolved.status === "not_bound") {
49976
50059
  return null;
49977
50060
  }
@@ -49986,65 +50069,6 @@ async function ensureActiveLaneBinding(params) {
49986
50069
  hint: `Run \`remix collab init\` on branch ${resolved.currentBranch} before running ${params.operation ?? "this command"}.`
49987
50070
  });
49988
50071
  }
49989
- async function provisionActiveLaneBinding(params) {
49990
- const resolved = await resolveActiveLaneBinding(params);
49991
- if (resolved.status === "resolved") {
49992
- if (resolved.source === "local") {
49993
- return { binding: resolved.binding, warnings: [] };
49994
- }
49995
- const persisted2 = await persistResolvedLane(params.repoRoot, resolved.binding);
49996
- return { binding: persisted2, warnings: [] };
49997
- }
49998
- if (resolved.status === "binding_conflict") {
49999
- throw new RemixError("Current branch binding conflicts with the server-resolved Remix lane.", {
50000
- exitCode: 2,
50001
- hint: `Local app ${resolved.binding.currentAppId}; server app ${resolved.resolvedLane.currentAppId ?? "(unknown)"}. Repair the branch binding before running ${params.operation}.`
50002
- });
50003
- }
50004
- if (resolved.status === "not_bound") {
50005
- return { binding: null, warnings: [] };
50006
- }
50007
- if (!resolved.currentBranch) {
50008
- throw new RemixError("Current branch is not yet bound to a Remix lane.", {
50009
- exitCode: 2,
50010
- hint: `Switch to a named branch before running ${params.operation}.`
50011
- });
50012
- }
50013
- let lane;
50014
- try {
50015
- const laneResp = await params.api.ensureProjectLaneBinding({
50016
- projectId: resolved.projectId ?? void 0,
50017
- repoFingerprint: resolved.repoFingerprint ?? void 0,
50018
- remoteUrl: resolved.remoteUrl ?? void 0,
50019
- defaultBranch: resolved.defaultBranch ?? void 0,
50020
- branchName: resolved.currentBranch
50021
- });
50022
- lane = unwrapResponseObject(laneResp, "project lane binding");
50023
- } catch (error2) {
50024
- throw new RemixError(`Failed to provision a Remix lane for branch ${resolved.currentBranch}.`, {
50025
- exitCode: error2 instanceof RemixError ? error2.exitCode : 1,
50026
- hint: formatCliErrorDetail(error2) || `Remix could not create or recover the branch lane required before ${params.operation}.`
50027
- });
50028
- }
50029
- const state = await readCollabBindingState(params.repoRoot);
50030
- if (!state) {
50031
- return { binding: null, warnings: [] };
50032
- }
50033
- const binding = buildBindingFromLane(state, lane);
50034
- if (!binding) {
50035
- throw new RemixError(`Failed to provision a Remix lane for branch ${resolved.currentBranch}.`, {
50036
- exitCode: 1,
50037
- hint: "The server returned incomplete lane binding metadata."
50038
- });
50039
- }
50040
- const persisted = await persistResolvedLane(params.repoRoot, binding);
50041
- return {
50042
- binding: persisted,
50043
- warnings: [
50044
- lane.created ? `Provisioned Remix lane for branch ${resolved.currentBranch}.` : `Recovered existing Remix lane binding for branch ${resolved.currentBranch}.`
50045
- ]
50046
- };
50047
- }
50048
50072
  async function collabRecordingPreflight(params) {
50049
50073
  let repoRoot;
50050
50074
  try {
@@ -50105,6 +50129,24 @@ async function collabRecordingPreflight(params) {
50105
50129
  hint: `Current branch ${bindingResolution.currentBranch ?? "(detached)"} is not yet bound to a Remix lane.`
50106
50130
  };
50107
50131
  }
50132
+ if (bindingResolution.status === "ambiguous_family_selection") {
50133
+ return {
50134
+ status: "family_ambiguous",
50135
+ repoRoot,
50136
+ appId: null,
50137
+ currentBranch: bindingResolution.currentBranch,
50138
+ branchName: bindingResolution.currentBranch,
50139
+ headCommitHash: null,
50140
+ worktreeClean: false,
50141
+ syncStatus: null,
50142
+ syncTargetCommitHash: null,
50143
+ syncTargetCommitId: null,
50144
+ reconcileTargetHeadCommitHash: null,
50145
+ reconcileTargetHeadCommitId: null,
50146
+ warnings: [],
50147
+ hint: "Multiple canonical Remix families match this repository. Continue from a checkout already bound to the intended family, or run `remix collab init --force-new` to create a new canonical family."
50148
+ };
50149
+ }
50108
50150
  if (bindingResolution.status === "binding_conflict") {
50109
50151
  return {
50110
50152
  status: "metadata_conflict",
@@ -50614,6 +50656,12 @@ function assertSupportedRecordingPreflight(preflight) {
50614
50656
  hint: preflight.hint
50615
50657
  });
50616
50658
  }
50659
+ if (preflight.status === "family_ambiguous") {
50660
+ throw new RemixError("Multiple canonical Remix families match this repository.", {
50661
+ exitCode: 2,
50662
+ hint: preflight.hint
50663
+ });
50664
+ }
50617
50665
  if (preflight.status === "not_git_repo") {
50618
50666
  throw new RemixError(preflight.hint || "Not inside a git repository.", {
50619
50667
  exitCode: 2,
@@ -50929,6 +50977,12 @@ function assertSupportedRecordingPreflight2(preflight) {
50929
50977
  hint: preflight.hint
50930
50978
  });
50931
50979
  }
50980
+ if (preflight.status === "family_ambiguous") {
50981
+ throw new RemixError("Multiple canonical Remix families match this repository.", {
50982
+ exitCode: 2,
50983
+ hint: preflight.hint
50984
+ });
50985
+ }
50932
50986
  if (preflight.status === "not_git_repo") {
50933
50987
  throw new RemixError(preflight.hint || "Not inside a git repository.", {
50934
50988
  exitCode: 2,
@@ -51424,6 +51478,12 @@ async function resolveQueueAppId(params) {
51424
51478
  hint: `Local app ${bindingResolution.binding.currentAppId}; server app ${bindingResolution.resolvedLane.currentAppId ?? "(unknown)"}.`
51425
51479
  });
51426
51480
  }
51481
+ if (bindingResolution.status === "ambiguous_family_selection") {
51482
+ throw new RemixError("Multiple canonical Remix families match this repository.", {
51483
+ exitCode: 2,
51484
+ hint: "This checkout does not identify a single canonical family. Continue from a checkout already bound to the intended family, or pass `appId` explicitly for the queue request."
51485
+ });
51486
+ }
51427
51487
  return bindingResolution.binding.currentAppId;
51428
51488
  }
51429
51489
  async function collabListMergeRequests(params) {
@@ -51468,6 +51528,12 @@ async function resolveScopeTarget(params) {
51468
51528
  hint: `Local app ${bindingResolution.binding.currentAppId}; server app ${bindingResolution.resolvedLane.currentAppId ?? "(unknown)"}.`
51469
51529
  });
51470
51530
  }
51531
+ if (bindingResolution.status === "ambiguous_family_selection") {
51532
+ throw new RemixError("Multiple canonical Remix families match this repository and no explicit target id was provided.", {
51533
+ exitCode: 2,
51534
+ hint: "This checkout does not identify a single canonical family. Continue from a checkout already bound to the intended family, or pass `targetId` explicitly."
51535
+ });
51536
+ }
51471
51537
  const binding = bindingResolution.binding;
51472
51538
  if (params.scope === "project") {
51473
51539
  if (!binding.projectId) {
@@ -51582,6 +51648,85 @@ ${text}`.trim() || null
51582
51648
  });
51583
51649
  }
51584
51650
  }
51651
+ function requireResolvedLaneBinding(lane, params) {
51652
+ if (lane.status === "resolved" && lane.currentAppId && lane.upstreamAppId) {
51653
+ return lane;
51654
+ }
51655
+ const branchLabel = params.branchName ?? "the current branch";
51656
+ const laneStatus = String(lane.status ?? "");
51657
+ throw new RemixError(`Failed to resolve a Remix lane for ${branchLabel}.`, {
51658
+ exitCode: 1,
51659
+ hint: laneStatus === "binding_not_found" ? `Run ${params.operation} again after the repository has been initialized.` : laneStatus === "ambiguous_family_selection" ? "Multiple canonical Remix families match this repository. Continue from a checkout already bound to the intended family, or rerun with `--force-new` to create a new family." : `Remix did not return complete lane metadata for ${branchLabel}.`
51660
+ });
51661
+ }
51662
+ function resolveProjectBindingResult(response) {
51663
+ const payload = response?.responseObject;
51664
+ if (!payload || typeof payload !== "object") {
51665
+ return { status: "not_found" };
51666
+ }
51667
+ if (payload.status === "ambiguous_family_selection") {
51668
+ const projectIds = Array.isArray(payload.projectIds) ? payload.projectIds.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
51669
+ return {
51670
+ status: "ambiguous_family_selection",
51671
+ candidateCount: typeof payload.candidateCount === "number" && Number.isFinite(payload.candidateCount) ? payload.candidateCount : projectIds.length,
51672
+ projectIds
51673
+ };
51674
+ }
51675
+ if (payload.projectId && payload.appId) {
51676
+ return {
51677
+ status: "resolved",
51678
+ projectId: String(payload.projectId),
51679
+ appId: String(payload.appId),
51680
+ upstreamAppId: String(payload.upstreamAppId ?? payload.appId),
51681
+ threadId: payload.threadId ? String(payload.threadId) : null
51682
+ };
51683
+ }
51684
+ return { status: "not_found" };
51685
+ }
51686
+ function throwAmbiguousFamilyError(params) {
51687
+ const familyCount = params.candidateCount || params.projectIds.length;
51688
+ const projectHint = params.projectIds.length > 0 ? ` Matching project ids: ${params.projectIds.join(", ")}.` : "";
51689
+ throw new RemixError("Multiple canonical Remix families already match this repository.", {
51690
+ exitCode: 2,
51691
+ hint: `Plain \`remix collab init\` cannot safely choose among ${familyCount} matching canonical families for repo fingerprint ${params.repoFingerprint}${params.remoteUrl ? ` (${params.remoteUrl})` : ""}.${projectHint} Run \`remix collab init --force-new\` to create a new canonical family, or continue from a checkout already bound to the intended family.`
51692
+ });
51693
+ }
51694
+ async function resolveOrEnsureLaneBinding(params) {
51695
+ const resolvePayload = {
51696
+ projectId: params.projectId ?? void 0,
51697
+ repoFingerprint: params.repoFingerprint,
51698
+ remoteUrl: params.remoteUrl ?? void 0,
51699
+ defaultBranch: params.defaultBranch ?? void 0,
51700
+ branchName: params.branchName
51701
+ };
51702
+ let lane = unwrapResponseObject(
51703
+ await params.api.resolveProjectLaneBinding(resolvePayload),
51704
+ "project lane binding"
51705
+ );
51706
+ if (lane.status !== "resolved") {
51707
+ lane = unwrapResponseObject(
51708
+ await params.api.ensureProjectLaneBinding({
51709
+ ...resolvePayload,
51710
+ seedAppId: params.seedAppId ?? void 0
51711
+ }),
51712
+ "project lane binding"
51713
+ );
51714
+ }
51715
+ return requireResolvedLaneBinding(lane, {
51716
+ branchName: params.branchName,
51717
+ operation: params.operation
51718
+ });
51719
+ }
51720
+ function branchBindingFromLane(lane, mode, fallback) {
51721
+ return {
51722
+ projectId: lane.projectId ?? fallback.projectId,
51723
+ currentAppId: lane.currentAppId ?? fallback.currentAppId,
51724
+ upstreamAppId: lane.upstreamAppId ?? fallback.upstreamAppId,
51725
+ threadId: lane.threadId ?? fallback.threadId,
51726
+ laneId: lane.laneId ?? null,
51727
+ bindingMode: mode
51728
+ };
51729
+ }
51585
51730
  async function collabInit(params) {
51586
51731
  return withRepoMutationLock(
51587
51732
  {
@@ -51603,72 +51748,207 @@ async function collabInit(params) {
51603
51748
  const branchName = currentBranch ?? defaultBranch ?? null;
51604
51749
  const repoFingerprint = await buildRepoFingerprint({ gitRoot: repoRoot, remoteUrl, defaultBranch });
51605
51750
  const repoSnapshot = await captureRepoSnapshot(repoRoot);
51606
- if (params.forceNew) {
51607
- const bindingResp = await params.api.resolveProjectBinding({
51608
- repoFingerprint,
51609
- remoteUrl: remoteUrl ?? void 0,
51610
- branchName: branchName ?? void 0
51611
- });
51612
- const existing = bindingResp?.responseObject;
51613
- if (existing?.projectId && existing?.appId) {
51614
- throw new RemixError("`remix collab init --force-new` is not allowed for repositories already known to Remix.", {
51615
- exitCode: 2,
51616
- hint: "This repository already resolves to an existing Remix lineage. Run `remix collab init` without `--force-new`, or use an explicit checkout/remix flow instead of creating a duplicate imported app."
51751
+ const localBindingState = await readCollabBindingState(repoRoot, { persist: true });
51752
+ if (!params.forceNew && localBindingState?.explicitRootBinding && branchName) {
51753
+ const explicitRoot = localBindingState.explicitRootBinding;
51754
+ const explicitProjectId = explicitRoot.projectId ?? localBindingState.projectId;
51755
+ let canonicalLane2 = null;
51756
+ let boundProjectId2 = explicitProjectId;
51757
+ let boundCurrentAppId2 = explicitRoot.currentAppId;
51758
+ let boundUpstreamAppId2 = explicitRoot.upstreamAppId;
51759
+ let boundThreadId2 = explicitRoot.threadId;
51760
+ let boundLaneId2 = explicitRoot.laneId;
51761
+ if (defaultBranch && branchName !== defaultBranch) {
51762
+ canonicalLane2 = await resolveOrEnsureLaneBinding({
51763
+ api: params.api,
51764
+ projectId: explicitProjectId ?? void 0,
51765
+ repoFingerprint,
51766
+ remoteUrl,
51767
+ defaultBranch,
51768
+ branchName: defaultBranch,
51769
+ operation: "`remix collab init`"
51617
51770
  });
51618
- }
51619
- }
51620
- if (!params.forceNew) {
51621
- const bindingResp = await params.api.resolveProjectBinding({
51622
- repoFingerprint,
51623
- remoteUrl: remoteUrl ?? void 0,
51624
- branchName: branchName ?? void 0
51625
- });
51626
- const existing = bindingResp?.responseObject;
51627
- if (existing?.projectId && existing?.appId) {
51628
- await assertRepoSnapshotUnchanged(repoRoot, repoSnapshot, {
51629
- operation: "`remix collab init`",
51630
- recoveryHint: "The repository changed while the local binding was being initialized. Review the local changes and rerun `remix collab init`."
51771
+ const lane = await resolveOrEnsureLaneBinding({
51772
+ api: params.api,
51773
+ projectId: canonicalLane2.projectId ?? explicitProjectId ?? void 0,
51774
+ repoFingerprint,
51775
+ remoteUrl,
51776
+ defaultBranch,
51777
+ branchName,
51778
+ operation: "`remix collab init`"
51631
51779
  });
51632
- const initialProjectId = String(existing.projectId);
51633
- const initialCurrentAppId = String(existing.appId);
51634
- const initialUpstreamAppId = String(existing.upstreamAppId ?? existing.appId);
51635
- const initialThreadId = existing.threadId ? String(existing.threadId) : null;
51636
- const bindingPath2 = await writeCollabBinding(repoRoot, {
51637
- projectId: initialProjectId,
51638
- currentAppId: initialCurrentAppId,
51639
- upstreamAppId: initialUpstreamAppId,
51640
- threadId: initialThreadId,
51780
+ boundProjectId2 = lane.projectId ?? boundProjectId2;
51781
+ boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
51782
+ boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
51783
+ boundThreadId2 = lane.threadId ?? boundThreadId2;
51784
+ boundLaneId2 = lane.laneId ?? null;
51785
+ } else {
51786
+ canonicalLane2 = await resolveOrEnsureLaneBinding({
51787
+ api: params.api,
51788
+ projectId: explicitProjectId ?? void 0,
51789
+ repoFingerprint,
51790
+ remoteUrl,
51791
+ defaultBranch,
51792
+ branchName,
51793
+ operation: "`remix collab init`"
51794
+ });
51795
+ boundProjectId2 = canonicalLane2.projectId ?? boundProjectId2;
51796
+ boundCurrentAppId2 = canonicalLane2.currentAppId ?? boundCurrentAppId2;
51797
+ boundUpstreamAppId2 = canonicalLane2.upstreamAppId ?? boundUpstreamAppId2;
51798
+ boundThreadId2 = canonicalLane2.threadId ?? boundThreadId2;
51799
+ boundLaneId2 = canonicalLane2.laneId ?? null;
51800
+ }
51801
+ const readyApp = await pollAppReady(params.api, boundCurrentAppId2);
51802
+ boundProjectId2 = String(readyApp.projectId ?? boundProjectId2);
51803
+ boundThreadId2 = readyApp.threadId ? String(readyApp.threadId) : boundThreadId2;
51804
+ await assertRepoSnapshotUnchanged(repoRoot, repoSnapshot, {
51805
+ operation: "`remix collab init`",
51806
+ recoveryHint: "The repository changed while the local binding was being initialized. Review the local changes and rerun `remix collab init`."
51807
+ });
51808
+ await writeCollabBinding(repoRoot, {
51809
+ projectId: canonicalLane2?.projectId ?? explicitProjectId ?? null,
51810
+ currentAppId: canonicalLane2?.currentAppId ?? explicitRoot.currentAppId,
51811
+ upstreamAppId: canonicalLane2?.upstreamAppId ?? canonicalLane2?.currentAppId ?? explicitRoot.upstreamAppId ?? explicitRoot.currentAppId,
51812
+ threadId: canonicalLane2?.threadId ?? explicitRoot.threadId,
51813
+ repoFingerprint: canonicalLane2?.repoFingerprint ?? explicitRoot.repoFingerprint ?? repoFingerprint,
51814
+ remoteUrl: canonicalLane2?.remoteUrl ?? explicitRoot.remoteUrl ?? remoteUrl,
51815
+ defaultBranch: canonicalLane2?.defaultBranch ?? explicitRoot.defaultBranch ?? defaultBranch ?? null,
51816
+ laneId: canonicalLane2?.laneId ?? explicitRoot.laneId,
51817
+ branchName: defaultBranch,
51818
+ bindingMode: "explicit_root"
51819
+ });
51820
+ if (defaultBranch && branchName !== defaultBranch) {
51821
+ await writeCollabBinding(repoRoot, {
51822
+ projectId: boundProjectId2,
51823
+ currentAppId: boundCurrentAppId2,
51824
+ upstreamAppId: boundUpstreamAppId2,
51825
+ threadId: boundThreadId2,
51641
51826
  repoFingerprint,
51642
51827
  remoteUrl,
51643
51828
  defaultBranch: defaultBranch ?? null,
51644
- laneId: null,
51829
+ laneId: boundLaneId2,
51645
51830
  branchName,
51646
51831
  bindingMode: "lane"
51647
51832
  });
51833
+ }
51834
+ return {
51835
+ reused: true,
51836
+ projectId: boundProjectId2 ?? explicitProjectId ?? "",
51837
+ appId: boundCurrentAppId2,
51838
+ dashboardUrl: buildDashboardAppUrl(boundCurrentAppId2),
51839
+ upstreamAppId: boundUpstreamAppId2,
51840
+ bindingPath: import_path9.default.join(repoRoot, ".remix", "config.json"),
51841
+ repoRoot,
51842
+ bindingMode: defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root",
51843
+ createdCanonicalFamily: false,
51844
+ ...warnings.length > 0 ? { warnings } : {}
51845
+ };
51846
+ }
51847
+ if (!params.forceNew) {
51848
+ const bindingResolution = resolveProjectBindingResult(
51849
+ await params.api.resolveProjectBinding({
51850
+ repoFingerprint,
51851
+ remoteUrl: remoteUrl ?? void 0,
51852
+ branchName: branchName ?? void 0
51853
+ })
51854
+ );
51855
+ if (bindingResolution.status === "ambiguous_family_selection") {
51856
+ throwAmbiguousFamilyError({
51857
+ repoFingerprint,
51858
+ remoteUrl,
51859
+ projectIds: bindingResolution.projectIds,
51860
+ candidateCount: bindingResolution.candidateCount
51861
+ });
51862
+ }
51863
+ if (bindingResolution.status === "resolved") {
51864
+ const initialProjectId = bindingResolution.projectId;
51865
+ const initialCurrentAppId = bindingResolution.appId;
51866
+ const initialUpstreamAppId = bindingResolution.upstreamAppId;
51867
+ const initialThreadId = bindingResolution.threadId;
51648
51868
  let boundProjectId2 = initialProjectId;
51649
51869
  let boundCurrentAppId2 = initialCurrentAppId;
51650
51870
  let boundUpstreamAppId2 = initialUpstreamAppId;
51651
51871
  let boundThreadId2 = initialThreadId;
51652
- let finalWarnings = [...warnings];
51872
+ let boundLaneId2 = null;
51873
+ let canonicalLane2 = null;
51653
51874
  if (branchName) {
51654
- const provisioned = await provisionActiveLaneBinding({
51655
- repoRoot,
51656
- api: params.api,
51657
- operation: "`remix collab init`"
51658
- });
51659
- if (provisioned.binding) {
51660
- boundProjectId2 = provisioned.binding.projectId ?? boundProjectId2;
51661
- boundCurrentAppId2 = provisioned.binding.currentAppId;
51662
- boundUpstreamAppId2 = provisioned.binding.upstreamAppId;
51663
- boundThreadId2 = provisioned.binding.threadId;
51875
+ if (defaultBranch && branchName !== defaultBranch) {
51876
+ canonicalLane2 = await resolveOrEnsureLaneBinding({
51877
+ api: params.api,
51878
+ repoFingerprint,
51879
+ remoteUrl,
51880
+ defaultBranch,
51881
+ branchName: defaultBranch,
51882
+ seedAppId: initialCurrentAppId,
51883
+ operation: "`remix collab init`"
51884
+ });
51885
+ const lane = await resolveOrEnsureLaneBinding({
51886
+ api: params.api,
51887
+ projectId: canonicalLane2.projectId ?? void 0,
51888
+ repoFingerprint,
51889
+ remoteUrl,
51890
+ defaultBranch,
51891
+ branchName,
51892
+ operation: "`remix collab init`"
51893
+ });
51894
+ boundProjectId2 = lane.projectId ?? boundProjectId2;
51895
+ boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
51896
+ boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
51897
+ boundThreadId2 = lane.threadId ?? boundThreadId2;
51898
+ boundLaneId2 = lane.laneId ?? null;
51899
+ } else {
51900
+ const lane = await resolveOrEnsureLaneBinding({
51901
+ api: params.api,
51902
+ repoFingerprint,
51903
+ remoteUrl,
51904
+ defaultBranch,
51905
+ branchName,
51906
+ seedAppId: initialCurrentAppId,
51907
+ operation: "`remix collab init`"
51908
+ });
51909
+ canonicalLane2 = lane;
51910
+ boundProjectId2 = lane.projectId ?? boundProjectId2;
51911
+ boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
51912
+ boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
51913
+ boundThreadId2 = lane.threadId ?? boundThreadId2;
51914
+ boundLaneId2 = lane.laneId ?? null;
51664
51915
  }
51665
- finalWarnings = [...finalWarnings, ...provisioned.warnings];
51666
51916
  }
51667
51917
  if (boundCurrentAppId2) {
51668
51918
  const readyApp = await pollAppReady(params.api, boundCurrentAppId2);
51669
51919
  boundProjectId2 = String(readyApp.projectId ?? boundProjectId2);
51670
51920
  boundThreadId2 = readyApp.threadId ? String(readyApp.threadId) : boundThreadId2;
51671
51921
  }
51922
+ await assertRepoSnapshotUnchanged(repoRoot, repoSnapshot, {
51923
+ operation: "`remix collab init`",
51924
+ recoveryHint: "The repository changed while the local binding was being initialized. Review the local changes and rerun `remix collab init`."
51925
+ });
51926
+ if (canonicalLane2 && defaultBranch && branchName && branchName !== defaultBranch) {
51927
+ await writeCollabBinding(repoRoot, {
51928
+ projectId: canonicalLane2.projectId ?? null,
51929
+ currentAppId: canonicalLane2.currentAppId ?? boundCurrentAppId2,
51930
+ upstreamAppId: canonicalLane2.upstreamAppId ?? canonicalLane2.currentAppId ?? boundCurrentAppId2,
51931
+ threadId: canonicalLane2.threadId ?? null,
51932
+ repoFingerprint: canonicalLane2.repoFingerprint ?? repoFingerprint,
51933
+ remoteUrl: canonicalLane2.remoteUrl ?? remoteUrl,
51934
+ defaultBranch: canonicalLane2.defaultBranch ?? defaultBranch,
51935
+ laneId: canonicalLane2.laneId ?? null,
51936
+ branchName: defaultBranch,
51937
+ bindingMode: "lane"
51938
+ });
51939
+ }
51940
+ const bindingPath2 = await writeCollabBinding(repoRoot, {
51941
+ projectId: boundProjectId2,
51942
+ currentAppId: boundCurrentAppId2,
51943
+ upstreamAppId: boundUpstreamAppId2,
51944
+ threadId: boundThreadId2,
51945
+ repoFingerprint,
51946
+ remoteUrl,
51947
+ defaultBranch: defaultBranch ?? null,
51948
+ laneId: boundLaneId2,
51949
+ branchName,
51950
+ bindingMode: "lane"
51951
+ });
51672
51952
  return {
51673
51953
  reused: true,
51674
51954
  projectId: boundProjectId2,
@@ -51677,7 +51957,9 @@ async function collabInit(params) {
51677
51957
  upstreamAppId: boundUpstreamAppId2,
51678
51958
  bindingPath: bindingPath2,
51679
51959
  repoRoot,
51680
- ...finalWarnings.length > 0 ? { warnings: finalWarnings } : {}
51960
+ bindingMode: "lane",
51961
+ createdCanonicalFamily: false,
51962
+ ...warnings.length > 0 ? { warnings } : {}
51681
51963
  };
51682
51964
  }
51683
51965
  }
@@ -51704,7 +51986,7 @@ async function collabInit(params) {
51704
51986
  path: params.path?.trim() || void 0,
51705
51987
  platform: "generic",
51706
51988
  isPublic: false,
51707
- branch: currentBranch ?? void 0,
51989
+ branch: defaultBranch && branchName && branchName !== defaultBranch ? defaultBranch : currentBranch ?? void 0,
51708
51990
  remoteUrl: remoteUrl ?? void 0,
51709
51991
  defaultBranch: defaultBranch ?? void 0,
51710
51992
  repoFingerprint,
@@ -51717,28 +51999,51 @@ async function collabInit(params) {
51717
51999
  let boundUpstreamAppId = String(app.id);
51718
52000
  let boundThreadId = app.threadId ? String(app.threadId) : null;
51719
52001
  let boundLaneId = null;
52002
+ let canonicalLane = null;
51720
52003
  if (branchName) {
51721
- const laneResp = defaultBranch && branchName !== defaultBranch ? await params.api.bootstrapFreshProjectLane({
51722
- projectId: boundProjectId,
51723
- repoFingerprint,
51724
- remoteUrl: remoteUrl ?? void 0,
51725
- defaultBranch: defaultBranch ?? void 0,
51726
- branchName,
51727
- seedAppId: String(app.id)
51728
- }) : await params.api.ensureProjectLaneBinding({
51729
- projectId: boundProjectId,
51730
- repoFingerprint,
51731
- remoteUrl: remoteUrl ?? void 0,
51732
- defaultBranch: defaultBranch ?? void 0,
51733
- branchName,
51734
- seedAppId: String(app.id)
51735
- });
51736
- const lane = unwrapResponseObject(laneResp, "project lane binding");
51737
- boundProjectId = typeof lane.projectId === "string" && lane.projectId ? lane.projectId : boundProjectId;
51738
- boundCurrentAppId = typeof lane.currentAppId === "string" && lane.currentAppId ? lane.currentAppId : boundCurrentAppId;
51739
- boundUpstreamAppId = typeof lane.upstreamAppId === "string" && lane.upstreamAppId ? lane.upstreamAppId : boundUpstreamAppId;
51740
- boundThreadId = typeof lane.threadId === "string" && lane.threadId ? lane.threadId : boundThreadId;
51741
- boundLaneId = typeof lane.laneId === "string" && lane.laneId ? lane.laneId : null;
52004
+ if (defaultBranch && branchName !== defaultBranch) {
52005
+ canonicalLane = await resolveOrEnsureLaneBinding({
52006
+ api: params.api,
52007
+ projectId: boundProjectId,
52008
+ repoFingerprint,
52009
+ remoteUrl,
52010
+ defaultBranch,
52011
+ branchName: defaultBranch,
52012
+ seedAppId: String(app.id),
52013
+ operation: "`remix collab init`"
52014
+ });
52015
+ const lane = await resolveOrEnsureLaneBinding({
52016
+ api: params.api,
52017
+ projectId: canonicalLane.projectId ?? boundProjectId,
52018
+ repoFingerprint,
52019
+ remoteUrl,
52020
+ defaultBranch,
52021
+ branchName,
52022
+ operation: "`remix collab init`"
52023
+ });
52024
+ boundProjectId = lane.projectId ?? boundProjectId;
52025
+ boundCurrentAppId = lane.currentAppId ?? boundCurrentAppId;
52026
+ boundUpstreamAppId = lane.upstreamAppId ?? boundUpstreamAppId;
52027
+ boundThreadId = lane.threadId ?? boundThreadId;
52028
+ boundLaneId = lane.laneId ?? null;
52029
+ } else {
52030
+ const lane = await resolveOrEnsureLaneBinding({
52031
+ api: params.api,
52032
+ projectId: boundProjectId,
52033
+ repoFingerprint,
52034
+ remoteUrl,
52035
+ defaultBranch,
52036
+ branchName,
52037
+ seedAppId: String(app.id),
52038
+ operation: "`remix collab init`"
52039
+ });
52040
+ canonicalLane = lane;
52041
+ boundProjectId = lane.projectId ?? boundProjectId;
52042
+ boundCurrentAppId = lane.currentAppId ?? boundCurrentAppId;
52043
+ boundUpstreamAppId = lane.upstreamAppId ?? boundUpstreamAppId;
52044
+ boundThreadId = lane.threadId ?? boundThreadId;
52045
+ boundLaneId = lane.laneId ?? null;
52046
+ }
51742
52047
  }
51743
52048
  if (boundCurrentAppId) {
51744
52049
  const readyApp = await pollAppReady(params.api, boundCurrentAppId);
@@ -51749,18 +52054,64 @@ async function collabInit(params) {
51749
52054
  operation: "`remix collab init`",
51750
52055
  recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
51751
52056
  });
51752
- const bindingPath = await writeCollabBinding(repoRoot, {
51753
- projectId: boundProjectId,
51754
- currentAppId: boundCurrentAppId,
51755
- upstreamAppId: boundUpstreamAppId,
51756
- threadId: boundThreadId,
51757
- repoFingerprint,
51758
- remoteUrl,
51759
- defaultBranch: defaultBranch ?? null,
51760
- laneId: boundLaneId,
51761
- branchName,
51762
- bindingMode: "lane"
51763
- });
52057
+ const bindingMode = params.forceNew && (!defaultBranch || branchName === defaultBranch) ? "explicit_root" : "lane";
52058
+ let bindingPath;
52059
+ if (params.forceNew && defaultBranch && canonicalLane) {
52060
+ const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
52061
+ projectId: canonicalLane.projectId ?? boundProjectId,
52062
+ currentAppId: canonicalLane.currentAppId ?? boundCurrentAppId,
52063
+ upstreamAppId: canonicalLane.upstreamAppId ?? canonicalLane.currentAppId ?? boundCurrentAppId,
52064
+ threadId: canonicalLane.threadId ?? boundThreadId
52065
+ });
52066
+ const branchBindings = {
52067
+ [defaultBranch]: canonicalBinding
52068
+ };
52069
+ if (branchName && branchName !== defaultBranch) {
52070
+ branchBindings[branchName] = {
52071
+ projectId: boundProjectId,
52072
+ currentAppId: boundCurrentAppId,
52073
+ upstreamAppId: boundUpstreamAppId,
52074
+ threadId: boundThreadId,
52075
+ laneId: boundLaneId,
52076
+ bindingMode: "lane"
52077
+ };
52078
+ }
52079
+ bindingPath = await writeCollabBindingSnapshot({
52080
+ repoRoot,
52081
+ repoFingerprint,
52082
+ remoteUrl,
52083
+ defaultBranch,
52084
+ branchBindings,
52085
+ explicitRootBinding: canonicalBinding
52086
+ });
52087
+ } else {
52088
+ if (canonicalLane && defaultBranch && branchName && branchName !== defaultBranch) {
52089
+ await writeCollabBinding(repoRoot, {
52090
+ projectId: canonicalLane.projectId ?? null,
52091
+ currentAppId: canonicalLane.currentAppId ?? boundCurrentAppId,
52092
+ upstreamAppId: canonicalLane.upstreamAppId ?? canonicalLane.currentAppId ?? boundCurrentAppId,
52093
+ threadId: canonicalLane.threadId ?? null,
52094
+ repoFingerprint: canonicalLane.repoFingerprint ?? repoFingerprint,
52095
+ remoteUrl: canonicalLane.remoteUrl ?? remoteUrl,
52096
+ defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
52097
+ laneId: canonicalLane.laneId ?? null,
52098
+ branchName: defaultBranch,
52099
+ bindingMode: params.forceNew ? "explicit_root" : "lane"
52100
+ });
52101
+ }
52102
+ bindingPath = await writeCollabBinding(repoRoot, {
52103
+ projectId: boundProjectId,
52104
+ currentAppId: boundCurrentAppId,
52105
+ upstreamAppId: boundUpstreamAppId,
52106
+ threadId: boundThreadId,
52107
+ repoFingerprint,
52108
+ remoteUrl,
52109
+ defaultBranch: defaultBranch ?? null,
52110
+ laneId: boundLaneId,
52111
+ branchName,
52112
+ bindingMode
52113
+ });
52114
+ }
51764
52115
  return {
51765
52116
  reused: false,
51766
52117
  projectId: boundProjectId,
@@ -51769,6 +52120,8 @@ async function collabInit(params) {
51769
52120
  upstreamAppId: boundUpstreamAppId,
51770
52121
  bindingPath,
51771
52122
  repoRoot,
52123
+ bindingMode,
52124
+ createdCanonicalFamily: Boolean(params.forceNew),
51772
52125
  remoteUrl,
51773
52126
  defaultBranch,
51774
52127
  ...warnings.length > 0 ? { warnings } : {}
@@ -52211,7 +52564,32 @@ async function collabStatus(params) {
52211
52564
  addBlockedReason(status.sync, "branch_binding_missing");
52212
52565
  addBlockedReason(status.reconcile, "branch_binding_missing");
52213
52566
  addWarning(status, `Current branch ${bindingResolution.currentBranch ?? "(detached)"} is not yet bound to a Remix lane.`);
52214
- status.recommendedAction = "no_action";
52567
+ status.recommendedAction = "init";
52568
+ return status;
52569
+ }
52570
+ if (bindingResolution.status === "ambiguous_family_selection") {
52571
+ status.binding = {
52572
+ isBound: true,
52573
+ path: getCollabBindingPath(repoRoot),
52574
+ projectId: null,
52575
+ currentAppId: null,
52576
+ upstreamAppId: null,
52577
+ isRemix: null,
52578
+ threadId: null,
52579
+ repoFingerprint: bindingResolution.repoFingerprint,
52580
+ remoteUrl: bindingResolution.remoteUrl,
52581
+ defaultBranch: bindingResolution.defaultBranch,
52582
+ laneId: null,
52583
+ branchName: bindingResolution.currentBranch,
52584
+ bindingMode: null
52585
+ };
52586
+ addBlockedReason(status.sync, "family_ambiguous");
52587
+ addBlockedReason(status.reconcile, "family_ambiguous");
52588
+ addWarning(
52589
+ status,
52590
+ `Multiple canonical Remix families match ${bindingResolution.currentBranch ?? "the current branch"}. Switch to a checkout already bound to the intended family or run \`remix collab init --force-new\`.`
52591
+ );
52592
+ status.recommendedAction = "choose_family";
52215
52593
  return status;
52216
52594
  }
52217
52595
  const binding = bindingResolution.binding;
@@ -52363,6 +52741,8 @@ async function collabStatus(params) {
52363
52741
  status.recommendedAction = "reconcile";
52364
52742
  } else if ((status.remote.incomingOpenMergeRequestCount ?? 0) > 0) {
52365
52743
  status.recommendedAction = "review_queue";
52744
+ } else if (status.sync.blockedReasons.includes("family_ambiguous") || status.reconcile.blockedReasons.includes("family_ambiguous")) {
52745
+ status.recommendedAction = "choose_family";
52366
52746
  } else {
52367
52747
  status.recommendedAction = "no_action";
52368
52748
  }
@@ -52506,6 +52886,7 @@ var ERROR_CODES = {
52506
52886
  DIRTY_WORKTREE: "DIRTY_WORKTREE",
52507
52887
  DETACHED_HEAD: "DETACHED_HEAD",
52508
52888
  PREFERRED_BRANCH_MISMATCH: "PREFERRED_BRANCH_MISMATCH",
52889
+ FAMILY_SELECTION_AMBIGUOUS: "FAMILY_SELECTION_AMBIGUOUS",
52509
52890
  MISSING_HEAD: "MISSING_HEAD",
52510
52891
  REPO_LOCK_HELD: "REPO_LOCK_HELD",
52511
52892
  REPO_LOCK_TIMEOUT: "REPO_LOCK_TIMEOUT",
@@ -52640,6 +53021,14 @@ function normalizeByMessage(err) {
52640
53021
  category: "local_state"
52641
53022
  });
52642
53023
  }
53024
+ if (message.includes("Multiple canonical Remix families") || message.includes("family selection is ambiguous")) {
53025
+ return makeNormalized({
53026
+ code: ERROR_CODES.FAMILY_SELECTION_AMBIGUOUS,
53027
+ message,
53028
+ hint,
53029
+ category: "local_state"
53030
+ });
53031
+ }
52643
53032
  if (message.includes("Failed to resolve local HEAD")) {
52644
53033
  return makeNormalized({
52645
53034
  code: ERROR_CODES.MISSING_HEAD,
@@ -53036,7 +53425,9 @@ var initDataSchema = external_exports.object({
53036
53425
  dashboardUrl: external_exports.string().url(),
53037
53426
  upstreamAppId: external_exports.string(),
53038
53427
  bindingPath: external_exports.string(),
53039
- repoRoot: external_exports.string()
53428
+ repoRoot: external_exports.string(),
53429
+ bindingMode: external_exports.enum(["legacy", "lane", "explicit_root"]).optional(),
53430
+ createdCanonicalFamily: external_exports.boolean().optional()
53040
53431
  });
53041
53432
  var listDataSchema = external_exports.object({
53042
53433
  apps: genericArraySchema,
@@ -53164,6 +53555,7 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
53164
53555
  var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
53165
53556
  function getRiskLevel(status) {
53166
53557
  if (status.recommendedAction === "reconcile") return "high";
53558
+ if (status.recommendedAction === "choose_family") return "medium";
53167
53559
  if (status.recommendedAction === "sync" || status.remote.incomingOpenMergeRequestCount) return "medium";
53168
53560
  if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
53169
53561
  return "low";
@@ -53183,6 +53575,10 @@ function getRecommendedNextActions(status) {
53183
53575
  return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile is the explicit Remix recovery path when fast-forward sync is no longer possible."];
53184
53576
  case "review_queue":
53185
53577
  return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
53578
+ case "choose_family":
53579
+ return [
53580
+ "This checkout is ambiguous across multiple canonical Remix families. Continue from a checkout already bound to the intended family, or run remix_collab_init with forceNew=true to create and bind a new canonical family."
53581
+ ];
53186
53582
  default:
53187
53583
  return [];
53188
53584
  }
@@ -56133,7 +56529,7 @@ async function listPendingTurnStateSummaries() {
56133
56529
  // package.json
56134
56530
  var package_default = {
56135
56531
  name: "@remixhq/claude-plugin",
56136
- version: "0.1.16",
56532
+ version: "0.1.17",
56137
56533
  description: "Claude Code plugin for Remix collaboration workflows",
56138
56534
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
56139
56535
  license: "MIT",
@@ -56164,8 +56560,8 @@ var package_default = {
56164
56560
  prepack: "npm run build"
56165
56561
  },
56166
56562
  dependencies: {
56167
- "@remixhq/core": "^0.1.11",
56168
- "@remixhq/mcp": "^0.1.11"
56563
+ "@remixhq/core": "^0.1.12",
56564
+ "@remixhq/mcp": "^0.1.12"
56169
56565
  },
56170
56566
  devDependencies: {
56171
56567
  "@types/node": "^25.4.0",