@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/cli.js
CHANGED
|
@@ -973,8 +973,8 @@ var require_command = __commonJS({
|
|
|
973
973
|
"../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports) {
|
|
974
974
|
var EventEmitter2 = __require("node:events").EventEmitter;
|
|
975
975
|
var childProcess2 = __require("node:child_process");
|
|
976
|
-
var
|
|
977
|
-
var
|
|
976
|
+
var path62 = __require("node:path");
|
|
977
|
+
var fs47 = __require("node:fs");
|
|
978
978
|
var process8 = __require("node:process");
|
|
979
979
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
980
980
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1906,11 +1906,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1906
1906
|
let launchWithNode = false;
|
|
1907
1907
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1908
1908
|
function findFile(baseDir, baseName) {
|
|
1909
|
-
const localBin =
|
|
1910
|
-
if (
|
|
1911
|
-
if (sourceExt.includes(
|
|
1909
|
+
const localBin = path62.resolve(baseDir, baseName);
|
|
1910
|
+
if (fs47.existsSync(localBin)) return localBin;
|
|
1911
|
+
if (sourceExt.includes(path62.extname(baseName))) return void 0;
|
|
1912
1912
|
const foundExt = sourceExt.find(
|
|
1913
|
-
(ext) =>
|
|
1913
|
+
(ext) => fs47.existsSync(`${localBin}${ext}`)
|
|
1914
1914
|
);
|
|
1915
1915
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1916
1916
|
return void 0;
|
|
@@ -1922,21 +1922,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1922
1922
|
if (this._scriptPath) {
|
|
1923
1923
|
let resolvedScriptPath;
|
|
1924
1924
|
try {
|
|
1925
|
-
resolvedScriptPath =
|
|
1925
|
+
resolvedScriptPath = fs47.realpathSync(this._scriptPath);
|
|
1926
1926
|
} catch (err) {
|
|
1927
1927
|
resolvedScriptPath = this._scriptPath;
|
|
1928
1928
|
}
|
|
1929
|
-
executableDir =
|
|
1930
|
-
|
|
1929
|
+
executableDir = path62.resolve(
|
|
1930
|
+
path62.dirname(resolvedScriptPath),
|
|
1931
1931
|
executableDir
|
|
1932
1932
|
);
|
|
1933
1933
|
}
|
|
1934
1934
|
if (executableDir) {
|
|
1935
1935
|
let localFile = findFile(executableDir, executableFile);
|
|
1936
1936
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1937
|
-
const legacyName =
|
|
1937
|
+
const legacyName = path62.basename(
|
|
1938
1938
|
this._scriptPath,
|
|
1939
|
-
|
|
1939
|
+
path62.extname(this._scriptPath)
|
|
1940
1940
|
);
|
|
1941
1941
|
if (legacyName !== this._name) {
|
|
1942
1942
|
localFile = findFile(
|
|
@@ -1947,7 +1947,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1947
1947
|
}
|
|
1948
1948
|
executableFile = localFile || executableFile;
|
|
1949
1949
|
}
|
|
1950
|
-
launchWithNode = sourceExt.includes(
|
|
1950
|
+
launchWithNode = sourceExt.includes(path62.extname(executableFile));
|
|
1951
1951
|
let proc;
|
|
1952
1952
|
if (process8.platform !== "win32") {
|
|
1953
1953
|
if (launchWithNode) {
|
|
@@ -2787,7 +2787,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2787
2787
|
* @return {Command}
|
|
2788
2788
|
*/
|
|
2789
2789
|
nameFromFilename(filename) {
|
|
2790
|
-
this._name =
|
|
2790
|
+
this._name = path62.basename(filename, path62.extname(filename));
|
|
2791
2791
|
return this;
|
|
2792
2792
|
}
|
|
2793
2793
|
/**
|
|
@@ -2801,9 +2801,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2801
2801
|
* @param {string} [path]
|
|
2802
2802
|
* @return {(string|null|Command)}
|
|
2803
2803
|
*/
|
|
2804
|
-
executableDir(
|
|
2805
|
-
if (
|
|
2806
|
-
this._executableDir =
|
|
2804
|
+
executableDir(path63) {
|
|
2805
|
+
if (path63 === void 0) return this._executableDir;
|
|
2806
|
+
this._executableDir = path63;
|
|
2807
2807
|
return this;
|
|
2808
2808
|
}
|
|
2809
2809
|
/**
|
|
@@ -7061,8 +7061,8 @@ var init_parseUtil = __esm({
|
|
|
7061
7061
|
init_errors();
|
|
7062
7062
|
init_en();
|
|
7063
7063
|
makeIssue = (params) => {
|
|
7064
|
-
const { data, path:
|
|
7065
|
-
const fullPath = [...
|
|
7064
|
+
const { data, path: path62, errorMaps, issueData } = params;
|
|
7065
|
+
const fullPath = [...path62, ...issueData.path || []];
|
|
7066
7066
|
const fullIssue = {
|
|
7067
7067
|
...issueData,
|
|
7068
7068
|
path: fullPath
|
|
@@ -7370,11 +7370,11 @@ var init_types = __esm({
|
|
|
7370
7370
|
init_parseUtil();
|
|
7371
7371
|
init_util();
|
|
7372
7372
|
ParseInputLazyPath = class {
|
|
7373
|
-
constructor(parent, value,
|
|
7373
|
+
constructor(parent, value, path62, key) {
|
|
7374
7374
|
this._cachedPath = [];
|
|
7375
7375
|
this.parent = parent;
|
|
7376
7376
|
this.data = value;
|
|
7377
|
-
this._path =
|
|
7377
|
+
this._path = path62;
|
|
7378
7378
|
this._key = key;
|
|
7379
7379
|
}
|
|
7380
7380
|
get path() {
|
|
@@ -11099,15 +11099,15 @@ function assignProp(target, prop, value) {
|
|
|
11099
11099
|
configurable: true
|
|
11100
11100
|
});
|
|
11101
11101
|
}
|
|
11102
|
-
function getElementAtPath(obj,
|
|
11103
|
-
if (!
|
|
11102
|
+
function getElementAtPath(obj, path62) {
|
|
11103
|
+
if (!path62)
|
|
11104
11104
|
return obj;
|
|
11105
|
-
return
|
|
11105
|
+
return path62.reduce((acc, key) => acc?.[key], obj);
|
|
11106
11106
|
}
|
|
11107
11107
|
function promiseAllObject(promisesObj) {
|
|
11108
11108
|
const keys = Object.keys(promisesObj);
|
|
11109
|
-
const
|
|
11110
|
-
return Promise.all(
|
|
11109
|
+
const promises11 = keys.map((key) => promisesObj[key]);
|
|
11110
|
+
return Promise.all(promises11).then((results) => {
|
|
11111
11111
|
const resolvedObj = {};
|
|
11112
11112
|
for (let i = 0; i < keys.length; i++) {
|
|
11113
11113
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -11351,11 +11351,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11351
11351
|
}
|
|
11352
11352
|
return false;
|
|
11353
11353
|
}
|
|
11354
|
-
function prefixIssues(
|
|
11354
|
+
function prefixIssues(path62, issues) {
|
|
11355
11355
|
return issues.map((iss) => {
|
|
11356
11356
|
var _a2;
|
|
11357
11357
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
11358
|
-
iss.path.unshift(
|
|
11358
|
+
iss.path.unshift(path62);
|
|
11359
11359
|
return iss;
|
|
11360
11360
|
});
|
|
11361
11361
|
}
|
|
@@ -11544,7 +11544,7 @@ function treeifyError(error40, _mapper) {
|
|
|
11544
11544
|
return issue2.message;
|
|
11545
11545
|
};
|
|
11546
11546
|
const result = { errors: [] };
|
|
11547
|
-
const processError = (error41,
|
|
11547
|
+
const processError = (error41, path62 = []) => {
|
|
11548
11548
|
var _a2, _b;
|
|
11549
11549
|
for (const issue2 of error41.issues) {
|
|
11550
11550
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -11554,7 +11554,7 @@ function treeifyError(error40, _mapper) {
|
|
|
11554
11554
|
} else if (issue2.code === "invalid_element") {
|
|
11555
11555
|
processError({ issues: issue2.issues }, issue2.path);
|
|
11556
11556
|
} else {
|
|
11557
|
-
const fullpath = [...
|
|
11557
|
+
const fullpath = [...path62, ...issue2.path];
|
|
11558
11558
|
if (fullpath.length === 0) {
|
|
11559
11559
|
result.errors.push(mapper(issue2));
|
|
11560
11560
|
continue;
|
|
@@ -11584,9 +11584,9 @@ function treeifyError(error40, _mapper) {
|
|
|
11584
11584
|
processError(error40);
|
|
11585
11585
|
return result;
|
|
11586
11586
|
}
|
|
11587
|
-
function toDotPath(
|
|
11587
|
+
function toDotPath(path62) {
|
|
11588
11588
|
const segs = [];
|
|
11589
|
-
for (const seg of
|
|
11589
|
+
for (const seg of path62) {
|
|
11590
11590
|
if (typeof seg === "number")
|
|
11591
11591
|
segs.push(`[${seg}]`);
|
|
11592
11592
|
else if (typeof seg === "symbol")
|
|
@@ -24049,8 +24049,8 @@ var init_acp = __esm({
|
|
|
24049
24049
|
this.#requestHandler = requestHandler;
|
|
24050
24050
|
this.#notificationHandler = notificationHandler;
|
|
24051
24051
|
this.#stream = stream;
|
|
24052
|
-
this.#closedPromise = new Promise((
|
|
24053
|
-
this.#abortController.signal.addEventListener("abort", () =>
|
|
24052
|
+
this.#closedPromise = new Promise((resolve34) => {
|
|
24053
|
+
this.#abortController.signal.addEventListener("abort", () => resolve34());
|
|
24054
24054
|
});
|
|
24055
24055
|
this.#receive();
|
|
24056
24056
|
}
|
|
@@ -24199,8 +24199,8 @@ var init_acp = __esm({
|
|
|
24199
24199
|
}
|
|
24200
24200
|
async sendRequest(method, params) {
|
|
24201
24201
|
const id = this.#nextRequestId++;
|
|
24202
|
-
const responsePromise = new Promise((
|
|
24203
|
-
this.#pendingResponses.set(id, { resolve:
|
|
24202
|
+
const responsePromise = new Promise((resolve34, reject) => {
|
|
24203
|
+
this.#pendingResponses.set(id, { resolve: resolve34, reject });
|
|
24204
24204
|
});
|
|
24205
24205
|
await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
|
|
24206
24206
|
return responsePromise;
|
|
@@ -25069,10 +25069,10 @@ var require_src2 = __commonJS({
|
|
|
25069
25069
|
var fs_1 = __require("fs");
|
|
25070
25070
|
var debug_1 = __importDefault(require_src());
|
|
25071
25071
|
var log2 = debug_1.default("@kwsites/file-exists");
|
|
25072
|
-
function check2(
|
|
25073
|
-
log2(`checking %s`,
|
|
25072
|
+
function check2(path62, isFile, isDirectory) {
|
|
25073
|
+
log2(`checking %s`, path62);
|
|
25074
25074
|
try {
|
|
25075
|
-
const stat3 = fs_1.statSync(
|
|
25075
|
+
const stat3 = fs_1.statSync(path62);
|
|
25076
25076
|
if (stat3.isFile() && isFile) {
|
|
25077
25077
|
log2(`[OK] path represents a file`);
|
|
25078
25078
|
return true;
|
|
@@ -25092,8 +25092,8 @@ var require_src2 = __commonJS({
|
|
|
25092
25092
|
throw e;
|
|
25093
25093
|
}
|
|
25094
25094
|
}
|
|
25095
|
-
function exists2(
|
|
25096
|
-
return check2(
|
|
25095
|
+
function exists2(path62, type = exports.READABLE) {
|
|
25096
|
+
return check2(path62, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
|
|
25097
25097
|
}
|
|
25098
25098
|
exports.exists = exists2;
|
|
25099
25099
|
exports.FILE = 1;
|
|
@@ -25174,15 +25174,15 @@ var {
|
|
|
25174
25174
|
} = import_index.default;
|
|
25175
25175
|
|
|
25176
25176
|
// src/cli-version.ts
|
|
25177
|
-
var CLI_VERSION = "0.1.
|
|
25177
|
+
var CLI_VERSION = "0.1.51".length > 0 ? "0.1.51" : "0.0.0-dev";
|
|
25178
25178
|
|
|
25179
25179
|
// src/cli/defaults.ts
|
|
25180
25180
|
var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
|
|
25181
25181
|
var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
|
|
25182
25182
|
|
|
25183
25183
|
// src/cli/run-cli-action.ts
|
|
25184
|
-
import * as
|
|
25185
|
-
import * as
|
|
25184
|
+
import * as fs46 from "node:fs";
|
|
25185
|
+
import * as path61 from "node:path";
|
|
25186
25186
|
|
|
25187
25187
|
// src/cli-log-level.ts
|
|
25188
25188
|
var verbosity = "info";
|
|
@@ -26297,14 +26297,14 @@ var baseOpen = async (options) => {
|
|
|
26297
26297
|
}
|
|
26298
26298
|
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
26299
26299
|
if (options.wait) {
|
|
26300
|
-
return new Promise((
|
|
26300
|
+
return new Promise((resolve34, reject) => {
|
|
26301
26301
|
subprocess.once("error", reject);
|
|
26302
26302
|
subprocess.once("close", (exitCode) => {
|
|
26303
26303
|
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
26304
26304
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
26305
26305
|
return;
|
|
26306
26306
|
}
|
|
26307
|
-
|
|
26307
|
+
resolve34(subprocess);
|
|
26308
26308
|
});
|
|
26309
26309
|
});
|
|
26310
26310
|
}
|
|
@@ -26848,8 +26848,8 @@ function runPendingAuth(options) {
|
|
|
26848
26848
|
let hasOpenedBrowser = false;
|
|
26849
26849
|
let resolved = false;
|
|
26850
26850
|
let resolveAuth;
|
|
26851
|
-
const authPromise = new Promise((
|
|
26852
|
-
resolveAuth =
|
|
26851
|
+
const authPromise = new Promise((resolve34) => {
|
|
26852
|
+
resolveAuth = resolve34;
|
|
26853
26853
|
});
|
|
26854
26854
|
let reconnectAttempt = 0;
|
|
26855
26855
|
const signInQuiet = createEmptyReconnectQuietSlot();
|
|
@@ -27000,7 +27000,7 @@ import sqliteWasm from "node-sqlite3-wasm";
|
|
|
27000
27000
|
|
|
27001
27001
|
// src/runtime/yield-to-event-loop.ts
|
|
27002
27002
|
function yieldToEventLoop() {
|
|
27003
|
-
return new Promise((
|
|
27003
|
+
return new Promise((resolve34) => setImmediate(resolve34));
|
|
27004
27004
|
}
|
|
27005
27005
|
|
|
27006
27006
|
// src/sqlite/cli-sqlite-paths.ts
|
|
@@ -28117,9 +28117,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
|
|
|
28117
28117
|
const rawPath = typeof o.path === "string" ? o.path.trim() : "";
|
|
28118
28118
|
const summary = typeof o.summary === "string" ? o.summary.trim() : "";
|
|
28119
28119
|
if (!rawPath || !summary) continue;
|
|
28120
|
-
const
|
|
28121
|
-
if (!
|
|
28122
|
-
rows.push({ path:
|
|
28120
|
+
const path62 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
|
|
28121
|
+
if (!path62) continue;
|
|
28122
|
+
rows.push({ path: path62, summary: clampSummaryToAtMostTwoLines(summary) });
|
|
28123
28123
|
}
|
|
28124
28124
|
return rows;
|
|
28125
28125
|
}
|
|
@@ -28337,7 +28337,7 @@ var GitRepoMetaSchema = external_exports.object({
|
|
|
28337
28337
|
updatedAt: external_exports.string()
|
|
28338
28338
|
});
|
|
28339
28339
|
|
|
28340
|
-
// ../types/src/bridge-mcp-
|
|
28340
|
+
// ../types/src/mcp/bridge-mcp-constants.ts
|
|
28341
28341
|
var GET_INTEGRATION_CONTENT_TOOL = "get_integration_content";
|
|
28342
28342
|
var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
|
|
28343
28343
|
var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
|
|
@@ -28631,10 +28631,10 @@ async function killChildProcessTreeGracefully(child, graceMs = ACP_PROCESS_TREE_
|
|
|
28631
28631
|
}
|
|
28632
28632
|
await killProcessTree(pid, "SIGTERM");
|
|
28633
28633
|
if (graceMs <= 0) return;
|
|
28634
|
-
const exited = new Promise((
|
|
28635
|
-
child.once("exit", () =>
|
|
28634
|
+
const exited = new Promise((resolve34) => {
|
|
28635
|
+
child.once("exit", () => resolve34());
|
|
28636
28636
|
});
|
|
28637
|
-
await Promise.race([exited, new Promise((
|
|
28637
|
+
await Promise.race([exited, new Promise((resolve34) => setTimeout(resolve34, graceMs))]);
|
|
28638
28638
|
if (child.exitCode == null && child.signalCode == null) {
|
|
28639
28639
|
await killProcessTree(pid, "SIGKILL");
|
|
28640
28640
|
}
|
|
@@ -28651,8 +28651,8 @@ async function gracefulAcpSubprocessDisconnect(params) {
|
|
|
28651
28651
|
await transport.closeSession?.(sessionId);
|
|
28652
28652
|
} catch {
|
|
28653
28653
|
}
|
|
28654
|
-
await new Promise((
|
|
28655
|
-
const timer = setTimeout(
|
|
28654
|
+
await new Promise((resolve34) => {
|
|
28655
|
+
const timer = setTimeout(resolve34, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
|
|
28656
28656
|
timer.unref?.();
|
|
28657
28657
|
});
|
|
28658
28658
|
}
|
|
@@ -28927,11 +28927,11 @@ function createSdkStdioExtNotificationHandler(options) {
|
|
|
28927
28927
|
// src/agents/acp/clients/sdk/sdk-stdio-permission-request-handshake.ts
|
|
28928
28928
|
function awaitSdkStdioPermissionRequestHandshake(params) {
|
|
28929
28929
|
const { requestId, paramsRecord, pending, onRequest } = params;
|
|
28930
|
-
return new Promise((
|
|
28931
|
-
pending.set(requestId, { resolve:
|
|
28930
|
+
return new Promise((resolve34) => {
|
|
28931
|
+
pending.set(requestId, { resolve: resolve34, params: paramsRecord });
|
|
28932
28932
|
if (onRequest == null) {
|
|
28933
28933
|
pending.delete(requestId);
|
|
28934
|
-
|
|
28934
|
+
resolve34({ outcome: { outcome: "denied" } });
|
|
28935
28935
|
return;
|
|
28936
28936
|
}
|
|
28937
28937
|
try {
|
|
@@ -29441,10 +29441,10 @@ function createSdkStdioInitSettle(child) {
|
|
|
29441
29441
|
forceAcpSubprocessDisconnect(child);
|
|
29442
29442
|
reject(err);
|
|
29443
29443
|
},
|
|
29444
|
-
settleResolve(
|
|
29444
|
+
settleResolve(resolve34, handle) {
|
|
29445
29445
|
if (initSettled) return;
|
|
29446
29446
|
initSettled = true;
|
|
29447
|
-
|
|
29447
|
+
resolve34(handle);
|
|
29448
29448
|
}
|
|
29449
29449
|
};
|
|
29450
29450
|
}
|
|
@@ -29513,7 +29513,7 @@ async function createSdkStdioAcpClient(options) {
|
|
|
29513
29513
|
onFileChange,
|
|
29514
29514
|
stderrCapture
|
|
29515
29515
|
});
|
|
29516
|
-
return new Promise((
|
|
29516
|
+
return new Promise((resolve34, reject) => {
|
|
29517
29517
|
const init = createSdkStdioInitSettle(child);
|
|
29518
29518
|
child.on("error", (err) => {
|
|
29519
29519
|
init.settleReject(reject, new Error(formatSpawnError(err, command[0])));
|
|
@@ -29538,7 +29538,7 @@ async function createSdkStdioAcpClient(options) {
|
|
|
29538
29538
|
protocolVersion: PROTOCOL_VERSION2
|
|
29539
29539
|
});
|
|
29540
29540
|
init.settleResolve(
|
|
29541
|
-
|
|
29541
|
+
resolve34,
|
|
29542
29542
|
createSdkStdioHandle({
|
|
29543
29543
|
child,
|
|
29544
29544
|
sessionId: established.sessionId,
|
|
@@ -30071,7 +30071,7 @@ async function createCursorAcpClient(options) {
|
|
|
30071
30071
|
onFileChange,
|
|
30072
30072
|
stderrCapture
|
|
30073
30073
|
});
|
|
30074
|
-
return new Promise((
|
|
30074
|
+
return new Promise((resolve34, reject) => {
|
|
30075
30075
|
child.on("error", (err) => {
|
|
30076
30076
|
killChildProcessTree(child, "SIGKILL");
|
|
30077
30077
|
reject(new Error(formatSpawnError(err, command[0])));
|
|
@@ -30083,7 +30083,7 @@ async function createCursorAcpClient(options) {
|
|
|
30083
30083
|
sessionCtx,
|
|
30084
30084
|
incomingDeps: { dbgFs, sessionCtx, onSessionUpdate, onRequest }
|
|
30085
30085
|
});
|
|
30086
|
-
|
|
30086
|
+
resolve34(
|
|
30087
30087
|
createCursorAcpHandle({
|
|
30088
30088
|
child,
|
|
30089
30089
|
sessionId: established.sessionId,
|
|
@@ -31021,7 +31021,7 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
|
|
|
31021
31021
|
}
|
|
31022
31022
|
|
|
31023
31023
|
// src/mcp/bridge-mcp-constants.ts
|
|
31024
|
-
var
|
|
31024
|
+
var BRIDGE_MCP_SERVER_NAME2 = "buildautomaton-bridge";
|
|
31025
31025
|
|
|
31026
31026
|
// src/mcp/resolve-bridge-mcp-server-path.ts
|
|
31027
31027
|
import { dirname as dirname4, join as join3 } from "node:path";
|
|
@@ -31036,7 +31036,7 @@ function buildAcpMcpServers(bridgeAccessPort) {
|
|
|
31036
31036
|
if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
|
|
31037
31037
|
return [
|
|
31038
31038
|
{
|
|
31039
|
-
name:
|
|
31039
|
+
name: BRIDGE_MCP_SERVER_NAME2,
|
|
31040
31040
|
command: process.execPath,
|
|
31041
31041
|
args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
|
|
31042
31042
|
env: []
|
|
@@ -31289,8 +31289,8 @@ function pathspec(...paths) {
|
|
|
31289
31289
|
cache.set(key, paths);
|
|
31290
31290
|
return key;
|
|
31291
31291
|
}
|
|
31292
|
-
function isPathSpec(
|
|
31293
|
-
return
|
|
31292
|
+
function isPathSpec(path62) {
|
|
31293
|
+
return path62 instanceof String && cache.has(path62);
|
|
31294
31294
|
}
|
|
31295
31295
|
function toPaths(pathSpec) {
|
|
31296
31296
|
return cache.get(pathSpec) || [];
|
|
@@ -31379,8 +31379,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
|
|
|
31379
31379
|
function forEachLineWithContent(input, callback) {
|
|
31380
31380
|
return toLinesWithContent(input, true).map((line) => callback(line));
|
|
31381
31381
|
}
|
|
31382
|
-
function folderExists(
|
|
31383
|
-
return (0, import_file_exists.exists)(
|
|
31382
|
+
function folderExists(path62) {
|
|
31383
|
+
return (0, import_file_exists.exists)(path62, import_file_exists.FOLDER);
|
|
31384
31384
|
}
|
|
31385
31385
|
function append(target, item) {
|
|
31386
31386
|
if (Array.isArray(target)) {
|
|
@@ -31784,8 +31784,8 @@ function checkIsRepoRootTask() {
|
|
|
31784
31784
|
commands,
|
|
31785
31785
|
format: "utf-8",
|
|
31786
31786
|
onError,
|
|
31787
|
-
parser(
|
|
31788
|
-
return /^\.(git)?$/.test(
|
|
31787
|
+
parser(path62) {
|
|
31788
|
+
return /^\.(git)?$/.test(path62.trim());
|
|
31789
31789
|
}
|
|
31790
31790
|
};
|
|
31791
31791
|
}
|
|
@@ -32219,11 +32219,11 @@ function parseGrep(grep) {
|
|
|
32219
32219
|
const paths = /* @__PURE__ */ new Set();
|
|
32220
32220
|
const results = {};
|
|
32221
32221
|
forEachLineWithContent(grep, (input) => {
|
|
32222
|
-
const [
|
|
32223
|
-
paths.add(
|
|
32224
|
-
(results[
|
|
32222
|
+
const [path62, line, preview] = input.split(NULL);
|
|
32223
|
+
paths.add(path62);
|
|
32224
|
+
(results[path62] = results[path62] || []).push({
|
|
32225
32225
|
line: asNumber(line),
|
|
32226
|
-
path:
|
|
32226
|
+
path: path62,
|
|
32227
32227
|
preview
|
|
32228
32228
|
});
|
|
32229
32229
|
});
|
|
@@ -32988,14 +32988,14 @@ var init_hash_object = __esm2({
|
|
|
32988
32988
|
init_task();
|
|
32989
32989
|
}
|
|
32990
32990
|
});
|
|
32991
|
-
function parseInit(bare,
|
|
32991
|
+
function parseInit(bare, path62, text) {
|
|
32992
32992
|
const response = String(text).trim();
|
|
32993
32993
|
let result;
|
|
32994
32994
|
if (result = initResponseRegex.exec(response)) {
|
|
32995
|
-
return new InitSummary(bare,
|
|
32995
|
+
return new InitSummary(bare, path62, false, result[1]);
|
|
32996
32996
|
}
|
|
32997
32997
|
if (result = reInitResponseRegex.exec(response)) {
|
|
32998
|
-
return new InitSummary(bare,
|
|
32998
|
+
return new InitSummary(bare, path62, true, result[1]);
|
|
32999
32999
|
}
|
|
33000
33000
|
let gitDir = "";
|
|
33001
33001
|
const tokens = response.split(" ");
|
|
@@ -33006,7 +33006,7 @@ function parseInit(bare, path48, text) {
|
|
|
33006
33006
|
break;
|
|
33007
33007
|
}
|
|
33008
33008
|
}
|
|
33009
|
-
return new InitSummary(bare,
|
|
33009
|
+
return new InitSummary(bare, path62, /^re/i.test(response), gitDir);
|
|
33010
33010
|
}
|
|
33011
33011
|
var InitSummary;
|
|
33012
33012
|
var initResponseRegex;
|
|
@@ -33015,9 +33015,9 @@ var init_InitSummary = __esm2({
|
|
|
33015
33015
|
"src/lib/responses/InitSummary.ts"() {
|
|
33016
33016
|
"use strict";
|
|
33017
33017
|
InitSummary = class {
|
|
33018
|
-
constructor(bare,
|
|
33018
|
+
constructor(bare, path62, existing, gitDir) {
|
|
33019
33019
|
this.bare = bare;
|
|
33020
|
-
this.path =
|
|
33020
|
+
this.path = path62;
|
|
33021
33021
|
this.existing = existing;
|
|
33022
33022
|
this.gitDir = gitDir;
|
|
33023
33023
|
}
|
|
@@ -33029,7 +33029,7 @@ var init_InitSummary = __esm2({
|
|
|
33029
33029
|
function hasBareCommand(command) {
|
|
33030
33030
|
return command.includes(bareCommand);
|
|
33031
33031
|
}
|
|
33032
|
-
function initTask(bare = false,
|
|
33032
|
+
function initTask(bare = false, path62, customArgs) {
|
|
33033
33033
|
const commands = ["init", ...customArgs];
|
|
33034
33034
|
if (bare && !hasBareCommand(commands)) {
|
|
33035
33035
|
commands.splice(1, 0, bareCommand);
|
|
@@ -33038,7 +33038,7 @@ function initTask(bare = false, path48, customArgs) {
|
|
|
33038
33038
|
commands,
|
|
33039
33039
|
format: "utf-8",
|
|
33040
33040
|
parser(text) {
|
|
33041
|
-
return parseInit(commands.includes("--bare"),
|
|
33041
|
+
return parseInit(commands.includes("--bare"), path62, text);
|
|
33042
33042
|
}
|
|
33043
33043
|
};
|
|
33044
33044
|
}
|
|
@@ -33854,12 +33854,12 @@ var init_FileStatusSummary = __esm2({
|
|
|
33854
33854
|
"use strict";
|
|
33855
33855
|
fromPathRegex = /^(.+)\0(.+)$/;
|
|
33856
33856
|
FileStatusSummary = class {
|
|
33857
|
-
constructor(
|
|
33858
|
-
this.path =
|
|
33857
|
+
constructor(path62, index, working_dir) {
|
|
33858
|
+
this.path = path62;
|
|
33859
33859
|
this.index = index;
|
|
33860
33860
|
this.working_dir = working_dir;
|
|
33861
33861
|
if (index === "R" || working_dir === "R") {
|
|
33862
|
-
const detail = fromPathRegex.exec(
|
|
33862
|
+
const detail = fromPathRegex.exec(path62) || [null, path62, path62];
|
|
33863
33863
|
this.from = detail[2] || "";
|
|
33864
33864
|
this.path = detail[1] || "";
|
|
33865
33865
|
}
|
|
@@ -33890,14 +33890,14 @@ function splitLine(result, lineStr) {
|
|
|
33890
33890
|
default:
|
|
33891
33891
|
return;
|
|
33892
33892
|
}
|
|
33893
|
-
function data(index, workingDir,
|
|
33893
|
+
function data(index, workingDir, path62) {
|
|
33894
33894
|
const raw = `${index}${workingDir}`;
|
|
33895
33895
|
const handler = parsers6.get(raw);
|
|
33896
33896
|
if (handler) {
|
|
33897
|
-
handler(result,
|
|
33897
|
+
handler(result, path62);
|
|
33898
33898
|
}
|
|
33899
33899
|
if (raw !== "##" && raw !== "!!") {
|
|
33900
|
-
result.files.push(new FileStatusSummary(
|
|
33900
|
+
result.files.push(new FileStatusSummary(path62, index, workingDir));
|
|
33901
33901
|
}
|
|
33902
33902
|
}
|
|
33903
33903
|
}
|
|
@@ -34206,9 +34206,9 @@ var init_simple_git_api = __esm2({
|
|
|
34206
34206
|
next
|
|
34207
34207
|
);
|
|
34208
34208
|
}
|
|
34209
|
-
hashObject(
|
|
34209
|
+
hashObject(path62, write) {
|
|
34210
34210
|
return this._runTask(
|
|
34211
|
-
hashObjectTask(
|
|
34211
|
+
hashObjectTask(path62, write === true),
|
|
34212
34212
|
trailingFunctionArgument(arguments)
|
|
34213
34213
|
);
|
|
34214
34214
|
}
|
|
@@ -34561,8 +34561,8 @@ var init_branch = __esm2({
|
|
|
34561
34561
|
}
|
|
34562
34562
|
});
|
|
34563
34563
|
function toPath(input) {
|
|
34564
|
-
const
|
|
34565
|
-
return
|
|
34564
|
+
const path62 = input.trim().replace(/^["']|["']$/g, "");
|
|
34565
|
+
return path62 && normalize3(path62);
|
|
34566
34566
|
}
|
|
34567
34567
|
var parseCheckIgnore;
|
|
34568
34568
|
var init_CheckIgnore = __esm2({
|
|
@@ -34876,8 +34876,8 @@ __export2(sub_module_exports, {
|
|
|
34876
34876
|
subModuleTask: () => subModuleTask,
|
|
34877
34877
|
updateSubModuleTask: () => updateSubModuleTask
|
|
34878
34878
|
});
|
|
34879
|
-
function addSubModuleTask(repo,
|
|
34880
|
-
return subModuleTask(["add", repo,
|
|
34879
|
+
function addSubModuleTask(repo, path62) {
|
|
34880
|
+
return subModuleTask(["add", repo, path62]);
|
|
34881
34881
|
}
|
|
34882
34882
|
function initSubModuleTask(customArgs) {
|
|
34883
34883
|
return subModuleTask(["init", ...customArgs]);
|
|
@@ -35210,8 +35210,8 @@ var require_git = __commonJS2({
|
|
|
35210
35210
|
}
|
|
35211
35211
|
return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
|
|
35212
35212
|
};
|
|
35213
|
-
Git2.prototype.submoduleAdd = function(repo,
|
|
35214
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
35213
|
+
Git2.prototype.submoduleAdd = function(repo, path62, then) {
|
|
35214
|
+
return this._runTask(addSubModuleTask2(repo, path62), trailingFunctionArgument2(arguments));
|
|
35215
35215
|
};
|
|
35216
35216
|
Git2.prototype.submoduleUpdate = function(args, then) {
|
|
35217
35217
|
return this._runTask(
|
|
@@ -35848,14 +35848,14 @@ async function runGitTask(fn) {
|
|
|
35848
35848
|
}
|
|
35849
35849
|
async function yieldToEventLoop2() {
|
|
35850
35850
|
throwIfGitShutdownRequested();
|
|
35851
|
-
await new Promise((
|
|
35852
|
-
setImmediate(
|
|
35851
|
+
await new Promise((resolve34) => {
|
|
35852
|
+
setImmediate(resolve34);
|
|
35853
35853
|
});
|
|
35854
35854
|
throwIfGitShutdownRequested();
|
|
35855
35855
|
}
|
|
35856
35856
|
async function forEachWithGitYield(items, fn) {
|
|
35857
35857
|
for (let i = 0; i < items.length; i++) {
|
|
35858
|
-
|
|
35858
|
+
await yieldToEventLoop2();
|
|
35859
35859
|
await fn(items[i], i);
|
|
35860
35860
|
}
|
|
35861
35861
|
}
|
|
@@ -35889,11 +35889,8 @@ function cliSimpleGit(baseDir) {
|
|
|
35889
35889
|
async function getRemoteOriginUrl(gitDir) {
|
|
35890
35890
|
try {
|
|
35891
35891
|
const git = cliSimpleGit(gitDir);
|
|
35892
|
-
const
|
|
35893
|
-
|
|
35894
|
-
const origin = list.find((r) => r.name === "origin");
|
|
35895
|
-
if (!origin?.refs?.fetch && !origin?.refs?.push) return "";
|
|
35896
|
-
return (origin.refs.fetch ?? origin.refs.push ?? "").trim();
|
|
35892
|
+
const out = await git.raw(["config", "--get", "remote.origin.url"]);
|
|
35893
|
+
return String(out).trim();
|
|
35897
35894
|
} catch {
|
|
35898
35895
|
return "";
|
|
35899
35896
|
}
|
|
@@ -35909,6 +35906,12 @@ async function isGitRepoDirectory(dirPath) {
|
|
|
35909
35906
|
}
|
|
35910
35907
|
|
|
35911
35908
|
// src/git/discover-repos.ts
|
|
35909
|
+
var GIT_DISCOVER_YIELD_EVERY = 32;
|
|
35910
|
+
async function yieldGitDiscoverWork(entryCount) {
|
|
35911
|
+
if (entryCount > 0 && entryCount % GIT_DISCOVER_YIELD_EVERY === 0) {
|
|
35912
|
+
await yieldToEventLoop();
|
|
35913
|
+
}
|
|
35914
|
+
}
|
|
35912
35915
|
async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
35913
35916
|
const result = [];
|
|
35914
35917
|
const cwdResolved = path17.resolve(cwd);
|
|
@@ -35922,7 +35925,9 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
|
35922
35925
|
} catch {
|
|
35923
35926
|
return result;
|
|
35924
35927
|
}
|
|
35925
|
-
for (
|
|
35928
|
+
for (let i = 0; i < entries.length; i++) {
|
|
35929
|
+
await yieldGitDiscoverWork(i + 1);
|
|
35930
|
+
const ent = entries[i];
|
|
35926
35931
|
if (!ent.isDirectory()) continue;
|
|
35927
35932
|
const childPath = path17.join(cwdResolved, ent.name);
|
|
35928
35933
|
if (await isGitRepoDirectory(childPath)) {
|
|
@@ -35935,6 +35940,7 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
|
|
|
35935
35940
|
async function discoverGitReposUnderRoot(rootPath) {
|
|
35936
35941
|
const root = path17.resolve(rootPath);
|
|
35937
35942
|
const roots = [];
|
|
35943
|
+
let walkEntries = 0;
|
|
35938
35944
|
async function walk(dir) {
|
|
35939
35945
|
if (await isGitRepoDirectory(dir)) {
|
|
35940
35946
|
roots.push(path17.resolve(dir));
|
|
@@ -35946,7 +35952,9 @@ async function discoverGitReposUnderRoot(rootPath) {
|
|
|
35946
35952
|
} catch {
|
|
35947
35953
|
return;
|
|
35948
35954
|
}
|
|
35949
|
-
for (
|
|
35955
|
+
for (let i = 0; i < entries.length; i++) {
|
|
35956
|
+
await yieldGitDiscoverWork(++walkEntries);
|
|
35957
|
+
const ent = entries[i];
|
|
35950
35958
|
if (!ent.isDirectory() || ent.name === ".git") continue;
|
|
35951
35959
|
await walk(path17.join(dir, ent.name));
|
|
35952
35960
|
}
|
|
@@ -35954,7 +35962,9 @@ async function discoverGitReposUnderRoot(rootPath) {
|
|
|
35954
35962
|
await walk(root);
|
|
35955
35963
|
const uniq = [...new Set(roots)];
|
|
35956
35964
|
const out = [];
|
|
35957
|
-
for (
|
|
35965
|
+
for (let i = 0; i < uniq.length; i++) {
|
|
35966
|
+
await yieldGitDiscoverWork(i + 1);
|
|
35967
|
+
const p = uniq[i];
|
|
35958
35968
|
const remoteUrl = await getRemoteOriginUrl(p);
|
|
35959
35969
|
out.push({ absolutePath: p, remoteUrl });
|
|
35960
35970
|
}
|
|
@@ -36174,9 +36184,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
|
36174
36184
|
// src/agents/acp/put-summarize-change-summaries.ts
|
|
36175
36185
|
async function putEncryptedChangeSummaryRows(params) {
|
|
36176
36186
|
const base = params.apiBaseUrl.replace(/\/+$/, "");
|
|
36177
|
-
const entries = params.rows.map(({ path:
|
|
36187
|
+
const entries = params.rows.map(({ path: path62, summary }) => {
|
|
36178
36188
|
const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
|
|
36179
|
-
return { path:
|
|
36189
|
+
return { path: path62, summary: JSON.stringify(enc) };
|
|
36180
36190
|
});
|
|
36181
36191
|
const res = await fetch(
|
|
36182
36192
|
`${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
|
|
@@ -36530,7 +36540,7 @@ function buildIntegrationContentAgentPromptNote(sessionId) {
|
|
|
36530
36540
|
"Use the get_integration_content MCP tool on the buildautomaton-bridge server to fetch the full content of each link.",
|
|
36531
36541
|
`When calling the tool, pass sessionId "${sessionRef}" and the URL as url.`,
|
|
36532
36542
|
"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).",
|
|
36533
|
-
"If the tool reports unsupported_url, do not retry with the same URL. If it reports temporarily_unavailable, you may retry later."
|
|
36543
|
+
"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."
|
|
36534
36544
|
].join("\n");
|
|
36535
36545
|
}
|
|
36536
36546
|
function injectIntegrationContentAgentPromptNote(agentPrompt, sessionId) {
|
|
@@ -36565,7 +36575,7 @@ async function sendPromptToAgent(options) {
|
|
|
36565
36575
|
runId,
|
|
36566
36576
|
agentType,
|
|
36567
36577
|
agentCwd,
|
|
36568
|
-
sendResult
|
|
36578
|
+
sendResult,
|
|
36569
36579
|
sendSessionUpdate,
|
|
36570
36580
|
log: log2,
|
|
36571
36581
|
followUpCatalogPromptId,
|
|
@@ -36595,7 +36605,7 @@ async function sendPromptToAgent(options) {
|
|
|
36595
36605
|
let sendOpts = {};
|
|
36596
36606
|
if (attachments && attachments.length > 0) {
|
|
36597
36607
|
if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
|
|
36598
|
-
|
|
36608
|
+
sendResult({
|
|
36599
36609
|
type: "prompt_result",
|
|
36600
36610
|
id: promptId,
|
|
36601
36611
|
...sessionId ? { sessionId } : {},
|
|
@@ -36616,7 +36626,7 @@ async function sendPromptToAgent(options) {
|
|
|
36616
36626
|
log: log2
|
|
36617
36627
|
});
|
|
36618
36628
|
if (!resolved.ok) {
|
|
36619
|
-
|
|
36629
|
+
sendResult({
|
|
36620
36630
|
type: "prompt_result",
|
|
36621
36631
|
id: promptId,
|
|
36622
36632
|
...sessionId ? { sessionId } : {},
|
|
@@ -36655,7 +36665,7 @@ async function sendPromptToAgent(options) {
|
|
|
36655
36665
|
const errStr = typeof result.error === "string" ? result.error : void 0;
|
|
36656
36666
|
const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
|
|
36657
36667
|
const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
|
|
36658
|
-
|
|
36668
|
+
sendResult({
|
|
36659
36669
|
type: "prompt_result",
|
|
36660
36670
|
id: promptId,
|
|
36661
36671
|
...sessionId ? { sessionId } : {},
|
|
@@ -36677,7 +36687,7 @@ async function sendPromptToAgent(options) {
|
|
|
36677
36687
|
} catch (err) {
|
|
36678
36688
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
36679
36689
|
log2(`[Agent] Send failed: ${errMsg}`);
|
|
36680
|
-
|
|
36690
|
+
sendResult({
|
|
36681
36691
|
type: "prompt_result",
|
|
36682
36692
|
id: promptId,
|
|
36683
36693
|
...sessionId ? { sessionId } : {},
|
|
@@ -36711,7 +36721,7 @@ function handlePrompt(ctx, opts) {
|
|
|
36711
36721
|
agentType,
|
|
36712
36722
|
agentConfig,
|
|
36713
36723
|
sessionParentPath,
|
|
36714
|
-
sendResult
|
|
36724
|
+
sendResult,
|
|
36715
36725
|
sendSessionUpdate,
|
|
36716
36726
|
followUpCatalogPromptId,
|
|
36717
36727
|
sessionChangeSummaryFilePaths,
|
|
@@ -36728,7 +36738,7 @@ function handlePrompt(ctx, opts) {
|
|
|
36728
36738
|
const activeRunId = runId;
|
|
36729
36739
|
const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
|
|
36730
36740
|
if (!acpAgentKey) {
|
|
36731
|
-
|
|
36741
|
+
sendResult({
|
|
36732
36742
|
type: "prompt_result",
|
|
36733
36743
|
id: promptId,
|
|
36734
36744
|
...sessionId ? { sessionId } : {},
|
|
@@ -36765,7 +36775,7 @@ function handlePrompt(ctx, opts) {
|
|
|
36765
36775
|
const evaluated = Boolean(preferredForPrompt && errMsg.trim());
|
|
36766
36776
|
const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
|
|
36767
36777
|
const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
|
|
36768
|
-
|
|
36778
|
+
sendResult({
|
|
36769
36779
|
type: "prompt_result",
|
|
36770
36780
|
id: promptId,
|
|
36771
36781
|
...sessionId ? { sessionId } : {},
|
|
@@ -36785,7 +36795,7 @@ function handlePrompt(ctx, opts) {
|
|
|
36785
36795
|
await handle.cancel?.();
|
|
36786
36796
|
} catch {
|
|
36787
36797
|
}
|
|
36788
|
-
|
|
36798
|
+
sendResult({
|
|
36789
36799
|
type: "prompt_result",
|
|
36790
36800
|
id: promptId,
|
|
36791
36801
|
...sessionId ? { sessionId } : {},
|
|
@@ -36806,7 +36816,7 @@ function handlePrompt(ctx, opts) {
|
|
|
36806
36816
|
runId: activeRunId,
|
|
36807
36817
|
agentType: preferredForPrompt,
|
|
36808
36818
|
agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
|
|
36809
|
-
sendResult
|
|
36819
|
+
sendResult,
|
|
36810
36820
|
sendSessionUpdate,
|
|
36811
36821
|
log: ctx.log,
|
|
36812
36822
|
followUpCatalogPromptId,
|
|
@@ -36862,6 +36872,496 @@ async function createAcpManager(options) {
|
|
|
36862
36872
|
};
|
|
36863
36873
|
}
|
|
36864
36874
|
|
|
36875
|
+
// src/worktrees/worktree-layout-file.ts
|
|
36876
|
+
import * as fs17 from "node:fs";
|
|
36877
|
+
import * as path20 from "node:path";
|
|
36878
|
+
import os7 from "node:os";
|
|
36879
|
+
var LAYOUT_FILENAME = "worktree-launcher-layout.json";
|
|
36880
|
+
function defaultWorktreeLayoutPath() {
|
|
36881
|
+
return path20.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
|
|
36882
|
+
}
|
|
36883
|
+
function normalizeLoadedLayout(raw) {
|
|
36884
|
+
if (raw && typeof raw === "object" && "launcherCwds" in raw) {
|
|
36885
|
+
const j = raw;
|
|
36886
|
+
if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
|
|
36887
|
+
}
|
|
36888
|
+
return { launcherCwds: [] };
|
|
36889
|
+
}
|
|
36890
|
+
function loadWorktreeLayout() {
|
|
36891
|
+
try {
|
|
36892
|
+
const p = defaultWorktreeLayoutPath();
|
|
36893
|
+
if (!fs17.existsSync(p)) return { launcherCwds: [] };
|
|
36894
|
+
const raw = JSON.parse(fs17.readFileSync(p, "utf8"));
|
|
36895
|
+
return normalizeLoadedLayout(raw);
|
|
36896
|
+
} catch {
|
|
36897
|
+
return { launcherCwds: [] };
|
|
36898
|
+
}
|
|
36899
|
+
}
|
|
36900
|
+
function saveWorktreeLayout(layout) {
|
|
36901
|
+
try {
|
|
36902
|
+
const dir = path20.dirname(defaultWorktreeLayoutPath());
|
|
36903
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
36904
|
+
fs17.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
|
|
36905
|
+
} catch {
|
|
36906
|
+
}
|
|
36907
|
+
}
|
|
36908
|
+
function baseNameSafe(pathString) {
|
|
36909
|
+
return path20.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
|
|
36910
|
+
}
|
|
36911
|
+
function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
|
|
36912
|
+
const norm = path20.resolve(bridgeRootPath2);
|
|
36913
|
+
const existing = layout.launcherCwds.find((e) => path20.resolve(e.absolutePath) === norm);
|
|
36914
|
+
return existing?.dirName;
|
|
36915
|
+
}
|
|
36916
|
+
function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
|
|
36917
|
+
const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
|
|
36918
|
+
if (existing) return existing;
|
|
36919
|
+
const norm = path20.resolve(bridgeRootPath2);
|
|
36920
|
+
const base = baseNameSafe(norm);
|
|
36921
|
+
const used = new Set(layout.launcherCwds.map((e) => e.dirName));
|
|
36922
|
+
let name = base;
|
|
36923
|
+
let n = 2;
|
|
36924
|
+
while (used.has(name)) {
|
|
36925
|
+
name = `${base}-${n}`;
|
|
36926
|
+
n += 1;
|
|
36927
|
+
}
|
|
36928
|
+
layout.launcherCwds.push({ absolutePath: norm, dirName: name });
|
|
36929
|
+
saveWorktreeLayout(layout);
|
|
36930
|
+
return name;
|
|
36931
|
+
}
|
|
36932
|
+
|
|
36933
|
+
// src/worktrees/manager/session-worktree-cache.ts
|
|
36934
|
+
import * as path21 from "node:path";
|
|
36935
|
+
var SessionWorktreeCache = class {
|
|
36936
|
+
sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
|
|
36937
|
+
sessionParentPathBySession = /* @__PURE__ */ new Map();
|
|
36938
|
+
sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
|
|
36939
|
+
remember(sessionId, binding) {
|
|
36940
|
+
const paths = binding.repoCheckoutPaths.map((p) => path21.resolve(p));
|
|
36941
|
+
this.sessionRepoCheckoutPaths.set(sessionId, paths);
|
|
36942
|
+
this.sessionParentPathBySession.set(sessionId, path21.resolve(binding.sessionParentPath));
|
|
36943
|
+
this.sessionWorkingTreeRelRootBySession.set(sessionId, path21.resolve(binding.workingTreeRelRoot));
|
|
36944
|
+
}
|
|
36945
|
+
clearSession(sessionId) {
|
|
36946
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
36947
|
+
this.sessionRepoCheckoutPaths.delete(sessionId);
|
|
36948
|
+
this.sessionParentPathBySession.delete(sessionId);
|
|
36949
|
+
this.sessionWorkingTreeRelRootBySession.delete(sessionId);
|
|
36950
|
+
return paths;
|
|
36951
|
+
}
|
|
36952
|
+
getSessionParentPath(sessionId) {
|
|
36953
|
+
return this.sessionParentPathBySession.get(sessionId);
|
|
36954
|
+
}
|
|
36955
|
+
getWorkingTreeRelRoot(sessionId) {
|
|
36956
|
+
return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
|
|
36957
|
+
}
|
|
36958
|
+
hasSession(sessionId) {
|
|
36959
|
+
return this.sessionParentPathBySession.has(sessionId);
|
|
36960
|
+
}
|
|
36961
|
+
getRepoCheckoutPaths(sessionId) {
|
|
36962
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
36963
|
+
return paths?.length ? [...paths] : void 0;
|
|
36964
|
+
}
|
|
36965
|
+
getRepoCheckoutPathsRef(sessionId) {
|
|
36966
|
+
const paths = this.sessionRepoCheckoutPaths.get(sessionId);
|
|
36967
|
+
return paths?.length ? paths : void 0;
|
|
36968
|
+
}
|
|
36969
|
+
isLegacyNestedLayout(sessionId) {
|
|
36970
|
+
const parent = this.sessionParentPathBySession.get(sessionId);
|
|
36971
|
+
const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
|
|
36972
|
+
if (!parent || !relRoot) return false;
|
|
36973
|
+
return path21.resolve(parent) !== path21.resolve(relRoot);
|
|
36974
|
+
}
|
|
36975
|
+
};
|
|
36976
|
+
|
|
36977
|
+
// src/worktrees/manager/session-worktree-manager-context.ts
|
|
36978
|
+
function createSessionWorktreeManagerContext(options) {
|
|
36979
|
+
return {
|
|
36980
|
+
worktreesRootPath: options.worktreesRootPath,
|
|
36981
|
+
log: options.log,
|
|
36982
|
+
cache: new SessionWorktreeCache(),
|
|
36983
|
+
layout: loadWorktreeLayout()
|
|
36984
|
+
};
|
|
36985
|
+
}
|
|
36986
|
+
|
|
36987
|
+
// src/git/branches/commit-and-push.ts
|
|
36988
|
+
async function gitCommitAllIfDirty(repoDir, message, options) {
|
|
36989
|
+
const g = cliSimpleGit(repoDir);
|
|
36990
|
+
const st = await g.status();
|
|
36991
|
+
if (!st.files?.length) return;
|
|
36992
|
+
const branch = options.branch.trim();
|
|
36993
|
+
if (!branch) {
|
|
36994
|
+
throw new Error("Branch name is required");
|
|
36995
|
+
}
|
|
36996
|
+
const branches = await g.branchLocal();
|
|
36997
|
+
const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
|
|
36998
|
+
if (!localNames.has(branch)) {
|
|
36999
|
+
await g.checkoutLocalBranch(branch);
|
|
37000
|
+
} else {
|
|
37001
|
+
await g.checkout(branch);
|
|
37002
|
+
}
|
|
37003
|
+
await g.add(".");
|
|
37004
|
+
await g.commit(message);
|
|
37005
|
+
if (options.push) {
|
|
37006
|
+
await g.push(["-u", "origin", branch]);
|
|
37007
|
+
}
|
|
37008
|
+
}
|
|
37009
|
+
|
|
37010
|
+
// src/worktrees/commit-session-worktrees.ts
|
|
37011
|
+
async function commitSessionWorktrees(options) {
|
|
37012
|
+
const { paths, branch, message, push } = options;
|
|
37013
|
+
try {
|
|
37014
|
+
for (const wt of paths) {
|
|
37015
|
+
await gitCommitAllIfDirty(wt, message, { push, branch });
|
|
37016
|
+
}
|
|
37017
|
+
return { ok: true };
|
|
37018
|
+
} catch (e) {
|
|
37019
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
37020
|
+
return { ok: false, error: err };
|
|
37021
|
+
}
|
|
37022
|
+
}
|
|
37023
|
+
|
|
37024
|
+
// src/worktrees/manager/git/commit-session-worktree.ts
|
|
37025
|
+
async function commitSessionWorktree(cache2, params) {
|
|
37026
|
+
const paths = cache2.getRepoCheckoutPathsRef(params.sessionId);
|
|
37027
|
+
const targets = paths?.length ? paths : [getBridgeRoot()];
|
|
37028
|
+
return commitSessionWorktrees({
|
|
37029
|
+
paths: targets,
|
|
37030
|
+
branch: params.branch,
|
|
37031
|
+
message: params.message,
|
|
37032
|
+
push: params.push
|
|
37033
|
+
});
|
|
37034
|
+
}
|
|
37035
|
+
|
|
37036
|
+
// src/git/changes/parse/normalize-git-diff-path.ts
|
|
37037
|
+
function normalizeGitDiffPath(path62) {
|
|
37038
|
+
return path62.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
37039
|
+
}
|
|
37040
|
+
|
|
37041
|
+
// src/git/changes/parse/expand-git-rename-numstat-path.ts
|
|
37042
|
+
function expandGitRenameNumstatPath(rawPath) {
|
|
37043
|
+
const open2 = rawPath.indexOf("{");
|
|
37044
|
+
const arrow = rawPath.indexOf("=>");
|
|
37045
|
+
const close = rawPath.lastIndexOf("}");
|
|
37046
|
+
if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
|
|
37047
|
+
return null;
|
|
37048
|
+
}
|
|
37049
|
+
const prefix = rawPath.slice(0, open2);
|
|
37050
|
+
const suffix = rawPath.slice(close + 1);
|
|
37051
|
+
const oldPart = rawPath.slice(open2 + 1, arrow).trim();
|
|
37052
|
+
const newPart = rawPath.slice(arrow + 2, close).trim();
|
|
37053
|
+
return {
|
|
37054
|
+
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
37055
|
+
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
37056
|
+
};
|
|
37057
|
+
}
|
|
37058
|
+
function isGitRenameNumstatPath(rawPath) {
|
|
37059
|
+
return expandGitRenameNumstatPath(rawPath) != null;
|
|
37060
|
+
}
|
|
37061
|
+
|
|
37062
|
+
// src/git/changes/parse/parse-name-status-entries.ts
|
|
37063
|
+
function parseNameStatusEntries(lines) {
|
|
37064
|
+
const entries = [];
|
|
37065
|
+
for (const line of lines) {
|
|
37066
|
+
if (!line.trim()) continue;
|
|
37067
|
+
const tabParts = line.split(" ");
|
|
37068
|
+
if (tabParts.length < 2) continue;
|
|
37069
|
+
const status = tabParts[0].trim();
|
|
37070
|
+
const code = status[0];
|
|
37071
|
+
if (code === "A") {
|
|
37072
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
|
|
37073
|
+
} else if (code === "D") {
|
|
37074
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
|
|
37075
|
+
} else if (code === "R" || code === "C") {
|
|
37076
|
+
if (tabParts.length >= 3) {
|
|
37077
|
+
entries.push({
|
|
37078
|
+
pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
|
|
37079
|
+
oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
|
|
37080
|
+
change: "moved"
|
|
37081
|
+
});
|
|
37082
|
+
}
|
|
37083
|
+
} else if (code === "M" || code === "U" || code === "T") {
|
|
37084
|
+
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
37085
|
+
}
|
|
37086
|
+
}
|
|
37087
|
+
return entries;
|
|
37088
|
+
}
|
|
37089
|
+
function parseNameStatusLines(lines) {
|
|
37090
|
+
const m = /* @__PURE__ */ new Map();
|
|
37091
|
+
for (const entry of parseNameStatusEntries(lines)) {
|
|
37092
|
+
m.set(entry.pathInRepo, entry.change);
|
|
37093
|
+
}
|
|
37094
|
+
return m;
|
|
37095
|
+
}
|
|
37096
|
+
|
|
37097
|
+
// src/git/changes/parse/parse-numstat-entries.ts
|
|
37098
|
+
function parseNumstatCounts(parts) {
|
|
37099
|
+
const [a, d] = parts;
|
|
37100
|
+
return {
|
|
37101
|
+
additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
|
|
37102
|
+
deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
|
|
37103
|
+
};
|
|
37104
|
+
}
|
|
37105
|
+
function parseNumstatLine(line) {
|
|
37106
|
+
const parts = line.split(" ");
|
|
37107
|
+
if (parts.length < 3) return null;
|
|
37108
|
+
const rawPath = parts[parts.length - 1];
|
|
37109
|
+
const { additions, deletions } = parseNumstatCounts(parts);
|
|
37110
|
+
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
37111
|
+
const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
|
|
37112
|
+
return {
|
|
37113
|
+
pathInRepo,
|
|
37114
|
+
additions,
|
|
37115
|
+
deletions,
|
|
37116
|
+
...expanded ? { oldPathInRepo: expanded.oldPath } : {}
|
|
37117
|
+
};
|
|
37118
|
+
}
|
|
37119
|
+
function parseNumstatEntries(lines) {
|
|
37120
|
+
const m = /* @__PURE__ */ new Map();
|
|
37121
|
+
for (const line of lines) {
|
|
37122
|
+
if (!line.trim()) continue;
|
|
37123
|
+
const parts = line.split(" ");
|
|
37124
|
+
if (parts.length < 3) continue;
|
|
37125
|
+
const rawPath = parts[parts.length - 1];
|
|
37126
|
+
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
37127
|
+
const entry = parseNumstatLine(line);
|
|
37128
|
+
if (!entry) continue;
|
|
37129
|
+
const existing = m.get(entry.pathInRepo);
|
|
37130
|
+
if (!existing || expanded) {
|
|
37131
|
+
m.set(entry.pathInRepo, entry);
|
|
37132
|
+
}
|
|
37133
|
+
}
|
|
37134
|
+
return m;
|
|
37135
|
+
}
|
|
37136
|
+
function parseNumstat(lines) {
|
|
37137
|
+
const m = /* @__PURE__ */ new Map();
|
|
37138
|
+
for (const [path62, entry] of parseNumstatEntries(lines)) {
|
|
37139
|
+
m.set(path62, { additions: entry.additions, deletions: entry.deletions });
|
|
37140
|
+
}
|
|
37141
|
+
return m;
|
|
37142
|
+
}
|
|
37143
|
+
|
|
37144
|
+
// src/git/changes/parse/numstat-from-git-no-index.ts
|
|
37145
|
+
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
37146
|
+
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
37147
|
+
try {
|
|
37148
|
+
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
37149
|
+
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
37150
|
+
const parsed = parseNumstatLine(first2);
|
|
37151
|
+
if (!parsed) return null;
|
|
37152
|
+
return { additions: parsed.additions, deletions: parsed.deletions };
|
|
37153
|
+
} catch {
|
|
37154
|
+
return null;
|
|
37155
|
+
}
|
|
37156
|
+
}
|
|
37157
|
+
|
|
37158
|
+
// src/git/changes/status/working-tree-changed-path-count.ts
|
|
37159
|
+
function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
|
|
37160
|
+
const kindByPath = parseNameStatusLines(nameStatusLines);
|
|
37161
|
+
const numByPath = parseNumstat(numstatLines);
|
|
37162
|
+
const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
|
|
37163
|
+
for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
|
|
37164
|
+
paths.add(p);
|
|
37165
|
+
}
|
|
37166
|
+
return paths.size;
|
|
37167
|
+
}
|
|
37168
|
+
|
|
37169
|
+
// src/git/changes/count-working-tree-changed-files.ts
|
|
37170
|
+
async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
|
|
37171
|
+
return runGitTask(async () => {
|
|
37172
|
+
const g = cliSimpleGit(repoGitCwd);
|
|
37173
|
+
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
37174
|
+
g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
|
|
37175
|
+
g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
|
|
37176
|
+
g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
|
|
37177
|
+
]);
|
|
37178
|
+
return workingTreeChangedPathCount(
|
|
37179
|
+
String(nameStatusRaw).split("\n"),
|
|
37180
|
+
String(numstatRaw).split("\n"),
|
|
37181
|
+
String(untrackedRaw).split("\n")
|
|
37182
|
+
);
|
|
37183
|
+
});
|
|
37184
|
+
}
|
|
37185
|
+
|
|
37186
|
+
// src/git/commits/remote-tracking/rev-parse-safe.ts
|
|
37187
|
+
async function revParseSafe(g, ref) {
|
|
37188
|
+
try {
|
|
37189
|
+
const v = (await g.raw(["rev-parse", "--verify", ref])).trim();
|
|
37190
|
+
return v || null;
|
|
37191
|
+
} catch {
|
|
37192
|
+
return null;
|
|
37193
|
+
}
|
|
37194
|
+
}
|
|
37195
|
+
|
|
37196
|
+
// src/git/commits/remote-tracking/git-config.ts
|
|
37197
|
+
async function tryConfigGet(g, key) {
|
|
37198
|
+
try {
|
|
37199
|
+
const out = await g.raw(["config", "--get", key]);
|
|
37200
|
+
const v = String(out).trim();
|
|
37201
|
+
return v || null;
|
|
37202
|
+
} catch {
|
|
37203
|
+
return null;
|
|
37204
|
+
}
|
|
37205
|
+
}
|
|
37206
|
+
|
|
37207
|
+
// src/git/commits/remote-tracking/branch-remote-config.ts
|
|
37208
|
+
async function readBranchRemoteConfig(g) {
|
|
37209
|
+
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
37210
|
+
if (!branch || branch === "HEAD") {
|
|
37211
|
+
return { branch: null, remote: "origin", merge: null };
|
|
37212
|
+
}
|
|
37213
|
+
const [remote, merge2] = await Promise.all([
|
|
37214
|
+
tryConfigGet(g, `branch.${branch}.remote`),
|
|
37215
|
+
tryConfigGet(g, `branch.${branch}.merge`)
|
|
37216
|
+
]);
|
|
37217
|
+
return { branch, remote: remote ?? "origin", merge: merge2 };
|
|
37218
|
+
}
|
|
37219
|
+
function trackingRefCandidates(config2) {
|
|
37220
|
+
const candidates = ["HEAD@{upstream}", "@{u}"];
|
|
37221
|
+
if (!config2.branch) return candidates;
|
|
37222
|
+
if (config2.merge) {
|
|
37223
|
+
candidates.push(
|
|
37224
|
+
`refs/remotes/${config2.remote}/${config2.merge.replace(/^refs\/heads\//, "")}`
|
|
37225
|
+
);
|
|
37226
|
+
}
|
|
37227
|
+
candidates.push(`refs/remotes/${config2.remote}/${config2.branch}`);
|
|
37228
|
+
return candidates;
|
|
37229
|
+
}
|
|
37230
|
+
|
|
37231
|
+
// src/git/commits/remote-tracking/first-resolved-ref.ts
|
|
37232
|
+
async function firstResolvedSha(g, candidates) {
|
|
37233
|
+
const resolved = await Promise.all(
|
|
37234
|
+
candidates.map(async (ref) => ({ ref, sha: await revParseSafe(g, ref) }))
|
|
37235
|
+
);
|
|
37236
|
+
return resolved.find((entry) => entry.sha)?.sha ?? null;
|
|
37237
|
+
}
|
|
37238
|
+
|
|
37239
|
+
// src/git/commits/remote-tracking/resolve-default-remote-branch-ref.ts
|
|
37240
|
+
var DEFAULT_REMOTE_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
|
|
37241
|
+
function defaultRemoteBranchNameRefs(remote) {
|
|
37242
|
+
return DEFAULT_REMOTE_BRANCH_NAMES.map((name) => `refs/remotes/${remote}/${name}`);
|
|
37243
|
+
}
|
|
37244
|
+
async function resolveRemoteHeadSymbolicRef(g, remote) {
|
|
37245
|
+
const headSym = `refs/remotes/${remote}/HEAD`;
|
|
37246
|
+
try {
|
|
37247
|
+
const out = await g.raw(["symbolic-ref", "-q", "--verify", headSym]);
|
|
37248
|
+
const resolved = String(out).trim();
|
|
37249
|
+
return resolved.startsWith("refs/remotes/") ? resolved : null;
|
|
37250
|
+
} catch {
|
|
37251
|
+
return null;
|
|
37252
|
+
}
|
|
37253
|
+
}
|
|
37254
|
+
async function resolveDefaultRemoteBranchSha(g, remote) {
|
|
37255
|
+
const nameRefs = defaultRemoteBranchNameRefs(remote);
|
|
37256
|
+
const [symRef, ...nameShas] = await Promise.all([
|
|
37257
|
+
resolveRemoteHeadSymbolicRef(g, remote),
|
|
37258
|
+
...nameRefs.map((ref) => revParseSafe(g, ref))
|
|
37259
|
+
]);
|
|
37260
|
+
if (symRef) return revParseSafe(g, symRef);
|
|
37261
|
+
for (let i = 0; i < nameRefs.length; i++) {
|
|
37262
|
+
if (nameShas[i]) return nameShas[i];
|
|
37263
|
+
}
|
|
37264
|
+
return null;
|
|
37265
|
+
}
|
|
37266
|
+
|
|
37267
|
+
// src/git/commits/remote-tracking/resolve-base-sha-for-unpushed-commits.ts
|
|
37268
|
+
import * as path22 from "node:path";
|
|
37269
|
+
var BASE_SHA_CACHE_TTL_MS = 3e4;
|
|
37270
|
+
var baseShaCache = /* @__PURE__ */ new Map();
|
|
37271
|
+
async function resolveBaseShaForUnpushedCommitsUncached(g) {
|
|
37272
|
+
const config2 = await readBranchRemoteConfig(g);
|
|
37273
|
+
const trackingSha = await firstResolvedSha(g, trackingRefCandidates(config2));
|
|
37274
|
+
if (trackingSha) return trackingSha;
|
|
37275
|
+
return resolveDefaultRemoteBranchSha(g, config2.remote);
|
|
37276
|
+
}
|
|
37277
|
+
async function resolveBaseShaForUnpushedCommits(g, repoDir) {
|
|
37278
|
+
if (repoDir) {
|
|
37279
|
+
const key = path22.resolve(repoDir);
|
|
37280
|
+
const now = Date.now();
|
|
37281
|
+
const cached2 = baseShaCache.get(key);
|
|
37282
|
+
if (cached2 && cached2.expiresAt > now) return cached2.baseSha;
|
|
37283
|
+
const baseSha = await resolveBaseShaForUnpushedCommitsUncached(g);
|
|
37284
|
+
baseShaCache.set(key, { baseSha, expiresAt: now + BASE_SHA_CACHE_TTL_MS });
|
|
37285
|
+
return baseSha;
|
|
37286
|
+
}
|
|
37287
|
+
return resolveBaseShaForUnpushedCommitsUncached(g);
|
|
37288
|
+
}
|
|
37289
|
+
|
|
37290
|
+
// src/git/commits/remote-tracking/commits-ahead-of-remote-tracking.ts
|
|
37291
|
+
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
37292
|
+
const g = cliSimpleGit(repoDir);
|
|
37293
|
+
const headSha = await revParseSafe(g, "HEAD");
|
|
37294
|
+
if (!headSha) return 0;
|
|
37295
|
+
const baseSha = await resolveBaseShaForUnpushedCommits(g, repoDir);
|
|
37296
|
+
if (!baseSha || baseSha === headSha) return 0;
|
|
37297
|
+
try {
|
|
37298
|
+
const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
37299
|
+
const n = parseInt(String(out).trim(), 10);
|
|
37300
|
+
return Number.isNaN(n) ? 0 : n;
|
|
37301
|
+
} catch {
|
|
37302
|
+
return 0;
|
|
37303
|
+
}
|
|
37304
|
+
}
|
|
37305
|
+
|
|
37306
|
+
// src/git/status/working-tree-status.ts
|
|
37307
|
+
async function getRepoWorkingTreeStatus(repoDir) {
|
|
37308
|
+
return runGitTask(async () => {
|
|
37309
|
+
const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
|
|
37310
|
+
const hasUncommittedChanges = uncommittedFileCount > 0;
|
|
37311
|
+
const ahead = await commitsAheadOfRemoteTracking(repoDir);
|
|
37312
|
+
return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
|
|
37313
|
+
});
|
|
37314
|
+
}
|
|
37315
|
+
async function aggregateSessionPathsWorkingTreeStatus(paths) {
|
|
37316
|
+
let hasUncommittedChanges = false;
|
|
37317
|
+
let hasUnpushedCommits = false;
|
|
37318
|
+
let uncommittedFileCount = 0;
|
|
37319
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
37320
|
+
const s = await getRepoWorkingTreeStatus(p);
|
|
37321
|
+
uncommittedFileCount += s.uncommittedFileCount;
|
|
37322
|
+
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
37323
|
+
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
37324
|
+
});
|
|
37325
|
+
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
37326
|
+
}
|
|
37327
|
+
async function pushAheadOfUpstreamForPaths(paths) {
|
|
37328
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
37329
|
+
const g = cliSimpleGit(p);
|
|
37330
|
+
const ahead = await commitsAheadOfRemoteTracking(p);
|
|
37331
|
+
if (ahead <= 0) return;
|
|
37332
|
+
await g.push();
|
|
37333
|
+
});
|
|
37334
|
+
}
|
|
37335
|
+
|
|
37336
|
+
// src/worktrees/manager/resolve-commit-targets.ts
|
|
37337
|
+
function bridgeRootBinding(bridgeRoot) {
|
|
37338
|
+
return {
|
|
37339
|
+
sessionParentPath: bridgeRoot,
|
|
37340
|
+
workingTreeRelRoot: bridgeRoot,
|
|
37341
|
+
repoCheckoutPaths: [bridgeRoot]
|
|
37342
|
+
};
|
|
37343
|
+
}
|
|
37344
|
+
async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
|
|
37345
|
+
const sid = sessionId.trim();
|
|
37346
|
+
const paths = cache2.getRepoCheckoutPathsRef(sid);
|
|
37347
|
+
if (paths?.length) return paths;
|
|
37348
|
+
const disc = await discover(sid);
|
|
37349
|
+
if (disc?.repoCheckoutPaths.length) {
|
|
37350
|
+
cache2.remember(sid, disc);
|
|
37351
|
+
return disc.repoCheckoutPaths;
|
|
37352
|
+
}
|
|
37353
|
+
const bridgeRoot = getBridgeRoot();
|
|
37354
|
+
cache2.remember(sid, bridgeRootBinding(bridgeRoot));
|
|
37355
|
+
return [bridgeRoot];
|
|
37356
|
+
}
|
|
37357
|
+
|
|
37358
|
+
// src/worktrees/manager/git/get-session-working-tree-status.ts
|
|
37359
|
+
async function getSessionWorkingTreeStatus(cache2, sessionId, discover) {
|
|
37360
|
+
return aggregateSessionPathsWorkingTreeStatus(
|
|
37361
|
+
await resolveCommitTargetsAsync(sessionId, cache2, discover)
|
|
37362
|
+
);
|
|
37363
|
+
}
|
|
37364
|
+
|
|
36865
37365
|
// src/git/changes/types.ts
|
|
36866
37366
|
var MAX_PATCH_CHARS = 35e4;
|
|
36867
37367
|
|
|
@@ -36919,106 +37419,14 @@ function formatRemoteDisplayLabel(remoteUrl) {
|
|
|
36919
37419
|
return `origin \xB7 ${hostPath}`;
|
|
36920
37420
|
}
|
|
36921
37421
|
|
|
36922
|
-
// src/git/changes/get-working-tree-change-repo-details.ts
|
|
36923
|
-
import * as
|
|
37422
|
+
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
37423
|
+
import * as path29 from "node:path";
|
|
36924
37424
|
|
|
36925
|
-
// src/git/
|
|
36926
|
-
|
|
36927
|
-
|
|
36928
|
-
|
|
36929
|
-
|
|
36930
|
-
return v || null;
|
|
36931
|
-
} catch {
|
|
36932
|
-
return null;
|
|
36933
|
-
}
|
|
36934
|
-
}
|
|
36935
|
-
async function revParseSafe(g, ref) {
|
|
36936
|
-
try {
|
|
36937
|
-
const v = (await g.raw(["rev-parse", ref])).trim();
|
|
36938
|
-
return v || null;
|
|
36939
|
-
} catch {
|
|
36940
|
-
return null;
|
|
36941
|
-
}
|
|
36942
|
-
}
|
|
36943
|
-
async function resolveRemoteTrackingRefForAhead(g) {
|
|
36944
|
-
try {
|
|
36945
|
-
await g.raw(["rev-parse", "--verify", "HEAD@{upstream}"]);
|
|
36946
|
-
return "HEAD@{upstream}";
|
|
36947
|
-
} catch {
|
|
36948
|
-
try {
|
|
36949
|
-
await g.raw(["rev-parse", "--verify", "@{u}"]);
|
|
36950
|
-
return "@{u}";
|
|
36951
|
-
} catch {
|
|
36952
|
-
}
|
|
36953
|
-
}
|
|
36954
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
36955
|
-
if (!branch || branch === "HEAD") return null;
|
|
36956
|
-
const remote = await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
36957
|
-
const merge2 = await tryConfigGet(g, `branch.${branch}.merge`);
|
|
36958
|
-
if (merge2) {
|
|
36959
|
-
const upstreamBranch = merge2.replace(/^refs\/heads\//, "");
|
|
36960
|
-
const ref = `refs/remotes/${remote}/${upstreamBranch}`;
|
|
36961
|
-
try {
|
|
36962
|
-
await g.raw(["rev-parse", "--verify", ref]);
|
|
36963
|
-
return ref;
|
|
36964
|
-
} catch {
|
|
36965
|
-
}
|
|
36966
|
-
}
|
|
36967
|
-
const fallbackRef = `refs/remotes/${remote}/${branch}`;
|
|
36968
|
-
try {
|
|
36969
|
-
await g.raw(["rev-parse", "--verify", fallbackRef]);
|
|
36970
|
-
return fallbackRef;
|
|
36971
|
-
} catch {
|
|
36972
|
-
return null;
|
|
36973
|
-
}
|
|
36974
|
-
}
|
|
36975
|
-
async function remoteForCurrentBranch(g) {
|
|
36976
|
-
const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
36977
|
-
if (!branch || branch === "HEAD") return "origin";
|
|
36978
|
-
return await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
|
|
36979
|
-
}
|
|
36980
|
-
async function resolveDefaultRemoteBranchRef(g, remote) {
|
|
36981
|
-
const headSym = `refs/remotes/${remote}/HEAD`;
|
|
36982
|
-
try {
|
|
36983
|
-
const resolved = (await g.raw(["symbolic-ref", "-q", "--verify", headSym])).trim();
|
|
36984
|
-
if (resolved.startsWith("refs/remotes/")) return resolved;
|
|
36985
|
-
} catch {
|
|
36986
|
-
}
|
|
36987
|
-
for (const name of ["main", "master", "trunk", "develop"]) {
|
|
36988
|
-
const r = `refs/remotes/${remote}/${name}`;
|
|
36989
|
-
try {
|
|
36990
|
-
await g.raw(["rev-parse", "--verify", r]);
|
|
36991
|
-
return r;
|
|
36992
|
-
} catch {
|
|
36993
|
-
}
|
|
36994
|
-
}
|
|
36995
|
-
return null;
|
|
36996
|
-
}
|
|
36997
|
-
async function resolveBaseShaForUnpushedCommits(g) {
|
|
36998
|
-
const trackingRef = await resolveRemoteTrackingRefForAhead(g);
|
|
36999
|
-
if (trackingRef) {
|
|
37000
|
-
const sha = await revParseSafe(g, trackingRef);
|
|
37001
|
-
if (sha) return sha;
|
|
37002
|
-
}
|
|
37003
|
-
const remote = await remoteForCurrentBranch(g);
|
|
37004
|
-
const defaultRef = await resolveDefaultRemoteBranchRef(g, remote);
|
|
37005
|
-
if (!defaultRef) return null;
|
|
37006
|
-
return revParseSafe(g, defaultRef);
|
|
37007
|
-
}
|
|
37008
|
-
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
37009
|
-
const g = cliSimpleGit(repoDir);
|
|
37010
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
37011
|
-
if (!headSha) return 0;
|
|
37012
|
-
const baseSha = await resolveBaseShaForUnpushedCommits(g);
|
|
37013
|
-
if (!baseSha || baseSha === headSha) return 0;
|
|
37014
|
-
try {
|
|
37015
|
-
const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
37016
|
-
const n = parseInt(String(out).trim(), 10);
|
|
37017
|
-
return Number.isNaN(n) ? 0 : n;
|
|
37018
|
-
} catch {
|
|
37019
|
-
return 0;
|
|
37020
|
-
}
|
|
37021
|
-
}
|
|
37425
|
+
// src/git/changes/repo/build-working-tree-change-repo-detail.ts
|
|
37426
|
+
import * as path28 from "node:path";
|
|
37427
|
+
|
|
37428
|
+
// src/git/commits/list-unpushed-commits.ts
|
|
37429
|
+
import * as path23 from "node:path";
|
|
37022
37430
|
|
|
37023
37431
|
// src/git/commits/lib/parse-log-lines.ts
|
|
37024
37432
|
function parseLogShaDateSubjectLines(raw) {
|
|
@@ -37038,9 +37446,14 @@ function parseLogShaDateSubjectLines(raw) {
|
|
|
37038
37446
|
}
|
|
37039
37447
|
|
|
37040
37448
|
// src/git/commits/list-unpushed-commits.ts
|
|
37449
|
+
var UNPUSHED_CACHE_TTL_MS = 3e4;
|
|
37450
|
+
var unpushedCache = /* @__PURE__ */ new Map();
|
|
37041
37451
|
async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
37042
37452
|
if (baseSha === headSha) return [];
|
|
37043
37453
|
try {
|
|
37454
|
+
const countOut = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
|
|
37455
|
+
const count = parseInt(String(countOut).trim(), 10);
|
|
37456
|
+
if (!Number.isFinite(count) || count <= 0) return [];
|
|
37044
37457
|
const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
|
|
37045
37458
|
return parseLogShaDateSubjectLines(logOut);
|
|
37046
37459
|
} catch {
|
|
@@ -37048,12 +37461,22 @@ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
|
37048
37461
|
}
|
|
37049
37462
|
}
|
|
37050
37463
|
async function listUnpushedCommits(repoDir) {
|
|
37464
|
+
const key = path23.resolve(repoDir);
|
|
37465
|
+
const now = Date.now();
|
|
37466
|
+
const cached2 = unpushedCache.get(key);
|
|
37467
|
+
if (cached2 && cached2.expiresAt > now) return cached2.commits;
|
|
37051
37468
|
const g = cliSimpleGit(repoDir);
|
|
37052
|
-
const headSha = await
|
|
37053
|
-
|
|
37054
|
-
|
|
37055
|
-
|
|
37056
|
-
|
|
37469
|
+
const [headSha, baseSha] = await Promise.all([
|
|
37470
|
+
revParseSafe(g, "HEAD"),
|
|
37471
|
+
resolveBaseShaForUnpushedCommits(g, repoDir)
|
|
37472
|
+
]);
|
|
37473
|
+
if (!headSha || !baseSha) {
|
|
37474
|
+
unpushedCache.set(key, { commits: [], expiresAt: now + UNPUSHED_CACHE_TTL_MS });
|
|
37475
|
+
return [];
|
|
37476
|
+
}
|
|
37477
|
+
const commits = await gitLogNotReachableFromBase(g, baseSha, headSha);
|
|
37478
|
+
unpushedCache.set(key, { commits, expiresAt: now + UNPUSHED_CACHE_TTL_MS });
|
|
37479
|
+
return commits;
|
|
37057
37480
|
}
|
|
37058
37481
|
|
|
37059
37482
|
// src/git/commits/lib/sanitize-recent-commits-limit.ts
|
|
@@ -37067,72 +37490,40 @@ function sanitizeRecentCommitsLimit(value) {
|
|
|
37067
37490
|
}
|
|
37068
37491
|
|
|
37069
37492
|
// src/git/commits/list-recent-commits.ts
|
|
37070
|
-
async function
|
|
37493
|
+
async function listRecentCommitRefs(repoDir, limitInput) {
|
|
37071
37494
|
const limit = sanitizeRecentCommitsLimit(limitInput);
|
|
37072
37495
|
const g = cliSimpleGit(repoDir);
|
|
37073
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
37074
|
-
if (!headSha) return { commits: [], hasMore: false };
|
|
37075
|
-
const unpushedSet = new Set((await listUnpushedCommits(repoDir)).map((c) => c.sha));
|
|
37076
37496
|
try {
|
|
37077
|
-
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit), "HEAD"]);
|
|
37078
|
-
const
|
|
37079
|
-
|
|
37080
|
-
|
|
37081
|
-
}));
|
|
37082
|
-
let hasMore = false;
|
|
37083
|
-
if (commits.length === limit) {
|
|
37084
|
-
const nextOut = await g.raw(["log", "-n", "1", `--skip=${limit}`, "--format=%H", "HEAD"]);
|
|
37085
|
-
hasMore = /^[0-9a-f]{7,40}$/i.test(String(nextOut).trim());
|
|
37086
|
-
}
|
|
37497
|
+
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit + 1), "HEAD"]);
|
|
37498
|
+
const parsed = parseLogShaDateSubjectLines(logOut);
|
|
37499
|
+
const hasMore = parsed.length > limit;
|
|
37500
|
+
const commits = parsed.slice(0, limit);
|
|
37087
37501
|
return { commits, hasMore };
|
|
37088
37502
|
} catch {
|
|
37089
37503
|
return { commits: [], hasMore: false };
|
|
37090
37504
|
}
|
|
37091
37505
|
}
|
|
37092
37506
|
|
|
37093
|
-
// src/git/changes/
|
|
37094
|
-
|
|
37095
|
-
return path48.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
|
|
37096
|
-
}
|
|
37097
|
-
|
|
37098
|
-
// src/git/changes/parse/expand-git-rename-numstat-path.ts
|
|
37099
|
-
function expandGitRenameNumstatPath(rawPath) {
|
|
37100
|
-
const open2 = rawPath.indexOf("{");
|
|
37101
|
-
const arrow = rawPath.indexOf("=>");
|
|
37102
|
-
const close = rawPath.lastIndexOf("}");
|
|
37103
|
-
if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
|
|
37104
|
-
return null;
|
|
37105
|
-
}
|
|
37106
|
-
const prefix = rawPath.slice(0, open2);
|
|
37107
|
-
const suffix = rawPath.slice(close + 1);
|
|
37108
|
-
const oldPart = rawPath.slice(open2 + 1, arrow).trim();
|
|
37109
|
-
const newPart = rawPath.slice(arrow + 2, close).trim();
|
|
37110
|
-
return {
|
|
37111
|
-
oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
|
|
37112
|
-
newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
|
|
37113
|
-
};
|
|
37114
|
-
}
|
|
37115
|
-
function isGitRenameNumstatPath(rawPath) {
|
|
37116
|
-
return expandGitRenameNumstatPath(rawPath) != null;
|
|
37117
|
-
}
|
|
37507
|
+
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
37508
|
+
import * as path25 from "node:path";
|
|
37118
37509
|
|
|
37119
37510
|
// src/git/changes/rows/build-changed-file-row.ts
|
|
37120
37511
|
function buildChangedFileRow(options) {
|
|
37121
37512
|
const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
|
|
37122
|
-
const
|
|
37513
|
+
const workspaceRelPath = posixJoinDirFile(options.repoRelPath, pathInRepo);
|
|
37123
37514
|
const additions = options.numEntry?.additions ?? 0;
|
|
37124
37515
|
const deletions = options.numEntry?.deletions ?? 0;
|
|
37125
37516
|
let change = options.nameEntry?.change ?? "modified";
|
|
37126
|
-
let
|
|
37517
|
+
let movedFromWorkspaceRelPath;
|
|
37127
37518
|
let movedFromPathInRepo;
|
|
37128
37519
|
const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
|
|
37129
37520
|
if (change === "moved" && oldPathInRepo) {
|
|
37130
37521
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
37131
|
-
|
|
37522
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
37132
37523
|
} else if (oldPathInRepo && change === "modified") {
|
|
37133
37524
|
change = "moved";
|
|
37134
37525
|
movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
|
|
37135
|
-
|
|
37526
|
+
movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
|
|
37136
37527
|
}
|
|
37137
37528
|
if (options.untracked && !options.nameEntry) {
|
|
37138
37529
|
change = "added";
|
|
@@ -37142,11 +37533,11 @@ function buildChangedFileRow(options) {
|
|
|
37142
37533
|
else change = "modified";
|
|
37143
37534
|
}
|
|
37144
37535
|
return {
|
|
37145
|
-
|
|
37536
|
+
workspaceRelPath,
|
|
37146
37537
|
additions,
|
|
37147
37538
|
deletions,
|
|
37148
37539
|
change,
|
|
37149
|
-
...
|
|
37540
|
+
...movedFromWorkspaceRelPath ? { movedFromWorkspaceRelPath } : {},
|
|
37150
37541
|
...movedFromPathInRepo ? { movedFromPathInRepo } : {}
|
|
37151
37542
|
};
|
|
37152
37543
|
}
|
|
@@ -37205,8 +37596,8 @@ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, mov
|
|
|
37205
37596
|
}
|
|
37206
37597
|
|
|
37207
37598
|
// src/git/changes/total-lines/resolve-changed-file-total-lines.ts
|
|
37208
|
-
import * as
|
|
37209
|
-
import * as
|
|
37599
|
+
import * as fs18 from "node:fs";
|
|
37600
|
+
import * as path24 from "node:path";
|
|
37210
37601
|
|
|
37211
37602
|
// src/git/changes/lines/count-lines.ts
|
|
37212
37603
|
import { createReadStream } from "node:fs";
|
|
@@ -37252,13 +37643,13 @@ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
|
|
|
37252
37643
|
}
|
|
37253
37644
|
async function resolveWorkingTreeFileTotalLines(options) {
|
|
37254
37645
|
if (options.isBinary) return null;
|
|
37255
|
-
const filePath =
|
|
37646
|
+
const filePath = path24.join(options.repoGitCwd, options.pathInRepo);
|
|
37256
37647
|
if (options.change === "removed") {
|
|
37257
37648
|
return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
|
|
37258
37649
|
}
|
|
37259
37650
|
if (options.change === "moved" || options.change === "modified") {
|
|
37260
37651
|
try {
|
|
37261
|
-
const st = await
|
|
37652
|
+
const st = await fs18.promises.stat(filePath);
|
|
37262
37653
|
if (!st.isFile()) return null;
|
|
37263
37654
|
return await countTextFileLines(filePath);
|
|
37264
37655
|
} catch {
|
|
@@ -37278,14 +37669,14 @@ async function resolveCommitFileTotalLines(options) {
|
|
|
37278
37669
|
return null;
|
|
37279
37670
|
}
|
|
37280
37671
|
|
|
37281
|
-
// src/git/changes/listing/path-in-repo-from-
|
|
37282
|
-
function
|
|
37672
|
+
// src/git/changes/listing/path-in-repo-from-workspace.ts
|
|
37673
|
+
function pathInRepoFromWorkspace(workspaceRelPath, repoRelPath) {
|
|
37283
37674
|
const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
37284
|
-
if (normRel === ".") return
|
|
37285
|
-
if (
|
|
37286
|
-
return
|
|
37675
|
+
if (normRel === ".") return workspaceRelPath;
|
|
37676
|
+
if (workspaceRelPath.startsWith(`${normRel}/`)) {
|
|
37677
|
+
return workspaceRelPath.slice(normRel.length + 1);
|
|
37287
37678
|
}
|
|
37288
|
-
return
|
|
37679
|
+
return workspaceRelPath;
|
|
37289
37680
|
}
|
|
37290
37681
|
function normalizeRepoRelPath(repoRelPath) {
|
|
37291
37682
|
return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
@@ -37294,7 +37685,7 @@ function normalizeRepoRelPath(repoRelPath) {
|
|
|
37294
37685
|
// src/git/changes/listing/enrich-commit-file-rows.ts
|
|
37295
37686
|
async function enrichCommitFileRows(options) {
|
|
37296
37687
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
37297
|
-
const pathInRepo =
|
|
37688
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
37298
37689
|
row.patchContent = await unifiedDiffForFileInRange(
|
|
37299
37690
|
options.repoGitCwd,
|
|
37300
37691
|
options.range,
|
|
@@ -37313,102 +37704,6 @@ async function enrichCommitFileRows(options) {
|
|
|
37313
37704
|
});
|
|
37314
37705
|
}
|
|
37315
37706
|
|
|
37316
|
-
// src/git/changes/parse/parse-name-status-entries.ts
|
|
37317
|
-
function parseNameStatusEntries(lines) {
|
|
37318
|
-
const entries = [];
|
|
37319
|
-
for (const line of lines) {
|
|
37320
|
-
if (!line.trim()) continue;
|
|
37321
|
-
const tabParts = line.split(" ");
|
|
37322
|
-
if (tabParts.length < 2) continue;
|
|
37323
|
-
const status = tabParts[0].trim();
|
|
37324
|
-
const code = status[0];
|
|
37325
|
-
if (code === "A") {
|
|
37326
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
|
|
37327
|
-
} else if (code === "D") {
|
|
37328
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
|
|
37329
|
-
} else if (code === "R" || code === "C") {
|
|
37330
|
-
if (tabParts.length >= 3) {
|
|
37331
|
-
entries.push({
|
|
37332
|
-
pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
|
|
37333
|
-
oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
|
|
37334
|
-
change: "moved"
|
|
37335
|
-
});
|
|
37336
|
-
}
|
|
37337
|
-
} else if (code === "M" || code === "U" || code === "T") {
|
|
37338
|
-
entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
|
|
37339
|
-
}
|
|
37340
|
-
}
|
|
37341
|
-
return entries;
|
|
37342
|
-
}
|
|
37343
|
-
function parseNameStatusLines(lines) {
|
|
37344
|
-
const m = /* @__PURE__ */ new Map();
|
|
37345
|
-
for (const entry of parseNameStatusEntries(lines)) {
|
|
37346
|
-
m.set(entry.pathInRepo, entry.change);
|
|
37347
|
-
}
|
|
37348
|
-
return m;
|
|
37349
|
-
}
|
|
37350
|
-
|
|
37351
|
-
// src/git/changes/parse/parse-numstat-entries.ts
|
|
37352
|
-
function parseNumstatCounts(parts) {
|
|
37353
|
-
const [a, d] = parts;
|
|
37354
|
-
return {
|
|
37355
|
-
additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
|
|
37356
|
-
deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
|
|
37357
|
-
};
|
|
37358
|
-
}
|
|
37359
|
-
function parseNumstatLine(line) {
|
|
37360
|
-
const parts = line.split(" ");
|
|
37361
|
-
if (parts.length < 3) return null;
|
|
37362
|
-
const rawPath = parts[parts.length - 1];
|
|
37363
|
-
const { additions, deletions } = parseNumstatCounts(parts);
|
|
37364
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
37365
|
-
const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
|
|
37366
|
-
return {
|
|
37367
|
-
pathInRepo,
|
|
37368
|
-
additions,
|
|
37369
|
-
deletions,
|
|
37370
|
-
...expanded ? { oldPathInRepo: expanded.oldPath } : {}
|
|
37371
|
-
};
|
|
37372
|
-
}
|
|
37373
|
-
function parseNumstatEntries(lines) {
|
|
37374
|
-
const m = /* @__PURE__ */ new Map();
|
|
37375
|
-
for (const line of lines) {
|
|
37376
|
-
if (!line.trim()) continue;
|
|
37377
|
-
const parts = line.split(" ");
|
|
37378
|
-
if (parts.length < 3) continue;
|
|
37379
|
-
const rawPath = parts[parts.length - 1];
|
|
37380
|
-
const expanded = expandGitRenameNumstatPath(rawPath);
|
|
37381
|
-
const entry = parseNumstatLine(line);
|
|
37382
|
-
if (!entry) continue;
|
|
37383
|
-
const existing = m.get(entry.pathInRepo);
|
|
37384
|
-
if (!existing || expanded) {
|
|
37385
|
-
m.set(entry.pathInRepo, entry);
|
|
37386
|
-
}
|
|
37387
|
-
}
|
|
37388
|
-
return m;
|
|
37389
|
-
}
|
|
37390
|
-
function parseNumstat(lines) {
|
|
37391
|
-
const m = /* @__PURE__ */ new Map();
|
|
37392
|
-
for (const [path48, entry] of parseNumstatEntries(lines)) {
|
|
37393
|
-
m.set(path48, { additions: entry.additions, deletions: entry.deletions });
|
|
37394
|
-
}
|
|
37395
|
-
return m;
|
|
37396
|
-
}
|
|
37397
|
-
|
|
37398
|
-
// src/git/changes/parse/numstat-from-git-no-index.ts
|
|
37399
|
-
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
37400
|
-
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
37401
|
-
try {
|
|
37402
|
-
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
37403
|
-
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
37404
|
-
const parsed = parseNumstatLine(first2);
|
|
37405
|
-
if (!parsed) return null;
|
|
37406
|
-
return { additions: parsed.additions, deletions: parsed.deletions };
|
|
37407
|
-
} catch {
|
|
37408
|
-
return null;
|
|
37409
|
-
}
|
|
37410
|
-
}
|
|
37411
|
-
|
|
37412
37707
|
// src/git/changes/rows/collect-moved-source-paths.ts
|
|
37413
37708
|
function collectMovedSourcePaths(nameEntries, numByPath) {
|
|
37414
37709
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -37488,8 +37783,8 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
37488
37783
|
const aHasPatch = Boolean(a.patchContent?.trim());
|
|
37489
37784
|
const bHasPatch = Boolean(b.patchContent?.trim());
|
|
37490
37785
|
if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
|
|
37491
|
-
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.
|
|
37492
|
-
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.
|
|
37786
|
+
const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromWorkspaceRelPath);
|
|
37787
|
+
const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromWorkspaceRelPath);
|
|
37493
37788
|
if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
|
|
37494
37789
|
return a;
|
|
37495
37790
|
}
|
|
@@ -37498,30 +37793,39 @@ function pickPreferredChangedFileRow(a, b) {
|
|
|
37498
37793
|
function dedupeChangedFileRows(rows) {
|
|
37499
37794
|
const byPath = /* @__PURE__ */ new Map();
|
|
37500
37795
|
for (const row of rows) {
|
|
37501
|
-
const key = normalizeGitDiffPath(row.
|
|
37502
|
-
const normalizedRow = key === row.
|
|
37796
|
+
const key = normalizeGitDiffPath(row.workspaceRelPath);
|
|
37797
|
+
const normalizedRow = key === row.workspaceRelPath ? row : { ...row, workspaceRelPath: key };
|
|
37503
37798
|
const existing = byPath.get(key);
|
|
37504
37799
|
if (!existing) {
|
|
37505
37800
|
byPath.set(key, normalizedRow);
|
|
37506
37801
|
continue;
|
|
37507
37802
|
}
|
|
37508
37803
|
const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
|
|
37509
|
-
byPath.set(key, { ...preferRow,
|
|
37804
|
+
byPath.set(key, { ...preferRow, workspaceRelPath: key });
|
|
37510
37805
|
}
|
|
37511
37806
|
return [...byPath.values()];
|
|
37512
37807
|
}
|
|
37513
37808
|
|
|
37514
37809
|
// src/git/changes/listing/list-changed-files-for-commit.ts
|
|
37515
|
-
|
|
37810
|
+
var COMMIT_FILES_CACHE_TTL_MS = 5 * 6e4;
|
|
37811
|
+
var commitFilesCache = /* @__PURE__ */ new Map();
|
|
37812
|
+
function commitFilesCacheKey(repoGitCwd, commitSha) {
|
|
37813
|
+
return `${path25.resolve(repoGitCwd)}:${commitSha}`;
|
|
37814
|
+
}
|
|
37815
|
+
async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha, options = {}) {
|
|
37816
|
+
const cacheKey = commitFilesCacheKey(repoGitCwd, commitSha);
|
|
37817
|
+
if (!options.includeFilePatches) {
|
|
37818
|
+
const now = Date.now();
|
|
37819
|
+
const cached2 = commitFilesCache.get(cacheKey);
|
|
37820
|
+
if (cached2 && cached2.expiresAt > now) return cached2.rows;
|
|
37821
|
+
}
|
|
37516
37822
|
const g = cliSimpleGit(repoGitCwd);
|
|
37517
|
-
const
|
|
37518
|
-
const range = `${parent}..${commitSha}`;
|
|
37823
|
+
const normRel = normalizeRepoRelPath(repoRelPath);
|
|
37519
37824
|
const [nameStatusRaw, numstatRaw] = await Promise.all([
|
|
37520
|
-
g.raw(["diff",
|
|
37521
|
-
g.raw(["
|
|
37825
|
+
g.raw(["diff-tree", "--no-commit-id", "--name-status", "-r", ...RENAME_DIFF_ARGS, commitSha]).catch(() => ""),
|
|
37826
|
+
g.raw(["show", "--numstat", "--format=format:", commitSha]).catch(() => "")
|
|
37522
37827
|
]);
|
|
37523
37828
|
const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
|
|
37524
|
-
const normRel = normalizeRepoRelPath(repoRelPath);
|
|
37525
37829
|
const paths = collectChangedPaths({
|
|
37526
37830
|
nameEntries: parsed.nameEntries,
|
|
37527
37831
|
numByPath: parsed.numByPath
|
|
@@ -37534,29 +37838,39 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
37534
37838
|
movedSourcePaths: parsed.movedSourcePaths
|
|
37535
37839
|
});
|
|
37536
37840
|
rows = dedupeChangedFileRows(rows);
|
|
37537
|
-
|
|
37538
|
-
|
|
37539
|
-
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
|
|
37543
|
-
|
|
37544
|
-
|
|
37545
|
-
|
|
37841
|
+
if (options.includeFilePatches) {
|
|
37842
|
+
const parent = await parentForCommitDiff(g, commitSha);
|
|
37843
|
+
const range = `${parent}..${commitSha}`;
|
|
37844
|
+
await enrichCommitFileRows({
|
|
37845
|
+
rows,
|
|
37846
|
+
repoGitCwd,
|
|
37847
|
+
repoRelPath: normRel,
|
|
37848
|
+
range,
|
|
37849
|
+
commitSha,
|
|
37850
|
+
parentSha: parent,
|
|
37851
|
+
binaryByPath: parsed.binaryByPath
|
|
37852
|
+
});
|
|
37853
|
+
}
|
|
37546
37854
|
const finalRows = dedupeChangedFileRows(rows);
|
|
37547
|
-
finalRows.sort((a, b) => a.
|
|
37855
|
+
finalRows.sort((a, b) => a.workspaceRelPath.localeCompare(b.workspaceRelPath));
|
|
37856
|
+
if (!options.includeFilePatches) {
|
|
37857
|
+
commitFilesCache.set(cacheKey, {
|
|
37858
|
+
rows: finalRows,
|
|
37859
|
+
expiresAt: Date.now() + COMMIT_FILES_CACHE_TTL_MS
|
|
37860
|
+
});
|
|
37861
|
+
}
|
|
37548
37862
|
return finalRows;
|
|
37549
37863
|
}
|
|
37550
37864
|
|
|
37551
37865
|
// src/git/changes/listing/apply-untracked-file-stats.ts
|
|
37552
|
-
import * as
|
|
37553
|
-
import * as
|
|
37866
|
+
import * as fs19 from "node:fs";
|
|
37867
|
+
import * as path26 from "node:path";
|
|
37554
37868
|
function createUntrackedStatsApplier(options) {
|
|
37555
37869
|
return async (row, pathInRepo) => {
|
|
37556
37870
|
if (!options.untrackedSet.has(pathInRepo)) return;
|
|
37557
37871
|
if (options.nameByPath.has(pathInRepo)) return;
|
|
37558
37872
|
if (row.change === "moved") return;
|
|
37559
|
-
const repoFilePath =
|
|
37873
|
+
const repoFilePath = path26.join(options.repoGitCwd, pathInRepo);
|
|
37560
37874
|
const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
|
|
37561
37875
|
if (fromGit) {
|
|
37562
37876
|
row.additions = fromGit.additions;
|
|
@@ -37564,7 +37878,7 @@ function createUntrackedStatsApplier(options) {
|
|
|
37564
37878
|
return;
|
|
37565
37879
|
}
|
|
37566
37880
|
try {
|
|
37567
|
-
const st = await
|
|
37881
|
+
const st = await fs19.promises.stat(repoFilePath);
|
|
37568
37882
|
row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
|
|
37569
37883
|
} catch {
|
|
37570
37884
|
row.additions = 0;
|
|
@@ -37574,13 +37888,75 @@ function createUntrackedStatsApplier(options) {
|
|
|
37574
37888
|
}
|
|
37575
37889
|
|
|
37576
37890
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
37577
|
-
import * as
|
|
37891
|
+
import * as path27 from "node:path";
|
|
37578
37892
|
|
|
37579
|
-
// src/git/changes/patch/hydrate-
|
|
37580
|
-
|
|
37893
|
+
// src/git/changes/patch/hydrate/append-injected-context-lines.ts
|
|
37894
|
+
async function appendInjectedContextLines(out, inject, maybeYield, injectedTotal) {
|
|
37895
|
+
for (const t of inject) {
|
|
37896
|
+
out.push(` ${t}`);
|
|
37897
|
+
injectedTotal.value++;
|
|
37898
|
+
await maybeYield();
|
|
37899
|
+
}
|
|
37900
|
+
}
|
|
37901
|
+
|
|
37902
|
+
// src/git/changes/patch/hydrate/constants.ts
|
|
37581
37903
|
var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
37582
37904
|
var MAX_HYDRATE_LINES_PER_GAP = 8e3;
|
|
37583
37905
|
var MAX_HYDRATE_LINES_PER_FILE = 8e4;
|
|
37906
|
+
var HYDRATE_YIELD_EVERY_LINES = 512;
|
|
37907
|
+
|
|
37908
|
+
// src/git/changes/patch/hydrate/collect-gap-inject-lines.ts
|
|
37909
|
+
async function collectGapInjectLines(options) {
|
|
37910
|
+
if (options.injectedTotal >= MAX_HYDRATE_LINES_PER_FILE) return null;
|
|
37911
|
+
const remaining = MAX_HYDRATE_LINES_PER_FILE - options.injectedTotal;
|
|
37912
|
+
if (options.gapNewEnd >= options.gapNewStart && options.change !== "removed") {
|
|
37913
|
+
const nNew = options.gapNewEnd - options.gapNewStart + 1;
|
|
37914
|
+
if (options.gapOldEnd < options.gapOldStart || options.gapOldEnd - options.gapOldStart + 1 === nNew) {
|
|
37915
|
+
const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, remaining);
|
|
37916
|
+
const dl = await options.diskLines();
|
|
37917
|
+
if (dl && cap > 0) {
|
|
37918
|
+
return dl.slice(options.gapNewStart - 1, options.gapNewStart - 1 + cap);
|
|
37919
|
+
}
|
|
37920
|
+
}
|
|
37921
|
+
return null;
|
|
37922
|
+
}
|
|
37923
|
+
if (options.gapOldEnd >= options.gapOldStart && options.change === "removed") {
|
|
37924
|
+
const nOld = options.gapOldEnd - options.gapOldStart + 1;
|
|
37925
|
+
const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, remaining);
|
|
37926
|
+
const bl = await options.blobLines();
|
|
37927
|
+
if (bl && cap > 0) {
|
|
37928
|
+
return bl.slice(options.gapOldStart - 1, options.gapOldStart - 1 + cap);
|
|
37929
|
+
}
|
|
37930
|
+
}
|
|
37931
|
+
return null;
|
|
37932
|
+
}
|
|
37933
|
+
|
|
37934
|
+
// src/git/changes/patch/hydrate/consume-unified-hunk-body.ts
|
|
37935
|
+
async function consumeUnifiedHunkBody(options) {
|
|
37936
|
+
let i = options.startIndex;
|
|
37937
|
+
let oldConsumed = 0;
|
|
37938
|
+
let newConsumed = 0;
|
|
37939
|
+
while (i < options.all.length) {
|
|
37940
|
+
await options.maybeYield();
|
|
37941
|
+
const bl = options.all[i];
|
|
37942
|
+
if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
|
|
37943
|
+
options.out.push(bl);
|
|
37944
|
+
i++;
|
|
37945
|
+
if (bl.startsWith("\\")) continue;
|
|
37946
|
+
const ch = bl[0];
|
|
37947
|
+
if (ch === " ") {
|
|
37948
|
+
oldConsumed++;
|
|
37949
|
+
newConsumed++;
|
|
37950
|
+
} else if (ch === "-") {
|
|
37951
|
+
oldConsumed++;
|
|
37952
|
+
} else if (ch === "+") {
|
|
37953
|
+
newConsumed++;
|
|
37954
|
+
}
|
|
37955
|
+
}
|
|
37956
|
+
return { nextIndex: i, oldConsumed, newConsumed };
|
|
37957
|
+
}
|
|
37958
|
+
|
|
37959
|
+
// src/git/changes/patch/hydrate/read-git-blob-lines.ts
|
|
37584
37960
|
async function readGitBlobLines(repoCwd, pathInRepo) {
|
|
37585
37961
|
try {
|
|
37586
37962
|
const rel = pathInRepo.replace(/\\/g, "/");
|
|
@@ -37590,35 +37966,66 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
|
|
|
37590
37966
|
return null;
|
|
37591
37967
|
}
|
|
37592
37968
|
}
|
|
37969
|
+
|
|
37970
|
+
// src/git/changes/patch/hydrate/read-worktree-file-lines.ts
|
|
37971
|
+
import * as fs20 from "node:fs";
|
|
37593
37972
|
async function readWorktreeFileLines(filePath) {
|
|
37594
37973
|
try {
|
|
37595
|
-
const raw = await
|
|
37974
|
+
const raw = await fs20.promises.readFile(filePath, "utf8");
|
|
37596
37975
|
return raw.split(/\r?\n/);
|
|
37597
37976
|
} catch {
|
|
37598
37977
|
return null;
|
|
37599
37978
|
}
|
|
37600
37979
|
}
|
|
37601
|
-
|
|
37602
|
-
|
|
37603
|
-
|
|
37604
|
-
const out = [];
|
|
37605
|
-
let prevOldEnd = 0;
|
|
37606
|
-
let prevNewEnd = 0;
|
|
37607
|
-
let injectedTotal = 0;
|
|
37608
|
-
let i = 0;
|
|
37980
|
+
|
|
37981
|
+
// src/git/changes/patch/hydrate/create-hydrate-source-lines.ts
|
|
37982
|
+
function createHydrateSourceLines(options) {
|
|
37609
37983
|
let blobCache;
|
|
37610
37984
|
let diskCache;
|
|
37611
37985
|
const blobLines = async () => {
|
|
37612
37986
|
if (blobCache !== void 0) return blobCache;
|
|
37613
|
-
blobCache = await readGitBlobLines(repoGitCwd, pathInRepo);
|
|
37987
|
+
blobCache = await readGitBlobLines(options.repoGitCwd, options.pathInRepo);
|
|
37614
37988
|
return blobCache;
|
|
37615
37989
|
};
|
|
37616
37990
|
const diskLines = async () => {
|
|
37617
37991
|
if (diskCache !== void 0) return diskCache;
|
|
37618
|
-
diskCache = await readWorktreeFileLines(filePath);
|
|
37992
|
+
diskCache = await readWorktreeFileLines(options.filePath);
|
|
37619
37993
|
return diskCache;
|
|
37620
37994
|
};
|
|
37995
|
+
return { blobLines, diskLines };
|
|
37996
|
+
}
|
|
37997
|
+
|
|
37998
|
+
// src/git/changes/patch/hydrate/create-hydrate-yield.ts
|
|
37999
|
+
function createHydrateYield() {
|
|
38000
|
+
let processedLines = 0;
|
|
38001
|
+
return async () => {
|
|
38002
|
+
processedLines++;
|
|
38003
|
+
if (processedLines % HYDRATE_YIELD_EVERY_LINES === 0) {
|
|
38004
|
+
await yieldToEventLoop();
|
|
38005
|
+
}
|
|
38006
|
+
};
|
|
38007
|
+
}
|
|
38008
|
+
|
|
38009
|
+
// src/git/changes/patch/hydrate/update-hunk-line-ends.ts
|
|
38010
|
+
function updateHunkLineEnds(options) {
|
|
38011
|
+
const prevOldEnd = options.oldStart > 0 ? options.oldStart + options.oldConsumed - 1 : 0;
|
|
38012
|
+
const prevNewEnd = options.newStart > 0 ? options.newStart + options.newConsumed - 1 : 0;
|
|
38013
|
+
return { prevOldEnd, prevNewEnd };
|
|
38014
|
+
}
|
|
38015
|
+
|
|
38016
|
+
// src/git/changes/patch/hydrate/hydrate-unified-patch-with-file-context.ts
|
|
38017
|
+
async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
|
|
38018
|
+
if (!patch.trim() || patch.includes("Binary files")) return patch;
|
|
38019
|
+
const all = patch.split("\n");
|
|
38020
|
+
const out = [];
|
|
38021
|
+
let prevOldEnd = 0;
|
|
38022
|
+
let prevNewEnd = 0;
|
|
38023
|
+
const injectedTotal = { value: 0 };
|
|
38024
|
+
let i = 0;
|
|
38025
|
+
const maybeYield = createHydrateYield();
|
|
38026
|
+
const { blobLines, diskLines } = createHydrateSourceLines({ repoGitCwd, pathInRepo, filePath });
|
|
37621
38027
|
while (i < all.length) {
|
|
38028
|
+
await maybeYield();
|
|
37622
38029
|
const line = all[i];
|
|
37623
38030
|
const hm = line.match(UNIFIED_HUNK_HEADER_RE);
|
|
37624
38031
|
if (!hm) {
|
|
@@ -37628,66 +38035,31 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
37628
38035
|
}
|
|
37629
38036
|
const oldStart = parseInt(hm[1], 10) || 0;
|
|
37630
38037
|
const newStart = parseInt(hm[3], 10) || 0;
|
|
37631
|
-
const
|
|
37632
|
-
|
|
37633
|
-
|
|
37634
|
-
|
|
37635
|
-
|
|
37636
|
-
|
|
37637
|
-
|
|
37638
|
-
|
|
37639
|
-
|
|
37640
|
-
|
|
37641
|
-
|
|
37642
|
-
|
|
37643
|
-
inject = dl.slice(gapNewStart - 1, gapNewStart - 1 + cap);
|
|
37644
|
-
}
|
|
37645
|
-
}
|
|
37646
|
-
} else if (gapOldEnd >= gapOldStart && change === "removed") {
|
|
37647
|
-
const nOld = gapOldEnd - gapOldStart + 1;
|
|
37648
|
-
const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
|
|
37649
|
-
const bl = await blobLines();
|
|
37650
|
-
if (bl && cap > 0) {
|
|
37651
|
-
inject = bl.slice(gapOldStart - 1, gapOldStart - 1 + cap);
|
|
37652
|
-
}
|
|
37653
|
-
}
|
|
37654
|
-
if (inject && inject.length > 0) {
|
|
37655
|
-
for (const t of inject) {
|
|
37656
|
-
out.push(` ${t}`);
|
|
37657
|
-
injectedTotal++;
|
|
37658
|
-
}
|
|
37659
|
-
}
|
|
38038
|
+
const inject = await collectGapInjectLines({
|
|
38039
|
+
change,
|
|
38040
|
+
gapOldStart: prevOldEnd + 1,
|
|
38041
|
+
gapOldEnd: oldStart - 1,
|
|
38042
|
+
gapNewStart: prevNewEnd + 1,
|
|
38043
|
+
gapNewEnd: newStart - 1,
|
|
38044
|
+
injectedTotal: injectedTotal.value,
|
|
38045
|
+
blobLines,
|
|
38046
|
+
diskLines
|
|
38047
|
+
});
|
|
38048
|
+
if (inject && inject.length > 0) {
|
|
38049
|
+
await appendInjectedContextLines(out, inject, maybeYield, injectedTotal);
|
|
37660
38050
|
}
|
|
37661
38051
|
out.push(line);
|
|
37662
38052
|
i++;
|
|
37663
|
-
|
|
37664
|
-
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
|
|
37670
|
-
|
|
37671
|
-
|
|
37672
|
-
|
|
37673
|
-
oldConsumed++;
|
|
37674
|
-
newConsumed++;
|
|
37675
|
-
} else if (ch === "-") {
|
|
37676
|
-
oldConsumed++;
|
|
37677
|
-
} else if (ch === "+") {
|
|
37678
|
-
newConsumed++;
|
|
37679
|
-
}
|
|
37680
|
-
}
|
|
37681
|
-
if (oldStart > 0) {
|
|
37682
|
-
prevOldEnd = oldStart + oldConsumed - 1;
|
|
37683
|
-
} else {
|
|
37684
|
-
prevOldEnd = 0;
|
|
37685
|
-
}
|
|
37686
|
-
if (newStart > 0) {
|
|
37687
|
-
prevNewEnd = newStart + newConsumed - 1;
|
|
37688
|
-
} else {
|
|
37689
|
-
prevNewEnd = 0;
|
|
37690
|
-
}
|
|
38053
|
+
const body = await consumeUnifiedHunkBody({ all, startIndex: i, out, maybeYield });
|
|
38054
|
+
i = body.nextIndex;
|
|
38055
|
+
const ends = updateHunkLineEnds({
|
|
38056
|
+
oldStart,
|
|
38057
|
+
newStart,
|
|
38058
|
+
oldConsumed: body.oldConsumed,
|
|
38059
|
+
newConsumed: body.newConsumed
|
|
38060
|
+
});
|
|
38061
|
+
prevOldEnd = ends.prevOldEnd;
|
|
38062
|
+
prevNewEnd = ends.prevNewEnd;
|
|
37691
38063
|
}
|
|
37692
38064
|
return truncatePatch(out.join("\n"));
|
|
37693
38065
|
}
|
|
@@ -37695,8 +38067,8 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
37695
38067
|
// src/git/changes/listing/enrich-working-tree-file-rows.ts
|
|
37696
38068
|
async function enrichWorkingTreeFileRows(options) {
|
|
37697
38069
|
await forEachWithGitYield(options.rows, async (row) => {
|
|
37698
|
-
const pathInRepo =
|
|
37699
|
-
const filePath =
|
|
38070
|
+
const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
|
|
38071
|
+
const filePath = path27.join(options.repoGitCwd, pathInRepo);
|
|
37700
38072
|
const hydrateKind = row.change === "moved" ? "modified" : row.change;
|
|
37701
38073
|
let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
|
|
37702
38074
|
if (patch) {
|
|
@@ -37719,7 +38091,7 @@ async function enrichWorkingTreeFileRows(options) {
|
|
|
37719
38091
|
}
|
|
37720
38092
|
|
|
37721
38093
|
// src/git/changes/listing/list-changed-files-for-repo.ts
|
|
37722
|
-
async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
38094
|
+
async function listChangedFilesForRepo(repoGitCwd, repoRelPath, options = {}) {
|
|
37723
38095
|
const g = cliSimpleGit(repoGitCwd);
|
|
37724
38096
|
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
37725
38097
|
g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
|
|
@@ -37749,23 +38121,101 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
37749
38121
|
})
|
|
37750
38122
|
});
|
|
37751
38123
|
rows = dedupeChangedFileRows(rows);
|
|
37752
|
-
|
|
37753
|
-
|
|
37754
|
-
|
|
37755
|
-
|
|
37756
|
-
|
|
37757
|
-
|
|
38124
|
+
if (options.includeFilePatches) {
|
|
38125
|
+
await enrichWorkingTreeFileRows({
|
|
38126
|
+
rows,
|
|
38127
|
+
repoGitCwd,
|
|
38128
|
+
repoRelPath,
|
|
38129
|
+
binaryByPath: parsed.binaryByPath
|
|
38130
|
+
});
|
|
38131
|
+
}
|
|
37758
38132
|
return dedupeChangedFileRows(rows);
|
|
37759
38133
|
}
|
|
37760
38134
|
|
|
37761
|
-
// src/git/changes/
|
|
38135
|
+
// src/git/changes/repo/norm-repo-rel-path.ts
|
|
37762
38136
|
function normRepoRel(p) {
|
|
37763
38137
|
const x = p.replace(/\\/g, "/").trim();
|
|
37764
38138
|
return x === "" ? "." : x;
|
|
37765
38139
|
}
|
|
38140
|
+
|
|
38141
|
+
// src/git/changes/repo/build-working-tree-change-repo-detail.ts
|
|
38142
|
+
async function loadRecentAndUnpushedCommits(repoDir, recentCommitsLimit) {
|
|
38143
|
+
const [unpushedCommits, recentCommitList] = await Promise.all([
|
|
38144
|
+
listUnpushedCommits(repoDir),
|
|
38145
|
+
listRecentCommitRefs(repoDir, recentCommitsLimit)
|
|
38146
|
+
]);
|
|
38147
|
+
const unpushedSet = new Set(unpushedCommits.map((c) => c.sha));
|
|
38148
|
+
return {
|
|
38149
|
+
unpushedCommits,
|
|
38150
|
+
recentCommits: recentCommitList.commits.map((c) => ({
|
|
38151
|
+
...c,
|
|
38152
|
+
needsPush: unpushedSet.has(c.sha)
|
|
38153
|
+
})),
|
|
38154
|
+
recentCommitsHasMore: recentCommitList.hasMore
|
|
38155
|
+
};
|
|
38156
|
+
}
|
|
38157
|
+
async function buildWorkingTreeChangeRepoDetail(options) {
|
|
38158
|
+
const t = path28.resolve(options.targetPath);
|
|
38159
|
+
if (!await isGitRepoDirectory(t)) return null;
|
|
38160
|
+
const g = cliSimpleGit(t);
|
|
38161
|
+
const isCommitView = options.basis.kind === "commit";
|
|
38162
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path28.resolve(options.sessionWorktreeRootPath) : null;
|
|
38163
|
+
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
38164
|
+
await yieldToEventLoop2();
|
|
38165
|
+
const branchPromise = g.raw(["rev-parse", "--abbrev-ref", "HEAD"]).then((out) => out.trim() || "HEAD").catch(() => "HEAD");
|
|
38166
|
+
const remoteUrlPromise = getRemoteOriginUrl(t);
|
|
38167
|
+
const topLevelPromise = sessionWtRoot ? Promise.resolve(t) : g.raw(["rev-parse", "--show-toplevel"]).then((out) => out.trim()).catch(() => t);
|
|
38168
|
+
const [branch, remoteUrl, topOrT] = await Promise.all([
|
|
38169
|
+
branchPromise,
|
|
38170
|
+
remoteUrlPromise,
|
|
38171
|
+
topLevelPromise
|
|
38172
|
+
]);
|
|
38173
|
+
const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
|
|
38174
|
+
let repoRelPath;
|
|
38175
|
+
if (sessionWtRoot) {
|
|
38176
|
+
const anchor = legacyNested ? path28.dirname(t) : t;
|
|
38177
|
+
const relNorm = path28.relative(sessionWtRoot, anchor);
|
|
38178
|
+
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
38179
|
+
} else {
|
|
38180
|
+
const top = topOrT;
|
|
38181
|
+
const rel = path28.relative(options.bridgeRoot, path28.resolve(top)).replace(/\\/g, "/") || ".";
|
|
38182
|
+
repoRelPath = rel.startsWith("..") ? path28.basename(path28.resolve(top)) : rel;
|
|
38183
|
+
}
|
|
38184
|
+
const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
38185
|
+
if (options.repoFilterRelPath && norm !== options.repoFilterRelPath) return null;
|
|
38186
|
+
const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
|
|
38187
|
+
const relForList = norm === "." ? "." : norm;
|
|
38188
|
+
const commitSha = isCommitView ? options.basis.sha.trim() : null;
|
|
38189
|
+
const filesPromise = isCommitView && commitSha ? listChangedFilesForCommit(t, relForList, commitSha, {
|
|
38190
|
+
includeFilePatches: options.includeFilePatches
|
|
38191
|
+
}) : listChangedFilesForRepo(t, relForList, { includeFilePatches: options.includeFilePatches });
|
|
38192
|
+
const statusPromise = isCommitView ? Promise.resolve(false) : g.status().then((st) => (st.files?.length ?? 0) > 0);
|
|
38193
|
+
const commitsPromise = isCommitView ? Promise.resolve(null) : loadRecentAndUnpushedCommits(t, options.recentCommitsLimit);
|
|
38194
|
+
const [files, hasUncommittedChanges, commitData] = await Promise.all([
|
|
38195
|
+
filesPromise,
|
|
38196
|
+
statusPromise,
|
|
38197
|
+
commitsPromise
|
|
38198
|
+
]);
|
|
38199
|
+
return {
|
|
38200
|
+
repoRelPath: norm,
|
|
38201
|
+
repoDisplayName,
|
|
38202
|
+
branch,
|
|
38203
|
+
remoteUrl,
|
|
38204
|
+
remoteDisplay,
|
|
38205
|
+
files,
|
|
38206
|
+
hasUncommittedChanges,
|
|
38207
|
+
unpushedCommits: commitData?.unpushedCommits ?? [],
|
|
38208
|
+
recentCommits: commitData?.recentCommits ?? [],
|
|
38209
|
+
recentCommitsHasMore: commitData?.recentCommitsHasMore ?? false,
|
|
38210
|
+
changesView: isCommitView ? "commit" : "working",
|
|
38211
|
+
changesCommitSha: commitSha
|
|
38212
|
+
};
|
|
38213
|
+
}
|
|
38214
|
+
|
|
38215
|
+
// src/git/changes/repo/get-working-tree-change-repo-details.ts
|
|
37766
38216
|
async function getWorkingTreeChangeRepoDetails(options) {
|
|
37767
|
-
const bridgeRoot =
|
|
37768
|
-
const sessionWtRoot = options.sessionWorktreeRootPath ?
|
|
38217
|
+
const bridgeRoot = path29.resolve(getBridgeRoot());
|
|
38218
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path29.resolve(options.sessionWorktreeRootPath) : null;
|
|
37769
38219
|
const legacyNested = options.legacyRepoNestedSessionLayout === true;
|
|
37770
38220
|
const out = [];
|
|
37771
38221
|
const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
|
|
@@ -37778,152 +38228,167 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
37778
38228
|
}
|
|
37779
38229
|
const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
|
|
37780
38230
|
for (let i = 0; i < options.commitTargetPaths.length; i++) {
|
|
37781
|
-
|
|
37782
|
-
const
|
|
37783
|
-
const
|
|
37784
|
-
|
|
37785
|
-
|
|
37786
|
-
|
|
37787
|
-
|
|
37788
|
-
|
|
37789
|
-
|
|
37790
|
-
|
|
37791
|
-
|
|
37792
|
-
|
|
37793
|
-
|
|
37794
|
-
|
|
37795
|
-
if (sessionWtRoot) {
|
|
37796
|
-
const anchor = legacyNested ? path23.dirname(t) : t;
|
|
37797
|
-
const relNorm = path23.relative(sessionWtRoot, anchor);
|
|
37798
|
-
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
37799
|
-
} else {
|
|
37800
|
-
let top = t;
|
|
37801
|
-
try {
|
|
37802
|
-
top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
|
|
37803
|
-
} catch {
|
|
37804
|
-
top = t;
|
|
37805
|
-
}
|
|
37806
|
-
const rel = path23.relative(bridgeRoot, path23.resolve(top)).replace(/\\/g, "/") || ".";
|
|
37807
|
-
repoRelPath = rel.startsWith("..") ? path23.basename(path23.resolve(top)) : rel;
|
|
37808
|
-
}
|
|
37809
|
-
const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
37810
|
-
if (filter && norm !== filter) continue;
|
|
37811
|
-
const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
|
|
37812
|
-
const relForList = norm === "." ? "." : norm;
|
|
37813
|
-
const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
|
|
37814
|
-
const st = await g.status();
|
|
37815
|
-
const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
|
|
37816
|
-
const [unpushedCommits, recentCommitList] = await Promise.all([
|
|
37817
|
-
listUnpushedCommits(t),
|
|
37818
|
-
listRecentCommits(t, options.recentCommitsLimit)
|
|
37819
|
-
]);
|
|
37820
|
-
out.push({
|
|
37821
|
-
repoRelPath: norm,
|
|
37822
|
-
repoDisplayName,
|
|
37823
|
-
branch,
|
|
37824
|
-
remoteUrl,
|
|
37825
|
-
remoteDisplay,
|
|
37826
|
-
files,
|
|
37827
|
-
hasUncommittedChanges,
|
|
37828
|
-
unpushedCommits,
|
|
37829
|
-
recentCommits: recentCommitList.commits,
|
|
37830
|
-
recentCommitsHasMore: recentCommitList.hasMore,
|
|
37831
|
-
changesView: basis.kind === "commit" ? "commit" : "working",
|
|
37832
|
-
changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
|
|
37833
|
-
});
|
|
38231
|
+
await yieldToEventLoop2();
|
|
38232
|
+
const targetPath = options.commitTargetPaths[i];
|
|
38233
|
+
const detail = await buildWorkingTreeChangeRepoDetail({
|
|
38234
|
+
targetPath,
|
|
38235
|
+
bridgeRoot,
|
|
38236
|
+
sessionWorktreeRootPath: sessionWtRoot,
|
|
38237
|
+
legacyRepoNestedSessionLayout: legacyNested,
|
|
38238
|
+
repoFilterRelPath: filter,
|
|
38239
|
+
basis,
|
|
38240
|
+
recentCommitsLimit: options.recentCommitsLimit,
|
|
38241
|
+
includeFilePatches: options.includeFilePatches
|
|
38242
|
+
});
|
|
38243
|
+
if (!detail) continue;
|
|
38244
|
+
out.push(detail);
|
|
37834
38245
|
if (filter) return out;
|
|
37835
38246
|
}
|
|
37836
38247
|
return out;
|
|
37837
38248
|
}
|
|
37838
38249
|
|
|
37839
|
-
// src/git/changes/
|
|
37840
|
-
|
|
37841
|
-
|
|
37842
|
-
const
|
|
37843
|
-
|
|
37844
|
-
|
|
37845
|
-
|
|
37846
|
-
|
|
37847
|
-
|
|
37848
|
-
|
|
37849
|
-
|
|
37850
|
-
|
|
37851
|
-
|
|
37852
|
-
return runGitTask(async () => {
|
|
38250
|
+
// src/git/changes/repo/get-working-tree-change-file-patch.ts
|
|
38251
|
+
import * as path30 from "node:path";
|
|
38252
|
+
async function getWorkingTreeChangeFilePatch(options) {
|
|
38253
|
+
const repoGitCwd = path30.resolve(options.repoGitCwd);
|
|
38254
|
+
if (!await isGitRepoDirectory(repoGitCwd)) {
|
|
38255
|
+
throw new Error("Not a git repository");
|
|
38256
|
+
}
|
|
38257
|
+
const normRel = options.repoRelPath === "." || options.repoRelPath === "" ? "." : options.repoRelPath;
|
|
38258
|
+
const pathInRepo = pathInRepoFromWorkspace(options.workspaceRelPath, normRel);
|
|
38259
|
+
const movedFromPathInRepo = options.change === "moved" && options.movedFromWorkspaceRelPath ? pathInRepoFromWorkspace(options.movedFromWorkspaceRelPath, normRel) : void 0;
|
|
38260
|
+
if (options.basis.kind === "commit") {
|
|
38261
|
+
const commitSha = options.basis.sha.trim();
|
|
38262
|
+
if (!commitSha) throw new Error("commit sha is required for commit file patch");
|
|
37853
38263
|
const g = cliSimpleGit(repoGitCwd);
|
|
37854
|
-
const
|
|
37855
|
-
|
|
37856
|
-
|
|
37857
|
-
|
|
37858
|
-
|
|
37859
|
-
|
|
37860
|
-
|
|
37861
|
-
|
|
37862
|
-
String(untrackedRaw).split("\n")
|
|
38264
|
+
const parentSha = await parentForCommitDiff(g, commitSha);
|
|
38265
|
+
const range = `${parentSha}..${commitSha}`;
|
|
38266
|
+
const patchContent2 = await unifiedDiffForFileInRange(
|
|
38267
|
+
repoGitCwd,
|
|
38268
|
+
range,
|
|
38269
|
+
pathInRepo,
|
|
38270
|
+
options.change,
|
|
38271
|
+
movedFromPathInRepo
|
|
37863
38272
|
);
|
|
38273
|
+
const isBinary = Boolean(patchContent2?.includes("Binary files"));
|
|
38274
|
+
const totalLines2 = await resolveCommitFileTotalLines({
|
|
38275
|
+
repoGitCwd,
|
|
38276
|
+
pathInRepo,
|
|
38277
|
+
change: options.change,
|
|
38278
|
+
commitSha,
|
|
38279
|
+
parentSha,
|
|
38280
|
+
isBinary
|
|
38281
|
+
});
|
|
38282
|
+
return { patchContent: patchContent2, totalLines: totalLines2 };
|
|
38283
|
+
}
|
|
38284
|
+
const filePath = path30.join(repoGitCwd, pathInRepo);
|
|
38285
|
+
const hydrateKind = options.change === "moved" ? "modified" : options.change;
|
|
38286
|
+
let patchContent = await unifiedDiffForFile(
|
|
38287
|
+
repoGitCwd,
|
|
38288
|
+
pathInRepo,
|
|
38289
|
+
options.change,
|
|
38290
|
+
movedFromPathInRepo
|
|
38291
|
+
);
|
|
38292
|
+
if (patchContent) {
|
|
38293
|
+
patchContent = await hydrateUnifiedPatchWithFileContext(
|
|
38294
|
+
patchContent,
|
|
38295
|
+
filePath,
|
|
38296
|
+
repoGitCwd,
|
|
38297
|
+
pathInRepo,
|
|
38298
|
+
hydrateKind
|
|
38299
|
+
);
|
|
38300
|
+
}
|
|
38301
|
+
const totalLines = await resolveWorkingTreeFileTotalLines({
|
|
38302
|
+
repoGitCwd,
|
|
38303
|
+
pathInRepo,
|
|
38304
|
+
change: options.change,
|
|
38305
|
+
isBinary: Boolean(patchContent?.includes("Binary files"))
|
|
37864
38306
|
});
|
|
38307
|
+
return { patchContent, totalLines };
|
|
37865
38308
|
}
|
|
37866
38309
|
|
|
37867
|
-
// src/git/
|
|
37868
|
-
async function
|
|
37869
|
-
|
|
37870
|
-
|
|
37871
|
-
|
|
37872
|
-
|
|
37873
|
-
|
|
38310
|
+
// src/worktrees/manager/git/get-session-working-tree-change-details.ts
|
|
38311
|
+
async function getSessionWorkingTreeChangeDetails(cache2, sessionId, discover, opts) {
|
|
38312
|
+
const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
|
|
38313
|
+
return getWorkingTreeChangeRepoDetails({
|
|
38314
|
+
commitTargetPaths: targets,
|
|
38315
|
+
sessionWorktreeRootPath: cache2.getWorkingTreeRelRoot(sessionId),
|
|
38316
|
+
legacyRepoNestedSessionLayout: cache2.isLegacyNestedLayout(sessionId),
|
|
38317
|
+
repoFilterRelPath: opts?.repoRelPath?.trim() ? normRepoRel(opts.repoRelPath.trim()) : null,
|
|
38318
|
+
basis: opts?.basis,
|
|
38319
|
+
recentCommitsLimit: opts?.recentCommitsLimit,
|
|
38320
|
+
includeFilePatches: false
|
|
37874
38321
|
});
|
|
37875
38322
|
}
|
|
37876
|
-
|
|
37877
|
-
|
|
37878
|
-
|
|
37879
|
-
|
|
37880
|
-
|
|
37881
|
-
const s = await getRepoWorkingTreeStatus(p);
|
|
37882
|
-
uncommittedFileCount += s.uncommittedFileCount;
|
|
37883
|
-
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
37884
|
-
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
37885
|
-
});
|
|
37886
|
-
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
37887
|
-
}
|
|
37888
|
-
async function pushAheadOfUpstreamForPaths(paths) {
|
|
37889
|
-
await forEachWithGitYield(paths, async (p) => {
|
|
37890
|
-
const g = cliSimpleGit(p);
|
|
37891
|
-
const ahead = await commitsAheadOfRemoteTracking(p);
|
|
37892
|
-
if (ahead <= 0) return;
|
|
37893
|
-
await g.push();
|
|
37894
|
-
});
|
|
38323
|
+
|
|
38324
|
+
// src/worktrees/manager/git/parse-working-tree-change-kind.ts
|
|
38325
|
+
function parseWorkingTreeChangeKind(value) {
|
|
38326
|
+
if (value === "added" || value === "removed" || value === "modified" || value === "moved") return value;
|
|
38327
|
+
return null;
|
|
37895
38328
|
}
|
|
37896
38329
|
|
|
37897
|
-
// src/git/
|
|
37898
|
-
|
|
37899
|
-
|
|
37900
|
-
|
|
37901
|
-
|
|
37902
|
-
|
|
37903
|
-
|
|
37904
|
-
|
|
37905
|
-
|
|
37906
|
-
const
|
|
37907
|
-
|
|
37908
|
-
if (
|
|
37909
|
-
|
|
38330
|
+
// src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
|
|
38331
|
+
import * as path32 from "node:path";
|
|
38332
|
+
|
|
38333
|
+
// src/git/changes/repo/resolve-checkout-repo-rel-path.ts
|
|
38334
|
+
import * as path31 from "node:path";
|
|
38335
|
+
async function resolveCheckoutRepoRelPath(options) {
|
|
38336
|
+
const t = path31.resolve(options.checkoutPath);
|
|
38337
|
+
const g = cliSimpleGit(t);
|
|
38338
|
+
const sessionWtRoot = options.sessionWorktreeRootPath ? path31.resolve(options.sessionWorktreeRootPath) : null;
|
|
38339
|
+
const legacyNested = options.legacyRepoNestedSessionLayout;
|
|
38340
|
+
let repoRelPath;
|
|
38341
|
+
if (sessionWtRoot) {
|
|
38342
|
+
const anchor = legacyNested ? path31.dirname(t) : t;
|
|
38343
|
+
const relNorm = path31.relative(sessionWtRoot, anchor);
|
|
38344
|
+
repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
|
|
37910
38345
|
} else {
|
|
37911
|
-
|
|
38346
|
+
let top = t;
|
|
38347
|
+
try {
|
|
38348
|
+
await yieldToEventLoop2();
|
|
38349
|
+
top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
|
|
38350
|
+
} catch {
|
|
38351
|
+
top = t;
|
|
38352
|
+
}
|
|
38353
|
+
const rel = path31.relative(options.bridgeRoot, path31.resolve(top)).replace(/\\/g, "/") || ".";
|
|
38354
|
+
repoRelPath = rel.startsWith("..") ? path31.basename(path31.resolve(top)) : rel;
|
|
37912
38355
|
}
|
|
37913
|
-
|
|
37914
|
-
|
|
37915
|
-
|
|
37916
|
-
|
|
38356
|
+
return normRepoRel(repoRelPath === "" ? "." : repoRelPath);
|
|
38357
|
+
}
|
|
38358
|
+
|
|
38359
|
+
// src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
|
|
38360
|
+
async function getSessionWorkingTreeChangeFilePatch(cache2, sessionId, discover, opts) {
|
|
38361
|
+
const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
|
|
38362
|
+
const bridgeRoot = path32.resolve(getBridgeRoot());
|
|
38363
|
+
const sessionWtRoot = cache2.getWorkingTreeRelRoot(sessionId) ? path32.resolve(cache2.getWorkingTreeRelRoot(sessionId)) : null;
|
|
38364
|
+
const legacyNested = cache2.isLegacyNestedLayout(sessionId);
|
|
38365
|
+
const filter = normRepoRel(opts.repoRelPath.trim());
|
|
38366
|
+
for (const targetPath of targets) {
|
|
38367
|
+
const checkoutPath = path32.resolve(targetPath);
|
|
38368
|
+
if (!await isGitRepoDirectory(checkoutPath)) continue;
|
|
38369
|
+
const repoRelPath = await resolveCheckoutRepoRelPath({
|
|
38370
|
+
checkoutPath,
|
|
38371
|
+
bridgeRoot,
|
|
38372
|
+
sessionWorktreeRootPath: sessionWtRoot,
|
|
38373
|
+
legacyRepoNestedSessionLayout: legacyNested
|
|
38374
|
+
});
|
|
38375
|
+
if (repoRelPath !== filter) continue;
|
|
38376
|
+
return getWorkingTreeChangeFilePatch({
|
|
38377
|
+
repoGitCwd: checkoutPath,
|
|
38378
|
+
repoRelPath: filter,
|
|
38379
|
+
workspaceRelPath: opts.workspaceRelPath.trim(),
|
|
38380
|
+
change: opts.change,
|
|
38381
|
+
movedFromWorkspaceRelPath: opts.movedFromWorkspaceRelPath?.trim() || null,
|
|
38382
|
+
basis: opts.basis
|
|
38383
|
+
});
|
|
37917
38384
|
}
|
|
38385
|
+
throw new Error(`Repository not found: ${filter}`);
|
|
37918
38386
|
}
|
|
37919
38387
|
|
|
37920
|
-
// src/worktrees/
|
|
37921
|
-
async function
|
|
37922
|
-
const { paths, branch, message, push } = options;
|
|
38388
|
+
// src/worktrees/manager/git/push-session-upstream.ts
|
|
38389
|
+
async function pushSessionUpstream(cache2, sessionId, discover) {
|
|
37923
38390
|
try {
|
|
37924
|
-
|
|
37925
|
-
await gitCommitAllIfDirty(wt, message, { push, branch });
|
|
37926
|
-
}
|
|
38391
|
+
await pushAheadOfUpstreamForPaths(await resolveCommitTargetsAsync(sessionId, cache2, discover));
|
|
37927
38392
|
return { ok: true };
|
|
37928
38393
|
} catch (e) {
|
|
37929
38394
|
const err = e instanceof Error ? e.message : String(e);
|
|
@@ -37932,23 +38397,23 @@ async function commitSessionWorktrees(options) {
|
|
|
37932
38397
|
}
|
|
37933
38398
|
|
|
37934
38399
|
// src/worktrees/remove-session-worktrees.ts
|
|
37935
|
-
import * as
|
|
38400
|
+
import * as fs23 from "node:fs";
|
|
37936
38401
|
|
|
37937
38402
|
// src/git/worktrees/worktree-remove.ts
|
|
37938
|
-
import * as
|
|
38403
|
+
import * as fs22 from "node:fs";
|
|
37939
38404
|
|
|
37940
38405
|
// src/git/worktrees/resolve-main-repo-from-git-file.ts
|
|
37941
|
-
import * as
|
|
37942
|
-
import * as
|
|
38406
|
+
import * as fs21 from "node:fs";
|
|
38407
|
+
import * as path33 from "node:path";
|
|
37943
38408
|
function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
37944
|
-
const gitDirFile =
|
|
37945
|
-
if (!
|
|
37946
|
-
const first2 =
|
|
38409
|
+
const gitDirFile = path33.join(wt, ".git");
|
|
38410
|
+
if (!fs21.existsSync(gitDirFile) || !fs21.statSync(gitDirFile).isFile()) return "";
|
|
38411
|
+
const first2 = fs21.readFileSync(gitDirFile, "utf8").trim();
|
|
37947
38412
|
const m = first2.match(/^gitdir:\s*(.+)$/im);
|
|
37948
38413
|
if (!m) return "";
|
|
37949
|
-
const gitWorktreePath =
|
|
37950
|
-
const gitDir =
|
|
37951
|
-
return
|
|
38414
|
+
const gitWorktreePath = path33.resolve(wt, m[1].trim());
|
|
38415
|
+
const gitDir = path33.dirname(path33.dirname(gitWorktreePath));
|
|
38416
|
+
return path33.dirname(gitDir);
|
|
37952
38417
|
}
|
|
37953
38418
|
|
|
37954
38419
|
// src/git/worktrees/worktree-remove.ts
|
|
@@ -37957,7 +38422,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
|
|
|
37957
38422
|
if (mainRepo) {
|
|
37958
38423
|
await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
|
|
37959
38424
|
} else {
|
|
37960
|
-
|
|
38425
|
+
fs22.rmSync(worktreePath, { recursive: true, force: true });
|
|
37961
38426
|
}
|
|
37962
38427
|
}
|
|
37963
38428
|
|
|
@@ -37970,13 +38435,20 @@ async function removeSessionWorktrees(paths, log2) {
|
|
|
37970
38435
|
} catch (e) {
|
|
37971
38436
|
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
37972
38437
|
try {
|
|
37973
|
-
|
|
38438
|
+
fs23.rmSync(wt, { recursive: true, force: true });
|
|
37974
38439
|
} catch {
|
|
37975
38440
|
}
|
|
37976
38441
|
}
|
|
37977
38442
|
}
|
|
37978
38443
|
}
|
|
37979
38444
|
|
|
38445
|
+
// src/worktrees/manager/git/remove-session-worktree-checkouts.ts
|
|
38446
|
+
async function removeSessionWorktreeCheckouts(cache2, sessionId, log2) {
|
|
38447
|
+
const paths = cache2.clearSession(sessionId);
|
|
38448
|
+
if (!paths?.length) return;
|
|
38449
|
+
await removeSessionWorktrees(paths, log2);
|
|
38450
|
+
}
|
|
38451
|
+
|
|
37980
38452
|
// src/git/branches/rename-branch.ts
|
|
37981
38453
|
async function gitRenameCurrentBranch(repoDir, newName) {
|
|
37982
38454
|
const g = cliSimpleGit(repoDir);
|
|
@@ -37998,239 +38470,310 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
|
37998
38470
|
}
|
|
37999
38471
|
}
|
|
38000
38472
|
|
|
38001
|
-
// src/worktrees/worktree-
|
|
38002
|
-
|
|
38003
|
-
|
|
38004
|
-
|
|
38005
|
-
|
|
38006
|
-
function defaultWorktreeLayoutPath() {
|
|
38007
|
-
return path25.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
|
|
38008
|
-
}
|
|
38009
|
-
function normalizeLoadedLayout(raw) {
|
|
38010
|
-
if (raw && typeof raw === "object" && "launcherCwds" in raw) {
|
|
38011
|
-
const j = raw;
|
|
38012
|
-
if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
|
|
38013
|
-
}
|
|
38014
|
-
return { launcherCwds: [] };
|
|
38015
|
-
}
|
|
38016
|
-
function loadWorktreeLayout() {
|
|
38017
|
-
try {
|
|
38018
|
-
const p = defaultWorktreeLayoutPath();
|
|
38019
|
-
if (!fs23.existsSync(p)) return { launcherCwds: [] };
|
|
38020
|
-
const raw = JSON.parse(fs23.readFileSync(p, "utf8"));
|
|
38021
|
-
return normalizeLoadedLayout(raw);
|
|
38022
|
-
} catch {
|
|
38023
|
-
return { launcherCwds: [] };
|
|
38024
|
-
}
|
|
38025
|
-
}
|
|
38026
|
-
function saveWorktreeLayout(layout) {
|
|
38027
|
-
try {
|
|
38028
|
-
const dir = path25.dirname(defaultWorktreeLayoutPath());
|
|
38029
|
-
fs23.mkdirSync(dir, { recursive: true });
|
|
38030
|
-
fs23.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
|
|
38031
|
-
} catch {
|
|
38032
|
-
}
|
|
38033
|
-
}
|
|
38034
|
-
function baseNameSafe(pathString) {
|
|
38035
|
-
return path25.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
|
|
38036
|
-
}
|
|
38037
|
-
function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
|
|
38038
|
-
const norm = path25.resolve(bridgeRootPath2);
|
|
38039
|
-
const existing = layout.launcherCwds.find((e) => path25.resolve(e.absolutePath) === norm);
|
|
38040
|
-
return existing?.dirName;
|
|
38473
|
+
// src/worktrees/manager/git/rename-session-worktree-branch.ts
|
|
38474
|
+
async function renameSessionWorktreeBranch(cache2, sessionId, newBranch, log2) {
|
|
38475
|
+
const paths = cache2.getRepoCheckoutPathsRef(sessionId);
|
|
38476
|
+
if (!paths?.length) return;
|
|
38477
|
+
await renameSessionWorktreeBranches(paths, newBranch, log2);
|
|
38041
38478
|
}
|
|
38042
|
-
|
|
38043
|
-
|
|
38044
|
-
|
|
38045
|
-
|
|
38046
|
-
|
|
38047
|
-
|
|
38048
|
-
|
|
38049
|
-
|
|
38050
|
-
|
|
38051
|
-
|
|
38052
|
-
|
|
38053
|
-
|
|
38054
|
-
|
|
38055
|
-
|
|
38056
|
-
|
|
38479
|
+
|
|
38480
|
+
// src/worktrees/discovery/discover-session-worktree-on-disk.ts
|
|
38481
|
+
import * as fs27 from "node:fs";
|
|
38482
|
+
import * as path38 from "node:path";
|
|
38483
|
+
|
|
38484
|
+
// src/worktrees/discovery/collect-worktree-paths.ts
|
|
38485
|
+
import * as fs25 from "node:fs";
|
|
38486
|
+
import * as path35 from "node:path";
|
|
38487
|
+
|
|
38488
|
+
// src/worktrees/discovery/disk-walk-constants.ts
|
|
38489
|
+
var DISK_WALK_YIELD_EVERY = 64;
|
|
38490
|
+
var LEGACY_WALK_MAX_DEPTH = 12;
|
|
38491
|
+
var SKIP_DISK_WALK_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
38492
|
+
"node_modules",
|
|
38493
|
+
"bower_components",
|
|
38494
|
+
"vendor",
|
|
38495
|
+
"Pods",
|
|
38496
|
+
"Carthage",
|
|
38497
|
+
"DerivedData",
|
|
38498
|
+
".yarn",
|
|
38499
|
+
".pnpm-store",
|
|
38500
|
+
"dist",
|
|
38501
|
+
"build",
|
|
38502
|
+
".next",
|
|
38503
|
+
"coverage",
|
|
38504
|
+
".turbo",
|
|
38505
|
+
".cache",
|
|
38506
|
+
"target",
|
|
38507
|
+
"__pycache__"
|
|
38508
|
+
]);
|
|
38509
|
+
function shouldSkipDiskWalkEntry(name) {
|
|
38510
|
+
return name.startsWith(".") || SKIP_DISK_WALK_DIR_NAMES.has(name);
|
|
38057
38511
|
}
|
|
38058
38512
|
|
|
38059
|
-
// src/worktrees/
|
|
38513
|
+
// src/worktrees/discovery/disk-walk-utils.ts
|
|
38060
38514
|
import * as fs24 from "node:fs";
|
|
38061
|
-
import * as
|
|
38062
|
-
function
|
|
38515
|
+
import * as path34 from "node:path";
|
|
38516
|
+
async function yieldDuringDiskWalk(state) {
|
|
38517
|
+
state.entries++;
|
|
38518
|
+
if (state.entries % DISK_WALK_YIELD_EVERY !== 0) return true;
|
|
38519
|
+
if (isCliImmediateShutdownRequested()) return false;
|
|
38520
|
+
await yieldToEventLoop();
|
|
38521
|
+
return !isCliImmediateShutdownRequested();
|
|
38522
|
+
}
|
|
38523
|
+
async function isGitDir(dirPath) {
|
|
38063
38524
|
try {
|
|
38064
|
-
|
|
38525
|
+
await fs24.promises.access(path34.join(dirPath, ".git"));
|
|
38526
|
+
return true;
|
|
38065
38527
|
} catch {
|
|
38066
38528
|
return false;
|
|
38067
38529
|
}
|
|
38068
38530
|
}
|
|
38069
|
-
|
|
38531
|
+
|
|
38532
|
+
// src/worktrees/discovery/collect-worktree-paths.ts
|
|
38533
|
+
async function collectGitRepoRootsUnderDirectory(rootPath) {
|
|
38070
38534
|
const out = [];
|
|
38071
|
-
const
|
|
38072
|
-
|
|
38073
|
-
|
|
38535
|
+
const state = { entries: 0 };
|
|
38536
|
+
const walk = async (dir) => {
|
|
38537
|
+
if (!await yieldDuringDiskWalk(state)) return;
|
|
38538
|
+
if (await isGitDir(dir)) {
|
|
38539
|
+
out.push(path35.resolve(dir));
|
|
38074
38540
|
return;
|
|
38075
38541
|
}
|
|
38076
38542
|
let entries;
|
|
38077
38543
|
try {
|
|
38078
|
-
entries =
|
|
38544
|
+
entries = await fs25.promises.readdir(dir, { withFileTypes: true });
|
|
38079
38545
|
} catch {
|
|
38080
38546
|
return;
|
|
38081
38547
|
}
|
|
38082
38548
|
for (const e of entries) {
|
|
38083
|
-
if (e.name
|
|
38084
|
-
const full =
|
|
38549
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
38550
|
+
const full = path35.join(dir, e.name);
|
|
38085
38551
|
if (!e.isDirectory()) continue;
|
|
38086
|
-
walk(full);
|
|
38552
|
+
await walk(full);
|
|
38087
38553
|
}
|
|
38088
38554
|
};
|
|
38089
|
-
walk(
|
|
38090
|
-
return [...new Set(out)];
|
|
38555
|
+
await walk(path35.resolve(rootPath));
|
|
38556
|
+
return { paths: [...new Set(out)], entriesVisited: state.entries };
|
|
38091
38557
|
}
|
|
38092
|
-
function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
|
|
38558
|
+
async function collectWorktreeRootsNamed(root, sessionId, maxDepth = LEGACY_WALK_MAX_DEPTH) {
|
|
38093
38559
|
const out = [];
|
|
38094
|
-
const
|
|
38560
|
+
const state = { entries: 0 };
|
|
38561
|
+
const walk = async (dir, depth) => {
|
|
38095
38562
|
if (depth > maxDepth) return;
|
|
38563
|
+
if (!await yieldDuringDiskWalk(state)) return;
|
|
38096
38564
|
let entries;
|
|
38097
38565
|
try {
|
|
38098
|
-
entries =
|
|
38566
|
+
entries = await fs25.promises.readdir(dir, { withFileTypes: true });
|
|
38099
38567
|
} catch {
|
|
38100
38568
|
return;
|
|
38101
38569
|
}
|
|
38102
38570
|
for (const e of entries) {
|
|
38103
|
-
if (e.name
|
|
38104
|
-
const full =
|
|
38571
|
+
if (shouldSkipDiskWalkEntry(e.name)) continue;
|
|
38572
|
+
const full = path35.join(dir, e.name);
|
|
38105
38573
|
if (!e.isDirectory()) continue;
|
|
38106
38574
|
if (e.name === sessionId) {
|
|
38107
|
-
if (isGitDir(full)) out.push(
|
|
38108
|
-
|
|
38109
|
-
walk(full, depth + 1);
|
|
38575
|
+
if (await isGitDir(full)) out.push(path35.resolve(full));
|
|
38576
|
+
continue;
|
|
38110
38577
|
}
|
|
38578
|
+
if (await isGitDir(full)) continue;
|
|
38579
|
+
await walk(full, depth + 1);
|
|
38111
38580
|
}
|
|
38112
38581
|
};
|
|
38113
|
-
walk(root, 0);
|
|
38114
|
-
return out;
|
|
38582
|
+
await walk(root, 0);
|
|
38583
|
+
return { paths: out, entriesVisited: state.entries };
|
|
38584
|
+
}
|
|
38585
|
+
|
|
38586
|
+
// src/worktrees/discovery/layout-keys-for-bridge-root.ts
|
|
38587
|
+
import * as path36 from "node:path";
|
|
38588
|
+
function layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk) {
|
|
38589
|
+
const bridge = path36.resolve(bridgeRoot);
|
|
38590
|
+
const preferred = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
38591
|
+
const relevant = layout.launcherCwds.filter((e) => {
|
|
38592
|
+
const entry = path36.resolve(e.absolutePath);
|
|
38593
|
+
return bridge === entry || bridge.startsWith(entry + path36.sep) || entry.startsWith(bridge + path36.sep);
|
|
38594
|
+
}).sort((a, b) => path36.resolve(b.absolutePath).length - path36.resolve(a.absolutePath).length).map((e) => e.dirName).filter((name) => keysOnDisk.includes(name));
|
|
38595
|
+
const ordered = [];
|
|
38596
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38597
|
+
const add = (k) => {
|
|
38598
|
+
if (seen.has(k) || !keysOnDisk.includes(k)) return;
|
|
38599
|
+
seen.add(k);
|
|
38600
|
+
ordered.push(k);
|
|
38601
|
+
};
|
|
38602
|
+
if (preferred) add(preferred);
|
|
38603
|
+
for (const k of relevant) add(k);
|
|
38604
|
+
if (relevant.length > 0) {
|
|
38605
|
+
return ordered;
|
|
38606
|
+
}
|
|
38607
|
+
for (const k of keysOnDisk) add(k);
|
|
38608
|
+
return ordered;
|
|
38115
38609
|
}
|
|
38116
|
-
|
|
38610
|
+
|
|
38611
|
+
// src/worktrees/discovery/try-binding-from-session-directory.ts
|
|
38612
|
+
import * as fs26 from "node:fs";
|
|
38613
|
+
import * as path37 from "node:path";
|
|
38614
|
+
async function tryBindingFromSessionDirectory(sessionDir) {
|
|
38117
38615
|
let st;
|
|
38118
38616
|
try {
|
|
38119
|
-
st =
|
|
38617
|
+
st = await fs26.promises.stat(sessionDir);
|
|
38120
38618
|
} catch {
|
|
38121
38619
|
return null;
|
|
38122
38620
|
}
|
|
38123
38621
|
if (!st.isDirectory()) return null;
|
|
38124
|
-
const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
|
|
38622
|
+
const { paths: worktreePaths } = await collectGitRepoRootsUnderDirectory(sessionDir);
|
|
38125
38623
|
if (worktreePaths.length === 0) return null;
|
|
38126
|
-
const abs =
|
|
38624
|
+
const abs = path37.resolve(sessionDir);
|
|
38127
38625
|
return {
|
|
38128
38626
|
sessionParentPath: abs,
|
|
38129
38627
|
workingTreeRelRoot: abs,
|
|
38130
38628
|
repoCheckoutPaths: worktreePaths
|
|
38131
38629
|
};
|
|
38132
38630
|
}
|
|
38133
|
-
|
|
38631
|
+
|
|
38632
|
+
// src/worktrees/discovery/discover-session-worktree-on-disk.ts
|
|
38633
|
+
async function discoverSessionWorktreeOnDisk(options) {
|
|
38634
|
+
const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
|
|
38134
38635
|
const sid = sessionId.trim();
|
|
38135
38636
|
if (!sid) return null;
|
|
38136
|
-
|
|
38137
|
-
|
|
38138
|
-
|
|
38139
|
-
|
|
38140
|
-
const paths = collectWorktreeRootsNamed(cur, sid, 24);
|
|
38141
|
-
if (paths.some((p) => path26.resolve(p) === hintR)) {
|
|
38142
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path26.resolve(paths[0]);
|
|
38143
|
-
best = {
|
|
38144
|
-
sessionParentPath: path26.resolve(isolated),
|
|
38145
|
-
workingTreeRelRoot: path26.resolve(cur),
|
|
38146
|
-
repoCheckoutPaths: paths.map((p) => path26.resolve(p))
|
|
38147
|
-
};
|
|
38148
|
-
}
|
|
38149
|
-
const next = path26.dirname(cur);
|
|
38150
|
-
if (next === cur) break;
|
|
38151
|
-
cur = next;
|
|
38637
|
+
try {
|
|
38638
|
+
await fs27.promises.access(worktreesRootPath);
|
|
38639
|
+
} catch {
|
|
38640
|
+
return null;
|
|
38152
38641
|
}
|
|
38153
|
-
return best;
|
|
38154
|
-
}
|
|
38155
|
-
function discoverSessionWorktreeOnDisk(options) {
|
|
38156
|
-
const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
|
|
38157
|
-
if (!sessionId.trim() || !fs24.existsSync(worktreesRootPath)) return null;
|
|
38158
38642
|
const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
|
|
38159
|
-
const
|
|
38160
|
-
if (preferredKey)
|
|
38643
|
+
const keysOnDisk = [];
|
|
38644
|
+
if (preferredKey) keysOnDisk.push(preferredKey);
|
|
38161
38645
|
try {
|
|
38162
|
-
for (const name of
|
|
38646
|
+
for (const name of await fs27.promises.readdir(worktreesRootPath)) {
|
|
38163
38647
|
if (name.startsWith(".")) continue;
|
|
38164
|
-
const p =
|
|
38165
|
-
|
|
38166
|
-
|
|
38648
|
+
const p = path38.join(worktreesRootPath, name);
|
|
38649
|
+
let st;
|
|
38650
|
+
try {
|
|
38651
|
+
st = await fs27.promises.stat(p);
|
|
38652
|
+
} catch {
|
|
38653
|
+
continue;
|
|
38654
|
+
}
|
|
38655
|
+
if (!st.isDirectory()) continue;
|
|
38656
|
+
if (!keysOnDisk.includes(name)) keysOnDisk.push(name);
|
|
38167
38657
|
}
|
|
38168
38658
|
} catch {
|
|
38169
38659
|
return null;
|
|
38170
38660
|
}
|
|
38661
|
+
const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
|
|
38171
38662
|
for (const key of keys) {
|
|
38172
|
-
|
|
38173
|
-
|
|
38174
|
-
const
|
|
38175
|
-
|
|
38663
|
+
if (isCliImmediateShutdownRequested()) return null;
|
|
38664
|
+
await yieldToEventLoop();
|
|
38665
|
+
const layoutRoot = path38.join(worktreesRootPath, key);
|
|
38666
|
+
let layoutSt;
|
|
38667
|
+
try {
|
|
38668
|
+
layoutSt = await fs27.promises.stat(layoutRoot);
|
|
38669
|
+
} catch {
|
|
38670
|
+
continue;
|
|
38671
|
+
}
|
|
38672
|
+
if (!layoutSt.isDirectory()) continue;
|
|
38673
|
+
const sessionDir = path38.join(layoutRoot, sid);
|
|
38674
|
+
const nested = await tryBindingFromSessionDirectory(sessionDir);
|
|
38176
38675
|
if (nested) return nested;
|
|
38177
|
-
const
|
|
38178
|
-
if (
|
|
38179
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(
|
|
38676
|
+
const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
|
|
38677
|
+
if (legacy.paths.length > 0) {
|
|
38678
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path38.resolve(legacy.paths[0]);
|
|
38180
38679
|
return {
|
|
38181
|
-
sessionParentPath:
|
|
38182
|
-
workingTreeRelRoot:
|
|
38183
|
-
repoCheckoutPaths:
|
|
38680
|
+
sessionParentPath: path38.resolve(isolated),
|
|
38681
|
+
workingTreeRelRoot: path38.resolve(layoutRoot),
|
|
38682
|
+
repoCheckoutPaths: legacy.paths.map((p) => path38.resolve(p))
|
|
38184
38683
|
};
|
|
38185
38684
|
}
|
|
38186
38685
|
}
|
|
38187
38686
|
return null;
|
|
38188
38687
|
}
|
|
38189
|
-
|
|
38688
|
+
|
|
38689
|
+
// src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
|
|
38690
|
+
import * as fs28 from "node:fs";
|
|
38691
|
+
import * as path40 from "node:path";
|
|
38692
|
+
|
|
38693
|
+
// src/worktrees/discovery/discover-legacy-binding-ascending-from-checkout.ts
|
|
38694
|
+
import * as path39 from "node:path";
|
|
38695
|
+
async function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
|
|
38696
|
+
const sid = sessionId.trim();
|
|
38697
|
+
if (!sid) return null;
|
|
38698
|
+
const hintR = path39.resolve(checkoutPath);
|
|
38699
|
+
let best = null;
|
|
38700
|
+
let cur = path39.dirname(hintR);
|
|
38701
|
+
for (let i = 0; i < 40; i++) {
|
|
38702
|
+
if (isCliImmediateShutdownRequested()) return best;
|
|
38703
|
+
await yieldToEventLoop();
|
|
38704
|
+
const paths = await collectWorktreeRootsNamed(cur, sid);
|
|
38705
|
+
if (paths.paths.some((p) => path39.resolve(p) === hintR)) {
|
|
38706
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths.paths) ?? path39.resolve(paths.paths[0]);
|
|
38707
|
+
best = {
|
|
38708
|
+
sessionParentPath: path39.resolve(isolated),
|
|
38709
|
+
workingTreeRelRoot: path39.resolve(cur),
|
|
38710
|
+
repoCheckoutPaths: paths.paths.map((p) => path39.resolve(p))
|
|
38711
|
+
};
|
|
38712
|
+
}
|
|
38713
|
+
const next = path39.dirname(cur);
|
|
38714
|
+
if (next === cur) break;
|
|
38715
|
+
cur = next;
|
|
38716
|
+
}
|
|
38717
|
+
return best;
|
|
38718
|
+
}
|
|
38719
|
+
|
|
38720
|
+
// src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
|
|
38721
|
+
async function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
|
|
38190
38722
|
const sid = sessionId.trim();
|
|
38191
38723
|
if (!sid) return null;
|
|
38192
|
-
const hint =
|
|
38193
|
-
const underHint = tryBindingFromSessionDirectory(
|
|
38724
|
+
const hint = path40.resolve(sessionWorktreeRootPathOrHint);
|
|
38725
|
+
const underHint = await tryBindingFromSessionDirectory(path40.join(hint, sid));
|
|
38194
38726
|
if (underHint) return underHint;
|
|
38195
|
-
const direct = tryBindingFromSessionDirectory(hint);
|
|
38727
|
+
const direct = await tryBindingFromSessionDirectory(hint);
|
|
38196
38728
|
if (direct) {
|
|
38197
|
-
if (
|
|
38198
|
-
const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
38729
|
+
if (path40.basename(hint) === sid && await isGitDir(hint)) {
|
|
38730
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
38199
38731
|
if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
|
|
38200
38732
|
return legacyFromCheckout;
|
|
38201
38733
|
}
|
|
38202
38734
|
}
|
|
38203
38735
|
return direct;
|
|
38204
38736
|
}
|
|
38205
|
-
if (
|
|
38206
|
-
const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
38737
|
+
if (path40.basename(hint) === sid && await isGitDir(hint)) {
|
|
38738
|
+
const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
|
|
38207
38739
|
if (legacyFromCheckout) return legacyFromCheckout;
|
|
38208
38740
|
}
|
|
38209
38741
|
let st;
|
|
38210
38742
|
try {
|
|
38211
|
-
st =
|
|
38743
|
+
st = await fs28.promises.stat(hint);
|
|
38212
38744
|
} catch {
|
|
38213
38745
|
return null;
|
|
38214
38746
|
}
|
|
38215
38747
|
if (!st.isDirectory()) return null;
|
|
38216
|
-
const legacyPaths = collectWorktreeRootsNamed(hint, sid
|
|
38217
|
-
if (legacyPaths.length === 0) return null;
|
|
38218
|
-
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ??
|
|
38748
|
+
const legacyPaths = await collectWorktreeRootsNamed(hint, sid);
|
|
38749
|
+
if (legacyPaths.paths.length === 0) return null;
|
|
38750
|
+
const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths.paths) ?? path40.resolve(legacyPaths.paths[0]);
|
|
38219
38751
|
return {
|
|
38220
|
-
sessionParentPath:
|
|
38752
|
+
sessionParentPath: path40.resolve(isolated),
|
|
38221
38753
|
workingTreeRelRoot: hint,
|
|
38222
|
-
repoCheckoutPaths: legacyPaths.map((p) =>
|
|
38754
|
+
repoCheckoutPaths: legacyPaths.paths.map((p) => path40.resolve(p))
|
|
38223
38755
|
};
|
|
38224
38756
|
}
|
|
38225
38757
|
|
|
38226
38758
|
// src/worktrees/manager/discover-session-binding.ts
|
|
38227
|
-
|
|
38228
|
-
|
|
38229
|
-
|
|
38759
|
+
var discoverInflightBySessionId = /* @__PURE__ */ new Map();
|
|
38760
|
+
function discoverSessionBindingAsync(params) {
|
|
38761
|
+
const sid = params.sessionId.trim();
|
|
38762
|
+
if (!sid) return Promise.resolve(null);
|
|
38763
|
+
const existing = discoverInflightBySessionId.get(sid);
|
|
38764
|
+
if (existing) return existing;
|
|
38765
|
+
const promise2 = discoverSessionWorktreeOnDisk({
|
|
38766
|
+
sessionId: sid,
|
|
38230
38767
|
worktreesRootPath: params.worktreesRootPath,
|
|
38231
38768
|
layout: params.layout,
|
|
38232
38769
|
bridgeRoot: getBridgeRoot()
|
|
38770
|
+
}).finally(() => {
|
|
38771
|
+
if (discoverInflightBySessionId.get(sid) === promise2) {
|
|
38772
|
+
discoverInflightBySessionId.delete(sid);
|
|
38773
|
+
}
|
|
38233
38774
|
});
|
|
38775
|
+
discoverInflightBySessionId.set(sid, promise2);
|
|
38776
|
+
return promise2;
|
|
38234
38777
|
}
|
|
38235
38778
|
|
|
38236
38779
|
// src/worktrees/manager/resolve-isolated-session-parent-path.ts
|
|
@@ -38243,12 +38786,17 @@ function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutP
|
|
|
38243
38786
|
if (!paths?.length) return null;
|
|
38244
38787
|
return resolveIsolatedSessionParentPathFromCheckouts(paths);
|
|
38245
38788
|
}
|
|
38246
|
-
function ensureRepoCheckoutPathsForSession(sessionId, cache2
|
|
38789
|
+
function ensureRepoCheckoutPathsForSession(sessionId, cache2) {
|
|
38790
|
+
if (!sessionId?.trim()) return void 0;
|
|
38791
|
+
const cached2 = cache2.getRepoCheckoutPaths(sessionId.trim());
|
|
38792
|
+
return cached2?.length ? [...cached2] : void 0;
|
|
38793
|
+
}
|
|
38794
|
+
async function ensureRepoCheckoutPathsForSessionAsync(sessionId, cache2, discover) {
|
|
38247
38795
|
if (!sessionId?.trim()) return void 0;
|
|
38248
38796
|
const sid = sessionId.trim();
|
|
38249
38797
|
const cached2 = cache2.getRepoCheckoutPaths(sid);
|
|
38250
38798
|
if (cached2?.length) return cached2;
|
|
38251
|
-
const disc = discover(sid);
|
|
38799
|
+
const disc = await discover(sid);
|
|
38252
38800
|
if (disc?.repoCheckoutPaths.length) {
|
|
38253
38801
|
cache2.remember(sid, disc);
|
|
38254
38802
|
return [...disc.repoCheckoutPaths];
|
|
@@ -38256,18 +38804,6 @@ function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
|
|
|
38256
38804
|
return void 0;
|
|
38257
38805
|
}
|
|
38258
38806
|
|
|
38259
|
-
// src/worktrees/manager/resolve-commit-targets.ts
|
|
38260
|
-
function resolveCommitTargets(sessionId, cache2, discover) {
|
|
38261
|
-
const paths = cache2.getRepoCheckoutPathsRef(sessionId);
|
|
38262
|
-
if (paths?.length) return paths;
|
|
38263
|
-
const disc = discover(sessionId);
|
|
38264
|
-
if (disc?.repoCheckoutPaths.length) {
|
|
38265
|
-
cache2.remember(sessionId, disc);
|
|
38266
|
-
return disc.repoCheckoutPaths;
|
|
38267
|
-
}
|
|
38268
|
-
return [getBridgeRoot()];
|
|
38269
|
-
}
|
|
38270
|
-
|
|
38271
38807
|
// src/worktrees/manager/parse-session-parent.ts
|
|
38272
38808
|
function parseSessionParent(v) {
|
|
38273
38809
|
if (v === "bridge_root" || v === "worktrees_root") return v;
|
|
@@ -38276,8 +38812,8 @@ function parseSessionParent(v) {
|
|
|
38276
38812
|
}
|
|
38277
38813
|
|
|
38278
38814
|
// src/worktrees/prepare-new-session-worktrees.ts
|
|
38279
|
-
import * as
|
|
38280
|
-
import * as
|
|
38815
|
+
import * as fs29 from "node:fs";
|
|
38816
|
+
import * as path41 from "node:path";
|
|
38281
38817
|
|
|
38282
38818
|
// src/git/worktrees/worktree-add.ts
|
|
38283
38819
|
async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
|
|
@@ -38299,10 +38835,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
|
|
|
38299
38835
|
}
|
|
38300
38836
|
async function prepareNewSessionWorktrees(options) {
|
|
38301
38837
|
const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
|
|
38302
|
-
const bridgeResolved =
|
|
38838
|
+
const bridgeResolved = path41.resolve(bridgeRoot);
|
|
38303
38839
|
const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
|
|
38304
|
-
const bridgeKeyDir =
|
|
38305
|
-
const sessionDir =
|
|
38840
|
+
const bridgeKeyDir = path41.join(worktreesRootPath, cwdKey);
|
|
38841
|
+
const sessionDir = path41.join(bridgeKeyDir, sessionId);
|
|
38306
38842
|
const repos = await discoverGitReposUnderRoot(bridgeResolved);
|
|
38307
38843
|
if (repos.length === 0) {
|
|
38308
38844
|
log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
|
|
@@ -38310,14 +38846,14 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
38310
38846
|
}
|
|
38311
38847
|
const branch = `session-${sessionId}`;
|
|
38312
38848
|
const worktreePaths = [];
|
|
38313
|
-
|
|
38849
|
+
fs29.mkdirSync(sessionDir, { recursive: true });
|
|
38314
38850
|
for (const repo of repos) {
|
|
38315
|
-
let rel =
|
|
38316
|
-
if (rel.startsWith("..") ||
|
|
38851
|
+
let rel = path41.relative(bridgeResolved, repo.absolutePath);
|
|
38852
|
+
if (rel.startsWith("..") || path41.isAbsolute(rel)) continue;
|
|
38317
38853
|
const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
|
|
38318
|
-
const wtPath = relNorm === "." ? sessionDir :
|
|
38854
|
+
const wtPath = relNorm === "." ? sessionDir : path41.join(sessionDir, relNorm);
|
|
38319
38855
|
if (relNorm !== ".") {
|
|
38320
|
-
|
|
38856
|
+
fs29.mkdirSync(path41.dirname(wtPath), { recursive: true });
|
|
38321
38857
|
}
|
|
38322
38858
|
const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
|
|
38323
38859
|
try {
|
|
@@ -38358,10 +38894,10 @@ async function prepareAndRememberSessionWorktrees(params) {
|
|
|
38358
38894
|
}
|
|
38359
38895
|
|
|
38360
38896
|
// src/worktrees/manager/resolve-existing-session-parent-path.ts
|
|
38361
|
-
function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
38897
|
+
async function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
38362
38898
|
const cached2 = cache2.getSessionParentPath(sessionId);
|
|
38363
38899
|
if (cached2) return cached2;
|
|
38364
|
-
const disc = discover();
|
|
38900
|
+
const disc = await discover();
|
|
38365
38901
|
if (disc) {
|
|
38366
38902
|
cache2.remember(sessionId, disc);
|
|
38367
38903
|
return cache2.getSessionParentPath(sessionId);
|
|
@@ -38370,9 +38906,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
|
|
|
38370
38906
|
}
|
|
38371
38907
|
|
|
38372
38908
|
// src/worktrees/manager/resolve-explicit-session-parent-path.ts
|
|
38373
|
-
import * as
|
|
38374
|
-
function resolveExplicitSessionParentPath(params) {
|
|
38375
|
-
const resolved =
|
|
38909
|
+
import * as path42 from "node:path";
|
|
38910
|
+
async function resolveExplicitSessionParentPath(params) {
|
|
38911
|
+
const resolved = path42.resolve(params.parentPathRaw);
|
|
38376
38912
|
if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
|
|
38377
38913
|
return resolved;
|
|
38378
38914
|
}
|
|
@@ -38380,15 +38916,16 @@ function resolveExplicitSessionParentPath(params) {
|
|
|
38380
38916
|
params.cache.remember(params.sessionId, binding);
|
|
38381
38917
|
return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
|
|
38382
38918
|
};
|
|
38383
|
-
const diskFirst = params.discover();
|
|
38919
|
+
const diskFirst = await params.discover();
|
|
38384
38920
|
if (diskFirst) return rememberAndReturn(diskFirst);
|
|
38385
|
-
const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
38921
|
+
const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
|
|
38386
38922
|
if (fromRoot) return rememberAndReturn(fromRoot);
|
|
38387
38923
|
let cur = resolved;
|
|
38388
38924
|
for (let i = 0; i < 16; i++) {
|
|
38389
|
-
|
|
38925
|
+
await yieldToEventLoop();
|
|
38926
|
+
const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
|
|
38390
38927
|
if (tryRoot) return rememberAndReturn(tryRoot);
|
|
38391
|
-
const next =
|
|
38928
|
+
const next = path42.dirname(cur);
|
|
38392
38929
|
if (next === cur) break;
|
|
38393
38930
|
cur = next;
|
|
38394
38931
|
}
|
|
@@ -38439,173 +38976,117 @@ async function resolveSessionParentPathForPrompt(params) {
|
|
|
38439
38976
|
});
|
|
38440
38977
|
}
|
|
38441
38978
|
|
|
38442
|
-
// src/worktrees/manager/session-worktree-
|
|
38443
|
-
|
|
38444
|
-
|
|
38445
|
-
|
|
38446
|
-
|
|
38447
|
-
|
|
38448
|
-
|
|
38449
|
-
|
|
38450
|
-
|
|
38451
|
-
|
|
38452
|
-
|
|
38453
|
-
|
|
38454
|
-
|
|
38455
|
-
|
|
38456
|
-
|
|
38457
|
-
|
|
38458
|
-
|
|
38459
|
-
|
|
38460
|
-
|
|
38461
|
-
|
|
38462
|
-
|
|
38463
|
-
|
|
38464
|
-
|
|
38465
|
-
|
|
38466
|
-
|
|
38467
|
-
|
|
38468
|
-
|
|
38469
|
-
|
|
38470
|
-
|
|
38471
|
-
|
|
38472
|
-
|
|
38473
|
-
|
|
38474
|
-
|
|
38475
|
-
|
|
38476
|
-
|
|
38477
|
-
|
|
38478
|
-
|
|
38479
|
-
|
|
38480
|
-
|
|
38481
|
-
|
|
38482
|
-
|
|
38483
|
-
|
|
38484
|
-
|
|
38979
|
+
// src/worktrees/manager/session-worktree-paths.ts
|
|
38980
|
+
function createSessionWorktreePathsApi(ctx) {
|
|
38981
|
+
const discoverAsync = (sessionId) => discoverSessionBindingAsync({
|
|
38982
|
+
sessionId,
|
|
38983
|
+
worktreesRootPath: ctx.worktreesRootPath,
|
|
38984
|
+
layout: ctx.layout
|
|
38985
|
+
});
|
|
38986
|
+
return {
|
|
38987
|
+
discoverAsync,
|
|
38988
|
+
getIsolatedSessionParentPathForSession(sessionId) {
|
|
38989
|
+
return resolveIsolatedSessionParentPath(
|
|
38990
|
+
sessionId,
|
|
38991
|
+
ctx.cache,
|
|
38992
|
+
(sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
|
|
38993
|
+
);
|
|
38994
|
+
},
|
|
38995
|
+
resolveSessionParentPathForPrompt(sessionId, opts) {
|
|
38996
|
+
return resolveSessionParentPathForPrompt({
|
|
38997
|
+
sessionId,
|
|
38998
|
+
cache: ctx.cache,
|
|
38999
|
+
worktreesRootPath: ctx.worktreesRootPath,
|
|
39000
|
+
layout: ctx.layout,
|
|
39001
|
+
log: ctx.log,
|
|
39002
|
+
discover: (sid) => discoverAsync(sid),
|
|
39003
|
+
opts
|
|
39004
|
+
});
|
|
39005
|
+
},
|
|
39006
|
+
usesWorktreeSession(sessionId) {
|
|
39007
|
+
if (!sessionId) return false;
|
|
39008
|
+
return ctx.cache.hasSession(sessionId);
|
|
39009
|
+
},
|
|
39010
|
+
getRepoCheckoutPathsForSession(sessionId) {
|
|
39011
|
+
if (!sessionId) return void 0;
|
|
39012
|
+
return ctx.cache.getRepoCheckoutPaths(sessionId);
|
|
39013
|
+
},
|
|
39014
|
+
ensureRepoCheckoutPathsForSession(sessionId) {
|
|
39015
|
+
return ensureRepoCheckoutPathsForSession(sessionId, ctx.cache);
|
|
39016
|
+
},
|
|
39017
|
+
async ensureRepoCheckoutPathsForSessionAsync(sessionId) {
|
|
39018
|
+
return ensureRepoCheckoutPathsForSessionAsync(sessionId, ctx.cache, (sid) => discoverAsync(sid));
|
|
39019
|
+
},
|
|
39020
|
+
getSessionWorktreeRootForSession(sessionId) {
|
|
39021
|
+
return resolveIsolatedSessionParentPath(
|
|
39022
|
+
sessionId,
|
|
39023
|
+
ctx.cache,
|
|
39024
|
+
(sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
|
|
39025
|
+
);
|
|
39026
|
+
}
|
|
39027
|
+
};
|
|
39028
|
+
}
|
|
39029
|
+
|
|
39030
|
+
// src/worktrees/manager/session-worktree-git.ts
|
|
39031
|
+
function createSessionWorktreeGitApi(ctx) {
|
|
39032
|
+
const paths = createSessionWorktreePathsApi(ctx);
|
|
39033
|
+
return {
|
|
39034
|
+
async renameSessionBranch(sessionId, newBranch) {
|
|
39035
|
+
await renameSessionWorktreeBranch(ctx.cache, sessionId, newBranch, ctx.log);
|
|
39036
|
+
},
|
|
39037
|
+
async removeSessionWorktrees(sessionId) {
|
|
39038
|
+
await removeSessionWorktreeCheckouts(ctx.cache, sessionId, ctx.log);
|
|
39039
|
+
},
|
|
39040
|
+
async commitSession(params) {
|
|
39041
|
+
return commitSessionWorktree(ctx.cache, params);
|
|
39042
|
+
},
|
|
39043
|
+
async getSessionWorkingTreeStatus(sessionId) {
|
|
39044
|
+
return getSessionWorkingTreeStatus(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
|
|
39045
|
+
},
|
|
39046
|
+
async getSessionWorkingTreeChangeDetails(sessionId, opts) {
|
|
39047
|
+
return getSessionWorkingTreeChangeDetails(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
|
|
39048
|
+
},
|
|
39049
|
+
async getSessionWorkingTreeChangeFilePatch(sessionId, opts) {
|
|
39050
|
+
return getSessionWorkingTreeChangeFilePatch(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
|
|
39051
|
+
},
|
|
39052
|
+
async pushSessionUpstream(sessionId) {
|
|
39053
|
+
return pushSessionUpstream(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
|
|
39054
|
+
}
|
|
39055
|
+
};
|
|
39056
|
+
}
|
|
38485
39057
|
|
|
38486
|
-
// src/worktrees/manager/manager.ts
|
|
39058
|
+
// src/worktrees/manager/session-worktree-manager.ts
|
|
38487
39059
|
var SessionWorktreeManager = class {
|
|
38488
|
-
worktreesRootPath;
|
|
38489
|
-
log;
|
|
38490
|
-
cache = new SessionWorktreeCache();
|
|
38491
|
-
layout;
|
|
38492
39060
|
constructor(options) {
|
|
38493
|
-
|
|
38494
|
-
this
|
|
38495
|
-
this.layout = loadWorktreeLayout();
|
|
38496
|
-
}
|
|
38497
|
-
discover(sessionId) {
|
|
38498
|
-
return discoverSessionBinding({
|
|
38499
|
-
sessionId,
|
|
38500
|
-
worktreesRootPath: this.worktreesRootPath,
|
|
38501
|
-
layout: this.layout
|
|
38502
|
-
});
|
|
38503
|
-
}
|
|
38504
|
-
getIsolatedSessionParentPathForSession(sessionId) {
|
|
38505
|
-
return resolveIsolatedSessionParentPath(
|
|
38506
|
-
sessionId,
|
|
38507
|
-
this.cache,
|
|
38508
|
-
(sid) => this.ensureRepoCheckoutPathsForSession(sid)
|
|
38509
|
-
);
|
|
38510
|
-
}
|
|
38511
|
-
resolveSessionParentPathForPrompt(sessionId, opts) {
|
|
38512
|
-
return resolveSessionParentPathForPrompt({
|
|
38513
|
-
sessionId,
|
|
38514
|
-
cache: this.cache,
|
|
38515
|
-
worktreesRootPath: this.worktreesRootPath,
|
|
38516
|
-
layout: this.layout,
|
|
38517
|
-
log: this.log,
|
|
38518
|
-
discover: (sid) => this.discover(sid),
|
|
38519
|
-
opts
|
|
38520
|
-
});
|
|
38521
|
-
}
|
|
38522
|
-
async renameSessionBranch(sessionId, newBranch) {
|
|
38523
|
-
const paths = this.cache.getRepoCheckoutPathsRef(sessionId);
|
|
38524
|
-
if (!paths?.length) return;
|
|
38525
|
-
await renameSessionWorktreeBranches(paths, newBranch, this.log);
|
|
38526
|
-
}
|
|
38527
|
-
usesWorktreeSession(sessionId) {
|
|
38528
|
-
if (!sessionId) return false;
|
|
38529
|
-
return this.cache.hasSession(sessionId);
|
|
38530
|
-
}
|
|
38531
|
-
getRepoCheckoutPathsForSession(sessionId) {
|
|
38532
|
-
if (!sessionId) return void 0;
|
|
38533
|
-
return this.cache.getRepoCheckoutPaths(sessionId);
|
|
38534
|
-
}
|
|
38535
|
-
ensureRepoCheckoutPathsForSession(sessionId) {
|
|
38536
|
-
return ensureRepoCheckoutPathsForSession(sessionId, this.cache, (sid) => this.discover(sid));
|
|
38537
|
-
}
|
|
38538
|
-
getSessionWorktreeRootForSession(sessionId) {
|
|
38539
|
-
return this.getIsolatedSessionParentPathForSession(sessionId);
|
|
38540
|
-
}
|
|
38541
|
-
async removeSessionWorktrees(sessionId) {
|
|
38542
|
-
const paths = this.cache.clearSession(sessionId);
|
|
38543
|
-
if (!paths?.length) return;
|
|
38544
|
-
await removeSessionWorktrees(paths, this.log);
|
|
38545
|
-
}
|
|
38546
|
-
async commitSession(params) {
|
|
38547
|
-
const paths = this.cache.getRepoCheckoutPathsRef(params.sessionId);
|
|
38548
|
-
const targets = paths?.length ? paths : [getBridgeRoot()];
|
|
38549
|
-
return commitSessionWorktrees({
|
|
38550
|
-
paths: targets,
|
|
38551
|
-
branch: params.branch,
|
|
38552
|
-
message: params.message,
|
|
38553
|
-
push: params.push
|
|
38554
|
-
});
|
|
38555
|
-
}
|
|
38556
|
-
async getSessionWorkingTreeStatus(sessionId) {
|
|
38557
|
-
return aggregateSessionPathsWorkingTreeStatus(
|
|
38558
|
-
resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
|
|
38559
|
-
);
|
|
38560
|
-
}
|
|
38561
|
-
async getSessionWorkingTreeChangeDetails(sessionId, opts) {
|
|
38562
|
-
const targets = resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid));
|
|
38563
|
-
return getWorkingTreeChangeRepoDetails({
|
|
38564
|
-
commitTargetPaths: targets,
|
|
38565
|
-
sessionWorktreeRootPath: this.cache.getWorkingTreeRelRoot(sessionId),
|
|
38566
|
-
legacyRepoNestedSessionLayout: this.cache.isLegacyNestedLayout(sessionId),
|
|
38567
|
-
repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
|
|
38568
|
-
basis: opts?.basis,
|
|
38569
|
-
recentCommitsLimit: opts?.recentCommitsLimit
|
|
38570
|
-
});
|
|
38571
|
-
}
|
|
38572
|
-
async pushSessionUpstream(sessionId) {
|
|
38573
|
-
try {
|
|
38574
|
-
await pushAheadOfUpstreamForPaths(
|
|
38575
|
-
resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
|
|
38576
|
-
);
|
|
38577
|
-
return { ok: true };
|
|
38578
|
-
} catch (e) {
|
|
38579
|
-
const err = e instanceof Error ? e.message : String(e);
|
|
38580
|
-
return { ok: false, error: err };
|
|
38581
|
-
}
|
|
39061
|
+
const ctx = createSessionWorktreeManagerContext(options);
|
|
39062
|
+
Object.assign(this, createSessionWorktreePathsApi(ctx), createSessionWorktreeGitApi(ctx));
|
|
38582
39063
|
}
|
|
38583
39064
|
};
|
|
38584
39065
|
|
|
38585
39066
|
// src/worktrees/manager/default-worktrees-root-path.ts
|
|
38586
|
-
import * as
|
|
39067
|
+
import * as path43 from "node:path";
|
|
38587
39068
|
import os8 from "node:os";
|
|
38588
39069
|
function defaultWorktreesRootPath() {
|
|
38589
|
-
return
|
|
39070
|
+
return path43.join(os8.homedir(), ".buildautomaton", "worktrees");
|
|
38590
39071
|
}
|
|
38591
39072
|
|
|
38592
39073
|
// src/files/watch-file-index.ts
|
|
38593
39074
|
import { watch } from "node:fs";
|
|
38594
|
-
import
|
|
39075
|
+
import path48 from "node:path";
|
|
38595
39076
|
|
|
38596
39077
|
// src/files/index/paths.ts
|
|
38597
|
-
import
|
|
39078
|
+
import path44 from "node:path";
|
|
38598
39079
|
import crypto2 from "node:crypto";
|
|
38599
39080
|
function getCwdHashForFileIndex(resolvedCwd) {
|
|
38600
|
-
return crypto2.createHash("sha256").update(
|
|
39081
|
+
return crypto2.createHash("sha256").update(path44.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
|
|
38601
39082
|
}
|
|
38602
39083
|
|
|
38603
39084
|
// src/files/index/build-file-index.ts
|
|
38604
|
-
import
|
|
39085
|
+
import path46 from "node:path";
|
|
38605
39086
|
|
|
38606
39087
|
// src/files/index/walk-workspace-tree.ts
|
|
38607
|
-
import
|
|
38608
|
-
import
|
|
39088
|
+
import fs30 from "node:fs";
|
|
39089
|
+
import path45 from "node:path";
|
|
38609
39090
|
var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
38610
39091
|
"node_modules",
|
|
38611
39092
|
"bower_components",
|
|
@@ -38622,7 +39103,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
|
|
|
38622
39103
|
async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
|
|
38623
39104
|
let names;
|
|
38624
39105
|
try {
|
|
38625
|
-
names = await
|
|
39106
|
+
names = await fs30.promises.readdir(dir);
|
|
38626
39107
|
} catch {
|
|
38627
39108
|
return;
|
|
38628
39109
|
}
|
|
@@ -38634,18 +39115,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
|
|
|
38634
39115
|
if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
|
|
38635
39116
|
}
|
|
38636
39117
|
state.n++;
|
|
38637
|
-
const full =
|
|
39118
|
+
const full = path45.join(dir, name);
|
|
38638
39119
|
let stat3;
|
|
38639
39120
|
try {
|
|
38640
|
-
stat3 = await
|
|
39121
|
+
stat3 = await fs30.promises.stat(full);
|
|
38641
39122
|
} catch {
|
|
38642
39123
|
continue;
|
|
38643
39124
|
}
|
|
38644
|
-
const
|
|
39125
|
+
const relative7 = path45.relative(baseDir, full).replace(/\\/g, "/");
|
|
38645
39126
|
if (stat3.isDirectory()) {
|
|
38646
39127
|
await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
|
|
38647
39128
|
} else if (stat3.isFile()) {
|
|
38648
|
-
onFile(
|
|
39129
|
+
onFile(relative7);
|
|
38649
39130
|
}
|
|
38650
39131
|
}
|
|
38651
39132
|
}
|
|
@@ -38654,7 +39135,7 @@ function createWalkYieldState() {
|
|
|
38654
39135
|
}
|
|
38655
39136
|
|
|
38656
39137
|
// src/files/index/file-index-sqlite-lock.ts
|
|
38657
|
-
import
|
|
39138
|
+
import fs31 from "node:fs";
|
|
38658
39139
|
function isSqliteCorruptError(e) {
|
|
38659
39140
|
const msg = e instanceof Error ? e.message : String(e);
|
|
38660
39141
|
return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
|
|
@@ -38668,7 +39149,7 @@ function withFileIndexSqliteLock(fn) {
|
|
|
38668
39149
|
if (!isSqliteCorruptError(e)) throw e;
|
|
38669
39150
|
closeAllCliSqliteConnections();
|
|
38670
39151
|
try {
|
|
38671
|
-
|
|
39152
|
+
fs31.unlinkSync(getCliSqlitePath());
|
|
38672
39153
|
} catch {
|
|
38673
39154
|
}
|
|
38674
39155
|
chain = Promise.resolve();
|
|
@@ -38743,7 +39224,7 @@ async function collectWorkspacePathsAsync(resolved) {
|
|
|
38743
39224
|
}
|
|
38744
39225
|
async function buildFileIndexAsync(cwd) {
|
|
38745
39226
|
return withFileIndexSqliteLock(async () => {
|
|
38746
|
-
const resolved =
|
|
39227
|
+
const resolved = path46.resolve(cwd);
|
|
38747
39228
|
await yieldToEventLoop();
|
|
38748
39229
|
assertNotShutdown();
|
|
38749
39230
|
const paths = await collectWorkspacePathsAsync(resolved);
|
|
@@ -38755,7 +39236,7 @@ async function buildFileIndexAsync(cwd) {
|
|
|
38755
39236
|
}
|
|
38756
39237
|
|
|
38757
39238
|
// src/files/index/ensure-file-index.ts
|
|
38758
|
-
import
|
|
39239
|
+
import path47 from "node:path";
|
|
38759
39240
|
|
|
38760
39241
|
// src/files/index/search-file-index.ts
|
|
38761
39242
|
function escapeLikePattern(fragment) {
|
|
@@ -38807,7 +39288,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
|
|
|
38807
39288
|
|
|
38808
39289
|
// src/files/index/ensure-file-index.ts
|
|
38809
39290
|
async function ensureFileIndexAsync(cwd) {
|
|
38810
|
-
const resolved =
|
|
39291
|
+
const resolved = path47.resolve(cwd);
|
|
38811
39292
|
if (await bridgeFileIndexIsPopulated(resolved)) {
|
|
38812
39293
|
return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
|
|
38813
39294
|
}
|
|
@@ -38851,7 +39332,7 @@ function createFsWatcher(resolved, schedule) {
|
|
|
38851
39332
|
}
|
|
38852
39333
|
}
|
|
38853
39334
|
function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
38854
|
-
const resolved =
|
|
39335
|
+
const resolved = path48.resolve(cwd);
|
|
38855
39336
|
void buildFileIndexAsync(resolved).catch((e) => {
|
|
38856
39337
|
if (e instanceof CliSqliteInterrupted) return;
|
|
38857
39338
|
console.error("[file-index] Initial index build failed:", e);
|
|
@@ -38881,7 +39362,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
|
|
|
38881
39362
|
}
|
|
38882
39363
|
|
|
38883
39364
|
// src/connection/create-bridge-connection.ts
|
|
38884
|
-
import * as
|
|
39365
|
+
import * as path60 from "node:path";
|
|
38885
39366
|
|
|
38886
39367
|
// src/dev-servers/manager/dev-server-manager.ts
|
|
38887
39368
|
import { rm as rm2 } from "node:fs/promises";
|
|
@@ -38903,15 +39384,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
|
|
|
38903
39384
|
|
|
38904
39385
|
// src/dev-servers/process/terminate-child-process.ts
|
|
38905
39386
|
async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
38906
|
-
const exited = new Promise((
|
|
38907
|
-
proc.once("exit", () =>
|
|
39387
|
+
const exited = new Promise((resolve34) => {
|
|
39388
|
+
proc.once("exit", () => resolve34());
|
|
38908
39389
|
});
|
|
38909
39390
|
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
|
|
38910
39391
|
try {
|
|
38911
39392
|
proc.kill("SIGTERM");
|
|
38912
39393
|
} catch {
|
|
38913
39394
|
}
|
|
38914
|
-
await Promise.race([exited, new Promise((
|
|
39395
|
+
await Promise.race([exited, new Promise((resolve34) => setTimeout(resolve34, graceMs))]);
|
|
38915
39396
|
}
|
|
38916
39397
|
function forceKillChild(proc, log2, shortId, graceMs) {
|
|
38917
39398
|
log2(
|
|
@@ -38925,7 +39406,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
|
|
|
38925
39406
|
}
|
|
38926
39407
|
|
|
38927
39408
|
// src/dev-servers/process/wire-dev-server-child-process.ts
|
|
38928
|
-
import
|
|
39409
|
+
import fs32 from "node:fs";
|
|
38929
39410
|
|
|
38930
39411
|
// src/dev-servers/manager/forward-pipe.ts
|
|
38931
39412
|
function forwardChildPipe(childReadable, terminal, onData) {
|
|
@@ -38961,7 +39442,7 @@ function wireDevServerChildProcess(d) {
|
|
|
38961
39442
|
d.setPollInterval(void 0);
|
|
38962
39443
|
return;
|
|
38963
39444
|
}
|
|
38964
|
-
|
|
39445
|
+
fs32.readFile(d.mergedLogPath, (err, buf) => {
|
|
38965
39446
|
if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
|
|
38966
39447
|
if (buf.length <= d.mergedReadPos.value) return;
|
|
38967
39448
|
const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
|
|
@@ -38999,7 +39480,7 @@ ${errTail}` : ""}`);
|
|
|
38999
39480
|
d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
|
|
39000
39481
|
};
|
|
39001
39482
|
if (mergedPath) {
|
|
39002
|
-
|
|
39483
|
+
fs32.readFile(mergedPath, (err, buf) => {
|
|
39003
39484
|
if (!err && buf.length > d.mergedReadPos.value) {
|
|
39004
39485
|
const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
|
|
39005
39486
|
if (chunk.length > 0) {
|
|
@@ -39101,13 +39582,13 @@ function parseDevServerDefs(servers) {
|
|
|
39101
39582
|
}
|
|
39102
39583
|
|
|
39103
39584
|
// src/dev-servers/manager/shell-spawn/utils.ts
|
|
39104
|
-
import
|
|
39585
|
+
import fs33 from "node:fs";
|
|
39105
39586
|
function isSpawnEbadf(e) {
|
|
39106
39587
|
return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
|
|
39107
39588
|
}
|
|
39108
39589
|
function rmDirQuiet(dir) {
|
|
39109
39590
|
try {
|
|
39110
|
-
|
|
39591
|
+
fs33.rmSync(dir, { recursive: true, force: true });
|
|
39111
39592
|
} catch {
|
|
39112
39593
|
}
|
|
39113
39594
|
}
|
|
@@ -39115,7 +39596,7 @@ var cachedDevNullReadFd;
|
|
|
39115
39596
|
function devNullReadFd() {
|
|
39116
39597
|
if (cachedDevNullReadFd === void 0) {
|
|
39117
39598
|
const devPath = process.platform === "win32" ? "nul" : "/dev/null";
|
|
39118
|
-
cachedDevNullReadFd =
|
|
39599
|
+
cachedDevNullReadFd = fs33.openSync(devPath, "r");
|
|
39119
39600
|
}
|
|
39120
39601
|
return cachedDevNullReadFd;
|
|
39121
39602
|
}
|
|
@@ -39189,15 +39670,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
|
39189
39670
|
|
|
39190
39671
|
// src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
|
|
39191
39672
|
import { spawn as spawn7 } from "node:child_process";
|
|
39192
|
-
import
|
|
39673
|
+
import fs34 from "node:fs";
|
|
39193
39674
|
import { tmpdir } from "node:os";
|
|
39194
|
-
import
|
|
39675
|
+
import path49 from "node:path";
|
|
39195
39676
|
function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
39196
|
-
const tmpRoot =
|
|
39197
|
-
const logPath =
|
|
39677
|
+
const tmpRoot = fs34.mkdtempSync(path49.join(tmpdir(), "ba-devsrv-log-"));
|
|
39678
|
+
const logPath = path49.join(tmpRoot, "combined.log");
|
|
39198
39679
|
let logFd;
|
|
39199
39680
|
try {
|
|
39200
|
-
logFd =
|
|
39681
|
+
logFd = fs34.openSync(logPath, "a");
|
|
39201
39682
|
} catch {
|
|
39202
39683
|
rmDirQuiet(tmpRoot);
|
|
39203
39684
|
return null;
|
|
@@ -39216,7 +39697,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
39216
39697
|
} else {
|
|
39217
39698
|
proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
|
|
39218
39699
|
}
|
|
39219
|
-
|
|
39700
|
+
fs34.closeSync(logFd);
|
|
39220
39701
|
return {
|
|
39221
39702
|
proc,
|
|
39222
39703
|
pipedStdoutStderr: true,
|
|
@@ -39225,7 +39706,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
39225
39706
|
};
|
|
39226
39707
|
} catch (e) {
|
|
39227
39708
|
try {
|
|
39228
|
-
|
|
39709
|
+
fs34.closeSync(logFd);
|
|
39229
39710
|
} catch {
|
|
39230
39711
|
}
|
|
39231
39712
|
rmDirQuiet(tmpRoot);
|
|
@@ -39236,22 +39717,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
39236
39717
|
|
|
39237
39718
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
|
|
39238
39719
|
import { spawn as spawn8 } from "node:child_process";
|
|
39239
|
-
import
|
|
39720
|
+
import fs35 from "node:fs";
|
|
39240
39721
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
39241
|
-
import
|
|
39722
|
+
import path50 from "node:path";
|
|
39242
39723
|
function shSingleQuote(s) {
|
|
39243
39724
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
39244
39725
|
}
|
|
39245
39726
|
function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
|
|
39246
|
-
const tmpRoot =
|
|
39247
|
-
const logPath =
|
|
39248
|
-
const innerPath =
|
|
39249
|
-
const runnerPath =
|
|
39727
|
+
const tmpRoot = fs35.mkdtempSync(path50.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
39728
|
+
const logPath = path50.join(tmpRoot, "combined.log");
|
|
39729
|
+
const innerPath = path50.join(tmpRoot, "_cmd.sh");
|
|
39730
|
+
const runnerPath = path50.join(tmpRoot, "_run.sh");
|
|
39250
39731
|
try {
|
|
39251
|
-
|
|
39732
|
+
fs35.writeFileSync(innerPath, `#!/bin/sh
|
|
39252
39733
|
${command}
|
|
39253
39734
|
`);
|
|
39254
|
-
|
|
39735
|
+
fs35.writeFileSync(
|
|
39255
39736
|
runnerPath,
|
|
39256
39737
|
`#!/bin/sh
|
|
39257
39738
|
cd ${shSingleQuote(cwd)}
|
|
@@ -39277,13 +39758,13 @@ cd ${shSingleQuote(cwd)}
|
|
|
39277
39758
|
}
|
|
39278
39759
|
}
|
|
39279
39760
|
function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
|
|
39280
|
-
const tmpRoot =
|
|
39281
|
-
const logPath =
|
|
39282
|
-
const runnerPath =
|
|
39761
|
+
const tmpRoot = fs35.mkdtempSync(path50.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
39762
|
+
const logPath = path50.join(tmpRoot, "combined.log");
|
|
39763
|
+
const runnerPath = path50.join(tmpRoot, "_run.bat");
|
|
39283
39764
|
const q = (p) => `"${p.replace(/"/g, '""')}"`;
|
|
39284
39765
|
const com = process.env.ComSpec || "cmd.exe";
|
|
39285
39766
|
try {
|
|
39286
|
-
|
|
39767
|
+
fs35.writeFileSync(
|
|
39287
39768
|
runnerPath,
|
|
39288
39769
|
`@ECHO OFF\r
|
|
39289
39770
|
CD /D ${q(cwd)}\r
|
|
@@ -39792,7 +40273,7 @@ async function proxyToLocal(request) {
|
|
|
39792
40273
|
};
|
|
39793
40274
|
const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
|
|
39794
40275
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
39795
|
-
const once = await new Promise((
|
|
40276
|
+
const once = await new Promise((resolve34) => {
|
|
39796
40277
|
const req = mod.request(opts, (res) => {
|
|
39797
40278
|
const chunks = [];
|
|
39798
40279
|
res.on("data", (c) => chunks.push(c));
|
|
@@ -39803,7 +40284,7 @@ async function proxyToLocal(request) {
|
|
|
39803
40284
|
if (typeof v === "string") headers[k] = v;
|
|
39804
40285
|
else if (Array.isArray(v) && v[0]) headers[k] = v[0];
|
|
39805
40286
|
}
|
|
39806
|
-
|
|
40287
|
+
resolve34({
|
|
39807
40288
|
id: request.id,
|
|
39808
40289
|
statusCode: res.statusCode ?? 0,
|
|
39809
40290
|
headers,
|
|
@@ -39812,7 +40293,7 @@ async function proxyToLocal(request) {
|
|
|
39812
40293
|
});
|
|
39813
40294
|
});
|
|
39814
40295
|
req.on("error", (err) => {
|
|
39815
|
-
|
|
40296
|
+
resolve34({
|
|
39816
40297
|
id: request.id,
|
|
39817
40298
|
statusCode: 0,
|
|
39818
40299
|
headers: {},
|
|
@@ -40219,61 +40700,84 @@ function createOnBridgeIdentified(opts) {
|
|
|
40219
40700
|
};
|
|
40220
40701
|
}
|
|
40221
40702
|
|
|
40222
|
-
// src/skills/
|
|
40223
|
-
import fs32 from "node:fs";
|
|
40224
|
-
import path38 from "node:path";
|
|
40703
|
+
// src/skills/discovery/skill-discovery-roots.ts
|
|
40225
40704
|
var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
|
|
40226
|
-
function
|
|
40705
|
+
async function yieldSkillDiscoveryWork(entryCount) {
|
|
40706
|
+
if (entryCount > 0 && entryCount % INDEX_WORK_YIELD_EVERY === 0) {
|
|
40707
|
+
await yieldToEventLoop();
|
|
40708
|
+
}
|
|
40709
|
+
}
|
|
40710
|
+
|
|
40711
|
+
// src/skills/discovery/discover-local-skills.ts
|
|
40712
|
+
import fs36 from "node:fs";
|
|
40713
|
+
import path51 from "node:path";
|
|
40714
|
+
function collectSkillFromDir(rel, base, name, seenKeys, out) {
|
|
40715
|
+
const dir = path51.join(base, name);
|
|
40716
|
+
try {
|
|
40717
|
+
if (!fs36.statSync(dir).isDirectory()) return;
|
|
40718
|
+
} catch {
|
|
40719
|
+
return;
|
|
40720
|
+
}
|
|
40721
|
+
const skillMd = path51.join(dir, "SKILL.md");
|
|
40722
|
+
if (!fs36.existsSync(skillMd)) return;
|
|
40723
|
+
const key = `${rel}/${name}`;
|
|
40724
|
+
if (seenKeys.has(key)) return;
|
|
40725
|
+
seenKeys.add(key);
|
|
40726
|
+
out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
|
|
40727
|
+
}
|
|
40728
|
+
async function discoverLocalSkillsAsync(cwd) {
|
|
40227
40729
|
const out = [];
|
|
40228
40730
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
40731
|
+
let work = 0;
|
|
40229
40732
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
40230
|
-
|
|
40231
|
-
|
|
40733
|
+
await yieldSkillDiscoveryWork(++work);
|
|
40734
|
+
const base = path51.join(cwd, rel);
|
|
40735
|
+
if (!fs36.existsSync(base) || !fs36.statSync(base).isDirectory()) continue;
|
|
40232
40736
|
let entries = [];
|
|
40233
40737
|
try {
|
|
40234
|
-
entries =
|
|
40738
|
+
entries = fs36.readdirSync(base);
|
|
40235
40739
|
} catch {
|
|
40236
40740
|
continue;
|
|
40237
40741
|
}
|
|
40238
|
-
for (
|
|
40239
|
-
|
|
40240
|
-
|
|
40241
|
-
if (!fs32.statSync(dir).isDirectory()) continue;
|
|
40242
|
-
} catch {
|
|
40243
|
-
continue;
|
|
40244
|
-
}
|
|
40245
|
-
const skillMd = path38.join(dir, "SKILL.md");
|
|
40246
|
-
if (!fs32.existsSync(skillMd)) continue;
|
|
40247
|
-
const key = `${rel}/${name}`;
|
|
40248
|
-
if (seenKeys.has(key)) continue;
|
|
40249
|
-
seenKeys.add(key);
|
|
40250
|
-
out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
|
|
40742
|
+
for (let i = 0; i < entries.length; i++) {
|
|
40743
|
+
await yieldSkillDiscoveryWork(++work);
|
|
40744
|
+
collectSkillFromDir(rel, base, entries[i], seenKeys, out);
|
|
40251
40745
|
}
|
|
40252
40746
|
}
|
|
40253
40747
|
return out;
|
|
40254
40748
|
}
|
|
40255
|
-
|
|
40749
|
+
|
|
40750
|
+
// src/skills/discovery/discover-skill-layout-roots.ts
|
|
40751
|
+
import fs37 from "node:fs";
|
|
40752
|
+
import path52 from "node:path";
|
|
40753
|
+
function collectLayoutSkill(rel, base, name, skills2) {
|
|
40754
|
+
const dir = path52.join(base, name);
|
|
40755
|
+
try {
|
|
40756
|
+
if (!fs37.statSync(dir).isDirectory()) return;
|
|
40757
|
+
} catch {
|
|
40758
|
+
return;
|
|
40759
|
+
}
|
|
40760
|
+
if (!fs37.existsSync(path52.join(dir, "SKILL.md"))) return;
|
|
40761
|
+
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
40762
|
+
skills2.push({ name, relPath });
|
|
40763
|
+
}
|
|
40764
|
+
async function discoverSkillLayoutRootsAsync(cwd) {
|
|
40256
40765
|
const roots = [];
|
|
40766
|
+
let work = 0;
|
|
40257
40767
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
40258
|
-
|
|
40259
|
-
|
|
40768
|
+
await yieldSkillDiscoveryWork(++work);
|
|
40769
|
+
const base = path52.join(cwd, rel);
|
|
40770
|
+
if (!fs37.existsSync(base) || !fs37.statSync(base).isDirectory()) continue;
|
|
40260
40771
|
let entries = [];
|
|
40261
40772
|
try {
|
|
40262
|
-
entries =
|
|
40773
|
+
entries = fs37.readdirSync(base);
|
|
40263
40774
|
} catch {
|
|
40264
40775
|
continue;
|
|
40265
40776
|
}
|
|
40266
40777
|
const skills2 = [];
|
|
40267
|
-
for (
|
|
40268
|
-
|
|
40269
|
-
|
|
40270
|
-
if (!fs32.statSync(dir).isDirectory()) continue;
|
|
40271
|
-
} catch {
|
|
40272
|
-
continue;
|
|
40273
|
-
}
|
|
40274
|
-
if (!fs32.existsSync(path38.join(dir, "SKILL.md"))) continue;
|
|
40275
|
-
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
40276
|
-
skills2.push({ name, relPath });
|
|
40778
|
+
for (let i = 0; i < entries.length; i++) {
|
|
40779
|
+
await yieldSkillDiscoveryWork(++work);
|
|
40780
|
+
collectLayoutSkill(rel, base, entries[i], skills2);
|
|
40277
40781
|
}
|
|
40278
40782
|
if (skills2.length > 0) {
|
|
40279
40783
|
roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
|
|
@@ -40315,16 +40819,18 @@ async function detectLocalAgentTypes() {
|
|
|
40315
40819
|
function createSendLocalSkillsReport(getWs, logFn) {
|
|
40316
40820
|
return () => {
|
|
40317
40821
|
setImmediate(() => {
|
|
40318
|
-
|
|
40319
|
-
|
|
40320
|
-
|
|
40321
|
-
|
|
40322
|
-
|
|
40323
|
-
|
|
40324
|
-
|
|
40325
|
-
|
|
40326
|
-
|
|
40327
|
-
|
|
40822
|
+
void (async () => {
|
|
40823
|
+
try {
|
|
40824
|
+
const socket = getWs();
|
|
40825
|
+
if (!socket || socket.readyState !== wrapper_default.OPEN) return;
|
|
40826
|
+
const skills2 = await discoverLocalSkillsAsync(getBridgeRoot());
|
|
40827
|
+
sendWsMessage(socket, { type: "local_skills", skills: skills2 });
|
|
40828
|
+
} catch (e) {
|
|
40829
|
+
logFn(
|
|
40830
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
40831
|
+
);
|
|
40832
|
+
}
|
|
40833
|
+
})();
|
|
40328
40834
|
});
|
|
40329
40835
|
};
|
|
40330
40836
|
}
|
|
@@ -40627,7 +41133,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
|
|
|
40627
41133
|
}
|
|
40628
41134
|
|
|
40629
41135
|
// src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
|
|
40630
|
-
import
|
|
41136
|
+
import fs38 from "node:fs";
|
|
40631
41137
|
async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
40632
41138
|
if (next.serverState !== "requeued_with_revert") return true;
|
|
40633
41139
|
const sid = next.sessionId;
|
|
@@ -40636,7 +41142,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
|
|
|
40636
41142
|
const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
|
|
40637
41143
|
const file2 = snapshotFilePath(agentBase, tid);
|
|
40638
41144
|
try {
|
|
40639
|
-
await
|
|
41145
|
+
await fs38.promises.access(file2, fs38.constants.F_OK);
|
|
40640
41146
|
} catch {
|
|
40641
41147
|
deps.log(
|
|
40642
41148
|
`[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
|
|
@@ -40751,7 +41257,7 @@ function createBridgePromptSenders(deps, getWs) {
|
|
|
40751
41257
|
sendWsMessage(s, wire);
|
|
40752
41258
|
return true;
|
|
40753
41259
|
};
|
|
40754
|
-
const
|
|
41260
|
+
const sendResult = (result) => {
|
|
40755
41261
|
const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
|
|
40756
41262
|
const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
|
|
40757
41263
|
sendBridgeMessage(result, encryptedFields);
|
|
@@ -40784,7 +41290,7 @@ function createBridgePromptSenders(deps, getWs) {
|
|
|
40784
41290
|
]) : payload;
|
|
40785
41291
|
sendWsMessage(s, wire);
|
|
40786
41292
|
};
|
|
40787
|
-
return { sendBridgeMessage, sendResult
|
|
41293
|
+
return { sendBridgeMessage, sendResult, sendSessionUpdate };
|
|
40788
41294
|
}
|
|
40789
41295
|
|
|
40790
41296
|
// src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
|
|
@@ -40888,7 +41394,7 @@ async function readGitBranch(cwd) {
|
|
|
40888
41394
|
async function runBridgePromptPreamble(params) {
|
|
40889
41395
|
const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
|
|
40890
41396
|
const s = getWs();
|
|
40891
|
-
const repoCheckoutPaths = sessionWorktreeManager.
|
|
41397
|
+
const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
|
|
40892
41398
|
const repoRoots = await resolveSnapshotRepoRoots({
|
|
40893
41399
|
worktreePaths: repoCheckoutPaths,
|
|
40894
41400
|
fallbackCwd: effectiveCwd,
|
|
@@ -40924,9 +41430,9 @@ function parseChangeSummarySnapshots(raw) {
|
|
|
40924
41430
|
for (const item of raw) {
|
|
40925
41431
|
if (!item || typeof item !== "object") continue;
|
|
40926
41432
|
const o = item;
|
|
40927
|
-
const
|
|
40928
|
-
if (!
|
|
40929
|
-
const row = { path:
|
|
41433
|
+
const path62 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
|
|
41434
|
+
if (!path62) continue;
|
|
41435
|
+
const row = { path: path62 };
|
|
40930
41436
|
if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
|
|
40931
41437
|
if (typeof o.oldText === "string") row.oldText = o.oldText;
|
|
40932
41438
|
if (typeof o.newText === "string") row.newText = o.newText;
|
|
@@ -40960,7 +41466,7 @@ async function runPreambleAndPrompt(params) {
|
|
|
40960
41466
|
agentId,
|
|
40961
41467
|
agentConfig,
|
|
40962
41468
|
resolvedCwd,
|
|
40963
|
-
senders: { sendResult
|
|
41469
|
+
senders: { sendResult, sendSessionUpdate }
|
|
40964
41470
|
} = params;
|
|
40965
41471
|
const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
|
|
40966
41472
|
await runBridgePromptPreamble({
|
|
@@ -40998,7 +41504,7 @@ async function runPreambleAndPrompt(params) {
|
|
|
40998
41504
|
agentId,
|
|
40999
41505
|
agentConfig,
|
|
41000
41506
|
sessionParentPath: effectiveCwd,
|
|
41001
|
-
sendResult
|
|
41507
|
+
sendResult,
|
|
41002
41508
|
sendSessionUpdate,
|
|
41003
41509
|
followUpCatalogPromptId,
|
|
41004
41510
|
sessionChangeSummaryFilePaths: pathsForUpload,
|
|
@@ -41136,8 +41642,8 @@ function randomSecret() {
|
|
|
41136
41642
|
}
|
|
41137
41643
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
41138
41644
|
}
|
|
41139
|
-
async function requestPreviewApi(port, secret, method,
|
|
41140
|
-
const url2 = `http://127.0.0.1:${port}${
|
|
41645
|
+
async function requestPreviewApi(port, secret, method, path62, body) {
|
|
41646
|
+
const url2 = `http://127.0.0.1:${port}${path62}`;
|
|
41141
41647
|
const headers = {
|
|
41142
41648
|
[PREVIEW_SECRET_HEADER]: secret,
|
|
41143
41649
|
"Content-Type": "application/json"
|
|
@@ -41149,7 +41655,7 @@ async function requestPreviewApi(port, secret, method, path48, body) {
|
|
|
41149
41655
|
});
|
|
41150
41656
|
const data = await res.json().catch(() => ({}));
|
|
41151
41657
|
if (!res.ok) {
|
|
41152
|
-
throw new Error(data?.error ?? `Preview API ${method} ${
|
|
41658
|
+
throw new Error(data?.error ?? `Preview API ${method} ${path62}: ${res.status}`);
|
|
41153
41659
|
}
|
|
41154
41660
|
return data;
|
|
41155
41661
|
}
|
|
@@ -41314,14 +41820,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
|
|
|
41314
41820
|
};
|
|
41315
41821
|
|
|
41316
41822
|
// src/files/list-dir/index.ts
|
|
41317
|
-
import
|
|
41823
|
+
import fs40 from "node:fs";
|
|
41318
41824
|
|
|
41319
41825
|
// src/files/ensure-under-cwd.ts
|
|
41320
|
-
import
|
|
41826
|
+
import path53 from "node:path";
|
|
41321
41827
|
function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
41322
|
-
const normalized =
|
|
41323
|
-
const resolved =
|
|
41324
|
-
if (!resolved.startsWith(cwd +
|
|
41828
|
+
const normalized = path53.normalize(relativePath).replace(/^(\.\/)+/, "");
|
|
41829
|
+
const resolved = path53.resolve(cwd, normalized);
|
|
41830
|
+
if (!resolved.startsWith(cwd + path53.sep) && resolved !== cwd) {
|
|
41325
41831
|
return null;
|
|
41326
41832
|
}
|
|
41327
41833
|
return resolved;
|
|
@@ -41331,15 +41837,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
|
|
|
41331
41837
|
var LIST_DIR_YIELD_EVERY = 256;
|
|
41332
41838
|
|
|
41333
41839
|
// src/files/list-dir/map-dir-entry.ts
|
|
41334
|
-
import
|
|
41335
|
-
import
|
|
41840
|
+
import path54 from "node:path";
|
|
41841
|
+
import fs39 from "node:fs";
|
|
41336
41842
|
async function mapDirEntry(d, relativePath, resolved) {
|
|
41337
|
-
const entryPath =
|
|
41338
|
-
const fullPath =
|
|
41843
|
+
const entryPath = path54.join(relativePath || ".", d.name).replace(/\\/g, "/");
|
|
41844
|
+
const fullPath = path54.join(resolved, d.name);
|
|
41339
41845
|
let isDir = d.isDirectory();
|
|
41340
41846
|
if (d.isSymbolicLink()) {
|
|
41341
41847
|
try {
|
|
41342
|
-
const targetStat = await
|
|
41848
|
+
const targetStat = await fs39.promises.stat(fullPath);
|
|
41343
41849
|
isDir = targetStat.isDirectory();
|
|
41344
41850
|
} catch {
|
|
41345
41851
|
isDir = false;
|
|
@@ -41369,7 +41875,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
|
41369
41875
|
return { error: "Path is outside working directory" };
|
|
41370
41876
|
}
|
|
41371
41877
|
try {
|
|
41372
|
-
const names = await
|
|
41878
|
+
const names = await fs40.promises.readdir(resolved, { withFileTypes: true });
|
|
41373
41879
|
const entries = [];
|
|
41374
41880
|
for (let i = 0; i < names.length; i++) {
|
|
41375
41881
|
if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
|
|
@@ -41389,18 +41895,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
|
|
|
41389
41895
|
var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
|
|
41390
41896
|
|
|
41391
41897
|
// src/files/read-file/resolve-file-path.ts
|
|
41392
|
-
import
|
|
41898
|
+
import fs41 from "node:fs";
|
|
41393
41899
|
async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
|
|
41394
41900
|
const resolved = ensureUnderCwd(relativePath, sessionParentPath);
|
|
41395
41901
|
if (!resolved) return { error: "Path is outside working directory" };
|
|
41396
41902
|
let real;
|
|
41397
41903
|
try {
|
|
41398
|
-
real = await
|
|
41904
|
+
real = await fs41.promises.realpath(resolved);
|
|
41399
41905
|
} catch {
|
|
41400
41906
|
real = resolved;
|
|
41401
41907
|
}
|
|
41402
41908
|
try {
|
|
41403
|
-
const stat3 = await
|
|
41909
|
+
const stat3 = await fs41.promises.stat(real);
|
|
41404
41910
|
if (!stat3.isFile()) return { error: "Not a file" };
|
|
41405
41911
|
return real;
|
|
41406
41912
|
} catch (err) {
|
|
@@ -41409,11 +41915,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
|
|
|
41409
41915
|
}
|
|
41410
41916
|
|
|
41411
41917
|
// src/files/read-file/read-file-range-async.ts
|
|
41412
|
-
import
|
|
41918
|
+
import fs42 from "node:fs";
|
|
41413
41919
|
import { StringDecoder } from "node:string_decoder";
|
|
41414
41920
|
async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
|
|
41415
|
-
const fileSize = (await
|
|
41416
|
-
const fd = await
|
|
41921
|
+
const fileSize = (await fs42.promises.stat(filePath)).size;
|
|
41922
|
+
const fd = await fs42.promises.open(filePath, "r");
|
|
41417
41923
|
const bufSize = 64 * 1024;
|
|
41418
41924
|
const buf = Buffer.alloc(bufSize);
|
|
41419
41925
|
const decoder = new StringDecoder("utf8");
|
|
@@ -41575,11 +42081,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
|
|
|
41575
42081
|
}
|
|
41576
42082
|
|
|
41577
42083
|
// src/files/read-file/read-file-buffer-full-async.ts
|
|
41578
|
-
import
|
|
42084
|
+
import fs43 from "node:fs";
|
|
41579
42085
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
41580
42086
|
async function readFileBufferFullAsync(filePath) {
|
|
41581
|
-
const stat3 = await
|
|
41582
|
-
const fd = await
|
|
42087
|
+
const stat3 = await fs43.promises.stat(filePath);
|
|
42088
|
+
const fd = await fs43.promises.open(filePath, "r");
|
|
41583
42089
|
const chunks = [];
|
|
41584
42090
|
let position = 0;
|
|
41585
42091
|
let bytesSinceYield = 0;
|
|
@@ -41675,10 +42181,10 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
|
|
|
41675
42181
|
}
|
|
41676
42182
|
|
|
41677
42183
|
// src/files/resolve-file-browser-session-parent.ts
|
|
41678
|
-
function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
42184
|
+
async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
41679
42185
|
const sid = sessionId?.trim();
|
|
41680
42186
|
if (sid) {
|
|
41681
|
-
sessionWorktreeManager.
|
|
42187
|
+
await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
|
|
41682
42188
|
const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
|
|
41683
42189
|
if (worktreeRoot) return worktreeRoot;
|
|
41684
42190
|
}
|
|
@@ -41686,14 +42192,14 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
|
|
|
41686
42192
|
}
|
|
41687
42193
|
|
|
41688
42194
|
// src/files/handle-file-browser-search.ts
|
|
41689
|
-
import
|
|
42195
|
+
import path55 from "node:path";
|
|
41690
42196
|
var SEARCH_LIMIT = 100;
|
|
41691
42197
|
function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
|
|
41692
42198
|
void (async () => {
|
|
41693
42199
|
await yieldToEventLoop();
|
|
41694
42200
|
const q = typeof msg.q === "string" ? msg.q : "";
|
|
41695
|
-
const sessionParentPath =
|
|
41696
|
-
sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
|
|
42201
|
+
const sessionParentPath = path55.resolve(
|
|
42202
|
+
sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
|
|
41697
42203
|
);
|
|
41698
42204
|
if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
|
|
41699
42205
|
const payload2 = {
|
|
@@ -41731,7 +42237,7 @@ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
|
|
|
41731
42237
|
void (async () => {
|
|
41732
42238
|
const reqPath = msg.path.replace(/^\/+/, "") || ".";
|
|
41733
42239
|
const op = msg.op === "read" ? "read" : "list";
|
|
41734
|
-
const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
|
|
42240
|
+
const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
|
|
41735
42241
|
if (op === "list") {
|
|
41736
42242
|
const result = await listDirAsync(reqPath, sessionParentPath);
|
|
41737
42243
|
if ("error" in result) {
|
|
@@ -41802,37 +42308,56 @@ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManag
|
|
|
41802
42308
|
|
|
41803
42309
|
// src/routing/handlers/skill-layout-request.ts
|
|
41804
42310
|
function handleSkillLayoutRequest(msg, deps) {
|
|
41805
|
-
const socket = deps.getWs();
|
|
41806
42311
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
41807
|
-
|
|
41808
|
-
|
|
41809
|
-
|
|
41810
|
-
|
|
42312
|
+
void (async () => {
|
|
42313
|
+
const socket = deps.getWs();
|
|
42314
|
+
const roots = await discoverSkillLayoutRootsAsync(getBridgeRoot());
|
|
42315
|
+
if (socket) {
|
|
42316
|
+
sendWsMessage(socket, { type: "skill_layout_response", id, roots });
|
|
42317
|
+
}
|
|
42318
|
+
})();
|
|
41811
42319
|
}
|
|
41812
42320
|
|
|
41813
|
-
// src/skills/install-
|
|
41814
|
-
|
|
41815
|
-
|
|
41816
|
-
|
|
42321
|
+
// src/skills/install/is-valid-install-item.ts
|
|
42322
|
+
function isValidRemoteSkillInstallItem(item) {
|
|
42323
|
+
if (item === null || typeof item !== "object") return false;
|
|
42324
|
+
const o = item;
|
|
42325
|
+
return typeof o.sourceId === "string" && typeof o.skillName === "string" && typeof o.versionHash === "string" && Array.isArray(o.files);
|
|
42326
|
+
}
|
|
42327
|
+
|
|
42328
|
+
// src/skills/install/install-remote-skills-async.ts
|
|
42329
|
+
import fs44 from "node:fs";
|
|
42330
|
+
import path56 from "node:path";
|
|
42331
|
+
|
|
42332
|
+
// src/skills/install/constants.ts
|
|
42333
|
+
var INSTALL_SKILLS_YIELD_EVERY = 16;
|
|
42334
|
+
|
|
42335
|
+
// src/skills/install/install-remote-skills-async.ts
|
|
42336
|
+
async function writeInstallFileAsync(skillDir, f, filesWritten) {
|
|
42337
|
+
if (typeof f.path !== "string" || !f.text && !f.base64) return;
|
|
42338
|
+
if (++filesWritten.count % INSTALL_SKILLS_YIELD_EVERY === 0) {
|
|
42339
|
+
await yieldToEventLoop();
|
|
42340
|
+
}
|
|
42341
|
+
const dest = path56.join(skillDir, f.path);
|
|
42342
|
+
await fs44.promises.mkdir(path56.dirname(dest), { recursive: true });
|
|
42343
|
+
if (f.text !== void 0) {
|
|
42344
|
+
await fs44.promises.writeFile(dest, f.text, "utf8");
|
|
42345
|
+
} else if (f.base64) {
|
|
42346
|
+
await fs44.promises.writeFile(dest, Buffer.from(f.base64, "base64"));
|
|
42347
|
+
}
|
|
42348
|
+
}
|
|
42349
|
+
async function installRemoteSkillsAsync(cwd, targetDir, items) {
|
|
41817
42350
|
const installed2 = [];
|
|
41818
42351
|
if (!Array.isArray(items)) {
|
|
41819
42352
|
return { success: false, error: "Invalid items" };
|
|
41820
42353
|
}
|
|
42354
|
+
const filesWritten = { count: 0 };
|
|
41821
42355
|
try {
|
|
41822
42356
|
for (const item of items) {
|
|
41823
|
-
if (
|
|
41824
|
-
|
|
41825
|
-
}
|
|
41826
|
-
const skillDir = path42.join(cwd, targetDir, item.skillName);
|
|
42357
|
+
if (!isValidRemoteSkillInstallItem(item)) continue;
|
|
42358
|
+
const skillDir = path56.join(cwd, targetDir, item.skillName);
|
|
41827
42359
|
for (const f of item.files) {
|
|
41828
|
-
|
|
41829
|
-
const dest = path42.join(skillDir, f.path);
|
|
41830
|
-
fs39.mkdirSync(path42.dirname(dest), { recursive: true });
|
|
41831
|
-
if (f.text !== void 0) {
|
|
41832
|
-
fs39.writeFileSync(dest, f.text, "utf8");
|
|
41833
|
-
} else if (f.base64) {
|
|
41834
|
-
fs39.writeFileSync(dest, Buffer.from(f.base64, "base64"));
|
|
41835
|
-
}
|
|
42360
|
+
await writeInstallFileAsync(skillDir, f, filesWritten);
|
|
41836
42361
|
}
|
|
41837
42362
|
installed2.push({
|
|
41838
42363
|
sourceId: item.sourceId,
|
|
@@ -41848,118 +42373,241 @@ function installRemoteSkills(cwd, targetDir, items) {
|
|
|
41848
42373
|
|
|
41849
42374
|
// src/routing/handlers/install-skills.ts
|
|
41850
42375
|
var handleInstallSkillsMessage = (msg, deps) => {
|
|
41851
|
-
const socket = deps.getWs();
|
|
41852
42376
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
41853
42377
|
const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
|
|
41854
42378
|
const rawItems = msg.items;
|
|
41855
42379
|
const cwd = getBridgeRoot();
|
|
41856
|
-
|
|
41857
|
-
|
|
41858
|
-
const
|
|
41859
|
-
|
|
42380
|
+
void (async () => {
|
|
42381
|
+
const socket = deps.getWs();
|
|
42382
|
+
const result = await installRemoteSkillsAsync(cwd, targetDir, rawItems);
|
|
42383
|
+
if (!result.success) {
|
|
42384
|
+
const err = result.error ?? "Invalid items";
|
|
42385
|
+
deps.log(`[Bridge service] Install skills failed: ${err}`);
|
|
42386
|
+
if (socket) {
|
|
42387
|
+
sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
|
|
42388
|
+
}
|
|
42389
|
+
return;
|
|
42390
|
+
}
|
|
41860
42391
|
if (socket) {
|
|
41861
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success:
|
|
42392
|
+
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
41862
42393
|
}
|
|
41863
|
-
|
|
41864
|
-
}
|
|
41865
|
-
if (socket) {
|
|
41866
|
-
sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
|
|
41867
|
-
}
|
|
42394
|
+
})();
|
|
41868
42395
|
};
|
|
41869
42396
|
|
|
41870
42397
|
// src/routing/handlers/refresh-local-skills.ts
|
|
41871
42398
|
var handleRefreshLocalSkills = (_msg, deps) => {
|
|
41872
|
-
|
|
42399
|
+
setImmediate(() => {
|
|
42400
|
+
deps.sendLocalSkillsReport?.();
|
|
42401
|
+
});
|
|
41873
42402
|
};
|
|
41874
42403
|
|
|
41875
|
-
// src/routing/handlers/session-git-
|
|
41876
|
-
function
|
|
42404
|
+
// src/routing/handlers/git/handle-session-git-commit.ts
|
|
42405
|
+
async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
|
|
42406
|
+
const branch = typeof msg.branch === "string" ? msg.branch : "";
|
|
42407
|
+
const message = typeof msg.message === "string" ? msg.message : "";
|
|
42408
|
+
const pushAfterCommit = msg.pushAfterCommit === true;
|
|
42409
|
+
if (!branch.trim() || !message.trim()) {
|
|
42410
|
+
reply({ ok: false, error: "branch and message are required for commit" });
|
|
42411
|
+
return;
|
|
42412
|
+
}
|
|
42413
|
+
const commitRes = await deps.sessionWorktreeManager.commitSession({
|
|
42414
|
+
sessionId,
|
|
42415
|
+
branch: branch.trim(),
|
|
42416
|
+
message: message.trim(),
|
|
42417
|
+
push: pushAfterCommit
|
|
42418
|
+
});
|
|
42419
|
+
if (!commitRes.ok) {
|
|
42420
|
+
reply({ ok: false, error: commitRes.error ?? "Commit failed" });
|
|
42421
|
+
return;
|
|
42422
|
+
}
|
|
42423
|
+
const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
42424
|
+
reply({
|
|
42425
|
+
ok: true,
|
|
42426
|
+
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
42427
|
+
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
42428
|
+
uncommittedFileCount: st.uncommittedFileCount
|
|
42429
|
+
});
|
|
42430
|
+
}
|
|
42431
|
+
|
|
42432
|
+
// src/routing/handlers/git/session-git-changes-params.ts
|
|
42433
|
+
function readString(value) {
|
|
42434
|
+
return typeof value === "string" ? value.trim() : "";
|
|
42435
|
+
}
|
|
42436
|
+
function readChangesObject(msg) {
|
|
42437
|
+
const changes = msg.changes;
|
|
42438
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return null;
|
|
42439
|
+
return changes;
|
|
42440
|
+
}
|
|
42441
|
+
function parseSessionGitChangesParams(msg) {
|
|
42442
|
+
const nested = readChangesObject(msg);
|
|
42443
|
+
const file2 = nested?.file && typeof nested.file === "object" && !Array.isArray(nested.file) ? nested.file : null;
|
|
42444
|
+
const repoRelPath = readString(nested?.repoRelPath) || readString(msg.changesRepoRelPath);
|
|
42445
|
+
const viewRaw = nested?.view ?? msg.changesView;
|
|
42446
|
+
const view = viewRaw === "commit" ? "commit" : "working";
|
|
42447
|
+
const commitSha = readString(nested?.commitSha) || readString(msg.changesCommitSha);
|
|
42448
|
+
const recentCommitsLimit = nested?.recentCommitsLimit ?? msg.changesRecentCommitsLimit;
|
|
42449
|
+
return {
|
|
42450
|
+
repoRelPath,
|
|
42451
|
+
view,
|
|
42452
|
+
commitSha,
|
|
42453
|
+
recentCommitsLimit: typeof recentCommitsLimit === "number" || typeof recentCommitsLimit === "string" ? recentCommitsLimit : void 0,
|
|
42454
|
+
fileWorkspaceRelPath: readString(file2?.workspaceRelPath),
|
|
42455
|
+
fileChange: readString(file2?.change),
|
|
42456
|
+
fileMovedFromWorkspaceRelPath: readString(file2?.movedFromWorkspaceRelPath)
|
|
42457
|
+
};
|
|
42458
|
+
}
|
|
42459
|
+
|
|
42460
|
+
// src/routing/handlers/git/handle-session-git-get-change-file-patch.ts
|
|
42461
|
+
async function handleSessionGitGetChangeFilePatchAction(deps, msg, reply) {
|
|
42462
|
+
const changes = parseSessionGitChangesParams(msg);
|
|
42463
|
+
const { repoRelPath: repoRel, view, commitSha, fileWorkspaceRelPath, fileChange, fileMovedFromWorkspaceRelPath } = changes;
|
|
42464
|
+
const change = parseWorkingTreeChangeKind(fileChange);
|
|
42465
|
+
if (!repoRel || !fileWorkspaceRelPath || !change) {
|
|
42466
|
+
reply({
|
|
42467
|
+
ok: false,
|
|
42468
|
+
error: "changes.repoRelPath, changes.file.workspaceRelPath, and changes.file.change are required"
|
|
42469
|
+
});
|
|
42470
|
+
return;
|
|
42471
|
+
}
|
|
42472
|
+
if (view === "commit" && !commitSha) {
|
|
42473
|
+
reply({ ok: false, error: "changes.commitSha is required for commit file patch" });
|
|
42474
|
+
return;
|
|
42475
|
+
}
|
|
42476
|
+
const patch = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeFilePatch(msg.sessionId, {
|
|
42477
|
+
repoRelPath: repoRel,
|
|
42478
|
+
workspaceRelPath: fileWorkspaceRelPath,
|
|
42479
|
+
change,
|
|
42480
|
+
movedFromWorkspaceRelPath: fileMovedFromWorkspaceRelPath || null,
|
|
42481
|
+
basis: view === "commit" ? { kind: "commit", sha: commitSha } : { kind: "working" }
|
|
42482
|
+
});
|
|
42483
|
+
reply(
|
|
42484
|
+
{
|
|
42485
|
+
ok: true,
|
|
42486
|
+
patchContent: patch.patchContent ?? null,
|
|
42487
|
+
totalLines: patch.totalLines
|
|
42488
|
+
},
|
|
42489
|
+
["patchContent"]
|
|
42490
|
+
);
|
|
42491
|
+
}
|
|
42492
|
+
|
|
42493
|
+
// src/routing/handlers/git/coalesce-list-changes.ts
|
|
42494
|
+
var listChangesInflight = /* @__PURE__ */ new Map();
|
|
42495
|
+
function normalizeListChangesRepoRel(repoRelPath) {
|
|
42496
|
+
if (!repoRelPath?.trim()) return "";
|
|
42497
|
+
return normRepoRel(repoRelPath.trim());
|
|
42498
|
+
}
|
|
42499
|
+
function listChangesInflightKey(sessionId, opts) {
|
|
42500
|
+
const basis = opts?.basis;
|
|
42501
|
+
return [
|
|
42502
|
+
sessionId,
|
|
42503
|
+
normalizeListChangesRepoRel(opts?.repoRelPath),
|
|
42504
|
+
basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
|
|
42505
|
+
String(opts?.recentCommitsLimit ?? "")
|
|
42506
|
+
].join("|");
|
|
42507
|
+
}
|
|
42508
|
+
function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
|
|
42509
|
+
const key = listChangesInflightKey(sessionId, opts);
|
|
42510
|
+
const existing = listChangesInflight.get(key);
|
|
42511
|
+
if (existing) return existing;
|
|
42512
|
+
const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
|
|
42513
|
+
if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
|
|
42514
|
+
});
|
|
42515
|
+
listChangesInflight.set(key, promise2);
|
|
42516
|
+
return promise2;
|
|
42517
|
+
}
|
|
42518
|
+
|
|
42519
|
+
// src/routing/handlers/git/handle-session-git-list-changes.ts
|
|
42520
|
+
async function handleSessionGitListChangesAction(deps, msg, reply) {
|
|
42521
|
+
const changes = parseSessionGitChangesParams(msg);
|
|
42522
|
+
const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
|
|
42523
|
+
if (view === "commit") {
|
|
42524
|
+
if (!repoRel || !commitSha) {
|
|
42525
|
+
reply({
|
|
42526
|
+
ok: false,
|
|
42527
|
+
error: "changes.repoRelPath and changes.commitSha are required for commit view"
|
|
42528
|
+
});
|
|
42529
|
+
return;
|
|
42530
|
+
}
|
|
42531
|
+
}
|
|
42532
|
+
const opts = repoRel && view === "commit" && commitSha ? {
|
|
42533
|
+
repoRelPath: repoRel,
|
|
42534
|
+
basis: { kind: "commit", sha: commitSha },
|
|
42535
|
+
recentCommitsLimit
|
|
42536
|
+
} : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
|
|
42537
|
+
const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
|
|
42538
|
+
deps.sessionWorktreeManager,
|
|
42539
|
+
msg.sessionId,
|
|
42540
|
+
opts
|
|
42541
|
+
);
|
|
42542
|
+
reply({ ok: true, repos }, ["repos"]);
|
|
42543
|
+
}
|
|
42544
|
+
|
|
42545
|
+
// src/routing/handlers/git/handle-session-git-push.ts
|
|
42546
|
+
async function handleSessionGitPushAction(deps, sessionId, reply) {
|
|
42547
|
+
const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
|
|
42548
|
+
if (!pushRes.ok) {
|
|
42549
|
+
reply({ ok: false, error: pushRes.error ?? "Push failed" });
|
|
42550
|
+
return;
|
|
42551
|
+
}
|
|
42552
|
+
const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
42553
|
+
reply({
|
|
42554
|
+
ok: true,
|
|
42555
|
+
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
42556
|
+
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
42557
|
+
uncommittedFileCount: st.uncommittedFileCount
|
|
42558
|
+
});
|
|
42559
|
+
}
|
|
42560
|
+
|
|
42561
|
+
// src/routing/handlers/git/handle-session-git-status.ts
|
|
42562
|
+
async function handleSessionGitStatusAction(deps, sessionId, reply) {
|
|
42563
|
+
const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
42564
|
+
reply({
|
|
42565
|
+
ok: true,
|
|
42566
|
+
hasUncommittedChanges: r.hasUncommittedChanges,
|
|
42567
|
+
hasUnpushedCommits: r.hasUnpushedCommits,
|
|
42568
|
+
uncommittedFileCount: r.uncommittedFileCount
|
|
42569
|
+
});
|
|
42570
|
+
}
|
|
42571
|
+
|
|
42572
|
+
// src/routing/handlers/git/send-session-git-result.ts
|
|
42573
|
+
function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
|
|
41877
42574
|
if (!ws) return;
|
|
41878
42575
|
const message = { type: "session_git_result", id, ...payload };
|
|
41879
|
-
|
|
42576
|
+
let wire = message;
|
|
42577
|
+
if (e2ee && encryptedFields.length > 0) {
|
|
42578
|
+
wire = e2ee.encryptFields(message, encryptedFields);
|
|
42579
|
+
}
|
|
42580
|
+
sendWsMessage(ws, wire);
|
|
41880
42581
|
}
|
|
42582
|
+
|
|
42583
|
+
// src/routing/handlers/git/session-git-request.ts
|
|
41881
42584
|
var handleSessionGitRequestMessage = (msg, deps) => {
|
|
41882
42585
|
if (typeof msg.id !== "string") return;
|
|
41883
42586
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
41884
42587
|
const action = msg.action;
|
|
41885
|
-
if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes")
|
|
42588
|
+
if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes" && action !== "file_diff")
|
|
41886
42589
|
return;
|
|
41887
42590
|
void (async () => {
|
|
41888
42591
|
const ws = deps.getWs();
|
|
41889
|
-
const reply = (payload, encryptedFields = []) =>
|
|
42592
|
+
const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
|
|
41890
42593
|
try {
|
|
41891
42594
|
if (action === "status") {
|
|
41892
|
-
|
|
41893
|
-
reply({
|
|
41894
|
-
ok: true,
|
|
41895
|
-
hasUncommittedChanges: r.hasUncommittedChanges,
|
|
41896
|
-
hasUnpushedCommits: r.hasUnpushedCommits,
|
|
41897
|
-
uncommittedFileCount: r.uncommittedFileCount
|
|
41898
|
-
});
|
|
42595
|
+
await handleSessionGitStatusAction(deps, sessionId, reply);
|
|
41899
42596
|
return;
|
|
41900
42597
|
}
|
|
41901
42598
|
if (action === "list_changes") {
|
|
41902
|
-
|
|
41903
|
-
const view = msg.changesView === "commit" ? "commit" : "working";
|
|
41904
|
-
const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
|
|
41905
|
-
if (view === "commit") {
|
|
41906
|
-
if (!repoRel || !commitSha) {
|
|
41907
|
-
reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
|
|
41908
|
-
return;
|
|
41909
|
-
}
|
|
41910
|
-
}
|
|
41911
|
-
const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
|
|
41912
|
-
const opts = repoRel && view === "commit" && commitSha ? {
|
|
41913
|
-
repoRelPath: repoRel,
|
|
41914
|
-
basis: { kind: "commit", sha: commitSha },
|
|
41915
|
-
recentCommitsLimit
|
|
41916
|
-
} : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
|
|
41917
|
-
const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
|
|
41918
|
-
reply({
|
|
41919
|
-
ok: true,
|
|
41920
|
-
repos
|
|
41921
|
-
}, ["repos"]);
|
|
42599
|
+
await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
|
|
41922
42600
|
return;
|
|
41923
42601
|
}
|
|
41924
|
-
if (action === "
|
|
41925
|
-
|
|
41926
|
-
if (!pushRes.ok) {
|
|
41927
|
-
reply({ ok: false, error: pushRes.error ?? "Push failed" });
|
|
41928
|
-
return;
|
|
41929
|
-
}
|
|
41930
|
-
const st2 = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
|
|
41931
|
-
reply({
|
|
41932
|
-
ok: true,
|
|
41933
|
-
hasUncommittedChanges: st2.hasUncommittedChanges,
|
|
41934
|
-
hasUnpushedCommits: st2.hasUnpushedCommits,
|
|
41935
|
-
uncommittedFileCount: st2.uncommittedFileCount
|
|
41936
|
-
});
|
|
41937
|
-
return;
|
|
41938
|
-
}
|
|
41939
|
-
const branch = typeof msg.branch === "string" ? msg.branch : "";
|
|
41940
|
-
const message = typeof msg.message === "string" ? msg.message : "";
|
|
41941
|
-
const pushAfterCommit = msg.pushAfterCommit === true;
|
|
41942
|
-
if (!branch.trim() || !message.trim()) {
|
|
41943
|
-
reply({ ok: false, error: "branch and message are required for commit" });
|
|
42602
|
+
if (action === "file_diff") {
|
|
42603
|
+
await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
|
|
41944
42604
|
return;
|
|
41945
42605
|
}
|
|
41946
|
-
|
|
41947
|
-
sessionId,
|
|
41948
|
-
branch: branch.trim(),
|
|
41949
|
-
message: message.trim(),
|
|
41950
|
-
push: pushAfterCommit
|
|
41951
|
-
});
|
|
41952
|
-
if (!commitRes.ok) {
|
|
41953
|
-
reply({ ok: false, error: commitRes.error ?? "Commit failed" });
|
|
42606
|
+
if (action === "push") {
|
|
42607
|
+
await handleSessionGitPushAction(deps, sessionId, reply);
|
|
41954
42608
|
return;
|
|
41955
42609
|
}
|
|
41956
|
-
|
|
41957
|
-
reply({
|
|
41958
|
-
ok: true,
|
|
41959
|
-
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
41960
|
-
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
41961
|
-
uncommittedFileCount: st.uncommittedFileCount
|
|
41962
|
-
});
|
|
42610
|
+
await handleSessionGitCommitAction(deps, sessionId, msg, reply);
|
|
41963
42611
|
} catch (e) {
|
|
41964
42612
|
reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
41965
42613
|
}
|
|
@@ -41989,7 +42637,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
|
|
|
41989
42637
|
};
|
|
41990
42638
|
|
|
41991
42639
|
// src/routing/handlers/revert-turn-snapshot.ts
|
|
41992
|
-
import * as
|
|
42640
|
+
import * as fs45 from "node:fs";
|
|
41993
42641
|
var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
41994
42642
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
41995
42643
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
@@ -42002,7 +42650,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
|
42002
42650
|
const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
|
|
42003
42651
|
const file2 = snapshotFilePath(agentBase, turnId);
|
|
42004
42652
|
try {
|
|
42005
|
-
await
|
|
42653
|
+
await fs45.promises.access(file2, fs45.constants.F_OK);
|
|
42006
42654
|
} catch {
|
|
42007
42655
|
sendWsMessage(s, {
|
|
42008
42656
|
type: "revert_turn_snapshot_result",
|
|
@@ -42040,11 +42688,13 @@ var handleDevServerControl = (msg, deps) => {
|
|
|
42040
42688
|
// src/routing/handlers/dev-servers-config.ts
|
|
42041
42689
|
var handleDevServersConfig = (msg, deps) => {
|
|
42042
42690
|
const devServers = msg.devServers;
|
|
42043
|
-
|
|
42691
|
+
setImmediate(() => {
|
|
42692
|
+
deps.devServerManager?.applyConfig(devServers ?? []);
|
|
42693
|
+
});
|
|
42044
42694
|
};
|
|
42045
42695
|
|
|
42046
42696
|
// src/git/bridge-git-context.ts
|
|
42047
|
-
import * as
|
|
42697
|
+
import * as path57 from "node:path";
|
|
42048
42698
|
|
|
42049
42699
|
// src/git/branches/get-current-branch.ts
|
|
42050
42700
|
async function getCurrentBranch(repoPath) {
|
|
@@ -42094,12 +42744,12 @@ async function listRepoBranchRefs(repoPath) {
|
|
|
42094
42744
|
// src/git/bridge-git-context.ts
|
|
42095
42745
|
function folderNameForRelPath(relPath, bridgeRoot) {
|
|
42096
42746
|
if (relPath === "." || relPath === "") {
|
|
42097
|
-
return
|
|
42747
|
+
return path57.basename(path57.resolve(bridgeRoot)) || "repo";
|
|
42098
42748
|
}
|
|
42099
|
-
return
|
|
42749
|
+
return path57.basename(relPath) || relPath;
|
|
42100
42750
|
}
|
|
42101
42751
|
async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
42102
|
-
const root =
|
|
42752
|
+
const root = path57.resolve(bridgeRoot);
|
|
42103
42753
|
if (await isGitRepoDirectory(root)) {
|
|
42104
42754
|
const remoteUrl = await getRemoteOriginUrl(root);
|
|
42105
42755
|
return [{ absolutePath: root, remoteUrl }];
|
|
@@ -42107,17 +42757,19 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
|
|
|
42107
42757
|
const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
|
|
42108
42758
|
const byPath = /* @__PURE__ */ new Map();
|
|
42109
42759
|
for (const repo of [...deep, ...shallow]) {
|
|
42110
|
-
byPath.set(
|
|
42760
|
+
byPath.set(path57.resolve(repo.absolutePath), repo);
|
|
42111
42761
|
}
|
|
42112
42762
|
return [...byPath.values()];
|
|
42113
42763
|
}
|
|
42114
42764
|
async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
42115
|
-
const bridgeResolved =
|
|
42765
|
+
const bridgeResolved = path57.resolve(bridgeRoot);
|
|
42116
42766
|
const repos = await discoverGitReposForBridgeContext(bridgeResolved);
|
|
42117
42767
|
const rows = [];
|
|
42118
|
-
for (
|
|
42119
|
-
|
|
42120
|
-
|
|
42768
|
+
for (let i = 0; i < repos.length; i++) {
|
|
42769
|
+
if (i > 0) await yieldToEventLoop();
|
|
42770
|
+
const repo = repos[i];
|
|
42771
|
+
let rel = path57.relative(bridgeResolved, repo.absolutePath);
|
|
42772
|
+
if (rel.startsWith("..") || path57.isAbsolute(rel)) continue;
|
|
42121
42773
|
const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
|
|
42122
42774
|
const currentBranch = await getCurrentBranch(repo.absolutePath);
|
|
42123
42775
|
const remoteUrl = repo.remoteUrl.trim() || null;
|
|
@@ -42132,11 +42784,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
|
|
|
42132
42784
|
return rows;
|
|
42133
42785
|
}
|
|
42134
42786
|
async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
|
|
42135
|
-
const bridgeResolved =
|
|
42787
|
+
const bridgeResolved = path57.resolve(bridgeRoot);
|
|
42136
42788
|
const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
|
|
42137
|
-
const repoPath = rel === "" ? bridgeResolved :
|
|
42138
|
-
const resolved =
|
|
42139
|
-
if (!resolved.startsWith(bridgeResolved +
|
|
42789
|
+
const repoPath = rel === "" ? bridgeResolved : path57.join(bridgeResolved, rel);
|
|
42790
|
+
const resolved = path57.resolve(repoPath);
|
|
42791
|
+
if (!resolved.startsWith(bridgeResolved + path57.sep) && resolved !== bridgeResolved) {
|
|
42140
42792
|
return [];
|
|
42141
42793
|
}
|
|
42142
42794
|
return listRepoBranchRefs(resolved);
|
|
@@ -42641,10 +43293,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
|
|
|
42641
43293
|
}
|
|
42642
43294
|
|
|
42643
43295
|
// src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
|
|
42644
|
-
import * as
|
|
43296
|
+
import * as path59 from "node:path";
|
|
42645
43297
|
|
|
42646
43298
|
// src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
|
|
42647
|
-
import * as
|
|
43299
|
+
import * as path58 from "node:path";
|
|
42648
43300
|
async function probeOneAgentTypeForCapabilities(params) {
|
|
42649
43301
|
const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
|
|
42650
43302
|
const canContinue = () => shouldContinue?.() !== false;
|
|
@@ -42682,7 +43334,7 @@ async function probeOneAgentTypeForCapabilities(params) {
|
|
|
42682
43334
|
if (!canContinue()) return false;
|
|
42683
43335
|
handle = await resolved.createClient({
|
|
42684
43336
|
command: resolved.command,
|
|
42685
|
-
cwd:
|
|
43337
|
+
cwd: path58.resolve(cwd),
|
|
42686
43338
|
backendAgentType: agentType,
|
|
42687
43339
|
sessionMode: "agent",
|
|
42688
43340
|
persistedAcpSessionId: null,
|
|
@@ -42760,7 +43412,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
|
|
|
42760
43412
|
const { workspaceId, log: log2, getWs } = params;
|
|
42761
43413
|
const isCurrent = beginAgentCapabilityWarmupRun();
|
|
42762
43414
|
if (!isCurrent()) return;
|
|
42763
|
-
const cwd =
|
|
43415
|
+
const cwd = path59.resolve(getBridgeRoot());
|
|
42764
43416
|
async function sendBatchFromCache() {
|
|
42765
43417
|
if (!isCurrent()) return;
|
|
42766
43418
|
const socket = getWs();
|
|
@@ -43106,18 +43758,18 @@ import * as http from "node:http";
|
|
|
43106
43758
|
|
|
43107
43759
|
// src/mcp/bridge-access/read-json-body.ts
|
|
43108
43760
|
function readJsonBody(req) {
|
|
43109
|
-
return new Promise((
|
|
43761
|
+
return new Promise((resolve34, reject) => {
|
|
43110
43762
|
const chunks = [];
|
|
43111
43763
|
req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
43112
43764
|
req.on("end", () => {
|
|
43113
43765
|
try {
|
|
43114
43766
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
43115
43767
|
if (!raw) {
|
|
43116
|
-
|
|
43768
|
+
resolve34({});
|
|
43117
43769
|
return;
|
|
43118
43770
|
}
|
|
43119
43771
|
const parsed = JSON.parse(raw);
|
|
43120
|
-
|
|
43772
|
+
resolve34(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
|
|
43121
43773
|
} catch (e) {
|
|
43122
43774
|
reject(e);
|
|
43123
43775
|
}
|
|
@@ -43175,7 +43827,7 @@ function createBridgeAccessRequestHandler(registry2) {
|
|
|
43175
43827
|
// src/mcp/bridge-access/start-server.ts
|
|
43176
43828
|
function startBridgeAccessServer(registry2) {
|
|
43177
43829
|
const server = http.createServer(createBridgeAccessRequestHandler(registry2));
|
|
43178
|
-
return new Promise((
|
|
43830
|
+
return new Promise((resolve34, reject) => {
|
|
43179
43831
|
server.once("error", reject);
|
|
43180
43832
|
server.listen(0, "127.0.0.1", () => {
|
|
43181
43833
|
const addr = server.address();
|
|
@@ -43183,7 +43835,7 @@ function startBridgeAccessServer(registry2) {
|
|
|
43183
43835
|
reject(new Error("Bridge access server did not bind"));
|
|
43184
43836
|
return;
|
|
43185
43837
|
}
|
|
43186
|
-
|
|
43838
|
+
resolve34({
|
|
43187
43839
|
port: addr.port,
|
|
43188
43840
|
close: () => new Promise((closeResolve, closeReject) => {
|
|
43189
43841
|
server.close((err) => err ? closeReject(err) : closeResolve());
|
|
@@ -43323,8 +43975,8 @@ async function createBridgeConnection(options) {
|
|
|
43323
43975
|
getCloudAccessToken: () => tokens.accessToken
|
|
43324
43976
|
};
|
|
43325
43977
|
const identifyReportedPaths = {
|
|
43326
|
-
bridgeRootPath:
|
|
43327
|
-
worktreesRootPath:
|
|
43978
|
+
bridgeRootPath: path60.resolve(getBridgeRoot()),
|
|
43979
|
+
worktreesRootPath: path60.resolve(worktreesRootPath)
|
|
43328
43980
|
};
|
|
43329
43981
|
const { connect } = createMainBridgeWebSocketLifecycle({
|
|
43330
43982
|
state,
|
|
@@ -43579,9 +44231,9 @@ async function runCliAction(program2, opts) {
|
|
|
43579
44231
|
const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
|
|
43580
44232
|
const bridgeRootOpt = (opts.bridgeRoot && typeof opts.bridgeRoot === "string" && opts.bridgeRoot.trim() ? opts.bridgeRoot.trim() : null) ?? (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim() ? opts.cwd.trim() : null);
|
|
43581
44233
|
if (bridgeRootOpt) {
|
|
43582
|
-
const resolvedBridgeRoot =
|
|
44234
|
+
const resolvedBridgeRoot = path61.resolve(process.cwd(), bridgeRootOpt);
|
|
43583
44235
|
try {
|
|
43584
|
-
const st =
|
|
44236
|
+
const st = fs46.statSync(resolvedBridgeRoot);
|
|
43585
44237
|
if (!st.isDirectory()) {
|
|
43586
44238
|
console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
|
|
43587
44239
|
process.exit(1);
|
|
@@ -43601,7 +44253,7 @@ async function runCliAction(program2, opts) {
|
|
|
43601
44253
|
);
|
|
43602
44254
|
let worktreesRootPath;
|
|
43603
44255
|
if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
|
|
43604
|
-
worktreesRootPath =
|
|
44256
|
+
worktreesRootPath = path61.resolve(opts.worktreesRoot.trim());
|
|
43605
44257
|
}
|
|
43606
44258
|
const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
|
|
43607
44259
|
if (e2eCertificates) {
|