@remixhq/core 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.
- package/dist/api.d.ts +29 -1
- package/dist/api.js +2 -2
- package/dist/auth.js +2 -2
- package/dist/binding.js +3 -3
- package/dist/chunk-7XJGOKEO.js +23 -0
- package/dist/{chunk-YCFLOHJV.js → chunk-DBVN42RF.js} +2 -2
- package/dist/{chunk-US5SM7ZC.js → chunk-RCNOSZP6.js} +20 -5
- package/dist/{chunk-WT6VRLXU.js → chunk-S4ECO35X.js} +1 -1
- package/dist/{chunk-VM3CGCNX.js → chunk-XCZRNB35.js} +1 -1
- package/dist/{chunk-P6JHXOV4.js → chunk-XETDXVGM.js} +1 -1
- package/dist/collab.d.ts +6 -127
- package/dist/collab.js +239 -330
- package/dist/config.js +2 -2
- package/dist/{contracts-CHmD-fMj.d.ts → contracts-DnNP-K3V.d.ts} +28 -2
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +1 -1
- package/dist/history.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/repo.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-YZ34ICNN.js +0 -17
package/dist/collab.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
writeCollabBinding,
|
|
9
9
|
writeCollabBindingSnapshot,
|
|
10
10
|
writeJsonAtomic
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DBVN42RF.js";
|
|
12
12
|
import {
|
|
13
13
|
applyUnifiedDiffToWorktree,
|
|
14
14
|
assertRepoSnapshotUnchanged,
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
requireCurrentBranch,
|
|
31
31
|
setRemoteOriginUrl,
|
|
32
32
|
summarizeUnifiedDiff
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-S4ECO35X.js";
|
|
34
34
|
import {
|
|
35
35
|
REMIX_ERROR_CODES
|
|
36
36
|
} from "./chunk-GC2MOT3U.js";
|
|
37
37
|
import {
|
|
38
38
|
RemixError
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-7XJGOKEO.js";
|
|
40
40
|
|
|
41
41
|
// src/application/collab/appDeltaCache.ts
|
|
42
42
|
var APP_DELTA_CACHE_TTL_MS = 5e3;
|
|
@@ -47,6 +47,8 @@ function buildAppDeltaCacheKey(appId, payload) {
|
|
|
47
47
|
appId,
|
|
48
48
|
payload.baseHeadHash,
|
|
49
49
|
payload.targetHeadHash ?? "",
|
|
50
|
+
payload.baseRevisionId ?? "",
|
|
51
|
+
payload.targetRevisionId ?? "",
|
|
50
52
|
payload.localSnapshotHash ?? "",
|
|
51
53
|
payload.repoFingerprint ?? "",
|
|
52
54
|
payload.remoteUrl ?? "",
|
|
@@ -189,9 +191,6 @@ function getAsyncJobDir(jobId) {
|
|
|
189
191
|
function getAsyncJobFilePath(jobId) {
|
|
190
192
|
return path.join(getAsyncJobDir(jobId), "job.json");
|
|
191
193
|
}
|
|
192
|
-
function getAsyncJobBundlePath(jobId) {
|
|
193
|
-
return path.join(getAsyncJobDir(jobId), "bundle.bundle");
|
|
194
|
-
}
|
|
195
194
|
function getLogsRoot() {
|
|
196
195
|
return path.join(getCollabStateRoot(), "logs");
|
|
197
196
|
}
|
|
@@ -493,11 +492,11 @@ async function readLocalBaseline(params) {
|
|
|
493
492
|
const raw = await fs2.readFile(getBaselinePath(params), "utf8");
|
|
494
493
|
const parsed = JSON.parse(raw);
|
|
495
494
|
if (!parsed || typeof parsed !== "object") return null;
|
|
496
|
-
if (parsed.schemaVersion
|
|
495
|
+
if (![1, 2].includes(Number(parsed.schemaVersion)) || typeof parsed.key !== "string" || typeof parsed.repoRoot !== "string") {
|
|
497
496
|
return null;
|
|
498
497
|
}
|
|
499
498
|
return {
|
|
500
|
-
schemaVersion: 1,
|
|
499
|
+
schemaVersion: Number(parsed.schemaVersion) === 2 ? 2 : 1,
|
|
501
500
|
key: parsed.key,
|
|
502
501
|
repoRoot: parsed.repoRoot,
|
|
503
502
|
repoFingerprint: parsed.repoFingerprint ?? null,
|
|
@@ -506,6 +505,8 @@ async function readLocalBaseline(params) {
|
|
|
506
505
|
branchName: parsed.branchName ?? null,
|
|
507
506
|
lastSnapshotId: parsed.lastSnapshotId ?? null,
|
|
508
507
|
lastSnapshotHash: parsed.lastSnapshotHash ?? null,
|
|
508
|
+
lastServerRevisionId: parsed.lastServerRevisionId ?? null,
|
|
509
|
+
lastServerTreeHash: parsed.lastServerTreeHash ?? null,
|
|
509
510
|
lastServerHeadHash: parsed.lastServerHeadHash ?? null,
|
|
510
511
|
lastSeenLocalCommitHash: parsed.lastSeenLocalCommitHash ?? null,
|
|
511
512
|
updatedAt: String(parsed.updatedAt ?? "")
|
|
@@ -517,7 +518,7 @@ async function readLocalBaseline(params) {
|
|
|
517
518
|
async function writeLocalBaseline(baseline) {
|
|
518
519
|
const key = buildLaneStateKey(baseline);
|
|
519
520
|
const normalized = {
|
|
520
|
-
schemaVersion:
|
|
521
|
+
schemaVersion: 2,
|
|
521
522
|
key,
|
|
522
523
|
repoRoot: baseline.repoRoot,
|
|
523
524
|
repoFingerprint: baseline.repoFingerprint ?? null,
|
|
@@ -526,6 +527,8 @@ async function writeLocalBaseline(baseline) {
|
|
|
526
527
|
branchName: baseline.branchName ?? null,
|
|
527
528
|
lastSnapshotId: baseline.lastSnapshotId ?? null,
|
|
528
529
|
lastSnapshotHash: baseline.lastSnapshotHash ?? null,
|
|
530
|
+
lastServerRevisionId: baseline.lastServerRevisionId ?? null,
|
|
531
|
+
lastServerTreeHash: baseline.lastServerTreeHash ?? null,
|
|
529
532
|
lastServerHeadHash: baseline.lastServerHeadHash ?? null,
|
|
530
533
|
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash ?? null,
|
|
531
534
|
updatedAt: baseline.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -912,6 +915,7 @@ function normalizeJob2(input) {
|
|
|
912
915
|
prompt: input.prompt,
|
|
913
916
|
assistantResponse: input.assistantResponse,
|
|
914
917
|
baselineSnapshotId: input.baselineSnapshotId ?? null,
|
|
918
|
+
baselineServerRevisionId: input.baselineServerRevisionId ?? null,
|
|
915
919
|
baselineServerHeadHash: input.baselineServerHeadHash ?? null,
|
|
916
920
|
currentSnapshotId: input.currentSnapshotId,
|
|
917
921
|
capturedAt: input.capturedAt ?? now,
|
|
@@ -946,6 +950,7 @@ async function readPendingFinalizeJob(jobId) {
|
|
|
946
950
|
prompt: String(parsed.prompt ?? ""),
|
|
947
951
|
assistantResponse: String(parsed.assistantResponse ?? ""),
|
|
948
952
|
baselineSnapshotId: parsed.baselineSnapshotId ?? null,
|
|
953
|
+
baselineServerRevisionId: parsed.baselineServerRevisionId ?? null,
|
|
949
954
|
baselineServerHeadHash: parsed.baselineServerHeadHash ?? null,
|
|
950
955
|
currentSnapshotId: String(parsed.currentSnapshotId ?? ""),
|
|
951
956
|
capturedAt: parsed.capturedAt,
|
|
@@ -1701,6 +1706,8 @@ function buildBaseState() {
|
|
|
1701
1706
|
branchName: null,
|
|
1702
1707
|
localCommitHash: null,
|
|
1703
1708
|
currentSnapshotHash: null,
|
|
1709
|
+
currentServerRevisionId: null,
|
|
1710
|
+
currentServerTreeHash: null,
|
|
1704
1711
|
currentServerHeadHash: null,
|
|
1705
1712
|
currentServerHeadCommitId: null,
|
|
1706
1713
|
worktreeClean: false,
|
|
@@ -1734,6 +1741,8 @@ function buildBaseState() {
|
|
|
1734
1741
|
baseline: {
|
|
1735
1742
|
lastSnapshotId: null,
|
|
1736
1743
|
lastSnapshotHash: null,
|
|
1744
|
+
lastServerRevisionId: null,
|
|
1745
|
+
lastServerTreeHash: null,
|
|
1737
1746
|
lastServerHeadHash: null,
|
|
1738
1747
|
lastSeenLocalCommitHash: null
|
|
1739
1748
|
}
|
|
@@ -1860,6 +1869,8 @@ async function collabDetectRepoState(params) {
|
|
|
1860
1869
|
summarizeAsyncJobs({ repoRoot, branchName: binding.branchName ?? null })
|
|
1861
1870
|
]);
|
|
1862
1871
|
const appHead = unwrapResponseObject(headResp, "app head");
|
|
1872
|
+
detected.currentServerRevisionId = appHead.headRevisionId ?? null;
|
|
1873
|
+
detected.currentServerTreeHash = appHead.treeHash ?? null;
|
|
1863
1874
|
detected.currentServerHeadHash = appHead.headCommitHash;
|
|
1864
1875
|
detected.currentServerHeadCommitId = appHead.headCommitId;
|
|
1865
1876
|
detected.currentSnapshotHash = inspection.snapshotHash;
|
|
@@ -1868,6 +1879,8 @@ async function collabDetectRepoState(params) {
|
|
|
1868
1879
|
detected.baseline = {
|
|
1869
1880
|
lastSnapshotId: baseline?.lastSnapshotId ?? null,
|
|
1870
1881
|
lastSnapshotHash: baseline?.lastSnapshotHash ?? null,
|
|
1882
|
+
lastServerRevisionId: baseline?.lastServerRevisionId ?? null,
|
|
1883
|
+
lastServerTreeHash: baseline?.lastServerTreeHash ?? null,
|
|
1871
1884
|
lastServerHeadHash: baseline?.lastServerHeadHash ?? null,
|
|
1872
1885
|
lastSeenLocalCommitHash: baseline?.lastSeenLocalCommitHash ?? null
|
|
1873
1886
|
};
|
|
@@ -1877,6 +1890,7 @@ async function collabDetectRepoState(params) {
|
|
|
1877
1890
|
const bootstrapResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
1878
1891
|
baseHeadHash: localCommitHash,
|
|
1879
1892
|
targetHeadHash: appHead.headCommitHash,
|
|
1893
|
+
targetRevisionId: appHead.headRevisionId,
|
|
1880
1894
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
1881
1895
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
1882
1896
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -1899,7 +1913,7 @@ async function collabDetectRepoState(params) {
|
|
|
1899
1913
|
}
|
|
1900
1914
|
}
|
|
1901
1915
|
detected.repoState = "external_local_base_changed";
|
|
1902
|
-
detected.hint = "No local Remix baseline exists for this lane yet. Run `remix collab
|
|
1916
|
+
detected.hint = "No local Remix revision baseline exists for this lane yet. Run `remix collab init` or sync this lane to seed the baseline.";
|
|
1903
1917
|
return detected;
|
|
1904
1918
|
}
|
|
1905
1919
|
const localHeadMovedSinceBaseline = Boolean(baseline.lastSeenLocalCommitHash) && localCommitHash !== baseline.lastSeenLocalCommitHash;
|
|
@@ -1918,7 +1932,30 @@ async function collabDetectRepoState(params) {
|
|
|
1918
1932
|
return detected;
|
|
1919
1933
|
}
|
|
1920
1934
|
const localChanged = inspection.snapshotHash !== baseline.lastSnapshotHash;
|
|
1921
|
-
const
|
|
1935
|
+
const serverHeadChanged = appHead.headCommitHash !== baseline.lastServerHeadHash;
|
|
1936
|
+
const revisionChanged = Boolean(
|
|
1937
|
+
baseline.lastServerRevisionId && (appHead.headRevisionId ?? null) !== baseline.lastServerRevisionId
|
|
1938
|
+
);
|
|
1939
|
+
const equivalentRevisionDrift = revisionChanged && !serverHeadChanged;
|
|
1940
|
+
if (equivalentRevisionDrift) {
|
|
1941
|
+
await writeLocalBaseline({
|
|
1942
|
+
repoRoot,
|
|
1943
|
+
repoFingerprint: binding.repoFingerprint,
|
|
1944
|
+
laneId: binding.laneId,
|
|
1945
|
+
currentAppId: binding.currentAppId,
|
|
1946
|
+
branchName: binding.branchName,
|
|
1947
|
+
lastSnapshotId: baseline.lastSnapshotId,
|
|
1948
|
+
lastSnapshotHash: baseline.lastSnapshotHash,
|
|
1949
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
1950
|
+
lastServerTreeHash: appHead.treeHash ?? baseline.lastServerTreeHash ?? null,
|
|
1951
|
+
lastServerHeadHash: appHead.headCommitHash,
|
|
1952
|
+
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash
|
|
1953
|
+
});
|
|
1954
|
+
detected.baseline.lastServerRevisionId = appHead.headRevisionId ?? null;
|
|
1955
|
+
detected.baseline.lastServerTreeHash = appHead.treeHash ?? baseline.lastServerTreeHash ?? null;
|
|
1956
|
+
detected.baseline.lastServerHeadHash = appHead.headCommitHash;
|
|
1957
|
+
}
|
|
1958
|
+
const serverChanged = serverHeadChanged;
|
|
1922
1959
|
if (!localChanged && !serverChanged) {
|
|
1923
1960
|
detected.repoState = "idle";
|
|
1924
1961
|
return detected;
|
|
@@ -2347,6 +2384,7 @@ function buildWorkspaceMetadata(params) {
|
|
|
2347
2384
|
recordingMode: "boundary_delta",
|
|
2348
2385
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
2349
2386
|
currentSnapshotId: params.currentSnapshotId,
|
|
2387
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
2350
2388
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
2351
2389
|
currentSnapshotHash: params.currentSnapshotHash,
|
|
2352
2390
|
localCommitHash: params.localCommitHash,
|
|
@@ -2425,12 +2463,12 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2425
2463
|
throw buildFinalizeCliError({
|
|
2426
2464
|
message: "Local baseline is missing for this queued finalize job.",
|
|
2427
2465
|
exitCode: 2,
|
|
2428
|
-
hint: "Run `remix collab
|
|
2466
|
+
hint: "Run `remix collab init` to seed this checkout's revision baseline.",
|
|
2429
2467
|
disposition: "terminal",
|
|
2430
2468
|
reason: "baseline_missing"
|
|
2431
2469
|
});
|
|
2432
2470
|
}
|
|
2433
|
-
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
2471
|
+
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || (job.baselineServerRevisionId ? baseline.lastServerRevisionId !== job.baselineServerRevisionId : false) || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
2434
2472
|
const appHead = unwrapResponseObject(appHeadResp, "app head");
|
|
2435
2473
|
const remoteUrl = readMetadataString(job, "remoteUrl");
|
|
2436
2474
|
const defaultBranch = readMetadataString(job, "defaultBranch");
|
|
@@ -2453,12 +2491,13 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2453
2491
|
throw buildFinalizeCliError({
|
|
2454
2492
|
message: "Finalize queue baseline drifted before this job was processed.",
|
|
2455
2493
|
exitCode: 1,
|
|
2456
|
-
hint: "Process queued finalize jobs in capture order, or
|
|
2494
|
+
hint: "Process queued finalize jobs in capture order, or run `remix collab init` to refresh the revision baseline before retrying.",
|
|
2457
2495
|
disposition: "terminal",
|
|
2458
2496
|
reason: "baseline_drifted"
|
|
2459
2497
|
});
|
|
2460
2498
|
}
|
|
2461
|
-
|
|
2499
|
+
const serverStillAtBaseline = job.baselineServerRevisionId ? appHead.headRevisionId === job.baselineServerRevisionId : appHead.headCommitHash === job.baselineServerHeadHash;
|
|
2500
|
+
if (!serverStillAtBaseline) {
|
|
2462
2501
|
throw buildFinalizeCliError({
|
|
2463
2502
|
message: "Server lane changed before a no-diff turn could be recorded.",
|
|
2464
2503
|
exitCode: 2,
|
|
@@ -2480,6 +2519,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2480
2519
|
defaultBranch,
|
|
2481
2520
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2482
2521
|
currentSnapshotId: job.currentSnapshotId,
|
|
2522
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2483
2523
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2484
2524
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2485
2525
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2500,6 +2540,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2500
2540
|
branchName: job.branchName,
|
|
2501
2541
|
lastSnapshotId: snapshot.id,
|
|
2502
2542
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
2543
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
2544
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
2503
2545
|
lastServerHeadHash: appHead.headCommitHash,
|
|
2504
2546
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
2505
2547
|
});
|
|
@@ -2520,14 +2562,14 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2520
2562
|
};
|
|
2521
2563
|
}
|
|
2522
2564
|
const localBaselineAdvanced = baseline.lastSnapshotId !== job.baselineSnapshotId;
|
|
2523
|
-
const serverHeadAdvanced = appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
2565
|
+
const serverHeadAdvanced = job.baselineServerRevisionId ? appHead.headRevisionId !== job.baselineServerRevisionId : appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
2524
2566
|
if (baselineDrifted) {
|
|
2525
2567
|
const consistentAdvance = localBaselineAdvanced && serverHeadAdvanced;
|
|
2526
2568
|
if (!consistentAdvance) {
|
|
2527
2569
|
throw buildFinalizeCliError({
|
|
2528
2570
|
message: `Finalize queue baseline advanced inconsistently before this job was processed (localBaselineAdvanced=${localBaselineAdvanced}, serverHeadAdvanced=${serverHeadAdvanced}, jobBaselineSnapshotId=${job.baselineSnapshotId ?? "null"}, liveBaselineSnapshotId=${baseline.lastSnapshotId ?? "null"}, jobBaselineServerHeadHash=${job.baselineServerHeadHash ?? "null"}, liveBaselineServerHeadHash=${baseline.lastServerHeadHash ?? "null"}, currentAppHeadHash=${appHead.headCommitHash}). This indicates local Remix state diverged from the backend in a way that should not be reachable in normal operation; please report this as a bug.`,
|
|
2529
2571
|
exitCode: 1,
|
|
2530
|
-
hint: "Run `remix collab status` to inspect, then
|
|
2572
|
+
hint: "Run `remix collab status` to inspect, then sync or reconcile before retrying.",
|
|
2531
2573
|
disposition: "terminal",
|
|
2532
2574
|
reason: "baseline_drifted"
|
|
2533
2575
|
});
|
|
@@ -2535,6 +2577,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2535
2577
|
}
|
|
2536
2578
|
let submissionDiff = diffResult.diff;
|
|
2537
2579
|
let submissionBaseHeadHash = job.baselineServerHeadHash;
|
|
2580
|
+
let submissionBaseRevisionId = job.baselineServerRevisionId;
|
|
2538
2581
|
let replayedFromBaseHash = null;
|
|
2539
2582
|
if (!submissionBaseHeadHash) {
|
|
2540
2583
|
throw buildFinalizeCliError({
|
|
@@ -2552,7 +2595,9 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2552
2595
|
assistantResponse: job.assistantResponse,
|
|
2553
2596
|
diff: diffResult.diff,
|
|
2554
2597
|
baseCommitHash: submissionBaseHeadHash,
|
|
2598
|
+
baseRevisionId: job.baselineServerRevisionId,
|
|
2555
2599
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
2600
|
+
targetRevisionId: appHead.headRevisionId,
|
|
2556
2601
|
expectedPaths: diffResult.changedPaths,
|
|
2557
2602
|
actor,
|
|
2558
2603
|
workspaceMetadata: buildWorkspaceMetadata({
|
|
@@ -2562,6 +2607,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2562
2607
|
defaultBranch,
|
|
2563
2608
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2564
2609
|
currentSnapshotId: job.currentSnapshotId,
|
|
2610
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2565
2611
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2566
2612
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2567
2613
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2587,6 +2633,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2587
2633
|
submissionDiff = replayDiff.diff;
|
|
2588
2634
|
replayedFromBaseHash = submissionBaseHeadHash;
|
|
2589
2635
|
submissionBaseHeadHash = appHead.headCommitHash;
|
|
2636
|
+
submissionBaseRevisionId = appHead.headRevisionId;
|
|
2590
2637
|
} catch (error) {
|
|
2591
2638
|
if (error instanceof RemixError && error.finalizeDisposition === void 0) {
|
|
2592
2639
|
const detail = error.hint ? `${error.message} (${error.hint})` : error.message;
|
|
@@ -2608,6 +2655,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2608
2655
|
assistantResponse: job.assistantResponse,
|
|
2609
2656
|
diff: submissionDiff,
|
|
2610
2657
|
baseCommitHash: submissionBaseHeadHash,
|
|
2658
|
+
baseRevisionId: submissionBaseRevisionId,
|
|
2611
2659
|
headCommitHash: submissionBaseHeadHash,
|
|
2612
2660
|
changedFilesCount: diffResult.stats.changedFilesCount,
|
|
2613
2661
|
insertions: diffResult.stats.insertions,
|
|
@@ -2620,6 +2668,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2620
2668
|
defaultBranch,
|
|
2621
2669
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2622
2670
|
currentSnapshotId: job.currentSnapshotId,
|
|
2671
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2623
2672
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2624
2673
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2625
2674
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2641,11 +2690,28 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2641
2690
|
throw buildFinalizeCliError({
|
|
2642
2691
|
message: "Backend returned a succeeded change step without a head commit hash.",
|
|
2643
2692
|
exitCode: 1,
|
|
2644
|
-
hint: "This is a backend invariant violation; retry will not help.
|
|
2693
|
+
hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
|
|
2645
2694
|
disposition: "terminal",
|
|
2646
2695
|
reason: "missing_head_commit_hash"
|
|
2647
2696
|
});
|
|
2648
2697
|
}
|
|
2698
|
+
let nextServerRevisionId = typeof changeStep.resultRevisionId === "string" ? changeStep.resultRevisionId.trim() : "";
|
|
2699
|
+
let nextServerTreeHash = null;
|
|
2700
|
+
if (!nextServerRevisionId) {
|
|
2701
|
+
const freshHeadResp = await params.api.getAppHead(job.currentAppId);
|
|
2702
|
+
const freshHead = unwrapResponseObject(freshHeadResp, "app head");
|
|
2703
|
+
if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
|
|
2704
|
+
throw buildFinalizeCliError({
|
|
2705
|
+
message: "Backend returned a succeeded change step without a matching result revision.",
|
|
2706
|
+
exitCode: 1,
|
|
2707
|
+
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`.",
|
|
2708
|
+
disposition: "terminal",
|
|
2709
|
+
reason: "missing_result_revision_id"
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
nextServerRevisionId = freshHead.headRevisionId;
|
|
2713
|
+
nextServerTreeHash = freshHead.treeHash ?? null;
|
|
2714
|
+
}
|
|
2649
2715
|
await writeLocalBaseline({
|
|
2650
2716
|
repoRoot: job.repoRoot,
|
|
2651
2717
|
repoFingerprint: job.repoFingerprint,
|
|
@@ -2654,6 +2720,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2654
2720
|
branchName: job.branchName,
|
|
2655
2721
|
lastSnapshotId: snapshot.id,
|
|
2656
2722
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
2723
|
+
lastServerRevisionId: nextServerRevisionId,
|
|
2724
|
+
lastServerTreeHash: nextServerTreeHash,
|
|
2657
2725
|
lastServerHeadHash: nextServerHeadHash,
|
|
2658
2726
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
2659
2727
|
});
|
|
@@ -2702,6 +2770,7 @@ async function enqueueCapturedFinalizeTurn(params) {
|
|
|
2702
2770
|
prompt: params.prompt,
|
|
2703
2771
|
assistantResponse: params.assistantResponse,
|
|
2704
2772
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
2773
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
2705
2774
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
2706
2775
|
currentSnapshotId: params.currentSnapshotId,
|
|
2707
2776
|
idempotencyKey: params.idempotencyKey,
|
|
@@ -2802,17 +2871,6 @@ async function collabFinalizeTurn(params) {
|
|
|
2802
2871
|
});
|
|
2803
2872
|
}
|
|
2804
2873
|
}
|
|
2805
|
-
const pendingReAnchor = await findPendingAsyncJob({
|
|
2806
|
-
repoRoot,
|
|
2807
|
-
branchName: binding.branchName ?? null,
|
|
2808
|
-
kind: "re_anchor"
|
|
2809
|
-
});
|
|
2810
|
-
if (pendingReAnchor) {
|
|
2811
|
-
throw new RemixError("Cannot finalize a turn while a re-anchor is still processing.", {
|
|
2812
|
-
exitCode: 2,
|
|
2813
|
-
hint: `Re-anchor job ${pendingReAnchor.id} is still in the background queue. Run \`remix collab status\` to check progress.`
|
|
2814
|
-
});
|
|
2815
|
-
}
|
|
2816
2874
|
const detected = await collabDetectRepoState({
|
|
2817
2875
|
api: params.api,
|
|
2818
2876
|
cwd: repoRoot,
|
|
@@ -2853,9 +2911,16 @@ async function collabFinalizeTurn(params) {
|
|
|
2853
2911
|
hint: detected.hint
|
|
2854
2912
|
});
|
|
2855
2913
|
}
|
|
2914
|
+
if (detected.repoState === "both_changed") {
|
|
2915
|
+
throw new RemixError("Local and server changes must be reconciled before finalizing this turn.", {
|
|
2916
|
+
code: "reconcile_required",
|
|
2917
|
+
exitCode: 2,
|
|
2918
|
+
hint: detected.hint || "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying."
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2856
2921
|
if (detected.repoState === "external_local_base_changed") {
|
|
2857
|
-
throw new RemixError("The local checkout
|
|
2858
|
-
code: "
|
|
2922
|
+
throw new RemixError("The local checkout is missing a Remix revision baseline for this lane.", {
|
|
2923
|
+
code: "baseline_missing",
|
|
2859
2924
|
exitCode: 2,
|
|
2860
2925
|
hint: detected.hint
|
|
2861
2926
|
});
|
|
@@ -2867,8 +2932,9 @@ async function collabFinalizeTurn(params) {
|
|
|
2867
2932
|
});
|
|
2868
2933
|
if (!baseline) {
|
|
2869
2934
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
2935
|
+
code: "baseline_missing",
|
|
2870
2936
|
exitCode: 2,
|
|
2871
|
-
hint: "Run `remix collab
|
|
2937
|
+
hint: "Run `remix collab init` or sync this lane to create a fresh revision baseline."
|
|
2872
2938
|
});
|
|
2873
2939
|
}
|
|
2874
2940
|
const snapshot = await captureLocalSnapshot({
|
|
@@ -2879,10 +2945,11 @@ async function collabFinalizeTurn(params) {
|
|
|
2879
2945
|
});
|
|
2880
2946
|
const mode = snapshot.snapshotHash === baseline.lastSnapshotHash ? "no_diff_turn" : "changed_turn";
|
|
2881
2947
|
const idempotencyKey = params.idempotencyKey?.trim() || buildDeterministicIdempotencyKey({
|
|
2882
|
-
kind: "
|
|
2948
|
+
kind: "collab_finalize_turn_boundary_v2",
|
|
2883
2949
|
appId: binding.currentAppId,
|
|
2884
2950
|
laneId: binding.laneId,
|
|
2885
2951
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
2952
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
2886
2953
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
2887
2954
|
currentSnapshotId: snapshot.id,
|
|
2888
2955
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
@@ -2902,6 +2969,7 @@ async function collabFinalizeTurn(params) {
|
|
|
2902
2969
|
prompt,
|
|
2903
2970
|
assistantResponse,
|
|
2904
2971
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
2972
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
2905
2973
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
2906
2974
|
currentSnapshotId: snapshot.id,
|
|
2907
2975
|
idempotencyKey,
|
|
@@ -2950,9 +3018,10 @@ var FINALIZE_PREFLIGHT_FAILURE_CODES = [
|
|
|
2950
3018
|
// Server has commits we don't. Fix: `remix collab sync` (safe to
|
|
2951
3019
|
// auto-run for fast-forward; non-FF refused by the command itself).
|
|
2952
3020
|
"pull_required",
|
|
2953
|
-
//
|
|
2954
|
-
|
|
2955
|
-
|
|
3021
|
+
// Both local and server changed. Fix: inspect and apply reconcile.
|
|
3022
|
+
"reconcile_required",
|
|
3023
|
+
// Local revision baseline is missing. Fix: `remix collab init` or sync.
|
|
3024
|
+
"baseline_missing"
|
|
2956
3025
|
];
|
|
2957
3026
|
var CODE_SET = new Set(FINALIZE_PREFLIGHT_FAILURE_CODES);
|
|
2958
3027
|
function isFinalizePreflightFailureCode(value) {
|
|
@@ -3011,7 +3080,7 @@ async function collabRecordingPreflight(params) {
|
|
|
3011
3080
|
if (detected.status === "branch_mismatch") return { status: "branch_mismatch", ...base };
|
|
3012
3081
|
if (detected.repoState === "server_only_changed") return { status: "pull_required", ...base };
|
|
3013
3082
|
if (detected.repoState === "both_changed") return { status: "reconcile_required", ...base };
|
|
3014
|
-
if (detected.repoState === "external_local_base_changed") return { status: "
|
|
3083
|
+
if (detected.repoState === "external_local_base_changed") return { status: "baseline_missing", ...base };
|
|
3015
3084
|
return { status: "ready", ...base };
|
|
3016
3085
|
}
|
|
3017
3086
|
|
|
@@ -3238,7 +3307,7 @@ async function ensureWorkspaceMatchesBaseline(params) {
|
|
|
3238
3307
|
if (!baseline?.lastSnapshotHash || !baseline.lastServerHeadHash) {
|
|
3239
3308
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
3240
3309
|
exitCode: 2,
|
|
3241
|
-
hint: "Run `remix collab
|
|
3310
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline before applying server changes."
|
|
3242
3311
|
});
|
|
3243
3312
|
}
|
|
3244
3313
|
const inspection = await inspectLocalSnapshot({
|
|
@@ -3312,11 +3381,12 @@ async function collabSync(params) {
|
|
|
3312
3381
|
const repoSnapshot = await captureRepoSnapshot(repoRoot, { includeWorkspaceDiffHash: true });
|
|
3313
3382
|
const bootstrapFromLocalHead = !detected.baseline.lastSnapshotHash || !detected.baseline.lastServerHeadHash;
|
|
3314
3383
|
let baselineServerHeadHash;
|
|
3384
|
+
let baselineServerRevisionId = null;
|
|
3315
3385
|
if (bootstrapFromLocalHead) {
|
|
3316
3386
|
if (!headCommitHash) {
|
|
3317
3387
|
throw new RemixError("Failed to resolve local HEAD commit for the initial sync bootstrap.", {
|
|
3318
3388
|
exitCode: 1,
|
|
3319
|
-
hint: "Retry after Git HEAD is available, or run `remix collab
|
|
3389
|
+
hint: "Retry after Git HEAD is available, or run `remix collab init` to seed this checkout's revision baseline."
|
|
3320
3390
|
});
|
|
3321
3391
|
}
|
|
3322
3392
|
baselineServerHeadHash = headCommitHash;
|
|
@@ -3331,13 +3401,15 @@ async function collabSync(params) {
|
|
|
3331
3401
|
if (!baseline.lastServerHeadHash) {
|
|
3332
3402
|
throw new RemixError("Local Remix baseline is missing the last acknowledged server head.", {
|
|
3333
3403
|
exitCode: 2,
|
|
3334
|
-
hint: "Run `remix collab
|
|
3404
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline before pulling server changes."
|
|
3335
3405
|
});
|
|
3336
3406
|
}
|
|
3337
3407
|
baselineServerHeadHash = baseline.lastServerHeadHash;
|
|
3408
|
+
baselineServerRevisionId = baseline.lastServerRevisionId;
|
|
3338
3409
|
}
|
|
3339
3410
|
const deltaResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
3340
3411
|
baseHeadHash: baselineServerHeadHash,
|
|
3412
|
+
baseRevisionId: baselineServerRevisionId,
|
|
3341
3413
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
3342
3414
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
3343
3415
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -3361,13 +3433,54 @@ async function collabSync(params) {
|
|
|
3361
3433
|
applied: false,
|
|
3362
3434
|
dryRun: params.dryRun
|
|
3363
3435
|
};
|
|
3364
|
-
if (params.dryRun
|
|
3436
|
+
if (params.dryRun) {
|
|
3365
3437
|
return previewResult;
|
|
3366
3438
|
}
|
|
3439
|
+
if (delta.status === "up_to_date") {
|
|
3440
|
+
if (!bootstrapFromLocalHead) {
|
|
3441
|
+
return previewResult;
|
|
3442
|
+
}
|
|
3443
|
+
return withRepoMutationLock(
|
|
3444
|
+
{
|
|
3445
|
+
cwd: repoRoot,
|
|
3446
|
+
operation: "collabSync"
|
|
3447
|
+
},
|
|
3448
|
+
async ({ repoRoot: lockedRepoRoot, warnings }) => {
|
|
3449
|
+
await assertRepoSnapshotUnchanged(lockedRepoRoot, repoSnapshot, {
|
|
3450
|
+
operation: "`remix collab sync`",
|
|
3451
|
+
recoveryHint: "The repository changed before the first local Remix baseline could be created. Review the local changes and rerun `remix collab sync`."
|
|
3452
|
+
});
|
|
3453
|
+
const snapshot = await captureLocalSnapshot({
|
|
3454
|
+
repoRoot: lockedRepoRoot,
|
|
3455
|
+
repoFingerprint: binding.repoFingerprint,
|
|
3456
|
+
laneId: binding.laneId,
|
|
3457
|
+
branchName: binding.branchName
|
|
3458
|
+
});
|
|
3459
|
+
await writeLocalBaseline({
|
|
3460
|
+
repoRoot: lockedRepoRoot,
|
|
3461
|
+
repoFingerprint: binding.repoFingerprint,
|
|
3462
|
+
laneId: binding.laneId,
|
|
3463
|
+
currentAppId: binding.currentAppId,
|
|
3464
|
+
branchName: binding.branchName,
|
|
3465
|
+
lastSnapshotId: snapshot.id,
|
|
3466
|
+
lastSnapshotHash: snapshot.snapshotHash,
|
|
3467
|
+
lastServerRevisionId: delta.targetRevisionId ?? null,
|
|
3468
|
+
lastServerTreeHash: delta.targetTreeHash ?? null,
|
|
3469
|
+
lastServerHeadHash: delta.targetHeadHash,
|
|
3470
|
+
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3471
|
+
});
|
|
3472
|
+
return {
|
|
3473
|
+
...previewResult,
|
|
3474
|
+
localCommitHash: snapshot.localCommitHash,
|
|
3475
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3367
3480
|
if (delta.status === "base_unknown") {
|
|
3368
3481
|
throw new RemixError("Direct pull is unavailable because Remix can no longer diff from the last acknowledged server head.", {
|
|
3369
3482
|
exitCode: 2,
|
|
3370
|
-
hint: "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying. If this checkout has no local Remix baseline yet for this lane, `remix collab
|
|
3483
|
+
hint: "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying. If this checkout has no local Remix baseline yet for this lane, run `remix collab init` to seed one."
|
|
3371
3484
|
});
|
|
3372
3485
|
}
|
|
3373
3486
|
if (delta.status !== "delta_ready") {
|
|
@@ -3411,6 +3524,8 @@ async function collabSync(params) {
|
|
|
3411
3524
|
branchName: binding.branchName,
|
|
3412
3525
|
lastSnapshotId: snapshot.id,
|
|
3413
3526
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
3527
|
+
lastServerRevisionId: delta.targetRevisionId ?? null,
|
|
3528
|
+
lastServerTreeHash: delta.targetTreeHash ?? null,
|
|
3414
3529
|
lastServerHeadHash: delta.targetHeadHash,
|
|
3415
3530
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3416
3531
|
});
|
|
@@ -3705,6 +3820,8 @@ async function collabCheckout(params) {
|
|
|
3705
3820
|
branchName: branchNameForBaseline,
|
|
3706
3821
|
lastSnapshotId: snapshot.id,
|
|
3707
3822
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
3823
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
3824
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
3708
3825
|
lastServerHeadHash: appHead.headCommitHash,
|
|
3709
3826
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3710
3827
|
});
|
|
@@ -4107,6 +4224,8 @@ async function trySeedEquivalentBranchBaseline(params) {
|
|
|
4107
4224
|
branchName: params.branchName,
|
|
4108
4225
|
lastSnapshotId: snapshot.id,
|
|
4109
4226
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
4227
|
+
lastServerRevisionId: params.appHeadRevisionId,
|
|
4228
|
+
lastServerTreeHash: params.appTreeHash,
|
|
4110
4229
|
lastServerHeadHash: params.appHeadHash,
|
|
4111
4230
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
4112
4231
|
});
|
|
@@ -4118,11 +4237,11 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4118
4237
|
laneId: params.laneId,
|
|
4119
4238
|
repoRoot: params.repoRoot
|
|
4120
4239
|
});
|
|
4121
|
-
if (baseline?.lastSnapshotHash && baseline.lastServerHeadHash) {
|
|
4240
|
+
if (baseline?.lastSnapshotHash && (baseline.lastServerRevisionId || baseline.lastServerHeadHash)) {
|
|
4122
4241
|
return "existing";
|
|
4123
4242
|
}
|
|
4124
4243
|
const localHeadCommitHash = await getHeadCommitHash(params.repoRoot);
|
|
4125
|
-
if (!localHeadCommitHash) return "
|
|
4244
|
+
if (!localHeadCommitHash) return "baseline_missing";
|
|
4126
4245
|
const appHead = unwrapResponseObject(
|
|
4127
4246
|
await params.api.getAppHead(params.currentAppId),
|
|
4128
4247
|
"app head"
|
|
@@ -4149,6 +4268,7 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4149
4268
|
const deltaResp = await params.api.getAppDelta(params.currentAppId, {
|
|
4150
4269
|
baseHeadHash: localHeadCommitHash,
|
|
4151
4270
|
targetHeadHash: appHead.headCommitHash,
|
|
4271
|
+
targetRevisionId: appHead.headRevisionId,
|
|
4152
4272
|
localSnapshotHash,
|
|
4153
4273
|
repoFingerprint: params.repoFingerprint,
|
|
4154
4274
|
remoteUrl: params.remoteUrl ?? void 0,
|
|
@@ -4178,7 +4298,9 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4178
4298
|
upstreamAppId: params.upstreamAppId ?? null,
|
|
4179
4299
|
branchName: params.branchName,
|
|
4180
4300
|
defaultBranch: params.defaultBranch,
|
|
4181
|
-
appHeadHash: appHead.headCommitHash
|
|
4301
|
+
appHeadHash: appHead.headCommitHash,
|
|
4302
|
+
appHeadRevisionId: appHead.headRevisionId ?? null,
|
|
4303
|
+
appTreeHash: appHead.treeHash ?? null
|
|
4182
4304
|
});
|
|
4183
4305
|
if (equivalentBaseline) {
|
|
4184
4306
|
return equivalentBaseline;
|
|
@@ -4186,7 +4308,7 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4186
4308
|
}
|
|
4187
4309
|
} catch {
|
|
4188
4310
|
}
|
|
4189
|
-
return "
|
|
4311
|
+
return "baseline_missing";
|
|
4190
4312
|
}
|
|
4191
4313
|
async function seedImportedInitBaseline(params) {
|
|
4192
4314
|
const appHead = unwrapResponseObject(
|
|
@@ -4207,6 +4329,8 @@ async function seedImportedInitBaseline(params) {
|
|
|
4207
4329
|
branchName: params.branchName,
|
|
4208
4330
|
lastSnapshotId: snapshot.id,
|
|
4209
4331
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
4332
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
4333
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
4210
4334
|
lastServerHeadHash: appHead.headCommitHash,
|
|
4211
4335
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
4212
4336
|
});
|
|
@@ -4220,7 +4344,6 @@ async function collabInit(params) {
|
|
|
4220
4344
|
},
|
|
4221
4345
|
async ({ repoRoot, warnings }) => {
|
|
4222
4346
|
await ensureGitInfoExcludeEntries(repoRoot, [".remix/"]);
|
|
4223
|
-
await ensureCleanWorktree(repoRoot, "`remix collab init`");
|
|
4224
4347
|
if (params.path?.trim()) {
|
|
4225
4348
|
throw new RemixError("`remix collab init --path` is not supported.", {
|
|
4226
4349
|
exitCode: 2,
|
|
@@ -4228,6 +4351,10 @@ async function collabInit(params) {
|
|
|
4228
4351
|
});
|
|
4229
4352
|
}
|
|
4230
4353
|
const localBindingState = await readCollabBindingState(repoRoot, { persist: true });
|
|
4354
|
+
const hasExistingBinding = localBindingState != null && Object.keys(localBindingState.branchBindings ?? {}).length > 0;
|
|
4355
|
+
if (params.forceNew || !hasExistingBinding) {
|
|
4356
|
+
await ensureCleanWorktree(repoRoot, "`remix collab init`");
|
|
4357
|
+
}
|
|
4231
4358
|
const persistedRemoteUrl = normalizeGitRemote(localBindingState?.remoteUrl ?? null);
|
|
4232
4359
|
const currentBranch = await getCurrentBranch(repoRoot);
|
|
4233
4360
|
const defaultBranch = localBindingState?.defaultBranch ?? await getDefaultBranch(repoRoot) ?? currentBranch;
|
|
@@ -4744,7 +4871,7 @@ async function collabInit(params) {
|
|
|
4744
4871
|
operation: "`remix collab init`",
|
|
4745
4872
|
recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
|
|
4746
4873
|
});
|
|
4747
|
-
const bindingMode2 =
|
|
4874
|
+
const bindingMode2 = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
|
|
4748
4875
|
let bindingPath2;
|
|
4749
4876
|
if (params.forceNew && defaultBranch && canonicalLane2) {
|
|
4750
4877
|
const canonicalBinding = branchBindingFromLane(canonicalLane2, "explicit_root", {
|
|
@@ -4786,7 +4913,15 @@ async function collabInit(params) {
|
|
|
4786
4913
|
defaultBranch: canonicalLane2.defaultBranch ?? defaultBranch,
|
|
4787
4914
|
laneId: canonicalLane2.laneId ?? null,
|
|
4788
4915
|
branchName: defaultBranch,
|
|
4789
|
-
|
|
4916
|
+
// This branch is reached only when the CURRENT branch is
|
|
4917
|
+
// not the default branch — so the binding being written
|
|
4918
|
+
// here is for the DEFAULT branch (the canonical/main app).
|
|
4919
|
+
// It must always be `explicit_root` regardless of
|
|
4920
|
+
// `forceNew`; the previous `forceNew ? ... : "lane"`
|
|
4921
|
+
// produced a `lane`-marked default-branch binding for
|
|
4922
|
+
// every plain init, which silently disabled the
|
|
4923
|
+
// history-import auto-spawn.
|
|
4924
|
+
bindingMode: "explicit_root"
|
|
4790
4925
|
});
|
|
4791
4926
|
}
|
|
4792
4927
|
bindingPath2 = await writeCollabBinding(repoRoot, {
|
|
@@ -4938,7 +5073,7 @@ async function collabInit(params) {
|
|
|
4938
5073
|
operation: "`remix collab init`",
|
|
4939
5074
|
recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
|
|
4940
5075
|
});
|
|
4941
|
-
const bindingMode =
|
|
5076
|
+
const bindingMode = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
|
|
4942
5077
|
let bindingPath;
|
|
4943
5078
|
if (params.forceNew && defaultBranch && canonicalLane) {
|
|
4944
5079
|
const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
|
|
@@ -4980,7 +5115,12 @@ async function collabInit(params) {
|
|
|
4980
5115
|
defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
|
|
4981
5116
|
laneId: canonicalLane.laneId ?? null,
|
|
4982
5117
|
branchName: defaultBranch,
|
|
4983
|
-
|
|
5118
|
+
// Same reasoning as the queued-path default-branch write
|
|
5119
|
+
// above: this is the canonical/main-app binding for the
|
|
5120
|
+
// default branch and must be `explicit_root`, otherwise the
|
|
5121
|
+
// history-import auto-spawn (gated on explicit_root) will
|
|
5122
|
+
// silently no-op for every plain init.
|
|
5123
|
+
bindingMode: "explicit_root"
|
|
4984
5124
|
});
|
|
4985
5125
|
}
|
|
4986
5126
|
bindingPath = await writeCollabBinding(repoRoot, {
|
|
@@ -5068,10 +5208,11 @@ async function collabList(params) {
|
|
|
5068
5208
|
};
|
|
5069
5209
|
}
|
|
5070
5210
|
|
|
5071
|
-
// src/application/collab/
|
|
5072
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
5211
|
+
// src/application/collab/collabReconcile.ts
|
|
5073
5212
|
import fs11 from "fs/promises";
|
|
5213
|
+
import os5 from "os";
|
|
5074
5214
|
import path10 from "path";
|
|
5215
|
+
import { execa as execa3 } from "execa";
|
|
5075
5216
|
|
|
5076
5217
|
// src/application/collab/pendingFinalize.ts
|
|
5077
5218
|
function hasPendingFinalize(summary) {
|
|
@@ -5081,258 +5222,7 @@ function buildPendingFinalizeHint() {
|
|
|
5081
5222
|
return "Drain or await the local finalize queue first, then retry after the queued Remix turn finishes recording remotely.";
|
|
5082
5223
|
}
|
|
5083
5224
|
|
|
5084
|
-
// src/application/collab/collabReAnchor.ts
|
|
5085
|
-
async function collabReAnchor(params) {
|
|
5086
|
-
const repoRoot = await findGitRoot(params.cwd);
|
|
5087
|
-
const binding = await ensureActiveLaneBinding({
|
|
5088
|
-
repoRoot,
|
|
5089
|
-
api: params.api,
|
|
5090
|
-
operation: "`remix collab re-anchor`"
|
|
5091
|
-
});
|
|
5092
|
-
if (!binding) {
|
|
5093
|
-
throw new RemixError("Repository is not bound to Remix.", {
|
|
5094
|
-
exitCode: 2,
|
|
5095
|
-
hint: "Run `remix collab init` first."
|
|
5096
|
-
});
|
|
5097
|
-
}
|
|
5098
|
-
const detected = await collabDetectRepoState({
|
|
5099
|
-
api: params.api,
|
|
5100
|
-
cwd: repoRoot,
|
|
5101
|
-
allowBranchMismatch: params.allowBranchMismatch
|
|
5102
|
-
});
|
|
5103
|
-
if (detected.status === "metadata_conflict" || detected.status === "branch_mismatch") {
|
|
5104
|
-
throw new RemixError("Repository must be realigned before seeding a fresh local Remix baseline.", {
|
|
5105
|
-
exitCode: 2,
|
|
5106
|
-
hint: detected.hint
|
|
5107
|
-
});
|
|
5108
|
-
}
|
|
5109
|
-
if (detected.status !== "ready" || !detected.binding) {
|
|
5110
|
-
throw new RemixError(detected.hint || "Repository is not ready for re-anchor.", {
|
|
5111
|
-
exitCode: 2,
|
|
5112
|
-
hint: detected.hint
|
|
5113
|
-
});
|
|
5114
|
-
}
|
|
5115
|
-
if (detected.repoState === "server_only_changed") {
|
|
5116
|
-
throw new RemixError("This checkout is already on a server-known base and only needs a local pull.", {
|
|
5117
|
-
exitCode: 2,
|
|
5118
|
-
hint: "Run `remix collab sync` instead of `remix collab re-anchor`."
|
|
5119
|
-
});
|
|
5120
|
-
}
|
|
5121
|
-
if (detected.repoState === "both_changed") {
|
|
5122
|
-
throw new RemixError("Both the local workspace and the server lane changed since the last agreed baseline.", {
|
|
5123
|
-
exitCode: 2,
|
|
5124
|
-
hint: "Run `remix collab reconcile` to replay the local boundary onto the newer server head."
|
|
5125
|
-
});
|
|
5126
|
-
}
|
|
5127
|
-
if (detected.repoState === "local_only_changed") {
|
|
5128
|
-
if (hasPendingFinalize(detected.pendingFinalize)) {
|
|
5129
|
-
throw new RemixError("Re-anchor is not needed while queued Remix turn recording is still processing.", {
|
|
5130
|
-
exitCode: 2,
|
|
5131
|
-
hint: buildPendingFinalizeHint()
|
|
5132
|
-
});
|
|
5133
|
-
}
|
|
5134
|
-
throw new RemixError("Re-anchor is not the right command for local content changes.", {
|
|
5135
|
-
exitCode: 2,
|
|
5136
|
-
hint: "Remix is source-blind: any local content change since the last recorded turn \u2014 including manual commits, pulls, merges, and rebases \u2014 is recorded with `remix collab finalize-turn`. Use `remix collab re-anchor` only when no local Remix baseline exists yet for this lane (status reports `re_anchor`)."
|
|
5137
|
-
});
|
|
5138
|
-
}
|
|
5139
|
-
if (detected.repoState === "idle") {
|
|
5140
|
-
throw new RemixError("This checkout is already aligned with Remix.", {
|
|
5141
|
-
exitCode: 2,
|
|
5142
|
-
hint: "No re-anchor step is needed. Re-anchor only applies when no local Remix baseline exists yet for this lane."
|
|
5143
|
-
});
|
|
5144
|
-
}
|
|
5145
|
-
await ensureCleanWorktree(repoRoot, "`remix collab re-anchor`");
|
|
5146
|
-
const branch = await requireCurrentBranch(repoRoot);
|
|
5147
|
-
const headCommitHash = await getHeadCommitHash(repoRoot);
|
|
5148
|
-
if (!headCommitHash) {
|
|
5149
|
-
throw new RemixError("Failed to resolve local HEAD commit.", { exitCode: 1 });
|
|
5150
|
-
}
|
|
5151
|
-
if (params.asyncSubmit && !params.dryRun) {
|
|
5152
|
-
const pending = await findPendingAsyncJob({
|
|
5153
|
-
repoRoot,
|
|
5154
|
-
branchName: binding.branchName ?? branch,
|
|
5155
|
-
kind: "re_anchor"
|
|
5156
|
-
});
|
|
5157
|
-
if (pending) {
|
|
5158
|
-
return {
|
|
5159
|
-
status: "queued",
|
|
5160
|
-
queued: true,
|
|
5161
|
-
jobId: pending.id,
|
|
5162
|
-
repoRoot,
|
|
5163
|
-
branch,
|
|
5164
|
-
currentAppId: binding.currentAppId,
|
|
5165
|
-
dryRun: false,
|
|
5166
|
-
applied: false
|
|
5167
|
-
};
|
|
5168
|
-
}
|
|
5169
|
-
}
|
|
5170
|
-
const preflightResp = await params.api.preflightAppReconcile(binding.currentAppId, {
|
|
5171
|
-
localHeadCommitHash: headCommitHash,
|
|
5172
|
-
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5173
|
-
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5174
|
-
defaultBranch: binding.defaultBranch ?? void 0
|
|
5175
|
-
});
|
|
5176
|
-
const preflight = unwrapResponseObject(preflightResp, "reconcile preflight");
|
|
5177
|
-
if (preflight.status === "metadata_conflict") {
|
|
5178
|
-
throw new RemixError("Local repository metadata conflicts with the bound Remix app.", {
|
|
5179
|
-
exitCode: 2,
|
|
5180
|
-
hint: preflight.warnings.join("\n") || "Run the command from the correct bound repository."
|
|
5181
|
-
});
|
|
5182
|
-
}
|
|
5183
|
-
const preview = {
|
|
5184
|
-
status: preflight.status === "up_to_date" ? "reanchored" : "re_anchor_required",
|
|
5185
|
-
repoRoot,
|
|
5186
|
-
branch,
|
|
5187
|
-
currentAppId: binding.currentAppId,
|
|
5188
|
-
localHeadCommitHash: headCommitHash,
|
|
5189
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5190
|
-
targetHeadCommitId: preflight.targetHeadCommitId,
|
|
5191
|
-
warnings: preflight.warnings,
|
|
5192
|
-
applied: false,
|
|
5193
|
-
dryRun: params.dryRun === true
|
|
5194
|
-
};
|
|
5195
|
-
if (params.dryRun) {
|
|
5196
|
-
return preview;
|
|
5197
|
-
}
|
|
5198
|
-
let anchoredServerHeadHash = preflight.targetHeadCommitHash;
|
|
5199
|
-
if (params.asyncSubmit && preflight.status === "ready_to_reconcile") {
|
|
5200
|
-
const failed = await findFailedAsyncJob({
|
|
5201
|
-
repoRoot,
|
|
5202
|
-
branchName: binding.branchName ?? branch,
|
|
5203
|
-
kind: "re_anchor"
|
|
5204
|
-
});
|
|
5205
|
-
if (failed) {
|
|
5206
|
-
await deleteAsyncJob(failed.id);
|
|
5207
|
-
}
|
|
5208
|
-
const { bundlePath: tmpBundlePath, headCommitHash: bundledHeadCommitHash } = await createGitBundle(
|
|
5209
|
-
repoRoot,
|
|
5210
|
-
"re-anchor.bundle"
|
|
5211
|
-
);
|
|
5212
|
-
const tmpBundleDir = path10.dirname(tmpBundlePath);
|
|
5213
|
-
try {
|
|
5214
|
-
const jobId = randomUUID5();
|
|
5215
|
-
const durableBundlePath = getAsyncJobBundlePath(jobId);
|
|
5216
|
-
await fs11.mkdir(getAsyncJobDir(jobId), { recursive: true });
|
|
5217
|
-
try {
|
|
5218
|
-
await fs11.rename(tmpBundlePath, durableBundlePath);
|
|
5219
|
-
} catch (error) {
|
|
5220
|
-
if (error?.code !== "EXDEV") throw error;
|
|
5221
|
-
await fs11.copyFile(tmpBundlePath, durableBundlePath);
|
|
5222
|
-
await fs11.unlink(tmpBundlePath).catch(() => void 0);
|
|
5223
|
-
}
|
|
5224
|
-
const bundleSha = await sha256FileHex(durableBundlePath);
|
|
5225
|
-
const job = await enqueueAsyncJob({
|
|
5226
|
-
id: jobId,
|
|
5227
|
-
kind: "re_anchor",
|
|
5228
|
-
status: "queued",
|
|
5229
|
-
repoRoot,
|
|
5230
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5231
|
-
branchName: binding.branchName ?? branch,
|
|
5232
|
-
laneId: binding.laneId,
|
|
5233
|
-
retryCount: 0,
|
|
5234
|
-
error: null,
|
|
5235
|
-
idempotencyKey: null,
|
|
5236
|
-
payload: {
|
|
5237
|
-
bundlePath: durableBundlePath,
|
|
5238
|
-
bundleSha256: bundleSha,
|
|
5239
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5240
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5241
|
-
appId: binding.currentAppId
|
|
5242
|
-
}
|
|
5243
|
-
});
|
|
5244
|
-
await logDrainerEvent(job.id, "submitted", { kind: "re_anchor" });
|
|
5245
|
-
return {
|
|
5246
|
-
status: "queued",
|
|
5247
|
-
queued: true,
|
|
5248
|
-
jobId: job.id,
|
|
5249
|
-
repoRoot,
|
|
5250
|
-
branch,
|
|
5251
|
-
currentAppId: binding.currentAppId,
|
|
5252
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5253
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5254
|
-
warnings: preflight.warnings,
|
|
5255
|
-
dryRun: false,
|
|
5256
|
-
applied: false
|
|
5257
|
-
};
|
|
5258
|
-
} finally {
|
|
5259
|
-
await fs11.rm(tmpBundleDir, { recursive: true, force: true }).catch(() => void 0);
|
|
5260
|
-
}
|
|
5261
|
-
}
|
|
5262
|
-
if (preflight.status === "ready_to_reconcile") {
|
|
5263
|
-
const { bundlePath, headCommitHash: bundledHeadCommitHash } = await createGitBundle(repoRoot, "re-anchor.bundle");
|
|
5264
|
-
const bundleTempDir = path10.dirname(bundlePath);
|
|
5265
|
-
try {
|
|
5266
|
-
const bundleStat = await fs11.stat(bundlePath);
|
|
5267
|
-
const checksumSha256 = await sha256FileHex(bundlePath);
|
|
5268
|
-
const presignResp = await params.api.presignImportUploadFirstParty({
|
|
5269
|
-
file: {
|
|
5270
|
-
name: path10.basename(bundlePath),
|
|
5271
|
-
mimeType: "application/x-git-bundle",
|
|
5272
|
-
size: bundleStat.size,
|
|
5273
|
-
checksumSha256
|
|
5274
|
-
}
|
|
5275
|
-
});
|
|
5276
|
-
const uploadTarget = unwrapResponseObject(presignResp, "import upload target");
|
|
5277
|
-
await uploadPresigned({
|
|
5278
|
-
uploadUrl: String(uploadTarget.uploadUrl),
|
|
5279
|
-
filePath: bundlePath,
|
|
5280
|
-
headers: uploadTarget.headers ?? {}
|
|
5281
|
-
});
|
|
5282
|
-
const startResp = await params.api.startAppReconcile(binding.currentAppId, {
|
|
5283
|
-
uploadId: String(uploadTarget.uploadId),
|
|
5284
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5285
|
-
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5286
|
-
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5287
|
-
defaultBranch: binding.defaultBranch ?? void 0,
|
|
5288
|
-
idempotencyKey: buildDeterministicIdempotencyKey({
|
|
5289
|
-
kind: "collab_re_anchor_v1",
|
|
5290
|
-
appId: binding.currentAppId,
|
|
5291
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5292
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash
|
|
5293
|
-
})
|
|
5294
|
-
});
|
|
5295
|
-
const started = unwrapResponseObject(startResp, "reconcile");
|
|
5296
|
-
const reconcile = await pollReconcile(params.api, binding.currentAppId, started.id);
|
|
5297
|
-
anchoredServerHeadHash = reconcile.reconciledHeadCommitHash ?? reconcile.targetHeadCommitHash ?? preflight.targetHeadCommitHash;
|
|
5298
|
-
} finally {
|
|
5299
|
-
await fs11.rm(bundleTempDir, { recursive: true, force: true });
|
|
5300
|
-
}
|
|
5301
|
-
}
|
|
5302
|
-
const snapshot = await captureLocalSnapshot({
|
|
5303
|
-
repoRoot,
|
|
5304
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5305
|
-
laneId: binding.laneId,
|
|
5306
|
-
branchName: binding.branchName
|
|
5307
|
-
});
|
|
5308
|
-
await writeLocalBaseline({
|
|
5309
|
-
repoRoot,
|
|
5310
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5311
|
-
laneId: binding.laneId,
|
|
5312
|
-
currentAppId: binding.currentAppId,
|
|
5313
|
-
branchName: binding.branchName,
|
|
5314
|
-
lastSnapshotId: snapshot.id,
|
|
5315
|
-
lastSnapshotHash: snapshot.snapshotHash,
|
|
5316
|
-
lastServerHeadHash: anchoredServerHeadHash,
|
|
5317
|
-
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
5318
|
-
});
|
|
5319
|
-
return {
|
|
5320
|
-
...preview,
|
|
5321
|
-
status: "reanchored",
|
|
5322
|
-
targetHeadCommitHash: anchoredServerHeadHash,
|
|
5323
|
-
applied: true,
|
|
5324
|
-
dryRun: false
|
|
5325
|
-
};
|
|
5326
|
-
}
|
|
5327
|
-
async function collabReAnchorSubmit(params) {
|
|
5328
|
-
return collabReAnchor({ ...params, asyncSubmit: true });
|
|
5329
|
-
}
|
|
5330
|
-
|
|
5331
5225
|
// src/application/collab/collabReconcile.ts
|
|
5332
|
-
import fs12 from "fs/promises";
|
|
5333
|
-
import os5 from "os";
|
|
5334
|
-
import path11 from "path";
|
|
5335
|
-
import { execa as execa3 } from "execa";
|
|
5336
5226
|
async function reconcileBothChanged(params) {
|
|
5337
5227
|
const repoRoot = await findGitRoot(params.cwd);
|
|
5338
5228
|
const binding = await ensureActiveLaneBinding({
|
|
@@ -5355,7 +5245,7 @@ async function reconcileBothChanged(params) {
|
|
|
5355
5245
|
if (!baseline?.lastSnapshotId || !baseline.lastServerHeadHash) {
|
|
5356
5246
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
5357
5247
|
exitCode: 2,
|
|
5358
|
-
hint: "Run `remix collab
|
|
5248
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline first."
|
|
5359
5249
|
});
|
|
5360
5250
|
}
|
|
5361
5251
|
const currentSnapshot = await captureLocalSnapshot({
|
|
@@ -5378,6 +5268,7 @@ async function reconcileBothChanged(params) {
|
|
|
5378
5268
|
params.api.getAppHead(binding.currentAppId),
|
|
5379
5269
|
params.api.getAppDelta(binding.currentAppId, {
|
|
5380
5270
|
baseHeadHash: baseline.lastServerHeadHash,
|
|
5271
|
+
baseRevisionId: baseline.lastServerRevisionId,
|
|
5381
5272
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5382
5273
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5383
5274
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -5395,7 +5286,7 @@ async function reconcileBothChanged(params) {
|
|
|
5395
5286
|
if (delta.status === "base_unknown") {
|
|
5396
5287
|
throw new RemixError("Reconcile cannot pull the newer server state from the last acknowledged baseline.", {
|
|
5397
5288
|
exitCode: 2,
|
|
5398
|
-
hint: "Run `remix collab
|
|
5289
|
+
hint: "Run `remix collab init` to seed a fresh revision baseline for this checkout before retrying."
|
|
5399
5290
|
});
|
|
5400
5291
|
}
|
|
5401
5292
|
if (delta.status !== "delta_ready" && delta.status !== "up_to_date") {
|
|
@@ -5426,7 +5317,9 @@ async function reconcileBothChanged(params) {
|
|
|
5426
5317
|
assistantResponse: "Replay the local boundary delta onto the latest server head without recording a new change step.",
|
|
5427
5318
|
diff: diffResult.diff,
|
|
5428
5319
|
baseCommitHash: baseline.lastServerHeadHash,
|
|
5320
|
+
baseRevisionId: baseline.lastServerRevisionId,
|
|
5429
5321
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
5322
|
+
targetRevisionId: appHead.headRevisionId,
|
|
5430
5323
|
expectedPaths: diffResult.changedPaths,
|
|
5431
5324
|
workspaceMetadata: {
|
|
5432
5325
|
recordingMode: "boundary_delta",
|
|
@@ -5434,6 +5327,7 @@ async function reconcileBothChanged(params) {
|
|
|
5434
5327
|
branch,
|
|
5435
5328
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
5436
5329
|
currentSnapshotId: currentSnapshot.id,
|
|
5330
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
5437
5331
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
5438
5332
|
currentSnapshotHash: currentSnapshot.snapshotHash,
|
|
5439
5333
|
localCommitHash: currentSnapshot.localCommitHash,
|
|
@@ -5452,12 +5346,12 @@ async function reconcileBothChanged(params) {
|
|
|
5452
5346
|
const replay = await pollChangeStepReplay(params.api, binding.currentAppId, String(replayStart.id));
|
|
5453
5347
|
const replayDiffResp = await params.api.getChangeStepReplayDiff(binding.currentAppId, replay.id);
|
|
5454
5348
|
const replayDiff = unwrapResponseObject(replayDiffResp, "change step replay diff");
|
|
5455
|
-
const tempRoot = await
|
|
5349
|
+
const tempRoot = await fs11.mkdtemp(path10.join(os5.tmpdir(), "remix-reconcile-"));
|
|
5456
5350
|
let serverHeadSnapshot = null;
|
|
5457
5351
|
let mergedSnapshot = null;
|
|
5458
5352
|
try {
|
|
5459
|
-
const tempRepoRoot =
|
|
5460
|
-
await
|
|
5353
|
+
const tempRepoRoot = path10.join(tempRoot, "repo");
|
|
5354
|
+
await fs11.mkdir(tempRepoRoot, { recursive: true });
|
|
5461
5355
|
await execa3("git", ["init"], { cwd: tempRepoRoot, stderr: "ignore" });
|
|
5462
5356
|
await materializeLocalSnapshot(baseline.lastSnapshotId, tempRepoRoot);
|
|
5463
5357
|
if (delta.status === "delta_ready" && delta.diff.trim()) {
|
|
@@ -5479,7 +5373,7 @@ async function reconcileBothChanged(params) {
|
|
|
5479
5373
|
branchName: binding.branchName
|
|
5480
5374
|
});
|
|
5481
5375
|
} finally {
|
|
5482
|
-
await
|
|
5376
|
+
await fs11.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
5483
5377
|
}
|
|
5484
5378
|
if (!serverHeadSnapshot || !mergedSnapshot) {
|
|
5485
5379
|
throw new RemixError("Failed to materialize the reconciled local workspace.", { exitCode: 1 });
|
|
@@ -5516,6 +5410,8 @@ async function reconcileBothChanged(params) {
|
|
|
5516
5410
|
branchName: binding.branchName,
|
|
5517
5411
|
lastSnapshotId: serverHeadSnapshot.id,
|
|
5518
5412
|
lastSnapshotHash: serverHeadSnapshot.snapshotHash,
|
|
5413
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
5414
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
5519
5415
|
lastServerHeadHash: appHead.headCommitHash,
|
|
5520
5416
|
lastSeenLocalCommitHash: restoredSnapshot.localCommitHash
|
|
5521
5417
|
});
|
|
@@ -5551,7 +5447,10 @@ async function collabReconcile(params) {
|
|
|
5551
5447
|
return reconcileBothChanged(params);
|
|
5552
5448
|
}
|
|
5553
5449
|
if (detected.repoState === "external_local_base_changed") {
|
|
5554
|
-
|
|
5450
|
+
throw new RemixError("This checkout needs a local Remix revision baseline before reconciliation.", {
|
|
5451
|
+
exitCode: 2,
|
|
5452
|
+
hint: detected.hint || "Run `remix collab init` or `remix collab sync` to seed the baseline."
|
|
5453
|
+
});
|
|
5555
5454
|
}
|
|
5556
5455
|
if (detected.repoState === "local_only_changed") {
|
|
5557
5456
|
if (hasPendingFinalize(detected.pendingFinalize)) {
|
|
@@ -5662,6 +5561,8 @@ async function collabRemix(params) {
|
|
|
5662
5561
|
branchName: branchNameForBaseline,
|
|
5663
5562
|
lastSnapshotId: snapshot.id,
|
|
5664
5563
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
5564
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
5565
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
5665
5566
|
lastServerHeadHash: appHead.headCommitHash,
|
|
5666
5567
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
5667
5568
|
});
|
|
@@ -5786,11 +5687,15 @@ function createBaseStatus() {
|
|
|
5786
5687
|
baseline: {
|
|
5787
5688
|
lastSnapshotId: null,
|
|
5788
5689
|
lastSnapshotHash: null,
|
|
5690
|
+
lastServerRevisionId: null,
|
|
5691
|
+
lastServerTreeHash: null,
|
|
5789
5692
|
lastServerHeadHash: null,
|
|
5790
5693
|
lastSeenLocalCommitHash: null
|
|
5791
5694
|
},
|
|
5792
5695
|
current: {
|
|
5793
5696
|
snapshotHash: null,
|
|
5697
|
+
serverRevisionId: null,
|
|
5698
|
+
serverTreeHash: null,
|
|
5794
5699
|
serverHeadHash: null,
|
|
5795
5700
|
serverHeadCommitId: null,
|
|
5796
5701
|
localCommitHash: null
|
|
@@ -5877,6 +5782,8 @@ async function collabStatus(params) {
|
|
|
5877
5782
|
status.alignment.baseline = detected.baseline;
|
|
5878
5783
|
status.alignment.current = {
|
|
5879
5784
|
snapshotHash: detected.currentSnapshotHash,
|
|
5785
|
+
serverRevisionId: detected.currentServerRevisionId,
|
|
5786
|
+
serverTreeHash: detected.currentServerTreeHash,
|
|
5880
5787
|
serverHeadHash: detected.currentServerHeadHash,
|
|
5881
5788
|
serverHeadCommitId: detected.currentServerHeadCommitId,
|
|
5882
5789
|
localCommitHash: detected.localCommitHash
|
|
@@ -5940,7 +5847,7 @@ async function collabStatus(params) {
|
|
|
5940
5847
|
status.reconcile.canApply = !status.repo.branchMismatch;
|
|
5941
5848
|
status.recommendedAction = "reconcile";
|
|
5942
5849
|
} else if (detected.repoState === "external_local_base_changed") {
|
|
5943
|
-
status.recommendedAction = "
|
|
5850
|
+
status.recommendedAction = "init";
|
|
5944
5851
|
addBlockedReason(status.sync, "baseline_missing");
|
|
5945
5852
|
addBlockedReason(status.reconcile, "baseline_missing");
|
|
5946
5853
|
} else if (detected.repoState === "local_only_changed") {
|
|
@@ -6104,8 +6011,8 @@ async function collabView(params) {
|
|
|
6104
6011
|
}
|
|
6105
6012
|
|
|
6106
6013
|
// src/application/collab/collabAsyncProcessing.ts
|
|
6107
|
-
import
|
|
6108
|
-
import
|
|
6014
|
+
import fs12 from "fs/promises";
|
|
6015
|
+
import path11 from "path";
|
|
6109
6016
|
var MAX_TRANSIENT_RETRIES = 5;
|
|
6110
6017
|
var TRANSIENT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
6111
6018
|
"ECONNREFUSED",
|
|
@@ -6201,10 +6108,10 @@ async function processInitJob(job, api) {
|
|
|
6201
6108
|
try {
|
|
6202
6109
|
await updateAsyncJob(job.id, { status: "submitting", error: null });
|
|
6203
6110
|
await logDrainerEvent(job.id, "claimed", { kind: "init" });
|
|
6204
|
-
const bundleStat = await
|
|
6111
|
+
const bundleStat = await fs12.stat(job.payload.bundlePath);
|
|
6205
6112
|
const presignResp = await api.presignImportUploadFirstParty({
|
|
6206
6113
|
file: {
|
|
6207
|
-
name:
|
|
6114
|
+
name: path11.basename(job.payload.bundlePath),
|
|
6208
6115
|
mimeType: "application/x-git-bundle",
|
|
6209
6116
|
size: bundleStat.size,
|
|
6210
6117
|
checksumSha256: job.payload.bundleSha256
|
|
@@ -6221,7 +6128,7 @@ async function processInitJob(job, api) {
|
|
|
6221
6128
|
await updateAsyncJob(job.id, { status: "server_processing" });
|
|
6222
6129
|
const importResp = await api.importFromUploadFirstParty({
|
|
6223
6130
|
uploadId: String(presign.uploadId),
|
|
6224
|
-
appName: job.payload.appName?.trim() ||
|
|
6131
|
+
appName: job.payload.appName?.trim() || path11.basename(job.repoRoot),
|
|
6225
6132
|
platform: "generic",
|
|
6226
6133
|
isPublic: false,
|
|
6227
6134
|
branch: job.payload.defaultBranch && job.branchName && job.branchName !== job.payload.defaultBranch ? job.payload.defaultBranch : job.branchName ?? void 0,
|
|
@@ -6296,7 +6203,7 @@ async function processInitJob(job, api) {
|
|
|
6296
6203
|
boundProjectId = String(readyApp.projectId ?? boundProjectId);
|
|
6297
6204
|
boundThreadId = readyApp.threadId ? String(readyApp.threadId) : boundThreadId;
|
|
6298
6205
|
}
|
|
6299
|
-
const bindingMode =
|
|
6206
|
+
const bindingMode = defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root";
|
|
6300
6207
|
if (job.payload.forceNew && defaultBranch && canonicalLane) {
|
|
6301
6208
|
const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
|
|
6302
6209
|
projectId: canonicalLane.projectId ?? boundProjectId,
|
|
@@ -6335,7 +6242,12 @@ async function processInitJob(job, api) {
|
|
|
6335
6242
|
defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
|
|
6336
6243
|
laneId: canonicalLane.laneId ?? null,
|
|
6337
6244
|
branchName: defaultBranch,
|
|
6338
|
-
|
|
6245
|
+
// This branch is reached only when the current branch is NOT
|
|
6246
|
+
// the default branch — so the binding being written here is
|
|
6247
|
+
// for the DEFAULT branch (the canonical/main app). It must
|
|
6248
|
+
// always be `explicit_root` so the history-import auto-spawn
|
|
6249
|
+
// can fire on first-ever inits (see autoSpawnHistoryImport.ts).
|
|
6250
|
+
bindingMode: "explicit_root"
|
|
6339
6251
|
});
|
|
6340
6252
|
}
|
|
6341
6253
|
await writeCollabBinding(repoRoot, {
|
|
@@ -6410,7 +6322,7 @@ async function processInitPostJob(job, api) {
|
|
|
6410
6322
|
if (outcome.status === "failed") {
|
|
6411
6323
|
const bindingPath = getCollabBindingPath(job.repoRoot);
|
|
6412
6324
|
try {
|
|
6413
|
-
await
|
|
6325
|
+
await fs12.unlink(bindingPath);
|
|
6414
6326
|
await logDrainerEvent(job.id, "binding_cleared", {
|
|
6415
6327
|
kind: "init_post",
|
|
6416
6328
|
appId: job.payload.appId,
|
|
@@ -6451,10 +6363,10 @@ async function processReAnchorJob(job, api) {
|
|
|
6451
6363
|
}
|
|
6452
6364
|
let anchoredServerHeadHash = preflight.targetHeadCommitHash;
|
|
6453
6365
|
if (preflight.status === "ready_to_reconcile") {
|
|
6454
|
-
const bundleStat = await
|
|
6366
|
+
const bundleStat = await fs12.stat(job.payload.bundlePath);
|
|
6455
6367
|
const presignResp = await api.presignImportUploadFirstParty({
|
|
6456
6368
|
file: {
|
|
6457
|
-
name:
|
|
6369
|
+
name: path11.basename(job.payload.bundlePath),
|
|
6458
6370
|
mimeType: "application/x-git-bundle",
|
|
6459
6371
|
size: bundleStat.size,
|
|
6460
6372
|
checksumSha256: job.payload.bundleSha256
|
|
@@ -6558,9 +6470,9 @@ async function collabReAnchorProcess(jobId, opts) {
|
|
|
6558
6470
|
}
|
|
6559
6471
|
async function acquireDrainerPidLock() {
|
|
6560
6472
|
const pidPath = getDrainerPidPath();
|
|
6561
|
-
await
|
|
6473
|
+
await fs12.mkdir(path11.dirname(pidPath), { recursive: true });
|
|
6562
6474
|
try {
|
|
6563
|
-
const existing = await
|
|
6475
|
+
const existing = await fs12.readFile(pidPath, "utf8").catch(() => "");
|
|
6564
6476
|
const existingPid = parseInt(existing.trim(), 10);
|
|
6565
6477
|
if (Number.isFinite(existingPid) && existingPid > 0 && existingPid !== process.pid) {
|
|
6566
6478
|
try {
|
|
@@ -6570,13 +6482,13 @@ async function acquireDrainerPidLock() {
|
|
|
6570
6482
|
if (error?.code !== "ESRCH") return null;
|
|
6571
6483
|
}
|
|
6572
6484
|
}
|
|
6573
|
-
await
|
|
6485
|
+
await fs12.writeFile(pidPath, String(process.pid), "utf8");
|
|
6574
6486
|
return {
|
|
6575
6487
|
release: async () => {
|
|
6576
6488
|
try {
|
|
6577
|
-
const current = (await
|
|
6489
|
+
const current = (await fs12.readFile(pidPath, "utf8")).trim();
|
|
6578
6490
|
if (current === String(process.pid)) {
|
|
6579
|
-
await
|
|
6491
|
+
await fs12.unlink(pidPath).catch(() => void 0);
|
|
6580
6492
|
}
|
|
6581
6493
|
} catch {
|
|
6582
6494
|
}
|
|
@@ -6627,9 +6539,6 @@ export {
|
|
|
6627
6539
|
collabList,
|
|
6628
6540
|
collabListMembers,
|
|
6629
6541
|
collabListMergeRequests,
|
|
6630
|
-
collabReAnchor,
|
|
6631
|
-
collabReAnchorProcess,
|
|
6632
|
-
collabReAnchorSubmit,
|
|
6633
6542
|
collabReconcile,
|
|
6634
6543
|
collabRecordingPreflight,
|
|
6635
6544
|
collabReject,
|