@remixhq/claude-plugin 0.1.20 → 0.1.22

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.20",
6
+ version: "0.1.22",
7
7
  description: "Claude Code plugin for Remix collaboration workflows",
8
8
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
9
9
  license: "MIT",
@@ -37,12 +37,12 @@ var package_default = {
37
37
  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);"`,
38
38
  dev: "tsx src/mcp-server.ts",
39
39
  typecheck: "tsc -p tsconfig.json --noEmit",
40
- test: "node --import tsx --test src/**/*.test.ts",
40
+ test: "node --import tsx --test 'src/**/*.test.ts'",
41
41
  prepack: "npm run build"
42
42
  },
43
43
  dependencies: {
44
- "@remixhq/core": "^0.1.15",
45
- "@remixhq/mcp": "^0.1.15"
44
+ "@remixhq/core": "^0.1.17",
45
+ "@remixhq/mcp": "^0.1.17"
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.20\",\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.15\",\n \"@remixhq/mcp\": \"^0.1.15\"\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.22\",\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.17\",\n \"@remixhq/mcp\": \"^0.1.17\"\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":[]}
@@ -21451,33 +21451,6 @@ async function pruneTerminalAsyncJobs() {
21451
21451
  }).map((job) => deleteAsyncJob(job.id))
21452
21452
  );
21453
21453
  }
21454
- async function awaitAsyncJob(params) {
21455
- const pollIntervalMs = Math.max(50, params.pollIntervalMs ?? 500);
21456
- const deadline = Date.now() + params.timeoutMs;
21457
- let lastSeen = null;
21458
- while (Date.now() < deadline) {
21459
- const job = await readAsyncJob(params.jobId);
21460
- if (job) {
21461
- lastSeen = job;
21462
- if (job.status === "completed") return { status: "completed", job };
21463
- if (job.status === "failed") return { status: "failed", job };
21464
- } else if (lastSeen) {
21465
- return { status: "timeout", job: lastSeen };
21466
- } else {
21467
- return { status: "timeout", job: null };
21468
- }
21469
- const remaining = deadline - Date.now();
21470
- if (remaining <= 0) break;
21471
- await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
21472
- }
21473
- const final = await readAsyncJob(params.jobId);
21474
- if (final) {
21475
- if (final.status === "completed") return { status: "completed", job: final };
21476
- if (final.status === "failed") return { status: "failed", job: final };
21477
- return { status: "timeout", job: final };
21478
- }
21479
- return { status: "timeout", job: lastSeen };
21480
- }
21481
21454
  function getBaselinePath(params) {
21482
21455
  return import_path6.default.join(getBaselinesRoot(), `${buildLaneStateKey(params)}.json`);
21483
21456
  }
@@ -21872,11 +21845,6 @@ function normalizeJob2(input) {
21872
21845
  metadata: input.metadata ?? {}
21873
21846
  };
21874
21847
  }
