@remixhq/claude-plugin 0.1.16 → 0.1.18

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remix",
3
3
  "description": "Remix collaboration workflows for Claude Code",
4
- "version": "0.1.16",
4
+ "version": "0.1.18",
5
5
  "author": {
6
6
  "name": "Remix"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: remix-collab
3
- description: Specialized Claude agent for Remix repository collaboration, merge request review, sync, reconcile, and change-step recording workflows.
3
+ description: Specialized Claude agent for Remix repository collaboration, merge request review, sync, reconcile, and queued finalize-turn workflows.
4
4
  ---
5
5
 
6
6
  You are the Remix collaboration specialist.
@@ -32,19 +32,24 @@ Operating rules:
32
32
  9. Clearly explain local mutation risk before using tools that can modify the local repo.
33
33
  10. In a bound repo, Remix MCP tools are the required workflow layer for ordinary collaboration work.
34
34
  11. In a bound repo, exactly one final `remix_collab_finalize_turn` is required before the final user-facing response.
35
- 12. The final recording call must use the exact user prompt and your final assistant response.
36
- 13. Do not finish the turn before recording. Do not make additional repo mutations after the final turn-recording call unless you intend to record again.
37
- 14. Do not duplicate core business logic in reasoning. Use the MCP tools to inspect and execute the workflow.
35
+ 12. `remix_collab_finalize_turn` is queued only. A remote change step does not exist yet until the finalize queue drains successfully.
36
+ 13. The final recording call must use the exact user prompt and your final assistant response.
37
+ 14. If `remix_collab_status` reports `await_finalize`, use `remix_collab_drain_finalize_queue`, then re-check status before merge-related or recovery flows that depend on a remote recorded turn.
38
+ 15. Do not finish the turn before recording. Do not make additional repo mutations after the final turn-recording call unless you intend to record again.
39
+ 16. Do not duplicate core business logic in reasoning. Use the MCP tools to inspect and execute the workflow.
38
40
 
39
41
  When appropriate:
40
42
 
41
43
  - use `remix_collab_init` to bind the current repo
42
44
  - use `remix_collab_remix` to start from an existing app lineage
43
45
  - use `remix_collab_checkout` to continue work on an existing app id without creating a fork
46
+ - when the user asks for an app's dashboard URL and an `appId` is known, answer with `https://dashboard.remix.one/apps/<appId>` without requiring an additional tool call
44
47
  - when helping the user choose an app, prefer scoped or membership-oriented discovery first: use `organizationId` / `projectId` when known, or use `ownership` with `accessScope="explicit_member"` when the user means “apps I can work on”
45
48
  - reserve `accessScope="all_readable"` for explicit public or broad readable discovery, not as the default work-oriented app picker
46
49
  - use memory summary/search/timeline tools before repo inspection when historical context or reasoning is needed
47
50
  - use the explicit change-step diff tool only after you already know which `changeStepId` matters
48
51
  - use `remix_collab_finalize_turn` as the final turn recorder
52
+ - treat `remix_collab_finalize_turn` as local capture plus queueing, not immediate remote materialization
53
+ - use `remix_collab_drain_finalize_queue` and `remix_collab_status` when the workflow is blocked on `await_finalize`
49
54
  - use `remix_collab_review_queue` for reviewable merge requests, `remix_collab_my_merge_requests` for authored requests, and `remix_collab_list_app_merge_requests` for app-scoped MR flows with required `queue` set to `app_reviewable`, `app_outgoing`, or `app_related_visible`
50
55
  - use merge request tools for review and approval flows
@@ -685,6 +685,12 @@ function normalizeStringArray(value) {
685
685
  )
686
686
  );
687
687
  }
