opencode-swarm 7.200.0 → 7.200.2
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/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/cli/{attest-task-8ekwny8v.js → attest-task-s9cpm1pa.js} +42 -42
- package/dist/cli/{auto-checkpoint-dqjk82s7.js → auto-checkpoint-bhtc5gjf.js} +7 -7
- package/dist/cli/{checkpoint-2ahps2sp.js → checkpoint-1jmfejsh.js} +17 -15
- package/dist/cli/{coder-settlement-1dk37pa9.js → coder-settlement-qabkaq79.js} +10 -10
- package/dist/cli/{config-doctor-73rctg85.js → config-doctor-q4805y29.js} +13 -13
- package/dist/cli/{core-y5mfws8v.js → core-jtmr767v.js} +3 -3
- package/dist/cli/{curation-policy-43gq6t0y.js → curation-policy-fth5n93x.js} +4 -4
- package/dist/cli/{curator-m278hyrq.js → curator-4tqbp1re.js} +41 -41
- package/dist/cli/{curator-drift-k7j9rcgp.js → curator-drift-681mc3a2.js} +8 -8
- package/dist/cli/curator-llm-factory-46bzwbzr.js +82 -0
- package/dist/cli/{doctor-knowledge-receipt-phase-id-1m7pkzvt.js → doctor-knowledge-receipt-phase-id-h16qsh6e.js} +7 -7
- package/dist/cli/{evidence-summary-service-19k8x1g9.js → evidence-summary-service-qr8kf99s.js} +17 -17
- package/dist/cli/{gate-evidence-hche0hf8.js → gate-evidence-phq4rs28.js} +5 -5
- package/dist/cli/guardrail-explain-q2eqawnk.js +83 -0
- package/dist/cli/{guardrail-log-tvbgnxns.js → guardrail-log-jk30vszs.js} +6 -6
- package/dist/cli/{guardrail-reset-v8kvh9cn.js → guardrail-reset-0sfceecs.js} +41 -41
- package/dist/cli/{hive-promoter-bqfa763z.js → hive-promoter-8v9vcd75.js} +41 -41
- package/dist/cli/{index-kwfhr3mz.js → index-048p7deh.js} +1 -1
- package/dist/cli/{index-wfb6vrc2.js → index-0jmt8y9r.js} +2 -2
- package/dist/cli/{index-jbm301bq.js → index-1feqa2vz.js} +41 -41
- package/dist/cli/{index-bp476gwc.js → index-1v277wxc.js} +2 -2
- package/dist/cli/{index-yss701b3.js → index-43scxm6a.js} +4 -4
- package/dist/cli/{index-9nmrw7ek.js → index-4cw3w8qm.js} +3 -3
- package/dist/cli/{index-vqtca3xv.js → index-4ts6jpmj.js} +2 -2
- package/dist/cli/{index-tv9c3b3q.js → index-56w2vy52.js} +3 -3
- package/dist/cli/{index-0wg5q595.js → index-5snwc3dz.js} +1 -1
- package/dist/cli/{index-m9j9varx.js → index-ab1k780s.js} +3 -3
- package/dist/cli/{index-r02aqt0a.js → index-b4kdms7x.js} +2 -2
- package/dist/cli/{index-7v74n9a1.js → index-bncex5hw.js} +3 -3
- package/dist/cli/{index-qnabg1wp.js → index-bt2h69a2.js} +6 -6
- package/dist/cli/{index-md2180vx.js → index-bw6q2v41.js} +3 -3
- package/dist/cli/{index-q1x73nnd.js → index-epzadpmm.js} +1 -1
- package/dist/cli/{index-71bqn1nn.js → index-evf5t4ba.js} +7 -7
- package/dist/cli/{index-511hw14d.js → index-f50e1pn3.js} +43 -43
- package/dist/cli/{index-dng8z8me.js → index-g57p8p1k.js} +1 -1
- package/dist/cli/{index-p5m71yph.js → index-gpr3j0w1.js} +4 -4
- package/dist/cli/{index-hxp5jgd8.js → index-gss7ch5k.js} +2 -2
- package/dist/cli/{index-rfvnq6wd.js → index-h29ckdg5.js} +8 -8
- package/dist/cli/{index-qnst0jwc.js → index-kezh6nhq.js} +3 -3
- package/dist/cli/{index-e8m8bb1r.js → index-mrq61h10.js} +4 -4
- package/dist/cli/{index-vmevv6my.js → index-na6ph1fp.js} +1 -1
- package/dist/cli/{index-n5k81wym.js → index-nbg19t1b.js} +7 -7
- package/dist/cli/{index-8rrs1nc0.js → index-ngzt2xmr.js} +2 -2
- package/dist/cli/{index-wr0n2w3z.js → index-nzfq1ms2.js} +1 -1
- package/dist/cli/{index-sk1rva12.js → index-pktvge7a.js} +2 -2
- package/dist/cli/{index-gwc7jahh.js → index-pvpeggb5.js} +1 -1
- package/dist/cli/{index-58cr1xg0.js → index-rav29yb2.js} +3 -3
- package/dist/cli/{index-nq3zcrhm.js → index-s3w41p1x.js} +1 -1
- package/dist/cli/{index-99zt998b.js → index-s9y49avf.js} +1 -1
- package/dist/cli/{index-22atsy06.js → index-sv514p10.js} +918 -154
- package/dist/cli/{index-ac8vdmgv.js → index-traz987v.js} +1 -1
- package/dist/cli/{index-1rfgzsaw.js → index-trvn0kg0.js} +4 -4
- package/dist/cli/{index-jy0mgzjq.js → index-v1h70r3y.js} +1 -1
- package/dist/cli/{index-d083d1q7.js → index-ves1rtqt.js} +1 -1
- package/dist/cli/{index-x25rhkmk.js → index-vrvcn7k1.js} +4 -4
- package/dist/cli/{index-8a3aasag.js → index-wgf1780e.js} +5 -5
- package/dist/cli/{index-pyv8vzkx.js → index-xfkzwc4p.js} +5 -5
- package/dist/cli/{index-x1z5d5kj.js → index-xyvsdn07.js} +1 -1
- package/dist/cli/{index-hg4qa0aq.js → index-y7khxj1g.js} +1940 -2357
- package/dist/cli/{index-yvv0my1a.js → index-yctvn9z9.js} +5 -3
- package/dist/cli/{index-35tf03js.js → index-yk3b7ywa.js} +10 -10
- package/dist/cli/{index-1tpz99db.js → index-ypxq0x7r.js} +1 -1
- package/dist/cli/index.js +42 -42
- package/dist/cli/{knowledge-escalator-qcra58bx.js → knowledge-escalator-53n7wa7s.js} +12 -12
- package/dist/cli/{knowledge-events-9mnx1a3f.js → knowledge-events-80fw8pf6.js} +10 -10
- package/dist/cli/{knowledge-link-2jsn9f6a.js → knowledge-link-wfhsfyn2.js} +3 -3
- package/dist/cli/{knowledge-store-fdneqf7z.js → knowledge-store-rh1v0509.js} +4 -4
- package/dist/cli/{knowledge-validator-7nj987ga.js → knowledge-validator-c3926fw5.js} +6 -6
- package/dist/cli/{mcp-0g34wmz4.js → mcp-r43tvhzy.js} +1 -1
- package/dist/cli/{model-preflight-g11ykgc0.js → model-preflight-vx9px5ac.js} +41 -41
- package/dist/cli/{pending-delegations-5vme18wx.js → pending-delegations-0vbkv5d2.js} +11 -11
- package/dist/cli/{pr-subscriptions-cbx5p838.js → pr-subscriptions-a6cm44fs.js} +10 -10
- package/dist/cli/{pr-workflow-gate-376gw6yj.js → pr-workflow-gate-yvgpmywz.js} +41 -41
- package/dist/cli/{runner-vvd26z2z.js → runner-s1a73y5h.js} +4 -4
- package/dist/cli/{scan-cursor-x3j5wvfm.js → scan-cursor-pk4h80c1.js} +5 -5
- package/dist/cli/{schema-d98zzrpn.js → schema-5rxfybx2.js} +1 -1
- package/dist/cli/{scope-persistence-vkb8z4hc.js → scope-persistence-qzs1f91v.js} +13 -13
- package/dist/cli/{server-25svy6cb.js → server-gdewbeyb.js} +41 -41
- package/dist/cli/{skill-generator-ktz33dt6.js → skill-generator-xh2w2pvq.js} +14 -14
- package/dist/cli/{snapshot-coordination-init-74x3psxc.js → snapshot-coordination-init-jn2rh2s8.js} +41 -41
- package/dist/cli/{speckit-checkoff-9t6cdn4a.js → speckit-checkoff-98zsw8c4.js} +4 -4
- package/dist/cli/{workspace-snapshot-afqvh9bq.js → workspace-snapshot-93gzfac2.js} +3 -3
- package/dist/cli/{worktree-collision-ownership-7a1n1f9j.js → worktree-collision-ownership-2xqxqp0x.js} +12 -12
- package/dist/cli/{worktree-isolation-8snw7w34.js → worktree-isolation-rxpwxvw3.js} +41 -41
- package/dist/commands/close/internals.d.ts +1 -1
- package/dist/commands/close/orchestrator.d.ts +7 -0
- package/dist/commands/destructive-purge.d.ts +15 -0
- package/dist/commands/git-status-read.d.ts +23 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/rollback-gate.d.ts +77 -10
- package/dist/config/schema.d.ts +2 -0
- package/dist/full-auto/code-mode-program.d.ts +31 -0
- package/dist/full-auto/critic-tools.d.ts +18 -0
- package/dist/full-auto/policy.d.ts +17 -0
- package/dist/git/branch.d.ts +22 -0
- package/dist/hooks/code-mode-wrapper.d.ts +28 -0
- package/dist/hooks/full-auto-intercept.d.ts +5 -0
- package/dist/hooks/full-auto-permission.d.ts +25 -0
- package/dist/hooks/pr-workflow-gate.d.ts +21 -1
- package/dist/host/v2/hook-budget.d.ts +37 -0
- package/dist/host/v2/hooks.d.ts +9 -0
- package/dist/host/v2/setup.d.ts +14 -0
- package/dist/index.js +77 -70
- package/dist/session/snapshot-coordination-init.d.ts +34 -0
- package/dist/session/snapshot-writer.d.ts +5 -1
- package/dist/tools/checkpoint.d.ts +17 -0
- package/opencode-swarm.schema.json +8 -0
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-rke0964p.js +0 -82
- package/dist/cli/guardrail-explain-jg5png4a.js +0 -83
|
@@ -4,10 +4,15 @@ import {
|
|
|
4
4
|
ensureTaskCheckpointReceipt,
|
|
5
5
|
loadPlan,
|
|
6
6
|
updateTaskCheckpointReceipt
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-yk3b7ywa.js";
|
|
8
8
|
import {
|
|
9
9
|
tool
|
|
10
10
|
} from "./index-9nyqt3kb.js";
|
|
11
|
+
import {
|
|
12
|
+
init_executor,
|
|
13
|
+
isUntrustedEnvKey,
|
|
14
|
+
isValidEnvKey
|
|
15
|
+
} from "./index-8mrentmw.js";
|
|
11
16
|
import {
|
|
12
17
|
GitBinaryMissingError,
|
|
13
18
|
GitSpawnCwdError,
|
|
@@ -17,13 +22,13 @@ import {
|
|
|
17
22
|
} from "./index-spggmr30.js";
|
|
18
23
|
import {
|
|
19
24
|
validateSwarmPath
|
|
20
|
-
} from "./index-
|
|
25
|
+
} from "./index-xyvsdn07.js";
|
|
21
26
|
import {
|
|
22
27
|
readLedgerEvents
|
|
23
|
-
} from "./index-
|
|
28
|
+
} from "./index-bncex5hw.js";
|
|
24
29
|
import {
|
|
25
30
|
appendCoreEventSync
|
|
26
|
-
} from "./index-
|
|
31
|
+
} from "./index-nzfq1ms2.js";
|
|
27
32
|
import {
|
|
28
33
|
derivePlanId,
|
|
29
34
|
derivePlanIdentityHash
|
|
@@ -38,18 +43,22 @@ import {
|
|
|
38
43
|
} from "./index-ejhxgyvm.js";
|
|
39
44
|
import {
|
|
40
45
|
atomicWriteSwarmFileSync
|
|
41
|
-
} from "./index-
|
|
46
|
+
} from "./index-epzadpmm.js";
|
|
42
47
|
import {
|
|
43
48
|
loadPluginConfigWithMeta
|
|
44
|
-
} from "./index-
|
|
49
|
+
} from "./index-pvpeggb5.js";
|
|
45
50
|
import {
|
|
51
|
+
GIT_BINARY_ENV_VAR,
|
|
52
|
+
describeGitResolution,
|
|
46
53
|
init_git_executable,
|
|
47
54
|
resolveGitExecutable,
|
|
48
55
|
resolveGitExecutableAsync
|
|
49
56
|
} from "./index-sp2cxpaq.js";
|
|
50
57
|
import {
|
|
51
58
|
bunSpawn,
|
|
52
|
-
|
|
59
|
+
classifySpawnFailure,
|
|
60
|
+
init_bun_compat,
|
|
61
|
+
mergeEnvForChild
|
|
53
62
|
} from "./index-qbxk2rn6.js";
|
|
54
63
|
import {
|
|
55
64
|
init_logger,
|
|
@@ -62,16 +71,675 @@ import {
|
|
|
62
71
|
|
|
63
72
|
// src/tools/checkpoint.ts
|
|
64
73
|
init_zod();
|
|
65
|
-
import * as
|
|
74
|
+
import * as child_process2 from "child_process";
|
|
66
75
|
import * as fs4 from "fs";
|
|
67
|
-
import * as
|
|
76
|
+
import * as path6 from "path";
|
|
68
77
|
|
|
69
78
|
// src/commands/destructive-purge.ts
|
|
70
79
|
import { createHash, randomBytes } from "crypto";
|
|
71
80
|
import * as fs from "fs";
|
|
72
|
-
import * as
|
|
81
|
+
import * as path3 from "path";
|
|
82
|
+
init_logger();
|
|
83
|
+
|
|
84
|
+
// src/git/branch.ts
|
|
85
|
+
init_executor();
|
|
86
|
+
init_bun_compat();
|
|
87
|
+
import * as child_process from "child_process";
|
|
88
|
+
import * as fsSync from "fs";
|
|
89
|
+
import path from "path";
|
|
90
|
+
init_git_executable();
|
|
73
91
|
init_logger();
|
|
74
92
|
|
|
93
|
+
// src/utils/transient-retry.ts
|
|
94
|
+
var MAX_TRANSIENT_RETRIES = 5;
|
|
95
|
+
var RETRY_BASE_DELAY_MS = 200;
|
|
96
|
+
function isTransientSpawnError(error) {
|
|
97
|
+
if (!error || typeof error !== "object")
|
|
98
|
+
return false;
|
|
99
|
+
const code = error.code;
|
|
100
|
+
return code === "ETIMEDOUT";
|
|
101
|
+
}
|
|
102
|
+
function transientBackoff(attempt) {
|
|
103
|
+
const delay = RETRY_BASE_DELAY_MS * 2 ** attempt;
|
|
104
|
+
try {
|
|
105
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
106
|
+
} catch {
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
while (Date.now() - start < delay) {}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/git/safe-ref.ts
|
|
113
|
+
class UnsafeGitRefError extends Error {
|
|
114
|
+
ref;
|
|
115
|
+
context;
|
|
116
|
+
constructor(ref, context) {
|
|
117
|
+
super(`Refused unsafe git ref "${ref}" at ${context}: a ref argument ` + 'starting with "-" can be parsed by git as an option (issue #2265/#2476)');
|
|
118
|
+
this.ref = ref;
|
|
119
|
+
this.context = context;
|
|
120
|
+
this.name = "UnsafeGitRefError";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function isSafeGitRefArg(value) {
|
|
124
|
+
if (value.length === 0)
|
|
125
|
+
return false;
|
|
126
|
+
for (const side of value.split("..")) {
|
|
127
|
+
if (side.startsWith("-"))
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
function assertSafeGitRefArg(value, context) {
|
|
133
|
+
if (!isSafeGitRefArg(value)) {
|
|
134
|
+
throw new UnsafeGitRefError(value, context);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/git/branch.ts
|
|
140
|
+
var GIT_TIMEOUT_MS = 30000;
|
|
141
|
+
var GIT_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
|
|
142
|
+
var MAX_RENDERED_GIT_CANDIDATES = 10;
|
|
143
|
+
function errorMessage(err) {
|
|
144
|
+
return err instanceof Error ? err.message : String(err);
|
|
145
|
+
}
|
|
146
|
+
function isNotGitRepositoryMessage(message) {
|
|
147
|
+
const lower = message.toLowerCase();
|
|
148
|
+
return lower.includes("not a git repository") || lower.includes("not a git repo");
|
|
149
|
+
}
|
|
150
|
+
function getChildProcessSpawnSync() {
|
|
151
|
+
return child_process.spawnSync;
|
|
152
|
+
}
|
|
153
|
+
var __spawnSyncSeam = {
|
|
154
|
+
spawnSync: (cmd, args, options) => {
|
|
155
|
+
const mergedEnv = mergeEnvForChild(options?.env, options?.envOverrides);
|
|
156
|
+
return getChildProcessSpawnSync()(cmd, args, {
|
|
157
|
+
...options,
|
|
158
|
+
env: mergedEnv
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
function readLaneEnvFileFromDiskSync(worktreePath, laneIndex) {
|
|
163
|
+
const envPath = path.join(worktreePath, ".swarm", "lanes", `${laneIndex}.env`);
|
|
164
|
+
let content;
|
|
165
|
+
try {
|
|
166
|
+
content = fsSync.readFileSync(envPath, "utf-8");
|
|
167
|
+
} catch {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const rawLine of content.split(`
|
|
172
|
+
`)) {
|
|
173
|
+
const line = rawLine.trim();
|
|
174
|
+
if (!line || line.startsWith("#"))
|
|
175
|
+
continue;
|
|
176
|
+
const eqIdx = line.indexOf("=");
|
|
177
|
+
if (eqIdx < 0)
|
|
178
|
+
continue;
|
|
179
|
+
const k = line.slice(0, eqIdx);
|
|
180
|
+
const v = line.slice(eqIdx + 1);
|
|
181
|
+
if (!isValidEnvKey(k))
|
|
182
|
+
continue;
|
|
183
|
+
if (isUntrustedEnvKey(k))
|
|
184
|
+
continue;
|
|
185
|
+
result[k] = v;
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
function gitBinaryMissingMessage(cwd) {
|
|
190
|
+
const resolution = describeGitResolution();
|
|
191
|
+
const lines = [
|
|
192
|
+
`git executable is not available: spawning "${resolution.resolvedPath ?? "git"}" failed, and the working directory ${cwd} exists and is a directory.`
|
|
193
|
+
];
|
|
194
|
+
if (resolution.attempts.length > 0) {
|
|
195
|
+
lines.push("Candidates tried:");
|
|
196
|
+
const shown = resolution.attempts.slice(0, MAX_RENDERED_GIT_CANDIDATES);
|
|
197
|
+
for (const attempt of shown) {
|
|
198
|
+
lines.push(` - [${attempt.source}] ${attempt.candidate}: ${attempt.accepted ? "accepted" : attempt.reason ?? "rejected"}`);
|
|
199
|
+
}
|
|
200
|
+
const omitted = resolution.attempts.length - shown.length;
|
|
201
|
+
if (omitted > 0)
|
|
202
|
+
lines.push(` ... and ${omitted} more`);
|
|
203
|
+
} else {
|
|
204
|
+
lines.push("No candidate probe results are recorded for this process (the resolver was pre-seeded or has not probed yet).");
|
|
205
|
+
}
|
|
206
|
+
lines.push(resolution.overrideValue ? `The configured override (${resolution.overrideSource ?? "unknown"}) "${resolution.overrideValue}" did not work \u2014 point the ${GIT_BINARY_ENV_VAR} environment variable, or the "git.binary" value in your USER config (a project .opencode config is ignored for this key), at a working git executable.` : `No override is configured. Set the ${GIT_BINARY_ENV_VAR} environment variable, or the "git.binary" value in your USER config (a project .opencode config is ignored for this key), to point at a working git executable.`);
|
|
207
|
+
return lines.join(`
|
|
208
|
+
`);
|
|
209
|
+
}
|
|
210
|
+
function gitExec(args, cwd, laneEnv, laneIndex) {
|
|
211
|
+
const resolvedLaneEnv = laneEnv ?? (laneIndex !== undefined ? readLaneEnvFileFromDiskSync(cwd, laneIndex) : undefined);
|
|
212
|
+
const subcommand = args[0];
|
|
213
|
+
const hardenedArgs = subcommand === "commit" || subcommand === "tag" ? ["-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", ...args] : args;
|
|
214
|
+
const command = _internals.resolveGitExecutable();
|
|
215
|
+
for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
|
|
216
|
+
const result = _internals.spawnSync(command, hardenedArgs, {
|
|
217
|
+
cwd,
|
|
218
|
+
encoding: "utf-8",
|
|
219
|
+
timeout: GIT_TIMEOUT_MS,
|
|
220
|
+
windowsHide: true,
|
|
221
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
222
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
223
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
224
|
+
envOverrides: resolvedLaneEnv
|
|
225
|
+
});
|
|
226
|
+
if (!result.error && result.status === 0) {
|
|
227
|
+
return result.stdout;
|
|
228
|
+
}
|
|
229
|
+
if (result.error) {
|
|
230
|
+
if (isSpawnCwdMissing(result.error, cwd)) {
|
|
231
|
+
throw new GitSpawnCwdError(cwd, "missing", { cause: result.error });
|
|
232
|
+
}
|
|
233
|
+
if (isSpawnCwdUnreadable(result.error, cwd)) {
|
|
234
|
+
throw new GitSpawnCwdError(cwd, "unreadable", {
|
|
235
|
+
cause: result.error
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (isGitBinaryMissing(result.error, cwd)) {
|
|
239
|
+
throw new GitBinaryMissingError(gitBinaryMissingMessage(cwd), {
|
|
240
|
+
cause: result.error
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (isTransientSpawnError(result.error) && attempt < MAX_TRANSIENT_RETRIES - 1) {
|
|
244
|
+
transientBackoff(attempt);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
throw new Error(errorMessage(result.error));
|
|
248
|
+
}
|
|
249
|
+
if (result.status !== 0) {
|
|
250
|
+
throw new Error(result.stderr || result.stdout || `git exited with ${result.status}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw new GitBinaryMissingError(process.platform === "win32" ? "git executable is not available on PATH or common Windows install locations" : "git executable is not available on PATH");
|
|
254
|
+
}
|
|
255
|
+
function getGitRepositoryStatus(cwd) {
|
|
256
|
+
try {
|
|
257
|
+
gitExec(["rev-parse", "--git-dir"], cwd);
|
|
258
|
+
return { isRepo: true };
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (err instanceof GitBinaryMissingError) {
|
|
261
|
+
return {
|
|
262
|
+
isRepo: false,
|
|
263
|
+
reason: "git_unavailable",
|
|
264
|
+
message: err.message
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const message = errorMessage(err);
|
|
268
|
+
return {
|
|
269
|
+
isRepo: false,
|
|
270
|
+
reason: isNotGitRepositoryMessage(message) ? "not_git_repo" : "git_error",
|
|
271
|
+
message
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function isGitRepo(cwd) {
|
|
276
|
+
return getGitRepositoryStatus(cwd).isRepo;
|
|
277
|
+
}
|
|
278
|
+
function getCurrentBranch(cwd, laneEnv, laneIndex) {
|
|
279
|
+
const output = gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd, laneEnv, laneIndex);
|
|
280
|
+
return output.trim();
|
|
281
|
+
}
|
|
282
|
+
function getDefaultBaseBranch(cwd) {
|
|
283
|
+
try {
|
|
284
|
+
gitExec(["rev-parse", "--verify", "origin/main"], cwd);
|
|
285
|
+
return "origin/main";
|
|
286
|
+
} catch {
|
|
287
|
+
try {
|
|
288
|
+
gitExec(["rev-parse", "--verify", "origin/master"], cwd);
|
|
289
|
+
return "origin/master";
|
|
290
|
+
} catch {
|
|
291
|
+
return "origin/main";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function hasUncommittedChanges(cwd, laneEnv, laneIndex) {
|
|
296
|
+
const status = gitExec(["status", "--porcelain"], cwd, laneEnv, laneIndex);
|
|
297
|
+
return status.trim().length > 0;
|
|
298
|
+
}
|
|
299
|
+
function detectDefaultRemoteBranch(cwd) {
|
|
300
|
+
try {
|
|
301
|
+
const output = gitExec(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
|
|
302
|
+
const trimmed = output.trim();
|
|
303
|
+
if (trimmed.startsWith("refs/remotes/origin/")) {
|
|
304
|
+
return trimmed.slice("refs/remotes/origin/".length);
|
|
305
|
+
}
|
|
306
|
+
} catch {}
|
|
307
|
+
try {
|
|
308
|
+
const output = gitExec(["config", "init.defaultBranch"], cwd);
|
|
309
|
+
const branch = output.trim();
|
|
310
|
+
if (branch) {
|
|
311
|
+
return branch;
|
|
312
|
+
}
|
|
313
|
+
} catch {}
|
|
314
|
+
try {
|
|
315
|
+
gitExec(["rev-parse", "--verify", "origin/main"], cwd);
|
|
316
|
+
return "main";
|
|
317
|
+
} catch {}
|
|
318
|
+
try {
|
|
319
|
+
gitExec(["rev-parse", "--verify", "origin/master"], cwd);
|
|
320
|
+
return "master";
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function resetToRemoteBranch(cwd, options) {
|
|
326
|
+
const warnings = [];
|
|
327
|
+
const prunedBranches = [];
|
|
328
|
+
try {
|
|
329
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
330
|
+
const defaultRemoteBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
331
|
+
if (!defaultRemoteBranch) {
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
targetBranch: "",
|
|
335
|
+
localBranch: currentBranch,
|
|
336
|
+
message: "Could not detect default remote branch",
|
|
337
|
+
alreadyAligned: false,
|
|
338
|
+
prunedBranches: [],
|
|
339
|
+
warnings: []
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
assertSafeGitRefArg(currentBranch, "resetToRemoteBranch currentBranch");
|
|
343
|
+
assertSafeGitRefArg(defaultRemoteBranch, "resetToRemoteBranch defaultRemoteBranch");
|
|
344
|
+
const targetBranch = `origin/${defaultRemoteBranch}`;
|
|
345
|
+
if (currentBranch === "HEAD") {
|
|
346
|
+
return {
|
|
347
|
+
success: false,
|
|
348
|
+
targetBranch,
|
|
349
|
+
localBranch: "HEAD",
|
|
350
|
+
message: "Cannot reset: detached HEAD state",
|
|
351
|
+
alreadyAligned: false,
|
|
352
|
+
prunedBranches: [],
|
|
353
|
+
warnings: []
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (hasUncommittedChanges(cwd)) {
|
|
357
|
+
return {
|
|
358
|
+
success: false,
|
|
359
|
+
targetBranch,
|
|
360
|
+
localBranch: currentBranch,
|
|
361
|
+
message: "Cannot reset: uncommitted changes in working tree",
|
|
362
|
+
alreadyAligned: false,
|
|
363
|
+
prunedBranches: [],
|
|
364
|
+
warnings: []
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const logOutput = gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
369
|
+
if (logOutput.trim().length > 0) {
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
targetBranch,
|
|
373
|
+
localBranch: currentBranch,
|
|
374
|
+
message: "Cannot reset: unpushed commits",
|
|
375
|
+
alreadyAligned: false,
|
|
376
|
+
prunedBranches: [],
|
|
377
|
+
warnings: []
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
} catch {}
|
|
381
|
+
try {
|
|
382
|
+
gitExec(["fetch", "--prune", "origin"], cwd);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
targetBranch,
|
|
387
|
+
localBranch: currentBranch,
|
|
388
|
+
message: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
389
|
+
alreadyAligned: false,
|
|
390
|
+
prunedBranches: [],
|
|
391
|
+
warnings: []
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const headSha = gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
395
|
+
const remoteSha = gitExec(["rev-parse", `${targetBranch}`], cwd).trim();
|
|
396
|
+
if (headSha === remoteSha) {
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
targetBranch,
|
|
400
|
+
localBranch: currentBranch,
|
|
401
|
+
message: "Already aligned with remote",
|
|
402
|
+
alreadyAligned: true,
|
|
403
|
+
prunedBranches: [],
|
|
404
|
+
warnings: []
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
gitExec(["checkout", currentBranch], cwd);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return {
|
|
411
|
+
success: false,
|
|
412
|
+
targetBranch,
|
|
413
|
+
localBranch: currentBranch,
|
|
414
|
+
message: `Checkout failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
415
|
+
alreadyAligned: false,
|
|
416
|
+
prunedBranches: [],
|
|
417
|
+
warnings: []
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
let resetSucceeded = false;
|
|
421
|
+
let lastError;
|
|
422
|
+
for (let retry = 0;retry < 4; retry++) {
|
|
423
|
+
if (retry > 0 && process.platform === "win32") {
|
|
424
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
gitExec(["reset", "--hard", targetBranch], cwd);
|
|
428
|
+
resetSucceeded = true;
|
|
429
|
+
break;
|
|
430
|
+
} catch (err) {
|
|
431
|
+
lastError = err;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (!resetSucceeded) {
|
|
435
|
+
return {
|
|
436
|
+
success: false,
|
|
437
|
+
targetBranch,
|
|
438
|
+
localBranch: currentBranch,
|
|
439
|
+
message: `Reset failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`,
|
|
440
|
+
alreadyAligned: false,
|
|
441
|
+
prunedBranches: [],
|
|
442
|
+
warnings: []
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (options?.pruneBranches) {
|
|
446
|
+
try {
|
|
447
|
+
const mergedOutput = gitExec(["branch", "--merged", targetBranch], cwd);
|
|
448
|
+
const mergedLines = mergedOutput.split(`
|
|
449
|
+
`);
|
|
450
|
+
for (const line of mergedLines) {
|
|
451
|
+
const trimmedLine = line.trim();
|
|
452
|
+
if (!trimmedLine || trimmedLine.startsWith("*")) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
gitExec([
|
|
457
|
+
"branch",
|
|
458
|
+
"-d",
|
|
459
|
+
"--",
|
|
460
|
+
assertSafeGitRefArg(trimmedLine, "resetToRemoteBranch prune merged branch")
|
|
461
|
+
], cwd);
|
|
462
|
+
prunedBranches.push(trimmedLine);
|
|
463
|
+
} catch {
|
|
464
|
+
warnings.push(`Could not safely delete branch: ${trimmedLine}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch (err) {
|
|
468
|
+
warnings.push(`Failed to get merged branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const branchVvOutput = gitExec(["branch", "-vv"], cwd);
|
|
472
|
+
const vvLines = branchVvOutput.split(`
|
|
473
|
+
`);
|
|
474
|
+
for (const line of vvLines) {
|
|
475
|
+
const trimmedLine = line.trim();
|
|
476
|
+
if (!trimmedLine || trimmedLine.startsWith("*")) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (trimmedLine.includes(": gone]")) {
|
|
480
|
+
const parts = trimmedLine.split(/\s+/);
|
|
481
|
+
const branchName = parts[0];
|
|
482
|
+
try {
|
|
483
|
+
gitExec([
|
|
484
|
+
"branch",
|
|
485
|
+
"-d",
|
|
486
|
+
"--",
|
|
487
|
+
assertSafeGitRefArg(branchName, "resetToRemoteBranch prune gone branch")
|
|
488
|
+
], cwd);
|
|
489
|
+
prunedBranches.push(branchName);
|
|
490
|
+
} catch {
|
|
491
|
+
warnings.push(`Could not delete gone branch: ${branchName}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch (err) {
|
|
496
|
+
warnings.push(`Failed to prune gone branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
success: true,
|
|
501
|
+
targetBranch,
|
|
502
|
+
localBranch: currentBranch,
|
|
503
|
+
message: "Successfully reset to remote branch",
|
|
504
|
+
alreadyAligned: false,
|
|
505
|
+
prunedBranches,
|
|
506
|
+
warnings
|
|
507
|
+
};
|
|
508
|
+
} catch (err) {
|
|
509
|
+
return {
|
|
510
|
+
success: false,
|
|
511
|
+
targetBranch: "",
|
|
512
|
+
localBranch: "",
|
|
513
|
+
message: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
|
514
|
+
alreadyAligned: false,
|
|
515
|
+
prunedBranches: [],
|
|
516
|
+
warnings: []
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
var GITIGNORED_BUILD_ARTIFACTS = ["dist"];
|
|
521
|
+
async function resetToMainAfterMerge(cwd, options) {
|
|
522
|
+
const warnings = [];
|
|
523
|
+
try {
|
|
524
|
+
const defaultBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
525
|
+
if (!defaultBranch) {
|
|
526
|
+
return {
|
|
527
|
+
success: false,
|
|
528
|
+
targetBranch: "",
|
|
529
|
+
previousBranch: "",
|
|
530
|
+
message: "Could not detect default remote branch",
|
|
531
|
+
branchDeleted: false,
|
|
532
|
+
changesDiscarded: false,
|
|
533
|
+
warnings
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
537
|
+
assertSafeGitRefArg(currentBranch, "resetToMainAfterMerge currentBranch");
|
|
538
|
+
assertSafeGitRefArg(defaultBranch, "resetToMainAfterMerge defaultBranch");
|
|
539
|
+
const targetBranch = `origin/${defaultBranch}`;
|
|
540
|
+
if (currentBranch === "HEAD") {
|
|
541
|
+
return {
|
|
542
|
+
success: false,
|
|
543
|
+
targetBranch,
|
|
544
|
+
previousBranch: "HEAD",
|
|
545
|
+
message: "Cannot reset: detached HEAD state",
|
|
546
|
+
branchDeleted: false,
|
|
547
|
+
changesDiscarded: false,
|
|
548
|
+
warnings
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (currentBranch === defaultBranch) {
|
|
552
|
+
try {
|
|
553
|
+
const logOutput = _internals.gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
554
|
+
if (logOutput.trim().length > 0) {
|
|
555
|
+
return {
|
|
556
|
+
success: false,
|
|
557
|
+
targetBranch,
|
|
558
|
+
previousBranch: currentBranch,
|
|
559
|
+
message: `Cannot reset: ${defaultBranch} has unpushed commits. Push them first.`,
|
|
560
|
+
branchDeleted: false,
|
|
561
|
+
changesDiscarded: false,
|
|
562
|
+
warnings
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
} catch {}
|
|
566
|
+
} else {
|
|
567
|
+
try {
|
|
568
|
+
_internals.gitExec(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`], cwd);
|
|
569
|
+
} catch {
|
|
570
|
+
try {
|
|
571
|
+
const localSha = _internals.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
572
|
+
const remoteSha = _internals.gitExec(["rev-parse", targetBranch], cwd).trim();
|
|
573
|
+
if (localSha !== remoteSha) {
|
|
574
|
+
return {
|
|
575
|
+
success: false,
|
|
576
|
+
targetBranch,
|
|
577
|
+
previousBranch: currentBranch,
|
|
578
|
+
message: `Cannot reset: branch ${currentBranch} is local-only and diverges from ${defaultBranch}. Push or check manually.`,
|
|
579
|
+
branchDeleted: false,
|
|
580
|
+
changesDiscarded: false,
|
|
581
|
+
warnings
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
} catch {
|
|
585
|
+
return {
|
|
586
|
+
success: false,
|
|
587
|
+
targetBranch,
|
|
588
|
+
previousBranch: currentBranch,
|
|
589
|
+
message: `Cannot reset: unable to compare ${currentBranch} with ${defaultBranch}`,
|
|
590
|
+
branchDeleted: false,
|
|
591
|
+
changesDiscarded: false,
|
|
592
|
+
warnings
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
_internals.gitExec(["fetch", "--prune", "origin"], cwd);
|
|
599
|
+
} catch (err) {
|
|
600
|
+
return {
|
|
601
|
+
success: false,
|
|
602
|
+
targetBranch,
|
|
603
|
+
previousBranch: currentBranch,
|
|
604
|
+
message: `Cannot reset: fetch failed \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
605
|
+
branchDeleted: false,
|
|
606
|
+
changesDiscarded: false,
|
|
607
|
+
warnings
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
const previousBranch = currentBranch;
|
|
611
|
+
let switchedBranch = false;
|
|
612
|
+
if (currentBranch !== defaultBranch) {
|
|
613
|
+
try {
|
|
614
|
+
_internals.gitExec(["checkout", defaultBranch], cwd);
|
|
615
|
+
switchedBranch = true;
|
|
616
|
+
} catch (err) {
|
|
617
|
+
return {
|
|
618
|
+
success: false,
|
|
619
|
+
targetBranch,
|
|
620
|
+
previousBranch,
|
|
621
|
+
message: `Checkout to ${defaultBranch} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
622
|
+
branchDeleted: false,
|
|
623
|
+
changesDiscarded: false,
|
|
624
|
+
warnings
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
_internals.gitExec(["reset", "--hard", targetBranch], cwd);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
return {
|
|
632
|
+
success: false,
|
|
633
|
+
targetBranch,
|
|
634
|
+
previousBranch,
|
|
635
|
+
message: `Reset to ${targetBranch} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
636
|
+
branchDeleted: false,
|
|
637
|
+
changesDiscarded: false,
|
|
638
|
+
warnings
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
let changesDiscarded = false;
|
|
642
|
+
if (hasUncommittedChanges(cwd)) {
|
|
643
|
+
let discardSucceeded = false;
|
|
644
|
+
for (let retry = 0;retry < 4; retry++) {
|
|
645
|
+
if (retry > 0 && process.platform === "win32") {
|
|
646
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
_internals.gitExec(["checkout", "--", "."], cwd);
|
|
650
|
+
discardSucceeded = true;
|
|
651
|
+
break;
|
|
652
|
+
} catch {}
|
|
653
|
+
}
|
|
654
|
+
if (!discardSucceeded) {
|
|
655
|
+
warnings.push("Could not discard all uncommitted changes after reset");
|
|
656
|
+
}
|
|
657
|
+
changesDiscarded = discardSucceeded;
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
_internals.gitExec(["clean", "-fdX", "--", ...GITIGNORED_BUILD_ARTIFACTS], cwd);
|
|
661
|
+
} catch {
|
|
662
|
+
warnings.push("Could not clean build artifacts");
|
|
663
|
+
}
|
|
664
|
+
let branchDeleted = false;
|
|
665
|
+
if (switchedBranch && previousBranch !== defaultBranch) {
|
|
666
|
+
try {
|
|
667
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
668
|
+
const isMerged = mergedOutput.split(`
|
|
669
|
+
`).some((line) => line.trim() === previousBranch || line.trim() === `* ${previousBranch}`);
|
|
670
|
+
if (isMerged) {
|
|
671
|
+
_internals.gitExec([
|
|
672
|
+
"branch",
|
|
673
|
+
"-d",
|
|
674
|
+
"--",
|
|
675
|
+
assertSafeGitRefArg(previousBranch, "resetToMainAfterMerge previousBranch")
|
|
676
|
+
], cwd);
|
|
677
|
+
branchDeleted = true;
|
|
678
|
+
} else {
|
|
679
|
+
warnings.push(`Branch ${previousBranch} is not merged into ${defaultBranch} \u2014 keeping it`);
|
|
680
|
+
}
|
|
681
|
+
} catch {
|
|
682
|
+
warnings.push(`Could not delete branch ${previousBranch}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (options?.pruneBranches) {
|
|
686
|
+
try {
|
|
687
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
688
|
+
const mergedLines = mergedOutput.split(`
|
|
689
|
+
`);
|
|
690
|
+
for (const line of mergedLines) {
|
|
691
|
+
const trimmedLine = line.trim();
|
|
692
|
+
if (!trimmedLine || trimmedLine.startsWith("*") || trimmedLine === defaultBranch) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
_internals.gitExec([
|
|
697
|
+
"branch",
|
|
698
|
+
"-d",
|
|
699
|
+
"--",
|
|
700
|
+
assertSafeGitRefArg(trimmedLine, "resetToMainAfterMerge prune branch")
|
|
701
|
+
], cwd);
|
|
702
|
+
} catch {
|
|
703
|
+
warnings.push(`Could not prune branch: ${trimmedLine}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} catch (err) {
|
|
707
|
+
warnings.push(`Prune failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
success: true,
|
|
712
|
+
targetBranch,
|
|
713
|
+
previousBranch,
|
|
714
|
+
message: branchDeleted ? `Reset to ${defaultBranch} and deleted branch ${previousBranch}` : `Reset to ${defaultBranch}`,
|
|
715
|
+
branchDeleted,
|
|
716
|
+
changesDiscarded,
|
|
717
|
+
warnings
|
|
718
|
+
};
|
|
719
|
+
} catch (err) {
|
|
720
|
+
return {
|
|
721
|
+
success: false,
|
|
722
|
+
targetBranch: "",
|
|
723
|
+
previousBranch: "",
|
|
724
|
+
message: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
|
725
|
+
branchDeleted: false,
|
|
726
|
+
changesDiscarded: false,
|
|
727
|
+
warnings
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
var _internals = {
|
|
732
|
+
gitExec,
|
|
733
|
+
detectDefaultRemoteBranch,
|
|
734
|
+
getDefaultBaseBranch,
|
|
735
|
+
getGitRepositoryStatus,
|
|
736
|
+
resetToRemoteBranch,
|
|
737
|
+
resetToMainAfterMerge,
|
|
738
|
+
spawnSync: __spawnSyncSeam.spawnSync,
|
|
739
|
+
readLaneEnvFileFromDiskSync,
|
|
740
|
+
resolveGitExecutable
|
|
741
|
+
};
|
|
742
|
+
|
|
75
743
|
// src/commands/git-status-read.ts
|
|
76
744
|
init_bun_compat();
|
|
77
745
|
init_git_executable();
|
|
@@ -269,10 +937,59 @@ async function readGitRepositoryStatusAsync(cwd) {
|
|
|
269
937
|
const result = await gitStatusReadInternals.readOnce(["rev-parse", "--git-dir"], cwd, GIT_READ_DEFAULT_TIMEOUT_MS);
|
|
270
938
|
return result.ok;
|
|
271
939
|
}
|
|
940
|
+
async function readGitRepositoryStatusDetailedAsync(cwd) {
|
|
941
|
+
const result = await gitStatusReadInternals.readOnce(["rev-parse", "--git-dir"], cwd, GIT_READ_DEFAULT_TIMEOUT_MS);
|
|
942
|
+
if (result.ok)
|
|
943
|
+
return { isRepo: true };
|
|
944
|
+
if (result.code === "ENOENT") {
|
|
945
|
+
const failureClass = classifySpawnFailure({ code: result.code }, cwd);
|
|
946
|
+
if (failureClass === "cwd-missing") {
|
|
947
|
+
return {
|
|
948
|
+
isRepo: false,
|
|
949
|
+
reason: "git_error",
|
|
950
|
+
message: `git spawn cwd is missing: ${cwd} (ENOENT)`
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
if (failureClass === "cwd-unreadable") {
|
|
954
|
+
return {
|
|
955
|
+
isRepo: false,
|
|
956
|
+
reason: "git_error",
|
|
957
|
+
message: `git spawn cwd could not be inspected: ${cwd} (ENOENT)`
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
isRepo: false,
|
|
962
|
+
reason: "git_unavailable",
|
|
963
|
+
message: gitBinaryMissingMessage(cwd)
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
if (result.code === null) {
|
|
967
|
+
const stderrTail2 = result.stderrTail ?? "";
|
|
968
|
+
if (isNotGitRepositoryMessage(stderrTail2)) {
|
|
969
|
+
return {
|
|
970
|
+
isRepo: false,
|
|
971
|
+
reason: "not_git_repo",
|
|
972
|
+
message: stderrTail2 || `rev-parse exited ${result.status}`
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const exitNote = result.status === null ? "rev-parse failed (no exit status reported)" : `rev-parse exited ${result.status}`;
|
|
976
|
+
return {
|
|
977
|
+
isRepo: false,
|
|
978
|
+
reason: "git_error",
|
|
979
|
+
message: `${exitNote}${stderrTail2 ? `: ${stderrTail2}` : ""}`
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const tail = result.stderrTail ?? "";
|
|
983
|
+
return {
|
|
984
|
+
isRepo: false,
|
|
985
|
+
reason: "git_error",
|
|
986
|
+
message: `git read failed (${result.code})${tail ? `: ${tail}` : ""}`
|
|
987
|
+
};
|
|
988
|
+
}
|
|
272
989
|
|
|
273
990
|
// src/commands/rollback-gate.ts
|
|
274
|
-
import * as
|
|
275
|
-
import * as
|
|
991
|
+
import * as fsSync2 from "fs";
|
|
992
|
+
import * as path2 from "path";
|
|
276
993
|
var ROLLBACK_GIT_KIND = "swarm-rollback-git";
|
|
277
994
|
var ROLLBACK_PHASE_KIND = "swarm-rollback-phase";
|
|
278
995
|
var GIT_LIST_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
|
@@ -304,15 +1021,29 @@ async function readFailureDetail(raw) {
|
|
|
304
1021
|
}
|
|
305
1022
|
function isRealGitRepository(directory) {
|
|
306
1023
|
try {
|
|
307
|
-
if (
|
|
1024
|
+
if (fsSync2.statSync(path2.join(directory, ".git")).isFile())
|
|
308
1025
|
return true;
|
|
309
1026
|
} catch {
|
|
310
1027
|
return false;
|
|
311
1028
|
}
|
|
312
|
-
return
|
|
1029
|
+
return fsSync2.existsSync(path2.join(directory, ".git", "HEAD"));
|
|
1030
|
+
}
|
|
1031
|
+
function isOutOfRootRelativePath(rel) {
|
|
1032
|
+
if (rel === "")
|
|
1033
|
+
return false;
|
|
1034
|
+
const normalized = path2.posix.normalize(rel);
|
|
1035
|
+
return normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/");
|
|
313
1036
|
}
|
|
314
|
-
function
|
|
1037
|
+
function parseTrackedDirtyPathsWithExclusions(statusOutput) {
|
|
315
1038
|
const dirtyPaths = [];
|
|
1039
|
+
const excludedOutOfRoot = [];
|
|
1040
|
+
const push = (candidate) => {
|
|
1041
|
+
if (isOutOfRootRelativePath(candidate)) {
|
|
1042
|
+
excludedOutOfRoot.push(candidate);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
dirtyPaths.push(candidate);
|
|
1046
|
+
};
|
|
316
1047
|
const tokens = statusOutput.split("\x00");
|
|
317
1048
|
for (let i = 0;i < tokens.length; i += 1) {
|
|
318
1049
|
const token = tokens[i];
|
|
@@ -324,44 +1055,83 @@ function parseTrackedDirtyPaths(statusOutput) {
|
|
|
324
1055
|
const filePath = token.slice(3);
|
|
325
1056
|
if (!filePath)
|
|
326
1057
|
continue;
|
|
327
|
-
|
|
1058
|
+
push(filePath);
|
|
328
1059
|
if (/[RC]/.test(xy) && i + 1 < tokens.length && tokens[i + 1] !== "") {
|
|
329
|
-
|
|
1060
|
+
push(tokens[i + 1]);
|
|
330
1061
|
i += 1;
|
|
331
1062
|
}
|
|
332
1063
|
}
|
|
333
|
-
return dirtyPaths;
|
|
1064
|
+
return { dirtyPaths, excludedOutOfRoot };
|
|
334
1065
|
}
|
|
335
1066
|
async function untrackedPathsInTargetTree(directory, targetSha) {
|
|
336
|
-
if (!isRealGitRepository(directory))
|
|
337
|
-
return [];
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
1067
|
+
if (!isRealGitRepository(directory)) {
|
|
1068
|
+
return { paths: [], failure: null };
|
|
1069
|
+
}
|
|
1070
|
+
const targetTreeRaw = await _rollbackGateInternals.runGitBuffered(["ls-tree", "-r", "-z", "--name-only", targetSha], directory);
|
|
1071
|
+
const targetTree = await readToString(targetTreeRaw);
|
|
1072
|
+
if (targetTree === null) {
|
|
1073
|
+
const detail = await readFailureDetail(targetTreeRaw);
|
|
1074
|
+
return {
|
|
1075
|
+
paths: [],
|
|
1076
|
+
failure: detail ?? { code: null, status: null, stderrTail: "" }
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
const indexRaw = await _rollbackGateInternals.runGitBuffered(["ls-files", "-z"], directory);
|
|
1080
|
+
const index = await readToString(indexRaw);
|
|
1081
|
+
if (index === null) {
|
|
1082
|
+
const detail = await readFailureDetail(indexRaw);
|
|
1083
|
+
return {
|
|
1084
|
+
paths: [],
|
|
1085
|
+
failure: detail ?? { code: null, status: null, stderrTail: "" }
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
342
1088
|
const indexed = new Set(index.split("\x00").filter((p) => p !== ""));
|
|
343
1089
|
const inWay = [];
|
|
344
1090
|
for (const rel of targetTree.split("\x00")) {
|
|
345
1091
|
if (rel === "" || indexed.has(rel))
|
|
346
1092
|
continue;
|
|
347
|
-
if (
|
|
1093
|
+
if (fsSync2.existsSync(path2.join(directory, rel)))
|
|
348
1094
|
inWay.push(rel);
|
|
349
1095
|
}
|
|
350
|
-
return inWay;
|
|
1096
|
+
return { paths: inWay, failure: null };
|
|
351
1097
|
}
|
|
352
1098
|
function trackedDirtyCandidates(directory, dirtyRelPaths) {
|
|
353
1099
|
return dirtyRelPaths.map((rel) => ({
|
|
354
|
-
path:
|
|
1100
|
+
path: path2.join(directory, rel),
|
|
355
1101
|
reason: "uncommitted tracked change \u2014 destroyed by git reset --hard"
|
|
356
1102
|
}));
|
|
357
1103
|
}
|
|
358
1104
|
function swarmOverwriteCandidates(directory, overwriteRelPaths) {
|
|
359
1105
|
return overwriteRelPaths.map((rel) => ({
|
|
360
|
-
path:
|
|
1106
|
+
path: path2.join(directory, ".swarm", rel),
|
|
361
1107
|
reason: "live .swarm state replaced by the checkpoint copy"
|
|
362
1108
|
}));
|
|
363
1109
|
}
|
|
364
|
-
function describeUnreadableCause(measurement) {
|
|
1110
|
+
function describeUnreadableCause(measurement, source = "status") {
|
|
1111
|
+
if (source === "status") {
|
|
1112
|
+
return describeUnreadableStatusCause(measurement);
|
|
1113
|
+
}
|
|
1114
|
+
if (measurement.code === "ETIMEDOUT") {
|
|
1115
|
+
return "the target-tree listing read timed out (ETIMEDOUT) \u2014 the host may be unable to complete child spawns while loaded; restart the host and retry";
|
|
1116
|
+
}
|
|
1117
|
+
if (measurement.code === "EOUTPUT") {
|
|
1118
|
+
return `the listing output exceeded the read safety bound (${measurement.stderrTail ?? "output too large"}) \u2014 reduce the change footprint (commit or stash some work, or remove untracked bulk) and retry`;
|
|
1119
|
+
}
|
|
1120
|
+
if (measurement.code === "ESIGNAL") {
|
|
1121
|
+
return "the target-tree listing process was terminated before it answered (signal) \u2014 something external stopped it; retry, and inspect what terminates child processes if it recurs";
|
|
1122
|
+
}
|
|
1123
|
+
if (measurement.code === "ENOENT") {
|
|
1124
|
+
return "the git executable was not found (ENOENT) \u2014 install git on PATH, or set git.binary in your USER-level config, and retry";
|
|
1125
|
+
}
|
|
1126
|
+
if (measurement.status !== null && measurement.status !== undefined) {
|
|
1127
|
+
return `the target-tree listing exited with status ${measurement.status}${measurement.stderrTail ? `: ${measurement.stderrTail}` : ""} \u2014 repair the repository state and retry`;
|
|
1128
|
+
}
|
|
1129
|
+
if (measurement.code) {
|
|
1130
|
+
return `the listing read failed with code ${measurement.code} \u2014 retry, or inspect the repository state manually`;
|
|
1131
|
+
}
|
|
1132
|
+
return "retry, or inspect the repository state manually";
|
|
1133
|
+
}
|
|
1134
|
+
function describeUnreadableStatusCause(measurement) {
|
|
365
1135
|
if (measurement.code === "ETIMEDOUT") {
|
|
366
1136
|
return "the read timed out (ETIMEDOUT) \u2014 the host may be unable to complete child spawns while loaded; restart the host and retry";
|
|
367
1137
|
}
|
|
@@ -382,9 +1152,13 @@ function describeUnreadableCause(measurement) {
|
|
|
382
1152
|
}
|
|
383
1153
|
return "retry, or inspect git status manually";
|
|
384
1154
|
}
|
|
1155
|
+
function formatExcludedRecord(rel) {
|
|
1156
|
+
const sanitized = rel.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
|
|
1157
|
+
return sanitized.length > 200 ? `${sanitized.slice(0, 200)}\u2026` : sanitized;
|
|
1158
|
+
}
|
|
385
1159
|
async function measureGitDestruction(directory, targetSha) {
|
|
386
1160
|
if (!isRealGitRepository(directory)) {
|
|
387
|
-
return { kind: "ok", dirtyRelPaths: [] };
|
|
1161
|
+
return { kind: "ok", dirtyRelPaths: [], excludedOutOfRoot: [] };
|
|
388
1162
|
}
|
|
389
1163
|
const raw = _rollbackGateInternals.runGit(["--no-optional-locks", "status", "--porcelain", "-z"], directory);
|
|
390
1164
|
const status = await readToString(raw);
|
|
@@ -394,23 +1168,35 @@ async function measureGitDestruction(directory, targetSha) {
|
|
|
394
1168
|
kind: "unreadable",
|
|
395
1169
|
code: failure.code,
|
|
396
1170
|
status: failure.status,
|
|
397
|
-
stderrTail: failure.stderrTail
|
|
398
|
-
|
|
1171
|
+
stderrTail: failure.stderrTail,
|
|
1172
|
+
source: "status"
|
|
1173
|
+
} : { kind: "unreadable", source: "status" };
|
|
399
1174
|
}
|
|
400
|
-
const dirtyRelPaths =
|
|
1175
|
+
const { dirtyPaths: dirtyRelPaths, excludedOutOfRoot } = parseTrackedDirtyPathsWithExclusions(status);
|
|
401
1176
|
if (targetSha) {
|
|
402
|
-
|
|
1177
|
+
const { paths, failure } = await untrackedPathsInTargetTree(directory, targetSha);
|
|
1178
|
+
if (failure) {
|
|
1179
|
+
return {
|
|
1180
|
+
kind: "unreadable",
|
|
1181
|
+
code: failure.code,
|
|
1182
|
+
status: failure.status,
|
|
1183
|
+
stderrTail: failure.stderrTail,
|
|
1184
|
+
source: "enumeration",
|
|
1185
|
+
excludedOutOfRoot
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
for (const rel of paths) {
|
|
403
1189
|
if (!dirtyRelPaths.includes(rel))
|
|
404
1190
|
dirtyRelPaths.push(rel);
|
|
405
1191
|
}
|
|
406
1192
|
}
|
|
407
|
-
return { kind: "ok", dirtyRelPaths };
|
|
1193
|
+
return { kind: "ok", dirtyRelPaths, excludedOutOfRoot };
|
|
408
1194
|
}
|
|
409
1195
|
|
|
410
1196
|
// src/commands/destructive-purge.ts
|
|
411
1197
|
var PENDING_PURGE_PATH = "pending-purge.json";
|
|
412
1198
|
var PENDING_PURGE_TTL_MS = 15 * 60 * 1000;
|
|
413
|
-
var
|
|
1199
|
+
var _internals2 = {
|
|
414
1200
|
readFileSync: fs.readFileSync,
|
|
415
1201
|
rmSync: fs.rmSync.bind(fs),
|
|
416
1202
|
existsSync: fs.existsSync.bind(fs),
|
|
@@ -426,7 +1212,7 @@ var GIT_STATUS_ARGS = [
|
|
|
426
1212
|
"--porcelain"
|
|
427
1213
|
];
|
|
428
1214
|
async function runGitStatusRead(args, cwd) {
|
|
429
|
-
const raw = await
|
|
1215
|
+
const raw = await _internals2.runGitStatus(args, cwd);
|
|
430
1216
|
if (typeof raw === "string")
|
|
431
1217
|
return { stdout: raw };
|
|
432
1218
|
if (raw === null)
|
|
@@ -437,8 +1223,16 @@ async function runGitStatusRead(args, cwd) {
|
|
|
437
1223
|
cause: { code: raw.code, status: raw.status, stderrTail: raw.stderrTail }
|
|
438
1224
|
};
|
|
439
1225
|
}
|
|
440
|
-
function
|
|
1226
|
+
function parseTrackedDirtyPathsWithExclusions2(statusOutput) {
|
|
441
1227
|
const dirtyPaths = [];
|
|
1228
|
+
const excludedOutOfRoot = [];
|
|
1229
|
+
const push = (candidate) => {
|
|
1230
|
+
if (isOutOfRootRelativePath(candidate)) {
|
|
1231
|
+
excludedOutOfRoot.push(candidate);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
dirtyPaths.push(candidate);
|
|
1235
|
+
};
|
|
442
1236
|
for (const line of statusOutput.split(`
|
|
443
1237
|
`)) {
|
|
444
1238
|
if (!line)
|
|
@@ -452,13 +1246,13 @@ function parseTrackedDirtyPaths2(statusOutput) {
|
|
|
452
1246
|
for (const side of filePath.split(" -> ")) {
|
|
453
1247
|
const cleaned = side.trim().replace(/^"|"$/g, "");
|
|
454
1248
|
if (cleaned)
|
|
455
|
-
|
|
1249
|
+
push(cleaned);
|
|
456
1250
|
}
|
|
457
1251
|
} else {
|
|
458
|
-
|
|
1252
|
+
push(filePath);
|
|
459
1253
|
}
|
|
460
1254
|
}
|
|
461
|
-
return dirtyPaths;
|
|
1255
|
+
return { dirtyPaths, excludedOutOfRoot };
|
|
462
1256
|
}
|
|
463
1257
|
async function readTrackedDirtyPaths(directory) {
|
|
464
1258
|
const read = await runGitStatusRead(GIT_STATUS_ARGS, directory);
|
|
@@ -466,7 +1260,8 @@ async function readTrackedDirtyPaths(directory) {
|
|
|
466
1260
|
return { ok: false };
|
|
467
1261
|
if ("cause" in read)
|
|
468
1262
|
return { ok: false, failure: read.cause };
|
|
469
|
-
|
|
1263
|
+
const { dirtyPaths, excludedOutOfRoot } = parseTrackedDirtyPathsWithExclusions2(read.stdout);
|
|
1264
|
+
return { ok: true, dirtyPaths, excludedOutOfRoot };
|
|
470
1265
|
}
|
|
471
1266
|
async function recheckBeforeDestructive(directory, expectedDirtyPaths) {
|
|
472
1267
|
const read = await readTrackedDirtyPaths(directory);
|
|
@@ -502,7 +1297,7 @@ function resolveCandidates(scopeTarget, extra) {
|
|
|
502
1297
|
}
|
|
503
1298
|
function scopeDigest(kind, candidates) {
|
|
504
1299
|
const paths = candidates.map((c) => {
|
|
505
|
-
const resolved =
|
|
1300
|
+
const resolved = path3.resolve(c.path);
|
|
506
1301
|
if (resolved.includes("\x00") || resolved.includes(`
|
|
507
1302
|
`)) {
|
|
508
1303
|
throw new Error(`Purge candidate path contains a NUL or newline separator character and cannot be scope-bound: ${JSON.stringify(resolved)}`);
|
|
@@ -514,20 +1309,20 @@ function scopeDigest(kind, candidates) {
|
|
|
514
1309
|
return createHash("sha256").update(`${kind}\x00${sorted}`).digest("hex");
|
|
515
1310
|
}
|
|
516
1311
|
function mintToken(digest) {
|
|
517
|
-
return createHash("sha256").update(`${digest}:${
|
|
1312
|
+
return createHash("sha256").update(`${digest}:${_internals2.randomBytes(16).toString("hex")}`).digest("hex").slice(0, 24);
|
|
518
1313
|
}
|
|
519
1314
|
function readPending(directory) {
|
|
520
1315
|
try {
|
|
521
|
-
const raw =
|
|
1316
|
+
const raw = _internals2.readFileSync(pendingPath(directory), "utf-8");
|
|
522
1317
|
const parsed = JSON.parse(raw);
|
|
523
1318
|
if (!parsed || parsed.schemaVersion !== 1 || typeof parsed.scopeDigest !== "string" || typeof parsed.confirmToken !== "string" || typeof parsed.createdAt !== "number") {
|
|
524
1319
|
return null;
|
|
525
1320
|
}
|
|
526
1321
|
if (!Number.isFinite(parsed.createdAt))
|
|
527
1322
|
return null;
|
|
528
|
-
if (parsed.createdAt >
|
|
1323
|
+
if (parsed.createdAt > _internals2.now() + 60000)
|
|
529
1324
|
return null;
|
|
530
|
-
if (
|
|
1325
|
+
if (_internals2.now() - parsed.createdAt > PENDING_PURGE_TTL_MS) {
|
|
531
1326
|
return null;
|
|
532
1327
|
}
|
|
533
1328
|
return {
|
|
@@ -541,7 +1336,7 @@ function readPending(directory) {
|
|
|
541
1336
|
}
|
|
542
1337
|
}
|
|
543
1338
|
function writePending(directory, record) {
|
|
544
|
-
|
|
1339
|
+
_internals2.atomicWriteSwarmFileSync(pendingPath(directory), JSON.stringify(record, null, 2));
|
|
545
1340
|
}
|
|
546
1341
|
function previewDestructivePurge(scopeTarget, projectRoot, extra) {
|
|
547
1342
|
const scope = resolveCandidates(scopeTarget, extra);
|
|
@@ -566,7 +1361,7 @@ function issueConfirmToken(scopeTarget, projectRoot, extra) {
|
|
|
566
1361
|
schemaVersion: 1,
|
|
567
1362
|
scopeDigest: digest,
|
|
568
1363
|
confirmToken: token,
|
|
569
|
-
createdAt:
|
|
1364
|
+
createdAt: _internals2.now()
|
|
570
1365
|
});
|
|
571
1366
|
return token;
|
|
572
1367
|
}
|
|
@@ -575,7 +1370,7 @@ function consumeConfirmToken(scopeTarget, projectRoot, token, extra) {
|
|
|
575
1370
|
if (!verdict.ok)
|
|
576
1371
|
return verdict;
|
|
577
1372
|
try {
|
|
578
|
-
|
|
1373
|
+
_internals2.rmSync(pendingPath(projectRoot), { force: true });
|
|
579
1374
|
} catch (error) {
|
|
580
1375
|
log(`[destructive-purge] could not consume pending record: ${error instanceof Error ? error.message : String(error)}`);
|
|
581
1376
|
}
|
|
@@ -609,14 +1404,14 @@ function executeDestructivePurge(scopeTarget, projectRoot, token, extra) {
|
|
|
609
1404
|
const { candidates } = resolveCandidates(scopeTarget, extra);
|
|
610
1405
|
const purged = [];
|
|
611
1406
|
for (const candidate of candidates) {
|
|
612
|
-
if (!
|
|
1407
|
+
if (!_internals2.existsSync(candidate.path))
|
|
613
1408
|
continue;
|
|
614
1409
|
try {
|
|
615
|
-
|
|
1410
|
+
_internals2.rmSync(candidate.path, { recursive: true, force: true });
|
|
616
1411
|
purged.push(candidate.path);
|
|
617
1412
|
} catch (error) {
|
|
618
1413
|
try {
|
|
619
|
-
|
|
1414
|
+
_internals2.rmSync(pendingPath(projectRoot), { force: true });
|
|
620
1415
|
} catch {}
|
|
621
1416
|
return {
|
|
622
1417
|
ok: false,
|
|
@@ -625,7 +1420,7 @@ function executeDestructivePurge(scopeTarget, projectRoot, token, extra) {
|
|
|
625
1420
|
}
|
|
626
1421
|
}
|
|
627
1422
|
try {
|
|
628
|
-
|
|
1423
|
+
_internals2.rmSync(pendingPath(projectRoot), { force: true });
|
|
629
1424
|
} catch (error) {
|
|
630
1425
|
log(`[destructive-purge] could not consume pending record: ${error instanceof Error ? error.message : String(error)}`);
|
|
631
1426
|
}
|
|
@@ -634,12 +1429,12 @@ function executeDestructivePurge(scopeTarget, projectRoot, token, extra) {
|
|
|
634
1429
|
|
|
635
1430
|
// src/commands/reset-backup.ts
|
|
636
1431
|
import * as fs2 from "fs";
|
|
637
|
-
import * as
|
|
1432
|
+
import * as path4 from "path";
|
|
638
1433
|
var RESET_BACKUP_RETENTION = 5;
|
|
639
1434
|
function backupSwarmStateBeforeReset(directory, kind, relEntries, opts) {
|
|
640
1435
|
const warnings = [];
|
|
641
1436
|
const copied = [];
|
|
642
|
-
const swarmDir =
|
|
1437
|
+
const swarmDir = path4.join(directory, ".swarm");
|
|
643
1438
|
try {
|
|
644
1439
|
const stat = fs2.lstatSync(swarmDir);
|
|
645
1440
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -651,8 +1446,8 @@ function backupSwarmStateBeforeReset(directory, kind, relEntries, opts) {
|
|
|
651
1446
|
return { backupDir: null, copied, warnings };
|
|
652
1447
|
}
|
|
653
1448
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
654
|
-
const backupsRoot =
|
|
655
|
-
const backupDir =
|
|
1449
|
+
const backupsRoot = path4.join(swarmDir, opts?.backupsRoot ?? "reset-backups");
|
|
1450
|
+
const backupDir = path4.join(backupsRoot, `${kind}-${timestamp}`);
|
|
656
1451
|
let backupDirCreated = false;
|
|
657
1452
|
const ensureBackupDir = () => {
|
|
658
1453
|
if (!backupDirCreated) {
|
|
@@ -672,8 +1467,8 @@ function backupSwarmStateBeforeReset(directory, kind, relEntries, opts) {
|
|
|
672
1467
|
continue;
|
|
673
1468
|
try {
|
|
674
1469
|
ensureBackupDir();
|
|
675
|
-
const dest =
|
|
676
|
-
fs2.mkdirSync(
|
|
1470
|
+
const dest = path4.join(backupDir, rel);
|
|
1471
|
+
fs2.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
677
1472
|
fs2.cpSync(src, dest, { recursive: true });
|
|
678
1473
|
copied.push(rel);
|
|
679
1474
|
} catch (err) {
|
|
@@ -688,7 +1483,7 @@ function backupSwarmStateBeforeReset(directory, kind, relEntries, opts) {
|
|
|
688
1483
|
}
|
|
689
1484
|
return { backupDir: null, copied, warnings };
|
|
690
1485
|
}
|
|
691
|
-
pruneOldResetBackups(backupsRoot, warnings,
|
|
1486
|
+
pruneOldResetBackups(backupsRoot, warnings, path4.basename(backupDir));
|
|
692
1487
|
return { backupDir, copied, warnings };
|
|
693
1488
|
}
|
|
694
1489
|
var BACKUP_KIND_PREFIXES = [
|
|
@@ -712,7 +1507,7 @@ function pruneOldResetBackups(backupsRoot, warnings, keepDirName) {
|
|
|
712
1507
|
if (stale.name === keepDirName)
|
|
713
1508
|
continue;
|
|
714
1509
|
try {
|
|
715
|
-
fs2.rmSync(
|
|
1510
|
+
fs2.rmSync(path4.join(backupsRoot, stale.name), {
|
|
716
1511
|
recursive: true,
|
|
717
1512
|
force: true
|
|
718
1513
|
});
|
|
@@ -727,7 +1522,7 @@ function pruneOldResetBackups(backupsRoot, warnings, keepDirName) {
|
|
|
727
1522
|
function backupTrackedChangesBeforeRollback(directory, repoRelPaths) {
|
|
728
1523
|
const warnings = [];
|
|
729
1524
|
const copied = [];
|
|
730
|
-
const swarmDir =
|
|
1525
|
+
const swarmDir = path4.join(directory, ".swarm");
|
|
731
1526
|
try {
|
|
732
1527
|
const stat = fs2.lstatSync(swarmDir);
|
|
733
1528
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -739,8 +1534,8 @@ function backupTrackedChangesBeforeRollback(directory, repoRelPaths) {
|
|
|
739
1534
|
return { backupDir: null, copied, warnings };
|
|
740
1535
|
}
|
|
741
1536
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
742
|
-
const backupsRoot =
|
|
743
|
-
const backupDir =
|
|
1537
|
+
const backupsRoot = path4.join(swarmDir, "rollback-backups");
|
|
1538
|
+
const backupDir = path4.join(backupsRoot, `rollback-git-${timestamp}`);
|
|
744
1539
|
let backupDirCreated = false;
|
|
745
1540
|
const ensureBackupDir = () => {
|
|
746
1541
|
if (!backupDirCreated) {
|
|
@@ -751,13 +1546,13 @@ function backupTrackedChangesBeforeRollback(directory, repoRelPaths) {
|
|
|
751
1546
|
for (const rel of repoRelPaths) {
|
|
752
1547
|
if (!rel)
|
|
753
1548
|
continue;
|
|
754
|
-
const src =
|
|
1549
|
+
const src = path4.join(directory, rel);
|
|
755
1550
|
if (!fs2.existsSync(src))
|
|
756
1551
|
continue;
|
|
757
1552
|
try {
|
|
758
1553
|
ensureBackupDir();
|
|
759
|
-
const dest =
|
|
760
|
-
fs2.mkdirSync(
|
|
1554
|
+
const dest = path4.join(backupDir, "tracked", rel);
|
|
1555
|
+
fs2.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
761
1556
|
fs2.cpSync(src, dest, { recursive: true });
|
|
762
1557
|
copied.push(rel);
|
|
763
1558
|
} catch (err) {
|
|
@@ -772,38 +1567,19 @@ function backupTrackedChangesBeforeRollback(directory, repoRelPaths) {
|
|
|
772
1567
|
}
|
|
773
1568
|
return { backupDir: null, copied, warnings };
|
|
774
1569
|
}
|
|
775
|
-
pruneOldResetBackups(backupsRoot, warnings,
|
|
1570
|
+
pruneOldResetBackups(backupsRoot, warnings, path4.basename(backupDir));
|
|
776
1571
|
return { backupDir, copied, warnings };
|
|
777
1572
|
}
|
|
778
1573
|
|
|
779
1574
|
// src/tools/checkpoint.ts
|
|
780
1575
|
init_git_executable();
|
|
781
1576
|
|
|
782
|
-
// src/utils/transient-retry.ts
|
|
783
|
-
var MAX_TRANSIENT_RETRIES = 5;
|
|
784
|
-
var RETRY_BASE_DELAY_MS = 200;
|
|
785
|
-
function isTransientSpawnError(error) {
|
|
786
|
-
if (!error || typeof error !== "object")
|
|
787
|
-
return false;
|
|
788
|
-
const code = error.code;
|
|
789
|
-
return code === "ETIMEDOUT";
|
|
790
|
-
}
|
|
791
|
-
function transientBackoff(attempt) {
|
|
792
|
-
const delay = RETRY_BASE_DELAY_MS * 2 ** attempt;
|
|
793
|
-
try {
|
|
794
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
795
|
-
} catch {
|
|
796
|
-
const start = Date.now();
|
|
797
|
-
while (Date.now() - start < delay) {}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
1577
|
// src/tools/create-tool.ts
|
|
802
1578
|
init_zod();
|
|
803
1579
|
|
|
804
1580
|
// src/tools/resolve-working-directory.ts
|
|
805
1581
|
import * as fs3 from "fs";
|
|
806
|
-
import * as
|
|
1582
|
+
import * as path5 from "path";
|
|
807
1583
|
function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
808
1584
|
if (workingDirectory == null || workingDirectory === "") {
|
|
809
1585
|
if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
|
|
@@ -848,8 +1624,8 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
848
1624
|
message: "Invalid working_directory: path traversal sequences (..) are not allowed"
|
|
849
1625
|
};
|
|
850
1626
|
}
|
|
851
|
-
const normalizedDir =
|
|
852
|
-
const resolvedDir =
|
|
1627
|
+
const normalizedDir = path5.normalize(workingDirectory);
|
|
1628
|
+
const resolvedDir = path5.resolve(normalizedDir);
|
|
853
1629
|
let statResult;
|
|
854
1630
|
try {
|
|
855
1631
|
statResult = fs3.statSync(resolvedDir);
|
|
@@ -876,7 +1652,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
876
1652
|
if (typeof fallbackDirectory !== "string" || fallbackDirectory === "") {
|
|
877
1653
|
return { success: true, directory: resolvedDir };
|
|
878
1654
|
}
|
|
879
|
-
const resolvedFallback =
|
|
1655
|
+
const resolvedFallback = path5.resolve(fallbackDirectory);
|
|
880
1656
|
let fallbackExists = false;
|
|
881
1657
|
try {
|
|
882
1658
|
fs3.statSync(resolvedFallback);
|
|
@@ -966,8 +1742,8 @@ var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
|
966
1742
|
var PLAN_LOCK_PATH = "plan.json";
|
|
967
1743
|
var CHECKPOINT_AGENT_NAME = "checkpoint";
|
|
968
1744
|
var MAX_LABEL_LENGTH = 100;
|
|
969
|
-
var
|
|
970
|
-
var
|
|
1745
|
+
var GIT_TIMEOUT_MS2 = 30000;
|
|
1746
|
+
var GIT_MAX_BUFFER_BYTES2 = 5 * 1024 * 1024;
|
|
971
1747
|
var CHECKPOINT_LOCK_MAX_ATTEMPTS = 20;
|
|
972
1748
|
var CHECKPOINT_LOCK_DELAY_MS = 250;
|
|
973
1749
|
var CHECKPOINT_LOCK_DEADLINE_MS = 45000;
|
|
@@ -981,14 +1757,17 @@ var SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
|
|
|
981
1757
|
var SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
|
|
982
1758
|
var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
983
1759
|
var NON_ASCII_PATTERN = /[^\x20-\x7E]/;
|
|
984
|
-
var
|
|
1760
|
+
var _internals3 = {
|
|
985
1761
|
tryAcquireLock,
|
|
986
1762
|
loadPlan,
|
|
987
|
-
gitExec,
|
|
1763
|
+
gitExec: gitExec2,
|
|
988
1764
|
sleep: (ms) => new Promise((resolve3) => setTimeout(resolve3, ms)),
|
|
989
1765
|
findCommitByExactSubject,
|
|
990
1766
|
stageAllExcludingSwarm,
|
|
991
|
-
resolveGitExecutable
|
|
1767
|
+
resolveGitExecutable,
|
|
1768
|
+
measureGitDestruction,
|
|
1769
|
+
readGitStatusAsync,
|
|
1770
|
+
readGitRepositoryStatusDetailedAsync
|
|
992
1771
|
};
|
|
993
1772
|
function serializeResult(action, payload) {
|
|
994
1773
|
return JSON.stringify({
|
|
@@ -1039,7 +1818,7 @@ function validateLabel(label) {
|
|
|
1039
1818
|
return null;
|
|
1040
1819
|
}
|
|
1041
1820
|
function getCheckpointLogPath(directory) {
|
|
1042
|
-
return
|
|
1821
|
+
return path6.join(directory, CHECKPOINT_LOG_PATH);
|
|
1043
1822
|
}
|
|
1044
1823
|
function readCheckpointLog(directory) {
|
|
1045
1824
|
const logPath = getCheckpointLogPath(directory);
|
|
@@ -1057,7 +1836,7 @@ function readCheckpointLog(directory) {
|
|
|
1057
1836
|
}
|
|
1058
1837
|
function writeCheckpointLog(log2, directory) {
|
|
1059
1838
|
const logPath = getCheckpointLogPath(directory);
|
|
1060
|
-
const dir =
|
|
1839
|
+
const dir = path6.dirname(logPath);
|
|
1061
1840
|
if (!fs4.existsSync(dir)) {
|
|
1062
1841
|
fs4.mkdirSync(dir, { recursive: true });
|
|
1063
1842
|
}
|
|
@@ -1124,17 +1903,17 @@ function upsertTaskCompletionCheckpointEntry(directory, log2, entry, maxCheckpoi
|
|
|
1124
1903
|
}
|
|
1125
1904
|
}
|
|
1126
1905
|
function stageAllExcludingSwarm(directory) {
|
|
1127
|
-
|
|
1906
|
+
_internals3.gitExec(["add", "--all", "--", ...NON_SWARM_COMMIT_PATHSPEC], directory);
|
|
1128
1907
|
}
|
|
1129
|
-
function
|
|
1908
|
+
function gitExec2(args, cwd) {
|
|
1130
1909
|
for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
|
|
1131
|
-
const result =
|
|
1910
|
+
const result = child_process2.spawnSync(_internals3.resolveGitExecutable(), args, {
|
|
1132
1911
|
cwd,
|
|
1133
1912
|
encoding: "utf-8",
|
|
1134
|
-
timeout:
|
|
1913
|
+
timeout: GIT_TIMEOUT_MS2,
|
|
1135
1914
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1136
1915
|
windowsHide: true,
|
|
1137
|
-
maxBuffer:
|
|
1916
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES2
|
|
1138
1917
|
});
|
|
1139
1918
|
if (result.error) {
|
|
1140
1919
|
const code = result.error.code;
|
|
@@ -1163,29 +1942,10 @@ function gitExec(args, cwd) {
|
|
|
1163
1942
|
throw new Error("git command failed after transient retries");
|
|
1164
1943
|
}
|
|
1165
1944
|
function getCurrentSha(directory) {
|
|
1166
|
-
return
|
|
1167
|
-
}
|
|
1168
|
-
function isGitRepo(directory) {
|
|
1169
|
-
try {
|
|
1170
|
-
gitExec(["rev-parse", "--git-dir"], directory);
|
|
1171
|
-
return { isRepo: true };
|
|
1172
|
-
} catch (e) {
|
|
1173
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1174
|
-
const isTransient = /ETIMEDOUT|timed out/i.test(message) && !/not a git repository/i.test(message);
|
|
1175
|
-
if (isTransient) {
|
|
1176
|
-
return {
|
|
1177
|
-
isRepo: false,
|
|
1178
|
-
warning: "git probe failed after retry exhaustion - treating as not a git repository"
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
return {
|
|
1182
|
-
isRepo: false,
|
|
1183
|
-
warning: "git probe failed - directory may not be a git repository"
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1945
|
+
return gitExec2(["rev-parse", "HEAD"], directory).trim();
|
|
1186
1946
|
}
|
|
1187
1947
|
function findCommitByExactSubject(directory, subject) {
|
|
1188
|
-
const output =
|
|
1948
|
+
const output = _internals3.gitExec([
|
|
1189
1949
|
"log",
|
|
1190
1950
|
"HEAD",
|
|
1191
1951
|
"--format=%H%x00%s",
|
|
@@ -1215,7 +1975,7 @@ async function withTaskCompletionLocksResult(input) {
|
|
|
1215
1975
|
attempts++;
|
|
1216
1976
|
let planLockResult;
|
|
1217
1977
|
try {
|
|
1218
|
-
planLockResult = await
|
|
1978
|
+
planLockResult = await _internals3.tryAcquireLock(input.directory, PLAN_LOCK_PATH, CHECKPOINT_AGENT_NAME, `save-task-completion-plan-${input.taskId}`);
|
|
1219
1979
|
} catch (error) {
|
|
1220
1980
|
if (input.onFailure) {
|
|
1221
1981
|
return input.onFailure(error);
|
|
@@ -1229,13 +1989,13 @@ async function withTaskCompletionLocksResult(input) {
|
|
|
1229
1989
|
if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
|
|
1230
1990
|
break;
|
|
1231
1991
|
}
|
|
1232
|
-
await
|
|
1992
|
+
await _internals3.sleep(CHECKPOINT_LOCK_DELAY_MS);
|
|
1233
1993
|
continue;
|
|
1234
1994
|
}
|
|
1235
1995
|
try {
|
|
1236
1996
|
let checkpointLockResult;
|
|
1237
1997
|
try {
|
|
1238
|
-
checkpointLockResult = await
|
|
1998
|
+
checkpointLockResult = await _internals3.tryAcquireLock(input.directory, CHECKPOINT_LOG_PATH, CHECKPOINT_AGENT_NAME, `save-task-completion-checkpoint-${input.taskId}`);
|
|
1239
1999
|
} catch (error) {
|
|
1240
2000
|
if (input.onFailure) {
|
|
1241
2001
|
return input.onFailure(error);
|
|
@@ -1249,7 +2009,7 @@ async function withTaskCompletionLocksResult(input) {
|
|
|
1249
2009
|
if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
|
|
1250
2010
|
break;
|
|
1251
2011
|
}
|
|
1252
|
-
await
|
|
2012
|
+
await _internals3.sleep(CHECKPOINT_LOCK_DELAY_MS);
|
|
1253
2013
|
continue;
|
|
1254
2014
|
}
|
|
1255
2015
|
try {
|
|
@@ -1305,7 +2065,7 @@ async function withCheckpointMutationLockResult(input) {
|
|
|
1305
2065
|
attempts++;
|
|
1306
2066
|
let lockResult;
|
|
1307
2067
|
try {
|
|
1308
|
-
lockResult = await
|
|
2068
|
+
lockResult = await _internals3.tryAcquireLock(input.directory, CHECKPOINT_LOG_PATH, CHECKPOINT_AGENT_NAME, input.operationKey);
|
|
1309
2069
|
} catch (error) {
|
|
1310
2070
|
if (input.onFailure) {
|
|
1311
2071
|
return input.onFailure(error);
|
|
@@ -1342,7 +2102,7 @@ async function withCheckpointMutationLockResult(input) {
|
|
|
1342
2102
|
if (attempts >= CHECKPOINT_LOCK_MAX_ATTEMPTS || Date.now() + CHECKPOINT_LOCK_DELAY_MS > deadline) {
|
|
1343
2103
|
break;
|
|
1344
2104
|
}
|
|
1345
|
-
await
|
|
2105
|
+
await _internals3.sleep(CHECKPOINT_LOCK_DELAY_MS);
|
|
1346
2106
|
}
|
|
1347
2107
|
if (input.onBusy) {
|
|
1348
2108
|
return input.onBusy(attempts);
|
|
@@ -1367,19 +2127,19 @@ function handleSave(label, directory) {
|
|
|
1367
2127
|
});
|
|
1368
2128
|
}
|
|
1369
2129
|
const timestamp = new Date().toISOString();
|
|
1370
|
-
|
|
2130
|
+
_internals3.stageAllExcludingSwarm(directory);
|
|
1371
2131
|
const hasStagedChanges = (() => {
|
|
1372
2132
|
try {
|
|
1373
|
-
|
|
2133
|
+
gitExec2(["diff", "--cached", "--quiet"], directory);
|
|
1374
2134
|
return false;
|
|
1375
2135
|
} catch {
|
|
1376
2136
|
return true;
|
|
1377
2137
|
}
|
|
1378
2138
|
})();
|
|
1379
2139
|
if (hasStagedChanges) {
|
|
1380
|
-
|
|
2140
|
+
gitExec2(["commit", "-m", `checkpoint: ${label}`], directory);
|
|
1381
2141
|
} else if (allowEmptyCommits) {
|
|
1382
|
-
|
|
2142
|
+
gitExec2(["commit", "--allow-empty", "-m", `checkpoint: ${label}`], directory);
|
|
1383
2143
|
}
|
|
1384
2144
|
const newSha = getCurrentSha(directory);
|
|
1385
2145
|
appendCheckpointEntry(directory, log2, {
|
|
@@ -1416,8 +2176,8 @@ async function saveCheckpointRecord(label, directory) {
|
|
|
1416
2176
|
return { success: false, error: `duplicate label: "${label}"` };
|
|
1417
2177
|
}
|
|
1418
2178
|
let sha = "";
|
|
1419
|
-
const
|
|
1420
|
-
if (
|
|
2179
|
+
const saveProbe = await _internals3.readGitStatusAsync(["rev-parse", "--git-dir"], directory);
|
|
2180
|
+
if (saveProbe.ok) {
|
|
1421
2181
|
try {
|
|
1422
2182
|
sha = getCurrentSha(directory);
|
|
1423
2183
|
} catch {
|
|
@@ -1456,14 +2216,16 @@ async function handleRestore(label, directory, confirmToken) {
|
|
|
1456
2216
|
error: `checkpoint not found: "${label}"`
|
|
1457
2217
|
});
|
|
1458
2218
|
}
|
|
1459
|
-
const measurement = await measureGitDestruction(directory, checkpoint.sha);
|
|
2219
|
+
const measurement = await _internals3.measureGitDestruction(directory, checkpoint.sha);
|
|
1460
2220
|
if (measurement.kind === "unreadable") {
|
|
1461
|
-
const cause = describeUnreadableCause(measurement);
|
|
2221
|
+
const cause = describeUnreadableCause(measurement, measurement.source);
|
|
2222
|
+
const unreadableNote = measurement.excludedOutOfRoot && measurement.excludedOutOfRoot.length > 0 ? ` \u2014 ${measurement.excludedOutOfRoot.length} out-of-root porcelain record(s) had been excluded from the scope before the read failed (first: ${formatExcludedRecord(measurement.excludedOutOfRoot[0])})` : "";
|
|
1462
2223
|
return serializeResult("restore", {
|
|
1463
2224
|
success: false,
|
|
1464
|
-
error: `cannot verify the working tree (git status unreadable; ${cause}) \u2014 refusing to restore without a preview.`
|
|
2225
|
+
error: `cannot verify the working tree (${measurement.source === "enumeration" ? "the target-tree listing could not be read" : "git status unreadable"}; ${cause})${unreadableNote} \u2014 refusing to restore without a preview.`
|
|
1465
2226
|
});
|
|
1466
2227
|
}
|
|
2228
|
+
const exclusionNote = measurement.excludedOutOfRoot.length > 0 ? `${measurement.excludedOutOfRoot.length} out-of-root porcelain record(s) were excluded from this scope (first: ${formatExcludedRecord(measurement.excludedOutOfRoot[0])})` : null;
|
|
1467
2229
|
if (measurement.dirtyRelPaths.length > 0) {
|
|
1468
2230
|
const candidates = trackedDirtyCandidates(directory, measurement.dirtyRelPaths);
|
|
1469
2231
|
const scopeAnchor = candidates[0].path;
|
|
@@ -1481,6 +2243,7 @@ async function handleRestore(label, directory, confirmToken) {
|
|
|
1481
2243
|
`Restoring to checkpoint "${label}" runs a hard git reset and would DESTROY ${candidates.length} uncommitted tracked file(s):`,
|
|
1482
2244
|
...shown.map((p) => ` - ${p}`),
|
|
1483
2245
|
...overflow > 0 ? [` (+${overflow} more)`] : [],
|
|
2246
|
+
...exclusionNote ? [`\u26A0 ${exclusionNote}`] : [],
|
|
1484
2247
|
"Backups are taken automatically once confirmed."
|
|
1485
2248
|
],
|
|
1486
2249
|
confirm_token: token,
|
|
@@ -1495,7 +2258,7 @@ async function handleRestore(label, directory, confirmToken) {
|
|
|
1495
2258
|
});
|
|
1496
2259
|
}
|
|
1497
2260
|
const backup = backupTrackedChangesBeforeRollback(directory, measurement.dirtyRelPaths);
|
|
1498
|
-
const atRiskExisting = measurement.dirtyRelPaths.filter((rel) => fs4.existsSync(
|
|
2261
|
+
const atRiskExisting = measurement.dirtyRelPaths.filter((rel) => fs4.existsSync(path6.join(directory, rel))).length;
|
|
1499
2262
|
if (!backup.backupDir || backup.copied.length < atRiskExisting) {
|
|
1500
2263
|
return serializeResult("restore", {
|
|
1501
2264
|
success: false,
|
|
@@ -1503,7 +2266,7 @@ async function handleRestore(label, directory, confirmToken) {
|
|
|
1503
2266
|
});
|
|
1504
2267
|
}
|
|
1505
2268
|
const logBeforeReset2 = log2;
|
|
1506
|
-
|
|
2269
|
+
gitExec2(["reset", "--hard", checkpoint.sha], directory);
|
|
1507
2270
|
writeCheckpointLog(logBeforeReset2, directory);
|
|
1508
2271
|
return serializeResult("restore", {
|
|
1509
2272
|
success: true,
|
|
@@ -1511,17 +2274,17 @@ async function handleRestore(label, directory, confirmToken) {
|
|
|
1511
2274
|
sha: checkpoint.sha,
|
|
1512
2275
|
backup_dir: backup.backupDir,
|
|
1513
2276
|
...backup.warnings.length > 0 ? { backup_warnings: backup.warnings } : {},
|
|
1514
|
-
message: `Restored to checkpoint: "${label}" (hard reset)${backup.backupDir ? `; destroyed bytes backed up to ${
|
|
2277
|
+
message: `Restored to checkpoint: "${label}" (hard reset)${backup.backupDir ? `; destroyed bytes backed up to ${path6.relative(directory, backup.backupDir)}` : ""}${backup.warnings.length > 0 ? `; warnings: ${backup.warnings.join("; ")}` : ""}${exclusionNote ? `; \u26A0 ${exclusionNote}` : ""}`
|
|
1515
2278
|
});
|
|
1516
2279
|
}
|
|
1517
2280
|
const logBeforeReset = log2;
|
|
1518
|
-
|
|
2281
|
+
gitExec2(["reset", "--hard", checkpoint.sha], directory);
|
|
1519
2282
|
writeCheckpointLog(logBeforeReset, directory);
|
|
1520
2283
|
return serializeResult("restore", {
|
|
1521
2284
|
success: true,
|
|
1522
2285
|
label,
|
|
1523
2286
|
sha: checkpoint.sha,
|
|
1524
|
-
message: `Restored to checkpoint: "${label}" (hard reset)`
|
|
2287
|
+
message: `Restored to checkpoint: "${label}" (hard reset)${exclusionNote ? `; \u26A0 ${exclusionNote}` : ""}`
|
|
1525
2288
|
});
|
|
1526
2289
|
} catch (e) {
|
|
1527
2290
|
return serializeResult("restore", {
|
|
@@ -1568,7 +2331,7 @@ async function handleSaveTaskCompletion(taskId, directory) {
|
|
|
1568
2331
|
directory,
|
|
1569
2332
|
taskId,
|
|
1570
2333
|
run: async () => {
|
|
1571
|
-
const plan = await
|
|
2334
|
+
const plan = await _internals3.loadPlan(directory);
|
|
1572
2335
|
if (!plan) {
|
|
1573
2336
|
return serializeResult("save_task_completion", {
|
|
1574
2337
|
success: false,
|
|
@@ -1588,7 +2351,7 @@ async function handleSaveTaskCompletion(taskId, directory) {
|
|
|
1588
2351
|
error: `save_task_completion failed: task "${taskId}" is ${task.status}, not completed`
|
|
1589
2352
|
});
|
|
1590
2353
|
}
|
|
1591
|
-
await
|
|
2354
|
+
await _internals3.afterTaskEligibilityRead?.(directory, task.id);
|
|
1592
2355
|
const planIdentityHash = derivePlanIdentityHash(plan);
|
|
1593
2356
|
const planId = derivePlanId(plan);
|
|
1594
2357
|
const ledgerEvents = await readLedgerEvents(directory);
|
|
@@ -1624,12 +2387,12 @@ async function handleSaveTaskCompletion(taskId, directory) {
|
|
|
1624
2387
|
message: `Checkpoint task completion already logged for "${descriptor.taskId}"`
|
|
1625
2388
|
});
|
|
1626
2389
|
}
|
|
1627
|
-
const recoveredSha =
|
|
2390
|
+
const recoveredSha = _internals3.findCommitByExactSubject(directory, descriptor.subject);
|
|
1628
2391
|
let committedSha = null;
|
|
1629
2392
|
committedSha = recoveredSha ?? receipt.sha;
|
|
1630
2393
|
if (!committedSha) {
|
|
1631
|
-
|
|
1632
|
-
|
|
2394
|
+
_internals3.stageAllExcludingSwarm(directory);
|
|
2395
|
+
_internals3.gitExec([
|
|
1633
2396
|
"commit",
|
|
1634
2397
|
"--allow-empty",
|
|
1635
2398
|
"-m",
|
|
@@ -1671,11 +2434,12 @@ var checkpoint = createSwarmTool({
|
|
|
1671
2434
|
task_id: exports_external.string().optional().describe('Plan task ID (required for save_task_completion), e.g. "1.1"')
|
|
1672
2435
|
},
|
|
1673
2436
|
execute: async (args, directory) => {
|
|
1674
|
-
const
|
|
1675
|
-
if (!
|
|
2437
|
+
const repoStatus = await _internals3.readGitRepositoryStatusDetailedAsync(directory);
|
|
2438
|
+
if (!repoStatus.isRepo) {
|
|
2439
|
+
const reason = repoStatus.reason === "not_git_repo" ? "not a git repository" : repoStatus.reason === "git_unavailable" ? "git executable is not available" : `git probe failed (${repoStatus.message})`;
|
|
1676
2440
|
return serializeResult("unknown", {
|
|
1677
2441
|
success: false,
|
|
1678
|
-
error: `${
|
|
2442
|
+
error: `${reason} - checkpoint tools require a git repository`
|
|
1679
2443
|
});
|
|
1680
2444
|
}
|
|
1681
2445
|
let action;
|
|
@@ -1764,4 +2528,4 @@ var checkpoint = createSwarmTool({
|
|
|
1764
2528
|
}
|
|
1765
2529
|
});
|
|
1766
2530
|
|
|
1767
|
-
export { MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, gitStatusReadInternals, readGitStatusAsync, readGitRepositoryStatusAsync, ROLLBACK_GIT_KIND, ROLLBACK_PHASE_KIND, trackedDirtyCandidates, swarmOverwriteCandidates, describeUnreadableCause, measureGitDestruction, GIT_STATUS_ARGS,
|
|
2531
|
+
export { MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, assertSafeGitRefArg, readLaneEnvFileFromDiskSync, isGitRepo, getDefaultBaseBranch, detectDefaultRemoteBranch, resetToRemoteBranch, resetToMainAfterMerge, _internals, gitStatusReadInternals, readGitStatusAsync, readGitRepositoryStatusAsync, readGitRepositoryStatusDetailedAsync, ROLLBACK_GIT_KIND, ROLLBACK_PHASE_KIND, trackedDirtyCandidates, swarmOverwriteCandidates, describeUnreadableCause, formatExcludedRecord, measureGitDestruction, GIT_STATUS_ARGS, parseTrackedDirtyPathsWithExclusions2 as parseTrackedDirtyPathsWithExclusions, recheckBeforeDestructive, previewDestructivePurge, issueConfirmToken, consumeConfirmToken, executeDestructivePurge, backupSwarmStateBeforeReset, resolveWorkingDirectory, createSwarmTool, _internals3 as _internals1, saveCheckpointRecord, checkpoint };
|