@buildautomaton/cli 0.1.49 → 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 +1850 -1198
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1829 -1177
- 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,15 +8103,15 @@ 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);
|
|
8113
|
-
const
|
|
8114
|
-
return Promise.all(
|
|
8113
|
+
const promises11 = keys.map((key) => promisesObj[key]);
|
|
8114
|
+
return Promise.all(promises11).then((results) => {
|
|
8115
8115
|
const resolvedObj = {};
|
|
8116
8116
|
for (let i = 0; i < keys.length; i++) {
|
|
8117
8117
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -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
|
}
|
|
@@ -23392,7 +23392,7 @@ var GitRepoMetaSchema = external_exports.object({
|
|
|
23392
23392
|
updatedAt: external_exports.string()
|
|
23393
23393
|
});
|
|
23394
23394
|
|
|
23395
|
-
// ../types/src/bridge-mcp-
|
|
23395
|
+
// ../types/src/mcp/bridge-mcp-constants.ts
|
|
23396
23396
|
var GET_INTEGRATION_CONTENT_TOOL = "get_integration_content";
|
|
23397
23397
|
var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
|
|
23398
23398
|
var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
|
|
@@ -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, path46, 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,
|
|
@@ -27969,7 +27969,7 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
|
|
|
27969
27969
|
}
|
|
27970
27970
|
|
|
27971
27971
|
// src/mcp/bridge-mcp-constants.ts
|
|
27972
|
-
var
|
|
27972
|
+
var BRIDGE_MCP_SERVER_NAME2 = "buildautomaton-bridge";
|
|
27973
27973
|
|
|
27974
27974
|
// src/mcp/resolve-bridge-mcp-server-path.ts
|
|
27975
27975
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
@@ -27984,7 +27984,7 @@ function buildAcpMcpServers(bridgeAccessPort) {
|
|
|
27984
27984
|
if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
|
|
27985
27985
|
return [
|
|
27986
27986
|
{
|
|
27987
|
-
name:
|
|
27987
|
+
name: BRIDGE_MCP_SERVER_NAME2,
|
|
27988
27988
|
command: process.execPath,
|
|
27989
27989
|
args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
|
|
27990
27990
|
env: []
|
|
@@ -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, path46, 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, path46, 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,14 +32796,14 @@ 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
|
}
|
|
32804
32804
|
async function forEachWithGitYield(items, fn) {
|
|
32805
32805
|
for (let i = 0; i < items.length; i++) {
|
|
32806
|
-
|
|
32806
|
+
await yieldToEventLoop2();
|
|
32807
32807
|
await fn(items[i], i);
|
|
32808
32808
|
}
|
|
32809
32809
|
}
|
|
@@ -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`,
|
|
@@ -33570,7 +33580,7 @@ function buildIntegrationContentAgentPromptNote(sessionId) {
|
|
|
33570
33580
|
"Use the get_integration_content MCP tool on the buildautomaton-bridge server to fetch the full content of each link.",
|
|
33571
33581
|
`When calling the tool, pass sessionId "${sessionRef}" and the URL as url.`,
|
|
33572
33582
|
"By default the tool links the fetched URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false when fetching additional links for context only (after the primary ticket is linked, or when you do not want association).",
|
|
33573
|
-
"If the tool reports unsupported_url, do not retry with the same URL. If it reports temporarily_unavailable, you may retry later."
|
|
33583
|
+
"If the tool reports unsupported_url, do not retry with the same URL. If it reports integration_reconnect_required, ask the user to reconnect the integration in workspace settings. If it reports temporarily_unavailable, you may retry later."
|
|
33574
33584
|
].join("\n");
|
|
33575
33585
|
}
|
|
33576
33586
|
function injectIntegrationContentAgentPromptNote(agentPrompt, sessionId) {
|
|
@@ -33605,7 +33615,7 @@ async function sendPromptToAgent(options) {
|
|
|
33605
33615
|
runId,
|
|
33606
33616
|
agentType,
|
|
33607
33617
|
agentCwd,
|
|
33608
|
-
sendResult
|
|
33618
|
+
sendResult,
|
|
33609
33619
|
sendSessionUpdate,
|
|
33610
33620
|
log: log2,
|
|
33611
33621
|
followUpCatalogPromptId,
|
|
@@ -33635,7 +33645,7 @@ async function sendPromptToAgent(options) {
|
|
|
33635
33645
|
let sendOpts = {};
|
|
33636
33646
|
if (attachments && attachments.length > 0) {
|
|
33637
33647
|
if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
|
|
33638
|
-
|
|
33648
|
+
sendResult({
|
|
33639
33649
|
type: "prompt_result",
|
|
33640
33650
|
id: promptId,
|
|
33641
33651
|
...sessionId ? { sessionId } : {},
|
|
@@ -33656,7 +33666,7 @@ async function sendPromptToAgent(options) {
|
|
|
33656
33666
|
log: log2
|
|
33657
33667
|
});
|
|
33658
33668
|
if (!resolved.ok) {
|
|
33659
|
-
|
|
33669
|
+
sendResult({
|
|
33660
33670
|
type: "prompt_result",
|
|
33661
33671
|
id: promptId,
|
|
33662
33672
|
...sessionId ? { sessionId } : {},
|
|
@@ -33695,7 +33705,7 @@ async function sendPromptToAgent(options) {
|
|
|
33695
33705
|
const errStr = typeof result.error === "string" ? result.error : void 0;
|
|
33696
33706
|
const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
|
|
33697
33707
|
const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
|
|
33698
|
-
|
|
33708
|
+
sendResult({
|
|
33699
33709
|
type: "prompt_result",
|
|
33700
33710
|
id: promptId,
|
|
33701
33711
|
...sessionId ? { sessionId } : {},
|
|
@@ -33717,7 +33727,7 @@ async function sendPromptToAgent(options) {
|
|
|
33717
33727
|
} catch (err) {
|
|
33718
33728
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
33719
33729
|
log2(`[Agent] Send failed: ${errMsg}`);
|
|
33720
|
-
|
|
33730
|
+
sendResult({
|
|
33721
33731
|
type: "prompt_result",
|
|
33722
33732
|
id: promptId,
|
|
33723
33733
|
...sessionId ? { sessionId } : {},
|
|
@@ -33751,7 +33761,7 @@ function handlePrompt(ctx, opts) {
|
|
|
33751
33761
|
agentType,
|
|
33752
33762
|
agentConfig,
|
|
33753
33763
|
sessionParentPath,
|
|
33754
|
-
sendResult
|
|
33764
|
+
sendResult,
|
|
33755
33765
|
sendSessionUpdate,
|
|
33756
33766
|
followUpCatalogPromptId,
|
|
33757
33767
|
sessionChangeSummaryFilePaths,
|
|
@@ -33768,7 +33778,7 @@ function handlePrompt(ctx, opts) {
|
|
|
33768
33778
|
const activeRunId = runId;
|
|
33769
33779
|
const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
|
|
33770
33780
|
if (!acpAgentKey) {
|
|
33771
|
-
|
|
33781
|
+
sendResult({
|
|
33772
33782
|
type: "prompt_result",
|
|
33773
33783
|
id: promptId,
|
|
33774
33784
|
...sessionId ? { sessionId } : {},
|
|
@@ -33805,7 +33815,7 @@ function handlePrompt(ctx, opts) {
|
|
|
33805
33815
|
const evaluated = Boolean(preferredForPrompt && errMsg.trim());
|
|
33806
33816
|
const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
|
|
33807
33817
|
const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
|
|
33808
|
-
|
|
33818
|
+
sendResult({
|
|
33809
33819
|
type: "prompt_result",
|
|
33810
33820
|
id: promptId,
|
|
33811
33821
|
...sessionId ? { sessionId } : {},
|
|
@@ -33825,7 +33835,7 @@ function handlePrompt(ctx, opts) {
|
|
|
33825
33835
|
await handle.cancel?.();
|
|
33826
33836
|
} catch {
|
|
33827
33837
|
}
|
|
33828
|
-
|
|
33838
|
+
sendResult({
|
|
33829
33839
|
type: "prompt_result",
|
|
33830
33840
|
id: promptId,
|
|
33831
33841
|
...sessionId ? { sessionId } : {},
|
|
@@ -33846,7 +33856,7 @@ function handlePrompt(ctx, opts) {
|
|
|
33846
33856
|
runId: activeRunId,
|
|
33847
33857
|
agentType: preferredForPrompt,
|
|
33848
33858
|
agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
|
|
33849
|
-
sendResult
|
|
33859
|
+
sendResult,
|
|
33850
33860
|
sendSessionUpdate,
|
|
33851
33861
|
log: ctx.log,
|
|
33852
33862
|
followUpCatalogPromptId,
|
|
@@ -33902,6 +33912,496 @@ async function createAcpManager(options) {
|
|
|
33902
33912
|
};
|
|
33903
33913
|
}
|
|
33904
33914
|
|
|
33915
|
+
// src/worktrees/worktree-layout-file.ts
|
|
33916
|
+
import * as fs16 from "node:fs";
|
|
33917
|
+
import * as path19 from "node:path";
|
|
33918
|
+
import os7 from "node:os";
|
|
33919
|
+
var LAYOUT_FILENAME = "worktree-launcher-layout.json";
|
|
33920
|
+
function defaultWorktreeLayoutPath() {
|
|
33921
|
+
return path19.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
|
|
33922
|
+
}
|
|
33923
|
+
function normalizeLoadedLayout(raw) {
|
|
33924
|
+
if (raw && typeof raw === "object" && "launcherCwds" in raw) {
|
|
33925
|
+
const j = raw;
|
|
33926
|
+
if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
|
|
33927
|
+
}
|
|
33928
|
+
return { launcherCwds: [] };
|
|
33929
|
+
}
|
|
33930
|
+
function loadWorktreeLayout() {
|
|
33931
|
+
try {
|
|
33932
|
+
const p = defaultWorktreeLayoutPath();
|
|
33933
|
+
if (!fs16.existsSync(p)) return { launcherCwds: [] };
|
|
33934
|
+
const raw = JSON.parse(fs16.readFileSync(p, "utf8"));
|
|
33935
|
+
return normalizeLoadedLayout(raw);
|
|
33936
|
+
} catch {
|
|
33937
|
+
return { launcherCwds: [] };
|
|
33938
|
+
}
|
|
33939
|
+
}
|
|
33940
|
+
function saveWorktreeLayout(layout) {
|
|
33941
|
+
try {
|
|
33942
|
+
const dir = path19.dirname(defaultWorktreeLayoutPath());
|
|
33943
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
33944
|
+
fs16.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
|
|
33945
|
+
} catch {
|
|
33946
|
+
}
|
|
33947
|
+
}
|
|
33948
|
+
function baseNameSafe(pathString) {
|
|
33949
|
+
return path19.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
|
|
33950
|
+
}
|
|
33951
|
+
function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
|
|
33952
|
+
const norm = path19.resolve(bridgeRootPath2);
|
|
33953
|
+
const existing = layout.launcherCwds.find((e) => path19.resolve(e.absolutePath) === norm);
|
|
33954
|
+
return existing?.dirName;
|
|
33955
|
+
}
|
|
33956
|
+
function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
|
|
33957
|
+
const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
|
|
33958
|
+
if (existing) return existing;
|
|
33959
|
+
const norm = path19.resolve(bridgeRootPath2);
|
|
33960
|
+
const base = baseNameSafe(norm);
|
|
33961
|
+
const used = new Set(layout.launcherCwds.map((e) => e.dirName));
|
|
33962
|
+
let name = base;
|
|
33963
|
+
let n = 2;
|
|
33964
|
+
while (used.has(name)) {
|
|
33965
|
+
name = `${base}-${n}`;
|
|
33966
|
+
n += 1;
|
|
33967
|
+
}
|
|
33968
|
+
layout.launcherCwds.push({ absolutePath: norm, dirName: name });
|
|
33969
|
+
saveWorktreeLayout(layout);
|
|
33970
|
+
return name;
|
|
33971
|
+
}
|
|
33972
|
+
|
|
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
|
+
};
|
|
34016
|
+
|
|
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
|
+
};
|
|
34025
|
+
}
|
|
34026
|
+
|
|
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]);
|
|
34047
|
+
}
|
|
34048
|
+
}
|
|
34049
|
+
|
|
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 });
|
|
34056
|
+
}
|
|
34057
|
+
return { ok: true };
|
|
34058
|
+
} catch (e) {
|
|
34059
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
34060
|
+
return { ok: false, error: err };
|
|
34061
|
+
}
|
|
34062
|
+
}
|
|
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
|
+
});
|
|
34074
|
+
}
|
|
34075
|
+
|
|
34076
|
+
// src/git/changes/parse/normalize-git-diff-path.ts
|
|
34077
|
+
function normalizeGitDiffPath(path60) {
|
|
34078
|
+
return path60.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
34079
|
+
}
|
|
34080
|
+
|
|
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) {
|
|
34087
|
+
return null;
|
|
34088
|
+
}
|
|
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();
|
|
34093
|
+
return {
|
|
34094
|
+
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
34095
|
+
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
34096
|
+
};
|
|
34097
|
+
}
|
|
34098
|
+
function isGitRenameNumstatPath(rawPath) {
|
|
34099
|
+
return expandGitRenameNumstatPath(rawPath) != null;
|
|
34100
|
+
}
|
|
34101
|
+
|
|
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
|
+
});
|
|
34122
|
+
}
|
|
34123
|
+
} else if (code === "M" || code === "U" || code === "T") {
|
|
34124
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
34125
|
+
}
|
|
34126
|
+
}
|
|
34127
|
+
return entries;
|
|
34128
|
+
}
|
|
34129
|
+
function parseNameStatusLines(lines) {
|
|
34130
|
+
const m = /* @__PURE__ */ new Map();
|
|
34131
|
+
for (const entry of parseNameStatusEntries(lines)) {
|
|
34132
|
+
m.set(entry.pathInRepo, entry.change);
|
|
34133
|
+
}
|
|
34134
|
+
return m;
|
|
34135
|
+
}
|
|
34136
|
+
|
|
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);
|
|
34172
|
+
}
|
|
34173
|
+
}
|
|
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 });
|
|
34180
|
+
}
|
|
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";
|
|
34187
|
+
try {
|
|
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 };
|
|
34193
|
+
} catch {
|
|
34194
|
+
return null;
|
|
34195
|
+
}
|
|
34196
|
+
}
|
|
34197
|
+
|
|
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;
|
|
34207
|
+
}
|
|
34208
|
+
|
|
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
|
+
});
|
|
34224
|
+
}
|
|
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;
|
|
34233
|
+
}
|
|
34234
|
+
}
|
|
34235
|
+
|
|
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
|
+
}
|
|
34245
|
+
}
|
|
34246
|
+
|
|
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;
|
|
34269
|
+
}
|
|
34270
|
+
|
|
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;
|
|
34277
|
+
}
|
|
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}`);
|
|
34283
|
+
}
|
|
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 {
|
|
34291
|
+
return null;
|
|
34292
|
+
}
|
|
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];
|
|
34303
|
+
}
|
|
34304
|
+
return null;
|
|
34305
|
+
}
|
|
34306
|
+
|
|
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 };
|
|
34353
|
+
});
|
|
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;
|
|
34364
|
+
});
|
|
34365
|
+
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
34366
|
+
}
|
|
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
|
+
});
|
|
34374
|
+
}
|
|
34375
|
+
|
|
34376
|
+
// src/worktrees/manager/resolve-commit-targets.ts
|
|
34377
|
+
function bridgeRootBinding(bridgeRoot) {
|
|
34378
|
+
return {
|
|
34379
|
+
sessionParentPath: bridgeRoot,
|
|
34380
|
+
workingTreeRelRoot: bridgeRoot,
|
|
34381
|
+
repoCheckoutPaths: [bridgeRoot]
|
|
34382
|
+
};
|
|
34383
|
+
}
|
|
34384
|
+
async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
|
|
34385
|
+
const sid = sessionId.trim();
|
|
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;
|
|
34392
|
+
}
|
|
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
|
+
);
|
|
34403
|
+
}
|
|
34404
|
+
|
|
33905
34405
|
// src/git/changes/types.ts
|
|
33906
34406
|
var MAX_PATCH_CHARS = 35e4;
|
|
33907
34407
|
|
|
@@ -33959,106 +34459,14 @@ function formatRemoteDisplayLabel(remoteUrl) {
|
|
|
33959
34459
|
return `origin \xB7 ${hostPath}`;
|
|
33960
34460
|
}
|
|
33961
34461
|
|
|
33962
|
-
// src/git/changes/get-working-tree-change-repo-details.ts
|
|
33963
|
-
import * as
|
|
34462
|
+
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
34463
|
+
import * as path28 from "node:path";
|
|
33964
34464
|
|
|
33965
|
-
// src/git/
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
33970
|
-
return v || null;
|
|
33971
|
-
} catch {
|
|
33972
|
-
return null;
|
|
33973
|
-
}
|
|
33974
|
-
}
|
|
33975
|
-
async function revParseSafe(g, ref) {
|
|
33976
|
-
try {
|
|
33977
|
-
const v = (await g.raw(["rev-parse", ref])).trim();
|
|
33978
|
-
return v || null;
|
|
33979
|
-
} catch {
|
|
33980
|
-
return null;
|
|
33981
|
-
}
|
|
33982
|
-
}
|
|
33983
|
-
async function resolveRemoteTrackingRefForAhead(g) {
|
|
33984
|
-
try {
|
|
33985
|
-
await g.raw(["rev-parse", "--verify", "HEAD@{upstream}"]);
|
|
33986
|
-
return "HEAD@{upstream}";
|
|
33987
|
-
} catch {
|
|
33988
|
-
try {
|
|
33989
|
-
await g.raw(["rev-parse", "--verify", "@{u}"]);
|
|
33990
|
-
return "@{u}";
|
|
33991
|
-
} catch {
|
|
33992
|
-
}
|
|
33993
|
-
}
|
|
33994
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
33995
|
-
if (!branch || branch === "HEAD") return null;
|
|
33996
|
-
const remote = await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
33997
|
-
const merge2 = await tryConfigGet(g, `branch.${branch}.merge`);
|
|
33998
|
-
if (merge2) {
|
|
33999
|
-
const upstreamBranch = merge2.replace(/^refs\/heads\//, "");
|
|
34000
|
-
const ref = `refs/remotes/${remote}/${upstreamBranch}`;
|
|
34001
|
-
try {
|
|
34002
|
-
await g.raw(["rev-parse", "--verify", ref]);
|
|
34003
|
-
return ref;
|
|
34004
|
-
} catch {
|
|
34005
|
-
}
|
|
34006
|
-
}
|
|
34007
|
-
const fallbackRef = `refs/remotes/${remote}/${branch}`;
|
|
34008
|
-
try {
|
|
34009
|
-
await g.raw(["rev-parse", "--verify", fallbackRef]);
|
|
34010
|
-
return fallbackRef;
|
|
34011
|
-
} catch {
|
|
34012
|
-
return null;
|
|
34013
|
-
}
|
|
34014
|
-
}
|
|
34015
|
-
async function remoteForCurrentBranch(g) {
|
|
34016
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
34017
|
-
if (!branch || branch === "HEAD") return "origin";
|
|
34018
|
-
return await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
34019
|
-
}
|
|
34020
|
-
async function resolveDefaultRemoteBranchRef(g, remote) {
|
|
34021
|
-
const headSym = `refs/remotes/${remote}/HEAD`;
|
|
34022
|
-
try {
|
|
34023
|
-
const resolved = (await g.raw(["symbolic-ref", "-q", "--verify", headSym])).trim();
|
|
34024
|
-
if (resolved.startsWith("refs/remotes/")) return resolved;
|
|
34025
|
-
} catch {
|
|
34026
|
-
}
|
|
34027
|
-
for (const name of ["main", "master", "trunk", "develop"]) {
|
|
34028
|
-
const r = `refs/remotes/${remote}/${name}`;
|
|
34029
|
-
try {
|
|
34030
|
-
await g.raw(["rev-parse", "--verify", r]);
|
|
34031
|
-
return r;
|
|
34032
|
-
} catch {
|
|
34033
|
-
}
|
|
34034
|
-
}
|
|
34035
|
-
return null;
|
|
34036
|
-
}
|
|
34037
|
-
async function resolveBaseShaForUnpushedCommits(g) {
|
|
34038
|
-
const trackingRef = await resolveRemoteTrackingRefForAhead(g);
|
|
34039
|
-
if (trackingRef) {
|
|
34040
|
-
const sha = await revParseSafe(g, trackingRef);
|
|
34041
|
-
if (sha) return sha;
|
|
34042
|
-
}
|
|
34043
|
-
const remote = await remoteForCurrentBranch(g);
|
|
34044
|
-
const defaultRef = await resolveDefaultRemoteBranchRef(g, remote);
|
|
34045
|
-
if (!defaultRef) return null;
|
|
34046
|
-
return revParseSafe(g, defaultRef);
|
|
34047
|
-
}
|
|
34048
|
-
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
34049
|
-
const g = cliSimpleGit(repoDir);
|
|
34050
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
34051
|
-
if (!headSha) return 0;
|
|
34052
|
-
const baseSha = await resolveBaseShaForUnpushedCommits(g);
|
|
34053
|
-
if (!baseSha || baseSha === headSha) return 0;
|
|
34054
|
-
try {
|
|
34055
|
-
const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
34056
|
-
const n = parseInt(String(out).trim(), 10);
|
|
34057
|
-
return Number.isNaN(n) ? 0 : n;
|
|
34058
|
-
} catch {
|
|
34059
|
-
return 0;
|
|
34060
|
-
}
|
|
34061
|
-
}
|
|
34465
|
+
// src/git/changes/repo/build-working-tree-change-repo-detail.ts
|
|
34466
|
+
import * as path27 from "node:path";
|
|
34467
|
+
|
|
34468
|
+
// src/git/commits/list-unpushed-commits.ts
|
|
34469
|
+
import * as path22 from "node:path";
|
|
34062
34470
|
|
|
34063
34471
|
// src/git/commits/lib/parse-log-lines.ts
|
|
34064
34472
|
function parseLogShaDateSubjectLines(raw) {
|
|
@@ -34078,9 +34486,14 @@ function parseLogShaDateSubjectLines(raw) {
|
|
|
34078
34486
|
}
|
|
34079
34487
|
|
|
34080
34488
|
// src/git/commits/list-unpushed-commits.ts
|
|
34489
|
+
var UNPUSHED_CACHE_TTL_MS = 3e4;
|
|
34490
|
+
var unpushedCache = /* @__PURE__ */ new Map();
|
|
34081
34491
|
async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
34082
34492
|
if (baseSha === headSha) return [];
|
|
34083
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 [];
|
|
34084
34497
|
const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
|
|
34085
34498
|
return parseLogShaDateSubjectLines(logOut);
|
|
34086
34499
|
} catch {
|
|
@@ -34088,12 +34501,22 @@ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
|
34088
34501
|
}
|
|
34089
34502
|
}
|
|
34090
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;
|
|
34091
34508
|
const g = cliSimpleGit(repoDir);
|
|
34092
|
-
const headSha = await
|
|
34093
|
-
|
|
34094
|
-
|
|
34095
|
-
|
|
34096
|
-
|
|
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;
|
|
34097
34520
|
}
|
|
34098
34521
|
|
|
34099
34522
|
// src/git/commits/lib/sanitize-recent-commits-limit.ts
|
|
@@ -34107,72 +34530,40 @@ function sanitizeRecentCommitsLimit(value) {
|
|
|
34107
34530
|
}
|
|
34108
34531
|
|
|
34109
34532
|
// src/git/commits/list-recent-commits.ts
|
|
34110
|
-
async function
|
|
34533
|
+
async function listRecentCommitRefs(repoDir, limitInput) {
|
|
34111
34534
|
const limit = sanitizeRecentCommitsLimit(limitInput);
|
|
34112
34535
|
const g = cliSimpleGit(repoDir);
|
|
34113
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
34114
|
-
if (!headSha) return { commits: [], hasMore: false };
|
|
34115
|
-
const unpushedSet = new Set((await listUnpushedCommits(repoDir)).map((c) => c.sha));
|
|
34116
34536
|
try {
|
|
34117
|
-
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit), "HEAD"]);
|
|
34118
|
-
const
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
}));
|
|
34122
|
-
let hasMore = false;
|
|
34123
|
-
if (commits.length === limit) {
|
|
34124
|
-
const nextOut = await g.raw(["log", "-n", "1", `--skip=${limit}`, "--format=%H", "HEAD"]);
|
|
34125
|
-
hasMore = /^[0-9a-f]{7,40}$/i.test(String(nextOut).trim());
|
|
34126
|
-
}
|
|
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);
|
|
34127
34541
|
return { commits, hasMore };
|
|
34128
34542
|
} catch {
|
|
34129
34543
|
return { commits: [], hasMore: false };
|
|
34130
34544
|
}
|
|
34131
34545
|
}
|
|
34132
34546
|
|
|
34133
|
-
// src/git/changes/
|
|
34134
|
-
|
|
34135
|
-
return path46.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
34136
|
-
}
|
|
34137
|
-
|
|
34138
|
-
// src/git/changes/parse/expand-git-rename-numstat-path.ts
|
|
34139
|
-
function expandGitRenameNumstatPath(rawPath) {
|
|
34140
|
-
const open2 = rawPath.indexOf("{");
|
|
34141
|
-
const arrow = rawPath.indexOf("=>");
|
|
34142
|
-
const close = rawPath.lastIndexOf("}");
|
|
34143
|
-
if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
|
|
34144
|
-
return null;
|
|
34145
|
-
}
|
|
34146
|
-
const prefix = rawPath.slice(0, open2);
|
|
34147
|
-
const suffix = rawPath.slice(close + 1);
|
|
34148
|
-
const oldPart = rawPath.slice(open2 + 1, arrow).trim();
|
|
34149
|
-
const newPart = rawPath.slice(arrow + 2, close).trim();
|
|
34150
|
-
return {
|
|
34151
|
-
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
34152
|
-
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
34153
|
-
};
|
|
34154
|
-
}
|
|
34155
|
-
function isGitRenameNumstatPath(rawPath) {
|
|
34156
|
-
return expandGitRenameNumstatPath(rawPath) != null;
|
|
34157
|
-
}
|
|
34547
|
+
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
34548
|
+
import * as path24 from "node:path";
|
|
34158
34549
|
|
|
34159
34550
|
// src/git/changes/rows/build-changed-file-row.ts
|
|
34160
34551
|
function buildChangedFileRow(options) {
|
|
34161
34552
|
const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
|
|
34162
|
-
const
|
|
34553
|
+
const workspaceRelPath = posixJoinDirFile(options.repoRelPath, pathInRepo);
|
|
34163
34554
|
const additions = options.numEntry?.additions ?? 0;
|
|
34164
34555
|
const deletions = options.numEntry?.deletions ?? 0;
|
|
34165
34556
|
let change = options.nameEntry?.change ?? "modified";
|
|
34166
|
-
let
|
|
34557
|
+
let movedFromWorkspaceRelPath;
|
|
34167
34558
|
let movedFromPathInRepo;
|
|
34168
34559
|
const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
|
|
34169
34560
|
if (change === "moved" && oldPathInRepo) {
|
|
34170
34561
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
34171
|
-
|
|
34562
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
34172
34563
|
} else if (oldPathInRepo && change === "modified") {
|
|
34173
34564
|
change = "moved";
|
|
34174
34565
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
34175
|
-
|
|
34566
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
34176
34567
|
}
|
|
34177
34568
|
if (options.untracked && !options.nameEntry) {
|
|
34178
34569
|
change = "added";
|
|
@@ -34182,11 +34573,11 @@ function buildChangedFileRow(options) {
|
|
|
34182
34573
|
else change = "modified";
|
|
34183
34574
|
}
|
|
34184
34575
|
return {
|
|
34185
|
-
|
|
34576
|
+
workspaceRelPath,
|
|
34186
34577
|
additions,
|
|
34187
34578
|
deletions,
|
|
34188
34579
|
change,
|
|
34189
|
-
...
|
|
34580
|
+
...movedFromWorkspaceRelPath ? { movedFromWorkspaceRelPath } : {},
|
|
34190
34581
|
...movedFromPathInRepo ? { movedFromPathInRepo } : {}
|
|
34191
34582
|
};
|
|
34192
34583
|
}
|
|
@@ -34245,8 +34636,8 @@ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, mov
|
|
|
34245
34636
|
}
|
|
34246
34637
|
|
|
34247
34638
|
// src/git/changes/total-lines/resolve-changed-file-total-lines.ts
|
|
34248
|
-
import * as
|
|
34249
|
-
import * as
|
|
34639
|
+
import * as fs17 from "node:fs";
|
|
34640
|
+
import * as path23 from "node:path";
|
|
34250
34641
|
|
|
34251
34642
|
// src/git/changes/lines/count-lines.ts
|
|
34252
34643
|
import { createReadStream } from "node:fs";
|
|
@@ -34292,13 +34683,13 @@ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
|
|
|
34292
34683
|
}
|
|
34293
34684
|
async function resolveWorkingTreeFileTotalLines(options) {
|
|
34294
34685
|
if (options.isBinary) return null;
|
|
34295
|
-
const filePath =
|
|
34686
|
+
const filePath = path23.join(options.repoGitCwd, options.pathInRepo);
|
|
34296
34687
|
if (options.change === "removed") {
|
|
34297
34688
|
return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
|
|
34298
34689
|
}
|
|
34299
34690
|
if (options.change === "moved" || options.change === "modified") {
|
|
34300
34691
|
try {
|
|
34301
|
-
const st = await
|
|
34692
|
+
const st = await fs17.promises.stat(filePath);
|
|
34302
34693
|
if (!st.isFile()) return null;
|
|
34303
34694
|
return await countTextFileLines(filePath);
|
|
34304
34695
|
} catch {
|
|
@@ -34318,14 +34709,14 @@ async function resolveCommitFileTotalLines(options) {
|
|
|
34318
34709
|
return null;
|
|
34319
34710
|
}
|
|
34320
34711
|
|
|
34321
|
-
// src/git/changes/listing/path-in-repo-from-
|
|
34322
|
-
function
|
|
34712
|
+
// src/git/changes/listing/path-in-repo-from-workspace.ts
|
|
34713
|
+
function pathInRepoFromWorkspace(workspaceRelPath, repoRelPath) {
|
|
34323
34714
|
const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
34324
|
-
if (normRel === ".") return
|
|
34325
|
-
if (
|
|
34326
|
-
return
|
|
34715
|
+
if (normRel === ".") return workspaceRelPath;
|
|
34716
|
+
if (workspaceRelPath.startsWith(`${normRel}/`)) {
|
|
34717
|
+
return workspaceRelPath.slice(normRel.length + 1);
|
|
34327
34718
|
}
|
|
34328
|
-
return
|
|
34719
|
+
return workspaceRelPath;
|
|
34329
34720
|
}
|
|
34330
34721
|
function normalizeRepoRelPath(repoRelPath) {
|
|
34331
34722
|
return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
@@ -34334,7 +34725,7 @@ function normalizeRepoRelPath(repoRelPath) {
|
|
|
34334
34725
|
// src/git/changes/listing/enrich-commit-file-rows.ts
|
|
34335
34726
|
async function enrichCommitFileRows(options) {
|
|
34336
34727
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
34337
|
-
const pathInRepo =
|
|
34728
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
34338
34729
|
row.patchContent = await unifiedDiffForFileInRange(
|
|
34339
34730
|
options.repoGitCwd,
|
|
34340
34731
|
options.range,
|
|
@@ -34353,102 +34744,6 @@ async function enrichCommitFileRows(options) {
|
|
|
34353
34744
|
});
|
|
34354
34745
|
}
|
|
34355
34746
|
|
|
34356
|
-
// src/git/changes/parse/parse-name-status-entries.ts
|
|
34357
|
-
function parseNameStatusEntries(lines) {
|
|
34358
|
-
const entries = [];
|
|
34359
|
-
for (const line of lines) {
|
|
34360
|
-
if (!line.trim()) continue;
|
|
34361
|
-
const tabParts = line.split(" ");
|
|
34362
|
-
if (tabParts.length < 2) continue;
|
|
34363
|
-
const status = tabParts[0].trim();
|
|
34364
|
-
const code = status[0];
|
|
34365
|
-
if (code === "A") {
|
|
34366
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
|
|
34367
|
-
} else if (code === "D") {
|
|
34368
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
|
|
34369
|
-
} else if (code === "R" || code === "C") {
|
|
34370
|
-
if (tabParts.length >= 3) {
|
|
34371
|
-
entries.push({
|
|
34372
|
-
pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
|
|
34373
|
-
oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
|
|
34374
|
-
change: "moved"
|
|
34375
|
-
});
|
|
34376
|
-
}
|
|
34377
|
-
} else if (code === "M" || code === "U" || code === "T") {
|
|
34378
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
34379
|
-
}
|
|
34380
|
-
}
|
|
34381
|
-
return entries;
|
|
34382
|
-
}
|
|
34383
|
-
function parseNameStatusLines(lines) {
|
|
34384
|
-
const m = /* @__PURE__ */ new Map();
|
|
34385
|
-
for (const entry of parseNameStatusEntries(lines)) {
|
|
34386
|
-
m.set(entry.pathInRepo, entry.change);
|
|
34387
|
-
}
|
|
34388
|
-
return m;
|
|
34389
|
-
}
|
|
34390
|
-
|
|
34391
|
-
// src/git/changes/parse/parse-numstat-entries.ts
|
|
34392
|
-
function parseNumstatCounts(parts) {
|
|
34393
|
-
const [a, d] = parts;
|
|
34394
|
-
return {
|
|
34395
|
-
additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
|
|
34396
|
-
deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
|
|
34397
|
-
};
|
|
34398
|
-
}
|
|
34399
|
-
function parseNumstatLine(line) {
|
|
34400
|
-
const parts = line.split(" ");
|
|
34401
|
-
if (parts.length < 3) return null;
|
|
34402
|
-
const rawPath = parts[parts.length - 1];
|
|
34403
|
-
const { additions, deletions } = parseNumstatCounts(parts);
|
|
34404
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34405
|
-
const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
|
|
34406
|
-
return {
|
|
34407
|
-
pathInRepo,
|
|
34408
|
-
additions,
|
|
34409
|
-
deletions,
|
|
34410
|
-
...expanded ? { oldPathInRepo: expanded.oldPath } : {}
|
|
34411
|
-
};
|
|
34412
|
-
}
|
|
34413
|
-
function parseNumstatEntries(lines) {
|
|
34414
|
-
const m = /* @__PURE__ */ new Map();
|
|
34415
|
-
for (const line of lines) {
|
|
34416
|
-
if (!line.trim()) continue;
|
|
34417
|
-
const parts = line.split(" ");
|
|
34418
|
-
if (parts.length < 3) continue;
|
|
34419
|
-
const rawPath = parts[parts.length - 1];
|
|
34420
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
34421
|
-
const entry = parseNumstatLine(line);
|
|
34422
|
-
if (!entry) continue;
|
|
34423
|
-
const existing = m.get(entry.pathInRepo);
|
|
34424
|
-
if (!existing || expanded) {
|
|
34425
|
-
m.set(entry.pathInRepo, entry);
|
|
34426
|
-
}
|
|
34427
|
-
}
|
|
34428
|
-
return m;
|
|
34429
|
-
}
|
|
34430
|
-
function parseNumstat(lines) {
|
|
34431
|
-
const m = /* @__PURE__ */ new Map();
|
|
34432
|
-
for (const [path46, entry] of parseNumstatEntries(lines)) {
|
|
34433
|
-
m.set(path46, { additions: entry.additions, deletions: entry.deletions });
|
|
34434
|
-
}
|
|
34435
|
-
return m;
|
|
34436
|
-
}
|
|
34437
|
-
|
|
34438
|
-
// src/git/changes/parse/numstat-from-git-no-index.ts
|
|
34439
|
-
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
34440
|
-
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
34441
|
-
try {
|
|
34442
|
-
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
34443
|
-
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
34444
|
-
const parsed = parseNumstatLine(first2);
|
|
34445
|
-
if (!parsed) return null;
|
|
34446
|
-
return { additions: parsed.additions, deletions: parsed.deletions };
|
|
34447
|
-
} catch {
|
|
34448
|
-
return null;
|
|
34449
|
-
}
|
|
34450
|
-
}
|
|
34451
|
-
|
|
34452
34747
|
// src/git/changes/rows/collect-moved-source-paths.ts
|
|
34453
34748
|
function collectMovedSourcePaths(nameEntries, numByPath) {
|
|
34454
34749
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -34528,8 +34823,8 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
34528
34823
|
const aHasPatch = Boolean(a.patchContent?.trim());
|
|
34529
34824
|
const bHasPatch = Boolean(b.patchContent?.trim());
|
|
34530
34825
|
if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
|
|
34531
|
-
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.
|
|
34532
|
-
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.
|
|
34826
|
+
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromWorkspaceRelPath);
|
|
34827
|
+
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromWorkspaceRelPath);
|
|
34533
34828
|
if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
|
|
34534
34829
|
return a;
|
|
34535
34830
|
}
|
|
@@ -34538,30 +34833,39 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
34538
34833
|
function dedupeChangedFileRows(rows) {
|
|
34539
34834
|
const byPath = /* @__PURE__ */ new Map();
|
|
34540
34835
|
for (const row of rows) {
|
|
34541
|
-
const key = normalizeGitDiffPath(row.
|
|
34542
|
-
const normalizedRow = key === row.
|
|
34836
|
+
const key = normalizeGitDiffPath(row.workspaceRelPath);
|
|
34837
|
+
const normalizedRow = key === row.workspaceRelPath ? row : { ...row, workspaceRelPath: key };
|
|
34543
34838
|
const existing = byPath.get(key);
|
|
34544
34839
|
if (!existing) {
|
|
34545
34840
|
byPath.set(key, normalizedRow);
|
|
34546
34841
|
continue;
|
|
34547
34842
|
}
|
|
34548
34843
|
const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
|
|
34549
|
-
byPath.set(key, { ...preferRow,
|
|
34844
|
+
byPath.set(key, { ...preferRow, workspaceRelPath: key });
|
|
34550
34845
|
}
|
|
34551
34846
|
return [...byPath.values()];
|
|
34552
34847
|
}
|
|
34553
34848
|
|
|
34554
34849
|
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
34555
|
-
|
|
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
|
+
}
|
|
34556
34862
|
const g = cliSimpleGit(repoGitCwd);
|
|
34557
|
-
const
|
|
34558
|
-
const range = `${parent}..${commitSha}`;
|
|
34863
|
+
const normRel = normalizeRepoRelPath(repoRelPath);
|
|
34559
34864
|
const [nameStatusRaw, numstatRaw] = await Promise.all([
|
|
34560
|
-
g.raw(["diff",
|
|
34561
|
-
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(() => "")
|
|
34562
34867
|
]);
|
|
34563
34868
|
const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
|
|
34564
|
-
const normRel = normalizeRepoRelPath(repoRelPath);
|
|
34565
34869
|
const paths = collectChangedPaths({
|
|
34566
34870
|
nameEntries: parsed.nameEntries,
|
|
34567
34871
|
numByPath: parsed.numByPath
|
|
@@ -34574,29 +34878,39 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
34574
34878
|
movedSourcePaths: parsed.movedSourcePaths
|
|
34575
34879
|
});
|
|
34576
34880
|
rows = dedupeChangedFileRows(rows);
|
|
34577
|
-
|
|
34578
|
-
|
|
34579
|
-
|
|
34580
|
-
|
|
34581
|
-
|
|
34582
|
-
|
|
34583
|
-
|
|
34584
|
-
|
|
34585
|
-
|
|
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
|
+
}
|
|
34586
34894
|
const finalRows = dedupeChangedFileRows(rows);
|
|
34587
|
-
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
|
+
}
|
|
34588
34902
|
return finalRows;
|
|
34589
34903
|
}
|
|
34590
34904
|
|
|
34591
34905
|
// src/git/changes/listing/apply-untracked-file-stats.ts
|
|
34592
|
-
import * as
|
|
34593
|
-
import * as
|
|
34906
|
+
import * as fs18 from "node:fs";
|
|
34907
|
+
import * as path25 from "node:path";
|
|
34594
34908
|
function createUntrackedStatsApplier(options) {
|
|
34595
34909
|
return async (row, pathInRepo) => {
|
|
34596
34910
|
if (!options.untrackedSet.has(pathInRepo)) return;
|
|
34597
34911
|
if (options.nameByPath.has(pathInRepo)) return;
|
|
34598
34912
|
if (row.change === "moved") return;
|
|
34599
|
-
const repoFilePath =
|
|
34913
|
+
const repoFilePath = path25.join(options.repoGitCwd, pathInRepo);
|
|
34600
34914
|
const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
|
|
34601
34915
|
if (fromGit) {
|
|
34602
34916
|
row.additions = fromGit.additions;
|
|
@@ -34604,7 +34918,7 @@ function createUntrackedStatsApplier(options) {
|
|
|
34604
34918
|
return;
|
|
34605
34919
|
}
|
|
34606
34920
|
try {
|
|
34607
|
-
const st = await
|
|
34921
|
+
const st = await fs18.promises.stat(repoFilePath);
|
|
34608
34922
|
row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
|
|
34609
34923
|
} catch {
|
|
34610
34924
|
row.additions = 0;
|
|
@@ -34614,13 +34928,75 @@ function createUntrackedStatsApplier(options) {
|
|
|
34614
34928
|
}
|
|
34615
34929
|
|
|
34616
34930
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
34617
|
-
import * as
|
|
34931
|
+
import * as path26 from "node:path";
|
|
34618
34932
|
|
|
34619
|
-
// src/git/changes/patch/hydrate-
|
|
34620
|
-
|
|
34933
|
+
// src/git/changes/patch/hydrate/append-injected-context-lines.ts
|
|
34934
|
+
async function appendInjectedContextLines(out, inject, maybeYield, injectedTotal) {
|
|
34935
|
+
for (const t of inject) {
|
|
34936
|
+
out.push(` ${t}`);
|
|
34937
|
+
injectedTotal.value++;
|
|
34938
|
+
await maybeYield();
|
|
34939
|
+
}
|
|
34940
|
+
}
|
|
34941
|
+
|
|
34942
|
+
// src/git/changes/patch/hydrate/constants.ts
|
|
34621
34943
|
var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
34622
34944
|
var MAX_HYDRATE_LINES_PER_GAP = 8e3;
|
|
34623
34945
|
var MAX_HYDRATE_LINES_PER_FILE = 8e4;
|
|
34946
|
+
var HYDRATE_YIELD_EVERY_LINES = 512;
|
|
34947
|
+
|
|
34948
|
+
// src/git/changes/patch/hydrate/collect-gap-inject-lines.ts
|
|
34949
|
+
async function collectGapInjectLines(options) {
|
|
34950
|
+
if (options.injectedTotal >= MAX_HYDRATE_LINES_PER_FILE) return null;
|
|
34951
|
+
const remaining = MAX_HYDRATE_LINES_PER_FILE - options.injectedTotal;
|
|
34952
|
+
if (options.gapNewEnd >= options.gapNewStart && options.change !== "removed") {
|
|
34953
|
+
const nNew = options.gapNewEnd - options.gapNewStart + 1;
|
|
34954
|
+
if (options.gapOldEnd < options.gapOldStart || options.gapOldEnd - options.gapOldStart + 1 === nNew) {
|
|
34955
|
+
const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, remaining);
|
|
34956
|
+
const dl = await options.diskLines();
|
|
34957
|
+
if (dl && cap > 0) {
|
|
34958
|
+
return dl.slice(options.gapNewStart - 1, options.gapNewStart - 1 + cap);
|
|
34959
|
+
}
|
|
34960
|
+
}
|
|
34961
|
+
return null;
|
|
34962
|
+
}
|
|
34963
|
+
if (options.gapOldEnd >= options.gapOldStart && options.change === "removed") {
|
|
34964
|
+
const nOld = options.gapOldEnd - options.gapOldStart + 1;
|
|
34965
|
+
const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, remaining);
|
|
34966
|
+
const bl = await options.blobLines();
|
|
34967
|
+
if (bl && cap > 0) {
|
|
34968
|
+
return bl.slice(options.gapOldStart - 1, options.gapOldStart - 1 + cap);
|
|
34969
|
+
}
|
|
34970
|
+
}
|
|
34971
|
+
return null;
|
|
34972
|
+
}
|
|
34973
|
+
|
|
34974
|
+
// src/git/changes/patch/hydrate/consume-unified-hunk-body.ts
|
|
34975
|
+
async function consumeUnifiedHunkBody(options) {
|
|
34976
|
+
let i = options.startIndex;
|
|
34977
|
+
let oldConsumed = 0;
|
|
34978
|
+
let newConsumed = 0;
|
|
34979
|
+
while (i < options.all.length) {
|
|
34980
|
+
await options.maybeYield();
|
|
34981
|
+
const bl = options.all[i];
|
|
34982
|
+
if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
|
|
34983
|
+
options.out.push(bl);
|
|
34984
|
+
i++;
|
|
34985
|
+
if (bl.startsWith("\\")) continue;
|
|
34986
|
+
const ch = bl[0];
|
|
34987
|
+
if (ch === " ") {
|
|
34988
|
+
oldConsumed++;
|
|
34989
|
+
newConsumed++;
|
|
34990
|
+
} else if (ch === "-") {
|
|
34991
|
+
oldConsumed++;
|
|
34992
|
+
} else if (ch === "+") {
|
|
34993
|
+
newConsumed++;
|
|
34994
|
+
}
|
|
34995
|
+
}
|
|
34996
|
+
return { nextIndex: i, oldConsumed, newConsumed };
|
|
34997
|
+
}
|
|
34998
|
+
|
|
34999
|
+
// src/git/changes/patch/hydrate/read-git-blob-lines.ts
|
|
34624
35000
|
async function readGitBlobLines(repoCwd, pathInRepo) {
|
|
34625
35001
|
try {
|
|
34626
35002
|
const rel = pathInRepo.replace(/\\/g, "/");
|
|
@@ -34630,35 +35006,66 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
|
|
|
34630
35006
|
return null;
|
|
34631
35007
|
}
|
|
34632
35008
|
}
|
|
35009
|
+
|
|
35010
|
+
// src/git/changes/patch/hydrate/read-worktree-file-lines.ts
|
|
35011
|
+
import * as fs19 from "node:fs";
|
|
34633
35012
|
async function readWorktreeFileLines(filePath) {
|
|
34634
35013
|
try {
|
|
34635
|
-
const raw = await
|
|
35014
|
+
const raw = await fs19.promises.readFile(filePath, "utf8");
|
|
34636
35015
|
return raw.split(/\r?\n/);
|
|
34637
35016
|
} catch {
|
|
34638
35017
|
return null;
|
|
34639
35018
|
}
|
|
34640
35019
|
}
|
|
34641
|
-
|
|
34642
|
-
|
|
34643
|
-
|
|
34644
|
-
const out = [];
|
|
34645
|
-
let prevOldEnd = 0;
|
|
34646
|
-
let prevNewEnd = 0;
|
|
34647
|
-
let injectedTotal = 0;
|
|
34648
|
-
let i = 0;
|
|
35020
|
+
|
|
35021
|
+
// src/git/changes/patch/hydrate/create-hydrate-source-lines.ts
|
|
35022
|
+
function createHydrateSourceLines(options) {
|
|
34649
35023
|
let blobCache;
|
|
34650
35024
|
let diskCache;
|
|
34651
35025
|
const blobLines = async () => {
|
|
34652
35026
|
if (blobCache !== void 0) return blobCache;
|
|
34653
|
-
blobCache = await readGitBlobLines(repoGitCwd, pathInRepo);
|
|
35027
|
+
blobCache = await readGitBlobLines(options.repoGitCwd, options.pathInRepo);
|
|
34654
35028
|
return blobCache;
|
|
34655
35029
|
};
|
|
34656
35030
|
const diskLines = async () => {
|
|
34657
35031
|
if (diskCache !== void 0) return diskCache;
|
|
34658
|
-
diskCache = await readWorktreeFileLines(filePath);
|
|
35032
|
+
diskCache = await readWorktreeFileLines(options.filePath);
|
|
34659
35033
|
return diskCache;
|
|
34660
35034
|
};
|
|
35035
|
+
return { blobLines, diskLines };
|
|
35036
|
+
}
|
|
35037
|
+
|
|
35038
|
+
// src/git/changes/patch/hydrate/create-hydrate-yield.ts
|
|
35039
|
+
function createHydrateYield() {
|
|
35040
|
+
let processedLines = 0;
|
|
35041
|
+
return async () => {
|
|
35042
|
+
processedLines++;
|
|
35043
|
+
if (processedLines % HYDRATE_YIELD_EVERY_LINES === 0) {
|
|
35044
|
+
await yieldToEventLoop();
|
|
35045
|
+
}
|
|
35046
|
+
};
|
|
35047
|
+
}
|
|
35048
|
+
|
|
35049
|
+
// src/git/changes/patch/hydrate/update-hunk-line-ends.ts
|
|
35050
|
+
function updateHunkLineEnds(options) {
|
|
35051
|
+
const prevOldEnd = options.oldStart > 0 ? options.oldStart + options.oldConsumed - 1 : 0;
|
|
35052
|
+
const prevNewEnd = options.newStart > 0 ? options.newStart + options.newConsumed - 1 : 0;
|
|
35053
|
+
return { prevOldEnd, prevNewEnd };
|
|
35054
|
+
}
|
|
35055
|
+
|
|
35056
|
+
// src/git/changes/patch/hydrate/hydrate-unified-patch-with-file-context.ts
|
|
35057
|
+
async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
|
|
35058
|
+
if (!patch.trim() || patch.includes("Binary files")) return patch;
|
|
35059
|
+
const all = patch.split("\n");
|
|
35060
|
+
const out = [];
|
|
35061
|
+
let prevOldEnd = 0;
|
|
35062
|
+
let prevNewEnd = 0;
|
|
35063
|
+
const injectedTotal = { value: 0 };
|
|
35064
|
+
let i = 0;
|
|
35065
|
+
const maybeYield = createHydrateYield();
|
|
35066
|
+
const { blobLines, diskLines } = createHydrateSourceLines({ repoGitCwd, pathInRepo, filePath });
|
|
34661
35067
|
while (i < all.length) {
|
|
35068
|
+
await maybeYield();
|
|
34662
35069
|
const line = all[i];
|
|
34663
35070
|
const hm = line.match(UNIFIED_HUNK_HEADER_RE);
|
|
34664
35071
|
if (!hm) {
|
|
@@ -34668,66 +35075,31 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
34668
35075
|
}
|
|
34669
35076
|
const oldStart = parseInt(hm[1], 10) || 0;
|
|
34670
35077
|
const newStart = parseInt(hm[3], 10) || 0;
|
|
34671
|
-
const
|
|
34672
|
-
|
|
34673
|
-
|
|
34674
|
-
|
|
34675
|
-
|
|
34676
|
-
|
|
34677
|
-
|
|
34678
|
-
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
|
|
34682
|
-
|
|
34683
|
-
inject = dl.slice(gapNewStart - 1, gapNewStart - 1 + cap);
|
|
34684
|
-
}
|
|
34685
|
-
}
|
|
34686
|
-
} else if (gapOldEnd >= gapOldStart && change === "removed") {
|
|
34687
|
-
const nOld = gapOldEnd - gapOldStart + 1;
|
|
34688
|
-
const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
|
|
34689
|
-
const bl = await blobLines();
|
|
34690
|
-
if (bl && cap > 0) {
|
|
34691
|
-
inject = bl.slice(gapOldStart - 1, gapOldStart - 1 + cap);
|
|
34692
|
-
}
|
|
34693
|
-
}
|
|
34694
|
-
if (inject && inject.length > 0) {
|
|
34695
|
-
for (const t of inject) {
|
|
34696
|
-
out.push(` ${t}`);
|
|
34697
|
-
injectedTotal++;
|
|
34698
|
-
}
|
|
34699
|
-
}
|
|
35078
|
+
const inject = await collectGapInjectLines({
|
|
35079
|
+
change,
|
|
35080
|
+
gapOldStart: prevOldEnd + 1,
|
|
35081
|
+
gapOldEnd: oldStart - 1,
|
|
35082
|
+
gapNewStart: prevNewEnd + 1,
|
|
35083
|
+
gapNewEnd: newStart - 1,
|
|
35084
|
+
injectedTotal: injectedTotal.value,
|
|
35085
|
+
blobLines,
|
|
35086
|
+
diskLines
|
|
35087
|
+
});
|
|
35088
|
+
if (inject && inject.length > 0) {
|
|
35089
|
+
await appendInjectedContextLines(out, inject, maybeYield, injectedTotal);
|
|
34700
35090
|
}
|
|
34701
35091
|
out.push(line);
|
|
34702
35092
|
i++;
|
|
34703
|
-
|
|
34704
|
-
|
|
34705
|
-
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
34709
|
-
|
|
34710
|
-
|
|
34711
|
-
|
|
34712
|
-
|
|
34713
|
-
oldConsumed++;
|
|
34714
|
-
newConsumed++;
|
|
34715
|
-
} else if (ch === "-") {
|
|
34716
|
-
oldConsumed++;
|
|
34717
|
-
} else if (ch === "+") {
|
|
34718
|
-
newConsumed++;
|
|
34719
|
-
}
|
|
34720
|
-
}
|
|
34721
|
-
if (oldStart > 0) {
|
|
34722
|
-
prevOldEnd = oldStart + oldConsumed - 1;
|
|
34723
|
-
} else {
|
|
34724
|
-
prevOldEnd = 0;
|
|
34725
|
-
}
|
|
34726
|
-
if (newStart > 0) {
|
|
34727
|
-
prevNewEnd = newStart + newConsumed - 1;
|
|
34728
|
-
} else {
|
|
34729
|
-
prevNewEnd = 0;
|
|
34730
|
-
}
|
|
35093
|
+
const body = await consumeUnifiedHunkBody({ all, startIndex: i, out, maybeYield });
|
|
35094
|
+
i = body.nextIndex;
|
|
35095
|
+
const ends = updateHunkLineEnds({
|
|
35096
|
+
oldStart,
|
|
35097
|
+
newStart,
|
|
35098
|
+
oldConsumed: body.oldConsumed,
|
|
35099
|
+
newConsumed: body.newConsumed
|
|
35100
|
+
});
|
|
35101
|
+
prevOldEnd = ends.prevOldEnd;
|
|
35102
|
+
prevNewEnd = ends.prevNewEnd;
|
|
34731
35103
|
}
|
|
34732
35104
|
return truncatePatch(out.join("\n"));
|
|
34733
35105
|
}
|
|
@@ -34735,8 +35107,8 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
34735
35107
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
34736
35108
|
async function enrichWorkingTreeFileRows(options) {
|
|
34737
35109
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
34738
|
-
const pathInRepo =
|
|
34739
|
-
const filePath =
|
|
35110
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
35111
|
+
const filePath = path26.join(options.repoGitCwd, pathInRepo);
|
|
34740
35112
|
const hydrateKind = row.change === "moved" ? "modified" : row.change;
|
|
34741
35113
|
let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
|
|
34742
35114
|
if (patch) {
|
|
@@ -34759,7 +35131,7 @@ async function enrichWorkingTreeFileRows(options) {
|
|
|
34759
35131
|
}
|
|
34760
35132
|
|
|
34761
35133
|
// src/git/changes/listing/list-changed-files-for-repo.ts
|
|
34762
|
-
async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
35134
|
+
async function listChangedFilesForRepo(repoGitCwd, repoRelPath, options = {}) {
|
|
34763
35135
|
const g = cliSimpleGit(repoGitCwd);
|
|
34764
35136
|
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
34765
35137
|
g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
|
|
@@ -34789,23 +35161,101 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
34789
35161
|
})
|
|
34790
35162
|
});
|
|
34791
35163
|
rows = dedupeChangedFileRows(rows);
|
|
34792
|
-
|
|
34793
|
-
|
|
34794
|
-
|
|
34795
|
-
|
|
34796
|
-
|
|
34797
|
-
|
|
35164
|
+
if (options.includeFilePatches) {
|
|
35165
|
+
await enrichWorkingTreeFileRows({
|
|
35166
|
+
rows,
|
|
35167
|
+
repoGitCwd,
|
|
35168
|
+
repoRelPath,
|
|
35169
|
+
binaryByPath: parsed.binaryByPath
|
|
35170
|
+
});
|
|
35171
|
+
}
|
|
34798
35172
|
return dedupeChangedFileRows(rows);
|
|
34799
35173
|
}
|
|
34800
35174
|
|
|
34801
|
-
// src/git/changes/
|
|
35175
|
+
// src/git/changes/repo/norm-repo-rel-path.ts
|
|
34802
35176
|
function normRepoRel(p) {
|
|
34803
35177
|
const x = p.replace(/\\/g, "/").trim();
|
|
34804
35178
|
return x === "" ? "." : x;
|
|
34805
35179
|
}
|
|
35180
|
+
|
|
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
|
+
}
|
|
35197
|
+
async function buildWorkingTreeChangeRepoDetail(options) {
|
|
35198
|
+
const t = path27.resolve(options.targetPath);
|
|
35199
|
+
if (!await isGitRepoDirectory(t)) return null;
|
|
35200
|
+
const g = cliSimpleGit(t);
|
|
35201
|
+
const isCommitView = options.basis.kind === "commit";
|
|
35202
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path27.resolve(options.sessionWorktreeRootPath) : null;
|
|
35203
|
+
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
35204
|
+
await yieldToEventLoop2();
|
|
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
|
+
]);
|
|
35213
|
+
const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
|
|
35214
|
+
let repoRelPath;
|
|
35215
|
+
if (sessionWtRoot) {
|
|
35216
|
+
const anchor = legacyNested ? path27.dirname(t) : t;
|
|
35217
|
+
const relNorm = path27.relative(sessionWtRoot, anchor);
|
|
35218
|
+
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
35219
|
+
} else {
|
|
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;
|
|
35223
|
+
}
|
|
35224
|
+
const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
35225
|
+
if (options.repoFilterRelPath && norm !== options.repoFilterRelPath) return null;
|
|
35226
|
+
const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
|
|
35227
|
+
const relForList = norm === "." ? "." : norm;
|
|
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
|
|
35238
|
+
]);
|
|
35239
|
+
return {
|
|
35240
|
+
repoRelPath: norm,
|
|
35241
|
+
repoDisplayName,
|
|
35242
|
+
branch,
|
|
35243
|
+
remoteUrl,
|
|
35244
|
+
remoteDisplay,
|
|
35245
|
+
files,
|
|
35246
|
+
hasUncommittedChanges,
|
|
35247
|
+
unpushedCommits: commitData?.unpushedCommits ?? [],
|
|
35248
|
+
recentCommits: commitData?.recentCommits ?? [],
|
|
35249
|
+
recentCommitsHasMore: commitData?.recentCommitsHasMore ?? false,
|
|
35250
|
+
changesView: isCommitView ? "commit" : "working",
|
|
35251
|
+
changesCommitSha: commitSha
|
|
35252
|
+
};
|
|
35253
|
+
}
|
|
35254
|
+
|
|
35255
|
+
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
34806
35256
|
async function getWorkingTreeChangeRepoDetails(options) {
|
|
34807
|
-
const bridgeRoot =
|
|
34808
|
-
const sessionWtRoot = options.sessionWorktreeRootPath ?
|
|
35257
|
+
const bridgeRoot = path28.resolve(getBridgeRoot());
|
|
35258
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path28.resolve(options.sessionWorktreeRootPath) : null;
|
|
34809
35259
|
const legacyNested = options.legacyRepoNestedSessionLayout === true;
|
|
34810
35260
|
const out = [];
|
|
34811
35261
|
const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
|
|
@@ -34818,152 +35268,167 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
34818
35268
|
}
|
|
34819
35269
|
const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
|
|
34820
35270
|
for (let i = 0; i < options.commitTargetPaths.length; i++) {
|
|
34821
|
-
|
|
34822
|
-
const
|
|
34823
|
-
const
|
|
34824
|
-
|
|
34825
|
-
|
|
34826
|
-
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
34831
|
-
|
|
34832
|
-
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
if (sessionWtRoot) {
|
|
34836
|
-
const anchor = legacyNested ? path22.dirname(t) : t;
|
|
34837
|
-
const relNorm = path22.relative(sessionWtRoot, anchor);
|
|
34838
|
-
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
34839
|
-
} else {
|
|
34840
|
-
let top = t;
|
|
34841
|
-
try {
|
|
34842
|
-
top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
|
|
34843
|
-
} catch {
|
|
34844
|
-
top = t;
|
|
34845
|
-
}
|
|
34846
|
-
const rel = path22.relative(bridgeRoot, path22.resolve(top)).replace(/\\/g, "/") || ".";
|
|
34847
|
-
repoRelPath = rel.startsWith("..") ? path22.basename(path22.resolve(top)) : rel;
|
|
34848
|
-
}
|
|
34849
|
-
const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
34850
|
-
if (filter && norm !== filter) continue;
|
|
34851
|
-
const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
|
|
34852
|
-
const relForList = norm === "." ? "." : norm;
|
|
34853
|
-
const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
|
|
34854
|
-
const st = await g.status();
|
|
34855
|
-
const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
|
|
34856
|
-
const [unpushedCommits, recentCommitList] = await Promise.all([
|
|
34857
|
-
listUnpushedCommits(t),
|
|
34858
|
-
listRecentCommits(t, options.recentCommitsLimit)
|
|
34859
|
-
]);
|
|
34860
|
-
out.push({
|
|
34861
|
-
repoRelPath: norm,
|
|
34862
|
-
repoDisplayName,
|
|
34863
|
-
branch,
|
|
34864
|
-
remoteUrl,
|
|
34865
|
-
remoteDisplay,
|
|
34866
|
-
files,
|
|
34867
|
-
hasUncommittedChanges,
|
|
34868
|
-
unpushedCommits,
|
|
34869
|
-
recentCommits: recentCommitList.commits,
|
|
34870
|
-
recentCommitsHasMore: recentCommitList.hasMore,
|
|
34871
|
-
changesView: basis.kind === "commit" ? "commit" : "working",
|
|
34872
|
-
changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
|
|
34873
|
-
});
|
|
35271
|
+
await yieldToEventLoop2();
|
|
35272
|
+
const targetPath = options.commitTargetPaths[i];
|
|
35273
|
+
const detail = await buildWorkingTreeChangeRepoDetail({
|
|
35274
|
+
targetPath,
|
|
35275
|
+
bridgeRoot,
|
|
35276
|
+
sessionWorktreeRootPath: sessionWtRoot,
|
|
35277
|
+
legacyRepoNestedSessionLayout: legacyNested,
|
|
35278
|
+
repoFilterRelPath: filter,
|
|
35279
|
+
basis,
|
|
35280
|
+
recentCommitsLimit: options.recentCommitsLimit,
|
|
35281
|
+
includeFilePatches: options.includeFilePatches
|
|
35282
|
+
});
|
|
35283
|
+
if (!detail) continue;
|
|
35284
|
+
out.push(detail);
|
|
34874
35285
|
if (filter) return out;
|
|
34875
35286
|
}
|
|
34876
35287
|
return out;
|
|
34877
35288
|
}
|
|
34878
35289
|
|
|
34879
|
-
// src/git/changes/
|
|
34880
|
-
|
|
34881
|
-
|
|
34882
|
-
const
|
|
34883
|
-
|
|
34884
|
-
|
|
34885
|
-
|
|
34886
|
-
|
|
34887
|
-
|
|
34888
|
-
|
|
34889
|
-
|
|
34890
|
-
|
|
34891
|
-
|
|
34892
|
-
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");
|
|
34893
35303
|
const g = cliSimpleGit(repoGitCwd);
|
|
34894
|
-
const
|
|
34895
|
-
|
|
34896
|
-
|
|
34897
|
-
|
|
34898
|
-
|
|
34899
|
-
|
|
34900
|
-
|
|
34901
|
-
|
|
34902
|
-
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
|
|
34903
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"))
|
|
34904
35346
|
});
|
|
35347
|
+
return { patchContent, totalLines };
|
|
34905
35348
|
}
|
|
34906
35349
|
|
|
34907
|
-
// src/git/
|
|
34908
|
-
async function
|
|
34909
|
-
|
|
34910
|
-
|
|
34911
|
-
|
|
34912
|
-
|
|
34913
|
-
|
|
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
|
|
34914
35361
|
});
|
|
34915
35362
|
}
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
const s = await getRepoWorkingTreeStatus(p);
|
|
34922
|
-
uncommittedFileCount += s.uncommittedFileCount;
|
|
34923
|
-
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
34924
|
-
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
34925
|
-
});
|
|
34926
|
-
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
34927
|
-
}
|
|
34928
|
-
async function pushAheadOfUpstreamForPaths(paths) {
|
|
34929
|
-
await forEachWithGitYield(paths, async (p) => {
|
|
34930
|
-
const g = cliSimpleGit(p);
|
|
34931
|
-
const ahead = await commitsAheadOfRemoteTracking(p);
|
|
34932
|
-
if (ahead <= 0) return;
|
|
34933
|
-
await g.push();
|
|
34934
|
-
});
|
|
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;
|
|
34935
35368
|
}
|
|
34936
35369
|
|
|
34937
|
-
// src/git/
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
|
|
34943
|
-
|
|
34944
|
-
|
|
34945
|
-
|
|
34946
|
-
const
|
|
34947
|
-
|
|
34948
|
-
if (
|
|
34949
|
-
|
|
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, "/");
|
|
34950
35385
|
} else {
|
|
34951
|
-
|
|
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;
|
|
34952
35395
|
}
|
|
34953
|
-
|
|
34954
|
-
|
|
34955
|
-
|
|
34956
|
-
|
|
35396
|
+
return normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
35397
|
+
}
|
|
35398
|
+
|
|
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
|
+
});
|
|
34957
35424
|
}
|
|
35425
|
+
throw new Error(`Repository not found: ${filter}`);
|
|
34958
35426
|
}
|
|
34959
35427
|
|
|
34960
|
-
// src/worktrees/
|
|
34961
|
-
async function
|
|
34962
|
-
const { paths, branch, message, push } = options;
|
|
35428
|
+
// src/worktrees/manager/git/push-session-upstream.ts
|
|
35429
|
+
async function pushSessionUpstream(cache2, sessionId, discover) {
|
|
34963
35430
|
try {
|
|
34964
|
-
|
|
34965
|
-
await gitCommitAllIfDirty(wt, message, { push, branch });
|
|
34966
|
-
}
|
|
35431
|
+
await pushAheadOfUpstreamForPaths(await resolveCommitTargetsAsync(sessionId, cache2, discover));
|
|
34967
35432
|
return { ok: true };
|
|
34968
35433
|
} catch (e) {
|
|
34969
35434
|
const err = e instanceof Error ? e.message : String(e);
|
|
@@ -34972,23 +35437,23 @@ async function commitSessionWorktrees(options) {
|
|
|
34972
35437
|
}
|
|
34973
35438
|
|
|
34974
35439
|
// src/worktrees/remove-session-worktrees.ts
|
|
34975
|
-
import * as
|
|
35440
|
+
import * as fs22 from "node:fs";
|
|
34976
35441
|
|
|
34977
35442
|
// src/git/worktrees/worktree-remove.ts
|
|
34978
|
-
import * as
|
|
35443
|
+
import * as fs21 from "node:fs";
|
|
34979
35444
|
|
|
34980
35445
|
// src/git/worktrees/resolve-main-repo-from-git-file.ts
|
|
34981
|
-
import * as
|
|
34982
|
-
import * as
|
|
35446
|
+
import * as fs20 from "node:fs";
|
|
35447
|
+
import * as path32 from "node:path";
|
|
34983
35448
|
function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
34984
|
-
const gitDirFile =
|
|
34985
|
-
if (!
|
|
34986
|
-
const first2 =
|
|
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();
|
|
34987
35452
|
const m = first2.match(/^gitdir:\s*(.+)$/im);
|
|
34988
35453
|
if (!m) return "";
|
|
34989
|
-
const gitWorktreePath =
|
|
34990
|
-
const gitDir =
|
|
34991
|
-
return
|
|
35454
|
+
const gitWorktreePath = path32.resolve(wt, m[1].trim());
|
|
35455
|
+
const gitDir = path32.dirname(path32.dirname(gitWorktreePath));
|
|
35456
|
+
return path32.dirname(gitDir);
|
|
34992
35457
|
}
|
|
34993
35458
|
|
|
34994
35459
|
// src/git/worktrees/worktree-remove.ts
|
|
@@ -34997,7 +35462,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
|
|
|
34997
35462
|
if (mainRepo) {
|
|
34998
35463
|
await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
|
|
34999
35464
|
} else {
|
|
35000
|
-
|
|
35465
|
+
fs21.rmSync(worktreePath, { recursive: true, force: true });
|
|
35001
35466
|
}
|
|
35002
35467
|
}
|
|
35003
35468
|
|
|
@@ -35010,13 +35475,20 @@ async function removeSessionWorktrees(paths, log2) {
|
|
|
35010
35475
|
} catch (e) {
|
|
35011
35476
|
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
35012
35477
|
try {
|
|
35013
|
-
|
|
35478
|
+
fs22.rmSync(wt, { recursive: true, force: true });
|
|
35014
35479
|
} catch {
|
|
35015
35480
|
}
|
|
35016
35481
|
}
|
|
35017
35482
|
}
|
|
35018
35483
|
}
|
|
35019
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
|
+
|
|
35020
35492
|
// src/git/branches/rename-branch.ts
|
|
35021
35493
|
async function gitRenameCurrentBranch(repoDir, newName) {
|
|
35022
35494
|
const g = cliSimpleGit(repoDir);
|
|
@@ -35038,239 +35510,310 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
|
35038
35510
|
}
|
|
35039
35511
|
}
|
|
35040
35512
|
|
|
35041
|
-
// src/worktrees/worktree-
|
|
35042
|
-
|
|
35043
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
function defaultWorktreeLayoutPath() {
|
|
35047
|
-
return path24.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
|
|
35048
|
-
}
|
|
35049
|
-
function normalizeLoadedLayout(raw) {
|
|
35050
|
-
if (raw && typeof raw === "object" && "launcherCwds" in raw) {
|
|
35051
|
-
const j = raw;
|
|
35052
|
-
if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
|
|
35053
|
-
}
|
|
35054
|
-
return { launcherCwds: [] };
|
|
35055
|
-
}
|
|
35056
|
-
function loadWorktreeLayout() {
|
|
35057
|
-
try {
|
|
35058
|
-
const p = defaultWorktreeLayoutPath();
|
|
35059
|
-
if (!fs22.existsSync(p)) return { launcherCwds: [] };
|
|
35060
|
-
const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
|
|
35061
|
-
return normalizeLoadedLayout(raw);
|
|
35062
|
-
} catch {
|
|
35063
|
-
return { launcherCwds: [] };
|
|
35064
|
-
}
|
|
35065
|
-
}
|
|
35066
|
-
function saveWorktreeLayout(layout) {
|
|
35067
|
-
try {
|
|
35068
|
-
const dir = path24.dirname(defaultWorktreeLayoutPath());
|
|
35069
|
-
fs22.mkdirSync(dir, { recursive: true });
|
|
35070
|
-
fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
|
|
35071
|
-
} catch {
|
|
35072
|
-
}
|
|
35073
|
-
}
|
|
35074
|
-
function baseNameSafe(pathString) {
|
|
35075
|
-
return path24.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
|
|
35076
|
-
}
|
|
35077
|
-
function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
|
|
35078
|
-
const norm = path24.resolve(bridgeRootPath2);
|
|
35079
|
-
const existing = layout.launcherCwds.find((e) => path24.resolve(e.absolutePath) === norm);
|
|
35080
|
-
return existing?.dirName;
|
|
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);
|
|
35081
35518
|
}
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
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);
|
|
35097
35551
|
}
|
|
35098
35552
|
|
|
35099
|
-
// src/worktrees/
|
|
35553
|
+
// src/worktrees/discovery/disk-walk-utils.ts
|
|
35100
35554
|
import * as fs23 from "node:fs";
|
|
35101
|
-
import * as
|
|
35102
|
-
function
|
|
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) {
|
|
35103
35564
|
try {
|
|
35104
|
-
|
|
35565
|
+
await fs23.promises.access(path33.join(dirPath, ".git"));
|
|
35566
|
+
return true;
|
|
35105
35567
|
} catch {
|
|
35106
35568
|
return false;
|
|
35107
35569
|
}
|
|
35108
35570
|
}
|
|
35109
|
-
|
|
35571
|
+
|
|
35572
|
+
// src/worktrees/discovery/collect-worktree-paths.ts
|
|
35573
|
+
async function collectGitRepoRootsUnderDirectory(rootPath) {
|
|
35110
35574
|
const out = [];
|
|
35111
|
-
const
|
|
35112
|
-
|
|
35113
|
-
|
|
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));
|
|
35114
35580
|
return;
|
|
35115
35581
|
}
|
|
35116
35582
|
let entries;
|
|
35117
35583
|
try {
|
|
35118
|
-
entries =
|
|
35584
|
+
entries = await fs24.promises.readdir(dir, { withFileTypes: true });
|
|
35119
35585
|
} catch {
|
|
35120
35586
|
return;
|
|
35121
35587
|
}
|
|
35122
35588
|
for (const e of entries) {
|
|
35123
|
-
if (e.name
|
|
35124
|
-
const full =
|
|
35589
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
35590
|
+
const full = path34.join(dir, e.name);
|
|
35125
35591
|
if (!e.isDirectory()) continue;
|
|
35126
|
-
walk(full);
|
|
35592
|
+
await walk(full);
|
|
35127
35593
|
}
|
|
35128
35594
|
};
|
|
35129
|
-
walk(
|
|
35130
|
-
return [...new Set(out)];
|
|
35595
|
+
await walk(path34.resolve(rootPath));
|
|
35596
|
+
return { paths: [...new Set(out)], entriesVisited: state.entries };
|
|
35131
35597
|
}
|
|
35132
|
-
function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
|
|
35598
|
+
async function collectWorktreeRootsNamed(root, sessionId, maxDepth = LEGACY_WALK_MAX_DEPTH) {
|
|
35133
35599
|
const out = [];
|
|
35134
|
-
const
|
|
35600
|
+
const state = { entries: 0 };
|
|
35601
|
+
const walk = async (dir, depth) => {
|
|
35135
35602
|
if (depth > maxDepth) return;
|
|
35603
|
+
if (!await yieldDuringDiskWalk(state)) return;
|
|
35136
35604
|
let entries;
|
|
35137
35605
|
try {
|
|
35138
|
-
entries =
|
|
35606
|
+
entries = await fs24.promises.readdir(dir, { withFileTypes: true });
|
|
35139
35607
|
} catch {
|
|
35140
35608
|
return;
|
|
35141
35609
|
}
|
|
35142
35610
|
for (const e of entries) {
|
|
35143
|
-
if (e.name
|
|
35144
|
-
const full =
|
|
35611
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
35612
|
+
const full = path34.join(dir, e.name);
|
|
35145
35613
|
if (!e.isDirectory()) continue;
|
|
35146
35614
|
if (e.name === sessionId) {
|
|
35147
|
-
if (isGitDir(full)) out.push(
|
|
35148
|
-
|
|
35149
|
-
walk(full, depth + 1);
|
|
35615
|
+
if (await isGitDir(full)) out.push(path34.resolve(full));
|
|
35616
|
+
continue;
|
|
35150
35617
|
}
|
|
35618
|
+
if (await isGitDir(full)) continue;
|
|
35619
|
+
await walk(full, depth + 1);
|
|
35151
35620
|
}
|
|
35152
35621
|
};
|
|
35153
|
-
walk(root, 0);
|
|
35154
|
-
return out;
|
|
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;
|
|
35155
35649
|
}
|
|
35156
|
-
|
|
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) {
|
|
35157
35655
|
let st;
|
|
35158
35656
|
try {
|
|
35159
|
-
st =
|
|
35657
|
+
st = await fs25.promises.stat(sessionDir);
|
|
35160
35658
|
} catch {
|
|
35161
35659
|
return null;
|
|
35162
35660
|
}
|
|
35163
35661
|
if (!st.isDirectory()) return null;
|
|
35164
|
-
const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
|
|
35662
|
+
const { paths: worktreePaths } = await collectGitRepoRootsUnderDirectory(sessionDir);
|
|
35165
35663
|
if (worktreePaths.length === 0) return null;
|
|
35166
|
-
const abs =
|
|
35664
|
+
const abs = path36.resolve(sessionDir);
|
|
35167
35665
|
return {
|
|
35168
35666
|
sessionParentPath: abs,
|
|
35169
35667
|
workingTreeRelRoot: abs,
|
|
35170
35668
|
repoCheckoutPaths: worktreePaths
|
|
35171
35669
|
};
|
|
35172
35670
|
}
|
|
35173
|
-
|
|
35671
|
+
|
|
35672
|
+
// src/worktrees/discovery/discover-session-worktree-on-disk.ts
|
|
35673
|
+
async function discoverSessionWorktreeOnDisk(options) {
|
|
35674
|
+
const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
|
|
35174
35675
|
const sid = sessionId.trim();
|
|
35175
35676
|
if (!sid) return null;
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
const paths = collectWorktreeRootsNamed(cur, sid, 24);
|
|
35181
|
-
if (paths.some((p) => path25.resolve(p) === hintR)) {
|
|
35182
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path25.resolve(paths[0]);
|
|
35183
|
-
best = {
|
|
35184
|
-
sessionParentPath: path25.resolve(isolated),
|
|
35185
|
-
workingTreeRelRoot: path25.resolve(cur),
|
|
35186
|
-
repoCheckoutPaths: paths.map((p) => path25.resolve(p))
|
|
35187
|
-
};
|
|
35188
|
-
}
|
|
35189
|
-
const next = path25.dirname(cur);
|
|
35190
|
-
if (next === cur) break;
|
|
35191
|
-
cur = next;
|
|
35677
|
+
try {
|
|
35678
|
+
await fs26.promises.access(worktreesRootPath);
|
|
35679
|
+
} catch {
|
|
35680
|
+
return null;
|
|
35192
35681
|
}
|
|
35193
|
-
return best;
|
|
35194
|
-
}
|
|
35195
|
-
function discoverSessionWorktreeOnDisk(options) {
|
|
35196
|
-
const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
|
|
35197
|
-
if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
|
|
35198
35682
|
const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
35199
|
-
const
|
|
35200
|
-
if (preferredKey)
|
|
35683
|
+
const keysOnDisk = [];
|
|
35684
|
+
if (preferredKey) keysOnDisk.push(preferredKey);
|
|
35201
35685
|
try {
|
|
35202
|
-
for (const name of
|
|
35686
|
+
for (const name of await fs26.promises.readdir(worktreesRootPath)) {
|
|
35203
35687
|
if (name.startsWith(".")) continue;
|
|
35204
|
-
const p =
|
|
35205
|
-
|
|
35206
|
-
|
|
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);
|
|
35207
35697
|
}
|
|
35208
35698
|
} catch {
|
|
35209
35699
|
return null;
|
|
35210
35700
|
}
|
|
35701
|
+
const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
|
|
35211
35702
|
for (const key of keys) {
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
const
|
|
35215
|
-
|
|
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);
|
|
35216
35715
|
if (nested) return nested;
|
|
35217
|
-
const
|
|
35218
|
-
if (
|
|
35219
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(
|
|
35716
|
+
const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
|
|
35717
|
+
if (legacy.paths.length > 0) {
|
|
35718
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path37.resolve(legacy.paths[0]);
|
|
35220
35719
|
return {
|
|
35221
|
-
sessionParentPath:
|
|
35222
|
-
workingTreeRelRoot:
|
|
35223
|
-
repoCheckoutPaths:
|
|
35720
|
+
sessionParentPath: path37.resolve(isolated),
|
|
35721
|
+
workingTreeRelRoot: path37.resolve(layoutRoot),
|
|
35722
|
+
repoCheckoutPaths: legacy.paths.map((p) => path37.resolve(p))
|
|
35224
35723
|
};
|
|
35225
35724
|
}
|
|
35226
35725
|
}
|
|
35227
35726
|
return null;
|
|
35228
35727
|
}
|
|
35229
|
-
|
|
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) {
|
|
35230
35762
|
const sid = sessionId.trim();
|
|
35231
35763
|
if (!sid) return null;
|
|
35232
|
-
const hint =
|
|
35233
|
-
const underHint = tryBindingFromSessionDirectory(
|
|
35764
|
+
const hint = path39.resolve(sessionWorktreeRootPathOrHint);
|
|
35765
|
+
const underHint = await tryBindingFromSessionDirectory(path39.join(hint, sid));
|
|
35234
35766
|
if (underHint) return underHint;
|
|
35235
|
-
const direct = tryBindingFromSessionDirectory(hint);
|
|
35767
|
+
const direct = await tryBindingFromSessionDirectory(hint);
|
|
35236
35768
|
if (direct) {
|
|
35237
|
-
if (
|
|
35238
|
-
const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35769
|
+
if (path39.basename(hint) === sid && await isGitDir(hint)) {
|
|
35770
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35239
35771
|
if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
|
|
35240
35772
|
return legacyFromCheckout;
|
|
35241
35773
|
}
|
|
35242
35774
|
}
|
|
35243
35775
|
return direct;
|
|
35244
35776
|
}
|
|
35245
|
-
if (
|
|
35246
|
-
const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35777
|
+
if (path39.basename(hint) === sid && await isGitDir(hint)) {
|
|
35778
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
35247
35779
|
if (legacyFromCheckout) return legacyFromCheckout;
|
|
35248
35780
|
}
|
|
35249
35781
|
let st;
|
|
35250
35782
|
try {
|
|
35251
|
-
st =
|
|
35783
|
+
st = await fs27.promises.stat(hint);
|
|
35252
35784
|
} catch {
|
|
35253
35785
|
return null;
|
|
35254
35786
|
}
|
|
35255
35787
|
if (!st.isDirectory()) return null;
|
|
35256
|
-
const legacyPaths = collectWorktreeRootsNamed(hint, sid
|
|
35257
|
-
if (legacyPaths.length === 0) return null;
|
|
35258
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ??
|
|
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]);
|
|
35259
35791
|
return {
|
|
35260
|
-
sessionParentPath:
|
|
35792
|
+
sessionParentPath: path39.resolve(isolated),
|
|
35261
35793
|
workingTreeRelRoot: hint,
|
|
35262
|
-
repoCheckoutPaths: legacyPaths.map((p) =>
|
|
35794
|
+
repoCheckoutPaths: legacyPaths.paths.map((p) => path39.resolve(p))
|
|
35263
35795
|
};
|
|
35264
35796
|
}
|
|
35265
35797
|
|
|
35266
35798
|
// src/worktrees/manager/discover-session-binding.ts
|
|
35267
|
-
|
|
35268
|
-
|
|
35269
|
-
|
|
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,
|
|
35270
35807
|
worktreesRootPath: params.worktreesRootPath,
|
|
35271
35808
|
layout: params.layout,
|
|
35272
35809
|
bridgeRoot: getBridgeRoot()
|
|
35810
|
+
}).finally(() => {
|
|
35811
|
+
if (discoverInflightBySessionId.get(sid) === promise2) {
|
|
35812
|
+
discoverInflightBySessionId.delete(sid);
|
|
35813
|
+
}
|
|
35273
35814
|
});
|
|
35815
|
+
discoverInflightBySessionId.set(sid, promise2);
|
|
35816
|
+
return promise2;
|
|
35274
35817
|
}
|
|
35275
35818
|
|
|
35276
35819
|
// src/worktrees/manager/resolve-isolated-session-parent-path.ts
|
|
@@ -35283,12 +35826,17 @@ function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutP
|
|
|
35283
35826
|
if (!paths?.length) return null;
|
|
35284
35827
|
return resolveIsolatedSessionParentPathFromCheckouts(paths);
|
|
35285
35828
|
}
|
|
35286
|
-
function ensureRepoCheckoutPathsForSession(sessionId, cache2
|
|
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) {
|
|
35287
35835
|
if (!sessionId?.trim()) return void 0;
|
|
35288
35836
|
const sid = sessionId.trim();
|
|
35289
35837
|
const cached2 = cache2.getRepoCheckoutPaths(sid);
|
|
35290
35838
|
if (cached2?.length) return cached2;
|
|
35291
|
-
const disc = discover(sid);
|
|
35839
|
+
const disc = await discover(sid);
|
|
35292
35840
|
if (disc?.repoCheckoutPaths.length) {
|
|
35293
35841
|
cache2.remember(sid, disc);
|
|
35294
35842
|
return [...disc.repoCheckoutPaths];
|
|
@@ -35296,18 +35844,6 @@ function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
|
|
|
35296
35844
|
return void 0;
|
|
35297
35845
|
}
|
|
35298
35846
|
|
|
35299
|
-
// src/worktrees/manager/resolve-commit-targets.ts
|
|
35300
|
-
function resolveCommitTargets(sessionId, cache2, discover) {
|
|
35301
|
-
const paths = cache2.getRepoCheckoutPathsRef(sessionId);
|
|
35302
|
-
if (paths?.length) return paths;
|
|
35303
|
-
const disc = discover(sessionId);
|
|
35304
|
-
if (disc?.repoCheckoutPaths.length) {
|
|
35305
|
-
cache2.remember(sessionId, disc);
|
|
35306
|
-
return disc.repoCheckoutPaths;
|
|
35307
|
-
}
|
|
35308
|
-
return [getBridgeRoot()];
|
|
35309
|
-
}
|
|
35310
|
-
|
|
35311
35847
|
// src/worktrees/manager/parse-session-parent.ts
|
|
35312
35848
|
function parseSessionParent(v) {
|
|
35313
35849
|
if (v === "bridge_root" || v === "worktrees_root") return v;
|
|
@@ -35316,8 +35852,8 @@ function parseSessionParent(v) {
|
|
|
35316
35852
|
}
|
|
35317
35853
|
|
|
35318
35854
|
// src/worktrees/prepare-new-session-worktrees.ts
|
|
35319
|
-
import * as
|
|
35320
|
-
import * as
|
|
35855
|
+
import * as fs28 from "node:fs";
|
|
35856
|
+
import * as path40 from "node:path";
|
|
35321
35857
|
|
|
35322
35858
|
// src/git/worktrees/worktree-add.ts
|
|
35323
35859
|
async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
|
|
@@ -35339,10 +35875,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
|
|
|
35339
35875
|
}
|
|
35340
35876
|
async function prepareNewSessionWorktrees(options) {
|
|
35341
35877
|
const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
|
|
35342
|
-
const bridgeResolved =
|
|
35878
|
+
const bridgeResolved = path40.resolve(bridgeRoot);
|
|
35343
35879
|
const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
|
|
35344
|
-
const bridgeKeyDir =
|
|
35345
|
-
const sessionDir =
|
|
35880
|
+
const bridgeKeyDir = path40.join(worktreesRootPath, cwdKey);
|
|
35881
|
+
const sessionDir = path40.join(bridgeKeyDir, sessionId);
|
|
35346
35882
|
const repos = await discoverGitReposUnderRoot(bridgeResolved);
|
|
35347
35883
|
if (repos.length === 0) {
|
|
35348
35884
|
log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
|
|
@@ -35350,14 +35886,14 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
35350
35886
|
}
|
|
35351
35887
|
const branch = `session-${sessionId}`;
|
|
35352
35888
|
const worktreePaths = [];
|
|
35353
|
-
|
|
35889
|
+
fs28.mkdirSync(sessionDir, { recursive: true });
|
|
35354
35890
|
for (const repo of repos) {
|
|
35355
|
-
let rel =
|
|
35356
|
-
if (rel.startsWith("..") ||
|
|
35891
|
+
let rel = path40.relative(bridgeResolved, repo.absolutePath);
|
|
35892
|
+
if (rel.startsWith("..") || path40.isAbsolute(rel)) continue;
|
|
35357
35893
|
const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
|
|
35358
|
-
const wtPath = relNorm === "." ? sessionDir :
|
|
35894
|
+
const wtPath = relNorm === "." ? sessionDir : path40.join(sessionDir, relNorm);
|
|
35359
35895
|
if (relNorm !== ".") {
|
|
35360
|
-
|
|
35896
|
+
fs28.mkdirSync(path40.dirname(wtPath), { recursive: true });
|
|
35361
35897
|
}
|
|
35362
35898
|
const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
|
|
35363
35899
|
try {
|
|
@@ -35398,10 +35934,10 @@ async function prepareAndRememberSessionWorktrees(params) {
|
|
|
35398
35934
|
}
|
|
35399
35935
|
|
|
35400
35936
|
// src/worktrees/manager/resolve-existing-session-parent-path.ts
|
|
35401
|
-
function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
35937
|
+
async function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
35402
35938
|
const cached2 = cache2.getSessionParentPath(sessionId);
|
|
35403
35939
|
if (cached2) return cached2;
|
|
35404
|
-
const disc = discover();
|
|
35940
|
+
const disc = await discover();
|
|
35405
35941
|
if (disc) {
|
|
35406
35942
|
cache2.remember(sessionId, disc);
|
|
35407
35943
|
return cache2.getSessionParentPath(sessionId);
|
|
@@ -35410,9 +35946,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
|
35410
35946
|
}
|
|
35411
35947
|
|
|
35412
35948
|
// src/worktrees/manager/resolve-explicit-session-parent-path.ts
|
|
35413
|
-
import * as
|
|
35414
|
-
function resolveExplicitSessionParentPath(params) {
|
|
35415
|
-
const resolved =
|
|
35949
|
+
import * as path41 from "node:path";
|
|
35950
|
+
async function resolveExplicitSessionParentPath(params) {
|
|
35951
|
+
const resolved = path41.resolve(params.parentPathRaw);
|
|
35416
35952
|
if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
|
|
35417
35953
|
return resolved;
|
|
35418
35954
|
}
|
|
@@ -35420,15 +35956,16 @@ function resolveExplicitSessionParentPath(params) {
|
|
|
35420
35956
|
params.cache.remember(params.sessionId, binding);
|
|
35421
35957
|
return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
|
|
35422
35958
|
};
|
|
35423
|
-
const diskFirst = params.discover();
|
|
35959
|
+
const diskFirst = await params.discover();
|
|
35424
35960
|
if (diskFirst) return rememberAndReturn(diskFirst);
|
|
35425
|
-
const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
35961
|
+
const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
35426
35962
|
if (fromRoot) return rememberAndReturn(fromRoot);
|
|
35427
35963
|
let cur = resolved;
|
|
35428
35964
|
for (let i = 0; i < 16; i++) {
|
|
35429
|
-
|
|
35965
|
+
await yieldToEventLoop();
|
|
35966
|
+
const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
|
|
35430
35967
|
if (tryRoot) return rememberAndReturn(tryRoot);
|
|
35431
|
-
const next =
|
|
35968
|
+
const next = path41.dirname(cur);
|
|
35432
35969
|
if (next === cur) break;
|
|
35433
35970
|
cur = next;
|
|
35434
35971
|
}
|
|
@@ -35479,173 +36016,117 @@ async function resolveSessionParentPathForPrompt(params) {
|
|
|
35479
36016
|
});
|
|
35480
36017
|
}
|
|
35481
36018
|
|
|
35482
|
-
// src/worktrees/manager/session-worktree-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
|
|
35490
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
35493
|
-
|
|
35494
|
-
|
|
35495
|
-
|
|
35496
|
-
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
|
|
35500
|
-
|
|
35501
|
-
|
|
35502
|
-
|
|
35503
|
-
|
|
35504
|
-
|
|
35505
|
-
|
|
35506
|
-
|
|
35507
|
-
|
|
35508
|
-
|
|
35509
|
-
|
|
35510
|
-
|
|
35511
|
-
|
|
35512
|
-
|
|
35513
|
-
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
|
|
35524
|
-
|
|
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
|
+
}
|
|
35525
36097
|
|
|
35526
|
-
// src/worktrees/manager/manager.ts
|
|
36098
|
+
// src/worktrees/manager/session-worktree-manager.ts
|
|
35527
36099
|
var SessionWorktreeManager = class {
|
|
35528
|
-
worktreesRootPath;
|
|
35529
|
-
log;
|
|
35530
|
-
cache = new SessionWorktreeCache();
|
|
35531
|
-
layout;
|
|
35532
36100
|
constructor(options) {
|
|
35533
|
-
|
|
35534
|
-
this
|
|
35535
|
-
this.layout = loadWorktreeLayout();
|
|
35536
|
-
}
|
|
35537
|
-
discover(sessionId) {
|
|
35538
|
-
return discoverSessionBinding({
|
|
35539
|
-
sessionId,
|
|
35540
|
-
worktreesRootPath: this.worktreesRootPath,
|
|
35541
|
-
layout: this.layout
|
|
35542
|
-
});
|
|
35543
|
-
}
|
|
35544
|
-
getIsolatedSessionParentPathForSession(sessionId) {
|
|
35545
|
-
return resolveIsolatedSessionParentPath(
|
|
35546
|
-
sessionId,
|
|
35547
|
-
this.cache,
|
|
35548
|
-
(sid) => this.ensureRepoCheckoutPathsForSession(sid)
|
|
35549
|
-
);
|
|
35550
|
-
}
|
|
35551
|
-
resolveSessionParentPathForPrompt(sessionId, opts) {
|
|
35552
|
-
return resolveSessionParentPathForPrompt({
|
|
35553
|
-
sessionId,
|
|
35554
|
-
cache: this.cache,
|
|
35555
|
-
worktreesRootPath: this.worktreesRootPath,
|
|
35556
|
-
layout: this.layout,
|
|
35557
|
-
log: this.log,
|
|
35558
|
-
discover: (sid) => this.discover(sid),
|
|
35559
|
-
opts
|
|
35560
|
-
});
|
|
35561
|
-
}
|
|
35562
|
-
async renameSessionBranch(sessionId, newBranch) {
|
|
35563
|
-
const paths = this.cache.getRepoCheckoutPathsRef(sessionId);
|
|
35564
|
-
if (!paths?.length) return;
|
|
35565
|
-
await renameSessionWorktreeBranches(paths, newBranch, this.log);
|
|
35566
|
-
}
|
|
35567
|
-
usesWorktreeSession(sessionId) {
|
|
35568
|
-
if (!sessionId) return false;
|
|
35569
|
-
return this.cache.hasSession(sessionId);
|
|
35570
|
-
}
|
|
35571
|
-
getRepoCheckoutPathsForSession(sessionId) {
|
|
35572
|
-
if (!sessionId) return void 0;
|
|
35573
|
-
return this.cache.getRepoCheckoutPaths(sessionId);
|
|
35574
|
-
}
|
|
35575
|
-
ensureRepoCheckoutPathsForSession(sessionId) {
|
|
35576
|
-
return ensureRepoCheckoutPathsForSession(sessionId, this.cache, (sid) => this.discover(sid));
|
|
35577
|
-
}
|
|
35578
|
-
getSessionWorktreeRootForSession(sessionId) {
|
|
35579
|
-
return this.getIsolatedSessionParentPathForSession(sessionId);
|
|
35580
|
-
}
|
|
35581
|
-
async removeSessionWorktrees(sessionId) {
|
|
35582
|
-
const paths = this.cache.clearSession(sessionId);
|
|
35583
|
-
if (!paths?.length) return;
|
|
35584
|
-
await removeSessionWorktrees(paths, this.log);
|
|
35585
|
-
}
|
|
35586
|
-
async commitSession(params) {
|
|
35587
|
-
const paths = this.cache.getRepoCheckoutPathsRef(params.sessionId);
|
|
35588
|
-
const targets = paths?.length ? paths : [getBridgeRoot()];
|
|
35589
|
-
return commitSessionWorktrees({
|
|
35590
|
-
paths: targets,
|
|
35591
|
-
branch: params.branch,
|
|
35592
|
-
message: params.message,
|
|
35593
|
-
push: params.push
|
|
35594
|
-
});
|
|
35595
|
-
}
|
|
35596
|
-
async getSessionWorkingTreeStatus(sessionId) {
|
|
35597
|
-
return aggregateSessionPathsWorkingTreeStatus(
|
|
35598
|
-
resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
|
|
35599
|
-
);
|
|
35600
|
-
}
|
|
35601
|
-
async getSessionWorkingTreeChangeDetails(sessionId, opts) {
|
|
35602
|
-
const targets = resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid));
|
|
35603
|
-
return getWorkingTreeChangeRepoDetails({
|
|
35604
|
-
commitTargetPaths: targets,
|
|
35605
|
-
sessionWorktreeRootPath: this.cache.getWorkingTreeRelRoot(sessionId),
|
|
35606
|
-
legacyRepoNestedSessionLayout: this.cache.isLegacyNestedLayout(sessionId),
|
|
35607
|
-
repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
|
|
35608
|
-
basis: opts?.basis,
|
|
35609
|
-
recentCommitsLimit: opts?.recentCommitsLimit
|
|
35610
|
-
});
|
|
35611
|
-
}
|
|
35612
|
-
async pushSessionUpstream(sessionId) {
|
|
35613
|
-
try {
|
|
35614
|
-
await pushAheadOfUpstreamForPaths(
|
|
35615
|
-
resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
|
|
35616
|
-
);
|
|
35617
|
-
return { ok: true };
|
|
35618
|
-
} catch (e) {
|
|
35619
|
-
const err = e instanceof Error ? e.message : String(e);
|
|
35620
|
-
return { ok: false, error: err };
|
|
35621
|
-
}
|
|
36101
|
+
const ctx = createSessionWorktreeManagerContext(options);
|
|
36102
|
+
Object.assign(this, createSessionWorktreePathsApi(ctx), createSessionWorktreeGitApi(ctx));
|
|
35622
36103
|
}
|
|
35623
36104
|
};
|
|
35624
36105
|
|
|
35625
36106
|
// src/worktrees/manager/default-worktrees-root-path.ts
|
|
35626
|
-
import * as
|
|
36107
|
+
import * as path42 from "node:path";
|
|
35627
36108
|
import os8 from "node:os";
|
|
35628
36109
|
function defaultWorktreesRootPath() {
|
|
35629
|
-
return
|
|
36110
|
+
return path42.join(os8.homedir(), ".buildautomaton", "worktrees");
|
|
35630
36111
|
}
|
|
35631
36112
|
|
|
35632
36113
|
// src/files/watch-file-index.ts
|
|
35633
36114
|
import { watch } from "node:fs";
|
|
35634
|
-
import
|
|
36115
|
+
import path47 from "node:path";
|
|
35635
36116
|
|
|
35636
36117
|
// src/files/index/paths.ts
|
|
35637
|
-
import
|
|
36118
|
+
import path43 from "node:path";
|
|
35638
36119
|
import crypto2 from "node:crypto";
|
|
35639
36120
|
function getCwdHashForFileIndex(resolvedCwd) {
|
|
35640
|
-
return crypto2.createHash("sha256").update(
|
|
36121
|
+
return crypto2.createHash("sha256").update(path43.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
|
|
35641
36122
|
}
|
|
35642
36123
|
|
|
35643
36124
|
// src/files/index/build-file-index.ts
|
|
35644
|
-
import
|
|
36125
|
+
import path45 from "node:path";
|
|
35645
36126
|
|
|
35646
36127
|
// src/files/index/walk-workspace-tree.ts
|
|
35647
|
-
import
|
|
35648
|
-
import
|
|
36128
|
+
import fs29 from "node:fs";
|
|
36129
|
+
import path44 from "node:path";
|
|
35649
36130
|
var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
35650
36131
|
"node_modules",
|
|
35651
36132
|
"bower_components",
|
|
@@ -35662,7 +36143,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
|
|
|
35662
36143
|
async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
|
|
35663
36144
|
let names;
|
|
35664
36145
|
try {
|
|
35665
|
-
names = await
|
|
36146
|
+
names = await fs29.promises.readdir(dir);
|
|
35666
36147
|
} catch {
|
|
35667
36148
|
return;
|
|
35668
36149
|
}
|
|
@@ -35674,18 +36155,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
|
|
|
35674
36155
|
if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
|
|
35675
36156
|
}
|
|
35676
36157
|
state.n++;
|
|
35677
|
-
const full =
|
|
36158
|
+
const full = path44.join(dir, name);
|
|
35678
36159
|
let stat2;
|
|
35679
36160
|
try {
|
|
35680
|
-
stat2 = await
|
|
36161
|
+
stat2 = await fs29.promises.stat(full);
|
|
35681
36162
|
} catch {
|
|
35682
36163
|
continue;
|
|
35683
36164
|
}
|
|
35684
|
-
const
|
|
36165
|
+
const relative7 = path44.relative(baseDir, full).replace(/\\/g, "/");
|
|
35685
36166
|
if (stat2.isDirectory()) {
|
|
35686
36167
|
await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
|
|
35687
36168
|
} else if (stat2.isFile()) {
|
|
35688
|
-
onFile(
|
|
36169
|
+
onFile(relative7);
|
|
35689
36170
|
}
|
|
35690
36171
|
}
|
|
35691
36172
|
}
|
|
@@ -35694,7 +36175,7 @@ function createWalkYieldState() {
|
|
|
35694
36175
|
}
|
|
35695
36176
|
|
|
35696
36177
|
// src/files/index/file-index-sqlite-lock.ts
|
|
35697
|
-
import
|
|
36178
|
+
import fs30 from "node:fs";
|
|
35698
36179
|
function isSqliteCorruptError(e) {
|
|
35699
36180
|
const msg = e instanceof Error ? e.message : String(e);
|
|
35700
36181
|
return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
|
|
@@ -35708,7 +36189,7 @@ function withFileIndexSqliteLock(fn) {
|
|
|
35708
36189
|
if (!isSqliteCorruptError(e)) throw e;
|
|
35709
36190
|
closeAllCliSqliteConnections();
|
|
35710
36191
|
try {
|
|
35711
|
-
|
|
36192
|
+
fs30.unlinkSync(getCliSqlitePath());
|
|
35712
36193
|
} catch {
|
|
35713
36194
|
}
|
|
35714
36195
|
chain = Promise.resolve();
|
|
@@ -35783,7 +36264,7 @@ async function collectWorkspacePathsAsync(resolved) {
|
|
|
35783
36264
|
}
|
|
35784
36265
|
async function buildFileIndexAsync(cwd) {
|
|
35785
36266
|
return withFileIndexSqliteLock(async () => {
|
|
35786
|
-
const resolved =
|
|
36267
|
+
const resolved = path45.resolve(cwd);
|
|
35787
36268
|
await yieldToEventLoop();
|
|
35788
36269
|
assertNotShutdown();
|
|
35789
36270
|
const paths = await collectWorkspacePathsAsync(resolved);
|
|
@@ -35795,7 +36276,7 @@ async function buildFileIndexAsync(cwd) {
|
|
|
35795
36276
|
}
|
|
35796
36277
|
|
|
35797
36278
|
// src/files/index/ensure-file-index.ts
|
|
35798
|
-
import
|
|
36279
|
+
import path46 from "node:path";
|
|
35799
36280
|
|
|
35800
36281
|
// src/files/index/search-file-index.ts
|
|
35801
36282
|
function escapeLikePattern(fragment) {
|
|
@@ -35847,7 +36328,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
|
|
|
35847
36328
|
|
|
35848
36329
|
// src/files/index/ensure-file-index.ts
|
|
35849
36330
|
async function ensureFileIndexAsync(cwd) {
|
|
35850
|
-
const resolved =
|
|
36331
|
+
const resolved = path46.resolve(cwd);
|
|
35851
36332
|
if (await bridgeFileIndexIsPopulated(resolved)) {
|
|
35852
36333
|
return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
|
|
35853
36334
|
}
|
|
@@ -35891,7 +36372,7 @@ function createFsWatcher(resolved, schedule) {
|
|
|
35891
36372
|
}
|
|
35892
36373
|
}
|
|
35893
36374
|
function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
35894
|
-
const resolved =
|
|
36375
|
+
const resolved = path47.resolve(cwd);
|
|
35895
36376
|
void buildFileIndexAsync(resolved).catch((e) => {
|
|
35896
36377
|
if (e instanceof CliSqliteInterrupted) return;
|
|
35897
36378
|
console.error("[file-index] Initial index build failed:", e);
|
|
@@ -35921,7 +36402,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
|
35921
36402
|
}
|
|
35922
36403
|
|
|
35923
36404
|
// src/connection/create-bridge-connection.ts
|
|
35924
|
-
import * as
|
|
36405
|
+
import * as path59 from "node:path";
|
|
35925
36406
|
|
|
35926
36407
|
// src/dev-servers/manager/dev-server-manager.ts
|
|
35927
36408
|
import { rm as rm2 } from "node:fs/promises";
|
|
@@ -35943,15 +36424,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
|
|
|
35943
36424
|
|
|
35944
36425
|
// src/dev-servers/process/terminate-child-process.ts
|
|
35945
36426
|
async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
35946
|
-
const exited = new Promise((
|
|
35947
|
-
proc.once("exit", () =>
|
|
36427
|
+
const exited = new Promise((resolve32) => {
|
|
36428
|
+
proc.once("exit", () => resolve32());
|
|
35948
36429
|
});
|
|
35949
36430
|
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
|
|
35950
36431
|
try {
|
|
35951
36432
|
proc.kill("SIGTERM");
|
|
35952
36433
|
} catch {
|
|
35953
36434
|
}
|
|
35954
|
-
await Promise.race([exited, new Promise((
|
|
36435
|
+
await Promise.race([exited, new Promise((resolve32) => setTimeout(resolve32, graceMs))]);
|
|
35955
36436
|
}
|
|
35956
36437
|
function forceKillChild(proc, log2, shortId, graceMs) {
|
|
35957
36438
|
log2(
|
|
@@ -35965,7 +36446,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
|
|
|
35965
36446
|
}
|
|
35966
36447
|
|
|
35967
36448
|
// src/dev-servers/process/wire-dev-server-child-process.ts
|
|
35968
|
-
import
|
|
36449
|
+
import fs31 from "node:fs";
|
|
35969
36450
|
|
|
35970
36451
|
// src/dev-servers/manager/forward-pipe.ts
|
|
35971
36452
|
function forwardChildPipe(childReadable, terminal, onData) {
|
|
@@ -36001,7 +36482,7 @@ function wireDevServerChildProcess(d) {
|
|
|
36001
36482
|
d.setPollInterval(void 0);
|
|
36002
36483
|
return;
|
|
36003
36484
|
}
|
|
36004
|
-
|
|
36485
|
+
fs31.readFile(d.mergedLogPath, (err, buf) => {
|
|
36005
36486
|
if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
|
|
36006
36487
|
if (buf.length <= d.mergedReadPos.value) return;
|
|
36007
36488
|
const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
|
|
@@ -36039,7 +36520,7 @@ ${errTail}` : ""}`);
|
|
|
36039
36520
|
d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
|
|
36040
36521
|
};
|
|
36041
36522
|
if (mergedPath) {
|
|
36042
|
-
|
|
36523
|
+
fs31.readFile(mergedPath, (err, buf) => {
|
|
36043
36524
|
if (!err && buf.length > d.mergedReadPos.value) {
|
|
36044
36525
|
const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
|
|
36045
36526
|
if (chunk.length > 0) {
|
|
@@ -36141,13 +36622,13 @@ function parseDevServerDefs(servers) {
|
|
|
36141
36622
|
}
|
|
36142
36623
|
|
|
36143
36624
|
// src/dev-servers/manager/shell-spawn/utils.ts
|
|
36144
|
-
import
|
|
36625
|
+
import fs32 from "node:fs";
|
|
36145
36626
|
function isSpawnEbadf(e) {
|
|
36146
36627
|
return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
|
|
36147
36628
|
}
|
|
36148
36629
|
function rmDirQuiet(dir) {
|
|
36149
36630
|
try {
|
|
36150
|
-
|
|
36631
|
+
fs32.rmSync(dir, { recursive: true, force: true });
|
|
36151
36632
|
} catch {
|
|
36152
36633
|
}
|
|
36153
36634
|
}
|
|
@@ -36155,7 +36636,7 @@ var cachedDevNullReadFd;
|
|
|
36155
36636
|
function devNullReadFd() {
|
|
36156
36637
|
if (cachedDevNullReadFd === void 0) {
|
|
36157
36638
|
const devPath = process.platform === "win32" ? "nul" : "/dev/null";
|
|
36158
|
-
cachedDevNullReadFd =
|
|
36639
|
+
cachedDevNullReadFd = fs32.openSync(devPath, "r");
|
|
36159
36640
|
}
|
|
36160
36641
|
return cachedDevNullReadFd;
|
|
36161
36642
|
}
|
|
@@ -36229,15 +36710,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
|
36229
36710
|
|
|
36230
36711
|
// src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
|
|
36231
36712
|
import { spawn as spawn8 } from "node:child_process";
|
|
36232
|
-
import
|
|
36713
|
+
import fs33 from "node:fs";
|
|
36233
36714
|
import { tmpdir } from "node:os";
|
|
36234
|
-
import
|
|
36715
|
+
import path48 from "node:path";
|
|
36235
36716
|
function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
36236
|
-
const tmpRoot =
|
|
36237
|
-
const logPath =
|
|
36717
|
+
const tmpRoot = fs33.mkdtempSync(path48.join(tmpdir(), "ba-devsrv-log-"));
|
|
36718
|
+
const logPath = path48.join(tmpRoot, "combined.log");
|
|
36238
36719
|
let logFd;
|
|
36239
36720
|
try {
|
|
36240
|
-
logFd =
|
|
36721
|
+
logFd = fs33.openSync(logPath, "a");
|
|
36241
36722
|
} catch {
|
|
36242
36723
|
rmDirQuiet(tmpRoot);
|
|
36243
36724
|
return null;
|
|
@@ -36256,7 +36737,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
36256
36737
|
} else {
|
|
36257
36738
|
proc = spawn8("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
|
|
36258
36739
|
}
|
|
36259
|
-
|
|
36740
|
+
fs33.closeSync(logFd);
|
|
36260
36741
|
return {
|
|
36261
36742
|
proc,
|
|
36262
36743
|
pipedStdoutStderr: true,
|
|
@@ -36265,7 +36746,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
36265
36746
|
};
|
|
36266
36747
|
} catch (e) {
|
|
36267
36748
|
try {
|
|
36268
|
-
|
|
36749
|
+
fs33.closeSync(logFd);
|
|
36269
36750
|
} catch {
|
|
36270
36751
|
}
|
|
36271
36752
|
rmDirQuiet(tmpRoot);
|
|
@@ -36276,22 +36757,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
36276
36757
|
|
|
36277
36758
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
|
|
36278
36759
|
import { spawn as spawn9 } from "node:child_process";
|
|
36279
|
-
import
|
|
36760
|
+
import fs34 from "node:fs";
|
|
36280
36761
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
36281
|
-
import
|
|
36762
|
+
import path49 from "node:path";
|
|
36282
36763
|
function shSingleQuote(s) {
|
|
36283
36764
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
36284
36765
|
}
|
|
36285
36766
|
function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
|
|
36286
|
-
const tmpRoot =
|
|
36287
|
-
const logPath =
|
|
36288
|
-
const innerPath =
|
|
36289
|
-
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");
|
|
36290
36771
|
try {
|
|
36291
|
-
|
|
36772
|
+
fs34.writeFileSync(innerPath, `#!/bin/sh
|
|
36292
36773
|
${command}
|
|
36293
36774
|
`);
|
|
36294
|
-
|
|
36775
|
+
fs34.writeFileSync(
|
|
36295
36776
|
runnerPath,
|
|
36296
36777
|
`#!/bin/sh
|
|
36297
36778
|
cd ${shSingleQuote(cwd)}
|
|
@@ -36317,13 +36798,13 @@ cd ${shSingleQuote(cwd)}
|
|
|
36317
36798
|
}
|
|
36318
36799
|
}
|
|
36319
36800
|
function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
|
|
36320
|
-
const tmpRoot =
|
|
36321
|
-
const logPath =
|
|
36322
|
-
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");
|
|
36323
36804
|
const q = (p) => `"${p.replace(/"/g, '""')}"`;
|
|
36324
36805
|
const com = process.env.ComSpec || "cmd.exe";
|
|
36325
36806
|
try {
|
|
36326
|
-
|
|
36807
|
+
fs34.writeFileSync(
|
|
36327
36808
|
runnerPath,
|
|
36328
36809
|
`@ECHO OFF\r
|
|
36329
36810
|
CD /D ${q(cwd)}\r
|
|
@@ -37091,61 +37572,84 @@ function createOnBridgeIdentified(opts) {
|
|
|
37091
37572
|
};
|
|
37092
37573
|
}
|
|
37093
37574
|
|
|
37094
|
-
// src/skills/
|
|
37095
|
-
import fs31 from "node:fs";
|
|
37096
|
-
import path37 from "node:path";
|
|
37575
|
+
// src/skills/discovery/skill-discovery-roots.ts
|
|
37097
37576
|
var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
|
|
37098
|
-
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) {
|
|
37099
37601
|
const out = [];
|
|
37100
37602
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
37603
|
+
let work = 0;
|
|
37101
37604
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
37102
|
-
|
|
37103
|
-
|
|
37605
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37606
|
+
const base = path50.join(cwd, rel);
|
|
37607
|
+
if (!fs35.existsSync(base) || !fs35.statSync(base).isDirectory()) continue;
|
|
37104
37608
|
let entries = [];
|
|
37105
37609
|
try {
|
|
37106
|
-
entries =
|
|
37610
|
+
entries = fs35.readdirSync(base);
|
|
37107
37611
|
} catch {
|
|
37108
37612
|
continue;
|
|
37109
37613
|
}
|
|
37110
|
-
for (
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
if (!fs31.statSync(dir).isDirectory()) continue;
|
|
37114
|
-
} catch {
|
|
37115
|
-
continue;
|
|
37116
|
-
}
|
|
37117
|
-
const skillMd = path37.join(dir, "SKILL.md");
|
|
37118
|
-
if (!fs31.existsSync(skillMd)) continue;
|
|
37119
|
-
const key = `${rel}/${name}`;
|
|
37120
|
-
if (seenKeys.has(key)) continue;
|
|
37121
|
-
seenKeys.add(key);
|
|
37122
|
-
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);
|
|
37123
37617
|
}
|
|
37124
37618
|
}
|
|
37125
37619
|
return out;
|
|
37126
37620
|
}
|
|
37127
|
-
|
|
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) {
|
|
37128
37637
|
const roots = [];
|
|
37638
|
+
let work = 0;
|
|
37129
37639
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
37130
|
-
|
|
37131
|
-
|
|
37640
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37641
|
+
const base = path51.join(cwd, rel);
|
|
37642
|
+
if (!fs36.existsSync(base) || !fs36.statSync(base).isDirectory()) continue;
|
|
37132
37643
|
let entries = [];
|
|
37133
37644
|
try {
|
|
37134
|
-
entries =
|
|
37645
|
+
entries = fs36.readdirSync(base);
|
|
37135
37646
|
} catch {
|
|
37136
37647
|
continue;
|
|
37137
37648
|
}
|
|
37138
37649
|
const skills2 = [];
|
|
37139
|
-
for (
|
|
37140
|
-
|
|
37141
|
-
|
|
37142
|
-
if (!fs31.statSync(dir).isDirectory()) continue;
|
|
37143
|
-
} catch {
|
|
37144
|
-
continue;
|
|
37145
|
-
}
|
|
37146
|
-
if (!fs31.existsSync(path37.join(dir, "SKILL.md"))) continue;
|
|
37147
|
-
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
37148
|
-
skills2.push({ name, relPath });
|
|
37650
|
+
for (let i = 0; i < entries.length; i++) {
|
|
37651
|
+
await yieldSkillDiscoveryWork(++work);
|
|
37652
|
+
collectLayoutSkill(rel, base, entries[i], skills2);
|
|
37149
37653
|
}
|
|
37150
37654
|
if (skills2.length > 0) {
|
|
37151
37655
|
roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
|
|
@@ -37187,16 +37691,18 @@ async function detectLocalAgentTypes() {
|
|
|
37187
37691
|
function createSendLocalSkillsReport(getWs, logFn) {
|
|
37188
37692
|
return () => {
|
|
37189
37693
|
setImmediate(() => {
|
|
37190
|
-
|
|
37191
|
-
|
|
37192
|
-
|
|
37193
|
-
|
|
37194
|
-
|
|
37195
|
-
|
|
37196
|
-
|
|
37197
|
-
|
|
37198
|
-
|
|
37199
|
-
|
|
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
|
+
})();
|
|
37200
37706
|
});
|
|
37201
37707
|
};
|
|
37202
37708
|
}
|
|
@@ -37499,7 +38005,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
|
|
|
37499
38005
|
}
|
|
37500
38006
|
|
|
37501
38007
|
// src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
|
|
37502
|
-
import
|
|
38008
|
+
import fs37 from "node:fs";
|
|
37503
38009
|
async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
37504
38010
|
if (next.serverState !== "requeued_with_revert") return true;
|
|
37505
38011
|
const sid = next.sessionId;
|
|
@@ -37508,7 +38014,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
|
37508
38014
|
const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
|
|
37509
38015
|
const file2 = snapshotFilePath(agentBase, tid);
|
|
37510
38016
|
try {
|
|
37511
|
-
await
|
|
38017
|
+
await fs37.promises.access(file2, fs37.constants.F_OK);
|
|
37512
38018
|
} catch {
|
|
37513
38019
|
deps.log(
|
|
37514
38020
|
`[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
|
|
@@ -37623,7 +38129,7 @@ function createBridgePromptSenders(deps, getWs) {
|
|
|
37623
38129
|
sendWsMessage(s, wire);
|
|
37624
38130
|
return true;
|
|
37625
38131
|
};
|
|
37626
|
-
const
|
|
38132
|
+
const sendResult = (result) => {
|
|
37627
38133
|
const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
|
|
37628
38134
|
const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
|
|
37629
38135
|
sendBridgeMessage(result, encryptedFields);
|
|
@@ -37656,7 +38162,7 @@ function createBridgePromptSenders(deps, getWs) {
|
|
|
37656
38162
|
]) : payload;
|
|
37657
38163
|
sendWsMessage(s, wire);
|
|
37658
38164
|
};
|
|
37659
|
-
return { sendBridgeMessage, sendResult
|
|
38165
|
+
return { sendBridgeMessage, sendResult, sendSessionUpdate };
|
|
37660
38166
|
}
|
|
37661
38167
|
|
|
37662
38168
|
// src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
|
|
@@ -37760,7 +38266,7 @@ async function readGitBranch(cwd) {
|
|
|
37760
38266
|
async function runBridgePromptPreamble(params) {
|
|
37761
38267
|
const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
|
|
37762
38268
|
const s = getWs();
|
|
37763
|
-
const repoCheckoutPaths = sessionWorktreeManager.
|
|
38269
|
+
const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
|
|
37764
38270
|
const repoRoots = await resolveSnapshotRepoRoots({
|
|
37765
38271
|
worktreePaths: repoCheckoutPaths,
|
|
37766
38272
|
fallbackCwd: effectiveCwd,
|
|
@@ -37796,9 +38302,9 @@ function parseChangeSummarySnapshots(raw) {
|
|
|
37796
38302
|
for (const item of raw) {
|
|
37797
38303
|
if (!item || typeof item !== "object") continue;
|
|
37798
38304
|
const o = item;
|
|
37799
|
-
const
|
|
37800
|
-
if (!
|
|
37801
|
-
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 };
|
|
37802
38308
|
if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
|
|
37803
38309
|
if (typeof o.oldText === "string") row.oldText = o.oldText;
|
|
37804
38310
|
if (typeof o.newText === "string") row.newText = o.newText;
|
|
@@ -37832,7 +38338,7 @@ async function runPreambleAndPrompt(params) {
|
|
|
37832
38338
|
agentId,
|
|
37833
38339
|
agentConfig,
|
|
37834
38340
|
resolvedCwd,
|
|
37835
|
-
senders: { sendResult
|
|
38341
|
+
senders: { sendResult, sendSessionUpdate }
|
|
37836
38342
|
} = params;
|
|
37837
38343
|
const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
|
|
37838
38344
|
await runBridgePromptPreamble({
|
|
@@ -37870,7 +38376,7 @@ async function runPreambleAndPrompt(params) {
|
|
|
37870
38376
|
agentId,
|
|
37871
38377
|
agentConfig,
|
|
37872
38378
|
sessionParentPath: effectiveCwd,
|
|
37873
|
-
sendResult
|
|
38379
|
+
sendResult,
|
|
37874
38380
|
sendSessionUpdate,
|
|
37875
38381
|
followUpCatalogPromptId,
|
|
37876
38382
|
sessionChangeSummaryFilePaths: pathsForUpload,
|
|
@@ -38012,14 +38518,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
|
|
|
38012
38518
|
};
|
|
38013
38519
|
|
|
38014
38520
|
// src/files/list-dir/index.ts
|
|
38015
|
-
import
|
|
38521
|
+
import fs39 from "node:fs";
|
|
38016
38522
|
|
|
38017
38523
|
// src/files/ensure-under-cwd.ts
|
|
38018
|
-
import
|
|
38524
|
+
import path52 from "node:path";
|
|
38019
38525
|
function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
38020
|
-
const normalized =
|
|
38021
|
-
const resolved =
|
|
38022
|
-
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) {
|
|
38023
38529
|
return null;
|
|
38024
38530
|
}
|
|
38025
38531
|
return resolved;
|
|
@@ -38029,15 +38535,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
|
38029
38535
|
var LIST_DIR_YIELD_EVERY = 256;
|
|
38030
38536
|
|
|
38031
38537
|
// src/files/list-dir/map-dir-entry.ts
|
|
38032
|
-
import
|
|
38033
|
-
import
|
|
38538
|
+
import path53 from "node:path";
|
|
38539
|
+
import fs38 from "node:fs";
|
|
38034
38540
|
async function mapDirEntry(d, relativePath, resolved) {
|
|
38035
|
-
const entryPath =
|
|
38036
|
-
const fullPath =
|
|
38541
|
+
const entryPath = path53.join(relativePath || ".", d.name).replace(/\\/g, "/");
|
|
38542
|
+
const fullPath = path53.join(resolved, d.name);
|
|
38037
38543
|
let isDir = d.isDirectory();
|
|
38038
38544
|
if (d.isSymbolicLink()) {
|
|
38039
38545
|
try {
|
|
38040
|
-
const targetStat = await
|
|
38546
|
+
const targetStat = await fs38.promises.stat(fullPath);
|
|
38041
38547
|
isDir = targetStat.isDirectory();
|
|
38042
38548
|
} catch {
|
|
38043
38549
|
isDir = false;
|
|
@@ -38067,7 +38573,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
|
38067
38573
|
return { error: "Path is outside working directory" };
|
|
38068
38574
|
}
|
|
38069
38575
|
try {
|
|
38070
|
-
const names = await
|
|
38576
|
+
const names = await fs39.promises.readdir(resolved, { withFileTypes: true });
|
|
38071
38577
|
const entries = [];
|
|
38072
38578
|
for (let i = 0; i < names.length; i++) {
|
|
38073
38579
|
if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
|
|
@@ -38087,18 +38593,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
|
|
|
38087
38593
|
var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
|
|
38088
38594
|
|
|
38089
38595
|
// src/files/read-file/resolve-file-path.ts
|
|
38090
|
-
import
|
|
38596
|
+
import fs40 from "node:fs";
|
|
38091
38597
|
async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
38092
38598
|
const resolved = ensureUnderCwd(relativePath, sessionParentPath);
|
|
38093
38599
|
if (!resolved) return { error: "Path is outside working directory" };
|
|
38094
38600
|
let real;
|
|
38095
38601
|
try {
|
|
38096
|
-
real = await
|
|
38602
|
+
real = await fs40.promises.realpath(resolved);
|
|
38097
38603
|
} catch {
|
|
38098
38604
|
real = resolved;
|
|
38099
38605
|
}
|
|
38100
38606
|
try {
|
|
38101
|
-
const stat2 = await
|
|
38607
|
+
const stat2 = await fs40.promises.stat(real);
|
|
38102
38608
|
if (!stat2.isFile()) return { error: "Not a file" };
|
|
38103
38609
|
return real;
|
|
38104
38610
|
} catch (err) {
|
|
@@ -38107,11 +38613,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
|
|
|
38107
38613
|
}
|
|
38108
38614
|
|
|
38109
38615
|
// src/files/read-file/read-file-range-async.ts
|
|
38110
|
-
import
|
|
38616
|
+
import fs41 from "node:fs";
|
|
38111
38617
|
import { StringDecoder } from "node:string_decoder";
|
|
38112
38618
|
async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
|
|
38113
|
-
const fileSize = (await
|
|
38114
|
-
const fd = await
|
|
38619
|
+
const fileSize = (await fs41.promises.stat(filePath)).size;
|
|
38620
|
+
const fd = await fs41.promises.open(filePath, "r");
|
|
38115
38621
|
const bufSize = 64 * 1024;
|
|
38116
38622
|
const buf = Buffer.alloc(bufSize);
|
|
38117
38623
|
const decoder = new StringDecoder("utf8");
|
|
@@ -38273,11 +38779,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
|
|
|
38273
38779
|
}
|
|
38274
38780
|
|
|
38275
38781
|
// src/files/read-file/read-file-buffer-full-async.ts
|
|
38276
|
-
import
|
|
38782
|
+
import fs42 from "node:fs";
|
|
38277
38783
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
38278
38784
|
async function readFileBufferFullAsync(filePath) {
|
|
38279
|
-
const stat2 = await
|
|
38280
|
-
const fd = await
|
|
38785
|
+
const stat2 = await fs42.promises.stat(filePath);
|
|
38786
|
+
const fd = await fs42.promises.open(filePath, "r");
|
|
38281
38787
|
const chunks = [];
|
|
38282
38788
|
let position = 0;
|
|
38283
38789
|
let bytesSinceYield = 0;
|
|
@@ -38373,10 +38879,10 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
|
|
|
38373
38879
|
}
|
|
38374
38880
|
|
|
38375
38881
|
// src/files/resolve-file-browser-session-parent.ts
|
|
38376
|
-
function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
38882
|
+
async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
38377
38883
|
const sid = sessionId?.trim();
|
|
38378
38884
|
if (sid) {
|
|
38379
|
-
sessionWorktreeManager.
|
|
38885
|
+
await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
|
|
38380
38886
|
const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
|
|
38381
38887
|
if (worktreeRoot) return worktreeRoot;
|
|
38382
38888
|
}
|
|
@@ -38384,14 +38890,14 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
|
38384
38890
|
}
|
|
38385
38891
|
|
|
38386
38892
|
// src/files/handle-file-browser-search.ts
|
|
38387
|
-
import
|
|
38893
|
+
import path54 from "node:path";
|
|
38388
38894
|
var SEARCH_LIMIT = 100;
|
|
38389
38895
|
function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
|
|
38390
38896
|
void (async () => {
|
|
38391
38897
|
await yieldToEventLoop();
|
|
38392
38898
|
const q = typeof msg.q === "string" ? msg.q : "";
|
|
38393
|
-
const sessionParentPath =
|
|
38394
|
-
sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
|
|
38899
|
+
const sessionParentPath = path54.resolve(
|
|
38900
|
+
sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
|
|
38395
38901
|
);
|
|
38396
38902
|
if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
|
|
38397
38903
|
const payload2 = {
|
|
@@ -38429,7 +38935,7 @@ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
|
|
|
38429
38935
|
void (async () => {
|
|
38430
38936
|
const reqPath = msg.path.replace(/^\/+/, "") || ".";
|
|
38431
38937
|
const op = msg.op === "read" ? "read" : "list";
|
|
38432
|
-
const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
|
|
38938
|
+
const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
|
|
38433
38939
|
if (op === "list") {
|
|
38434
38940
|
const result = await listDirAsync(reqPath, sessionParentPath);
|
|
38435
38941
|
if ("error" in result) {
|
|
@@ -38500,37 +39006,56 @@ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManag
|
|
|
38500
39006
|
|
|
38501
39007
|
// src/routing/handlers/skill-layout-request.ts
|
|
38502
39008
|
function handleSkillLayoutRequest(msg, deps) {
|
|
38503
|
-
const socket = deps.getWs();
|
|
38504
39009
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38505
|
-
|
|
38506
|
-
|
|
38507
|
-
|
|
38508
|
-
|
|
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
|
+
})();
|
|
38509
39017
|
}
|
|
38510
39018
|
|
|
38511
|
-
// src/skills/install-
|
|
38512
|
-
|
|
38513
|
-
|
|
38514
|
-
|
|
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) {
|
|
38515
39048
|
const installed2 = [];
|
|
38516
39049
|
if (!Array.isArray(items)) {
|
|
38517
39050
|
return { success: false, error: "Invalid items" };
|
|
38518
39051
|
}
|
|
39052
|
+
const filesWritten = { count: 0 };
|
|
38519
39053
|
try {
|
|
38520
39054
|
for (const item of items) {
|
|
38521
|
-
if (
|
|
38522
|
-
|
|
38523
|
-
}
|
|
38524
|
-
const skillDir = path41.join(cwd, targetDir, item.skillName);
|
|
39055
|
+
if (!isValidRemoteSkillInstallItem(item)) continue;
|
|
39056
|
+
const skillDir = path55.join(cwd, targetDir, item.skillName);
|
|
38525
39057
|
for (const f of item.files) {
|
|
38526
|
-
|
|
38527
|
-
const dest = path41.join(skillDir, f.path);
|
|
38528
|
-
fs38.mkdirSync(path41.dirname(dest), { recursive: true });
|
|
38529
|
-
if (f.text !== void 0) {
|
|
38530
|
-
fs38.writeFileSync(dest, f.text, "utf8");
|
|
38531
|
-
} else if (f.base64) {
|
|
38532
|
-
fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
|
|
38533
|
-
}
|
|
39058
|
+
await writeInstallFileAsync(skillDir, f, filesWritten);
|
|
38534
39059
|
}
|
|
38535
39060
|
installed2.push({
|
|
38536
39061
|
sourceId: item.sourceId,
|
|
@@ -38546,118 +39071,241 @@ function installRemoteSkills(cwd, targetDir, items) {
|
|
|
38546
39071
|
|
|
38547
39072
|
// src/routing/handlers/install-skills.ts
|
|
38548
39073
|
var handleInstallSkillsMessage = (msg, deps) => {
|
|
38549
|
-
const socket = deps.getWs();
|
|
38550
39074
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38551
39075
|
const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
|
|
38552
39076
|
const rawItems = msg.items;
|
|
38553
39077
|
const cwd = getBridgeRoot();
|
|
38554
|
-
|
|
38555
|
-
|
|
38556
|
-
const
|
|
38557
|
-
|
|
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
|
+
}
|
|
38558
39089
|
if (socket) {
|
|
38559
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success:
|
|
39090
|
+
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
38560
39091
|
}
|
|
38561
|
-
|
|
38562
|
-
}
|
|
38563
|
-
if (socket) {
|
|
38564
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
38565
|
-
}
|
|
39092
|
+
})();
|
|
38566
39093
|
};
|
|
38567
39094
|
|
|
38568
39095
|
// src/routing/handlers/refresh-local-skills.ts
|
|
38569
39096
|
var handleRefreshLocalSkills = (_msg, deps) => {
|
|
38570
|
-
|
|
39097
|
+
setImmediate(() => {
|
|
39098
|
+
deps.sendLocalSkillsReport?.();
|
|
39099
|
+
});
|
|
38571
39100
|
};
|
|
38572
39101
|
|
|
38573
|
-
// src/routing/handlers/session-git-
|
|
38574
|
-
function
|
|
39102
|
+
// src/routing/handlers/git/handle-session-git-commit.ts
|
|
39103
|
+
async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
|
|
39104
|
+
const branch = typeof msg.branch === "string" ? msg.branch : "";
|
|
39105
|
+
const message = typeof msg.message === "string" ? msg.message : "";
|
|
39106
|
+
const pushAfterCommit = msg.pushAfterCommit === true;
|
|
39107
|
+
if (!branch.trim() || !message.trim()) {
|
|
39108
|
+
reply({ ok: false, error: "branch and message are required for commit" });
|
|
39109
|
+
return;
|
|
39110
|
+
}
|
|
39111
|
+
const commitRes = await deps.sessionWorktreeManager.commitSession({
|
|
39112
|
+
sessionId,
|
|
39113
|
+
branch: branch.trim(),
|
|
39114
|
+
message: message.trim(),
|
|
39115
|
+
push: pushAfterCommit
|
|
39116
|
+
});
|
|
39117
|
+
if (!commitRes.ok) {
|
|
39118
|
+
reply({ ok: false, error: commitRes.error ?? "Commit failed" });
|
|
39119
|
+
return;
|
|
39120
|
+
}
|
|
39121
|
+
const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
39122
|
+
reply({
|
|
39123
|
+
ok: true,
|
|
39124
|
+
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
39125
|
+
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
39126
|
+
uncommittedFileCount: st.uncommittedFileCount
|
|
39127
|
+
});
|
|
39128
|
+
}
|
|
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
|
+
|
|
39191
|
+
// src/routing/handlers/git/coalesce-list-changes.ts
|
|
39192
|
+
var listChangesInflight = /* @__PURE__ */ new Map();
|
|
39193
|
+
function normalizeListChangesRepoRel(repoRelPath) {
|
|
39194
|
+
if (!repoRelPath?.trim()) return "";
|
|
39195
|
+
return normRepoRel(repoRelPath.trim());
|
|
39196
|
+
}
|
|
39197
|
+
function listChangesInflightKey(sessionId, opts) {
|
|
39198
|
+
const basis = opts?.basis;
|
|
39199
|
+
return [
|
|
39200
|
+
sessionId,
|
|
39201
|
+
normalizeListChangesRepoRel(opts?.repoRelPath),
|
|
39202
|
+
basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
|
|
39203
|
+
String(opts?.recentCommitsLimit ?? "")
|
|
39204
|
+
].join("|");
|
|
39205
|
+
}
|
|
39206
|
+
function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
|
|
39207
|
+
const key = listChangesInflightKey(sessionId, opts);
|
|
39208
|
+
const existing = listChangesInflight.get(key);
|
|
39209
|
+
if (existing) return existing;
|
|
39210
|
+
const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
|
|
39211
|
+
if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
|
|
39212
|
+
});
|
|
39213
|
+
listChangesInflight.set(key, promise2);
|
|
39214
|
+
return promise2;
|
|
39215
|
+
}
|
|
39216
|
+
|
|
39217
|
+
// src/routing/handlers/git/handle-session-git-list-changes.ts
|
|
39218
|
+
async function handleSessionGitListChangesAction(deps, msg, reply) {
|
|
39219
|
+
const changes = parseSessionGitChangesParams(msg);
|
|
39220
|
+
const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
|
|
39221
|
+
if (view === "commit") {
|
|
39222
|
+
if (!repoRel || !commitSha) {
|
|
39223
|
+
reply({
|
|
39224
|
+
ok: false,
|
|
39225
|
+
error: "changes.repoRelPath and changes.commitSha are required for commit view"
|
|
39226
|
+
});
|
|
39227
|
+
return;
|
|
39228
|
+
}
|
|
39229
|
+
}
|
|
39230
|
+
const opts = repoRel && view === "commit" && commitSha ? {
|
|
39231
|
+
repoRelPath: repoRel,
|
|
39232
|
+
basis: { kind: "commit", sha: commitSha },
|
|
39233
|
+
recentCommitsLimit
|
|
39234
|
+
} : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
|
|
39235
|
+
const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
|
|
39236
|
+
deps.sessionWorktreeManager,
|
|
39237
|
+
msg.sessionId,
|
|
39238
|
+
opts
|
|
39239
|
+
);
|
|
39240
|
+
reply({ ok: true, repos }, ["repos"]);
|
|
39241
|
+
}
|
|
39242
|
+
|
|
39243
|
+
// src/routing/handlers/git/handle-session-git-push.ts
|
|
39244
|
+
async function handleSessionGitPushAction(deps, sessionId, reply) {
|
|
39245
|
+
const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
|
|
39246
|
+
if (!pushRes.ok) {
|
|
39247
|
+
reply({ ok: false, error: pushRes.error ?? "Push failed" });
|
|
39248
|
+
return;
|
|
39249
|
+
}
|
|
39250
|
+
const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
39251
|
+
reply({
|
|
39252
|
+
ok: true,
|
|
39253
|
+
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
39254
|
+
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
39255
|
+
uncommittedFileCount: st.uncommittedFileCount
|
|
39256
|
+
});
|
|
39257
|
+
}
|
|
39258
|
+
|
|
39259
|
+
// src/routing/handlers/git/handle-session-git-status.ts
|
|
39260
|
+
async function handleSessionGitStatusAction(deps, sessionId, reply) {
|
|
39261
|
+
const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
39262
|
+
reply({
|
|
39263
|
+
ok: true,
|
|
39264
|
+
hasUncommittedChanges: r.hasUncommittedChanges,
|
|
39265
|
+
hasUnpushedCommits: r.hasUnpushedCommits,
|
|
39266
|
+
uncommittedFileCount: r.uncommittedFileCount
|
|
39267
|
+
});
|
|
39268
|
+
}
|
|
39269
|
+
|
|
39270
|
+
// src/routing/handlers/git/send-session-git-result.ts
|
|
39271
|
+
function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
|
|
38575
39272
|
if (!ws) return;
|
|
38576
39273
|
const message = { type: "session_git_result", id, ...payload };
|
|
38577
|
-
|
|
39274
|
+
let wire = message;
|
|
39275
|
+
if (e2ee && encryptedFields.length > 0) {
|
|
39276
|
+
wire = e2ee.encryptFields(message, encryptedFields);
|
|
39277
|
+
}
|
|
39278
|
+
sendWsMessage(ws, wire);
|
|
38578
39279
|
}
|
|
39280
|
+
|
|
39281
|
+
// src/routing/handlers/git/session-git-request.ts
|
|
38579
39282
|
var handleSessionGitRequestMessage = (msg, deps) => {
|
|
38580
39283
|
if (typeof msg.id !== "string") return;
|
|
38581
39284
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
38582
39285
|
const action = msg.action;
|
|
38583
|
-
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")
|
|
38584
39287
|
return;
|
|
38585
39288
|
void (async () => {
|
|
38586
39289
|
const ws = deps.getWs();
|
|
38587
|
-
const reply = (payload, encryptedFields = []) =>
|
|
39290
|
+
const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
|
|
38588
39291
|
try {
|
|
38589
39292
|
if (action === "status") {
|
|
38590
|
-
|
|
38591
|
-
reply({
|
|
38592
|
-
ok: true,
|
|
38593
|
-
hasUncommittedChanges: r.hasUncommittedChanges,
|
|
38594
|
-
hasUnpushedCommits: r.hasUnpushedCommits,
|
|
38595
|
-
uncommittedFileCount: r.uncommittedFileCount
|
|
38596
|
-
});
|
|
39293
|
+
await handleSessionGitStatusAction(deps, sessionId, reply);
|
|
38597
39294
|
return;
|
|
38598
39295
|
}
|
|
38599
39296
|
if (action === "list_changes") {
|
|
38600
|
-
|
|
38601
|
-
const view = msg.changesView === "commit" ? "commit" : "working";
|
|
38602
|
-
const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
|
|
38603
|
-
if (view === "commit") {
|
|
38604
|
-
if (!repoRel || !commitSha) {
|
|
38605
|
-
reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
|
|
38606
|
-
return;
|
|
38607
|
-
}
|
|
38608
|
-
}
|
|
38609
|
-
const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
|
|
38610
|
-
const opts = repoRel && view === "commit" && commitSha ? {
|
|
38611
|
-
repoRelPath: repoRel,
|
|
38612
|
-
basis: { kind: "commit", sha: commitSha },
|
|
38613
|
-
recentCommitsLimit
|
|
38614
|
-
} : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
|
|
38615
|
-
const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
|
|
38616
|
-
reply({
|
|
38617
|
-
ok: true,
|
|
38618
|
-
repos
|
|
38619
|
-
}, ["repos"]);
|
|
39297
|
+
await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
|
|
38620
39298
|
return;
|
|
38621
39299
|
}
|
|
38622
|
-
if (action === "
|
|
38623
|
-
|
|
38624
|
-
if (!pushRes.ok) {
|
|
38625
|
-
reply({ ok: false, error: pushRes.error ?? "Push failed" });
|
|
38626
|
-
return;
|
|
38627
|
-
}
|
|
38628
|
-
const st2 = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
38629
|
-
reply({
|
|
38630
|
-
ok: true,
|
|
38631
|
-
hasUncommittedChanges: st2.hasUncommittedChanges,
|
|
38632
|
-
hasUnpushedCommits: st2.hasUnpushedCommits,
|
|
38633
|
-
uncommittedFileCount: st2.uncommittedFileCount
|
|
38634
|
-
});
|
|
38635
|
-
return;
|
|
38636
|
-
}
|
|
38637
|
-
const branch = typeof msg.branch === "string" ? msg.branch : "";
|
|
38638
|
-
const message = typeof msg.message === "string" ? msg.message : "";
|
|
38639
|
-
const pushAfterCommit = msg.pushAfterCommit === true;
|
|
38640
|
-
if (!branch.trim() || !message.trim()) {
|
|
38641
|
-
reply({ ok: false, error: "branch and message are required for commit" });
|
|
39300
|
+
if (action === "file_diff") {
|
|
39301
|
+
await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
|
|
38642
39302
|
return;
|
|
38643
39303
|
}
|
|
38644
|
-
|
|
38645
|
-
sessionId,
|
|
38646
|
-
branch: branch.trim(),
|
|
38647
|
-
message: message.trim(),
|
|
38648
|
-
push: pushAfterCommit
|
|
38649
|
-
});
|
|
38650
|
-
if (!commitRes.ok) {
|
|
38651
|
-
reply({ ok: false, error: commitRes.error ?? "Commit failed" });
|
|
39304
|
+
if (action === "push") {
|
|
39305
|
+
await handleSessionGitPushAction(deps, sessionId, reply);
|
|
38652
39306
|
return;
|
|
38653
39307
|
}
|
|
38654
|
-
|
|
38655
|
-
reply({
|
|
38656
|
-
ok: true,
|
|
38657
|
-
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
38658
|
-
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
38659
|
-
uncommittedFileCount: st.uncommittedFileCount
|
|
38660
|
-
});
|
|
39308
|
+
await handleSessionGitCommitAction(deps, sessionId, msg, reply);
|
|
38661
39309
|
} catch (e) {
|
|
38662
39310
|
reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
38663
39311
|
}
|
|
@@ -38687,7 +39335,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
|
|
|
38687
39335
|
};
|
|
38688
39336
|
|
|
38689
39337
|
// src/routing/handlers/revert-turn-snapshot.ts
|
|
38690
|
-
import * as
|
|
39338
|
+
import * as fs44 from "node:fs";
|
|
38691
39339
|
var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
38692
39340
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
38693
39341
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
@@ -38700,7 +39348,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
|
38700
39348
|
const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
|
|
38701
39349
|
const file2 = snapshotFilePath(agentBase, turnId);
|
|
38702
39350
|
try {
|
|
38703
|
-
await
|
|
39351
|
+
await fs44.promises.access(file2, fs44.constants.F_OK);
|
|
38704
39352
|
} catch {
|
|
38705
39353
|
sendWsMessage(s, {
|
|
38706
39354
|
type: "revert_turn_snapshot_result",
|
|
@@ -38738,11 +39386,13 @@ var handleDevServerControl = (msg, deps) => {
|
|
|
38738
39386
|
// src/routing/handlers/dev-servers-config.ts
|
|
38739
39387
|
var handleDevServersConfig = (msg, deps) => {
|
|
38740
39388
|
const devServers = msg.devServers;
|
|
38741
|
-
|
|
39389
|
+
setImmediate(() => {
|
|
39390
|
+
deps.devServerManager?.applyConfig(devServers ?? []);
|
|
39391
|
+
});
|
|
38742
39392
|
};
|
|
38743
39393
|
|
|
38744
39394
|
// src/git/bridge-git-context.ts
|
|
38745
|
-
import * as
|
|
39395
|
+
import * as path56 from "node:path";
|
|
38746
39396
|
|
|
38747
39397
|
// src/git/branches/get-current-branch.ts
|
|
38748
39398
|
async function getCurrentBranch(repoPath) {
|
|
@@ -38792,12 +39442,12 @@ async function listRepoBranchRefs(repoPath) {
|
|
|
38792
39442
|
// src/git/bridge-git-context.ts
|
|
38793
39443
|
function folderNameForRelPath(relPath, bridgeRoot) {
|
|
38794
39444
|
if (relPath === "." || relPath === "") {
|
|
38795
|
-
return
|
|
39445
|
+
return path56.basename(path56.resolve(bridgeRoot)) || "repo";
|
|
38796
39446
|
}
|
|
38797
|
-
return
|
|
39447
|
+
return path56.basename(relPath) || relPath;
|
|
38798
39448
|
}
|
|
38799
39449
|
async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
38800
|
-
const root =
|
|
39450
|
+
const root = path56.resolve(bridgeRoot);
|
|
38801
39451
|
if (await isGitRepoDirectory(root)) {
|
|
38802
39452
|
const remoteUrl = await getRemoteOriginUrl(root);
|
|
38803
39453
|
return [{ absolutePath: root, remoteUrl }];
|
|
@@ -38805,17 +39455,19 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
|
38805
39455
|
const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
|
|
38806
39456
|
const byPath = /* @__PURE__ */ new Map();
|
|
38807
39457
|
for (const repo of [...deep, ...shallow]) {
|
|
38808
|
-
byPath.set(
|
|
39458
|
+
byPath.set(path56.resolve(repo.absolutePath), repo);
|
|
38809
39459
|
}
|
|
38810
39460
|
return [...byPath.values()];
|
|
38811
39461
|
}
|
|
38812
39462
|
async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
38813
|
-
const bridgeResolved =
|
|
39463
|
+
const bridgeResolved = path56.resolve(bridgeRoot);
|
|
38814
39464
|
const repos = await discoverGitReposForBridgeContext(bridgeResolved);
|
|
38815
39465
|
const rows = [];
|
|
38816
|
-
for (
|
|
38817
|
-
|
|
38818
|
-
|
|
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;
|
|
38819
39471
|
const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
|
|
38820
39472
|
const currentBranch = await getCurrentBranch(repo.absolutePath);
|
|
38821
39473
|
const remoteUrl = repo.remoteUrl.trim() || null;
|
|
@@ -38830,11 +39482,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
|
38830
39482
|
return rows;
|
|
38831
39483
|
}
|
|
38832
39484
|
async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
|
|
38833
|
-
const bridgeResolved =
|
|
39485
|
+
const bridgeResolved = path56.resolve(bridgeRoot);
|
|
38834
39486
|
const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
|
|
38835
|
-
const repoPath = rel === "" ? bridgeResolved :
|
|
38836
|
-
const resolved =
|
|
38837
|
-
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) {
|
|
38838
39490
|
return [];
|
|
38839
39491
|
}
|
|
38840
39492
|
return listRepoBranchRefs(resolved);
|
|
@@ -39418,10 +40070,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
|
|
|
39418
40070
|
}
|
|
39419
40071
|
|
|
39420
40072
|
// src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
|
|
39421
|
-
import * as
|
|
40073
|
+
import * as path58 from "node:path";
|
|
39422
40074
|
|
|
39423
40075
|
// src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
|
|
39424
|
-
import * as
|
|
40076
|
+
import * as path57 from "node:path";
|
|
39425
40077
|
async function probeOneAgentTypeForCapabilities(params) {
|
|
39426
40078
|
const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
|
|
39427
40079
|
const canContinue = () => shouldContinue?.() !== false;
|
|
@@ -39459,7 +40111,7 @@ async function probeOneAgentTypeForCapabilities(params) {
|
|
|
39459
40111
|
if (!canContinue()) return false;
|
|
39460
40112
|
handle = await resolved.createClient({
|
|
39461
40113
|
command: resolved.command,
|
|
39462
|
-
cwd:
|
|
40114
|
+
cwd: path57.resolve(cwd),
|
|
39463
40115
|
backendAgentType: agentType,
|
|
39464
40116
|
sessionMode: "agent",
|
|
39465
40117
|
persistedAcpSessionId: null,
|
|
@@ -39537,7 +40189,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
|
|
|
39537
40189
|
const { workspaceId, log: log2, getWs } = params;
|
|
39538
40190
|
const isCurrent = beginAgentCapabilityWarmupRun();
|
|
39539
40191
|
if (!isCurrent()) return;
|
|
39540
|
-
const cwd =
|
|
40192
|
+
const cwd = path58.resolve(getBridgeRoot());
|
|
39541
40193
|
async function sendBatchFromCache() {
|
|
39542
40194
|
if (!isCurrent()) return;
|
|
39543
40195
|
const socket = getWs();
|
|
@@ -39883,18 +40535,18 @@ import * as http from "node:http";
|
|
|
39883
40535
|
|
|
39884
40536
|
// src/mcp/bridge-access/read-json-body.ts
|
|
39885
40537
|
function readJsonBody(req) {
|
|
39886
|
-
return new Promise((
|
|
40538
|
+
return new Promise((resolve32, reject) => {
|
|
39887
40539
|
const chunks = [];
|
|
39888
40540
|
req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
39889
40541
|
req.on("end", () => {
|
|
39890
40542
|
try {
|
|
39891
40543
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
39892
40544
|
if (!raw) {
|
|
39893
|
-
|
|
40545
|
+
resolve32({});
|
|
39894
40546
|
return;
|
|
39895
40547
|
}
|
|
39896
40548
|
const parsed = JSON.parse(raw);
|
|
39897
|
-
|
|
40549
|
+
resolve32(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
|
|
39898
40550
|
} catch (e) {
|
|
39899
40551
|
reject(e);
|
|
39900
40552
|
}
|
|
@@ -39952,7 +40604,7 @@ function createBridgeAccessRequestHandler(registry2) {
|
|
|
39952
40604
|
// src/mcp/bridge-access/start-server.ts
|
|
39953
40605
|
function startBridgeAccessServer(registry2) {
|
|
39954
40606
|
const server = http.createServer(createBridgeAccessRequestHandler(registry2));
|
|
39955
|
-
return new Promise((
|
|
40607
|
+
return new Promise((resolve32, reject) => {
|
|
39956
40608
|
server.once("error", reject);
|
|
39957
40609
|
server.listen(0, "127.0.0.1", () => {
|
|
39958
40610
|
const addr = server.address();
|
|
@@ -39960,7 +40612,7 @@ function startBridgeAccessServer(registry2) {
|
|
|
39960
40612
|
reject(new Error("Bridge access server did not bind"));
|
|
39961
40613
|
return;
|
|
39962
40614
|
}
|
|
39963
|
-
|
|
40615
|
+
resolve32({
|
|
39964
40616
|
port: addr.port,
|
|
39965
40617
|
close: () => new Promise((closeResolve, closeReject) => {
|
|
39966
40618
|
server.close((err) => err ? closeReject(err) : closeResolve());
|
|
@@ -40100,8 +40752,8 @@ async function createBridgeConnection(options) {
|
|
|
40100
40752
|
getCloudAccessToken: () => tokens.accessToken
|
|
40101
40753
|
};
|
|
40102
40754
|
const identifyReportedPaths = {
|
|
40103
|
-
bridgeRootPath:
|
|
40104
|
-
worktreesRootPath:
|
|
40755
|
+
bridgeRootPath: path59.resolve(getBridgeRoot()),
|
|
40756
|
+
worktreesRootPath: path59.resolve(worktreesRootPath)
|
|
40105
40757
|
};
|
|
40106
40758
|
const { connect } = createMainBridgeWebSocketLifecycle({
|
|
40107
40759
|
state,
|