@agimon-ai/po-git 0.0.0-alpha.7 → 0.0.0-alpha.71
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/LICENSE +149 -21
- package/README.md +16 -4
- package/dist/apiContracts.cjs +4 -1
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.mjs +4 -1
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/extensions/native.mjs +2163 -534
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +293 -117
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +2 -2
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +290 -114
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +1443 -1292
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{worktreeOperations-BSeyxUai.mjs → worktreeOperations-B3E4JY4H.mjs} +258 -85
- package/dist/worktreeOperations-B3E4JY4H.mjs.map +1 -0
- package/dist/{worktreeOperations-DGUbUnYU.cjs → worktreeOperations-CD7s0YnO.cjs} +288 -85
- package/dist/worktreeOperations-CD7s0YnO.cjs.map +1 -0
- package/llms.txt +1 -1
- package/package.json +21 -19
- package/src/prompts/{po-use-git → codemode-how-to-git}/SKILL.md +35 -32
- package/dist/worktreeOperations-BSeyxUai.mjs.map +0 -1
- package/dist/worktreeOperations-DGUbUnYU.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_worktreeOperations = require("./worktreeOperations-
|
|
2
|
+
const require_worktreeOperations = require("./worktreeOperations-CD7s0YnO.cjs");
|
|
3
3
|
//#region src/services/extensionService/index.ts
|
|
4
4
|
var DefaultGitExtensionService = class {
|
|
5
5
|
async execute() {
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["_$___worktreeEvents0.WorktreeMessage"],"sources":["../src/services/worktreeEvents/index.ts","../src/types/worktreeRegistry.ts","../src/services/worktreeNaming/index.ts","../src/services/worktreeOperations/index.ts","../src/types/extension.ts","../src/services/extensionService/index.ts","../src/services/gitDependencies/index.ts"],"mappings":";;;cASa;KASD;UAEK;WACN,gBAAgB;WAChB;WACA;WACA,MAAM;WACN;WACA;;;;;;;;;;;KCRC;cAEC;UAEI;EACf,gBAAgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA;EACA,QAAQ;;EAER;;;;UC8De;;EAEf,QAAQ;;EAER,QAAQ;;EAER;;EAEA,WAAW;;;;;;;EAOX;;;;;;;;iBASc,UAAU;EACxB,kBAAkB;EAClB;EACA;EACA,aAAa;IACX;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["_$___worktreeEvents0.WorktreeMessage"],"sources":["../src/services/worktreeEvents/index.ts","../src/types/worktreeRegistry.ts","../src/services/worktreeNaming/index.ts","../src/services/worktreeOperations/index.ts","../src/types/extension.ts","../src/services/extensionService/index.ts","../src/services/gitDependencies/index.ts"],"mappings":";;;cASa;KASD;UAEK;WACN,gBAAgB;WAChB;WACA;WACA,MAAM;WACN;WACA;;;;;;;;;;;KCRC;cAEC;UAEI;EACf,gBAAgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA;EACA,QAAQ;;EAER;;EAEA;;;;UC8De;;EAEf,QAAQ;;EAER,QAAQ;;EAER;;EAEA,WAAW;;;;;;;EAOX;;;;;;;;iBASc,UAAU;EACxB,kBAAkB;EAClB;EACA;EACA,aAAa;IACX;;;UCpFa;;EAEf;;;;;EAKA;;UAGe;EACf;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;UAGe;;EAEf,SAAS;;EAET,cAAc;;UAGC;EACf,MAAM,SAAS,iBAAiB,SAAS,sBAAsB,UAAU,eAAe,QAAQ;EAChG,MAAM,SAAS,iBAAiB,YAAY,iBAAiB,QAAQ;EACrE,KAAK,SAAS,kBAAkB,QAAQ;EACxC,OAAO,SAAS,iBAAiB,aAAa;IAAU,QAAQ;IAAgB;;EAChF,MAAM,SAAS,iBAAiB,YAAY,mBAAmB,QAAQ;EACvE,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ,kBAAkB;EAC5E,KAAK,SAAS,iBAAiB,YAAY,kBAAkB;EAC7D,SAAS,SAAS,iBAAiB,aAAa,QAA9BA;;;;KCvFR;UAEK;EACf;EACA,OAAO;;UAGQ;EACf,WAAW,QAAQ;;UAGJ;EACf,SAAS;EACT,YAAY;;;;qBCbD,sCAAsC;EAC3C,WAAW,QAAQ;;;;wBCEX,sBAAsB,YAAW,QAAQ,4BAAiC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as createWorktreeGit, t as createWorktreeOperations } from "./worktreeOperations-B3E4JY4H.mjs";
|
|
2
2
|
//#region src/services/extensionService/index.ts
|
|
3
3
|
var DefaultGitExtensionService = class {
|
|
4
4
|
async execute() {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { load } from "js-yaml";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
import { isDeepStrictEqual } from "node:util";
|
|
7
8
|
import { poHomeDirectory } from "@agimon-ai/po-core/piSettings";
|
|
8
9
|
import { setTimeout } from "node:timers/promises";
|
|
@@ -19,7 +20,7 @@ import { setTimeout } from "node:timers/promises";
|
|
|
19
20
|
*/
|
|
20
21
|
const READ_TIMEOUT_MS = 3e4;
|
|
21
22
|
const ADD_TIMEOUT_MS = 3e5;
|
|
22
|
-
const REMOVE_TIMEOUT_MS = 3e5;
|
|
23
|
+
const REMOVE_TIMEOUT_MS$1 = 3e5;
|
|
23
24
|
const PRUNE_TIMEOUT_MS = 15e3;
|
|
24
25
|
/** fetch and push: bounded by the network, so long enough for a slow link, short enough to notice a dead one. */
|
|
25
26
|
const NETWORK_TIMEOUT_MS = 12e4;
|
|
@@ -172,9 +173,9 @@ function createWorktreeGit() {
|
|
|
172
173
|
"remove",
|
|
173
174
|
...force ? ["--force"] : [],
|
|
174
175
|
path
|
|
175
|
-
], REMOVE_TIMEOUT_MS);
|
|
176
|
+
], REMOVE_TIMEOUT_MS$1);
|
|
176
177
|
if (result.code === 0) return;
|
|
177
|
-
if (missingWorktree(result.stderr) && !fs.
|
|
178
|
+
if (missingWorktree(result.stderr) && !await fs.access(path).then(() => true, () => false)) {
|
|
178
179
|
await prune(repositoryRoot);
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
@@ -260,6 +261,25 @@ function createWorktreeGit() {
|
|
|
260
261
|
};
|
|
261
262
|
}
|
|
262
263
|
//#endregion
|
|
264
|
+
//#region src/constants/git.ts
|
|
265
|
+
/** Workspace-mounted routes behind the new-session dialog. */
|
|
266
|
+
const GIT_BRANCHES_PATH = "/branches";
|
|
267
|
+
const GIT_SESSIONS_PATH = "/sessions";
|
|
268
|
+
/** Workspace-mounted: that workspace's remote auth for manual pull, push and rebase. */
|
|
269
|
+
const GIT_AUTH_PATH = "/auth";
|
|
270
|
+
/** Session-mounted: the session's change against its base, and one file's hunks. */
|
|
271
|
+
const GIT_REVIEW_PATH = "/review";
|
|
272
|
+
const GIT_REVIEW_FILE_PATH = "/review/file";
|
|
273
|
+
/** Repository file, relative to the checkout root, whose `setup` commands run in every new worktree. */
|
|
274
|
+
const WORKTREE_CONFIG_FILE = ".po/worktree.yaml";
|
|
275
|
+
/**
|
|
276
|
+
* po-runner's session command, so setup runs as a runner of the worktree session and its log
|
|
277
|
+
* shows in Runners. Mirrors po-runner's `runnerLaunchLine`; the command follows the separator.
|
|
278
|
+
*/
|
|
279
|
+
const WORKTREE_SETUP_LAUNCH = "/runners start name=worktree-setup --";
|
|
280
|
+
/** Installs the rift CLI. Shown to the person to copy, and named in the error an agent reads. */
|
|
281
|
+
const RIFT_INSTALL_COMMAND = "npm install -g rift-snapshot";
|
|
282
|
+
//#endregion
|
|
263
283
|
//#region src/services/errors/index.ts
|
|
264
284
|
var PoGitExpectedError = class extends Error {
|
|
265
285
|
code;
|
|
@@ -295,12 +315,12 @@ const OWNER_ONLY_FILE = 384;
|
|
|
295
315
|
* record in it. Writing beside the target and renaming makes the swap atomic on
|
|
296
316
|
* the same filesystem, so a reader sees either the old file or the new one.
|
|
297
317
|
*/
|
|
298
|
-
function writeJsonAtomic(file, value) {
|
|
318
|
+
async function writeJsonAtomic(file, value) {
|
|
299
319
|
const directory = path.dirname(file);
|
|
300
|
-
fs.
|
|
301
|
-
const temporary = path.join(directory, `.${path.basename(file)}.${
|
|
302
|
-
fs.
|
|
303
|
-
fs.
|
|
320
|
+
await fs.mkdir(directory, { recursive: true });
|
|
321
|
+
const temporary = path.join(directory, `.${path.basename(file)}.${randomUUID()}.tmp`);
|
|
322
|
+
await fs.writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: OWNER_ONLY_FILE });
|
|
323
|
+
await fs.rename(temporary, file);
|
|
304
324
|
}
|
|
305
325
|
/**
|
|
306
326
|
* Reads JSON, treating every failure as absent.
|
|
@@ -309,9 +329,9 @@ function writeJsonAtomic(file, value) {
|
|
|
309
329
|
* here: there is nothing to act on. Distinguishing them would only offer a
|
|
310
330
|
* choice no caller wants to make.
|
|
311
331
|
*/
|
|
312
|
-
function readJson(file) {
|
|
332
|
+
async function readJson(file) {
|
|
313
333
|
try {
|
|
314
|
-
return JSON.parse(fs.
|
|
334
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
315
335
|
} catch {
|
|
316
336
|
return;
|
|
317
337
|
}
|
|
@@ -330,12 +350,12 @@ const MAX_LABEL_LENGTH = 32;
|
|
|
330
350
|
* the registry is per repository and must be found from inside a worktree too.
|
|
331
351
|
* A session may run in a subdirectory, so the nearest enclosing checkout is used.
|
|
332
352
|
*/
|
|
333
|
-
function gitCommonDirectory(directory) {
|
|
353
|
+
async function gitCommonDirectory(directory) {
|
|
334
354
|
let root = path.resolve(directory);
|
|
335
355
|
let marker = path.join(root, ".git");
|
|
336
356
|
let stat;
|
|
337
357
|
for (;;) try {
|
|
338
|
-
stat = fs.
|
|
358
|
+
stat = await fs.stat(marker);
|
|
339
359
|
break;
|
|
340
360
|
} catch {
|
|
341
361
|
const parent = path.dirname(root);
|
|
@@ -346,14 +366,14 @@ function gitCommonDirectory(directory) {
|
|
|
346
366
|
if (stat.isDirectory()) return marker;
|
|
347
367
|
let pointer;
|
|
348
368
|
try {
|
|
349
|
-
pointer = fs.
|
|
369
|
+
pointer = (await fs.readFile(marker, "utf8")).trim();
|
|
350
370
|
} catch {
|
|
351
371
|
return;
|
|
352
372
|
}
|
|
353
373
|
if (!pointer.startsWith("gitdir:")) return void 0;
|
|
354
374
|
const gitDir = path.resolve(root, pointer.slice(7).trim());
|
|
355
375
|
try {
|
|
356
|
-
const common = fs.
|
|
376
|
+
const common = (await fs.readFile(path.join(gitDir, "commondir"), "utf8")).trim();
|
|
357
377
|
return path.resolve(gitDir, common);
|
|
358
378
|
} catch {
|
|
359
379
|
return gitDir;
|
|
@@ -372,8 +392,8 @@ function repositoryLabel(root) {
|
|
|
372
392
|
* registry. Falling back to the path keeps a non-repository directory usable
|
|
373
393
|
* instead of failing, though nothing here should reach that case.
|
|
374
394
|
*/
|
|
375
|
-
function repositoryId(root) {
|
|
376
|
-
const common = gitCommonDirectory(root);
|
|
395
|
+
async function repositoryId(root) {
|
|
396
|
+
const common = await gitCommonDirectory(root);
|
|
377
397
|
const token = common === void 0 ? `root:${path.resolve(root)}` : `git:${common}`;
|
|
378
398
|
return createHash("sha256").update(token).digest("hex").slice(0, ID_LENGTH);
|
|
379
399
|
}
|
|
@@ -409,22 +429,26 @@ function credentialsFile(homeDir = os.homedir()) {
|
|
|
409
429
|
return path.join(poGitRoot(homeDir), "credentials.json");
|
|
410
430
|
}
|
|
411
431
|
/** One registry file per repository, keyed by the shared git directory. */
|
|
412
|
-
function registryFile(repositoryRoot, homeDir = os.homedir()) {
|
|
413
|
-
const id = repositoryId(repositoryRoot);
|
|
432
|
+
async function registryFile(repositoryRoot, homeDir = os.homedir()) {
|
|
433
|
+
const id = await repositoryId(repositoryRoot);
|
|
414
434
|
const root = path.join(poGitRoot(homeDir), "registry");
|
|
415
435
|
const canonical = path.join(root, id, "worktrees.json");
|
|
416
|
-
if (fs.
|
|
436
|
+
if (await fs.access(canonical).then(() => true, () => false)) return canonical;
|
|
417
437
|
let names;
|
|
418
438
|
try {
|
|
419
|
-
names = fs.
|
|
439
|
+
names = await fs.readdir(root);
|
|
420
440
|
} catch {
|
|
421
441
|
return canonical;
|
|
422
442
|
}
|
|
423
|
-
const legacyFiles =
|
|
443
|
+
const legacyFiles = [];
|
|
444
|
+
for (const entry of names.filter((name) => name.endsWith(`--${id}`)).sort()) {
|
|
445
|
+
const file = path.join(root, entry, "worktrees.json");
|
|
446
|
+
if (await fs.access(file).then(() => true, () => false)) legacyFiles.push(file);
|
|
447
|
+
}
|
|
424
448
|
if (legacyFiles.length === 0) return canonical;
|
|
425
449
|
const entries = /* @__PURE__ */ new Map();
|
|
426
450
|
for (const file of legacyFiles) {
|
|
427
|
-
const legacy = readJson(file);
|
|
451
|
+
const legacy = await readJson(file);
|
|
428
452
|
if (legacy?.version !== 1 || !Array.isArray(legacy.entries)) continue;
|
|
429
453
|
for (const value of legacy.entries) {
|
|
430
454
|
if (typeof value !== "object" || value === null || !("id" in value) || typeof value.id !== "string" || value.id === "") continue;
|
|
@@ -435,20 +459,119 @@ function registryFile(repositoryRoot, homeDir = os.homedir()) {
|
|
|
435
459
|
}
|
|
436
460
|
}
|
|
437
461
|
const temporary = `${canonical}.${randomUUID()}.migration`;
|
|
438
|
-
writeJsonAtomic(temporary, {
|
|
462
|
+
await writeJsonAtomic(temporary, {
|
|
439
463
|
version: 1,
|
|
440
464
|
entries: [...entries.values()]
|
|
441
465
|
});
|
|
442
466
|
try {
|
|
443
|
-
fs.
|
|
467
|
+
await fs.link(temporary, canonical);
|
|
444
468
|
} catch (error) {
|
|
445
469
|
if (error.code !== "EEXIST") throw error;
|
|
446
470
|
} finally {
|
|
447
|
-
fs.
|
|
471
|
+
await fs.rm(temporary);
|
|
448
472
|
}
|
|
449
473
|
return canonical;
|
|
450
474
|
}
|
|
451
475
|
//#endregion
|
|
476
|
+
//#region src/services/riftCli/index.ts
|
|
477
|
+
/** Present at a repository root, it makes worktrees Rift copy-on-write workspaces instead of git worktrees. */
|
|
478
|
+
const RIFT_CONFIG_FILE = ".rift.toml";
|
|
479
|
+
/** Rift's identity marker; a root without it was never `rift init`ed. */
|
|
480
|
+
const RIFT_MARKER = ".rift";
|
|
481
|
+
/** Cloning is instant, but `.rift.toml` hooks such as an install run inside create. */
|
|
482
|
+
const CREATE_TIMEOUT_MS = 6e5;
|
|
483
|
+
const REMOVE_TIMEOUT_MS = 3e5;
|
|
484
|
+
/**
|
|
485
|
+
* Runs the rift CLI. Stderr is dropped rather than piped: hooks inherit it and may keep it open
|
|
486
|
+
* after rift exits (a `docker compose up -d`), and raw output never reaches a tool result anyway.
|
|
487
|
+
*/
|
|
488
|
+
function rift(cwd, args, timeout) {
|
|
489
|
+
return new Promise((resolve, reject) => {
|
|
490
|
+
const child = spawn("rift", [...args], {
|
|
491
|
+
cwd,
|
|
492
|
+
stdio: [
|
|
493
|
+
"ignore",
|
|
494
|
+
"pipe",
|
|
495
|
+
"ignore"
|
|
496
|
+
],
|
|
497
|
+
timeout
|
|
498
|
+
});
|
|
499
|
+
let stdout = "";
|
|
500
|
+
child.stdout.on("data", (chunk) => stdout += String(chunk));
|
|
501
|
+
child.on("error", (error) => reject(error.code === "ENOENT" ? new PoGitExpectedError("rift_missing", `This repository has ${RIFT_CONFIG_FILE}, but the rift CLI is not installed.`, false, `Install it with \`${RIFT_INSTALL_COMMAND}\`, run \`rift init\` in the repository root, then try again. An agent asks the person with ask_user_question before installing. Removing ${RIFT_CONFIG_FILE} falls back to git worktrees.`) : error));
|
|
502
|
+
child.on("exit", (code) => resolve({
|
|
503
|
+
code: code ?? 1,
|
|
504
|
+
stdout
|
|
505
|
+
}));
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
const exists = (file) => fs.access(file).then(() => true, () => false);
|
|
509
|
+
function createWorktreeRift() {
|
|
510
|
+
const remove = async (target) => {
|
|
511
|
+
if (!await exists(target)) return;
|
|
512
|
+
const result = await rift(path.dirname(target), ["remove", target], REMOVE_TIMEOUT_MS);
|
|
513
|
+
if (result.code !== 0) throw new Error(`rift remove failed (exit ${String(result.code)})`);
|
|
514
|
+
};
|
|
515
|
+
return {
|
|
516
|
+
enabled: (repositoryRoot) => exists(path.join(repositoryRoot, RIFT_CONFIG_FILE)),
|
|
517
|
+
async create({ repositoryRoot, path: target, branch, baseRef, existing, remote }) {
|
|
518
|
+
if (!await exists(path.join(repositoryRoot, RIFT_MARKER))) {
|
|
519
|
+
await rift(repositoryRoot, ["--help"], READ_TIMEOUT_MS);
|
|
520
|
+
throw new PoGitExpectedError("invalid_request", `This repository has ${RIFT_CONFIG_FILE}, but Rift is not initialized for ${repositoryRoot}.`, false, "Run `rift init` in the repository root (not inside a git worktree), then try again.");
|
|
521
|
+
}
|
|
522
|
+
if (await exists(target)) throw new Error(`The worktree path ${target} already exists.`);
|
|
523
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
524
|
+
const created = await rift(repositoryRoot, [
|
|
525
|
+
"create",
|
|
526
|
+
repositoryRoot,
|
|
527
|
+
"--into",
|
|
528
|
+
path.dirname(target),
|
|
529
|
+
"--name",
|
|
530
|
+
path.basename(target),
|
|
531
|
+
"--copy-all"
|
|
532
|
+
], CREATE_TIMEOUT_MS);
|
|
533
|
+
if (created.code !== 0) {
|
|
534
|
+
await remove(target).catch(() => void 0);
|
|
535
|
+
throw new Error(`rift create failed (exit ${String(created.code)})`);
|
|
536
|
+
}
|
|
537
|
+
const switchArgs = existing ? remote === void 0 ? [
|
|
538
|
+
"switch",
|
|
539
|
+
"--discard-changes",
|
|
540
|
+
branch
|
|
541
|
+
] : [
|
|
542
|
+
"switch",
|
|
543
|
+
"--discard-changes",
|
|
544
|
+
"--track",
|
|
545
|
+
"-c",
|
|
546
|
+
branch,
|
|
547
|
+
`${remote}/${branch}`
|
|
548
|
+
] : [
|
|
549
|
+
"switch",
|
|
550
|
+
"--discard-changes",
|
|
551
|
+
"-c",
|
|
552
|
+
branch,
|
|
553
|
+
baseRef
|
|
554
|
+
];
|
|
555
|
+
for (const args of [switchArgs, ["clean", "-fd"]]) {
|
|
556
|
+
const result = await runGit(target, args, { timeout: READ_TIMEOUT_MS });
|
|
557
|
+
if (result.code !== 0) {
|
|
558
|
+
await remove(target).catch(() => void 0);
|
|
559
|
+
throw new Error(`git ${String(args[0])} in the rift failed (exit ${String(result.code)})`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
remove,
|
|
564
|
+
async fetchBranch({ repositoryRoot, path: source, branch }) {
|
|
565
|
+
const result = await runGit(repositoryRoot, [
|
|
566
|
+
"fetch",
|
|
567
|
+
source,
|
|
568
|
+
`refs/heads/${branch}:refs/heads/${branch}`
|
|
569
|
+
], { timeout: READ_TIMEOUT_MS });
|
|
570
|
+
if (result.code !== 0) throw new Error(`git fetch from the rift failed (exit ${String(result.code)})`);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
452
575
|
//#region src/services/worktreeEvents/index.ts
|
|
453
576
|
const GIT_WORKTREE_LIFECYCLE_EVENT = "git_worktree_lifecycle";
|
|
454
577
|
const GIT_WORKTREE_MESSAGE_EVENT = "git_worktree_message";
|
|
@@ -563,18 +686,22 @@ function refuseClose(input) {
|
|
|
563
686
|
* uncommitted work, so losing the record would lose the only pointer to it.
|
|
564
687
|
* Deletion is always a separate, named decision.
|
|
565
688
|
*/
|
|
566
|
-
function reconcile(records, probe) {
|
|
689
|
+
async function reconcile(records, probe) {
|
|
567
690
|
const changed = [];
|
|
691
|
+
const next = [];
|
|
692
|
+
for (const record of records) {
|
|
693
|
+
if (record.status === "orphaned" || await probe.exists(record.path) && probe.alive(record)) {
|
|
694
|
+
next.push(record);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
changed.push(record.id);
|
|
698
|
+
next.push({
|
|
699
|
+
...record,
|
|
700
|
+
status: "orphaned"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
568
703
|
return {
|
|
569
|
-
records:
|
|
570
|
-
if (record.status === "orphaned") return record;
|
|
571
|
-
if (!(!probe.exists(record.path) || !probe.alive(record))) return record;
|
|
572
|
-
changed.push(record.id);
|
|
573
|
-
return {
|
|
574
|
-
...record,
|
|
575
|
-
status: "orphaned"
|
|
576
|
-
};
|
|
577
|
-
}),
|
|
704
|
+
records: next,
|
|
578
705
|
changed
|
|
579
706
|
};
|
|
580
707
|
}
|
|
@@ -634,25 +761,25 @@ function isRecord(value) {
|
|
|
634
761
|
*/
|
|
635
762
|
function createWorktreeRegistry(file) {
|
|
636
763
|
return {
|
|
637
|
-
list() {
|
|
638
|
-
const parsed = readJson(file);
|
|
764
|
+
async list() {
|
|
765
|
+
const parsed = await readJson(file);
|
|
639
766
|
if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.entries)) return [];
|
|
640
767
|
return parsed.entries.filter(isRecord);
|
|
641
768
|
},
|
|
642
|
-
replace(entries) {
|
|
643
|
-
writeJsonAtomic(file, {
|
|
769
|
+
async replace(entries) {
|
|
770
|
+
await writeJsonAtomic(file, {
|
|
644
771
|
version: 1,
|
|
645
772
|
entries
|
|
646
773
|
});
|
|
647
774
|
},
|
|
648
775
|
async transaction(operation, signal) {
|
|
649
776
|
const lock = `${file}.lock`;
|
|
650
|
-
fs.
|
|
777
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
651
778
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
652
779
|
for (;;) {
|
|
653
780
|
signal?.throwIfAborted();
|
|
654
781
|
try {
|
|
655
|
-
fs.
|
|
782
|
+
await fs.mkdir(lock, { mode: 448 });
|
|
656
783
|
break;
|
|
657
784
|
} catch (error) {
|
|
658
785
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -661,14 +788,14 @@ function createWorktreeRegistry(file) {
|
|
|
661
788
|
}
|
|
662
789
|
}
|
|
663
790
|
try {
|
|
664
|
-
fs.
|
|
791
|
+
await fs.writeFile(path.join(lock, "owner.json"), JSON.stringify({
|
|
665
792
|
pid: process.pid,
|
|
666
793
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
667
794
|
}), { mode: 384 });
|
|
668
795
|
signal?.throwIfAborted();
|
|
669
796
|
return await operation();
|
|
670
797
|
} finally {
|
|
671
|
-
fs.
|
|
798
|
+
await fs.rm(lock, { recursive: true });
|
|
672
799
|
}
|
|
673
800
|
}
|
|
674
801
|
};
|
|
@@ -678,8 +805,26 @@ function createWorktreeRegistry(file) {
|
|
|
678
805
|
const GIT_WORKTREE_DELEGATION_KIND = "git.worktree.delegation";
|
|
679
806
|
const GIT_WORKTREE_MESSAGE_KIND = "git.worktree.message";
|
|
680
807
|
const GIT_WORKTREE_REPORT_KIND = "git.worktree.report";
|
|
808
|
+
/**
|
|
809
|
+
* The repository's worktree setup as one shell command, or undefined when it asks for none.
|
|
810
|
+
* Read from the checkout the spawn starts in, so an uncommitted edit applies at once.
|
|
811
|
+
*/
|
|
812
|
+
async function readWorktreeSetup(root) {
|
|
813
|
+
const file = path.join(root, WORKTREE_CONFIG_FILE);
|
|
814
|
+
let setup;
|
|
815
|
+
try {
|
|
816
|
+
setup = load(await fs.readFile(file, "utf8"))?.setup;
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (error.code === "ENOENT") return void 0;
|
|
819
|
+
throw new PoGitExpectedError("invalid_request", `${WORKTREE_CONFIG_FILE} is not valid YAML (${error.message}).`, false, "Fix the file and try again.");
|
|
820
|
+
}
|
|
821
|
+
const commands = [setup ?? []].flat();
|
|
822
|
+
if (!commands.every((command) => typeof command === "string")) throw new PoGitExpectedError("invalid_request", `${WORKTREE_CONFIG_FILE} setup must be a command or a list of commands.`, false, "Fix the file and try again.");
|
|
823
|
+
return commands.filter((command) => command.trim() !== "").join(" && ") || void 0;
|
|
824
|
+
}
|
|
681
825
|
function createWorktreeOperations(deps) {
|
|
682
826
|
const { git } = deps;
|
|
827
|
+
const rift = deps.rift ?? createWorktreeRift();
|
|
683
828
|
const sessionService = deps.sessionService;
|
|
684
829
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
685
830
|
const requireSessionService = () => {
|
|
@@ -698,8 +843,19 @@ function createWorktreeOperations(deps) {
|
|
|
698
843
|
};
|
|
699
844
|
const probe = {
|
|
700
845
|
alive: (record) => requireSessionService().isLive(record.sessionId),
|
|
701
|
-
exists: (path) => fs.
|
|
846
|
+
exists: (path) => fs.access(path).then(() => true, () => false)
|
|
847
|
+
};
|
|
848
|
+
/** What git lists as worktrees, plus Rift workspaces, which git cannot see. */
|
|
849
|
+
const listedPaths = async (root, records) => {
|
|
850
|
+
const rifts = records.filter((record) => record.backend === "rift");
|
|
851
|
+
const present = await Promise.all(rifts.map((record) => probe.exists(record.path)));
|
|
852
|
+
return [...await git.listWorktreePaths(root), ...rifts.filter((_, index) => present[index]).map((r) => r.path)];
|
|
702
853
|
};
|
|
854
|
+
const removeCheckout = (root, record, force) => record.backend === "rift" ? rift.remove(record.path) : git.removeWorktree({
|
|
855
|
+
repositoryRoot: root,
|
|
856
|
+
path: record.path,
|
|
857
|
+
force
|
|
858
|
+
});
|
|
703
859
|
/**
|
|
704
860
|
* Everything an action needs about the repository it is acting on.
|
|
705
861
|
*
|
|
@@ -710,11 +866,11 @@ function createWorktreeOperations(deps) {
|
|
|
710
866
|
const resolve = async (context) => {
|
|
711
867
|
const root = await git.repositoryRoot(context.cwd);
|
|
712
868
|
if (root === void 0) throw new PoGitExpectedError("not_a_repository", `${context.cwd} is not inside a git repository.`, false, "Run this from a repository checkout.");
|
|
713
|
-
const store = createWorktreeRegistry(registryFile(root, deps.homeDir));
|
|
869
|
+
const store = createWorktreeRegistry(await registryFile(root, deps.homeDir));
|
|
714
870
|
return {
|
|
715
871
|
root,
|
|
716
872
|
store,
|
|
717
|
-
records: store.list()
|
|
873
|
+
records: await store.list()
|
|
718
874
|
};
|
|
719
875
|
};
|
|
720
876
|
const require = (records, id) => {
|
|
@@ -765,11 +921,11 @@ function createWorktreeOperations(deps) {
|
|
|
765
921
|
const expectedPath = worktreeDirectory({
|
|
766
922
|
worktreesRoot: worktreesRoot(deps.homeDir),
|
|
767
923
|
repositoryLabel: repositoryLabel(root),
|
|
768
|
-
repositoryId: repositoryId(root),
|
|
924
|
+
repositoryId: await repositoryId(root),
|
|
769
925
|
branch: request.branch,
|
|
770
926
|
shortId: shortId(reserved.sessionId)
|
|
771
927
|
});
|
|
772
|
-
if (existing.parentSessionId !== parentSessionId || existing.repositoryRoot !== root || existing.branch !== request.branch || existing.path !== expectedPath || existing.path !== reserved?.cwd || request.baseRef !== void 0 && existing.baseRef !== request.baseRef || !
|
|
928
|
+
if (existing.parentSessionId !== parentSessionId || existing.repositoryRoot !== root || existing.branch !== request.branch || existing.path !== expectedPath || existing.path !== reserved?.cwd || request.baseRef !== void 0 && existing.baseRef !== request.baseRef || !await probe.exists(existing.path) || !(await listedPaths(root, records)).includes(existing.path) || await git.repositoryRoot(existing.path) !== existing.path || await git.currentBranch(existing.path) !== request.branch) throw new PoGitExpectedError("invalid_request", "The reserved checkout no longer matches this setup.", false, "Inspect the existing branch and checkout; they will not be replaced.");
|
|
773
929
|
if (!requireSessionService().isLive(existing.sessionId)) {
|
|
774
930
|
if ((await requireSessionService().create({
|
|
775
931
|
cwd: existing.path,
|
|
@@ -789,18 +945,20 @@ function createWorktreeOperations(deps) {
|
|
|
789
945
|
});
|
|
790
946
|
if (refusal !== void 0) throw new PoGitExpectedError("worktree_exists", refusal, false, "Pick another branch name, or close it.");
|
|
791
947
|
const taskDelivery = request.task === void 0 ? void 0 : requireSessionDelivery();
|
|
948
|
+
const setup = await readWorktreeSetup(root);
|
|
949
|
+
const useRift = await rift.enabled(root);
|
|
792
950
|
const existingBranch = request.checkout === "existing";
|
|
793
951
|
const id = shortId(reserved?.sessionId ?? randomUUID());
|
|
794
952
|
let path = worktreeDirectory({
|
|
795
953
|
worktreesRoot: worktreesRoot(deps.homeDir),
|
|
796
954
|
repositoryLabel: repositoryLabel(root),
|
|
797
|
-
repositoryId: repositoryId(root),
|
|
955
|
+
repositoryId: await repositoryId(root),
|
|
798
956
|
branch: request.branch,
|
|
799
957
|
shortId: id
|
|
800
958
|
});
|
|
801
959
|
if (request.reservationId !== void 0) path = (await reservations.prepare(request.reservationId, parentSessionId, path)).cwd;
|
|
802
|
-
const recovering = reserved?.cwd !== void 0 &&
|
|
803
|
-
if (recovering && (!(await git.listWorktreePaths(root)).includes(path) || await git.currentBranch(path) !== request.branch)) throw new PoGitExpectedError("invalid_request", "The reserved checkout no longer matches this setup.", false, "Inspect the existing directory; it will not be replaced.");
|
|
960
|
+
const recovering = reserved?.cwd !== void 0 && await probe.exists(path);
|
|
961
|
+
if (recovering && (!(useRift ? await git.repositoryRoot(path) === path : (await git.listWorktreePaths(root)).includes(path)) || await git.currentBranch(path) !== request.branch)) throw new PoGitExpectedError("invalid_request", "The reserved checkout no longer matches this setup.", false, "Inspect the existing directory; it will not be replaced.");
|
|
804
962
|
if (!recovering) {
|
|
805
963
|
if (existingBranch) {
|
|
806
964
|
const branches = await git.listBranches(root);
|
|
@@ -813,11 +971,11 @@ function createWorktreeOperations(deps) {
|
|
|
813
971
|
const cancelled = () => options?.signal?.aborted === true;
|
|
814
972
|
const rollback = async () => {
|
|
815
973
|
if (reserved) return ` The reserved checkout at ${path} was retained for recovery.`;
|
|
816
|
-
await
|
|
817
|
-
repositoryRoot: root,
|
|
974
|
+
await removeCheckout(root, {
|
|
818
975
|
path,
|
|
819
|
-
|
|
820
|
-
}).catch(() => void 0);
|
|
976
|
+
...useRift ? { backend: "rift" } : {}
|
|
977
|
+
}, true).catch(() => void 0);
|
|
978
|
+
if (useRift) return "";
|
|
821
979
|
if (existingBranch && request.remote === void 0) return "";
|
|
822
980
|
return await git.deleteBranch({
|
|
823
981
|
repositoryRoot: root,
|
|
@@ -826,7 +984,15 @@ function createWorktreeOperations(deps) {
|
|
|
826
984
|
};
|
|
827
985
|
options?.onProgress?.(`creating branch ${request.branch}\u2026`);
|
|
828
986
|
if (!recovering) {
|
|
829
|
-
if (
|
|
987
|
+
if (useRift) await rift.create({
|
|
988
|
+
repositoryRoot: root,
|
|
989
|
+
path,
|
|
990
|
+
branch: request.branch,
|
|
991
|
+
baseRef,
|
|
992
|
+
existing: existingBranch,
|
|
993
|
+
...request.remote === void 0 ? {} : { remote: request.remote }
|
|
994
|
+
});
|
|
995
|
+
else if (existingBranch) await git.addExistingWorktree({
|
|
830
996
|
repositoryRoot: root,
|
|
831
997
|
path,
|
|
832
998
|
branch: request.branch,
|
|
@@ -868,20 +1034,30 @@ function createWorktreeOperations(deps) {
|
|
|
868
1034
|
sessionId,
|
|
869
1035
|
...parented,
|
|
870
1036
|
status: "running",
|
|
1037
|
+
...useRift ? { backend: "rift" } : {},
|
|
871
1038
|
createdAt: now().toISOString()
|
|
872
1039
|
};
|
|
873
1040
|
try {
|
|
874
|
-
store.replace([...records, record]);
|
|
1041
|
+
await store.replace([...records, record]);
|
|
875
1042
|
} catch (error) {
|
|
876
1043
|
await requireSessionService().close(sessionId).catch(() => void 0);
|
|
877
1044
|
const kept = await rollback();
|
|
878
1045
|
throw new PoGitExpectedError("registry_write_failed", `The worktree was created but the registry could not be written (${error.message}).${kept}`, true, "Check the registry file for permissions or free space, then try again.");
|
|
879
1046
|
}
|
|
1047
|
+
let setupStarted = false;
|
|
1048
|
+
if (setup !== void 0) try {
|
|
1049
|
+
const service = requireSessionService();
|
|
1050
|
+
if (service.prompt === void 0) throw new Error("this host cannot run session commands");
|
|
1051
|
+
await service.prompt(sessionId, `${WORKTREE_SETUP_LAUNCH} ${setup}`);
|
|
1052
|
+
setupStarted = true;
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
options?.onProgress?.(`worktree setup did not start (${String(error)})`);
|
|
1055
|
+
}
|
|
880
1056
|
if (request.task !== void 0 && taskDelivery !== void 0) try {
|
|
881
1057
|
const receipt = await taskDelivery.deliver({
|
|
882
1058
|
recipientKey: sessionId,
|
|
883
1059
|
kind: GIT_WORKTREE_DELEGATION_KIND,
|
|
884
|
-
prompt: request.task,
|
|
1060
|
+
prompt: setupStarted ? `${request.task}\n\nThe worktree setup (${setup}) is running as the runner "worktree-setup". Check it with po-runner before relying on installed dependencies or builds.` : request.task,
|
|
885
1061
|
metadata: {
|
|
886
1062
|
worktreeId: record.id,
|
|
887
1063
|
fromRole: "parent",
|
|
@@ -904,23 +1080,19 @@ function createWorktreeOperations(deps) {
|
|
|
904
1080
|
requireOwner(context, record, "close");
|
|
905
1081
|
const refusal = refuseClose({
|
|
906
1082
|
record,
|
|
907
|
-
dirtyFiles:
|
|
1083
|
+
dirtyFiles: await probe.exists(record.path) ? await git.dirtyFiles(record.path) : [],
|
|
908
1084
|
force
|
|
909
1085
|
});
|
|
910
1086
|
if (refusal !== void 0) throw new PoGitExpectedError("worktree_dirty", refusal, false, "Commit the work, or pass force: true.");
|
|
911
1087
|
await requireSessionService().close(record.sessionId);
|
|
912
|
-
await
|
|
913
|
-
|
|
914
|
-
path: record.path,
|
|
915
|
-
force
|
|
916
|
-
});
|
|
917
|
-
store.replace(records.filter((entry) => entry.id !== id));
|
|
1088
|
+
await removeCheckout(root, record, force);
|
|
1089
|
+
await store.replace(records.filter((entry) => entry.id !== id));
|
|
918
1090
|
return record;
|
|
919
1091
|
},
|
|
920
1092
|
async list(context) {
|
|
921
1093
|
const { store, records } = await resolve(context);
|
|
922
|
-
const outcome = reconcile(records, probe);
|
|
923
|
-
if (outcome.changed.length > 0) store.replace(outcome.records);
|
|
1094
|
+
const outcome = await reconcile(records, probe);
|
|
1095
|
+
if (outcome.changed.length > 0) await store.replace(outcome.records);
|
|
924
1096
|
return outcome.records;
|
|
925
1097
|
},
|
|
926
1098
|
async status(context, id) {
|
|
@@ -928,7 +1100,7 @@ function createWorktreeOperations(deps) {
|
|
|
928
1100
|
const record = require(records, id);
|
|
929
1101
|
return {
|
|
930
1102
|
record,
|
|
931
|
-
dirtyFiles:
|
|
1103
|
+
dirtyFiles: await probe.exists(record.path) ? await git.dirtyFiles(record.path) : []
|
|
932
1104
|
};
|
|
933
1105
|
},
|
|
934
1106
|
async merge(context, id, message) {
|
|
@@ -937,6 +1109,11 @@ function createWorktreeOperations(deps) {
|
|
|
937
1109
|
requireOwner(context, record, "merge");
|
|
938
1110
|
const parentDirty = await git.dirtyFiles(root);
|
|
939
1111
|
if (parentDirty.length > 0) throw new PoGitExpectedError("worktree_dirty", `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(", ")}.`, false, "Commit or stash the parent checkout first.");
|
|
1112
|
+
if (record.backend === "rift") await rift.fetchBranch({
|
|
1113
|
+
repositoryRoot: root,
|
|
1114
|
+
path: record.path,
|
|
1115
|
+
branch: record.branch
|
|
1116
|
+
});
|
|
940
1117
|
await git.mergeBranch({
|
|
941
1118
|
repositoryRoot: root,
|
|
942
1119
|
branch: record.branch,
|
|
@@ -1001,10 +1178,10 @@ function createWorktreeOperations(deps) {
|
|
|
1001
1178
|
},
|
|
1002
1179
|
async prune(context, dryRun) {
|
|
1003
1180
|
const { root, store, records } = await resolve(context);
|
|
1004
|
-
const reconciled = reconcile(records, probe);
|
|
1005
|
-
const gitPaths = await
|
|
1181
|
+
const reconciled = await reconcile(records, probe);
|
|
1182
|
+
const gitPaths = await listedPaths(root, reconciled.records);
|
|
1006
1183
|
const dirtyByPath = /* @__PURE__ */ new Map();
|
|
1007
|
-
for (const record of reconciled.records) if (record.status === "orphaned" &&
|
|
1184
|
+
for (const record of reconciled.records) if (record.status === "orphaned" && await probe.exists(record.path)) dirtyByPath.set(record.path, await git.dirtyFiles(record.path));
|
|
1008
1185
|
const plan = planPrune({
|
|
1009
1186
|
records: reconciled.records,
|
|
1010
1187
|
gitPaths,
|
|
@@ -1012,20 +1189,16 @@ function createWorktreeOperations(deps) {
|
|
|
1012
1189
|
dirtyByPath
|
|
1013
1190
|
});
|
|
1014
1191
|
if (dryRun) {
|
|
1015
|
-
store.replace(reconciled.records);
|
|
1192
|
+
await store.replace(reconciled.records);
|
|
1016
1193
|
return plan;
|
|
1017
1194
|
}
|
|
1018
|
-
for (const record of plan.remove) await
|
|
1019
|
-
repositoryRoot: root,
|
|
1020
|
-
path: record.path,
|
|
1021
|
-
force: false
|
|
1022
|
-
});
|
|
1195
|
+
for (const record of plan.remove) await removeCheckout(root, record, false);
|
|
1023
1196
|
for (const record of [...plan.remove, ...plan.forget]) if (!await git.deleteBranch({
|
|
1024
1197
|
repositoryRoot: root,
|
|
1025
1198
|
branch: record.branch
|
|
1026
|
-
}).catch(() => false)) plan.keptBranches.push(record.branch);
|
|
1199
|
+
}).catch(() => false) && record.backend !== "rift") plan.keptBranches.push(record.branch);
|
|
1027
1200
|
const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));
|
|
1028
|
-
store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));
|
|
1201
|
+
await store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));
|
|
1029
1202
|
return plan;
|
|
1030
1203
|
}
|
|
1031
1204
|
};
|
|
@@ -1047,7 +1220,7 @@ function createWorktreeOperations(deps) {
|
|
|
1047
1220
|
return {
|
|
1048
1221
|
...operations,
|
|
1049
1222
|
spawn: (context, request, options) => {
|
|
1050
|
-
if (sessionService === void 0) return Promise.reject(new PoGitExpectedError("hub_unavailable", "The cockpit session service is unavailable.", true, "Use the cockpit worktree tool after its Git server
|
|
1223
|
+
if (sessionService === void 0) return Promise.reject(new PoGitExpectedError("hub_unavailable", "The cockpit session service is unavailable.", true, "Use the cockpit worktree tool after its Git server plugin is available."));
|
|
1051
1224
|
if (!validRefName(request.branch) || request.baseRef !== void 0 && !validRefName(request.baseRef)) return Promise.reject(new PoGitExpectedError("invalid_request", "That is not a valid branch name.", false, "Pick another name."));
|
|
1052
1225
|
if (request.reservationId !== void 0 && context.sessionId === void 0) return Promise.reject(new PoGitExpectedError("invalid_request", "A reserved worktree session requires its owning parent.", false, "Create the session from its pending setup card."));
|
|
1053
1226
|
return locked(context, () => operations.spawn(context, request, options), options?.signal);
|
|
@@ -1059,6 +1232,6 @@ function createWorktreeOperations(deps) {
|
|
|
1059
1232
|
};
|
|
1060
1233
|
}
|
|
1061
1234
|
//#endregion
|
|
1062
|
-
export { NETWORK_TIMEOUT_MS as _, GIT_WORKTREE_LIFECYCLE_EVENT as a,
|
|
1235
|
+
export { READ_TIMEOUT_MS as C, runGit as E, NETWORK_TIMEOUT_MS as S, createWorktreeGit as T, GIT_AUTH_PATH as _, GIT_WORKTREE_LIFECYCLE_EVENT as a, GIT_REVIEW_PATH as b, isWorktreeLifecycleEvent as c, registryFile as d, readJson as f, invalidRequest as g, PoGitExpectedError as h, GIT_SESSION_CHANGED_EVENT as i, credentialsFile as l, HubUnavailableError as m, createWorktreeRegistry as n, createWorktreeMessageInbox as o, writeJsonAtomic as p, validRefName as r, isSessionChangedEvent as s, createWorktreeOperations as t, poGitRoot as u, GIT_BRANCHES_PATH as v, REBASE_TIMEOUT_MS as w, GIT_SESSIONS_PATH as x, GIT_REVIEW_FILE_PATH as y };
|
|
1063
1236
|
|
|
1064
|
-
//# sourceMappingURL=worktreeOperations-
|
|
1237
|
+
//# sourceMappingURL=worktreeOperations-B3E4JY4H.mjs.map
|