@remixhq/claude-plugin 0.1.23 → 0.1.25

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.
@@ -38,7 +38,7 @@ var require_windows = __commonJS({
38
38
  module2.exports = isexe;
39
39
  isexe.sync = sync;
40
40
  var fs11 = require("fs");
41
- function checkPathExt(path17, options) {
41
+ function checkPathExt(path18, options) {
42
42
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
43
43
  if (!pathext) {
44
44
  return true;
@@ -49,25 +49,25 @@ var require_windows = __commonJS({
49
49
  }
50
50
  for (var i2 = 0; i2 < pathext.length; i2++) {
51
51
  var p = pathext[i2].toLowerCase();
52
- if (p && path17.substr(-p.length).toLowerCase() === p) {
52
+ if (p && path18.substr(-p.length).toLowerCase() === p) {
53
53
  return true;
54
54
  }
55
55
  }
56
56
  return false;
57
57
  }
58
- function checkStat(stat, path17, options) {
58
+ function checkStat(stat, path18, options) {
59
59
  if (!stat.isSymbolicLink() && !stat.isFile()) {
60
60
  return false;
61
61
  }
62
- return checkPathExt(path17, options);
62
+ return checkPathExt(path18, options);
63
63
  }
64
- function isexe(path17, options, cb) {
65
- fs11.stat(path17, function(er, stat) {
66
- cb(er, er ? false : checkStat(stat, path17, options));
64
+ function isexe(path18, options, cb) {
65
+ fs11.stat(path18, function(er, stat) {
66
+ cb(er, er ? false : checkStat(stat, path18, options));
67
67
  });
68
68
  }
69
- function sync(path17, options) {
70
- return checkStat(fs11.statSync(path17), path17, options);
69
+ function sync(path18, options) {
70
+ return checkStat(fs11.statSync(path18), path18, options);
71
71
  }
72
72
  }
73
73
  });
@@ -79,13 +79,13 @@ var require_mode = __commonJS({
79
79
  module2.exports = isexe;
80
80
  isexe.sync = sync;
81
81
  var fs11 = require("fs");
82
- function isexe(path17, options, cb) {
83
- fs11.stat(path17, function(er, stat) {
82
+ function isexe(path18, options, cb) {
83
+ fs11.stat(path18, function(er, stat) {
84
84
  cb(er, er ? false : checkStat(stat, options));
85
85
  });
86
86
  }
87
- function sync(path17, options) {
88
- return checkStat(fs11.statSync(path17), options);
87
+ function sync(path18, options) {
88
+ return checkStat(fs11.statSync(path18), options);
89
89
  }
90
90
  function checkStat(stat, options) {
91
91
  return stat.isFile() && checkMode(stat, options);
@@ -119,7 +119,7 @@ var require_isexe = __commonJS({
119
119
  }
120
120
  module2.exports = isexe;
121
121
  isexe.sync = sync;
122
- function isexe(path17, options, cb) {
122
+ function isexe(path18, options, cb) {
123
123
  if (typeof options === "function") {
124
124
  cb = options;
125
125
  options = {};
@@ -129,7 +129,7 @@ var require_isexe = __commonJS({
129
129
  throw new TypeError("callback not provided");
130
130
  }
131
131
  return new Promise(function(resolve, reject) {
132
- isexe(path17, options || {}, function(er, is) {
132
+ isexe(path18, options || {}, function(er, is) {
133
133
  if (er) {
134
134
  reject(er);
135
135
  } else {
@@ -138,7 +138,7 @@ var require_isexe = __commonJS({
138
138
  });
139
139
  });
140
140
  }
141
- core(path17, options || {}, function(er, is) {
141
+ core(path18, options || {}, function(er, is) {
142
142
  if (er) {
143
143
  if (er.code === "EACCES" || options && options.ignoreErrors) {
144
144
  er = null;
@@ -148,9 +148,9 @@ var require_isexe = __commonJS({
148
148
  cb(er, is);
149
149
  });
150
150
  }
151
- function sync(path17, options) {
151
+ function sync(path18, options) {
152
152
  try {
153
- return core.sync(path17, options || {});
153
+ return core.sync(path18, options || {});
154
154
  } catch (er) {
155
155
  if (options && options.ignoreErrors || er.code === "EACCES") {
156
156
  return false;
@@ -167,7 +167,7 @@ var require_which = __commonJS({
167
167
  "node_modules/which/which.js"(exports2, module2) {
168
168
  "use strict";
169
169
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
170
- var path17 = require("path");
170
+ var path18 = require("path");
171
171
  var COLON = isWindows ? ";" : ":";
172
172
  var isexe = require_isexe();
173
173
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -205,7 +205,7 @@ var require_which = __commonJS({
205
205
  return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
206
206
  const ppRaw = pathEnv[i2];
207
207
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
208
- const pCmd = path17.join(pathPart, cmd);
208
+ const pCmd = path18.join(pathPart, cmd);
209
209
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
210
210
  resolve(subStep(p, i2, 0));
211
211
  });
@@ -232,7 +232,7 @@ var require_which = __commonJS({
232
232
  for (let i2 = 0; i2 < pathEnv.length; i2++) {
233
233
  const ppRaw = pathEnv[i2];
234
234
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
235
- const pCmd = path17.join(pathPart, cmd);
235
+ const pCmd = path18.join(pathPart, cmd);
236
236
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
237
237
  for (let j = 0; j < pathExt.length; j++) {
238
238
  const cur = p + pathExt[j];
@@ -280,7 +280,7 @@ var require_path_key = __commonJS({
280
280
  var require_resolveCommand = __commonJS({
281
281
  "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports2, module2) {
282
282
  "use strict";
283
- var path17 = require("path");
283
+ var path18 = require("path");
284
284
  var which = require_which();
285
285
  var getPathKey = require_path_key();
286
286
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -298,7 +298,7 @@ var require_resolveCommand = __commonJS({
298
298
  try {
299
299
  resolved = which.sync(parsed.command, {
300
300
  path: env[getPathKey({ env })],
301
- pathExt: withoutPathExt ? path17.delimiter : void 0
301
+ pathExt: withoutPathExt ? path18.delimiter : void 0
302
302
  });
303
303
  } catch (e) {
304
304
  } finally {
@@ -307,7 +307,7 @@ var require_resolveCommand = __commonJS({
307
307
  }
308
308
  }
309
309
  if (resolved) {
310
- resolved = path17.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
310
+ resolved = path18.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
311
311
  }
312
312
  return resolved;
313
313
  }
@@ -361,8 +361,8 @@ var require_shebang_command = __commonJS({
361
361
  if (!match) {
362
362
  return null;
363
363
  }
364
- const [path17, argument] = match[0].replace(/#! ?/, "").split(" ");
365
- const binary = path17.split("/").pop();
364
+ const [path18, argument] = match[0].replace(/#! ?/, "").split(" ");
365
+ const binary = path18.split("/").pop();
366
366
  if (binary === "env") {
367
367
  return argument;
368
368
  }
@@ -397,7 +397,7 @@ var require_readShebang = __commonJS({
397
397
  var require_parse = __commonJS({
398
398
  "node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
399
399
  "use strict";
400
- var path17 = require("path");
400
+ var path18 = require("path");
401
401
  var resolveCommand = require_resolveCommand();
402
402
  var escape = require_escape();
403
403
  var readShebang = require_readShebang();
@@ -422,7 +422,7 @@ var require_parse = __commonJS({
422
422
  const needsShell = !isExecutableRegExp.test(commandFile);
423
423
  if (parsed.options.forceShell || needsShell) {
424
424
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
425
- parsed.command = path17.normalize(parsed.command);
425
+ parsed.command = path18.normalize(parsed.command);
426
426
  parsed.command = escape.command(parsed.command);
427
427
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
428
428
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -540,7 +540,7 @@ __export(hook_user_prompt_exports, {
540
540
  module.exports = __toCommonJS(hook_user_prompt_exports);
541
541
  var import_node_child_process8 = require("child_process");
542
542
  var import_node_fs7 = require("fs");
543
- var import_node_path13 = __toESM(require("path"), 1);
543
+ var import_node_path14 = __toESM(require("path"), 1);
544
544
 
545
545
  // node_modules/@remixhq/core/dist/chunk-7XJGOKEO.js
546
546
  var RemixError = class extends Error {
@@ -4943,13 +4943,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
4943
4943
  }
4944
4944
  };
4945
4945
  var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
4946
- for (const { path: path17, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4947
- const pathString = typeof path17 === "string" ? path17 : path17.toString();
4946
+ for (const { path: path18, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4947
+ const pathString = typeof path18 === "string" ? path18 : path18.toString();
4948
4948
  if (append || outputFiles.has(pathString)) {
4949
- (0, import_node_fs4.appendFileSync)(path17, serializedResult);
4949
+ (0, import_node_fs4.appendFileSync)(path18, serializedResult);
4950
4950
  } else {
4951
4951
  outputFiles.add(pathString);
4952
- (0, import_node_fs4.writeFileSync)(path17, serializedResult);
4952
+ (0, import_node_fs4.writeFileSync)(path18, serializedResult);
4953
4953
  }
4954
4954
  }
4955
4955
  };
@@ -7668,6 +7668,21 @@ async function writeCollabBinding(repoRoot, binding) {
7668
7668
  return filePath;
7669
7669
  }
7670
7670
 
7671
+ // src/collab-init-spawn-lock.ts
7672
+ var import_node_crypto = require("crypto");
7673
+ var import_node_os4 = __toESM(require("os"), 1);
7674
+ var import_node_path6 = __toESM(require("path"), 1);
7675
+ function stateRoot() {
7676
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
7677
+ return configured || import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
7678
+ }
7679
+ function sha256Hex(value) {
7680
+ return (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
7681
+ }
7682
+ function collabInitSpawnLockPath(repoRoot) {
7683
+ return import_node_path6.default.join(stateRoot(), "collab-init-spawn-locks", `${sha256Hex(repoRoot)}.json`);
7684
+ }
7685
+
7671
7686
  // src/history-routing.ts
7672
7687
  var STRONG_MEMORY_FIRST_PATTERNS = [
7673
7688
  /\bwhy\b/i,
@@ -7773,25 +7788,25 @@ function buildPromptRoutingAdvisory(intent) {
7773
7788
 
7774
7789
  // src/deferred-turn-queue.ts
7775
7790
  var import_promises14 = __toESM(require("fs/promises"), 1);
7776
- var import_node_os4 = __toESM(require("os"), 1);
7777
- var import_node_path6 = __toESM(require("path"), 1);
7791
+ var import_node_os5 = __toESM(require("os"), 1);
7792
+ var import_node_path7 = __toESM(require("path"), 1);
7778
7793
  var DEFERRED_TURN_SCHEMA_VERSION = 1;
7779
7794
  var DEFERRED_TURN_MAX_ATTEMPTS = 10;
7780
7795
  var DEFERRED_TURN_TTL_MS = 24 * 60 * 60 * 1e3;
7781
7796
  var DEFERRED_TURN_DIR = "deferred-turns";
7782
- function stateRoot() {
7797
+ function stateRoot2() {
7783
7798
  const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
7784
- return configured || import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
7799
+ return configured || import_node_path7.default.join(import_node_os5.default.tmpdir(), "remix-claude-plugin-hooks");
7785
7800
  }
7786
7801
  function getDeferredTurnDirPath() {
7787
- return import_node_path6.default.join(stateRoot(), DEFERRED_TURN_DIR);
7802
+ return import_node_path7.default.join(stateRoot2(), DEFERRED_TURN_DIR);
7788
7803
  }
7789
7804
  function deferredTurnFileName(sessionId, turnId) {
7790
7805
  const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
7791
7806
  return `${safe(sessionId)}-${safe(turnId)}.json`;
7792
7807
  }
7793
7808
  function getDeferredTurnFilePath(sessionId, turnId) {
7794
- return import_node_path6.default.join(getDeferredTurnDirPath(), deferredTurnFileName(sessionId, turnId));
7809
+ return import_node_path7.default.join(getDeferredTurnDirPath(), deferredTurnFileName(sessionId, turnId));
7795
7810
  }
7796
7811
  async function writeDeferredTurn(record) {
7797
7812
  if (record.schemaVersion !== DEFERRED_TURN_SCHEMA_VERSION) {
@@ -7852,7 +7867,7 @@ async function listDeferredTurnsForRepo(repoRoot) {
7852
7867
  const entries = [];
7853
7868
  for (const entry of dirEntries) {
7854
7869
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
7855
- const filePath = import_node_path6.default.join(dir, entry.name);
7870
+ const filePath = import_node_path7.default.join(dir, entry.name);
7856
7871
  const record = await readDeferredTurnFile(filePath);
7857
7872
  if (!record) continue;
7858
7873
  if (record.repoRoot !== repoRoot) continue;
@@ -7876,7 +7891,7 @@ async function pruneStaleDeferredTurns(maxAgeMs = DEFERRED_TURN_TTL_MS) {
7876
7891
  const now = Date.now();
7877
7892
  for (const entry of dirEntries) {
7878
7893
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
7879
- const filePath = import_node_path6.default.join(dir, entry.name);
7894
+ const filePath = import_node_path7.default.join(dir, entry.name);
7880
7895
  const record = await readDeferredTurnFile(filePath);
7881
7896
  if (!record) {
7882
7897
  const stat = await import_promises14.default.stat(filePath).catch(() => null);
@@ -7912,8 +7927,8 @@ async function recordDeferredTurnFailedAttempt(filePath) {
7912
7927
 
7913
7928
  // src/deferred-turn-drainer.ts
7914
7929
  var import_promises23 = __toESM(require("fs/promises"), 1);
7915
- var import_node_path11 = __toESM(require("path"), 1);
7916
- var import_node_crypto3 = require("crypto");
7930
+ var import_node_path12 = __toESM(require("path"), 1);
7931
+ var import_node_crypto5 = require("crypto");
7917
7932
 
7918
7933
  // node_modules/@remixhq/core/dist/collab.js
7919
7934
  var import_promises15 = __toESM(require("fs/promises"), 1);
@@ -8015,7 +8030,7 @@ function buildBranchMismatchHint(params) {
8015
8030
  `Switch to ${describeBranch(params.branchName)} or rerun with ${overrideFlag} if this is intentional.`
8016
8031
  ].join("\n");
8017
8032
  }
8018
- function sha256Hex(value) {
8033
+ function sha256Hex2(value) {
8019
8034
  return (0, import_crypto.createHash)("sha256").update(value).digest("hex");
8020
8035
  }
8021
8036
  function getCollabStateRoot() {
@@ -8029,7 +8044,7 @@ function buildLaneStateKey(params) {
8029
8044
  const stableSource = repoRoot || "unknown-repo-root";
8030
8045
  const fingerprintSource = fingerprint || "unknown-repo-fingerprint";
8031
8046
  const laneSource = laneId || "unknown-lane";
8032
- return sha256Hex(`${stableSource}::${fingerprintSource}::${laneSource}`);
8047
+ return sha256Hex2(`${stableSource}::${fingerprintSource}::${laneSource}`);
8033
8048
  }
8034
8049
  function getSnapshotsRoot() {
8035
8050
  return import_path3.default.join(getCollabStateRoot(), "snapshots");
@@ -8230,7 +8245,7 @@ async function writeLocalBaseline(baseline) {
8230
8245
  await writeJsonAtomic(getBaselinePath(baseline), normalized);
8231
8246
  return normalized;
8232
8247
  }
8233
- function sha256Hex2(value) {
8248
+ function sha256Hex22(value) {
8234
8249
  return (0, import_crypto2.createHash)("sha256").update(value).digest("hex");
8235
8250
  }
8236
8251
  function getSnapshotRecordPath(snapshotId) {
@@ -8281,7 +8296,7 @@ async function persistBlob(blobHash, content) {
8281
8296
  }
8282
8297
  function buildSnapshotHash(files) {
8283
8298
  const manifest = files.map((file) => `${file.path} ${file.mode} ${file.blobHash} ${file.size}`).join("\n");
8284
- return sha256Hex2(manifest);
8299
+ return sha256Hex22(manifest);
8285
8300
  }
8286
8301
  async function inspectLocalSnapshot(params) {
8287
8302
  const repoRoot = params.repoRoot;
@@ -8292,7 +8307,7 @@ async function inspectLocalSnapshot(params) {
8292
8307
  const stat = await import_promises17.default.lstat(absolutePath);
8293
8308
  if (stat.isSymbolicLink()) {
8294
8309
  const linkTarget = await import_promises17.default.readlink(absolutePath);
8295
- const blobHash2 = sha256Hex2(`symlink:${linkTarget}`);
8310
+ const blobHash2 = sha256Hex22(`symlink:${linkTarget}`);
8296
8311
  if (params.persistBlobs !== false) {
8297
8312
  await persistBlob(blobHash2, linkTarget);
8298
8313
  }
@@ -8305,7 +8320,7 @@ async function inspectLocalSnapshot(params) {
8305
8320
  continue;
8306
8321
  }
8307
8322
  const content = await import_promises17.default.readFile(absolutePath);
8308
- const blobHash = sha256Hex2(content);
8323
+ const blobHash = sha256Hex22(content);
8309
8324
  if (params.persistBlobs !== false) {
8310
8325
  await persistBlob(blobHash, content);
8311
8326
  }
@@ -8409,7 +8424,7 @@ async function diffLocalSnapshots(params) {
8409
8424
  baseSnapshotId: params.baseSnapshotId,
8410
8425
  targetSnapshotId: params.targetSnapshotId,
8411
8426
  diff,
8412
- diffSha256: diff ? sha256Hex2(diff) : null,
8427
+ diffSha256: diff ? sha256Hex22(diff) : null,
8413
8428
  changedPaths,
8414
8429
  stats: summarizeUnifiedDiff(diff)
8415
8430
  };
@@ -8827,6 +8842,15 @@ function shouldRequireRemoteLaneForCurrentBranch(params) {
8827
8842
  if (params.currentBranch === defaultBranch) return false;
8828
8843
  return !params.binding.laneId || params.binding.currentAppId === params.binding.upstreamAppId;
8829
8844
  }
8845
+ function resolveLaneLookupProjectId(params) {
8846
+ const currentBranch = normalizeBranchName2(params.currentBranch);
8847
+ const defaultBranch = normalizeBranchName2(params.defaultBranch);
8848
+ const localProjectId = params.localBinding.projectId ?? null;
8849
+ if (currentBranch && currentBranch !== defaultBranch && localProjectId) {
8850
+ return localProjectId;
8851
+ }
8852
+ return params.explicitRootProjectId ?? (params.requireRemoteLane ? void 0 : localProjectId ?? params.fallbackProjectId ?? void 0);
8853
+ }
8830
8854
  async function persistResolvedLane(repoRoot, binding) {
8831
8855
  await writeCollabBinding(repoRoot, {
8832
8856
  projectId: binding.projectId,
@@ -8905,7 +8929,14 @@ async function resolveActiveLaneBindingUncached(params, state) {
8905
8929
  };
8906
8930
  }
8907
8931
  const laneResp2 = await params.api.resolveProjectLaneBinding({
8908
- projectId: state.explicitRootBinding?.projectId ?? (requireRemoteLane ? void 0 : localBinding.projectId ?? state.projectId ?? void 0),
8932
+ projectId: resolveLaneLookupProjectId({
8933
+ explicitRootProjectId: state.explicitRootBinding?.projectId,
8934
+ localBinding,
8935
+ currentBranch,
8936
+ defaultBranch: state.defaultBranch,
8937
+ requireRemoteLane,
8938
+ fallbackProjectId: state.projectId
8939
+ }),
8909
8940
  repoFingerprint: state.repoFingerprint ?? void 0,
8910
8941
  remoteUrl: state.remoteUrl ?? void 0,
8911
8942
  defaultBranch: state.defaultBranch ?? void 0,
@@ -9756,6 +9787,59 @@ function buildWorkspaceMetadata(params) {
9756
9787
  }
9757
9788
  return metadata;
9758
9789
  }
9790
+ async function findExistingChangeStepByIdempotency(params) {
9791
+ const idempotencyKey = params.idempotencyKey?.trim();
9792
+ if (!idempotencyKey) return null;
9793
+ const resp = await params.api.listChangeSteps(params.appId, { limit: 1, idempotencyKey });
9794
+ const responseObject = unwrapResponseObject(
9795
+ resp,
9796
+ "change step list"
9797
+ );
9798
+ const steps = Array.isArray(responseObject) ? responseObject : Array.isArray(responseObject.items) ? responseObject.items : [];
9799
+ return steps.find((step) => step.idempotencyKey === idempotencyKey) ?? null;
9800
+ }
9801
+ async function writeBaselineFromSucceededChangeStep(params) {
9802
+ const nextServerHeadHash = typeof params.changeStep.headCommitHash === "string" ? params.changeStep.headCommitHash.trim() : "";
9803
+ if (!nextServerHeadHash) {
9804
+ throw buildFinalizeCliError({
9805
+ message: "Backend returned a succeeded change step without a head commit hash.",
9806
+ exitCode: 1,
9807
+ hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
9808
+ disposition: "terminal",
9809
+ reason: "missing_head_commit_hash"
9810
+ });
9811
+ }
9812
+ let nextServerRevisionId = typeof params.changeStep.resultRevisionId === "string" ? params.changeStep.resultRevisionId.trim() : "";
9813
+ let nextServerTreeHash = null;
9814
+ if (!nextServerRevisionId) {
9815
+ const freshHeadResp = await params.api.getAppHead(params.job.currentAppId);
9816
+ const freshHead = unwrapResponseObject(freshHeadResp, "app head");
9817
+ if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
9818
+ throw buildFinalizeCliError({
9819
+ message: "Backend returned a succeeded change step without a matching result revision.",
9820
+ exitCode: 1,
9821
+ 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`.",
9822
+ disposition: "terminal",
9823
+ reason: "missing_result_revision_id"
9824
+ });
9825
+ }
9826
+ nextServerRevisionId = freshHead.headRevisionId;
9827
+ nextServerTreeHash = freshHead.treeHash ?? null;
9828
+ }
9829
+ await writeLocalBaseline({
9830
+ repoRoot: params.job.repoRoot,
9831
+ repoFingerprint: params.job.repoFingerprint,
9832
+ laneId: params.job.laneId,
9833
+ currentAppId: params.job.currentAppId,
9834
+ branchName: params.job.branchName,
9835
+ lastSnapshotId: params.snapshot.id,
9836
+ lastSnapshotHash: params.snapshot.snapshotHash,
9837
+ lastServerRevisionId: nextServerRevisionId,
9838
+ lastServerTreeHash: nextServerTreeHash,
9839
+ lastServerHeadHash: nextServerHeadHash,
9840
+ lastSeenLocalCommitHash: params.snapshot.localCommitHash
9841
+ });
9842
+ }
9759
9843
  async function harvestPreTurnEvents(repoRoot, fromCommit, toCommit) {
9760
9844
  if (!toCommit) return null;
9761
9845
  try {
@@ -9941,6 +10025,34 @@ async function processClaimedPendingFinalizeJobInner(params) {
9941
10025
  });
9942
10026
  }
9943
10027
  const replayNeeded = appHead.headCommitHash !== submissionBaseHeadHash || baselineDrifted;
10028
+ if (replayNeeded) {
10029
+ const existingChangeStep = await findExistingChangeStepByIdempotency({
10030
+ api: params.api,
10031
+ appId: job.currentAppId,
10032
+ idempotencyKey: job.idempotencyKey
10033
+ });
10034
+ if (existingChangeStep) {
10035
+ const changeStep2 = existingChangeStep.status === "succeeded" ? existingChangeStep : await pollChangeStep(params.api, job.currentAppId, existingChangeStep.id);
10036
+ invalidateAppHeadCache(job.currentAppId);
10037
+ invalidateAppDeltaCacheForApp(job.currentAppId);
10038
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep: changeStep2 });
10039
+ await updatePendingFinalizeJob(job.id, {
10040
+ status: "completed",
10041
+ metadata: { changeStepId: String(changeStep2.id ?? "") }
10042
+ });
10043
+ return {
10044
+ mode: "changed_turn",
10045
+ idempotencyKey: job.idempotencyKey ?? "",
10046
+ queued: false,
10047
+ jobId: job.id,
10048
+ repoState,
10049
+ changeStep: changeStep2,
10050
+ collabTurn: null,
10051
+ autoSync: null,
10052
+ warnings: []
10053
+ };
10054
+ }
10055
+ }
9944
10056
  if (replayNeeded) {
9945
10057
  try {
9946
10058
  const replayResp = await params.api.startChangeStepReplay(job.currentAppId, {
@@ -10038,46 +10150,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
10038
10150
  const changeStep = await pollChangeStep(params.api, job.currentAppId, String(createdStep.id));
10039
10151
  invalidateAppHeadCache(job.currentAppId);
10040
10152
  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
- });
10153
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep });
10081
10154
  await updatePendingFinalizeJob(job.id, {
10082
10155
  status: "completed",
10083
10156
  metadata: { changeStepId: String(changeStep.id ?? "") }
@@ -10362,15 +10435,17 @@ function isFinalizePreflightFailureCode(value) {
10362
10435
 
10363
10436
  // src/auto-fix-dispatcher.ts
10364
10437
  var import_node_child_process6 = require("child_process");
10438
+ var import_node_crypto4 = require("crypto");
10365
10439
  var import_node_fs6 = require("fs");
10366
- var import_node_path10 = __toESM(require("path"), 1);
10440
+ var import_node_os8 = __toESM(require("os"), 1);
10441
+ var import_node_path11 = __toESM(require("path"), 1);
10367
10442
 
10368
10443
  // src/finalize-failure-marker.ts
10369
10444
  var import_promises19 = __toESM(require("fs/promises"), 1);
10370
- var import_node_path7 = __toESM(require("path"), 1);
10371
- var FINALIZE_FAILURE_MARKER_REL = import_node_path7.default.join(".remix", ".last-finalize-failure.json");
10445
+ var import_node_path8 = __toESM(require("path"), 1);
10446
+ var FINALIZE_FAILURE_MARKER_REL = import_node_path8.default.join(".remix", ".last-finalize-failure.json");
10372
10447
  function markerPath(repoRoot) {
10373
- return import_node_path7.default.join(repoRoot, FINALIZE_FAILURE_MARKER_REL);
10448
+ return import_node_path8.default.join(repoRoot, FINALIZE_FAILURE_MARKER_REL);
10374
10449
  }
10375
10450
  async function readFinalizeFailureMarker(repoRoot) {
10376
10451
  const raw = await import_promises19.default.readFile(markerPath(repoRoot), "utf8").catch(() => null);
@@ -10387,7 +10462,7 @@ async function readFinalizeFailureMarker(repoRoot) {
10387
10462
  }
10388
10463
  async function writeFinalizeFailureMarker(marker) {
10389
10464
  const filePath = markerPath(marker.repoRoot);
10390
- await import_promises19.default.mkdir(import_node_path7.default.dirname(filePath), { recursive: true });
10465
+ await import_promises19.default.mkdir(import_node_path8.default.dirname(filePath), { recursive: true });
10391
10466
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
10392
10467
  await import_promises19.default.writeFile(tmpPath, JSON.stringify(marker, null, 2), "utf8");
10393
10468
  await import_promises19.default.rename(tmpPath, filePath);
@@ -10413,31 +10488,31 @@ function buildFreshFailureMarker(params) {
10413
10488
  }
10414
10489
 
10415
10490
  // src/hook-diagnostics.ts
10416
- var import_node_crypto2 = require("crypto");
10491
+ var import_node_crypto3 = require("crypto");
10417
10492
  var import_promises21 = __toESM(require("fs/promises"), 1);
10418
- var import_node_os6 = __toESM(require("os"), 1);
10419
- var import_node_path9 = __toESM(require("path"), 1);
10493
+ var import_node_os7 = __toESM(require("os"), 1);
10494
+ var import_node_path10 = __toESM(require("path"), 1);
10420
10495
 
10421
10496
  // src/hook-state.ts
10422
10497
  var import_promises20 = __toESM(require("fs/promises"), 1);
10423
- var import_node_os5 = __toESM(require("os"), 1);
10424
- var import_node_path8 = __toESM(require("path"), 1);
10425
- var import_node_crypto = require("crypto");
10426
- function stateRoot2() {
10498
+ var import_node_os6 = __toESM(require("os"), 1);
10499
+ var import_node_path9 = __toESM(require("path"), 1);
10500
+ var import_node_crypto2 = require("crypto");
10501
+ function stateRoot3() {
10427
10502
  const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
10428
- return configured || import_node_path8.default.join(import_node_os5.default.tmpdir(), "remix-claude-plugin-hooks");
10503
+ return configured || import_node_path9.default.join(import_node_os6.default.tmpdir(), "remix-claude-plugin-hooks");
10429
10504
  }
10430
10505
  function statePath(sessionId) {
10431
- return import_node_path8.default.join(stateRoot2(), `${sessionId}.json`);
10506
+ return import_node_path9.default.join(stateRoot3(), `${sessionId}.json`);
10432
10507
  }
10433
10508
  function stateLockPath(sessionId) {
10434
- return import_node_path8.default.join(stateRoot2(), `${sessionId}.lock`);
10509
+ return import_node_path9.default.join(stateRoot3(), `${sessionId}.lock`);
10435
10510
  }
10436
10511
  function stateLockMetaPath(sessionId) {
10437
- return import_node_path8.default.join(stateLockPath(sessionId), "owner.json");
10512
+ return import_node_path9.default.join(stateLockPath(sessionId), "owner.json");
10438
10513
  }
10439
10514
  async function writeJsonAtomic2(filePath, value) {
10440
- await import_promises20.default.mkdir(import_node_path8.default.dirname(filePath), { recursive: true });
10515
+ await import_promises20.default.mkdir(import_node_path9.default.dirname(filePath), { recursive: true });
10441
10516
  const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
10442
10517
  await import_promises20.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
10443
10518
  await import_promises20.default.rename(tmpPath, filePath);
@@ -10490,11 +10565,11 @@ async function tryRemoveStaleStateLock(sessionId) {
10490
10565
  async function acquireStateLock(sessionId) {
10491
10566
  const lockPath = stateLockPath(sessionId);
10492
10567
  const deadline = Date.now() + STATE_LOCK_WAIT_MS;
10493
- await import_promises20.default.mkdir(stateRoot2(), { recursive: true });
10568
+ await import_promises20.default.mkdir(stateRoot3(), { recursive: true });
10494
10569
  while (true) {
10495
10570
  try {
10496
10571
  await import_promises20.default.mkdir(lockPath);
10497
- const ownerId = (0, import_node_crypto.randomUUID)();
10572
+ const ownerId = (0, import_node_crypto2.randomUUID)();
10498
10573
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
10499
10574
  const metadata = {
10500
10575
  ownerId,
@@ -10551,7 +10626,7 @@ async function createPendingTurnState(params) {
10551
10626
  return withStateLock(params.sessionId, async () => {
10552
10627
  const state = {
10553
10628
  sessionId: params.sessionId,
10554
- turnId: (0, import_node_crypto.randomUUID)(),
10629
+ turnId: (0, import_node_crypto2.randomUUID)(),
10555
10630
  prompt: params.prompt,
10556
10631
  initialCwd: params.initialCwd?.trim() || null,
10557
10632
  intent: params.intent,
@@ -10570,7 +10645,7 @@ async function createPendingTurnState(params) {
10570
10645
  // package.json
10571
10646
  var package_default = {
10572
10647
  name: "@remixhq/claude-plugin",
10573
- version: "0.1.23",
10648
+ version: "0.1.25",
10574
10649
  description: "Claude Code plugin for Remix collaboration workflows",
10575
10650
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
10576
10651
  license: "MIT",
@@ -10608,8 +10683,8 @@ var package_default = {
10608
10683
  prepack: "npm run build"
10609
10684
  },
10610
10685
  dependencies: {
10611
- "@remixhq/core": "^0.1.18",
10612
- "@remixhq/mcp": "^0.1.18"
10686
+ "@remixhq/core": "^0.1.20",
10687
+ "@remixhq/mcp": "^0.1.20"
10613
10688
  },
10614
10689
  devDependencies: {
10615
10690
  "@types/node": "^25.4.0",
@@ -10632,17 +10707,17 @@ var pluginMetadata = {
10632
10707
  var MAX_LOG_BYTES = 512 * 1024;
10633
10708
  function resolveClaudeRoot() {
10634
10709
  const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
10635
- return configured || import_node_path9.default.join(import_node_os6.default.homedir(), ".claude");
10710
+ return configured || import_node_path10.default.join(import_node_os7.default.homedir(), ".claude");
10636
10711
  }
10637
10712
  function resolvePluginDataDirName() {
10638
10713
  return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
10639
10714
  }
10640
10715
  function getHookDiagnosticsDirPath() {
10641
10716
  const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
10642
- return configured || import_node_path9.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
10717
+ return configured || import_node_path10.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
10643
10718
  }
10644
10719
  function getHookDiagnosticsLogPath() {
10645
- return import_node_path9.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
10720
+ return import_node_path10.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
10646
10721
  }
10647
10722
  function toFieldValue(value) {
10648
10723
  if (value === null) return null;
@@ -10680,13 +10755,13 @@ function summarizeText(value) {
10680
10755
  return {
10681
10756
  present: true,
10682
10757
  length: trimmed.length,
10683
- sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
10758
+ sha256Prefix: (0, import_node_crypto3.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
10684
10759
  };
10685
10760
  }
10686
10761
  async function appendHookDiagnosticsEvent(params) {
10687
10762
  try {
10688
10763
  const logPath = getHookDiagnosticsLogPath();
10689
- await import_promises21.default.mkdir(import_node_path9.default.dirname(logPath), { recursive: true });
10764
+ await import_promises21.default.mkdir(import_node_path10.default.dirname(logPath), { recursive: true });
10690
10765
  await rotateLogIfNeeded(logPath);
10691
10766
  const event = {
10692
10767
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10733,18 +10808,27 @@ var RECOMMENDED_USER_COMMAND = {
10733
10808
  pull_required: "remix collab sync",
10734
10809
  baseline_missing: "remix collab init"
10735
10810
  };
10736
- var SPAWN_LOCK_REL = (cmdSlug) => import_node_path10.default.join(".remix", `.${cmdSlug}-spawning`);
10737
- var SPAWN_LOG_REL = (cmdSlug) => import_node_path10.default.join(".remix", `${cmdSlug}.log`);
10738
10811
  var SPAWN_THROTTLE_MS = 5 * 60 * 1e3;
10739
10812
  function commandSlug(args) {
10740
10813
  return args.join("-").replace(/[^a-zA-Z0-9_-]/g, "_");
10741
10814
  }
10815
+ function stateRoot4() {
10816
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
10817
+ return configured || import_node_path11.default.join(import_node_os8.default.tmpdir(), "remix-claude-plugin-hooks");
10818
+ }
10819
+ function sha256Hex3(value) {
10820
+ return (0, import_node_crypto4.createHash)("sha256").update(value).digest("hex");
10821
+ }
10822
+ function spawnLockPath(repoRoot, cmdSlug) {
10823
+ if (cmdSlug === "collab-init") {
10824
+ return collabInitSpawnLockPath(repoRoot);
10825
+ }
10826
+ return import_node_path11.default.join(stateRoot4(), "auto-fix-spawn-locks", sha256Hex3(repoRoot), `${cmdSlug}.lock`);
10827
+ }
10742
10828
  function spawnFixDetached(repoRoot, args) {
10743
10829
  const slug = commandSlug(args);
10744
10830
  const command = `remix ${args.join(" ")}`;
10745
- const remixDir = import_node_path10.default.join(repoRoot, ".remix");
10746
- const lockPath = import_node_path10.default.join(repoRoot, SPAWN_LOCK_REL(slug));
10747
- const logPath = import_node_path10.default.join(repoRoot, SPAWN_LOG_REL(slug));
10831
+ const lockPath = spawnLockPath(repoRoot, slug);
10748
10832
  try {
10749
10833
  if ((0, import_node_fs6.existsSync)(lockPath)) {
10750
10834
  const ageMs = Date.now() - (0, import_node_fs6.statSync)(lockPath).mtimeMs;
@@ -10755,27 +10839,14 @@ function spawnFixDetached(repoRoot, args) {
10755
10839
  } catch {
10756
10840
  }
10757
10841
  try {
10758
- (0, import_node_fs6.mkdirSync)(remixDir, { recursive: true });
10842
+ (0, import_node_fs6.mkdirSync)(import_node_path11.default.dirname(lockPath), { recursive: true });
10759
10843
  } catch {
10760
10844
  }
10761
- let out;
10762
- let err;
10763
- try {
10764
- out = (0, import_node_fs6.openSync)(logPath, "a");
10765
- err = (0, import_node_fs6.openSync)(logPath, "a");
10766
- } catch (logErr) {
10767
- return {
10768
- kind: "spawn_failed",
10769
- command,
10770
- reason: "log_open_failed",
10771
- message: logErr instanceof Error ? logErr.message : String(logErr)
10772
- };
10773
- }
10774
10845
  try {
10775
10846
  const child = (0, import_node_child_process6.spawn)("remix", [...args], {
10776
10847
  cwd: repoRoot,
10777
10848
  detached: true,
10778
- stdio: ["ignore", out, err],
10849
+ stdio: "ignore",
10779
10850
  env: { ...process.env, REMIX_AUTO_FIX_SPAWN: "1" }
10780
10851
  });
10781
10852
  child.unref();
@@ -10784,7 +10855,7 @@ function spawnFixDetached(repoRoot, args) {
10784
10855
  (0, import_node_fs6.utimesSync)(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
10785
10856
  } catch {
10786
10857
  }
10787
- return { kind: "spawned", command, pid: child.pid, logPath };
10858
+ return { kind: "spawned", command, pid: child.pid };
10788
10859
  } catch (spawnErr) {
10789
10860
  return {
10790
10861
  kind: "spawn_failed",
@@ -10840,7 +10911,6 @@ async function dispatchFinalizeFailure(input) {
10840
10911
  preflightCode: input.preflightCode,
10841
10912
  command: "command" in outcome ? outcome.command : null,
10842
10913
  pid: outcome.kind === "spawned" ? outcome.pid ?? null : null,
10843
- logPath: outcome.kind === "spawned" ? outcome.logPath : null,
10844
10914
  recommendedCommand
10845
10915
  },
10846
10916
  message: outcome.kind === "spawn_failed" ? outcome.message : null
@@ -10853,7 +10923,7 @@ function mergeOutcomeIntoMarker(existing, outcome) {
10853
10923
  status: "in_progress",
10854
10924
  command: outcome.command,
10855
10925
  pid: outcome.pid ?? null,
10856
- logPath: outcome.logPath,
10926
+ logPath: null,
10857
10927
  attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
10858
10928
  failureMessage: null
10859
10929
  };
@@ -10881,7 +10951,7 @@ function mergeOutcomeIntoMarker(existing, outcome) {
10881
10951
  return existing;
10882
10952
  }
10883
10953
 
10884
- // node_modules/@remixhq/core/dist/chunk-RCNOSZP6.js
10954
+ // node_modules/@remixhq/core/dist/chunk-C2FOZ3O7.js
10885
10955
  async function readJsonSafe(res) {
10886
10956
  const ct = res.headers.get("content-type") ?? "";
10887
10957
  if (!ct.toLowerCase().includes("application/json")) return null;
@@ -10900,7 +10970,7 @@ function createApiClient(config, opts) {
10900
10970
  const ms = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : defaultTimeoutMs;
10901
10971
  return ms != null ? AbortSignal.timeout(ms) : void 0;
10902
10972
  }
10903
- async function request(path17, init, opts2) {
10973
+ async function request(path18, init, opts2) {
10904
10974
  if (!tokenProvider) {
10905
10975
  throw new RemixError("API client is missing a token provider.", {
10906
10976
  exitCode: 1,
@@ -10908,7 +10978,7 @@ function createApiClient(config, opts) {
10908
10978
  });
10909
10979
  }
10910
10980
  const auth = await tokenProvider();
10911
- const url = new URL(path17, config.apiUrl).toString();
10981
+ const url = new URL(path18, config.apiUrl).toString();
10912
10982
  const doFetch = async (bearer) => fetch(url, {
10913
10983
  ...init,
10914
10984
  signal: makeTimeoutSignal(opts2?.timeoutMs),
@@ -10937,7 +11007,7 @@ function createApiClient(config, opts) {
10937
11007
  const json = await readJsonSafe(res);
10938
11008
  return json ?? null;
10939
11009
  }
10940
- async function requestBinary(path17, init, opts2) {
11010
+ async function requestBinary(path18, init, opts2) {
10941
11011
  if (!tokenProvider) {
10942
11012
  throw new RemixError("API client is missing a token provider.", {
10943
11013
  exitCode: 1,
@@ -10945,7 +11015,7 @@ function createApiClient(config, opts) {
10945
11015
  });
10946
11016
  }
10947
11017
  const auth = await tokenProvider();
10948
- const url = new URL(path17, config.apiUrl).toString();
11018
+ const url = new URL(path18, config.apiUrl).toString();
10949
11019
  const doFetch = async (bearer) => fetch(url, {
10950
11020
  ...init,
10951
11021
  signal: makeTimeoutSignal(opts2?.timeoutMs),
@@ -11075,6 +11145,14 @@ function createApiClient(config, opts) {
11075
11145
  method: "POST",
11076
11146
  body: JSON.stringify(payload)
11077
11147
  }),
11148
+ listChangeSteps: (appId, params) => {
11149
+ const qs = new URLSearchParams();
11150
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
11151
+ if (params?.offset !== void 0) qs.set("offset", String(params.offset));
11152
+ if (params?.idempotencyKey) qs.set("idempotencyKey", params.idempotencyKey);
11153
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
11154
+ return request(`/v1/apps/${encodeURIComponent(appId)}/change-steps${suffix}`, { method: "GET" });
11155
+ },
11078
11156
  createCollabTurn: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns`, {
11079
11157
  method: "POST",
11080
11158
  body: JSON.stringify(payload)
@@ -11800,8 +11878,8 @@ function getErrorMap() {
11800
11878
 
11801
11879
  // node_modules/zod/v3/helpers/parseUtil.js
11802
11880
  var makeIssue = (params) => {
11803
- const { data, path: path17, errorMaps, issueData } = params;
11804
- const fullPath = [...path17, ...issueData.path || []];
11881
+ const { data, path: path18, errorMaps, issueData } = params;
11882
+ const fullPath = [...path18, ...issueData.path || []];
11805
11883
  const fullIssue = {
11806
11884
  ...issueData,
11807
11885
  path: fullPath
@@ -11917,11 +11995,11 @@ var errorUtil;
11917
11995
 
11918
11996
  // node_modules/zod/v3/types.js
11919
11997
  var ParseInputLazyPath = class {
11920
- constructor(parent, value, path17, key) {
11998
+ constructor(parent, value, path18, key) {
11921
11999
  this._cachedPath = [];
11922
12000
  this.parent = parent;
11923
12001
  this.data = value;
11924
- this._path = path17;
12002
+ this._path = path18;
11925
12003
  this._key = key;
11926
12004
  }
11927
12005
  get path() {
@@ -24469,8 +24547,8 @@ var IcebergError = class extends Error {
24469
24547
  return this.status === 419;
24470
24548
  }
24471
24549
  };
24472
- function buildUrl(baseUrl, path17, query) {
24473
- const url = new URL(path17, baseUrl);
24550
+ function buildUrl(baseUrl, path18, query) {
24551
+ const url = new URL(path18, baseUrl);
24474
24552
  if (query) {
24475
24553
  for (const [key, value] of Object.entries(query)) {
24476
24554
  if (value !== void 0) {
@@ -24500,12 +24578,12 @@ function createFetchClient(options) {
24500
24578
  return {
24501
24579
  async request({
24502
24580
  method,
24503
- path: path17,
24581
+ path: path18,
24504
24582
  query,
24505
24583
  body,
24506
24584
  headers
24507
24585
  }) {
24508
- const url = buildUrl(options.baseUrl, path17, query);
24586
+ const url = buildUrl(options.baseUrl, path18, query);
24509
24587
  const authHeaders = await buildAuthHeaders(options.auth);
24510
24588
  const res = await fetchFn(url, {
24511
24589
  method,
@@ -25343,7 +25421,7 @@ var StorageFileApi = class extends BaseApiClient {
25343
25421
  * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
25344
25422
  * @param fileBody The body of the file to be stored in the bucket.
25345
25423
  */
25346
- async uploadOrUpdate(method, path17, fileBody, fileOptions) {
25424
+ async uploadOrUpdate(method, path18, fileBody, fileOptions) {
25347
25425
  var _this = this;
25348
25426
  return _this.handleOperation(async () => {
25349
25427
  let body;
@@ -25367,7 +25445,7 @@ var StorageFileApi = class extends BaseApiClient {
25367
25445
  if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
25368
25446
  }
25369
25447
  if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) for (const [key, value] of Object.entries(fileOptions.headers)) headers = setHeader(headers, key, value);
25370
- const cleanPath = _this._removeEmptyFolders(path17);
25448
+ const cleanPath = _this._removeEmptyFolders(path18);
25371
25449
  const _path = _this._getFinalPath(cleanPath);
25372
25450
  const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
25373
25451
  return {
@@ -25428,8 +25506,8 @@ var StorageFileApi = class extends BaseApiClient {
25428
25506
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25429
25507
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
25430
25508
  */
25431
- async upload(path17, fileBody, fileOptions) {
25432
- return this.uploadOrUpdate("POST", path17, fileBody, fileOptions);
25509
+ async upload(path18, fileBody, fileOptions) {
25510
+ return this.uploadOrUpdate("POST", path18, fileBody, fileOptions);
25433
25511
  }
25434
25512
  /**
25435
25513
  * Upload a file with a token generated from `createSignedUploadUrl`.
@@ -25468,9 +25546,9 @@ var StorageFileApi = class extends BaseApiClient {
25468
25546
  * - `objects` table permissions: none
25469
25547
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25470
25548
  */
25471
- async uploadToSignedUrl(path17, token, fileBody, fileOptions) {
25549
+ async uploadToSignedUrl(path18, token, fileBody, fileOptions) {
25472
25550
  var _this3 = this;
25473
- const cleanPath = _this3._removeEmptyFolders(path17);
25551
+ const cleanPath = _this3._removeEmptyFolders(path18);
25474
25552
  const _path = _this3._getFinalPath(cleanPath);
25475
25553
  const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
25476
25554
  url.searchParams.set("token", token);
@@ -25532,10 +25610,10 @@ var StorageFileApi = class extends BaseApiClient {
25532
25610
  * - `objects` table permissions: `insert`
25533
25611
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25534
25612
  */
25535
- async createSignedUploadUrl(path17, options) {
25613
+ async createSignedUploadUrl(path18, options) {
25536
25614
  var _this4 = this;
25537
25615
  return _this4.handleOperation(async () => {
25538
- let _path = _this4._getFinalPath(path17);
25616
+ let _path = _this4._getFinalPath(path18);
25539
25617
  const headers = _objectSpread22({}, _this4.headers);
25540
25618
  if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
25541
25619
  const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
@@ -25544,7 +25622,7 @@ var StorageFileApi = class extends BaseApiClient {
25544
25622
  if (!token) throw new StorageError("No token returned by API");
25545
25623
  return {
25546
25624
  signedUrl: url.toString(),
25547
- path: path17,
25625
+ path: path18,
25548
25626
  token
25549
25627
  };
25550
25628
  });
@@ -25600,8 +25678,8 @@ var StorageFileApi = class extends BaseApiClient {
25600
25678
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25601
25679
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
25602
25680
  */
25603
- async update(path17, fileBody, fileOptions) {
25604
- return this.uploadOrUpdate("PUT", path17, fileBody, fileOptions);
25681
+ async update(path18, fileBody, fileOptions) {
25682
+ return this.uploadOrUpdate("PUT", path18, fileBody, fileOptions);
25605
25683
  }
25606
25684
  /**
25607
25685
  * Moves an existing file to a new path in the same bucket.
@@ -25749,10 +25827,10 @@ var StorageFileApi = class extends BaseApiClient {
25749
25827
  * - `objects` table permissions: `select`
25750
25828
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25751
25829
  */
25752
- async createSignedUrl(path17, expiresIn, options) {
25830
+ async createSignedUrl(path18, expiresIn, options) {
25753
25831
  var _this8 = this;
25754
25832
  return _this8.handleOperation(async () => {
25755
- let _path = _this8._getFinalPath(path17);
25833
+ let _path = _this8._getFinalPath(path18);
25756
25834
  const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
25757
25835
  let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
25758
25836
  const query = new URLSearchParams();
@@ -25886,13 +25964,13 @@ var StorageFileApi = class extends BaseApiClient {
25886
25964
  * - `objects` table permissions: `select`
25887
25965
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
25888
25966
  */
25889
- download(path17, options, parameters) {
25967
+ download(path18, options, parameters) {
25890
25968
  const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0 ? "render/image/authenticated" : "object";
25891
25969
  const query = new URLSearchParams();
25892
25970
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
25893
25971
  if ((options === null || options === void 0 ? void 0 : options.cacheNonce) != null) query.set("cacheNonce", String(options.cacheNonce));
25894
25972
  const queryString = query.toString();
25895
- const _path = this._getFinalPath(path17);
25973
+ const _path = this._getFinalPath(path18);
25896
25974
  const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString ? `?${queryString}` : ""}`, {
25897
25975
  headers: this.headers,
25898
25976
  noResolveJson: true
@@ -25922,9 +26000,9 @@ var StorageFileApi = class extends BaseApiClient {
25922
26000
  * }
25923
26001
  * ```
25924
26002
  */
25925
- async info(path17) {
26003
+ async info(path18) {
25926
26004
  var _this10 = this;
25927
- const _path = _this10._getFinalPath(path17);
26005
+ const _path = _this10._getFinalPath(path18);
25928
26006
  return _this10.handleOperation(async () => {
25929
26007
  return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
25930
26008
  });
@@ -25944,9 +26022,9 @@ var StorageFileApi = class extends BaseApiClient {
25944
26022
  * .exists('folder/avatar1.png')
25945
26023
  * ```
25946
26024
  */
25947
- async exists(path17) {
26025
+ async exists(path18) {
25948
26026
  var _this11 = this;
25949
- const _path = _this11._getFinalPath(path17);
26027
+ const _path = _this11._getFinalPath(path18);
25950
26028
  try {
25951
26029
  await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
25952
26030
  return {
@@ -26024,8 +26102,8 @@ var StorageFileApi = class extends BaseApiClient {
26024
26102
  * - `objects` table permissions: none
26025
26103
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
26026
26104
  */
26027
- getPublicUrl(path17, options) {
26028
- const _path = this._getFinalPath(path17);
26105
+ getPublicUrl(path18, options) {
26106
+ const _path = this._getFinalPath(path18);
26029
26107
  const query = new URLSearchParams();
26030
26108
  if (options === null || options === void 0 ? void 0 : options.download) query.set("download", options.download === true ? "" : options.download);
26031
26109
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
@@ -26162,10 +26240,10 @@ var StorageFileApi = class extends BaseApiClient {
26162
26240
  * - `objects` table permissions: `select`
26163
26241
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
26164
26242
  */
26165
- async list(path17, options, parameters) {
26243
+ async list(path18, options, parameters) {
26166
26244
  var _this13 = this;
26167
26245
  return _this13.handleOperation(async () => {
26168
- const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path17 || "" });
26246
+ const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path18 || "" });
26169
26247
  return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
26170
26248
  });
26171
26249
  }
@@ -26229,11 +26307,11 @@ var StorageFileApi = class extends BaseApiClient {
26229
26307
  if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
26230
26308
  return btoa(data);
26231
26309
  }
26232
- _getFinalPath(path17) {
26233
- return `${this.bucketId}/${path17.replace(/^\/+/, "")}`;
26310
+ _getFinalPath(path18) {
26311
+ return `${this.bucketId}/${path18.replace(/^\/+/, "")}`;
26234
26312
  }
26235
- _removeEmptyFolders(path17) {
26236
- return path17.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
26313
+ _removeEmptyFolders(path18) {
26314
+ return path18.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
26237
26315
  }
26238
26316
  /** Modifies the `query`, appending values the from `transform` */
26239
26317
  applyTransformOptsToQuery(query, transform) {
@@ -35611,11 +35689,11 @@ function isPidAlive(pid) {
35611
35689
  }
35612
35690
  }
35613
35691
  function repoLockFileName(repoRoot) {
35614
- const hash = (0, import_node_crypto3.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 16);
35692
+ const hash = (0, import_node_crypto5.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 16);
35615
35693
  return `.drainer-${hash}.lock`;
35616
35694
  }
35617
35695
  function repoLockPath(repoRoot) {
35618
- return import_node_path11.default.join(getDeferredTurnDirPath(), repoLockFileName(repoRoot));
35696
+ return import_node_path12.default.join(getDeferredTurnDirPath(), repoLockFileName(repoRoot));
35619
35697
  }
35620
35698
  async function readDrainLockMetadata(lockPath) {
35621
35699
  const raw = await import_promises23.default.readFile(lockPath, "utf8").catch(() => null);
@@ -35637,7 +35715,7 @@ async function writeDrainLockMetadata(lockPath, metadata) {
35637
35715
  }
35638
35716
  async function tryAcquireDrainLock(repoRoot) {
35639
35717
  const lockPath = repoLockPath(repoRoot);
35640
- await import_promises23.default.mkdir(import_node_path11.default.dirname(lockPath), { recursive: true });
35718
+ await import_promises23.default.mkdir(import_node_path12.default.dirname(lockPath), { recursive: true });
35641
35719
  const existingMeta = await readDrainLockMetadata(lockPath);
35642
35720
  if (existingMeta) {
35643
35721
  const lockStat = await import_promises23.default.stat(lockPath).catch(() => null);
@@ -36034,7 +36112,7 @@ function spawnDeferredTurnDrainer(repoRoot) {
36034
36112
 
36035
36113
  // src/hook-utils.ts
36036
36114
  var import_promises24 = __toESM(require("fs/promises"), 1);
36037
- var import_node_path12 = __toESM(require("path"), 1);
36115
+ var import_node_path13 = __toESM(require("path"), 1);
36038
36116
  async function readJsonStdin() {
36039
36117
  const chunks = [];
36040
36118
  for await (const chunk of process.stdin) {
@@ -36060,16 +36138,16 @@ function extractString(input, keys) {
36060
36138
  }
36061
36139
  async function findBoundRepo(startPath) {
36062
36140
  if (!startPath) return null;
36063
- let current = import_node_path12.default.resolve(startPath);
36141
+ let current = import_node_path13.default.resolve(startPath);
36064
36142
  let stats = await import_promises24.default.stat(current).catch(() => null);
36065
36143
  if (stats?.isFile()) {
36066
- current = import_node_path12.default.dirname(current);
36144
+ current = import_node_path13.default.dirname(current);
36067
36145
  }
36068
36146
  while (true) {
36069
- const bindingPath = import_node_path12.default.join(current, ".remix", "config.json");
36147
+ const bindingPath = import_node_path13.default.join(current, ".remix", "config.json");
36070
36148
  const bindingStats = await import_promises24.default.stat(bindingPath).catch(() => null);
36071
36149
  if (bindingStats?.isFile()) return current;
36072
- const parent = import_node_path12.default.dirname(current);
36150
+ const parent = import_node_path13.default.dirname(current);
36073
36151
  if (parent === current) return null;
36074
36152
  current = parent;
36075
36153
  }
@@ -36093,8 +36171,6 @@ function buildRuntimeStatusOverride() {
36093
36171
  "Use `remix_collab_drain_finalize_queue` only for explicit recovery flows, such as status reporting `await_finalize` before a merge-related operation."
36094
36172
  ].join("\n");
36095
36173
  }
36096
- var COLLAB_INIT_LOG_REL = import_node_path13.default.join(".remix", "collab-init.log");
36097
- var COLLAB_INIT_SPAWN_LOCK_REL = import_node_path13.default.join(".remix", ".collab-init-spawning");
36098
36174
  var COLLAB_INIT_SPAWN_LOCK_STALE_MS = 90 * 1e3;
36099
36175
  function isPidAlive2(pid) {
36100
36176
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -36105,31 +36181,42 @@ function isPidAlive2(pid) {
36105
36181
  return false;
36106
36182
  }
36107
36183
  }
36108
- function readLockPid(spawnLockPath) {
36184
+ function readSpawnLock(spawnLockPath2) {
36109
36185
  try {
36110
- const raw = (0, import_node_fs7.readFileSync)(spawnLockPath, "utf8").trim();
36186
+ const raw = (0, import_node_fs7.readFileSync)(spawnLockPath2, "utf8").trim();
36111
36187
  if (!raw) return null;
36188
+ if (raw.startsWith("{")) {
36189
+ const parsed = JSON.parse(raw);
36190
+ const pid2 = Number(parsed?.pid ?? 0);
36191
+ return {
36192
+ pid: Number.isFinite(pid2) && pid2 > 0 ? pid2 : null,
36193
+ branchName: typeof parsed?.branchName === "string" && parsed.branchName.trim() ? parsed.branchName : null
36194
+ };
36195
+ }
36112
36196
  const pid = Number.parseInt(raw, 10);
36113
- return Number.isFinite(pid) && pid > 0 ? pid : null;
36197
+ return {
36198
+ pid: Number.isFinite(pid) && pid > 0 ? pid : null,
36199
+ branchName: null
36200
+ };
36114
36201
  } catch {
36115
36202
  return null;
36116
36203
  }
36117
36204
  }
36118
- function maybeAutoSpawnBranchInit(repoRoot) {
36119
- const remixDir = import_node_path13.default.join(repoRoot, ".remix");
36120
- const spawnLockPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_SPAWN_LOCK_REL);
36121
- const logPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_LOG_REL);
36205
+ function maybeAutoSpawnBranchInit(repoRoot, branchName) {
36206
+ const spawnLockPath2 = collabInitSpawnLockPath(repoRoot);
36122
36207
  try {
36123
- if ((0, import_node_fs7.existsSync)(spawnLockPath)) {
36124
- const lockPid = readLockPid(spawnLockPath);
36208
+ if ((0, import_node_fs7.existsSync)(spawnLockPath2)) {
36209
+ const lock = readSpawnLock(spawnLockPath2);
36210
+ const lockPid = lock?.pid ?? null;
36125
36211
  const lockAlive = lockPid !== null && isPidAlive2(lockPid);
36126
- const ageMs = Date.now() - (0, import_node_fs7.statSync)(spawnLockPath).mtimeMs;
36127
- if (lockAlive && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
36212
+ const sameBranch = !lock?.branchName || !branchName || lock.branchName === branchName;
36213
+ const ageMs = Date.now() - (0, import_node_fs7.statSync)(spawnLockPath2).mtimeMs;
36214
+ if (lockAlive && sameBranch && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
36128
36215
  return { spawned: false, reason: "spawn_lock_held" };
36129
36216
  }
36130
36217
  if (!lockAlive) {
36131
36218
  try {
36132
- (0, import_node_fs7.unlinkSync)(spawnLockPath);
36219
+ (0, import_node_fs7.unlinkSync)(spawnLockPath2);
36133
36220
  } catch {
36134
36221
  }
36135
36222
  }
@@ -36137,35 +36224,32 @@ function maybeAutoSpawnBranchInit(repoRoot) {
36137
36224
  } catch {
36138
36225
  }
36139
36226
  try {
36140
- (0, import_node_fs7.mkdirSync)(remixDir, { recursive: true });
36227
+ (0, import_node_fs7.mkdirSync)(import_node_path14.default.dirname(spawnLockPath2), { recursive: true });
36141
36228
  } catch {
36142
36229
  }
36143
- let out;
36144
- let err;
36145
- try {
36146
- out = (0, import_node_fs7.openSync)(logPath, "a");
36147
- err = (0, import_node_fs7.openSync)(logPath, "a");
36148
- } catch (logErr) {
36149
- return {
36150
- spawned: false,
36151
- reason: "log_open_failed",
36152
- message: logErr instanceof Error ? logErr.message : String(logErr)
36153
- };
36154
- }
36155
36230
  try {
36156
36231
  const child = (0, import_node_child_process8.spawn)("remix", ["collab", "init"], {
36157
36232
  cwd: repoRoot,
36158
36233
  detached: true,
36159
- stdio: ["ignore", out, err],
36234
+ stdio: "ignore",
36160
36235
  env: { ...process.env, REMIX_COLLAB_INIT_AUTO_SPAWN: "1" }
36161
36236
  });
36162
36237
  child.unref();
36163
36238
  try {
36164
- (0, import_node_fs7.writeFileSync)(spawnLockPath, String(child.pid ?? ""), "utf8");
36165
- (0, import_node_fs7.utimesSync)(spawnLockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
36239
+ const lock = {
36240
+ schemaVersion: 1,
36241
+ pid: child.pid ?? null,
36242
+ branchName,
36243
+ repoRoot,
36244
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
36245
+ command: "remix collab init"
36246
+ };
36247
+ (0, import_node_fs7.writeFileSync)(spawnLockPath2, `${JSON.stringify(lock, null, 2)}
36248
+ `, "utf8");
36249
+ (0, import_node_fs7.utimesSync)(spawnLockPath2, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
36166
36250
  } catch {
36167
36251
  }
36168
- return { spawned: true, pid: child.pid, logPath };
36252
+ return { spawned: true, pid: child.pid };
36169
36253
  } catch (spawnErr) {
36170
36254
  return {
36171
36255
  spawned: false,
@@ -36174,14 +36258,13 @@ function maybeAutoSpawnBranchInit(repoRoot) {
36174
36258
  };
36175
36259
  }
36176
36260
  }
36177
- function buildBranchInitContextMessage(branch, repoRoot, logPath) {
36261
+ function buildBranchInitContextMessage(branch, repoRoot) {
36178
36262
  const branchLabel = branch ? `\`${branch}\`` : "the current branch";
36179
36263
  return [
36180
36264
  "[Remix recovery in progress]:",
36181
36265
  `Remix is initializing recording for ${branchLabel} in ${repoRoot} in the background.`,
36182
36266
  "This turn may be recorded retroactively once init finishes (it may appear in the timeline with a small delay).",
36183
- "Do NOT call any Remix MCP tool to initialize, repair, or sync this branch \u2014 the plugin is handling it automatically.",
36184
- `Init log: ${logPath}`
36267
+ "Do NOT call any Remix MCP tool to initialize, repair, or sync this branch \u2014 the plugin is handling it automatically."
36185
36268
  ].join("\n");
36186
36269
  }
36187
36270
  var STALE_MARKER_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
@@ -36380,9 +36463,9 @@ async function runHookUserPrompt(payload) {
36380
36463
  }
36381
36464
  if (isCurrentBranchUnbound) {
36382
36465
  const currentBranch = bindingState?.currentBranch ?? null;
36383
- const outcome = maybeAutoSpawnBranchInit(boundRepo);
36466
+ const outcome = maybeAutoSpawnBranchInit(boundRepo, currentBranch);
36384
36467
  if (outcome.spawned) {
36385
- advisorySections.push(buildBranchInitContextMessage(currentBranch, boundRepo, outcome.logPath));
36468
+ advisorySections.push(buildBranchInitContextMessage(currentBranch, boundRepo));
36386
36469
  await appendHookDiagnosticsEvent({
36387
36470
  hook: "UserPromptSubmit",
36388
36471
  sessionId,
@@ -36393,8 +36476,7 @@ async function runHookUserPrompt(payload) {
36393
36476
  fields: {
36394
36477
  currentBranch,
36395
36478
  knownBoundBranchCount: knownBoundBranches.length,
36396
- pid: outcome.pid ?? null,
36397
- logPath: outcome.logPath
36479
+ pid: outcome.pid ?? null
36398
36480
  }
36399
36481
  });
36400
36482
  } else {