@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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@remixhq/claude-plugin",
6
- version: "0.1.23",
6
+ version: "0.1.25",
7
7
  description: "Claude Code plugin for Remix collaboration workflows",
8
8
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
9
9
  license: "MIT",
@@ -41,8 +41,8 @@ var package_default = {
41
41
  prepack: "npm run build"
42
42
  },
43
43
  dependencies: {
44
- "@remixhq/core": "^0.1.18",
45
- "@remixhq/mcp": "^0.1.18"
44
+ "@remixhq/core": "^0.1.20",
45
+ "@remixhq/mcp": "^0.1.20"
46
46
  },
47
47
  devDependencies: {
48
48
  "@types/node": "^25.4.0",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/metadata.ts"],"sourcesContent":["{\n \"name\": \"@remixhq/claude-plugin\",\n \"version\": \"0.1.23\",\n \"description\": \"Claude Code plugin for Remix collaboration workflows\",\n \"homepage\": \"https://github.com/RemixDotOne/remix-claude-plugin\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/RemixDotOne/remix-claude-plugin.git\"\n },\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"files\": [\n \"dist\",\n \".claude-plugin/plugin.json\",\n \".mcp.json\",\n \"skills\",\n \"hooks\",\n \"agents\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"postbuild\": \"node -e \\\"const fs=require('node:fs'); for (const p of ['dist/mcp-server.cjs','dist/hook-pre-git.cjs','dist/hook-user-prompt.cjs','dist/hook-post-collab.cjs','dist/hook-stop-collab.cjs']) fs.chmodSync(p, 0o755);\\\"\",\n \"dev\": \"tsx src/mcp-server.ts\",\n \"typecheck\": \"tsc -p tsconfig.json --noEmit\",\n \"test\": \"node --import tsx --test 'src/**/*.test.ts'\",\n \"prepack\": \"npm run build\"\n },\n \"dependencies\": {\n \"@remixhq/core\": \"^0.1.18\",\n \"@remixhq/mcp\": \"^0.1.18\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.4.0\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\"\n }\n}\n","import pkg from \"../package.json\";\n\nexport const pluginMetadata = {\n name: pkg.name,\n version: pkg.version,\n description: pkg.description,\n pluginId: \"remix\",\n agentName: \"remix-collab\",\n};\n"],"mappings":";;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AAAA,EACA,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,KAAO;AAAA,IACP,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,EAClB;AAAA,EACA,iBAAmB;AAAA,IACjB,eAAe;AAAA,IACf,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,EAChB;AACF;;;AC/CO,IAAM,iBAAiB;AAAA,EAC5B,MAAM,gBAAI;AAAA,EACV,SAAS,gBAAI;AAAA,EACb,aAAa,gBAAI;AAAA,EACjB,UAAU;AAAA,EACV,WAAW;AACb;","names":[]}
1
+ {"version":3,"sources":["../package.json","../src/metadata.ts"],"sourcesContent":["{\n \"name\": \"@remixhq/claude-plugin\",\n \"version\": \"0.1.25\",\n \"description\": \"Claude Code plugin for Remix collaboration workflows\",\n \"homepage\": \"https://github.com/RemixDotOne/remix-claude-plugin\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/RemixDotOne/remix-claude-plugin.git\"\n },\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"files\": [\n \"dist\",\n \".claude-plugin/plugin.json\",\n \".mcp.json\",\n \"skills\",\n \"hooks\",\n \"agents\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"postbuild\": \"node -e \\\"const fs=require('node:fs'); for (const p of ['dist/mcp-server.cjs','dist/hook-pre-git.cjs','dist/hook-user-prompt.cjs','dist/hook-post-collab.cjs','dist/hook-stop-collab.cjs']) fs.chmodSync(p, 0o755);\\\"\",\n \"dev\": \"tsx src/mcp-server.ts\",\n \"typecheck\": \"tsc -p tsconfig.json --noEmit\",\n \"test\": \"node --import tsx --test 'src/**/*.test.ts'\",\n \"prepack\": \"npm run build\"\n },\n \"dependencies\": {\n \"@remixhq/core\": \"^0.1.20\",\n \"@remixhq/mcp\": \"^0.1.20\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.4.0\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\"\n }\n}\n","import pkg from \"../package.json\";\n\nexport const pluginMetadata = {\n name: pkg.name,\n version: pkg.version,\n description: pkg.description,\n pluginId: \"remix\",\n agentName: \"remix-collab\",\n};\n"],"mappings":";;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AAAA,EACA,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,KAAO;AAAA,IACP,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,EAClB;AAAA,EACA,iBAAmB;AAAA,IACjB,eAAe;AAAA,IACf,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,EAChB;AACF;;;AC/CO,IAAM,iBAAiB;AAAA,EAC5B,MAAM,gBAAI;AAAA,EACV,SAAS,gBAAI;AAAA,EACb,aAAa,gBAAI;AAAA,EACjB,UAAU;AAAA,EACV,WAAW;AACb;","names":[]}
@@ -22359,6 +22359,15 @@ function shouldRequireRemoteLaneForCurrentBranch(params) {
22359
22359
  if (params.currentBranch === defaultBranch) return false;
22360
22360
  return !params.binding.laneId || params.binding.currentAppId === params.binding.upstreamAppId;
22361
22361
  }
