@buildautomaton/cli 0.1.50 → 0.1.51
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/cli.js +1653 -1287
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1688 -1322
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4175,8 +4175,8 @@ var init_parseUtil = __esm({
|
|
|
4175
4175
|
init_errors();
|
|
4176
4176
|
init_en();
|
|
4177
4177
|
makeIssue = (params) => {
|
|
4178
|
-
const { data, path:
|
|
4179
|
-
const fullPath = [...
|
|
4178
|
+
const { data, path: path60, errorMaps, issueData } = params;
|
|
4179
|
+
const fullPath = [...path60, ...issueData.path || []];
|
|
4180
4180
|
const fullIssue = {
|
|
4181
4181
|
...issueData,
|
|
4182
4182
|
path: fullPath
|
|
@@ -4484,11 +4484,11 @@ var init_types = __esm({
|
|
|
4484
4484
|
init_parseUtil();
|
|
4485
4485
|
init_util();
|
|
4486
4486
|
ParseInputLazyPath = class {
|
|
4487
|
-
constructor(parent, value,
|
|
4487
|
+
constructor(parent, value, path60, key) {
|
|
4488
4488
|
this._cachedPath = [];
|
|
4489
4489
|
this.parent = parent;
|
|
4490
4490
|
this.data = value;
|
|
4491
|
-
this._path =
|
|
4491
|
+
this._path = path60;
|
|
4492
4492
|
this._key = key;
|
|
4493
4493
|
}
|
|
4494
4494
|
get path() {
|
|
@@ -8103,10 +8103,10 @@ function assignProp(target, prop, value) {
|
|
|
8103
8103
|
configurable: true
|
|
8104
8104
|
});
|
|
8105
8105
|
}
|
|
8106
|
-
function getElementAtPath(obj,
|
|
8107
|
-
if (!
|
|
8106
|
+
function getElementAtPath(obj, path60) {
|
|
8107
|
+
if (!path60)
|
|
8108
8108
|
return obj;
|
|
8109
|
-
return
|
|
8109
|
+
return path60.reduce((acc, key) => acc?.[key], obj);
|
|
8110
8110
|
}
|
|
8111
8111
|
function promiseAllObject(promisesObj) {
|
|
8112
8112
|
const keys = Object.keys(promisesObj);
|
|
@@ -8355,11 +8355,11 @@ function aborted(x, startIndex = 0) {
|
|
|
8355
8355
|
}
|
|
8356
8356
|
return false;
|
|
8357
8357
|
}
|
|
8358
|
-
function prefixIssues(
|
|
8358
|
+
function prefixIssues(path60, issues) {
|
|
8359
8359
|
return issues.map((iss) => {
|
|
8360
8360
|
var _a2;
|
|
8361
8361
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
8362
|
-
iss.path.unshift(
|
|
8362
|
+
iss.path.unshift(path60);
|
|
8363
8363
|
return iss;
|
|
8364
8364
|
});
|
|
8365
8365
|
}
|
|
@@ -8548,7 +8548,7 @@ function treeifyError(error40, _mapper) {
|
|
|
8548
8548
|
return issue2.message;
|
|
8549
8549
|
};
|
|
8550
8550
|
const result = { errors: [] };
|
|
8551
|
-
const processError = (error41,
|
|
8551
|
+
const processError = (error41, path60 = []) => {
|
|
8552
8552
|
var _a2, _b;
|
|
8553
8553
|
for (const issue2 of error41.issues) {
|
|
8554
8554
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -8558,7 +8558,7 @@ function treeifyError(error40, _mapper) {
|
|
|
8558
8558
|
} else if (issue2.code === "invalid_element") {
|
|
8559
8559
|
processError({ issues: issue2.issues }, issue2.path);
|
|
8560
8560
|
} else {
|
|
8561
|
-
const fullpath = [...
|
|
8561
|
+
const fullpath = [...path60, ...issue2.path];
|
|
8562
8562
|
if (fullpath.length === 0) {
|
|
8563
8563
|
result.errors.push(mapper(issue2));
|
|
8564
8564
|
continue;
|
|
@@ -8588,9 +8588,9 @@ function treeifyError(error40, _mapper) {
|
|
|
8588
8588
|
processError(error40);
|
|
8589
8589
|
return result;
|
|
8590
8590
|
}
|
|
8591
|
-
function toDotPath(
|
|
8591
|
+
function toDotPath(path60) {
|
|
8592
8592
|
const segs = [];
|
|
8593
|
-
for (const seg of
|
|
8593
|
+
for (const seg of path60) {
|
|
8594
8594
|
if (typeof seg === "number")
|
|
8595
8595
|
segs.push(`[${seg}]`);
|
|
8596
8596
|
else if (typeof seg === "symbol")
|
|
@@ -21053,8 +21053,8 @@ var init_acp = __esm({
|
|
|
21053
21053
|
this.#requestHandler = requestHandler;
|
|
21054
21054
|
this.#notificationHandler = notificationHandler;
|
|
21055
21055
|
this.#stream = stream;
|
|
21056
|
-
this.#closedPromise = new Promise((
|
|
21057
|
-
this.#abortController.signal.addEventListener("abort", () =>
|
|
21056
|
+
this.#closedPromise = new Promise((resolve32) => {
|
|
21057
|
+
this.#abortController.signal.addEventListener("abort", () => resolve32());
|
|
21058
21058
|
});
|
|
21059
21059
|
this.#receive();
|
|
21060
21060
|
}
|
|
@@ -21203,8 +21203,8 @@ var init_acp = __esm({
|
|
|
21203
21203
|
}
|
|
21204
21204
|
async sendRequest(method, params) {
|
|
21205
21205
|
const id = this.#nextRequestId++;
|
|
21206
|
-
const responsePromise = new Promise((
|
|
21207
|
-
this.#pendingResponses.set(id, { resolve:
|
|
21206
|
+
const responsePromise = new Promise((resolve32, reject) => {
|
|
21207
|
+
this.#pendingResponses.set(id, { resolve: resolve32, reject });
|
|
21208
21208
|
});
|
|
21209
21209
|
await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
|
|
21210
21210
|
return responsePromise;
|
|
@@ -22073,10 +22073,10 @@ var require_src2 = __commonJS({
|
|
|
22073
22073
|
var fs_1 = __require("fs");
|
|
22074
22074
|
var debug_1 = __importDefault(require_src());
|
|
22075
22075
|
var log2 = debug_1.default("@kwsites/file-exists");
|
|
22076
|
-
function check2(
|
|
22077
|
-
log2(`checking %s`,
|
|
22076
|
+
function check2(path60, isFile, isDirectory) {
|
|
22077
|
+
log2(`checking %s`, path60);
|
|
22078
22078
|
try {
|
|
22079
|
-
const stat2 = fs_1.statSync(
|
|
22079
|
+
const stat2 = fs_1.statSync(path60);
|
|
22080
22080
|
if (stat2.isFile() && isFile) {
|
|
22081
22081
|
log2(`[OK] path represents a file`);
|
|
22082
22082
|
return true;
|
|
@@ -22096,8 +22096,8 @@ var require_src2 = __commonJS({
|
|
|
22096
22096
|
throw e;
|
|
22097
22097
|
}
|
|
22098
22098
|
}
|
|
22099
|
-
function exists2(
|
|
22100
|
-
return check2(
|
|
22099
|
+
function exists2(path60, type = exports.READABLE) {
|
|
22100
|
+
return check2(path60, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
|
|
22101
22101
|
}
|
|
22102
22102
|
exports.exists = exists2;
|
|
22103
22103
|
exports.FILE = 1;
|
|
@@ -22446,10 +22446,10 @@ async function killChildProcessTreeGracefully(child, graceMs = ACP_PROCESS_TREE_
|
|
|
22446
22446
|
}
|
|
22447
22447
|
await killProcessTree(pid, "SIGTERM");
|
|
22448
22448
|
if (graceMs <= 0) return;
|
|
22449
|
-
const exited = new Promise((
|
|
22450
|
-
child.once("exit", () =>
|
|
22449
|
+
const exited = new Promise((resolve32) => {
|
|
22450
|
+
child.once("exit", () => resolve32());
|
|
22451
22451
|
});
|
|
22452
|
-
await Promise.race([exited, new Promise((
|
|
22452
|
+
await Promise.race([exited, new Promise((resolve32) => setTimeout(resolve32, graceMs))]);
|
|
22453
22453
|
if (child.exitCode == null && child.signalCode == null) {
|
|
22454
22454
|
await killProcessTree(pid, "SIGKILL");
|
|
22455
22455
|
}
|
|
@@ -22466,8 +22466,8 @@ async function gracefulAcpSubprocessDisconnect(params) {
|
|
|
22466
22466
|
await transport.closeSession?.(sessionId);
|
|
22467
22467
|
} catch {
|
|
22468
22468
|
}
|
|
22469
|
-
await new Promise((
|
|
22470
|
-
const timer = setTimeout(
|
|
22469
|
+
await new Promise((resolve32) => {
|
|
22470
|
+
const timer = setTimeout(resolve32, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
|
|
22471
22471
|
timer.unref?.();
|
|
22472
22472
|
});
|
|
22473
22473
|
}
|
|
@@ -23172,9 +23172,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
|
|
|
23172
23172
|
const rawPath = typeof o.path === "string" ? o.path.trim() : "";
|
|
23173
23173
|
const summary = typeof o.summary === "string" ? o.summary.trim() : "";
|
|
23174
23174
|
if (!rawPath || !summary) continue;
|
|
23175
|
-
const
|
|
23176
|
-
if (!
|
|
23177
|
-
rows.push({ path:
|
|
23175
|
+
const path60 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
|
|
23176
|
+
if (!path60) continue;
|
|
23177
|
+
rows.push({ path: path60, summary: clampSummaryToAtMostTwoLines(summary) });
|
|
23178
23178
|
}
|
|
23179
23179
|
return rows;
|
|
23180
23180
|
}
|
|
@@ -23628,11 +23628,11 @@ function createSdkStdioExtNotificationHandler(options) {
|
|
|
23628
23628
|
// src/agents/acp/clients/sdk/sdk-stdio-permission-request-handshake.ts
|
|
23629
23629
|
function awaitSdkStdioPermissionRequestHandshake(params) {
|
|
23630
23630
|
const { requestId, paramsRecord, pending, onRequest } = params;
|
|
23631
|
-
return new Promise((
|
|
23632
|
-
pending.set(requestId, { resolve:
|
|
23631
|
+
return new Promise((resolve32) => {
|
|
23632
|
+
pending.set(requestId, { resolve: resolve32, params: paramsRecord });
|
|
23633
23633
|
if (onRequest == null) {
|
|
23634
23634
|
pending.delete(requestId);
|
|
23635
|
-
|
|
23635
|
+
resolve32({ outcome: { outcome: "denied" } });
|
|
23636
23636
|
return;
|
|
23637
23637
|
}
|
|
23638
23638
|
try {
|
|
@@ -24176,10 +24176,10 @@ function createSdkStdioInitSettle(child) {
|
|
|
24176
24176
|
forceAcpSubprocessDisconnect(child);
|
|
24177
24177
|
reject(err);
|
|
24178
24178
|
},
|
|
24179
|
-
settleResolve(
|
|
24179
|
+
settleResolve(resolve32, handle) {
|
|
24180
24180
|
if (initSettled) return;
|
|
24181
24181
|
initSettled = true;
|
|
24182
|
-
|
|
24182
|
+
resolve32(handle);
|
|
24183
24183
|
}
|
|
24184
24184
|
};
|
|
24185
24185
|
}
|
|
@@ -24248,7 +24248,7 @@ async function createSdkStdioAcpClient(options) {
|
|
|
24248
24248
|
onFileChange,
|
|
24249
24249
|
stderrCapture
|
|
24250
24250
|
});
|
|
24251
|
-
return new Promise((
|
|
24251
|
+
return new Promise((resolve32, reject) => {
|
|
24252
24252
|
const init = createSdkStdioInitSettle(child);
|
|
24253
24253
|
child.on("error", (err) => {
|
|
24254
24254
|
init.settleReject(reject, new Error(formatSpawnError(err, command[0])));
|
|
@@ -24273,7 +24273,7 @@ async function createSdkStdioAcpClient(options) {
|
|
|
24273
24273
|
protocolVersion: PROTOCOL_VERSION2
|
|
24274
24274
|
});
|
|
24275
24275
|
init.settleResolve(
|
|
24276
|
-
|
|
24276
|
+
resolve32,
|
|
24277
24277
|
createSdkStdioHandle({
|
|
24278
24278
|
child,
|
|
24279
24279
|
sessionId: established.sessionId,
|
|
@@ -24381,7 +24381,7 @@ async function proxyToLocal(request) {
|
|
|
24381
24381
|
};
|
|
24382
24382
|
const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
|
|
24383
24383
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
24384
|
-
const once = await new Promise((
|
|
24384
|
+
const once = await new Promise((resolve32) => {
|
|
24385
24385
|
const req = mod.request(opts, (res) => {
|
|
24386
24386
|
const chunks = [];
|
|
24387
24387
|
res.on("data", (c) => chunks.push(c));
|
|
@@ -24392,7 +24392,7 @@ async function proxyToLocal(request) {
|
|
|
24392
24392
|
if (typeof v === "string") headers[k] = v;
|
|
24393
24393
|
else if (Array.isArray(v) && v[0]) headers[k] = v[0];
|
|
24394
24394
|
}
|
|
24395
|
-
|
|
24395
|
+
resolve32({
|
|
24396
24396
|
id: request.id,
|
|
24397
24397
|
statusCode: res.statusCode ?? 0,
|
|
24398
24398
|
headers,
|
|
@@ -24401,7 +24401,7 @@ async function proxyToLocal(request) {
|
|
|
24401
24401
|
});
|
|
24402
24402
|
});
|
|
24403
24403
|
req.on("error", (err) => {
|
|
24404
|
-
|
|
24404
|
+
resolve32({
|
|
24405
24405
|
id: request.id,
|
|
24406
24406
|
statusCode: 0,
|
|
24407
24407
|
headers: {},
|
|
@@ -24483,8 +24483,8 @@ function randomSecret() {
|
|
|
24483
24483
|
}
|
|
24484
24484
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
24485
24485
|
}
|
|
24486
|
-
async function requestPreviewApi(port, secret, method,
|
|
24487
|
-
const url2 = `http://127.0.0.1:${port}${
|
|
24486
|
+
async function requestPreviewApi(port, secret, method, path60, body) {
|
|
24487
|
+
const url2 = `http://127.0.0.1:${port}${path60}`;
|
|
24488
24488
|
const headers = {
|
|
24489
24489
|
[PREVIEW_SECRET_HEADER]: secret,
|
|
24490
24490
|
"Content-Type": "application/json"
|
|
@@ -24496,7 +24496,7 @@ async function requestPreviewApi(port, secret, method, path53, body) {
|
|
|
24496
24496
|
});
|
|
24497
24497
|
const data = await res.json().catch(() => ({}));
|
|
24498
24498
|
if (!res.ok) {
|
|
24499
|
-
throw new Error(data?.error ?? `Preview API ${method} ${
|
|
24499
|
+
throw new Error(data?.error ?? `Preview API ${method} ${path60}: ${res.status}`);
|
|
24500
24500
|
}
|
|
24501
24501
|
return data;
|
|
24502
24502
|
}
|
|
@@ -24711,7 +24711,7 @@ function installBridgeProcessResilience() {
|
|
|
24711
24711
|
}
|
|
24712
24712
|
|
|
24713
24713
|
// src/cli-version.ts
|
|
24714
|
-
var CLI_VERSION = "0.1.
|
|
24714
|
+
var CLI_VERSION = "0.1.51".length > 0 ? "0.1.51" : "0.0.0-dev";
|
|
24715
24715
|
|
|
24716
24716
|
// src/connection/heartbeat/constants.ts
|
|
24717
24717
|
var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -25152,14 +25152,14 @@ var baseOpen = async (options) => {
|
|
|
25152
25152
|
}
|
|
25153
25153
|
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
25154
25154
|
if (options.wait) {
|
|
25155
|
-
return new Promise((
|
|
25155
|
+
return new Promise((resolve32, reject) => {
|
|
25156
25156
|
subprocess.once("error", reject);
|
|
25157
25157
|
subprocess.once("close", (exitCode) => {
|
|
25158
25158
|
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
25159
25159
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
25160
25160
|
return;
|
|
25161
25161
|
}
|
|
25162
|
-
|
|
25162
|
+
resolve32(subprocess);
|
|
25163
25163
|
});
|
|
25164
25164
|
});
|
|
25165
25165
|
}
|
|
@@ -25703,8 +25703,8 @@ function runPendingAuth(options) {
|
|
|
25703
25703
|
let hasOpenedBrowser = false;
|
|
25704
25704
|
let resolved = false;
|
|
25705
25705
|
let resolveAuth;
|
|
25706
|
-
const authPromise = new Promise((
|
|
25707
|
-
resolveAuth =
|
|
25706
|
+
const authPromise = new Promise((resolve32) => {
|
|
25707
|
+
resolveAuth = resolve32;
|
|
25708
25708
|
});
|
|
25709
25709
|
let reconnectAttempt = 0;
|
|
25710
25710
|
const signInQuiet = createEmptyReconnectQuietSlot();
|
|
@@ -25855,7 +25855,7 @@ import sqliteWasm from "node-sqlite3-wasm";
|
|
|
25855
25855
|
|
|
25856
25856
|
// src/runtime/yield-to-event-loop.ts
|
|
25857
25857
|
function yieldToEventLoop() {
|
|
25858
|
-
return new Promise((
|
|
25858
|
+
return new Promise((resolve32) => setImmediate(resolve32));
|
|
25859
25859
|
}
|
|
25860
25860
|
|
|
25861
25861
|
// src/sqlite/cli-sqlite-paths.ts
|
|
@@ -27019,7 +27019,7 @@ async function createCursorAcpClient(options) {
|
|
|
27019
27019
|
onFileChange,
|
|
27020
27020
|
stderrCapture
|
|
27021
27021
|
});
|
|
27022
|
-
return new Promise((
|
|
27022
|
+
return new Promise((resolve32, reject) => {
|
|
27023
27023
|
child.on("error", (err) => {
|
|
27024
27024
|
killChildProcessTree(child, "SIGKILL");
|
|
27025
27025
|
reject(new Error(formatSpawnError(err, command[0])));
|
|
@@ -27031,7 +27031,7 @@ async function createCursorAcpClient(options) {
|
|
|
27031
27031
|
sessionCtx,
|
|
27032
27032
|
incomingDeps: { dbgFs, sessionCtx, onSessionUpdate, onRequest }
|
|
27033
27033
|
});
|
|
27034
|
-
|
|
27034
|
+
resolve32(
|
|
27035
27035
|
createCursorAcpHandle({
|
|
27036
27036
|
child,
|
|
27037
27037
|
sessionId: established.sessionId,
|
|
@@ -28237,8 +28237,8 @@ function pathspec(...paths) {
|
|
|
28237
28237
|
cache.set(key, paths);
|
|
28238
28238
|
return key;
|
|
28239
28239
|
}
|
|
28240
|
-
function isPathSpec(
|
|
28241
|
-
return
|
|
28240
|
+
function isPathSpec(path60) {
|
|
28241
|
+
return path60 instanceof String && cache.has(path60);
|
|
28242
28242
|
}
|
|
28243
28243
|
function toPaths(pathSpec) {
|
|
28244
28244
|
return cache.get(pathSpec) || [];
|
|
@@ -28327,8 +28327,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
|
|
|
28327
28327
|
function forEachLineWithContent(input, callback) {
|
|
28328
28328
|
return toLinesWithContent(input, true).map((line) => callback(line));
|
|
28329
28329
|
}
|
|
28330
|
-
function folderExists(
|
|
28331
|
-
return (0, import_file_exists.exists)(
|
|
28330
|
+
function folderExists(path60) {
|
|
28331
|
+
return (0, import_file_exists.exists)(path60, import_file_exists.FOLDER);
|
|
28332
28332
|
}
|
|
28333
28333
|
function append(target, item) {
|
|
28334
28334
|
if (Array.isArray(target)) {
|
|
@@ -28732,8 +28732,8 @@ function checkIsRepoRootTask() {
|
|
|
28732
28732
|
commands,
|
|
28733
28733
|
format: "utf-8",
|
|
28734
28734
|
onError,
|
|
28735
|
-
parser(
|
|
28736
|
-
return /^\.(git)?$/.test(
|
|
28735
|
+
parser(path60) {
|
|
28736
|
+
return /^\.(git)?$/.test(path60.trim());
|
|
28737
28737
|
}
|
|
28738
28738
|
};
|
|
28739
28739
|
}
|
|
@@ -29167,11 +29167,11 @@ function parseGrep(grep) {
|
|
|
29167
29167
|
const paths = /* @__PURE__ */ new Set();
|
|
29168
29168
|
const results = {};
|
|
29169
29169
|
forEachLineWithContent(grep, (input) => {
|
|
29170
|
-
const [
|
|
29171
|
-
paths.add(
|
|
29172
|
-
(results[
|
|
29170
|
+
const [path60, line, preview] = input.split(NULL);
|
|
29171
|
+
paths.add(path60);
|
|
29172
|
+
(results[path60] = results[path60] || []).push({
|
|
29173
29173
|
line: asNumber(line),
|
|
29174
|
-
path:
|
|
29174
|
+
path: path60,
|
|
29175
29175
|
preview
|
|
29176
29176
|
});
|
|
29177
29177
|
});
|
|
@@ -29936,14 +29936,14 @@ var init_hash_object = __esm2({
|
|
|
29936
29936
|
init_task();
|
|
29937
29937
|
}
|
|
29938
29938
|
});
|
|
29939
|
-
function parseInit(bare,
|
|
29939
|
+
function parseInit(bare, path60, text) {
|
|
29940
29940
|
const response = String(text).trim();
|
|
29941
29941
|
let result;
|
|
29942
29942
|
if (result = initResponseRegex.exec(response)) {
|
|
29943
|
-
return new InitSummary(bare,
|
|
29943
|
+
return new InitSummary(bare, path60, false, result[1]);
|
|
29944
29944
|
}
|
|
29945
29945
|
if (result = reInitResponseRegex.exec(response)) {
|
|
29946
|
-
return new InitSummary(bare,
|
|
29946
|
+
return new InitSummary(bare, path60, true, result[1]);
|
|
29947
29947
|
}
|
|
29948
29948
|
let gitDir = "";
|
|
29949
29949
|
const tokens = response.split(" ");
|
|
@@ -29954,7 +29954,7 @@ function parseInit(bare, path53, text) {
|
|
|
29954
29954
|
break;
|
|
29955
29955
|
}
|
|
29956
29956
|
}
|
|
29957
|
-
return new InitSummary(bare,
|
|
29957
|
+
return new InitSummary(bare, path60, /^re/i.test(response), gitDir);
|
|
29958
29958
|
}
|
|
29959
29959
|
var InitSummary;
|
|
29960
29960
|
var initResponseRegex;
|
|
@@ -29963,9 +29963,9 @@ var init_InitSummary = __esm2({
|
|
|
29963
29963
|
"src/lib/responses/InitSummary.ts"() {
|
|
29964
29964
|
"use strict";
|
|
29965
29965
|
InitSummary = class {
|
|
29966
|
-
constructor(bare,
|
|
29966
|
+
constructor(bare, path60, existing, gitDir) {
|
|
29967
29967
|
this.bare = bare;
|
|
29968
|
-
this.path =
|
|
29968
|
+
this.path = path60;
|
|
29969
29969
|
this.existing = existing;
|
|
29970
29970
|
this.gitDir = gitDir;
|
|
29971
29971
|
}
|
|
@@ -29977,7 +29977,7 @@ var init_InitSummary = __esm2({
|
|
|
29977
29977
|
function hasBareCommand(command) {
|
|
29978
29978
|
return command.includes(bareCommand);
|
|
29979
29979
|
}
|
|
29980
|
-
function initTask(bare = false,
|
|
29980
|
+
function initTask(bare = false, path60, customArgs) {
|
|
29981
29981
|
const commands = ["init", ...customArgs];
|
|
29982
29982
|
if (bare && !hasBareCommand(commands)) {
|
|
29983
29983
|
commands.splice(1, 0, bareCommand);
|
|
@@ -29986,7 +29986,7 @@ function initTask(bare = false, path53, customArgs) {
|
|
|
29986
29986
|
commands,
|
|
29987
29987
|
format: "utf-8",
|
|
29988
29988
|
parser(text) {
|
|
29989
|
-
return parseInit(commands.includes("--bare"),
|
|
29989
|
+
return parseInit(commands.includes("--bare"), path60, text);
|
|
29990
29990
|
}
|
|
29991
29991
|
};
|
|
29992
29992
|
}
|
|
@@ -30802,12 +30802,12 @@ var init_FileStatusSummary = __esm2({
|
|
|
30802
30802
|
"use strict";
|
|
30803
30803
|
fromPathRegex = /^(.+)\0(.+)$/;
|
|
30804
30804
|
FileStatusSummary = class {
|
|
30805
|
-
constructor(
|
|
30806
|
-
this.path =
|
|
30805
|
+
constructor(path60, index, working_dir) {
|
|
30806
|
+
this.path = path60;
|
|
30807
30807
|
this.index = index;
|
|
30808
30808
|
this.working_dir = working_dir;
|
|
30809
30809
|
if (index === "R" || working_dir === "R") {
|
|
30810
|
-
const detail = fromPathRegex.exec(
|
|
30810
|
+
const detail = fromPathRegex.exec(path60) || [null, path60, path60];
|
|
30811
30811
|
this.from = detail[2] || "";
|
|
30812
30812
|
this.path = detail[1] || "";
|
|
30813
30813
|
}
|
|
@@ -30838,14 +30838,14 @@ function splitLine(result, lineStr) {
|
|
|
30838
30838
|
default:
|
|
30839
30839
|
return;
|
|
30840
30840
|
}
|
|
30841
|
-
function data(index, workingDir,
|
|
30841
|
+
function data(index, workingDir, path60) {
|
|
30842
30842
|
const raw = `${index}${workingDir}`;
|
|
30843
30843
|
const handler = parsers6.get(raw);
|
|
30844
30844
|
if (handler) {
|
|
30845
|
-
handler(result,
|
|
30845
|
+
handler(result, path60);
|
|
30846
30846
|
}
|
|
30847
30847
|
if (raw !== "##" && raw !== "!!") {
|
|
30848
|
-
result.files.push(new FileStatusSummary(
|
|
30848
|
+
result.files.push(new FileStatusSummary(path60, index, workingDir));
|
|
30849
30849
|
}
|
|
30850
30850
|
}
|
|
30851
30851
|
}
|
|
@@ -31154,9 +31154,9 @@ var init_simple_git_api = __esm2({
|
|
|
31154
31154
|
next
|
|
31155
31155
|
);
|
|
31156
31156
|
}
|
|
31157
|
-
hashObject(
|
|
31157
|
+
hashObject(path60, write) {
|
|
31158
31158
|
return this._runTask(
|
|
31159
|
-
hashObjectTask(
|
|
31159
|
+
hashObjectTask(path60, write === true),
|
|
31160
31160
|
trailingFunctionArgument(arguments)
|
|
31161
31161
|
);
|
|
31162
31162
|
}
|
|
@@ -31509,8 +31509,8 @@ var init_branch = __esm2({
|
|
|
31509
31509
|
}
|
|
31510
31510
|
});
|
|
31511
31511
|
function toPath(input) {
|
|
31512
|
-
const
|
|
31513
|
-
return
|
|
31512
|
+
const path60 = input.trim().replace(/^["']|["']$/g, "");
|
|
31513
|
+
return path60 && normalize3(path60);
|
|
31514
31514
|
}
|
|
31515
31515
|
var parseCheckIgnore;
|
|
31516
31516
|
var init_CheckIgnore = __esm2({
|
|
@@ -31824,8 +31824,8 @@ __export2(sub_module_exports, {
|
|
|
31824
31824
|
subModuleTask: () => subModuleTask,
|
|
31825
31825
|
updateSubModuleTask: () => updateSubModuleTask
|
|
31826
31826
|
});
|
|
31827
|
-
function addSubModuleTask(repo,
|
|
31828
|
-
return subModuleTask(["add", repo,
|
|
31827
|
+
function addSubModuleTask(repo, path60) {
|
|
31828
|
+
return subModuleTask(["add", repo, path60]);
|
|
31829
31829
|
}
|
|
31830
31830
|
function initSubModuleTask(customArgs) {
|
|
31831
31831
|
return subModuleTask(["init", ...customArgs]);
|
|
@@ -32158,8 +32158,8 @@ var require_git = __commonJS2({
|
|
|
32158
32158
|
}
|
|
32159
32159
|
return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
|
|
32160
32160
|
};
|
|
32161
|
-
Git2.prototype.submoduleAdd = function(repo,
|
|
32162
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
32161
|
+
Git2.prototype.submoduleAdd = function(repo, path60, then) {
|
|
32162
|
+
return this._runTask(addSubModuleTask2(repo, path60), trailingFunctionArgument2(arguments));
|
|
32163
32163
|
};
|
|
32164
32164
|
Git2.prototype.submoduleUpdate = function(args, then) {
|
|
32165
32165
|
return this._runTask(
|
|
@@ -32796,8 +32796,8 @@ async function runGitTask(fn) {
|
|
|
32796
32796
|
}
|
|
32797
32797
|
async function yieldToEventLoop2() {
|
|
32798
32798
|
throwIfGitShutdownRequested();
|
|
32799
|
-
await new Promise((
|
|
32800
|
-
setImmediate(
|
|
32799
|
+
await new Promise((resolve32) => {
|
|
32800
|
+
setImmediate(resolve32);
|
|
32801
32801
|
});
|
|
32802
32802
|
throwIfGitShutdownRequested();
|
|
32803
32803
|
}
|
|
@@ -32837,11 +32837,8 @@ function cliSimpleGit(baseDir) {
|
|
|
32837
32837
|
async function getRemoteOriginUrl(gitDir) {
|
|
32838
32838
|
try {
|
|
32839
32839
|
const git = cliSimpleGit(gitDir);
|
|
32840
|
-
const
|
|
32841
|
-
|
|
32842
|
-
const origin = list.find((r) => r.name === "origin");
|
|
32843
|
-
if (!origin?.refs?.fetch && !origin?.refs?.push) return "";
|
|
32844
|
-
return (origin.refs.fetch ?? origin.refs.push ?? "").trim();
|
|
32840
|
+
const out = await git.raw(["config", "--get", "remote.origin.url"]);
|
|
32841
|
+
return String(out).trim();
|
|
32845
32842
|
} catch {
|
|
32846
32843
|
return "";
|
|
32847
32844
|
}
|
|
@@ -32857,6 +32854,12 @@ async function isGitRepoDirectory(dirPath) {
|
|
|
32857
32854
|
}
|
|
32858
32855
|
|
|
32859
32856
|
// src/git/discover-repos.ts
|
|
32857
|
+
var GIT_DISCOVER_YIELD_EVERY = 32;
|
|
32858
|
+
async function yieldGitDiscoverWork(entryCount) {
|
|
32859
|
+
if (entryCount > 0 && entryCount % GIT_DISCOVER_YIELD_EVERY === 0) {
|
|
32860
|
+
await yieldToEventLoop();
|
|
32861
|
+
}
|
|
32862
|
+
}
|
|
32860
32863
|
async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
32861
32864
|
const result = [];
|
|
32862
32865
|
const cwdResolved = path16.resolve(cwd);
|
|
@@ -32870,7 +32873,9 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
|
32870
32873
|
} catch {
|
|
32871
32874
|
return result;
|
|
32872
32875
|
}
|
|
32873
|
-
for (
|
|
32876
|
+
for (let i = 0; i < entries.length; i++) {
|
|
32877
|
+
await yieldGitDiscoverWork(i + 1);
|
|
32878
|
+
const ent = entries[i];
|
|
32874
32879
|
if (!ent.isDirectory()) continue;
|
|
32875
32880
|
const childPath = path16.join(cwdResolved, ent.name);
|
|
32876
32881
|
if (await isGitRepoDirectory(childPath)) {
|
|
@@ -32883,6 +32888,7 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
|
32883
32888
|
async function discoverGitReposUnderRoot(rootPath) {
|
|
32884
32889
|
const root = path16.resolve(rootPath);
|
|
32885
32890
|
const roots = [];
|
|
32891
|
+
let walkEntries = 0;
|
|
32886
32892
|
async function walk(dir) {
|
|
32887
32893
|
if (await isGitRepoDirectory(dir)) {
|
|
32888
32894
|
roots.push(path16.resolve(dir));
|
|
@@ -32894,7 +32900,9 @@ async function discoverGitReposUnderRoot(rootPath) {
|
|
|
32894
32900
|
} catch {
|
|
32895
32901
|
return;
|
|
32896
32902
|
}
|
|
32897
|
-
for (
|
|
32903
|
+
for (let i = 0; i < entries.length; i++) {
|
|
32904
|
+
await yieldGitDiscoverWork(++walkEntries);
|
|
32905
|
+
const ent = entries[i];
|
|
32898
32906
|
if (!ent.isDirectory() || ent.name === ".git") continue;
|
|
32899
32907
|
await walk(path16.join(dir, ent.name));
|
|
32900
32908
|
}
|
|
@@ -32902,7 +32910,9 @@ async function discoverGitReposUnderRoot(rootPath) {
|
|
|
32902
32910
|
await walk(root);
|
|
32903
32911
|
const uniq = [...new Set(roots)];
|
|
32904
32912
|
const out = [];
|
|
32905
|
-
for (
|
|
32913
|
+
for (let i = 0; i < uniq.length; i++) {
|
|
32914
|
+
await yieldGitDiscoverWork(i + 1);
|
|
32915
|
+
const p = uniq[i];
|
|
32906
32916
|
const remoteUrl = await getRemoteOriginUrl(p);
|
|
32907
32917
|
out.push({ absolutePath: p, remoteUrl });
|
|
32908
32918
|
}
|
|
@@ -33122,9 +33132,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
|
33122
33132
|
// src/agents/acp/put-summarize-change-summaries.ts
|
|
33123
33133
|
async function putEncryptedChangeSummaryRows(params) {
|
|
33124
33134
|
const base = params.apiBaseUrl.replace(/\/+$/, "");
|
|
33125
|
-
const entries = params.rows.map(({ path:
|
|
33135
|
+
const entries = params.rows.map(({ path: path60, summary }) => {
|
|
33126
33136
|
const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
|
|
33127
|
-
return { path:
|
|
33137
|
+
return { path: path60, summary: JSON.stringify(enc) };
|
|
33128
33138
|
});
|
|
33129
33139
|
const res = await fetch(
|
|
33130
33140
|
`${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
|
|
@@ -33960,503 +33970,436 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
|
|
|
33960
33970
|
return name;
|
|
33961
33971
|
}
|
|
33962
33972
|
|
|
33963
|
-
// src/worktrees/
|
|
33964
|
-
import * as
|
|
33965
|
-
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
33973
|
+
// src/worktrees/manager/session-worktree-cache.ts
|
|
33974
|
+
import * as path20 from "node:path";
|
|
33975
|
+
var SessionWorktreeCache = class {
|
|
33976
|
+
sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
|
|
33977
|
+
sessionParentPathBySession = /* @__PURE__ */ new Map();
|
|
33978
|
+
sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
|
|
33979
|
+
remember(sessionId, binding) {
|
|
33980
|
+
const paths = binding.repoCheckoutPaths.map((p) => path20.resolve(p));
|
|
33981
|
+
this.sessionRepoCheckoutPaths.set(sessionId, paths);
|
|
33982
|
+
this.sessionParentPathBySession.set(sessionId, path20.resolve(binding.sessionParentPath));
|
|
33983
|
+
this.sessionWorkingTreeRelRootBySession.set(sessionId, path20.resolve(binding.workingTreeRelRoot));
|
|
33984
|
+
}
|
|
33985
|
+
clearSession(sessionId) {
|
|
33986
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
33987
|
+
this.sessionRepoCheckoutPaths.delete(sessionId);
|
|
33988
|
+
this.sessionParentPathBySession.delete(sessionId);
|
|
33989
|
+
this.sessionWorkingTreeRelRootBySession.delete(sessionId);
|
|
33990
|
+
return paths;
|
|
33991
|
+
}
|
|
33992
|
+
getSessionParentPath(sessionId) {
|
|
33993
|
+
return this.sessionParentPathBySession.get(sessionId);
|
|
33994
|
+
}
|
|
33995
|
+
getWorkingTreeRelRoot(sessionId) {
|
|
33996
|
+
return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
|
|
33997
|
+
}
|
|
33998
|
+
hasSession(sessionId) {
|
|
33999
|
+
return this.sessionParentPathBySession.has(sessionId);
|
|
34000
|
+
}
|
|
34001
|
+
getRepoCheckoutPaths(sessionId) {
|
|
34002
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
34003
|
+
return paths?.length ? [...paths] : void 0;
|
|
34004
|
+
}
|
|
34005
|
+
getRepoCheckoutPathsRef(sessionId) {
|
|
34006
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
34007
|
+
return paths?.length ? paths : void 0;
|
|
34008
|
+
}
|
|
34009
|
+
isLegacyNestedLayout(sessionId) {
|
|
34010
|
+
const parent = this.sessionParentPathBySession.get(sessionId);
|
|
34011
|
+
const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
|
|
34012
|
+
if (!parent || !relRoot) return false;
|
|
34013
|
+
return path20.resolve(parent) !== path20.resolve(relRoot);
|
|
34014
|
+
}
|
|
34015
|
+
};
|
|
33970
34016
|
|
|
33971
|
-
// src/worktrees/
|
|
33972
|
-
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
|
|
33976
|
-
|
|
33977
|
-
|
|
33978
|
-
|
|
33979
|
-
"Carthage",
|
|
33980
|
-
"DerivedData",
|
|
33981
|
-
".yarn",
|
|
33982
|
-
".pnpm-store",
|
|
33983
|
-
"dist",
|
|
33984
|
-
"build",
|
|
33985
|
-
".next",
|
|
33986
|
-
"coverage",
|
|
33987
|
-
".turbo",
|
|
33988
|
-
".cache",
|
|
33989
|
-
"target",
|
|
33990
|
-
"__pycache__"
|
|
33991
|
-
]);
|
|
33992
|
-
function shouldSkipDiskWalkEntry(name) {
|
|
33993
|
-
return name.startsWith(".") || SKIP_DISK_WALK_DIR_NAMES.has(name);
|
|
34017
|
+
// src/worktrees/manager/session-worktree-manager-context.ts
|
|
34018
|
+
function createSessionWorktreeManagerContext(options) {
|
|
34019
|
+
return {
|
|
34020
|
+
worktreesRootPath: options.worktreesRootPath,
|
|
34021
|
+
log: options.log,
|
|
34022
|
+
cache: new SessionWorktreeCache(),
|
|
34023
|
+
layout: loadWorktreeLayout()
|
|
34024
|
+
};
|
|
33994
34025
|
}
|
|
33995
34026
|
|
|
33996
|
-
// src/
|
|
33997
|
-
|
|
33998
|
-
|
|
33999
|
-
|
|
34000
|
-
|
|
34001
|
-
|
|
34002
|
-
if (
|
|
34003
|
-
|
|
34004
|
-
|
|
34005
|
-
|
|
34006
|
-
|
|
34007
|
-
|
|
34008
|
-
await
|
|
34009
|
-
|
|
34010
|
-
|
|
34011
|
-
|
|
34027
|
+
// src/git/branches/commit-and-push.ts
|
|
34028
|
+
async function gitCommitAllIfDirty(repoDir, message, options) {
|
|
34029
|
+
const g = cliSimpleGit(repoDir);
|
|
34030
|
+
const st = await g.status();
|
|
34031
|
+
if (!st.files?.length) return;
|
|
34032
|
+
const branch = options.branch.trim();
|
|
34033
|
+
if (!branch) {
|
|
34034
|
+
throw new Error("Branch name is required");
|
|
34035
|
+
}
|
|
34036
|
+
const branches = await g.branchLocal();
|
|
34037
|
+
const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
|
|
34038
|
+
if (!localNames.has(branch)) {
|
|
34039
|
+
await g.checkoutLocalBranch(branch);
|
|
34040
|
+
} else {
|
|
34041
|
+
await g.checkout(branch);
|
|
34042
|
+
}
|
|
34043
|
+
await g.add(".");
|
|
34044
|
+
await g.commit(message);
|
|
34045
|
+
if (options.push) {
|
|
34046
|
+
await g.push(["-u", "origin", branch]);
|
|
34012
34047
|
}
|
|
34013
34048
|
}
|
|
34014
34049
|
|
|
34015
|
-
// src/worktrees/
|
|
34016
|
-
async function
|
|
34017
|
-
const
|
|
34018
|
-
|
|
34019
|
-
|
|
34020
|
-
|
|
34021
|
-
if (await isGitDir(dir)) {
|
|
34022
|
-
out.push(path21.resolve(dir));
|
|
34023
|
-
return;
|
|
34024
|
-
}
|
|
34025
|
-
let entries;
|
|
34026
|
-
try {
|
|
34027
|
-
entries = await fs18.promises.readdir(dir, { withFileTypes: true });
|
|
34028
|
-
} catch {
|
|
34029
|
-
return;
|
|
34030
|
-
}
|
|
34031
|
-
for (const e of entries) {
|
|
34032
|
-
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
34033
|
-
const full = path21.join(dir, e.name);
|
|
34034
|
-
if (!e.isDirectory()) continue;
|
|
34035
|
-
await walk(full);
|
|
34050
|
+
// src/worktrees/commit-session-worktrees.ts
|
|
34051
|
+
async function commitSessionWorktrees(options) {
|
|
34052
|
+
const { paths, branch, message, push } = options;
|
|
34053
|
+
try {
|
|
34054
|
+
for (const wt of paths) {
|
|
34055
|
+
await gitCommitAllIfDirty(wt, message, { push, branch });
|
|
34036
34056
|
}
|
|
34037
|
-
|
|
34038
|
-
|
|
34039
|
-
|
|
34057
|
+
return { ok: true };
|
|
34058
|
+
} catch (e) {
|
|
34059
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
34060
|
+
return { ok: false, error: err };
|
|
34061
|
+
}
|
|
34040
34062
|
}
|
|
34041
|
-
|
|
34042
|
-
|
|
34043
|
-
|
|
34044
|
-
const
|
|
34045
|
-
|
|
34046
|
-
|
|
34047
|
-
|
|
34048
|
-
|
|
34049
|
-
|
|
34050
|
-
|
|
34051
|
-
|
|
34052
|
-
}
|
|
34053
|
-
for (const e of entries) {
|
|
34054
|
-
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
34055
|
-
const full = path21.join(dir, e.name);
|
|
34056
|
-
if (!e.isDirectory()) continue;
|
|
34057
|
-
if (e.name === sessionId) {
|
|
34058
|
-
if (await isGitDir(full)) out.push(path21.resolve(full));
|
|
34059
|
-
continue;
|
|
34060
|
-
}
|
|
34061
|
-
if (await isGitDir(full)) continue;
|
|
34062
|
-
await walk(full, depth + 1);
|
|
34063
|
-
}
|
|
34064
|
-
};
|
|
34065
|
-
await walk(root, 0);
|
|
34066
|
-
return { paths: out, entriesVisited: state.entries };
|
|
34063
|
+
|
|
34064
|
+
// src/worktrees/manager/git/commit-session-worktree.ts
|
|
34065
|
+
async function commitSessionWorktree(cache2, params) {
|
|
34066
|
+
const paths = cache2.getRepoCheckoutPathsRef(params.sessionId);
|
|
34067
|
+
const targets = paths?.length ? paths : [getBridgeRoot()];
|
|
34068
|
+
return commitSessionWorktrees({
|
|
34069
|
+
paths: targets,
|
|
34070
|
+
branch: params.branch,
|
|
34071
|
+
message: params.message,
|
|
34072
|
+
push: params.push
|
|
34073
|
+
});
|
|
34067
34074
|
}
|
|
34068
34075
|
|
|
34069
|
-
// src/
|
|
34070
|
-
|
|
34071
|
-
|
|
34072
|
-
const bridge = path22.resolve(bridgeRoot);
|
|
34073
|
-
const preferred = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
34074
|
-
const relevant = layout.launcherCwds.filter((e) => {
|
|
34075
|
-
const entry = path22.resolve(e.absolutePath);
|
|
34076
|
-
return bridge === entry || bridge.startsWith(entry + path22.sep) || entry.startsWith(bridge + path22.sep);
|
|
34077
|
-
}).sort((a, b) => path22.resolve(b.absolutePath).length - path22.resolve(a.absolutePath).length).map((e) => e.dirName).filter((name) => keysOnDisk.includes(name));
|
|
34078
|
-
const ordered = [];
|
|
34079
|
-
const seen = /* @__PURE__ */ new Set();
|
|
34080
|
-
const add = (k) => {
|
|
34081
|
-
if (seen.has(k) || !keysOnDisk.includes(k)) return;
|
|
34082
|
-
seen.add(k);
|
|
34083
|
-
ordered.push(k);
|
|
34084
|
-
};
|
|
34085
|
-
if (preferred) add(preferred);
|
|
34086
|
-
for (const k of relevant) add(k);
|
|
34087
|
-
if (relevant.length > 0) {
|
|
34088
|
-
return ordered;
|
|
34089
|
-
}
|
|
34090
|
-
for (const k of keysOnDisk) add(k);
|
|
34091
|
-
return ordered;
|
|
34076
|
+
// src/git/changes/parse/normalize-git-diff-path.ts
|
|
34077
|
+
function normalizeGitDiffPath(path60) {
|
|
34078
|
+
return path60.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
34092
34079
|
}
|
|
34093
34080
|
|
|
34094
|
-
// src/
|
|
34095
|
-
|
|
34096
|
-
|
|
34097
|
-
|
|
34098
|
-
|
|
34099
|
-
|
|
34100
|
-
st = await fs19.promises.stat(sessionDir);
|
|
34101
|
-
} catch {
|
|
34081
|
+
// src/git/changes/parse/expand-git-rename-numstat-path.ts
|
|
34082
|
+
function expandGitRenameNumstatPath(rawPath) {
|
|
34083
|
+
const open2 = rawPath.indexOf("{");
|
|
34084
|
+
const arrow = rawPath.indexOf("=>");
|
|
34085
|
+
const close = rawPath.lastIndexOf("}");
|
|
34086
|
+
if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
|
|
34102
34087
|
return null;
|
|
34103
34088
|
}
|
|
34104
|
-
|
|
34105
|
-
const
|
|
34106
|
-
|
|
34107
|
-
const
|
|
34089
|
+
const prefix = rawPath.slice(0, open2);
|
|
34090
|
+
const suffix = rawPath.slice(close + 1);
|
|
34091
|
+
const oldPart = rawPath.slice(open2 + 1, arrow).trim();
|
|
34092
|
+
const newPart = rawPath.slice(arrow + 2, close).trim();
|
|
34108
34093
|
return {
|
|
34109
|
-
|
|
34110
|
-
|
|
34111
|
-
repoCheckoutPaths: worktreePaths
|
|
34094
|
+
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
34095
|
+
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
34112
34096
|
};
|
|
34113
34097
|
}
|
|
34098
|
+
function isGitRenameNumstatPath(rawPath) {
|
|
34099
|
+
return expandGitRenameNumstatPath(rawPath) != null;
|
|
34100
|
+
}
|
|
34114
34101
|
|
|
34115
|
-
// src/
|
|
34116
|
-
|
|
34117
|
-
const
|
|
34118
|
-
const
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
|
|
34122
|
-
|
|
34123
|
-
|
|
34124
|
-
|
|
34125
|
-
|
|
34126
|
-
|
|
34127
|
-
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
} catch {
|
|
34136
|
-
continue;
|
|
34102
|
+
// src/git/changes/parse/parse-name-status-entries.ts
|
|
34103
|
+
function parseNameStatusEntries(lines) {
|
|
34104
|
+
const entries = [];
|
|
34105
|
+
for (const line of lines) {
|
|
34106
|
+
if (!line.trim()) continue;
|
|
34107
|
+
const tabParts = line.split(" ");
|
|
34108
|
+
if (tabParts.length < 2) continue;
|
|
34109
|
+
const status = tabParts[0].trim();
|
|
34110
|
+
const code = status[0];
|
|
34111
|
+
if (code === "A") {
|
|
34112
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
|
|
34113
|
+
} else if (code === "D") {
|
|
34114
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
|
|
34115
|
+
} else if (code === "R" || code === "C") {
|
|
34116
|
+
if (tabParts.length >= 3) {
|
|
34117
|
+
entries.push({
|
|
34118
|
+
pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
|
|
34119
|
+
oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
|
|
34120
|
+
change: "moved"
|
|
34121
|
+
});
|
|
34137
34122
|
}
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
}
|
|
34141
|
-
} catch {
|
|
34142
|
-
return null;
|
|
34143
|
-
}
|
|
34144
|
-
const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
|
|
34145
|
-
for (const key of keys) {
|
|
34146
|
-
if (isCliImmediateShutdownRequested()) return null;
|
|
34147
|
-
await yieldToEventLoop();
|
|
34148
|
-
const layoutRoot = path24.join(worktreesRootPath, key);
|
|
34149
|
-
let layoutSt;
|
|
34150
|
-
try {
|
|
34151
|
-
layoutSt = await fs20.promises.stat(layoutRoot);
|
|
34152
|
-
} catch {
|
|
34153
|
-
continue;
|
|
34154
|
-
}
|
|
34155
|
-
if (!layoutSt.isDirectory()) continue;
|
|
34156
|
-
const sessionDir = path24.join(layoutRoot, sid);
|
|
34157
|
-
const nested = await tryBindingFromSessionDirectory(sessionDir);
|
|
34158
|
-
if (nested) return nested;
|
|
34159
|
-
const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
|
|
34160
|
-
if (legacy.paths.length > 0) {
|
|
34161
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path24.resolve(legacy.paths[0]);
|
|
34162
|
-
return {
|
|
34163
|
-
sessionParentPath: path24.resolve(isolated),
|
|
34164
|
-
workingTreeRelRoot: path24.resolve(layoutRoot),
|
|
34165
|
-
repoCheckoutPaths: legacy.paths.map((p) => path24.resolve(p))
|
|
34166
|
-
};
|
|
34123
|
+
} else if (code === "M" || code === "U" || code === "T") {
|
|
34124
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
34167
34125
|
}
|
|
34168
34126
|
}
|
|
34169
|
-
return
|
|
34127
|
+
return entries;
|
|
34170
34128
|
}
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
// src/worktrees/discovery/discover-legacy-binding-ascending-from-checkout.ts
|
|
34177
|
-
import * as path25 from "node:path";
|
|
34178
|
-
async function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
|
|
34179
|
-
const sid = sessionId.trim();
|
|
34180
|
-
if (!sid) return null;
|
|
34181
|
-
const hintR = path25.resolve(checkoutPath);
|
|
34182
|
-
let best = null;
|
|
34183
|
-
let cur = path25.dirname(hintR);
|
|
34184
|
-
for (let i = 0; i < 40; i++) {
|
|
34185
|
-
if (isCliImmediateShutdownRequested()) return best;
|
|
34186
|
-
await yieldToEventLoop();
|
|
34187
|
-
const paths = await collectWorktreeRootsNamed(cur, sid);
|
|
34188
|
-
if (paths.paths.some((p) => path25.resolve(p) === hintR)) {
|
|
34189
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths.paths) ?? path25.resolve(paths.paths[0]);
|
|
34190
|
-
best = {
|
|
34191
|
-
sessionParentPath: path25.resolve(isolated),
|
|
34192
|
-
workingTreeRelRoot: path25.resolve(cur),
|
|
34193
|
-
repoCheckoutPaths: paths.paths.map((p) => path25.resolve(p))
|
|
34194
|
-
};
|
|
34195
|
-
}
|
|
34196
|
-
const next = path25.dirname(cur);
|
|
34197
|
-
if (next === cur) break;
|
|
34198
|
-
cur = next;
|
|
34129
|
+
function parseNameStatusLines(lines) {
|
|
34130
|
+
const m = /* @__PURE__ */ new Map();
|
|
34131
|
+
for (const entry of parseNameStatusEntries(lines)) {
|
|
34132
|
+
m.set(entry.pathInRepo, entry.change);
|
|
34199
34133
|
}
|
|
34200
|
-
return
|
|
34134
|
+
return m;
|
|
34201
34135
|
}
|
|
34202
34136
|
|
|
34203
|
-
// src/
|
|
34204
|
-
|
|
34205
|
-
const
|
|
34206
|
-
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
|
|
34210
|
-
|
|
34211
|
-
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
|
|
34137
|
+
// src/git/changes/parse/parse-numstat-entries.ts
|
|
34138
|
+
function parseNumstatCounts(parts) {
|
|
34139
|
+
const [a, d] = parts;
|
|
34140
|
+
return {
|
|
34141
|
+
additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
|
|
34142
|
+
deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
|
|
34143
|
+
};
|
|
34144
|
+
}
|
|
34145
|
+
function parseNumstatLine(line) {
|
|
34146
|
+
const parts = line.split(" ");
|
|
34147
|
+
if (parts.length < 3) return null;
|
|
34148
|
+
const rawPath = parts[parts.length - 1];
|
|
34149
|
+
const { additions, deletions } = parseNumstatCounts(parts);
|
|
34150
|
+
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34151
|
+
const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
|
|
34152
|
+
return {
|
|
34153
|
+
pathInRepo,
|
|
34154
|
+
additions,
|
|
34155
|
+
deletions,
|
|
34156
|
+
...expanded ? { oldPathInRepo: expanded.oldPath } : {}
|
|
34157
|
+
};
|
|
34158
|
+
}
|
|
34159
|
+
function parseNumstatEntries(lines) {
|
|
34160
|
+
const m = /* @__PURE__ */ new Map();
|
|
34161
|
+
for (const line of lines) {
|
|
34162
|
+
if (!line.trim()) continue;
|
|
34163
|
+
const parts = line.split(" ");
|
|
34164
|
+
if (parts.length < 3) continue;
|
|
34165
|
+
const rawPath = parts[parts.length - 1];
|
|
34166
|
+
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34167
|
+
const entry = parseNumstatLine(line);
|
|
34168
|
+
if (!entry) continue;
|
|
34169
|
+
const existing = m.get(entry.pathInRepo);
|
|
34170
|
+
if (!existing || expanded) {
|
|
34171
|
+
m.set(entry.pathInRepo, entry);
|
|
34217
34172
|
}
|
|
34218
|
-
return direct;
|
|
34219
34173
|
}
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34174
|
+
return m;
|
|
34175
|
+
}
|
|
34176
|
+
function parseNumstat(lines) {
|
|
34177
|
+
const m = /* @__PURE__ */ new Map();
|
|
34178
|
+
for (const [path60, entry] of parseNumstatEntries(lines)) {
|
|
34179
|
+
m.set(path60, { additions: entry.additions, deletions: entry.deletions });
|
|
34223
34180
|
}
|
|
34224
|
-
|
|
34181
|
+
return m;
|
|
34182
|
+
}
|
|
34183
|
+
|
|
34184
|
+
// src/git/changes/parse/numstat-from-git-no-index.ts
|
|
34185
|
+
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
34186
|
+
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
34225
34187
|
try {
|
|
34226
|
-
|
|
34188
|
+
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
34189
|
+
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
34190
|
+
const parsed = parseNumstatLine(first2);
|
|
34191
|
+
if (!parsed) return null;
|
|
34192
|
+
return { additions: parsed.additions, deletions: parsed.deletions };
|
|
34227
34193
|
} catch {
|
|
34228
34194
|
return null;
|
|
34229
34195
|
}
|
|
34230
|
-
if (!st.isDirectory()) return null;
|
|
34231
|
-
const legacyPaths = await collectWorktreeRootsNamed(hint, sid);
|
|
34232
|
-
if (legacyPaths.paths.length === 0) return null;
|
|
34233
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths.paths) ?? path26.resolve(legacyPaths.paths[0]);
|
|
34234
|
-
return {
|
|
34235
|
-
sessionParentPath: path26.resolve(isolated),
|
|
34236
|
-
workingTreeRelRoot: hint,
|
|
34237
|
-
repoCheckoutPaths: legacyPaths.paths.map((p) => path26.resolve(p))
|
|
34238
|
-
};
|
|
34239
34196
|
}
|
|
34240
34197
|
|
|
34241
|
-
// src/
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
const
|
|
34245
|
-
|
|
34246
|
-
const
|
|
34247
|
-
|
|
34248
|
-
|
|
34249
|
-
|
|
34250
|
-
worktreesRootPath: params.worktreesRootPath,
|
|
34251
|
-
layout: params.layout,
|
|
34252
|
-
bridgeRoot: getBridgeRoot()
|
|
34253
|
-
}).finally(() => {
|
|
34254
|
-
if (discoverInflightBySessionId.get(sid) === promise2) {
|
|
34255
|
-
discoverInflightBySessionId.delete(sid);
|
|
34256
|
-
}
|
|
34257
|
-
});
|
|
34258
|
-
discoverInflightBySessionId.set(sid, promise2);
|
|
34259
|
-
return promise2;
|
|
34198
|
+
// src/git/changes/status/working-tree-changed-path-count.ts
|
|
34199
|
+
function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
|
|
34200
|
+
const kindByPath = parseNameStatusLines(nameStatusLines);
|
|
34201
|
+
const numByPath = parseNumstat(numstatLines);
|
|
34202
|
+
const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
|
|
34203
|
+
for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
|
|
34204
|
+
paths.add(p);
|
|
34205
|
+
}
|
|
34206
|
+
return paths.size;
|
|
34260
34207
|
}
|
|
34261
34208
|
|
|
34262
|
-
// src/
|
|
34263
|
-
function
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
|
|
34271
|
-
|
|
34272
|
-
|
|
34273
|
-
|
|
34274
|
-
|
|
34275
|
-
|
|
34209
|
+
// src/git/changes/count-working-tree-changed-files.ts
|
|
34210
|
+
async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
|
|
34211
|
+
return runGitTask(async () => {
|
|
34212
|
+
const g = cliSimpleGit(repoGitCwd);
|
|
34213
|
+
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
34214
|
+
g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
|
|
34215
|
+
g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
|
|
34216
|
+
g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
|
|
34217
|
+
]);
|
|
34218
|
+
return workingTreeChangedPathCount(
|
|
34219
|
+
String(nameStatusRaw).split("\n"),
|
|
34220
|
+
String(numstatRaw).split("\n"),
|
|
34221
|
+
String(untrackedRaw).split("\n")
|
|
34222
|
+
);
|
|
34223
|
+
});
|
|
34276
34224
|
}
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
34284
|
-
|
|
34285
|
-
return [...disc.repoCheckoutPaths];
|
|
34225
|
+
|
|
34226
|
+
// src/git/commits/remote-tracking/rev-parse-safe.ts
|
|
34227
|
+
async function revParseSafe(g, ref) {
|
|
34228
|
+
try {
|
|
34229
|
+
const v = (await g.raw(["rev-parse", "--verify", ref])).trim();
|
|
34230
|
+
return v || null;
|
|
34231
|
+
} catch {
|
|
34232
|
+
return null;
|
|
34286
34233
|
}
|
|
34287
|
-
return void 0;
|
|
34288
34234
|
}
|
|
34289
34235
|
|
|
34290
|
-
// src/
|
|
34291
|
-
function
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34236
|
+
// src/git/commits/remote-tracking/git-config.ts
|
|
34237
|
+
async function tryConfigGet(g, key) {
|
|
34238
|
+
try {
|
|
34239
|
+
const out = await g.raw(["config", "--get", key]);
|
|
34240
|
+
const v = String(out).trim();
|
|
34241
|
+
return v || null;
|
|
34242
|
+
} catch {
|
|
34243
|
+
return null;
|
|
34244
|
+
}
|
|
34295
34245
|
}
|
|
34296
34246
|
|
|
34297
|
-
// src/
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
const
|
|
34304
|
-
|
|
34305
|
-
|
|
34247
|
+
// src/git/commits/remote-tracking/branch-remote-config.ts
|
|
34248
|
+
async function readBranchRemoteConfig(g) {
|
|
34249
|
+
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
34250
|
+
if (!branch || branch === "HEAD") {
|
|
34251
|
+
return { branch: null, remote: "origin", merge: null };
|
|
34252
|
+
}
|
|
34253
|
+
const [remote, merge2] = await Promise.all([
|
|
34254
|
+
tryConfigGet(g, `branch.${branch}.remote`),
|
|
34255
|
+
tryConfigGet(g, `branch.${branch}.merge`)
|
|
34256
|
+
]);
|
|
34257
|
+
return { branch, remote: remote ?? "origin", merge: merge2 };
|
|
34258
|
+
}
|
|
34259
|
+
function trackingRefCandidates(config2) {
|
|
34260
|
+
const candidates = ["HEAD@{upstream}", "@{u}"];
|
|
34261
|
+
if (!config2.branch) return candidates;
|
|
34262
|
+
if (config2.merge) {
|
|
34263
|
+
candidates.push(
|
|
34264
|
+
`refs/remotes/${config2.remote}/${config2.merge.replace(/^refs\/heads\//, "")}`
|
|
34265
|
+
);
|
|
34266
|
+
}
|
|
34267
|
+
candidates.push(`refs/remotes/${config2.remote}/${config2.branch}`);
|
|
34268
|
+
return candidates;
|
|
34306
34269
|
}
|
|
34307
34270
|
|
|
34308
|
-
// src/
|
|
34309
|
-
function
|
|
34310
|
-
|
|
34271
|
+
// src/git/commits/remote-tracking/first-resolved-ref.ts
|
|
34272
|
+
async function firstResolvedSha(g, candidates) {
|
|
34273
|
+
const resolved = await Promise.all(
|
|
34274
|
+
candidates.map(async (ref) => ({ ref, sha: await revParseSafe(g, ref) }))
|
|
34275
|
+
);
|
|
34276
|
+
return resolved.find((entry) => entry.sha)?.sha ?? null;
|
|
34311
34277
|
}
|
|
34312
|
-
|
|
34313
|
-
|
|
34314
|
-
|
|
34315
|
-
|
|
34316
|
-
|
|
34317
|
-
return "HEAD";
|
|
34278
|
+
|
|
34279
|
+
// src/git/commits/remote-tracking/resolve-default-remote-branch-ref.ts
|
|
34280
|
+
var DEFAULT_REMOTE_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
|
|
34281
|
+
function defaultRemoteBranchNameRefs(remote) {
|
|
34282
|
+
return DEFAULT_REMOTE_BRANCH_NAMES.map((name) => `refs/remotes/${remote}/${name}`);
|
|
34318
34283
|
}
|
|
34319
|
-
async function
|
|
34320
|
-
const
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
|
|
34326
|
-
if (repos.length === 0) {
|
|
34327
|
-
log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
|
|
34284
|
+
async function resolveRemoteHeadSymbolicRef(g, remote) {
|
|
34285
|
+
const headSym = `refs/remotes/${remote}/HEAD`;
|
|
34286
|
+
try {
|
|
34287
|
+
const out = await g.raw(["symbolic-ref", "-q", "--verify", headSym]);
|
|
34288
|
+
const resolved = String(out).trim();
|
|
34289
|
+
return resolved.startsWith("refs/remotes/") ? resolved : null;
|
|
34290
|
+
} catch {
|
|
34328
34291
|
return null;
|
|
34329
34292
|
}
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
|
|
34334
|
-
|
|
34335
|
-
|
|
34336
|
-
|
|
34337
|
-
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
}
|
|
34341
|
-
const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
|
|
34342
|
-
try {
|
|
34343
|
-
await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch, baseRef);
|
|
34344
|
-
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}, base ${baseRef}).`);
|
|
34345
|
-
worktreePaths.push(wtPath);
|
|
34346
|
-
} catch (e) {
|
|
34347
|
-
log2(
|
|
34348
|
-
`[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
|
|
34349
|
-
);
|
|
34350
|
-
}
|
|
34293
|
+
}
|
|
34294
|
+
async function resolveDefaultRemoteBranchSha(g, remote) {
|
|
34295
|
+
const nameRefs = defaultRemoteBranchNameRefs(remote);
|
|
34296
|
+
const [symRef, ...nameShas] = await Promise.all([
|
|
34297
|
+
resolveRemoteHeadSymbolicRef(g, remote),
|
|
34298
|
+
...nameRefs.map((ref) => revParseSafe(g, ref))
|
|
34299
|
+
]);
|
|
34300
|
+
if (symRef) return revParseSafe(g, symRef);
|
|
34301
|
+
for (let i = 0; i < nameRefs.length; i++) {
|
|
34302
|
+
if (nameShas[i]) return nameShas[i];
|
|
34351
34303
|
}
|
|
34352
|
-
|
|
34353
|
-
return {
|
|
34354
|
-
worktreePaths,
|
|
34355
|
-
sessionParentPath: sessionDir,
|
|
34356
|
-
workingTreeRelRoot: sessionDir
|
|
34357
|
-
};
|
|
34304
|
+
return null;
|
|
34358
34305
|
}
|
|
34359
34306
|
|
|
34360
|
-
// src/
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34307
|
+
// src/git/commits/remote-tracking/resolve-base-sha-for-unpushed-commits.ts
|
|
34308
|
+
import * as path21 from "node:path";
|
|
34309
|
+
var BASE_SHA_CACHE_TTL_MS = 3e4;
|
|
34310
|
+
var baseShaCache = /* @__PURE__ */ new Map();
|
|
34311
|
+
async function resolveBaseShaForUnpushedCommitsUncached(g) {
|
|
34312
|
+
const config2 = await readBranchRemoteConfig(g);
|
|
34313
|
+
const trackingSha = await firstResolvedSha(g, trackingRefCandidates(config2));
|
|
34314
|
+
if (trackingSha) return trackingSha;
|
|
34315
|
+
return resolveDefaultRemoteBranchSha(g, config2.remote);
|
|
34316
|
+
}
|
|
34317
|
+
async function resolveBaseShaForUnpushedCommits(g, repoDir) {
|
|
34318
|
+
if (repoDir) {
|
|
34319
|
+
const key = path21.resolve(repoDir);
|
|
34320
|
+
const now = Date.now();
|
|
34321
|
+
const cached2 = baseShaCache.get(key);
|
|
34322
|
+
if (cached2 && cached2.expiresAt > now) return cached2.baseSha;
|
|
34323
|
+
const baseSha = await resolveBaseShaForUnpushedCommitsUncached(g);
|
|
34324
|
+
baseShaCache.set(key, { baseSha, expiresAt: now + BASE_SHA_CACHE_TTL_MS });
|
|
34325
|
+
return baseSha;
|
|
34326
|
+
}
|
|
34327
|
+
return resolveBaseShaForUnpushedCommitsUncached(g);
|
|
34328
|
+
}
|
|
34329
|
+
|
|
34330
|
+
// src/git/commits/remote-tracking/commits-ahead-of-remote-tracking.ts
|
|
34331
|
+
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
34332
|
+
const g = cliSimpleGit(repoDir);
|
|
34333
|
+
const headSha = await revParseSafe(g, "HEAD");
|
|
34334
|
+
if (!headSha) return 0;
|
|
34335
|
+
const baseSha = await resolveBaseShaForUnpushedCommits(g, repoDir);
|
|
34336
|
+
if (!baseSha || baseSha === headSha) return 0;
|
|
34337
|
+
try {
|
|
34338
|
+
const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
34339
|
+
const n = parseInt(String(out).trim(), 10);
|
|
34340
|
+
return Number.isNaN(n) ? 0 : n;
|
|
34341
|
+
} catch {
|
|
34342
|
+
return 0;
|
|
34343
|
+
}
|
|
34344
|
+
}
|
|
34345
|
+
|
|
34346
|
+
// src/git/status/working-tree-status.ts
|
|
34347
|
+
async function getRepoWorkingTreeStatus(repoDir) {
|
|
34348
|
+
return runGitTask(async () => {
|
|
34349
|
+
const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
|
|
34350
|
+
const hasUncommittedChanges = uncommittedFileCount > 0;
|
|
34351
|
+
const ahead = await commitsAheadOfRemoteTracking(repoDir);
|
|
34352
|
+
return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
|
|
34369
34353
|
});
|
|
34370
|
-
|
|
34371
|
-
|
|
34372
|
-
|
|
34373
|
-
|
|
34374
|
-
|
|
34354
|
+
}
|
|
34355
|
+
async function aggregateSessionPathsWorkingTreeStatus(paths) {
|
|
34356
|
+
let hasUncommittedChanges = false;
|
|
34357
|
+
let hasUnpushedCommits = false;
|
|
34358
|
+
let uncommittedFileCount = 0;
|
|
34359
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
34360
|
+
const s = await getRepoWorkingTreeStatus(p);
|
|
34361
|
+
uncommittedFileCount += s.uncommittedFileCount;
|
|
34362
|
+
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
34363
|
+
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
34375
34364
|
});
|
|
34376
|
-
return
|
|
34365
|
+
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
34377
34366
|
}
|
|
34378
|
-
|
|
34379
|
-
|
|
34380
|
-
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
34384
|
-
|
|
34385
|
-
cache2.remember(sessionId, disc);
|
|
34386
|
-
return cache2.getSessionParentPath(sessionId);
|
|
34387
|
-
}
|
|
34388
|
-
return void 0;
|
|
34367
|
+
async function pushAheadOfUpstreamForPaths(paths) {
|
|
34368
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
34369
|
+
const g = cliSimpleGit(p);
|
|
34370
|
+
const ahead = await commitsAheadOfRemoteTracking(p);
|
|
34371
|
+
if (ahead <= 0) return;
|
|
34372
|
+
await g.push();
|
|
34373
|
+
});
|
|
34389
34374
|
}
|
|
34390
34375
|
|
|
34391
|
-
// src/worktrees/manager/resolve-
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
}
|
|
34398
|
-
const rememberAndReturn = (binding) => {
|
|
34399
|
-
params.cache.remember(params.sessionId, binding);
|
|
34400
|
-
return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
|
|
34376
|
+
// src/worktrees/manager/resolve-commit-targets.ts
|
|
34377
|
+
function bridgeRootBinding(bridgeRoot) {
|
|
34378
|
+
return {
|
|
34379
|
+
sessionParentPath: bridgeRoot,
|
|
34380
|
+
workingTreeRelRoot: bridgeRoot,
|
|
34381
|
+
repoCheckoutPaths: [bridgeRoot]
|
|
34401
34382
|
};
|
|
34402
|
-
const diskFirst = await params.discover();
|
|
34403
|
-
if (diskFirst) return rememberAndReturn(diskFirst);
|
|
34404
|
-
const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
34405
|
-
if (fromRoot) return rememberAndReturn(fromRoot);
|
|
34406
|
-
let cur = resolved;
|
|
34407
|
-
for (let i = 0; i < 16; i++) {
|
|
34408
|
-
await yieldToEventLoop();
|
|
34409
|
-
const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
|
|
34410
|
-
if (tryRoot) return rememberAndReturn(tryRoot);
|
|
34411
|
-
const next = path28.dirname(cur);
|
|
34412
|
-
if (next === cur) break;
|
|
34413
|
-
cur = next;
|
|
34414
|
-
}
|
|
34415
|
-
return resolved;
|
|
34416
34383
|
}
|
|
34417
|
-
|
|
34418
|
-
// src/worktrees/manager/resolve-session-parent-path-for-prompt.ts
|
|
34419
|
-
async function resolveSessionParentPathForPrompt(params) {
|
|
34420
|
-
const { sessionId, cache: cache2, worktreesRootPath, layout, log: log2, discover, opts } = params;
|
|
34421
|
-
if (!sessionId) return void 0;
|
|
34384
|
+
async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
|
|
34422
34385
|
const sid = sessionId.trim();
|
|
34423
|
-
const
|
|
34424
|
-
if (
|
|
34425
|
-
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
cache: cache2,
|
|
34430
|
-
discover: () => discover(sid)
|
|
34431
|
-
});
|
|
34432
|
-
}
|
|
34433
|
-
const parentKind = parseSessionParent(opts.sessionParent);
|
|
34434
|
-
if (parentKind === "bridge_root") {
|
|
34435
|
-
return void 0;
|
|
34436
|
-
}
|
|
34437
|
-
if (parentKind === "worktrees_root") {
|
|
34438
|
-
if (!opts.isNewSession) {
|
|
34439
|
-
return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
|
|
34440
|
-
}
|
|
34441
|
-
return prepareAndRememberSessionWorktrees({
|
|
34442
|
-
cache: cache2,
|
|
34443
|
-
sessionId: sid,
|
|
34444
|
-
worktreesRootPath,
|
|
34445
|
-
layout,
|
|
34446
|
-
log: log2,
|
|
34447
|
-
...opts.worktreeBaseBranches ? { worktreeBaseBranches: opts.worktreeBaseBranches } : {}
|
|
34448
|
-
});
|
|
34449
|
-
}
|
|
34450
|
-
if (!opts.isNewSession) {
|
|
34451
|
-
return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
|
|
34386
|
+
const paths = cache2.getRepoCheckoutPathsRef(sid);
|
|
34387
|
+
if (paths?.length) return paths;
|
|
34388
|
+
const disc = await discover(sid);
|
|
34389
|
+
if (disc?.repoCheckoutPaths.length) {
|
|
34390
|
+
cache2.remember(sid, disc);
|
|
34391
|
+
return disc.repoCheckoutPaths;
|
|
34452
34392
|
}
|
|
34453
|
-
|
|
34454
|
-
|
|
34455
|
-
|
|
34456
|
-
|
|
34457
|
-
|
|
34458
|
-
|
|
34459
|
-
|
|
34393
|
+
const bridgeRoot = getBridgeRoot();
|
|
34394
|
+
cache2.remember(sid, bridgeRootBinding(bridgeRoot));
|
|
34395
|
+
return [bridgeRoot];
|
|
34396
|
+
}
|
|
34397
|
+
|
|
34398
|
+
// src/worktrees/manager/git/get-session-working-tree-status.ts
|
|
34399
|
+
async function getSessionWorkingTreeStatus(cache2, sessionId, discover) {
|
|
34400
|
+
return aggregateSessionPathsWorkingTreeStatus(
|
|
34401
|
+
await resolveCommitTargetsAsync(sessionId, cache2, discover)
|
|
34402
|
+
);
|
|
34460
34403
|
}
|
|
34461
34404
|
|
|
34462
34405
|
// src/git/changes/types.ts
|
|
@@ -34517,108 +34460,13 @@ function formatRemoteDisplayLabel(remoteUrl) {
|
|
|
34517
34460
|
}
|
|
34518
34461
|
|
|
34519
34462
|
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
34520
|
-
import * as
|
|
34463
|
+
import * as path28 from "node:path";
|
|
34521
34464
|
|
|
34522
34465
|
// src/git/changes/repo/build-working-tree-change-repo-detail.ts
|
|
34523
|
-
import * as
|
|
34466
|
+
import * as path27 from "node:path";
|
|
34524
34467
|
|
|
34525
|
-
// src/git/commits/
|
|
34526
|
-
|
|
34527
|
-
try {
|
|
34528
|
-
const out = await g.raw(["config", "--get", key]);
|
|
34529
|
-
const v = String(out).trim();
|
|
34530
|
-
return v || null;
|
|
34531
|
-
} catch {
|
|
34532
|
-
return null;
|
|
34533
|
-
}
|
|
34534
|
-
}
|
|
34535
|
-
async function revParseSafe(g, ref) {
|
|
34536
|
-
try {
|
|
34537
|
-
const v = (await g.raw(["rev-parse", ref])).trim();
|
|
34538
|
-
return v || null;
|
|
34539
|
-
} catch {
|
|
34540
|
-
return null;
|
|
34541
|
-
}
|
|
34542
|
-
}
|
|
34543
|
-
async function resolveRemoteTrackingRefForAhead(g) {
|
|
34544
|
-
try {
|
|
34545
|
-
await g.raw(["rev-parse", "--verify", "HEAD@{upstream}"]);
|
|
34546
|
-
return "HEAD@{upstream}";
|
|
34547
|
-
} catch {
|
|
34548
|
-
try {
|
|
34549
|
-
await g.raw(["rev-parse", "--verify", "@{u}"]);
|
|
34550
|
-
return "@{u}";
|
|
34551
|
-
} catch {
|
|
34552
|
-
}
|
|
34553
|
-
}
|
|
34554
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
34555
|
-
if (!branch || branch === "HEAD") return null;
|
|
34556
|
-
const remote = await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
34557
|
-
const merge2 = await tryConfigGet(g, `branch.${branch}.merge`);
|
|
34558
|
-
if (merge2) {
|
|
34559
|
-
const upstreamBranch = merge2.replace(/^refs\/heads\//, "");
|
|
34560
|
-
const ref = `refs/remotes/${remote}/${upstreamBranch}`;
|
|
34561
|
-
try {
|
|
34562
|
-
await g.raw(["rev-parse", "--verify", ref]);
|
|
34563
|
-
return ref;
|
|
34564
|
-
} catch {
|
|
34565
|
-
}
|
|
34566
|
-
}
|
|
34567
|
-
const fallbackRef = `refs/remotes/${remote}/${branch}`;
|
|
34568
|
-
try {
|
|
34569
|
-
await g.raw(["rev-parse", "--verify", fallbackRef]);
|
|
34570
|
-
return fallbackRef;
|
|
34571
|
-
} catch {
|
|
34572
|
-
return null;
|
|
34573
|
-
}
|
|
34574
|
-
}
|
|
34575
|
-
async function remoteForCurrentBranch(g) {
|
|
34576
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
34577
|
-
if (!branch || branch === "HEAD") return "origin";
|
|
34578
|
-
return await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
34579
|
-
}
|
|
34580
|
-
async function resolveDefaultRemoteBranchRef(g, remote) {
|
|
34581
|
-
const headSym = `refs/remotes/${remote}/HEAD`;
|
|
34582
|
-
try {
|
|
34583
|
-
const resolved = (await g.raw(["symbolic-ref", "-q", "--verify", headSym])).trim();
|
|
34584
|
-
if (resolved.startsWith("refs/remotes/")) return resolved;
|
|
34585
|
-
} catch {
|
|
34586
|
-
}
|
|
34587
|
-
for (const name of ["main", "master", "trunk", "develop"]) {
|
|
34588
|
-
const r = `refs/remotes/${remote}/${name}`;
|
|
34589
|
-
try {
|
|
34590
|
-
await g.raw(["rev-parse", "--verify", r]);
|
|
34591
|
-
return r;
|
|
34592
|
-
} catch {
|
|
34593
|
-
}
|
|
34594
|
-
}
|
|
34595
|
-
return null;
|
|
34596
|
-
}
|
|
34597
|
-
async function resolveBaseShaForUnpushedCommits(g) {
|
|
34598
|
-
const trackingRef = await resolveRemoteTrackingRefForAhead(g);
|
|
34599
|
-
if (trackingRef) {
|
|
34600
|
-
const sha = await revParseSafe(g, trackingRef);
|
|
34601
|
-
if (sha) return sha;
|
|
34602
|
-
}
|
|
34603
|
-
const remote = await remoteForCurrentBranch(g);
|
|
34604
|
-
const defaultRef = await resolveDefaultRemoteBranchRef(g, remote);
|
|
34605
|
-
if (!defaultRef) return null;
|
|
34606
|
-
return revParseSafe(g, defaultRef);
|
|
34607
|
-
}
|
|
34608
|
-
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
34609
|
-
const g = cliSimpleGit(repoDir);
|
|
34610
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
34611
|
-
if (!headSha) return 0;
|
|
34612
|
-
const baseSha = await resolveBaseShaForUnpushedCommits(g);
|
|
34613
|
-
if (!baseSha || baseSha === headSha) return 0;
|
|
34614
|
-
try {
|
|
34615
|
-
const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
34616
|
-
const n = parseInt(String(out).trim(), 10);
|
|
34617
|
-
return Number.isNaN(n) ? 0 : n;
|
|
34618
|
-
} catch {
|
|
34619
|
-
return 0;
|
|
34620
|
-
}
|
|
34621
|
-
}
|
|
34468
|
+
// src/git/commits/list-unpushed-commits.ts
|
|
34469
|
+
import * as path22 from "node:path";
|
|
34622
34470
|
|
|
34623
34471
|
// src/git/commits/lib/parse-log-lines.ts
|
|
34624
34472
|
function parseLogShaDateSubjectLines(raw) {
|
|
@@ -34638,9 +34486,14 @@ function parseLogShaDateSubjectLines(raw) {
|
|
|
34638
34486
|
}
|
|
34639
34487
|
|
|
34640
34488
|
// src/git/commits/list-unpushed-commits.ts
|
|
34489
|
+
var UNPUSHED_CACHE_TTL_MS = 3e4;
|
|
34490
|
+
var unpushedCache = /* @__PURE__ */ new Map();
|
|
34641
34491
|
async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
34642
34492
|
if (baseSha === headSha) return [];
|
|
34643
34493
|
try {
|
|
34494
|
+
const countOut = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
34495
|
+
const count = parseInt(String(countOut).trim(), 10);
|
|
34496
|
+
if (!Number.isFinite(count) || count <= 0) return [];
|
|
34644
34497
|
const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
|
|
34645
34498
|
return parseLogShaDateSubjectLines(logOut);
|
|
34646
34499
|
} catch {
|
|
@@ -34648,12 +34501,22 @@ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
|
34648
34501
|
}
|
|
34649
34502
|
}
|
|
34650
34503
|
async function listUnpushedCommits(repoDir) {
|
|
34504
|
+
const key = path22.resolve(repoDir);
|
|
34505
|
+
const now = Date.now();
|
|
34506
|
+
const cached2 = unpushedCache.get(key);
|
|
34507
|
+
if (cached2 && cached2.expiresAt > now) return cached2.commits;
|
|
34651
34508
|
const g = cliSimpleGit(repoDir);
|
|
34652
|
-
const headSha = await
|
|
34653
|
-
|
|
34654
|
-
|
|
34655
|
-
|
|
34656
|
-
|
|
34509
|
+
const [headSha, baseSha] = await Promise.all([
|
|
34510
|
+
revParseSafe(g, "HEAD"),
|
|
34511
|
+
resolveBaseShaForUnpushedCommits(g, repoDir)
|
|
34512
|
+
]);
|
|
34513
|
+
if (!headSha || !baseSha) {
|
|
34514
|
+
unpushedCache.set(key, { commits: [], expiresAt: now + UNPUSHED_CACHE_TTL_MS });
|
|
34515
|
+
return [];
|
|
34516
|
+
}
|
|
34517
|
+
const commits = await gitLogNotReachableFromBase(g, baseSha, headSha);
|
|
34518
|
+
unpushedCache.set(key, { commits, expiresAt: now + UNPUSHED_CACHE_TTL_MS });
|
|
34519
|
+
return commits;
|
|
34657
34520
|
}
|
|
34658
34521
|
|
|
34659
34522
|
// src/git/commits/lib/sanitize-recent-commits-limit.ts
|
|
@@ -34667,72 +34530,40 @@ function sanitizeRecentCommitsLimit(value) {
|
|
|
34667
34530
|
}
|
|
34668
34531
|
|
|
34669
34532
|
// src/git/commits/list-recent-commits.ts
|
|
34670
|
-
async function
|
|
34533
|
+
async function listRecentCommitRefs(repoDir, limitInput) {
|
|
34671
34534
|
const limit = sanitizeRecentCommitsLimit(limitInput);
|
|
34672
34535
|
const g = cliSimpleGit(repoDir);
|
|
34673
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
34674
|
-
if (!headSha) return { commits: [], hasMore: false };
|
|
34675
|
-
const unpushedSet = new Set((await listUnpushedCommits(repoDir)).map((c) => c.sha));
|
|
34676
34536
|
try {
|
|
34677
|
-
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit), "HEAD"]);
|
|
34678
|
-
const
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
}));
|
|
34682
|
-
let hasMore = false;
|
|
34683
|
-
if (commits.length === limit) {
|
|
34684
|
-
const nextOut = await g.raw(["log", "-n", "1", `--skip=${limit}`, "--format=%H", "HEAD"]);
|
|
34685
|
-
hasMore = /^[0-9a-f]{7,40}$/i.test(String(nextOut).trim());
|
|
34686
|
-
}
|
|
34537
|
+
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit + 1), "HEAD"]);
|
|
34538
|
+
const parsed = parseLogShaDateSubjectLines(logOut);
|
|
34539
|
+
const hasMore = parsed.length > limit;
|
|
34540
|
+
const commits = parsed.slice(0, limit);
|
|
34687
34541
|
return { commits, hasMore };
|
|
34688
34542
|
} catch {
|
|
34689
34543
|
return { commits: [], hasMore: false };
|
|
34690
34544
|
}
|
|
34691
34545
|
}
|
|
34692
34546
|
|
|
34693
|
-
// src/git/changes/
|
|
34694
|
-
|
|
34695
|
-
return path53.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
34696
|
-
}
|
|
34697
|
-
|
|
34698
|
-
// src/git/changes/parse/expand-git-rename-numstat-path.ts
|
|
34699
|
-
function expandGitRenameNumstatPath(rawPath) {
|
|
34700
|
-
const open2 = rawPath.indexOf("{");
|
|
34701
|
-
const arrow = rawPath.indexOf("=>");
|
|
34702
|
-
const close = rawPath.lastIndexOf("}");
|
|
34703
|
-
if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
|
|
34704
|
-
return null;
|
|
34705
|
-
}
|
|
34706
|
-
const prefix = rawPath.slice(0, open2);
|
|
34707
|
-
const suffix = rawPath.slice(close + 1);
|
|
34708
|
-
const oldPart = rawPath.slice(open2 + 1, arrow).trim();
|
|
34709
|
-
const newPart = rawPath.slice(arrow + 2, close).trim();
|
|
34710
|
-
return {
|
|
34711
|
-
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
34712
|
-
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
34713
|
-
};
|
|
34714
|
-
}
|
|
34715
|
-
function isGitRenameNumstatPath(rawPath) {
|
|
34716
|
-
return expandGitRenameNumstatPath(rawPath) != null;
|
|
34717
|
-
}
|
|
34547
|
+
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
34548
|
+
import * as path24 from "node:path";
|
|
34718
34549
|
|
|
34719
34550
|
// src/git/changes/rows/build-changed-file-row.ts
|
|
34720
34551
|
function buildChangedFileRow(options) {
|
|
34721
34552
|
const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
|
|
34722
|
-
const
|
|
34553
|
+
const workspaceRelPath = posixJoinDirFile(options.repoRelPath, pathInRepo);
|
|
34723
34554
|
const additions = options.numEntry?.additions ?? 0;
|
|
34724
34555
|
const deletions = options.numEntry?.deletions ?? 0;
|
|
34725
34556
|
let change = options.nameEntry?.change ?? "modified";
|
|
34726
|
-
let
|
|
34557
|
+
let movedFromWorkspaceRelPath;
|
|
34727
34558
|
let movedFromPathInRepo;
|
|
34728
34559
|
const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
|
|
34729
34560
|
if (change === "moved" && oldPathInRepo) {
|
|
34730
34561
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
34731
|
-
|
|
34562
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
34732
34563
|
} else if (oldPathInRepo && change === "modified") {
|
|
34733
34564
|
change = "moved";
|
|
34734
34565
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
34735
|
-
|
|
34566
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
34736
34567
|
}
|
|
34737
34568
|
if (options.untracked && !options.nameEntry) {
|
|
34738
34569
|
change = "added";
|
|
@@ -34742,11 +34573,11 @@ function buildChangedFileRow(options) {
|
|
|
34742
34573
|
else change = "modified";
|
|
34743
34574
|
}
|
|
34744
34575
|
return {
|
|
34745
|
-
|
|
34576
|
+
workspaceRelPath,
|
|
34746
34577
|
additions,
|
|
34747
34578
|
deletions,
|
|
34748
34579
|
change,
|
|
34749
|
-
...
|
|
34580
|
+
...movedFromWorkspaceRelPath ? { movedFromWorkspaceRelPath } : {},
|
|
34750
34581
|
...movedFromPathInRepo ? { movedFromPathInRepo } : {}
|
|
34751
34582
|
};
|
|
34752
34583
|
}
|
|
@@ -34805,8 +34636,8 @@ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, mov
|
|
|
34805
34636
|
}
|
|
34806
34637
|
|
|
34807
34638
|
// src/git/changes/total-lines/resolve-changed-file-total-lines.ts
|
|
34808
|
-
import * as
|
|
34809
|
-
import * as
|
|
34639
|
+
import * as fs17 from "node:fs";
|
|
34640
|
+
import * as path23 from "node:path";
|
|
34810
34641
|
|
|
34811
34642
|
// src/git/changes/lines/count-lines.ts
|
|
34812
34643
|
import { createReadStream } from "node:fs";
|
|
@@ -34852,13 +34683,13 @@ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
|
|
|
34852
34683
|
}
|
|
34853
34684
|
async function resolveWorkingTreeFileTotalLines(options) {
|
|
34854
34685
|
if (options.isBinary) return null;
|
|
34855
|
-
const filePath =
|
|
34686
|
+
const filePath = path23.join(options.repoGitCwd, options.pathInRepo);
|
|
34856
34687
|
if (options.change === "removed") {
|
|
34857
34688
|
return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
|
|
34858
34689
|
}
|
|
34859
34690
|
if (options.change === "moved" || options.change === "modified") {
|
|
34860
34691
|
try {
|
|
34861
|
-
const st = await
|
|
34692
|
+
const st = await fs17.promises.stat(filePath);
|
|
34862
34693
|
if (!st.isFile()) return null;
|
|
34863
34694
|
return await countTextFileLines(filePath);
|
|
34864
34695
|
} catch {
|
|
@@ -34878,23 +34709,23 @@ async function resolveCommitFileTotalLines(options) {
|
|
|
34878
34709
|
return null;
|
|
34879
34710
|
}
|
|
34880
34711
|
|
|
34881
|
-
// src/git/changes/listing/path-in-repo-from-
|
|
34882
|
-
function
|
|
34712
|
+
// src/git/changes/listing/path-in-repo-from-workspace.ts
|
|
34713
|
+
function pathInRepoFromWorkspace(workspaceRelPath, repoRelPath) {
|
|
34883
34714
|
const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
34884
|
-
if (normRel === ".") return
|
|
34885
|
-
if (
|
|
34886
|
-
return
|
|
34715
|
+
if (normRel === ".") return workspaceRelPath;
|
|
34716
|
+
if (workspaceRelPath.startsWith(`${normRel}/`)) {
|
|
34717
|
+
return workspaceRelPath.slice(normRel.length + 1);
|
|
34887
34718
|
}
|
|
34888
|
-
return
|
|
34719
|
+
return workspaceRelPath;
|
|
34889
34720
|
}
|
|
34890
|
-
function
|
|
34721
|
+
function normalizeRepoRelPath(repoRelPath) {
|
|
34891
34722
|
return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
34892
34723
|
}
|
|
34893
34724
|
|
|
34894
34725
|
// src/git/changes/listing/enrich-commit-file-rows.ts
|
|
34895
34726
|
async function enrichCommitFileRows(options) {
|
|
34896
34727
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
34897
|
-
const pathInRepo =
|
|
34728
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
34898
34729
|
row.patchContent = await unifiedDiffForFileInRange(
|
|
34899
34730
|
options.repoGitCwd,
|
|
34900
34731
|
options.range,
|
|
@@ -34913,102 +34744,6 @@ async function enrichCommitFileRows(options) {
|
|
|
34913
34744
|
});
|
|
34914
34745
|
}
|
|
34915
34746
|
|
|
34916
|
-
// src/git/changes/parse/parse-name-status-entries.ts
|
|
34917
|
-
function parseNameStatusEntries(lines) {
|
|
34918
|
-
const entries = [];
|
|
34919
|
-
for (const line of lines) {
|
|
34920
|
-
if (!line.trim()) continue;
|
|
34921
|
-
const tabParts = line.split(" ");
|
|
34922
|
-
if (tabParts.length < 2) continue;
|
|
34923
|
-
const status = tabParts[0].trim();
|
|
34924
|
-
const code = status[0];
|
|
34925
|
-
if (code === "A") {
|
|
34926
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
|
|
34927
|
-
} else if (code === "D") {
|
|
34928
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
|
|
34929
|
-
} else if (code === "R" || code === "C") {
|
|
34930
|
-
if (tabParts.length >= 3) {
|
|
34931
|
-
entries.push({
|
|
34932
|
-
pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
|
|
34933
|
-
oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
|
|
34934
|
-
change: "moved"
|
|
34935
|
-
});
|
|
34936
|
-
}
|
|
34937
|
-
} else if (code === "M" || code === "U" || code === "T") {
|
|
34938
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
34939
|
-
}
|
|
34940
|
-
}
|
|
34941
|
-
return entries;
|
|
34942
|
-
}
|
|
34943
|
-
function parseNameStatusLines(lines) {
|
|
34944
|
-
const m = /* @__PURE__ */ new Map();
|
|
34945
|
-
for (const entry of parseNameStatusEntries(lines)) {
|
|
34946
|
-
m.set(entry.pathInRepo, entry.change);
|
|
34947
|
-
}
|
|
34948
|
-
return m;
|
|
34949
|
-
}
|
|
34950
|
-
|
|
34951
|
-
// src/git/changes/parse/parse-numstat-entries.ts
|
|
34952
|
-
function parseNumstatCounts(parts) {
|
|
34953
|
-
const [a, d] = parts;
|
|
34954
|
-
return {
|
|
34955
|
-
additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
|
|
34956
|
-
deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
|
|
34957
|
-
};
|
|
34958
|
-
}
|
|
34959
|
-
function parseNumstatLine(line) {
|
|
34960
|
-
const parts = line.split(" ");
|
|
34961
|
-
if (parts.length < 3) return null;
|
|
34962
|
-
const rawPath = parts[parts.length - 1];
|
|
34963
|
-
const { additions, deletions } = parseNumstatCounts(parts);
|
|
34964
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34965
|
-
const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
|
|
34966
|
-
return {
|
|
34967
|
-
pathInRepo,
|
|
34968
|
-
additions,
|
|
34969
|
-
deletions,
|
|
34970
|
-
...expanded ? { oldPathInRepo: expanded.oldPath } : {}
|
|
34971
|
-
};
|
|
34972
|
-
}
|
|
34973
|
-
function parseNumstatEntries(lines) {
|
|
34974
|
-
const m = /* @__PURE__ */ new Map();
|
|
34975
|
-
for (const line of lines) {
|
|
34976
|
-
if (!line.trim()) continue;
|
|
34977
|
-
const parts = line.split(" ");
|
|
34978
|
-
if (parts.length < 3) continue;
|
|
34979
|
-
const rawPath = parts[parts.length - 1];
|
|
34980
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34981
|
-
const entry = parseNumstatLine(line);
|
|
34982
|
-
if (!entry) continue;
|
|
34983
|
-
const existing = m.get(entry.pathInRepo);
|
|
34984
|
-
if (!existing || expanded) {
|
|
34985
|
-
m.set(entry.pathInRepo, entry);
|
|
34986
|
-
}
|
|
34987
|
-
}
|
|
34988
|
-
return m;
|
|
34989
|
-
}
|
|
34990
|
-
function parseNumstat(lines) {
|
|
34991
|
-
const m = /* @__PURE__ */ new Map();
|
|
34992
|
-
for (const [path53, entry] of parseNumstatEntries(lines)) {
|
|
34993
|
-
m.set(path53, { additions: entry.additions, deletions: entry.deletions });
|
|
34994
|
-
}
|
|
34995
|
-
return m;
|
|
34996
|
-
}
|
|
34997
|
-
|
|
34998
|
-
// src/git/changes/parse/numstat-from-git-no-index.ts
|
|
34999
|
-
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
35000
|
-
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
35001
|
-
try {
|
|
35002
|
-
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
35003
|
-
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
35004
|
-
const parsed = parseNumstatLine(first2);
|
|
35005
|
-
if (!parsed) return null;
|
|
35006
|
-
return { additions: parsed.additions, deletions: parsed.deletions };
|
|
35007
|
-
} catch {
|
|
35008
|
-
return null;
|
|
35009
|
-
}
|
|
35010
|
-
}
|
|
35011
|
-
|
|
35012
34747
|
// src/git/changes/rows/collect-moved-source-paths.ts
|
|
35013
34748
|
function collectMovedSourcePaths(nameEntries, numByPath) {
|
|
35014
34749
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -35088,8 +34823,8 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
35088
34823
|
const aHasPatch = Boolean(a.patchContent?.trim());
|
|
35089
34824
|
const bHasPatch = Boolean(b.patchContent?.trim());
|
|
35090
34825
|
if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
|
|
35091
|
-
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.
|
|
35092
|
-
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.
|
|
34826
|
+
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromWorkspaceRelPath);
|
|
34827
|
+
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromWorkspaceRelPath);
|
|
35093
34828
|
if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
|
|
35094
34829
|
return a;
|
|
35095
34830
|
}
|
|
@@ -35098,30 +34833,39 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
35098
34833
|
function dedupeChangedFileRows(rows) {
|
|
35099
34834
|
const byPath = /* @__PURE__ */ new Map();
|
|
35100
34835
|
for (const row of rows) {
|
|
35101
|
-
const key = normalizeGitDiffPath(row.
|
|
35102
|
-
const normalizedRow = key === row.
|
|
34836
|
+
const key = normalizeGitDiffPath(row.workspaceRelPath);
|
|
34837
|
+
const normalizedRow = key === row.workspaceRelPath ? row : { ...row, workspaceRelPath: key };
|
|
35103
34838
|
const existing = byPath.get(key);
|
|
35104
34839
|
if (!existing) {
|
|
35105
34840
|
byPath.set(key, normalizedRow);
|
|
35106
34841
|
continue;
|
|
35107
34842
|
}
|
|
35108
34843
|
const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
|
|
35109
|
-
byPath.set(key, { ...preferRow,
|
|
34844
|
+
byPath.set(key, { ...preferRow, workspaceRelPath: key });
|
|
35110
34845
|
}
|
|
35111
34846
|
return [...byPath.values()];
|
|
35112
34847
|
}
|
|
35113
34848
|
|
|
35114
34849
|
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
35115
|
-
|
|
34850
|
+
var COMMIT_FILES_CACHE_TTL_MS = 5 * 6e4;
|
|
34851
|
+
var commitFilesCache = /* @__PURE__ */ new Map();
|
|
34852
|
+
function commitFilesCacheKey(repoGitCwd, commitSha) {
|
|
34853
|
+
return `${path24.resolve(repoGitCwd)}:${commitSha}`;
|
|
34854
|
+
}
|
|
34855
|
+
async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha, options = {}) {
|
|
34856
|
+
const cacheKey = commitFilesCacheKey(repoGitCwd, commitSha);
|
|
34857
|
+
if (!options.includeFilePatches) {
|
|
34858
|
+
const now = Date.now();
|
|
34859
|
+
const cached2 = commitFilesCache.get(cacheKey);
|
|
34860
|
+
if (cached2 && cached2.expiresAt > now) return cached2.rows;
|
|
34861
|
+
}
|
|
35116
34862
|
const g = cliSimpleGit(repoGitCwd);
|
|
35117
|
-
const
|
|
35118
|
-
const range = `${parent}..${commitSha}`;
|
|
34863
|
+
const normRel = normalizeRepoRelPath(repoRelPath);
|
|
35119
34864
|
const [nameStatusRaw, numstatRaw] = await Promise.all([
|
|
35120
|
-
g.raw(["diff",
|
|
35121
|
-
g.raw(["
|
|
34865
|
+
g.raw(["diff-tree", "--no-commit-id", "--name-status", "-r", ...RENAME_DIFF_ARGS, commitSha]).catch(() => ""),
|
|
34866
|
+
g.raw(["show", "--numstat", "--format=format:", commitSha]).catch(() => "")
|
|
35122
34867
|
]);
|
|
35123
34868
|
const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
|
|
35124
|
-
const normRel = normalizeRepoRelPath2(repoRelPath);
|
|
35125
34869
|
const paths = collectChangedPaths({
|
|
35126
34870
|
nameEntries: parsed.nameEntries,
|
|
35127
34871
|
numByPath: parsed.numByPath
|
|
@@ -35134,29 +34878,39 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
35134
34878
|
movedSourcePaths: parsed.movedSourcePaths
|
|
35135
34879
|
});
|
|
35136
34880
|
rows = dedupeChangedFileRows(rows);
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
34881
|
+
if (options.includeFilePatches) {
|
|
34882
|
+
const parent = await parentForCommitDiff(g, commitSha);
|
|
34883
|
+
const range = `${parent}..${commitSha}`;
|
|
34884
|
+
await enrichCommitFileRows({
|
|
34885
|
+
rows,
|
|
34886
|
+
repoGitCwd,
|
|
34887
|
+
repoRelPath: normRel,
|
|
34888
|
+
range,
|
|
34889
|
+
commitSha,
|
|
34890
|
+
parentSha: parent,
|
|
34891
|
+
binaryByPath: parsed.binaryByPath
|
|
34892
|
+
});
|
|
34893
|
+
}
|
|
35146
34894
|
const finalRows = dedupeChangedFileRows(rows);
|
|
35147
|
-
finalRows.sort((a, b) => a.
|
|
34895
|
+
finalRows.sort((a, b) => a.workspaceRelPath.localeCompare(b.workspaceRelPath));
|
|
34896
|
+
if (!options.includeFilePatches) {
|
|
34897
|
+
commitFilesCache.set(cacheKey, {
|
|
34898
|
+
rows: finalRows,
|
|
34899
|
+
expiresAt: Date.now() + COMMIT_FILES_CACHE_TTL_MS
|
|
34900
|
+
});
|
|
34901
|
+
}
|
|
35148
34902
|
return finalRows;
|
|
35149
34903
|
}
|
|
35150
34904
|
|
|
35151
34905
|
// src/git/changes/listing/apply-untracked-file-stats.ts
|
|
35152
|
-
import * as
|
|
35153
|
-
import * as
|
|
34906
|
+
import * as fs18 from "node:fs";
|
|
34907
|
+
import * as path25 from "node:path";
|
|
35154
34908
|
function createUntrackedStatsApplier(options) {
|
|
35155
34909
|
return async (row, pathInRepo) => {
|
|
35156
34910
|
if (!options.untrackedSet.has(pathInRepo)) return;
|
|
35157
34911
|
if (options.nameByPath.has(pathInRepo)) return;
|
|
35158
34912
|
if (row.change === "moved") return;
|
|
35159
|
-
const repoFilePath =
|
|
34913
|
+
const repoFilePath = path25.join(options.repoGitCwd, pathInRepo);
|
|
35160
34914
|
const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
|
|
35161
34915
|
if (fromGit) {
|
|
35162
34916
|
row.additions = fromGit.additions;
|
|
@@ -35164,7 +34918,7 @@ function createUntrackedStatsApplier(options) {
|
|
|
35164
34918
|
return;
|
|
35165
34919
|
}
|
|
35166
34920
|
try {
|
|
35167
|
-
const st = await
|
|
34921
|
+
const st = await fs18.promises.stat(repoFilePath);
|
|
35168
34922
|
row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
|
|
35169
34923
|
} catch {
|
|
35170
34924
|
row.additions = 0;
|
|
@@ -35174,7 +34928,7 @@ function createUntrackedStatsApplier(options) {
|
|
|
35174
34928
|
}
|
|
35175
34929
|
|
|
35176
34930
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
35177
|
-
import * as
|
|
34931
|
+
import * as path26 from "node:path";
|
|
35178
34932
|
|
|
35179
34933
|
// src/git/changes/patch/hydrate/append-injected-context-lines.ts
|
|
35180
34934
|
async function appendInjectedContextLines(out, inject, maybeYield, injectedTotal) {
|
|
@@ -35254,10 +35008,10 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
|
|
|
35254
35008
|
}
|
|
35255
35009
|
|
|
35256
35010
|
// src/git/changes/patch/hydrate/read-worktree-file-lines.ts
|
|
35257
|
-
import * as
|
|
35011
|
+
import * as fs19 from "node:fs";
|
|
35258
35012
|
async function readWorktreeFileLines(filePath) {
|
|
35259
35013
|
try {
|
|
35260
|
-
const raw = await
|
|
35014
|
+
const raw = await fs19.promises.readFile(filePath, "utf8");
|
|
35261
35015
|
return raw.split(/\r?\n/);
|
|
35262
35016
|
} catch {
|
|
35263
35017
|
return null;
|
|
@@ -35353,8 +35107,8 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
35353
35107
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
35354
35108
|
async function enrichWorkingTreeFileRows(options) {
|
|
35355
35109
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
35356
|
-
const pathInRepo =
|
|
35357
|
-
const filePath =
|
|
35110
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
35111
|
+
const filePath = path26.join(options.repoGitCwd, pathInRepo);
|
|
35358
35112
|
const hydrateKind = row.change === "moved" ? "modified" : row.change;
|
|
35359
35113
|
let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
|
|
35360
35114
|
if (patch) {
|
|
@@ -35377,7 +35131,7 @@ async function enrichWorkingTreeFileRows(options) {
|
|
|
35377
35131
|
}
|
|
35378
35132
|
|
|
35379
35133
|
// src/git/changes/listing/list-changed-files-for-repo.ts
|
|
35380
|
-
async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
35134
|
+
async function listChangedFilesForRepo(repoGitCwd, repoRelPath, options = {}) {
|
|
35381
35135
|
const g = cliSimpleGit(repoGitCwd);
|
|
35382
35136
|
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
35383
35137
|
g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
|
|
@@ -35407,12 +35161,14 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
35407
35161
|
})
|
|
35408
35162
|
});
|
|
35409
35163
|
rows = dedupeChangedFileRows(rows);
|
|
35410
|
-
|
|
35411
|
-
|
|
35412
|
-
|
|
35413
|
-
|
|
35414
|
-
|
|
35415
|
-
|
|
35164
|
+
if (options.includeFilePatches) {
|
|
35165
|
+
await enrichWorkingTreeFileRows({
|
|
35166
|
+
rows,
|
|
35167
|
+
repoGitCwd,
|
|
35168
|
+
repoRelPath,
|
|
35169
|
+
binaryByPath: parsed.binaryByPath
|
|
35170
|
+
});
|
|
35171
|
+
}
|
|
35416
35172
|
return dedupeChangedFileRows(rows);
|
|
35417
35173
|
}
|
|
35418
35174
|
|
|
@@ -35423,48 +35179,62 @@ function normRepoRel(p) {
|
|
|
35423
35179
|
}
|
|
35424
35180
|
|
|
35425
35181
|
// src/git/changes/repo/build-working-tree-change-repo-detail.ts
|
|
35182
|
+
async function loadRecentAndUnpushedCommits(repoDir, recentCommitsLimit) {
|
|
35183
|
+
const [unpushedCommits, recentCommitList] = await Promise.all([
|
|
35184
|
+
listUnpushedCommits(repoDir),
|
|
35185
|
+
listRecentCommitRefs(repoDir, recentCommitsLimit)
|
|
35186
|
+
]);
|
|
35187
|
+
const unpushedSet = new Set(unpushedCommits.map((c) => c.sha));
|
|
35188
|
+
return {
|
|
35189
|
+
unpushedCommits,
|
|
35190
|
+
recentCommits: recentCommitList.commits.map((c) => ({
|
|
35191
|
+
...c,
|
|
35192
|
+
needsPush: unpushedSet.has(c.sha)
|
|
35193
|
+
})),
|
|
35194
|
+
recentCommitsHasMore: recentCommitList.hasMore
|
|
35195
|
+
};
|
|
35196
|
+
}
|
|
35426
35197
|
async function buildWorkingTreeChangeRepoDetail(options) {
|
|
35427
|
-
const t =
|
|
35198
|
+
const t = path27.resolve(options.targetPath);
|
|
35428
35199
|
if (!await isGitRepoDirectory(t)) return null;
|
|
35429
35200
|
const g = cliSimpleGit(t);
|
|
35430
|
-
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
|
|
35434
|
-
} catch {
|
|
35435
|
-
branch = "HEAD";
|
|
35436
|
-
}
|
|
35201
|
+
const isCommitView = options.basis.kind === "commit";
|
|
35202
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path27.resolve(options.sessionWorktreeRootPath) : null;
|
|
35203
|
+
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
35437
35204
|
await yieldToEventLoop2();
|
|
35438
|
-
const
|
|
35205
|
+
const branchPromise = g.raw(["rev-parse", "--abbrev-ref", "HEAD"]).then((out) => out.trim() || "HEAD").catch(() => "HEAD");
|
|
35206
|
+
const remoteUrlPromise = getRemoteOriginUrl(t);
|
|
35207
|
+
const topLevelPromise = sessionWtRoot ? Promise.resolve(t) : g.raw(["rev-parse", "--show-toplevel"]).then((out) => out.trim()).catch(() => t);
|
|
35208
|
+
const [branch, remoteUrl, topOrT] = await Promise.all([
|
|
35209
|
+
branchPromise,
|
|
35210
|
+
remoteUrlPromise,
|
|
35211
|
+
topLevelPromise
|
|
35212
|
+
]);
|
|
35439
35213
|
const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
|
|
35440
|
-
const sessionWtRoot = options.sessionWorktreeRootPath ? path32.resolve(options.sessionWorktreeRootPath) : null;
|
|
35441
|
-
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
35442
35214
|
let repoRelPath;
|
|
35443
35215
|
if (sessionWtRoot) {
|
|
35444
|
-
const anchor = legacyNested ?
|
|
35445
|
-
const relNorm =
|
|
35216
|
+
const anchor = legacyNested ? path27.dirname(t) : t;
|
|
35217
|
+
const relNorm = path27.relative(sessionWtRoot, anchor);
|
|
35446
35218
|
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
35447
35219
|
} else {
|
|
35448
|
-
|
|
35449
|
-
|
|
35450
|
-
|
|
35451
|
-
top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
|
|
35452
|
-
} catch {
|
|
35453
|
-
top = t;
|
|
35454
|
-
}
|
|
35455
|
-
const rel = path32.relative(options.bridgeRoot, path32.resolve(top)).replace(/\\/g, "/") || ".";
|
|
35456
|
-
repoRelPath = rel.startsWith("..") ? path32.basename(path32.resolve(top)) : rel;
|
|
35220
|
+
const top = topOrT;
|
|
35221
|
+
const rel = path27.relative(options.bridgeRoot, path27.resolve(top)).replace(/\\/g, "/") || ".";
|
|
35222
|
+
repoRelPath = rel.startsWith("..") ? path27.basename(path27.resolve(top)) : rel;
|
|
35457
35223
|
}
|
|
35458
35224
|
const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
35459
35225
|
if (options.repoFilterRelPath && norm !== options.repoFilterRelPath) return null;
|
|
35460
35226
|
const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
|
|
35461
35227
|
const relForList = norm === "." ? "." : norm;
|
|
35462
|
-
const
|
|
35463
|
-
const
|
|
35464
|
-
|
|
35465
|
-
|
|
35466
|
-
|
|
35467
|
-
|
|
35228
|
+
const commitSha = isCommitView ? options.basis.sha.trim() : null;
|
|
35229
|
+
const filesPromise = isCommitView && commitSha ? listChangedFilesForCommit(t, relForList, commitSha, {
|
|
35230
|
+
includeFilePatches: options.includeFilePatches
|
|
35231
|
+
}) : listChangedFilesForRepo(t, relForList, { includeFilePatches: options.includeFilePatches });
|
|
35232
|
+
const statusPromise = isCommitView ? Promise.resolve(false) : g.status().then((st) => (st.files?.length ?? 0) > 0);
|
|
35233
|
+
const commitsPromise = isCommitView ? Promise.resolve(null) : loadRecentAndUnpushedCommits(t, options.recentCommitsLimit);
|
|
35234
|
+
const [files, hasUncommittedChanges, commitData] = await Promise.all([
|
|
35235
|
+
filesPromise,
|
|
35236
|
+
statusPromise,
|
|
35237
|
+
commitsPromise
|
|
35468
35238
|
]);
|
|
35469
35239
|
return {
|
|
35470
35240
|
repoRelPath: norm,
|
|
@@ -35474,18 +35244,18 @@ async function buildWorkingTreeChangeRepoDetail(options) {
|
|
|
35474
35244
|
remoteDisplay,
|
|
35475
35245
|
files,
|
|
35476
35246
|
hasUncommittedChanges,
|
|
35477
|
-
unpushedCommits,
|
|
35478
|
-
recentCommits:
|
|
35479
|
-
recentCommitsHasMore:
|
|
35480
|
-
changesView:
|
|
35481
|
-
changesCommitSha:
|
|
35247
|
+
unpushedCommits: commitData?.unpushedCommits ?? [],
|
|
35248
|
+
recentCommits: commitData?.recentCommits ?? [],
|
|
35249
|
+
recentCommitsHasMore: commitData?.recentCommitsHasMore ?? false,
|
|
35250
|
+
changesView: isCommitView ? "commit" : "working",
|
|
35251
|
+
changesCommitSha: commitSha
|
|
35482
35252
|
};
|
|
35483
35253
|
}
|
|
35484
35254
|
|
|
35485
35255
|
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
35486
35256
|
async function getWorkingTreeChangeRepoDetails(options) {
|
|
35487
|
-
const bridgeRoot =
|
|
35488
|
-
const sessionWtRoot = options.sessionWorktreeRootPath ?
|
|
35257
|
+
const bridgeRoot = path28.resolve(getBridgeRoot());
|
|
35258
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path28.resolve(options.sessionWorktreeRootPath) : null;
|
|
35489
35259
|
const legacyNested = options.legacyRepoNestedSessionLayout === true;
|
|
35490
35260
|
const out = [];
|
|
35491
35261
|
const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
|
|
@@ -35499,14 +35269,16 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
35499
35269
|
const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
|
|
35500
35270
|
for (let i = 0; i < options.commitTargetPaths.length; i++) {
|
|
35501
35271
|
await yieldToEventLoop2();
|
|
35272
|
+
const targetPath = options.commitTargetPaths[i];
|
|
35502
35273
|
const detail = await buildWorkingTreeChangeRepoDetail({
|
|
35503
|
-
targetPath
|
|
35274
|
+
targetPath,
|
|
35504
35275
|
bridgeRoot,
|
|
35505
35276
|
sessionWorktreeRootPath: sessionWtRoot,
|
|
35506
35277
|
legacyRepoNestedSessionLayout: legacyNested,
|
|
35507
35278
|
repoFilterRelPath: filter,
|
|
35508
35279
|
basis,
|
|
35509
|
-
recentCommitsLimit: options.recentCommitsLimit
|
|
35280
|
+
recentCommitsLimit: options.recentCommitsLimit,
|
|
35281
|
+
includeFilePatches: options.includeFilePatches
|
|
35510
35282
|
});
|
|
35511
35283
|
if (!detail) continue;
|
|
35512
35284
|
out.push(detail);
|
|
@@ -35515,378 +35287,846 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
35515
35287
|
return out;
|
|
35516
35288
|
}
|
|
35517
35289
|
|
|
35518
|
-
// src/git/changes/
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
const
|
|
35522
|
-
|
|
35523
|
-
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
|
|
35530
|
-
|
|
35531
|
-
return runGitTask(async () => {
|
|
35290
|
+
// src/git/changes/repo/get-working-tree-change-file-patch.ts
|
|
35291
|
+
import * as path29 from "node:path";
|
|
35292
|
+
async function getWorkingTreeChangeFilePatch(options) {
|
|
35293
|
+
const repoGitCwd = path29.resolve(options.repoGitCwd);
|
|
35294
|
+
if (!await isGitRepoDirectory(repoGitCwd)) {
|
|
35295
|
+
throw new Error("Not a git repository");
|
|
35296
|
+
}
|
|
35297
|
+
const normRel = options.repoRelPath === "." || options.repoRelPath === "" ? "." : options.repoRelPath;
|
|
35298
|
+
const pathInRepo = pathInRepoFromWorkspace(options.workspaceRelPath, normRel);
|
|
35299
|
+
const movedFromPathInRepo = options.change === "moved" && options.movedFromWorkspaceRelPath ? pathInRepoFromWorkspace(options.movedFromWorkspaceRelPath, normRel) : void 0;
|
|
35300
|
+
if (options.basis.kind === "commit") {
|
|
35301
|
+
const commitSha = options.basis.sha.trim();
|
|
35302
|
+
if (!commitSha) throw new Error("commit sha is required for commit file patch");
|
|
35532
35303
|
const g = cliSimpleGit(repoGitCwd);
|
|
35533
|
-
const
|
|
35534
|
-
|
|
35535
|
-
|
|
35536
|
-
|
|
35537
|
-
|
|
35538
|
-
|
|
35539
|
-
|
|
35540
|
-
|
|
35541
|
-
String(untrackedRaw).split("\n")
|
|
35304
|
+
const parentSha = await parentForCommitDiff(g, commitSha);
|
|
35305
|
+
const range = `${parentSha}..${commitSha}`;
|
|
35306
|
+
const patchContent2 = await unifiedDiffForFileInRange(
|
|
35307
|
+
repoGitCwd,
|
|
35308
|
+
range,
|
|
35309
|
+
pathInRepo,
|
|
35310
|
+
options.change,
|
|
35311
|
+
movedFromPathInRepo
|
|
35542
35312
|
);
|
|
35313
|
+
const isBinary = Boolean(patchContent2?.includes("Binary files"));
|
|
35314
|
+
const totalLines2 = await resolveCommitFileTotalLines({
|
|
35315
|
+
repoGitCwd,
|
|
35316
|
+
pathInRepo,
|
|
35317
|
+
change: options.change,
|
|
35318
|
+
commitSha,
|
|
35319
|
+
parentSha,
|
|
35320
|
+
isBinary
|
|
35321
|
+
});
|
|
35322
|
+
return { patchContent: patchContent2, totalLines: totalLines2 };
|
|
35323
|
+
}
|
|
35324
|
+
const filePath = path29.join(repoGitCwd, pathInRepo);
|
|
35325
|
+
const hydrateKind = options.change === "moved" ? "modified" : options.change;
|
|
35326
|
+
let patchContent = await unifiedDiffForFile(
|
|
35327
|
+
repoGitCwd,
|
|
35328
|
+
pathInRepo,
|
|
35329
|
+
options.change,
|
|
35330
|
+
movedFromPathInRepo
|
|
35331
|
+
);
|
|
35332
|
+
if (patchContent) {
|
|
35333
|
+
patchContent = await hydrateUnifiedPatchWithFileContext(
|
|
35334
|
+
patchContent,
|
|
35335
|
+
filePath,
|
|
35336
|
+
repoGitCwd,
|
|
35337
|
+
pathInRepo,
|
|
35338
|
+
hydrateKind
|
|
35339
|
+
);
|
|
35340
|
+
}
|
|
35341
|
+
const totalLines = await resolveWorkingTreeFileTotalLines({
|
|
35342
|
+
repoGitCwd,
|
|
35343
|
+
pathInRepo,
|
|
35344
|
+
change: options.change,
|
|
35345
|
+
isBinary: Boolean(patchContent?.includes("Binary files"))
|
|
35543
35346
|
});
|
|
35347
|
+
return { patchContent, totalLines };
|
|
35544
35348
|
}
|
|
35545
35349
|
|
|
35546
|
-
// src/git/
|
|
35547
|
-
async function
|
|
35548
|
-
|
|
35549
|
-
|
|
35550
|
-
|
|
35551
|
-
|
|
35552
|
-
|
|
35350
|
+
// src/worktrees/manager/git/get-session-working-tree-change-details.ts
|
|
35351
|
+
async function getSessionWorkingTreeChangeDetails(cache2, sessionId, discover, opts) {
|
|
35352
|
+
const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
|
|
35353
|
+
return getWorkingTreeChangeRepoDetails({
|
|
35354
|
+
commitTargetPaths: targets,
|
|
35355
|
+
sessionWorktreeRootPath: cache2.getWorkingTreeRelRoot(sessionId),
|
|
35356
|
+
legacyRepoNestedSessionLayout: cache2.isLegacyNestedLayout(sessionId),
|
|
35357
|
+
repoFilterRelPath: opts?.repoRelPath?.trim() ? normRepoRel(opts.repoRelPath.trim()) : null,
|
|
35358
|
+
basis: opts?.basis,
|
|
35359
|
+
recentCommitsLimit: opts?.recentCommitsLimit,
|
|
35360
|
+
includeFilePatches: false
|
|
35553
35361
|
});
|
|
35554
35362
|
}
|
|
35555
|
-
|
|
35556
|
-
|
|
35557
|
-
|
|
35558
|
-
|
|
35559
|
-
|
|
35560
|
-
const s = await getRepoWorkingTreeStatus(p);
|
|
35561
|
-
uncommittedFileCount += s.uncommittedFileCount;
|
|
35562
|
-
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
35563
|
-
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
35564
|
-
});
|
|
35565
|
-
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
35363
|
+
|
|
35364
|
+
// src/worktrees/manager/git/parse-working-tree-change-kind.ts
|
|
35365
|
+
function parseWorkingTreeChangeKind(value) {
|
|
35366
|
+
if (value === "added" || value === "removed" || value === "modified" || value === "moved") return value;
|
|
35367
|
+
return null;
|
|
35566
35368
|
}
|
|
35567
|
-
|
|
35568
|
-
|
|
35569
|
-
|
|
35570
|
-
|
|
35571
|
-
|
|
35572
|
-
|
|
35573
|
-
|
|
35369
|
+
|
|
35370
|
+
// src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
|
|
35371
|
+
import * as path31 from "node:path";
|
|
35372
|
+
|
|
35373
|
+
// src/git/changes/repo/resolve-checkout-repo-rel-path.ts
|
|
35374
|
+
import * as path30 from "node:path";
|
|
35375
|
+
async function resolveCheckoutRepoRelPath(options) {
|
|
35376
|
+
const t = path30.resolve(options.checkoutPath);
|
|
35377
|
+
const g = cliSimpleGit(t);
|
|
35378
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path30.resolve(options.sessionWorktreeRootPath) : null;
|
|
35379
|
+
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
35380
|
+
let repoRelPath;
|
|
35381
|
+
if (sessionWtRoot) {
|
|
35382
|
+
const anchor = legacyNested ? path30.dirname(t) : t;
|
|
35383
|
+
const relNorm = path30.relative(sessionWtRoot, anchor);
|
|
35384
|
+
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
35385
|
+
} else {
|
|
35386
|
+
let top = t;
|
|
35387
|
+
try {
|
|
35388
|
+
await yieldToEventLoop2();
|
|
35389
|
+
top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
|
|
35390
|
+
} catch {
|
|
35391
|
+
top = t;
|
|
35392
|
+
}
|
|
35393
|
+
const rel = path30.relative(options.bridgeRoot, path30.resolve(top)).replace(/\\/g, "/") || ".";
|
|
35394
|
+
repoRelPath = rel.startsWith("..") ? path30.basename(path30.resolve(top)) : rel;
|
|
35395
|
+
}
|
|
35396
|
+
return normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
35574
35397
|
}
|
|
35575
35398
|
|
|
35576
|
-
// src/git/
|
|
35577
|
-
async function
|
|
35578
|
-
const
|
|
35579
|
-
const
|
|
35580
|
-
|
|
35581
|
-
const
|
|
35582
|
-
|
|
35583
|
-
|
|
35399
|
+
// src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
|
|
35400
|
+
async function getSessionWorkingTreeChangeFilePatch(cache2, sessionId, discover, opts) {
|
|
35401
|
+
const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
|
|
35402
|
+
const bridgeRoot = path31.resolve(getBridgeRoot());
|
|
35403
|
+
const sessionWtRoot = cache2.getWorkingTreeRelRoot(sessionId) ? path31.resolve(cache2.getWorkingTreeRelRoot(sessionId)) : null;
|
|
35404
|
+
const legacyNested = cache2.isLegacyNestedLayout(sessionId);
|
|
35405
|
+
const filter = normRepoRel(opts.repoRelPath.trim());
|
|
35406
|
+
for (const targetPath of targets) {
|
|
35407
|
+
const checkoutPath = path31.resolve(targetPath);
|
|
35408
|
+
if (!await isGitRepoDirectory(checkoutPath)) continue;
|
|
35409
|
+
const repoRelPath = await resolveCheckoutRepoRelPath({
|
|
35410
|
+
checkoutPath,
|
|
35411
|
+
bridgeRoot,
|
|
35412
|
+
sessionWorktreeRootPath: sessionWtRoot,
|
|
35413
|
+
legacyRepoNestedSessionLayout: legacyNested
|
|
35414
|
+
});
|
|
35415
|
+
if (repoRelPath !== filter) continue;
|
|
35416
|
+
return getWorkingTreeChangeFilePatch({
|
|
35417
|
+
repoGitCwd: checkoutPath,
|
|
35418
|
+
repoRelPath: filter,
|
|
35419
|
+
workspaceRelPath: opts.workspaceRelPath.trim(),
|
|
35420
|
+
change: opts.change,
|
|
35421
|
+
movedFromWorkspaceRelPath: opts.movedFromWorkspaceRelPath?.trim() || null,
|
|
35422
|
+
basis: opts.basis
|
|
35423
|
+
});
|
|
35584
35424
|
}
|
|
35585
|
-
|
|
35586
|
-
|
|
35587
|
-
|
|
35588
|
-
|
|
35425
|
+
throw new Error(`Repository not found: ${filter}`);
|
|
35426
|
+
}
|
|
35427
|
+
|
|
35428
|
+
// src/worktrees/manager/git/push-session-upstream.ts
|
|
35429
|
+
async function pushSessionUpstream(cache2, sessionId, discover) {
|
|
35430
|
+
try {
|
|
35431
|
+
await pushAheadOfUpstreamForPaths(await resolveCommitTargetsAsync(sessionId, cache2, discover));
|
|
35432
|
+
return { ok: true };
|
|
35433
|
+
} catch (e) {
|
|
35434
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
35435
|
+
return { ok: false, error: err };
|
|
35436
|
+
}
|
|
35437
|
+
}
|
|
35438
|
+
|
|
35439
|
+
// src/worktrees/remove-session-worktrees.ts
|
|
35440
|
+
import * as fs22 from "node:fs";
|
|
35441
|
+
|
|
35442
|
+
// src/git/worktrees/worktree-remove.ts
|
|
35443
|
+
import * as fs21 from "node:fs";
|
|
35444
|
+
|
|
35445
|
+
// src/git/worktrees/resolve-main-repo-from-git-file.ts
|
|
35446
|
+
import * as fs20 from "node:fs";
|
|
35447
|
+
import * as path32 from "node:path";
|
|
35448
|
+
function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
35449
|
+
const gitDirFile = path32.join(wt, ".git");
|
|
35450
|
+
if (!fs20.existsSync(gitDirFile) || !fs20.statSync(gitDirFile).isFile()) return "";
|
|
35451
|
+
const first2 = fs20.readFileSync(gitDirFile, "utf8").trim();
|
|
35452
|
+
const m = first2.match(/^gitdir:\s*(.+)$/im);
|
|
35453
|
+
if (!m) return "";
|
|
35454
|
+
const gitWorktreePath = path32.resolve(wt, m[1].trim());
|
|
35455
|
+
const gitDir = path32.dirname(path32.dirname(gitWorktreePath));
|
|
35456
|
+
return path32.dirname(gitDir);
|
|
35457
|
+
}
|
|
35458
|
+
|
|
35459
|
+
// src/git/worktrees/worktree-remove.ts
|
|
35460
|
+
async function gitWorktreeRemoveForce(worktreePath) {
|
|
35461
|
+
const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
|
|
35462
|
+
if (mainRepo) {
|
|
35463
|
+
await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
|
|
35589
35464
|
} else {
|
|
35590
|
-
|
|
35465
|
+
fs21.rmSync(worktreePath, { recursive: true, force: true });
|
|
35466
|
+
}
|
|
35467
|
+
}
|
|
35468
|
+
|
|
35469
|
+
// src/worktrees/remove-session-worktrees.ts
|
|
35470
|
+
async function removeSessionWorktrees(paths, log2) {
|
|
35471
|
+
for (const wt of paths) {
|
|
35472
|
+
try {
|
|
35473
|
+
await gitWorktreeRemoveForce(wt);
|
|
35474
|
+
log2(`[worktrees] Removed worktree ${wt}`);
|
|
35475
|
+
} catch (e) {
|
|
35476
|
+
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
35477
|
+
try {
|
|
35478
|
+
fs22.rmSync(wt, { recursive: true, force: true });
|
|
35479
|
+
} catch {
|
|
35480
|
+
}
|
|
35481
|
+
}
|
|
35482
|
+
}
|
|
35483
|
+
}
|
|
35484
|
+
|
|
35485
|
+
// src/worktrees/manager/git/remove-session-worktree-checkouts.ts
|
|
35486
|
+
async function removeSessionWorktreeCheckouts(cache2, sessionId, log2) {
|
|
35487
|
+
const paths = cache2.clearSession(sessionId);
|
|
35488
|
+
if (!paths?.length) return;
|
|
35489
|
+
await removeSessionWorktrees(paths, log2);
|
|
35490
|
+
}
|
|
35491
|
+
|
|
35492
|
+
// src/git/branches/rename-branch.ts
|
|
35493
|
+
async function gitRenameCurrentBranch(repoDir, newName) {
|
|
35494
|
+
const g = cliSimpleGit(repoDir);
|
|
35495
|
+
await g.raw(["branch", "-m", newName]);
|
|
35496
|
+
}
|
|
35497
|
+
|
|
35498
|
+
// src/worktrees/rename-session-worktree-branches.ts
|
|
35499
|
+
async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
35500
|
+
const safe = newBranch.replace(/[^a-zA-Z0-9/_-]+/g, "-").slice(0, 80) || "session-branch";
|
|
35501
|
+
for (const wt of paths) {
|
|
35502
|
+
try {
|
|
35503
|
+
await gitRenameCurrentBranch(wt, safe);
|
|
35504
|
+
log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
|
|
35505
|
+
} catch (e) {
|
|
35506
|
+
log2(
|
|
35507
|
+
`[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
|
|
35508
|
+
);
|
|
35509
|
+
}
|
|
35510
|
+
}
|
|
35511
|
+
}
|
|
35512
|
+
|
|
35513
|
+
// src/worktrees/manager/git/rename-session-worktree-branch.ts
|
|
35514
|
+
async function renameSessionWorktreeBranch(cache2, sessionId, newBranch, log2) {
|
|
35515
|
+
const paths = cache2.getRepoCheckoutPathsRef(sessionId);
|
|
35516
|
+
if (!paths?.length) return;
|
|
35517
|
+
await renameSessionWorktreeBranches(paths, newBranch, log2);
|
|
35518
|
+
}
|
|
35519
|
+
|
|
35520
|
+
// src/worktrees/discovery/discover-session-worktree-on-disk.ts
|
|
35521
|
+
import * as fs26 from "node:fs";
|
|
35522
|
+
import * as path37 from "node:path";
|
|
35523
|
+
|
|
35524
|
+
// src/worktrees/discovery/collect-worktree-paths.ts
|
|
35525
|
+
import * as fs24 from "node:fs";
|
|
35526
|
+
import * as path34 from "node:path";
|
|
35527
|
+
|
|
35528
|
+
// src/worktrees/discovery/disk-walk-constants.ts
|
|
35529
|
+
var DISK_WALK_YIELD_EVERY = 64;
|
|
35530
|
+
var LEGACY_WALK_MAX_DEPTH = 12;
|
|
35531
|
+
var SKIP_DISK_WALK_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
35532
|
+
"node_modules",
|
|
35533
|
+
"bower_components",
|
|
35534
|
+
"vendor",
|
|
35535
|
+
"Pods",
|
|
35536
|
+
"Carthage",
|
|
35537
|
+
"DerivedData",
|
|
35538
|
+
".yarn",
|
|
35539
|
+
".pnpm-store",
|
|
35540
|
+
"dist",
|
|
35541
|
+
"build",
|
|
35542
|
+
".next",
|
|
35543
|
+
"coverage",
|
|
35544
|
+
".turbo",
|
|
35545
|
+
".cache",
|
|
35546
|
+
"target",
|
|
35547
|
+
"__pycache__"
|
|
35548
|
+
]);
|
|
35549
|
+
function shouldSkipDiskWalkEntry(name) {
|
|
35550
|
+
return name.startsWith(".") || SKIP_DISK_WALK_DIR_NAMES.has(name);
|
|
35551
|
+
}
|
|
35552
|
+
|
|
35553
|
+
// src/worktrees/discovery/disk-walk-utils.ts
|
|
35554
|
+
import * as fs23 from "node:fs";
|
|
35555
|
+
import * as path33 from "node:path";
|
|
35556
|
+
async function yieldDuringDiskWalk(state) {
|
|
35557
|
+
state.entries++;
|
|
35558
|
+
if (state.entries % DISK_WALK_YIELD_EVERY !== 0) return true;
|
|
35559
|
+
if (isCliImmediateShutdownRequested()) return false;
|
|
35560
|
+
await yieldToEventLoop();
|
|
35561
|
+
return !isCliImmediateShutdownRequested();
|
|
35562
|
+
}
|
|
35563
|
+
async function isGitDir(dirPath) {
|
|
35564
|
+
try {
|
|
35565
|
+
await fs23.promises.access(path33.join(dirPath, ".git"));
|
|
35566
|
+
return true;
|
|
35567
|
+
} catch {
|
|
35568
|
+
return false;
|
|
35569
|
+
}
|
|
35570
|
+
}
|
|
35571
|
+
|
|
35572
|
+
// src/worktrees/discovery/collect-worktree-paths.ts
|
|
35573
|
+
async function collectGitRepoRootsUnderDirectory(rootPath) {
|
|
35574
|
+
const out = [];
|
|
35575
|
+
const state = { entries: 0 };
|
|
35576
|
+
const walk = async (dir) => {
|
|
35577
|
+
if (!await yieldDuringDiskWalk(state)) return;
|
|
35578
|
+
if (await isGitDir(dir)) {
|
|
35579
|
+
out.push(path34.resolve(dir));
|
|
35580
|
+
return;
|
|
35581
|
+
}
|
|
35582
|
+
let entries;
|
|
35583
|
+
try {
|
|
35584
|
+
entries = await fs24.promises.readdir(dir, { withFileTypes: true });
|
|
35585
|
+
} catch {
|
|
35586
|
+
return;
|
|
35587
|
+
}
|
|
35588
|
+
for (const e of entries) {
|
|
35589
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
35590
|
+
const full = path34.join(dir, e.name);
|
|
35591
|
+
if (!e.isDirectory()) continue;
|
|
35592
|
+
await walk(full);
|
|
35593
|
+
}
|
|
35594
|
+
};
|
|
35595
|
+
await walk(path34.resolve(rootPath));
|
|
35596
|
+
return { paths: [...new Set(out)], entriesVisited: state.entries };
|
|
35597
|
+
}
|
|
35598
|
+
async function collectWorktreeRootsNamed(root, sessionId, maxDepth = LEGACY_WALK_MAX_DEPTH) {
|
|
35599
|
+
const out = [];
|
|
35600
|
+
const state = { entries: 0 };
|
|
35601
|
+
const walk = async (dir, depth) => {
|
|
35602
|
+
if (depth > maxDepth) return;
|
|
35603
|
+
if (!await yieldDuringDiskWalk(state)) return;
|
|
35604
|
+
let entries;
|
|
35605
|
+
try {
|
|
35606
|
+
entries = await fs24.promises.readdir(dir, { withFileTypes: true });
|
|
35607
|
+
} catch {
|
|
35608
|
+
return;
|
|
35609
|
+
}
|
|
35610
|
+
for (const e of entries) {
|
|
35611
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
35612
|
+
const full = path34.join(dir, e.name);
|
|
35613
|
+
if (!e.isDirectory()) continue;
|
|
35614
|
+
if (e.name === sessionId) {
|
|
35615
|
+
if (await isGitDir(full)) out.push(path34.resolve(full));
|
|
35616
|
+
continue;
|
|
35617
|
+
}
|
|
35618
|
+
if (await isGitDir(full)) continue;
|
|
35619
|
+
await walk(full, depth + 1);
|
|
35620
|
+
}
|
|
35621
|
+
};
|
|
35622
|
+
await walk(root, 0);
|
|
35623
|
+
return { paths: out, entriesVisited: state.entries };
|
|
35624
|
+
}
|
|
35625
|
+
|
|
35626
|
+
// src/worktrees/discovery/layout-keys-for-bridge-root.ts
|
|
35627
|
+
import * as path35 from "node:path";
|
|
35628
|
+
function layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk) {
|
|
35629
|
+
const bridge = path35.resolve(bridgeRoot);
|
|
35630
|
+
const preferred = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
35631
|
+
const relevant = layout.launcherCwds.filter((e) => {
|
|
35632
|
+
const entry = path35.resolve(e.absolutePath);
|
|
35633
|
+
return bridge === entry || bridge.startsWith(entry + path35.sep) || entry.startsWith(bridge + path35.sep);
|
|
35634
|
+
}).sort((a, b) => path35.resolve(b.absolutePath).length - path35.resolve(a.absolutePath).length).map((e) => e.dirName).filter((name) => keysOnDisk.includes(name));
|
|
35635
|
+
const ordered = [];
|
|
35636
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35637
|
+
const add = (k) => {
|
|
35638
|
+
if (seen.has(k) || !keysOnDisk.includes(k)) return;
|
|
35639
|
+
seen.add(k);
|
|
35640
|
+
ordered.push(k);
|
|
35641
|
+
};
|
|
35642
|
+
if (preferred) add(preferred);
|
|
35643
|
+
for (const k of relevant) add(k);
|
|
35644
|
+
if (relevant.length > 0) {
|
|
35645
|
+
return ordered;
|
|
35646
|
+
}
|
|
35647
|
+
for (const k of keysOnDisk) add(k);
|
|
35648
|
+
return ordered;
|
|
35649
|
+
}
|
|
35650
|
+
|
|
35651
|
+
// src/worktrees/discovery/try-binding-from-session-directory.ts
|
|
35652
|
+
import * as fs25 from "node:fs";
|
|
35653
|
+
import * as path36 from "node:path";
|
|
35654
|
+
async function tryBindingFromSessionDirectory(sessionDir) {
|
|
35655
|
+
let st;
|
|
35656
|
+
try {
|
|
35657
|
+
st = await fs25.promises.stat(sessionDir);
|
|
35658
|
+
} catch {
|
|
35659
|
+
return null;
|
|
35660
|
+
}
|
|
35661
|
+
if (!st.isDirectory()) return null;
|
|
35662
|
+
const { paths: worktreePaths } = await collectGitRepoRootsUnderDirectory(sessionDir);
|
|
35663
|
+
if (worktreePaths.length === 0) return null;
|
|
35664
|
+
const abs = path36.resolve(sessionDir);
|
|
35665
|
+
return {
|
|
35666
|
+
sessionParentPath: abs,
|
|
35667
|
+
workingTreeRelRoot: abs,
|
|
35668
|
+
repoCheckoutPaths: worktreePaths
|
|
35669
|
+
};
|
|
35670
|
+
}
|
|
35671
|
+
|
|
35672
|
+
// src/worktrees/discovery/discover-session-worktree-on-disk.ts
|
|
35673
|
+
async function discoverSessionWorktreeOnDisk(options) {
|
|
35674
|
+
const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
|
|
35675
|
+
const sid = sessionId.trim();
|
|
35676
|
+
if (!sid) return null;
|
|
35677
|
+
try {
|
|
35678
|
+
await fs26.promises.access(worktreesRootPath);
|
|
35679
|
+
} catch {
|
|
35680
|
+
return null;
|
|
35681
|
+
}
|
|
35682
|
+
const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
35683
|
+
const keysOnDisk = [];
|
|
35684
|
+
if (preferredKey) keysOnDisk.push(preferredKey);
|
|
35685
|
+
try {
|
|
35686
|
+
for (const name of await fs26.promises.readdir(worktreesRootPath)) {
|
|
35687
|
+
if (name.startsWith(".")) continue;
|
|
35688
|
+
const p = path37.join(worktreesRootPath, name);
|
|
35689
|
+
let st;
|
|
35690
|
+
try {
|
|
35691
|
+
st = await fs26.promises.stat(p);
|
|
35692
|
+
} catch {
|
|
35693
|
+
continue;
|
|
35694
|
+
}
|
|
35695
|
+
if (!st.isDirectory()) continue;
|
|
35696
|
+
if (!keysOnDisk.includes(name)) keysOnDisk.push(name);
|
|
35697
|
+
}
|
|
35698
|
+
} catch {
|
|
35699
|
+
return null;
|
|
35700
|
+
}
|
|
35701
|
+
const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
|
|
35702
|
+
for (const key of keys) {
|
|
35703
|
+
if (isCliImmediateShutdownRequested()) return null;
|
|
35704
|
+
await yieldToEventLoop();
|
|
35705
|
+
const layoutRoot = path37.join(worktreesRootPath, key);
|
|
35706
|
+
let layoutSt;
|
|
35707
|
+
try {
|
|
35708
|
+
layoutSt = await fs26.promises.stat(layoutRoot);
|
|
35709
|
+
} catch {
|
|
35710
|
+
continue;
|
|
35711
|
+
}
|
|
35712
|
+
if (!layoutSt.isDirectory()) continue;
|
|
35713
|
+
const sessionDir = path37.join(layoutRoot, sid);
|
|
35714
|
+
const nested = await tryBindingFromSessionDirectory(sessionDir);
|
|
35715
|
+
if (nested) return nested;
|
|
35716
|
+
const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
|
|
35717
|
+
if (legacy.paths.length > 0) {
|
|
35718
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path37.resolve(legacy.paths[0]);
|
|
35719
|
+
return {
|
|
35720
|
+
sessionParentPath: path37.resolve(isolated),
|
|
35721
|
+
workingTreeRelRoot: path37.resolve(layoutRoot),
|
|
35722
|
+
repoCheckoutPaths: legacy.paths.map((p) => path37.resolve(p))
|
|
35723
|
+
};
|
|
35724
|
+
}
|
|
35725
|
+
}
|
|
35726
|
+
return null;
|
|
35727
|
+
}
|
|
35728
|
+
|
|
35729
|
+
// src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
|
|
35730
|
+
import * as fs27 from "node:fs";
|
|
35731
|
+
import * as path39 from "node:path";
|
|
35732
|
+
|
|
35733
|
+
// src/worktrees/discovery/discover-legacy-binding-ascending-from-checkout.ts
|
|
35734
|
+
import * as path38 from "node:path";
|
|
35735
|
+
async function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
|
|
35736
|
+
const sid = sessionId.trim();
|
|
35737
|
+
if (!sid) return null;
|
|
35738
|
+
const hintR = path38.resolve(checkoutPath);
|
|
35739
|
+
let best = null;
|
|
35740
|
+
let cur = path38.dirname(hintR);
|
|
35741
|
+
for (let i = 0; i < 40; i++) {
|
|
35742
|
+
if (isCliImmediateShutdownRequested()) return best;
|
|
35743
|
+
await yieldToEventLoop();
|
|
35744
|
+
const paths = await collectWorktreeRootsNamed(cur, sid);
|
|
35745
|
+
if (paths.paths.some((p) => path38.resolve(p) === hintR)) {
|
|
35746
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths.paths) ?? path38.resolve(paths.paths[0]);
|
|
35747
|
+
best = {
|
|
35748
|
+
sessionParentPath: path38.resolve(isolated),
|
|
35749
|
+
workingTreeRelRoot: path38.resolve(cur),
|
|
35750
|
+
repoCheckoutPaths: paths.paths.map((p) => path38.resolve(p))
|
|
35751
|
+
};
|
|
35752
|
+
}
|
|
35753
|
+
const next = path38.dirname(cur);
|
|
35754
|
+
if (next === cur) break;
|
|
35755
|
+
cur = next;
|
|
35756
|
+
}
|
|
35757
|
+
return best;
|
|
35758
|
+
}
|
|
35759
|
+
|
|
35760
|
+
// src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
|
|
35761
|
+
async function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
|
|
35762
|
+
const sid = sessionId.trim();
|
|
35763
|
+
if (!sid) return null;
|
|
35764
|
+
const hint = path39.resolve(sessionWorktreeRootPathOrHint);
|
|
35765
|
+
const underHint = await tryBindingFromSessionDirectory(path39.join(hint, sid));
|
|
35766
|
+
if (underHint) return underHint;
|
|
35767
|
+
const direct = await tryBindingFromSessionDirectory(hint);
|
|
35768
|
+
if (direct) {
|
|
35769
|
+
if (path39.basename(hint) === sid && await isGitDir(hint)) {
|
|
35770
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35771
|
+
if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
|
|
35772
|
+
return legacyFromCheckout;
|
|
35773
|
+
}
|
|
35774
|
+
}
|
|
35775
|
+
return direct;
|
|
35776
|
+
}
|
|
35777
|
+
if (path39.basename(hint) === sid && await isGitDir(hint)) {
|
|
35778
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35779
|
+
if (legacyFromCheckout) return legacyFromCheckout;
|
|
35591
35780
|
}
|
|
35592
|
-
|
|
35593
|
-
|
|
35594
|
-
|
|
35595
|
-
|
|
35781
|
+
let st;
|
|
35782
|
+
try {
|
|
35783
|
+
st = await fs27.promises.stat(hint);
|
|
35784
|
+
} catch {
|
|
35785
|
+
return null;
|
|
35596
35786
|
}
|
|
35787
|
+
if (!st.isDirectory()) return null;
|
|
35788
|
+
const legacyPaths = await collectWorktreeRootsNamed(hint, sid);
|
|
35789
|
+
if (legacyPaths.paths.length === 0) return null;
|
|
35790
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths.paths) ?? path39.resolve(legacyPaths.paths[0]);
|
|
35791
|
+
return {
|
|
35792
|
+
sessionParentPath: path39.resolve(isolated),
|
|
35793
|
+
workingTreeRelRoot: hint,
|
|
35794
|
+
repoCheckoutPaths: legacyPaths.paths.map((p) => path39.resolve(p))
|
|
35795
|
+
};
|
|
35597
35796
|
}
|
|
35598
35797
|
|
|
35599
|
-
// src/worktrees/
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35798
|
+
// src/worktrees/manager/discover-session-binding.ts
|
|
35799
|
+
var discoverInflightBySessionId = /* @__PURE__ */ new Map();
|
|
35800
|
+
function discoverSessionBindingAsync(params) {
|
|
35801
|
+
const sid = params.sessionId.trim();
|
|
35802
|
+
if (!sid) return Promise.resolve(null);
|
|
35803
|
+
const existing = discoverInflightBySessionId.get(sid);
|
|
35804
|
+
if (existing) return existing;
|
|
35805
|
+
const promise2 = discoverSessionWorktreeOnDisk({
|
|
35806
|
+
sessionId: sid,
|
|
35807
|
+
worktreesRootPath: params.worktreesRootPath,
|
|
35808
|
+
layout: params.layout,
|
|
35809
|
+
bridgeRoot: getBridgeRoot()
|
|
35810
|
+
}).finally(() => {
|
|
35811
|
+
if (discoverInflightBySessionId.get(sid) === promise2) {
|
|
35812
|
+
discoverInflightBySessionId.delete(sid);
|
|
35605
35813
|
}
|
|
35606
|
-
|
|
35607
|
-
|
|
35608
|
-
|
|
35609
|
-
return { ok: false, error: err };
|
|
35610
|
-
}
|
|
35814
|
+
});
|
|
35815
|
+
discoverInflightBySessionId.set(sid, promise2);
|
|
35816
|
+
return promise2;
|
|
35611
35817
|
}
|
|
35612
35818
|
|
|
35613
|
-
// src/worktrees/
|
|
35614
|
-
|
|
35615
|
-
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
35623
|
-
const gitDirFile = path34.join(wt, ".git");
|
|
35624
|
-
if (!fs26.existsSync(gitDirFile) || !fs26.statSync(gitDirFile).isFile()) return "";
|
|
35625
|
-
const first2 = fs26.readFileSync(gitDirFile, "utf8").trim();
|
|
35626
|
-
const m = first2.match(/^gitdir:\s*(.+)$/im);
|
|
35627
|
-
if (!m) return "";
|
|
35628
|
-
const gitWorktreePath = path34.resolve(wt, m[1].trim());
|
|
35629
|
-
const gitDir = path34.dirname(path34.dirname(gitWorktreePath));
|
|
35630
|
-
return path34.dirname(gitDir);
|
|
35819
|
+
// src/worktrees/manager/resolve-isolated-session-parent-path.ts
|
|
35820
|
+
function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutPaths) {
|
|
35821
|
+
if (!sessionId) return null;
|
|
35822
|
+
const sid = sessionId.trim();
|
|
35823
|
+
const cached2 = cache2.getSessionParentPath(sid);
|
|
35824
|
+
if (cached2) return cached2;
|
|
35825
|
+
const paths = ensureRepoCheckoutPaths(sid);
|
|
35826
|
+
if (!paths?.length) return null;
|
|
35827
|
+
return resolveIsolatedSessionParentPathFromCheckouts(paths);
|
|
35631
35828
|
}
|
|
35632
|
-
|
|
35633
|
-
|
|
35634
|
-
|
|
35635
|
-
|
|
35636
|
-
|
|
35637
|
-
|
|
35638
|
-
|
|
35639
|
-
|
|
35829
|
+
function ensureRepoCheckoutPathsForSession(sessionId, cache2) {
|
|
35830
|
+
if (!sessionId?.trim()) return void 0;
|
|
35831
|
+
const cached2 = cache2.getRepoCheckoutPaths(sessionId.trim());
|
|
35832
|
+
return cached2?.length ? [...cached2] : void 0;
|
|
35833
|
+
}
|
|
35834
|
+
async function ensureRepoCheckoutPathsForSessionAsync(sessionId, cache2, discover) {
|
|
35835
|
+
if (!sessionId?.trim()) return void 0;
|
|
35836
|
+
const sid = sessionId.trim();
|
|
35837
|
+
const cached2 = cache2.getRepoCheckoutPaths(sid);
|
|
35838
|
+
if (cached2?.length) return cached2;
|
|
35839
|
+
const disc = await discover(sid);
|
|
35840
|
+
if (disc?.repoCheckoutPaths.length) {
|
|
35841
|
+
cache2.remember(sid, disc);
|
|
35842
|
+
return [...disc.repoCheckoutPaths];
|
|
35640
35843
|
}
|
|
35844
|
+
return void 0;
|
|
35641
35845
|
}
|
|
35642
35846
|
|
|
35643
|
-
// src/worktrees/
|
|
35644
|
-
|
|
35645
|
-
|
|
35646
|
-
|
|
35647
|
-
|
|
35648
|
-
log2(`[worktrees] Removed worktree ${wt}`);
|
|
35649
|
-
} catch (e) {
|
|
35650
|
-
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
35651
|
-
try {
|
|
35652
|
-
fs28.rmSync(wt, { recursive: true, force: true });
|
|
35653
|
-
} catch {
|
|
35654
|
-
}
|
|
35655
|
-
}
|
|
35656
|
-
}
|
|
35847
|
+
// src/worktrees/manager/parse-session-parent.ts
|
|
35848
|
+
function parseSessionParent(v) {
|
|
35849
|
+
if (v === "bridge_root" || v === "worktrees_root") return v;
|
|
35850
|
+
if (v === "session_worktrees_root") return "worktrees_root";
|
|
35851
|
+
return null;
|
|
35657
35852
|
}
|
|
35658
35853
|
|
|
35659
|
-
// src/
|
|
35660
|
-
|
|
35661
|
-
|
|
35662
|
-
|
|
35854
|
+
// src/worktrees/prepare-new-session-worktrees.ts
|
|
35855
|
+
import * as fs28 from "node:fs";
|
|
35856
|
+
import * as path40 from "node:path";
|
|
35857
|
+
|
|
35858
|
+
// src/git/worktrees/worktree-add.ts
|
|
35859
|
+
async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
|
|
35860
|
+
const mainGit = cliSimpleGit(mainRepoPath);
|
|
35861
|
+
const base = baseRef.trim() || "HEAD";
|
|
35862
|
+
await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, base]);
|
|
35663
35863
|
}
|
|
35664
35864
|
|
|
35665
|
-
// src/worktrees/
|
|
35666
|
-
|
|
35667
|
-
|
|
35668
|
-
|
|
35865
|
+
// src/worktrees/prepare-new-session-worktrees.ts
|
|
35866
|
+
function normalizeRepoRelPath2(rel) {
|
|
35867
|
+
return rel === "" ? "." : rel.replace(/\\/g, "/");
|
|
35868
|
+
}
|
|
35869
|
+
function resolveBaseRefForRepo(relNorm, baseBranches) {
|
|
35870
|
+
if (!baseBranches) return "HEAD";
|
|
35871
|
+
const direct = baseBranches[relNorm]?.trim();
|
|
35872
|
+
if (direct) return direct;
|
|
35873
|
+
if (relNorm !== "." && baseBranches["."]?.trim()) return baseBranches["."].trim();
|
|
35874
|
+
return "HEAD";
|
|
35875
|
+
}
|
|
35876
|
+
async function prepareNewSessionWorktrees(options) {
|
|
35877
|
+
const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
|
|
35878
|
+
const bridgeResolved = path40.resolve(bridgeRoot);
|
|
35879
|
+
const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
|
|
35880
|
+
const bridgeKeyDir = path40.join(worktreesRootPath, cwdKey);
|
|
35881
|
+
const sessionDir = path40.join(bridgeKeyDir, sessionId);
|
|
35882
|
+
const repos = await discoverGitReposUnderRoot(bridgeResolved);
|
|
35883
|
+
if (repos.length === 0) {
|
|
35884
|
+
log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
|
|
35885
|
+
return null;
|
|
35886
|
+
}
|
|
35887
|
+
const branch = `session-${sessionId}`;
|
|
35888
|
+
const worktreePaths = [];
|
|
35889
|
+
fs28.mkdirSync(sessionDir, { recursive: true });
|
|
35890
|
+
for (const repo of repos) {
|
|
35891
|
+
let rel = path40.relative(bridgeResolved, repo.absolutePath);
|
|
35892
|
+
if (rel.startsWith("..") || path40.isAbsolute(rel)) continue;
|
|
35893
|
+
const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
|
|
35894
|
+
const wtPath = relNorm === "." ? sessionDir : path40.join(sessionDir, relNorm);
|
|
35895
|
+
if (relNorm !== ".") {
|
|
35896
|
+
fs28.mkdirSync(path40.dirname(wtPath), { recursive: true });
|
|
35897
|
+
}
|
|
35898
|
+
const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
|
|
35669
35899
|
try {
|
|
35670
|
-
await
|
|
35671
|
-
log2(`[worktrees]
|
|
35900
|
+
await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch, baseRef);
|
|
35901
|
+
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}, base ${baseRef}).`);
|
|
35902
|
+
worktreePaths.push(wtPath);
|
|
35672
35903
|
} catch (e) {
|
|
35673
35904
|
log2(
|
|
35674
|
-
`[worktrees]
|
|
35905
|
+
`[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
|
|
35675
35906
|
);
|
|
35676
35907
|
}
|
|
35677
35908
|
}
|
|
35678
|
-
|
|
35679
|
-
|
|
35680
|
-
// src/worktrees/manager/resolve-commit-targets.ts
|
|
35681
|
-
function bridgeRootBinding(bridgeRoot) {
|
|
35909
|
+
if (worktreePaths.length === 0) return null;
|
|
35682
35910
|
return {
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
|
|
35911
|
+
worktreePaths,
|
|
35912
|
+
sessionParentPath: sessionDir,
|
|
35913
|
+
workingTreeRelRoot: sessionDir
|
|
35686
35914
|
};
|
|
35687
35915
|
}
|
|
35688
|
-
async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
|
|
35689
|
-
const sid = sessionId.trim();
|
|
35690
|
-
const paths = cache2.getRepoCheckoutPathsRef(sid);
|
|
35691
|
-
if (paths?.length) return paths;
|
|
35692
|
-
const disc = await discover(sid);
|
|
35693
|
-
if (disc?.repoCheckoutPaths.length) {
|
|
35694
|
-
cache2.remember(sid, disc);
|
|
35695
|
-
return disc.repoCheckoutPaths;
|
|
35696
|
-
}
|
|
35697
|
-
const bridgeRoot = getBridgeRoot();
|
|
35698
|
-
cache2.remember(sid, bridgeRootBinding(bridgeRoot));
|
|
35699
|
-
return [bridgeRoot];
|
|
35700
|
-
}
|
|
35701
35916
|
|
|
35702
|
-
// src/worktrees/manager/session-
|
|
35703
|
-
async function
|
|
35704
|
-
const
|
|
35705
|
-
|
|
35706
|
-
|
|
35707
|
-
|
|
35708
|
-
|
|
35709
|
-
|
|
35710
|
-
|
|
35917
|
+
// src/worktrees/manager/prepare-and-remember-session-worktrees.ts
|
|
35918
|
+
async function prepareAndRememberSessionWorktrees(params) {
|
|
35919
|
+
const prep = await prepareNewSessionWorktrees({
|
|
35920
|
+
worktreesRootPath: params.worktreesRootPath,
|
|
35921
|
+
bridgeRoot: getBridgeRoot(),
|
|
35922
|
+
sessionId: params.sessionId,
|
|
35923
|
+
layout: params.layout,
|
|
35924
|
+
log: params.log,
|
|
35925
|
+
...params.worktreeBaseBranches && Object.keys(params.worktreeBaseBranches).length > 0 ? { worktreeBaseBranches: params.worktreeBaseBranches } : {}
|
|
35711
35926
|
});
|
|
35712
|
-
|
|
35713
|
-
|
|
35714
|
-
|
|
35715
|
-
|
|
35716
|
-
|
|
35717
|
-
}
|
|
35718
|
-
async function getSessionWorkingTreeChangeDetails(cache2, sessionId, discover, opts) {
|
|
35719
|
-
const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
|
|
35720
|
-
return getWorkingTreeChangeRepoDetails({
|
|
35721
|
-
commitTargetPaths: targets,
|
|
35722
|
-
sessionWorktreeRootPath: cache2.getWorkingTreeRelRoot(sessionId),
|
|
35723
|
-
legacyRepoNestedSessionLayout: cache2.isLegacyNestedLayout(sessionId),
|
|
35724
|
-
repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
|
|
35725
|
-
basis: opts?.basis,
|
|
35726
|
-
recentCommitsLimit: opts?.recentCommitsLimit
|
|
35927
|
+
if (!prep) return void 0;
|
|
35928
|
+
params.cache.remember(params.sessionId, {
|
|
35929
|
+
sessionParentPath: prep.sessionParentPath,
|
|
35930
|
+
workingTreeRelRoot: prep.workingTreeRelRoot,
|
|
35931
|
+
repoCheckoutPaths: prep.worktreePaths
|
|
35727
35932
|
});
|
|
35933
|
+
return params.cache.getSessionParentPath(params.sessionId);
|
|
35728
35934
|
}
|
|
35729
|
-
|
|
35730
|
-
|
|
35731
|
-
|
|
35732
|
-
|
|
35733
|
-
|
|
35734
|
-
|
|
35735
|
-
|
|
35935
|
+
|
|
35936
|
+
// src/worktrees/manager/resolve-existing-session-parent-path.ts
|
|
35937
|
+
async function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
35938
|
+
const cached2 = cache2.getSessionParentPath(sessionId);
|
|
35939
|
+
if (cached2) return cached2;
|
|
35940
|
+
const disc = await discover();
|
|
35941
|
+
if (disc) {
|
|
35942
|
+
cache2.remember(sessionId, disc);
|
|
35943
|
+
return cache2.getSessionParentPath(sessionId);
|
|
35736
35944
|
}
|
|
35737
|
-
|
|
35738
|
-
async function removeSessionWorktreeCheckouts(cache2, sessionId, log2) {
|
|
35739
|
-
const paths = cache2.clearSession(sessionId);
|
|
35740
|
-
if (!paths?.length) return;
|
|
35741
|
-
await removeSessionWorktrees(paths, log2);
|
|
35742
|
-
}
|
|
35743
|
-
async function renameSessionWorktreeBranch(cache2, sessionId, newBranch, log2) {
|
|
35744
|
-
const paths = cache2.getRepoCheckoutPathsRef(sessionId);
|
|
35745
|
-
if (!paths?.length) return;
|
|
35746
|
-
await renameSessionWorktreeBranches(paths, newBranch, log2);
|
|
35945
|
+
return void 0;
|
|
35747
35946
|
}
|
|
35748
35947
|
|
|
35749
|
-
// src/worktrees/manager/session-
|
|
35750
|
-
import * as
|
|
35751
|
-
|
|
35752
|
-
|
|
35753
|
-
|
|
35754
|
-
|
|
35755
|
-
remember(sessionId, binding) {
|
|
35756
|
-
const paths = binding.repoCheckoutPaths.map((p) => path35.resolve(p));
|
|
35757
|
-
this.sessionRepoCheckoutPaths.set(sessionId, paths);
|
|
35758
|
-
this.sessionParentPathBySession.set(sessionId, path35.resolve(binding.sessionParentPath));
|
|
35759
|
-
this.sessionWorkingTreeRelRootBySession.set(sessionId, path35.resolve(binding.workingTreeRelRoot));
|
|
35760
|
-
}
|
|
35761
|
-
clearSession(sessionId) {
|
|
35762
|
-
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
35763
|
-
this.sessionRepoCheckoutPaths.delete(sessionId);
|
|
35764
|
-
this.sessionParentPathBySession.delete(sessionId);
|
|
35765
|
-
this.sessionWorkingTreeRelRootBySession.delete(sessionId);
|
|
35766
|
-
return paths;
|
|
35767
|
-
}
|
|
35768
|
-
getSessionParentPath(sessionId) {
|
|
35769
|
-
return this.sessionParentPathBySession.get(sessionId);
|
|
35770
|
-
}
|
|
35771
|
-
getWorkingTreeRelRoot(sessionId) {
|
|
35772
|
-
return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
|
|
35773
|
-
}
|
|
35774
|
-
hasSession(sessionId) {
|
|
35775
|
-
return this.sessionParentPathBySession.has(sessionId);
|
|
35776
|
-
}
|
|
35777
|
-
getRepoCheckoutPaths(sessionId) {
|
|
35778
|
-
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
35779
|
-
return paths?.length ? [...paths] : void 0;
|
|
35780
|
-
}
|
|
35781
|
-
getRepoCheckoutPathsRef(sessionId) {
|
|
35782
|
-
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
35783
|
-
return paths?.length ? paths : void 0;
|
|
35948
|
+
// src/worktrees/manager/resolve-explicit-session-parent-path.ts
|
|
35949
|
+
import * as path41 from "node:path";
|
|
35950
|
+
async function resolveExplicitSessionParentPath(params) {
|
|
35951
|
+
const resolved = path41.resolve(params.parentPathRaw);
|
|
35952
|
+
if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
|
|
35953
|
+
return resolved;
|
|
35784
35954
|
}
|
|
35785
|
-
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35955
|
+
const rememberAndReturn = (binding) => {
|
|
35956
|
+
params.cache.remember(params.sessionId, binding);
|
|
35957
|
+
return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
|
|
35958
|
+
};
|
|
35959
|
+
const diskFirst = await params.discover();
|
|
35960
|
+
if (diskFirst) return rememberAndReturn(diskFirst);
|
|
35961
|
+
const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
35962
|
+
if (fromRoot) return rememberAndReturn(fromRoot);
|
|
35963
|
+
let cur = resolved;
|
|
35964
|
+
for (let i = 0; i < 16; i++) {
|
|
35965
|
+
await yieldToEventLoop();
|
|
35966
|
+
const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
|
|
35967
|
+
if (tryRoot) return rememberAndReturn(tryRoot);
|
|
35968
|
+
const next = path41.dirname(cur);
|
|
35969
|
+
if (next === cur) break;
|
|
35970
|
+
cur = next;
|
|
35790
35971
|
}
|
|
35791
|
-
|
|
35972
|
+
return resolved;
|
|
35973
|
+
}
|
|
35792
35974
|
|
|
35793
|
-
// src/worktrees/manager/
|
|
35794
|
-
|
|
35795
|
-
worktreesRootPath;
|
|
35796
|
-
|
|
35797
|
-
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
|
|
35801
|
-
|
|
35802
|
-
|
|
35803
|
-
|
|
35804
|
-
|
|
35805
|
-
|
|
35806
|
-
sessionId,
|
|
35807
|
-
worktreesRootPath: this.worktreesRootPath,
|
|
35808
|
-
layout: this.layout
|
|
35975
|
+
// src/worktrees/manager/resolve-session-parent-path-for-prompt.ts
|
|
35976
|
+
async function resolveSessionParentPathForPrompt(params) {
|
|
35977
|
+
const { sessionId, cache: cache2, worktreesRootPath, layout, log: log2, discover, opts } = params;
|
|
35978
|
+
if (!sessionId) return void 0;
|
|
35979
|
+
const sid = sessionId.trim();
|
|
35980
|
+
const parentPathRaw = opts.sessionParentPath?.trim();
|
|
35981
|
+
if (parentPathRaw) {
|
|
35982
|
+
return resolveExplicitSessionParentPath({
|
|
35983
|
+
sessionId: sid,
|
|
35984
|
+
sessionParent: opts.sessionParent,
|
|
35985
|
+
parentPathRaw,
|
|
35986
|
+
cache: cache2,
|
|
35987
|
+
discover: () => discover(sid)
|
|
35809
35988
|
});
|
|
35810
35989
|
}
|
|
35811
|
-
|
|
35812
|
-
|
|
35813
|
-
|
|
35814
|
-
this.cache,
|
|
35815
|
-
(sid) => this.ensureRepoCheckoutPathsForSession(sid)
|
|
35816
|
-
);
|
|
35990
|
+
const parentKind = parseSessionParent(opts.sessionParent);
|
|
35991
|
+
if (parentKind === "bridge_root") {
|
|
35992
|
+
return void 0;
|
|
35817
35993
|
}
|
|
35818
|
-
|
|
35819
|
-
|
|
35820
|
-
|
|
35821
|
-
|
|
35822
|
-
|
|
35823
|
-
|
|
35824
|
-
|
|
35825
|
-
|
|
35826
|
-
|
|
35994
|
+
if (parentKind === "worktrees_root") {
|
|
35995
|
+
if (!opts.isNewSession) {
|
|
35996
|
+
return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
|
|
35997
|
+
}
|
|
35998
|
+
return prepareAndRememberSessionWorktrees({
|
|
35999
|
+
cache: cache2,
|
|
36000
|
+
sessionId: sid,
|
|
36001
|
+
worktreesRootPath,
|
|
36002
|
+
layout,
|
|
36003
|
+
log: log2,
|
|
36004
|
+
...opts.worktreeBaseBranches ? { worktreeBaseBranches: opts.worktreeBaseBranches } : {}
|
|
35827
36005
|
});
|
|
35828
36006
|
}
|
|
35829
|
-
|
|
35830
|
-
|
|
35831
|
-
}
|
|
35832
|
-
usesWorktreeSession(sessionId) {
|
|
35833
|
-
if (!sessionId) return false;
|
|
35834
|
-
return this.cache.hasSession(sessionId);
|
|
35835
|
-
}
|
|
35836
|
-
getRepoCheckoutPathsForSession(sessionId) {
|
|
35837
|
-
if (!sessionId) return void 0;
|
|
35838
|
-
return this.cache.getRepoCheckoutPaths(sessionId);
|
|
35839
|
-
}
|
|
35840
|
-
ensureRepoCheckoutPathsForSession(sessionId) {
|
|
35841
|
-
return ensureRepoCheckoutPathsForSession(sessionId, this.cache);
|
|
35842
|
-
}
|
|
35843
|
-
async ensureRepoCheckoutPathsForSessionAsync(sessionId) {
|
|
35844
|
-
return ensureRepoCheckoutPathsForSessionAsync(sessionId, this.cache, (sid) => this.discoverAsync(sid));
|
|
35845
|
-
}
|
|
35846
|
-
getSessionWorktreeRootForSession(sessionId) {
|
|
35847
|
-
return this.getIsolatedSessionParentPathForSession(sessionId);
|
|
35848
|
-
}
|
|
35849
|
-
async removeSessionWorktrees(sessionId) {
|
|
35850
|
-
await removeSessionWorktreeCheckouts(this.cache, sessionId, this.log);
|
|
35851
|
-
}
|
|
35852
|
-
async commitSession(params) {
|
|
35853
|
-
return commitSessionWorktree(this.cache, params);
|
|
35854
|
-
}
|
|
35855
|
-
async getSessionWorkingTreeStatus(sessionId) {
|
|
35856
|
-
return getSessionWorkingTreeStatus(this.cache, sessionId, (sid) => this.discoverAsync(sid));
|
|
35857
|
-
}
|
|
35858
|
-
async getSessionWorkingTreeChangeDetails(sessionId, opts) {
|
|
35859
|
-
return getSessionWorkingTreeChangeDetails(this.cache, sessionId, (sid) => this.discoverAsync(sid), opts);
|
|
36007
|
+
if (!opts.isNewSession) {
|
|
36008
|
+
return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
|
|
35860
36009
|
}
|
|
35861
|
-
|
|
35862
|
-
|
|
36010
|
+
return prepareAndRememberSessionWorktrees({
|
|
36011
|
+
cache: cache2,
|
|
36012
|
+
sessionId: sid,
|
|
36013
|
+
worktreesRootPath,
|
|
36014
|
+
layout,
|
|
36015
|
+
log: log2
|
|
36016
|
+
});
|
|
36017
|
+
}
|
|
36018
|
+
|
|
36019
|
+
// src/worktrees/manager/session-worktree-paths.ts
|
|
36020
|
+
function createSessionWorktreePathsApi(ctx) {
|
|
36021
|
+
const discoverAsync = (sessionId) => discoverSessionBindingAsync({
|
|
36022
|
+
sessionId,
|
|
36023
|
+
worktreesRootPath: ctx.worktreesRootPath,
|
|
36024
|
+
layout: ctx.layout
|
|
36025
|
+
});
|
|
36026
|
+
return {
|
|
36027
|
+
discoverAsync,
|
|
36028
|
+
getIsolatedSessionParentPathForSession(sessionId) {
|
|
36029
|
+
return resolveIsolatedSessionParentPath(
|
|
36030
|
+
sessionId,
|
|
36031
|
+
ctx.cache,
|
|
36032
|
+
(sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
|
|
36033
|
+
);
|
|
36034
|
+
},
|
|
36035
|
+
resolveSessionParentPathForPrompt(sessionId, opts) {
|
|
36036
|
+
return resolveSessionParentPathForPrompt({
|
|
36037
|
+
sessionId,
|
|
36038
|
+
cache: ctx.cache,
|
|
36039
|
+
worktreesRootPath: ctx.worktreesRootPath,
|
|
36040
|
+
layout: ctx.layout,
|
|
36041
|
+
log: ctx.log,
|
|
36042
|
+
discover: (sid) => discoverAsync(sid),
|
|
36043
|
+
opts
|
|
36044
|
+
});
|
|
36045
|
+
},
|
|
36046
|
+
usesWorktreeSession(sessionId) {
|
|
36047
|
+
if (!sessionId) return false;
|
|
36048
|
+
return ctx.cache.hasSession(sessionId);
|
|
36049
|
+
},
|
|
36050
|
+
getRepoCheckoutPathsForSession(sessionId) {
|
|
36051
|
+
if (!sessionId) return void 0;
|
|
36052
|
+
return ctx.cache.getRepoCheckoutPaths(sessionId);
|
|
36053
|
+
},
|
|
36054
|
+
ensureRepoCheckoutPathsForSession(sessionId) {
|
|
36055
|
+
return ensureRepoCheckoutPathsForSession(sessionId, ctx.cache);
|
|
36056
|
+
},
|
|
36057
|
+
async ensureRepoCheckoutPathsForSessionAsync(sessionId) {
|
|
36058
|
+
return ensureRepoCheckoutPathsForSessionAsync(sessionId, ctx.cache, (sid) => discoverAsync(sid));
|
|
36059
|
+
},
|
|
36060
|
+
getSessionWorktreeRootForSession(sessionId) {
|
|
36061
|
+
return resolveIsolatedSessionParentPath(
|
|
36062
|
+
sessionId,
|
|
36063
|
+
ctx.cache,
|
|
36064
|
+
(sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
|
|
36065
|
+
);
|
|
36066
|
+
}
|
|
36067
|
+
};
|
|
36068
|
+
}
|
|
36069
|
+
|
|
36070
|
+
// src/worktrees/manager/session-worktree-git.ts
|
|
36071
|
+
function createSessionWorktreeGitApi(ctx) {
|
|
36072
|
+
const paths = createSessionWorktreePathsApi(ctx);
|
|
36073
|
+
return {
|
|
36074
|
+
async renameSessionBranch(sessionId, newBranch) {
|
|
36075
|
+
await renameSessionWorktreeBranch(ctx.cache, sessionId, newBranch, ctx.log);
|
|
36076
|
+
},
|
|
36077
|
+
async removeSessionWorktrees(sessionId) {
|
|
36078
|
+
await removeSessionWorktreeCheckouts(ctx.cache, sessionId, ctx.log);
|
|
36079
|
+
},
|
|
36080
|
+
async commitSession(params) {
|
|
36081
|
+
return commitSessionWorktree(ctx.cache, params);
|
|
36082
|
+
},
|
|
36083
|
+
async getSessionWorkingTreeStatus(sessionId) {
|
|
36084
|
+
return getSessionWorkingTreeStatus(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
|
|
36085
|
+
},
|
|
36086
|
+
async getSessionWorkingTreeChangeDetails(sessionId, opts) {
|
|
36087
|
+
return getSessionWorkingTreeChangeDetails(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
|
|
36088
|
+
},
|
|
36089
|
+
async getSessionWorkingTreeChangeFilePatch(sessionId, opts) {
|
|
36090
|
+
return getSessionWorkingTreeChangeFilePatch(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
|
|
36091
|
+
},
|
|
36092
|
+
async pushSessionUpstream(sessionId) {
|
|
36093
|
+
return pushSessionUpstream(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
|
|
36094
|
+
}
|
|
36095
|
+
};
|
|
36096
|
+
}
|
|
36097
|
+
|
|
36098
|
+
// src/worktrees/manager/session-worktree-manager.ts
|
|
36099
|
+
var SessionWorktreeManager = class {
|
|
36100
|
+
constructor(options) {
|
|
36101
|
+
const ctx = createSessionWorktreeManagerContext(options);
|
|
36102
|
+
Object.assign(this, createSessionWorktreePathsApi(ctx), createSessionWorktreeGitApi(ctx));
|
|
35863
36103
|
}
|
|
35864
36104
|
};
|
|
35865
36105
|
|
|
35866
36106
|
// src/worktrees/manager/default-worktrees-root-path.ts
|
|
35867
|
-
import * as
|
|
36107
|
+
import * as path42 from "node:path";
|
|
35868
36108
|
import os8 from "node:os";
|
|
35869
36109
|
function defaultWorktreesRootPath() {
|
|
35870
|
-
return
|
|
36110
|
+
return path42.join(os8.homedir(), ".buildautomaton", "worktrees");
|
|
35871
36111
|
}
|
|
35872
36112
|
|
|
35873
36113
|
// src/files/watch-file-index.ts
|
|
35874
36114
|
import { watch } from "node:fs";
|
|
35875
|
-
import
|
|
36115
|
+
import path47 from "node:path";
|
|
35876
36116
|
|
|
35877
36117
|
// src/files/index/paths.ts
|
|
35878
|
-
import
|
|
36118
|
+
import path43 from "node:path";
|
|
35879
36119
|
import crypto2 from "node:crypto";
|
|
35880
36120
|
function getCwdHashForFileIndex(resolvedCwd) {
|
|
35881
|
-
return crypto2.createHash("sha256").update(
|
|
36121
|
+
return crypto2.createHash("sha256").update(path43.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
|
|
35882
36122
|
}
|
|
35883
36123
|
|
|
35884
36124
|
// src/files/index/build-file-index.ts
|
|
35885
|
-
import
|
|
36125
|
+
import path45 from "node:path";
|
|
35886
36126
|
|
|
35887
36127
|
// src/files/index/walk-workspace-tree.ts
|
|
35888
36128
|
import fs29 from "node:fs";
|
|
35889
|
-
import
|
|
36129
|
+
import path44 from "node:path";
|
|
35890
36130
|
var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
35891
36131
|
"node_modules",
|
|
35892
36132
|
"bower_components",
|
|
@@ -35915,18 +36155,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
|
|
|
35915
36155
|
if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
|
|
35916
36156
|
}
|
|
35917
36157
|
state.n++;
|
|
35918
|
-
const full =
|
|
36158
|
+
const full = path44.join(dir, name);
|
|
35919
36159
|
let stat2;
|
|
35920
36160
|
try {
|
|
35921
36161
|
stat2 = await fs29.promises.stat(full);
|
|
35922
36162
|
} catch {
|
|
35923
36163
|
continue;
|
|
35924
36164
|
}
|
|
35925
|
-
const
|
|
36165
|
+
const relative7 = path44.relative(baseDir, full).replace(/\\/g, "/");
|
|
35926
36166
|
if (stat2.isDirectory()) {
|
|
35927
36167
|
await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
|
|
35928
36168
|
} else if (stat2.isFile()) {
|
|
35929
|
-
onFile(
|
|
36169
|
+
onFile(relative7);
|
|
35930
36170
|
}
|
|
35931
36171
|
}
|
|
35932
36172
|
}
|
|
@@ -36024,7 +36264,7 @@ async function collectWorkspacePathsAsync(resolved) {
|
|
|
36024
36264
|
}
|
|
36025
36265
|
async function buildFileIndexAsync(cwd) {
|
|
36026
36266
|
return withFileIndexSqliteLock(async () => {
|
|
36027
|
-
const resolved =
|
|
36267
|
+
const resolved = path45.resolve(cwd);
|
|
36028
36268
|
await yieldToEventLoop();
|
|
36029
36269
|
assertNotShutdown();
|
|
36030
36270
|
const paths = await collectWorkspacePathsAsync(resolved);
|
|
@@ -36036,7 +36276,7 @@ async function buildFileIndexAsync(cwd) {
|
|
|
36036
36276
|
}
|
|
36037
36277
|
|
|
36038
36278
|
// src/files/index/ensure-file-index.ts
|
|
36039
|
-
import
|
|
36279
|
+
import path46 from "node:path";
|
|
36040
36280
|
|
|
36041
36281
|
// src/files/index/search-file-index.ts
|
|
36042
36282
|
function escapeLikePattern(fragment) {
|
|
@@ -36088,7 +36328,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
|
|
|
36088
36328
|
|
|
36089
36329
|
// src/files/index/ensure-file-index.ts
|
|
36090
36330
|
async function ensureFileIndexAsync(cwd) {
|
|
36091
|
-
const resolved =
|
|
36331
|
+
const resolved = path46.resolve(cwd);
|
|
36092
36332
|
if (await bridgeFileIndexIsPopulated(resolved)) {
|
|
36093
36333
|
return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
|
|
36094
36334
|
}
|
|
@@ -36132,7 +36372,7 @@ function createFsWatcher(resolved, schedule) {
|
|
|
36132
36372
|
}
|
|
36133
36373
|
}
|
|
36134
36374
|
function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
36135
|
-
const resolved =
|
|
36375
|
+
const resolved = path47.resolve(cwd);
|
|
36136
36376
|
void buildFileIndexAsync(resolved).catch((e) => {
|
|
36137
36377
|
if (e instanceof CliSqliteInterrupted) return;
|
|
36138
36378
|
console.error("[file-index] Initial index build failed:", e);
|
|
@@ -36162,7 +36402,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
|
36162
36402
|
}
|
|
36163
36403
|
|
|
36164
36404
|
// src/connection/create-bridge-connection.ts
|
|
36165
|
-
import * as
|
|
36405
|
+
import * as path59 from "node:path";
|
|
36166
36406
|
|
|
36167
36407
|
// src/dev-servers/manager/dev-server-manager.ts
|
|
36168
36408
|
import { rm as rm2 } from "node:fs/promises";
|
|
@@ -36184,15 +36424,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
|
|
|
36184
36424
|
|
|
36185
36425
|
// src/dev-servers/process/terminate-child-process.ts
|
|
36186
36426
|
async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
36187
|
-
const exited = new Promise((
|
|
36188
|
-
proc.once("exit", () =>
|
|
36427
|
+
const exited = new Promise((resolve32) => {
|
|
36428
|
+
proc.once("exit", () => resolve32());
|
|
36189
36429
|
});
|
|
36190
36430
|
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
|
|
36191
36431
|
try {
|
|
36192
36432
|
proc.kill("SIGTERM");
|
|
36193
36433
|
} catch {
|
|
36194
36434
|
}
|
|
36195
|
-
await Promise.race([exited, new Promise((
|
|
36435
|
+
await Promise.race([exited, new Promise((resolve32) => setTimeout(resolve32, graceMs))]);
|
|
36196
36436
|
}
|
|
36197
36437
|
function forceKillChild(proc, log2, shortId, graceMs) {
|
|
36198
36438
|
log2(
|
|
@@ -36472,10 +36712,10 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
|
36472
36712
|
import { spawn as spawn8 } from "node:child_process";
|
|
36473
36713
|
import fs33 from "node:fs";
|
|
36474
36714
|
import { tmpdir } from "node:os";
|
|
36475
|
-
import
|
|
36715
|
+
import path48 from "node:path";
|
|
36476
36716
|
function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
36477
|
-
const tmpRoot = fs33.mkdtempSync(
|
|
36478
|
-
const logPath =
|
|
36717
|
+
const tmpRoot = fs33.mkdtempSync(path48.join(tmpdir(), "ba-devsrv-log-"));
|
|
36718
|
+
const logPath = path48.join(tmpRoot, "combined.log");
|
|
36479
36719
|
let logFd;
|
|
36480
36720
|
try {
|
|
36481
36721
|
logFd = fs33.openSync(logPath, "a");
|
|
@@ -36519,15 +36759,15 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
36519
36759
|
import { spawn as spawn9 } from "node:child_process";
|
|
36520
36760
|
import fs34 from "node:fs";
|
|
36521
36761
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
36522
|
-
import
|
|
36762
|
+
import path49 from "node:path";
|
|
36523
36763
|
function shSingleQuote(s) {
|
|
36524
36764
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
36525
36765
|
}
|
|
36526
36766
|
function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
|
|
36527
|
-
const tmpRoot = fs34.mkdtempSync(
|
|
36528
|
-
const logPath =
|
|
36529
|
-
const innerPath =
|
|
36530
|
-
const runnerPath =
|
|
36767
|
+
const tmpRoot = fs34.mkdtempSync(path49.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
36768
|
+
const logPath = path49.join(tmpRoot, "combined.log");
|
|
36769
|
+
const innerPath = path49.join(tmpRoot, "_cmd.sh");
|
|
36770
|
+
const runnerPath = path49.join(tmpRoot, "_run.sh");
|
|
36531
36771
|
try {
|
|
36532
36772
|
fs34.writeFileSync(innerPath, `#!/bin/sh
|
|
36533
36773
|
${command}
|
|
@@ -36558,9 +36798,9 @@ cd ${shSingleQuote(cwd)}
|
|
|
36558
36798
|
}
|
|
36559
36799
|
}
|
|
36560
36800
|
function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
|
|
36561
|
-
const tmpRoot = fs34.mkdtempSync(
|
|
36562
|
-
const logPath =
|
|
36563
|
-
const runnerPath =
|
|
36801
|
+
const tmpRoot = fs34.mkdtempSync(path49.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
36802
|
+
const logPath = path49.join(tmpRoot, "combined.log");
|
|
36803
|
+
const runnerPath = path49.join(tmpRoot, "_run.bat");
|
|
36564
36804
|
const q = (p) => `"${p.replace(/"/g, '""')}"`;
|
|
36565
36805
|
const com = process.env.ComSpec || "cmd.exe";
|
|
36566
36806
|
try {
|
|
@@ -37332,15 +37572,38 @@ function createOnBridgeIdentified(opts) {
|
|
|
37332
37572
|
};
|
|
37333
37573
|
}
|
|
37334
37574
|
|
|
37335
|
-
// src/skills/
|
|
37336
|
-
import fs35 from "node:fs";
|
|
37337
|
-
import path44 from "node:path";
|
|
37575
|
+
// src/skills/discovery/skill-discovery-roots.ts
|
|
37338
37576
|
var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
|
|
37339
|
-
function
|
|
37577
|
+
async function yieldSkillDiscoveryWork(entryCount) {
|
|
37578
|
+
if (entryCount > 0 && entryCount % INDEX_WORK_YIELD_EVERY === 0) {
|
|
37579
|
+
await yieldToEventLoop();
|
|
37580
|
+
}
|
|
37581
|
+
}
|
|
37582
|
+
|
|
37583
|
+
// src/skills/discovery/discover-local-skills.ts
|
|
37584
|
+
import fs35 from "node:fs";
|
|
37585
|
+
import path50 from "node:path";
|
|
37586
|
+
function collectSkillFromDir(rel, base, name, seenKeys, out) {
|
|
37587
|
+
const dir = path50.join(base, name);
|
|
37588
|
+
try {
|
|
37589
|
+
if (!fs35.statSync(dir).isDirectory()) return;
|
|
37590
|
+
} catch {
|
|
37591
|
+
return;
|
|
37592
|
+
}
|
|
37593
|
+
const skillMd = path50.join(dir, "SKILL.md");
|
|
37594
|
+
if (!fs35.existsSync(skillMd)) return;
|
|
37595
|
+
const key = `${rel}/${name}`;
|
|
37596
|
+
if (seenKeys.has(key)) return;
|
|
37597
|
+
seenKeys.add(key);
|
|
37598
|
+
out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
|
|
37599
|
+
}
|
|
37600
|
+
async function discoverLocalSkillsAsync(cwd) {
|
|
37340
37601
|
const out = [];
|
|
37341
37602
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
37603
|
+
let work = 0;
|
|
37342
37604
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
37343
|
-
|
|
37605
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37606
|
+
const base = path50.join(cwd, rel);
|
|
37344
37607
|
if (!fs35.existsSync(base) || !fs35.statSync(base).isDirectory()) continue;
|
|
37345
37608
|
let entries = [];
|
|
37346
37609
|
try {
|
|
@@ -37348,45 +37611,45 @@ function discoverLocalSkills(cwd) {
|
|
|
37348
37611
|
} catch {
|
|
37349
37612
|
continue;
|
|
37350
37613
|
}
|
|
37351
|
-
for (
|
|
37352
|
-
|
|
37353
|
-
|
|
37354
|
-
if (!fs35.statSync(dir).isDirectory()) continue;
|
|
37355
|
-
} catch {
|
|
37356
|
-
continue;
|
|
37357
|
-
}
|
|
37358
|
-
const skillMd = path44.join(dir, "SKILL.md");
|
|
37359
|
-
if (!fs35.existsSync(skillMd)) continue;
|
|
37360
|
-
const key = `${rel}/${name}`;
|
|
37361
|
-
if (seenKeys.has(key)) continue;
|
|
37362
|
-
seenKeys.add(key);
|
|
37363
|
-
out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
|
|
37614
|
+
for (let i = 0; i < entries.length; i++) {
|
|
37615
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37616
|
+
collectSkillFromDir(rel, base, entries[i], seenKeys, out);
|
|
37364
37617
|
}
|
|
37365
37618
|
}
|
|
37366
37619
|
return out;
|
|
37367
37620
|
}
|
|
37368
|
-
|
|
37621
|
+
|
|
37622
|
+
// src/skills/discovery/discover-skill-layout-roots.ts
|
|
37623
|
+
import fs36 from "node:fs";
|
|
37624
|
+
import path51 from "node:path";
|
|
37625
|
+
function collectLayoutSkill(rel, base, name, skills2) {
|
|
37626
|
+
const dir = path51.join(base, name);
|
|
37627
|
+
try {
|
|
37628
|
+
if (!fs36.statSync(dir).isDirectory()) return;
|
|
37629
|
+
} catch {
|
|
37630
|
+
return;
|
|
37631
|
+
}
|
|
37632
|
+
if (!fs36.existsSync(path51.join(dir, "SKILL.md"))) return;
|
|
37633
|
+
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
37634
|
+
skills2.push({ name, relPath });
|
|
37635
|
+
}
|
|
37636
|
+
async function discoverSkillLayoutRootsAsync(cwd) {
|
|
37369
37637
|
const roots = [];
|
|
37638
|
+
let work = 0;
|
|
37370
37639
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
37371
|
-
|
|
37372
|
-
|
|
37640
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37641
|
+
const base = path51.join(cwd, rel);
|
|
37642
|
+
if (!fs36.existsSync(base) || !fs36.statSync(base).isDirectory()) continue;
|
|
37373
37643
|
let entries = [];
|
|
37374
37644
|
try {
|
|
37375
|
-
entries =
|
|
37645
|
+
entries = fs36.readdirSync(base);
|
|
37376
37646
|
} catch {
|
|
37377
37647
|
continue;
|
|
37378
37648
|
}
|
|
37379
37649
|
const skills2 = [];
|
|
37380
|
-
for (
|
|
37381
|
-
|
|
37382
|
-
|
|
37383
|
-
if (!fs35.statSync(dir).isDirectory()) continue;
|
|
37384
|
-
} catch {
|
|
37385
|
-
continue;
|
|
37386
|
-
}
|
|
37387
|
-
if (!fs35.existsSync(path44.join(dir, "SKILL.md"))) continue;
|
|
37388
|
-
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
37389
|
-
skills2.push({ name, relPath });
|
|
37650
|
+
for (let i = 0; i < entries.length; i++) {
|
|
37651
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37652
|
+
collectLayoutSkill(rel, base, entries[i], skills2);
|
|
37390
37653
|
}
|
|
37391
37654
|
if (skills2.length > 0) {
|
|
37392
37655
|
roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
|
|
@@ -37428,16 +37691,18 @@ async function detectLocalAgentTypes() {
|
|
|
37428
37691
|
function createSendLocalSkillsReport(getWs, logFn) {
|
|
37429
37692
|
return () => {
|
|
37430
37693
|
setImmediate(() => {
|
|
37431
|
-
|
|
37432
|
-
|
|
37433
|
-
|
|
37434
|
-
|
|
37435
|
-
|
|
37436
|
-
|
|
37437
|
-
|
|
37438
|
-
|
|
37439
|
-
|
|
37440
|
-
|
|
37694
|
+
void (async () => {
|
|
37695
|
+
try {
|
|
37696
|
+
const socket = getWs();
|
|
37697
|
+
if (!socket || socket.readyState !== wrapper_default.OPEN) return;
|
|
37698
|
+
const skills2 = await discoverLocalSkillsAsync(getBridgeRoot());
|
|
37699
|
+
sendWsMessage(socket, { type: "local_skills", skills: skills2 });
|
|
37700
|
+
} catch (e) {
|
|
37701
|
+
logFn(
|
|
37702
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
37703
|
+
);
|
|
37704
|
+
}
|
|
37705
|
+
})();
|
|
37441
37706
|
});
|
|
37442
37707
|
};
|
|
37443
37708
|
}
|
|
@@ -37740,7 +38005,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
|
|
|
37740
38005
|
}
|
|
37741
38006
|
|
|
37742
38007
|
// src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
|
|
37743
|
-
import
|
|
38008
|
+
import fs37 from "node:fs";
|
|
37744
38009
|
async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
37745
38010
|
if (next.serverState !== "requeued_with_revert") return true;
|
|
37746
38011
|
const sid = next.sessionId;
|
|
@@ -37749,7 +38014,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
|
37749
38014
|
const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
|
|
37750
38015
|
const file2 = snapshotFilePath(agentBase, tid);
|
|
37751
38016
|
try {
|
|
37752
|
-
await
|
|
38017
|
+
await fs37.promises.access(file2, fs37.constants.F_OK);
|
|
37753
38018
|
} catch {
|
|
37754
38019
|
deps.log(
|
|
37755
38020
|
`[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
|
|
@@ -38037,9 +38302,9 @@ function parseChangeSummarySnapshots(raw) {
|
|
|
38037
38302
|
for (const item of raw) {
|
|
38038
38303
|
if (!item || typeof item !== "object") continue;
|
|
38039
38304
|
const o = item;
|
|
38040
|
-
const
|
|
38041
|
-
if (!
|
|
38042
|
-
const row = { path:
|
|
38305
|
+
const path60 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
|
|
38306
|
+
if (!path60) continue;
|
|
38307
|
+
const row = { path: path60 };
|
|
38043
38308
|
if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
|
|
38044
38309
|
if (typeof o.oldText === "string") row.oldText = o.oldText;
|
|
38045
38310
|
if (typeof o.newText === "string") row.newText = o.newText;
|
|
@@ -38253,14 +38518,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
|
|
|
38253
38518
|
};
|
|
38254
38519
|
|
|
38255
38520
|
// src/files/list-dir/index.ts
|
|
38256
|
-
import
|
|
38521
|
+
import fs39 from "node:fs";
|
|
38257
38522
|
|
|
38258
38523
|
// src/files/ensure-under-cwd.ts
|
|
38259
|
-
import
|
|
38524
|
+
import path52 from "node:path";
|
|
38260
38525
|
function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
38261
|
-
const normalized =
|
|
38262
|
-
const resolved =
|
|
38263
|
-
if (!resolved.startsWith(cwd +
|
|
38526
|
+
const normalized = path52.normalize(relativePath).replace(/^(\.\/)+/, "");
|
|
38527
|
+
const resolved = path52.resolve(cwd, normalized);
|
|
38528
|
+
if (!resolved.startsWith(cwd + path52.sep) && resolved !== cwd) {
|
|
38264
38529
|
return null;
|
|
38265
38530
|
}
|
|
38266
38531
|
return resolved;
|
|
@@ -38270,15 +38535,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
|
38270
38535
|
var LIST_DIR_YIELD_EVERY = 256;
|
|
38271
38536
|
|
|
38272
38537
|
// src/files/list-dir/map-dir-entry.ts
|
|
38273
|
-
import
|
|
38274
|
-
import
|
|
38538
|
+
import path53 from "node:path";
|
|
38539
|
+
import fs38 from "node:fs";
|
|
38275
38540
|
async function mapDirEntry(d, relativePath, resolved) {
|
|
38276
|
-
const entryPath =
|
|
38277
|
-
const fullPath =
|
|
38541
|
+
const entryPath = path53.join(relativePath || ".", d.name).replace(/\\/g, "/");
|
|
38542
|
+
const fullPath = path53.join(resolved, d.name);
|
|
38278
38543
|
let isDir = d.isDirectory();
|
|
38279
38544
|
if (d.isSymbolicLink()) {
|
|
38280
38545
|
try {
|
|
38281
|
-
const targetStat = await
|
|
38546
|
+
const targetStat = await fs38.promises.stat(fullPath);
|
|
38282
38547
|
isDir = targetStat.isDirectory();
|
|
38283
38548
|
} catch {
|
|
38284
38549
|
isDir = false;
|
|
@@ -38308,7 +38573,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
|
38308
38573
|
return { error: "Path is outside working directory" };
|
|
38309
38574
|
}
|
|
38310
38575
|
try {
|
|
38311
|
-
const names = await
|
|
38576
|
+
const names = await fs39.promises.readdir(resolved, { withFileTypes: true });
|
|
38312
38577
|
const entries = [];
|
|
38313
38578
|
for (let i = 0; i < names.length; i++) {
|
|
38314
38579
|
if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
|
|
@@ -38328,18 +38593,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
|
|
|
38328
38593
|
var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
|
|
38329
38594
|
|
|
38330
38595
|
// src/files/read-file/resolve-file-path.ts
|
|
38331
|
-
import
|
|
38596
|
+
import fs40 from "node:fs";
|
|
38332
38597
|
async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
38333
38598
|
const resolved = ensureUnderCwd(relativePath, sessionParentPath);
|
|
38334
38599
|
if (!resolved) return { error: "Path is outside working directory" };
|
|
38335
38600
|
let real;
|
|
38336
38601
|
try {
|
|
38337
|
-
real = await
|
|
38602
|
+
real = await fs40.promises.realpath(resolved);
|
|
38338
38603
|
} catch {
|
|
38339
38604
|
real = resolved;
|
|
38340
38605
|
}
|
|
38341
38606
|
try {
|
|
38342
|
-
const stat2 = await
|
|
38607
|
+
const stat2 = await fs40.promises.stat(real);
|
|
38343
38608
|
if (!stat2.isFile()) return { error: "Not a file" };
|
|
38344
38609
|
return real;
|
|
38345
38610
|
} catch (err) {
|
|
@@ -38348,11 +38613,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
|
|
|
38348
38613
|
}
|
|
38349
38614
|
|
|
38350
38615
|
// src/files/read-file/read-file-range-async.ts
|
|
38351
|
-
import
|
|
38616
|
+
import fs41 from "node:fs";
|
|
38352
38617
|
import { StringDecoder } from "node:string_decoder";
|
|
38353
38618
|
async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
|
|
38354
|
-
const fileSize = (await
|
|
38355
|
-
const fd = await
|
|
38619
|
+
const fileSize = (await fs41.promises.stat(filePath)).size;
|
|
38620
|
+
const fd = await fs41.promises.open(filePath, "r");
|
|
38356
38621
|
const bufSize = 64 * 1024;
|
|
38357
38622
|
const buf = Buffer.alloc(bufSize);
|
|
38358
38623
|
const decoder = new StringDecoder("utf8");
|
|
@@ -38514,11 +38779,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
|
|
|
38514
38779
|
}
|
|
38515
38780
|
|
|
38516
38781
|
// src/files/read-file/read-file-buffer-full-async.ts
|
|
38517
|
-
import
|
|
38782
|
+
import fs42 from "node:fs";
|
|
38518
38783
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
38519
38784
|
async function readFileBufferFullAsync(filePath) {
|
|
38520
|
-
const stat2 = await
|
|
38521
|
-
const fd = await
|
|
38785
|
+
const stat2 = await fs42.promises.stat(filePath);
|
|
38786
|
+
const fd = await fs42.promises.open(filePath, "r");
|
|
38522
38787
|
const chunks = [];
|
|
38523
38788
|
let position = 0;
|
|
38524
38789
|
let bytesSinceYield = 0;
|
|
@@ -38625,13 +38890,13 @@ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId
|
|
|
38625
38890
|
}
|
|
38626
38891
|
|
|
38627
38892
|
// src/files/handle-file-browser-search.ts
|
|
38628
|
-
import
|
|
38893
|
+
import path54 from "node:path";
|
|
38629
38894
|
var SEARCH_LIMIT = 100;
|
|
38630
38895
|
function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
|
|
38631
38896
|
void (async () => {
|
|
38632
38897
|
await yieldToEventLoop();
|
|
38633
38898
|
const q = typeof msg.q === "string" ? msg.q : "";
|
|
38634
|
-
const sessionParentPath =
|
|
38899
|
+
const sessionParentPath = path54.resolve(
|
|
38635
38900
|
sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
|
|
38636
38901
|
);
|
|
38637
38902
|
if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
|
|
@@ -38741,37 +39006,56 @@ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManag
|
|
|
38741
39006
|
|
|
38742
39007
|
// src/routing/handlers/skill-layout-request.ts
|
|
38743
39008
|
function handleSkillLayoutRequest(msg, deps) {
|
|
38744
|
-
const socket = deps.getWs();
|
|
38745
39009
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38746
|
-
|
|
38747
|
-
|
|
38748
|
-
|
|
38749
|
-
|
|
39010
|
+
void (async () => {
|
|
39011
|
+
const socket = deps.getWs();
|
|
39012
|
+
const roots = await discoverSkillLayoutRootsAsync(getBridgeRoot());
|
|
39013
|
+
if (socket) {
|
|
39014
|
+
sendWsMessage(socket, { type: "skill_layout_response", id, roots });
|
|
39015
|
+
}
|
|
39016
|
+
})();
|
|
38750
39017
|
}
|
|
38751
39018
|
|
|
38752
|
-
// src/skills/install-
|
|
38753
|
-
|
|
38754
|
-
|
|
38755
|
-
|
|
39019
|
+
// src/skills/install/is-valid-install-item.ts
|
|
39020
|
+
function isValidRemoteSkillInstallItem(item) {
|
|
39021
|
+
if (item === null || typeof item !== "object") return false;
|
|
39022
|
+
const o = item;
|
|
39023
|
+
return typeof o.sourceId === "string" && typeof o.skillName === "string" && typeof o.versionHash === "string" && Array.isArray(o.files);
|
|
39024
|
+
}
|
|
39025
|
+
|
|
39026
|
+
// src/skills/install/install-remote-skills-async.ts
|
|
39027
|
+
import fs43 from "node:fs";
|
|
39028
|
+
import path55 from "node:path";
|
|
39029
|
+
|
|
39030
|
+
// src/skills/install/constants.ts
|
|
39031
|
+
var INSTALL_SKILLS_YIELD_EVERY = 16;
|
|
39032
|
+
|
|
39033
|
+
// src/skills/install/install-remote-skills-async.ts
|
|
39034
|
+
async function writeInstallFileAsync(skillDir, f, filesWritten) {
|
|
39035
|
+
if (typeof f.path !== "string" || !f.text && !f.base64) return;
|
|
39036
|
+
if (++filesWritten.count % INSTALL_SKILLS_YIELD_EVERY === 0) {
|
|
39037
|
+
await yieldToEventLoop();
|
|
39038
|
+
}
|
|
39039
|
+
const dest = path55.join(skillDir, f.path);
|
|
39040
|
+
await fs43.promises.mkdir(path55.dirname(dest), { recursive: true });
|
|
39041
|
+
if (f.text !== void 0) {
|
|
39042
|
+
await fs43.promises.writeFile(dest, f.text, "utf8");
|
|
39043
|
+
} else if (f.base64) {
|
|
39044
|
+
await fs43.promises.writeFile(dest, Buffer.from(f.base64, "base64"));
|
|
39045
|
+
}
|
|
39046
|
+
}
|
|
39047
|
+
async function installRemoteSkillsAsync(cwd, targetDir, items) {
|
|
38756
39048
|
const installed2 = [];
|
|
38757
39049
|
if (!Array.isArray(items)) {
|
|
38758
39050
|
return { success: false, error: "Invalid items" };
|
|
38759
39051
|
}
|
|
39052
|
+
const filesWritten = { count: 0 };
|
|
38760
39053
|
try {
|
|
38761
39054
|
for (const item of items) {
|
|
38762
|
-
if (
|
|
38763
|
-
|
|
38764
|
-
}
|
|
38765
|
-
const skillDir = path48.join(cwd, targetDir, item.skillName);
|
|
39055
|
+
if (!isValidRemoteSkillInstallItem(item)) continue;
|
|
39056
|
+
const skillDir = path55.join(cwd, targetDir, item.skillName);
|
|
38766
39057
|
for (const f of item.files) {
|
|
38767
|
-
|
|
38768
|
-
const dest = path48.join(skillDir, f.path);
|
|
38769
|
-
fs42.mkdirSync(path48.dirname(dest), { recursive: true });
|
|
38770
|
-
if (f.text !== void 0) {
|
|
38771
|
-
fs42.writeFileSync(dest, f.text, "utf8");
|
|
38772
|
-
} else if (f.base64) {
|
|
38773
|
-
fs42.writeFileSync(dest, Buffer.from(f.base64, "base64"));
|
|
38774
|
-
}
|
|
39058
|
+
await writeInstallFileAsync(skillDir, f, filesWritten);
|
|
38775
39059
|
}
|
|
38776
39060
|
installed2.push({
|
|
38777
39061
|
sourceId: item.sourceId,
|
|
@@ -38787,28 +39071,32 @@ function installRemoteSkills(cwd, targetDir, items) {
|
|
|
38787
39071
|
|
|
38788
39072
|
// src/routing/handlers/install-skills.ts
|
|
38789
39073
|
var handleInstallSkillsMessage = (msg, deps) => {
|
|
38790
|
-
const socket = deps.getWs();
|
|
38791
39074
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38792
39075
|
const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
|
|
38793
39076
|
const rawItems = msg.items;
|
|
38794
39077
|
const cwd = getBridgeRoot();
|
|
38795
|
-
|
|
38796
|
-
|
|
38797
|
-
const
|
|
38798
|
-
|
|
39078
|
+
void (async () => {
|
|
39079
|
+
const socket = deps.getWs();
|
|
39080
|
+
const result = await installRemoteSkillsAsync(cwd, targetDir, rawItems);
|
|
39081
|
+
if (!result.success) {
|
|
39082
|
+
const err = result.error ?? "Invalid items";
|
|
39083
|
+
deps.log(`[Bridge service] Install skills failed: ${err}`);
|
|
39084
|
+
if (socket) {
|
|
39085
|
+
sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
|
|
39086
|
+
}
|
|
39087
|
+
return;
|
|
39088
|
+
}
|
|
38799
39089
|
if (socket) {
|
|
38800
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success:
|
|
39090
|
+
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
38801
39091
|
}
|
|
38802
|
-
|
|
38803
|
-
}
|
|
38804
|
-
if (socket) {
|
|
38805
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
38806
|
-
}
|
|
39092
|
+
})();
|
|
38807
39093
|
};
|
|
38808
39094
|
|
|
38809
39095
|
// src/routing/handlers/refresh-local-skills.ts
|
|
38810
39096
|
var handleRefreshLocalSkills = (_msg, deps) => {
|
|
38811
|
-
|
|
39097
|
+
setImmediate(() => {
|
|
39098
|
+
deps.sendLocalSkillsReport?.();
|
|
39099
|
+
});
|
|
38812
39100
|
};
|
|
38813
39101
|
|
|
38814
39102
|
// src/routing/handlers/git/handle-session-git-commit.ts
|
|
@@ -38839,13 +39127,78 @@ async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
|
|
|
38839
39127
|
});
|
|
38840
39128
|
}
|
|
38841
39129
|
|
|
39130
|
+
// src/routing/handlers/git/session-git-changes-params.ts
|
|
39131
|
+
function readString(value) {
|
|
39132
|
+
return typeof value === "string" ? value.trim() : "";
|
|
39133
|
+
}
|
|
39134
|
+
function readChangesObject(msg) {
|
|
39135
|
+
const changes = msg.changes;
|
|
39136
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return null;
|
|
39137
|
+
return changes;
|
|
39138
|
+
}
|
|
39139
|
+
function parseSessionGitChangesParams(msg) {
|
|
39140
|
+
const nested = readChangesObject(msg);
|
|
39141
|
+
const file2 = nested?.file && typeof nested.file === "object" && !Array.isArray(nested.file) ? nested.file : null;
|
|
39142
|
+
const repoRelPath = readString(nested?.repoRelPath) || readString(msg.changesRepoRelPath);
|
|
39143
|
+
const viewRaw = nested?.view ?? msg.changesView;
|
|
39144
|
+
const view = viewRaw === "commit" ? "commit" : "working";
|
|
39145
|
+
const commitSha = readString(nested?.commitSha) || readString(msg.changesCommitSha);
|
|
39146
|
+
const recentCommitsLimit = nested?.recentCommitsLimit ?? msg.changesRecentCommitsLimit;
|
|
39147
|
+
return {
|
|
39148
|
+
repoRelPath,
|
|
39149
|
+
view,
|
|
39150
|
+
commitSha,
|
|
39151
|
+
recentCommitsLimit: typeof recentCommitsLimit === "number" || typeof recentCommitsLimit === "string" ? recentCommitsLimit : void 0,
|
|
39152
|
+
fileWorkspaceRelPath: readString(file2?.workspaceRelPath),
|
|
39153
|
+
fileChange: readString(file2?.change),
|
|
39154
|
+
fileMovedFromWorkspaceRelPath: readString(file2?.movedFromWorkspaceRelPath)
|
|
39155
|
+
};
|
|
39156
|
+
}
|
|
39157
|
+
|
|
39158
|
+
// src/routing/handlers/git/handle-session-git-get-change-file-patch.ts
|
|
39159
|
+
async function handleSessionGitGetChangeFilePatchAction(deps, msg, reply) {
|
|
39160
|
+
const changes = parseSessionGitChangesParams(msg);
|
|
39161
|
+
const { repoRelPath: repoRel, view, commitSha, fileWorkspaceRelPath, fileChange, fileMovedFromWorkspaceRelPath } = changes;
|
|
39162
|
+
const change = parseWorkingTreeChangeKind(fileChange);
|
|
39163
|
+
if (!repoRel || !fileWorkspaceRelPath || !change) {
|
|
39164
|
+
reply({
|
|
39165
|
+
ok: false,
|
|
39166
|
+
error: "changes.repoRelPath, changes.file.workspaceRelPath, and changes.file.change are required"
|
|
39167
|
+
});
|
|
39168
|
+
return;
|
|
39169
|
+
}
|
|
39170
|
+
if (view === "commit" && !commitSha) {
|
|
39171
|
+
reply({ ok: false, error: "changes.commitSha is required for commit file patch" });
|
|
39172
|
+
return;
|
|
39173
|
+
}
|
|
39174
|
+
const patch = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeFilePatch(msg.sessionId, {
|
|
39175
|
+
repoRelPath: repoRel,
|
|
39176
|
+
workspaceRelPath: fileWorkspaceRelPath,
|
|
39177
|
+
change,
|
|
39178
|
+
movedFromWorkspaceRelPath: fileMovedFromWorkspaceRelPath || null,
|
|
39179
|
+
basis: view === "commit" ? { kind: "commit", sha: commitSha } : { kind: "working" }
|
|
39180
|
+
});
|
|
39181
|
+
reply(
|
|
39182
|
+
{
|
|
39183
|
+
ok: true,
|
|
39184
|
+
patchContent: patch.patchContent ?? null,
|
|
39185
|
+
totalLines: patch.totalLines
|
|
39186
|
+
},
|
|
39187
|
+
["patchContent"]
|
|
39188
|
+
);
|
|
39189
|
+
}
|
|
39190
|
+
|
|
38842
39191
|
// src/routing/handlers/git/coalesce-list-changes.ts
|
|
38843
39192
|
var listChangesInflight = /* @__PURE__ */ new Map();
|
|
39193
|
+
function normalizeListChangesRepoRel(repoRelPath) {
|
|
39194
|
+
if (!repoRelPath?.trim()) return "";
|
|
39195
|
+
return normRepoRel(repoRelPath.trim());
|
|
39196
|
+
}
|
|
38844
39197
|
function listChangesInflightKey(sessionId, opts) {
|
|
38845
39198
|
const basis = opts?.basis;
|
|
38846
39199
|
return [
|
|
38847
39200
|
sessionId,
|
|
38848
|
-
opts?.repoRelPath
|
|
39201
|
+
normalizeListChangesRepoRel(opts?.repoRelPath),
|
|
38849
39202
|
basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
|
|
38850
39203
|
String(opts?.recentCommitsLimit ?? "")
|
|
38851
39204
|
].join("|");
|
|
@@ -38863,16 +39216,17 @@ function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, ses
|
|
|
38863
39216
|
|
|
38864
39217
|
// src/routing/handlers/git/handle-session-git-list-changes.ts
|
|
38865
39218
|
async function handleSessionGitListChangesAction(deps, msg, reply) {
|
|
38866
|
-
const
|
|
38867
|
-
const
|
|
38868
|
-
const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
|
|
39219
|
+
const changes = parseSessionGitChangesParams(msg);
|
|
39220
|
+
const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
|
|
38869
39221
|
if (view === "commit") {
|
|
38870
39222
|
if (!repoRel || !commitSha) {
|
|
38871
|
-
reply({
|
|
39223
|
+
reply({
|
|
39224
|
+
ok: false,
|
|
39225
|
+
error: "changes.repoRelPath and changes.commitSha are required for commit view"
|
|
39226
|
+
});
|
|
38872
39227
|
return;
|
|
38873
39228
|
}
|
|
38874
39229
|
}
|
|
38875
|
-
const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
|
|
38876
39230
|
const opts = repoRel && view === "commit" && commitSha ? {
|
|
38877
39231
|
repoRelPath: repoRel,
|
|
38878
39232
|
basis: { kind: "commit", sha: commitSha },
|
|
@@ -38917,7 +39271,11 @@ async function handleSessionGitStatusAction(deps, sessionId, reply) {
|
|
|
38917
39271
|
function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
|
|
38918
39272
|
if (!ws) return;
|
|
38919
39273
|
const message = { type: "session_git_result", id, ...payload };
|
|
38920
|
-
|
|
39274
|
+
let wire = message;
|
|
39275
|
+
if (e2ee && encryptedFields.length > 0) {
|
|
39276
|
+
wire = e2ee.encryptFields(message, encryptedFields);
|
|
39277
|
+
}
|
|
39278
|
+
sendWsMessage(ws, wire);
|
|
38921
39279
|
}
|
|
38922
39280
|
|
|
38923
39281
|
// src/routing/handlers/git/session-git-request.ts
|
|
@@ -38925,7 +39283,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
38925
39283
|
if (typeof msg.id !== "string") return;
|
|
38926
39284
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
38927
39285
|
const action = msg.action;
|
|
38928
|
-
if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes")
|
|
39286
|
+
if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes" && action !== "file_diff")
|
|
38929
39287
|
return;
|
|
38930
39288
|
void (async () => {
|
|
38931
39289
|
const ws = deps.getWs();
|
|
@@ -38939,6 +39297,10 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
38939
39297
|
await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
|
|
38940
39298
|
return;
|
|
38941
39299
|
}
|
|
39300
|
+
if (action === "file_diff") {
|
|
39301
|
+
await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
|
|
39302
|
+
return;
|
|
39303
|
+
}
|
|
38942
39304
|
if (action === "push") {
|
|
38943
39305
|
await handleSessionGitPushAction(deps, sessionId, reply);
|
|
38944
39306
|
return;
|
|
@@ -38973,7 +39335,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
|
|
|
38973
39335
|
};
|
|
38974
39336
|
|
|
38975
39337
|
// src/routing/handlers/revert-turn-snapshot.ts
|
|
38976
|
-
import * as
|
|
39338
|
+
import * as fs44 from "node:fs";
|
|
38977
39339
|
var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
38978
39340
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38979
39341
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
@@ -38986,7 +39348,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
|
38986
39348
|
const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
|
|
38987
39349
|
const file2 = snapshotFilePath(agentBase, turnId);
|
|
38988
39350
|
try {
|
|
38989
|
-
await
|
|
39351
|
+
await fs44.promises.access(file2, fs44.constants.F_OK);
|
|
38990
39352
|
} catch {
|
|
38991
39353
|
sendWsMessage(s, {
|
|
38992
39354
|
type: "revert_turn_snapshot_result",
|
|
@@ -39024,11 +39386,13 @@ var handleDevServerControl = (msg, deps) => {
|
|
|
39024
39386
|
// src/routing/handlers/dev-servers-config.ts
|
|
39025
39387
|
var handleDevServersConfig = (msg, deps) => {
|
|
39026
39388
|
const devServers = msg.devServers;
|
|
39027
|
-
|
|
39389
|
+
setImmediate(() => {
|
|
39390
|
+
deps.devServerManager?.applyConfig(devServers ?? []);
|
|
39391
|
+
});
|
|
39028
39392
|
};
|
|
39029
39393
|
|
|
39030
39394
|
// src/git/bridge-git-context.ts
|
|
39031
|
-
import * as
|
|
39395
|
+
import * as path56 from "node:path";
|
|
39032
39396
|
|
|
39033
39397
|
// src/git/branches/get-current-branch.ts
|
|
39034
39398
|
async function getCurrentBranch(repoPath) {
|
|
@@ -39078,12 +39442,12 @@ async function listRepoBranchRefs(repoPath) {
|
|
|
39078
39442
|
// src/git/bridge-git-context.ts
|
|
39079
39443
|
function folderNameForRelPath(relPath, bridgeRoot) {
|
|
39080
39444
|
if (relPath === "." || relPath === "") {
|
|
39081
|
-
return
|
|
39445
|
+
return path56.basename(path56.resolve(bridgeRoot)) || "repo";
|
|
39082
39446
|
}
|
|
39083
|
-
return
|
|
39447
|
+
return path56.basename(relPath) || relPath;
|
|
39084
39448
|
}
|
|
39085
39449
|
async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
39086
|
-
const root =
|
|
39450
|
+
const root = path56.resolve(bridgeRoot);
|
|
39087
39451
|
if (await isGitRepoDirectory(root)) {
|
|
39088
39452
|
const remoteUrl = await getRemoteOriginUrl(root);
|
|
39089
39453
|
return [{ absolutePath: root, remoteUrl }];
|
|
@@ -39091,17 +39455,19 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
|
39091
39455
|
const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
|
|
39092
39456
|
const byPath = /* @__PURE__ */ new Map();
|
|
39093
39457
|
for (const repo of [...deep, ...shallow]) {
|
|
39094
|
-
byPath.set(
|
|
39458
|
+
byPath.set(path56.resolve(repo.absolutePath), repo);
|
|
39095
39459
|
}
|
|
39096
39460
|
return [...byPath.values()];
|
|
39097
39461
|
}
|
|
39098
39462
|
async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
39099
|
-
const bridgeResolved =
|
|
39463
|
+
const bridgeResolved = path56.resolve(bridgeRoot);
|
|
39100
39464
|
const repos = await discoverGitReposForBridgeContext(bridgeResolved);
|
|
39101
39465
|
const rows = [];
|
|
39102
|
-
for (
|
|
39103
|
-
|
|
39104
|
-
|
|
39466
|
+
for (let i = 0; i < repos.length; i++) {
|
|
39467
|
+
if (i > 0) await yieldToEventLoop();
|
|
39468
|
+
const repo = repos[i];
|
|
39469
|
+
let rel = path56.relative(bridgeResolved, repo.absolutePath);
|
|
39470
|
+
if (rel.startsWith("..") || path56.isAbsolute(rel)) continue;
|
|
39105
39471
|
const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
|
|
39106
39472
|
const currentBranch = await getCurrentBranch(repo.absolutePath);
|
|
39107
39473
|
const remoteUrl = repo.remoteUrl.trim() || null;
|
|
@@ -39116,11 +39482,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
|
39116
39482
|
return rows;
|
|
39117
39483
|
}
|
|
39118
39484
|
async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
|
|
39119
|
-
const bridgeResolved =
|
|
39485
|
+
const bridgeResolved = path56.resolve(bridgeRoot);
|
|
39120
39486
|
const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
|
|
39121
|
-
const repoPath = rel === "" ? bridgeResolved :
|
|
39122
|
-
const resolved =
|
|
39123
|
-
if (!resolved.startsWith(bridgeResolved +
|
|
39487
|
+
const repoPath = rel === "" ? bridgeResolved : path56.join(bridgeResolved, rel);
|
|
39488
|
+
const resolved = path56.resolve(repoPath);
|
|
39489
|
+
if (!resolved.startsWith(bridgeResolved + path56.sep) && resolved !== bridgeResolved) {
|
|
39124
39490
|
return [];
|
|
39125
39491
|
}
|
|
39126
39492
|
return listRepoBranchRefs(resolved);
|
|
@@ -39704,10 +40070,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
|
|
|
39704
40070
|
}
|
|
39705
40071
|
|
|
39706
40072
|
// src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
|
|
39707
|
-
import * as
|
|
40073
|
+
import * as path58 from "node:path";
|
|
39708
40074
|
|
|
39709
40075
|
// src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
|
|
39710
|
-
import * as
|
|
40076
|
+
import * as path57 from "node:path";
|
|
39711
40077
|
async function probeOneAgentTypeForCapabilities(params) {
|
|
39712
40078
|
const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
|
|
39713
40079
|
const canContinue = () => shouldContinue?.() !== false;
|
|
@@ -39745,7 +40111,7 @@ async function probeOneAgentTypeForCapabilities(params) {
|
|
|
39745
40111
|
if (!canContinue()) return false;
|
|
39746
40112
|
handle = await resolved.createClient({
|
|
39747
40113
|
command: resolved.command,
|
|
39748
|
-
cwd:
|
|
40114
|
+
cwd: path57.resolve(cwd),
|
|
39749
40115
|
backendAgentType: agentType,
|
|
39750
40116
|
sessionMode: "agent",
|
|
39751
40117
|
persistedAcpSessionId: null,
|
|
@@ -39823,7 +40189,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
|
|
|
39823
40189
|
const { workspaceId, log: log2, getWs } = params;
|
|
39824
40190
|
const isCurrent = beginAgentCapabilityWarmupRun();
|
|
39825
40191
|
if (!isCurrent()) return;
|
|
39826
|
-
const cwd =
|
|
40192
|
+
const cwd = path58.resolve(getBridgeRoot());
|
|
39827
40193
|
async function sendBatchFromCache() {
|
|
39828
40194
|
if (!isCurrent()) return;
|
|
39829
40195
|
const socket = getWs();
|
|
@@ -40169,18 +40535,18 @@ import * as http from "node:http";
|
|
|
40169
40535
|
|
|
40170
40536
|
// src/mcp/bridge-access/read-json-body.ts
|
|
40171
40537
|
function readJsonBody(req) {
|
|
40172
|
-
return new Promise((
|
|
40538
|
+
return new Promise((resolve32, reject) => {
|
|
40173
40539
|
const chunks = [];
|
|
40174
40540
|
req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
40175
40541
|
req.on("end", () => {
|
|
40176
40542
|
try {
|
|
40177
40543
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
40178
40544
|
if (!raw) {
|
|
40179
|
-
|
|
40545
|
+
resolve32({});
|
|
40180
40546
|
return;
|
|
40181
40547
|
}
|
|
40182
40548
|
const parsed = JSON.parse(raw);
|
|
40183
|
-
|
|
40549
|
+
resolve32(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
|
|
40184
40550
|
} catch (e) {
|
|
40185
40551
|
reject(e);
|
|
40186
40552
|
}
|
|
@@ -40238,7 +40604,7 @@ function createBridgeAccessRequestHandler(registry2) {
|
|
|
40238
40604
|
// src/mcp/bridge-access/start-server.ts
|
|
40239
40605
|
function startBridgeAccessServer(registry2) {
|
|
40240
40606
|
const server = http.createServer(createBridgeAccessRequestHandler(registry2));
|
|
40241
|
-
return new Promise((
|
|
40607
|
+
return new Promise((resolve32, reject) => {
|
|
40242
40608
|
server.once("error", reject);
|
|
40243
40609
|
server.listen(0, "127.0.0.1", () => {
|
|
40244
40610
|
const addr = server.address();
|
|
@@ -40246,7 +40612,7 @@ function startBridgeAccessServer(registry2) {
|
|
|
40246
40612
|
reject(new Error("Bridge access server did not bind"));
|
|
40247
40613
|
return;
|
|
40248
40614
|
}
|
|
40249
|
-
|
|
40615
|
+
resolve32({
|
|
40250
40616
|
port: addr.port,
|
|
40251
40617
|
close: () => new Promise((closeResolve, closeReject) => {
|
|
40252
40618
|
server.close((err) => err ? closeReject(err) : closeResolve());
|
|
@@ -40386,8 +40752,8 @@ async function createBridgeConnection(options) {
|
|
|
40386
40752
|
getCloudAccessToken: () => tokens.accessToken
|
|
40387
40753
|
};
|
|
40388
40754
|
const identifyReportedPaths = {
|
|
40389
|
-
bridgeRootPath:
|
|
40390
|
-
worktreesRootPath:
|
|
40755
|
+
bridgeRootPath: path59.resolve(getBridgeRoot()),
|
|
40756
|
+
worktreesRootPath: path59.resolve(worktreesRootPath)
|
|
40391
40757
|
};
|
|
40392
40758
|
const { connect } = createMainBridgeWebSocketLifecycle({
|
|
40393
40759
|
state,
|