688
+ function normalizeManualRecordingScope(value) {
689
+ if (value === "full_turn") {
690
+ return "full_turn";
691
+ }
692
+ return null;
693
+ }
688
694
  function normalizeTouchedRepo(value, repoRoot) {
689
695
  if (!value || typeof value !== "object") return null;
690
696
  const parsed = value;
@@ -703,7 +709,7 @@ function normalizeTouchedRepo(value, repoRoot) {
703
709
  manuallyRecorded: Boolean(parsed.manuallyRecorded),
704
710
  manuallyRecordedAt: normalizeString(parsed.manuallyRecordedAt),
705
711
  manuallyRecordedByTool: normalizeString(parsed.manuallyRecordedByTool),
706
- manualRecordingScope: parsed.manualRecordingScope === "change_step" || parsed.manualRecordingScope === "full_turn" ? parsed.manualRecordingScope : null,
712
+ manualRecordingScope: normalizeManualRecordingScope(parsed.manualRecordingScope),
707
713
  manualRemoteChangeRecordedAt: normalizeString(parsed.manualRemoteChangeRecordedAt),
708
714
  stopAttempted: Boolean(parsed.stopAttempted),
709
715
  stopRecorded: Boolean(parsed.stopRecorded),
@@ -855,7 +861,7 @@ async function markPendingTurnConsultedMemory(sessionId) {
855
861
  // package.json
856
862
  var package_default = {
857
863
  name: "@remixhq/claude-plugin",
858
- version: "0.1.16",
864
+ version: "0.1.18",
859
865
  description: "Claude Code plugin for Remix collaboration workflows",
860
866
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
861
867
  license: "MIT",
@@ -886,8 +892,8 @@ var package_default = {
886
892
  prepack: "npm run build"
887
893
  },
888
894
  dependencies: {
889
- "@remixhq/core": "^0.1.11",
890
- "@remixhq/mcp": "^0.1.11"
895
+ "@remixhq/core": "^0.1.13",
896
+ "@remixhq/mcp": "^0.1.13"
891
897
  },
892
898
  devDependencies: {
893
899
  "@types/node": "^25.4.0",
@@ -7751,7 +7757,7 @@ var {
7751
7757
  getCancelSignal: getCancelSignal2
7752
7758
  } = getIpcExport();
7753
7759
 
7754
- // node_modules/@remixhq/core/dist/chunk-RREREIGW.js
7760
+ // node_modules/@remixhq/core/dist/chunk-WT6VRLXU.js
7755
7761
  async function runGit(args, cwd) {
7756
7762
  const res = await execa("git", args, { cwd, stderr: "ignore" });
7757
7763
  return String(res.stdout || "").trim();
@@ -7765,7 +7771,7 @@ async function getCurrentBranch(cwd) {
7765
7771
  }
7766
7772
  }
7767
7773
 
7768
- // node_modules/@remixhq/core/dist/chunk-4L3ZBZUQ.js
7774
+ // node_modules/@remixhq/core/dist/chunk-YCFLOHJV.js
7769
7775
  var import_promises14 = __toESM(require("fs/promises"), 1);
7770
7776
  var import_path = __toESM(require("path"), 1);
7771
7777
  var import_promises15 = __toESM(require("fs/promises"), 1);
@@ -7787,7 +7793,8 @@ function buildBindingFileV3(params) {
7787
7793
  repoFingerprint: params.repoFingerprint,
7788
7794
  remoteUrl: params.remoteUrl,
7789
7795
  defaultBranch: params.defaultBranch,
7790
- branchBindings: params.branchBindings
7796
+ branchBindings: params.branchBindings,
7797
+ ...params.explicitRootBinding ? { explicitRootBinding: params.explicitRootBinding } : {}
7791
7798
  };
7792
7799
  }
7793
7800
  function normalizeBranchName(value) {
@@ -7806,7 +7813,7 @@ function normalizeBranchBinding(value) {
7806
7813
  upstreamAppId: value.upstreamAppId,
7807
7814
  threadId: value.threadId ?? null,
7808
7815
  laneId: value.laneId ?? null,
7809
- bindingMode: value.bindingMode === "legacy" ? "legacy" : "lane"
7816
+ bindingMode: value.bindingMode === "legacy" ? "legacy" : value.bindingMode === "explicit_root" ? "explicit_root" : "lane"
7810
7817
  };
7811
7818
  }
7812
7819
  function buildResolvedBinding(params) {
@@ -7880,6 +7887,7 @@ async function readCollabBindingState(repoRoot, options) {
7880
7887
  defaultBranch: migratedFile.defaultBranch,
7881
7888
  currentBranch,
7882
7889
  branchBindings: migratedFile.branchBindings,
7890
+ explicitRootBinding: null,
7883
7891
  binding: buildResolvedBinding({
7884
7892
  fallbackProjectId: projectId,
7885
7893
  repoFingerprint: migratedFile.repoFingerprint,
@@ -7923,7 +7931,22 @@ async function readCollabBindingState(repoRoot, options) {
7923
7931
  };
7924
7932
  shouldPersistNormalizedBranchBindings = true;
7925
7933
  }
7926
- if (persist && ("explicitBinding" in file || shouldPersistNormalizedBranchBindings || parsed.schemaVersion === 2)) {
7934
+ let explicitRootBinding = normalizeBranchBinding(file.explicitRootBinding ?? null);
7935
+ if (explicitRootBinding && !explicitRootBinding.projectId && legacyProjectId) {
7936
+ explicitRootBinding = {
7937
+ ...explicitRootBinding,
7938
+ projectId: legacyProjectId
7939
+ };
7940
+ shouldPersistNormalizedBranchBindings = true;
7941
+ }
7942
+ if (explicitRootBinding && explicitRootBinding.bindingMode !== "explicit_root") {
7943
+ explicitRootBinding = {
7944
+ ...explicitRootBinding,
7945
+ bindingMode: "explicit_root"
7946
+ };
7947
+ shouldPersistNormalizedBranchBindings = true;
7948
+ }
7949
+ if (persist && ("explicitBinding" in file || "explicitRootBinding" in file || shouldPersistNormalizedBranchBindings || parsed.schemaVersion === 2)) {
7927
7950
  try {
7928
7951
  await writeJsonAtomic2(
7929
7952
  filePath,
@@ -7931,7 +7954,8 @@ async function readCollabBindingState(repoRoot, options) {
7931
7954
  repoFingerprint: file.repoFingerprint ?? null,
7932
7955
  remoteUrl: file.remoteUrl ?? null,
7933
7956
  defaultBranch: file.defaultBranch ?? null,
7934
- branchBindings
7957
+ branchBindings,
7958
+ explicitRootBinding
7935
7959
  })
7936
7960
  );
7937
7961
  } catch {
@@ -7942,8 +7966,23 @@ async function readCollabBindingState(repoRoot, options) {
7942
7966
  branchBindings,
7943
7967
  currentBranch: resolvedBranch,
7944
7968
  defaultBranch: normalizeBranchName(file.defaultBranch),
7945
- legacyProjectId
7969
+ legacyProjectId: explicitRootBinding?.projectId ?? legacyProjectId
7946
7970
  });
7971
+ const resolvedBinding = buildResolvedBinding({
7972
+ fallbackProjectId,
7973
+ repoFingerprint: file.repoFingerprint ?? null,
7974
+ remoteUrl: file.remoteUrl ?? null,
7975
+ defaultBranch: file.defaultBranch ?? null,
7976
+ branchName: resolvedBranch,
7977
+ binding: resolvedBranch ? branchBindings[resolvedBranch] ?? null : null
7978
+ }) ?? (resolvedBranch && resolvedBranch === normalizeBranchName(file.defaultBranch) && explicitRootBinding ? buildResolvedBinding({
7979
+ fallbackProjectId,
7980
+ repoFingerprint: file.repoFingerprint ?? null,
7981
+ remoteUrl: file.remoteUrl ?? null,
7982
+ defaultBranch: file.defaultBranch ?? null,
7983
+ branchName: normalizeBranchName(file.defaultBranch),
7984
+ binding: explicitRootBinding
7985
+ }) : null);
7947
7986
  return {
7948
7987
  schemaVersion: parsed.schemaVersion,
7949
7988
  projectId: fallbackProjectId,
@@ -7952,14 +7991,15 @@ async function readCollabBindingState(repoRoot, options) {
7952
7991
  defaultBranch: file.defaultBranch ?? null,
7953
7992
  currentBranch,
7954
7993
  branchBindings,
7955
- binding: buildResolvedBinding({
7994
+ explicitRootBinding: buildResolvedBinding({
7956
7995
  fallbackProjectId,
7957
7996
  repoFingerprint: file.repoFingerprint ?? null,
7958
7997
  remoteUrl: file.remoteUrl ?? null,
7959
7998
  defaultBranch: file.defaultBranch ?? null,
7960
- branchName: resolvedBranch,
7961
- binding: resolvedBranch ? branchBindings[resolvedBranch] ?? null : null
7962
- })
7999
+ branchName: normalizeBranchName(file.defaultBranch),
8000
+ binding: explicitRootBinding
8001
+ }),
8002
+ binding: resolvedBinding
7963
8003
  };
7964
8004
  } catch {
7965
8005
  return null;
@@ -7994,11 +8034,6 @@ function extractToolInput(payload) {
7994
8034
  function extractToolResponse(payload) {
7995
8035
  return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);
7996
8036
  }
7997
- function extractToolStructuredData(payload) {
7998
- const toolResponse = extractToolResponse(payload);
7999
- const structuredContent = getNestedRecord(toolResponse?.structuredContent) ?? getNestedRecord(payload.structuredContent);
8000
- return getNestedRecord(toolResponse?.data) ?? getNestedRecord(structuredContent?.data) ?? structuredContent;
8001
- }
8002
8037
  function extractToolName(payload) {
8003
8038
  return extractString(payload, ["tool_name", "toolName"]);
8004
8039
  }
@@ -8012,26 +8047,6 @@ function normalizeHookToolName(toolName) {
8012
8047
  }
8013
8048
  return trimmed;
8014
8049
  }
8015
- function extractAssistantResponse(payload) {
8016
- const candidateKeys = [
8017
- "last_assistant_message",
8018
- "lastAssistantMessage",
8019
- "assistant_response",
8020
- "assistantResponse",
8021
- "assistant_message",
8022
- "assistantMessage",
8023
- "response",
8024
- "message"
8025
- ];
8026
- return extractString(payload, candidateKeys) ?? extractString(extractToolResponse(payload) ?? {}, candidateKeys) ?? extractString(extractToolStructuredData(payload) ?? {}, candidateKeys) ?? extractString(extractToolInput(payload), candidateKeys);
8027
- }
8028
- function extractFinalizeTurnMode(payload) {
8029
- const mode = extractString(extractToolStructuredData(payload) ?? {}, ["mode"]) ?? extractString(extractToolResponse(payload) ?? {}, ["mode"]) ?? extractString(payload, ["mode"]);
8030
- if (mode === "changed_turn" || mode === "no_diff_turn") {
8031
- return mode;
8032
- }
8033
- return null;
8034
- }
8035
8050
  function extractString(input, keys) {
8036
8051
  for (const key of keys) {
8037
8052
  const value = input[key];
@@ -8083,9 +8098,6 @@ function didToolSucceed(payload) {
8083
8098
  const hookEventName = extractString(payload, ["hook_event_name", "hookEventName"]);
8084
8099
  return hookEventName === "PostToolUse";
8085
8100
  }
8086
- function isRemoteChangeRecordedButLocalSyncFailed(payload) {
8087
- return extractToolErrorMessage(payload) === "Change step succeeded remotely, but automatic local sync failed.";
8088
- }
8089
8101
  function collectStringPathValue(value) {
8090
8102
  if (typeof value === "string" && value.trim()) return [value.trim()];
8091
8103
  if (Array.isArray(value)) {
@@ -8150,7 +8162,7 @@ async function resolveBoundRepoFromToolCwd(payload) {
8150
8162
 
8151
8163
  // src/hook-post-collab.ts
8152
8164
  function isRepoMutationToolName(toolName) {
8153
- return /remix_collab_(add|add_change_step|sync_apply|approve_and_sync_target|sync_upstream|reconcile_apply)$/i.test(toolName);
8165
+ return /remix_collab_(sync_apply|approve_and_sync_target|sync_upstream|reconcile_apply)$/i.test(toolName);
8154
8166
  }
8155
8167
  function isMemoryToolName(toolName) {
8156
8168
  return /remix_collab_memory_(summary|search|timeline|change_step_diff)$/i.test(toolName);
@@ -8164,31 +8176,12 @@ function isStructuredLocalReadToolName(toolName) {
8164
8176
  function isShellToolName(toolName) {
8165
8177
  return /^Bash$/i.test(toolName);
8166
8178
  }
8167
- function isRemoteChangeRecordingToolName(toolName) {
8168
- return /remix_collab_(add|add_change_step)$/i.test(toolName);
8169
- }
8170
- function hasManualFullTurnPayload(payload) {
8171
- const toolInput = extractToolInput(payload);
8172
- return Boolean(extractString(toolInput, ["prompt"]) && extractAssistantResponse(payload));
8173
- }
8174
- function getManualRecordingScope(payload, toolName) {
8179
+ function getManualRecordingScope(toolName) {
8175
8180
  if (/remix_collab_finalize_turn$/i.test(toolName)) {
8176
8181
  return "full_turn";
8177
8182
  }
8178
- if (/remix_collab_(add|add_change_step)$/i.test(toolName)) {
8179
- return hasManualFullTurnPayload(payload) ? "full_turn" : "change_step";
8180
- }
8181
- if (/remix_collab_(record_turn|record_no_diff_turn)$/i.test(toolName)) {
8182
- return "full_turn";
8183
- }
8184
8183
  return null;
8185
8184
  }
8186
- function didFinalizeTurnRecordRemoteChange(payload, toolName) {
8187
- if (!/remix_collab_finalize_turn$/i.test(toolName)) {
8188
- return false;
8189
- }
8190
- return extractFinalizeTurnMode(payload) === "changed_turn";
8191
- }
8192
8185
  function isLikelyMutatingShellCommand(command) {
8193
8186
  const normalized = command.trim().toLowerCase();
8194
8187
  if (!normalized) return false;
@@ -8254,7 +8247,6 @@ async function runHookPostCollab(payload) {
8254
8247
  return;
8255
8248
  }
8256
8249
  const toolSucceeded = didToolSucceed(payload);
8257
- const remoteChangeRecordedButSyncFailed = (isRemoteChangeRecordingToolName(toolName) || /remix_collab_finalize_turn$/i.test(toolName)) && isRemoteChangeRecordedButLocalSyncFailed(payload);
8258
8250
  await appendHookDiagnosticsEvent({
8259
8251
  hook: "PostToolUse",
8260
8252
  sessionId,
@@ -8263,7 +8255,6 @@ async function runHookPostCollab(payload) {
8263
8255
  toolName,
8264
8256
  fields: {
8265
8257
  toolSucceeded,
8266
- remoteChangeRecordedButSyncFailed,
8267
8258
  isMemoryTool: isMemoryToolName(toolName),
8268
8259
  isRepoMutationTool: isRepoMutationToolName(toolName),
8269
8260
  isShellTool: isShellToolName(toolName),
@@ -8271,7 +8262,7 @@ async function runHookPostCollab(payload) {
8271
8262
  isStructuredLocalReadTool: isStructuredLocalReadToolName(toolName)
8272
8263
  }
8273
8264
  });
8274
- if (!toolSucceeded && !remoteChangeRecordedButSyncFailed) {
8265
+ if (!toolSucceeded) {
8275
8266
  await appendHookDiagnosticsEvent({
8276
8267
  hook: "PostToolUse",
8277
8268
  sessionId,
@@ -8292,7 +8283,7 @@ async function runHookPostCollab(payload) {
8292
8283
  toolName
8293
8284
  });
8294
8285
  }
8295
- const manualRecordingScope = getManualRecordingScope(payload, toolName);
8286
+ const manualRecordingScope = getManualRecordingScope(toolName);
8296
8287
  if (isRepoMutationToolName(toolName) || manualRecordingScope) {
8297
8288
  const targetRepo = await resolveBoundRepoFromToolCwd(payload);
8298
8289
  if (targetRepo) {
@@ -8311,7 +8302,7 @@ async function runHookPostCollab(payload) {
8311
8302
  await markTouchedRepoManuallyRecorded(sessionId, targetRepo.repoRoot, {
8312
8303
  toolName,
8313
8304
  scope: manualRecordingScope,
8314
- remoteChangeRecorded: toolSucceeded ? isRemoteChangeRecordingToolName(toolName) || didFinalizeTurnRecordRemoteChange(payload, toolName) : remoteChangeRecordedButSyncFailed
8305
+ remoteChangeRecorded: false
8315
8306
  });
8316
8307
  }
8317
8308
  await appendHookDiagnosticsEvent({
@@ -8323,8 +8314,7 @@ async function runHookPostCollab(payload) {
8323
8314
  repoRoot: targetRepo.repoRoot,
8324
8315
  fields: {
8325
8316
  hasObservedWrite: isRepoMutationToolName(toolName),
8326
- manualRecordingScope,
8327
- remoteChangeRecordedButSyncFailed
8317
+ manualRecordingScope
8328
8318
  }
8329
8319
  });
8330
8320
  } else {