22362
+ function resolveLaneLookupProjectId(params) {
22363
+ const currentBranch = normalizeBranchName2(params.currentBranch);
22364
+ const defaultBranch = normalizeBranchName2(params.defaultBranch);
22365
+ const localProjectId = params.localBinding.projectId ?? null;
22366
+ if (currentBranch && currentBranch !== defaultBranch && localProjectId) {
22367
+ return localProjectId;
22368
+ }
22369
+ return params.explicitRootProjectId ?? (params.requireRemoteLane ? void 0 : localProjectId ?? params.fallbackProjectId ?? void 0);
22370
+ }
22362
22371
  async function persistResolvedLane(repoRoot, binding) {
22363
22372
  await writeCollabBinding(repoRoot, {
22364
22373
  projectId: binding.projectId,
@@ -22437,7 +22446,14 @@ async function resolveActiveLaneBindingUncached(params, state) {
22437
22446
  };
22438
22447
  }
22439
22448
  const laneResp2 = await params.api.resolveProjectLaneBinding({
22440
- projectId: state.explicitRootBinding?.projectId ?? (requireRemoteLane ? void 0 : localBinding.projectId ?? state.projectId ?? void 0),
22449
+ projectId: resolveLaneLookupProjectId({
22450
+ explicitRootProjectId: state.explicitRootBinding?.projectId,
22451
+ localBinding,
22452
+ currentBranch,
22453
+ defaultBranch: state.defaultBranch,
22454
+ requireRemoteLane,
22455
+ fallbackProjectId: state.projectId
22456
+ }),
22441
22457
  repoFingerprint: state.repoFingerprint ?? void 0,
22442
22458
  remoteUrl: state.remoteUrl ?? void 0,
22443
22459
  defaultBranch: state.defaultBranch ?? void 0,
@@ -23288,6 +23304,59 @@ function buildWorkspaceMetadata(params) {
23288
23304
  }
23289
23305
  return metadata;
23290
23306
  }
23307
+ async function findExistingChangeStepByIdempotency(params) {
23308
+ const idempotencyKey = params.idempotencyKey?.trim();
23309
+ if (!idempotencyKey) return null;
23310
+ const resp = await params.api.listChangeSteps(params.appId, { limit: 1, idempotencyKey });
23311
+ const responseObject = unwrapResponseObject(
23312
+ resp,
23313
+ "change step list"
23314
+ );
23315
+ const steps = Array.isArray(responseObject) ? responseObject : Array.isArray(responseObject.items) ? responseObject.items : [];
23316
+ return steps.find((step) => step.idempotencyKey === idempotencyKey) ?? null;
23317
+ }
23318
+ async function writeBaselineFromSucceededChangeStep(params) {
23319
+ const nextServerHeadHash = typeof params.changeStep.headCommitHash === "string" ? params.changeStep.headCommitHash.trim() : "";
23320
+ if (!nextServerHeadHash) {
23321
+ throw buildFinalizeCliError({
23322
+ message: "Backend returned a succeeded change step without a head commit hash.",
23323
+ exitCode: 1,
23324
+ hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
23325
+ disposition: "terminal",
23326
+ reason: "missing_head_commit_hash"
23327
+ });
23328
+ }
23329
+ let nextServerRevisionId = typeof params.changeStep.resultRevisionId === "string" ? params.changeStep.resultRevisionId.trim() : "";
23330
+ let nextServerTreeHash = null;
23331
+ if (!nextServerRevisionId) {
23332
+ const freshHeadResp = await params.api.getAppHead(params.job.currentAppId);
23333
+ const freshHead = unwrapResponseObject(freshHeadResp, "app head");
23334
+ if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
23335
+ throw buildFinalizeCliError({
23336
+ message: "Backend returned a succeeded change step without a matching result revision.",
23337
+ exitCode: 1,
23338
+ 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`.",
23339
+ disposition: "terminal",
23340
+ reason: "missing_result_revision_id"
23341
+ });
23342
+ }
23343
+ nextServerRevisionId = freshHead.headRevisionId;
23344
+ nextServerTreeHash = freshHead.treeHash ?? null;
23345
+ }
23346
+ await writeLocalBaseline({
23347
+ repoRoot: params.job.repoRoot,
23348
+ repoFingerprint: params.job.repoFingerprint,
23349
+ laneId: params.job.laneId,
23350
+ currentAppId: params.job.currentAppId,
23351
+ branchName: params.job.branchName,
23352
+ lastSnapshotId: params.snapshot.id,
23353
+ lastSnapshotHash: params.snapshot.snapshotHash,
23354
+ lastServerRevisionId: nextServerRevisionId,
23355
+ lastServerTreeHash: nextServerTreeHash,
23356
+ lastServerHeadHash: nextServerHeadHash,
23357
+ lastSeenLocalCommitHash: params.snapshot.localCommitHash
23358
+ });
23359
+ }
23291
23360
  async function harvestPreTurnEvents(repoRoot, fromCommit, toCommit) {
23292
23361
  if (!toCommit) return null;
23293
23362
  try {
@@ -23473,6 +23542,34 @@ async function processClaimedPendingFinalizeJobInner(params) {
23473
23542
  });
23474
23543
  }
23475
23544
  const replayNeeded = appHead.headCommitHash !== submissionBaseHeadHash || baselineDrifted;
23545
+ if (replayNeeded) {
23546
+ const existingChangeStep = await findExistingChangeStepByIdempotency({
23547
+ api: params.api,
23548
+ appId: job.currentAppId,
23549
+ idempotencyKey: job.idempotencyKey
23550
+ });
23551
+ if (existingChangeStep) {
23552
+ const changeStep2 = existingChangeStep.status === "succeeded" ? existingChangeStep : await pollChangeStep(params.api, job.currentAppId, existingChangeStep.id);
23553
+ invalidateAppHeadCache(job.currentAppId);
23554
+ invalidateAppDeltaCacheForApp(job.currentAppId);
23555
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep: changeStep2 });
23556
+ await updatePendingFinalizeJob(job.id, {
23557
+ status: "completed",
23558
+ metadata: { changeStepId: String(changeStep2.id ?? "") }
23559
+ });
23560
+ return {
23561
+ mode: "changed_turn",
23562
+ idempotencyKey: job.idempotencyKey ?? "",
23563
+ queued: false,
23564
+ jobId: job.id,
23565
+ repoState,
23566
+ changeStep: changeStep2,
23567
+ collabTurn: null,
23568
+ autoSync: null,
23569
+ warnings: []
23570
+ };
23571
+ }
23572
+ }
23476
23573
  if (replayNeeded) {
23477
23574
  try {
23478
23575
  const replayResp = await params.api.startChangeStepReplay(job.currentAppId, {
@@ -23570,46 +23667,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
23570
23667
  const changeStep = await pollChangeStep(params.api, job.currentAppId, String(createdStep.id));
23571
23668
  invalidateAppHeadCache(job.currentAppId);
23572
23669
  invalidateAppDeltaCacheForApp(job.currentAppId);
23573
- const nextServerHeadHash = typeof changeStep.headCommitHash === "string" ? changeStep.headCommitHash.trim() : "";
23574
- if (!nextServerHeadHash) {
23575
- throw buildFinalizeCliError({
23576
- message: "Backend returned a succeeded change step without a head commit hash.",
23577
- exitCode: 1,
23578
- hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
23579
- disposition: "terminal",
23580
- reason: "missing_head_commit_hash"
23581
- });
23582
- }
23583
- let nextServerRevisionId = typeof changeStep.resultRevisionId === "string" ? changeStep.resultRevisionId.trim() : "";
23584
- let nextServerTreeHash = null;
23585
- if (!nextServerRevisionId) {
23586
- const freshHeadResp = await params.api.getAppHead(job.currentAppId);
23587
- const freshHead = unwrapResponseObject(freshHeadResp, "app head");
23588
- if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
23589
- throw buildFinalizeCliError({
23590
- message: "Backend returned a succeeded change step without a matching result revision.",
23591
- exitCode: 1,
23592
- 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`.",
23593
- disposition: "terminal",
23594
- reason: "missing_result_revision_id"
23595
- });
23596
- }
23597
- nextServerRevisionId = freshHead.headRevisionId;
23598
- nextServerTreeHash = freshHead.treeHash ?? null;
23599
- }
23600
- await writeLocalBaseline({
23601
- repoRoot: job.repoRoot,
23602
- repoFingerprint: job.repoFingerprint,
23603
- laneId: job.laneId,
23604
- currentAppId: job.currentAppId,
23605
- branchName: job.branchName,
23606
- lastSnapshotId: snapshot.id,
23607
- lastSnapshotHash: snapshot.snapshotHash,
23608
- lastServerRevisionId: nextServerRevisionId,
23609
- lastServerTreeHash: nextServerTreeHash,
23610
- lastServerHeadHash: nextServerHeadHash,
23611
- lastSeenLocalCommitHash: snapshot.localCommitHash
23612
- });
23670
+ await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep });
23613
23671
  await updatePendingFinalizeJob(job.id, {
23614
23672
  status: "completed",
23615
23673
  metadata: { changeStepId: String(changeStep.id ?? "") }
@@ -24751,27 +24809,21 @@ async function trySeedEquivalentBranchBaseline(params) {
24751
24809
  branchName: params.branchName,
24752
24810
  persistBlobs: false
24753
24811
  });
24754
- if (inspection.snapshotHash !== defaultBaseline.lastSnapshotHash) {
24812
+ if (inspection.snapshotHash !== defaultBaseline.lastSnapshotHash && inspection.localCommitHash !== defaultBaseline.lastSeenLocalCommitHash) {
24755
24813
  return null;
24756
24814
  }
24757
- const snapshot = await captureLocalSnapshot({
24758
- repoRoot: params.repoRoot,
24759
- repoFingerprint: params.repoFingerprint,
24760
- laneId: params.laneId,
24761
- branchName: params.branchName
24762
- });
24763
24815
  await writeLocalBaseline({
24764
24816
  repoRoot: params.repoRoot,
24765
24817
  repoFingerprint: params.repoFingerprint,
24766
24818
  laneId: params.laneId,
24767
24819
  currentAppId: params.currentAppId,
24768
24820
  branchName: params.branchName,
24769
- lastSnapshotId: snapshot.id,
24770
- lastSnapshotHash: snapshot.snapshotHash,
24821
+ lastSnapshotId: defaultBaseline.lastSnapshotId,
24822
+ lastSnapshotHash: defaultBaseline.lastSnapshotHash,
24771
24823
  lastServerRevisionId: params.appHeadRevisionId,
24772
24824
  lastServerTreeHash: params.appTreeHash,
24773
24825
  lastServerHeadHash: params.appHeadHash,
24774
- lastSeenLocalCommitHash: snapshot.localCommitHash
24826
+ lastSeenLocalCommitHash: inspection.localCommitHash
24775
24827
  });
24776
24828
  return "seeded";
24777
24829
  }
@@ -24820,6 +24872,21 @@ async function resolveInitBaselineStatus(params) {
24820
24872
  });
24821
24873
  const delta = unwrapResponseObject(deltaResp, "app delta");
24822
24874
  if (delta.status === "up_to_date" || delta.status === "delta_ready") {
24875
+ const equivalentBaseline = await trySeedEquivalentBranchBaseline({
24876
+ repoRoot: params.repoRoot,
24877
+ repoFingerprint: params.repoFingerprint,
24878
+ laneId: params.laneId,
24879
+ currentAppId: params.currentAppId,
24880
+ upstreamAppId: params.upstreamAppId ?? null,
24881
+ branchName: params.branchName,
24882
+ defaultBranch: params.defaultBranch,
24883
+ appHeadHash: appHead.headCommitHash,
24884
+ appHeadRevisionId: appHead.headRevisionId ?? null,
24885
+ appTreeHash: appHead.treeHash ?? null
24886
+ });
24887
+ if (equivalentBaseline) {
24888
+ return equivalentBaseline;
24889
+ }
24823
24890
  return "requires_sync";
24824
24891
  }
24825
24892
  if (delta.status === "content_equivalent") {
@@ -24936,6 +25003,7 @@ async function collabInit(params) {
24936
25003
  upstreamAppId: existingBinding.upstreamAppId ?? existingBinding.currentAppId,
24937
25004
  dashboardUrl: buildDashboardAppUrl(existingBinding.currentAppId),
24938
25005
  bindingPath: getCollabBindingPath(repoRoot),
25006
+ repoFingerprint: existingBinding.repoFingerprint ?? repoFingerprint,
24939
25007
  bindingMode: existingBinding.bindingMode,
24940
25008
  createdCanonicalFamily: false,
24941
25009
  remoteUrl: existingBinding.remoteUrl ?? remoteUrl,
@@ -24952,6 +25020,7 @@ async function collabInit(params) {
24952
25020
  upstreamAppId: "",
24953
25021
  dashboardUrl: "",
24954
25022
  bindingPath: getCollabBindingPath(repoRoot),
25023
+ repoFingerprint,
24955
25024
  bindingMode: "lane",
24956
25025
  createdCanonicalFamily: false,
24957
25026
  remoteUrl,
@@ -25052,6 +25121,7 @@ async function collabInit(params) {
25052
25121
  upstreamAppId: boundUpstreamAppId2,
25053
25122
  bindingPath: import_path11.default.join(repoRoot, ".remix", "config.json"),
25054
25123
  repoRoot,
25124
+ repoFingerprint,
25055
25125
  bindingMode: defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root",
25056
25126
  createdCanonicalFamily: false,
25057
25127
  baselineStatus: await resolveInitBaselineStatus({
@@ -25164,6 +25234,7 @@ async function collabInit(params) {
25164
25234
  upstreamAppId: boundUpstreamAppId2,
25165
25235
  bindingPath: bindingPath2,
25166
25236
  repoRoot,
25237
+ repoFingerprint,
25167
25238
  bindingMode: "lane",
25168
25239
  createdCanonicalFamily: false,
25169
25240
  baselineStatus: await resolveInitBaselineStatus({
@@ -25294,6 +25365,7 @@ async function collabInit(params) {
25294
25365
  upstreamAppId: boundUpstreamAppId2,
25295
25366
  bindingPath: bindingPath2,
25296
25367
  repoRoot,
25368
+ repoFingerprint,
25297
25369
  bindingMode: "lane",
25298
25370
  createdCanonicalFamily: false,
25299
25371
  baselineStatus: await resolveInitBaselineStatus({
@@ -25514,6 +25586,7 @@ async function collabInit(params) {
25514
25586
  upstreamAppId: boundUpstreamAppId2,
25515
25587
  dashboardUrl: buildDashboardAppUrl(boundCurrentAppId2),
25516
25588
  bindingPath: bindingPath2,
25589
+ repoFingerprint,
25517
25590
  bindingMode: bindingMode2,
25518
25591
  createdCanonicalFamily: Boolean(params.forceNew),
25519
25592
  remoteUrl,
@@ -25696,6 +25769,7 @@ async function collabInit(params) {
25696
25769
  upstreamAppId: boundUpstreamAppId,
25697
25770
  bindingPath,
25698
25771
  repoRoot,
25772
+ repoFingerprint,
25699
25773
  bindingMode,
25700
25774
  createdCanonicalFamily: Boolean(params.forceNew),
25701
25775
  baselineStatus,
@@ -35127,7 +35201,7 @@ var EMPTY_COMPLETION_RESULT = {
35127
35201
  }
35128
35202
  };
35129
35203
 
35130
- // node_modules/@remixhq/core/dist/chunk-RCNOSZP6.js
35204
+ // node_modules/@remixhq/core/dist/chunk-C2FOZ3O7.js
35131
35205
  async function readJsonSafe(res) {
35132
35206
  const ct = res.headers.get("content-type") ?? "";
35133
35207
  if (!ct.toLowerCase().includes("application/json")) return null;
@@ -35321,6 +35395,14 @@ function createApiClient(config2, opts) {
35321
35395
  method: "POST",
35322
35396
  body: JSON.stringify(payload)
35323
35397
  }),
35398
+ listChangeSteps: (appId, params) => {
35399
+ const qs = new URLSearchParams();
35400
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
35401
+ if (params?.offset !== void 0) qs.set("offset", String(params.offset));
35402
+ if (params?.idempotencyKey) qs.set("idempotencyKey", params.idempotencyKey);
35403
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
35404
+ return request(`/v1/apps/${encodeURIComponent(appId)}/change-steps${suffix}`, { method: "GET" });
35405
+ },
35324
35406
  createCollabTurn: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns`, {
35325
35407
  method: "POST",
35326
35408
  body: JSON.stringify(payload)
@@ -55767,7 +55849,9 @@ async function resolveConfig(_opts) {
55767
55849
  // node_modules/@remixhq/mcp/dist/index.js
55768
55850
  var import_path15 = __toESM(require("path"), 1);
55769
55851
  var import_child_process = require("child_process");
55852
+ var import_crypto9 = require("crypto");
55770
55853
  var import_fs3 = require("fs");
55854
+ var import_os8 = __toESM(require("os"), 1);
55771
55855
  var import_path16 = __toESM(require("path"), 1);
55772
55856
  async function createRemixTokenProvider(config2) {
55773
55857
  const resolvedConfig = config2 ?? await resolveConfig();
@@ -55997,6 +56081,15 @@ function normalizeByMessage(err) {
55997
56081
  category: "remote_state"
55998
56082
  });
55999
56083
  }
56084
+ if (statusCode === 409) {
56085
+ return makeNormalized({
56086
+ code: ERROR_CODES.REMOTE_ERROR,
56087
+ message,
56088
+ hint,
56089
+ retryable: true,
56090
+ category: "remote_state"
56091
+ });
56092
+ }
56000
56093
  if (message.includes("Timed out") || message.includes("failed") || message.includes("error state")) {
56001
56094
  return makeNormalized({
56002
56095
  code: ERROR_CODES.REMOTE_ERROR,
@@ -56160,7 +56253,6 @@ function makeSuccessResult2(envelope) {
56160
56253
  function makeErrorResult(envelope) {
56161
56254
  return {
56162
56255
  content: [{ type: "text", text: toJsonText(envelope) }],
56163
- structuredContent: envelope,
56164
56256
  isError: true
56165
56257
  };
56166
56258
  }
@@ -57411,11 +57503,30 @@ async function accessDebug(params) {
57411
57503
  }
57412
57504
  };
57413
57505
  }
57414
- var MARKER_REL_PATH = import_path16.default.join(".remix", ".history-imported");
57415
- var LOG_REL_PATH = import_path16.default.join(".remix", "history-import.log");
57416
- function shouldAutoSpawnHistoryImport(repoRoot) {
57506
+ var LEGACY_MARKER_REL_PATH = import_path16.default.join(".remix", ".history-imported");
57507
+ function collabStateRoot() {
57508
+ const configured = process.env.REMIX_COLLAB_STATE_ROOT?.trim();
57509
+ return configured || import_path16.default.join(import_os8.default.homedir(), ".remix", "collab-state");
57510
+ }
57511
+ function sha256Hex3(value) {
57512
+ return (0, import_crypto9.createHash)("sha256").update(value).digest("hex");
57513
+ }
57514
+ function autoSpawnMarkerPath(repoRoot, repoFingerprint) {
57515
+ const identityKey = typeof repoFingerprint === "string" && repoFingerprint.trim() ? `repoFingerprint:${repoFingerprint.trim()}` : `repoRoot:${repoRoot}`;
57516
+ return import_path16.default.join(collabStateRoot(), "history-import", sha256Hex3(identityKey), "history-imported.json");
57517
+ }
57518
+ function fallbackRepoRootMarkerPath(repoRoot) {
57519
+ return autoSpawnMarkerPath(repoRoot, null);
57520
+ }
57521
+ function legacyLocalRepoRootMarkerPath(repoRoot) {
57522
+ return import_path16.default.join(collabStateRoot(), "history-import", sha256Hex3(repoRoot), "history-imported.json");
57523
+ }
57524
+ function legacyAutoSpawnMarkerPath(repoRoot) {
57525
+ return import_path16.default.join(repoRoot, LEGACY_MARKER_REL_PATH);
57526
+ }
57527
+ function shouldAutoSpawnHistoryImport(repoRoot, repoFingerprint) {
57417
57528
  try {
57418
- return !(0, import_fs3.existsSync)(import_path16.default.join(repoRoot, MARKER_REL_PATH));
57529
+ return !(0, import_fs3.existsSync)(autoSpawnMarkerPath(repoRoot, repoFingerprint)) && !(0, import_fs3.existsSync)(fallbackRepoRootMarkerPath(repoRoot)) && !(0, import_fs3.existsSync)(legacyLocalRepoRootMarkerPath(repoRoot)) && !(0, import_fs3.existsSync)(legacyAutoSpawnMarkerPath(repoRoot));
57419
57530
  } catch {
57420
57531
  return false;
57421
57532
  }
@@ -57424,14 +57535,6 @@ function isAutoSpawnEligibleBindingMode(bindingMode) {
57424
57535
  return bindingMode === "explicit_root";
57425
57536
  }
57426
57537
  function spawnHistoryImportDetached(repoRoot, options) {
57427
- const remixDir = import_path16.default.join(repoRoot, ".remix");
57428
- try {
57429
- (0, import_fs3.mkdirSync)(remixDir, { recursive: true });
57430
- } catch {
57431
- }
57432
- const logPath = import_path16.default.join(repoRoot, LOG_REL_PATH);
57433
- const out = (0, import_fs3.openSync)(logPath, "a");
57434
- const err = (0, import_fs3.openSync)(logPath, "a");
57435
57538
  const child = (0, import_child_process.spawn)(
57436
57539
  "remix",
57437
57540
  [
@@ -57448,12 +57551,12 @@ function spawnHistoryImportDetached(repoRoot, options) {
57448
57551
  ],
57449
57552
  {
57450
57553
  detached: true,
57451
- stdio: ["ignore", out, err],
57554
+ stdio: "ignore",
57452
57555
  env: { ...process.env, REMIX_HISTORY_AUTO_SPAWN: "1" }
57453
57556
  }
57454
57557
  );
57455
57558
  child.unref();
57456
- return { pid: child.pid, logPath };
57559
+ return { pid: child.pid };
57457
57560
  }
57458
57561
  function getAnnotations(access, options) {
57459
57562
  if (access === "read") {
@@ -57508,6 +57611,24 @@ function buildErrorEnvelope(tool, requestId, error2) {
57508
57611
  recommendedNextActions
57509
57612
  };
57510
57613
  }
57614
+ function buildOutputValidationErrorEnvelope(tool, requestId, error2) {
57615
+ return {
57616
+ schemaVersion: SCHEMA_VERSION,
57617
+ ok: false,
57618
+ tool,
57619
+ requestId: requestId ?? null,
57620
+ error: {
57621
+ code: ERROR_CODES.INTERNAL_ERROR,
57622
+ message: "Tool output failed validation against its declared MCP schema.",
57623
+ hint: error2.issues.map((issue2) => `${issue2.path.join(".") || "output"}: ${issue2.message}`).join("; "),
57624
+ retryable: false,
57625
+ category: "internal"
57626
+ },
57627
+ warnings: [],
57628
+ risks: ["The MCP server returned an output shape that did not match the tool descriptor."],
57629
+ recommendedNextActions: ["Report this MCP schema mismatch with the tool name and error hint."]
57630
+ };
57631
+ }
57511
57632
  function registerTool(server, context, params) {
57512
57633
  const errorSchema = makeErrorSchema();
57513
57634
  server.registerTool(
@@ -57526,7 +57647,20 @@ function registerTool(server, context, params) {
57526
57647
  assertToolAccess(context.policy, params.access);
57527
57648
  const result = await params.run(rawArgs);
57528
57649
  const envelope = buildSuccessEnvelope(params.name, requestId, result);
57529
- params.outputSchema.parse(envelope);
57650
+ const parsedEnvelope = params.outputSchema.safeParse(envelope);
57651
+ if (!parsedEnvelope.success) {
57652
+ const errorEnvelope = buildOutputValidationErrorEnvelope(params.name, requestId, parsedEnvelope.error);
57653
+ context.logger.log({
57654
+ level: "error",
57655
+ message: "tool_output_validation_failed",
57656
+ tool: params.name,
57657
+ requestId: errorEnvelope.requestId,
57658
+ durationMs: Date.now() - startedAt,
57659
+ result: "error",
57660
+ errorCode: errorEnvelope.error.code
57661
+ });
57662
+ return makeErrorResult(errorEnvelope);
57663
+ }
57530
57664
  context.logger.log({
57531
57665
  level: "info",
57532
57666
  message: "tool_completed",
@@ -57590,12 +57724,14 @@ function registerCollabTools(server, context) {
57590
57724
  try {
57591
57725
  const repoRoot = result && typeof result === "object" && "data" in result && result.data && typeof result.data.repoRoot === "string" ? result.data.repoRoot : null;
57592
57726
  const bindingMode = result && typeof result === "object" && "data" in result && result.data && typeof result.data.bindingMode === "string" ? result.data.bindingMode : null;
57593
- if (repoRoot && isAutoSpawnEligibleBindingMode(bindingMode) && shouldAutoSpawnHistoryImport(repoRoot)) {
57727
+ const resultData = result && typeof result === "object" && "data" in result && result.data && typeof result.data === "object" ? result.data : null;
57728
+ const repoFingerprint = typeof resultData?.repoFingerprint === "string" ? resultData.repoFingerprint : null;
57729
+ if (repoRoot && isAutoSpawnEligibleBindingMode(bindingMode) && shouldAutoSpawnHistoryImport(repoRoot, repoFingerprint)) {
57594
57730
  const cutoffAt = (/* @__PURE__ */ new Date()).toISOString();
57595
57731
  const spawned = spawnHistoryImportDetached(repoRoot, { cutoffAt });
57596
57732
  context.logger.log({
57597
57733
  level: "info",
57598
- message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} log=${spawned.logPath} cutoffAt=${cutoffAt}`,
57734
+ message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} cutoffAt=${cutoffAt}`,
57599
57735
  tool: "remix_collab_init",
57600
57736
  repoRoot
57601
57737
  });
@@ -59629,7 +59765,7 @@ async function listPendingTurnStateSummaries() {
59629
59765
  // package.json
59630
59766
  var package_default = {
59631
59767
  name: "@remixhq/claude-plugin",
59632
- version: "0.1.23",
59768
+ version: "0.1.25",
59633
59769
  description: "Claude Code plugin for Remix collaboration workflows",
59634
59770
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
59635
59771
  license: "MIT",
@@ -59667,8 +59803,8 @@ var package_default = {
59667
59803
  prepack: "npm run build"
59668
59804
  },
59669
59805
  dependencies: {
59670
- "@remixhq/core": "^0.1.18",
59671
- "@remixhq/mcp": "^0.1.18"
59806
+ "@remixhq/core": "^0.1.20",
59807
+ "@remixhq/mcp": "^0.1.20"
59672
59808
  },
59673
59809
  devDependencies: {
59674
59810
  "@types/node": "^25.4.0",