21875
- async function enqueuePendingFinalizeJob(input) {
21876
- const job = normalizeJob2(input);
21877
- await writeJsonAtomic(getJobPath(job.id), job);
21878
- return job;
21879
- }
21880
21848
  async function readPendingFinalizeJob(jobId) {
21881
21849
  try {
21882
21850
  const raw = await import_promises18.default.readFile(getJobPath(jobId), "utf8");
@@ -23587,28 +23555,6 @@ async function processClaimedPendingFinalizeJobInner(params) {
23587
23555
  warnings: []
23588
23556
  };
23589
23557
  }
23590
- async function enqueueCapturedFinalizeTurn(params) {
23591
- return enqueuePendingFinalizeJob({
23592
- status: "queued",
23593
- repoRoot: params.repoRoot,
23594
- repoFingerprint: params.repoFingerprint,
23595
- currentAppId: params.currentAppId,
23596
- laneId: params.laneId,
23597
- threadId: params.threadId,
23598
- branchName: params.branchName,
23599
- prompt: params.prompt,
23600
- assistantResponse: params.assistantResponse,
23601
- baselineSnapshotId: params.baselineSnapshotId,
23602
- baselineServerHeadHash: params.baselineServerHeadHash,
23603
- currentSnapshotId: params.currentSnapshotId,
23604
- idempotencyKey: params.idempotencyKey,
23605
- error: null,
23606
- retryCount: 0,
23607
- lastAttemptAt: null,
23608
- nextRetryAt: params.nextRetryAt ?? null,
23609
- metadata: params.metadata ?? {}
23610
- });
23611
- }
23612
23558
  async function drainPendingFinalizeQueue(params) {
23613
23559
  await prunePendingFinalizeJobs();
23614
23560
  const jobs = await listPendingFinalizeJobs();
@@ -23629,199 +23575,6 @@ async function drainPendingFinalizeQueue(params) {
23629
23575
  }
23630
23576
  return results;
23631
23577
  }
23632
- var FINALIZE_AWAIT_INIT_POST_TIMEOUT_MS = 6e4;
23633
- function collectWarnings(value) {
23634
- if (!Array.isArray(value)) return [];
23635
- return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
23636
- }
23637
- var FINALIZE_QUEUED_WARNING = "Queued only: the local Remix turn was captured, but no remote change step or collab turn exists yet. Drain or await finalize before merge-related flows.";
23638
- async function collabFinalizeTurn(params) {
23639
- const repoRoot = await findGitRoot(params.cwd);
23640
- const binding = await ensureActiveLaneBinding({
23641
- repoRoot,
23642
- api: params.api,
23643
- operation: "`remix collab finalize-turn`"
23644
- });
23645
- if (!binding) {
23646
- throw new RemixError("Repository is not bound to Remix.", {
23647
- exitCode: 2,
23648
- hint: "Run `remix collab init` first."
23649
- });
23650
- }
23651
- const prompt = params.prompt.trim();
23652
- const assistantResponse = params.assistantResponse.trim();
23653
- if (!prompt) throw new RemixError("Prompt is required.", { exitCode: 2 });
23654
- if (!assistantResponse) throw new RemixError("Assistant response is required.", { exitCode: 2 });
23655
- if (params.diff?.trim()) {
23656
- throw new RemixError("External diff submission is no longer supported for `finalize_turn`.", {
23657
- exitCode: 2,
23658
- hint: "Finalize turns now capture the real workspace boundary from the local snapshot store."
23659
- });
23660
- }
23661
- const pendingInit = await findPendingAsyncJob({
23662
- repoRoot,
23663
- branchName: binding.branchName ?? null,
23664
- kind: "init"
23665
- });
23666
- if (pendingInit) {
23667
- throw new RemixError("Cannot finalize a turn while the initial Remix import is still processing.", {
23668
- exitCode: 2,
23669
- hint: `Init job ${pendingInit.id} is still in the background queue. Run \`remix collab status\` to check progress.`
23670
- });
23671
- }
23672
- const pendingInitPost = await findPendingAsyncJob({
23673
- repoRoot,
23674
- branchName: binding.branchName ?? null,
23675
- kind: "init_post"
23676
- });
23677
- if (pendingInitPost) {
23678
- const result = await awaitAsyncJob({
23679
- jobId: pendingInitPost.id,
23680
- timeoutMs: FINALIZE_AWAIT_INIT_POST_TIMEOUT_MS,
23681
- pollIntervalMs: 500
23682
- });
23683
- if (result.status === "failed") {
23684
- throw new RemixError("The initial Remix import failed; this turn cannot be recorded.", {
23685
- exitCode: 2,
23686
- hint: (result.job?.error ? `Last error: ${result.job.error}
23687
-
23688
- ` : "") + "Run `remix collab init` again to retry \u2014 the post-init drainer cleared the local binding so this is safe."
23689
- });
23690
- }
23691
- if (result.status === "timeout") {
23692
- throw new RemixError("Timed out waiting for the initial Remix import to finish provisioning.", {
23693
- exitCode: 2,
23694
- hint: `Init post job ${pendingInitPost.id} did not complete within ${Math.round(
23695
- FINALIZE_AWAIT_INIT_POST_TIMEOUT_MS / 1e3
23696
- )}s. Run \`remix collab status\` to check progress, then retry once it reports ready.`
23697
- });
23698
- }
23699
- }
23700
- const pendingReAnchor = await findPendingAsyncJob({
23701
- repoRoot,
23702
- branchName: binding.branchName ?? null,
23703
- kind: "re_anchor"
23704
- });
23705
- if (pendingReAnchor) {
23706
- throw new RemixError("Cannot finalize a turn while a re-anchor is still processing.", {
23707
- exitCode: 2,
23708
- hint: `Re-anchor job ${pendingReAnchor.id} is still in the background queue. Run \`remix collab status\` to check progress.`
23709
- });
23710
- }
23711
- const detected = await collabDetectRepoState({
23712
- api: params.api,
23713
- cwd: repoRoot,
23714
- allowBranchMismatch: params.allowBranchMismatch
23715
- });
23716
- if (detected.status === "not_bound") {
23717
- throw new RemixError("Repository is not bound to Remix.", {
23718
- code: "not_bound",
23719
- exitCode: 2,
23720
- hint: detected.hint
23721
- });
23722
- }
23723
- if (detected.status === "branch_binding_missing" || detected.status === "family_ambiguous") {
23724
- throw new RemixError(detected.hint || "Current branch is not ready for Remix recording.", {
23725
- code: detected.status,
23726
- exitCode: 2,
23727
- hint: detected.hint
23728
- });
23729
- }
23730
- if (detected.status === "metadata_conflict" || detected.status === "branch_mismatch") {
23731
- throw new RemixError("Repository must be realigned before finalizing the turn.", {
23732
- code: detected.status,
23733
- exitCode: 2,
23734
- hint: detected.hint
23735
- });
23736
- }
23737
- if (detected.status === "missing_head" || detected.status === "remote_error") {
23738
- throw new RemixError(detected.hint || "Failed to determine the current repo state.", {
23739
- code: detected.status,
23740
- exitCode: 1,
23741
- hint: detected.hint
23742
- });
23743
- }
23744
- if (detected.repoState === "server_only_changed") {
23745
- throw new RemixError("Server changes must be pulled locally before finalizing this turn.", {
23746
- code: "pull_required",
23747
- exitCode: 2,
23748
- hint: detected.hint
23749
- });
23750
- }
23751
- if (detected.repoState === "external_local_base_changed") {
23752
- throw new RemixError("The local checkout must be re-anchored before finalizing this turn.", {
23753
- code: "re_anchor_required",
23754
- exitCode: 2,
23755
- hint: detected.hint
23756
- });
23757
- }
23758
- const baseline = await readLocalBaseline({
23759
- repoFingerprint: binding.repoFingerprint,
23760
- laneId: binding.laneId,
23761
- repoRoot
23762
- });
23763
- if (!baseline) {
23764
- throw new RemixError("Local Remix baseline is missing for this lane.", {
23765
- exitCode: 2,
23766
- hint: "Run `remix collab re-anchor` to create a fresh baseline."
23767
- });
23768
- }
23769
- const snapshot = await captureLocalSnapshot({
23770
- repoRoot,
23771
- repoFingerprint: binding.repoFingerprint,
23772
- laneId: binding.laneId,
23773
- branchName: binding.branchName
23774
- });
23775
- const mode = snapshot.snapshotHash === baseline.lastSnapshotHash ? "no_diff_turn" : "changed_turn";
23776
- const idempotencyKey = params.idempotencyKey?.trim() || buildDeterministicIdempotencyKey({
23777
- kind: "collab_finalize_turn_boundary_v1",
23778
- appId: binding.currentAppId,
23779
- laneId: binding.laneId,
23780
- baselineSnapshotId: baseline.lastSnapshotId,
23781
- baselineServerHeadHash: baseline.lastServerHeadHash,
23782
- currentSnapshotId: snapshot.id,
23783
- currentSnapshotHash: snapshot.snapshotHash,
23784
- repoState: detected.repoState,
23785
- prompt,
23786
- assistantResponse
23787
- });
23788
- const awaitingDeadlineMs = typeof params.awaitingUsageDeadlineMs === "number" && params.awaitingUsageDeadlineMs > 0 ? params.awaitingUsageDeadlineMs : null;
23789
- const nextRetryAt = awaitingDeadlineMs === null ? null : new Date(Date.now() + awaitingDeadlineMs).toISOString();
23790
- const job = await enqueueCapturedFinalizeTurn({
23791
- repoRoot,
23792
- repoFingerprint: binding.repoFingerprint,
23793
- currentAppId: binding.currentAppId,
23794
- laneId: binding.laneId,
23795
- threadId: binding.threadId,
23796
- branchName: binding.branchName,
23797
- prompt,
23798
- assistantResponse,
23799
- baselineSnapshotId: baseline.lastSnapshotId,
23800
- baselineServerHeadHash: baseline.lastServerHeadHash,
23801
- currentSnapshotId: snapshot.id,
23802
- idempotencyKey,
23803
- metadata: {
23804
- remoteUrl: binding.remoteUrl,
23805
- defaultBranch: binding.defaultBranch,
23806
- actor: params.actor ?? null,
23807
- repoState: detected.repoState,
23808
- turnUsage: params.turnUsage ?? null,
23809
- promptedAt: typeof params.promptedAt === "string" && params.promptedAt.trim() ? params.promptedAt.trim() : null
23810
- },
23811
- nextRetryAt
23812
- });
23813
- return {
23814
- mode,
23815
- idempotencyKey,
23816
- queued: true,
23817
- jobId: job.id,
23818
- repoState: detected.repoState,
23819
- changeStep: null,
23820
- collabTurn: null,
23821
- autoSync: null,
23822
- warnings: [FINALIZE_QUEUED_WARNING, ...collectWarnings(detected.warnings)]
23823
- };
23824
- }
23825
23578
  var FINALIZE_PREFLIGHT_FAILURE_CODES = [
23826
23579
  // Repo has no .remix/ binding at all. Fix: `remix collab init`.
23827
23580
  "not_bound",
@@ -25532,7 +25285,7 @@ async function collabInit(params) {
25532
25285
  operation: "`remix collab init`",
25533
25286
  recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
25534
25287
  });
25535
- const bindingMode2 = params.forceNew && (!defaultBranch || branchName === defaultBranch) ? "explicit_root" : "lane";
25288
+ const bindingMode2 = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
25536
25289
  let bindingPath2;
25537
25290
  if (params.forceNew && defaultBranch && canonicalLane2) {
25538
25291
  const canonicalBinding = branchBindingFromLane(canonicalLane2, "explicit_root", {
@@ -25574,7 +25327,15 @@ async function collabInit(params) {
25574
25327
  defaultBranch: canonicalLane2.defaultBranch ?? defaultBranch,
25575
25328
  laneId: canonicalLane2.laneId ?? null,
25576
25329
  branchName: defaultBranch,
25577
- bindingMode: params.forceNew ? "explicit_root" : "lane"
25330
+ // This branch is reached only when the CURRENT branch is
25331
+ // not the default branch — so the binding being written
25332
+ // here is for the DEFAULT branch (the canonical/main app).
25333
+ // It must always be `explicit_root` regardless of
25334
+ // `forceNew`; the previous `forceNew ? ... : "lane"`
25335
+ // produced a `lane`-marked default-branch binding for
25336
+ // every plain init, which silently disabled the
25337
+ // history-import auto-spawn.
25338
+ bindingMode: "explicit_root"
25578
25339
  });
25579
25340
  }
25580
25341
  bindingPath2 = await writeCollabBinding(repoRoot, {
@@ -25726,7 +25487,7 @@ async function collabInit(params) {
25726
25487
  operation: "`remix collab init`",
25727
25488
  recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
25728
25489
  });
25729
- const bindingMode = params.forceNew && (!defaultBranch || branchName === defaultBranch) ? "explicit_root" : "lane";
25490
+ const bindingMode = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
25730
25491
  let bindingPath;
25731
25492
  if (params.forceNew && defaultBranch && canonicalLane) {
25732
25493
  const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
@@ -25768,7 +25529,12 @@ async function collabInit(params) {
25768
25529
  defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
25769
25530
  laneId: canonicalLane.laneId ?? null,
25770
25531
  branchName: defaultBranch,
25771
- bindingMode: params.forceNew ? "explicit_root" : "lane"
25532
+ // Same reasoning as the queued-path default-branch write
25533
+ // above: this is the canonical/main-app binding for the
25534
+ // default branch and must be `explicit_root`, otherwise the
25535
+ // history-import auto-spawn (gated on explicit_root) will
25536
+ // silently no-op for every plain init.
25537
+ bindingMode: "explicit_root"
25772
25538
  });
25773
25539
  }
25774
25540
  bindingPath = await writeCollabBinding(repoRoot, {
@@ -27043,7 +26809,7 @@ async function processInitJob(job, api) {
27043
26809
  boundProjectId = String(readyApp.projectId ?? boundProjectId);
27044
26810
  boundThreadId = readyApp.threadId ? String(readyApp.threadId) : boundThreadId;
27045
26811
  }
27046
- const bindingMode = job.payload.forceNew && (!defaultBranch || branchName === defaultBranch) ? "explicit_root" : "lane";
26812
+ const bindingMode = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
27047
26813
  if (job.payload.forceNew && defaultBranch && canonicalLane) {
27048
26814
  const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
27049
26815
  projectId: canonicalLane.projectId ?? boundProjectId,
@@ -27082,7 +26848,12 @@ async function processInitJob(job, api) {
27082
26848
  defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
27083
26849
  laneId: canonicalLane.laneId ?? null,
27084
26850
  branchName: defaultBranch,
27085
- bindingMode: job.payload.forceNew ? "explicit_root" : "lane"
26851
+ // This branch is reached only when the current branch is NOT
26852
+ // the default branch — so the binding being written here is
26853
+ // for the DEFAULT branch (the canonical/main app). It must
26854
+ // always be `explicit_root` so the history-import auto-spawn
26855
+ // can fire on first-ever inits (see autoSpawnHistoryImport.ts).
26856
+ bindingMode: "explicit_root"
27086
26857
  });
27087
26858
  }
27088
26859
  await writeCollabBinding(repoRoot, {
@@ -56076,7 +55847,6 @@ async function resolveConfig(_opts) {
56076
55847
  // node_modules/@remixhq/mcp/dist/index.js
56077
55848
  var import_path16 = __toESM(require("path"), 1);
56078
55849
  var import_child_process = require("child_process");
56079
- var import_child_process2 = require("child_process");
56080
55850
  var import_fs3 = require("fs");
56081
55851
  var import_path17 = __toESM(require("path"), 1);
56082
55852
  async function createRemixTokenProvider(config2) {
@@ -56536,14 +56306,6 @@ var recordTurnInputSchema = {
56536
56306
  allowBranchMismatch: external_exports.boolean().optional(),
56537
56307
  idempotencyKey: external_exports.string().trim().min(1).optional()
56538
56308
  };
56539
- var finalizeTurnInputSchema = {
56540
- ...commonRequestFieldsSchema,
56541
- prompt: external_exports.string().trim().min(1),
56542
- assistantResponse: external_exports.string().trim().min(1),
56543
- sync: external_exports.boolean().optional(),
56544
- allowBranchMismatch: external_exports.boolean().optional(),
56545
- idempotencyKey: external_exports.string().trim().min(1).optional()
56546
- };
56547
56309
  var previewInputSchema = {
56548
56310
  ...commonRequestFieldsSchema
56549
56311
  };
@@ -56713,17 +56475,6 @@ var addDataSchema = external_exports.object({
56713
56475
  changeStep: genericRecordSchema
56714
56476
  });
56715
56477
  var recordTurnDataSchema = genericRecordSchema;
56716
- var finalizeTurnDataSchema = external_exports.object({
56717
- mode: external_exports.enum(["changed_turn", "no_diff_turn"]),
56718
- idempotencyKey: external_exports.string().min(1),
56719
- queued: external_exports.boolean(),
56720
- jobId: external_exports.string().nullable(),
56721
- repoState: external_exports.string().nullable(),
56722
- changeStep: genericRecordSchema.nullable(),
56723
- collabTurn: genericRecordSchema.nullable(),
56724
- autoSync: genericRecordSchema.nullable(),
56725
- warnings: external_exports.array(external_exports.string())
56726
- });
56727
56478
  var drainFinalizeQueueDataSchema = external_exports.object({
56728
56479
  processed: external_exports.number().int().nonnegative(),
56729
56480
  results: external_exports.array(genericRecordSchema)
@@ -56803,7 +56554,6 @@ var remixSuccessSchema = makeSuccessSchema(remixDataSchema);
56803
56554
  var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
56804
56555
  var addSuccessSchema = makeSuccessSchema(addDataSchema);
56805
56556
  var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
56806
- var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
56807
56557
  var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
56808
56558
  var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
56809
56559
  var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
@@ -56844,17 +56594,17 @@ function getRecommendedNextActions(status) {
56844
56594
  return ["Run remix_collab_sync_preview, then remix_collab_sync_apply if the preview is acceptable. This pulls the server delta into the local working tree without rewriting local git history."];
56845
56595
  case "re_anchor":
56846
56596
  return [
56847
- "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply. This seeds a local Remix baseline. It is required because no local baseline exists for this lane yet (fresh clone, deleted .remix/ state, or first init didn't seed) \u2014 not because of any specific git operation. After it succeeds, normal recording (remix_collab_finalize_turn) becomes available."
56597
+ "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply. This seeds a local Remix baseline. It is required because no local baseline exists for this lane yet (fresh clone, deleted .remix/ state, or first init didn't seed) \u2014 not because of any specific git operation. After it succeeds, automatic hook recording can capture completed turns."
56848
56598
  ];
56849
56599
  case "record":
56850
56600
  return [
56851
- "Run remix_collab_finalize_turn to capture the local boundary delta. This is the catch-all for any local content change since the last recorded turn, regardless of whether the change came from agent edits, manual user edits, git commit, git pull, git merge, git rebase, or git reset."
56601
+ "No MCP recording tool is required. Automatic hook finalization will capture the local boundary at the end of the completed turn; this covers agent edits, manual user edits, git commit, git pull, git merge, git rebase, and git reset."
56852
56602
  ];
56853
56603
  case "reconcile":
56854
56604
  return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile applies only when both the local workspace and the server lane changed since the last agreed baseline."];
56855
56605
  case "await_finalize":
56856
56606
  return [
56857
- "Run remix_collab_drain_finalize_queue before merge-related or recovery flows. finalize_turn is queued only until the local finalize queue is drained."
56607
+ "Run remix_collab_drain_finalize_queue before merge-related or recovery flows. Hook recording is queued until the local finalize queue is drained."
56858
56608
  ];
56859
56609
  case "review_queue":
56860
56610
  return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
@@ -56866,12 +56616,12 @@ function getRecommendedNextActions(status) {
56866
56616
  return [];
56867
56617
  }
56868
56618
  }
56869
- function collectWarnings2(value) {
56619
+ function collectWarnings(value) {
56870
56620
  if (!value || !Array.isArray(value)) return [];
56871
56621
  return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
56872
56622
  }
56873
56623
  function collectResultWarnings(value) {
56874
- return collectWarnings2(value.warnings);
56624
+ return collectWarnings(value.warnings);
56875
56625
  }
56876
56626
  function truncateText(value, maxChars) {
56877
56627
  if (value.length <= maxChars) {
@@ -56889,17 +56639,6 @@ function truncateText(value, maxChars) {
56889
56639
  originalChars: value.length
56890
56640
  };
56891
56641
  }
56892
- function spawnFinalizeQueueDrainer() {
56893
- const entrypoint = process.argv[1];
56894
- if (!entrypoint) return false;
56895
- const child = (0, import_child_process.spawn)(process.execPath, [...process.execArgv, entrypoint, "--drain-finalize-queue"], {
56896
- detached: true,
56897
- stdio: "ignore",
56898
- env: process.env
56899
- });
56900
- child.unref();
56901
- return true;
56902
- }
56903
56642
  async function drainBeforeMutation(api) {
56904
56643
  const results = await drainPendingFinalizeQueue({ api });
56905
56644
  return results.flatMap((result) => collectResultWarnings(result));
@@ -56942,7 +56681,7 @@ async function initCollab(params) {
56942
56681
  data: syncResult,
56943
56682
  warnings: collectResultWarnings(result),
56944
56683
  recommendedNextActions: syncResult.baselineStatus === "requires_re_anchor" ? [
56945
- "This checkout has no local Remix baseline yet. Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to seed one. After it succeeds, normal recording (remix_collab_finalize_turn) becomes available."
56684
+ "This checkout has no local Remix baseline yet. Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to seed one. After it succeeds, automatic hook recording can capture completed turns."
56946
56685
  ] : syncResult.baselineStatus === "requires_sync" ? [
56947
56686
  "Run remix_collab_sync_preview, then remix_collab_sync_apply to pull the server delta and create the first local baseline for this checkout."
56948
56687
  ] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
@@ -57007,36 +56746,6 @@ async function checkoutCollab(params) {
57007
56746
  }
57008
56747
  };
57009
56748
  }
57010
- async function finalizeCollabTurn(params) {
57011
- const api = await createCollabApiClient();
57012
- const repoRoot = await findGitRoot(params.cwd);
57013
- const result = await collabFinalizeTurn({
57014
- api,
57015
- cwd: params.cwd,
57016
- prompt: params.prompt,
57017
- assistantResponse: params.assistantResponse,
57018
- sync: params.sync,
57019
- allowBranchMismatch: params.allowBranchMismatch ?? false,
57020
- idempotencyKey: params.idempotencyKey ?? null,
57021
- actor: params.agent,
57022
- awaitingUsageDeadlineMs: params.awaitingUsageDeadlineMs ?? null
57023
- });
57024
- const hasAwaitingDeadline = typeof params.awaitingUsageDeadlineMs === "number" && params.awaitingUsageDeadlineMs > 0;
57025
- if (result.queued && !hasAwaitingDeadline) {
57026
- if (!spawnFinalizeQueueDrainer()) {
57027
- await drainPendingFinalizeQueue({ api });
57028
- }
57029
- }
57030
- return {
57031
- data: result,
57032
- warnings: result.warnings,
57033
- recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
57034
- logContext: {
57035
- repoRoot,
57036
- appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
57037
- }
57038
- };
57039
- }
57040
56749
  async function drainFinalizeQueue(params) {
57041
56750
  const api = await createCollabApiClient();
57042
56751
  const repoRoot = await findGitRoot(params.cwd);
@@ -57085,9 +56794,9 @@ async function reAnchor(params) {
57085
56794
  });
57086
56795
  return {
57087
56796
  data: result,
57088
- warnings: collectWarnings2(result.warnings),
56797
+ warnings: collectWarnings(result.warnings),
57089
56798
  recommendedNextActions: params.dryRun ? [
57090
- "Run remix_collab_re_anchor_apply with confirm=true to seed a local Remix baseline for this checkout. Re-anchor is for missing-baseline cases only and does not replace remix_collab_finalize_turn for ordinary local content changes."
56799
+ "Run remix_collab_re_anchor_apply with confirm=true to seed a local Remix baseline for this checkout. Re-anchor is for missing-baseline cases only and does not replace automatic hook recording for ordinary local content changes."
57091
56800
  ] : [],
57092
56801
  logContext: {
57093
56802
  repoRoot: result.repoRoot,
@@ -57269,7 +56978,7 @@ async function reconcile(params) {
57269
56978
  });
57270
56979
  return {
57271
56980
  data: result,
57272
- warnings: [...drainWarnings, ...collectWarnings2(result.warnings)],
56981
+ warnings: [...drainWarnings, ...collectWarnings(result.warnings)],
57273
56982
  recommendedNextActions: params.dryRun ? ["Run remix_collab_reconcile_apply with confirm=true only if the preview is acceptable. This is the explicit Remix recovery flow for diverged state."] : [],
57274
56983
  risks: params.dryRun ? ["Reconcile may upload local history to recover the server lane onto the latest agreed state before future recording continues."] : [],
57275
56984
  logContext: {
@@ -57829,7 +57538,7 @@ function spawnHistoryImportDetached(repoRoot) {
57829
57538
  const logPath = import_path17.default.join(repoRoot, LOG_REL_PATH);
57830
57539
  const out = (0, import_fs3.openSync)(logPath, "a");
57831
57540
  const err = (0, import_fs3.openSync)(logPath, "a");
57832
- const child = (0, import_child_process2.spawn)(
57541
+ const child = (0, import_child_process.spawn)(
57833
57542
  "remix",
57834
57543
  [
57835
57544
  "history",
@@ -57970,7 +57679,7 @@ function registerCollabTools(server, context) {
57970
57679
  });
57971
57680
  registerTool(server, context, {
57972
57681
  name: "remix_collab_init",
57973
- description: "Import the current repository into Remix and write the local binding file. Synchronous: by the time this tool resolves, the local binding file AND the local Remix baseline are both on disk, so the very next call to remix_collab_finalize_turn will succeed. Brand-new init on the default branch typically takes ~10s; non-default-branch init can take 30-90s while the server provisions a feature lane. The result includes `reused: boolean` (false for a brand-new app, true if a binding already existed) plus the canonical app/project identifiers and the dashboard URL. Use forceNew=true only when intentionally creating a new canonical family from scratch in a previously-bound repo; do NOT use forceNew as a retry mechanism for a failed init \u2014 it creates orphan backend apps and triggers canonical-family ambiguity errors on subsequent inits in this directory.",
57682
+ description: "Import the current repository into Remix and write the local binding file. Synchronous: by the time this tool resolves, the local binding file AND the local Remix baseline are both on disk, so automatic hook recording can capture subsequent completed turns. Brand-new init on the default branch typically takes ~10s; non-default-branch init can take 30-90s while the server provisions a feature lane. The result includes `reused: boolean` (false for a brand-new app, true if a binding already existed) plus the canonical app/project identifiers and the dashboard URL. Use forceNew=true only when intentionally creating a new canonical family from scratch in a previously-bound repo; do NOT use forceNew as a retry mechanism for a failed init \u2014 it creates orphan backend apps and triggers canonical-family ambiguity errors on subsequent inits in this directory.",
57974
57683
  access: "remote_write",
57975
57684
  inputSchema: initInputSchema,
57976
57685
  outputSchema: initSuccessSchema,
@@ -58054,31 +57763,9 @@ function registerCollabTools(server, context) {
58054
57763
  });
58055
57764
  }
58056
57765
  });
58057
- registerTool(server, context, {
58058
- name: "remix_collab_finalize_turn",
58059
- description: "Primary turn recorder for the current bound repository. Required: call this exactly once before the final response on every turn that touched a Remix-bound repo. Captures the current boundary locally and queues remote processing. Queued only: no remote change step exists yet until the finalize queue drains. Runtime exception: if this turn's context contains a [Remix runtime status] system note from the Remix UserPromptSubmit hook stating that the Stop hook will record this turn automatically, do NOT call this tool \u2014 the runtime is recording it on your behalf and a manual call would create a duplicate record. The runtime note is the only sanctioned override; trivial prompts, error states, and ambiguity all still require this call.",
58060
- access: "local_write",
58061
- inputSchema: finalizeTurnInputSchema,
58062
- outputSchema: finalizeTurnSuccessSchema,
58063
- annotations: getAnnotations("local_write", { idempotent: true }),
58064
- run: async (args) => {
58065
- const input = external_exports.object(finalizeTurnInputSchema).parse(args);
58066
- const cwd = resolvePolicyCwd(context.policy, input.cwd);
58067
- return finalizeCollabTurn({
58068
- cwd,
58069
- prompt: input.prompt,
58070
- assistantResponse: input.assistantResponse,
58071
- sync: input.sync,
58072
- allowBranchMismatch: input.allowBranchMismatch ?? false,
58073
- idempotencyKey: input.idempotencyKey,
58074
- agent: context.agentMetadata,
58075
- awaitingUsageDeadlineMs: 3e4
58076
- });
58077
- }
58078
- });
58079
57766
  registerTool(server, context, {
58080
57767
  name: "remix_collab_drain_finalize_queue",
58081
- description: "Drain the local finalize queue and record queued finalize_turn jobs immediately. NOT required as a precondition for `remix_collab_request_merge` or `remix_collab_reconcile_apply` \u2014 those tools drain the queue internally before they run. Useful only for explicit recovery flows (e.g. status reports `await_finalize` and you want to flush before re-checking). Runtime exception: if this turn's context contains a [Remix runtime status] system note from the Remix UserPromptSubmit hook, the runtime drains the queue automatically in the background; do NOT call this tool unless an explicit recovery flow requires it.",
57768
+ description: "Drain the local finalize queue and record queued hook-finalize jobs immediately. NOT required as a precondition for `remix_collab_request_merge` or `remix_collab_reconcile_apply` \u2014 those tools drain the queue internally before they run. Useful only for explicit recovery flows (e.g. status reports `await_finalize` and you want to flush before re-checking). Runtime exception: if this turn's context contains a [Remix runtime status] system note from the Remix UserPromptSubmit hook, the runtime drains the queue automatically in the background; do NOT call this tool unless an explicit recovery flow requires it.",
58082
57769
  access: "local_write",
58083
57770
  inputSchema: drainFinalizeQueueInputSchema,
58084
57771
  outputSchema: drainFinalizeQueueSuccessSchema,
@@ -58116,7 +57803,7 @@ function registerCollabTools(server, context) {
58116
57803
  });
58117
57804
  registerTool(server, context, {
58118
57805
  name: "remix_collab_re_anchor_preview",
58119
- description: "Preview whether this checkout needs a fresh local Remix baseline. Use only when status reports `re_anchor` (no local baseline exists for this lane yet \u2014 fresh clone, deleted `.remix/` state, or first init didn't seed). Re-anchor does not replace `remix_collab_finalize_turn`; ordinary local content changes (including merges, pulls, and rebases) are recorded by `finalize-turn`, not by re-anchor.",
57806
+ description: "Preview whether this checkout needs a fresh local Remix baseline. Use only when status reports `re_anchor` (no local baseline exists for this lane yet \u2014 fresh clone, deleted `.remix/` state, or first init didn't seed). Re-anchor does not replace automatic hook recording; ordinary local content changes (including merges, pulls, and rebases) are captured at the completed-turn boundary, not by re-anchor.",
58120
57807
  access: "read",
58121
57808
  inputSchema: previewInputSchema,
58122
57809
  outputSchema: reAnchorSuccessSchema,
@@ -58128,7 +57815,7 @@ function registerCollabTools(server, context) {
58128
57815
  });
58129
57816
  registerTool(server, context, {
58130
57817
  name: "remix_collab_re_anchor_apply",
58131
- description: "Establish a local Remix baseline for the current checkout against the existing app head, without rewriting the local checkout afterward. Required only when status reports `re_anchor` (missing local baseline). It does not replace `remix_collab_finalize_turn` \u2014 local commits, pulls, merges, and rebases must still be recorded with `finalize-turn`.",
57818
+ description: "Establish a local Remix baseline for the current checkout against the existing app head, without rewriting the local checkout afterward. Required only when status reports `re_anchor` (missing local baseline). It does not replace automatic hook recording \u2014 local commits, pulls, merges, and rebases are still captured at the completed-turn boundary.",
58132
57819
  access: "local_write",
58133
57820
  inputSchema: reAnchorInputSchema,
58134
57821
  outputSchema: reAnchorSuccessSchema,
@@ -60067,7 +59754,7 @@ async function listPendingTurnStateSummaries() {
60067
59754
  // package.json
60068
59755
  var package_default = {
60069
59756
  name: "@remixhq/claude-plugin",
60070
- version: "0.1.20",
59757
+ version: "0.1.22",
60071
59758
  description: "Claude Code plugin for Remix collaboration workflows",
60072
59759
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
60073
59760
  license: "MIT",
@@ -60101,12 +59788,12 @@ var package_default = {
60101
59788
  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);"`,
60102
59789
  dev: "tsx src/mcp-server.ts",
60103
59790
  typecheck: "tsc -p tsconfig.json --noEmit",
60104
- test: "node --import tsx --test src/**/*.test.ts",
59791
+ test: "node --import tsx --test 'src/**/*.test.ts'",
60105
59792
  prepack: "npm run build"
60106
59793
  },
60107
59794
  dependencies: {
60108
- "@remixhq/core": "^0.1.15",
60109
- "@remixhq/mcp": "^0.1.15"
59795
+ "@remixhq/core": "^0.1.17",
59796
+ "@remixhq/mcp": "^0.1.17"
60110
59797
  },
60111
59798
  devDependencies: {
60112
59799
  "@types/node": "^25.4.0",