@remixhq/claude-plugin 0.1.23 → 0.1.24

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.
@@ -8827,6 +8827,15 @@ function shouldRequireRemoteLaneForCurrentBranch(params) {
8827
8827
  if (params.currentBranch === defaultBranch) return false;
8828
8828
  return !params.binding.laneId || params.binding.currentAppId === params.binding.upstreamAppId;
8829
8829
  }
8830
+ function resolveLaneLookupProjectId(params) {
8831
+ const currentBranch = normalizeBranchName2(params.currentBranch);
8832
+ const defaultBranch = normalizeBranchName2(params.defaultBranch);
8833
+ const localProjectId = params.localBinding.projectId ?? null;
8834
+ if (currentBranch && currentBranch !== defaultBranch && localProjectId) {
8835
+ return localProjectId;
8836
+ }
8837
+ return params.explicitRootProjectId ?? (params.requireRemoteLane ? void 0 : localProjectId ?? params.fallbackProjectId ?? void 0);
8838
+ }
8830
8839
  async function persistResolvedLane(repoRoot, binding) {
8831
8840
  await writeCollabBinding(repoRoot, {
8832
8841
  projectId: binding.projectId,
@@ -8905,7 +8914,14 @@ async function resolveActiveLaneBindingUncached(params, state) {
8905
8914
  };
8906
8915
  }
8907
8916
  const laneResp2 = await params.api.resolveProjectLaneBinding({
8908
- projectId: state.explicitRootBinding?.projectId ?? (requireRemoteLane ? void 0 : localBinding.projectId ?? state.projectId ?? void 0),
8917
+ projectId: resolveLaneLookupProjectId({
8918
+ explicitRootProjectId: state.explicitRootBinding?.projectId,
8919
+ localBinding,
8920
+ currentBranch,
8921
+ defaultBranch: state.defaultBranch,
8922
+ requireRemoteLane,
8923
+ fallbackProjectId: state.projectId
8924
+ }),
8909
8925
  repoFingerprint: state.repoFingerprint ?? void 0,
8910
8926
  remoteUrl: state.remoteUrl ?? void 0,
8911
8927
  defaultBranch: state.defaultBranch ?? void 0,
@@ -9756,6 +9772,59 @@ function buildWorkspaceMetadata(params) {
9756
9772
  }
9757
9773
  return metadata;
9758
9774
  }
9775
+ async function findExistingChangeStepByIdempotency(params) {
9776
+ const idempotencyKey = params.idempotencyKey?.trim();
9777
+ if (!idempotencyKey) return null;
9778
+ const resp = await params.api.listChangeSteps(params.appId, { limit: 1, idempotencyKey });
9779
+ const responseObject = unwrapResponseObject(
9780
+ resp,
9781
+ "change step list"
9782
+ );
9783
+ const steps = Array.isArray(responseObject) ? responseObject : Array.isArray(responseObject.items) ? responseObject.items : [];
9784
+ return steps.find((step) => step.idempotencyKey === idempotencyKey) ?? null;
9785
+ }
9786
+ async function writeBaselineFromSucceededChangeStep(params) {
9787
+ const nextServerHeadHash = typeof params.changeStep.headCommitHash === "string" ? params.changeStep.headCommitHash.trim() : "";
9788
+ if (!nextServerHeadHash) {
9789
+ throw buildFinalizeCliError({
9790
+ message: "Backend returned a succeeded change step without a head commit hash.",
9791
+ exitCode: 1,
9792
+ hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
9793
+ disposition: "terminal",
9794
+ reason: "missing_head_commit_hash"
9795
+ });
9796
+ }
9797
+ let nextServerRevisionId = typeof params.changeStep.resultRevisionId === "string" ? params.changeStep.resultRevisionId.trim() : "";
9798
+ let nextServerTreeHash = null;
9799
+ if (!nextServerRevisionId) {
9800
+ const freshHeadResp = await params.api.getAppHead(params.job.currentAppId);
9801
+ const freshHead = unwrapResponseObject(freshHeadResp, "app head");
9802
+ if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
9803
+ throw buildFinalizeCliError({
9804
+ message: "Backend returned a succeeded change step without a matching result revision.",
9805
+ exitCode: 1,
9806
+ hint: "The local baseline was not advanced because the post-step revision could not be verified. Restart the backend/CLI and retry after checking `remix collab status`.",
9807
+ disposition: "terminal",
9808
+ reason: "missing_result_revision_id"
9809
+ });
9810
+ }
9811
+ nextServerRevisionId = freshHead.headRevisionId;
9812
+ nextServerTreeHash = freshHead.treeHash ?? null;
9813
+ }
9814
+ await writeLocalBaseline({
9815
+ repoRoot: params.job.repoRoot,
9816
+ repoFingerprint: params.job.repoFingerprint,
9817
+ laneId: params.job.laneId,
9818
+ currentAppId: params.job.currentAppId,
9819
+ branchName: params.job.branchName,
9820
+ lastSnapshotId: params.snapshot.id,
9821
+ lastSnapshotHash: params.snapshot.snapshotHash,
9822
+ lastServerRevisionId: nextServerRevisionId,
9823
+ lastServerTreeHash: nextServerTreeHash,
9824
+ lastServerHeadHash: nextServerHeadHash,
9825
+ lastSeenLocalCommitHash: params.snapshot.localCommitHash
9826
+ });
9827
+ }
9759
9828
  async function harvestPreTurnEvents(repoRoot, fromCommit, toCommit) {
9760
9829
  if (!toCommit) return null;
9761
9830
  try {
@@ -9941,6 +10010,34 @@ async function processClaimedPendingFinalizeJobInner(params) {
9941
10010
  });
9942
10011
  }
9943
10012
  const replayNeeded = appHead.headCommitHash !== submissionBaseHeadHash || baselineDrifted;
10013
+ if (replayNeeded) {
10014
+ const existingChangeStep = await findExistingChangeStepByIdempotency({
10015
+ api: params.api,
10016
+ appId: job.currentAppId,
10017
+ idempotencyKey: job.idempotencyKey
10018
+ });
10019
+ if (existingChangeStep) {
10020
+ const changeStep2 = existingChangeStep.status === "succeeded" ? existingChangeStep : await pollChangeStep(params.api, job.currentAppId, existingChangeStep.id);
10021
+ invalidateAppHeadCache(job.currentAppId);
10022
+ invalidateAppDeltaCacheForApp(job.currentAppId);
10023
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep: changeStep2 });
10024
+ await updatePendingFinalizeJob(job.id, {
10025
+ status: "completed",
10026
+ metadata: { changeStepId: String(changeStep2.id ?? "") }
10027
+ });
10028
+ return {
10029
+ mode: "changed_turn",
10030
+ idempotencyKey: job.idempotencyKey ?? "",
10031
+ queued: false,
10032
+ jobId: job.id,
10033
+ repoState,
10034
+ changeStep: changeStep2,
10035
+ collabTurn: null,
10036
+ autoSync: null,
10037
+ warnings: []
10038
+ };
10039
+ }
10040
+ }
9944
10041
  if (replayNeeded) {
9945
10042
  try {
9946
10043
  const replayResp = await params.api.startChangeStepReplay(job.currentAppId, {
@@ -10038,46 +10135,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
10038
10135
  const changeStep = await pollChangeStep(params.api, job.currentAppId, String(createdStep.id));
10039
10136
  invalidateAppHeadCache(job.currentAppId);
10040
10137
  invalidateAppDeltaCacheForApp(job.currentAppId);
10041
- const nextServerHeadHash = typeof changeStep.headCommitHash === "string" ? changeStep.headCommitHash.trim() : "";
10042
- if (!nextServerHeadHash) {
10043
- throw buildFinalizeCliError({
10044
- message: "Backend returned a succeeded change step without a head commit hash.",
10045
- exitCode: 1,
10046
- hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
10047
- disposition: "terminal",
10048
- reason: "missing_head_commit_hash"
10049
- });
10050
- }
10051
- let nextServerRevisionId = typeof changeStep.resultRevisionId === "string" ? changeStep.resultRevisionId.trim() : "";
10052
- let nextServerTreeHash = null;
10053
- if (!nextServerRevisionId) {
10054
- const freshHeadResp = await params.api.getAppHead(job.currentAppId);
10055
- const freshHead = unwrapResponseObject(freshHeadResp, "app head");
10056
- if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
10057
- throw buildFinalizeCliError({
10058
- message: "Backend returned a succeeded change step without a matching result revision.",
10059
- exitCode: 1,
10060
- hint: "The local baseline was not advanced because the post-step revision could not be verified. Restart the backend/CLI and retry after checking `remix collab status`.",
10061
- disposition: "terminal",
10062
- reason: "missing_result_revision_id"
10063
- });
10064
- }
10065
- nextServerRevisionId = freshHead.headRevisionId;
10066
- nextServerTreeHash = freshHead.treeHash ?? null;
10067
- }
10068
- await writeLocalBaseline({
10069
- repoRoot: job.repoRoot,
10070
- repoFingerprint: job.repoFingerprint,
10071
- laneId: job.laneId,
10072
- currentAppId: job.currentAppId,
10073
- branchName: job.branchName,
10074
- lastSnapshotId: snapshot.id,
10075
- lastSnapshotHash: snapshot.snapshotHash,
10076
- lastServerRevisionId: nextServerRevisionId,
10077
- lastServerTreeHash: nextServerTreeHash,
10078
- lastServerHeadHash: nextServerHeadHash,
10079
- lastSeenLocalCommitHash: snapshot.localCommitHash
10080
- });
10138
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep });
10081
10139
  await updatePendingFinalizeJob(job.id, {
10082
10140
  status: "completed",
10083
10141
  metadata: { changeStepId: String(changeStep.id ?? "") }
@@ -10570,7 +10628,7 @@ async function createPendingTurnState(params) {
10570
10628
  // package.json
10571
10629
  var package_default = {
10572
10630
  name: "@remixhq/claude-plugin",
10573
- version: "0.1.23",
10631
+ version: "0.1.24",
10574
10632
  description: "Claude Code plugin for Remix collaboration workflows",
10575
10633
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
10576
10634
  license: "MIT",
@@ -10608,8 +10666,8 @@ var package_default = {
10608
10666
  prepack: "npm run build"
10609
10667
  },
10610
10668
  dependencies: {
10611
- "@remixhq/core": "^0.1.18",
10612
- "@remixhq/mcp": "^0.1.18"
10669
+ "@remixhq/core": "^0.1.19",
10670
+ "@remixhq/mcp": "^0.1.19"
10613
10671
  },
10614
10672
  devDependencies: {
10615
10673
  "@types/node": "^25.4.0",
@@ -10881,7 +10939,7 @@ function mergeOutcomeIntoMarker(existing, outcome) {
10881
10939
  return existing;
10882
10940
  }
10883
10941
 
10884
- // node_modules/@remixhq/core/dist/chunk-RCNOSZP6.js
10942
+ // node_modules/@remixhq/core/dist/chunk-C2FOZ3O7.js
10885
10943
  async function readJsonSafe(res) {
10886
10944
  const ct = res.headers.get("content-type") ?? "";
10887
10945
  if (!ct.toLowerCase().includes("application/json")) return null;
@@ -11075,6 +11133,14 @@ function createApiClient(config, opts) {
11075
11133
  method: "POST",
11076
11134
  body: JSON.stringify(payload)
11077
11135
  }),
11136
+ listChangeSteps: (appId, params) => {
11137
+ const qs = new URLSearchParams();
11138
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
11139
+ if (params?.offset !== void 0) qs.set("offset", String(params.offset));
11140
+ if (params?.idempotencyKey) qs.set("idempotencyKey", params.idempotencyKey);
11141
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
11142
+ return request(`/v1/apps/${encodeURIComponent(appId)}/change-steps${suffix}`, { method: "GET" });
11143
+ },
11078
11144
  createCollabTurn: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns`, {
11079
11145
  method: "POST",
11080
11146
  body: JSON.stringify(payload)
@@ -36105,26 +36171,39 @@ function isPidAlive2(pid) {
36105
36171
  return false;
36106
36172
  }
36107
36173
  }
36108
- function readLockPid(spawnLockPath) {
36174
+ function readSpawnLock(spawnLockPath) {
36109
36175
  try {
36110
36176
  const raw = (0, import_node_fs7.readFileSync)(spawnLockPath, "utf8").trim();
36111
36177
  if (!raw) return null;
36178
+ if (raw.startsWith("{")) {
36179
+ const parsed = JSON.parse(raw);
36180
+ const pid2 = Number(parsed?.pid ?? 0);
36181
+ return {
36182
+ pid: Number.isFinite(pid2) && pid2 > 0 ? pid2 : null,
36183
+ branchName: typeof parsed?.branchName === "string" && parsed.branchName.trim() ? parsed.branchName : null
36184
+ };
36185
+ }
36112
36186
  const pid = Number.parseInt(raw, 10);
36113
- return Number.isFinite(pid) && pid > 0 ? pid : null;
36187
+ return {
36188
+ pid: Number.isFinite(pid) && pid > 0 ? pid : null,
36189
+ branchName: null
36190
+ };
36114
36191
  } catch {
36115
36192
  return null;
36116
36193
  }
36117
36194
  }
36118
- function maybeAutoSpawnBranchInit(repoRoot) {
36195
+ function maybeAutoSpawnBranchInit(repoRoot, branchName) {
36119
36196
  const remixDir = import_node_path13.default.join(repoRoot, ".remix");
36120
36197
  const spawnLockPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_SPAWN_LOCK_REL);
36121
36198
  const logPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_LOG_REL);
36122
36199
  try {
36123
36200
  if ((0, import_node_fs7.existsSync)(spawnLockPath)) {
36124
- const lockPid = readLockPid(spawnLockPath);
36201
+ const lock = readSpawnLock(spawnLockPath);
36202
+ const lockPid = lock?.pid ?? null;
36125
36203
  const lockAlive = lockPid !== null && isPidAlive2(lockPid);
36204
+ const sameBranch = !lock?.branchName || !branchName || lock.branchName === branchName;
36126
36205
  const ageMs = Date.now() - (0, import_node_fs7.statSync)(spawnLockPath).mtimeMs;
36127
- if (lockAlive && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
36206
+ if (lockAlive && sameBranch && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
36128
36207
  return { spawned: false, reason: "spawn_lock_held" };
36129
36208
  }
36130
36209
  if (!lockAlive) {
@@ -36153,6 +36232,13 @@ function maybeAutoSpawnBranchInit(repoRoot) {
36153
36232
  };
36154
36233
  }
36155
36234
  try {
36235
+ (0, import_node_fs7.appendFileSync)(
36236
+ logPath,
36237
+ `
36238
+ [${(/* @__PURE__ */ new Date()).toISOString()}] auto-spawning remix collab init for branch=${branchName ?? "(unknown)"} repo=${repoRoot}
36239
+ `,
36240
+ "utf8"
36241
+ );
36156
36242
  const child = (0, import_node_child_process8.spawn)("remix", ["collab", "init"], {
36157
36243
  cwd: repoRoot,
36158
36244
  detached: true,
@@ -36161,7 +36247,16 @@ function maybeAutoSpawnBranchInit(repoRoot) {
36161
36247
  });
36162
36248
  child.unref();
36163
36249
  try {
36164
- (0, import_node_fs7.writeFileSync)(spawnLockPath, String(child.pid ?? ""), "utf8");
36250
+ const lock = {
36251
+ schemaVersion: 1,
36252
+ pid: child.pid ?? null,
36253
+ branchName,
36254
+ repoRoot,
36255
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
36256
+ command: "remix collab init"
36257
+ };
36258
+ (0, import_node_fs7.writeFileSync)(spawnLockPath, `${JSON.stringify(lock, null, 2)}
36259
+ `, "utf8");
36165
36260
  (0, import_node_fs7.utimesSync)(spawnLockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
36166
36261
  } catch {
36167
36262
  }
@@ -36380,7 +36475,7 @@ async function runHookUserPrompt(payload) {
36380
36475
  }
36381
36476
  if (isCurrentBranchUnbound) {
36382
36477
  const currentBranch = bindingState?.currentBranch ?? null;
36383
- const outcome = maybeAutoSpawnBranchInit(boundRepo);
36478
+ const outcome = maybeAutoSpawnBranchInit(boundRepo, currentBranch);
36384
36479
  if (outcome.spawned) {
36385
36480
  advisorySections.push(buildBranchInitContextMessage(currentBranch, boundRepo, outcome.logPath));
36386
36481
  await appendHookDiagnosticsEvent({